agentproc CLI
The agentproc command-line tool is the canonical bridge-side runner. It reads a profile YAML, spawns the configured agent process, parses stdout per the protocol spec, and prints the reply. Any conformant agent — from hub profiles to your own — can be driven through this single entry point.
The CLI ships in the same npm package as the Node SDK:
npm install -g agentproc # global install
# or:
npx agentproc ... # run without installingA Go implementation ships in the sdk/go module — same commands, same flags, same output semantics:
go install github.com/jeffkit/agentproc/sdk/go/cmd/agentproc@latest
# or grab a prebuilt binary (linux/darwin amd64+arm64, windows amd64)
# from the GitHub Releases page, tagged agentproc-cli/v*Go CLI divergence: the hub commands resolve profiles via the jsDelivr CDN and cache them at ~/.agentproc/cache/hub/<name>/ (24h TTL) — there is no bundled profile copy inside the binary, so the first hub command needs network. AGENTPROC_HUB_REF pins fetches to an exact upstream ref, same as the Node/Python CLIs.
Two ways to invoke
The CLI supports two equivalent entry points:
| Entry point | When to use |
|---|---|
agentproc hub <subcommand> | Recommended. Run a profile from the official hub with zero local files. The CLI fetches from GitHub on first use, caches at ~/.agentproc/cache/hub/<name>/ (24h TTL), and defaults the agent's cwd to your current directory. |
agentproc --profile <path> | Run a profile YAML you already have locally (your own, an installed hub profile, or a repo checkout). Pass --cwd to control where the agent runs. |
Quick start
# Smoke test, no API key, no clone:
agentproc hub run echo-agent -p "hello"
# → You said: hello
# Real agent against your project:
cd ~/projects/my-app
agentproc hub run claude-code -p "explain this codebase" --env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEYFor local profiles (no hub fetch):
git clone https://github.com/jeffkit/agentproc
cd agentproc
# echo-agent (uses {{PROFILE_DIR}} so cwd doesn't matter)
agentproc --profile hub/echo-agent/profile.yaml --prompt "hello"
# claude-code — point --cwd at your project so claude works on it
agentproc --profile hub/claude-code/profile.yaml \
--prompt "explain this codebase" \
--cwd /path/to/your/project \
--env ANTHROPIC_API_KEY=$ANTHROPIC_API_KEYGITHUB_TOKEN raises the rate limit
Anonymous hub fetches are capped at ~60/hour. Raise to 5,000/hour with:
export GITHUB_TOKEN=$(gh auth token) # or any personal access tokenIf you'd rather skip the network entirely, use agentproc --profile ./hub/<name>/profile.yaml ... against a local checkout.
Hub subcommands
| Command | Purpose |
|---|---|
agentproc hub list | List all profiles in the hub |
agentproc hub show <name> | Show a profile's README |
agentproc hub run <name> [opts] | Fetch (if needed) and run a profile |
agentproc hub install <name> | Copy a profile to ./<name>/ for local editing |
hub run accepts the same runner flags as --profile (see below), with one convenience: if you don't pass --cwd, it defaults to your current directory (so the wrapped CLI operates on whatever project you're in).
Add --refresh to any hub command to force re-fetch from GitHub.
Set the AGENTPROC_HUB_REF environment variable to pin fetches to a specific tag, branch, or commit sha (default main). The profile cache is keyed by ref — switching the variable re-fetches rather than serving a cached copy from another ref.
Usage
agentproc --profile <path.yaml> --prompt "hello" [options]Required (for --profile mode)
| Flag | Description |
|---|---|
--profile, -p <path> | Profile YAML path |
--prompt <text> | User message (or use --stdin) |
About -p
In --profile mode, -p is the short form of --profile. In hub run mode, since the profile is identified by name (positional, not a path), -p is reused as the short form of --prompt instead. This is the only flag whose short form changes between modes — when in doubt, use the long form.
Session
| Flag | Description |
|---|---|
--session <id> | Previous session id for multi-turn continuity |
--session-name <name> | Human-readable session name (default: default) |
Attachments
| Flag | Description |
|---|---|
--image-url <url> | Image attachment URL (carried in the turn's attachments) |
--file-url <url> | File attachment URL (carried in the turn's attachments) |
Execution
| Flag | Description |
|---|---|
--cwd <path> | Override profile.cwd. Relative paths resolve against the profile's directory. In hub run, defaults to your current directory. |
--env KEY=VALUE | Extra env var (repeatable) |
--timeout <secs> | Override profile.timeout_secs (0 = no limit) |
--journal <path> | Append an NDJSON event/decision journal to <path> (opt-in; off by default, never written to stdout) |
--no-stream | Disable streaming (ignore {"type":"partial"} events) |
Output
| Flag | Description |
|---|---|
--verbose | Forward protocol lines to stderr (default) |
--quiet | Suppress protocol lines on stderr |
--raw | Quiet mode: only print the final reply body (no live events on stderr) |
--stdin | Read prompt from stdin instead of --prompt |
Other
| Flag | Description |
|---|---|
--version | Print version and exit |
--help, -h | Show help |
Output semantics
Default mode
| Stream | Content |
|---|---|
| stderr | NDJSON events as they arrive ({"type":"partial"}, {"type":"error"}; session ids via the agentproc:session:<id> trailer) |
| stdout | Final reply body, printed after the agent exits |
| exit | 0 success · 1 error (including timeouts) · 2 usage · 130 SIGINT / 143 SIGTERM (the agent group is killed first) |
(The underlying RunResult.ExitCode distinguishes further — 124 timeout, 125 cancelled — but the CLIs fold every failure to 1, matching the reference Python/Node CLIs.)
The final session id is also printed on stderr as agentproc:session:<id>, so shell scripts can capture it:
output=$(agentproc -p prof.yaml --prompt "hi" 2>/tmp/err.log)
session=$(grep '^agentproc:session:' /tmp/err.log | cut -d: -f3)
agentproc -p prof.yaml --prompt "follow up" --session "$session"--raw mode
Quiet mode: suppress the live event echo on stderr and print only the final reply body to stdout. (All CLIs still run the full protocol parser — the reply is the assembled result, not raw agent bytes.)
agentproc -p prof.yaml --prompt "hi" --rawExamples
Multi-turn with claude-code
# First turn — capture the session id
agentproc -p hub/claude-code/profile.yaml \
--prompt "what is this codebase?" \
--cwd ~/projects/myapp \
--env ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
2>/tmp/err.log
session=$(grep '^agentproc:session:' /tmp/err.log | cut -d: -f3)
# Second turn — continue the session
agentproc -p hub/claude-code/profile.yaml \
--prompt "tell me more about the auth module" \
--cwd ~/projects/myapp \
--env ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--session "$session"Prompt from stdin
echo "what files are in this directory?" | \
agentproc -p hub/claude-code/profile.yaml --cwd . --stdinQuiet mode (clean stdout for piping)
agentproc -p hub/claude-code/profile.yaml --prompt "summarize" --quiet | jq .How it implements the spec
The CLI is a thin wrapper over the SDK's run() function in sdk/node/src/runner.js. That module is the canonical reference implementation of the AgentProc bridge-side contract:
- Profile parsing: accepts both top-level form (
command:at root) and hub form (command:nested underagentproc:). - Placeholder substitution:
,,incommand,args,cwd, andenvvalues — no shell involved.is not a placeholder; the message travels via stdin. - Relative
cwd: whencwdis a relative path andis known (i.e. the CLI was invoked with a profile path), it resolves against the profile's directory rather than the process cwd. - Turn input (stdin): writes one
{"type":"turn",...}NDJSON line to the agent's stdin (message,session_id,session_name,attachments,permission,protocol_version), then EOF — unlesspermission: true, in which case stdin stays open for{"type":"permission_response"}frames. The per-turn request does not travel in env vars. - Env injection: the profile
envblock (with${VAR}expansion gated byenv_allowlist) plus a fixed infra set (PATH/HOME/TERM/…).--env KEY=VALUEadds per-run extras. - stdout classification: each line is a JSON object dispatched on
type—{"type":"partial"}(forwarded whenstreaming: true),{"type":"result"}(terminal reply body; optionalsession_id/usage),{"type":"error"}(fails the turn).session_idis a field on events (first non-empty persisted; early omit OK; SHOULD attach once known). Non-JSON / unknowntypelines are logged and ignored. - Timeout handling: SIGTERM →
kill_grace_secs(default 5s) → SIGKILL, all three targeted at the agent's whole process group (so a shell tool call or sub-agent the CLI spawned dies with it), not just the direct child. Exit code 124. The limit itself is the earliest expiry of the profile'stimeout_secs,budget_secs, anddeadline. If the agent exits while a helper still holds its stdout/stderr open, the run still ends: the pipes get a 1s drain window and are then abandoned (with a stderr warning) instead of hanging the turn. - Exit codes: 0 success · 1 error (including when
{"type":"error"}was emitted, and including timeouts — the runner's 124 surfaces inRunResult.ExitCode, the CLI folds it to 1).
If you're writing your own bridge in another language, runner.js is the spec in code form.