mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
docs: document codeman tui
The user guide covers what the dashboard is (and is not), the two non-interactive fast paths, the four groups and their ordering, the full keymap, what answering an approval does server-side, and the SSH/narrow and degraded cases. The example frame is a real 100x30 capture against the E2E fake server, not a drawing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -1,6 +1,11 @@
|
||||
# Codeman TUI Rework Plan
|
||||
|
||||
Status: PROPOSED (research done, nothing implemented). Owner review needed on the open questions at the bottom.
|
||||
Status: **phases 0-2 implemented** on `feat/tui`; phases 3-4 remain follow-ups. The user guide is [`docs/tui.md`](tui.md); this document stays the design record.
|
||||
|
||||
- Phase 0: `src/cli-style.ts` (palette, glyphs, `heading`/`kv`/`table`/`spinner`/`confirm`) plus the mechanical fixes of §5, and `test/cli-commands.test.ts` now derives its inventory from the real commander `program` instead of parsing a fixture.
|
||||
- Phases 1-2: `src/tui/`. `tui-app.ts` (main loop, attach handoff, verbs) and `tui-client.ts` (API, SSE, degraded enumeration) are the only IO; `tui-model`, `tui-layout`, `tui-render`, `tui-keys`, `tui-ansi`, `tui-composer`, `tui-approvals`, `tui-digest`, `tui-sse` and `tui-types` are pure and unit-tested, with an E2E suite driving the real binary under node-pty.
|
||||
- Deferred with the rest of phase 3: `r` (resume a RECENT row) is not wired up, so the help overlay does not advertise it.
|
||||
- Not started: phase 3 (mouse, `--pick` popup switcher, opt-in attach status line, OSC 9) and phase 4 (retiring the bash choosers).
|
||||
|
||||
The goal: replace Codeman's scattered terminal surfaces with one first-class TUI, `codeman tui`, that gives SSH/terminal users the same at-a-glance awareness the web UI gives browsers. The reference point is herdr (herdr.dev), the trending Rust "agent multiplexer" whose defining feature is a live agent-state sidebar. Codeman can match and beat that sidebar in the terminal because the states herdr infers from screen-scraping heuristics are states our server already computes from hooks, pane probing, and the approvals inbox.
|
||||
|
||||
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
# Terminal UI (`codeman tui`)
|
||||
|
||||
`codeman tui` is a full-screen dashboard for your Codeman sessions, in the terminal.
|
||||
It shows every session grouped by whether it needs you, lets you answer a permission
|
||||
dialog or send a prompt without switching anywhere, and puts you inside a session's
|
||||
tmux pane with one keystroke.
|
||||
|
||||
It is **additional, not a replacement**: the web UI stays the primary surface and
|
||||
gets every feature first. The TUI exists for the terminal workflow (SSH, Termius,
|
||||
a tmux window you keep open all day), and it is a *client* of the running server,
|
||||
so the two surfaces can never disagree about what a session is doing. It is also
|
||||
not a multiplexer: tmux still owns every pane, and attaching hands the terminal to
|
||||
tmux rather than proxying bytes.
|
||||
|
||||
## Starting it
|
||||
|
||||
```bash
|
||||
codeman tui # the dashboard
|
||||
codeman tui --list # print the numbered session list and exit
|
||||
codeman tui 2 # attach straight to session 2 of that list
|
||||
```
|
||||
|
||||
The two fast paths are the scriptable ones (they are the `sc -l` / `sc 2` shapes).
|
||||
Neither sets up a screen, so both are as quick as the one API call they make, and
|
||||
`--list` prints plain text when piped, so it composes with `grep`/`awk`.
|
||||
|
||||
What it needs:
|
||||
|
||||
| Needs | What you get |
|
||||
| --- | --- |
|
||||
| **Full features** | A running Codeman server (states, approvals, preview, prompts, search, digest). The TUI finds it the way `codeman attach` does: `CODEMAN_API_URL`, else loopback on `CODEMAN_PORT` for this `CODEMAN_INSTANCE`. The self-signed certificate an `--https` install generates is accepted, as it is everywhere else in the CLI. |
|
||||
| **Server down** | It still starts, in **degraded mode**: sessions are enumerated straight from `tmux -L codeman` plus a read-only peek at `state.json`, and attach is the only verb. See [Troubleshooting](#troubleshooting). |
|
||||
| **A terminal** | `codeman tui` refuses to run when stdin/stdout are not a TTY, and says to use `--list` instead. A cron job or a pipe therefore fails loudly rather than emitting escape codes into a log. |
|
||||
|
||||
## What it looks like
|
||||
|
||||
A real frame at 100x30 (`NO_COLOR`, trailing blank rows trimmed). The selected
|
||||
session has a pending permission dialog, so the preview pane leads with the card:
|
||||
|
||||
```
|
||||
codeman ⚠ 2 tnode · v1.19.0 · 5 sessions · 5h 32% · wk 61% ? help q quit
|
||||
NEEDS YOU ─────────────────────────│ w4-api-refactor · claude · /home/you/dev/api · blocked
|
||||
1 w6-docs ✋ 11m│ ⚠ requests: Bash(git push origin main)
|
||||
▶ 2 w4-api-refactor ⚠ 2m│ 1. Yes
|
||||
WORKING ───────────────────────────│ 2. Yes, and do not ask again
|
||||
3 w1-codeman ∗ 1h│ 3. No, tell Claude what to do
|
||||
4 w2-gallery ∗ 15m│ y approve · n deny · digit chooses
|
||||
IDLE ──────────────────────────────│
|
||||
5 w3-promo shell ○ 2h│ > refactor the api routes onto the shared port interface
|
||||
RECENT ────────────────────────────│
|
||||
6 api-hotfix ✔ 3d│ Read src/web/ports/session-port.ts (48 lines)
|
||||
│ Read src/api/routes.ts (312 lines)
|
||||
│ Edit src/api/routes.ts
|
||||
│ 1 -import { SessionManager } from "../session-manager.js";
|
||||
│ 2 +import type { SessionPort } from "../web/ports/session-
|
||||
│
|
||||
│ Bash(npm run typecheck)
|
||||
│ └ tsc --noEmit: no errors
|
||||
│
|
||||
│ ✻ Actualizing… (2m 14s · ↓ 12.3k tokens)
|
||||
↑↓ select · ⏎ attach · y approve · n deny · 1-9 option · p prompt · x kill · / search · g digest ·
|
||||
```
|
||||
|
||||
- **Header**: the machine, the server version, how many sessions are live, and the
|
||||
plan-usage chip (the same statusLine telemetry that feeds the web chip, when the
|
||||
server has a snapshot). A `⚠ n` badge counts pending approvals.
|
||||
- **Sidebar**: every session, grouped and numbered.
|
||||
- **Preview**: a live tail of the selected session, its own colors preserved, with
|
||||
the parsed dialog card on top when that session is blocked.
|
||||
- **Footer**: only the keys that work right now. `n` reads `n new` normally and
|
||||
`n deny` when the selected session has a dialog, because it cannot be both.
|
||||
|
||||
The same world through `--list`:
|
||||
|
||||
```
|
||||
1 waiting w6-docs /home/you/dev/docs
|
||||
2 blocked w4-api-refactor /home/you/dev/api
|
||||
3 working w1-codeman /home/you/dev/codeman
|
||||
4 working w2-gallery /home/you/dev/gallery
|
||||
5 idle w3-promo /home/you/dev/promo
|
||||
6 done api-hotfix /home/you/dev/api
|
||||
```
|
||||
|
||||
The numbers are the same on both surfaces, so `codeman tui --list` then
|
||||
`codeman tui 4` is one thought.
|
||||
|
||||
## The four groups
|
||||
|
||||
Groups are always in this order, and a session is in exactly one of them:
|
||||
|
||||
| Group | Glyph | Means | Comes from |
|
||||
| --- | --- | --- | --- |
|
||||
| **NEEDS YOU** | `⚠` | A permission or question dialog is blocking the agent | The approvals inbox (`permission_prompt` hooks, with the on-screen options parsed) |
|
||||
| | `✋` | Waiting for your next instruction, or errored | `idle_prompt`, or an errored session (equally something only a human clears) |
|
||||
| **WORKING** | `✻` animating | A turn is running | The same working classification the web dashboard uses |
|
||||
| **IDLE** | `○` | Live, but sitting there | |
|
||||
| **RECENT** | `✔` | A past session from the unified list | History rows, no live pane |
|
||||
|
||||
Ordering inside a group is "the one that has waited longest, first": blocked
|
||||
sessions sort by how long the dialog has been up, working sessions by when their
|
||||
turn started (the pane's last Enter, since a working pane repaints every second
|
||||
and would otherwise always look freshly started), and quiet ones by last activity.
|
||||
That is the ordering the web home screens already use.
|
||||
|
||||
The cursor sticks to a **session**, not a row number, so a session that jumps to
|
||||
NEEDS YOU does not drag your selection with it. The number beside each row is what
|
||||
`1-9` and `codeman tui <n>` mean, and it is renumbered on every re-sort.
|
||||
|
||||
When a new dialog appears, the terminal bell rings once, for that dialog only: the
|
||||
same item announced twice does not ring twice.
|
||||
|
||||
## Keymap
|
||||
|
||||
| Key | Does |
|
||||
| --- | --- |
|
||||
| `↑` `↓` or `j` `k` | Move the cursor. PageUp/PageDown jump five rows. |
|
||||
| `Enter` | Attach to the selected session (see [Attaching](#attaching)) |
|
||||
| `1`-`9` | Jump to that row and attach. When a dialog is on screen, a digit answers it instead (see below). |
|
||||
| `y` | Approve the selected session's dialog |
|
||||
| `n` | Deny it, or **start a new session** when there is no dialog |
|
||||
| `p` | Send one line to the selected session without attaching |
|
||||
| `x` | Kill the selected session, with a typed confirmation |
|
||||
| `/` | Search sessions, events and files |
|
||||
| `g` | Away digest: what happened while you were gone |
|
||||
| `?` | Help overlay |
|
||||
| `Esc` | Close whatever overlay is open |
|
||||
| `q` or `Ctrl+C` | Quit, restoring the screen you started with |
|
||||
|
||||
Inside the `p` composer and the `/` query: `←` `→` `Home` `End` `Delete`
|
||||
`Backspace` plus `Ctrl+A` / `Ctrl+E` / `Ctrl+U` / `Ctrl+W`, `Enter` to send or open,
|
||||
`Esc` (or `Ctrl+C`) to cancel. In the kill confirmation you retype the session name;
|
||||
anything else cancels. In the `n` pickers, type to filter, `Enter` chooses.
|
||||
|
||||
Verbs that need the server (`y`/`n`/`p`/`x`/`/`/`g`) say so in degraded mode
|
||||
instead of failing silently; `Enter` and `1-9` keep working.
|
||||
|
||||
### `p` sends exactly one line
|
||||
|
||||
The composer is a single line by design, ending in a carriage return: that is the
|
||||
input contract every Codeman path follows, because multi-line text breaks the
|
||||
agent's own composer. Pasted newlines become spaces rather than being rejected, so
|
||||
a paste cannot silently run a different command than the one you read.
|
||||
|
||||
## Answering approvals
|
||||
|
||||
This is the thing the terminal could not do before. Select a blocked session and:
|
||||
|
||||
- `y` approves.
|
||||
- `n` picks the parsed "No" option, or sends Esc when the dialog did not parse one.
|
||||
- A digit picks that numbered option, **but only a digit the dialog actually
|
||||
offers**. A digit with no matching option falls through to the list's own
|
||||
jump-and-attach binding, so it can never be typed at whatever has focus.
|
||||
|
||||
The answer goes through `POST /api/approvals/:id/answer`, which **re-captures the
|
||||
pane before it types anything**. If the dialog is no longer on screen (you answered
|
||||
it in tmux a moment ago, or the agent moved on), the server refuses with a 409 and
|
||||
the TUI says `that dialog is no longer on screen` rather than pressing a key into a
|
||||
live composer. The answer is scoped to the options the server parsed off the actual
|
||||
frame, never to a guess.
|
||||
|
||||
An idle prompt (`✋`) is not a dialog: there is nothing to approve, so `p` is the
|
||||
reply path and the footer says `p reply` instead of `p prompt`.
|
||||
|
||||
## Attaching
|
||||
|
||||
`Enter` suspends the dashboard (main screen back, cooked mode back) and hands the
|
||||
terminal to tmux with `stdio: inherit`. Colors, mouse and paste are tmux's, at full
|
||||
fidelity. Detach with **`Ctrl+B D`** (tmux's default prefix, which Codeman does not
|
||||
change for local sessions) and the dashboard comes back and refreshes.
|
||||
|
||||
Three cases:
|
||||
|
||||
| Where you are | What happens |
|
||||
| --- | --- |
|
||||
| Not in tmux | `tmux -L codeman attach-session` |
|
||||
| Already in tmux on Codeman's socket | `switch-client`, so you do not nest |
|
||||
| In tmux on a **different** socket | Refused, with an explanation: detach first (`Ctrl+B D`), then run `codeman tui` again |
|
||||
|
||||
A RECENT row and a direct-PTY session have no pane to attach to, and say so.
|
||||
|
||||
`x` never bulk-kills: it kills one session, only after you retype its name, never a
|
||||
history row, and never the session the TUI itself is running in.
|
||||
|
||||
## Over SSH, and on a phone
|
||||
|
||||
The TUI is an ordinary terminal program with no local dependencies beyond tmux, so
|
||||
`ssh box` then `codeman tui` works exactly like running it locally. There is no
|
||||
separate remote mode.
|
||||
|
||||
Below 72 columns (Termius, an iPhone in portrait) the preview pane is dropped and
|
||||
rows take two lines each: the same constraint the `sc` chooser was built around,
|
||||
now with a cursor, live states and the answer/prompt/kill verbs. The switch is
|
||||
width-driven at draw time, so unfolding a foldable or resizing a window re-lays out
|
||||
immediately; there is no mode flag to set.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"The Codeman server rejected these credentials."** The server has
|
||||
`CODEMAN_PASSWORD` set. Export `CODEMAN_PASSWORD` (and `CODEMAN_USERNAME` if it is
|
||||
not `admin`), or put them in the data dir's `.env` (`~/.codeman/.env`), which is
|
||||
where `codeman attach` already reads them from.
|
||||
|
||||
**`server not running: attach only`** in a yellow banner. Nothing answered on the
|
||||
expected port, so the TUI fell back to enumerating tmux. You get names and attach;
|
||||
you do not get states, approvals or previews, because those only exist on the
|
||||
server. Start the server (`codeman web -d`, or `systemctl --user start codeman-web`)
|
||||
and the banner clears on its own: the TUI keeps re-probing.
|
||||
|
||||
**It found the wrong server, or none.** Discovery is instance-scoped. A beta
|
||||
instance (`CODEMAN_INSTANCE=beta`) has its own data dir *and* its own tmux socket,
|
||||
so its TUI sees only its own sessions. Set `CODEMAN_PORT` or `CODEMAN_API_URL`
|
||||
explicitly when you run more than one.
|
||||
|
||||
**"this terminal is already inside tmux on socket ..."** You are in a tmux session
|
||||
on a socket that is not Codeman's, so attaching would nest two multiplexers whose
|
||||
prefix keys collide. Detach (`Ctrl+B D`) and run `codeman tui` from outside.
|
||||
|
||||
**Boxes and glyphs render as garbage.** The TUI picks a glyph tier from the
|
||||
environment: no `TERM` (or `dumb`), or a non-UTF-8 locale, gets the ASCII set
|
||||
(`[!] [w] [*] [-]`, `+`/`-`/`|` frames). Force it either way with
|
||||
`CODEMAN_TUI_GLYPHS=ascii|unicode|nerd`.
|
||||
|
||||
**Colors.** Standard `NO_COLOR` / `FORCE_COLOR` handling (chalk's, the same as the
|
||||
rest of the CLI). Under `NO_COLOR` the frame is cursor addressing and text only,
|
||||
and the preview's own colors are stripped too, so a session's output cannot repaint
|
||||
the dashboard.
|
||||
|
||||
**It refuses to open at all**, saying it needs an interactive terminal. stdout or
|
||||
stdin is not a TTY. That is the guard: use `codeman tui --list`.
|
||||
|
||||
## Related
|
||||
|
||||
- [`docs/tui-plan.md`](tui-plan.md): the design record. Why hand-rolled ANSI, why a
|
||||
client and not a second brain, and what is deliberately deferred.
|
||||
- [`docs/approvals-inbox-plan.md`](approvals-inbox-plan.md): where the parsed
|
||||
dialogs and the answer endpoint come from.
|
||||
- [`docs/remote-sessions.md`](remote-sessions.md): remote-SSH cases, which the TUI
|
||||
lists like any other session.
|
||||
Reference in New Issue
Block a user