plank โ€บ User guide โ€บ The interface

3. The interface

The screen

The TUI is three regions: scrollback (the conversation), the prompt, and the status bar along the bottom.

Assistant replies render as markdown โ€” headings, lists, tables, and fenced code blocks with tree-sitter syntax highlighting. The model's thinking appears dimmed above its answer (turn it off with ui.showThinking). File edits render as git-style diff cards: an Update(path) header, an added/removed summary, and red/green @@ hunks. A brand-new file streams its contents dimmed as it is written.

The status bar is two rows. The top one answers "which tree am I in" and holds still: the working directory, the git branch, and a summary of what you have changed in that tree (๐Ÿ“„ 3 ยท +128 -41 โ€” files touched, then lines added in green and lines deleted in red, staged and unstaged together, untracked files included; a clean tree shows nothing). The bottom one carries everything that churns โ€” where inference is running, the reasoning level, a context-usage gauge, an activity throbber, what the model is doing, generation stats, the task counter, and the remote marker. When a tool is running its name sits in the notification slot and blinks; otherwise a rotating tip appears there.

Where inference runs is named on the second row, and there can be more than one answer:

api.regolo.ai, (local โšก100%)

Every engine in play is listed in the order it first appeared, so a hosted main agent beside a provider: local subagent shows both. The local engine carries its GPU power share (/power, --power), because the cap applies to that engine and to no other.

The brain blinks while the local model is prefilling or generating. It is the one signal that says which engine is working right now, which is otherwise invisible when a local subagent runs under a hosted main agent. It blinks in step with the elapsed and tokens/second readouts beside it.

While plank is compacting, the throbber-and-verb line is replaced by a flashing compacting with a progress bar and percentage:

compacting โ–ฐโ–ฐโ–ฐโ–ฐโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑโ–ฑ 21%

Most of the bar tracks re-reading the conversation, which is the slow part; the tail tracks the summary being written. It goes away as soon as the pass ends. See Compaction.

Every turn closes with a line of its own before the prompt comes back:

โœป Planked for 0h 02m 20s

That clock covers the whole turn โ€” every generate-and-tools round, plus any lines you typed while it was busy and it absorbed โ€” not just the last pass. It is the number to quote when something felt slow.

Keys

Editing the prompt

Key Action
Enter submit
Shift+Enter, Alt+Enter, Ctrl-J newline instead of submitting
Ctrl-A / Home start of line
Ctrl-E / End end of line
Ctrl-B / Ctrl-F left / right one character
Alt+Left / Alt+Right left / right one word
Ctrl-K delete to end of line
Ctrl-U delete to start of line
Ctrl-W / Alt+Backspace delete previous word
Ctrl-D delete forward, or quit on an empty line
Up / Down walk prompt history
Ctrl-P / Ctrl-N history back / forward (REPL)
Ctrl-L clear the screen (REPL)
Ctrl-G open the current prompt in an editor

Shift+Enter needs the kitty keyboard protocol to be reported, which not every terminal does โ€” Alt+Enter and Ctrl-J work everywhere.

Ctrl-G is the escape hatch for a prompt too long to edit inline. By default it opens plank's built-in editor (a fork of Microsoft Edit, running in-process, no external dependency). Set ui.builtinEditor to false to shell out to $EDITOR instead. Either way, what you save comes back into the prompt; cancelling keeps what you typed.

The same editor also edits files on disk: /open [path] takes over the terminal with the file loaded, Ctrl-S saves and Esc discards. See Slash commands.

During a turn

Key Action
Ctrl-C interrupt the generation (at an idle prompt: clear the input line)
Esc interrupt the generation
โ† on an empty prompt step into the agent roster, when there is one
mouse wheel / trackpad scroll the scrollback
click-drag select text to copy

With an arcade game open, the first Ctrl-C closes the game and a second interrupts the model โ€” you are never locked out of stopping a turn.

Esc at an idle prompt dismisses a /btw panel left open from an earlier turn, which is the only way it closes.

In a question panel

The ask tool's panel takes Up/Down to move, Space to toggle an option when the question is multi-select, Enter to answer, Esc to decline, and Ctrl-C to interrupt.

The agent roster

The moment anything is delegated โ€” by you with /subagent, or by the model with the agent tool โ€” a roster appears below the status bar: main first, then one row per run with a state bullet (โ— working, โ—‹ finished), the agent's name, the task it was given on one line, and, flush right, how long it has been going and what it has spent in tokens.

โ— main
โ— reviewer   check the diff on this branch          1m 12s  ยท 4.1k
โ—‹ researcher find every caller of load_config()        48s  ยท 9.7k

A fan-out gets a row each, with its own output buffer, so concurrent agents never overwrite one another. โ† on an empty prompt steps into the roster, โ†/โ†’ walk the rows, Enter expands the selected agent's output over the transcript with its own scroll position, and Esc comes back.

It is a live readout: it appears with the first agent and goes away with the last, staying put only while you are reading it, and โ† brings a finished roster back so a report you delegated is still reachable. The last eight runs are kept. The transcript itself gets only a one-line signpost, which is the point of delegating in the first place โ€” see Extending plank.

The plain REPL has no roster and prints subagent output inline instead; --non-interactive stays silent so its stdout protocol is not corrupted.

@ file completion

Type @ in the prompt and a fuzzy-completion popup offers file paths from the working tree; pick one and the path is spliced into your message. Tab accepts.

! and !! โ€” run a shell command yourself

Prefix a line with ! and it runs in your shell, in plank's working directory, with the output streaming into the screen as it is produced:

!cargo test --lib parser

The number of !s decides whether the model ever sees it:

What the model gets
!command the command and its output are recorded in the conversation, so the model has them as history on your next message
!!command nothing โ€” the output is yours alone

Neither form starts a turn. A ! command does not make the model respond; it just means that when you do send your next message, the model can already see what happened. So !cargo test followed by "fix the failure" works without pasting anything, while !!git log keeps a bit of poking around out of a context you are paying for.

Recorded output is capped (200 lines, 16 KB) with a truncation marker, so one runaway command cannot flood the conversation. If you want the model to act on something right now rather than on your next message, ask it in a normal turn and let it run bash itself.

Either form is your command, not the model's, so it is never sandboxed โ€” you typing it is the authorization. Both are also the right way to do anything interactive (a login flow, an editor, a pager) that the model's bash tool cannot drive.

Esc or Ctrl-C kills a running command, and Up/Down on a line that starts with ! walks only your previous shell commands.

The marker is coloured as you type, by where the output goes: red ! feeds the command and its output to the model, green !! keeps it between you and the shell. That is the only difference between the two forms and the only thing you cannot see once the line is typed. When the command finishes, plank says done. in green โ€” a command that printed nothing is otherwise indistinguishable from one still running. An interrupted command says [interrupted] instead, and a non-zero exit adds [exit code: N].

Pasting images

Paste an image (or a path to one) into the prompt and plank attaches it. On macOS an image on the clipboard arrives as an empty paste, which is the signal plank uses; pasting the path to an image file works too, including a file dragged onto the terminal. Either way the file is deduplicated by content into ~/.plank/image-cache/ and attached to your message.

The bytes are cached exactly as you pasted them. plank does not resample or re-encode, which it used to do to satisfy an image-upload limit it never actually had. Nothing uploads your pixels anywhere, so shrinking them only threw away the pixel density and DPI metadata that anything reading the image later would want. The vision encoder does its own resizing at encode time, from the original file.

The model can see it. plank ships a vision encoder alongside the main model, at ~/.plank/ds4flash.vision.gguf, and offers it to the model without any setup on your part: it is downloaded on first launch if it is missing, and the view_image tool is always available. Paste a screenshot of a stack trace and ask what broke, and the model looks at the picture rather than at a filename. The same applies to a diagram, a screenshot of a failing UI, or a photo of a whiteboard.

What your message carries is still the path. The model turns that path into pixels by calling view_image on it, which is why you see a view_image line in the transcript before the answer. The image is encoded locally, on the same Metal device as the model, so nothing about it leaves the Mac.

There is a size budget. An image is resized to fit 384 visual tokens, so a very wide or very tall screenshot loses detail before it loses shape. If you want the model to read small text, crop to the part that matters rather than sending the whole desktop.

For a page of dense text, an OCR pass is still the better tool: ocr-mcp transcribes an image to text instead of describing it, which is what you want for a full-page stack trace or a scanned document.

Reading PDFs

read converts PDFs to Markdown transparently, so you can just point at one:

summarize the first few pages of manual.pdf

See Tools.

Notifications and the window title

Long turns end with a native macOS notification: your prompt as the headline, the tail of the answer as the body (interrupted for an aborted turn). The terminal title tracks the current task, e.g. ๐Ÿชต plank - fix the bugโ€ฆ, and names the phase when plank is busy with something that is not your turn: ๐Ÿ—‘๏ธ compacting... while it reclaims context, ๐Ÿ‘€ introspecting... during /insights. The title it displaced comes back afterwards, so a compaction mid-turn returns the title to your prompt.

Animation, screensaver, exit

Leaving prints the session's token totals and, per model, the fastest sustained rates it reached:

peak DeepSeek V4 Flash  prefill 167.1 tok/s  ยท  generation 16.8 tok/s

Both figures are scoped to this session and never persisted โ€” a peak from last week was a different engine build on a differently loaded machine. They are the quickest way to see what a flag like --dspark actually did on your hardware.

Quieting the display

Three settings control how much of the machinery you see. None of them change what the model receives:

Setting Default Off means
ui.showToolCalls false tool-call banners hidden; tools still run
ui.showToolResults false tool output not echoed; the model still gets it
ui.showThinking true thinking hidden from the display; the model still thinks

Next: Slash commands โ†’