The prowl CLI #
A command-line interface to inspect and drive the running Prowl app — so you (or an agent) can list panes, read their screens, run commands and capture output, send keystrokes, focus, and open/close tabs and panes programmatically.
Keywords: prowl cli, command line, prowl list, prowl agents, prowl read, prowl send, prowl key, prowl focus, prowl tab, prowl pane, prowl open, pane id, automation, json, capture, socket
Related: terminal · concepts · active-agents · agent-detection · the bundled prowl-cli skill (skills/prowl-cli/SKILL.md)
This is the reference for the
prowlbinary. For an opinionated, safety-first workflow guide (recipes, pitfalls, quoting), the repository also ships theprowl-cliskill atskills/prowl-cli/SKILL.md— same tool, task-oriented.
What it is & when to use it #
prowl talks to a running Prowl GUI app over a Unix socket. Reach for it whenever
the task is to act on a pane other than the current one — check a sibling
agent, run something in another tab and grab the output, focus a worktree, open a
project, or close a scratch tab. It is not for ordinary editing/building
inside a repo, and not for how-to questions about Prowl's settings.
Install #
From the app: Settings → Advanced → Install Command Line Tool, or Command
Palette → "Install Command Line Tool". This symlinks prowl into
/usr/local/bin (prompting for admin if needed).
Global options #
--json— emit structured JSON (recommended for automation). Each command's JSON has aschema_versionlikeprowl.cli.list.v1.--no-color— disable colored text output (implied by--json).
Success envelope: { "ok": true, "command": "...", "schema_version": "...", "data": {...} }.
Error envelope: { "ok": false, "command": "...", "error": { "code": "...", "message": "..." } }.
Exit code is 0 on success, non-zero on failure. Parser errors print plain text
(not JSON) even with --json, because parsing happens before execution — always
check the exit code before piping to jq.
Targeting model #
Most commands accept one selector (mutually exclusive):
--pane <uuid>— a specific pane (safest for automation).--tab <uuid>— a specific tab (its focused/first pane).--worktree <id|name|path>— a worktree (its selected/first tab → focused/first pane).-t, --target <value>— auto-resolve: tries pane UUID, then tab UUID, then worktree id/name/path.- No selector → the current focus (focused worktree → selected tab → focused pane). Some commands (close) refuse this for safety.
Rules: at most one selector (else INVALID_ARGUMENT); prefer explicit
--pane. The focused pane is not stable — open and focus change it.
Never target by tab title. Titles are free-form and can lie. Resolve a concrete
pane.idfromprowl list --jsonfirst.
Commands #
prowl list #
Snapshot of all worktrees → tabs → panes. No selectors.
prowl list --json
Each item contains:
worktree:id,name,path,root_path,kind(git|plain|workspace)tab:id,title,selectedpane:id,title,cwd,focusedtask:status(running|idle| null)
task.status is the same agent activity signal described in
agent-detection. It's good for coordination but can flip to
idle before a TUI finishes painting — confirm with read --wait-stable.
Find your own pane (to avoid operating on yourself):
self_pane="$(prowl list --json | jq -r '.data.items[] | select(.pane.focused==true) | .pane.id')"
prowl agents #
Snapshot of detected agent panes, matching the Active Agents roster. No selectors.
prowl agents --json
Each agent contains:
id: the pane/surface UUID, suitable for--pane.type,name: normalized detector type and displayed command name. Aliases such asompare preserved inname.status,raw_state: detected agent state.statusis one ofblocked,working,done,idle;raw_stateis the lower-level detector state.last_changed_at: ISO-8601 timestamp for the most recent state change.project: display-orientedname,branch,pathresolved from the agent's working directory.worktree,tab,pane: the actual terminal owner and pane metadata for automation.
prowl agents is read-only. To jump to or operate on an agent, resolve
.data.agents[].pane.id, then use existing commands:
pane="$(prowl agents --json | jq -r '.data.agents[] | select(.status=="blocked") | .pane.id' | head -n1)"
prowl focus --pane "$pane"
prowl read --pane "$pane" --last 120 --wait-stable
Text output is sorted for triage: Blocked, Working, Done, then Idle.
Empty output prints No agents found..
prowl read [target] #
Read a pane's content.
--last <n>— last N lines (scrollback + screen); omit for a full snapshot.--wait-stable— re-read until the screen stops changing (best for live TUIs).--stable-interval <50–5000ms>(default 200),--stable-period <100–60000ms>(default 800),--wait-timeout <1–300s>(default 10) — tune the stable wait.
prowl read --pane "$pane" --last 200 --wait-stable --json
Response includes mode (snapshot|last), source (screen|scrollback|mixed),
truncated, line_count, text, and (when waiting) stabilized, waited_ms,
samples. truncated: false with fewer lines than --last just means the pane
has less history — don't retry. truncated: true flags a possibly-incomplete
read.
prowl send [target] [text] #
Type into a pane, optionally wait for completion and capture output.
- Text source: argv, or stdin if no argv (don't provide both →
EMPTY_INPUT). --capture— wait and capture the command's output (screen diff). Requires OSC 133 shell integration on the target; sends a trailing Enter; cannot combine with--no-waitor--no-enter.--no-wait— fire and forget.--no-enter— pre-fill text without submitting (submit later withkey enter).--timeout <1–300s>— wait budget (default 30).
prowl send --pane "$pane" 'npm test' --capture --timeout 60 --json # run & capture
prowl send --pane "$pane" 'long-task' --no-wait --json # don't wait
printf '%s\n' 'echo a' 'echo b' | prowl send --pane "$pane" --capture # stdin
Response: input (source/characters/bytes/trailing_enter_sent), wait
(exit_code, duration_ms) when waiting, and capture (text, line_count,
truncated) when capturing. If the pane lacks shell integration you get
CAPTURE_UNSUPPORTED — drop --capture and use read --wait-stable, or redirect
the command's output to a file and cat it.
prowl key [target] [token] #
Send a keystroke.
--repeat <1–100>— repeat the key.- Tokens: named keys (
enter/return,esc,tab,backspace—deleteis an alias for backspace; usedelete-forwardfor a forward delete —space, arrowsup/down/left/right,pageup/pagedown,home/end,f1–f12, punctuation), single characters (a–z,0–9, etc.), and modifier combos joined with-:cmd/command,shift,opt/option/alt,ctrl/control— e.g.ctrl-c,cmd-k,shift-tab,cmd-shift-p.
prowl key --pane "$pane" enter --json
prowl key --pane "$pane" down --repeat 10 --json
prowl focus [target] #
Focus a worktree/tab/pane and bring Prowl to the front.
prowl focus --pane "$pane" --json
prowl focus --worktree MyApp --json
prowl tab create #
Create a new terminal tab (deterministic — unlike open).
--path <dir>— working directory (must be inside the worktree root).- Selectors choose the worktree (defaults to current).
pane="$(prowl tab create --worktree "$wt" --json | jq -r '.data.target.pane.id')"
prowl tab close / prowl pane close #
Close a tab or a pane. Require an explicit selector (--tab/--pane/
--worktree/--target) — they intentionally do not default to the focused
pane. If the target has protected agent work or a long-running command, Prowl may
ask for GUI confirmation; --force skips it (use only after positively
identifying the target).
prowl pane close --pane "$pane" --json
prowl tab close --tab "$tab" --force --json
prowl open [path] (the default command) #
Navigate Prowl to a path (or bring it to front with no argument). It may focus an
existing pane or create a tab — it is not a deterministic "new pane" command.
For a guaranteed fresh shell, use tab create.
prowl open ~/projects/app # open/focus that project
prowl open # just bring Prowl forward
Supports ~ and file://. Reports resolution (no-argument / exact-root /
inside-root / new-root), app_launched, brought_to_front, created_tab, and a
target.
Transport & app launch #
- Socket:
~/Library/Application Support/com.onevcat.prowl/cli.sock(override withPROWL_CLI_SOCKET). If that primary path would exceed the AF_UNIX 104-byte limit (e.g. a very long home-directory path), it falls back to$TMPDIR/prowl-cli.sock. - If the app isn't running, the CLI launches it (
open -a Prowl) and waits up to ~15s for the socket — except whenPROWL_CLI_SOCKETis set. - Framed protocol: 4-byte length prefix + JSON, both directions.
Error codes #
| Code | Meaning / recovery |
|---|---|
APP_NOT_RUNNING |
Can't reach Prowl. Ask before restarting it. |
TARGET_NOT_FOUND |
Selector matched nothing — re-run list and pick a UUID. |
TARGET_NOT_UNIQUE |
Selector matched several — be more specific (use --pane). |
NO_ACTIVE_PANE |
No pane for focused-target; pass an explicit --pane. |
EMPTY_INPUT |
send got neither argv nor stdin (or both). |
INVALID_ARGUMENT |
Bad flag/combo (e.g. --capture --no-wait) or out-of-range value. |
CAPTURE_UNSUPPORTED |
Target lacks OSC 133 — drop --capture, use read --wait-stable. |
WAIT_TIMEOUT |
Command didn't finish in time — raise --timeout or use --no-wait. |
UNSUPPORTED_KEY / INVALID_REPEAT |
Check prowl key --help. |
PATH_NOT_FOUND / PATH_NOT_DIRECTORY / PATH_NOT_ALLOWED |
Fix the open/tab create path. |
LAUNCH_FAILED |
App launch or socket wait failed. |
*_FAILED (LIST_FAILED, AGENTS_FAILED, FOCUS_FAILED, SEND_FAILED, READ_FAILED, TAB_FAILED, PANE_FAILED, OPEN_FAILED) |
The action itself failed. |
Safety & self-targeting #
- If your shell runs inside a Prowl pane, the focused pane is probably you.
Identify and avoid it (the
self_panesnippet above) so you don'tkey enterinto your own session. - Close commands require explicit targets and may prompt for GUI confirmation on
protected work;
--forcebypasses the prompt.
A complete loop (run, read, clean up) #
self_pane="$(prowl list --json | jq -r '.data.items[]|select(.pane.focused==true)|.pane.id')"
pane="$(prowl tab create --worktree MyApp --json | jq -r '.data.target.pane.id')"
test "$pane" != "$self_pane"
prowl send --pane "$pane" 'swift build' --capture --timeout 300 --json
prowl read --pane "$pane" --last 100 --wait-stable --json
prowl pane close --pane "$pane" --json
Gotchas for agents (quick list) #
- Resolve a
pane.idbeforeread/send/key/focus/close — never trust tab titles. - Use
prowl agents --jsonwhen you need agent status; useprowl list --jsonwhen you need all panes, including ordinary shells. --captureneeds shell integration; otherwiseread --wait-stableor file redirection.openis navigation, not a guaranteed new pane — usetab create.- In zsh, don't name a variable
status(it's readonly). - Pass shell values into
jqwith--arg.