Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7d18075
docs: TUI rework plan (codeman tui, herdr research)
claude Aug 16, 2026
ddcfac5
feat: shared CLI style kit
claude Aug 16, 2026
e48dd35
fix: measure the doctor table columns and let the CLI paint them
claude Aug 16, 2026
5893f93
feat: wire the CLI through the style kit (doctor colors, spinners, co…
claude Aug 16, 2026
d764917
fix: color the server startup line and its security warning
claude Aug 16, 2026
0d3b13e
test: derive the CLI inventory from the real commander program
claude Aug 16, 2026
6f9fb10
docs: fix the codeman attach description and the detach prefix
claude Aug 16, 2026
5e5d504
chore: stop ignoring src/tui
claude Aug 16, 2026
3c3a8f8
feat: add the TUI's SGR-aware preview helpers
claude Aug 16, 2026
f161c4e
feat: add the TUI raw-mode key parser
claude Aug 16, 2026
44ad932
feat: add the TUI session model, classification and cursor
claude Aug 16, 2026
b8d00b7
feat: add the TUI responsive layout math
claude Aug 16, 2026
f0138fb
feat: render TUI frames from the model and layout
claude Aug 16, 2026
4060696
docs: record why the key parser reads LF as Enter
claude Aug 16, 2026
00ef72c
refactor: resolve the tmux socket from the instance config
claude Aug 16, 2026
6b7fe7b
feat: decode the SSE wire format for the TUI
claude Aug 16, 2026
b828920
feat: add the TUI's API, SSE and degraded-mode client
claude Aug 16, 2026
c25aaca
feat: give the TUI model a revision signal and picker state
claude Aug 16, 2026
658aa28
feat: render the TUI picker overlay and a caller-supplied keymap
claude Aug 16, 2026
8fd57dd
feat: add the codeman tui dashboard
claude Aug 16, 2026
941b11f
feat: register the tui command with its two fast paths
claude Aug 16, 2026
b39bb40
test: drive codeman tui end to end under a pty
claude Aug 16, 2026
bfc7e69
feat: add the TUI's editor, approval and digest pure cores
claude Aug 16, 2026
ff9735f
feat: hold composer, search and digest state in the TUI model
claude Aug 16, 2026
36d1ef6
feat: render the approval card, composer, search and digest
claude Aug 16, 2026
f644ce3
feat: answer approvals and send prompts from the dashboard
claude Aug 16, 2026
4696c38
test: drive the phase-2 verbs end to end under a pty
claude Aug 16, 2026
39a1337
fix: read a row-addressed repaint as lines in the preview
claude Aug 16, 2026
d107715
fix: drop stale approvals and re-check the preview when the world cha…
claude Aug 16, 2026
ee3ebc0
fix: keep the plan-usage chip across a degraded-to-connected upgrade
claude Aug 16, 2026
9e3e446
docs: document codeman tui
claude Aug 16, 2026
03f5f56
fix: drop the two keymap and style entries nothing reaches
claude Aug 16, 2026
15cb142
fix: cover tui in the CLI inventory and drop the em-dashes it printed
claude Aug 16, 2026
b555d2b
docs: extend the instance-isolation rule to tmux socket resolution
claude Aug 16, 2026
715b484
fix: date a working row by its turn, not by the session age
claude Aug 17, 2026
0a80671
refactor: drop the two store members nothing consults
claude Aug 17, 2026
c9b3d51
perf: pace the refetch and back the tail poll off a quiet pane
claude Aug 17, 2026
61d84ac
fix(tui): make an attach fit the terminal, show the way out, and resu…
claude Aug 19, 2026
1fea9d1
fix(tui): advertise the key that actually detaches, and stop tmux pai…
claude Aug 19, 2026
1b6cab1
fix(tui): sweep an attach status bar a killed terminal left behind
claude Aug 19, 2026
6cce77e
fix(tui): refuse to attach to a dead pane, and stop naming sessions a…
claude Aug 19, 2026
027e40f
fix(tui): make the detach chord work when Ctrl is never released
claude Aug 19, 2026
20cd47e
feat(tui): leave an attach with ONE key, F12, and no modifier
claude Aug 19, 2026
643edc6
feat(tui): offer r to resume a session whose pane has died
claude Aug 19, 2026
ecce1e2
fix(tui): fold rare prompt glyphs in the preview so they stop renderi…
claude Aug 19, 2026
fea8fb1
feat(tui): switch sessions with the web UI's shortcuts
claude Aug 19, 2026
5202eac
fix(tui): start a session straight into it, and drop two unsafe glyphs
claude Aug 19, 2026
ed4d0da
fix(tui): confirm a kill with y, and make the dialog say what it woul…
claude Aug 19, 2026
fb3f847
fix(tui): stop drawing from the unicode blocks a plain terminal font …
claude Aug 19, 2026
4eb13ba
feat(tui): keep the session tabs visible inside a pane, and move the …
claude Aug 20, 2026
c18ef66
fix(tui): keep the way out on the bar, and make Alt+1..9 actually switch
claude Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,6 @@ packages/gesture-control/.vite/
# Claude Code plan tracking
plan.json

# Unfinished TUI (local development only)
src/tui/
.claude/
media-assets/
commands
Expand Down
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph

| Task | Command |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Terminal dashboard | `codeman tui` (`--list` prints the numbered list and exits, `codeman tui <n>` attaches to row n; both short-circuit before any screen setup). Needs a TTY; without a server it starts attach-only. `docs/tui.md` |
| Dev with TLS | `npx tsx src/index.ts web --https` |
| Override window title hostname | `npx tsx src/index.ts web --title-hostname <name>` (default: `os.hostname()` — `codeman:<name>` is used for tab title, title-flash, and OS desktop notification prefix) |
| Bind a non-loopback host | `npx tsx src/index.ts web --host 0.0.0.0` (or `-H`; env `CODEMAN_HOST`; default `127.0.0.1`). Without `CODEMAN_PASSWORD` it **starts but warns loudly** — see Common Gotchas + `docs/security-architecture.md` |
Expand Down Expand Up @@ -131,7 +132,7 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph
- **Zod `.optional()` rejects `null`** — accepts `undefined` only. When the frontend builds a request body with `JSON.stringify`, an explicit `null` field is preserved on the wire and fails validation with `INVALID_INPUT`. Convert `null` → `undefined` before stringifying (e.g. `field: value ?? undefined`), or declare the schema `.nullish()`. This has caused real shipped bugs twice
- **`xterm-zerolag-input` is single-source** — BOTH echo addons live ONLY in `packages/xterm-zerolag-input/src/`, bundled into TWO **gitignored** vendor files: `vendor/xterm-zerolag-input.js` (buffer overlay, entry `zerolag-input-addon.ts`) and `vendor/xterm-predictive-echo.js` (codex write-through, entry `predictive-echo-addon.ts`) — dev by `scripts/postinstall.js`, prod by `scripts/build.mjs`. `app.js`/terminal-ui.js only **consume** them via `new LocalEchoOverlay(terminal)` / `new PredictiveEchoOverlay(terminal)`; there is no inline copy. So: change the package source, then rerun the bundle step (`npm install` for dev, `npm run build` for prod). **Never hand-edit `app.js` for overlay behavior, and never commit the gitignored vendor bundles.** Always test on mobile after touching it. → [architecture-invariants#xterm-zerolag-input-is-single-source](docs/architecture-invariants.md#xterm-zerolag-input-is-single-source), `docs/local-echo-overlay-plan.md`
- **Default bind is loopback-only; non-loopback without a password starts but warns** — the server defaults to `--host 127.0.0.1`. Binding non-loopback (`--host`/`-H`/`CODEMAN_HOST`) without `CODEMAN_PASSWORD` starts anyway but prints a loud warning; `--allow-unauthenticated-network` / `CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK=1` acknowledges it. ⚠️ The production systemd unit passes no `--host`, so prod binds **localhost only**: reach it via `tailscale serve`/tunnel to `127.0.0.1`. A loopback bind is reachable through a same-host tunnel but NOT by a browser hitting the box's LAN IP. `install.sh` is separate and prompts for the binding (defaulting to LAN + a password), and preserves the existing binding on re-runs. → [architecture-invariants#default-bind-and-the-non-loopback-warning-path](docs/architecture-invariants.md#default-bind-and-the-non-loopback-warning-path), `docs/security-architecture.md`
- **Instance isolation / multi-instance attach danger** — the data dir (`~/.codeman`) and tmux socket (`tmux -L codeman`) are PROCESS-WIDE and shared by every Codeman on the machine, derived from `CODEMAN_INSTANCE` via `src/config/instance.ts`. ⚠️ A 2nd instance on the SAME socket **discovers and attaches PTYs to the first instance's live sessions**, resizing and mutating them. `$HOME` isolation is NOT enough because tmux is system-global. To run two instances, give each a distinct `CODEMAN_INSTANCE` (scopes dir + socket together), or set `CODEMAN_TMUX_SOCKET` + `CODEMAN_DATA_DIR` individually; `scripts/run-beta.sh` does this for a beta alongside prod. **Any new `~/.codeman/...` path MUST go through `dataPath()`**, never `join(homedir(), '.codeman', …)`. → [architecture-invariants#instance-isolation-and-the-multi-instance-attach-danger](docs/architecture-invariants.md#instance-isolation-and-the-multi-instance-attach-danger)
- **Instance isolation / multi-instance attach danger** — the data dir (`~/.codeman`) and tmux socket (`tmux -L codeman`) are PROCESS-WIDE and shared by every Codeman on the machine, derived from `CODEMAN_INSTANCE` via `src/config/instance.ts`. ⚠️ A 2nd instance on the SAME socket **discovers and attaches PTYs to the first instance's live sessions**, resizing and mutating them. `$HOME` isolation is NOT enough because tmux is system-global. To run two instances, give each a distinct `CODEMAN_INSTANCE` (scopes dir + socket together), or set `CODEMAN_TMUX_SOCKET` + `CODEMAN_DATA_DIR` individually; `scripts/run-beta.sh` does this for a beta alongside prod. **Any new `~/.codeman/...` path MUST go through `dataPath()`**, never `join(homedir(), '.codeman', …)`, and **any new `tmux -L` caller through `resolveTmuxSocketName()`** (both in `config/instance.ts`): the TUI shells out to tmux from a second process, and a hardcoded `codeman` there would point a beta instance at prod's panes. → [architecture-invariants#instance-isolation-and-the-multi-instance-attach-danger](docs/architecture-invariants.md#instance-isolation-and-the-multi-instance-attach-danger)
- **node-pty's macOS `spawn-helper` ships without `+x`** (issues #6, #204): `node-pty@1.1.0` publishes `prebuilds/darwin-<arch>/spawn-helper` as mode 0644, and macOS launches every PTY through it, so a stock macOS install fails every session start with `Error: posix_spawnp failed.` **Linux can never reproduce it**: `spawn-helper` is an `OS=="mac"` gyp target and node-pty ships no Linux prebuild, so node-gyp always emits an executable helper there. ⚠️ Look in **`prebuilds/<platform>-<arch>/`**, not just `build/Release/`, which does not exist on macOS. Repair is a chmod, never a mandatory rebuild (that would require Xcode CLI tools and deletes `prebuilds/` before compiling): `npm run fix:node-pty` chmods every helper then proves it by really opening a PTY. `spawnPtyWithHelperRepair()` (`utils/node-pty-repair.ts`) wraps every `pty.spawn()` in `session.ts` and self-heals a broken install on the first failure. → [architecture-invariants#node-ptys-macos-spawn-helper-must-be-executable](docs/architecture-invariants.md#node-ptys-macos-spawn-helper-must-be-executable)
- **Headless screenshots: `deviceScaleFactor` MUST be 1, and write unique filenames** — under DSF=2 xterm's WebGL renderer draws glyphs at ~2× nominal size while still *reporting* nominal cell dims, so only the pixels reveal it and only the terminal font looks wrong. And overwriting a fixed output path leaves OS image viewers showing the old render, which reads as "the fix didn't work"; `scripts/capture-real-overview.mjs` mints a timestamped filename per run. Seed the per-device `localStorage` keys (`codeman:skin`, `codeman-font-size`, `codeman-app-settings`) so the capture matches a real device. → [architecture-invariants#headless-screenshot-capture](docs/architecture-invariants.md#headless-screenshot-capture)

Expand All @@ -143,7 +144,8 @@ Codeman is a Claude Code session manager with web interface and autonomous Ralph

| Domain | Key files | Notes |
| ---------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Entry** | `src/index.ts`, `src/cli.ts`, `daemon-control`, `service-installer`, `config/service-names` | The last three back `web -d` / `service install` |
| **Entry** | `src/index.ts`, `src/cli.ts`, `daemon-control`, `service-installer`, `config/service-names`, `cli-style` | The last three back `web -d` / `service install`; `cli-style` is the shared palette/table/spinner/confirm kit |
| **TUI** | `src/tui/`: `tui-app` ★ + `tui-client` (the only IO) over a pure core (`-model`, `-layout`, `-render`, `-keys`, `-ansi`, `-composer`, `-approvals`, `-digest`, `-sse`, `-types`) | `codeman tui`, a CLIENT of the server, never a second brain. Design doc: `docs/tui-plan.md`; user guide `docs/tui.md` |
| **Session** | `src/session.ts` ★, `session-manager`, `session-auto-ops`, `session-cli-builder`, `session-task-cache`, `session-order` (pure), `session-pty-exit-breaker`, `usage-limit-patterns`, `usage-telemetry`; `src/services/unified-session-service.ts` | Pure/unit-tested helpers are split out of `session.ts` on purpose |
| **Mux** | `src/mux-interface.ts`, `src/mux-factory.ts`, `src/tmux-manager.ts` ★ | |
| **Respawn** | `src/respawn-controller.ts` ★ + 4 helpers (`-adaptive-timing`, `-health`, `-metrics`, `-patterns`) | Read `docs/respawn-state-machine.md` first |
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,22 @@ These run for **every** request — before auth, even on the default no-password

---

## Terminal UI (`codeman tui`)

A full-screen dashboard for your sessions, in the terminal. Same states as the web UI, because it is a client of the same server:

```bash
codeman tui # the dashboard
codeman tui --list # numbered session list, then exit (scriptable)
codeman tui 2 # attach straight to session 2 of that list
```

Sessions are grouped **NEEDS YOU → WORKING → IDLE → RECENT**, longest-waiting first. `↑↓`/`j`/`k` select, `Enter` attaches into the tmux pane (`Ctrl+B D` to come back), `1`-`9` jump-attach. `y`/`n`/digit answer a pending permission dialog right from the list, `p` sends a one-line prompt, `n` starts a session, `x` kills one after a typed confirmation, `/` searches, `g` shows the away digest, `?` is help, `q` quits. Below 72 columns it drops the preview pane and becomes a single-column list, so it stays usable in Termius on a phone. With no server running it still starts in attach-only degraded mode.

The web UI remains the primary surface; see **[docs/tui.md](docs/tui.md)** for the full guide.

---

## SSH Alternative (`sc`)

If you prefer SSH (Termius, Blink, etc.), the `sc` command is a thumb-friendly session chooser:
Expand All @@ -668,7 +684,7 @@ sc 2 # Quick attach to session 2
sc -l # List sessions
```

Single-digit selection (1-9), color-coded status, token counts, auto-refresh. Detach with `Ctrl+A D`.
Single-digit selection (1-9), color-coded status, token counts, auto-refresh. Detach with `Ctrl+B D` (tmux's default prefix, which Codeman does not change for local sessions).

---

Expand Down Expand Up @@ -893,7 +909,9 @@ codeman session start -d /path/to/repo # (s) start a session
codeman session list # list sessions
codeman session logs <id> # tail output
codeman task add "fix the failing test" # (t) queue a task
codeman attach <path> # attach a Claude hook context
codeman attach <path> # show an attachment card for a local file
codeman tui --list # numbered session list (plain text when piped)
codeman tui 3 # attach to session 3 of that list
```

### Hooks (events flowing _back_ to Codeman)
Expand Down
Loading
Loading