The interactive TUI
On a real terminal locac run opens an interactive UI: a transcript, a prompt box you can keep
typing into while the agent works, an approval overlay for dangerous tools, and a status footer.
This page documents what is on the screen and every key that does something.
If you are piping output or running in CI, use --print instead. See
Running an audit.
The two renderers
| Renderer | Flag | What it is |
|---|---|---|
| Fullscreen | default, or --fullscreen | Alt-screen (like vim/less): header, input and footer pinned, the transcript windowed to the viewport. The terminal is restored on exit |
| Inline | --inline | A <Static> scrollback renderer. Output stays in your terminal's scrollback after locac exits, and your terminal does its own native scrolling |
Fullscreen is the default because the transcript is long and a windowed view keeps the input box where you left it. Inline is the right choice when you want the run to still be there after the process ends, or when your terminal's scroll and selection are better than anything a program can reimplement.
The fullscreen root is deliberately rendered one column short of the terminal width. Windows consoles wrap the cursor the moment the last column is written instead of deferring it, which lands the erase-to-end-of-line on the row below and shifts the rest of the frame down. Never writing the final cell makes that difference unobservable.
The footer
The status bar under the input box:
lecter · 3f2a91c8 · ↑42.1k ↓3.8k R120.4k W12.0k CH74% · $0.3120 · ctx 38% · claude-opus-4-8 ⚡high
| Segment | Meaning |
|---|---|
lecter | The last path segment of the target root |
3f2a91c8 | The session id |
↑ / ↓ | Input and output tokens |
R / W | Cache read and cache write tokens |
CH | Cache hit rate, shown once there is any input or cache-read traffic |
$… | Accumulated cost, hidden entirely while it is zero |
ctx N% | How full the context window is. This is what compaction watches |
| model | The model id, with ⚡<level> appended when a reasoning budget is actually going out on the wire |
The effort badge is drawn from the same field the adapter sends, so a level the endpoint would drop never appears there. See Providers for which wire protocols carry one.
Slash commands
Type / at the start of the line to open the palette. It filters as you type (a subsequence rank:
prefix beats substring beats subsequence), Tab completes, Enter runs.
| Command | Argument | Available | What it does |
|---|---|---|---|
/model | - | idle | Switch provider + model from your saved list, as an ↑/↓ menu |
/effort | [low|medium|high] | idle | Set the reasoning budget. With no argument, opens a menu |
/compact | [instructions] | always | Compact and shrink the live context now |
/context | - | always | Token and context usage, plus what a compaction pass would free |
/new | - | idle | Start a fresh session |
/name | <title> | idle | Rename this session |
/session | - | always | Show the current session's id and title |
/resume | - | idle | Pick a past session from a filterable list |
/fork | - | idle | Branch the session at an earlier turn |
/tree | - | idle | The same picker drawn as the fork tree: parent above child, indented by depth |
/reload | - | idle | Re-read keybindings.json without restarting |
/mouse | - | always | Release or re-capture the mouse |
/help | - | always | The keyboard-shortcut line. /hotkeys is an alias |
/clear | - | always | Clear the transcript view |
/quit | - | always | Exit locac |
Commands marked idle are hidden while the agent is running: forking or switching models mid-turn would branch or re-target a turn the loop is still writing.
Every discovered skill also appears as a /skill:<name> entry, so you can inject a playbook without
remembering its exact filename.
/context and /compact
Both print a deterministic one-liner. /context ends with what a compaction pass would do right
now:
would elide 14 results → ~28,400 freed
…or no compaction headroom yet when there is nothing to elide. /compact reports what it just
did:
compacted: 96,240 → 61,880 tok · elided 14 results · kept 6 evidence · freed ~34,360
kept N evidence is the evidence floor: results a finding cites are never elided, however old they
get.
/resume and /tree
/resume lists your past sessions newest first. /tree opens the same picker over the same
sessions, but drawn as the fork forest: a session that /fork branched sits under the session it
came from, one indent per generation.
session tree ▸ 4 of 12
auth audit — deserialization sweep
↳ retry with the taint tracer
↳ narrowed to the cookie parser
release checklist
That is derived entirely from the parent id /fork already records, so nothing extra is stored and
nothing is queried twice. Typing still filters, Enter still resumes, and a fork whose parent was
deleted becomes a top-level row rather than disappearing.
The input box
Four line-start prefixes change what Enter does:
| Prefix | Effect |
|---|---|
/ | Slash-command palette |
@ | Fuzzy file-reference picker over the project's files |
! | Run a shell command, with the output shown to you only |
!! | Run a shell command and also hand its output to the model |
The @ picker enumerates project files with Bun.Glob, skipping vendored, build and VCS trees so a
node_modules does not flood it, and it tells you when the file list itself was cut short rather
than silently showing nothing.
A bang line outranks both submit and steer, and works mid-run: !git log --oneline -5 while the
agent is working is exactly what the escape is for. The ! and !! variants differ in one way that
matters:
!cmdnever touches the conversation. That is the point of having a local variant next to thebashtool.!!cmdappends a user turn containing the command and its output, wrapped in the same⟦untrusted-output⟧fence a tool result would get. You typed the command, but its output came from the target, so it arrives as evidence, not as instruction.
If you have used pi, note that locac's two bangs are the other way round: here the single ! is
the local-only one and !! is the one that shares. That is deliberate. The dangerous variant, the
one that puts target output into the model's context, is the one that costs an extra keystroke.
Attaching an image
There are two ways in. Ctrl+V attaches the image currently on your clipboard to the draft, and
Alt+V does the same on terminals that keep Ctrl+V for themselves. Or drag the file into the
terminal window - your terminal writes its path into the line, and locac attaches the image instead
of pasting the path. Either way: a screenshot of a stack trace, a rendered payload or a crash dialog,
sent with your next message.
📎 1 image attached - sent with the next message
The attachment goes out with the next Enter and the draft is cleared. Attach several and they all
ride along. A message with an attachment and no text is a normal turn.
The clipboard reader is platform-native and adds no dependency: Clipboard::GetImage through
PowerShell on Windows, osascript on macOS, wl-paste or xclip on Linux. PNG, JPEG, GIF and WebP
are accepted. The format is decided by sniffing the magic bytes, never by what the helper claims
or by what the filename ends in, because a screenshot taken inside a target environment is content
you did not write. If the clipboard has no image on it, the box says so and nothing is attached.
A drop is only ever added to your paste, never substituted for it. Drag a .ts file in, drop two
files at once, or paste prose that happens to contain a path, and the text lands in the editor
exactly as it always did - only a single absolute path that turns out to hold real image bytes is
swallowed. Quoted paths, shell-escaped spaces and file:// URLs are all understood, since which one
your terminal writes is its choice, not yours.
The size ceiling
An attachment is capped at 3 MiB of raw image data. Over that, locac refuses it and says so:
image is 7.2 MB, over the 3.0 MB limit - not attached
It is a refusal rather than a resize on purpose. Re-encoding your evidence would mean a decoder dependency and a lossy transform of the thing you are asking the model to look at. Take a tighter screenshot, or crop it, and the answer improves along with the size.
The ceiling is not only about the provider's own per-image limit (base64 inflates the bytes by a
third, and 3 MiB lands under the usual 5 MB). An attachment is stored in the session database with
the turn that carried it and replayed to the model on every later turn of the run, so an
unbounded screenshot is a cost that repeats for as long as the session does. The transcript view
shows only the count; the bytes are in the message, which is why /compact and a fresh session are
the two ways to stop paying for one.
Keybindings
These are the defaults. Every one of them is remappable.
Global, resolved before any overlay:
| Key | Action |
|---|---|
Ctrl+D | Quit — on an empty editor. Inside a draft it is forward-delete instead (see below) |
Ctrl+C | Clear the editor, or quit when it is already empty |
PageUp / PageDown | Scroll the transcript |
Ctrl+End | Jump back to the tail and resume following it |
Ctrl+Home | Jump to the first line of the transcript |
Ctrl+O | Hand the mouse back to the terminal, or take it again |
Editing, active when no overlay is open:
| Key | Action |
|---|---|
Enter | Submit, or steer a running turn |
Shift+Enter | Newline |
Alt+Enter | Queue a follow-up |
Escape | Abort the running turn, else clear the editor |
← / → | Move by character |
Alt+← / Alt+B, Alt+→ / Alt+F | Move by word |
Ctrl+A / Home, Ctrl+E / End | Start / end of line |
↑ / ↓ | History, or vertical movement inside a multi-line draft |
Alt+↑ | Pull queued steers and follow-ups back into the editor |
Backspace | Delete backward |
Delete / Ctrl+D | Delete forward (Ctrl+D only while the draft is non-empty) |
Ctrl+U / Ctrl+K | Delete to line start / end |
Ctrl+W | Delete the previous word |
Ctrl+Y | Yank |
Ctrl+V / Alt+V | Attach the clipboard image to the draft (or drag a file into the window) |
Ctrl+X | Copy the last assistant message to the clipboard |
Ctrl+G | Hand the draft to $VISUAL / $EDITOR and take back whatever you save |
Ctrl+L | Open the /model picker (idle only) |
Shift+Tab | Cycle the reasoning-effort level (idle only) |
Three of those need a word of explanation.
Backspace and Delete really are different keys here. Backspace sends 0x7f on macOS and Linux
and 0x08 on Windows; the terminal layer reports both as backspace, and reserves delete for the
Delete key proper. So each one deletes its own side of the cursor, and Ctrl+D — readline's own
forward-delete — is the binding you want when your keyboard has no Delete key at all.
Ctrl+D is overloaded on purpose, the way a shell overloads it: on an empty editor it quits, and
inside a draft it deletes the character under the cursor. Without that split, one keystroke throws
away a long typed prompt. Both halves are separate actions (app.exit and
tui.editor.deleteCharForward) so you can rebind either one independently.
Alt+↑ un-queues. A line you Enter while the agent is running is queued, not sent; Alt+Enter
queues a follow-up the same way. Alt+↑ takes every still-queued line back out of the queue and puts
it at the top of your draft, so a steer typed too early can be rewritten before the loop pulls it.
Attachments are not restored — re-attaching is one keystroke or one drag, and a base64 screenshot in
the editor is not editable text.
Overlays. The approval prompt, /model, /effort, the palette and the session picker share
one navigation set:
| Key | Action |
|---|---|
↑ / ↓ | Previous / next row |
Enter | Confirm |
Escape | Cancel |
Tab | Complete |
Approval. Letter shortcuts on the approval overlay: y yes, n no, a always.
/help prints the same list, regenerated from whatever is actually bound:
keys — submit/steer enter · follow-up alt+enter · abort/clear escape · clear-or-quit ctrl+c ·
quit ctrl+d · copy last ctrl+x · paste image ctrl+v · model ctrl+l · effort shift+tab ·
unqueue alt+up · word left alt+left · word right alt+right · kill word ctrl+w · yank ctrl+y ·
$EDITOR ctrl+g · newline shift+enter · scroll pageup · follow tail ctrl+end ·
mouse → terminal ctrl+o · @ file · / command · ! shell (!! shares output) · ↑/↓ history · Esc abort
Rebinding
Write keybindings.json under LOCAC_HOME (~/.locac/keybindings.json). Each key is an action id;
each value is a combo string or an array of them:
{
"tui.input.submit": ["ctrl+enter"],
"tui.input.newLine": ["enter"],
"app.mouse.toggle": ["ctrl+t", "alt+m"]
}
The rules:
- A listed action's combos replace its defaults. Unlisted actions keep theirs.
- Combos are
ctrl+/alt+/shift+in that fixed order, then the base key.altis the meta modifier.shiftis only meaningful on named keys, soYcanonicalises toy. - An unknown action id or an unparseable combo is warned about and ignored, never fatal. If every combo for an action was invalid, the default is kept, so a typo can never silently unbind something.
- Malformed JSON falls back to the full default keymap with a warning.
- The file is read only from
LOCAC_HOME, never from the repository under audit.
/reload re-reads the file mid-session, and /help immediately reflects the new bindings.
The approval overlay
When the agent calls a dangerous tool and the run has no approval flag, the TUI parks the call and
asks. The overlay works mid-run and takes ↑/↓ + Enter or the letter shortcuts:
| Option | Key | Effect |
|---|---|---|
| Yes | y | Approve this one call |
| No | n | Deny it. The model is told, and continues |
| Always (don't ask again) | a | Approve it and stop asking for that tool this session |
The prompt shows the actual command, script or code in full, not truncated to a preview, because approving a command you cannot fully see is not approval.
Off a TTY there is no overlay, and with no --yes / --auto the call is simply denied.
Running an audit covers the full approval matrix.
Mouse
The fullscreen renderer has no native scrollback, so it turns on mouse tracking (button events, motion-while-held, and SGR coordinates). That gives you wheel scrolling that the transcript actually scrolls, instead of the terminal translating the wheel into ↑/↓ keys that the editor eats as history.
The cost is that the terminal itself no longer sees the mouse. locac therefore implements what it took: drag to select, and the selection is copied on release. Copying goes out both as OSC 52 (the only path that reaches the clipboard of the machine you are sitting at when locac runs over SSH) and through the local clipboard command, because several terminals, VS Code's among them, refuse OSC 52 writes by default.
For the one thing that cannot be done in-band, your terminal's own right-click menu, Ctrl+O or
/mouse hands the mouse back. It is a typed command as well as a keybinding on purpose: some hosts
swallow Ctrl+O (VS Code binds it to Open File), and a mode reachable only through a stolen key is
not reachable.
Both tracking modes are always toggled together, and are paired off on exit so your terminal's selection works again afterwards.
Next
Providers covers every built-in endpoint the /model switcher can select, and
Environment variables documents $EDITOR, $VISUAL, NO_COLOR and every
other variable the TUI reads.