Guide.

Everything conn does, in the order you'll meet it.

§ 1

Install

curl -fsSL https://conn.w0zro.com/install.sh | sh

conn needs two neighbors: tmux, which holds the shells, and lsof, which reads the process list. macOS ships lsof; add tmux with brew install tmux. On Linux both come from the package manager:

apt install tmux lsof # or dnf, pacman, apk…

The script fetches the release built for this machine, checks it against the release's checksums.txt, and puts the binary in ~/.local/bin and the manpage under ~/.local/share/man; man conn is the reference. Three variables override the script:

CONN_INSTALL_DIRinstall somewhere else
CONN_MAN_DIRthe manpage somewhere else
CONN_VERSIONa release other than the latest

Building from source works too, with Go 1.25 or later:

go install github.com/w0zro/conn@latest
§ 2

First run

conn

There is nothing to set up. conn brings up a tmux server of its own, attaches the terminal to it, and opens the navigator down the left of the session's first window; the shell under its cursor is the pane beside it. The status line at the foot is tmux's, with the mode the keys are in. conn looks for repositories under ~/projects; the config points it elsewhere.

The list opens showing what is running, so on a quiet machine it may say nothing running. Two keys from there:

.show every repository, running or not
/find one by name
§ 4

Shells

s opens a shell at the selected place, beside the navigator, and puts the keys in it. enter on a shell's row does the same for that shell. From then on every keystroke is the shell's: the pane is tmux's, and it behaves the way a tmux pane behaves, with the navigator still down the left. conn keeps one key: ctrl-space, the prefix — tmux's own, bound by conn's configuration — which reaches conn from any shell. ctrl-space - is back to the navigator; ctrl-space j and k are the next and previous shell, ctrl-space ctrl-space switches between the navigator and the shell, ctrl-space q leaves, and ? — in the navigator, or after the prefix from any shell — lists the rest in a popup over the whole window. Every key tmux would otherwise bind is unbound.

The pane beside the navigator is the shell under its cursor: move the cursor to another shell's row and that shell moves in, the last one out to a tmux window of its own, where it keeps running. J and K step through the shells in the list's order, keys and all. On a row with no shell the navigator has the whole window.

The status line at the foot is tmux's. It begins with conn's own name, CONN, in purple, which stays; to its right is the mode the keys are in — NAV or PROC for the pane that has them, each a chip in its own color with the line washed one tone after it, PREFIX while a chord hangs, COPY in copy mode, the query while one is typed and the filter while one stands, CONTINUE with the picker open, CONFIRM while a kill waits on its second key — and then anything the navigator has to say: a report, an error, a confirmation, until the next key.

Shells keep running when the terminal closes. q in the navigator, or ctrl-space q anywhere, detaches; quit the terminal, come back, run conn, and everything is where it was.

ctrl-space v reads back through what has scrolled away: tmux's copy mode, with vi keys. v marks, y copies the marked text to the system clipboard, q leaves. Each shell keeps 10,000 lines of transcript by default — scrollback in the config raises it.

The mouse is tmux's, with tmux's own bindings and nothing of conn's: the wheel scrolls back through a pane's transcript, a drag selects and copies to the clipboard on release, a click takes the keys to the pane under it, and a program that speaks mouse gets its own clicks and wheel. Hold shift to drag a selection the terminal's own way. A program that copies for itself — vim's clipboard yank, anything speaking OSC 52 — reaches the system clipboard through tmux.

§ 5

Agents

a starts an agent in the selected place, in a shell like any other — Claude Code, unless the config's agent names another kind. Claude Code reports its state, so the navigator can mark it:

turning — an instance is working
blocked mid-turn on something specific: a permission prompt, a question
finished its turn, waiting for what you ask next
idle since it started

The mark tells you, from across the list, which projects need attention — the ◆ most of all, since answering it resumes work already underway. The detail pane for a Claude row names what a blocked instance is waiting on, the last thing asked of it, and what it says it is doing.

ctrl-space enter goes to the next agent waiting on you, from any shell. Pressing it again continues around the waiting ones in turn; tab does the same at the list.

A conversation doesn't end when its instance exits; it's suspended, and the transcript is enough to pick it back up. A on a place lists what's suspended there — sub-projects included — newest first: when each conversation last moved, its branch, and the last thing you asked of it. Letters narrow the list the way the filter does, enter continues the one under the cursor in a shell opened where the conversation was had, and esc closes the list. A conversation an instance is still carrying isn't offered. Every kind that keeps conversations answers the picker, and each is continued by the kind that had it.

Other kinds show as plain processes, unmarked. Ollama runs the first model it lists, unless agentRuns names one.

§ 6

Plans

Working on a project usually means the same processes every time: the dev server, the test watcher, an agent. A project writes them down in .conn — Procfile format, a name and a command per line:

.conn
dev:   npm run dev
api:   go run ./cmd/api
agent: claude

Without one, conn reads the conventions in this order: a Procfile means every line is a process; a package.json contributes only its dev or start script, whichever exists first.

r starts the plan's entries that aren't running, each in its own window named for its entry. It is a list to run, not a supervisor: nothing restarts on its own. A process that has exited leaves the list, and the pane's checklist shows its entry as down until you press r again.

§ 7

Killing

xkill the selected process
Xkill it and everything under it
x on a repo or groupkill everything running in it

Every kill asks first — x, y or enter confirms, anything else cancels. Processes get SIGTERM, and a signalled row wears a red marker until a rescan actually observes the exit; the list never claims an exit it hasn't seen. Kills work on any listed process, including ones conn didn't start.

§ 8

Configuration

One JSON file, entirely optional. It lives at ~/.config/conn/config.json (XDG_CONFIG_HOME is honored).

~/.config/conn/config.json
{
  "projectsDirs": ["~/projects", "~/work/mono"],
  "scrollback": 50000,
  "navWidth": 34,
  "skipDirs": ["bazel-out", "dist"]
}
FIELDDEFAULT
projectsDir$HOME/projectsthe directory searched for repositories; ~ and $VARS expand
projectsDirsseveral roots; when set, projectsDir is ignored. A root that only exists on another machine is fine
scrollback10000transcript lines each shell keeps. Written into the server's configuration at launch, so raising it takes a fresh server: R, then conn again, or conn restart
navWidth30the navigator column's width, 16–60
themedarkthe side tmux draws for, dark or light; the navigator draws with the terminal's own sixteen colors
agentclaudethe kind of agent a starts: claude or ollama
agentRunswhat starting a kind runs, by name — {"ollama": "ollama run mistral"}
skipDirsdirectory names the scan never enters, on top of the built-in list
§ 9

The server

The shells live in a tmux server of conn's own, one per user for all projects, on a socket at ~/.local/state/conn/tmux-<uid>.sock (CONN_SOCKET overrides, and XDG_STATE_HOME moves the directory). Every conn writes the server's configuration — the prefix and its chords, the status line, the transcript cap — to ~/.local/state/conn/tmux.conf, brings the server up under it or sources it into one already running, and attaches. No .tmux.conf reaches it. The session's first window is the home window: the navigator in the pane down its left, at the config's navWidth through every resize, and the shell under its cursor beside it; the shells not shown wait in windows of their own. Close the navigator and ctrl-space - makes it again, on the left. A conn from a newer build than the navigator's restarts the navigator in its pane, the way it re-reads the configuration: the server keeps the shells, and everything else is the build's. Run from a shell inside the window, it does that and no more, the terminal being attached already; inside any other tmux it refuses, since attaching would be nesting.

The chords run conn itself with a word — home, shell, agent, run, jump, next, prev, keys — each a short command against the server; the navigator's pane runs conn nav and the keys popup conn page. None of them are for typing.

Upgrading conn does not touch the server: install the new binary, and the next conn sources the new configuration into the same server. R ends the server and every shell it holds, after asking; it is the way to a fresh server when one is wanted, for a raised scrollback or on the day something wedges. conn restart, typed at a terminal outside the window, is R and then conn as one command: the server ended, after asking when it holds any shells, and brought up again under this build's configuration with the terminal attached. Inside the window it refuses, since the pane running it would go with the server. Anything tmux can do to the server, you can do to it — tmux -S ~/.local/state/conn/tmux-<uid>.sock attach is what conn does.

conn ls is the same state for scripts: the held shells, one per line on stdout — pid, directory and name, tab-separated. No server listening is an empty list, not an error.