beside the tile
Inspect, compose, drive.
Every tile has a right panel with two tabs: an Inspector and a Code tab. The Inspector is where you read a terminal’s context and act on it without leaving the canvas.
What it shows, in order of urgency
The Inspector is ordered by how much a fact wants your attention, not by how the data is grouped internally.
The agent’s state leads. When a terminal is running an agent, the panel opens with its status: the agent’s name, what it’s working on, and a state chip in the colour that state wears everywhere else in kolu — violet when it’s your turn, rust while it’s working. A quiet line underneath carries the model, how long it has been running, task progress, and how much context it has used. You read “does this need me?” off the colour before reading a word.
What you’re working on comes next, as a row of chips: the branch in the
same hue the dock paints on branch labels (worktree glyph when it is one), the
repo (monogram + soft repo-colour frame), the pull request, and a single
chip rolling up CI — ✓ 9 passed when everything passed. Below them the PR
title and the working directory.
Detail folds away until it’s interesting. The per-check list, the repo’s root and worktree paths, and the terminal-CLI reference all sit behind expandable summaries. The CI list is the clearest case: nine passing checks stay folded behind their rollup, but a check that fails or is still running expands the list on its own, with the exceptions sorted to the top — you never open a list to find good news, and never miss bad news because it was folded away.
Whatever is left — the foreground process, the terminal’s colour theme — sits in one quiet line at the very bottom.
Compose a prompt
Above all of it is the Compose box, a single line at rest that grows into a
multiline textarea with a Send button when you click into it. Write a prompt
in peace, editing it before it ever reaches the terminal, then Send it (or
Cmd/Ctrl + Enter) straight into the active terminal’s input
line. It’s the in-app twin of kaval-tui send: it inserts, it
doesn’t submit — the text lands in the agent’s input box for you to review and
press Enter yourself, dodging the same paste race the CLI is careful about. Each
terminal keeps its own draft in your browser, so a half-written prompt survives
switching tiles or reloading.
Ports the terminal is serving
When something in a terminal starts listening on a TCP port, a Ports section appears listing it — the port number and the full command line of the program holding it. This is the answer to “where is the thing the agent just ran?”, without scrolling back through output for a URL that has long since gone by. Detection is not a guess at printed text: kolu reads the host’s own socket table every few seconds (and immediately whenever a terminal produces output), and attributes each listener to the terminal whose process subtree holds it. So a port appears because something is genuinely listening, and it disappears when that stops.
A port bound to all interfaces on the machine running kolu already answers on the address you’re viewing kolu at, so its row has an open link that opens it in a new tab.
A port bound only to loopback — or one on a remote host — can’t be reached
from your browser as-is, so kolu opens a door for it. Click forward & open
and kolu makes that port answer on the machine serving this page, then opens the
tab. The row then carries a teal ⇄ :<port> pill naming where it answers, a
copy button for the full address, and a ⨯ that closes the door — one list,
one row per port.
Servers that detached, and the rest of the host
Some servers leave the terminal that started them: a tool that daemonizes itself
(odu’s web daemon, anything under setsid, a double-forked dev server)
reparents away from the shell and keeps serving. When the terminal printed
that server’s URL, the server is still listed under the terminal, marked
detached — the printed URL says which terminal it belongs with, and the
host’s socket table says it is really listening. The claim is read from what the
browser has loaded of the terminal’s output, so after a reload a server whose URL
scrolled far back shows under elsewhere on this host instead, until you
scroll back to the URL.
Below the terminal’s own rows, a folded elsewhere on this host group counts everything else with a story on that machine: your other servers, another user’s socket that a terminal printed or a door points at, and any door whose server has since died. Open it to see them; a row with an open door stays visible even while the group is folded, because a door belongs to the machine rather than to any one terminal. A row served by another terminal names that terminal, and the name is a link that takes you there.
A printed localhost URL is the same door
Agents constantly print http://localhost:5173/ (and 127.*, [::1],
0.0.0.0). In your browser that hostname is the wrong machine — the laptop
you are reading kolu from, not the host running the server. Click a loopback URL
in the terminal and a small card appears at the cursor. When something on the
host is listening on that port — in this terminal, in another one, or detached
from all of them — the card says which, shows the owner’s command line, and
offers forward & open (and copy), with the printed path carried through the
door. Only when nothing on the whole host listens there does it say nothing is
listening yet, instead of inventing a forward from text. ⌘/Ctrl-click opens the
raw URL with no card. Non-loopback links keep the usual open behaviour.
The card is live — leave it open while a server starts and it upgrades the moment the listener is real. Printed text never creates a port fact of its own; it only looks up what observation already established.
Port forwarding is documented in full under
Add another machine — the two mechanisms, the one case that has no door at all, the
⌘K → Forward a port… path for a port kolu never saw, and the security
note that a forwarded port is unauthenticated on every interface of the machine
running kolu.
The terminal CLI, documented in place
An Attach section — folded until you open it, since reaching a terminal from a shell is something you do occasionally rather than watch — documents the whole shell-facing CLI for the terminal you’re looking at.
Every command that targets a terminal has the same shape, so the section is built that way: pick a pane (the main one, or any split — each split is its own PTY with its own id) and a verb (attach to take the session over, snapshot to dump its scrollback, send to type a prompt into it), and one ready-to-copy command line appears for that combination, already pinned to this kolu’s daemon. The pane’s id sits beside the pickers, copyable on its own.
Underneath it, a Drive one agent from another callout spells out the send →
wait → snapshot loop, and a folded reference covers the rest of
kaval-tui (list · create · kill), its awareness sibling
padi-tui (status · watch · wait), and the --socket argument that
points those id-less commands at this server.
It follows the terminal
Whether the panel is open, and which tab it’s on, is remembered per terminal, not as one global switch. Collapse it on a build-log tile and it stays collapsed there; leave it open on a branch diff and switching back restores it exactly. It rides session restore, so each terminal’s panel state survives a reload. New terminals open with the panel showing by default; the panel width and the Code-tab tree/content split stay global.