cyberine
UsageRun recipes

Fleet hub — blocks, inspector and motion

cyberine agents (and cyberine tui) open onto a triage view (kp3fe): what needs you first, the one selected item in an inspector, and the finished archive closed behind a drawer. There is no overview splash, no session browser and no s view cycle. The layout contract is docs/design/tui-hub-blocks.md (the 01 triage inbox combined with the 03 library and inspector from the 2026-10-04 hub redesign options), on top of the base language in docs/design/tui-direction-a.md; the screen is drawn over the pure block model in src/tui/hub/hubBlockModel.ts.

cyberine agents                       # open the hub (global registry, every project)
CYBERINE_HOME=/tmp/scratch cyberine agents   # an isolated hub: its own registry, chats and prefs

Layout, top to bottom: Cyberine and the counts (1 needs you · 2 running · 14 done today · $3.12 today); a dim strip naming the grouping and filter (group: state · filter: all · 2 pinned); the body; a rule; the dispatch input (› describe a task to dispatch to <folder>); a rule; one row of key hints.

The body is a stack of blocks, each with a header row such as ▾ Needs you (2) or ▸ History Done 291 · Failed 83. ▾ is open, ▸ is a closed drawer that costs one row and still shows its counts.

  • Needs you: a stale or paused run, or a run that finished in the last 24 hours with a diff winner and no operator decision. Each row reads glyph title reason. A pinned item that needs you sits in Pinned, so the header says so (Needs you (0 + 1 pinned)) and the empty message points at it.
  • Live watch: working and queued runs, glyph title current activity. A working row's ● is a braille spinner, and under the rows a two-line strip describes the selected run (see "Live watch" below).
  • Pinned: items pinned with Ctrl+T, whatever their state. Closed until focused, hidden while empty.
  • History: Done (finished and stopped runs plus chats) and Failed, each with a selectable header and a … N more fold. Closed until focused, so hundreds of finished rows never form a wall.

Rows in Pinned and History are a glyph and a title only (◐ needs you, ● working, ○ queued, ✓ done, ✕ failed, ⊘ stopped, ▲ stale engine, ◆ chat). Age, agents, cost, tokens, folder and the keys that apply to the item live in the Inspector, which always describes the selected item of the block you last focused. A block you are not in keeps its own selection, drawn as a dim ▎; only the focused block draws the violet ▌ and the selection band.

How the body adapts to the terminal width (columns, counted on the whole terminal):

  • 400 and wider: three panes, inbox with its drawers (140 cells), inspector (140) and Live watch (100). Extra width stays an empty margin on the right; rows never get longer.
  • 160 to 399: the block stack and the inspector side by side, separated by a vertical rule.
  • 80 to 159: one pane. The inspector replaces the stack while it has focus (space, or Tab onto it), and esc returns to the stack.
  • 79 and narrower, down to 40: the same single pane with titles capped at 24 cells and no reason column.

Tab and Shift+Tab walk the focus ring Needs you, Live watch, Pinned, History, Inspector, dispatch input (an empty block drops out, and the inspector is skipped while nothing is selected). Focusing a closed block opens it, and ← closes the focused Pinned or History drawer again (the cursor keeps its place; ⏎, →, ↓, j or PgDn opens it, Tab away and back opens it too). ↑ ↓ j k PgUp PgDn move only inside the focused block, never across blocks; outside History PgUp PgDn move the selection by the body height less two rows. While History is open its header adds a position label for the selection, · Done 3/29 or · Failed 1/31. While a filter is active every block opens so no match hides behind a drawer, and a block that is the only one with rows is always open. The focused block takes rows first when the terminal is short; the others keep at least two rows when they can.

History starts with Done and Failed folded to their default shares. The Failed section starts with at most 3 rows whenever another block is on screen (FAILED_DEFAULT_SHARE in src/tui/hub/hubModel.ts), so a pile of failed leftovers never takes the room from the rest; filtered to failed alone it gets the normal share. Select a … N more row and press ⏎ to show the next page (one block height of rows, at least 5), and again for the page after that: the count shrinks each press and the cursor stays on the fold row until the last page, then moves to the first newly shown item. Needs you, Live watch and Pinned fold only past their own caps (10, 8 and 4 rows at a short terminal; Needs you and Live watch grow with the terminal height, Pinned stays at 4). In History PgDn and PgUp page only the section under the cursor: PgDn moves a page down inside Done or Failed and, when it reaches the end of the rows shown, reveals the next page of that section alone and lands on its first new row; PgUp moves a page up and stops at the section header, so Done and Failed never page each other. Changing the grouping or the filter folds every section back to its default, and clearing a filter puts the History selection (and the pages it needs) back where it was before the filter.

Keys on the list:

  • ⏎ open: a run opens its transcript, a chat resumes in place, a folder header (folder grouping) opens a NEW chat in that folder. In the chat, ← on an empty prompt returns to the hub with the selection kept.
  • Tab / Shift+Tab move the focus between blocks (see above); the hint row shows tab blocks.
  • space inspects: the inspector takes the keys, with the full title, state and activity, age, agents, tokens, cost, run or chat id, folder, the task, the last activity lines or chat messages and a reply input. Typing a reply and ⏎: a chat reopens with the reply as its next message, a run dispatches the reply as a follow-up run in its folder. ↑ ↓ PgUp PgDn scroll the inspector text; Tab moves on to the next block; space on an empty reply or esc hands the keys back to the block you came from. On a group header or a … N more row there is nothing to inspect: space does nothing there and the hint row drops space peek.
  • n focuses the dispatch input: ⏎ dispatches a background run into the selected item's folder; the text stays put until the dispatch is accepted, and a refusal (spend cap, hook, no fleet) is shown under the input with the text kept for a retry. esc clears, a second esc returns to the block you came from. ↑ ↓ stay in the input; Tab leaves it.
  • / filters live from the same input: failed, needs, live and today (or s:failed, t:today) scope by status and day, any other words match title, activity, folder or a run-id prefix. A filter with no match says so: with no match anywhere the body says nothing matches; when only History has none, its drawer reads No matches in History for <filter> and the other blocks keep their matches; an inbox emptied by the filter reads The filter hides N needs-you items (the header count stays global). The filter applies to every block, so a word that matches only finished runs empties the inbox until esc clears it.
  • Ctrl+S cycles the grouping: state (the blocks above), folder, day. The folder and day groupings are one flat library list with group headers instead of blocks (folders fold with ← →, ⏎ on a folder header opens a new chat there), next to the same inspector. Ctrl+T pins or unpins the selected item (focus follows it into or out of Pinned) or folder header (pinned folders sort first). Shift+↑ / Shift+↓ move the selected item within its group; items you never moved keep the automatic newest-first order and new arrivals land at the top.
  • Ctrl+R renames the selected chat (written into its session file) or run (kept in the hub prefs); an empty name resets a run to its derived title.
  • Ctrl+X on a live run stops it; a second Ctrl+X on the same item within 3 s deletes it. On a finished run or a chat the first press arms the delete and the second deletes (runs: removed from the global history, the run directory stays). Ctrl+K twice stops every live run.
  • Ctrl+X on the Done or Failed header inside History (they are selectable; ⏎ there shows the next page) arms delete all N failed runs for 3 s, and a second Ctrl+X deletes every item the group holds under the current filter: runs are tombstoned in the global history with their directories left on disk, chats in Done lose their session files. A live or stale run is never touched and is counted in the result line (deleted 398 failed runs, with N could not be deleted and N live skipped when they apply). Pinned items sit in Pinned, not in their state group, so a group delete does not reach them. On the other headers Ctrl+X only says which groups it works on.
  • a applies the winner diff of a run in Needs you (k2k49), the hub's version of cyberine apply <run-id> and the same code underneath. The first a runs the clean-apply check and shows, in the inspector, the target folder (the run's own cwd), the diff stat and, when the tree has uncommitted tracked changes, how many; it writes nothing. A second a within 30 s applies the diff to that working tree WITHOUT committing, then records an approved operator decision with the comment winner diff applied from the hub to <folder>, not committed, so the run leaves Needs you on the next refresh. esc cancels, and so does moving to another item. A diff that does not apply (a conflict, or uncommitted edits in a file the diff touches) is reported with git's own reason, the tree was not changed, and the run stays in Needs you. On a terminal under 160 columns the first a opens the inspector so the target and stat are on screen; there a confirms only while the reply input is empty. There is no commit key: land the change with git diff and your own commit.
  • x marks the selected run or chat for a bulk delete (a ▣ replaces the space before its state glyph and the strip counts N marked); marks can span blocks, and x marks in Live watch too (stopping a worker there is X). With any row marked, Ctrl+X arms delete N marked runs (items when a chat is marked) for 3 s and a second Ctrl+X deletes every marked row through the same path as a single delete. A marked live or stale run is skipped and counted (deleted 3 marked runs · 1 live skipped). esc clears the marks, as does a filter change, a grouping change or a marked row disappearing from the list; nothing is deleted on one keypress. In a scoped cyberine tui (no global history) run deletion is off, so the result line says could not be deleted.
  • m steers a running run, i opens the raw run detail, w the workflow monitor, o the run directory, H hides the selected folder (U shows every hidden folder again), Ctrl+F forks the selected chat into a new session and opens the copy, r retries after a failed history read (only while that line is shown), t the team roster, ? help, q quit.
  • ? opens the keybindings overlay with the hub sections first: ↑ ↓ j k scroll a row, PgDn or space and PgUp a page, ? or esc close it. Its rows are derived from src/tui/fleetActions.ts, the same registry the handler reads.
  • O then c opens the selected item's folder in Claude Code, O then x in Codex (any other key cancels). The hub hands the terminal to the harness and comes back when you quit it; a binary that is not on PATH is named in a notice instead.

Motion, the empty state and the delete cue (kjge7)

cyberine agents                                  # cues on: Tab, open a drawer, page History, delete a row
TERM_REDUCED_MOTION=1 cyberine agents            # every cue shows its final frame at once
CYBERINE_HOME="$(mktemp -d)" cyberine agents     # a fresh home: the empty state with the brand mark

Each transition is a short cue on top of a state change that has already happened, so the keyboard never waits for it, and any key ends a cue in flight:

  • Moving between blocks (or onto the inspector reply or the dispatch input): the gutter of the old and the new selection shows the bar dimmed, then the new one turns violet and the old one becomes the dim ▎ (2 frames of 80 ms).
  • Opening a drawer: the drawer's final rows are reserved at once, the first row shows, then the rest (3 frames of 80 ms), and the caret flashes. Closing is immediate; only the caret flashes.
  • A run or chat that appears (after a dispatch from the hub, or started anywhere else) gets a violet bar in its gutter that fades over 3 frames of 80 ms. The focus, the selection and the text in the input never move.
  • Confirming a delete moves the selection to the neighbouring row at once; the deleted row dims (with deleting… where the row has a reason column) and leaves the list once the delete has succeeded and the 2-frame cue (80 ms each) is over. A delete that fails puts the row back and says so.
  • Paging History flashes the position label in its header (2 frames of 60 ms); the rows swap at once.

TERM_REDUCED_MOTION=1 draws every final frame at once with no timer (a confirmed delete still dims until it resolves). The cues run in their own small components with their own timers, so a cue never redraws the rest of the hub.

With no runs and no chats (a brand-new CYBERINE_HOME) the hub shows the static Cyberine confluence ring next to Nothing here yet., whether a worker is ready, the folder a dispatch would land in, and the three ways to start (n, ⏎, ?). Below 64 columns, or with fewer than 7 body rows, the ring is left out and the text stays.

Known Limitations and Deferred Work (kjge7, k1kzw). The study's run-state-change cue (a spinner replacing ○, a needs-you gutter cue, a mint done cue) is not built: a state change shows as the new glyph on the next read. The arrival cue follows every run or chat key the hub had not seen at its last read, not only dispatches from this hub. Drawers hide their not-yet-revealed rows one by one instead of clipping the drawer, because Ink applies only the innermost overflow clip and every hub row clips itself; a future clip-based animation hits the same limit. A cue frame can reach the screen late on a busy or slow terminal, since Ink rewrites the whole frame each time; the cue's own timing stays exact and the final frame always lands. The tui:e2e cases assert where each transition ends and that reduced motion skips the frames, never a single frame's colour or timing.

Known Limitations and Deferred Work (kp3fe). The design's separate 20-cell collapsed rail at 160 columns is not drawn: the closed drawers sit under the inbox in the same pane, which costs one row each and keeps the same counts. Live watch shows the current activity text only; the sampled token-rate plot of the design is not built. The inspector offers the keys that already exist; applying a winner from the hub is the a key (k2k49, below). The folder and day groupings are a flat library list without blocks, and an unfocused block's remembered selection is drawn only for blocks that have rows on screen.

Known Limitations and Deferred Work (k2k49). a applies uncommitted and has no --commit or --step form; use cyberine apply <run-id> --commit or --step <id> for those. It takes the same winning step cyberine apply picks (the implement step, else the last step with a diff) and runs the same git apply --check, so a dirty tree is refused only when the dirty files overlap the diff; a tree that is dirty elsewhere gets a warning line and still applies, exactly as the CLI does. Only a run in Needs you (finished, diff winner, no decision yet, not paused) offers a; an older or already-decided run in History is applied with the CLI. Applying counts as the operator's review decision, but a later follow-up dispatch never does. If the diff lands and recording the decision fails, the line says so and the run stays in Needs you until you delete it or decide through the MCP approve_run tool.

Known Limitations and Deferred Work (kf366, ky347). The mark key is x, not m as the task text proposed, because m already steers a running run and a live run has to be markable so that it can be skipped and counted. Marks live in the hub only: they do not survive a chat handoff or leaving the hub, while the History pages, the open or closed drawers and the selection do. Search spans the loaded title, activity, folder and run-id prefix of every block; there is no regex, no agent-name search and no per-block scope. Paging reveals rows cumulatively (the section grows by a page) rather than swapping a window, and a page opened in the folder or day grouping is not carried over a chat handoff. There is no Undo for a delete, marked or not.

Live watch: spinner, token rate and step progress (k90w4)

cyberine agents                       # a running run shows a spinning glyph in Live watch
TERM_REDUCED_MOTION=1 cyberine agents   # spinner holds one frame, rate sampled once a second
npm run selftest:hub-live-watch       # offline: rate maths, sparkline cells, step totals, spinner clock, reduced motion

Each working run in Live watch spins its own glyph (⠋ ⠙ ⠹ …, one frame every 120 ms). Stale and queued rows never spin: stale keeps ▲, queued keeps ○. The spinners share one clock that lives outside the hub and runs only while a spinner is on screen, so a refresh or a keypress does not repaint every row; the clock stops when the last live row leaves the screen.

Under the rows, the selected run in Live watch gets a two-line strip (one line at 60 to 79 columns, where the plot is replaced by text):

  rate  ▁▂▂▂▃▅▆█ 180 tok/s
  steps ████████──────── 2/4
  • rate is the run's token counter (input plus output, summed over its workers) growth per second, sampled locally every 500 ms while the strip is on screen and averaged over the last 2 s so the hub's once-a-second read of run.json does not make the line flicker. The eight cells start as a dim ─ line, fill from the right with real samples only (no history is invented for a run you only just selected), and are scaled to the biggest rate in view, so read the number beside them, not the height. Selecting another run restarts the line empty. A run whose provider reports usage only when a worker finishes shows a flat ▁ floor and 0 tok/s until then.
  • steps is finished steps / total steps, drawn as a bar only when the run has two or more steps created up front (a workflow definition). A one-step run shows 0/1 as text, and a script workflow or an autopilot loop, whose steps are added as it goes, shows N done · total unknown with no bar. Nothing here is a percentage of an open-ended generation.
  • The charts are neutral grey; colour stays on the state glyphs.
  • space on a running row opens the inspector, which now ends with latest output, the last lines of the streaming worker's output file (up to 6, refreshed every 750 ms while the inspector is open). A worker with no streamed output file shows the activity line only.
  • With the Live watch block focused, s stops the selected run and X (Shift+x) stops its first running worker only (the same control file Ctrl+X and the run detail's s / x write); a plain x there marks the row like in every other block; the notice reads stopping run <id> or stopping worker <name>, and the row keeps spinning until the next read of the run confirms the new state. The hint row offers both keys while Live watch is focused.
  • TERM_REDUCED_MOTION=1 holds the spinner on its first frame, starts no spinner clock and samples the rate once a second; every number and state still updates.

Known Limitations and Deferred Work (k90w4). The strip follows only the selected run; there is no per-row rate. X stops the first running worker, so a run with several parallel workers has no way yet to pick another one from the hub (use i for the run detail, where x stops the highlighted worker). The app-level clock still re-renders the hub root at the animation cadence while anything is live; only the spinner glyphs and the rate leaf are isolated, so a faster fix needs the app to stop passing its fast now into the hub (follow-up). The Live watch block has no mouse or scroll control of its own, and s / X do nothing in the folder and day library groupings, which have no Live watch block.

Opening the hub beside a harness session

cyberine open            # inside tmux: split the current window and run cyberine agents in the new pane
cyberine open --window   # inside tmux: a new tmux window named cyberine instead of a split

Run it from the shell (or a harness's shell tool) in the folder you are working in; the new pane starts there and inherits CYBERINE_HOME, CYBERINE_DEV and CYBERINE_THEME from the caller (tmux otherwise starts panes with its server's environment). q in the hub closes the pane.

Known limitations: tmux only. Outside tmux cyberine open prints a one-line hint to run cyberine agents in another terminal tab and exits 1; it does not drive iTerm, Terminal.app, kitty or WezTerm splits. Only those three variables are forwarded, so a PATH set only in the calling shell does not reach the pane.

The grouping, pins, manual order, collapsed folders and run renames persist in ~/.cyberine/settings.json under hub (versioned, validated on load: an invalid stored value falls back to the default rather than breaking the hub). While the history is loading the header says reading run history…; a run record whose run.json cannot be parsed is counted on a warning line (r retries). Registry records whose run directory no longer exists are skipped silently and are not re-read on every poll; finished runs are only re-read when their registry record changes.

A newly actionable item (it enters Needs you) fires one notification through the usual CYBERINE_NOTIFICATIONS channel and the terminal title reads 1 needs you · cyberine agents.

Known limitations and deferred work. Group paging and the group delete cover the state grouping's Done and Failed groups only: the day grouping pages its groups the same way but has no group delete, the folder grouping never folds, and there is no way to page backwards or collapse a paged group short of changing the grouping or the filter. The group delete runs from the hub only (the project-scoped cyberine tui cannot delete runs, so it reports them as not deleted), has no undo, and clears by tombstone, so the run directories keep their disk space. The peek always renders inline; below 160 columns the inspector replaces the block stack instead of sitting beside it. A chat's needs-you state (a pending question or plan) is not persisted by the chat, so chats never enter Needs you. Ages use the hub's own compact format, not the old formatAge. Every hub key is remappable in ~/.cyberine/keybindings.json (the Hub, HubInput, Inspector, Rename and Harness contexts, plus fleet.help, fleet.quit, fleet.roster and fleet.hideFolder); the footer, the inspector actions and the ? overlay follow the remap. There is no copy key; the run id is in the peek, and the terminal's own selection copies any text on screen (the hub does not capture the mouse). Folder grouping cannot reorder folders by hand (Shift+↑↓ moves items within a group only).

The Cyberine panel inside Claude Code (/cy)

The panel ships in the plugin, so the ordinary install brings it; start a new Claude Code session afterwards and type the command:

cyberine plugin install                        # links the plugin into ~/.claude/skills/cyberine (cyberine@skills-dir)
claude --plugin-dir /path/to/cyberine/plugin   # alternative for one session, without installing
/cy                                            # inside the session: open the panel (last tab, Commands at first)
/cy board                                      # open it on a tab: commands, fleet, board, handoffs or cockpit
/cy close                                      # close it from the prompt
/cy nudge on                                   # opt in to one clickable line above the prompt for new events (off by default)
/cy nudge off                                  # turn it off again; /cy nudge shows the setting
/cy nudge notify on                            # opt in to one native notification per new event (off by default); /cy nudge notify off turns it off
/cy help                                       # the keys
/cy end                                        # shortcut: runs /cyberine:end now; any /cyberine:<verb> works as /cy <verb> [args] and starts a Claude turn
cyberine pulse --json                          # the nudge's cheap check (schema pulse/1)
cyberine agents --pane --limit 12              # the Fleet tab's JSON, for a script or a check
cyberine next --json                           # the Board tab's JSON (schema board-next/1)
cyberine handoff list --json --limit 30        # the Handoffs tab's JSON (schema handoff-list/1)
cyberine stop <runId>                          # what the panel's x runs: stop a running run (the hub's Ctrl+X)
cyberine delete <runId>                        # what the panel's d runs on a finished run: tombstone it, files kept
cyberine delete --chat <sessionId>             # what the panel's d runs on a chat: remove the session file
cyberine delete --state failed --match "noop task" --dry-run   # bulk: list what would be tombstoned, change nothing
cyberine delete --state failed --match "noop task" --yes       # bulk: tombstone every match

/cy <verb> [args] runs /cyberine:<verb> with those arguments as if it had been typed: /cy end is /cyberine:end, /cy tasks what is next is /cyberine:tasks what is next. A verb that names a panel tab (commands, fleet, board, handoffs, cockpit) or nudge, close and help keeps its panel meaning, and a verb no /cyberine:* command matches prints the help. While you type the first word after /cy , the typeahead lists the matching verbs under Claude Code's own rows (k5btw, through the prompt.autocomplete mod event of Claude Code 2.1.292): /cy e offers /cy end runs /cyberine:end: ..., each panel verb with what it opens, each /cyberine:* verb with what it runs; taking a row only writes the verb, nothing is sent. Known Limitations: the shortcut runs the skill from a timer just after the /cy command returns, because Claude Code refuses $.command.run inside the hook that is answering /cy, and $.prompt.submit refuses text that starts with a slash (both measured 2026-10-07); when the run is refused the full /cyberine:<verb> line is placed in the prompt instead, for one Enter. It starts a Claude turn on the session's own account, so the Claude seat fallback of the claude -p workers does not apply to it.

A summary line on top counts what waits on you (runs in Needs you plus board tasks whose blocker starts with "Operator"), this repo's runnable tasks, the runs working now and the long handoffs written today. 1 Commands, the first tab and the one a fresh session opens on, starts the plugin's own commands (below); 2 Fleet shows the hub's state groups (Needs you, Working, Done, Failed); 3 Board shows this repo's board in sections Waits on you, Doing, Runnable, Blocked and Waiting on other tasks; 4 Handoffs shows this repo's newest handoff of any kind, then the long handoffs of every project, newest first; 5 Cockpit shows every opted-in repo (next section). It docks beside the transcript in fullscreen from 110 columns and sits above the prompt when narrower. The arrows or Tab walk the rows; a list longer than the panel ends in ↓ N more (and starts with ↑ N more once paged), and walking onto that line pages the list and moves the selection to the next row, so every row is reachable inline at any width (kxjyk). Enter opens one (its facts and the next command, such as cyberine digest <id>, cyberine tasks status unblock <id> or the handoff's path); there a puts a question about the row into the prompt for you to send and moves the keyboard there, b goes back. r refreshes, h runs cyberine open (the full hub in a tmux split), Esc closes. The command runs at once even mid-turn.

What is running, and how the keys move. The summary line's ◆ turns into a yellow ↻ and updated Ns ago into refreshing while a refresh runs, a live run keeps the CLI's own working glyph, a running action shows a spinner and its elapsed seconds, and a fresh notice (Delete done, a command placed in the prompt) shows bold in the accent for 2.5 s and then dims. Only the running action animates frame by frame: every redraw re-issues the panel's buttons, and a press that lands on an older drawing is dropped, so the list itself never redraws on a timer. The tab bar sits at the bottom with Refresh and Hub; every one of them has a hotkey (1-5, r, h), so the arrows and Tab walk only the current tab's rows, opening the panel or switching tabs selects the first row (or the row you last selected there), and walking past the last row pages instead of leaving the list.

Commands (kjmr4). The 1 Commands tab lists every /cyberine:* command the session offers, read with the host's command list each refresh, so a skill added later appears without a panel change. A command that takes an argument (ask, autopilot, az, go, plan, research, run) shows types arg: Enter fills the prompt with /cyberine:<verb> , sends nothing and hands the keyboard to the prompt, where you type the argument and press Enter. A command that takes none (end, handoff, init, tasks) shows runs: Enter opens a confirmation inside the panel (y runs, n cancels) that names the cost class, then the panel starts the skill through the host's command runner, exactly as typing /cyberine:<verb> would, and says so under the row. The panel never shells out to the cyberine CLI for these, so a skill keeps its own hand-offs, such as /cyberine:autopilot reading init and running that flow when a repo is not set up. The Board tab's "no board" note carries a /cyberine:init row that opens the same confirmation. Every command here is a skill, a skill is a prompt, and running one starts a Claude turn on your plan (measured on 2.1.288 with /cyberine:tasks); the confirmation says "starts a Claude turn" for init, handoff and tasks, and "spends" for end, whose full handoff is one further background model turn.

Glyph legend (k1mr1). /cy help ends with a legend of every glyph the panel draws: Fleet (◐ needs you, ● running, ○ queued, ✓ done, ✕ failed, ⊘ stopped, ◆ chat, ▲ a run listed as running whose process is gone), Board (⚑ waits on you, ▸ doing, ○ runnable, ■ blocked, ◌ waiting on other tasks), Handoffs (◆ this repo's newest, ✓ written today), Commands (▸ runs after a y/n, ✎ fills the prompt) and the summary line (◆ up to date, ↻ reading, a braille spinner while an action runs). The hint line under every list and detail names the first tab: 1-5 tabs (1 Commands). Known limitation: the legend is text in /cy help, not a key inside the panel, because the panel has no spare row for one; the Fleet glyphs come from the CLI's snapshot, so a new glyph there needs a line in the legend in plugin/hooks/fleet-pane.tsx.

Glyph colours (krscr). The Fleet tab draws each row's state glyph as its own coloured cell in front of the title, in the same tint the hub gives that state (needs you, running, queued, done, failed, stopped, chat, and the stuck ▲). The title stays its own button: Enter, the focus highlight and the action keys work as before, and the glyph keeps its tint while the row holds the focus. The detail view tints the glyph in the title the same way, the summary line tints its "wait on you", "runnable" and "running" counts, and the active tab's brackets take the accent. The colours come from the CLI, not the mod: cyberine agents --pane now carries a color on every item and a tones object (needs, working, queued, done, failed, stopped, chat, stuck, accent, muted), resolved from the active Cyberine theme (CYBERINE_THEME, the same scheme the hub uses), through the same table the hub reads (HUB_STATE_TONE in src/tui/hub/hubModel.ts). No CLI call was added. With NO_COLOR set in the environment the panel's CLI runs in, FORCE_COLOR=0, TERM=dumb or CYBERINE_THEME=mono every colour is the empty string and the panel draws the glyphs plain; the glyph shape alone still says the state. Known limitation: only the Fleet tab, the summary line and the tab brackets are tinted; Board, Handoffs, Cockpit and Commands glyphs stay uncoloured, and the legend in /cy help is plain text that names the glyphs and says they are tinted by state, not which colour each one is, because the colours follow the theme. A cyberine older than this field gives an untinted panel.

The Hub button (kne4j). cyberine agents --pane now carries canOpenHub (true when cyberine open can split a tmux pane, that is, when TMUX is set in the environment the panel's CLI calls run in). The panel draws the Hub button, its h key and the h hub hint only when the latest Fleet read says true; outside tmux, or before the first read, or with a cyberine older than this field, there is no button, and /cy help points at cyberine agents in another terminal tab instead. No CLI call was added: the flag rides the Fleet read the panel already makes. Known limitation: the check is the environment variable only, not whether tmux accepts the split, so a stale TMUX can still draw a button that then reports tmux's refusal as a notice.

Known limitations of the Commands tab: which commands take no argument is a small map keyed by verb in plugin/hooks/fleet-pane.tsx (RUN_COMMANDS), because the host's command list carries only a name and a description and a skill's argument-hint cannot tell a required argument from an optional one; a verb the map does not know (a new skill) fills the prompt and is never run from the panel. The result line says the command ran; the model's answer is in the transcript, not the panel. Filling the prompt replaces whatever you had typed there. A command waits for an idle session, so one started mid-turn runs when the turn ends.

Actions (k8syc). On the Fleet and Board lists a row strip under the list names the selected row and carries the same action keys (x stop, d delete, p apply, f follow up, u answer, a ask Claude), dimmed where they do not apply; a key that does not apply says so on the hint line and keeps the keyboard in the panel, and one that does opens the row's detail with that action's confirmation (kxdsz: before this, a letter the list did not bind left the panel, landed in the prompt and the next Enter sent it). The detail screen carries the actions that fit the row, each one a cyberine command run by the panel only after a confirmation step inside it (y confirms, n cancels); the command's own output appears under the row in green or red, never as a toast, and the panel refreshes after it. A live run gets x Stop (cyberine stop <id>: a stop request its engine picks up, or, when the engine is gone, the run is marked stopped at once). A finished run gets p Apply when its winner is a diff (the panel first runs cyberine apply <id> --dry-run and shows the file list, then cyberine apply <id>; nothing is committed), f Follow up (a field takes what the next run should do, the confirmation names the cost class "spends", then cyberine run --bg "Follow-up to run <id8> (\"<title>\"): <text>" runs in the run's folder, the same prompt the hub's follow-up builds) and d Delete (cyberine delete <id>). A chat gets d Delete (cyberine delete --chat <id>). A board task in Waits on you or Blocked gets u Answer and unblock: a field takes the answer, then cyberine tasks note <id> "answer: <text>" and cyberine tasks status unblock <id> run in that order, and the unblock is skipped when the note fails.

cyberine stop <runId> and cyberine delete <runId> | --chat <sessionId> are the CLI form of the hub's Ctrl+X, added so the panel has a command to run: stop on a finished run says there is nothing to stop and exits 0; delete refuses a running or queued run (stop it first) and a run already deleted, both with exit 1, tombstones the run's index entry and leaves its directory on disk, and for a chat removes the session file. Both resolve a run id prefix the way cyberine apply does.

Bulk delete (kcrs9). cyberine delete --state <failed|done|stopped> [--match <text>] [--project <name-or-path>] [--older-than <Nd|Nh>] [--dry-run] [--yes] tombstones every finished run in the global registry that matches all the flags given, for clearing leftovers such as sweep fixtures. --state is required and is the hub's own classification (the same function the hub uses): failed is a run whose state is error, stopped is one whose state is cancelled, done is a finished run the hub shows as Done, which leaves out a done run with an unreviewed diff inside its 24-hour review window (the hub shows that one under Needs you); error and cancelled are accepted as aliases. --match is a case-insensitive substring of the run's title or task text, --project is a folder name or an absolute path (a worktree or .local path counts as its parent repo, as in the hub), --older-than takes whole days or hours (7d, 12h) against the run's last update. A running or queued run is never selected, whatever the flags, and is re-checked against its registry record just before it is tombstoned. --dry-run prints the count and up to 20 rows (id8, state, age, title) and writes nothing. Without --dry-run, more than 20 matches print the same list and exit 1 until you add --yes; with up to 20 matches it deletes at once. The final line gives the deleted and skipped counts. A runId or --chat cannot be combined with these flags.

Known limitations: bulk delete is the same tombstone as cyberine delete <runId> and the hub's Ctrl+X: the index entry is marked deleted and a .cyberine-deleted marker is written in the run's own directory, but the run directory and its files stay on disk, so it frees no space. There is no command to undo it: the registry has no undelete path, and the marker stops a registry rebuild from re-adding the run. --state takes one state per call, and there is no filter on cost, agent or workflow name.

Nothing is drawn and no CLI runs until you type /cy: opening the panel starts a 10-second refresh that runs the three commands above, and closing it (Esc or /cy close) cancels it, so a session with the panel closed spends nothing on it, unless you opted in to the nudge or native notifications below (both off by default), which run cyberine pulse every 3 minutes.

The nudge (kdcg2). /cy nudge on shows one line above the prompt, for operators who want a cue without opening the panel: ◆ Cyberine: new long handoff: <topic> · 1 run needs you (/cy). It appears only for something new since you last opened the panel (a long handoff written after that moment, or more runs in Needs you than the lowest count seen since then), clicking it or pressing o after focusing the band (ctrl+x then tab) opens the panel, and opening or closing the panel hides it. The setting is stored per user and survives restarts; /cy nudge off or never turning it on leaves nothing drawn and nothing running. While it is on, the mod runs cyberine pulse when it turns on or the session starts and then every 3 minutes (180 s), never while the panel is open.

Native notifications (km1fs). /cy nudge notify on raises one native notification through Claude Code's own notification channel (your preferredNotifChannel, $.ui.notify, Claude Code 2.1.295 or newer) when the number of runs in Needs you rises since the previous check, or a long handoff is written: Cyberine: 1 run needs you · long handoff written: <topic>. One notification per check at most, never one per poll, and nothing when nothing changed. It is off by default, stored per user like the nudge and independent of the chip: it reuses the same cyberine pulse check every 3 minutes (180 s), so turning it on starts that check even with the chip off. Known limitations: it names a count and a handoff topic, not the run, because cyberine pulse carries only the count; the first check after turning it on or starting a session sets the baseline and notifies nothing; checks are skipped while the panel is open, so a run that needs you during that time is compared against the last check made before it; the terminal decides whether a banner shows (a terminal that drops the sequence still counts as sent), and on a Claude Code without $.ui.notify the command refuses and nothing is raised.

Pressable rows (kzr9j). On Claude Code 2.1.295 or newer each list row of the panel (Fleet, Board, Handoffs, Commands, Cockpit) is one Button made of text parts (the state glyph, the title, the dim activity and the age), so a click anywhere on the row opens it, not only on the title; older versions keep the title-only Button with the other parts beside it. The row shows the same information at 80 and at 500 columns. Known limitations: the version is read once at session start, so an upgrade needs a new session, and a development build whose version is not spelled as a release keeps the fallback rows.

cyberine pulse [--json] is read-only and prints one JSON line { schema: "pulse/1", generatedAt, needs, handoffs: { total, items: [{ file, project, writtenAt, title }] } }: needs is the number of runs the hub shows in Needs you (loaded only from live runs and the last 24 hours of registry records), items are the five newest long handoffs. The global shim starts it from src/pulse.ts instead of src/cyberine.ts, which skips loading the whole CLI: about 1.7 CPU-seconds per call against about 4.5 for cyberine handoff list --json --limit 1 on the same machine.

cyberine handoff list [--json] [--limit N] [--project <name>] is read-only: it lists the long handoffs under ~/.cyberine/handoffs/ ($CYBERINE_HOME/handoffs/) across every project, newest first by the timestamp in the file name (default 20), counts the ones written today, and, inside a git repo, names that repo's newest handoff of any kind (the same set cyberine handoff pull reads). The JSON is { schema: "handoff-list/1", dir, generatedAt, today, total, repo: { project, newest: { path, source, mtimeMs, title } | null } | null, items: [{ file, path, project, writtenAt, title, today, thisRepo }] }.

cyberine agents --pane reads only the registry records worth showing (live runs, anything updated in the last 24 hours, and the newest --limit runs) plus the ten most recent chats, so it stays quick on a registry of thousands of runs; the JSON is { version, generatedAt, counts: { needs, working, doneToday, failedToday, costToday }, total, items: [{ kind, id, state, group, glyph, title, activity, agents, folder, ageMs, costUsd?, live, stale, cwd, diff }] }, where cwd is the run's or chat's own working directory (unfolded, unlike folder) and diff says a step's winner is a git diff.

Known limitations: Claude Code loads a plugin's hooks module only while its server-side rollout flag tengu_plugin_hooks_modules is on for the account, and that gate applies to every non-built-in load path alike: the ~/.claude/skills/cyberine symlink, a marketplace install and --plugin-dir (measured on 2.1.288, kx5r2). With the flag off the skills and the MCP server still load and only /cy is missing; Claude Code caches the flag on disk, so the first session after the flag turns on can still start with it off and the next one picks it up. To see which case you are in, start claude --debug and look for hooks module cyberine@skills-dir loaded or not loaded in the debug log. The panel's actions are stop, delete, apply, follow-up and answer-and-unblock only: steering a run, stopping one worker, pausing, restarting, closing a task as done and Stop All stay in the hub or the CLI. Delete is offered only once a run has stopped, where the hub's second Ctrl+X also deletes a live run. A follow-up is refused when the run's folder is gone or has no commit yet, and its confirmation names the cost class rather than the workers, because cyberine route may call the LLM router and spend to find out. After an action the list catches up on the next refresh when a refresh was already in flight, up to 10 s later. The answer and the follow-up text are one line each, and the fields need a keyboard surface. The panel is Claude Code only (Codex has no pane surface), and the VS Code panel and claude -p run the command without drawing it. Its Fleet counts cover the runs the snapshot read, which include every run of today, so the today figures match the hub. "Waits on you" reads a board task as the operator's when its blocker text starts with "Operator", the board convention, so a blocker worded otherwise lands under Blocked. The Handoffs tab lists long handoffs by file name and only this repo's newest short breadcrumb, not every breadcrumb of every repo. While open, each refresh runs the three CLI commands (the Fleet read alone costs about 2.6 s of CPU), so leave it closed when you do not need it. The nudge is off by default, counts runs only (not board tasks that wait on you), judges "new" against the session's start and the panel's last open or close rather than anything remembered between sessions, and is as late as its 180 s check: a handoff or a run that needs you can take up to 3 minutes to show. It is not drawn on the VS Code and mobile surfaces, and yields to a survey. The former /cyberine-fleet command is gone, with no alias. Inside a background session you have attached to, Claude Code itself takes Left arrow, even while the /cy panel has focus, to go back to its agents view, and that action is not in the keybindings.json action list, so it cannot be rebound; the panel never needs it (1-5 switch tabs, Up/Down or Tab/Shift+Tab move, letters press the buttons, Esc closes), so do not reach for Left/Right there, or use the mouse. The dock's width is Claude Code's, not the panel's (k2aq5): by default 45% of the terminal, at most 90 columns, which leaves the transcript 70 columns at 110, but a width you once dragged the dock to is kept in ~/.claude.json (pluginPanes.dockColumns) and wins at every terminal width, down to Claude Code's own 24-column transcript floor; drag the dock narrower on a narrow terminal to change it. The panel asks for no width, because a kept width would override any request.

The cross-repo cockpit (cyberine cockpit, the /cy Cockpit tab) (kmp8k)

One read across every repo you opted in, so the session-start round of checking each repo by hand becomes one tab. Per repo it shows the board tasks that wait on you (read by the same rules as cyberine next --groups: an owner decision or an owner resource, which covers an Owner: decide or provision task, an exec: manual task, and a blocker that names the owner or a resource), the other blocked tasks, the runnable ones, runs in Needs you, the autopilot state, whether the repo's STOP file is set, the recorded delivery mode and the newest handoff; under all repos a handoff feed and a digest.

cyberine cockpit add                                   # opt the current repo in (default budget: 1 session, 1 hour)
cyberine cockpit add ~/work/api --max-sessions 2 --max-hours 1.5   # opt another repo in, or change its autopilot budget
cyberine cockpit list                                  # the opted-in repos and their budgets
cyberine cockpit                                       # the digest and one block per repo, as text
cyberine cockpit --json                                # the same as one JSON line (schema cockpit/1); the Cockpit tab renders it
cyberine cockpit stop ~/work/api                       # write ~/work/api/.local/autopilot/STOP
cyberine cockpit stop ~/work/api --clear               # remove it again
cyberine cockpit digest                                # the digest lines alone
cyberine cockpit digest --telegram                     # also send them to Telegram
cyberine cockpit remove ~/work/api                     # opt a repo out; nothing in the repo is touched
/cy cockpit                                            # inside Claude Code: open the panel on the Cockpit tab (key 5)

Which repos. Only the repos listed in $CYBERINE_HOME/cockpit.json (~/.cyberine/cockpit.json), which cyberine cockpit add writes; nothing scans the disk. add takes any path inside a git repository and records the root cyberine autopilot would use there, so add from a subfolder opts the whole repo in, and add from a linked worktree (or a submodule whose board lives in the superproject) records the main checkout (or the superproject), where that autopilot reads its board and its STOP file. Every cockpit read and every Answer, Start and Stop from the panel ignore TASKBOARD_ROOT, CYBERINE_TASKS_ROOT and NF_TASKS_ROOT, so a board pin the calling session inherited never redirects another repo's row to the pinned board. A repo does not have to be set up for Cyberine to be listed: its board, runs and handoffs show either way, and only Start needs more (below).

In the panel. The Cockpit tab is the fifth tab. It reads cyberine cockpit --json when you switch to it and on every 10 s refresh while it is the shown tab, never while another tab is shown or the panel is closed. The first row is the digest headline; when this session has live subagents or teammates, a second row counts them by state (running, waiting, idle, pending) from Claude Code's $.agent.list(), with no CLI call. Then each repo: a heading with its delivery mode and budget, the repo row (▶ autopilot running, ⊘ STOP file set, ○ idle, ✕ unreadable) and its tasks (⚑ waits on you, ■ blocked, ○ runnable), then the handoff feed (✓ written today). On a repo row s starts the autopilot and x stops it; on a blocked task u answers it; a asks Claude about any row; Enter opens a row's detail. Every action asks y/n first, and a key that does not apply says why in the hint line (for Start, the missing precondition).

  • Answer (u) is the Board tab's answer-and-unblock action pointed at that repo: it runs cyberine tasks note <id> "answer: <text>" and then cyberine tasks status unblock <id> with that repo as the working folder.
  • Stop (x) runs cyberine cockpit stop <repo>, which writes <repo>/.local/autopilot/STOP. The autopilot checks that file before every turn and every new session: a running one finishes the turn in flight, hands off and exits 0; one that has not started a session yet starts nothing. The file stays until Start or cyberine cockpit stop <repo> --clear removes it.
  • Start (s) runs, in that repo, cyberine autopilot --engine claude --bg --yes --max-sessions <N> --max-hours <H> with the repo's budget, after clearing a STOP file if one is set. --bg detaches the autopilot, so it keeps running after the panel or the session closes; the launch itself returns in a few seconds. Its confirmation names the spend (your Claude plan), the budget, the delivery mode and the exact command. Start is offered only when the repo has the Cyberine rules applied, a delivery mode recorded in rules.delivery (cyberine init --rules --delivery pr|branch|main), at least one runnable exec: agent task, and no autopilot already running there. The claude engine reads that delivery mode and tells every session it drives how to land the work: pr pushes a branch auto/<task-id>-<short-slug> and opens a pull request, branch pushes the branch only, main commits on and pushes the default branch.

The digest. cyberine cockpit digest prints one headline (repos, what waits on you, runnable, autopilots running, handoffs today) and one line per repo (what waits on you, runnable and blocked counts, the autopilot's state or last outcome, the newest handoff). The Cockpit tab shows the headline; --telegram sends all of it through CYBERINE_TELEGRAM_BOT_TOKEN and CYBERINE_TELEGRAM_CHAT_ID (shell or ~/.cyberine/.env, the same channel the autopilot and the handoff watcher use) and exits 1 when they are not set or the send fails. For a morning digest, schedule it with the OS, for example the crontab line 0 8 * * * cyberine cockpit digest --telegram.

JSON. cyberine cockpit --json prints { schema: "cockpit/1", generatedAt, config, repos: [...], handoffs: { today, total, items }, digest: [...] }. Each repo carries path, project, error, budget, rulesApplied, delivery ({ mode, source, summary } or null), stop ({ present, path, since }), autopilot (state running, stale or idle, plus the newest autopilot run's id, session, current task, done tasks and last state), runs (needs, working), board (counts, blocked with an operator flag, up to ten runnable) or null, handoff (the newest of any kind) and start ({ ready, reason, argv }, the exact command Start runs). The global shim starts cockpit from the light entry src/cockpit.ts, which skips loading the whole CLI: about 1.5 CPU-seconds per read against about 4.5 through src/cyberine.ts.

Known limitations and deferred work: the cockpit reads only repos on this machine, from this machine's run registry; a repo whose folder has moved shows an error row until cyberine cockpit remove drops it. Start always uses the claude engine, because the fleet engine lands a winner as a local commit on the current branch and does not follow a delivery mode; a repo that wants the fleet engine runs cyberine autopilot itself. Start needs a recorded delivery mode even though cyberine autopilot alone falls back to the repo's own rules. The budget is per repo and set only with cyberine cockpit add, not from the panel. The autopilot still applies its own quota gate after launch: when the subscription window cannot fit the cheapest runnable task's estimate it exits with no session (the cockpit then shows its last state). Answering does not reach a running autopilot's current turn (the autopilot reads the board at each session start). Long handoffs are matched to a repo by folder name, so two opted-in repos with the same folder name share them. The digest is not scheduled by Cyberine itself: the daemon's cron dispatches tasks, not CLI commands, so the morning push is an OS crontab line. The tab inside an interactive Claude Code session is checked by npm run tui:e2e -- --only ccpanel.cockpit (tmux, scratch repos, never starts an autopilot) and otherwise by hand; the root resolution, the board-pin isolation and the owner tasks kept above the ten-row cap are checked offline by npm run selftest:cockpit-roots.