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 agents read, prowl read, prowl send, prowl key, prowl focus, prowl create, prowl close, 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:pNas a pane,tNas a tab, then pane UUID, tab UUID, or 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. They work in every
generic target position (read p7, focus t6, send p7 '…'); bare numbers remain
worktree references there. A stale prefixed handle fails rather than falling back to
a same-named worktree. JSON keeps canonical UUIDs in id; do not cache handles
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 p7 --last 120 --wait-stable
prowl close 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. Pi usespi; Oh My Pi usesomp, withoh-my-pipreserved as a display alias.status,raw_state: detected agent state.statusis one ofblocked,working,done,idle;raw_stateis the lower-level detector state.detection_reason: optional stable screen-classifier explanation. A profile rule emits its rule ID, an ordinary profile miss emitsfallback.noRuleMatched, and an unmigrated classifier emitslegacy.detector. The field is omitted when no current screen result is available and never includes screen text.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 without additional confirmation.
prowl agents is read-only. Text output is sorted for triage: Blocked,
Working, Done, then Idle. It prints a pane handle such as p7; JSON keeps
the canonical pane UUID. Either form now feeds the semantic snapshot command:
prowl agents read p7
prowl agents read p7 --json
pane="$(prowl agents --json | jq -r '.data.agents[] | select(.status=="blocked") | .pane.id' | head -n1)"
prowl agents read "$pane" --json
prowl agents read <pN|pane-uuid> #
Immediate, read-only semantic snapshot for a currently active Codex or
Claude Code pane. It requires an explicit pN handle or UUID from agents;
it never guesses from focus, accepts no worktree/tab selector, and has no wait or
timeout mode.
Default text output always reports current Status, classifier Reason, last
state-change time, and a result state. A blocked snapshot includes the raw current
interaction under ## Blocker, preserving the question, numbered choices, selected
row, and Enter/Esc hints. It is the right command for deciding what another agent is
waiting on; use prowl key --pane "$pane" ... to navigate/confirm a menu or
prowl send --pane "$pane" ... for free-form input. Those writes are not atomic with
the read, so re-read before a consequential choice.
prowl agents read p7
prowl agents read "$pane" --max-bytes 2097152 --json
prowl agents read p7 --result-only > /tmp/agent-result.txt
JSON is prowl.cli.agents.read.v1. .data.result.state is independent from live
agent state: complete includes trusted text; pending means a working/blocked
agent has not completed a turn; unavailable, missing, incomplete, and
too_large retain a successful live snapshot but include a reason under
.data.result.error. Prowl reads a transcript only after a fresh exact or high
session resolution — never a medium candidate — and never returns partial text.
--max-bytes defaults to 1 MiB and accepts up to 4 MiB. --result-only is mutually
exclusive with --json; it writes exactly a complete trusted result to stdout, with
no heading or added newline. For every other result state it exits non-zero with
SESSION_UNRESOLVED, RESULT_NOT_FOUND, RESULT_INCOMPLETE, or
RESULT_TOO_LARGE. Empty agents roster output remains No agents found..
prowl read [target] #
Read a pane's content.
--last <n>— last N lines (scrollback + screen); omit for a full snapshot.--source <viewport|detection>—viewportpreserves the normal read behavior (default);detectionreads the exact active-screen buffer used by agent-state detection, which can differ from the viewport when a pane is scrolled. If the running app is too old to honordetection, the CLI fails withREAD_FAILEDinstead of returning viewport text; update or restart Prowl and retry.--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|detection), 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.
For detector regression captures, omit --last, require the returned source, and
extract the JSON string without adding a newline:
# Run from the Prowl source checkout so the private staging path is ignored.
repo_root="$(git rev-parse --show-toplevel)"
test -f "$repo_root/supacode.xcodeproj/project.pbxproj"
staging="$repo_root/.local/agent-screen-captures"
mkdir -p "$staging"
capture="$(prowl read --pane "$pane" --source detection --json)"
printf '%s\n' "$capture" | jq -e '.data.source == "detection"' >/dev/null
printf '%s\n' "$capture" | jq -j '.data.text' > "$staging/raw-capture.txt"
This is a diagnostic/capture source, not a more complete terminal-history read; normal pane inspection should keep the default viewport source.
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 create tab #
Create a new terminal tab (deterministic — unlike open). A worktree is required,
either positionally or with --worktree; --path must remain inside it.
pane="$(prowl create tab "$wt" --json | jq -r '.data.target.pane.id')"
prowl close #
Close one explicit tab or pane. The positional form uses a UUID, pN, or tN; the
long forms are --pane <uuid|pN|N> and --tab <uuid|tN|N>. close rejects
worktree targeting and has no focus fallback. Protected agent work or a long-running
command may trigger GUI confirmation; --force skips it only after positive
identification.
prowl close "$pane" --json
prowl close --tab "$tab" --force --json
prowl tab create,prowl tab close, andprowl pane closeare deprecated compatibility aliases. They warn on stderr and will be removed after one release.
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 create tab.
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 #
Hand a task off between agents: archive the outgoing state under the target's
.prowl/handoff/, install a fresh agent-authored briefing, and launch the
receiver in a background tab. Centred on workspaces, but works
for any runnable target. Two subcommands:
prowl handoff to <agent> [target] [--brief -|--no-brief] [--note "…"] [--no-launch]
prowl handoff save [target] [--brief -|--no-brief] [--note "…"]
Source resolution. An explicit selector (--pane p3, --tab t2,
--worktree <name>, or the positional target) wins; otherwise the source is
the calling pane — Prowl maps the prowl process's ancestry to the pane
whose shell spawned it, so an agent running the command hands off itself
regardless of UI focus. Outside any Prowl pane with no selector the command
errors with SOURCE_REQUIRED; the focused pane is never guessed.
Briefing. --brief - reads an inline agent-authored briefing from stdin
(heredoc). Every handoff must provide it or use --no-brief as the explicit
context-only escape; otherwise the command errors (BRIEF_REQUIRED) with a
copy-pasteable example and zero side effects. A briefing must
contain at least ## Objective, ## Current State, and ## Next Steps; an
invalid inline brief errors (INVALID_BRIEF) with zero side effects. Prowl
never resumes the source session or starts a hidden model turn, including when
the caller targets another pane.
to <agent>— archives the current artifact to.prowl/handoff/archive/<ts>-<from>-to-<to>.mdfirst, installs the fresh briefing ascurrent.md(or removes a stale one when the transition is context-only), regeneratescontext.mdfrom live git state, and launches the receiver in a background tab — no worktree switch, no focus steal; a notification announces the completed handoff unless you are already watching that worktree. The kickoff prompt adapts to whether a briefing exists. An observed unrestricted source execution policy carries over between the verified Claude Code and Codex adapters for the destination launch only; model identifiers remain with their original agent family. Interactive launch is verified forclaudeandcodex;--no-launchstill archives + saves and accepts the full detected-agent list:pi,omp,claude,codex,gemini,cursor-agent,cline,opencode,copilot,kimi,droid,amp,qodercli,qwen,grok.save— a deferred-handoff checkpoint: installs a fresh briefing (archiving the replaced one) and regeneratescontext.md, with no destination and no launch. A context-onlysave --no-briefrefreshes generated state without touching the last valid briefing.
prowl handoff to codex --brief - <<'EOF' # self-handoff with inline briefing
# Handoff
## Objective
…
## Current State
…
## Next Steps
…
EOF
prowl handoff save --brief - --note "eod checkpoint" <<'EOF' … EOF
prowl handoff to claude --pane p7 --no-brief --json # third pane, generated context only
The outgoing agent is whatever Prowl detects in the source pane (see
pane.agent in list). Response payload
(prowl.cli.handoff.v2) includes action, artifact_path, outgoing_agent,
to_agent, repos, changed_file_count, archived_path, session_context,
briefing (inline / none), has_briefing, and
launched_pane. session_context includes the generated excerpt path plus
native session_id / transcript_path only when the source pane has
unambiguous native-session evidence (the same identity exposed by
prowl agents); ambiguous sessions are never forked. current.md exists iff
a validated briefing produced it — there is no template and nothing to
maintain between handoffs. 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.
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). |
AGENT_NOT_FOUND / AGENT_UNSUPPORTED |
agents read target no longer hosts an agent, or it is not Codex/Claude Code. Re-run agents. |
BLOCKER_UNREADABLE |
A blocked screen was detected but Prowl could not safely extract its current interaction text. Re-run agents read or inspect with read. |
SESSION_UNRESOLVED / RESULT_NOT_FOUND / RESULT_INCOMPLETE / RESULT_TOO_LARGE |
agents read --result-only could not provide one trustworthy complete result. Drop --result-only to retain the live snapshot and inspect .data.result. |
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/create tab 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, CREATE_FAILED, CLOSE_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 create tab 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 close "$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 --jsonfor discovery, thenprowl agents read <pN|uuid>for a supported agent's status, blocker, and trustworthy result state; 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 — usecreate tab.- In zsh, don't name a variable
status(it's readonly). - Pass shell values into
jqwith--arg.