The TUI

KHAELOR is not a > prompt / AI: response REPL. It is a modern interactive computing environment built for the terminal — and this page is how to drive it.

How the screen works#

KHAELOR renders into your terminal's normal scrollback, like a well-behaved CLI — not a full-screen alternate-screen app. Two zones:

  • Settled content — finished messages, completed tool rows, your submitted prompts. Printed exactly once, never touched again. Native selection, copy, and terminal search work on all of it, and the transcript survives even if KHAELOR crashes.
  • Live region — a small strip pinned to the bottom (at most ~24 rows): the streaming tail of the current response, one agent status line, the composer, and the status bar. It is the only thing that ever repaints, in flicker-free synchronized frames.

Consequences you will feel: no scroll jumps, no flicker, no layout destruction from long output, and typing latency that stays under 16 ms even while the model streams at full rate. If you scroll up to read something, nothing yanks you back down — new content accumulates below, exactly like tail -f.

The one full-screen exception is the /diff viewer, which behaves like less: enter, navigate, q, and your conversation is untouched.

The composer#

The input line is a real editor, not a bare readline:

  refactor @src/session/store.ts to journal events atomically,
   then run the tests

Multiline editing

  • Enter submits. Shift+Enter or Ctrl+J inserts a newline (Shift+Enter works in terminals with the kitty keyboard protocol or modifyOtherKeys; Ctrl+J works everywhere; a trailing \ before Enter also continues the line).
  • The composer grows from one row up to a third of the screen, then scrolls internally.
  • Full cursor movement: characters (/, Ctrl+B/Ctrl+F), words (Alt+←/→, Alt+B/Alt+F), line start/end (Ctrl+A/Ctrl+E), lines (/ inside a multiline buffer).
  • Deletion: word back Ctrl+W/Alt+Backspace, word forward Alt+D, to line start Ctrl+U. Undo with Ctrl+_.
  • Ctrl+G round-trips the whole buffer through $EDITOR for heavy editing, then returns it to the composer.

History

Prompt history is per-project (50 entries, deduplicated, stored in ~/.khaelor/prompt-history.jsonl). at the start of the buffer and at the end navigate it — cursor-position-aware, so history never hijacks multiline editing. Ctrl+R opens incremental history search.

Paste intelligence

Bracketed paste is handled structurally. A paste of three or more lines (or >150 characters) collapses into a compact span — ◫ pasted 47 lines — that expands only at submit, so a big stack trace never wrecks your editing view. A paste that looks like a file path becomes a file mention automatically.

@ file mentions#

Type @ anywhere to fuzzy-search the repository (respecting .gitignore and .khaelorignore), ranked by match quality and frecency — files you touch often float to the top:

  refactor @agent
            ┌────────────────────────────────────────┐
             src/kernel/agent.ts                 
             src/agents/agent-runtime.ts
             tests/agent.test.ts
            └────────────────────────────────────────┘

Selecting inserts a structured file reference — the agent's context engine receives a reference to resolve intelligently, not a blind dump of the file's contents. Line ranges are supported in the syntax: @src/kernel/agent.ts:40-90.

! shell mode#

Type ! as the first character to run a shell command yourself, without involving the model:

 ! git status --short

 $ git status --short
  M src/session/store.ts
 ?? notes.md
 a — add output to context

The prompt glyph switches to ! (warning tint). Output prints into the conversation as a settled block; press a immediately after to hand that output to the agent as context — it never enters the model's context without your say-so. Shell mode runs under the same permission rules as the agent's own bash tool.

Knowing what the agent is doing#

One compact status line above the composer tracks the agent's real state — thinking · reading · searching · editing · running · waiting · verifying · idle:

  Searching repository · 2.3s
  Editing src/kernel/agent.ts
  Running npm test · 41/148
  Waiting for permission

Every element is real data: elapsed time is a real timer; counts like 41/148 appear only when a tool's output actually contained them. There are no fake spinners and no invented percentages, ever. When idle, the line disappears entirely.

Tool call presentation

Each tool call settles into one collapsed line — enough to audit, quiet enough to ignore:

  Read src/kernel/agent.ts · 212 lines
  Search "ContextEngine" · 14 matches
  Edit src/context/engine.ts · +31 −12
  Run npm test · passed · 4.2s
  • Ctrl+T cycles detail on the currently-running tool (collapsed ↔ a rolling 12-line tail of its output).
  • d (with an empty composer) prints the diff of the most recent edit.
  • Very long output is truncated head/tail with an explicit marker; the full output is saved to a file whose path is shown — nothing is silently lost.

Interruption — Esc#

Press Esc at any moment. Acknowledgment is immediate — the status line flips to ◎ Stopping… in the same frame — then cancellation propagates cleanly through the model stream, any in-flight tools, and the agent loop:

  Run npm test · cancelled · 3.2s
 ◎ Interrupted — partial response kept

The session is never corrupted by an interrupt: partial output is kept and marked, and background processes you started (dev servers, watchers) are deliberately left running. Esc with an overlay open closes the overlay instead; with text in an idle composer it clears the current completion/selection first.

Steering while the agent works#

The composer never locks. Type while the agent is mid-task and press Enter — your message is queued and injected at the next safe boundary between tool calls:

  Run npm test · running 8s

  Queued — use the smaller fixture instead
   Esc cancel run · Ctrl+U discard queued

Queued messages show with the marker until injected, then settle into the transcript as normal user messages. Multiple queued messages stack in order. Ctrl+U with an empty composer discards the most recent queued message. This is how you course-correct without killing a run: "skip the lint step", "target Node 22", "that file is generated, edit the template instead".

The status bar#

One dim row at the very bottom — persistent, never animated:

 main +4 −1  │  claude-sonnet-4-5  │  context 31%  │  $0.42  │  ● 2
SegmentMeaning
main +4 −1Git branch and dirty-file counts, live.
claude-sonnet-4-5The session's model (shortens to an alias on narrow terminals).
context 31%Real context-window utilization. At ≥80% it tints and suggests /compact.
$0.42Session cost from actual API usage metadata.
● 2Running background processes.
⋯ 1 queuedQueued steering messages, when any.

Segments with no real data are absent, not zeroed. On narrow terminals, lower-priority segments drop whole rather than truncating.

Color, monochrome, accessibility#

  • One calm default theme (dark), with an automatic light variant when your terminal background is detected as light.
  • NO_COLOR and TERM=dumb are honored absolutely. State is never conveyed by color alone — every state pairs a symbol or a word: done, tool, / running/stopped, queued, interrupted, failed/passed spelled out.
  • Keyboard-only use is complete — there is nothing that requires a mouse.