Documentation menu

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

RendererFlagWhat it is
Fullscreendefault, or --fullscreenAlt-screen (like vim/less): header, input and footer pinned, the transcript windowed to the viewport. The terminal is restored on exit
Inline--inlineA <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 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
SegmentMeaning
lecterThe last path segment of the target root
3f2a91c8The session id
/ Input and output tokens
R / WCache read and cache write tokens
CHCache 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
modelThe 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.

CommandArgumentAvailableWhat it does
/model-idleSwitch provider + model from your saved list, as an ↑/↓ menu
/effort[low|medium|high]idleSet the reasoning budget. With no argument, opens a menu
/compact[instructions]alwaysCompact and shrink the live context now
/context-alwaysToken and context usage, plus what a compaction pass would free
/new-idleStart a fresh session
/name<title>idleRename this session
/session-alwaysShow the current session's id and title
/resume-idlePick a past session from a filterable list
/fork-idleBranch the session at an earlier turn
/tree-idleThe same picker drawn as the fork tree: parent above child, indented by depth
/reload-idleRe-read keybindings.json without restarting
/mouse-alwaysRelease or re-capture the mouse
/help-alwaysThe keyboard-shortcut line. /hotkeys is an alias
/clear-alwaysClear the transcript view
/quit-alwaysExit 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:

PrefixEffect
/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:

  • !cmd never touches the conversation. That is the point of having a local variant next to the bash tool.
  • !!cmd appends 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:

KeyAction
Ctrl+DQuit — on an empty editor. Inside a draft it is forward-delete instead (see below)
Ctrl+CClear the editor, or quit when it is already empty
PageUp / PageDownScroll the transcript
Ctrl+EndJump back to the tail and resume following it
Ctrl+HomeJump to the first line of the transcript
Ctrl+OHand the mouse back to the terminal, or take it again

Editing, active when no overlay is open:

KeyAction
EnterSubmit, or steer a running turn
Shift+EnterNewline
Alt+EnterQueue a follow-up
EscapeAbort the running turn, else clear the editor
/ Move by character
Alt+← / Alt+B, Alt+→ / Alt+FMove by word
Ctrl+A / Home, Ctrl+E / EndStart / end of line
/ History, or vertical movement inside a multi-line draft
Alt+↑Pull queued steers and follow-ups back into the editor
BackspaceDelete backward
Delete / Ctrl+DDelete forward (Ctrl+D only while the draft is non-empty)
Ctrl+U / Ctrl+KDelete to line start / end
Ctrl+WDelete the previous word
Ctrl+YYank
Ctrl+V / Alt+VAttach the clipboard image to the draft (or drag a file into the window)
Ctrl+XCopy the last assistant message to the clipboard
Ctrl+GHand the draft to $VISUAL / $EDITOR and take back whatever you save
Ctrl+LOpen the /model picker (idle only)
Shift+TabCycle 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:

KeyAction
/ Previous / next row
EnterConfirm
EscapeCancel
TabComplete

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. alt is the meta modifier. shift is only meaningful on named keys, so Y canonicalises to y.
  • 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:

OptionKeyEffect
YesyApprove this one call
NonDeny it. The model is told, and continues
Always (don't ask again)aApprove 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.