Skip to content

Command reference

Every subcommand, and what it takes. tmux-companion --help prints the same list, and tmux-companion <command> --help the flags.

Command Does
server Bind the socket and serve until killed. Started automatically by any client that finds nothing listening, so you rarely type it.
shutdown Stop the daemon, and only that. tmux and its sessions are not touched; the next client starts a new daemon. sessions shutdown is the one that stops tmux
restart Stop the daemon and start a fresh one. This is how a change to config.toml takes effect, since the file is read once at startup and held for the daemon’s whole life. tmux is not touched; sessions restart is the one that restarts it

A client that finds a daemon from an older build replaces it and says so; one that finds a newer build leaves it alone and says so once. A development build run beside the installed one wants its own socket, in TMUX_COMPANION_SOCK, rather than the two taking turns replacing each other’s daemon.

Each one prints tmux markup on stdout and exits.

Command Arguments
status-right [PATH] The whole right-hand side in one call: git, bandwidth and battery, computed concurrently, plus the agents count and the health mark when [status.right] lists them. --style is fill, outline or outline-bright and anything else is refused with that list; --branch-max-len N middle-ellipsizes a branch name longer than N, and without the flag [git] branch_max_len decides, 20 out of the box; --branch-icon, --force, --ttl SECS
gst [PATH] [PANE_PID] Git status on its own. Same flags, plus --no-cap, --no-daemon and --no-tmux

A daemon error exits the command 1 with the error on stderr. tmux ignores the exit status of a #(), so the bar sees nothing different; a prompt or a script can tell a failed segment from an empty one. | battery | Percentage and icon | | net | Bandwidth since the previous call. --no-daemon, --no-tmux | | clients SESSION_ATTACHED WINDOW_ACTIVE_CLIENTS | How many other clients are attached | | sh-jobs PANE_PID | Jobs stopped or running under a pane, per [sh_jobs] | | window -i INDEX [flags] | One window’s status. Driven by tmux format strings: -c current, -n name, -w path, -p process, -s start path, -f flags, -P pane count, -A pane index | | preview | Sample segments in every style, locally, with no daemon |

vim-bg PANE_PID still works and is sh-jobs under its old name, and close-project still works and is project close. Both print a line saying so, and go away after one release.

Both print a string and neither needs tmux to be running, so they work in a shell prompt, in a bar that takes a command, or in a script. --no-tmux writes ANSI escapes instead of tmux’s #[fg=...] markup and resets the terminal at the end, and a segment that drew nothing prints nothing at all, not even a newline.

Terminal window
PS1='$(tmux-companion gst --no-daemon --no-tmux) $ '

--no-daemon computes the answer in that one process and exits, with no socket opened and no server started. What it costs is the cache: every call pays for a cold git status, which was 51 ms against a large tree, where the daemon answers a warm one in well under a millisecond. That’s the right trade for a prompt you press enter on and the wrong one for a bar redrawing once a second, so inside tmux leave both flags off.

net is a rate and needs two counter readings, so with no daemon holding the first one it goes in $XDG_STATE_HOME/tmux-companion/net-sample. The first call after a reboot records the reading and draws nothing, which is exactly what the daemon does on its own first call.

The two flags are independent. --no-tmux on its own still asks the daemon and is the cheap way to put a segment in a prompt on a machine where tmux is running anyway, and --no-daemon on its own prints tmux markup for a #() in a config on a machine where you would rather not have a resident process.

Command Does
config path Print which config file is being read
config check [PATH] Parse it, report what’s wrong, exit nonzero if it is
config dump Print every setting with its default, as a config file
config init Write a short starter config where config path would read it, creating the directory, and print the path. It sets the glyph preset, the directory source and the snapshot timer, and carries the editor-beside-an-agent layout as a comment to uncomment. A file already there is refused unless --force

Every other client-side command reads the config too, and one that does not parse is used as the defaults with one line on stderr saying so, once per process, pointing at config check. The daemon is stricter and refuses to start on it.

| Command | Does | | ———————–– | —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————–– | —————————————————————————————————————————————————————————————————————————————————————————————————–– | | keys | Searchable key bindings. Enter runs the binding, ctrl-a widens past the opening query to tmux’s own, esc cancels. --all opens with no query and adds the prefix and root bindings that have no note, shown by their command, --query TEXT sets a different one from the default companion: , --refresh rebuilds from tmux rather than using what the daemon holds, and --print lists the rows instead of opening the picker. --unused narrows to the bindings you wrote that the usage log has never seen pressed, with a line saying how many presses the log holds; not with --all | | keys discover | What tmux’s root table and the apps in its panes bind, saved to keys-discovered.json under the state dir. nvim is asked live over its socket, or started headless when none is open; [keys.app.<name>] claims adds the rest. claude, fzf, vim, nano and zsh are read from their own config, over shipped defaults for claude and fzf. --layer tmux|nvim|vim|claude|fzf|nano|zsh reads one layer, --print also lists the rows. Runs in the client | | keys collide | Keys tmux’s root table and an app both bind, with each app’s modes, then keys a layer binds to something other than [keys.key.*] says (drift), then the Ctrl, Alt and F keys nobody binds. Reads the last discovery, discovering first when there is none or with --refresh; --json prints the report | | keys route | Wraps tmux’s root Ctrl, Alt and F bindings so a key the app in front claims is held for [keys] hold_ms: a double press goes to the app, anything else lets tmux have it. The last line of tmux.conf. Off unless [keys] route = true, when it puts everything back; needs tmux 3.4. Silent on success; --print says what it did, --dry-run prints the script and applies nothing | | keys claim | Sets @kc_claim, @kc_app (--app NAME) and @kc_owner (--owner, the pane’s current command) on $TMUX_PANE or -t PANE, for a hook or a wrapper | | keys release | Unsets the three on $TMUX_PANE or -t PANE | | cheatsheet | The bindings you keep looking up, in four boxes: three for your companion: bindings, one for tmux’s own and plugin bindings you’ve looked up. Recent lookups are marked ▸ at the top of their box; one not looked up for [usage] learned_after_days has been learned and drops off. Needs [usage] enabled; off, it lists your bindings unranked. Any key closes it. --print writes box state count table key shown note lines instead (--plain is the old spelling and still works) | | click RANGE | What a mouse click on a segment of the right side does. agents and health carry a range=user | NAMEmark like tmux’s window list, sobind -T root MouseDown1StatusRight run-shell "tmux-companion click '#{mouse_status_range}'"reaches this with the name; it runs[[status.right.segments]] on_click for that segment or the default: the inbox for the agent count, the brief for the health mark | | brief | What needs you, on one screen: the agents waiting and what each asked, the health reasons, the sessions idle for three days, tmux-companion’s last three messages, and one line of numbers with the last snapshot’s age. In a terminal it takes keys: a number goes to that agent or idle session, c and a number closes an idle session, h acknowledges health, d shows doctor, i and j hand over to the inbox and today’s journal, s saves a snapshot, z toggles an hour of quiet, q closes; in screen-reader mode the same keys are a typed answer. --print prints and exits; --hook is for client-attached and opens the popup only when something is waiting or wrong | | agent busy\|asked\|done | What an agent is doing, said by the agent itself from one of its hooks: busy when a prompt was sent or a tool ran, asked when it stopped on a question or a permission prompt, done when its answer is complete. The pane is $TMUX_PANE unless --pane ID says otherwise. The last word per pane wins over the window’s quiet time on the bar, in the inbox, in panes and in the brief, for as long as the pane runs one of [agents] programs. Prints nothing and exits 0 whatever happens, since it runs inside the agent’s hook | | agent clear | Forget what the pane last said, so the window’s quiet time decides again: for an agent restarted without its hooks in a pane still carrying the old one’s last word | | agent hooks [PROGRAM] | Print the hooks block for an agent’s settings file. claude, the only one so far: UserPromptSubmit and PostToolUse say busy, Stop says done, a permission prompt or an AskUserQuestion says asked; merge it into ~/.claude/settings.json. An agent with no hooks that rings the terminal bell reads as asked until its window is visited | | inbox | The agents waiting on you, longest wait first, each with the question it asked: the daemon captures an agent’s last screen lines the moment it stops, or the moment its hook says so, so the question is on record for a window nobody has looked at. The state column is asked 3m for a hook or the bell, done 3m for a finished answer, which is listed to be read and not coloured, or waiting 3m for silence alone. Enter jumps there; --print writes where, program, state and wait, question and pane id as TSV. [agents] inbox turns the capture off, nudge_after_secs says it out loud | | panes | Every pane on the server: session:window.pane, what it runs (with the pane’s title after a dash when a program set one), busy, asked 3m, done 3m or waiting 3m for an agent (the first three when it said so through agent or rang the bell) and active or idle 3m for anything else, reading in copy mode, and the directory, with the last lines of its screen in the preview. Enter jumps there; the pane you are in is left out. Quiet is the window’s activity time, since tmux keeps none per pane. --agents keeps only [agents] programs, -t SESSION one session, and --print writes the rows as tab-separated columns with the pane id last and exits | | earcon EVENT | Play the sound asked, done, health or chunk makes, on or off, and say which command ran. With [earcons] enabled the daemon plays them itself, held by quiet hours | | chunk [status\|snooze\|reset\|close\|budget D] | The chunk clock: focus time this sitting across every session, in words (38 min, 12 min for break), --json for every number; snooze once past budget, reset, close, budget 45m for this sitting. Needs [chunk] enabled. | | jump | Type a few characters of anything on screen, then the label beside it: a borderless popup over the window greys every pane, lights up the matches nearest the cursor first and labels each with a letter that can’t continue the search, so typing on narrows it and typing a label jumps. The pane is selected and put in copy mode with the cursor on the match. Smartcase; enter takes the nearest, escape or backspace on an empty search closes it. --pane ID and --client NAME come from the binding. flash.nvim’s motion; replaces tmux-jump | | search [PATTERN] | Every line of every pane’s scrollback, newest first: session:window.pane, what the pane runs and the line, with the five lines either side of it in the preview. Enter switches the client to the pane, puts it in copy mode scrolled to the line and selects the line. PATTERN keeps the lines it matches, a regular expression in either case unless it holds a capital, and -F takes it as text; --kind url\|path\|sha\|ip is a stored search in place of one. -t SESSION and --pane ID narrow where it looks, --lines N is how far back each pane is read (1000), --print writes where, pane id, line number and line as tab-separated columns, and --first goes to the newest match without the list. A line one pane drew twice is one row. The stored searches are tmux-copycat’s, not pushed since May 2023; tmux-fuzzback is the fuzzy list over one pane and the one to install without this | | ports | The TCP ports something is listening on: port and protocol, * for every interface or localhost or the address, the program and its pid, the pane that started it as session:window.pane, and that pane’s directory, with the pane’s screen in the preview. Enter switches the client to the pane. A port belongs to the pane whose own process is an ancestor of the listener. Only ports a pane started are listed unless --all; -t SESSION keeps one session’s; --udp adds UDP sockets that are bound and not connected; --print writes port, protocol, address, program, pid, where and pane id as tab-separated columns. --kill makes enter stop the program, TERM and then KILL after --grace SECS (3), signalling the process that holds the socket and not its group. Reads lsof, or ss where there is no lsof | | start [DIR] | The way in from a shell that is not in tmux yet: the project picker, then attach. --last goes back to the session used most recently without asking; DIR skips the picker. Inside tmux it switches rather than attaching, so it is the same thing as project. --hook is for the client-attached hook in tmux.conf: it opens the picker only when the session is one tmux named itself, all digits with one window, one pane and a shell in it | | project [DIR] | Switch to a project, or build its session from [[layout]]. With no argument it lists live sessions newest first, then the directories [project] dirs_source knows — zoxide by default, or z, cdr, ghq, a dirs_command, or none at all; --print lists and exits, opens nothing, and cannot be combined with a directory. A live session nobody is attached to that has been quiet for a day or more carries an idle 5d column, so the stale ones show without leaving the list | | project save | Capture this session’s windows and panes as this project’s layout. All or nothing: a line tmux cannot answer for leaves the saved layout untouched. --no-commands keeps the shape and leaves every pane a shell | | project forget [DIR] | Delete this project’s saved layout, so the config decides again. The project is the session this runs in, or DIR | | project show [DIR] | Which layout this project gets, which file decided, and the windows it opens: a saved layout, then the checkout’s .tmux-companion.toml, then [[project.override]], then [project] layout. A saved or checkout file that is there and not used is named with the reason, and a checkout file whose commands were blanked for want of [project] trusted says so | | project close [SESSION] | Close this project by letting every window exit, capturing the layout on the way out. An editor (nvim, vim, vi, hx) is asked to quit first and the close stops with it on screen when it will not; --discard quits editors with :qa!, --no-save leaves the saved layout alone. A session that is not there is an error. close-project is the old name and works for one release |

keys and cheatsheet list the bindings whose -N note starts with companion: , which is what the shipped configs write. When no binding carries the note, both print one hint on stderr pointing at docs/tmux.conf.starter.example and exit 0 rather than drawing nothing. Inside tmux and without --print the hint also goes to the tmux message line, so a popup that closes doesn’t take it with it. A note still written custom: is read as companion: for one release.

A saved layout wins over [[layout]], because somebody pressed a key to make it and the config is what they had before they did. project show is the way to find out which one is in force without opening a session to see.

Run project save from a binding rather than by typing it into a pane. Typed, the pane it runs in is running tmux-companion at the moment it looks, so that is the command it records for that pane.

A directory whose name is save, forget or show has to be written as a path, project ./save, because a bare one reads as the subcommand.

The picker runs in this process rather than in the daemon, because a daemon has no terminal.

Command Does
run [--pane ID] Pick a command from history and run it in a pane beside this one. Enter runs the pick, alt-enter runs exactly what you typed, --print lists and exits. --pane says which pane it belongs beside, for a caller that knows it. The binding passes nothing and the attached client’s session decides, because the picker is a popup: a popup is not a client, an untargeted split lands in whichever session the server touched last, and tmux does not expand #{pane_id} in a display-popup command anyway
note [TEXT] Leave a one-line note on a pane, as tmux’s pane title, which panes shows beside the program and the pane border draws when pane-border-status is on. No text prints the note that is there; --clear takes it off; a snapshot carries it and a restore puts it back; --pane ID picks the pane, defaulting to the one the key was pressed in
journal What happened in each project, newest first: a command that ran past [journal] min_secs with how long it took, an agent that stopped and what it asked, an agent that worked a turn past agent_min_secs and said it was done, a project opened or closed. Enter goes to that session; -t SESSION keeps one, --days N reaches back, --print writes TSV
quiet [DURATION] Quiet hours: for 45m, 2h or a bare number of minutes, [notify] announces nothing, the inbox nudges nobody and the agents segment leaves the bar, with the health mark saying quiet in its place. Nothing given says how long is left; --off ends it
toggle [SESSION] Move to the next window in this session by index, wrapping at the end. --last flips to the window the session was on before instead, tmux’s own last-window, which is what a toggle means once there are more than two; a trailing WINDOW is accepted and ignored
autosave Deprecated: the [autosave] script timer, which [sessions] autosave replaces. --once runs the script now, --status says when it last ran, and with no flag it reports like --status
sessions save Capture every session on the server as a new generation. All or nothing, like project save. --skip-pane-history leaves out what was on each pane’s screen; --exclude a,b adds to [sessions] exclude
sessions export [FILE] A generation as one file that reads on another machine: paths under home spelled ~, the project colours bundled, screens left out. The newest unless --stamp; printed with no file
sessions import FILE Store a file from export here as a new generation with this home in place of ~, adding the project colours this machine has not set; --no-map leaves the map alone. Then sessions resurrect
sessions resurrect [STAMP] Rebuild a server from a generation, newest by default, falling back to tmux-resurrect’s own newest save when there is no generation of ours. Refuses a server that already holds sessions; --merge adds only what is missing. --dry-run prints the exact tmux commands, --only/--exclude pick, --yes runs everything the table claimed without asking, --detach leaves the server running. A snapshot from a newer build is read with one stderr line saying the restore may miss what that build knew. Exit codes 0/2/3/4/1
sessions autosave The snapshot timer the daemon runs. --once takes one now, the same one the timer takes; --status, or no flag at all, says when the last snapshot was, what the timer is set to, and whether the last daemon stopped cleanly
sessions shutdown Save every session, then stop the tmux server. --exclude a,b is not “leave these alone” — the server takes every session with it either way, so an excluded one does not come back, and the command says which before it acts. The daemon keeps running unless --daemon-too. --dry-run says what it would do
sessions restart The same, then bring the server back with what it had. Stops the daemon by default so config.toml is reread; --keep-daemon turns that off. With no server running it says to use sessions resurrect
sessions list Every generation, newest first, with what each holds and whether it was taken at shutdown or while running. --json for a script
sessions show [STAMP] What one generation holds, down to each pane’s directory and command, defaulting to the newest. --json prints the snapshot itself
sessions idle Every live session with no client attached and nothing happening in it for longer than --days N (3 by default), most idle first: name, directory, idle 5d, windows. Enter runs project close on the pick, layout capture included; --print writes the rows as TSV and exits. None is one stderr line and exit 0; inside tmux and without --print that line also goes to the tmux message line, as it does for inbox, ports, panes, journal and search

The autosave loop itself runs in the daemon, so there’s nothing to start and nothing to keep from starting twice.

sessions shutdown and sessions restart refuse to run from inside tmux, and there is no flag for it: stopping the server would take the pane they were typed into, and nothing after that would run. Refusals, and “no tmux server running”, go to stderr; stdout carries only what the command did.

project and sessions answer different questions and neither replaces the other. A project’s layout is a catalogue entry: one file, overwritten when you press the key, edited by hand, kept for good. A snapshot is a moment: one file per capture, kept in generations, dropped oldest first. A session with no project to be keyed on, a scratch session you named yourself, exists only in the second.

When a restore does not know what a pane should run and somebody is watching, it shows the list and counts down before going ahead — never on a count of panes, only on what it is unsure of. [sessions] confirm_secs is the countdown, zero never draws it, and any key stops the clock. A pane left unapproved still opens in the right directory, at a prompt.

What a restore is allowed to run lives in [[restore.program]], and it is default deny: match is a regular expression against the whole saved command, command is what runs with {command} and {cwd} filled in, and run = false refuses a program outright. A command no row claims is shown and left alone, because restoring is executing what your own machine recorded weeks ago.

Pane history is a directory of one file per pane beside the snapshot rather than an archive, because this crate has no tar or gzip dependency and fourteen panes come to 50 KB uncompressed against 16.7 KB gzipped. Twenty generations is a megabyte either way. Both the snapshot and the history are written 0600 under a 0700 directory, since the history is the text that was on your screen.

Command Does
theme pick Choose a theme and apply it to the session an attached client is on. -r SESSION remembers it for a session by name instead, one that need not exist yet; -t TARGET applies it somewhere specific, --print lists and exits
theme apply SESSION Apply the theme that session should have, from the project map or the namespace rules. This is what the session-created hook calls. -t TARGET applies it somewhere other than the session
theme apply --all Repaint every session with its own theme, which is what a tmux.conf reload needs: sourcing the file resets the global options a theme sets
theme init Write six starter colours and the two files that apply them, into the themes directory this machine’s tmux actually reads. Overwrites nothing
theme add --bg C Write a theme from one colour. --fg chooses the text colour instead of computing it, --name names the file instead of the colour naming it, and a pair under AA is refused unless --force
theme list-colours Every colour tmux takes, painted, with its hex and the contrast its text colour clears. --print is one name per line with no swatch, for piping (--plain is the old spelling and still works)
theme gen Report which themes need a different text colour or a more visible border
theme gen --apply Write @theme-color-on-main and @theme-color-border into each theme file
theme gen --shades [LEVEL] Also write a theme for every cube colour whose text clears a contrast floor, named after the bundled one each sits nearest to. aa 216, aaa 151 (the default), a4 105, a5 75, a6 the bundled six and their siblings, 18

--themes DIR says where the files are, and --background '#rrggbb' gives the terminal background to measure borders against. Without it, ghostty +show-config is asked and the xterm default of colour232 stands in when ghostty isn’t there.

Reporting is the default and writing takes a flag, because a command that rewrites 76 files on a bare invocation is one people run once by accident.

Command Does
open [TEXT…] Open a URL or a file:line:col found in text. --cursor-x picks whatever is under that column, which is how the copy-mode binding needs nothing selected; --pane says which pane it is for; -s scans the tmux selection, -d DIR resolves a relative path against DIR rather than the pane’s directory, -i (--choose) asks which [[open.application]] opens it, -n (--dry-run) prints what it would open
new-window Pick a directory and open a window there, from the same source as project. The query starts on the pane’s own directory, so the key then enter is “another window here”; any path can be typed in full, listed or not. Both the directory it starts on and the session the window lands in come from the attached client, not from tmux’s current session, which inside a popup is whichever one the server touched last --print writes here, the short path and the full one as tab-separated columns and exits.
shell-init [SHELL] Print the shell code that emits the OSC 133 prompt marks, for zsh, bash or fish. Defaults to $SHELL
clipboard Copy to the system clipboard, picking the command for the platform. --stdin reads standard input rather than the tmux buffer
pocket [NAME] A shell you pull out beside this pane on the first press, is parked in a window called _pocket on the second, and comes back with its process and scrollback on the third. NAME tells pockets apart and defaults to shell; --pane ID is the pane the key was pressed in, which the binding passes. The width is [run]’s; unlike run’s pane it doesn’t slide, since a shell drawn one column wide garbles its own redraws. toggle cycles past _pocket, and project save leaves it and any pocket that is out off the layout
promote [NAME] Give a pane a session of its own, process and scrollback kept. The session is named from the pane’s directory the way project names one, or NAME. A session of that name that is already there is joined as a window; a pane that is all its session holds renames the session; a pane already in that session is refused. The client follows the pane. --pane ID is the pane the key was pressed in. tmux-sessionist had this on prefix @ and was last pushed in May 2023
kill [--pane ID] [--ask] Stop what runs in front in a pane: TERM to the foreground process group of the pane’s terminal, the one C-c goes to, and KILL if it is still running after --grace SECS (3). A job in the background is left alone. Prints how it ended and exits 1 when the program outlived KILL. A pane with only its own shell in front is refused, since kill-pane is the key for that. --ask looks first: a shell at its prompt is refused with no question, and anything else gets tmux’s confirmation naming the program and its pid. tmux-cowboy sent KILL straight away and was last pushed in May 2021
zen [--pane ID] Clear everything but this pane: a zoom when there are other panes, the status bar when there are not. --pane says which, and the binding passes it. zoom is the old name, kept one release
Command Does
doctor The binary and its build, the daemon and its build, the socket with its mode and owner, the config in use, the glyph preset, the state directory, the daemon log’s last line, both autosave timers, how many setup items are open, the tmux version and the platform. It ends on tmux’s own options that cost something as they are set, escape-time, history-limit, display-time, status-interval, focus-events, default-terminal, set-clipboard, monitor-bell and mouse, each with its value, what that costs and the line for tmux.conf; it sets nothing. tmux-sensible set these as a plugin and was last pushed in April 2024
health Why the health mark is up, one reason a line, or ok. A timer or a segment that failed is a reason for an hour, or until the same timer runs and works. The sessions timer finding no tmux server, or one with no session in it, is not a failure
health ack Forget the failures, so the mark comes down now, and print each one forgotten. A config.toml edited after the daemon started, a newer binary, quiet hours and a network that’s gone are not failures and stay, listed as still on the mark
probe keys Show what the terminal sends for a key. -n COUNT stops after that many
probe cells [STRING…] Ask how many cells the terminal advances for a string, or for a built-in set
setup What the tool offers and the state of each: on, open, off-by-choice, skipped or cant-tell. Enter copies the lines and asks before adding them to tmux.conf or config.toml; ctrl-x skips; ctrl-e sets a binding’s key. --print prints id, group, state, key and line, tab-separated

Ask for doctor output on any bug report. It reads without starting or replacing anything. A daemon from another build, one that does not answer, and a config file edited after the daemon started each end their line with run tmux-companion restart, because that is the answer to all three.