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
$EDITORfor 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
| Segment | Meaning |
|---|---|
main +4 −1 | Git branch and dirty-file counts, live. |
claude-sonnet-4-5 | The session's model (shortens to an alias on narrow terminals). |
context 31% | Real context-window utilization. At ≥80% it tints and suggests /compact. |
$0.42 | Session cost from actual API usage metadata. |
● 2 | Running background processes. |
⋯ 1 queued | Queued 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_COLORandTERM=dumbare honored absolutely. State is never conveyed by color alone — every state pairs a symbol or a word:✓done,▸tool,●/○running/stopped,⋯queued,◎interrupted,failed/passedspelled out.- Keyboard-only use is complete — there is nothing that requires a mouse.