butai docs 1.3.0

Keys#

Every action in the workbench has a keyboard shortcut. This page is the whole list; the in-app reference (?, or [help] in the footer) is the same material split by subject, and the footer under each list is the short version that fits.

The rule#

Nothing is reachable by pointer alone, and nothing is bound that cannot be found.

Two halves, and both are enforced rather than intended:

  • Every click target has a key. every_click_target_has_a_key in crates/butai-client/src/workbench.rs is a match over hit::Target with no catch-all, so a new clickable thing does not compile until someone has said which key reaches it. The assertions read the real tables, not a list of letters, so a key that moves fails the test rather than leaving a stale comment behind.
  • Every key is in a table. crates/butai-client/src/verbs.rs holds one table per surface, and it drives four things at once: the footer text, the click hit-test, the key dispatch and the ? reference. Binding a key without listing it is not possible — dispatch reads VerbId.

A verb that does not fit the footer is marked quiet: still bound, still in the reference, just not competing for a column. That is the difference between not shown here and undiscoverable, and it is the answer to "why isn't m written under the rail?" — the PROCESSES footer is t new · r restart · x kill, 26 columns in 26.

Two gestures are the pointer's alone, deliberately: dragging to select text and the wheel. Neither stands for a verb, so neither has a key.

The two layers#

The same set on the same letters, reaching the workbench from anywhere.

  • Alt works from inside a running program — that is what it is for. An Alt key the workbench does not bind falls through, so alt-b and alt-f still move by words in readline.
  • The prefix (C-b by default, prefix under [general]) is for terminals that eat Alt. Press it twice to send a literal one through.

Spaces and workspaces#

Alt prefix
files alt-o, alt-e C-b o, C-b e alt-e does not toggle back
docs alt-m C-b m a project's own markdown
docker alt-c C-b c alt-d is detach, so containers take c
git alt-r C-b r the repository: its refs, its history, its working tree
work the space key again C-b w each space key toggles back
walk the spaces alt-, alt-. C-b , C-b .
the spaces menu alt-space C-b space every space with its badge — what the tab bar's own control opens
BOOTH alt-0 — a peer of the workspaces, not a space
settings alt-s — (C-b S in the browser) entered and left, not cycled
workspace by number alt-1..alt-9 C-b 1..C-b 9
walk the tab bar alt-< alt-> C-b [ C-b ] spans every machine
open a workspace alt-n C-b n bare n too, off the stage
close this one alt-x C-b X asks first
machines alt-h C-b H connect one, or disconnect one

The rails#

Alt prefix bare
AGENTS alt-a C-b A
PROCESSES alt-p C-b P
CHANGES alt-g C-b G
the fleet (BOOTH) alt-w C-b W x and m too — see below
the stage — C-b s enter
off the stage alt-esc
cycle tab
move, open j k, enter

alt-g is the CHANGES rail, not the git space — alt-r is. They are the two most easily confused things here, so they do not share a letter. Both show the working tree's files and both stage them with the same letters; the rail is the one that sits beside the agents doing the changing and owns the commit box, and the space is the one with the history and the diff beside it.

What fills them#

Alt prefix bare
spawn the pinned agent a
choose an agent alt-enter C-b a A
a new shell alt-t C-b t t (PROCESSES)
restart r (PROCESSES)
kill the row x
kill what is staged C-b x
the row's menu m
the system monitor C-b S click a gauge
the gpu monitor C-b Y click a gauge
scroll the stage C-b PgUp/PgDn the wheel

m is the right-click menu opened from the keyboard: on a rail it is that row's, and off one it is the workspace's own. It is the only route to close others and close all agents — two actions that live nowhere else in the interface. It also carries disconnect host on a remote tab, which is the one row of it that has a second way in: alt-h lists the machines you are connected to, and choosing one drops it.

x, m, a and A also answer on BOOTH's fleet, and there they act on the row's own machine and project rather than on the tab you are looking at — the one list in the workbench where those are routinely not the same thing.

The SYSTEM gauges are the one part of the left rail the cursor cannot walk, so their monitor is keyed rather than entered: C-b S is htop, C-b Y is whichever GPU monitor the machine has (nvtop, nvidia-smi, rocm-smi, radeontop).

The rest of the workbench#

Alt prefix bare
zen — collapse the rails alt-z C-b z
layout — resize them alt-l C-b l
find alt-/ C-b / /
links — the URLs on screen C-b f f
the git menu C-b g g
branches C-b b b
paste an image alt-v C-b v
this reference C-b ? ?
the command prompt C-b :
detach alt-d C-b d q

Bare keys need the cursor off the stage. On it, every key is the program's — which is what makes it a terminal and not a preview.

Keys that belong to one surface#

Each of these comes from that surface's verb table, and the footer under it names the ones that fit.

CHANGES — the verbs follow the selected row:

row keys
unstaged file s stage · x discard · d diff
staged file u unstage · d diff
conflicted file o ours · t theirs · a resolved · d diff
a commit d show
always c commit · C stage all and commit · p push · g git menu · r refresh · ? keys

The diff — ] [ hunks · space stage the hunk · v line-select (space picks, enter applies) · x discard · z fold the file · Z fold them all · r refresh · q close. On a staged diff the same keys unstage; a commit's diff is read-only and offers none of them.

The same widget draws every diff — the DIFF space, the GIT page's body, a commit, a stash — so these keys mean the same thing wherever you meet one. It draws one card per file (its path, whether it was added, deleted or renamed, and its +n -m) over the lines, each numbered on the side it exists in: a removed line has no number on the new side, an added one none on the old. On a body too narrow for the numbers they are dropped and the @@ headers come back, which is the same bargain the commit graph makes with its lanes.

GIT — tab walks the three columns. What acts depends on the row.

row keys
working tree enter diff the whole worktree · C go to the CHANGES rail
unstaged file enter diff · s stage · x discard
staged file enter diff · u unstage
conflicted file o ours · t theirs · a resolved · enter diff
a branch enter scope · c checkout · m merge · d delete
a tag enter scope · x delete
a stash enter show · p pop · x drop
a remote f fetch
a worktree enter open · x remove
a commit enter diff · y copy the sha · v revert · p cherry-pick
always esc widen the scope back · g the menu · r refresh · ? keys

The file rows are the CHANGES rail's rows, under the rail's headings, answering the rail's letters — one gesture for "stage this file" wherever you meet it. enter opens the file's diff in the body beside the lists rather than taking over the DIFF space, so you keep the refs and the history you opened it from, and the diff keys above work there. The rail is unchanged and still owns the commit box and the sync buttons, which is what C goes to.

BOOTH — j k walk the rows · enter go there: an agent's workspace on its machine, or the project the cursor names · a start that project's agent · A pick which · x end what the row is: the session on an agent, the workspace and everything in it on a project (which asks) · z fold this machine or project · Z fold every project · tab the preview · m the row's menu.

Ten keys, and every one of them is a key this workbench already had: a and A are the AGENTS rail's, and z/Z are the DIFF page's, marks (v open, > folded) and all. It used to be six, and the reason given was that the rest of the rails' table is about a project and this page is not in one — which stopped being true when its rows became projects.

Clicking an agent in FLEET selects its preview and gives the stage keyboard focus. Clicking a machine/project keeps focus on the fleet; double-click its name within 400ms to fold/unfold it. The chevron and z also fold/unfold. There is no separate NEEDS YOU tray; each chat carries its status in FLEET.

FILES / DOCS — j k move · ← h up a level · → l into · space peek · enter open · backspace up · / find · e edit · C-s save · esc stop editing · x delete · q close.

The browser is a trail of columns — every directory on the way to where you are, side by side — so ← and → walk it and the columns to the right of the cursor are kept: ← then → asks the daemon nothing. From inside the file, ← hands the keyboard back to the browser.

space and enter both read the file the cursor is on, and differ only in where they leave the keyboard: enter gives it to the file, space keeps it in the browser so the next j walks to the next name and shows you that one. That is what makes space a peek rather than a second enter.

x deletes the file the cursor is on, and it asks first — the box names the path and opens on "no", as discarding does. It is the one key here that git cannot undo: an unstaged file is not in the index and a never-committed one is not anywhere, so there is nothing to restore from. Directories are refused; the key does nothing on one.

DOCKER — enter follow the logs · r restart · x stop · s a shell in the container · q close.

SETTINGS — j k rows · tab groups · enter open a list, choose, or carry out an action row · space toggle · - + resize a rail · 0 back to automatic · r re-read MACHINES · esc leave. enter does more here than anywhere else because the MACHINES section holds rows that act rather than hold a value — connect, disconnect, forget, update — and an action has no value a toggle could report while an ssh is still landing.

Overlays — j k move · enter choose · esc or q dismiss · y / n answer a confirmation. In the agent picker, d pins the highlighted agent as what a spawns. In the link picker, y copies the highlighted URL instead of opening it — which is the one that works from an ssh session with no browser on it, and what enter falls back to there.

Links have no Alt key, unlike everything else in the table above. alt-f is readline's forward-word and butai leaves it — with alt-b and alt-y — to the pane, so a shell inside butai edits its line the way it does everywhere else. f bare and C-b f reach the picker instead, which is the pairing g (the git menu) and b (branches) already have.

Changing them#

Every key is a name in one mini-language, shared by [keys], the : prompt and the command palette — so anything the prompt can say can go on a key, including the things the shipped table does not bind.

[keys] is the prefix layer. An entry names the key you press after the prefix, and it overrides that one row of the table above — the Alt layer is built in and not reconfigurable. So o = "space files" binds C-b o, and M-y binds C-b alt-y, which is a real binding but rarely the one you meant.

[general]
prefix = "C-a"

[keys]
o = "space files"                     # C-a o
F5 = "process build cargo build"      # C-a F5

The vocabulary:

space work|files|docker|docs|git|booth|next|prev|menu
workspace 1..9|next|prev|new|close
focus agents|processes|changes|fleet|stage
agent [NAME]            spawn one, or pick from the list
agent-default [NAME]    pin what `a` spawns; bare unpins
process NAME COMMAND    start a process
terminal                a new shell
monitor [gpu]           the machine's own monitor
host                    add a machine
branch                  the branch picker
update                  check for a newer butai, and offer it
find                    search the workspace
layout                  resize the rails
zoom                    zen
git-menu
close-pane              kill what is staged
paste-image
help
detach
reload-config
kill-server [clear]

An entry that does not parse is a warning on the SETTINGS page, not a refusal to start — and that page also reports how many keys are bound and how many of them came from your config, which is the question you have when a key does something you did not expect.

In the browser#

The browser client keeps its verb tables in web/src/logic/verbs.ts and keyboard dispatch in web/src/logic/keys.ts. Its HELP page renders the browser's own reference; use that page for the actions available on the current surface.

The C-b prefix offers an alternative when a browser reserves an Alt shortcut, such as Alt plus a digit for switching browser tabs. On a Mac the browser can read KeyboardEvent.code, so Option combinations that compose characters in a terminal can still be recognized.

When the live pane has focus, ordinary typing goes to that pane. Use alt-w or alt-esc to return to the fleet or rail. The browser's page components live under web/src/pages/; the terminal and browser share the daemon API but have separate renderers and keyboard handlers.

On a Mac#

Option is a compose key by default: pressing Option-o types ø, and no terminal reports Alt at all. butai reads those characters back, so Option-o is alt-o and nothing needs configuring. Only the keys the Alt layer binds are read this way — ∫ (Option-b) is still a character you can type.

Option-e and Option-n are dead keys and emit nothing until the next keystroke, so they cannot be recovered: use C-b n to open a workspace, and alt-o for files. To type the punctuation instead, set option_as_alt = false under [general] and use the prefix layer — or set the terminal to send a real Alt, which is better than either:

Terminal.app    Settings › Profiles › Keyboard › Use Option as Meta Key
iTerm2          Profiles › Keys › Left Option Key › Esc+
Ghostty         macos-option-as-alt = true
kitty           macos_option_as_alt = yes

Inside tmux, keep xterm-keys on so it passes Alt through rather than eating it.

    ↑↓ move ↵ open esc close