Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

TUI and keybindings

The TUI is a normal-screen inline terminal interface built with crossterm and ratatui. It preserves terminal and tmux scrollback instead of switching to the alternate screen. It is selected when both stdin and stdout are terminals; otherwise the CLI uses print mode for positional prompts or the line REPL for a text session. The dispatch lives in crates/pi-cli/src/lib.rs:138-169.

Layout

 rpi (rs) · provider/model · /cwd
┌─────────────────────────────────────┐
│ Conversation                        │
│ ...                                 │
├─────────────────────────────────────┤
│ Message                             │
│ > type here                         │
├─────────────────────────────────────┤
│ Status line                         │
└─────────────────────────────────────┘

The conversation area keeps the most recent 4,000 transcript entries; when the limit is exceeded the oldest entries are dropped (MAX_TRANSCRIPT_LINES = 4_000 in crates/pi-cli/src/tui.rs:58, trimmed in tui.rs:1703). Tool execution is shown as status lines (· toolName(args) / └ ok / └ error).

When the transcript is empty, the welcome screen lists a few Recent sessions from the unified catalog, scoped to the current working directory. Each entry shows a [source] badge for native Pi, OMP, Codex, Claude, Grok/Hyper, or Droid sessions. Selecting a foreign session imports it once; subsequent resumes reuse the converted file under the effective native session root.

Built-in themes

Only two built-in palettes are always available:

  • dark — the exact installed OMP v17.2.6 default titanium palette: electric blue chrome, green success/readouts, gold highlights, and dark titanium surfaces (DARK in crates/pi-cli/src/theme.rs).
  • light — a high-contrast palette for light terminals (LIGHT in theme.rs).

The initial theme follows safe terminal background detection; dark terminals therefore use the OMP-default Titanium palette. It can be pinned in settings.json:

{
  "theme": "light"
}

Switch themes in the TUI with /theme, /theme next, /theme prev, or /theme <name>. The theme manager also live-reloads custom theme files and leaves the active palette unchanged when a reload fails (theme.rs:353).

Custom themes

Place JSON theme files in one of the theme directories:

  • Global: <pi-dir>/themes/ (the .pi directory under the user's home directory: HOME on Unix, USERPROFILE on Windows, resolved by home_dir() in crates/pi-cli/src/tui.rs:4985)
  • Project: <workspace>/.pi/themes/

Directory order is global-first, project-last (config_paths in crates/pi-cli/src/tui.rs:4971).

A theme file extends dark or light and overrides only the colors it specifies:

{
  "name": "solarized",
  "extends": "dark",
  "vars": {
    "blue": "#268bd2",
    "green": "#859900"
  },
  "colors": {
    "accent": "blue",
    "success": "green",
    "selectedBg": "#073642",
    "mdHeading": "#b58900",
    "syntaxKeyword": "blue",
    "toolDiffAdded": "green"
  }
}

extends must be "dark" or "light" (theme.rs:522). Role names may be camelCase (upstream) or snake_case (Rust-native); underscores and hyphens are ignored (theme.rs:567).

Semantic roles cover the complete transcript and editor surface (defined on Theme in theme.rs:41):

  • Core: accent, border, borderAccent, borderMuted, success, error, warning, muted, dim, text, thinkingText
  • Messages and tools: userMessageText, customMessageText, customMessageLabel, toolTitle, toolOutput
  • Markdown: mdHeading, mdLink, mdLinkUrl, mdCode, mdCodeBlock, mdCodeBlockBorder, mdQuote, mdQuoteBorder, mdHr, mdListBullet
  • Diffs and syntax: toolDiffAdded, toolDiffRemoved, toolDiffContext, syntaxComment, syntaxKeyword, syntaxFunction, syntaxVariable, syntaxString, syntaxNumber, syntaxType, syntaxOperator, syntaxPunctuation
  • Thinking and modes: thinkingOff, thinkingMinimal, thinkingLow, thinkingMedium, thinkingHigh, thinkingXhigh, thinkingMax, bashMode
  • Backgrounds: selectedBg, userMessageBg, customMessageBg, toolPendingBg, toolSuccessBg, toolErrorBg

Color values may be ratatui named colors (black, red, cyan, gray, darkgray, lightgreen, white, reset, ...) or hex #rrggbb / #rgb. Empty-string values resolve to the terminal default color for text, userMessageText, customMessageText, and toolTitle (theme.rs:100). Theme files are validated before loading; a malformed file is skipped with a diagnostic and the active theme is left unchanged.

Keybindings

The TUI maps incoming keys to stable actions through a KeyBindingsManager. Built-in defaults reproduce the original TUI behavior (default_bindings in crates/pi-cli/src/keybindings.rs:354). The dispatch layer never checks hard-coded chords; it resolves every key event to an Action (keybindings.rs:298).

Global editor and application defaults

ChordActionStable ID (config name)
enterSubmit messagetui.input.submit
shift+enter, ctrl+jInsert newlinetui.input.newLine
backspaceDelete character backwardtui.editor.deleteCharBackward
deleteDelete character forwardtui.editor.deleteCharForward
left, ctrl+bMove cursor lefttui.editor.cursorLeft
right, ctrl+fMove cursor righttui.editor.cursorRight
upMove cursor / previous history linetui.editor.cursorUp
downMove cursor / next history linetui.editor.cursorDown
alt+left, ctrl+left, alt+bWord lefttui.editor.cursorWordLeft
alt+right, ctrl+right, alt+fWord righttui.editor.cursorWordRight
home, ctrl+aStart of linetui.editor.cursorLineStart
end, ctrl+eEnd of linetui.editor.cursorLineEnd
ctrl+]Jump forwardtui.editor.jumpForward
ctrl+alt+]Jump backwardtui.editor.jumpBackward
pageupScroll transcript up one pagetui.editor.pageUp
pagedownScroll transcript down one pagetui.editor.pageDown
ctrl+w, alt+backspaceDelete word backwardtui.editor.deleteWordBackward
alt+d, alt+deleteDelete word forwardtui.editor.deleteWordForward
ctrl+uClear the entire composertui.editor.clear
ctrl+kDelete to line endtui.editor.deleteToLineEnd
ctrl+yYanktui.editor.yank
alt+yYank poptui.editor.yankPop
ctrl+-Undotui.editor.undo
escAbort in-flight run, or clear input when idleapp.interrupt
ctrl+cClear inputapp.clear
ctrl+dQuit (when input is empty)app.exit
tabAccept slash-command completiontui.input.tab
ctrl+v, alt+vPaste from clipboardapp.clipboard.pasteImage
ctrl+x, ctrl+shift+cCopy last assistant messageapp.message.copy
ctrl+gOpen external editorapp.editor.external
ctrl+zSuspend (yield terminal)app.suspend
shift+tabCycle thinking levelapp.thinking.cycle
ctrl+tToggle thinking block visibilityapp.thinking.toggle
ctrl+pCycle model forwardapp.model.cycleForward
ctrl+shift+pCycle model backwardapp.model.cycleBackward
ctrl+lOpen model selectorapp.model.select
ctrl+oExpand/collapse tool detailsapp.tools.expand
alt+enterQueue follow-upapp.message.followUp
alt+upDequeue last promptapp.message.dequeue

Contextual selector defaults

Some chords are intentionally reused across mutually exclusive panels; they only dispatch when the matching panel is open (resolve_in in keybindings.rs:303, selector actions in keybindings.rs:323).

Saved session selector (/sessions, /resume with no argument):

ChordAction
ctrl+nToggle named-only filter (app.session.toggleNamedFilter)
ctrl+pToggle path display (app.session.togglePath)
ctrl+sToggle sort (app.session.toggleSort)
ctrl+rRename selected session (app.session.rename)
ctrl+dDelete selected session (app.session.delete)
ctrl+backspaceDelete without exiting selector (app.session.deleteNoninvasive)

Scoped model selector (/scoped-models):

ChordAction
ctrl+sSave scoped patterns (app.models.save)
ctrl+aEnable all (app.models.enableAll)
ctrl+xClear all (app.models.clearAll)
ctrl+pToggle provider (app.models.toggleProvider)
alt+upMove selected up (app.models.reorderUp)
alt+downMove selected down (app.models.reorderDown)

Session tree / fork panel (/tree, /fork):

ChordAction
leftFold the current node, or move up (app.tree.foldOrUp)
rightUnfold the current node, or move down (app.tree.unfoldOrDown)
alt+shift+lEdit node label (app.tree.editLabel)
alt+shift+tToggle label timestamps (app.tree.toggleLabelTimestamp)
ctrl+dDefault filter (app.tree.filter.default)
ctrl+tNo-tools filter (app.tree.filter.noTools)
ctrl+uUser-only filter (app.tree.filter.userOnly)
ctrl+lLabeled-only filter (app.tree.filter.labeledOnly)
ctrl+aAll entries filter (app.tree.filter.all)
ctrl+oCycle filter forward (app.tree.filter.cycleForward)
ctrl+shift+oCycle filter backward (app.tree.filter.cycleBackward)

Up/Down/PageUp/PageDown move the selection in all selector and tree panels.

Custom keybindings

Place keybinding files in one of the keybinding paths:

  • Global: <pi-dir>/keybindings.json (the .pi directory under the user's home directory)
  • Project: <workspace>/.pi/keybindings.json

Project files overlay global files, which overlay built-in defaults (KeyBindingsManager::load in keybindings.rs:267). A file with any malformed chord, unknown action, or duplicate canonical chord is rejected in full with a diagnostic.

Two file formats are accepted:

Upstream action-to-chord object (preferred):

{
  "tui.input.submit": ["ctrl+enter"],
  "tui.input.newLine": ["shift+enter", "ctrl+j"],
  "app.exit": "ctrl+q"
}

Legacy pi-rs bindings array:

{
  "bindings": [
    { "chord": "ctrl+enter", "action": "editor_submit" },
    { "chord": "enter", "action": "editor_newline" },
    { "chord": "ctrl+q", "action": "quit" }
  ]
}

Overriding an action replaces all of its earlier chords, matching upstream behavior.

Valid actions are the stable IDs listed above. The canonical registry is VALID_ACTION_NAMES in keybindings.rs:177. Chords use +-separated modifiers and a key token. Modifiers are ctrl (alias control), alt (alias option), and shift. Key tokens include special names (enter, tab, esc, left, right, up, down, home, end, backspace, delete, space, pageup, pagedown, f1–f12) or a single Unicode scalar (a, /, 1).

Selectors, tree, fork, and dialogs

The TUI uses several modal page overlays inside the inline viewport. Typing filters the list, Enter confirms the selection, and Esc closes the overlay.

  • Model selector (ctrl+l or /model with no argument): searchable list of configured providers and models (open_model_panel in tui.rs:1963).
  • Thinking level selector (/settings → "Thinking level" or /theme not used): one entry per level (open_thinking_panel in tui.rs:1984).
  • Settings panel (/settings): toggles thinking level, theme, and automatic compaction (open_settings_panel in tui.rs:2027).
  • Trust panel (/trust): choose Trusted, Untrusted, or Ask for the current project (open_trust_panel in tui.rs:2080). Saved session selector (/sessions, /resume with no argument): unified catalog scoped to the current cwd, filter by name/path, sort by newest or name, rename (ctrl+r), and delete (ctrl+d) saved sessions. Rows show a [source] badge; foreign source files cannot be renamed or deleted, while native Pi sessions and already-imported conversions can be (handle_session_selector_key in tui.rs:2281).
  • Scoped model selector (/scoped-models): enable/disable models for ctrl+p/ctrl+shift+p cycling, save the scope, and reorder enabled models (handle_scoped_model_selector_key in tui.rs:2387).
  • Session tree (/tree): browse the branching message tree, fold/unfold, apply filters, and edit node labels (handle_tree_panel_key in tui.rs:2143).
  • Fork panel (/fork): a tree filtered to user messages; selecting one and pressing Enter copies the active path up to that message into a new session (TreePanelMode::Fork in tree_panel.rs:9, open_fork_panel in tui.rs:2066).

Extension dialogs are raised when an extension requests interactive UI (ExtensionDialog in tui.rs:127). They include:

  • Select: Up/Down to choose, Enter to accept, Esc to cancel.
  • Confirm: arrow keys or Tab to swap the default; y/Y or n/N or Enter to accept; Esc to cancel.
  • Input / Editor: full editor keys from the keybinding table, Enter (tui.input.submit) to accept, Esc to cancel.

(handle_extension_dialog_key in tui.rs:2576)

Terminal image behavior

Images are rendered only in the inline TUI. The line REPL and structured modes never receive image protocol bytes.

Protocol detection uses explicit environment evidence (detect_protocol in terminal_images.rs):

  • Kitty: KITTY_WINDOW_ID, TERM=xterm-kitty, or TERM_PROGRAM equal to wezterm/ghostty. Under tmux, Kitty output is enabled only for tmux 3.3 or newer and every graphics APC is DCS-wrapped for passthrough. GNU Screen and older/unknown tmux versions fall back to metadata text.
  • iTerm2: TERM_PROGRAM=iTerm.app and ITERM_SESSION_ID present.
  • Sixel: TERM contains sixel. Sixel is detected for truthful diagnostics but is not emitted because the project does not yet include a bounded safe encoder; the TUI falls back to metadata text (supports_images in terminal_images.rs).

The TUI does not read stdin for an active graphics probe during startup, so terminal capability detection cannot consume user keystrokes. The implemented query contract uses Kitty's valid 1x1 RGB form and its response splitter preserves unrelated bytes, but the interactive startup path does not invoke it.

Image display settings come from terminal.showImages and terminal.imageWidthCells in settings.json (TerminalSettings in settings.rs; the default width is 60). Images are suppressed whenever an overlay such as a selector, tree, or dialog is open.

Layout and safety: the renderer decodes and validates the image, refuses empty data and payloads larger than the configured MAX_IMAGE_BYTES limit, clamps the layout to the viewport and imageWidthCells, preserves aspect ratio, and falls back to ordinary metadata when display is disabled or the protocol is unsupported.

Presentation: validated PNG payloads are transmitted in chunks of at most 4,096 base64 bytes with Kitty's transmit-only action (a=t) and separate pixel width/height controls (s=, v=). Image and placement IDs are explicit, non-zero, and randomized per renderer. Frame reconciliation deletes only the renderer-owned placement that moved or disappeared; cleanup deletes only its owned image IDs, never another application's placements. iTerm images retain the inline 1337 fallback with preserveAspectRatio=1.

Slash commands

The TUI and line REPL can execute the same built-ins, but /help, slash completion, and RPC discovery expose only the 22 commands in PRIMARY_COMMAND_NAMES (interactive_commands.rs). Prompt templates, dynamic skills, and extension commands remain executable through their namespaced paths.

CommandDescription
/settingsInspect settings or open the settings page
/model [provider/model]Select or switch model
/branchCreate a branch from a previous message
/resume [path, id, or prefix]Resume native or discovered foreign sessions
/forkFork from a previous user message
/export [path]Export session to HTML or JSONL
/dump [--jsonl] [path]Export with an explicit format flag
/handoff [--prose]Generate a handoff summary (copied to clipboard)
/agentsManage agent definitions and model overrides
/roleManage the current agent role
/compact [--snap] [instructions]Manually compact session context (--snap is deterministic)
/rewind [entry-index|checkpoint-name]Roll the session back, archiving the dropped tail
/checkpoint <name>Mark the current position as a named rewind target
/psList supervised processes
/loop [interval] <prompt>Run a recurring prompt
/goalManage the durable session goal
/workflowManage isolated concurrent workflows
/code-review [<from> <to>]Open a fullscreen Git diff browser: bare shows tracked HEAD→working-tree changes; two refs compare any two commits/branches/tags
/btw [prompt]Open a persistent detached side conversation forked from the active main branch
/queue [cancel]Show pending steering/follow-up prompts (cancel clears them)
/liveHold-to-talk voice input

Other built-ins such as /help, /new, /sessions, /tree, /todo, /snapcompact, /share, /copy, /login, /logout, /process, /theme, and /quit remain manually executable but are intentionally omitted from primary discovery.

/btw is read-only by default. Ctrl+T toggles edit/exec tools while the side agent is idle; Esc aborts a streaming turn or closes an idle overlay; Alt+R reforks from the current main leaf; Alt+N clears the side transcript. The overlay has its own editor, transcript, events, stream, and abort lifecycle. Closing it preserves the side controller for reopening, while TUI shutdown aborts and joins active side work.

/code-review displays staged and unstaged tracked changes against HEAD. Tab changes pane focus; j/k, arrows, and mouse clicks select hunks; c comments on the selected hunk; Space folds its inline review thread. Each thread appears after the complete hunk body with distinct comment/answer cards, and repeated agent progress messages collapse into one answer per exchange. Page keys and the mouse wheel scroll; tree clicks select or fold; Esc/q closes the page. Mouse capture is enabled only for this page and restored on every close or overlay transition.

/code-review accepts zero or exactly two arguments. Bare /code-review shows tracked HEAD→working-tree changes. /code-review <from> <to> resolves each ref (commit hash, branch, or tag) to a commit and renders the commit-to-commit diff labeled <from> → <to> in the panel title; working-tree changes are ignored. One argument or more than two is invalid — the page does not open and the status line shows Usage: /code-review [<from> <to>]. Pressing r refreshes the snapshot and preserves the selected revision pair.

Loop intervals accept positive bare seconds (/loop 300 check status) or compact s, m, h, and d units (/loop 3s echo hello, /loop 30m check deploy). Scheduled turns appear as Loop <id> · <cadence> system cards; the internal model instruction wrapper is not shown as a user message.

TUI vs REPL-only limitations

When stdout is not a TTY, main_run falls back to repl::interactive (lib.rs:142). The REPL shares the same slash-command catalog but lacks the TUI's modal page overlays:

  • No modal pages: model, settings, trust, saved-session, scoped-model, session-tree, fork, /btw, and /code-review overlays are TUI-only. In the REPL, /scoped-models and /theme report that they require the TUI, /tree prints JSON, /fork with no argument prints candidate messages, and /model accepts a concrete spec.
  • No configurable keybindings or themes: the REPL uses a fixed line-editing interface and ignores theme files.
  • No terminal image rendering: image attachments are processed and sent to the model, but the REPL displays only text metadata.
  • No interactive extension UI: the REPL is composed without the TUI's ExtensionUiAdapter (session_run.rs:491), so extension select/confirm/input dialogs cannot be answered interactively.