rook

module
v0.58.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2026 License: MIT

README

rook

rook is what you run after starting your terminal, in place of tmux: a Go front door over a multiplexer rook owns — ptys and ghostty's terminal emulator in one Zig process, behind a server that outlives the glass.

The bet. The multiplexer is solved; what is not solved is the layer above it: N agents in N sessions, each producing turns, and no way to route attention across them. That layer is the product. Rook draws it, publishes everything it knows, and holds one opinion about what an agent is: a program by name. Everything an agent is doing is a producer's word, pushed in from outside — vera is the first producer and must not be the last. docs/surfaces.md is that seam, in full.

What this is not. Not a terminal emulator, not an editor. Ghostty and neovim are tenants. tmux is the reference implementation the conformance corpus is diffed against, and nothing else.

Run it.

make install     # go build ./cmd/rook → ~/.local/bin/rook, the engine → ~/.local/libexec/rook/engine
rook             # attach; boots the server when none is listening

One command. The engine is off $PATH; rook execs it for every mux verb and keeps worktrees, the picker and the web URL in Go. mux/README.md is the engine's own manual: the keys, the rail, the state feed, the build. The prefix comes from ~/.config/rook/rook.toml ([tmux] prefix = " "", C-b` when unset).

The config is live. rook.toml has one reader, the front door: rook config check says whether it is good (and why not), rook config json is the engine's half compiled — what the engine boots on — and rook reload hands a running server the file as it is now. rookd does that for you: save the file and it reloads, or, if the file does not load, the calm bar says why and the config that was running stays. Keys, colours, glyphs, the bar, the rail and home's colour change on the spot; [home] windows seed the next home; restore and startup wait for the next boot.

Workspaces, windows, panes. rook ls / new / switch / close / pick (rook new -q <name> <dir> -- claude is a space born running a program); prefix-c a window, prefix-v / prefix-- a split, prefix-hjkl focus (a bare ctrl-hjkl too, vim-navigator style, with mux/nvim handing edge moves back), prefix-z zoom, prefix-[ copy mode, prefix-P pin a pane to the rail, prefix-u the oldest thing you have not read. Those are defaults: every key after the prefix is a row in [keys] (v = "split-right", x = "" unbinds), and rook binds nothing that runs a program. A picker, worktrees, an agent — you float them yourself: s = "popup rook pick", g = "popup 72x86@124x48 grim" (rook popup is the same from a prompt). The verbs are listed in mux/src/keys.zig. The tabs are clickable: a chip is its window, the + a new one. A tab is named once — the first program in it that was not the shell, or rook rename — and rook never renames it; the actor that claimed a pane in it rides after the name, deploy · main ◐; the tool is the bar's word, never the tab's.

Style. The chrome is a stylesheet: [style] everywhere, then [[style.match]] rules on the workspace on the glass — home, workspace, dir, repo (the origin, github.com/grafana/*), branch, program — each setting any of the theme's colours, the chip's and tabs' caps (round, powerline, …), the separator, a fill pattern for the bars, and the chip's and calm bar's words as templates (label = "{REPO}:{branch}"). [[style.tab]] colours tabs one by one — by name, program or class, filled when selected and toned down when not (a docker tab blue, a mongo tab green). Rules also match classes — names anyone puts on a workspace or pane (rook class api +error --ttl 5m, or a program's own OSC 1337 SetUserVar=rook_class) — and rook's own states (unread, working, zoomed, …): an error detector or a loop watchdog is a script that sets a class, never something rook knows about. A later rule wins, property by property; rook's only rule of its own is home's look. rook style explains it — the facts, which rules held, what won and from where. Stylesheets can live in files of their own — rices — and be included (include = ["~/.config/rook/rices/*.toml"], at the top); rices/ in this repository holds eleven to start from. docs/style.md.

The frame. One tab bar across the top — the space's name in the scope slot, then its tabs — the work at full width under it, and one calm bar across the bottom. No sidebar: the legacy spaces/agents panel is off unless [mux] sidebar_mode = "open" asks for it.

Home. Plain rook lands at home: a workspace like any other — panes, windows, splits — that lives outside the list of spaces. prefix-o goes home from any space and back to the one you came from; C-o never lands on it, and rook ls and the picker leave it out. With nothing configured it is one shell in ~, a scratch pad: check something, close it, and you are back where you were. Closing its last pane goes back to that space, and home starts over the next time ([home] on_empty = "stay" starts it over in place). It is never saved; every boot seeds it fresh. What it holds is yours:

[home]
dir = "~"
[[home.window]]
name = "me"
panes = ["docket", ""]          # commands, side by side; "" is a shell
[[home.window]]
name = "notes"
dir = "~/notes"
[[home.window.pane]]
command = "nvim scratch.md"
[[home.window.pane]]
split = "down"

A pane's command is typed into its shell, so a program that quits leaves the shell behind. rook . and rook --space <name> land in a space outright; startup = "last-space" makes plain rook do that too. docs/home.md is the model; scripts/home-fixture.py drives it end to end.

The calm bar. One row at the bottom: who holds the focused pane's keyboard on the left — you ▸ nvim, or claude·main ▸ owns input · you observe once an agent has claimed it with rook own — and the signals on the right (◐ 2 · !1 · •3 · ⊕g 1). Typing at an owned pane opens a gate instead of landing: request a handoff, take now, or send the keys as a message. prefix-i inspects the pane. [mux] bar = false turns the row off.

The rail (legacy, off by default). A left panel of spaces over agents, a dot and two lines each, behind [mux] sidebar_mode = "open". Its model still feeds the state feed: rook lists its own workspaces and the panes it can see running an agent; a producer pushes the rest, one JSON frame per line:

rook side demo | rook side -     # the herdr design, as frames
my-producer    | rook side -     # the real thing (vera's verad does this)

What the programs say. Every pane's bell, desktop notification (OSC 9 / 99 / 777), title, working directory (OSC 7) and progress bar (OSC 9;4) is heard and published. A bell rings the glass; a notification reaches it as OSC 777, so the terminal that can reach your desktop does. A signal that arrives while nobody is looking at its pane puts the pane on the unread channel — a dot on its tab and on the rail — until you look. prefix-u and rook jump go to the oldest one. Rook publishes the words; it never reads them for meaning. docs/attention.md.

The state feed. Everything rook knows, as one JSON snapshot, so anything can hold an exact replica and never has to ask:

rook state          # the snapshot
rook watch          # the snapshot, then one line per change
rook companion      # where vera is open in rook, if she is; exit 1 when not

A pane, by id — for the agent inside one. $ROOK_MUX_PANE is the id of the pane a program runs in, and . names it:

rook split . --cwd "$PWD"        # a shell beside you; focus stays put
rook run 7 'go test ./...'       # type it, with Enter
rook wait 7 --match 'ok  ' --timeout 120000
rook read 7 -n 120               # its last 120 lines, history included
rook key 7 ctrl-c
rook skill --install             # the whole manual, for an agent, where Claude Code loads it

Coming back. The server saves its workspaces, windows and cwds, and restores them on boot ([mux] restore = false to opt out). A pane whose program told rook how to bring it back comes back running:

rook resume . "claude --resume $SESSION_ID"   # the program's own word, kept while it is in front

For Claude Code that is one SessionStart hook, and claude-plugin/ carries it (/plugin marketplace add <repo>/claude-plugin, then /plugin install rook@incantery); by hand it is:

{"hooks": {"SessionStart": [{"hooks": [{"type": "command",
  "command": "rook resume . \"claude --resume $(jq -r .session_id)\""}]}]}}

Outside rook the hook is silent and exits 0. After rook kill && rook — or a crash, or a reboot — every Claude pane reopens its own conversation, typed into the rebuilt shell as you would have typed it. When Claude has quit, the pane is a shell again and comes back as one.

Worktrees. One agent, one branch, one checkout, one workspace — and a lifecycle that ends with all of them gone. The model is grove's, with rook as the place a worktree is worked in; prefix-w floats grove's manager, and the verbs are plain commands from any checkout — grove at a prompt does the same without rook:

rook worktree ls              # the rows, once; --json for machines
rook worktree new agent-a     # ../<repo>--agent-a on branch agent-a, workspace opened:
                              # the local branch, origin's (tracked) if origin has it,
                              # else a fresh one off main; --fetch asks origin first
rook worktree merge agent-a   # merge into main, then remove worktree + workspace + branch
rook worktree rm agent-a      # refuses dirty or unmerged; --force to discard

Files git doesn't carry but a checkout needs are conventions in rook.toml (this person's, for every repo) or grove.toml at the repo root (the repo's own), copied or linked from the main checkout into every new worktree:

[worktree]
copy = [".env"]
link = ["node_modules"]

The companion. One resident may be named in the config, and rook knows it by sight — none unless named — so "is it already open, and where" is a question rook answers:

[companion]
command = "vera"        # or program = "vera"; program = "" turns the slot off

A second glass. web/ is the browser as a peer client on the same wire; rookd supervises the server and runs the bridge, rook url prints the address for a phone.

Pulling things back. The previous rook — the Zig app with an editor in it, the plugin vocabulary, the environments graph — is intact on the pre-tmux branch, and the tmux-era rook on rook/tmux. Take a file when it earns its place, not by default:

git checkout pre-tmux -- docs/plugins/VOCABULARY.md

Directories

Path Synopsis
cmd
rook command
Command rook is the front door to the rook multiplexer.
Command rook is the front door to the rook multiplexer.
rookd command
rookd keeps rook alive: it supervises the mux server (adopting one that already runs — rookd is a nanny, not an owner: the server is spawned into its own session and survives rookd restarts) and runs the web bridge in-process.
rookd keeps rook alive: it supervises the mux server (adopting one that already runs — rookd is a nanny, not an owner: the server is spawned into its own session and survives rookd restarts) and runs the web bridge in-process.
internal
config
Package config loads rook's user configuration from ~/.config/rook/rook.toml (XDG_CONFIG_HOME respected).
Package config loads rook's user configuration from ~/.config/rook/rook.toml (XDG_CONFIG_HOME respected).
mux
Package mux is the Go layer's client for the Zig engine.
Package mux is the Go layer's client for the Zig engine.
namer
Package namer gives tabs better names than the first program that ran in them.
Package namer gives tabs better names than the first program that ran in them.
picker
Package picker is the workspace picker prefix-s floats in a popup: fzf over the workspaces, where enter switches to the row under the cursor and ctrl-o creates the one you typed.
Package picker is the workspace picker prefix-s floats in a popup: fzf over the workspaces, where enter switches to the row under the cursor and ctrl-o creates the one you typed.
webd
Package webd bridges browsers to the engine socket and serves the web client.
Package webd bridges browsers to the engine socket and serves the web client.
sdk
provider module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL