Agent Wikis

wikis / Prime Agent / wiki / concepts / tui-and-themes.md view as markdown report a mistake

type: conceptconfidence: highupdated: 2026-08-05prime_agent_version: v0.7.0sources: 4

Definition

Prime Agent's terminal user interface (TUI) is built on packages/tui (published as prime-agent-tui, source name @earendil-works/pi-tui), a minimal terminal UI framework with differential rendering and synchronized output for flicker-free interactive CLI applications. The interactive-mode screen it renders is composed, top to bottom, of a startup header, the message transcript, an input editor, and a footer. Themes are JSON files that supply the 51 color tokens the TUI's components use to style everything from borders to syntax highlighting, and keybindings are a namespaced, user-remappable id-to-key mapping that drives every keyboard interaction in that screen.

How It Works

Rendering. The TUI uses three rendering strategies: first render (output all lines without clearing scrollback), width-changed-or-change-above-viewport (clear screen, full re-render), and normal update (move cursor to the first changed line, clear to end, render only changed lines). All updates are wrapped in synchronized output (\x1b[?2026h ... \x1b[?2026l) for atomic, flicker-free updates. The TUI works with any object implementing a Terminal interface (ProcessTerminal for real terminals, VirtualTerminal for tests using @xterm/headless).

Components. Every component implements render(width): string[], optional handleInput(data), optional wantsKeyRelease, and invalidate(). Each returned line must not exceed the given width or the TUI errors; helpers visibleWidth(), truncateToWidth(), and wrapTextWithAnsi() handle ANSI-aware sizing. Built-ins include Text, TruncatedText, Box, Container, Spacer, Input, Editor, Markdown, Loader/CancellableLoader, SelectList, SettingsList, and Image (Prime Agent renders compact image metadata rather than terminal graphics). Components needing a text cursor (for CJK IME support) implement the Focusable interface; the TUI scans rendered output for a zero-width CURSOR_MARKER and positions the hardware cursor there. Container components with embedded Input/Editor children must propagate focused down to the child or IME candidate windows appear in the wrong place.

Overlays. Overlays render on top of existing content without clearing the screen (tui.showOverlay() at the TUI-primitives layer, or { overlay: true } passed to ctx.ui.custom() from an extension). Overlay placement supports anchor-based positioning (9 anchors: center, top-left, ..., right-center), percentage or absolute row/col, size as fixed or percentage with minWidth/maxHeight floors, margins, and a visible(termWidth, termHeight) callback for responsive hiding. Resolution order: minWidth floors width; absolute row/col beats percentage beats anchor; margin clamps the final position; visible is re-evaluated every frame. Overlay components are disposed on close — extensions must create fresh instances rather than reusing a stale reference to re-show one.

Theming inside components. Components accept theme.fg(color, text) and theme.bg(color, text) callbacks rather than importing a theme module directly. When the active theme changes, the TUI calls invalidate() on every component to clear render caches. A component that pre-bakes theme colors into cached strings (e.g., via theme.fg() stored in a child Text) must rebuild that content inside its own invalidate() override, or the old theme's ANSI codes remain baked in after a theme switch.

Themes as data. A theme is a JSON file with name (required, unique), optional vars (reusable named colors), and colors (must define all 51 required tokens across Core UI, Backgrounds & Content, Markdown, Tool Diffs, Syntax Highlighting, Thinking Level Borders, and Bash Mode categories — there are no optional colors). Color values are hex ("#ff0000"), a 256-color palette index (0-255), a vars reference, or "" for the terminal's default color. An optional export section customizes /export HTML colors; if omitted, they derive from userMessageBg. Prime Agent uses 24-bit RGB color and falls back to nearest approximation on 256-color terminals (check with echo $COLORTERM).

Theme discovery and hot reload. Prime Agent loads themes from built-ins (dark, light), ~/.prime/agent/themes/*.json (global), .prime/agent/themes/*.json (project), package themes/ directories or pi.themes manifest entries, the themes array in settings, and repeatable --theme <path> CLI flags; --no-themes disables discovery. Select via /settings or {"theme": "my-theme"} in settings.json. On first run, Prime Agent detects the terminal background and defaults to dark or light. Editing the currently active custom theme file triggers an automatic hot reload for immediate visual feedback.

Keybindings. All shortcuts are customizable via ~/.prime/agent/keybindings.json, keyed by namespaced ids (e.g., tui.editor.cursorUp, app.model.select) that are the same ids extension authors use in keyHint() and the injected keybindings manager. Older pre-namespaced ids (like cursorUp) are migrated automatically on startup. Each action can bind to one key or an array of keys; user config overrides (not merges into) the default list per-action. Key format is modifier+key with combinable modifiers ctrl, shift, alt and keys spanning letters, digits, special keys (escape, enter, tab, arrows, home/end, pageUp/pageDown, etc.), function keys f1-f12, and symbols. After editing keybindings.json, run /reload to apply changes without restarting the session. app.suspend (Ctrl+Z) has no default binding on native Windows because Windows terminals lack Unix job control; binding it manually there just shows a status message instead of suspending (WSL keeps normal Linux behavior).

Key Parameters

  • 51 required color tokens, grouped as: Core UI (11: accent, border, borderAccent, borderMuted, success, error, warning, muted, dim, text, thinkingText), Backgrounds & Content (12), Markdown (10), Tool Diffs (3: toolDiffAdded/Removed/Context), Syntax Highlighting (9), Thinking Level Borders (6: thinkingOff through thinkingXhigh), and Bash Mode (1: bashMode, the editor border color when a !-prefixed bash command is being entered).
  • Component interface contract: render(width): string[] (line length must never exceed width), handleInput?(data), wantsKeyRelease? (Kitty protocol key-release events, default false), invalidate().
  • Overlay sizing/position keys: width/minWidth/maxHeight (number or % string), anchor, offsetX/offsetY, row/col (percent or absolute), margin, visible, nonCapturing.
  • PI_TUI_WRITE_LOG: env var that captures the raw ANSI stream written to stdout, for debugging renders.
  • Debug key: Shift+Ctrl+D triggers tui.onDebug.

When To Use

Reach for the TUI component/overlay system when building an extension that needs custom interactive UI — selection dialogs (SelectList + DynamicBorder), cancellable async operations (BorderedLoader), settings toggles (SettingsList), persistent status indicators (ctx.ui.setStatus), widgets above/below the editor (ctx.ui.setWidget), a custom footer (ctx.ui.setFooter), or a fully custom editor such as a vim-mode input (CustomEditor subclass via ctx.ui.setEditorComponent). Reach for the theming system when Prime Agent's default dark/light themes don't match a terminal or personal palette preference, or when shipping a themed extension/package. Reach for keybindings customization when the defaults conflict with terminal-level bindings (see platform setup for terminal-specific Enter-key caveats) or to emulate emacs/vim editing conventions.

Risks & Pitfalls

  • Emitting multi-line styled text without reapplying ANSI codes per line: the TUI appends a full SGR/OSC-8 reset at the end of every rendered line, so styles never carry across lines — use wrapTextWithAnsi() for correctly-styled wrapped output.
  • Forgetting the Focusable propagation pattern in container components (dialogs, selectors) with embedded Input/Editor children breaks IME candidate-window positioning for CJK input.
  • Pre-baking theme colors into cached component state without overriding invalidate() to rebuild that content means a theme switch leaves stale ANSI codes on screen.
  • Reusing a disposed overlay component reference (e.g., holding onto a MenuComponent instance after close()) is a dangling reference — always re-invoke the factory to show an overlay again.
  • A theme file that omits any of the 51 required tokens is invalid — there is no partial/optional-token mode.
  • On xfce4-terminal, terminator, and IntelliJ IDEA's integrated terminal, modifier-augmented Enter (Ctrl+Enter, Shift+Enter) can't be distinguished from plain Enter, which silently breaks any keybinding relying on that distinction (see platform setup).

Related Concepts

  • settings and customization — where the active theme name, editorPaddingX, autocompleteMaxVisible, and other UI settings are configured
  • platform setup — terminal-specific configuration (Ghostty, WezTerm, Windows Terminal, tmux) required for reliable modifier-key detection that the keybinding system depends on
  • extensions — extensions are the primary consumer of the TUI component, overlay, and custom-editor APIs described here
  • daemon — the daemon delivers session/transcript state that the TUI renders; TUI invalidation and re-render is triggered by daemon-sourced events

Sources

  • raw/github_doc-packages-coding-agent-docs-tui-md.md
  • raw/github_doc-packages-coding-agent-docs-keybindings-md.md
  • raw/github_doc-packages-coding-agent-docs-themes-md.md
  • raw/github_doc-packages-tui-readme-md.md