中文说明 · Feature tour → · FAQ
keepane is a terminal multiplexer. The programs in its panes keep running after the terminal connection closes, and panes can pass messages to each other through inboxes, on one computer or across two you have paired. The usual tmux keys, commands and config file keep working.
- After you detach, the programs in the panes keep running. After a reboot,
keepane resumebrings the layout back; a pane closed by mistake comes back withC-b uwithin 10 seconds. - Name a pane and you can send it messages. A message goes into the inbox first and is delivered when the pane is ready: a shell runs it when it is back at its prompt, other programs read it themselves.
- Every message carries an envelope of a fixed format, with the sender,
the recipient and the task. The event log keeps 30 days;
C-b vshows the panes, messages and tasks. - People, scripts and AI agents use the same messages; an agent can also
work through the built-in MCP server, and
keepane setuphooks up Claude Code, Codex, Gemini CLI, Cursor CLI or opencode so that it is handed its next message when a turn ends. - Two computers on one network or on Tailscale pair once, the way
ssh-copy-idworks, and from then on panes on one send messages and commands to panes on the other (keepane link). What comes from another machine reaches agents only, unless you allow shells on this one. - On your phone, over Wi-Fi or Tailscale,
keepane webshows every window and pane, whether each is busy or free and what waits in its inbox; what you type there runs on the computer. - tmux's
C-bprefix, splits, copy mode, command line, config syntax (keepane import-configbrings a.tmux.confover), format strings, hooks and plugins are there.
It runs on Windows (ConPTY), with PowerShell, WSL and cmd in the panes, and on Linux and macOS, with bash and zsh.
Every pane has an inbox and can be given a name. Messages queue up and are delivered according to the receiving pane's work mode.
keepane rename-pane -t %3 builder # -t %builder finds it from now on
keepane set-work-mode -t %builder shell # it runs what it gets, at its prompt (from outside keepane; inside, a pane sets its own)
keepane send-message -t %builder -w 30 "cargo test"
#12 delivered to $1:@2.%3 (shell)
keepane trace-message 12 -w 600 # waits until it is done: output, successA target can be %7 (a pane id), %builder (a name), or $1:@2.%7
(session, window and pane; whoami shows it). A full address also pins
where the pane is: if the pane has been moved to another window, the
delivery fails.
A pane handles messages according to its work mode:
| Mode | Ready when | Delivery |
|---|---|---|
normal (default) |
never on its own | read with read-message; the window's flags show @ |
shell |
keepane's prompt hook sees the shell back at its prompt | typed in and run |
ai |
the agent's end-of-turn hook runs pane-ready, or its transcript says the turn is over (Claude Code, Codex, pi) |
typed in as a prompt, then Enter on its own |
The status line shows the current pane's mode (ai and shell marked out,
normal dim), and C-b q writes each pane's name and mode under its
number. A status-right of your own shows it with #{pane_work_mode}.
After someone types into a pane, keepane treats it as taken until the next ready signal, so a message never lands in the middle of what is being typed. If someone presses keys while a command keepane delivered is running, the prompt that ends that command does not trigger a new message either; what a user types ahead during a command of their own is beyond keepane's reach.
What a pane is up to shows in a word (#{pane_state}, on the dashboard and
the phone): idle (free for its next message), busy (its program at
work), typing (keys went in with no Enter: someone is at its line, and
messages wait), no-agent (an ai pane back at its shell's prompt: its
agent has exited), unknown (a shell pane whose prompt hook was never
heard, such as cmd's), exited; a normal pane is neither free nor busy
(-). #{pane_idle} is 1 for idle.
In ai mode, if the agent has exited and a shell prompt shows again,
nothing is delivered. A message is delivered the way the recipient's mode
was when it was sent: text sent to an agent is never run as a shell
command, even if the mode changes in between.
Every message carries a header of fixed fields, with its source and route:
[keepane id=12 task=12 from=$1:@1.%3 name=lead mode=ai to=$1:@2.%7 via=shell hop=0]
Each field is name=value, separated by spaces; no value holds a space,
so a program reads it back by splitting. set -g message-envelope json
writes the same fields as one line of JSON instead
({"keepane":1,"id":12,…}), as before 0.17, also in front of a shell's
command; the event log always keeps the JSON.
Delivered to a shell, a short header (the id and the sender: its name,
else its pane, else user) goes before the command in a form that runs
nothing and stays in the history: a comment in PowerShell
(<# keepane #12 from %lead #> cargo test), the argument of : in bash
and zsh (: 'keepane #12 from %lead'; cargo test); trace-message 12
shows the rest. Several lines are joined into one command
that runs them together, with one result (in bash and zsh, the last
line's). Delivered to an agent, it is the header, the text and the end
line [keepane end=12], pasted in; its Enter comes on its own once the
paste has settled (300 ms on at the soonest, after 150 ms with nothing
printed, 2 s at the latest), since an agent's input box can drop an Enter
that comes on the heels of a paste (Codex does). If nothing at all is
printed in answer within 2.5 s, Enter goes once more; if still nothing,
show-messages says so. Someone typing in the meantime has the Enter left
to them. task ties together the order, the work and the
replies; hop counts how many times a message was passed on, and past
message-hop-limit (8 by default) it is refused, so agents cannot answer
each other in a loop.
The fields a sender chooses are given by name: --to (the pane; -t for
short), --re 12 (it answers message 12, and goes to 12's sender unless
--to says otherwise; -r answers the message the pane is working on)
and --task 12 (it carries task 12 on). The rest is keepane's to fill in:
who sent it (from, name, mode), its number, its hop and via. A
sender cannot set them, so a header can be trusted and the hop limit
holds:
keepane send-message --to %builder "cargo test"
keepane send-message --re 12 "the tests pass" # to whoever sent #12
keepane send-message --to %lead --task 12 "one more thing"Run inside a pane, set-work-mode changes only that pane; run from a
terminal outside keepane, a key or the C-b : prompt, it can change any
pane. So a program in a pane cannot switch another pane into shell mode,
which runs its messages by itself. Renaming, managing inboxes and closing
panes are not limited by this; a close can be undone with C-b u within
10 seconds. The rule is there to cut down on mistakes, not to isolate
processes of the same user from each other.
C-b v (a popup) or keepane dashboard (any terminal) lays everything out in
panels, lazygit style. On the left: [1] every pane, grouped by session, with
its work mode, whether it is free, its inbox, how long it has been quiet and
its program (or its agent's cost and context, the total in the title), under
named columns, each colour saying one thing (free green, busy yellow,
messages waiting blue, a context red as it fills); in a narrow window the
least needed columns give way first; [2] the chosen pane's inbox; [3] the tasks. On the right,
[0] the chosen pane: its address, directory, pid, how long it has run, what
it says it is doing, and then its screen live (with its colours), its
scrollback, its events, or the message or task under the cursor: moving onto
one in [2] or [3] shows it at once, laid out (its stage on its colour, who
sent it to whom, when each step came, then its text and what it printed; a
task, its messages joined down the left). Tab, 1 2 3 0 and h/l move
between panels, j/k within one, g/G (or Home/End) to its first or
last row (the top or bottom on the right), [/] change what the right
side shows;
the mouse clicks and scrolls; ? lists every key.
It also acts. On a pane: s sends it a message, r renames it, m
changes its work mode, R marks it ready (unsticks it), o goes there
(and closes the popup), x closes it. In the inbox: d deletes a queued
message (u brings it back), K/J move it, t puts it first, Enter
moves into it to scroll. Closing a pane, deleting a message and switching a pane
to shell (where what it gets is run) ask first. The design:
docs/design/dashboard.md.
(The agents in the recording are stand-ins that write Claude Code's and Codex's transcripts; keepane reads the real ones the same way.)
keepane knows the agents its panes run: Claude Code, Codex and pi (by the
program's name, or by what node runs). For each, it reads the transcript
the agent writes for its directory (~/.claude/projects, ~/.codex/sessions,
~/.pi/agent/sessions; CLAUDE_CONFIG_DIR and CODEX_HOME, as the server
was started with, are followed),
only the part written since the last read, every two seconds. From it come
the model, the tokens used, the tools called, the replies made and how full
the context is; from the process table, the CPU and memory of the agent and
everything under it. Claude Code's subagents count toward its pane.
The cost: pi says its own. For the others, keepane prices each model's tokens
(fresh, cached, written to the cache, received) by
LiteLLM's public price list,
fetched with curl once a day (an hour later after a failed try), only while
a pane runs an agent, and kept in the data directory. The list is the providers' list prices: a
subscription (Claude Max, ChatGPT Pro) is billed otherwise, so read it as
what the work would cost through the API. A model the list does not have is
left out, and the cost then says so with a + ($2.10+: at least that).
set -g agent-cost off stops the
fetch and leaves cost out; KEEPANE_PRICE_LIST names a list of your own
(LiteLLM's form) instead.
Where it shows: the dashboard's pane list (claude $2.10 34%: cost, context)
and a line over the chosen pane with everything; a line on each agent's card
on the phone page; keepane list-agents (-J for JSON, every figure a
number, null when not known); and the format variables agent agent_model agent_cost
agent_tokens agent_context agent_cpu agent_mem agent_tools
agent_turns, for a border or the status line:
set -g pane-border-status top
set -g pane-border-format " #{pane_index} #{?agent,#{agent_model} #{agent_cost} ctx #{agent_context},#{pane_current_command}} "Only agents in keepane's panes are read. An agent elsewhere (another terminal, an editor) working in the same directory at the same time writes to the same folder, and its transcript is counted with the pane's.
Messages and changes to panes are written to an event log,
%LOCALAPPDATA%\keepane\events\<socket>\2026-09-26.jsonl, kept for 30 days
(event-log, event-log-days, event-log-max). list-tasks,
show-task, trace-message and list-events read it. When the server
stops, messages not yet delivered are dropped, and the log says so. Pane
names and work modes are saved with the session. The design in detail:
docs/design/mailbox.md.
keepane keeps its files (logs, the event log, saved sessions, pane history)
in its data directory: %LOCALAPPDATA%\keepane on Windows,
~/.local/share/keepane on Linux, ~/Library/Application Support/keepane
on macOS. The paths below are written the Windows way.
With Homebrew (macOS on Apple silicon or Intel, Linux x86_64):
brew install newdee/tap/keepanebrew upgrade keepane takes a new version. Or from the
releases page:
keepane-v<version>-linux-x86_64.tar.gz (a static build that runs on any
x86_64 Linux), keepane-v<version>-macos-aarch64.tar.gz (Apple silicon) or
keepane-v<version>-macos-x86_64.tar.gz (Intel). Unpack it and put
keepane on your PATH:
tar xzf keepane-v<version>-linux-x86_64.tar.gz
install keepane-v<version>-linux-x86_64/keepane ~/.local/bin/Or build it (below). The server listens on a socket in
$XDG_RUNTIME_DIR/keepane-<uid>/ (else /tmp/keepane-<uid>/), a directory
only you can open. keepane startup, keepane update and the Windows
Terminal profile are Windows matters: on Linux and macOS, start the server
from your login files if you want it at logon, and update it the way you
installed it (brew upgrade keepane, or the releases page; keepane update
says which).
keepane needs Windows 10 1809 or newer (for ConPTY). From the releases page:
keepane-v<version>-windows-x86_64.zipholds one folder,keepane-v<version>-windows-x86_64, withkeepane.exein it. Unzip it and put that folder on yourPATH. No administrator rights needed.keepane-<version>-windows-x86_64.msiinstalls intoProgram Filesfor every user and putskeepaneon the systemPATH; it uninstalls from "Apps & features". It needs administrator rights (unattended:msiexec /i keepane-<version>-windows-x86_64.msi /qn).keepane-<version>-windows-x86_64-user.msiinstalls for you alone, into%LOCALAPPDATA%\Programs\keepane, and puts it on yourPATH. Neither it norkeepane updateneeds administrator rights, so both work over SSH.
Scoop installs the zip straight from the manifest in this repository, with no administrator rights:
scoop install https://raw.githubusercontent.com/newdee/keepane/master/packaging/scoop/keepane.jsonOver SSH, Windows 11 does not let the session go through a junction made
without administrator rights, and Scoop's current folder is one: the shim
fails with "The path cannot be traversed because it contains an untrusted
mount point". Point Scoop's shims at the version folder instead:
scoop config no_junction true
scoop reset keepaneOver SSH, the per-machine MSI's permission prompt appears on a desktop where
nobody can answer it; keepane update says so and stops. Use the per-user
MSI, the zip or Scoop there.
WinGet manifests for the MSI are in packaging/winget/ (validated with
winget validate); winget install newdee.keepane works once they are
merged into winget-pkgs, and until then
winget install --manifest packaging/winget/manifests/n/newdee/keepane/<version>
from a clone does the same. See packaging/README.md.
Building from source needs Rust 1.88 or newer:
cargo install --git https://github.com/newdee/keepane --locked # latest master
cargo install --path . # a local cloneTo build the Windows MSI, the script downloads WiX for the occasion if it is not installed:
cargo build --release
pwsh -File installer/build-msi.ps1 # target\keepane-<version>-windows-x86_64.msi
On Windows keepane passes keys on in Windows' own win32-input-mode, so
PSReadLine chords, Ctrl+Space, Shift+Enter, arrows with modifiers, IME
input, and vim and htop under WSL all work. It runs in Windows Terminal,
the classic console, VS Code's terminal and other Windows console hosts. On
Linux and macOS it reads what the terminal sends (xterm keys with their
modifiers, bracketed paste, SGR mouse) and passes it on as a terminal
would, so it runs in any xterm-compatible terminal and over SSH.
keepane # the session used last; else resume; else new
keepane new # always a new session
keepane new -s work # named session
keepane new -d -s bg htop # detached session running htop
keepane ls # list sessions
keepane attach -t work # re-attach (works from a different terminal window)
keepane send-keys -t work "git status" Enter
keepane capture-pane -p -t work # print what the pane shows (-S -200 adds scrollback)
keepane kill-serverAs in tmux, a command name can be any unambiguous prefix: keepane att,
keepane lsp, keepane splitw -h. keepane kill is refused, because four
commands start that way. keepane list-commands prints them all, and
docs/tmux-parity.md sets them against tmux's, command
by command and key by key.
Several panes at once: keepane split-window -N 3 makes three more and tiles
the window (-d keeps the focus where it is). A window too small for all of
them keeps the ones that fit and says how many it made.
In a session, press the prefix Ctrl+b, then a key from this table:
| Key | Action |
|---|---|
c / n / p / Tab / 0-9 |
new window / next / previous / last / select by index (' asks for an index, for windows past 9) |
, / & |
rename / kill window |
% / " |
split left-right / top-bottom |
h j k l or arrows / o / ; |
move between panes (vim keys) / next pane / last pane |
H J K L, Alt+arrows / Ctrl+arrows |
resize the current pane by 5 / by 1 |
Shift+arrows |
when the window is bigger than this terminal (window-size took another client's), pan this client's view by 5 rows / 10 columns; the view follows the cursor again at the next key |
(moving, resizing, n / p and { / } repeat: after the prefix, keep pressing the key for half a second, repeat-time) |
|
S |
toggle synchronize-panes (type into every pane of the window; S flag on the status line) |
C-s / C-r |
save every session, each into its own file / restore saved sessions (see Resume) |
z |
zoom (toggle) the current pane; moving to another pane of the window (h j k l, q and a number, ;) keeps the zoom and takes it there, until z again (set -g keep-zoom off unzooms instead, as tmux does) |
x |
kill the current pane |
u |
bring back the pane or window killed in the last 10 seconds (undo-kill) |
C-t |
show when each command ran, how long it took and how it ended, at the end of its line (pane-timestamps) |
/ |
browse what panes printed, by pane and day (choose-history) |
y |
copy what the last command printed, without the prompt or the command, to a paste buffer and the clipboard (copy-output) |
F |
put a label on every path, web address and git hash on screen; type one to copy that thing, in capitals to open it (hints, see below) |
{ / } |
swap pane with previous / next |
q |
show the pane numbers (with the name and work mode, %name · ai, under them); press one to go there at once (past 9, a second digit straight after goes on from there: 1 2 is pane 12) |
Space / M-1…M-5 / E |
cycle the layout / pick one (even-horizontal, even-vertical, main-horizontal, main-vertical, tiled) / even out the panes next to this one |
C-o / M-o |
rotate the panes through the layout |
! |
break the pane out into its own window |
m / M |
mark this pane / clear the mark (join-pane takes the marked one) |
T / f |
name this pane / find a window by name or title |
[ / PgUp |
copy mode (see below) |
# / - / = |
list paste buffers / delete the newest / pick one to paste |
t / ~ / r |
clock, and under it the date (and UTC), the pane (program, folder, branch, how long it has run and been quiet), its last command (how long, how it ended; a long folder or command shortened with … so the rest fits), its agent (model, cost, context) and the machine (clock-mode-info off: the time alone; clock-mode-style 12, clock-mode-colour); below, something that moves with the machine (see below); q leaves it / recent messages / redraw |
] |
paste the clipboard |
: |
command prompt (:split-window -h -c C:\src, :set mouse off, ...; Tab completes the command, its flags, a -t target and option names) |
d |
detach |
? |
list key bindings |
| (wait) | after the prefix, a key not pressed within half a second brings up a panel of what the keys do, by group (panes, windows, sessions, tools, and keys you bound yourself); the key then works as ever (prefix-hint, prefix-hint-delay) |
s / w |
pick a session / a window, w down to every pane. By default a chart: keepane centred on top, the sessions on the row under it, then the windows of the session you are on, then that window's panes, each row spread across (a row wider than the screen scrolls, ‹ › at its ends), each an open box in the terminal's own colours (the selected one in the highlight colour) with what it is in its top border (host, session, window, pane); h l or ← → move along a row and the rows under it follow, k or ↑ goes to the parent, j or ↓ to the current window or the active pane. a opens every node at once, each parent over its own; when that does not fit the boxes get narrower, then become one line each, then just their numbers, and what is still too wide scrolls to the selection (a again: only the branch). Paired with other machines (keepane link, below), a row of machines comes under keepane, this one first: ← → on it shows another machine's sessions, windows and panes (asked of it every 5 seconds while the chart is open; offline when it does not answer), and Enter on one of its panes shows that pane's screen (it needs link allow <this machine> --screen there). v turns it into a tree of blocks, one a line (↑ ↓ the parent and the first child, ← → along the same level, - / + fold and unfold a session or a window), then the plain list (j k or arrows move, -/+ or Left/Right fold), then the chart again. In the tree and the list j k go line by line; in all three g G top/bottom, digits jump (every line you can pick is numbered; past 9 type both digits, 1 2 is line 12), Enter goes there, q cancels; f filters by a substring as you type, Enter keeps it and Esc puts the old one back; t tags the line, T clears the tags, x kills the tagged lines, or the current one; D (or Delete) kills them and also forgets a session's save, so that resume does not bring it back. On a session both ask first (y/n). set -g choose-tree-style tree (or list) opens it that way |
( / ) |
switch the client to the previous / next session |
D |
pick a client from a list and detach it |
> / < |
pane menu / window menu (the letter in brackets runs the entry, Enter runs the highlighted one) |
M-n / M-p |
next / previous window with an alert (see monitor-activity) |
With a Chinese, Japanese or Korean input method on, a key that comes in its
full-width form (【 for [, : for :, y for y) works as the key:
after the prefix, in copy mode, in the pickers and menus, at a y/n question
and in the dashboard. What is typed into a pane, or as text at a prompt (a
window's name), stays as typed; a key bound in its full-width form is that
binding.
Zooming, unzooming and moving the focus are animated, for 160 ms by
default. Programs are resized once, to the size they end up at, so nothing
waits for the animation. set -g animation off turns it off, and
animation-time sets the milliseconds.
Under the clock (prefix t), in the rows the time and its lines leave, something
moves with the machine and with keepane (clock-mode-animation):
pulse: a monitor. The heart beats at 60 a minute with the cores idle and 150 with them all busy, and once early for each message keepane delivers (at most four waiting); a network wave stands as tall as the traffic; a disk wave breathes faster and deeper the more the disks do; a system wave swings faster with the context switches and taller with the interrupts. Each trace has its figures beside it (♥ 92 CPU 35% 67W 61°C ✉ 2,NET ↓1.2M ↑80K 3.1k pkt/s,DISK r12M w0,SYS 48k/s 312 proc 4.3k thr).67Wis the CPU package's power;PWR 20W, what a battery gives while the machine runs on it;✉ 2, the messages delivered while this clock is up.matrix: falling characters. Faster with the cores, more columns with memory, cyan columns with the network, quicker flicker with the disks, stray magenta characters with the interrupts; each message delivered falls as a white drop headed by✉.bars: an equalizer with a bar for each core, then memory, the disks (read, written), the network (in, out), the temperature and a bar for each pane of the window by what it prints; then, where they fit beside those, messages (full on one, falling back), the CPU's watts, the battery's, swap, and context switches with interrupts. Each group labelled, peaks falling slowly.fire: a flame for each core across the rows, as tall as the core is busy; its tip yellow, bright yellow or white as the CPU gets hotter; sparks with the interrupts; each message an✉rising through it.aquarium: a fish for each pane of the window, swimming faster the more the pane prints; bubbles with the disks; weed taller with memory, swaying with the context switches; waves quicker with the cores; each message an✉rising from the sand.starfield: a flight through stars, more of them the more processes run, faster (streaking, at a warp) the more the network carries; each message a comet with✉at its head.life: Conway's game of life, more generations a second the busier the cores, sown again with the context switches up to a share of the board set by the memory in use; new cells bright; each message a glider by the middle,✉on it a moment.donut: a turning torus, faster with the cores, its tube thicker with memory; each message turns it once more,✉in its hole.weather: clouds covering more of the sky the busier the cores, rain heavier with the disks and slanting in the network's wind, lightning with the interrupts, water as deep as the memory in use; each message an✉falling into it.
The lines over it end with keepane's own: its sessions and panes, and the messages it has delivered since it started.
In pulse, matrix, starfield, life and donut the colour goes from
green through yellow to red with the CPU's temperature, or with the cores'
load when there is no temperature; the bars are green at the bottom, yellow
higher up and red at the top. random (the default) picks one of the nine
each time the clock is put on a pane, never the one it had before; cycle
takes them in turn; a name sets that one; off, or animation off, leaves
the clock still. Under a clock, / turns to the next kind and ? to the
one before, its name shown for a second; that holds until the clock goes,
the option is not changed (/ starts one under clock-mode-animation off,
none under animation off). The clock stays until q (tmux's goes at any
key); the prefix still works under it. It is drawn in the 16 colours a theme
sets (pane-colours; the terminal's own when the theme sets none), so each
theme colours it. The machine is read twice a second, only while a clock is on
screen. A figure the system does not give is left out, never made up: macOS
gives no disk figure, no context switches or interrupts, no threads and no
power; the temperature comes from a real CPU sensor only (Linux: thermal
zones and hwmon; Windows: LibreHardwareMonitor or OpenHardwareMonitor while
one runs, since Windows' own ACPI thermal zone is a fixed figure on many
machines; macOS: none). The CPU's power is its own meter (RAPL): Windows reads
it for anyone, where the CPU has one; Linux lets only root read it on most
systems (/sys/class/powercap), so there it shows only when readable.
Copy mode, the keys used most:
- Moving:
hjklor the arrows;wbeby word;0^$,HML,{},gGjump;[]go to the previous / next command the shell ran, where it was typed.0and$go to the ends of the line the program printed, across the rows a long one wrapped onto. - Paging:
PageUp/PageDownorC-b/C-f;C-u/C-dby half a page. SinceC-bis also the prefix,C-b C-bpages up in copy mode. A number repeats a key, such as3j. - Selecting and copying:
Spaceorvstarts a selection,C-vmakes it a rectangle,Enterorycopies to a paste buffer and the Windows clipboard. - Searching:
/searches,?searches backwards,n/Ngo to the next hit;qleaves.
A script does the same with send-keys -X <command>, with tmux's command
names.
The mouse selects a pane, drags a border to resize, and switches windows
from the status line. The wheel enters copy mode and scrolls back on the
normal screen, sends arrow keys to full-screen programs, and is passed
through to programs that ask for mouse events; send-keys WheelUp (or
WheelDown) sends a program what the wheel would, from a script (nothing on
the normal screen, where the wheel scrolls keepane's own history). Drag to
select text; it is copied to the Windows clipboard on release, wherever the
pointer is let go, and a right click pastes the clipboard into the pane, as
the terminal itself would. Dragged to the pane's top or bottom row, or past
it, the pane scrolls while the pointer stays there, so a selection can take
more than the screen holds. A selection copies the columns it covers: a
Chinese character, two columns wide, is copied whole.
C-b F (hints) puts a label of one or two letters on every path, web
address and git hash in the panes on screen: a compiler's
src/main.rs:12:5 or App.cs(12,5), https://…, af9af7e. Typing a
label copies that thing to a paste buffer and the clipboard (a path without
its line). Typing it in capitals opens it: an address in the browser, a path
at its line in VS Code when code is on the PATH, else in $VISUAL or
$EDITOR in a new window (vim +12 src/main.rs), else in what the desktop
opens it with. A relative path is taken from the pane's directory. Escape
puts the labels away. A bare name with no slash and no line number
(notes.txt) counts only when that file is there, so prose is left alone;
something the terminal wrapped onto the next row is not found.
set -g hint-open '<command>' opens paths your way: a keepane command in
which {file}, {line} and {col} are put as they are, so quote a path
that may hold spaces with single quotes:
set -g hint-open "new-window hx '{file}:{line}:{col}'"
set -g hint-open "run-shell 'idea --line {line} {file}'"
A PowerShell, bash or zsh pane reports each command it runs through the
prompt hook keepane starts it with (after your own ~/.bashrc or
.zshrc, whose prompt it leaves as it is). macOS's own /bin/bash (3.2)
does not say when a command starts (PS0 came in 4.4); keepane takes the
moment Enter went into the pane instead, so a time there counts from the
key rather than from the shell.
C-b C-t (or set -g pane-timestamps on) shows, at the right end of the
line the command was typed on, when it started, how long it took and
whether it failed:
PS C:\src> cargo build 14:03:22 41s ✓
PS C:\src> cargo test 14:04:10 12s ✗
The time goes in the blank end of the line and changes neither the pane's
width nor what the program printed; copy mode and capture-pane do not
include it. A line without room for it goes without. keepane list-marks
prints the same for a script; list-marks -J as JSON, with the lines each
command takes (what was typed may wrap, a prompt may take two lines) and
the command itself. On the phone, When each command ran (in the view menu) shows the times
in a column to the left.
Knowing where each command starts and ends, C-b y (copy-output) copies
what the last one printed (the lines between it and the next prompt, at most
4 MB) to a paste buffer and the clipboard: a failing test's output, ready to
paste into an issue or an agent. keepane copy-output -p -t %3 prints it
instead, for a script. In copy mode, [ and ] go from command to command.
A test fails; C-b y takes what it printed; one message hands it to the
agent beside it (keepane send-message --to %claude "fix this: $(keepane show-buffer)"), which gets it when it is free. (The agent in the recording
is a stand-in that writes an agent's transcript; any agent set up with
keepane setup works the same way.)
A shell started with a script or command of its own (pwsh -File,
bash -c) is left as it is, hook and all; that script can install the hook
itself: Invoke-Expression (keepane __shell-hook | Out-String) in
PowerShell, eval "$(keepane __shell-hook)" in bash.
Other shells report their commands with the sequences Windows Terminal and VS Code read too (OSC 133). For bash under WSL, or over SSH on another machine:
PS0='\e]133;C\e\\'
PROMPT_COMMAND='printf "\e]133;D;%s\e\\\e]133;A\e\\" "$?"'The panes' output can also be saved to disk (log-history, on by default): one
text file per pane position per day, at
%LOCALAPPDATA%\keepane\history\<session>\<window>.<pane>\2026-09-25.log,
for 30 days (log-history-days), at most 20 MB a day each. A line is
written when it scrolls off the top of the pane, so a progress bar or a
prompt being edited leaves its final text; full-screen programs such as vim
leave nothing; what is still on screen when the pane closes is written
then. Each reported command gets a line of its times before it
(── 14:03:22 · 41s · ✓ ──).
C-b / (choose-history) lists the pane positions that have history,
with their days. Enter on a day opens it in a viewer in a popup. The viewer
starts at the end and moves like less: j k, Space b, g G,
/ ? to search, n N to repeat, [ ] to jump from command to
command, q to quit. keepane view FILE opens any file in it. set -g log-history off stops the logging.
A pane or window closed with kill-pane or kill-window (C-b x,
C-b &) is kept for 10 seconds with its programs still running. C-b u
(undo-kill) puts it back where it was. After that it is gone as before.
undo-kill-time sets the seconds; 0 ends it at once, which is what you
want when you kill something to free a port or a file. The last pane of a
session is not kept, because the session ends with it.
Every session is saved to its own file under %LOCALAPPDATA%\keepane\sessions
whenever its shape changes (windows, panes, layout, names, start commands
and directories) and when the server exits. A reboot, a crash or a
kill-session does not lose that file. A session whose last program exits
(you typed exit in its last shell) forgets it 10 seconds later, unless the
machine is shutting down; D in C-b w or delete-saved forgets one by
hand. So:
keepane resume # bring back every saved session, attach to the first
keepane resume work # bring back (or just attach to) the session "work"
keepane list-saved # what can be resumed, newest first
keepane delete-saved old # forget one
keepane save-session -a # save each session to its own file now (prefix C-s)A bare keepane resumes too: with sessions running it goes into the one
used last, with none running it brings back every saved one and goes into
the one saved last, and only with nothing at all does it start a new one
(keepane new always does). So starting keepane again and again does not
pile up empty sessions that come back on the next resume.
Resuming recreates the pane tree, puts back the last save-history lines
each pane had on screen (500 by default; set -g save-history all keeps
the whole scrollback, colours included) and starts each pane's original
command in the pane's last known directory. What the programs themselves
were doing does not come back; no multiplexer can do that. Saving happens
by itself: the tree whenever it changes, the pane text every 30 seconds,
and everything once more when Windows shuts down, restarts or you log
off (the server holds the shutdown up for the moment that takes), or when
the server is sent SIGTERM or SIGHUP on Linux and macOS. set -g restore-on-start on makes a fresh server restore everything by itself;
set -g autosave off turns saving off; sessions-dir moves the files.
Each PowerShell, bash or zsh pane keeps its own command history (what Up
brings back), in a file under the sessions directory, so a resumed pane has
what it ran and not what every other pane ran. A new pane starts with a
copy of the history of the pane it came from (the one split, or the one in
use for a new window), else of the shell's own history file. Files no pane or saved
session refers to go after log-history-days.
A message delivered to a pane in shell mode is a command there too, and
goes into that history with its header in front (<# keepane #12 from %builder #>).
keepane shell-history -t %3 reads the history back, each message marked
(✉ #12 %builder cargo test); -c leaves the messages out, -m lists
instead every message sent to the pane in any mode, from the event log, with
how it went; -n 20 shows the last 20.
To have all of that happen by itself when you log on to Windows:
keepane startup on # start the server at logon and restore every saved session
keepane startup status # what is registered
keepane startup off # stop doing thatThis writes one value under the per-user Run registry key (no
administrator rights, nothing in Task Scheduler), running the server
through conhost --headless so no console window appears at logon. After a
reboot, keepane attach finds everything as it was.
Upgrading: installing a new keepane does not replace the server that is already running, which keeps your sessions and is still the old program.
keepane version # this keepane, and the server's version when it differs
keepane update --check # is there a newer release?
keepane update # Windows: install it the way this one was installed (MSI or scoop)
keepane restart-server # move every running session to a server of this versionrestart-server saves the running sessions, stops the old server, starts
one of this version and restores exactly those sessions, layout, history
and directories included; terminals attached to it attach again by
themselves (from 0.10 on; an older server's terminals are detached, and
keepane attach takes them back). Run from inside a pane it finishes outside
the pane and writes what it did to %LOCALAPPDATA%\keepane\restart.log.
Options and key bindings come from the config file again, as after
kill-server: ones typed with set/bind since are not carried over
(the old server cannot tell them from its defaults, and copying all of
them would pin the old version's defaults on the new one).
While a terminal is attached to a server of another version, its title
says so. update downloads the MSI, checks it against the SHA-256
published beside it, and hands it to Windows Installer; nothing is ever
installed in the background.
Once a day the server asks GitHub for the latest release's version (one
request to api.github.com, with curl, sending nothing about you or
your sessions), and when it is newer the status line says so (⇡ 0.19.0,
the #{keepane_update} format variable) and show-messages says how to
get it. What it heard is kept in the data directory, so a restarted server
does not ask again that day. set -g update-check off (or the environment
variable KEEPANE_NO_UPDATE_CHECK) stops it.
Tab completion in PowerShell, bash, zsh and fish (command names, each
command's flags, -t targets from the running server, and option names and
values after set / show; aliases like splitw and prefixes like
split-w count as the command they stand for) comes from a completer the
program prints; one line loads it. In PowerShell it also covers a tmux
alias of keepane. Windows PowerShell 5.1 does not ask a program's completer
about words starting with -, so there flags complete in PowerShell 7 only.
Homebrew installs the bash, zsh and fish ones itself.
keepane completion powershell | Out-String | Invoke-Expression # $PROFILEeval "$(keepane completion bash)" # ~/.bashrc
source <(keepane completion zsh) # ~/.zshrc, after compinit
keepane completion fish > ~/.config/fish/completions/keepane.fishInside keepane, Tab at the : prompt completes the command name, its flags
once a - is typed (an alias or a prefix counts as its command; a flag
already given is not offered again), a target after -t, and after
set / show the option's name (abbreviations too:
sync, mon-act) and then its value when it is one of a few (on/off,
top/bottom); several candidates are typed as far as they agree and
listed in the prompt.
PowerShell's Ctrl+D does nothing by default (bash's exits), so it does
not close a pane either. To have it exit on an empty line, add to
$PROFILE:
Set-PSReadLineKeyHandler -Chord Ctrl+d -Function DeleteCharOrExitAnd to have keepane in the Windows Terminal dropdown:
keepane windows-terminal install # a "keepane" profile that attaches to (or starts) the session "main"
keepane windows-terminal status
keepane windows-terminal removeThis is a profile fragment, one JSON file of keepane's own under
%LOCALAPPDATA%\Microsoft\Windows Terminal\Fragments\keepane\; Windows
Terminal merges it in, settings.json is never touched, and the profile
keeps its identity across reinstalls so the font or colours you give it
stay. Every tab opened from it joins the same session, as tmux new -A -s main would.
A pane's directory follows its shell's cd, with nothing to set up:
PowerShell (pwsh or Windows PowerShell), bash and zsh are started with a
prompt hook that reports the directory after every prompt (an invisible
OSC 9;9 or OSC 7; your own prompt, oh-my-posh and the like included, is
left as it is), and for cmd.exe, sh and other programs keepane reads
the process's own directory. A
shell that announces its directory itself (OSC 9;9, quoted or not, or OSC 7
from bash/zsh under WSL) is believed first, and keepane set-cwd (no argument:
the directory you run it from) sets it by hand:
# WSL bash: /mnt/<drive>/... paths map back to Windows drives
PROMPT_COMMAND='printf "\e]7;file://%s%s\e\\" "$HOSTNAME" "$PWD"'list-panes shows the recorded directory, and #{pane_current_path} puts
it on the status line.
An agent in a pane sends and takes the same messages as anything else. Two
things make that work: a hook that runs keepane pane-ready -q when the
agent finishes a turn (so the pane counts as ready in ai mode), and
keepane mcp, an MCP server (stdio) offering the commands above as tools.
It takes the pane it serves from KEEPANE_PANE, so what the agent sends
goes out with that pane as the sender.
keepane setup shows, for each agent it knows, whether it is on this
machine and whether its hook and keepane's MCP server are set up.
keepane setup <agent> prints what that agent needs; --install does it,
each file backed up first and only keepane's entries added:
| Agent | setup word |
Hook (session start, turn end) | MCP server |
|---|---|---|---|
| Claude Code | claude |
SessionStart, Stop in ~/.claude/settings.json |
claude mcp add --scope user |
| Codex | codex |
SessionStart, Stop in ~/.codex/hooks.json |
codex mcp add |
| Gemini CLI | gemini |
SessionStart, AfterAgent in ~/.gemini/settings.json |
mcpServers there |
| Cursor CLI | cursor |
sessionStart, stop in ~/.cursor/hooks.json |
~/.cursor/mcp.json |
| opencode | opencode |
a plugin, ~/.config/opencode/plugins/keepane.js, on start and session.idle |
mcp in opencode.json |
Each runs keepane pane-ready -q, which is quiet outside keepane and
ignored in panes not in ai mode. Codex's notify holds one program only
and is left alone; Codex runs a new hook once you trust it (/hooks in
Codex). A file that is not plain JSON (comments, say) is left alone, and
setup says what to add by hand. These follow each agent's documentation;
Claude Code is the one checked end to end. Writing the hook yourself, leave
the program path unquoted (or write & "C:\path\keepane.exe" pane-ready -q): an agent on Windows may run hooks in PowerShell, where a quoted path
followed by arguments is a syntax error. Any other agent works the same way
if it can run a command at the end of each turn and use an MCP server over
stdio; run keepane set-work-mode ai in its pane before starting it.
Codex (0.160 and later) and pi (1.0 and later) draw in the alternate screen by
default, as vim does: what they print never reaches the scrollback, in
keepane, in tmux or in any terminal, so there is no history to scroll back to.
The wheel (and a drag on the phone) still scrolls them, through their own
view. To keep their history in the pane instead, run them inline: for Codex,
alternate_screen = "never" under [tui] in ~/.codex/config.toml (or
codex --no-alt-screen); for pi, "tuiMode": "regular" in
~/.pi/agent/settings.json.
A message for an ai pane whose agent has never said it is free (since it
started) waits: send-message says so, with the setup that adds the hook;
the dashboard and the phone page say it too (#{pane_unheard}). For Claude
Code, Codex and pi keepane also reads the agent's transcript (as for what it
costs, every 2 seconds): a turn over there frees the pane with no hook, about
3 seconds late at the most, and a turn the person broke off (Esc), when no hook runs,
frees it too, its message ending unfinished. The hook says it at once.
The 24 tools:
| Tools | For |
|---|---|
whoami, list_panes |
its own pane, and every pane: address, name, mode, free or busy, inbox, program, status |
send_message, reply, wait_message, current_message |
send a message, answer the sender, take the next message within a turn, see the message being worked on as keepane recorded it |
list_messages, trace_message, drop_message, move_message |
the inboxes, and what became of a message (a shell command's output included) |
create_session, create_window, split_pane, rename_pane, kill_pane |
make panes (each with a name, a mode and a first message), name any pane, close any pane |
set_status, set_work_mode |
say what it is doing (the dashboard shows it); change its own pane's mode |
list_tasks, show_task, query_events |
chains of messages, and the event log |
list_links, link_info, read_screen, create_remote_pane |
the machines paired with this one, what one of them is, what a pane (here or there) shows, and a pane started there (kill_pane closes it) |
A pane made through MCP starts in ai mode when it runs claude, codex,
gemini, cursor-agent or opencode, in shell mode when it runs pwsh, powershell, bash or
zsh, and in normal mode otherwise, unless a mode is given. What an
agent may start is limited to agent-commands (pwsh powershell claude codex on Windows, bash zsh sh claude codex elsewhere), and how many
panes it and the panes it made may make to agent-pane-limit (8). Claude
Code asks you before each MCP call you have not allowed.
Leave a build, a deploy or an agent running, and check on it from the sofa:
keepane webprints a QR code in the terminal. Scan it with the phone's camera (same
network) and the browser opens a page that lists every pane, under its session
and window (the directory most of a session's panes are in beside its name):
its name large (else the title its program set, else the program), then the
program under it and its directory when it is elsewhere, the last thing it
printed (not a prompt) and how long it has been quiet; a dot for its state (an
agent or shell pane free green or busy yellow, exited red), the messages
waiting in its inbox, and the window's alert marks from the status line (#
printed, ! bell, ~ silent, with monitor-activity and friends on), so you
can see which job finished without opening it. Tap one to see its screen,
colours and all; keepane sends it again whenever it changes, so there is no
refresh to wait for. Swipe across it for the next pane, or tap its title to
pick one; swipe in from the left edge to go back to the list. A reload of the
page keeps the pane that was open. What you type in the box at the bottom goes
to the pane as you type it (deleting in the box deletes there too; an input
method's word goes once it is chosen), and Enter is the Enter. The view menu
(the sliders in the pane's bar) can make the box wait for Enter instead, for
this device: Send then types what is in it, with no Enter after it (Send
again, the box empty, is the Enter), or types and runs it. The history button
beside the box brings back what you sent before (☆ keeps one at the top). The
keys the phone keyboard lacks are in a row above it (Esc, Tab, arrows, more
under ⋯; Ctrl+C and Enter stay in view at its end); Ctrl, Alt or Shift (any of
them together), then a key of the row or a letter typed, sends it with them
(Shift then → is Shift+Right). The ⋯ menu in the pane's bar, and a right click
(a long press on a phone) on a pane in the list, split the pane, open a
window, rename the pane, its window or its session (a pane's name empty takes
it away), close the pane or show its inbox (also a tap on a card's
"queued" or "on #N" mark): the message it works on, each waiting one whole, to
put first, up, down or delete (and undo), and the last few it finished. The
view menu holds the rest: When each command ran adds a column with the time
each command started (tap one for its date, how long it took and its exit
code, and to copy the command or what it printed; see "Command times and
history"). Under the screen is the session's status line, as the terminal
draws it, in its colours: its two ends stay put and the windows between them
scroll sideways when there are more than fit (the current one in view), so
the machine and the clock always show (a right end too long for the room is
cut at its start, marked …); tapping a window opens it. Status
line, in the view menu, hides it on this device; set -g status off hides it
everywhere. A tap on a session or window folds it (the device remembers); its
right click or long press renames it or folds every window of a session at
once. A pane wider than the phone wraps its long lines at the phone's edge
(the view menu turns that off, for a full-screen program). Fit to this screen,
in the same menu, sizes the pane to the phone: it fills its window and its
session takes the phone's columns and rows, so a full-screen program (vim,
htop, an agent's screen) draws for the phone. Meanwhile the computer and any
other phone see that session at the phone's size too (a session has one size;
the page and the status line say so); leaving the pane, going back to the list
or putting the phone away gives it back its size, and so does keepane when no
phone has shown it for 10 seconds. Everything runs on the computer; the phone
only shows and types. "Add to Home Screen" makes it open like an app.
On a shell or ai pane, the envelope beside the box sends what is in it as a
message instead of typing it: into the pane's inbox, from the user, to wait
its turn like any other (as send-message does). The ⋯ menu and a card's menu
set what a pane does with the messages it gets: normal leaves them waiting,
shell runs them at its prompt, ai hands them to its agent. The view menu also
finds text in the output (the lines with it marked, ↑ ↓ between them), copies
the screen's text, and sizes the text (pinching the screen does too; each
device keeps its own). The + at the top starts a new session in your home
directory. A pane whose program ended (with remain-on-exit on) says how it
ended, and Run it again starts the program again. A full-screen program (Codex
or pi as they come, vim) keeps no history: a drag up or down on its screen
scrolls it instead, as the wheel does on the computer, and the pane's bar says
so.
The same page works in a computer's browser: on a wide screen the list stays
on the left and the pane fills the right. The button at the top left folds the
list down to a strip of one button per pane (and back); on a touch screen a
swipe left on the list folds it, a swipe right on the strip opens it. The
button at the top right picks the page's own look, light, dark or the system's
(each device keeps its own), and the terminal's theme (theme, see Themes):
that one is the computer's, so its terminal changes too, and every page shows
the panes in it. The same menu picks the page's language: the system's
(Chinese on a Chinese system, else English), Chinese or English. Beside it,
the time a request takes to the computer and back (23 ms, a green dot under
100 ms, yellow under 300, red above or offline). The full-screen button in a
pane's bar leaves only its screen and the input box, over the whole screen
where the browser can (on an iPhone, over the whole page); the button beside
the box, or the browser's own way out, brings the rest back, and the list
button beside it picks another pane without leaving full screen. (The page is
built from web/, React and HeroUI, into one file keepane carries.)
The code carries the address and a key made fresh at each start (128 random
bits); nothing but the page itself answers without it, and the phone can only
look, type into a pane and use that menu: no command of its own reaches keepane.
It is off until started. Once started, the keepane server serves it in the
background, so the terminal you ran keepane web in is free again, and it
keeps serving until you stop it or the server exits:
keepane web status # the address, and who is connected (watching which pane)
keepane web stop # end it; the phones on it are cut off
keepane web # while it runs: the same code again, to scanTo have it on whenever keepane runs, put web-start -k in ~/.keepane.conf: the
server starts it itself, with the same code each time (-k, below).
While it serves, the status line shows web and how many are connected
(#{web_url} and #{web_clients}, for a status line of your own), the
dashboard (prefix v) says the same over its pane list, and a phone
connecting, or a request with a wrong key, is said on the status line.
keepane web --read-only # look, but not type
keepane web --keep-key # the same code next time, so a bookmark keeps working
keepane web --port 8080 --bind 192.168.1.23 # another port, or another network card
keepane web --bind 192.168.1.23,10.0.0.5 # more than one card: each address (or -b again); the code is for the firstIt is plain HTTP, meant for your own network: on a shared one, someone
watching the traffic could read the key. From elsewhere, put a private
network such as Tailscale in between: keepane web listens on this
machine's network address and on its Tailscale addresses (web status lists
them). Windows asks once whether keepane may use the network; allow it for
private networks.
keepane knows when a pane is done: a command that ran a while ended (its
shell reports its commands, as for the command times), an agent finished
its turn (its hook ran keepane pane-ready), a message's task ended, or a
program exited. It tells you where you are:
- The phone's page, while it is open: a banner across the top (tap it to go to the pane), a short sound, a buzz on Android, and the count in the tab's title. What was done while the page was hidden is told when it is shown again. A page cannot raise a notification over plain HTTP, and a phone stops a page it is not showing, so for a locked phone use the hook or the webhook.
- The desktop, with
set -g notify on. - A webhook:
set -g done-webhook <address>and keepane posts each one there, throughcurl, in the shapedone-webhook-formatnames:json(keepane's own),text(the line alone: ntfy, Bark),feishu,wecom,dingtalk,slackordiscord(a chat's incoming webhook). A chat's message starts withkeepane, which a Feishu or DingTalk bot's keyword check can be set to; a chat that refuses shows inshow-messages. - The
pane-donehook, for anything else. What it is about is in its environment:KEEPANE_DONE_KIND(command,agent,task,exit),KEEPANE_DONE_TEXT(the line),KEEPANE_DONE_OUTPUT(what the pane printed last, a line each),KEEPANE_DONE_PANE,KEEPANE_DONE_NAME,KEEPANE_DONE_OK(1 or 0, when known),KEEPANE_DONE_EXIT(the exit code, when the shell gave it: PowerShell says only whether a command failed),KEEPANE_DONE_SECONDS,KEEPANE_DONE_GIT(an agent's work in its repository, in a line; see below), andKEEPANE_DONE_JSON.
set -g done-webhook https://open.feishu.cn/open-apis/bot/v2/hook/<token>
set -g done-webhook-format feishu
# ntfy (an app on iOS and Android), from the hook: sh on Linux and macOS
set-hook -g pane-done run-shell 'curl -s -d "$KEEPANE_DONE_TEXT" ntfy.sh/<your-topic>'
# ... and PowerShell on Windows
set-hook -g pane-done run-shell 'curl.exe -s -d $env:KEEPANE_DONE_TEXT ntfy.sh/<your-topic>'What counts, and for which panes:
set -g done-events command agent # any of command agent task exit; all; none
set -g done-after 30 # seconds a command must run to count
set -g done-panes named # panes with a name or in ai/shell mode; all for every pane
set -g done-lines 5 # the last lines the pane printed go with it (0: none)Each telling carries what the pane printed last (a command's own lines; an
agent's last pane-status), under the line in a chat or a notification, and
as output in keepane's JSON.
A pane is told of once in three seconds: an agent's turn and the task it
finished are one telling. keepane list-done lists the last hundred.
When a pane's directory is in a git repository, keepane reads it for two things: what is there now, and what an agent's work did.
What is there now shows after the branch in the status line and on the
clock: main ●3 ↑2 ↓1 !1 is 3 files changed (new ones too), 2 commits
ahead of the upstream, 1 behind, 1 file in conflict; nothing is shown for
what there is none of. The variables are git_branch, git_head (the
commit, short), git_changed, git_ahead, git_behind, git_conflicts
(each empty at none) and git_marks (all of it as above), for a
pane-border-format or a status line of your own. The dashboard shows the
same on a line of the chosen pane.
What an agent's work did goes with its end, in an ai pane: measured from
the commit the repository was at when the work began (its message was
delivered, or someone typed to it while it was free), it is the commits
made since, what in the working tree differs from that commit (each file's
lines added and removed) and the new files git does not track yet:
git:
main 1a2b3c4..5d6e7f8: 1 commit, 2 files +40 -12, 1 new file
5d6e7f8 parse the header
src/parser.rs +38 -12
src/lib.rs +2 -0
new notes.md
It is under the message in trace-message (and git in its JSON), so a
lead that waits with trace-message <id> -w gets it with the answer; and
it goes with the telling (list-done, the webhook, the desktop
notification, KEEPANE_DONE_GIT and git in KEEPANE_DONE_JSON). The
message counts as done, and is told of, once this is read: git runs on a
thread of its own, so nothing else waits (here, a tenth of a second with
3000 new files in the repository). A work that ends in another repository than it began in, or
whose first commit was rebased away, is measured from where it ends and
says (start unknown); when git is there but cannot answer, it says
changes unknown (git failed). It is what changed in the working tree
since the work began, whoever changed it: two agents in one working tree share it, so give each
its own worktree (git worktree add) when that matters.
git is run only for what it alone can say, never while the server waits
for it, each time for at most 10 seconds (an agent's work takes up to
four), and never taking the index's lock from an agent at work in the
same repository (--no-optional-locks). keepane runs it on its own, in
whatever repository a pane is in, so it turns off the programs a
repository's own config can name for it to start: the file watcher
(core.fsmonitor), an external diff, text conversion and the pager. What
git status of your own would still run there (a filter such as Git
LFS's) it runs too. The status
is asked again at most every 10 seconds while something shows it, and at
once after a command or a turn ends there. keepane uses the git on the
PATH, else where Git for Windows, GitHub Desktop or Homebrew put it; set -g git-command <path> names another. Without git, the branch and the
commit ids are still read from the repository's own files, the figures are
left empty, and an agent's work says changes unknown (git not found).
Panes on two computers can message each other the same way panes on one do,
over the port keepane web serves on, once the two keepane servers are
paired. It works like SSH keys: each server has a key pair of its own and a
table of the machines it lets in, and pairing puts each one's key in the
other's table, once. On the machine to pair with, run keepane web and
take the address it prints (the one the phone scans, key included); on this
one:
keepane link add http://100.64.0.3:7681/#k=... # pairs both ways, once
keepane link list # the machines paired
keepane link panes 100.64.0.3:7681 # their panes
keepane send-message --to 100.64.0.3:7681/%worker "run the tests"A pane there is written host:port/ and then the pane as that machine names
it (%worker, $1:@3.%7). The message arrives with from= this machine and
pane, so send-message -r there answers back here, into the same task. Both
machines need keepane web running; the web key is used for the pairing only,
and every request after it is signed with the server's own key, so a new web
key changes nothing. A machine is known by its key, not its address: turning
up from another network (Tailscale instead of the LAN), it is the same
machine. The two clocks must agree to within two minutes.
A paired machine's messages reach ai and normal panes only. To let them
run as commands in shell panes, allow that machine, from a terminal outside
keepane (never from inside a pane, where an agent could):
keepane link allow 100.64.0.3:7681 --shell # --no-shell takes it back
keepane link allow 100.64.0.3:7681 --screen # it may read the panes here; --no-screen takes it back
keepane link allow 100.64.0.3:7681 --panes # it may start panes here; --no-panes takes it back
keepane link remove 100.64.0.3:7681 # unpair, on both
keepane link trust 100.64.0.3:7681 <key> # by hand, with the key `keepane link id` prints there
keepane link rekey # a new key: every pairing has to be made againWhat the other machine can be asked, beyond its panes:
keepane link info 100.64.0.3:7681 # its name, system, keepane, uptime, CPU, memory, panes
keepane trace-message 12 -w 60 # a message sent there: what became of it there (and a command's output)
keepane link capture -S 100 100.64.0.3:7681/%worker # what a pane there shows (it must allow it: --screen)A machine that allowed this one to (--panes) runs panes for it:
keepane link start 100.64.0.3:7681 -n helper -- claude # its address there comes back
keepane send-message --to 100.64.0.3:7681/%helper "look at the failing test"
keepane link kill 100.64.0.3:7681/%helper # only a pane this machine started thereThe program must be one of that machine's agent-commands, with any
arguments: a shell on that list (pwsh, bash, ...) runs whatever it is
given, so there --panes allows as much as --shell, and
set -g agent-commands "claude codex" allows agents only (link allow --panes says what the list holds). A pane counts toward that machine's
agent-pane-limit, for this machine alone. The pane goes in a
session named after this machine (-s names another), with its work mode
from its program as for a pane an agent makes (-m gives one); a message to
a shell pane there still needs --shell as well. That machine keeps which
panes this one started only while its server runs. Agents do the same
through MCP: create_remote_pane, and kill_pane with the address.
A message for a shell pane from a machine not allowed is refused, and the
sender told how to allow it. keepane web --read-only takes nothing from
other machines either. When the other machine cannot be reached, the message
fails at once (nothing is queued to be sent later); with -w, the other
machine waits for it to be delivered, and one still queued when the time runs
out is a time-out here, as it is for a pane on this machine. Pairing and
unpairing are said on the status line and kept in the event log, and so are
refused requests, once a minute for each address that sends them (a flood of
bad requests fills neither). The design and every decision:
docs/design/link.md.
keepane man prints the whole manual (every command, the default keys,
the config, the environment and the files) as Markdown, the way
docs/keepane.1.md is written: for reading, or to hand to
a program. keepane man --roff writes it as a manual page;
Homebrew installs that, so man keepane works.
~/.keepane.conf (or ~/.config/keepane/keepane.conf, or the file named
by KEEPANE_CONFIG; ~ is %USERPROFILE% on Windows) holds one command
per line, tmux syntax.
keepane reads only its own config, never ~/.tmux.conf. To bring a tmux
config over, ask for it once:
keepane import-config -n # show what would be written, write nothing
keepane import-config # from ~/.tmux.conf (or ~/.config/tmux/tmux.conf)
keepane import-config some.conf -o ~/.keepane.conf # any config, anywhereEach line is tried the way keepane would read it at start. What it takes
is written as it was (continuation lines and %if blocks keep their shape,
every branch checked); the rest goes in commented out, with why: TPM and
every @ plugin setting (tmux's plugins do not run in keepane), options
tmux has and keepane has no use for (escape-time, mode-keys, ...),
bindings of mouse "keys" such as MouseDragEnd1Pane (keepane's mouse
handling is fixed), commands that run programs (run, if-shell: an
import runs nothing), and anything keepane rejects. It goes into the config keepane reads (a new
~/.keepane.conf if there is none) and says so; a file imported once is
refused the second time. A running server read its config when it started:
keepane source-file ~/.keepane.conf applies the import now.
set -g prefix C-a
set -g default-shell wsl # Windows: pwsh (default), powershell, wsl, cmd, or a path; elsewhere $SHELL (default) or a path
# set -g default-command "wsl.exe -d Ubuntu"
set -g mouse on
set -g history-limit 10000
set -g status-position top
set -g status-style fg=black,bg=colour39
set -g pane-active-border-style fg=colour39
set -g base-index 1 # windows and panes counted from 1
set -g pane-base-index 1
set -g repeat-time 500 # how long a `bind -r` key keeps working; 0 disables
set -g prefix-hint on # a panel of what the keys do, when the key after the prefix is late
set -g prefix-hint-delay 500 # how late, in ms (0: at once)
set -g remain-on-exit on # keep a pane whose program exited, showing why
set -g save-history 500 # lines of each pane saved for `resume`; 0 saves none, all saves the whole scrollback
set -g monitor-activity on # flag a background window that prints (`#` on the status line)
set -g monitor-bell on # and one that rings the bell (`!`); on by default
set -g monitor-silence 60 # flag one that has said nothing for 60s (`~`); 0 disables
set -g visual-bell on # say it on the status line instead of ringing
set -g done-after 60 # tell of a command only when it ran a minute (see "Told when a pane is done")
set -g pane-timestamps on # each command's time at the end of its line (prefix C-t flips it)
set -g log-history on # keep what panes print, a file per pane per day (prefix / to read)
set -g log-history-days 30 # for how long; 0 keeps everything (log-history-dir moves the files)
set -g undo-kill-time 10 # seconds a killed pane or window can come back (prefix u); 0 for none
set -g keep-zoom off # moving to another pane unzooms, as in tmux (on: the zoom moves with you)
set -g animation off # no frame flying to where the keys go (animation-time 160: its milliseconds, 0-10000)
set -g clock-mode-animation bars # under the clock: pulse, matrix, bars, fire, aquarium, starfield, life, donut, weather, random (the default), cycle or off
set -g agent-cost off # no price list fetched, no cost for the panes' agents (on by default)
set -g git-command C:\Tools\git\cmd\git.exe # the git to run for the panes' repositories (default: the one found)
bind -r h select-pane -L # -r: press h h h after one prefix
bind -r j select-pane -D
bind -r k select-pane -U
bind -r l select-pane -R
bind | split-window -h
bind - split-window -v
bind -n M-Left previous-window # -n: no prefix
bind -n M-Right next-window
bind r source-file ~/.keepane.conf
set -ag status-right " | keepane" # -a adds to what the option already holds
source-file ~/.keepane/themes/nord.confOption names take an unambiguous abbreviation, the way command names do:
set sync is set synchronize-panes, set mon-act on is
set monitor-activity on, and each dash-separated word can be shortened
(set w-s-f "#I:#W"). An on/off option with no value flips: set mouse,
set sync. Something that could mean several options says which
(set mon lists the three monitor-* ones).
Options unknown to keepane but common in .tmux.conf (escape-time,
focus-events, ...) are accepted and ignored if they are set anyway (by a
source-file of a tmux config, say); import-config leaves them out.
default-terminal (xterm-256color
unless set) is the TERM panes get on Linux and macOS; on Windows ConPTY
sets up the terminal and the option does nothing.
keepane looks like Tokyo Night out of the box: the session on a blue block,
the current window on a purple one, muted tabs, quiet pane borders with the
active pane outlined in blue. themes/ in this repository holds other
colour schemes (Nord, Gruvbox dark, Dracula, Catppuccin Mocha), Tokyo Night
itself to edit, and plain.conf, tmux's plain green bar. They are ordinary
keepane commands, so a theme is just a file to source and an easy thing to
copy and edit:
source-file ~/.keepane/themes/dracula.conf
source-file ~/.keepane/themes/plain.conf # tmux's look insteadTwo themes are built in, to switch with one option (or from the phone's
page): tokyo-night, the default, and tokyo-day, its light one.
set -g theme tokyo-daykeepane draws in the terminal it runs in, so with tokyo-night a pane's
background and the colours programs use (their red, blue...) are the
terminal's own. A light theme has to draw them itself, or it would be a
light status line over dark panes: tokyo-day sets window-style (a
pane's default text and background, as in tmux) and pane-colours (the 16
colours programs use, the first 16 of the palette), so it is light in a
dark terminal too. A theme sets these and the status line and borders all
at once, so set the theme first and your own changes after it. Once one of those options is set some other way (a theme file, by hand), the look is no built-in theme's and show -gv theme says custom.
The default look uses 24-bit colours. On a terminal without them (macOS's
Terminal, unless it says COLORTERM=truecolor), keepane shows the nearest
of the 256 colours instead, for its own look and for programs in panes.
status-left, status-right, window-status-format,
window-status-current-format and pane-border-format take tmux format
strings: #S session, #W window name, #I window index, #P pane
index, #T pane title, #H host, #F flags, #{session_name}-style
names, #{?flag,then,else} conditionals (#{?window_flags,busy,idle},
#{?session_name==work,…,…}), %H:%M time fields,
#[fg=colour39,bg=black,bold] style changes and #(command), which runs the
command every status-interval seconds (default 15) and shows its first
line (a one-shot display-message -p or jobs -F runs it right away,
giving up after 3 s). status-left-length / status-right-length clip. status-justify left|centre|right|absolute-centre places the window list, and
window-status-separator is what goes between the labels (a space by
default). With more windows than fit, the right side stays whole and the
windows around the current one fill the rest, < and > marking where
some are left out when there is a column for them (as in tmux), reversed
when a window left out there has an alert; only a right side too long for
the current window gives way, as far as that window and its marks need,
cut at its start and marked … so the clock at its end stays.
The variables, by kind:
- session:
session_namesession_idsession_windowssession_attachedsession_created - window:
window_namewindow_idwindow_indexwindow_paneswindow_activewindow_last_flagwindow_zoomed_flagwindow_widthwindow_heightwindow_bell_flagwindow_activity_flagwindow_silence_flagwindow_flags - pane:
pane_indexpane_idpane_titlepane_current_commandpane_start_commandpane_current_pathpane_widthpane_heightpane_activepane_deadpane_dead_statusalternate_onmouse_any_flagpane_synchronizedpane_in_modepane_pidpane_start_timepane_activitypane_dead_timepane_lastpane_modepane_toppane_leftpane_bottompane_rightpane_at_toppane_at_bottompane_at_leftpane_at_rightcursor_xcursor_yhistory_sizehistory_limit - client:
client_widthclient_heightclient_nameclient_sessionclient_createdclient_activityclient_prefix - server:
hosthost_shortsocket_pathversionpid. Alsosession_activitysession_last_attachedwindow_activitywindow_start_flagwindow_end_flagwindow_layout.
The machine, read in-process with no #(command) needed: cpu_percentage
ram_percentage ram_used battery_percentage (empty without a battery)
battery_charging uptime. Other variables used often: git_branch (the
branch of the pane's directory, read from .git, empty outside a
repository; git_marks and the other git_* in
What a pane's repository says), pane_current_path_short (~ for home), pane_pid_command
(the program the pane is running right now, cargo during a build) and
pane_output_count (how many times the pane has printed; a script can
compare two readings to tell whether anything changed, which
pane_activity, in whole seconds, cannot). keepane_update is a newer
keepane's version once the daily check found one. The agent_* variables
describe the agent a pane runs (What the agents use and cost).
The network: local_ip is the address this machine reaches the network
from (read from the system, nothing sent), and public_ip the address the
internet sees it at. Only a service outside knows the second, so keepane
asks one (api.ipify.org, every ten minutes) only while a format that is
drawn uses #{public_ip}; without it nobody is asked. Neither is in the
default look; to show them:
set -ag status-right " #{local_ip} #{public_ip}"The default status-right uses the machine's (see
themes/tokyo-night.conf, which is the default look written out, and
themes/plain.conf for tmux's); set -g status-right ... replaces it, and
set -g status off hides the line (prompts and messages then show over the bottom row).
Comparisons, as in tmux: #{==:a,b} #{!=:a,b} #{<:a,b} #{>:a,b}
#{<=:a,b} #{>=:a,b} #{&&:a,b} #{||:a,b} and #{m:pattern,text} (a
glob; m/i: ignores case) answer 1 or 0, and can be the condition of
#{?…} or of a %if in the config file.
Modifiers, as in tmux: #{=10:pane_title} (first 10 characters),
#{=-10:…} (last 10), #{b:pane_current_path} (basename), #{d:…}
(dirname), #{t:session_created} (a time as a clock), #{s/foo/bar/:…}
(substitution); they nest (#{=8:b:pane_current_path}).
set -g pane-border-status top (or bottom) puts a line of
pane-border-format on every pane's border: by default the pane's number
and title, the active pane's in bold.
set -g status-right "#[fg=yellow]#(pwsh -NoProfile -c (Get-Date).ToString('HH:mm'))#[default] #H"Plugins work the way tmux plugins do: a plugin is a directory with a
<name>.keepane (or plugin.keepane) file of keepane commands, plus any scripts it
needs, in any language. Scripts call back through the CLI: KEEPANE holds the
socket name and KEEPANE_PANE the pane, so keepane -L $env:KEEPANE display-message ... from a script reaches the right server.
# ~/.keepane.conf
set -g plugin-path ~/.keepane/plugins # default
set -g @plugin demo # ~/.keepane/plugins/demo/demo.keepane
set -g @plugin C:\src\my-plugin # or a path (dir or file)Inside a plugin file you have everything the config has, plus:
run-shell [-b] [-t target] commandruns a command (through pwsh) with the keepane environment; its output is shown when it finishes (-b: ignore).set-hook -g <hook> <command>runs a command when something happens:after-new-session,after-new-window,after-split-window,after-select-window,after-select-pane,after-kill-pane,client-attached,client-detached,pane-exited,pane-done(see "Told when a pane is done").set-hook -gu <hook>removes it;show-hookslists them.set -g @anything valuestores a user option;show-options -gqv @anythingreads it back (from a script:keepane -L $env:KEEPANE show-options -gqv @anything).#(command)pieces on the status line (above).load-plugin name-or-pathandlist-pluginsat runtime.
A minimal plugin that shows an agent's progress file on the status line and
pops the full log with prefix A:
# ~/.keepane/plugins/agent-status/agent-status.keepane
set -g status-right "#[fg=cyan]#(pwsh -NoProfile -File ~/.keepane/plugins/agent-status/summary.ps1)#[default] %H:%M"
set -g status-interval 5
bind A run-shell "pwsh -NoProfile -Command Get-Content $env:TEMP\agent.log -Tail 30"Commands a script or a binding reaches for, beyond the obvious ones
(keepane list-commands prints them all, and any unambiguous prefix works):
-
pipe-pane [-o] [-I] [-O] [-t target] [command]copies everything a pane prints into a command's standard input (-O, the default); with no command it stops.keepane pipe-pane "$input | Add-Content build.log"keeps a build log without touching the build (PowerShell reads all of its input before it runs, so that file is written when the pipe stops; acmd.exe /c findstr ... > filecommand writes as lines arrive).-Igoes the other way: what the command prints is typed into the pane, and the pipe ends with the command's output (-IOdoes both). -
wait-for [-L|-U|-S] channelblocks until another client signals the channel (or unlocks it), so two scripts can take turns:keepane wait-for readywaits,keepane wait-for -S readyreleases it. -
display-menu [-T title] name key command ...opens a menu over the window; an empty name is a separator.display-popup [-E] [-w W] [-h H] [-x X] [-y Y] [-d dir] [command]runs a program in a box over the window (-Ecloses it when the program exits,-Ccloses it from outside;-x/-ytake a column or row, a percentage,Cfor centred orR/Bfor the right or bottom edge, and the box is centred without them). -
choose-clientlists the attached clients and detaches the one picked. -
send-keys -X <copy-command>drives copy mode, with tmux's command names (search-backward,begin-selection,copy-selection, ...). -
capture-pane -p [-e] [-J] [-S -N]prints a pane, with the colours if asked (-e), wrapped lines joined back into one (-J), N lines of scrollback above it (-S -N,-S -for all of it). -
find-text patternsearches what every pane has printed and says where each hit is and how far back:ft:0.0 -8 REDIS-TIMEOUT-here.-Cmatches case,-tnarrows to a session or window,-ncaps the hits per pane. keepane'sfind-windowsearches only window names and titles. -
record [-t target] out.castwrites everything a pane prints from now on as an asciinema v2 file (resizes included);record -t targetwith no path stops. Play it back withasciinema play, upload it, or embed it in a page. -
notify [-T title] messageraises a desktop notification (a Windows toast, in the Action Center under keepane's own name), andset -g notify onsends one for every alert, so a job that ends while the terminal is behind other windows still reaches you. An alert's toast has a "Go to pane" button: it opens akeepane://link that runsfocus-pane, which switches every attached client to that pane and brings its window forward (best effort: Windows Terminal does not always let a window be raised from outside). The first toast registers two entries underHKCU\Software\Classes(the AppUserModelID and thekeepane:protocol), nothing machine-wide; when a toast cannot be shown a tray balloon is used instead. -
focus-pane %Nis that command on its own: every attached client goes to pane%N(the idlist-panesand#{pane_id}show). -
jobsis the task board: one line per pane on the whole server, with whether its program is still running or what it exited with, how long it has been up, how long since it last printed, its pid, command and directory.-t sessionnarrows it;-F formatprints what you want instead (#{pane_start_time},#{pane_activity}and#{pane_dead_time}are the raw times).prefix B(choose-jobs) is the same board as a picker:Entergoes to that pane,xkills it,rrestarts it, and the rows update in place while it is open.PANE STATE UP IDLE PID COMMAND DIR build:0.0 running 2h13m 4s 21608 cargo build C:\src\keepane web:0.0 exit 1 2h13m 1h02m 28748 npm run dev C:\src\site
Every window and pane command accepts -t target as in tmux:
session, session:window, :window, session:window.pane, and the window
part may be an index, a name, +, - or !. %N is a pane by its id (as
list-panes shows it), which stays the same pane as others come and go;
list-panes -F prints a format for each pane. The one exception is
select-pane, whose -t takes the pane to move to (next, last or an
index) rather than a window target, next to -L -R -U -D.
keepane is a client. The first invocation starts a detached server process
(keepane __server) that owns every session; clients talk to it over a per-user
named pipe on Windows (\\.\pipe\keepane-<user>-<socket>) and a Unix socket
elsewhere (keepane-<uid>/<socket>, see Install); choose the socket with -L.
Each pane is a pseudo terminal (a ConPTY on Windows) with a vt100 terminal
model on the server side; the
server composites the visible panes, borders and status line into a frame and
sends only the cells that changed to the attached client, which writes them to
the console with VT sequences. The server exits when its last session ends.
On Windows it leaves the job it was started in when that job allows it:
OpenSSH runs each session in a kill-on-close job, so a server started over
SSH would otherwise end with the connection. On Linux and macOS it runs in a
session of its own, which a terminal's or an SSH connection's hang-up does
not reach.
Environment inside panes: KEEPANE (socket name) and KEEPANE_PANE (pane id). A
keepane command run inside a pane talks to the server that owns it, the way
$TMUX works for tmux, so keepane ls from a script or a plugin needs no -L.
Server log: %LOCALAPPDATA%\keepane\server.log (KEEPANE_LOG=debug for more).
Past 5 MB it becomes server.log.1 and a new one starts, so a server that
runs for months keeps at most about 10 MB of log.
When a key does nothing (the prefix, say), run keepane show-keys in that same
terminal and press it: each key prints what the console handed over and the
key keepane reads it as, and q quits. Nothing printed means the program
hosting the terminal kept the key for itself (VS Code, for one, binds
Ctrl+B). Hosts that pass input on as bytes rather than key events (SSH,
some remote tools) send Ctrl+B as the character 0x02 with no Ctrl flag;
keepane reads control characters the way tmux does, so that is still C-b.
The pipe carries a DACL that admits only the creating user (and SYSTEM), the
Windows equivalent of tmux's mode-0700 socket directory (which is what the
Unix socket gets). Every pane runs in a kill-on-close job object on Windows,
and is hung up with its whole session elsewhere, so kill-pane,
kill-session and a server exit take the whole process tree down, and a
slow client console never makes the server buffer
frames without bound: it drops to a full redraw instead.
vendor/vt100 is vt100 0.16.2 with a one-function fix for a panic when a
pane shrinks through a wide (CJK) character; see vendor/vt100/KEEPANE-PATCH.md.
The server also logs and survives any panic in a command (server.log).
cargo test # unit + end-to-end tests (spawns cmd.exe panes on Windows, sh elsewhere)
cargo clippy --all-targetsThe web page has browser tests of its own (web/tests, run with
e2e): see web/README.md.
CI runs both on Windows, Linux and macOS. The platform's own code lives in
src/platform/windows and src/platform/unix, each with the same modules
(see docs/design/platform.md). On Windows the tests/console.rs suite
runs the real keepane.exe inside a ConPTY, so the console code path (raw
input mode, alternate screen, detach cleanup) is covered without a human at
the keyboard.
On Linux and macOS, keepane startup (the server at logon), keepane update (it says where the new version is) and desktop notifications with
a "Go to pane" button are not there yet, and tab completion is for
PowerShell only.
Compared with tmux, these differ for now:
- Clients attached to the same session share one window size.
window-size latest|smallest|largest|manualtakes the size of the client used last, the smallest or the largest, or only whatresize-windowsets. A smaller client shows its own view of the window, panned withShift+arrows (refresh-client -U/-D/-L/-R); the view follows the cursor while you type. - Hooks are only the events listed above;
choose-treefilters by a substring, not by a tmux format. - In
display-popup, the prefix key still belongs to keepane; pressing it twice sends it to the program in the box.
Pane messages: shell work mode needs keepane's prompt hook (PowerShell,
bash, zsh), so cmd, sh, fish and the shells under WSL do not take messages
on their own yet (they can read-message); a pane started before keepane
0.15 has the older hook and must be restarted for it. The phone page shows each
pane's inbox; the dashboard's tasks and events are not on it yet.
docs/tmux-parity.md has the command-by-command and key-by-key list.
Up to 0.13.1 the project was called wmux. Projects of that name already exist on GitHub, winget and crates.io, so from 0.14.0 it is keepane, from keep + pane: the programs in the panes keep running when the terminal is gone.
Coming from wmux:
keepane migratemoves everything in one go: the sessions of a wmux server still running (saved, the old server stopped, restored in keepane with their layout, history and directories; the programs in them start again, as withrestart-server), what wmux saved under%LOCALAPPDATA%\wmux(sessions, history, the phone key), the start at logon, the Windows Terminal profile and the notification link. A wmux session whose name keepane already runs comes back beside it as<name>-wmux. Starting keepane while a wmux server still runs says so.- Your
~/.wmux.confkeeps working, and so doWMUX_*environment variables,~/.wmux/pluginsand*.wmuxplugin files, until you rename them (~/.keepane.conf,KEEPANE_*,~/.keepane/plugins,*.keepane). - The MSI replaces the wmux one in "Apps & features".
wmux updateno longer finds anything: install keepane from the releases page once. With scoop,scoop uninstall wmuxand install the keepane manifest (see Install). - The repository is now github.com/newdee/keepane (the old links lead there) and the site dfine.tech/keepane.
- A
tmuxorwmuxalias in your$PROFILEneeds to point at keepane.