Using the TUI¶
Once the TUI is up you have an input box at the bottom of the screen. What you type there is interpreted by its first character:
- A plain message is sent to the model as your prompt. The assistant's
reply, its thinking, and any tool calls stream into the transcript above the
input box. While a turn is running you can keep typing: pressing
Entersteers the model with the new text, andEscinterrupts the turn. !commandrunscommandin a shell in the current directory. Its output is committed to the transcript and buffered, so it is included with your next message to the model (handy for showing the assistant a build error or agit diff).!!commandrunscommandthe same way but keeps the output display-only: it is shown to you but not fed to the model./commandruns a slash command (see below), recognised only when/is the very first character of the first row.
Slash commands¶
Slash commands drive the session itself rather than the model. Type / on its
own (or /help) to see the full list rendered in the transcript, and
/<command> --help for one command's own help. The available commands are:
| Command | Action |
|---|---|
/quit (/q) |
Quit Wallah; with background jobs still running, reports them and stays. /quit --force cancels them and quits anyway |
/jobs |
List the background sub-agents (launched by config slash commands) still running |
/kill <id> |
Cancel a running background job by its id (from /jobs), or every job with --all |
/model [ref] |
Switch the active model as [provider/]model[@effort]; with no argument, open the model picker |
/personality [ref] |
Switch the active personality as personality[/mode]; with no argument, open the personality picker |
/new [name] |
Start a new session, keeping the current provider, model, and effort |
/name [name] |
Rename the current session; with no argument, clear the name |
/resume |
Resume a previous session in this project |
/fork |
Fork the current session at an earlier turn |
/compact |
Compact the conversation now, replacing the oldest turns with a summary while keeping a recent tail |
/reset-provider-session |
Re-seed the provider from the session history, discarding any continuable server-side session |
/recall <id> |
Show the full original content of an item by its recall id (the number in a #12 tag) |
/ollama |
Inspect and tune the native Ollama provider's context window |
/reload |
Re-read config.lua and rebuild the runtime environment for the current session |
/keys |
Show the current key bindings |
/usage |
Report token usage across stored sessions for this month, week, and day, split by provider and model and priced from the model catalog. --project <name> scopes it to one project. --by-role adds a second breakdown by personality and mode. |
/codex-login |
Sign in with a ChatGPT subscription for Codex models, renewing an expired sign-in without leaving the session |
/codex-logout |
Remove the stored ChatGPT sign-in |
/help |
Print the slash-command help |
Your config can add its own commands, too, either expanding into a canned
prompt or launching a background sub-agent. See defining your own slash
commands, and personalities, modes, and
sub-agents for the personalities /personality switches
between.
Key assignments¶
The TUI recognises the following keys. The composing hotkeys are configurable; see Rebinding keys below.
While composing a prompt:
| Key | Action | Config name |
|---|---|---|
Enter |
Send the message (or run a / command or ! shell command) |
submit |
Shift+Enter |
Insert a newline | insert_newline |
Tab |
Open the completion popup (slash commands, file paths) | complete |
Shift+Tab |
Cycle the active personality's mode | cycle_mode |
Up / Down |
Move through input history (at the first / last row) | (fixed) |
Ctrl-o |
Toggle full-output expansion for tool and shell blocks | expand_output |
Ctrl-g |
Compose the prompt in $VISUAL / $EDITOR |
edit_externally |
Escape / Ctrl-c |
Interrupt a running shell command | interrupt |
Ctrl-d |
Quit (when the input is empty) | quit |
While the completion popup is open:
| Key | Action |
|---|---|
Tab / Down |
Select the next candidate |
Shift+Tab / Up |
Select the previous candidate |
Enter |
Accept the selected candidate |
Esc |
Close the popup |
While a turn (or a compaction pass) is running:
| Key | Action |
|---|---|
Esc / Ctrl-c |
Interrupt the turn |
Enter |
Steer the running turn with the typed text |
Shift+Enter |
Insert a newline |
Shift+Tab |
Cycle the active personality's mode |
Ctrl-o |
Toggle full-output expansion |
In a picker (/model, /resume, fork selection):
| Key | Action |
|---|---|
Up / Down |
Move the selection |
Ctrl-p / Ctrl-n |
Move the selection |
Enter |
Accept the highlighted item |
Esc / Ctrl-c |
Cancel the picker |
Backspace / printable keys |
Edit the fuzzy filter |
Rebinding keys¶
The composing hotkeys can be reassigned from your config with
agent.configure_keys. Each field
names an action (the config names in the table above) and the press, or list of
presses, that triggers it:
agent.configure_keys {
submit = 'ctrl-enter', -- a single press, or
quit = { 'ctrl-d', 'ctrl-q' }, -- several presses, or
complete = {}, -- an empty list to unbind
}
A press is written as optional ctrl-, alt-, shift-, and super- prefixes
followed by a key: a single character (a, /), space, a function key (f1
through f24), or a named key. A named key is spelled as its termwiz key name
in snake_case, such as enter, tab, escape, backspace, delete,
insert, home, end, page_up, page_down, up_arrow, down_arrow,
left_arrow, and right_arrow. An omitted action keeps its default; an empty
list unbinds it. A press bound to two actions, or a key name that does not
parse, is reported against the line that set it.
Actions read differently in each context: interrupt cancels a running turn, a
compaction pass, or a shell command depending on what is active, and does nothing
when nothing is running. Set disable_default_keymap = true to start from an
empty map, so only the presses you configure take effect. The completion popup
and picker keys are fixed and not yet configurable.
Transcript display¶
When a turn settles, the various tool and other UI blocks commit to scrollback. Tool and shell blocks
commit as a short summary by default. Ctrl-o toggles them to commit their full
output instead.
Thinking blocks commit their full body by default. Your config
can change that with
agent.configure_display:
If a block was collapsed and you find that you want to see into it,
look for the /recall note that shows you the record number to use
to recall that output from the block: /recall <id>.
A harness watchdog nudge shows as a dim chrome line,
» watchdog: <text>, distinct from an ordinary user message. The nudge message
itself is part of the conversation history. The line is display-only.
The status bar¶
Below the input box sits the status bar. By default it is two dim lines: a path line on top, and a status line below with your usage on the left and your session on the right.
/repos/wallah (main)
↑1.2k ↓3.4k R5.0k CH60.0% 50.0%/140 42 tok/s 210 ch/s claude-code · sonnet • medium
The path line shows the working directory and the git branch. The status line's left shows token counts, the cache-hit rate, the context gauge, and live throughput; its right shows the session name, the personality and mode, the model, and the effort.
Your config can throw all of that out and draw the bar itself, by handling the
format_status event:
local status = require('status')
agent.on('format_status', function(ctx)
return {
status.flat { status.span { text = 'hello from my config' } },
}
end)
The handler receives a ctx argument and returns a list of lines. One handler
may be registered. Without one the built-in bar shows. It runs on a roughly
once-a-second tick and again whenever the state it reads changes, off the main
loop, so a slow handler never stalls your typing or the model's output. If the
handler raises an error, the bar shows the traceback rather than taking down the
session.
Laying out a line¶
Each line is one of two shapes. Require the
status module to build them.
A status.flat line lays its pieces out
left to right. A status.fill between two
pieces eats the leftover width, pushing what follows to the right edge:
status.flat {
status.span { text = 'sonnet' },
status.fill { glyph = ' ' },
status.span { text = 'main' },
}
Give the fill a visible glyph and it draws a rule the width of the bar:
A status.zones line aligns independent
groups: left at the left edge, right at the right edge, and center in the
true middle of the whole width.
status.zones {
left = { status.span { text = 'sonnet' } },
center = { status.span { text = 'plan mode' } },
right = { status.span { text = 'main' } },
}
When the groups would collide, the center drops out first. If that is still not enough room, the right is trimmed from its own leading edge, keeping the left and the tail end of the right (the part closest to the edge) intact.
The pieces of text are status.span values.
A span is { text = '...' } plus any styling: fg, bg, bold, italic,
underline, dim, reverse, and strikethrough. Each color is a "#rrggbb"
hex string, a color name, a CSS color spec, or a palette index from 0 to 255:
Return as many lines as you like. The bar grows to fit, and when the terminal is too short for everything, the transcript above gives up its rows first and the bar is trimmed last.
Reusing the built-in pieces¶
ctx (a StatusCtx) contains the raw
values behind the bar, to render them however you like:
- Model and session:
model,provider,effort,thinking,personality,mode,session_name - Context and usage:
context_window,context_used,context_percent,input_tokens,output_tokens,cache_read,cache_write,cache_hit_rate,tokens_per_second,chars_per_second - Environment:
cwd,git_branch,turn_active,width
ctx.default (a
StatusCtxDefault) returns the
built-in bar's own pieces, already styled, each a list of spans you can drop
straight into a line. A piece with nothing to show is an empty list.
| Piece | What it renders |
|---|---|
tokens |
Up, down, and cache token counts |
cache_hit |
The cache-hit rate |
context |
The context-occupancy gauge |
throughput |
Tokens and characters per second, only mid-turn |
name |
The session name |
scope |
The personality and mode |
model |
The provider and model |
effort |
The effort level and thinking keyword |
cwd |
The working directory |
branch |
The git branch |
left |
The whole stock left group (tokens, cache_hit, context, throughput) |
right |
The whole stock right group (name, scope, model, effort) |
path |
The whole stock path line (cwd, branch) |
So you can keep most of the stock bar and change one part. Here the left side is left unchanged, the right is replaced with just the model, and the model turns red while a turn is running: