Driving Dormouse with dor
You are running inside Dormouse, a multitasking terminal. Every terminal it launches has the dor CLI on PATH. dor lets you create, inspect, type into, and kill other terminal panes, and open browser panes — so instead of backgrounding a process inside your own shell (where its output is invisible and it dies with you), you run it in its own surface that the user can see and that outlives your session.
Two hard rules
These override your usual defaults. They matter more than anything else here:
NEVER start a long-running process — a dev server, a
--, any daemon — as a background subprocess. It would be invisible to the user and die with your shell. ALWAYS run it withwatch dor ensure --, which puts it in a visible pane that outlives you and gets reused instead of duplicated.<command> NEVER use a built-in, native, or bundled browser tool to open, view, or drive a web page. ALWAYS use
dor ab(agent-browser), so the page renders in a Dormouse pane the user can watch and you can drive.
The rest of this guide is how to do everything well.
Targeting: three ways to name a surface
CLI reference: Targeting
Action commands (read, send, kill) take a surface handle — there is deliberately no dor kill "npm dev". You name the surface you want one of three ways:
Hold the handle. Commands that create surfaces (
split,ensure,iframe) print the new ref (created surface:3). Capture it and act on it directly — refs stay valid across any layout churn.Address by identity key. Surfaces with a natural identity skip handle bookkeeping:
dor ensure --uses its exact command + cwd as an implicit key (match-or-create in one idempotent call), and browser surfaces are addressed by an explicit key (<command> dor ab --). A browser you did not create has no key you know — hold its ref and usekey <name> dor ab --.surface <ref> Rediscover. When you hold nothing — a fresh session, or a process the user started by hand —
dor list(filtered) turns a description (--,command --,cwd --) into a handle.port
Text output is designed for you to read: it is terse and carries the same refs. Reach for -- (every command except dor ab supports it) only when a shell script or pipeline using jq consumes the output.
Surface handles
CLI reference: Surface handles
surface:N— short ref, e.g.surface:3. Stable for the surface's whole life: reordering, minimizing, zooming, and focus changes never change it, and numbers are never reused after a kill. A ref for a killed surface fails loudly instead of silently retargeting, so refs from earlier in your session stay safe to use.A stable surface id (or
surface:<stable-) — fromid> --output.json surface:self— the terminal you are running in.surface:focused— whatever the user currently has focused.title:<exact title>— exists for human recovery; avoid it in automation (titles drift). Prefer refs from command responses ordor list.
Bare numbers and pane:N are not valid handles.
Command reference
Run dor <command> -- for full details on any command.
dor list — find surfaces
CLI reference: dor list
dor list # everything in the workspace
dor list --command "npm run dev" --cwd . # exact command + cwd match
dor list --port 5173 # which terminal owns port 5173
dor list --kind terminal --view minimized # filters AND together
dor list --ports # add each terminal's listening portsLists every surface in the current workspace — terminals and browser surfaces, including minimized ones. -- matches the exact command the shell reports it is running (npm run dev ≠ npm run dev --). -- resolves relative to your PWD. --/-- trigger an opt-in port scan and -- only ever matches the terminal that owns the socket, never a browser showing that URL. Text rows mark the user's focus with * and your own terminal with (you); -- adds stable ids and a host/identity block.
dor split — create a terminal
CLI reference: dor split
dor split -- npm test # runs in background; focus stays with you
dor split --minimize -- ./watch.sh
dor split -- # blank terminal, focus stays with youDirection flags -- (default --). -- picks which surface to split from. The response includes the new surface's ref — save it.
Never run a bare dor split (no --) — it moves the user's keyboard focus to the new pane, hijacking their keystrokes. For an empty terminal, write dor split --: same blank pane, focus stays put. Every dor split you run has a --.
dor ensure — idempotent "make sure this is running"
CLI reference: dor ensure
dor ensure -- npm run dev # reuse if live, else create
dor ensure --restart -- npm run dev # interrupt + re-run in place
dor ensure --minimize --cwd ../worktree-b -- npm run devMatches on exact command + resolved cwd against commands that are currently live (via shell integration), so it also adopts a server the user started by hand. It never changes focus, and never collapses the same command running in two directories. -- preserves the surface's place in the layout and its minimized/visible state. Requires a shell with OSC 633 integration in the target (Dormouse-launched shells have it; cmd.exe does not).
Prefer ensure over split for anything with a natural identity ("the dev server for this directory") — it is your dedupe key across re-runs and layout churn.
dor send — type into a terminal
CLI reference: dor send
dor send surface:3 --text "npm test" --key enter # the canonical run-a-command
dor send surface:3 --key ctrl-c # interrupt
cat answers.txt | dor send surface:3 --stdin
dor send surface:3 --sequence '[{"text":"y"},{"key":"enter"},{"key":"tab"}]'Exactly one input mode per call: --/-- (only in that order, text first), --, or -- for anything more complex. Special keys go through -- (enter, escape, tab, backspace, delete, arrows, ctrl-..ctrl-) so they are never confused with literal text. -- interprets \n \r \t \\ unless --.
dor read — read a terminal's screen
CLI reference: dor read
dor read surface:3 # visible screen, printed directly
dor read surface:3 --scrollback --lines 200dor kill — kill a surface (confirmation required)
CLI reference: dor kill
dor kill surface:3 --confirm-if-read "npm test" # preferred: verify then kill
dor kill surface:3 --confirm-dangerously # only when already validated-- kills only if the surface's visible screen contains the text (≥4 non-whitespace chars) — use it as a cheap guard that you are killing what you think you are.
dor ab / dor agent- browser — agent-drivable browser pane
CLI reference: dor ab
Forwards everything to your installed agent- CLI (not bundled — npm i -) and binds the session to a Dormouse browser surface so the user watches what you drive.
dor ab open http://localhost:5173 # key "default"
dor ab --key server open http://localhost:3000
dor ab click @e3 # further args are agent-browser's own
dor ab --key server reload
dor ab --surface surface:4 click @e3 # drive the browser a ref names-- is a workspace-scoped browser identity: one key = one session = one surface, reused across commands. Use distinct keys when you need independent browsers at once.
-- drives whatever browser a handle names, so a ref from dor list works here exactly as it does for read / send / kill. Prefer it whenever you hold a ref rather than a key — it is the only way to reach a browser the user opened from the GUI, which has no key. It fails on a terminal (no browser), and on an iframe-rendered surface (nothing to drive — open it with dor ab instead). The three identity flags are mutually exclusive.
dor ab has no -- of its own; any JSON flags belong to agent-.
dor iframe — high-fidelity URL pane for the user
CLI reference: dor iframe
dor iframe http://localhost:6006 # absolute http(s) URL requiredFor showing a page to the human at full fidelity (no automation hooks). Provisional: some sites refuse framing. To drive a page, use dor ab.
Recipes
Run a dev server and show it to the user. Ensure it, find its port, open a browser on it:
dor ensure -- npm run dev
dor list --command "npm run dev" --cwd . --ports # read the port
dor ab open http://localhost:<port>Launch and drive a sub-agent (another CLI agent in a sibling pane):
dor split -- codex # prints "created surface:N"
dor send surface:N --text "/review" --key enter
dor await surface:N --until quiet && dor read surface:NBlock on the peer instead of polling. -- wakes when it settles, exits, or rings — use it for agents that never exit; -- waits only for the command to exit, for builds and test runs that fall silent mid-run. Awaiting absorbs the bell, so the human is not summoned for news you received.
Client/server browser testing. Two keys, two independent browsers:
dor ab --key server open http://localhost:3000/admin
dor ab --key client open http://localhost:5173Same command in multiple worktrees. cwd keeps them distinct:
dor ensure --cwd ~/wt/feature-a -- npm run dev
dor ensure --cwd ~/wt/feature-b -- npm run dev
dor list --command "npm run dev" --cwd ~/wt/feature-a # picks oneLong-running background job, out of the way. Minimize it; rediscover it later by command instead of remembering the ref:
dor ensure --minimize -- npm test -- --watch
dor list --command "npm test -- --watch"
dor read surface:N --lines 50Who owns this port?
dor list --port 5173 # the terminal, never a browser surfaceSafe cleanup. List, verify, kill:
dor list --command "npm run dev" --cwd .
dor kill surface:N --confirm-if-read "npm run dev"Rules and pitfalls
Never pre-quote command tails. Everything after
--is forwarded as a raw argv array; Dormouse quotes it correctly for whatever shell the target surface runs (POSIX, cmd, PowerShell). Pass--, notnpm test -- -- watch --."npm test -- -- watch" Focus etiquette.
dor ensureand anything with a--tail (dor split --, or a bare-terminal<command> dor split --) never steal focus. Only a baredor splitwith no--does — never run that in automation; usedor split --for an empty pane instead.Take refs from responses. Capture the ref that
split/ensure/iframeprint rather than re-listing and guessing.--is exact. Match the command string you launched with, including its flags.command Prefer
--overconfirm- if- read --unless you have just read the surface yourself.confirm- dangerously Scope:
dorsees the current workspace only. Terminals ring bells and carry todo flags ([ringing]/[todo]indor list); browser surfaces are the only ones with explicit keys, because their sessions live inagent-.browser