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, prowl handoff, pane id, agent, 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|pN|N>— a specific pane.pNis the short handle shown in text output; bareNis accepted too.--tab <uuid|tN|N>— a specific tab (its focused/first pane).tNis the short handle shown in text output; bareNis accepted too.--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.
Text list and agents output exposes short, type-prefixed handles such as
p7 and t6. They are valid only for the current app process, are globally
monotonic, and are never reused after a tab or pane closes. Use them with an
explicit --pane or --tab; --target and positional targets deliberately do
not resolve handles because a bare number can be a worktree name. JSON always
keeps the canonical UUID in id; do not cache either form across an app restart.
Never target by tab title. Titles are free-form and can lie. For scripts, resolve a concrete UUID
pane.idfromprowl list --json; for an interactive same-session handoff, copy thepNhandle from textprowl list.
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,focused,agenttask:status(running|idle| null)
pane.agent is the coding agent detected in that pane — a stable machine token
(claude, codex, gemini, cursor-agent, …) or null when none is detected.
It comes from the same agent detection described in
agent-detection and is useful for coordinating who is who
(e.g. before a handoff).
These are JSON fields, so tab.id and pane.id remain UUIDs. Plain prowl list
instead shows tN for each tab and pN for each pane; pass either handle back
with the corresponding explicit selector:
prowl list
prowl read --pane p7 --last 120 --wait-stable
prowl tab close --tab t6 --force
task.status is running when any pane in the worktree is busy — a terminal
command reporting progress, or a detected agent that is Working/Blocked (including
Claude running a background workflow); otherwise idle. See the
worktree running indicator. It's
good for coordination but lags a screen by ~2–3 s and 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.session: optional native agent session metadata. When resolved, it containsid, local transcriptpath(may be null when the id comes from a non-file artifact),confidence(exact,high, ormedium), and the evidencesource(open_file,process_log,transcript_match,recent_file, orstore_record). Ambiguous sessions are omitted instead of guessed. Amediumsession id must not be used for automatic resume/fork without additional confirmation.
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.
It prints a pane handle such as p7 for each agent; use it as
prowl read --pane p7, not as an agents subcommand. 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.
prowl handoff #
Manage the cross-agent handoff artifact under a target's .prowl/handoff/ and
launch the receiving agent. Centred on workspaces, but works for
any runnable target. Three subcommands:
prowl handoff save [target] [--note "…"] [--no-prepare] # refresh context + session excerpt
prowl handoff to <agent> [target] [--note "…"] [--no-launch] [--no-prepare]
prowl handoff status [target]
save— when Prowl has an exact or high-confidence native session for the detected outgoing Claude Code or Codex process, it first resumes that session non-interactively (read-only, bounded to 2 minutes) and asks it to reply with a fresh agent-authoredcurrent.mdsnapshot, written entirely from that session's own knowledge; Prowl validates the reply and transcribes it into the file (the previous version is backed up toarchive/first). It then refreshes.prowl/handoff/context.mdfrom live git state (per-repo branch + change counts, changed-file list, detected outgoing agent, and captured session excerpt). The singlesavelog line records whether source preparation completed, failed, or was skipped.--no-prepareskips the source turn for a fast mechanical refresh.current.mdis seeded from a template on the first run; Prowl only rewrites it to transcribe a validated preparation reply.to <agent>— performs the same safe source preparation, saves, archives the current artifact to.prowl/handoff/archive/<ts>-<from>-to-<to>.md, and launches the receiving agent in a new tab with a semantic kickoff prompt forcurrent.mdand generatedcontext.md. Returns the launchedpane. An observed unrestricted source execution policy is translated between the verified Claude Code and Codex adapters for the destination launch only; model identifiers remain with their original agent family.--no-launchstill prepares, archives, and saves;--no-prepareskips the source turn. Interactive launch is verified forclaudeandcodex;--no-launchaccepts the full detected-agent list:pi,claude,codex,gemini,cursor-agent,cline,opencode,copilot,kimi,droid,amp,qodercli,qwen,grok.status— report the artifact path, whether it exists, the agent currently detected in the target, and the last handoff-log line.
prowl handoff to claude --json # codex → claude, launch claude in a new tab
prowl handoff save --note "ui done, api next" --json
The outgoing agent is whatever Prowl detects in the target's pane (see
pane.agent in list). Response payload includes action,
artifact_path, outgoing_agent, to_agent, repos, changed_file_count,
archived_path, session_context, preparation (completed / failed /
skipped, for save and to), and launched_pane. session_context includes
the generated excerpt path plus native session_id / transcript_path only when
the selected pane already has unambiguous native-session evidence (the same
identity exposed by prowl agents). When no session is resolved, Prowl keeps the
terminal excerpt and omits native metadata. Unsafe or ambiguous native sessions
are never resumed: source preparation is skipped and any existing current.md
(or its template) remains the durable artifact. Full feature guide:
handoff.
The generated .prowl/handoff/ directory contains its own .gitignore, so its
artifacts and terminal excerpts do not appear in git status.
After a target has an existing .prowl/handoff/current.md, the app also
auto-runs the same save path when Prowl sees the detected agent move from
working to done or blocked. This auto-save is throttled per pane and
does not initialize handoff files by itself; use prowl handoff save or
prowl handoff to once to opt the target in.
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. - Sandboxed agents must be allowed to connect to the Unix socket. If the CLI
reports
SOCKET_PERMISSION_DENIED, allowlist the socket path in the agent sandbox, runprowloutside that sandbox, or start both the app and CLI with the samePROWL_CLI_SOCKETpointing at a sandbox-accessible path. - Framed protocol: 4-byte length prefix + JSON, both directions.
Error codes #
| Code | Meaning / recovery |
|---|---|
APP_NOT_RUNNING |
Prowl is not reachable, or the socket is missing/stale. Start or restart Prowl, then retry. |
SOCKET_PERMISSION_DENIED |
The socket exists but the client cannot connect, usually because a sandbox blocked the Unix socket. Allowlist the socket path, run outside the sandbox, or use matching PROWL_CLI_SOCKET values for both app and CLI. |
TARGET_NOT_FOUND |
Selector matched nothing — re-run list and pick a UUID or current short handle. |
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; the message includes the last socket diagnostic when available. |
TRANSPORT_FAILED |
Socket transport failed for a reason other than app availability or permission, such as ENOTSOCK or an invalid PROWL_CLI_SOCKET path. |
*_FAILED (LIST_FAILED, AGENTS_FAILED, FOCUS_FAILED, SEND_FAILED, READ_FAILED, TAB_FAILED, PANE_FAILED, OPEN_FAILED, HANDOFF_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 UUID
pane.idor current textpNbeforeread/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.