A Terminal UI, and Why It's Boring on Purpose


kodr gets a terminal UI across these four phases, and I want to be upfront: it’s deliberately plain. No curses layout, no dependency, no fancy panes. The point of the TUI was never the pixels. It was to prove a boundary.

Phase 45: the channel boundary

Until now kodr had exactly one way in: the command line. kodr tui adds a second, and a future web UI would be a third. The trap is obvious once you name it - if every surface learns its own way to run prompts, continue sessions, list runs, and write artifacts, you get three subtly different harnesses and endless drift.

So the TUI doesn’t reimplement any of that. It adapts user input into the same central request shape the CLI already uses. Normal lines become run turns. Slash commands are handled locally and never reach the model:

/sessions   /show <id>   /use <id>   /new
/apply on|off   /tools on|off   /model <id>   /status   /quit

Dry-run stays the default. After each turn the TUI updates its active session from the run summary, so follow-ups continue the same artifact-backed conversation - kodr tui --continue picks up the latest run, --session <id> a named one. The value isn’t visual polish; it’s proving the request flow supports more than one channel without duplicating execution behaviour.

Phase 46: apply review

/apply on was too blunt for an interactive coding loop. You want to ask for a change, see the proposed writes, then decide. So a dry-run turn that returns writes now parks a pending review in the TUI state - run dir, write count, proposed paths, model messages - and exposes:

/review   /accept   /reject   /test

The detail I care about: /accept applies by sending the same prompt back through the shared run-turn channel with apply enabled. It reuses the existing kodr run --yes path instead of inventing a separate TUI write path. /test routes through the central channel too. The terminal UI stays a thin adapter, and the safe-write gate keeps doing its job unchanged. The honest tradeoff: /accept is re-run-to-apply, not apply-from-existing-artifact, which costs a second model call. Simpler and safe today; a candidate for direct artifact application later.

Phase 47: a heartbeat

Local models can sit on “processing prompt” for a long time before anything appears. A line-oriented UI doesn’t need a full display system, but it does need to say what it’s doing. So each turn prints its request metadata up front (model, provider, session, apply mode, tools, timeout, budgets) and emits elapsed-time status lines while the call runs. Streaming gets a real terminal path too: the model client takes an onStreamContent callback, and kodr tui --stream wires it to stdout so chunks land as they arrive, while the run artifacts still capture the complete final response. (A piped-input EOF fix rode along here too, so scripted TUI sessions exit cleanly instead of throwing a readline exception - it matters for automation later.)

Phase 48: export

session show is good at the terminal and --json is good for tooling, but neither is good for review notes or, well, a blog post. So:

kodr session export <id> --format markdown

Deterministic, plain Markdown: session id, turn count, per-turn model, status, token usage, run dir, and fenced user/assistant text. Suitable for saving, diffing, pasting into notes, or as evidence when diagnosing a model or harness failure. Only Markdown for now - JSON is already session show --json, and keeping export narrow avoids standing up a premature format registry for one consumer.

Four phases, one theme: the surface is boring because all the interesting behaviour lives in one shared flow underneath it. That’s the whole idea.

Links: