Skip to content

Latest commit

 

History

History
212 lines (180 loc) · 11.7 KB

keys.md

File metadata and controls

212 lines (180 loc) · 11.7 KB

Keys

Key Does
Enter send the prompt
Shift+Enter new line — or Alt+Enter, Ctrl+J, or end the line with \
paste again the same block again: shows it, then collapses it back
Up / Down walk prompt history
Ctrl+C stop the turn; again to stop it now; twice at an empty prompt, exit
Ctrl+D exit, on an empty prompt
Shift+Tab cycle the permission posture, from the next turn
Ctrl+L clear the viewport, never the scrollback
Esc at an empty prompt, ask whether to undo the last turn — its files and all
Ctrl+T put the whole conversation back into the scrollback
Ctrl+F show the fleet: the children this turn has spawned
Ctrl+E put the running step's full text into the scrollback — what /expand shows, without typing a command while a step is moving
y / a / w / n choose an approval's answer: allow once, allow this session, allow and write it down, deny — Enter gives it
Esc stop the running turn, or close a picker without choosing
Tab in any list, take the row under the marker; Shift+Tab steps back
/ open the command palette — at the prompt or while a turn runs
@ after a space, complete a path from the workspace
! run the rest of the line in your shell; the agent never sees it

Shift+Enter works where the terminal reports it. io negotiates the Kitty keyboard protocol on terminals that advertise it, asking for one flag — DISAMBIGUATE_ESCAPE_CODES — because without it a terminal sends the same byte for Enter and for Shift+Enter and the newline binding is unreachable. What is pushed is popped again on every path out of the process, a panic included. The trailing-backslash fallback still works everywhere, and on a terminal that does not advertise the protocol nothing is written at all.

And from 0.13.0 io tells you which one that is. The table above is the shipped naming, and a README is read on a machine other than the one it describes — so /help and the wizard's closing screen name the key this terminal can report. On one that cannot report Shift+Enter they name Alt+Enter and the trailing backslash, and say the key is unreportable here rather than leaving you to press it and watch a half-written prompt go to the model. Nothing about the composer changed: Shift+Enter, Alt+Enter, Ctrl+J and a trailing \ all still work wherever the terminal can distinguish them.

Four more keys exist only while something is queued, and they are deliberately not in the table above: that table is what the session binds all the time, and these are borrowed by the queue surface for as long as it is open and handed back the moment it shuts. While a prompt is waiting behind a running turn, the arrows mark a queued line instead of walking prompt history, Shift+the arrows move the marked line up and down the queue, Enter on an empty prompt takes the marked line back into the composer to edit — Enter again puts it back where it was — and Esc abandons an edit in progress, or closes the surface and gives all four keys back. Every other key still falls through to the composer, because typing is how the next line joins the queue. It is the same trade the fleet view makes with the same two arrows, and /steer is what sends the queue into the turn rather than waiting for it.

Two more are borrowed by /config from 0.28.0, and they are left out of the table above for that same reason: it is what the session binds all the time, and these are held for as long as one list is open and handed back when it shuts. Right and Left on a /config row open that setting's values as a list, with the marker already on the value in force, so you can see where you are before you move. No picker does anything with a horizontal arrow — they fall through to a do-nothing arm — and the interception is scoped to this one list, so it takes no key away from any other surface. The composer still moves its cursor with them and an approval still moves between its answers; neither has the keyboard while a picker is up.

Until 0.33.0 those two arrows wrote the file on the keystroke. On a boolean or a closed set of words they stepped to the next value, wrote it into a scope file and redrew the row — one press, one write, no confirmation. It was the only unconfirmed write in this product reachable from a bare arrow key, and it is why /config could not be opened while a turn was running. The arrows now do what Enter does; Enter on a value is the confirmation, exactly as it is on every other managed surface, and Up/Down move inside the values while Left/Right do nothing there. Nothing about the arrows is a shortcut past a decision any more, which is the whole of the change: the affordance was worth keeping and the write was not.

A third is borrowed by a question that takes several answers, and it is the spacebar. Where the agent asks something that accepts more than one choice, the offers are drawn with a box in front of each — [ ] and [x] in both glyph sets, because a tick is not a character every terminal has — and Space marks and unmarks the offer under the marker. Enter sends the marked set; with nothing marked it sends the offer you are looking at, so a reflexive Enter still answers with something the agent asked rather than with nothing.

It is borrowed and not bound, and the distinction is load-bearing. Every other list in this product treats a printable character as a filter, and half the labels it filters over have spaces in them — Any OpenAI-compatible endpoint, a session's own workspace row — so a spacebar taken from every picker at once would be a filter that cannot spell the rows it is filtering. Only a list that was told it accepts several spends the key, and on that list a space typed into the free-text row is prose again, because that row is not an offer and cannot be marked.

A marked offer stays marked while you narrow the list, including when the narrowing hides it. An operator marking five rows out of four hundred narrows the list to find each one, and a filter that un-marked as it went would throw away the marks made under the last query. The cost is stated rather than hidden: a marked row can be off the screen while it is marked.

PgUp and PgDn are borrowed when the agent asks several things at once. They walk the batch, one question on the screen at a time; see While it works for what that surface does.

Moving a key

The keys the session itself owns can be rebound in [app.io-cli.keys], by action name:

[app.io-cli.keys]
clear = "ctrl+k"
rewind = "ctrl+r ctrl+r"
Action Default
exit Ctrl+D
posture Shift+Tab
clear Ctrl+L
transcript Ctrl+T
rewind Esc
fleet Ctrl+F
expand Ctrl+E

Ctrl+E may already belong to your terminal or your multiplexer, and if it does, rebind it — that is what this table is for. It was chosen over Ctrl+X and Ctrl+R, which readline and every shell built on it have spoken for.

A binding is a chord, or two chords separated by a space. Modifiers are ctrl, alt and shift, joined to the key with +, in any order and any case; a key is a single character, a named key — esc, enter, tab, backtab, space, the four arrows, home, end, pageup, pagedown, backspace, delete, insert — or f1 through f12. Because + is the join, + itself cannot be bound; that is a real limit of the syntax and stating it beats a rule that quietly works for plus and not for the character anyone would type. This spelling is public contract from 0.6.0 on: it is the one VS Code, Zed and helix already write.

The rest of the table is not rebindable, because those keys belong to whatever owns the keyboard while it is up — the composer, an approval, a picker — and an approval's y, a and n are the words of the answer rather than shortcuts for it.

From 0.40.0 an approval waits for Enter, and this is a change to a key you already know. y, a and n move the highlight to that answer; nothing resolves until you press Enter, which is the same two-step the arrow keys have always used. It costs one keystroke and it buys the thing that was wrong: a letter inside a sentence can no longer decide a write. Typing "no, not that one" at an approval used to deny with the n and leave the rest of the sentence in the composer, which was then sent as a prompt.

Ctrl+C is fixed, and it is the only one that is. It interrupts a running turn and leaves io, so a configuration file able to take it away is one able to lock you inside a running agent. Both spellings of that mistake are refused out loud with the reason: naming interrupt, and putting any other action onto ctrl+c.

Nothing about a bad line is fatal and nothing is silent. A value that cannot be read leaves its action on the default and names the key it kept; a name that is no action of ours says which names there are; and every notice is committed into the scrollback as the session starts, rather than left to be discovered by pressing something. /help renders the table as the session actually behaves rather than the defaults that shipped, and marks Ctrl+C as fixed.


README · All guides · What you may depend on

While a turn is running

Eleven commands run while the agent works, and they are /status, /context, /cost, /stats, /help, /theme, /copy, /expand, /fleet, /image and /config.

Ten of them only report. /context withhold <tool> is the one that changes something, and it is admitted because watching the agent reach for a tool is exactly when you know you want it withheld. It takes effect on the next turn — this one's contract was built before you typed — and it writes no file, which is the test every command on this list has to pass.

The palette opens mid-turn too, and path completion with it — the / and the @ in the table above. /compact and /steer reach the running turn through their own arms, as they always have, and are not part of this set.

Until 0.32.0 every slash but those last two was refused with not while a turn is running — Ctrl+C interrupts it first, so reading a status page meant stopping your own work to do it. None of these was a missing capability; each was one the product had and withheld.

Everything else keeps that refusal, and the rule is what a command does rather than how harmless it looks. A command that reassigns the session or the provider, writes the store or a configuration file, or submits a turn of its own is refused: /exit, /setup, /model, /resume, /fork, /commit, /policy, /remember, /memory, /skills, /mcp, /provider, /plugin, /import, /profile, /effort, /contain, /undo, /plan, /clear, /store, /export, /gates, and a ! line.

/config joined the first list in 0.33.0, and only in its bare form. Through 0.32.0 it was refused in every form, because the bare list carried a row that re-read your provider's catalogue, wrote the prices into a scope file and reassigned the configuration the running turn was holding — and because a horizontal arrow on a row wrote the file where it stood. Neither is there now: the refresh moved one descent below prices.as_of, the arrows open the values instead of writing one, and what is left is a list of what every setting is and which file decided it.

So the split is no longer between a safe spelling of a command and a dangerous one. /config <key> and /config <key> <value> descend toward a write and keep the refusal, which is the first time the mid-turn rule has read past a command's first word. A trailing space is still the bare form.