The Nermalcommand
Driving the workbench from a script, a Makefile, or another agent.
Nermal is a thin, non-interactive client of the Nermalserver. Every verb runs and
exits; --json makes the output machine-readable. The GUI does not have to be
running — the server is what owns the panes.
It ships inside every installer and is put on PATH at launch, so it works from any terminal on the machine. Installation →
Start with doctor
NermaldoctorOne table that answers everything you need before doing anything else: whether a
server is reachable, whether its wire dialect matches this binary, and whether
Nermal_CONFIG_DIR / Nermal_WS / Nermal_PANE are set — that is, whether you are
running inside a Nermalpane.
Being inside a pane matters because the address-taking verbs (split, send,
capture, procs, wait, pane close) default to $Nermal_PANE, and
run --keep files its pane into $Nermal_WS. Outside one you must name a target,
and the error says so rather than guessing.
Addresses
| Shape | Means | Stable? |
|---|---|---|
%42 | A pane | Yes — a pane keeps its id for its whole life |
@7 | A tab, numbered across the whole machine in tree order | No — it shifts whenever any workspace or tab appears or disappears |
api · 76698a44 · a full UUID | A workspace, by name, unique id prefix, or id | Yes |
Re-resolve @N immediately before using it. Pane and workspace ids are safe to
remember.
Two ways to run something
Blocking, with a real exit code
Nermalrun -- cargo test # streams to stdout, exits with cargo's code
Nermalrun --cwd /path -- make
Nermalrun --keep -- cargo build # leaves the pane behind as a new tabThe closest thing to running the command yourself — the difference is that it gets a real PTY (so colour, progress bars, and TUIs behave), and that you can watch it happen in the window.
Everything after -- belongs to the child: Nermalrun -- cargo test --keep
passes --keep to cargo, not to Nermal.
Non-blocking: a pane you talk to over time
This is the one worth reaching for. Get a pane, give it work, come back.
PANE=$(Nermalsplit --v) # or --h; prints "%83"
Nermalsend "$PANE" 'npm run dev' --enter
# ... later
Nermalcapture "$PANE" --plain
Nermalpane close "$PANE"If you are not inside a Nermalpane there is nothing to split, so make your own place to work:
Nermalnew --json /path/to/repo # {"id": "...", "pane": 83}Reading a pane
Nermalcapture %83 --plaincapture returns what the server stored. Without --plain that is the raw
bytes, escapes and all. With --plain those bytes are replayed through a real
terminal grid and you get the text that produced — which is not the same as
stripping escapes yourself:
- A line the shell wrapped at the pane width comes back as one line
- A progress bar that rewrote itself with
\rreads as its final value - Cursor addressing puts text where the program put it, so a TUI's screen lands where it was drawn
Use --plain whenever a human would want to read the output.
When all you want is how the last command ended, ask for that much:
Nermalcapture %83 --plain --tail 5--tail N keeps the last N lines of the answer, counted after --plain has
resolved the wraps — so no pipe through tail(1), which is one less thing to
have on PATH.
A screen is a rectangle. Whatever scrolled off the top is gone, and an exit code was never on it. When you want the answer rather than the view, have the shell write it somewhere clean:
Nermalsend "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enterKnowing when something finished
Nermalprocs %83The process tree inside the pane, indented by depth, with * on the foreground
process — plus the ports those processes are listening on. When the only entry
left is the depth-0 shell, the command is done. That is far more reliable than
grepping the screen for a sentinel that can wrap or echo twice.
For agents specifically, use Nermalwait instead of
polling.
Looking around
Nermalls # every workspace: tabs, panes, who's attached
Nermalws tree api # one workspace as a tree
Nermalpane ls --all # every pane, including orphans no workspace holds
Nermalagents # every coding agent and its status
Nermalstatus # server pid, uptime, pane count, build, socket
Nermalmachine ls # this machine plus any linked remotes
Nermalevents # stream server events until interrupted--json on any of them, -q to suppress success output (errors still print).
Remote machines
Nermal-m devbox ls
Nermal-m devbox run -- cargo test-m routes over a link the local server already holds. It will not dial a fresh
connection — connect from the GUI first.
Remote workspaces →
Full command reference
Every verb, flag, and JSON shape.