Nermal
CLI

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

Nermaldoctor

One 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

ShapeMeansStable?
%42A paneYes — a pane keeps its id for its whole life
@7A tab, numbered across the whole machine in tree orderNo — it shifts whenever any workspace or tab appears or disappears
api · 76698a44 · a full UUIDA workspace, by name, unique id prefix, or idYes

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 tab

The 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 --plain

capture 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 \r reads 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' --enter

Knowing when something finished

Nermalprocs %83

The 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.

Sponsor