# KEEPANE 1 ## NAME keepane - a terminal multiplexer whose panes keep running and pass messages to each other ## SYNOPSIS `keepane` [`-L` *socket-name*] [*command* [*flags*] ...] `keepane -V` | `keepane --help` ## DESCRIPTION keepane runs programs in panes, inside windows, inside sessions, in a server that keeps them running after the terminal that showed them is closed. A client attaches to a session to show it and type into it, from any terminal. Its keys, commands and config are tmux's: `C-b` is the prefix, `C-b %` splits, `C-b d` detaches, and `keepane attach` comes back. Panes can also be named and pass messages to each other: a message sent to a pane waits in its inbox until the pane is free, then goes in; a pane in *shell* work mode runs it as a command and its output goes on record, and an AI agent in a pane in *ai* work mode reads it and answers. Everything a message does is in an event log. With no command, `keepane` goes into what is there (`new-session -A` with no name): the session used last; with none running, every saved session back and the one saved last; with nothing at all, a new session. `keepane new` always starts a new one. A command name can be cut to any unambiguous prefix (`keepane att`, `keepane splitw -h`). Run inside a pane, a command goes to the server that pane belongs to, as tmux's do with `$TMUX`. This page is `docs/keepane.1.md` in keepane's repository, and `keepane man` prints it as it is written (Markdown, for people and for programs that read it); `man keepane` shows it where the manual page is installed (`keepane man --roff` writes one). The README has every command's flags and examples. ## OPTIONS `-L` *socket-name* : Talk to (or start) the server with this name instead of `default`. Servers with different names are separate: their sessions, options and key bindings are their own. `-V`, `--version` : Print keepane's version. `-h`, `--help`, `help` : Print a summary of every command. ## COMMANDS Each command below is also what the `:` prompt (`C-b :`), key bindings and the config file take. `keepane list-commands` lists them, and `keepane --help` gives each one's flags. ### Sessions `new-session` (`new`), `attach-session` (`attach`), `list-sessions` (`ls`), `has-session`, `kill-session`, `kill-server`, `rename-session`, `switch-client`, `detach-client`, `start-server`, `choose-session`, `choose-tree` (sessions, windows and panes, and the paired machines' in the chart; a chart of rows by default, a tree of blocks with `choose-tree-style tree`, the plain list with `choose-tree-style list`; `v` goes to the next), `choose-client`, `list-clients`. ### Windows and panes `new-window` (`neww`), `kill-window`, `rename-window`, `select-window`, `next-window`, `previous-window`, `last-window`, `list-windows`, `swap-window`, `move-window`, `rotate-window`, `resize-window`, `respawn-window`, `choose-window`, `find-window`, `select-layout`, `next-layout`, `previous-layout`, `split-window` (`splitw`), `kill-pane`, `select-pane`, `last-pane`, `resize-pane`, `swap-pane`, `break-pane`, `join-pane`, `move-pane`, `respawn-pane`, `list-panes`, `display-panes`, `capture-pane`, `send-keys` (`WheelUp`, `WheelDown`: what the wheel sends a full-screen or mouse-reading program; nothing otherwise), `send-prefix`, `pipe-pane`, `clear-history`, `record` (the pane's output as an asciinema file), `focus-pane`, `passthrough` (`[-t pane] [on|off]`: every key typed in the pane goes to it, the prefix too; see KEYS), `undo-kill` (the pane or window killed in the last `undo-kill-time` seconds comes back). ### Copy mode and buffers `copy-mode`, `set-buffer`, `list-buffers`, `show-buffer`, `delete-buffer`, `choose-buffer`, `load-buffer`, `save-buffer`, `paste-buffer`. `copy-output` (what the last command printed, to a buffer and the clipboard; `-p` prints it) and `hints` (label the paths, addresses and hashes on screen to copy or, in capitals, open one; `hint-open`). ### Seeing what happened `jobs` and `choose-jobs` (every pane: running or exited, for how long), `find-text` (search what every pane printed; `-H` every day of the history log too, each hit with its day and line), `choose-history` and `view` (what panes printed, a file a day; `choose-history -s text`, `/` in it, the lines of every day that hold it, each opened at its line, which `view -n LINE -s TEXT` does), `list-marks` (the commands a pane ran; `-J` as JSON, with the lines each takes), `choose-marks` (the commands every pane's shell ran, newest first: Enter goes to one in copy mode, `y` copies what it printed, `r` runs it again), `shell-history` (a pane's own shell history, the messages delivered to it marked; `-c` the commands only, `-m` every message sent to it), `list-done` (when panes were done: `done-events`, told to the phone, `notify`, the `pane-done` hook and `done-webhook`), `show-messages`, `clock-mode`, `dashboard` (panes, inboxes, tasks and the chosen pane's screen, to act on; its Git tab, the pane's repository or files; from 150 columns, the machine's CPU, memory, disk and network on the left), `list-agents` (the Claude Code, Codex, pi and Cursor CLI the panes run: model, cost, tokens, context, CPU, memory; Cursor's CPU and memory only; `-J` as JSON), `trace-pane` (what a pane's agent did, step by step: its latest turns, each call of the model and each tool, from when to when, failed or not; a tree, or `-J` JSON; the dashboard's Trace tab (`T`) draws it as a waterfall, each kind in its colour, the latest step in sight, `e` opening every turn), `status-line` (a session's status line as drawn; `-J` its parts, their colours and each window's pane, which the phone page draws under the screen), `choose-ssh` (prefix `@`: the machines `ssh` reached, connected ones first, then by last use; Enter goes to the pane on one, else connects in a new window named after it; `s` connects in a split, `y` copies the command, `d` removes one, `f` filters), `ssh-hosts` (the same list; `-J` as JSON). ### Panes that pass messages `rename-pane` (then `-t %name` finds it), `whoami`, `set-work-mode` (`normal`, `shell` or `ai`), `send-message` (`--to` a pane, `--re` a message, `--task`), `read-message`, `list-messages`, `trace-message` (`-J`: as JSON), `drop-message`, `move-message`, `pane-ready`, `pane-status`, `list-tasks`, `show-task` (`-J`), `list-events`, `create-pane`, `mcp` (MCP for an agent in a pane), `setup` (`setup` alone lists the agents and what each lacks; `setup claude`, `codex`, `gemini`, `cursor` or `opencode` with `--install` adds the hook that says when the agent is free, and keepane's MCP server). Claude Code's, Codex's and pi's transcripts say it too, about 3 seconds late at the most, with no hook. A message to an `ai` pane is pasted, and its Enter comes on its own once the paste has settled. What a pane is up to is `#{pane_state}`: idle, busy, typing (keys with no Enter), no-agent (an `ai` pane back at its shell), unknown (a shell whose prompt hook was never heard), exited, or `-`. ### Resuming after a reboot Sessions save themselves to the data directory. `resume`, `list-saved`, `save-session`, `restore-session`, `delete-saved`, `set-cwd`. ### Options, keys and scripting `set-option` (`set`), `set-window-option` (`setw`), `show-options` (`show`), `show-window-options`, `bind-key` (`bind`), `unbind-key` (`unbind`), `list-keys`, `source-file`, `set-hook`, `show-hooks`, `set-environment` (every pane: a new one at its start, a shell already open at its next prompt; kept across restarts; `-u` takes a name out of new panes even when the server was started with it), `show-environment` (attached: in colour, with what keepane sets and the names taken away), `run-shell`, `if-shell`, `wait-for`, `wait-output` (block until a pane prints one of some texts, and print that line; `-a` counts what it already holds, `-i` any case, `-w` a time limit), `command-prompt`, `confirm-before`, `display-message`, `display-menu`, `display-popup`, `refresh-client`, `load-plugin`, `list-plugins`, `list-commands`, `notify` (a desktop notification). ### The program itself `version` (this keepane's and the server's), `update` (a newer keepane, the way this one was installed; from Scoop, which skips an app while any of its programs runs, a copy outside Scoop's folder saves the sessions, stops the server, runs Scoop and starts the server again with them, and says what it did in `update.log`), `restart-server` (sessions move to a server of this version), `import-config` (bring what keepane can use from a tmux config), `completion` (a Tab completer for PowerShell, bash, zsh or fish), `man` (this page), `web` (the panes on a phone), `link` (panes on other machines), `show-keys` (what the terminal sends for each key), `migrate` (from wmux, keepane's old name), `startup` (`on`, `off`, `status`: start the server at logon, every saved session restored: on Windows a `Run` value, on macOS a launchd agent in `~/Library/LaunchAgents`, on Linux a systemd user unit enabled for `default.target`, with the `PATH`, `SHELL` and locale it ran with). On Windows: `windows-terminal` (a Windows Terminal profile). Both run keepane by its full path (on macOS and Linux the name the `PATH` finds, such as Homebrew's `bin/keepane`); when that is an older keepane or one that is gone (an update into a folder of its own, as Scoop with `no_junction` or Homebrew's Cellar does), the next keepane to start takes them over and says so. One run from cargo's target folder, or under test directories, never does. ### The panes on a phone `keepane web [--port N] [--bind IP[,IP...]] [--read-only] [--keep-key]` has the server serve a page for a phone on the local network, in the background, and prints a QR code carrying its address and a key. Without `--bind` it listens on the address this machine reaches the network through and on its Tailscale addresses; with it, on each address given (more than one card: commas, or `--bind` again), and an address this machine does not have is an error. The code is for the first; `web status` lists the others. `keepane web status` (the server command `web-status`) says how it serves and who is connected; `keepane web stop` (`web-stop`) ends it, cutting off the phones on it. The server command `web-start` takes the same flags as `-p`, `-b`, `-r`, `-k` (`-p 0`: any free port). It stops with the server; a `web-start -k` line in the config has it on whenever the server runs, with the same code each time. From `web-start` to `web-stop` the server keeps how it is served (the file `web` in its link directory): when the sessions are restored (`__server --restore`, which `keepane startup` registers, or `restore-on-start on`) it is served the same way again, and `web-resume` (which `restart-server` sends) does the same at once; what cannot be served yet (an address not up so soon after logon) is tried again every 10 seconds, `web-status` saying why meanwhile. Without `--bind`, this machine's addresses are looked at again every 10 seconds while it serves: it is served on those that came and no longer on those gone. `#{web_url}` is its address (empty while it is off) and `#{web_clients}` how many are on it; the default status line shows `web` and that number while it serves, and a phone connecting, or a wrong key, is said on the status line. The page's Fit to this screen (in the pane's view menu) sizes the pane shown to the phone (`web-fit`): `web-fit -t pane -x cols -y rows` zooms the pane in its window and gives its session the phone's columns and rows, so a full-screen program draws for the phone. A session has one size, so meanwhile the computer and any other phone see that session at the phone's size too (the page and the status line say so). It ends with `web-fit -t pane -u` (the button again, another pane, the list, the phone put away), when no phone has shown the pane for 10 seconds, when `keepane web` stops, or when the session is sized by hand (`resize-window`); the session gets its size back. The page's list shown as a grid asks for each pane's last lines with it (`/api/panes?tail=4`, 1 to 8), which the server gives as `web-tails` (with N): `{"%3": [...]}`, the last N lines each pane printed, oldest first, prompts passed over as `#{pane_last_line}` passes them: a keepane command of the page's, not for people. `keepane share PATH [--for TIME] [--once]` (the server command `share`) gives out a file or a folder by a link on the phone page's address and port, `http://host:port/s/TOKEN`, and prints it with a QR code: one link a machine address, the code for the first. The path is the command's (relative to where it ran, `~` home). The token is 128 random bits of its own, not the page's key; whoever has the link can download that one thing. `--for` takes `90s`, `30m`, `2h`, `7d` (365d at most), `0` for no end; 24 hours when not given. `--once` ends it after one whole download. A file is sent as it is, with one range of it on request (`Range`, 206; past its end, 416); a folder as a zip written while it is read (STORED, ZIP64 when needed, UTF-8 names under `NAME/`), links in it and what cannot be read left out (a file that stops reading part of the way, cut there). Every download is `application/octet-stream` with `Content-Disposition: attachment`, and every answer carries `Content-Security-Policy: sandbox`: nothing is shown as a page on the page's address. A link that ended, was stopped or never was answers 404; a download under way goes on. With the page off, `share` serves it, as it was last served (else as `keepane web` does), for as long as the server runs (not kept for a restore). `keepane share` (or `-l`) lists what is shared; `share stop ID` or `share stop --all` ends them. Each share, the first download from each address and each whole download are said on the status line and kept in `show-messages`. The page asks the server `web-share-open` (a token and the address asking) for what a link shares, and tells it `web-share-sent` (the same, and a note) when all of it went: keepane's own commands, not for people. ### Panes on other machines `send-message --to host:port/$1:@3.%7` (or `host:port/%name`) reaches a pane of the keepane server on another machine, over the port `keepane web` serves on there, once the two servers are paired; the answer comes back the same way (`send-message -r`). Both machines need `keepane web` running. Pairing is like SSH keys: each server has a key pair of its own, and a table of the machines it lets in. `keepane link add
` (`link-add`), given the address `keepane web` prints on the other machine (the one the phone scans, key included), puts each server's key in the other's table, once; from then on every request is signed, and the web key plays no part. `keepane link trust [--shell]` (`link-trust`) lets a machine in by hand, by the key `keepane link id` (`link-id`) prints there. `keepane link list` (`link-list`) shows the machines paired; `keepane link panes ` (`link-panes`) their panes (address, name, mode, idle or busy, inbox, program, then session name, window index, window active, pane index, pane active, window name); `keepane link info ` (`link-info`) the machine: its name, system, keepane version, uptime, CPU, memory, panes; `keepane link capture [-S lines] ` (`link-capture`) what one of its panes shows, as text, which that machine must allow (`--screen`, below); `trace-message` of a message sent there asks that machine what became of it (queued, delivered, done or failed, a shell command's output; `-w` waits there). `keepane link remove ` (`link-remove`) unpairs; `keepane link rekey` (`link-rekey`) makes a new key, after which every pairing has to be made again. A paired machine's messages go to `ai` and `normal` panes only. To let them run as commands in `shell` panes here: `keepane link allow --shell` (`link-allow`; `--no-shell` takes it back); to let it read what the panes here show: `--screen` (`--no-screen`); to let it start panes here: `--panes` (`--no-panes`); to let it send commits into repositories here: `--push` (`--no-push`); to let it send projects and their agents' chats here: `--agents` (`--no-agents`). All are given from a terminal outside keepane or the `C-b :` prompt, never from inside a pane (`link-trust` takes `--shell`, `--screen`, `--panes` and `--push` too). `keepane link start [-s session] [-n name] [-m mode] [-c dir] [-- program args]` (`link-start`) starts a pane on a machine that allowed this one to: one of its `agent-commands` (with any arguments, so a shell on that list makes `--panes` as much as `--shell`), within its `agent-pane-limit` counted for this machine alone, in a session named after this machine unless `-s` names one; its address there comes back. `keepane link kill ` (`link-kill`) closes a pane this machine started there, and no other (that machine knows which only while its server runs). A message for a `shell` pane from a machine not allowed is refused, and the sender told how to allow it. A message from another machine reads `from=host:port/…` in its envelope. A machine is known by its key, not its address: when it turns up from a new address (another network, Tailscale), its entry follows. The two machines' clocks must agree to within 2 minutes. When the other machine cannot be reached the message fails at once; nothing is queued to go later. `-w` waits on the other machine, and a message still queued there when it runs out is a time-out here. Pairing and unpairing are said on the status line and kept in the event log, as are refused requests, once a minute for each address. `keepane web` listens on this machine's network address and on its Tailscale addresses, so either network works; `web-status` lists them. `link-inbound` is how `keepane web` hands a request from another machine to the server, which holds the keys. `keepane clone [path] [--tell pane]` (`clone`, `link-clone`) sends the repository the command runs in to a paired machine that allowed this one to (`--push`, above), as a new repository at `path` there: nothing may be there yet, or an empty directory. The path is that machine's: `~` is its home directory, and a relative path, or none, is under its `link-clone-dir` (default `~/keepane`), with none the name of this repository's directory. The current branch is checked out there, and its remote is named after this machine. `keepane push [ [path]] [--tell pane]` (`push`, `link-push`) sends the commits of the current branch that machine lacks to where the repository was cloned or pushed before (this machine keeps where, by that machine's key, so a new address changes nothing); with no machine, to the one it went to (named when it went to more than one); with a path, to a repository already there, which must share a first commit with this one (or have none yet), kept from then on. There the commits go to `/`; the branch itself moves on when that loses nothing of that side's: a fast-forward, its files too when it is checked out and no tracked file is changed. Otherwise it is left as it was and the answer says why (commits of its own there, files changed), for that side to merge. Only committed work goes; files not committed here are counted in the answer. `--tell pane` sends that pane there a message once the commits are in; the status line there says so either way, and the event log keeps it. `push -l` lists where this machine's repositories go. The commits go as a git bundle, in pieces over the link, checked whole (SHA-256) before git takes them; `link-push-limit` (default 200M) is the most a bundle may be, here when it is made and there as it arrives. Both machines need git (`git-command`). Git LFS contents are not sent (their pointer files are); a shallow clone is refused (its early history is not there to send: `git fetch --unshallow` first). `keepane send-agents [] [dir] [-s chat-id]... [-n]` (`send-agents`, `link-send-agents`; docs/design/agent-sync.md) sends the project the client is in (its repository, or `dir`) with its agents' chats to a machine that allowed this one to (`--agents`, above). A repository's commits go as `push` sends them, and with them its files that git lists as changed or new (ignored ones stay); another folder goes whole, without `node_modules`, `target` and the like. The chats are those Claude Code, Codex and pi keep for the folder, their `cwd` set to the folder there, so `claude --resume`, `codex resume` and `pi --session` go on with them there. The first time it lands in `/from//` there; each side keeps where (`send-agents -l` lists it), so sending again goes to the same folder, a folder sent back goes back where it came from, and the machine can be left out. Sent again, only what changed goes: a chat the other side has the start of goes as its new end. Nothing of the other side's is lost: a file changed there since the last send is kept, and the one sent put beside it as `.from-`; a chat that went on there too comes as a new chat. `-s` sends only the chats whose id starts so; `-n` says what would go and sends nothing. It goes as one gzip stream, in pieces, checked whole; `link-agents-limit` (default 1G) is the most it may be, here and there. ## KEYS Keys are pressed after the prefix, `C-b` (`set -g prefix C-a` changes it). `C-b ?` lists every binding. A key not pressed within half a second of the prefix brings up a panel of what the keys do, by group, read from the bindings as they are; the key then works as ever (`prefix-hint off` hides it, `prefix-hint-delay` sets the wait in milliseconds). A key an input method gives in its full-width form (`【`, `:`, `y`) is read as the ASCII key wherever keepane reads keys itself; what is typed into a pane or a text prompt stays as typed. The defaults: | Key | Does | |---|---| | `c` | new window | | `%` / `"` | split side by side / one above the other | | `h` `j` `k` `l`, arrows | go to the pane that way (repeatable) | | `H` `J` `K` `L`, `M-`arrows | resize the pane by 5 (repeatable) | | `z` | zoom the pane in and out | | `x` / `&` | kill the pane / the window (asks first) | | `u` | bring back what was killed a moment ago | | `0` to `9`, `n`, `p`, `Tab` | window 0 to 9, next, previous, last | | `s` / `w` | pick a session / a window | | `d` | detach | | `[` / `]` | copy mode / paste | | `/` | what panes printed, by day | | `v` | the dashboard | | `B` | every pane at a glance (jobs) | | `S` | send what is typed to every pane of the window | | `C-t` | each command's time at the end of its line | | `e` | the environment every pane gets, in colour | | `Y` | pick a command any pane ran: go there, copy its output, run it again | | `,` / `$` | rename the window / the session | | `:` | the command prompt (Up / Down, `C-p` / `C-n`: the last 100 lines run there; `show-prompt-history` lists them, `clear-prompt-history` forgets them) | | `~` | recent messages | | `?` | list the keys (output longer than the window opens at its end and scrolls: Up / Down, `j` / `k`, PgUp / PgDn, `g` / `G`, the wheel; `/` `?` search it, `n` `N` go on; any other key closes it) | `F12`, without the prefix, turns `passthrough` on and off for the pane: while it is on, every key typed there goes to its program, the prefix and the root table's keys too, for a keepane running inside the pane (over ssh). The status line says `passthrough`. Other panes keep the prefix. `C-b C-b` sends a single `C-b` on without it. ## CONFIGURATION At start the server reads `~/.keepane.conf` (or `~/.config/keepane/keepane.conf`, or the file `KEEPANE_CONFIG` names): one command per line, as tmux's config is written. set -g prefix C-a set -g mouse on bind | split-window -h source-file ~/.keepane/themes/plain.conf A tmux config is not read on its own: `keepane import-config` brings over what keepane can use from `~/.tmux.conf` (or any config), once, and writes the rest commented out with why. The default look is Paper Dark, ink on dark paper with one accent in cinnabar; the themes in the repository's `themes/` directory (`plain.conf` is tmux's look) are ordinary config files to source. Four are built in: `set -g theme paper` switches to the light one, `paper-dark` back; `tokyo-night` (the default up to 0.37, the panes in the terminal's own colours) and its light `tokyo-day`. All but `tokyo-night` draw the panes with `window-style` and `pane-colours`, whatever the terminal's colours. The lines between panes are heavy (`┃` `━`); `set -g pane-border-lines single` draws tmux's thin ones, `double` or `simple` (ASCII) the others. `set -g pane-frames on` puts each pane in a rounded frame of its own instead (`╭` `╮` `╰` `╯`), the active one's in the active border colour, the pane's `pane-border-format` text on its top (or its bottom with `pane-border-status bottom`); the frame takes a column and a row on each side, so `resize-pane -x`/`-y` size the pane with its frame. `show-options -g` lists every option and its value. Once a day the server asks GitHub for the latest release's version (one request with `curl`, nothing about you in it); a newer one shows on the status line (`#{keepane_update}`) and in `show-messages`. `set -g update-check off` stops it. In a git repository the status line and the clock show, after the branch, what git last said is there: `main ●3 ↑2 ↓1 !1` (files changed, commits ahead of and behind the upstream, files in conflict; the formats `git_head`, `git_changed`, `git_ahead`, `git_behind`, `git_conflicts`, `git_marks`). In an `ai` pane, a message's end and the agent's turn carry what the work did in the repository since it began: the commits made, the files changed with their lines, the new files (under `git:` in `trace-message`, `git` in its JSON and in `list-done -J`, `KEEPANE_DONE_GIT` for the `pane-done` hook). git runs on a thread of its own, at most 10 seconds a run, without the index's lock, and without the file watcher, external diff, text conversion or pager a repository's config may name; `git-command` names the git to run (by default the one on the PATH, else where Git for Windows, GitHub Desktop or Homebrew put it). Without git, the branch and the commit ids are read from the repository's files and the rest is left out. `clock-mode` (prefix t) draws the time in big digits and under it the date (and UTC), other places' time (`clock-mode-zones`: IANA names, commas between them, 8 at most; a name in any case, or a city alone when one zone ends with it, as `tokyo`; each shown by its city, its weekday too when its day is not this one), the pane (its program, folder and branch, how long it has run and been quiet), its last command (how long it took, how it ended), its agent (model, cost, context), the machine (CPU, memory, battery, time since boot) and keepane (its sessions and panes, the messages it has delivered since it started); then, when all of them are shown and it fits whole, the month (Monday first, the weekend red, today reversed in the hours' colour; an animation under the clock keeps 10 rows before it). `clock-mode-info off` leaves the time alone, as tmux draws it; `clock-mode-style 12` counts the hours to 12; `clock-mode-colour` colours all the digits. Unset, the hours take the active pane border's colour and, with a theme that draws the panes (paper, paper-dark, tokyo-day), the minutes are yellow and the colon faint. Under that, in the rows left over, something moves with the machine and with keepane (`clock-mode-animation`). `pulse` is a monitor: a heartbeat at 60 beats a minute with the cores idle, 150 with them all busy, once early for each message keepane delivers (at most four waiting); a network wave as tall as the traffic is heavy; a disk wave that breathes faster and deeper the more the disks do; a system wave that swings faster with the context switches and taller with the interrupts. Beside the heart: the CPU package's watts, the temperature, a battery's watts while the machine runs on it (`PWR`), and the messages delivered while the clock is up (`✉`); beside the others, packets a second, processes and threads. `matrix` is falling characters: faster with the cores, more columns with memory, cyan ones with the network, quicker flicker with the disks, stray magenta characters with the interrupts, and a white drop headed by `✉` for each message delivered. `bars` is an equalizer: a bar for each core, memory, the disks read and written, the network in and out, the temperature, and a bar for each pane of the window by what it prints; then, where they fit beside those, messages, the CPU's watts, the battery's, swap, and context switches with interrupts. `fire` is a flame for each core, as tall as the core is busy, its tip whiter the hotter the CPU, sparks with the interrupts. `starfield` is a flight through stars, more with the processes, faster with the network. `sharks` is a sea seen from above: a school of fish for each pane (six at most), faster the more it prints, turning more with the context switches; a shark hunting them, hungry sooner with the cores, longer with memory; all of it drifting with the network, glints with the disks. `bonsai` is a tree growing through the day, a limb for each pane (six at most) shaking with its output, its leaves stirring quicker with the context switches, more leaves with memory, leaning in the network's wind, a leaf falling now and then, more with the disks. `sea` is a moonlit sea: the moon crossing the sky with the hours, stars by night with memory, waves with the cores, the moon's path glittering with the network, a boat for each pane faster the more it prints. `cat` is a cat asleep, breathing with the cores, a `z` with each breath, its tail with the network, an ear with the disks, a ball of yarn rolling faster the more the panes print. `dino` is a dinosaur running a desert, faster with the cores, jumping every cactus, more cacti the more panes, clouds with the network, its score the distance. `penguins` is an ice shelf, a penguin for each pane waddling on its own stretch, sliding on its belly while its pane prints a lot, the sea with the cores, snow with the network, an aurora with memory. `synthwave` is a neon grid running towards you, faster with the cores, under a striped sun whose stripes slide with the panes' output, mountains taller with memory, stars with the network. `fireworks` is a city at night, its windows lit with memory, rockets going up more often the more the panes print, bursting bigger the busier the cores. In each of these 10 a message delivered shows as an `✉` (rising, a comet, in a ripple the shark races to, from a leaf, drifting ashore, falling beside the cat, which wakes, falling to the dinosaur, on a fish leaping onto the ice, a meteor, a rocket twice the size). In `pulse`, `matrix` and `starfield`, and in the shark's eyes and the bonsai's leaves, 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 13 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 (`aquarium`, `life`, `donut`, `weather`, `hourglass`, `orbits` and `radar`, taken out in 0.46, give `random`). 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. `clock-mode-look` draws any of them in a set of characters instead of its own: `dots`, `blocks` or `binary`, each cell the character with as much ink as it had (how much of the cell its own character covered, how bright it was). The colours stay, and so do the figures, the names and each message's `✉`. `plain`, the default, keeps its own characters. It is drawn in the 16 colours a theme sets with `pane-colours` (the terminal's own colours when the theme sets none), so each theme colours it. The machine is read twice a second, and only while a clock is on screen. Not every figure can be read everywhere: macOS gives no interrupts and no power, and counts the context switches and threads of the processes you may look into, not the whole system's; a figure the system does not give is left out, never made up. The temperature comes from a real CPU sensor only: on Linux the thermal zones and hwmon sensors; on Windows from LibreHardwareMonitor or OpenHardwareMonitor while one runs (Windows has none of its own: its ACPI thermal zone is a fixed figure on many machines); not on macOS. The CPU's power is its own meter (RAPL): Windows reads it for any user where the CPU has one; Linux (`/sys/class/powercap`) lets only root read it on most systems, so there it shows only when readable. While a pane runs an agent (Claude Code, Codex, pi), the server reads the transcript the agent writes and gives its figures to `list-agents`, the dashboard, the phone page and the formats `agent`, `agent_model`, `agent_cost`, `agent_tokens`, `agent_context`, `agent_cpu`, `agent_mem`, `agent_tools` and `agent_turns`. Cost prices the tokens by LiteLLM's public list, fetched with `curl` once a day (an hour later after a failed try) while an agent runs (pi says its own); these are API list prices, not what a subscription is billed. A cost with `+` after it left out a model the list does not have. `set -g agent-cost off` stops the fetch and leaves cost out. The machines `choose-ssh` and `ssh-hosts` list come from four places: an `ssh` running in a pane (each new one counts a use, with its time; `#{pane_ssh_host}` says where it goes), the `Host` lines of `~/.ssh/config` (not patterns), the names in `~/.ssh/known_hosts` (not hashed ones), and, once, the `ssh` commands in the shells' history files. `ssh-hosts.json` in the data directory (in an `ssh` folder of the saved sessions when `sessions-dir` or `KEEPANE_SESSIONS_DIR` moves them) keeps the used ones: names, users, ports, times, counts, and the names `d` took off. No password or key is read or kept. ## ENVIRONMENT `KEEPANE` : The socket name of the server a pane belongs to, set in every pane. `KEEPANE_PANE` : The pane's id (the *N* of `%`*N*), set in every pane. `KEEPANE_CONFIG` : The config file to read instead of `~/.keepane.conf`. `KEEPANE_SESSIONS_DIR` : Where sessions are saved, instead of the data directory's `sessions`. `KEEPANE_TMPDIR` : Where the servers' sockets go, instead of `$XDG_RUNTIME_DIR` (or `/tmp`). `KEEPANE_NO_UPDATE_CHECK` : Set, the server never asks whether a newer keepane is out. `KEEPANE_PRICE_LIST` : A price list (LiteLLM's form) to price the agents' tokens by, instead of the one fetched; nothing is fetched. `CLAUDE_CONFIG_DIR`, `CODEX_HOME` : Where Claude Code and Codex keep their transcripts, as the agents themselves read them (the server's own environment). `COLORTERM`, `TERM_PROGRAM` : A terminal that says `TERM_PROGRAM=Apple_Terminal` without `COLORTERM=truecolor` gets the nearest of the 256 colours for every 24-bit one. ## FILES `~/.keepane.conf` : The config. `~/.local/share/keepane/` (Linux), `~/Library/Application Support/keepane/` (macOS), `%LOCALAPPDATA%\keepane\` (Windows) : The data directory: logs, saved sessions, what panes printed, the event log, `prices.json` (the agents' price list, as last fetched), `web.key` (the phone's code kept by `keepane web --keep-key`), and `link/`*socket*`/`: a server's key pair (`key`, readable by its user alone) and the machines it lets in (`authorized`: a public key, an address, and `shell` and `screen` for what they may do, one machine a line). Beside the saved sessions, `environment/`*socket*`.json` keeps `set-environment`'s table, and `.ps1`, `.sh` and `.fish` beside it its changes for the shells' prompt hook. `shell/` holds the start files that give bash, zsh, fish and a `wsl.exe` pane's shell keepane's hook. `$XDG_RUNTIME_DIR/keepane-`*uid*`/`*socket-name* (or under `/tmp`) : A server's socket; only its user can open the directory. ## EXAMPLES Start a session named *work* in the background, split it, and attach: keepane new -d -s work keepane split-window -h -t work keepane attach -t work Name a pane and send it a command to run: keepane rename-pane -t work:0.1 build keepane set-work-mode -t %build shell keepane send-message --to %build -w 60 "cargo test" ## SEE ALSO tmux(1). keepane's README and design notes: