Checklist-style backlog organized for small, reviewable steps.
-
P0: Observability + failure transparency (must-do, iterate until DONE)
- Runner logging regression + output forwarding: centaurx serve must emit server-side logs for every runner command/exec (Info/Debug/Trace), include stdout+stderr forwarding, default runner LOG_MODE=json, and add regression tests + log reader that assert logs across the server↔runner flow.
- COMPREHENSIVE LOGGING EVERYWHERE: add Warn/Info/Debug/Trace coverage across the codebase (HTTP, SSH, runner, service, repo, persistence, shipohoy, UI).
- Surface runner failures to user buffers (system output) with clear, actionable messages (e.g. codex auth missing/401, runner exit, container start failure).
- Make
/newand prompt submission emit user-facing status lines while work is in-flight (starting runner, creating repo, cloning, git init, etc.). - Ensure runner errors are logged server-side with root cause (gRPC status, exit codes, stderr, container logs).
- Expand tests to assert failure paths are surfaced (e.g. codex 401, runner not starting) and that user-visible output/logs appear.
- Add tests that prove codex auth failure yields explicit errors (not silent blank output).
- Runner lifecycle hygiene: runner containers must not linger after server exit/crash.
- Implement server-side shutdown cleanup (close all runner tabs on Stop).
- Add runner keepalive (ping every 10s; exit after 3 missed) so orphaned runners self-terminate.
- Add tests that verify runners stop on server shutdown and auto-remove cleans them up.
- Context plumbing fix: eliminate detached
context.Background()usage that prevents cancellation; plumb request/session context through runner commands and codex exec so shutdown/timeout cancels correctly (SSH + HTTP + runner).- Replace background contexts in
internal/command/handler.goandcore/service.gowith caller contexts or derived timeouts. - Add regression tests that ensure cancellation propagates (e.g. HTTP request cancel does not orphan runner; service shutdown stops in-flight operations).
- Replace background contexts in
- Regression tests for runner repo root mapping: add unit tests that fail if
runner.repo_rootis set to a host path (e.g. equalsrunner.host_repo_root), and ensurerunnercontainer.NewProviderrefuses host paths whenHostRepoRootis set.
-
P0: TUI/Web theming + formatting (outrun default, future palettes)
- SSH TUI tab bar redesign (approach A):
- Render a full-width colored tab bar (solid background across entire top line) so output cannot overdraw it.
- Active tab is indicated by background color change (no
[]brackets). - Introduce an ANSI-aware tab renderer that trims by visible width without cutting escape codes.
- Sanitize output lines for SSH TUI rendering (strip ANSI/control sequences like
\x1b[and bare\r) to prevent top-bar corruption. - Add regression tests for: tab bar always renders on line 1; active tab background color changes; ANSI/control sequences in output cannot hide the bar.
- Theme registry:
- Default theme name
outrun(formerlyoutrun-electric). - Additional palettes registered:
gruvbox,tokyo-midnight(selectable but not necessarily wired to config yet). -
/theme <name>command supported in SSH TUI and HTTP UI.
- Default theme name
- Stream formatting tweaks:
- Remove
stderr:prefix; instead render stderr lines in a high-contrast pink (outrun palette) with bold styling in SSH TUI. - Ensure error lines remain red across both SSH and web UI.
- Remove
- Markdown rendering (SSH TUI + web UI) per
MESSAGES.md:-
item.type: command_executionstays plain (no markdown), but add/togglefullcommandoutput(default terse: max 5 lines of command + output) with mode toggle persisted per user/session. -
item.type: reasoningrender markdown; default italic; bold is bold-italic; apply distinct outrun blue/pink color (not same as agent text). -
item.type: agent_messagerender markdown; bold/italic styles; inline code in accent color. - Decide whether to adopt a markdown renderer or a minimal internal parser (document the choice + tests).
-
- Worked-for separator line before final
item.completedagent_message:- Add a full-width divider (e.g.
─ Worked for 19s ─────) in SSH TUI. - Add the same in web UI (CSS-driven full-width rule).
- Compute duration from prompt start to final item completion; show
s/m/has appropriate.
- Add a full-width divider (e.g.
- Web UI TAB key behavior:
- TAB should only cycle between tabs (not move focus between input/terminal/etc).
- Add chromedp regression test asserting TAB cycles to the next tab.
- Web UI multiline input scrollbar should be themed (match current palette; no default white scrollbar).
- SSH TUI tab bar redesign (approach A):
-
P0: SSH auth hardening + pubkey management
- Require SSH pubkey authentication and TOTP (prompt after successful pubkey auth).
- SSH TOTP prompt must be exactly
Verification code:(no echo). - Do not auto-generate SSH login pubkeys on user creation; new users have zero login pubkeys.
- Add admin CLI pubkey management (add/list/remove) for users.
- Add user commands:
-
/addloginpubkey <pubkey> -
/listloginpubkeys -
/rmloginpubkey <id>(1-based in insertion order) -
/pubkey(print git SSH public key for use in GitHub, etc.)
-
- Tests: SSH auth flow requires pubkey + TOTP; prompt text and no-echo behavior asserted.
- Tests: anonymous users rejected; wrong pubkey rejected; missing/wrong TOTP rejected; any valid pubkey for a user succeeds.
- Tests: only pubkeys associated with the username are accepted (no cross-user auth).
-
P0: Terminal UX regression (copy/paste)
- Fix SSH TUI selection so mouse drag selection persists and can be copied (avoid redundant redraws that clear selection).
- Add regression test that ensures SSH TUI does not redraw when state is unchanged (prevents selection loss).
-
P1: Session UX + tab isolation
-
/renewcommand: renew the current tab by starting a fresh codex session (new session ID) without changing repo/tab name. - Tab switching is session-scoped: active tab changes in one SSH session do not affect other SSH sessions or the web UI (and vice versa).
-
-
Phase 0: Repo hygiene and scaffolding
- Add
doc.goto each new package (Go package docs). - Create
.golangci.ymlin repo root if missing (per AGENTS.md). - Establish module layout:
-
cmd/centaurx(CLI) -
centaurx(root compositor) -
core(service API + implementation) -
schema(domain data types) -
httpapi(HTTP API + UI) -
sshserver(SSH server) -
internal/...for implementation details
-
- Refactor package layout to avoid import cycles (core/schema split + root compositor)
- Add
-
Phase 1: Core domain model (transport-agnostic)
- Define core types:
-
Tab,TabID,TabName,SessionID,RepoRef -
Buffer(scrollback) with cursor and scroll position -
Eventtypes for parsed codex JSONL -
Rendererinterface for SSH/HTTP formatting -
Command/SlashCommandrouting
-
- Implement tab manager:
- Create, close, switch, list
- Per-user tab registry
- Scrollback persistence per tab
- Implement repo manager:
- Repo prefix config
-
/newcreates repo dir +git init -
/repovalidatesrepo/.git -
/listreposenumerates repo dirs
- Define core types:
-
Phase 2: Codex exec runner + JSONL parsing
- Implement
codex execrunner:- Start new session (
codex exec --json <prompt>) - Resume existing session (
codex exec resume <id> --json <prompt>) - Thread ID capture from
thread.started - Per-tab model selection
- Support prompt via stdin using
-
- Start new session (
- JSONL parser:
- Discriminated union on
type - Tolerant to unknown fields/types
- Parse
item.*types (agent_message,reasoning,command_execution, etc.)
- Discriminated union on
- Output formatter:
- Convert parsed events into lines for terminal buffer
- Separate SSH vs HTTP formatting (no ANSI in HTTP)
- Mock codex exec CLI:
-
centaurx codex-mocksubcommand withexecandexec resume <id> - Support prompt via args or stdin (
-) - Emit deterministic JSONL with varied event shapes
- Support optional
--seed,--scenario,--delay-ms,--linger-ms - Handle SIGHUP/SIGTERM gracefully (emit error event, exit)
-
- Implement
-
Phase 3: Slash commands
-
/new <repo_name>:- Open existing repo in a new tab if it already exists
- Create repo dir and
git init - Switch to
centaurxbranch on creation - New tab named after repo (truncate to 10 chars; if longer, use first 9 chars +
$)
-
/reporemoved (use/newfor create/open) -
/listrepos -
/rm <number_or_name> - Command semantics update:
- Add
/closeto close the current tab (no args). -
/quit,/exit,/logoutshould exit the SSH session (no tab close). - Ensure
/quitno longer aliases/rmanywhere (SSH/HTTP/UI/handler).
- Add
-
/model <model>per-tab model selection- Normalize/validate to
[A-Za-z0-9._-] - If missing argument, return syntax error and include seeded model list
- Normalize/validate to
-
/stopor/z:- SIGTERM, wait 10s
- SIGKILL if still running
- Emit status messages
-
/git commit [message]:-
git add -A - If message missing, call codex exec resume with
gpt-5.1-codex-mini - Use returned single-line message for
git commit -m
-
-
-
Phase 3b: State persistence
- Persist per-user tabs, buffers, scroll offsets, and session ids to disk
- Load persisted state on startup
-
Phase 4: Runner split + IPC (gRPC over UDS)
- Define gRPC proto for runner (see
docs/runner-grpc.md):- Start new codex session (exec)
- Resume codex session (exec resume)
- Run shell command (for
!and/git) - Signal session (stop/z)
- Server-streamed events for output
- Add
proto/runner/v1/runner.proto(spec only; no codegen yet) - Add top-level
generate.gousingprotoc(no codegen run yet) - Add
internal/runnerpbpackage placeholder withdoc.go - Add
centaurx runnergRPC server (wraps codex exec + shell commands) - Add gRPC client in
centaurxserver and replace local runner usage - Use Unix domain sockets only (no TCP); add configurable socket path
- Bump config version to enforce runner socket path
- Ensure runner supports stdin prompts and preserves thread_id semantics
- Define gRPC proto for runner (see
-
Phase 4b: Per-user repo isolation
- Per-user repo roots:
$HOME/.centaurx/repos/<user>/... - Enforce username validation
[a-z0-9._-]in users CLI (no normalization) - Update repo manager to scope list/open/create to the authenticated user
- Per-user repo roots:
-
Phase 4c: Per-user SSH credentials (Option B)
- Store per-user SSH private keys encrypted at rest (kryptograf)
- Extend
centaurx users:- Generate SSH key on
users add(ed25519 default) - Support
--ssh-key-type(ed25519 | rsa) and--ssh-key-bits - Output public key for user onboarding (GitHub, etc.)
- Add
users rotate-ssh-key
- Generate SSH key on
- Implement per-user SSH agent in server (x/crypto/ssh/agent)
- Expose per-user agent socket to runner (shared volume)
-
Phase 4d: Extended commands and repo flows (post-runner split)
-
/helpslash command with concise usage for all supported commands -
! <cmd>run arbitrary shell commands in the tab's repo directory (via runner) -
/newaccepts SSH git URLs (e.g.github.com/org/repo) and clones:- Use SSH by default (convert to
git@host:org/repo.gitwhere needed) - Tab name = repo basename (truncate to 10 chars; if longer, use first 9 chars +
$) - If repo already exists locally, open it in a new tab (no clone)
- Only local repo creation switches to
centaurxbranch (do not switch on clones)
- Use SSH by default (convert to
-
-
Phase 5: SSH server (TUI)
- SSH server:
-
gliderlabs/sshsession handling - PTY allocation via
gliderlabs/ssh - Window resize handling
- Auto-generate host key if missing
-
- TUI renderer:
- Top row tab bar
- Scrollback viewport + prompt
- TAB to switch tabs
- PageUp/PageDown to scroll
- Typing cancels scrollback
- Prompt editing:
- Custom line editor (ctrl+w, alt+f/b, ctrl+a/e)
- Screen renderer + cursor positioning
- Spinner prompt when codex is running:
- Rotate
|/-\every ~250ms - Allow input while running; queue prompts until current run completes
- Allow slash commands and
!while spinner active
- Rotate
- SSH server:
-
Phase 6: HTTP API + UI
- Auth:
- JSON file at
~/.centaurx/users.json - bcrypt password hashing
- TOTP via
pquerna/otp - Session cookie (long-lived) + logout endpoint
- JSON file at
- API:
- List tabs, create tab, close tab
- Send prompt to tab
- Stream output via SSE (EventSource)
- Switch tabs and query scrollback buffer
- UI:
- Single-page app (minimal JS + SSE)
- Monospace terminal-lookalike
- Tab bar + scrollback + prompt per tab
- No ANSI rendering; plain text
- Mobile-friendly (Chrome/Duck)
- Keyboard tab cycling (TAB) for HTTP view
- Preserve per-tab scroll position + auto-scroll toggle
- Auth:
-
Phase 7: Config and CLI
- Viper config file:
- Repo prefix (default
$HOME/.centaurx/repos) - Default model
- Allowed models list
- HTTP auth users (seeded)
- SSH/HTTP ports
- Codex exec path/flags if needed
- Runner socket path (UDS)
- Per-user repo roots under
repo_root/<user> - SSH key storage paths (key store + key dir + agent dir)
- Config version bump to
4for breaking changes
- Repo prefix (default
- Cobra CLI:
-
centaurx serve(start SSH + HTTP) -
centaurx bootstrap(dump default config) -
centaurx version -
centaurx users(list/add/delete/chpasswd/rotate-totp)
-
- Viper config file:
-
Phase 8: Quality gates and tests
- Add tests for:
- JSONL parsing and session id capture
- Repo manager behavior (
/new,/listrepos) - Slash command routing
- Buffer scroll/viewport logic
- State persistence load/save (integration coverage)
- HTTP API + SSE integration tests
- SSH session integration tests
- Web UI smoke test (chromedp)
- gRPC runner integration tests (start/run/stop/stream)
-
!command and queued prompt behavior tests - Ensure integration tests run without containers (local binaries + UDS)
- Run required checks:
-
go test ./... -
go vet ./... -
golint ./... -
golangci-lint run ./...
-
- Add tests for:
-
Phase 9: Containerization (nerdctl compose)
- Ensure
centaurxruns as PID1 viapkt.systems/psibefore containerizing - Add Containerfile for
centaurx(server container) - Add Containerfile for
cxrunner(runner container) - Add
docker-compose.yaml:- Build both images
- Expose HTTP 27480 and SSH 27422
- Shared volume for repos and runner socket
- Mount per-user SSH agent sockets into runner
- Run both containers with host UID/GID (rootless)
- Mount host containerd socket at
/run/user/<uid>/containerd/containerd.sockintocentaurxand setXDG_RUNTIME_DIR=/run/user/<uid>
- Ensure
-
Open questions (remaining)
- Confirm session model for HTTP (per-user scope vs multi-user sharing).
- Define master key source and rotation strategy for encrypted SSH keys.