The app layer repaints on state change, so the store has to be able to
say that something changed: `revision` is bumped by every mutating
method, and the repaint test compares it against the last painted frame.
Without it an idle dashboard would either redraw on a timer or go stale.
Three additions come with it, all optional so nothing existing changes
shape: `TuiSessionRow.muxName` (the unified list carries no mux name, so
the app fills it in from the local tmux enumeration and a row without one
cannot be attached), a `new-session` UI mode, and `TuiPickerState`, the
one-column chooser behind `n`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Everything the dashboard needs from outside the process, behind one typed
surface, so the app loop stays a loop. It is a client of the running server and
nothing else: rows come from the unified list, blocked states from the
approvals inbox, and answering goes through the endpoint that re-captures the
pane and refuses with a 409 when the dialog has already been answered in tmux.
That refusal is a typed result rather than an exception, because a human
beating you to a prompt is normal operation.
Discovery mirrors the daemon probe (`CODEMAN_API_URL`, else loopback on
`CODEMAN_PORT`, self-signed TLS accepted) and credentials come from where
`codeman attach` already reads them. An explicit port outranks the ambient
`CODEMAN_API_URL`, which every managed session exports: a caller that named a
port must not be redirected at whatever server owns its shell.
Input is single-line and `\r`-terminated at this layer, so no caller can strand
text on an unsubmitted composer, and each send is tagged for the server's
exactly-once path. The event stream defaults to a `?sessions=` filter that
matches nothing, which drops the terminal firehose while lifecycle, hook and
approval events still arrive. A silent-but-open stream is caught by a watchdog
rather than a socket error, since that failure mode reports nothing at all.
With no server answering, sessions are listed from tmux on the instance socket
(argv, never a shell string) and decorated from a read-only peek at state.json,
which keeps the "the server died, get me to my sessions" path alive.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node has no EventSource, so the live-update stream is read as raw bytes and
decoded here. Three details are what the parser exists for: a TCP read can end
between the CR and the LF of a CRLF, so a trailing CR is held back rather than
dispatched; the tunnel padding the server appends after a frame is a comment
with no blank line after it and must not split anything; and the keepalive is a
NAMED event, because an SSE comment is invisible to a browser client by spec.
Event classification lives here too, as a set rather than a prefix test:
`session:terminal` is most of the stream and the preview pane pulls its own
tail, so it is deliberately not a resync trigger.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One absolutely-addressed line per row, each closed with an erase-to-end, so
nothing scrolls and a repaint cannot leave the previous frame's tail behind.
The caller wraps the result in synchronized-output brackets; that is an IO
decision and stays out of the renderer.
Color is passed in rather than detected. chalk's detection is right for the
one-shot CLI but would make a frame non-deterministic, so the palette is raw
SGR in the same semantic roles cli-style uses, and `color: false` emits nothing
but the cursor addressing, the session's own colors in the preview included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Below 72 columns the preview pane is dropped and rows take two lines, the
constraint the `sc` chooser was built around and the reason it is still usable
on a phone; above it a clamped sidebar carries the list and the preview takes
the rest.
Every region is clamped to a non-negative size, so a 5x5 terminal degrades to a
header instead of handing the renderer negative widths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows are the ones GET /api/sessions/unified already returns and blocked states
are the items the approvals inbox already parsed, both imported as types only
so a CLI process pulls in neither the server nor node-pty. Classification
speaks the web UI's language (red blocked, yellow waiting, green working) so a
user with both surfaces open never has to translate between them.
Groups order by how long a session has been in its state, which is why WORKING
anchors on the pane's last Enter: a working pane repaints about once a second,
so its last-activity stamp always says "now".
Selection is tracked by session id, never by row index: rows re-sort under the
cursor whenever a session starts working or an approval lands, and an
index-tracked cursor would quietly move the selection to another session
between two keystrokes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Decodes printable UTF-8, the control keys, arrows in both CSI and SS3 forms and
SGR mouse reports out of a byte stream that can tear anywhere, so a sequence
split across two reads decodes the same as one that arrives whole.
A lone ESC cannot be told from the start of an arrow key by looking at bytes,
so the parser holds it and the caller resolves it with flush() once its
disambiguation timer fires. Unknown sequences are swallowed: a stray CSI must
never reach a prompt composer as typed text.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The preview pane shows a session's raw terminal stream, so it needs the tail
reconstructed rather than emulated: SGR survives, cursor steering and OSC do
not, and a carriage return returns to column 0 so a spinner that repaints its
line 200 times contributes one line instead of 200.
Widths count East Asian Wide characters as two columns, which the clip and pad
helpers rely on to never cut a wide character, a code point or an escape
sequence in half.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Remove TUI from git (moved to .gitignore for local development)
- Remove React/Ink dependencies (unused in published version)
- Remove tui command from CLI
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename inner-loop-tracker.ts → ralph-tracker.ts throughout codebase
- Add ralph-config.ts for parsing .claude/ralph-loop.local.md config
- Standardize API error responses using createErrorResponse()
- Add input validation for auto-compact/auto-clear thresholds
- Update UI labels to "Ralph / Todo Tracker" consistently
- Add 46 integration tests for Ralph tracking functionality
- Update test badge to 438 total tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add getFullScreenId() to get PID.screenName format for unambiguous attach
- Update DirectAttach.ts and index.tsx to use full screen IDs
- Remove TUI documentation from README (still in development)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add killAllScreensAndClaude() function to useSessionManager
- Kills all tracked sessions, orphan claudeman screens, and Claude CLI processes
- Press 'D' (Shift+d) in sessions view to trigger
- Update HelpOverlay with new shortcut documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add state store flush on shutdown to prevent data loss from debounced saves
- Add error handling in session exit handler to ensure cleanup always runs
- Add 5s timeout to all TUI execSync calls to prevent hangs
- Cache screen -ls output with 100ms TTL (90% reduction in execSync calls)
- Add token pre-check to skip expensive regex when "token" not in data
Based on findings from security, performance, and reliability analysis.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TUI now checks if web server is running on startup and offers to start
it in the background. Added new CLI options: --with-web (auto-start),
--no-web (skip check), -p (port).
TUI feature parity with web interface:
- Shell mode: press 'h' in cases view to start bash instead of Claude
- Multi-start: press 'm' to start 1-20 sessions at once
- Respawn toggle: Ctrl+R to enable/disable respawn on Claude sessions
- Session rename: API support via useSessionManager hook
Security fixes from previous analysis:
- Command injection prevention in screen-manager.ts
- Path traversal protection in server.ts
- Input validation for shell-interpolated values
Also fixes memory leak in session.ts (timer tracking) and flaky test
timeout in session-cleanup.test.ts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Handle SIGINT/SIGTERM signals properly
- Unmount Ink app before exit
- Restore cursor visibility and clear screen on exit
- Use try/finally to ensure cleanup runs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused imports (Text, execSync, writeFileSync, React)
- Move n/r/q key handlers from App.tsx to StartScreen component
- Mark unused onClose prop in HelpOverlay with underscore prefix
- Consolidate keyboard handling in components that display the controls
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Improves documentation across all TUI files:
- App.tsx: Main component with keyboard shortcuts reference
- index.tsx: Entry point with usage examples
- useSessionManager.ts: Hook with detailed API documentation
- All components: Props, returns, and behavior descriptions
- Helper functions: Parameter and return type documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Poll session API every 2 seconds for respawn status
- Display respawn state and cycle count in StatusBar
- Show respawn status in magenta to match web UI
- Format state (underscores to spaces) for readability
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Poll inner loop state from state-inner.json every 500ms
- Display RalphPanel when loop is enabled and has data
- Auto-adjust terminal height when panel is shown
- Add innerLoopState and innerTodos to session manager hook
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Display Ralph loop status (active/idle)
- Show completion phrase, cycle count, elapsed time
- Display todo list with status icons (pending/in_progress/completed)
- Truncate long todo items, limit to 5 visible
- Use magenta border to match web UI styling
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Show session name prominently in status bar
- Add optional inputMode prop for visual feedback
- Update keyboard hints to indicate typing sends input
- Reorganize status bar layout for better readability
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Press 'd' or 'x' to delete/kill selected session
- Auto-adjust selection index when sessions are removed
- Update footer controls with delete shortcut
- Update help overlay with delete shortcut
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Start Screen section with arrow navigation and attach
- Separate Main View into Navigation and Session Management
- Include all new shortcuts (Enter, a, arrows)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add arrow key navigation in StartScreen (up/down to move)
- Highlight selected session with blue background
- Press Enter to view session in TUI mode
- Press 'a' to attach directly to GNU screen session
- Attach exits TUI and inherits stdio for full terminal control
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Poll screen hardcopy every 500ms for live output
- Clean up temp files after reading
- Update CLAUDE.md with TUI documentation
- Add TUI command examples to Commands section
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a full TUI for Claudeman using Ink (React for CLI):
- StartScreen: Shows existing sessions from screens.json
- TabBar: Session tabs with status indicators
- TerminalView: PTY output display with scrolling
- StatusBar: Session info and keyboard shortcuts
- HelpOverlay: Full keyboard shortcut reference
Features:
- Session discovery from ~/.claudeman/screens.json
- Tab navigation (Ctrl+Tab, Ctrl+1-9)
- Create new sessions (connects to web API)
- Kill sessions via screen commands
- Keyboard-driven navigation
Usage: claudeman tui
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>