Scripts and agents
Besides opening the window, the cornercase command drives the server that is already running. A script can open a tab and run the tests in it, a git hook can leave a note, and a coding agent in one tab can start others in their own worktrees, wait until they finish or need an answer, read what they wrote and send them the next step, or follow all of them at once as things happen.
pane=$(cornercase new-tab --name tests -- cargo test)cornercase wait --pane "$pane" --until shell --timeout 900cornercase read --pane "$pane" --lines 40These commands never start a server, except start-server. Without one, they say no cornercase server is running and exit with status 1.
Start without a window
Section titled “Start without a window”A server waits for its first window before it restores your session, and until then commands that open terminals refuse. A script, a cron job or another UI that never opens a window starts the server with start-server first:
cornercase start-serverpane=$(cornercase start claude --prompt "fix the failing test" --worktree fix/test)It starts the server if none runs, restores the saved session once (or opens the folder you ran it from), and returns once its terminals exist; run again, it does nothing and says the server is already running. Until a window attaches, terminals are 160×48, or --cols and --rows. A window that attaches later finds the same tabs, with the same ids, takes over the size and gives the terminals its colours, unless you gave them with --theme FILE. See Command line for its output.
Ids and where commands act
Section titled “Ids and where commands act”cornercase status lists every project, workspace, tab and pane with its id:
project 1 web-shop ~/code/web-shop (shown) workspace 2 main ~/code/web-shop branch main (shown) tab 3 claude working (shown) pane 4 claude working Opus 5.5 · 23% ~/code/web-shop (shown) tab 6 zsh pane 5 zsh ~/code/web-shop (you) tab 14 claude working pane 13 claude working Opus 5.5 · 4% ~/code/web-shop (parent 5) workspace 7 fix/login ~/.cornercase/worktrees/web-shop/fix-login branch fix/login worktreegroup 9 personal collapsed project 10 blog ~/code/blog- Ids come from one counter and last as long as the server runs: after a restart, the same tabs get new ids. Take them from
status, or from what a command printed. - Every pane’s shell has
CORNERCASE_PANEset to its id. Inside a pane, commands act on that pane, its tab, workspace and project unless you name another one;(you)marks it instatus. Run from anywhere else, they act on what the window shows, marked(shown). - A pane opened by
start,new-taborsplitrun inside another pane remembers that pane as its parent, marked(parent 5)instatus(and(collapsed)once its children are collapsed in the agents section): an agent that starts others finds them as its children, and the agents section lists them under it. A flow’s members have its orchestrator as parent, and(role coder)instatussays which role each plays. Run from anywhere else, the new pane has none. Once the parent pane closes, the mark goes. - Where a command takes
--workspace ID,--worktree BRANCHnames the workspace on that branch instead, looked up in the same project: the one the command runs in, else the shown one.cornercase start claude --worktree fix/loginmade it, socornercase close --worktree fix/login --remove-worktreecan take it down without reading its id fromstatus. A branch that no workspace there is on, or that several are on (a workspace without a worktree of its own shares its project’s branch), exits with status 2 and lists the workspaces to pick from. - The pane is what
send,keys,readandwaitwork on. Where they take--tab ID, it stands for the tab’s agent pane, or its active pane when no agent runs in it. - Nothing changes what the window shows except
focus,goand the--focusoption. Whoever is looking at the window keeps seeing what they were looking at while a script opens tabs next to them. With--view IDthey move that view instead, and the window stays where it is. - With
--view ID, the defaults are what that view shows: its project, workspace, tab and that tab’s active pane, even inside a pane.
Commands
Section titled “Commands”| Command | What it does |
|---|---|
status |
Lists groups, projects, workspaces, tabs and panes with their ids, or one line per pane with --panes; --follow keeps printing the whole state as it changes. |
open PATH |
Opens a folder as a project, or finds the open one. Prints the project’s id. |
new-workspace NAME |
Adds a workspace to a project, or a git worktree with --worktree. Prints its id. |
new-tab |
Opens a tab, optionally typing a command into it. Prints the pane’s id. |
split |
Splits a pane right, or down, optionally typing a command. Prints the new pane’s id. |
start [AGENT] |
Starts a coding agent in a new tab and gives it a prompt, or with --issue the issue’s. Prints the pane’s id. |
issue sources, issue list, issue read KEY, issue people SOURCE |
Read the issue trackers through the server: which are connected, their lists with the Issues dialog’s filters, an issue with its comments, and whom the filters can name. |
flow list, flow run NAME, flow export NAME, flow import SOURCE, flow delete NAME, flow hide NAME, flow restore NAME |
Lists the flows, starts one (a team of agents in a worktree, run by an orchestrator agent), prints one as text to share, adds one someone shared, deletes one of yours or the project’s, or deletes a built-in flow and brings it back. |
flow runs, flow messages, flow files |
Where each running flow stands, what its roles sent each other, and the files of its folder: see Follow a flow run. |
send [TEXT] |
Pastes text into a pane, and presses Enter with --enter. |
keys KEY… |
Presses keys in a pane. |
search TEXT |
Finds the panes where some text was said: in the agents’ prompts and replies and in every pane’s history. |
read |
Prints a pane’s screen, its last lines, or the last messages of one or several agents. |
wait |
Waits until an agent stops working, a program ends, a line shows up or the output stops, in one pane or in the first or every one of several. |
events |
Prints what happens as it happens: agents changing state, programs ending, panes, tabs, workspaces and projects opening and closing, with --host programs’ clipboard writes and bells, and with --notices the notices about agents. |
surface |
Streams a tab’s terminals as cells with their panes and dividers, for a program that draws them, optionally as patches. |
changes |
Lists, reads and marks the changes of a workspace, as the changes panel shows them, for a program that draws it. |
view |
Opens a view of your own: its own tab and size, for a program that draws cornercase itself. |
view-set |
Changes a view: its size, whether it is active, or takes back the tab it shows. |
input --pane ID EVENT… |
Types keys with modifiers, text, pastes and mouse events into a pane, for a program that draws it. |
scroll, selection, ratio, resize, link |
Scroll a pane’s view, select or copy text in it, move a divider of a tab’s split, lay a surface stream out at another size, or say which file a click on a cell opens, all for a program that draws the terminals. |
redraw |
Sends a surface stream a complete line now, for a program that applies patches. |
close |
Closes a pane, tab, workspace or project; --remove-worktree --check says first what removing a worktree would lose. |
group, move, collapse, expand, move-pane |
Arrange the sidebar and the panes: create, style and delete groups, move rows, fold them, swap or move panes of a tab. |
rename NAME |
Renames a tab, workspace, project or group. |
focus |
Shows a pane, tab, workspace or project in the window. |
go TOKEN |
Goes to the agent a notice is about, as a click on its desktop notification does, in the window or with --view in a view. |
notify TEXT |
Shows a message in the window, with a desktop notification. |
todo |
Adds, lists, edits, checks off and back, reorders and removes items of the TODO list (with undo), and sends one to an agent as send --enter would. |
config |
Reads every setting, connection and project’s commands, and changes them with the Settings dialog’s own checks: settings for a program that draws its own preferences. |
away |
Has agents message you on Telegram or Discord; --off stops it. |
usage |
Prints your Claude Code and Codex plan limits as the Usage dialog’s Limits tab shows them; --cached prints the last answer without asking the agents again. |
plugin actions, plugin run PLUGIN ACTION |
List the actions your plugins add to the menus, and run one on a project, workspace, tab or pane as its menu entry does, waiting for it to end. See Plugins. |
skill |
Prints the instructions that teach coding agents these commands. |
contract |
Says what the running server supports: its version, the contract it speaks, its boot and its operations and streams. |
cornercase --help and cornercase COMMAND --help describe every option, with examples.
cornercase status # the tree abovecornercase status --json # the same, as JSONcornercase status --panes # one line per pane, for scriptscornercase status --panes --json # the same, as JSONcornercase status --follow # the whole state as JSON, again after each changecornercase status --app # the TODO list, queue, away, update and keep awake as JSONcornercase status --app --follow # the same, again after each changeEach pane shows its program and folder. Recognised agents that emit OSC 7501 show their reported status, including when no session reader exists. A pane running Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI or GitHub Copilot CLI also shows what the agent is doing (working, waiting for an answer, pending review, done while you were elsewhere, failed, idle; without OSC 7501, opencode is never waiting), its model and how full its context is, as the tab icons do. working (background shell) is a Claude Code agent whose turn is over while a shell it started in the background (a test watcher, a dev server) still runs: its tab keeps showing it as working, since Claude wakes up when that shell ends, but a script can tell it apart and stop waiting with --until turn-over. working (2 subagents) is an agent whose own turn is over while subagents it started (Claude Code, GitHub Copilot CLI) still run: it goes on when they report back, so it stays working, and the default, --until idle and --until turn-over waits keep waiting for them. (dialog open) is a Claude Code agent showing something else where its input box should be (a picker, the transcript view, a background shell’s details, its ! shell mode), which send refuses. (survey open) is Claude Code’s feedback survey above its input box, which send refuses too. A workspace shows its branch, worktree when it has one of its own, the commits it is behind, and its pull request with the state of its checks, such as pull request #485 open, checks failing, or pull request #486 open, conflicts with main, checks passing when it has merge conflicts.
One line per pane
Section titled “One line per pane”A script that coordinates agents mostly needs the panes: which agent runs where, what it is doing, in which worktree. status --panes lists them flat, so nothing has to walk the tree: one line per pane, in the order of the sidebar, under a header line that names the columns, separated by tabs.
pane tab workspace project program agent status background_shell dialog at_prompt model context caller shown active tab_name workspace_name branch worktree project_name path survey resuming parent subagents role held_sends draft4 3 2 1 claude claude working false false false Opus 5.5 23 false true true claude main main false web-shop /home/ana/code/web-shop false false - 2 - 1 true5 6 2 1 zsh - - false false true - - true false true zsh main main false web-shop /home/ana/code/web-shop false false - 0 - 0 false12 11 7 1 codex codex waiting false false false gpt-5.4 41 false false true codex fix/login fix/login true web-shop /home/ana/.cornercase/worktrees/web-shop/fix-login false false 5 0 - 0 false| Column | What it holds |
|---|---|
pane, tab, workspace, project |
The ids of the pane and of the tab, workspace and project it is in. |
program |
The program in the foreground, as the tab names it. |
agent |
claude, codex, gemini or opencode when one runs in the pane. |
status |
What the agent is doing: working, waiting, pending, done, failed or idle. |
background_shell, dialog, at_prompt |
As in the JSON: the agent’s turn is over but a background shell still runs; a Claude Code agent shows a dialog instead of its input box; the shell sits at its prompt with nothing running. |
model, context |
The agent’s model and how full its context is, in percent. |
caller |
true for the pane running the command. |
shown |
true for the pane the window shows. |
active |
true for its tab’s active pane. |
tab_name, workspace_name, branch, worktree, project_name |
The tab’s and workspace’s names, the workspace’s branch and whether it has a worktree of its own, and the project’s name. |
path |
The folder of the program in the foreground, in full. |
survey |
As in the JSON: a Claude Code agent shows its feedback survey above its input box, which send refuses until keys --pane … 0 dismisses it. |
resuming |
As in the JSON: the pane’s agent is coming back in its conversation after a restart, and takes no input until it is back. |
parent |
The id of the pane this one was started from, while that pane is open: start, new-tab or split run inside it, or a flow’s orchestrator for its members. |
subagents |
How many subagents the pane’s agent started that still run (Claude Code, GitHub Copilot CLI), 0 otherwise. While it is above 0, subagents keep an otherwise idle (or background-shell) agent’s status at working, even when its own turn is over; a question (waiting) and an OSC 7501 report still take precedence, so scripts must still handle the reported status. |
role |
The role the pane plays in a flow run (orchestrator, coder…) while the run lives, - otherwise. |
held_sends, draft |
As in the JSON: how many messages send is holding for this pane, and whether someone is typing a draft in it. |
- The values are the ones
--panes --jsongives:trueorfalse, numbers without a unit, full paths. A-stands where there is no value (no agent, no branch, a field the server is too old to know), so a column never goes missing andreadkeeps them in place. A tab or a line break in a name shows as a space. - The columns keep their order, and new ones only ever go at the end, so
$7stays the status. --panes --jsonprints{"panes": [...]}, one object per pane with the columns as keys, in the same order, andnullwhere the text shows-.
# the agents that wait for an answercornercase status --panes | awk -F'\t' '$7 == "waiting" { print $1 }'
# each pane with its agent and status, skipping the headercornercase status --panes | tail -n +2 | while IFS=$'\t' read -r pane tab workspace project program agent status rest; do echo "$pane $agent $status"done
# the agents in the worktree of fix/logincornercase status --panes --json | jq '.panes[] | select(.branch == "fix/login" and .agent != null) | .pane'Follow the whole state
Section titled “Follow the whole state”Another UI for cornercase, or a script that draws the sidebar somewhere else, needs the whole session and every change to it: names, order, groups, folds and how each tab is split. status --follow prints it as one JSON line, first as it is now, then again shortly after each time something in it changes:
cornercase status --follow | jq -c '[.revision, (.projects | length)]'cornercase status --follow | while read -r state; do echo "$state" > /tmp/cornercase-state.json; done- Each line is what
status --jsonprints, plusrevision, a number that grows with every line, exceptcaller, which is alwaysnull: find your own pane withCORNERCASE_PANEorstatus --jsoninstead. It is always JSON, with--jsonor without, and it can’t be combined with--panes. - Each line is the whole state and replaces the one before: there are no differences to apply, so a reader can skip lines and lose nothing but the steps in between.
revisiononly compares within oneboot, the start of the server it came from. It ends, with status 0, when the server stops or restarts; to go on, run it again: its first line is the state at that moment, so nothing is missed. The same goes for a connection that ended (status 1).- A line comes shortly after a change, not for every change: several changes close together give one line with all of them. Output alone (a program printing in a pane) gives none, but what
status --jsonshows moves too, such as an agent’s status, its context or a pane’s folder, and each of those gives a line, so a working agent can bring one every few seconds. - A reader that falls behind isn’t disconnected for it: the server keeps up to four lines waiting for it, which it still reads, older ones first; past those it keeps only the latest, so once it has read them it reads the state as it is now, with a jump in
revision. One that stops reading altogether is disconnected once the server has waited a second to write to it, like any client.
Follow a workspace’s changes
Section titled “Follow a workspace’s changes”The changes panel’s data is there for another UI too: the changed files with their status and counts, each file’s diff with the words that changed and its highlighting, the unchanged lines between two hunks, the branch it compares with and the files you marked as viewed.
cornercase changes list # the workspace you are in, uncommittedcornercase changes list --every --follow # every project's uncommitted changes, as JSON linescornercase changes list --worktree fix/login --mode commits --jsoncornercase changes file src/main.rs --json # one file's diffcornercase changes gap src/main.rs --hunk 1 # the unchanged lines before its second hunkcornercase changes base origin/main # compare Commits and All with another branchcornercase changes viewed src/main.rs # mark it viewed; --clear clears itchanges(cornercase changes list --follow) is a stream, one per connection. It answers a first summary at once, which may say"state": "loading"while git reads, then sends a whole new one each time the files change, with a higherrevision; a line is only sent when its content differs from the last. Each summary replaces the one before it, and a reader that falls behind reads the latest, as withstatus --follow.- A summary has
boot,stream(its number, forchanges-mode),revision,scope(projectorevery),mode(uncommitted,commitsorall),state(loading,readyorfailed, witherror) andworkspaces: each withproject,workspace,base(what Commits and All compare with,nullin Uncommitted),added,removedandfiles. A file haspath,old_path(for a rename),status(modified,added,deleted,renamedoruntracked),added,removed,fold(open,lockfile,binaryorlarge: lockfiles show their lines, binary and large files have none),viewedanddigest. Summaries have ids only; the names are instatus. - A project stream has one workspace entry, with no files when there are none. An Every project stream (
--every, only for Uncommitted) has one entry per workspace with changes, in the sidebar’s order, and never ends by itself. A project stream ends when its workspace or project closes. A workspace nobody follows costs nothing: git runs only for what a stream or the window shows. - The mode belongs to the stream:
changes-modechanges a running stream’s mode, and the window’s tabs don’t move. It only reaches a stream the same connection opened, so it is a request for a program holding the stream’s connection open over the socket: a separate command invocation uses a new connection and getsnot-found, which is whycornercase changeshas no command for it (start anotherchanges list --mode …instead). A secondchangeson one connection is refused withconflict. The base and the viewed marks are shared with the window: choosing a base here changes what its panel compares with, and a mark set here shows as its✓. changes-fileanswers a file’shunks:old_start,new_start,contextandlines, each withkind(context,removedoradded),oldandnewnumbers (nullwhere the line is not on that side),text(tabs as four spaces),emphasis(the words that changed, as[start, end)pairs in characters oftext) andsyntax(runs ofstyleandtext, left out where the line isn’t highlighted).stylesis the table the runs point into, in the same shape as a surface’s: palette indices, never RGB, so the client’s own theme and tints apply; entry 0 is plain.changes-gapanswerslineswith theiroldandnewnumbers andsyntax, for the lines before hunkhunk(counted from 1: hunk 0 has no gap before it).changes-basewithout arguments listsbranches, thedefaultbranch and the currentbase; withbaseit sets it (the default branch goes back to following the default) and withclearit clears it.changes-viewedsets a mark with the file’sdigest, or clears it withclear: a mark set with the digest of a version the file no longer has does not apply, as when the file changes under the window’s mark.digestis a string of 16 hexadecimal digits that changes whenever the file’s change does.status --jsonalso gives each workspacechanges(files,added,removedandmode), for a badge like the button’s, while cornercase keeps it current: the window’s workspace, one a stream follows, or any while a stream follows every project; it is left out otherwise.
Follow the rest of the app
Section titled “Follow the rest of the app”The session is not all of cornercase’s state. The TODO list, the queue, whether you are away on each chat app, the update found and keep awake live outside it, and another UI needs them too. status --app prints them as one JSON object, and --follow prints it again each time one of them changes:
cornercase status --appcornercase status --app --follow | jq -c '[.revision, (.todos | length)]'todos: the items astodo list --jsonhas them, in the panel’s order.queue: the entriesqueue list --jsonhas, prompts included.away: one row per chat app,telegramthendiscord, each withready(paired, and the bot’s token not refused) andaway.update:release(versionand itsnotes, each with aversionandtext), ornonesaying why there is no release:disabled(a development build),off(check_updatesis off and nothing was checked),unchecked,up-to-dateorfailed. Alsochecking,checked(UTC time of the last answer),error(why the last check failed),install({"method": "replace", "path": …}when cornercase can replace itself,{"method": "command", "command": …}when you must run a command, as with Homebrew),installing,installed(restart to use it) andinstall_error.update-checkandupdate-installdrive it.keep_awake: as instatus --json, ornullwhile the setting is off.versionandbootare the server’s, as instatus.- It follows the rules of
status --follow: every line is the whole state and carries arevisionthat grows by one per line,revisiononly compares within oneboot, a reader that falls behind reads at most four older lines and then the latest, and it ends when the server stops or restarts. A line is built at most twice a second, and only while someone follows. It is a stream of its own so that the session’s lines do not carry the queue’s prompts and the release notes every half second.
Follow a flow run
Section titled “Follow a flow run”A flow run is state of its own: the stage the work is in, the loops, each role’s pane and status, what the roles sent each other, the files they wrote. Another UI needs it to draw the Flow panel, and a script needs it to know whether a team is done.
cornercase flow runscornercase flow runs --follow | jq -c '[.revision, (.runs | length)]'cornercase flow messages --worktree fix-login --followcornercase flow files --worktree fix-loginflow runsprints one block per running flow: its workspace, the stage (counted from 1 for people), the rounds of its loops, the task’s first line, each role’s pane and status (closedonce its pane is gone), the last step and the last one outside the plan, and whether the orchestrator marked its pane for review, which is how a run ends.--followprints the same as one JSON line each time something changes, until the server stops. The request isflow-runs, a stream likestatus --app: every line is the whole state with arevision(seestatus --followfor what that guarantees), built at most every half second and only while someone follows.- A run in the JSON has
run(its id, whichflow runanswers too and which a restore changes),project,workspace,flow(the plan the team was briefed with, as in Manage flows, frozen at the start: editing or deleting the flow does not change it),task,dir(the run’s folder),roles(the orchestrator first, then the flow’s roles in order; each withrole,pane,tab,status,closed, andworktreeandbranchfor a role with a worktree of its own; a role with no pane, after a restore that could not bring it back, hasnullfor all),stage,rounds,holder,last,outside,endedandmessages. - Stages here count from 0, the index in
flow.stages, unlike a loop’sback.to, which counts from 1 as in the flow’s file.stageis the stage the work is in (nullbefore the orchestrator sends anything),roundshas one number per stage (how many times its loop went back), andholderis the role that last got the work. A step is{"edge": …, "from": …, "to": …, "time": …}, whereedgeis{"kind": "forward", "stage": N}(it leaves stage N),{"kind": "loop", "stage": N}(stage N’s loop) or{"kind": "outside", "from": N or null, "to": M}(a role of a stage the plan does not lead to),fromis the role that held the work before (not the sender, which is always the orchestrator) andtimeis UTC.outsideis the last step outside the plan, andendedistrueonce the orchestrator’s pane is marked for review. Only what the orchestrator sends into a role moves the work, as in the diagram. flow messagesprints what the orchestrator and the roles of a run sent each other: its sends, and the keys it pressed, the screens it read and the waits it made, each with its time, who, to whom and the text.--workspace IDor--worktree BRANCHnames the run (the one in the pane’s own workspace, else the shown one, by default); with no run there it says so.--followgoes on printing until the run ends. With--jsoneach message is one line:run,workspace,seq(counted from 1 for the run),time,from,to,kind(send,keys,readorwait),text(nullwhen there was none),bytes(the real size: a text is kept up to 32 KiB) andedge, the step along the plan it moved the work through (nullwhen it moved nothing); the backlog comes first, expanded into those lines. Only the last 256 messages of a run are kept, in memory, and never logged or saved, so a run that was restored starts with none. The request isflow-messages, a stream of its own on its connection: its answer is{"boot", "run", "workspace", "messages": [...]}with every message kept so far, then each new message is one line, in order, with no gap in between, and it ends (Detached) when the run does. A client that cannot keep up gets a{"event": "dropped", "count": N}line, as in Follow events, and reconnects to read what is kept. A secondflow-messageson one connection isconflict.flow fileslists the files of the run’s folder, newest first, at most 200, as a table (name,bytes,modified,path;--jsonprintsworkspace,run,dir,filesandmore). After the run ended it lists the last folder the workspace’s runs made, for as long as the workspace lives; a workspace that never ran a flow isconflict, and a folder that was deletedfailed. The request isflow-files, one answer. To read a file, open itspath: reading it over the socket is left out, as it is for the files panel.
Stream a tab’s terminals
Section titled “Stream a tab’s terminals”A program that draws cornercase’s terminals itself, such as an editor extension or a GUI, needs more than read’s plain text: the cells with their colours, where the cursor is, which modes the program turned on, and how the tab is split. surface streams all of it for one tab, laid out at the size you give, as one JSON line:
cornercase surface --tab 3 --cols 120 --rows 40cornercase surface --tab 3 --cols 120 --rows 40 | jq -c '[.revision, [.panes[].pane]]'The first line is the tab as it is now, the next ones come when something it shows changes, at most about 60 a second and never twice the same. Each line is complete and replaces the one before, unless you ask for patches. Find the tab’s id with cornercase status. The tab doesn’t have to be the one the window shows, and streaming it doesn’t switch to it or change anything the window shows. It works on a server started with start-server, with no window.
A line has these keys:
boot,session,surfaceandrevision: the server’s start (see Contract and compatibility), therevisionof the lateststatus --followline sent before this one was drawn (0 if none was sent since the server started), this stream’s id (the oneresizetakes) and its own counter, from 1, up by one per line. A line from anotherbootis stale. To get a complete baseline again, run the command again: its first line is always complete.tab,colsandrows: what you asked for.layout: the tab’s whole split tree, as instatus --json, hidden panes included.dividers: each divider with itsdir, itspaththroughlayout(falsefor the first side,truefor the second), the region it divides (area) and its cells (line).panes: the panes the surface shows, each withpane(its id),active,rect(x,y,cols,rows: where to draw it in the surface),size(its terminal’s own columns and rows),cursor(x,y,shapeasblock,bar,underlineorblock_hollow, andblinking, ornullwhen hidden or scrolled out of view),modes,scroll,coloursandlines.modes:mouse(none,press,press_release,button_motionorany_motion),mouse_encoding(default,utf8orsgr),alternate_screen,alternate_scroll,bracketed_paste,application_cursor,focus_eventsandkitty_keyboard(the flag bits). They are what you need to turn keys and the mouse into what the program expects.scroll:totalrows of history plus screen,offsetof the view’s first row, androwsin the view. What is under the view istotal - offset - rows. A pane scrolled up shows its history inlines.hyperlinks: the table the runs’hyperlinknumbers index, shared by all the panes, left out when no cell has a link:[{"uri": "https://example.com"}], from OSC 8 (ls --hyperlink, compilers,gh). A link is its address: cells with the same address are one entry, so a link that wraps onto the next line is one link, and two separate spans to the same address open the same place. OSC 8 ids aren’t sent.colours: the colours the pane’s program changed, left out when it changed none:fg,bgandcursoras[r, g, b](OSC 10, 11, 12) andpalette, such as{"1": [255, 0, 0]}(OSC 4). Resolve a colour{"index": n}throughpalettebefore your own palette, and draw default-coloured cells withfgandbgwhen they are there. Your theme is never listed: only what the program set, until it resets it.lines: one entry per row of the pane’s terminal (size.rows), each a list of runs,{"style": 1, "hyperlink": 0, "cells": ["o", "k"]}: consecutive cells of one style and one hyperlink (hyperlinkis left out when the run has none). A blank that is part of a link is kept at the end of a line. A cell is a string, the character with any combining marks. The right half of a wide character is"": don’t draw it, the cell before covers it. The cells after the last one that isn’t a blank in the default style are left out, so an empty line is[]and the rest of a line is blank.styles: the table the runs’stylenumbers index, shared by all the panes. Entry 0 is{}, the terminal’s default colours. Others havefgandbg(absent for the default colour,{"index": 0-255}for a palette colour, which your theme decides, or{"rgb": [r, g, b]}) and the attributes that are on:bold,dim,italic,underline,blink,inverse,invisible,strikethrough,overline, and for underlined cellsunderline_style(double,curly,dottedordashed; a plain underline has none) andunderline_colour(likefg). A client that knows onlyunderlinedraws every style as a plain one. A selection made in a window shows asinverse.
Patches
Section titled “Patches”A long-lived program that parses every line pays for the whole tab each time, and so does a link that carries it. With --patches the first line is complete and the next ones carry only what changed:
cornercase surface --tab 3 --cols 120 --rows 40 --patchesA server from before patches ignores the option and sends complete lines, which you apply the same way, and cornercase contract lists the feature surface-patches to check for it. A line with no base is complete, as above. A line with base is a patch, and has boot, session, surface, revision, tab, cols and rows as before (and view and projection for --view), then:
base: therevisionit applies to, always the one before it. Apply a patch only whenbaseis therevisionof the last line you applied; otherwise ask for a complete line withcornercase redraw --surface ID, or connect again.stylesandhyperlinks: tables for this patch’s runs only. A run’sstyleandhyperlinkin a patch index these, not the tables of the line you hold, so resolve them as you apply a row and keep what you resolved.panes: only the panes that changed, each withpaneandcursor(always:nullis a hidden cursor), and only what changed ofactive,modes,scroll,colours({}once the program resets them),copyandlines. A pane that is not listed did not change.copy:from,toandcount. Before you applylines, take rowsfromtofrom + count - 1of the pane as you held them and write them atto. It is how output that scrolls arrives: the rows that moved, not their content. Rows it doesn’t cover keep what they had.lines: row number ("3"is row 3) to that row’s whole line of runs, as in a complete line;[]is a blank row. Replace those rows aftercopy.
The layout, dividers, the set of panes, their rect and size, and the tab and size of the surface never change in a patch: when any of them does, the next line is complete. It is also complete when the view’s projection changes, after the server held back a line because you were slow to read, and when you ask:
cornercase redraw --surface 4redraw takes the surface of the lines. The surface’s next line is complete and comes at once, even if nothing changed. It works with or without --patches. An unknown or ended surface is not-found, and no --surface is wrong usage.
Things to know:
- The panes are laid out in your
cols×rowsthe way the window lays out a tab, including when the tab is too small for all its panes: then only the active one shows, over the whole surface. The terminals keep the size of whoever sizes the tab (the window, or a view), though, so a pane’ssizecan differ from itsrect:linesandcursorfollowsize. Streaming a--tabdoesn’t resize anything. colsis 1 to 1000 androws1 to 500. A connection streams one tab;cornercase resize --surface IDchanges the size of one given with--tabin place, and another tab needs another connection.- It ends, with status 0, when the tab closes or the server stops or restarts (stopping it yourself leaves the shells running). A pane whose shell exits drops out of the next line. A tab that doesn’t exist, or an out-of-range size is an error, with status 1; a missing
--tab,--colsor--rowsis wrong usage, with status 2. - A reader that falls behind gets fewer lines, each complete (with
--patches, the line after falling behind is complete), so it never has to repair a picture: the server draws a new line only while little is waiting for it. One that stops reading altogether is disconnected once the server has waited a second to write to it, like any client. - Keys added later, such as
hyperlinks,colours,underline_style,underline_colourandoverline, are left out when empty, so a reader that doesn’t know them keeps working and a terminal that uses none of them sends exactly what it did. - Not sent yet: images, OSC 8 link ids, pixel sizes and a terminal size set pane by pane. A pane’s title is in
status --json, and its bells and clipboard writes inevents --host.
A view of your own
Section titled “A view of your own”Two programs that draw cornercase, or a program and the window, shouldn’t have to share one selection and one size. view opens a view: its own project, workspace and tab, an optional size, and an active flag. It keeps running and prints the session as one JSON line each time it changes, like status --follow, plus a view object, and the view lives as long as the command does: stopping it closes the view and hands its tabs back. The processes keep running.
cornercase view --cols 120 --rows 40 > view.jsonl &id=$(head -n 1 view.jsonl | jq .view.id)cornercase new-tab --view "$id" --focus --name logscornercase surface --view "$id"viewhasid,projection,active,colsandrows(nullwithout a size),shown(theproject,workspace,tabandpaneit shows) andcontrols(whether it sizes its tab’s terminals now). It starts on what the window shows, or on--tab,--workspaceor--project; the window doesn’t move. Each tab in the lines hascontroller: the id of the view sizing it, absent when the window does.--view IDworks onstatus,focus,open,new-workspace,new-tab,split,start,send,keys,read,wait,mark,rename,goandsurface, also from separate commands. The view’s tab is their default, andfocus,--focusmove the view, not the window or the other views.focus --viewprints the view’s new tab and itsprojection. Other commands refuse it. A server without views is refused up front, naming the update.- When something the view shows goes away, it moves like the window would: a closed tab to the one before it, a closing workspace or project to its neighbour. That never resizes a tab for it.
surface --view IDstreams the tab the view shows, at the view’s size, and follows the view to other tabs. It takes no--tab,--colsor--rows, and each frame carriesviewandprojection.view-set --view IDchanges it:--colsand--rows,--activeor--inactive,--hiddenor--visible(see below), and--claimto size the tab it shows from now on. A view without a size, or inactive, sizes nothing and streams no terminals, but its lines keep following the session.- The tab a view shows counts as seen, like the window’s, while the view is active, has a size and is visible: an agent there gets no
✓, toast or desktop notification.view --hiddenandview-set --hiddensay its window is hidden or minimised, so agents there notify again, and--visiblesays it is back. While a view is open, the window’s tab counts as seen only while a window is attached; a program that only shows notices can open a view without a size.
Who sizes a tab. One at a time, so two programs showing the same tab never fight over its size. The window is the default. A view claims the tab it shows when it opens, moves there, turns active, or with --claim; using a window (a key, a paste, a click) takes back the tab it shows, until the window shows another tab. The latest claim sizes the tab’s terminals at the view’s cols × rows, without the window’s sidebar or panels; when it lets go (inactive, moved on, closed) the tab goes to the latest one left, else the window.
projection starts at 1 and goes up each time what the view’s surface shows changes: its tab, its size or whether it is active. Show a frame only when its projection is the latest one you know: an older one was drawn before your last move.
Still shared with the window and every other view: the programs and their output, how a tab is split, which pane of a tab is focused (focus --view --pane moves it for everyone), the scroll position and the selected text. The window’s overlays, side panels and sidebar scrolling are its own.
Drive a tab’s terminals
Section titled “Drive a tab’s terminals”A program that draws a tab with surface also has to type into it and handle its own mouse. A window’s keys and clicks go through the window, where a dialog or a field can take them and a click is a cell of the window’s layout. These commands act on a pane or tab you name, whatever the window shows:
cornercase input --pane 12 '{"text": "ls"}' '{"key": {"code": "enter"}}'cornercase input --pane 12 '{"key": {"code": "c", "mods": ["ctrl"]}}'cornercase input --pane 12 '{"mouse": {"kind": "scroll-up", "x": 10, "y": 5}}'cornercase scroll --pane 12 --up 10cornercase selection --pane 12 --from 0,3 --to 79,5 --copycornercase ratio --tab 3 --path second 0.3cornercase resize --surface 4 --cols 100 --rows 30cornercase link --pane 12 --cell 10,3inputtakes one JSON event per argument, sent in order (-reads one per line from standard input):{"key": {"code": "c", "mods": ["ctrl"], "kind": "press"}}(codeas forkeys;modsany ofctrl,alt,shift,super;kindpress,repeatorrelease),{"text": "héllo"}(committed text, no control characters),{"paste": "two\nlines"}(bracketed when the program asked for it) and{"mouse": {"kind": "down", "button": "left", "x": 3, "y": 1, "mods": []}}(kinddown,up,drag,moved,scroll-up,scroll-down,scroll-leftorscroll-right;xandyare cells of the pane’s terminal, from 0, as in thelinesof a frame).- Events are encoded the way the program in the pane asked for, the way the window encodes what you type: its keyboard protocol, application cursor keys, bracketed paste and mouse mode and encoding, all in the
modesof a frame. Keys, text and pastes bring the pane’s view back to the bottom. They don’t set the shared window size and don’t answer for the agent: unlikesendandkeys,inputtypes into an agent’s dialogs and answers its permission prompts, because it is a person typing through another UI. Scripts and agents should keep usingsendandkeys, which check first. - A mouse event the program doesn’t take in its current mode, or a key release when it didn’t ask for event types, is not sent and is listed in the answer’s
skipped({"event": 0, "why": "mouse"}; the command prints a warning). Everything else is encoded first and written in one go: when one event is wrong, or the program isn’t reading its input, nothing is sent. Add"size": {"cols": 80, "rows": 24}to the request to say which terminal size your coordinates are for: if the pane has another size now, nothing is sent (conflict). scrollmoves the pane’s view through its history by--upor--downrows, or to--topor--bottom, and answers with itsscrollas a frame has it. The view is the pane’s, so the window shows the same one. On the alternate screen, or when the program reads the mouse, the program scrolls itself: send it keys, or the wheel withinput;scrollrefuses withconflict.selectionwith--from X,Y --to X,Yselects between two cells of the pane’s view (columns and rows from 0, as inlines;--rectanglefor a block) and prints the text. Without them it prints what is selected,--clearremoves the selection, and--copyalso copies it through the window, as selecting there does, and prints what it copied (the answer’stext), so a program without a window can put it in the clipboard itself. The selection is shared with the window, which shows it asinverse, and with every client; the last one to change it wins.ratio RATIOsets the share of the first side of a tab’s divider, between 0 and 1.--pathnames the divider as thepathof a frame’sdividersdoes (firstorsecondat each level down; without it, the outermost). The terminals follow at once, with the window’s geometry. To open or close splits usesplitandclose.resizelays a runningsurfaceout at another size without starting it again;IDis thesurfaceof its lines. Its next line comes at the new size, with the nextrevision. It resizes no terminal: they keep the size of whoever sizes the tab. A surface that follows a view (surface --view) takes the view’s size, soresizerefuses it: change the view withview-set.link --cell X,Ysays which file a click on that cell of the pane’s view opens in the window (columns and rows from 0, as inlines), so you get cornercase’s detection of the paths programs print without copying it: the path under the cell, looked up from the folder of the program in the foreground, then from the root of the pane’s workspace, with its line or lines. It printspath,path:12orpath:12-30, and nothing, with status 0, when the cell holds no file;--jsonalso says whether the file is inside the workspace. It opens nothing, and OSC 8 links are in the frame’shyperlinks, not here.- Requests on different connections aren’t ordered: send the next
inputafter the answer to the previous one, or put several events in one request. Unlike the other commands, these six log at debug level, since a program sends them as fast as someone types or drags.
Colours for programs
Section titled “Colours for programs”Programs in the terminals ask for the colours they are drawn with (OSC 10, 11 and 4, and the light or dark query): Claude Code, Neovim and delta pick their look from the answer. The server takes them from the first window, so a program that drives cornercase without one has to give them:
cornercase start-server --theme dark.jsoncornercase theme light.json # the system switched to lightcornercase theme - < dark.json # read from standard inputcornercase theme # client colours, darkcornercase theme --clear # back to the window's colours, or the defaults{ "foreground": [192, 202, 245], "background": [26, 27, 38], "palette": [[21, 22, 30], [247, 118, 142], [158, 206, 106], [224, 175, 104], [122, 162, 247], [187, 154, 247], [125, 207, 255], [169, 177, 214], [65, 72, 104], [247, 118, 142], [158, 206, 106], [224, 175, 104], [122, 162, 247], [187, 154, 247], [125, 207, 255], [192, 202, 245]]}- The foreground, the background and the 16 ANSI colours are required, as
[r, g, b].palettemay go up to 256 entries; entries 16 and up are optional (absent ornullkeeps the default cube and greys). A theme that misses a part is refused (malformed, andcornercaseexits 1 before connecting). Over the socket the request is{"command": "theme", "args": {"theme": {…}}},{"args": {"clear": true}}or{"args": {}}to read;start-servertakes the same object asargs.theme. - The answer, and
themeinstatus --json,status --followand thestart-serveranswer, say where the colours come from:{"from": "default" | "window" | "client", "scheme": "light" | "dark" | null}. - These colours win over a window’s until you give others, clear them, or the server stops. They are not saved: after a restart give them again. A window that attaches later draws its own chrome with its own colours and leaves the terminals’ alone; clearing gives the terminals the first window’s colours, if one ever gave them.
start-server --themeapplies even when the server already runs. The last theme given wins. - Every open terminal and every one made afterwards takes the new colours at once. Programs that turned on colour scheme reports (mode 2031) are told when light and dark switch. A program that read the colours once at start keeps its guess until it asks again, and a colour a program set itself (OSC 4, 10, 11) wins over these.
surfaceframes keep palette indices: resolve them with the colours you gave, so the program’s guess and your drawing agree.- Not covered: the cursor colour, colours per view, and the TUI’s own colours, which stay the window’s.
Find where something was said
Section titled “Find where something was said”search answers “which pane dealt with this?” without opening tabs one by one. It looks, ignoring case, in what the coding agents in your panes were told and answered (Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI and GitHub Copilot CLI: the records read --last-message reads) and in the history of every pane, shells included. It is the same search as in sessions in the window, and leaves out panes of projects being closed and workspaces being removed.
cornercase search 'migration failed'cornercase search --json checkoutpane=$(cornercase search --json 'tax bug' | jq -r '.matches[0].pane')cornercase focus --pane "$pane"Text is one line per match under a header, separated by tabs, the way status --panes is. Matches come in the order of the sidebar, an agent’s messages first, newest first, then the pane’s history from the bottom up.
pane who source place line text4 claude reply web-shop › feat/dark-mode › claude - The checkout tax bug is in src/checkout.rs:42, where the rate is rounded twice.4 claude prompt web-shop › feat/dark-mode › claude - Why is the checkout tax wrong for gift cards?9 zsh screen orders-api › main › zsh 118 migration failed: duplicate key value violates unique constraint| Column | What it holds |
|---|---|
pane |
The pane’s id, for read, send, focus and the other commands. |
who |
The agent’s name, else the program in the foreground. |
source |
prompt or reply when it comes from an agent’s record, screen when it comes from what the pane printed. |
place |
project › workspace › tab. |
line |
The line in the pane’s history, for screen matches; - for an agent’s messages. |
text |
The matching line, cut to about 160 characters around the match. |
- A
-stands where there is no value, and a tab or a line break shows as a space, so every row is one line with the same columns. New columns only ever go at the end. - A match of a screen that the agent’s record already holds (agents print what they say) is listed once, as the agent’s message.
- It lists at most 20 matches per pane and 300 in all. With
--jsoncappedsays some were left out. - No match prints the header alone and exits 0, since finding nothing is an answer. An empty search is an error.
--jsonprints{"matches": [...], "capped": false}, each match withpane,tab,workspace,project(the ids),place,who,source,line(nullwhen there is none),textandwritten(when an agent’s message was written, in UTC, ornull).- The text you search for is never written to
server.log.
Create
Section titled “Create”cornercase open ~/code/orders-api # prints the project's idcornercase new-workspace experiments # a workspace in the project's foldercornercase new-workspace fix/login --worktree # a git worktree on branch fix/logincornercase new-tab --name server -- npm run dev # prints the new pane's idcornercase split --down -- tail -f server.log # splits the pane belowopenfinds a project that is already open instead of opening it twice.new-workspaceadds to the project the command runs in (--project IDfor another one). With--worktree, the project must be at a repository’s root, as in the new workspace dialog: the checkout goes in the worktrees folder from settings, the branch is created fromHEADwhen it does not exist, and, when the project has no setup, the command returns once git has made it. Each new workspace gets one tab, which runs the project’s setup first when it has one;new-workspace --worktreeandstart --worktreewait for it and answer an error naming the exit status if it fails;status --jsonshows a workspace’ssetupasrunningorfailed.close --remove-worktreeruns the project’s teardown first; if that fails the worktree is still removed and the command printswarning: …on stderr.new-tabopens the tab in the workspace the command runs in (--workspace IDor--worktree BRANCHfor another one).splitsplits the pane it runs in (--pane IDfor another one), and refuses a pane too small to split.- After
--, the words of a command are joined with spaces and typed at the prompt once the shell is ready, assshdoes with its command, so quote what the shell should read as one argument:-- 'cargo test 2>&1 | tee test.log'. The command returns once it is typed. - Each prints the new pane’s, workspace’s or project’s id, so
pane=$(cornercase new-tab …)needs nothing else.--jsonprints every id:{"project": 1, "workspace": 2, "tab": 8, "pane": 9}. --focusalso shows what was made.
Start an agent
Section titled “Start an agent”cornercase start claude --worktree fix-login --prompt 'Fix the login form, then commit'cornercase start codex --prompt-file task.md --wait --timeout 1800git diff | cornercase start claude --prompt-file -start does what start does in the issue browser: it opens a tab, types the agent’s command with its arguments and mode from settings → Agents, waits for its folder trust question to be answered (by you, unless trust prompts is on), and once the agent is ready pastes the prompt and presses Enter. It returns then, printing the pane’s id, or fails if the agent never shows up.
- Which agent: the one you name (
claude,codex,geminior any other kind); without one, the default from settings → Agents, or, when that is auto, the agent running in the pane the command runs in. - Where: a new tab in the workspace the command runs in; in another workspace with
--workspace ID; or with--worktree BRANCH, in the workspace on that branch, made as a new worktree when there is none. --namenames the tab,--promptgives the prompt and--prompt-filereads it from a file, or from standard input with-. Without a prompt, the agent just starts.--modelstarts it with that model, through the agent’s own flag (--model opus,--model gpt-5.5), replacing a model set in its arguments.--modepicks another of the agent’s modes, as settings → Agents lists them (--mode plan), instead of the one chosen there. A mode the agent lacks is refused with the list of its modes.--effortsets how hard the agent thinks, through its own switch (--effort highfor Claude Code,-c model_reasoning_effort=highfor Codex,--reasoning-effort highfor Grok Build and GitHub Copilot CLI; Cursor CLI takes it in the model id), replacing an effort set in its arguments; a level the agent does not take is refused with its levels, and Gemini CLI, opencode and Cursor CLI, which have no effort switch, refuse any. A server older than these options refuses them rather than start the agent with the settings’ mode, which could be one that never asks.--issue KEYstarts the agent on an issue instead of a prompt, as the Issues dialog’s start does: the branch is the one named after the issue, the prompt comes from settings → Issues → prompt, and it is always submitted, whatever send the prompt says there. A key such as#482,sc-48orENG-42, or the issue’s URL;--sourcepicks the tracker when a key could be several. A GitHub or GitLab issue is worked on in the repository of--project ID(default: the one the command runs in, else the shown one), in a worktree on its branch; a workspace already on that branch gets a new tab with the agent. Shortcut, Linear, Jira, Plane and Asana issues aren’t tied to a repository, so with several places open the command lists them and asks for--project ID(a new worktree) or--workspace ID(a new tab). The workspace is named after the issue, and so is a tab unless--namesays otherwise.--waitthen waits likewaitdoes, and prints how it ended on a second line.--untilwith one ofwait’s states (idle,working,waiting,shell,turn-over) waits for that one instead.
Accounts and profiles
Section titled “Accounts and profiles”cornercase account add claude work --logincornercase account list --jsoncornercase account use claude workcornercase account rename claude --system Personalcornercase start claude --account claude= --prompt 'fix the tests'cornercase profile new Work claude=work codex=workcornercase profile use Workcornercase start claude --profile Home --prompt 'fix the tests'account and profile manage the accounts and profiles agents start with. Every answer has the same shape (--json): the agents that take accounts, each with its variable, its accounts (name, canonical path, label when you named it, and unshared, the settings that stopped being links), system_label, system_path and system_login for the agent’s own login, and active, the account its new tabs use (left out for its own login); then the profiles with their accounts, and profile, the active one ("" for none). account use AGENT ACCOUNT (account-use) makes new tabs of that agent use an account (--system, "" on the wire, for its own login), keeping the other agents’ and leaving any active profile; account rename AGENT ACCOUNT NAME (account-rename) sets the name the window shows (--system for the agent’s own login, --clear or "" to remove it): a name already used by another account of that agent is a conflict. Both name an account by its folder, never by its name. --account AGENT=ACCOUNT on start, new-tab, flow run and queue add|edit (the accounts field, AGENT= for its own login) starts with that account once, over a profile; --profile NAME on start, new-tab and flow run uses a profile once without changing the active one (--profile '' is every agent’s own login). An unknown agent, account or profile fails with not-found before anything opens. A server without accounts, or without account-switch for --account, would ignore the field, so the command checks the server’s contract first and refuses. profile use --system (profile-use with "") also forgets the accounts chosen with account use, so every agent goes back to its own login. status --app gives accounts, the account each agent’s new tabs use. status --json names a pane’s account, and a pane’s opened event its accounts. account list --check (the check field of account-list, behind the account-login feature) first asks each account whether it is logged in, with the agent’s own claude auth status or codex login status, and answers once they have all replied: each account then has login (state: signed-in, signed-out, unknown or failed; email and plan when the agent gives them; checked, UTC). tokens lists the names, never the values, of the token variables set where the server runs, which win over the accounts. A queue entry keeps its accounts; one whose account was removed before its time fails and says why.
Start a flow
Section titled “Start a flow”cornercase flow listcornercase flow run Quick --prompt 'Fix the login form'cornercase flow run 'Full issue' --prompt-file issue.md --worktree fix-login --jsoncornercase flow run 'Full issue' --issue ENG-42 --project 3flow list prints each flow, yours, the built-in ones (those you have not replaced) and the project’s, sorted by name, with its description, its roles with their agents, models and efforts, and what keeps it from running, if anything; files it cannot read are listed with their error. flow run starts one as the window’s start does: it makes the worktree (from --worktree, else from the task’s first words, with a short suffix when they give nothing, a default branch or a branch already taken), opens a tab per role, starts every agent without permission prompts and answers their trust questions, and starts the orchestrator last with its brief. It answers once the tabs exist, while the agents are still starting: the workspace’s id, then one line per role with its pane’s id and the role’s name, separated by a tab. The run’s folder for its files is printed on stderr (run_dir with --json), and every pane of the team has it in $CORNERCASE_RUN_DIR. To follow a run’s end, watch run.reached in status --follow.
810 orchestrator12 coder14 reviewer- A role set to
worktree = "own"gets a worktree of its own on the branch<branch>--<role>, made from the run’s branch, with the project’s setup.--jsonthen adds, for each such role, itsworktree(the folder) andbranch; the other roles’ entries have neither. The answer waits for every worktree and its setup, and a role worktree that cannot be made opens nothing. - The task comes from
--prompt, or from a file with--prompt-file(-reads standard input), or from an issue with--issue KEY, as the dialog’s start a flow takes it: its branch, its prompt template and, for an issue that isn’t tied to a repository,--project IDwhen several projects are repositories (see Issues). - The flows of the project the command runs in, else the shown one, else
--project ID, are found too. A name is looked up in yours, then the built-in flows, then the project’s: yours replaces a built-in flow of its name, and a project’s flow, which comes with the repository, never stands in for another of the same name; the command says so on standard error when a name is in several places. A built-in flow you deleted in settings → Flows is refused, and keeps its name: a project’s flow named like it does not run either. An unknown name lists the flows. - It refuses before anything opens a flow with problems, an agent that is not installed, a project that is not the root of a git repository, a branch that already runs a flow, and a
--worktreethat ismain,master, the project’s default branch or the one its own folder is on: a flow works on a branch of its own. - Nothing changes in the window unless you pass
--focus, which shows the orchestrator’s tab and opens the Flow panel.
flow export NAME prints a flow as TOML, so cornercase flow export Quick > quick.toml makes a file to send, and --json prints its name, origin, path and text. flow import SOURCE (a file, - for standard input, an https:// address or OWNER/REPO on GitHub, see community flows) adds one to yours, or to the project’s with --to project, and prints where it went. A flow from an address or a repository, or that sets env or args, asks first, or needs --yes without a terminal; a name that is taken is refused unless --replace overwrites it or --name gives the new one another. A text over 256 KiB is refused. See Share a flow.
Manage flows
Section titled “Manage flows”cornercase flow list --jsoncornercase flow delete 'Quick mine'cornercase flow hide Quickcornercase flow restore QuickEach verb means one thing. flow delete NAME deletes the file of a flow of yours or the project’s (--in yours or --in project when both have the name, --project ID, or --path FILE for a file that cannot be read, as flow list --json gives it) and nothing else: a run already going keeps the plan it was started with. It refuses a built-in flow, which has no file, and your version of one, with the command that does what you mean (conflict). flow hide NAME is what the delete button of settings → Flows does with a built-in flow: it hides its name, and deletes your version of it if you edited it. flow restore NAME brings a hidden built-in back and deletes your version of it. Both change nothing when there is nothing to do (changed is false), and name only built-in flows (not-found for others). None asks first, and --json prints name, origin, deleted (the file), hidden and changed.
An app with an editor of its own works on the flow as JSON. flow list --json gives each flow whole as flow: name, description, notes, end (pull_request or commits), orchestrator and roles (each with name, agent, model, effort, args, env, edits, worktree (shared or own) and prompt) and stages (roles and back, null or to counted from 1, as in the file and the editor, when and at_most), with edited for your version of a built-in. The deleted built-ins come as hidden, and builtins lists every built-in name, which stays reserved even while it is hidden. The request flow-save takes that same flow, place (yours or project), project, path (the file of the flow being edited, as flow list --json gave it, when it is renamed or moved: the old file is deleted; any other path is not-found) and check:
{"command": "flow-save", "args": {"flow": {"name": "Quick mine", "description": "", "notes": "", "end": "pull_request", "orchestrator": {"name": "orchestrator", "agent": "claude", "model": null, "effort": null, "args": [], "env": {}, "edits": false, "worktree": "shared", "prompt": "Run it."}, "roles": [{"name": "coder", "agent": "claude", "model": null, "effort": null, "args": [], "env": {}, "edits": true, "worktree": "shared", "prompt": "Write it."}], "stages": [{"roles": ["coder"], "back": null}]}, "place": "yours", "path": null, "check": true}}With check it answers problems (what the editor shows in red: the flow’s own and a name conflict) and the check text (what a project’s flow runs unattended: its env and args), and writes nothing; without it, it saves, answers the path and is refused as malformed while the flow has problems of its own. A request is not-found for a project that is not open and a path that is no flow file, and malformed for another place. The last write wins over an editor open on the same flow, as with a file edited by hand.
Issues
Section titled “Issues”cornercase issue sourcescornercase issue list --source github --assignee mecornercase issue list --closed --jsoncornercase issue read '#482'cornercase issue people jiracornercase start claude --issue '#482'These read the trackers through the connections the server already holds, the ones the Issues dialog uses: GitHub through gh, the others through the tokens saved in settings or set in the environment. They change nothing at a tracker. Another program that wants the issues gets them without keeping a client for each tracker or a token of its own.
issue sources says, for the project (--project ID, default the one the command runs in, else the shown one), which trackers are connected and as whom, in the order of the dialog’s tabs, then the others. Each is one of:
connected, with its account and where its token comes from (through gh, saved, orfrom LINEAR_API_KEY).missing, with what is missing:token(connect it withcornercase config secret SOURCE, the token on standard input),siteandemailfor Jira (cornercase config set jira_site …),workspacefor Plane (plane_workspace), orrepository(the project isn’t a git repository, or for GitLab none of its remotes is ongitlab_url). An Asana token with several workspaces shows asfailinguntilasana_workspaceis set.failing, with the reason the last list failed (a rejected token, a rate limit, an Asana token that belongs to several workspaces). It is a list’s failure only, and the next good list or a change to that tracker’s token or settings clears it.
It ends with the places an issue that isn’t tied to a repository can be worked on: a repository-root project is a new worktree, an open workspace of any other project a new tab, and the one you are in is marked.
issue list prints one line per issue under a header, separated by tabs (source, key, state, author, updated_at, title, url, branch, with - for nothing; new columns only at the end). Without --source it lists every connected tracker the project can use, newest first, as the dialog’s All tab does. --closed adds closed issues, and --assignee and --author (also --owner and --requester, --creator, --reporter, the names the dialog uses) take me, anyone, a handle or, for Jira, Plane and Asana, an id from issue people; a handle needs --source, since a filter names one tracker’s people. --cached answers from the lists the server already holds, without asking any tracker, and an empty answer then means nothing was held for those filters. A tracker that fails does not stop the others: it is named on standard error with its reason, its cached list (if there is one) is still printed, and the command exits 1 after printing the rest.
issue read KEY prints the issue’s title, the dialog’s info line, its URL and the branch start would use, then its description as Markdown and its comments. KEY is a key or a URL, as for start --issue; a URL of another repository, site or workspace than the project’s is refused. issue people SOURCE prints whom that tracker’s filters can name, one per line with the handle, name and id.
With --json, sources prints {"project", "closed", "sources": [...], "places": [...]}, list prints {"project", "issues": [...], "sources": [{"source", "count", "cached", "account", "error"}]}, read prints {"issue", "info", "body", "comments", "prompt"} and people prints {"source", "fields", "cached", "people"}. They are four requests, issue-sources, issue-list, issue-read and issue-people, with the same options as fields (people is the two filters in the order fields names them: "me", "anyone" or {"person": {"handle": "bo", "id": null}}). A request is refused with not-found for an unknown project, malformed for an unknown source or a person without one, and conflict for an issue of a tracker that is not connected or that was reconnected while it was read. start and flow-run take issue and source, which an older server would ignore, so check the issues feature first.
Talk to a pane
Section titled “Talk to a pane”cornercase send --pane 12 --enter 'Now add tests for it'cornercase send --pane 12 --enter --wait --timeout 900 - < next-step.mdcornercase send --pane 12 --enter --wait --until turn-over 'Run the tests'cornercase keys --pane 12 ctrl+ccornercase keys --pane 12 down entercornercase read --pane 12cornercase read --pane 7 --lines 200cornercase read --pane 12 --last-messagecornercase read --last-message --pane 12 --pane 15cornercase read --last-message --pane 12 --pane 15 --jsonsendpastes the text in one go, bracketed when the program asked for it, as a paste from your terminal would be.-reads it from standard input.--enterthen presses Enter once the screen settles.- With
--enter,sendto Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI or GitHub Copilot CLI returns once the agent has recorded the prompt in its own history (Claude Code’s transcript, Codex’s rollout, Gemini’s session file, opencode’s database, Grok Build’s session, Cursor CLI’s chat store), so its success means the prompt was submitted, not just typed. When the agent records nothing, it fails withnot confirmed: the prompt may not have been submitted: read the pane before sending it again. Codex, Gemini, Grok Build, Cursor CLI and GitHub Copilot CLI keep a prompt sent while they work until they take it, sosendreturns then;--timeoutgives up earlier, with status 1. Grok Build queues that one Enter. Pressing Enter again sends the queued prompt now and cancels the turn, whichsenddoes not do. In any other program,send --enterreturns once Enter is pressed. - GitHub Copilot CLI’s
/and!lines and a lone?write no prompt record, sosend --enterreturns after Enter for them;? textis a real prompt. A prompt typed while it works is queued, and Copilot records it when it takes it, so the confirmation waits for that. - Cursor CLI’s
/commands and!shell input return after Enter without a prompt confirmation too. Cursor’s chat store is read from its code, not a live session, so a prompt it records in a layout cornercase cannot read fails withnot confirmed; read the pane then. - Gemini local slash commands,
?help and!shell input return after Enter without a prompt confirmation, so they do not fail withnot confirmed; usereadto inspect their result. Gemini has no calibrated input-box check, so read its screen before sending into a picker or dialog. --waitthen waits until the agent stops working; it fails if the agent does not start working within 10 seconds of the Enter, rather than waiting forever for an agent that never got the text.--untilwaits for another ofwait’s states, asstartdoes.sendrefuses a pane whose agent iswaitingfor an answer to a question or a permission prompt: whatever it typed would answer it. Answer such a prompt withkeys, after deciding what the answer should be.sendalso refuses a Claude Code agent that shows a dialog, a panel or its shell mode instead of its input box: in the details of a background shell anxstops the shell and Enter closes the panel, and in shell mode (!) a prompt would run as a command. cornercase tells from the screen, and only once it has seen that agent’s input box, so a Claude Code that draws it differently is never refused. Read the pane, close what is open and send again;--forcesends anyway.sendalso refuses a Claude Code agent that shows its feedback survey above its input box (How is Claude doing this session? (optional), then1: Bad 2: Fine 3: Good 0: Dismiss): one digit sent alone answers the survey instead of reaching the agent, sosend --enter 2, the answer to a numbered question, would be lost.cornercase keys --pane 12 0dismisses the survey without answering it; then send again.--forcesends anyway: a longer text hides the survey and goes to the input box, as when you type over it.sendalso refuses a pane whose agent is resuming its conversation after a restart (resuminginstatus): before the agent is back, text would land in front of the resume command or in the agent’s start-up. Wait untilstatusno longer shows it, or pass--force.keysis not refused.send(with or without--enter) andtodo sendwait while a person is typing in the pane, so your text never joins theirs and they never send half a sentence. It waits while the pane has a draft (text typed or pasted into it from a window, the browser orcornercase inputand not yet sent with Enter or dropped withEsc,Ctrl+CorCtrl+U; a draft lapses after a minute without typing, and for Claude Code as soon as its input box is empty again), while someone typed in it in the last 2 seconds, and while an earlier message to that pane has not gone out. Then it pastes, presses Enter and confirms as usual; messages held for one pane go out one at a time, in the order they came. Meanwhile a bar on the pane’s bottom row says1 message waiting from pane 40 · goes after yours, withsend now(sends it at once, as--force) andwait turn, which only says the person saw it: the bar stays, with the working icon andqueued: goes right after yours, and a new held message showswait turnagain. Either way the message goes out right after the person’s prompt.statusshowsheld_sendsanddraftfor each pane.--timeoutbounds the wait and its error says whether the pane had a draft or recent typing;--forcesends at once. While a message sits between its paste and its Enter (at most about 3 seconds), what a window types into that pane is dropped with a toast.keysnever waits.keyspresses keys, encoded the way the program in the pane asked for:enter,esc,tab,backspace,space,up,down,left,right,home,end,pageup,pagedown,delete,insert,f1tof12, or one character, each after any ofctrl+,alt+andshift+(shift+tab,ctrl+c,alt+x).- A program that draws the terminals itself should type with
inputinstead: it sends keys, text, pastes and mouse events to the pane you name without checking what the agent shows, which is right for a person typing and wrong for a script. Scripts and agents keepsendandkeys. readprints the screen as text.--lines Nprints the lastNlines instead, scrollback included (a pane keeps its last 10 MB of output, about 14,000 lines at 80 columns). Lines the terminal wrapped come back as one, and empty lines at the end are left out.- A Claude Code screen may end with its feedback survey right above the input box,
● How is Claude doing this session? (optional)and1: Bad 2: Fine 3: Good 0: Dismiss: that is Claude Code asking you, not something the agent wrote.statusmarks it(survey open), andkeys --pane 12 0dismisses it. read --last-messageprints the last message the agent in the pane wrote, as plain text. It comes from the agent’s own record, not the screen: Claude Code’s transcript, Codex’s rollout, Gemini’s session file or opencode’s database, the ones cornercase already reads for the context line. So the message comes whole even once it scrolled off, without the input box, the status lines or a suggestion Claude Code shows in grey, and nobody has to ask an agent to write its report to a file. While the agent works it is the newest message so far, such as a note on what it is about to do; once its turn is over it is its answer.--jsonaddswritten, when the agent wrote it, andturn_over.- With
--last-message, repeat--paneor--tabto read several agents in one call, using the ids fromstatus --panes. Each message appears under a header with its pane id, agent,turn_overandwritten; unknown metadata isunknown.--jsongives{"panes": [...]}, with the usual individual answer in each entry. Panes come first, then tabs, each in the order given, and a repeated id is read once. A failed target gets its own error block or JSON entry witherrorand the requestedpane(ortab, when its pane could not be resolved). The command reads every target before exiting 1 if any failed. One distinct target keeps the usual output exactly. Reading screens takes only one target. read --last-messagefails, with status 1 and a message saying why, on a pane without Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI or GitHub Copilot CLI, before the agent has written anything, and while cornercase has not found its record: Codex writes its rollout from its first turn on, and two Codex, two Gemini with the same home, or two opencode, in the same folder can’t be told apart. Read the screen then. A permission prompt or a question the agent is asking is not a message either: it is only on the screen.
cornercase wait --pane 12 --timeout 600 # until the agent stops workingcornercase wait --pane 12 --until waiting # until it needs an answercornercase wait --pane 12 --until turn-over # until its turn is over, background shells or notcornercase wait --pane 7 --until shell # until the program endscornercase wait --pane 7 --text 'test result: (ok|FAILED)' # until a line matchescornercase wait --pane 7 --quiet 5 # until it writes nothing for 5 swait blocks until its condition holds, then prints how it ended:
| Condition | Ends when | Prints |
|---|---|---|
| (none) | the agent stops working: it is idle, done, failed, pending review or waiting | idle, done, failed, pending or waiting |
--until idle |
the agent is idle, done, failed or pending review | idle, done, failed or pending |
--until working |
the agent is working | working |
--until waiting |
the agent waits for an answer to a question or a permission prompt | waiting |
--until turn-over |
the agent’s turn is over: it stops working, as with no condition, or only a background shell it started still runs (subagents still running keep it waiting) | idle, done, failed, pending, waiting or shell |
--until shell |
the program in the foreground ended and the shell is back, quiet (a second when cornercase never saw the program run) | shell |
--text REGEX |
a line on the screen matches, one already there included | the line |
--quiet SECONDS |
the pane wrote nothing for that long | quiet |
- The agent conditions need a recognised agent reporting OSC 7501, or a reader for Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI or GitHub Copilot CLI in the pane. On a pane with no agent status or supported reader, wait for
shell, a line or quiet instead. - A Claude Code agent that ends its turn while a shell it started in the background still runs counts as working: it wakes up when that shell ends, and may then carry on. So the plain
waitkeeps waiting, and its timeout says that only a background shell is left.--until turn-overends there and printsshell; read the pane to see what the agent said, and stop the shell yourself if nothing should wake the agent. An agent whose own turn is over while subagents it started still run (working (2 subagents)) keeps the default,--until idleand--until turn-overwaits waiting, since it wakes up for their results; the timeout names them. Codex, Gemini and opencode report no such state: their turn ends when they say so, whatever they left running, so for themturn-overends as the plainwaitdoes. - After a prompt reached the agent (through
start,send --enterorkeys … enter), a wait for it to stop first gives the agent up to 10 seconds to take the prompt, so it doesn’t end at once on an agent that hasn’t started yet. If the agent shows no change in that time, the wait fails: it may have finished at once, or never got the text, andreadtells which. If the agent was already working when the text arrived, the wait ends when it next stops. An agent that only had a background shell left counts as taking the prompt once it works again. - Waits run in the server and don’t slow it down. They end with their condition, their
--timeout(none by default) or when you interrupt the command. A timeout exits with status 1 and says what the pane was doing. It doesn’t prove that the agent missed what you sent: read the pane before sending it again.
Several panes at once
Section titled “Several panes at once”cornercase wait --any --pane 12 --pane 15 --tab 9 --timeout 1800 # until the first of them stopscornercase wait --all --pane 7 --pane 8 --until shell # until both programs endRepeat --pane and --tab to wait on several panes, with --any or --all (several without either is wrong usage). The condition and the --timeout apply to each of them.
--anyreturns as soon as one of them meets the condition and prints one line: the id you gave and how it ended, such as15 idle. When several meet it at the same moment, it prints the first in the order you gave them; wait again on the rest to hear about the others.--allreturns once every one of them has met the condition, and prints one such line each, in the order you gave them (--paneones first, then--tabones). A pane that met the condition counts as done even if it changed again before the others caught up.- The id on each line is the one you gave, so a
--tabline starts with the tab’s id; panes and tabs never share an id. - A pane that closes while you wait ends as
closed(12 closed) instead of failing the wait, so--allgoes on with the others and--anyreturns it. Anything else that fails a wait on one pane (its agent exits, it doesn’t take a prompt) fails the whole wait, naming the pane. - A timeout says what each pane still being waited for is doing.
--jsonprints{"panes": [...]}, one entry per line of text.
Follow events
Section titled “Follow events”cornercase events # everything, until the server stopscornercase events --pane 12 --pane 14 # only these panes, until they all closedcornercase events --host --json # also clipboard writes and bells of programscornercase events --notices --json # also the notices about agents, with their tokenscornercase events --json | while read -r event; do …; doneevents keeps running and prints one line per event, as it happens, so a script that coordinates agents can react to each change, such as an agent asking for an answer or a pane closing, instead of polling status or waiting again after each wait:
tab 15 opened in workspace 2pane 14 opened in tab 15 from pane 12pane 14 claude none → idlepane 14 claude idle → workingpane 12 claude working → waitingpane 14 claude working → shellpane 7 cargo exitedpane 7 clipboard 11 bytespane 7 bell 3 timespane 14 closedtab 15 closedworkspace 2 notice claude needs you in web-shop › main cc-3f2a9c-4| Event | When |
|---|---|
pane ID AGENT FROM → TO |
An agent changed state: working, shell (its turn is over, but a background shell it started still runs), waiting for an answer, pending review, done while you were elsewhere, failed, idle, or none before it started and once it left the pane. Every recognised agent reporting OSC 7501, or Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI and GitHub Copilot CLI through their readers, as for wait. |
pane ID PROGRAM exited |
The program in the foreground ended and the shell is back, or another program took its place. |
pane ID clipboard N bytes |
Only with --host: a program wrote to the clipboard (OSC 52, such as a yank in Neovim). The text is in --json’s text, never in the line. Reads and the primary selection are not told. |
pane ID bell |
Only with --host: a program rang the bell (a BEL character, not one that ends another escape sequence). It reads bell N times when several rang. |
workspace ID notice MESSAGE TOKEN |
Only with --notices: cornercase told you an agent needs you, failed or finished, as the window’s toast says it, with the same rules (a second’s wait, once per change, never for a tab seen in the window or in a view). --json has token, agent, status, place, message and panes, and sound (needs-you or finished), the sound the server chose for it when sound is on for this agent (left out otherwise), so a program that draws cornercase plays the same sounds the window does. It says which sound was chosen, not that one played: a window in a plain ssh session, the browser or a machine without a player plays nothing. A --pane reader gets the notices naming its panes. cornercase go TOKEN [--view ID] does what a click on the notification does; it fails for a token the server never gave or no longer keeps (the last 64) and when the agent and its workspace are gone. Check the notices feature before sending --notices. notify, the queue’s and a failed setup’s messages are not notices. |
KIND ID opened in PARENT |
A project, workspace, tab or pane opened. A pane started from another pane (see Ids) ends with from pane ID. |
KIND ID closed |
A project, workspace, tab or pane closed. A pane closes when its shell exits, whatever closed it. |
--pane ID, given once or more, keeps only what happens to those panes, and ends the command once they have all closed. Without it,eventsends when the server stops (kill-server, a restart), with status 0.--jsonprints one JSON object per line instead:event(status,exited,opened,closed),time(UTC, as in the server’s log), theproject,workspace,tabandpaneids it concerns, andagent,fromandtofor astatus(nullfor no agent),programforexited,kindforopenedandclosed, andparentfor anopenedpane started from another pane (left out when it has none).--hostis how a program that draws the panes learns what their programs ask of the terminal: a UI has no window to receive an OSC 52 write, so it putstextin the clipboard itself. Readers without--hostget none of it. Bells come at most one line a second per pane, with how many rang (count). A server without thehost-eventsfeature refuses--host; checkcontractbefore sending"host": true. A reader that falls behind loses these events like any other, clipboard texts included.- It only tells what happens after it starts. Start it before what you want to follow, or read
cornercase statusonce it runs, so nothing falls in between. - cornercase looks at the program in each pane every half second while something follows events, so a program that runs for less than that may not show. To wait for one program,
wait --until shellis exact. - A reader that falls far behind (over a thousand events waiting for it) loses events, and gets a line such as
12 events droppedwhen it catches up, so it knows to readstatusagain. One that stops reading altogether is disconnected once the server has waited a second to write to it, like any client; the command then says the connection ended and exits with status 1.
Arrange
Section titled “Arrange”cornercase close --tab 9cornercase close --worktree fix/login --remove-worktreecornercase close --worktree fix/login --remove-worktree --checkcornercase close --worktree fix/login --remove-worktree --unlockcornercase group new workcornercase group style 12 --icon '★' --colour 99cornercase group delete 12 --close-projectscornercase move --project 5 --into 12cornercase move --tab 9 --before 7cornercase move --worktree fix/login --lastcornercase collapse --group 12cornercase collapse --pane 12cornercase expand --worktree fix/logincornercase move-pane --pane 4 --target 7 --place belowcornercase rename 'review #42'cornercase rename --workspace 5 ''cornercase focus --pane 12cornercase mark --pane 12cornercase mark --pane 12 --reviewedclosetakes exactly one of--pane,--tab,--workspace,--worktreeor--projectand stops its shells, as its×would, but never asks first, not even for a project. A worktree’s workspace stays listed while its worktree is on disk;--remove-worktreeremoves the worktree too and answers once it is gone. Without--force, a worktree with uncommitted or untracked changes is refused before anything stops;--forcethrows those changes away. A locked worktree (git worktree lock) is refused before anything stops, even with--force; the error gives the reason and thegit worktree unlockcommand to run, and--unlockdoes what the dialog’s unlock and remove does. It does not imply--force: changes still need it. The branch is never deleted.close --remove-worktree --checkonly says what removing the worktree would lose, as the Remove workspace dialog does, and stops nothing: whether it has changes that are not committed, its lock with the reason and whether the process the reason names still runs, the worktrees of a flow’s roles that go with it, and whether the project’s teardown runs first.--jsonprintsworkspace,path,branch,changed,lock(reason,path,role,pid,running, ornull),roles(role,path,branch) andteardown. Run it before removing a worktree you did not just make.group new NAMEcreates a group with the next icon and colour, as new group does, and prints its id.group style IDsets--icon(one of● ◉ ◐ ◆ ■ ▲ ▼ ★ ✦ ♥ ♣ ♠) and--colour(a palette index:1 9 208 214 3 11 2 10 6 14 4 12 99 5 13 205); with neither it prints the group’s style, and--jsonalso lists both sets.group delete IDkeeps the group’s projects, outside any group; with--close-projectsit closes them and deletes the group once their shells have stopped.moveputs a group, project, workspace or tab (--group,--project,--workspace,--worktree,--tab)--beforeanother of the same kind, or--lastat the end of its list. A project moved before another joins that project’s group;--into GROUPputs it after the projects of that group and--ungroupafter the ones that have none. A workspace stays in its project and a tab in its workspace. Programs keep running, and the window keeps showing what it showed.collapseandexpandfold or unfold a group, project or workspace (--group,--project,--workspace,--worktree), or an agent’s pane (--pane, refused withconflictfor a pane that runs no agent cornercase lists), whose agents then hide under it in the agents section, whatever it was.move-pane --pane ID --target ID --place swap|left|right|above|belowswaps two panes of a tab or moves one to that side of the other, as dragging a pane with the right button does. It is refused (conflict) when the tab would have no room for every pane at the size it has, or when the panes are in different tabs; the moved pane becomes the tab’s active one.--jsonprints the tab’s newlayout. Over the socket the requests aregroup-new,group-style,group-delete,move,collapse,expand,move-paneandremoval-check, andclosetakesunlock; each change shows in the nextstatus --followsnapshot.renamerenames the tab it runs in, or the--tab,--workspace,--worktree,--projector--groupyou name. An empty name goes back to the default one (a group always keeps a name).focusshows a pane, tab, workspace or project, as picking it in the search does.markmarks the agent in the pane it runs in, or in the--paneor--tabyou name, as pending review: its tab shows a blue●until it is marked--reviewedor the agent starts working again on a new prompt or an answer, andstatusandeventsgive it aspending. A pane without Claude Code, Codex, Gemini CLI, opencode, Grok Build, Cursor CLI or GitHub Copilot CLI is refused. An agent that ends its turn with work for you to check can run it on its own pane.
Leave notes
Section titled “Leave notes”cornercase notify 'the release build is ready'cornercase todo add review the login fix # prints the item's idcornercase todo listcornercase todo done 3 # checks it off, or back oncornercase todo undone 3 # puts a done item back among the ones to docornercase todo edit 3 review the login fix againcornercase todo move 3 --before 5 # or --lastcornercase todo send 3 --pane 5 # types it into the agent and checks it offcornercase todo rm 3 # prints a number that restore takescornercase todo clear-done # the same, for every done itemcornercase todo restore 7todo does what the TODO panel does. An item moves among its kind: before an item of the other kind it stops at the end of the ones to do (or becomes the first done one). done checks an item off or back on; undone only puts a done item back, and does nothing for one still to do. rm and clear-done print an undo number (--json prints the removed items with it); restore puts that removal back where it was. The server keeps the last 16 removals until it stops, and clear-done with nothing done removes nothing and prints no number. Over the socket these are the todo request’s edit, undone, move, clear-done and restore, listed as the todo-panel feature.
notify shows a toast in the window and sends a desktop notification through the terminal of every open window, like the one for an agent that needs you.
todo send types an item’s text into an agent and presses Enter, exactly as send --enter does: the same target (--pane or --tab, else the pane the command runs in, else the one the window shows), the same refusals (an agent waiting for an answer, resuming, showing a dialog, a panel, its shell mode or its feedback survey, a program that stopped reading; --force for the ones send --force lifts) and the same confirmation that the agent recorded the prompt. Once it did, the item is checked off and the command prints sent todo 3 to pane 5. A refused or unconfirmed send leaves the item pending, prints the reason and exits 1. When cornercase cannot confirm that kind of send at all (a / command, an agent it does not read), the text is sent and the command exits 0 with sent todo 3 to pane 5, but cornercase cannot tell whether the agent took it, so it stays to do; the item stays pending, and --json has "unconfirmed": true. An item edited while it is being sent is not checked off: the command prints sent todo 3 to pane 5, but it changed while it was sent, so it stays to do and --json has "checked": false. Only a pending item can be sent: a done one is refused until it is unchecked. The TODO panel’s ▸ button does the same from the window.
Away on Telegram or Discord
Section titled “Away on Telegram or Discord”cornercase away # agents message you on Telegram or Discordcornercase away --off # they stopaway turns on the away mode of every chat app paired, Telegram and Discord, as Remote › continue on Telegram (or on Discord) does: while it is on, an agent that needs you or finishes its turn sends you its question or its last message, and what you reply goes to it, until you turn it off. It succeeds when at least one of them went away, and fails when none could, saying where to set it up. An agent can run it for you when you tell it you are leaving.
Updates from a program
Section titled “Updates from a program”cornercase update checks and installs from its own process. A program that drives the server’s own update flow, such as a GUI’s update button, uses the requests update-check and update-install, the dialog’s own check and install, whose state is the update of status --app:
{"command": "update-check", "args": {}}{"command": "update-install", "args": {}}update-checkasks GitHub whatevercheck_updatessays, and answers theupdateobject when it ends. A check already running is joined. A failure isfailedwith the reason, and the release found before is kept.update-installinstalls the release found, and answers theupdateobject when it ends, withinstalledtrue: restart to use it (restartorrestart --when-idle). It isconflictwhen there is no release (ask forupdate-checkfirst) or when cornercase can’t replace itself here (the message names the command to run). An install already running is joined, one done answers at once, and a failure isfailedand can be asked again.- A development build, which never looks for updates, lists both as unavailable in
contractand refuses them withunavailable.
There is no command for them: cornercase update does the same from a shell, and works without a server.
Restart without cutting an agent off
Section titled “Restart without cutting an agent off”An agent that coordinates others may need a newer cornercase, for a command that was just released. A plain restart would stop every agent in the middle of its turn. --when-idle restarts the server only once no agent is working, in the same step it sees that, so none starts a turn in between:
cornercase update --when-idle --yes --timeout 3600cornercase restart --when-idle --yesThe pane the command runs in doesn’t hold the restart, so an agent can run it as its last step: the restart stops it too, and its conversation resumes. Until then every window says a restart is pending, with a button that cancels it. cornercase restart has the details.
Plan usage
Section titled “Plan usage”cornercase usagecornercase usage --jsoncornercase usage --cachedusage prints what the Limits tab of the Usage dialog shows, so a script or another app can read how much of your plan is left:
Claude Code · max plan updated just now session (5h) 34% · resets in 2h 14m week 81% · resets in 3d 4h week · Opus 12% · resets in 3d 4h extra usage: 12.34 USD of 50.00 USD
Codex usage unavailable: codex did not answer in timeIt asks the installed agents (Claude Code and Codex; both when neither is installed, each saying why it could not run) by running claude and codex on the server’s machine, the ones agent_commands names. That sends no prompt and costs no tokens, and each agent gets at most ten seconds. A probe already running, started by the dialog or by another usage, is waited for rather than started again. Nothing is asked between requests: a program that polls should use --cached, which answers at once with the last answer and its age and asks nothing. The command needs a running server, since the probes and their last answers live there. It exits 1, after printing every agent, when one could not be asked.
With --json it prints {"agents": [...]}, one entry per agent with every key present (null when it has no value): agent and title, loading (a probe is running now), read_at (UTC) and age (seconds) of the last answer with numbers, plan, limited (false means the account has no plan limits, null that nothing was read yet), limits, extra (Claude Code’s extra usage or Codex’s credits, as text), notice (what the dialog shows instead of bars, such as not signed in · run codex login) and error. After a failed probe, plan, limits and the rest are from the answer before it, as old as age says. A limit has label (the dialog’s text), kind (the agent’s own name for it: Claude Code’s session, weekly_all or weekly_scoped, or Codex’s primary or secondary), scope (the model or bucket it applies to, null for the whole plan), percent, severity (normal, warning or critical; read it leniently) and resets_at (UTC, or null). kind and scope together name a limit from one answer to the next. With accounts, an agent has one entry for its usual login and one per account, which adds account (its folder name) and label (the name you gave it, or System for the usual login); the entry new tabs use (the usual login’s when that agent uses no account) has active: true. These keys are left out when there is nothing to say, so an answer without accounts is the same as before.
It is the request {"command": "usage", "args": {"cached": false}}. An older server answers that it has no usage command.
cornercase statscornercase stats --jsoncornercase stats --sharestats prints the parallel-work headline, the 24 h / 7 days / total table and a text heatmap (·░▒▓█). With a server it includes numbers still in memory; without one it reads stats.json. An older server that has no stats command says to update cornercase and restart it.
--json prints an object with since (UTC), windows (24h, 7d, total) and blocks (newest first). Each window has agents, prompts, turns, agent_seconds, busy_seconds, most_at_once, waited_seconds, tokens (in, out, cache_read, cache_write) and by_agent (claude, codex, gemini, opencode). Durations preserve milliseconds as fractional seconds. Busy time and most-at-once span all kinds; the per-agent counters contain starts, prompts, turns, working/waiting durations and token kinds. Every key is present, even when zero. Each block gives agent_seconds. merged_pull_requests is null unless the server has read your merged pull requests (see Usage and stats); otherwise it has 24h, 7d, total, complete (false when a count is a lower bound) and accounts (provider, host, login, error). The text form adds a merged PRs row, with a + after an incomplete number; a server is needed for it.
--share prints the same share link as Usage → Stats → share. Copy and post it yourself; anyone with the link sees the numbers on the card. See Usage and stats for the counting rules and privacy.
Settings
Section titled “Settings”cornercase configcornercase config --jsoncornercase config set sidebar treecornercase config set fetch_minutes 10 counts falsecornercase config set issue_tabs github,linearprintf '%s' "$LINEAR_API_KEY" | cornercase config secret linearcornercase config remove linearcornercase config project ~/code/web-shop --setup 'npm ci' --teardown 'rm -rf node_modules'config lists every setting of the Settings dialog, the connections (accounts and bots) and each project’s setup and teardown. A setting that the dialog shows as a row has a key, the name of its config.json key (an agent’s mode and extra arguments are agents.claude.mode and agents.claude.extra, and the issue tabs are one list, issue_tabs), so a program can draw a form from the answer instead of knowing each row.
set takes KEY VALUE pairs and changes them as the dialog does: the same checks, the same live effects (browser access starts or stops, keep awake takes effect, the layout changes) and the same notices. Either every value is accepted and saved, or nothing is. On the command line the value is converted by the setting’s type: true or false (also on, off, yes, no) for a switch, a whole number, a comma-separated list for list, otherwise text.
A token is never an argument. secret KEY reads it from standard input (hidden on a terminal), checks it with its service exactly as the dialog does (a tracker’s token check, a bot’s getMe), saves it and answers with the account, never the token. remove KEY forgets a token, a bot, a paired chat (telegram.chat, discord.chat) or the paired browsers (browsers). A token that comes from an environment variable cannot be replaced or removed from here, as in the dialog. Pairing a phone stays in the window: it needs its QR code.
project sets what a project’s folder runs when a worktree is made and before one goes; --project ID or the folder names it, and an empty text removes the commands. Only a repository’s root has worktrees, so a folder that isn’t one is refused.
Over the socket, every answer has the same shape and the request is {"command": "config", "args": {"action": "get"}} (or "set", "project", "secret", "remove", each with its entry):
{"command": "config", "args": {"action": "set", "entry": {"fetch_minutes": 10, "sidebar": "tree"}}}{"command": "config", "args": {"action": "project", "entry": {"path": "~/code/web-shop", "setup": "npm ci"}}}{"command": "config", "args": {"action": "secret", "entry": {"key": "linear", "value": "lin_api_…"}}}{"command": "config", "args": {"action": "remove", "entry": "linear"}}settings: for each one,key,pageandsection(where the dialog shows it),label,type(switch,number,text,choice,listorkey),valueanddefault(as JSON),shown(what the dialog shows as its value),note,choices(for achoiceand thelist:value,note,dangerous),field(the label of its edit field),question(the title of its pick),dangerousandrestart.restartsays the change only applies after a restart; none of today’s settings does.settakes strict JSON types:true, a number, a string, a list of strings.connections:key(shortcut,linear,jira,plane,gitlab,asana,browsers,telegram,telegram.chat,discord,discord.chat),kind,label,note,state(missing,connectedorrefused),account(who it is connected as),from(savedorenv) andenvfor a token from the environment,countfor the paired browsers,field(what to paste) andremovable. A token or a bot is set withsecret, never throughset.projects:folder,key(as written inconfig.json),project(the id of the open project in that folder),repository,setupandteardown.revision,path(theconfig.jsonfile),problem(why it cannot be read, when it cannot) andnotices: what the dialog would have said for each change made.
revision goes up when a setting, a project’s commands or a connection changes, however it changed (the dialog, a request, or an edit of the file), and status --json, status --follow and view lines carry it as config.revision: a program that mirrors the session reads the settings again when it grows. Notes that change by themselves, such as the keep-awake state, don’t move it.
A refusal has a code: not-found for a key that names no setting or connection, malformed for a value of the wrong type or one the dialog’s check refuses (the message is the dialog’s), conflict for what the current state blocks (a token set by an environment variable, Jira’s site and email missing, a folder that isn’t a repository, a config.json cornercase cannot read), and failed when the service refused a token. Changes made through set never write over a config.json that cannot be read.
Plugins
Section titled “Plugins”cornercase plugin actionscornercase plugin actions --context pane --jsoncornercase plugin run acme.notes note --pane 5cornercase plugin run acme.notes note --worktree feat/xplugin actions lists what the plugins you turned on add to the menus, in the order the menus show them (your plugins in the order of plugins.json, then each manifest’s actions), for one kind of row (--context project, workspace, tab or pane) or all of them. The text is a tab-separated table under a header, plugin, action, contexts, title, so cut and awk can read it.
plugin run runs one on what you name (--pane, --tab, --workspace, --worktree or --project) the way its menu entry does: in the workspace’s folder (the project’s for a project), with the ids of what you named in the environment, stopped after 5 minutes, its output in the plugin’s log. It waits for the action, prints how it ended and exits with 1 when it failed. Nothing changes in the window: no toast, no focus. A workspace being removed or closing, and a project closing, are refused, since the action would run in a folder being deleted.
Actions are named by plugin id and action id, and listed with a revision that goes up each time the plugins change (counted from the server’s start, so send boot too). plugin run refuses a revision the plugins moved past, conflict, as a menu opened before the change does nothing, so an action whose command or contexts changed since you listed it never runs by surprise. Left out, --revision is the current one, which plugin actions --json also prints.
The requests behind them are plugin-list, plugin-actions and plugin-run. {"command": "plugin-actions", "args": {"context": "pane"}} answers revision and actions (plugin, name, action, title, contexts). {"command": "plugin-run", "args": {"plugin": "acme.notes", "action": "note", "revision": 3, "item": {"pane": 5}}} takes every field, the item as close and focus do, and answers when the action ends:
{"plugin": "acme.notes", "action": "note", "context": "pane", "project": 1, "workspace": 2, "tab": 3, "pane": 5, "ended": "exited", "code": 0, "signal": null, "error": null, "ms": 41, "log": "/…/plugins/state/acme.notes/plugin.log"}ended is exited (with code), signal (with signal), timeout or error (it could not start); error is null exactly when it exited with 0. An action that ran and failed is an answer, not a refusal. A group, or an action run on a kind of row it isn’t offered for, is malformed; a plugin that is unknown, off or cannot run, an action it lacks, or an id that names nothing is not-found. A client that leaves before the action ends gets no answer, and the action runs to its end.
plugin-list answers what Settings › Plugins shows: revision, problem (why plugins.json cannot be read, or null) and plugins, one row per plugin with id, enabled, state (on, off or cannot-run), error, Settings’ label and note, source, log, manifest and services. A service is running (with its pid), stopped (with restart_in, the seconds until it starts again) or starting. Processes a plugin keeps running in each local window belong to those windows, so the server can’t tell. Turning plugins on and off, installing and updating stay with cornercase plugin, which works on the files. An older server refuses each of the three by name.
--json prints a command’s answer as JSON. These fields are kept stable; new ones may be added.
| Command | Fields |
|---|---|
status |
version, boot (which start of the server this is, see Contract and compatibility), caller (your pane’s id, or null), shown (the ids of what the window shows), groups (id, name, collapsed, icon, colour), projects (id, name, custom_name, path, group, active, collapsed, workspaces), each workspace (id, name, custom_name, path, branch, worktree, behind, pull_request, run_dir, run, active, collapsed, tabs), each tab (id, name, custom_name, status, active, layout, panes), each pane (id, path, program, agent, status, background_shell, dialog, survey, at_prompt, model, context, active, caller, resumes, resuming, title, parent, subagents, role, held_sends, draft, and collapsed, there only while it is true). held_sends is how many messages send is holding until the person in that pane is done typing, and draft is whether that pane has text a person typed and has not sent yet (see Talk to a pane). subagents is how many subagents the agent started that still run (a number, 0 for shells and agents without a reader); role is the role the pane plays in a flow run (orchestrator, coder…), or null outside one; title is what the program last set as its title (OSC 0 or 2), as a terminal tab shows it, or null; it can change several times a second while an agent works, and status --follow sends it at most every half second. pull_request is null, or the pull request of the workspace’s branch: number, title, url, state (open, draft, merged or closed), checks (failing, pending, passing, or null without checks) and failed, the names of the checks that failed, conflicts (true while GitHub says it has merge conflicts with its base; its pull_request workflows don’t run until they are resolved, so checks covers only what ran) and base, the branch it merges into, or null. run is there while the workspace runs a flow: end (pull_request or commits, what the flow delivers) and reached (for pull_request, true once the branch has a pull request that is open, a draft or merged, or once the orchestrator marked its own pane for review; for commits, only that mark counts. The mark counts until the worktree moves to another branch). Groups, projects, workspaces and tabs come in the order of the sidebar. colour is a group’s palette index (0-255) and icon its symbol; custom_name is the name given in cornercase, or null when the item takes its folder’s, branch’s or program’s; collapsed is whether a project or workspace is folded. layout is how the tab’s panes are split: {"leaf": PANE}, or {"split": {"dir": D, "ratio": R, "first": …, "second": …}}, where dir is right (side by side) or down (one above the other) and ratio is the share of the first side, from 0 to 1. active is whether it is the active one of its parent; status is working, waiting, pending, done, idle or null; background_shell is true while the agent’s turn is over but a shell it started in the background still runs (its status stays working); dialog is true while a Claude Code agent shows a dialog, a panel or its shell mode instead of its input box; survey is true while it shows its feedback survey above its input box; resumes is true when the pane’s agent conversation comes back after a restart; resuming is true while it is coming back, and the pane takes no input; parent is the id of the pane it was started from (by start, new-tab or split run inside that pane, or a flow’s orchestrator), or null when it has none or that pane closed; at_prompt is whether the pane’s shell sits at its prompt, with nothing else running in it, so it is false whenever an agent runs, whatever the agent shows. While keep awake is on, keep_awake has held (whether the computer is being kept awake now), by (the tool holding it) and unavailable (why it can’t be, on this machine); see Keep the computer awake. theme says where the colours of the terminals’ programs come from: from and scheme (see Colours for programs). config is {"revision": N}, which goes up whenever a setting changes. |
status --follow |
One object per line: what status prints, plus revision. |
status --app |
revision (with --follow), version, boot, todos, queue, away, update and keep_awake, see Follow the rest of the app. |
status --panes |
panes: one object per pane, with the columns of --panes as keys, in their order: pane, tab, workspace, project, program, agent, status, background_shell, dialog, at_prompt, model, context, caller, shown, active, tab_name, workspace_name, branch, worktree, project_name, path, survey, resuming, parent, subagents, role, held_sends, draft. Every object has every key, null where there is no value. |
open, new-workspace, new-tab, split, start |
project, workspace, tab, pane: the ids of what was made (for open, the project’s shown workspace, tab and pane), and projection when --focus --view moved a view. |
focus --view |
project, workspace, tab, pane the view shows now, and its projection. |
send, wait |
pane, and, once a wait ended, ended (idle, done, pending, waiting, working, shell, text or quiet) and line for a --text match. start --wait adds ended and line too. |
wait --any, wait --all |
panes: one entry per line of text, each with pane, tab when it was given as a --tab, ended (as above, or closed) and line for a --text match. |
flow list |
flows (each with name, origin, path, description, roles, problems, check, flow and edited), broken, hidden (the deleted built-in flows, like flows) and builtins. See Manage flows. |
flow run |
run (the run’s id), project, workspace, branch, members and run_dir. |
flow delete, flow hide, flow restore |
name, origin, deleted, hidden, changed. |
flow runs |
version, boot, runs (with --follow, each line also has revision), see Follow a flow run. |
flow messages |
One object per line: run, workspace, seq, time, from, to, kind, text, bytes, edge. |
flow files |
workspace, run, dir, files (name, path, bytes, modified) and more. |
flow-save (a request) |
name, origin, path, saved, problems, check. |
usage |
agents: agent, title, loading, read_at, age, plan, limited, limits (label, kind, scope, percent, severity, resets_at), extra, notice, error. See Plan usage. |
plugin-list (a socket request) |
revision, problem and plugins: id, enabled, state, error, label, note, source, log, manifest and services (id, state, pid, restart_in). See Plugins. |
plugin actions |
revision and actions: plugin, name, action, title, contexts. |
plugin run |
plugin, action, context, the ids it got (project, workspace, tab, pane), ended, code, signal, error, ms and log. |
search |
matches: pane, tab, workspace, project, place, who, source (prompt, reply or screen), line, text, written; and capped. See Find where something was said. |
read |
pane and text. With --last-message, also agent (claude, codex, gemini or opencode), written (when the agent wrote it, in UTC, such as 2026-10-08T19:56:37.241Z) and turn_over (true once the agent stopped working, as --until turn-over sees it; missing while cornercase doesn’t know yet). |
read --last-message with several targets |
panes: the individual read --last-message answers above, or entries with error and the requested pane or tab id. |
events |
One object per line, see Follow events: time, event, project, workspace, tab, pane, and agent, from, to, program, kind, parent (for an opened pane started from another pane), token, status, place, message, panes (for notice), text (for clipboard) or count (for bell and dropped) depending on the event. |
changes |
One object per line, see Follow a workspace’s changes: boot, stream, revision, scope, mode, state, error and workspaces. |
changes-mode |
stream, scope, workspace and mode. |
changes-file |
workspace, mode, the file (path, old_path, status, added, removed, fold, viewed, digest), styles and hunks. |
changes-gap |
workspace, path, hunk, styles and lines. |
changes-base |
workspace, base, default and branches. |
changes-viewed |
workspace, path and viewed. |
surface |
One object per line, see Stream a tab’s terminals: boot, session, surface, revision, tab, cols, rows, layout, dividers, styles, hyperlinks and panes; for --view, also view and projection. With --patches, later lines carry base and only the changed panes. |
view |
One object per line, see A view of your own: what status --follow prints, plus view (id, projection, active, cols, rows, shown, controls, visible) and each tab’s controller. |
view-set |
The view: id, projection, active, cols, rows, shown, controls, visible. |
go |
The ids it went to (project, workspace, tab, pane), and projection with --view. |
input |
pane, bytes (what was written) and skipped: event (its position from 0) and why (mouse, release, or key for a press cornercase has no encoding for) of each event that was not sent. |
scroll |
pane and scroll: total, offset and rows, as in a frame. |
selection |
pane and text (absent when nothing is selected). |
resize |
surface, tab, cols and rows. |
redraw |
surface and tab. |
link |
pane and link: null when the cell holds no file, else path (canonical), inside (in the pane’s workspace), workspace, relative (inside only), lines (first and last, absent without lines), and row, start and end, the cells the path takes (end excluded). |
ratio, move-pane |
tab and layout, the tab’s split tree as status --json gives it. |
group-new |
group, the new group’s id. |
group-style |
group, icon, colour, and icons and colours, the sets to choose from. |
removal-check |
workspace, path, branch, changed, lock, roles and teardown, as close --check --json prints them. |
todo list |
todos: id, text, done. |
todo rm, todo clear-done |
todos: the items removed, and undo, the number todo restore takes (none when nothing was removed). |
todo send |
pane, as send gives it, todo, the item checked off, and checked (whether the item was checked off) and unconfirmed (true when it was sent but stays pending). |
config |
revision, path, problem, settings, connections, projects and notices: see Settings. |
contract |
version, contract, boot, operations (name, available, and reason when it is not), features and streams (name, version). See Contract and compatibility. |
Exit status
Section titled “Exit status”| Status | Meaning |
|---|---|
0 |
Done. |
1 |
An error or a timeout; the message says which. |
2 |
Wrong usage, such as an unknown option. |
Some messages you may meet:
| Message | Meaning |
|---|---|
no cornercase server is running |
Start cornercase first, or cornercase start-server without a window; the other commands never start one. |
the running cornercase server is too old for cornercase … |
The server comes from a release without these commands, or without this one. Restart it: cornercase kill-server (it closes all its terminals), then cornercase. |
the running cornercase server (…) has no … command |
The command is newer than the server. Restart the server so it runs the version you installed. |
this cornercase server has not started its session yet … |
A server started without a window has no size to give a new terminal, and hasn’t restored your session yet. Run cornercase start-server, or open a window with cornercase. |
the agent in pane … is waiting for an answer … |
send refused to type into an agent’s question or permission prompt. |
the agent in pane … shows a dialog, a panel or its shell mode … |
send refused to type into a Claude Code dialog, panel or shell mode. Read the pane, close it and send again, or pass --force. |
the agent in pane … shows Claude Code's feedback survey … |
send refused to type while Claude Code’s survey would take a digit as its answer. Dismiss it with cornercase keys --pane … 0 and send again, or pass --force. |
pane … is resuming its agent's conversation … |
send refused to type while the pane’s agent comes back in its conversation after a restart. Wait until status no longer shows it resuming, or pass --force. |
not confirmed: the prompt may not have been submitted … |
send --enter pressed Enter, but the agent recorded no new prompt. Read the pane before sending it again. |
the … agent in pane … has not written a message yet |
read --last-message found the agent’s record, with no message in it. |
the program in pane … is not reading its input … |
Over 16 MiB sent to the pane are still waiting for its program to read them, so send and keys stop adding more. |
the program in pane … is not reading its input … so nothing was sent |
input found over 16 MiB waiting for the program. With a code it is busy. |
pane … is … now; read its latest frame and try again and other conflict messages |
The terminal’s size changed since your frame, a cell is outside it, the pane’s program scrolls itself, or nothing is selected to copy. Nothing was done. |
timed out after … |
The --timeout passed; the rest of the message says what the pane was doing. |
the restart was cancelled in the window … |
Someone pressed cancel on the window’s notice while restart --when-idle waited; the server keeps running. |
cornercase hit a bug, see server.log |
cornercase hit a bug while it handled the command or waited for it. Your shells keep running; see this message in troubleshooting. |
Contract and compatibility
Section titled “Contract and compatibility”The commands and the server don’t have to come from the same release: after cornercase update, a command of the new version talks to the server still running the old one, and the other way round. A program of your own, such as another UI for cornercase, can rely on the same rules, through the commands or straight through the socket.
Ask the server first
Section titled “Ask the server first”cornercase contractcornercase contract --jsoncornercase 0.12.137contract 1boot 4242-1700000000commands 78 (78 available)stream events 1stream watch 1stream surface 1stream view 1stream app 1stream changes 1stream flow-runs 1stream flow-messages 1features boot, structured-errors, strict-fields, views, host-events, theme, notices, surface-patches, config, close-unlock, sound, issues, todo-panel, changes, agent-folds, accounts, account-login, account-switch--json prints an object with:
version: the server’s release.contract: the version of the rules on this page, an integer starting at 1. It goes up only when something that was there changes meaning or shape, never when something is added.boot: which start of the server this is. It changes on every start,cornercase restartincluded, since ids start again from 1. It is the same value asCORNERCASE_SERVERin the panes andbootinstatus --json.operations: every request the server takes, by name, withavailable. A server withstart-serveramong them can be started without a window; one without it needs a window before it opens terminals. One that is not available on this server hasavailable: falseand areason; the others keep working.features: what a request can ask for (below):boot,structured-errors,strict-fields,views(theviewfield and--view; a client checks it before sending a view to a server),host-events(thehostfield ofevents, which an older server would ignore; a client checks it before sending it)theme(thethemerequest andstart-server’stheme; check it before sending colours),notices(a view’svisible, thenoticesfield ofeventsand thegorequest; check it before sending them),surface-patches(thepatchesfield ofsurface; an older server ignores it and sends complete lines, so check it before asking)config(theconfigrequest andconfig.revision)close-unlock(theunlockfield ofclose, which an older server ignores and then refuses with the lock’s message; check it before sending it),sound(a notice’ssoundfield: an older server never sends it),issues(theissueandsourcefields ofstartandflow-run, which an older server ignores and then starts the agent without the issue; check it before sending them),todo-panel(thetodorequest’sedit,undone,move,clear-doneandrestore; an older server refuses them as malformed, so check it before sending them),changes(thechangesrequests and thechangescount ofstatus),agent-folds(thepaneitem ofcollapseandexpand; an older server refuses it as malformed, so check it before sending it),accounts(theaccount-*andprofile-*requests and theprofilefield ofstart,start-with,new-tabandflow-run, which an older server ignores and then starts the agent with its own login; check it before sending it),account-login(thecheckfield ofaccount-listand each account’slogin; an older server ignorescheckand answers without asking, so check it before sending it), andaccount-switch(theaccount-renameandaccount-userequests and theaccountsfield ofstart,start-with,new-tab,flow-runand the queue’saddandedit, which an older server ignores and then starts with the accounts in use; check it before sending it).streams: the formats of what the server streams, with their versions:{"name": "events", "version": 1}forevents{"name": "watch", "version": 1}forstatus --follow{"name": "surface", "version": 1}forsurfaceand{"name": "view", "version": 1}forview, whose lines arewatchformat 1 plusview, and{"name": "app", "version": 1}forstatus --app,{"name": "changes", "version": 1}forchanges, and{"name": "flow-runs", "version": 1}and{"name": "flow-messages", "version": 1}forflow runsandflow messages.
A server from before contract says the running cornercase server (…) has no contract command, or that it is too old: it knows none of the rest of this section, so don’t send it ids you kept from before.
What stays compatible
Section titled “What stays compatible”- Fields are only added, never renamed or removed. Read answers leniently: ignore keys you don’t know, and don’t depend on their order (
eventslines come out with their keys sorted). - A new behaviour that must not be ignored gets a new request name, which an older server refuses with
has no … commandrather than doing something else. contractgoes up when something that was there changes. Additions show inoperations,featuresandstreamsinstead.- The format of each stream has its own version. Format 1 of
eventsis the one described in Follow events, format 1 ofwatchthe one in Follow the whole state, format 1 ofsurfacethe one in Stream a tab’s terminals, format 1 ofappthe one in Follow the rest of the app, format 1 ofchangesthe one in Follow a workspace’s changes.
Errors with a code
Section titled “Errors with a code”A request through the socket is JSON: {"command": "status", "args": {}}, its args named after the command’s options; the repository’s tests/fixtures/control/ holds examples of what goes over the socket. It is answered with {"ok": …} or {"error": "…"}. A request that also carries "contract": 1, or "boot": "…", gets errors with a code instead:
{"error": {"code": "not-found", "message": "there is no pane 9; `cornercase status` lists them"}}| Code | Meaning |
|---|---|
unsupported |
The server has no such request, or the request’s contract is newer than the server’s. Nothing was done. |
malformed |
The request is not one: a plugin action run on a group or on a kind of row it isn’t offered for, its arguments don’t have the expected types, a setting’s value is of the wrong type or one the Settings dialog refuses, or it names a view and takes none (`stats` takes no view). |
stale-boot |
The request’s boot is not this server’s: it restarted since, and the ids you hold may name other panes now. Nothing was done. |
not-found |
A plugin that isn’t on or an action it doesn’t have, no group, project, workspace, tab, pane, view, surface or changes stream has that id, a file has no change or a hunk no gap, changes-mode names a stream another connection opened, a branch doesn’t exist, no setting or connection has that key, or the tab has no divider at that path. A view is gone once the connection that opened it closes. |
unknown-field |
A field this server doesn’t know, named by its path, such as args.bogus. Nothing was done. |
unavailable |
The request is listed in operations with available: false, with the reason as its message. A development build turns off update-check and update-install; a release turns off none. |
busy |
The pane’s program isn’t reading its input: 16 MiB are waiting for it. Only input answers this; send and keys keep answering failed. Nothing was sent. |
conflict |
The request doesn’t fit the current state: a plugin action listed at another revision of the plugins, the pane’s terminal has another size, a cell is outside it, the program scrolls itself, nothing is selected to copy, or, for update-install, no release found or a cornercase that can’t replace itself, a workspace for changes isn’t in a git repository, a connection already follows changes, or, for config, a token set by an environment variable, a project folder that isn’t a repository or a config.json that cannot be read; or a row moved before one of another workspace or project, a pane moved to another tab, or where its tab has no room; or an issue of a tracker that is not connected, a place to choose for an issue that isn’t tied to a repository, or tracker settings that changed during the lookup. Read the latest state and try again. Nothing was done. |
failed |
Anything else. |
The message is the text the command would print. Text that is not JSON at all gets the plain form, {"error": "…"}, since the server can’t tell what the request asked for. Without contract or boot, a field the server doesn’t know is ignored, as it always was; with them it is refused, so a newer option never passes for done on an older server. Either one set to null counts as not sent. A contract newer than the server’s is refused with unsupported, so a client written for later rules learns it at once instead of being read under older ones. The cornercase commands themselves send neither, so what they print doesn’t change.
With boot set to the value from contract or status, a request kept across a restart is refused before it acts on an id the new server gave to something else.
Talking to the socket directly
Section titled “Talking to the socket directly”The socket takes one request per connection. Each message is framed as its length, 4 bytes little-endian, then its postcard encoding: a request is variant 4 followed by the JSON text (its length as a varint, then its UTF-8 bytes), an answer variant 5 in the same form. events sends one answer per event and ends with variant 2, 3 or 4 (its panes closed, the server stopped, or it restarted); watch, the request behind status --follow, answers with the state and then sends it again in the same form after each change, until variant 3 or 4, and app, the request behind status --app --follow, does the same with the rest of the app’s state; surface does the same with a tab’s frame, and ends with variant 2 when the tab closes (or its view does); view sends the state, with the view in it, and holds the view open until the connection closes. A request for a view carries its id as view next to command, once contract lists the feature views. Only your own user can connect. Nothing needs to be sent before the request.
Teach your agents
Section titled “Teach your agents”An agent only knows what is in its context, so it doesn’t know about these commands until you tell it. cornercase ships a short set of instructions as an agent skill (a SKILL.md in the repository); install it for Claude Code, Codex and other agents with:
npx skills add usecornercase/cornercase-terminal --skill cornercase -gOnly the skill’s name and description stay in the agent’s context; the rest loads when a task needs it. cornercase skill prints the copy that matches your version; for an agent without skills, put that text in your AGENTS.md or CLAUDE.md. cornercase never installs it by itself, for the same reason it installs no hooks: it doesn’t write into your agents’ setup.
The instructions tell agents to use the commands only when you ask them to open tabs, run something in another pane, or start and coordinate agents; to learn the syntax from cornercase --help; to find panes with status --panes rather than walking the tree; never to steal focus, run kill-server or close what they didn’t open; not to answer another agent’s permission prompt without asking you; to read a pane before sending again after a timeout or a prompt that was not confirmed; to keep their --timeout under their own command timeout; to read several agents’ answers in one read --last-message call; and to restart or update cornercase only when you ask, with --when-idle.
Environment
Section titled “Environment”| Variable | Use |
|---|---|
CORNERCASE |
1 in every pane. The commands work there; only opening the window inside cornercase is refused. |
CORNERCASE_PANE |
The id of the pane a shell runs in. The commands use it to know where they run. |
CORNERCASE_RUN_DIR |
In the panes of a flow run only: the folder where the team keeps its files. |
CORNERCASE_SERVER |
Which server started the shell, the boot of contract. A server only takes CORNERCASE_PANE as the pane a command runs in when this is its own, since every server numbers its panes from 1. |
CORNERCASE_SOCKET |
Which server to talk to, as for every cornercase command. Panes don’t get it: a shell inherits the server’s environment, so cornercase in a pane finds its own server anyway, and setting it would also move config.json and the session (see Files and environment). |
Security
Section titled “Security”As before, only your own user can reach the server’s socket. But whoever reaches it can type into every shell, and change your settings and tokens, so a coding agent that can run cornercase can run anything in any pane, and run commands outside its own sandbox through another pane. Keep the socket out of reach of agents you sandbox: run them in a container or under another user without access to it, or don’t give them a shell inside cornercase.
Queue work for later
Section titled “Queue work for later”cornercase queue add --at 'in 2h' --agent claude --prompt 'Fix the login form' schedules work and prints its persistent id. Use --issue URL_OR_KEY for an issue, --workspace ID for a tab, or --worktree BRANCH for a worktree. --at 'claude reset' and --at 'codex reset' use the next five-hour plan reset. queue list --json, queue edit ID --at TIME, queue remove ID and queue run ID inspect and manage it. See Queue agents for formats and flags. Queued launches accept folder trust prompts automatically.