Chapter 16 · Print/JSON Mode and CI Automation
Chapter goals: Master
-pprint mode and--mode jsonevent streams; transform pi from an "interactive tool" into an unattended automation component runnable in Shell pipes and CI pipelines.
16.1 Print Mode: One-Shot Execution
Beyond interactive mode, pi's most常用形态 is print mode: execute a task, output the result, exit immediately. This is the foundation of all automation:
# Basic usage: output response then exit (-p is shorthand for --print)
pi -p "Summarize this repo's purpose in one sentence"
# Named one-shot session:便于事后在 ~/.pi/agent/sessions 里追溯
pi --name "release audit" -p "Audit current repo dependency versions"Print mode has another key feature: it reads pipe stdin and merges it into the initial prompt. This makes pi天然兼容 the Unix pipe philosophy:
cat README.md | pi -p "Summarize this text"
git diff | pi -p "Review these changes for security hazards"Essential Difference from Interactive Mode
Non-interactive modes (-p, --mode json, --mode rpc) do not弹出 the project trust confirmation. Without a saved trust决定, behavior falls to the global defaultProjectTrust: ask and never ignore project resources; only always trusts. In CI either pre-/trust 保存 the决定, or explicitly pass -a.
16.2 File Arguments and Combined Input
The @file syntax attaches files直接进 messages;配合 print mode for batch processing is非常顺手:
pi @code.ts @test.ts "Review these files"
pi -p @screenshot.png "What's in this image?"stdin, @file, and CLI message三类 can be自由组合; pi merges them into a single初始提示.
16.3 JSON Event Stream Mode
When results need programmatic consumption,纯 text output is not structured enough. --mode json打全部会话事件 as JSON Lines to stdout — one JSON object per line:
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'Event stream structure (based on the official docs/json.md):
// First line is always the session header
{"type":"session","version":3,"id":"uuid","timestamp":"...","cwd":"/path"}
// Then lifecycle events
{"type":"agent_start"}
{"type":"turn_start"}
{"type":"message_update","usage":{},"assistantMessageEvent":{"type":"text_delta","contentIndex":0,"delta":"Hello"}}
{"type":"message_end","message":{}}
{"type":"turn_end","message":{},"toolResults":[]}
{"type":"tool_execution_start","toolCallId":"...","toolName":"bash","args":{}}
{"type":"agent_end","messages":[]}Three details必须理解:
message_updatecontains only increments (delta), no cumulative snapshot;流体积与文本量线性相关; to get the final authoritative content取message_end;- Tool execution有独立事件 (
tool_execution_start/update/end);endhas anisErrorfield可用于统计失败的工具调用; - Failure判定看
stopReason: assistant messagestopReason取值包括"stop","length","toolUse","error","aborted"— in CI treaterror/abortedas任务失败, not just看进程是否退出.
16.4 Consuming pi in Pipes
Combined with jq you can write各种 "one-liners". E.g. extract the final回答 text:
# Extract all text blocks from the assistant message
pi --mode json -p "List all TODO comments under src" 2>/dev/null \
| jq -r 'select(.type == "message_end" and .message.role == "assistant")
| .message.content[]? | select(.type == "text") | .text' \
| tail -n 40Another example:统计本次任务的 token 成本 (usage挂 in each assistant message):
pi --mode json -p "Explain this error" 2>/dev/null \
| jq -s '[ .[] | select(.type=="message_end" and .message.role=="assistant")
| .message.usage.totalTokens ] | add // 0'Stream Separation
JSON events走 stdout; logs and errors走 stderr. So the above examples all use 2>/dev/null to丢弃 stderr. Conversely, when debugging看 stderr not stdout.
16.5 Running pi in CI
When putting pi into GitHub Actions / Jenkins这类流水线, design per these four principles:
① Secret management — inject API keys via CI secrets into env vars (e.g. ANTHROPIC_API_KEY); never write into the repo or CLI args (--api-key留 in the process list):
# .github/workflows/ai-review.yml snippet
- name: AI code review
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/main...HEAD > /tmp/diff.txt
cat /tmp/diff.txt | pi --no-session -p "Review these diffs; output only the issue list" > review.md
cat review.md >> $GITHUB_STEP_SUMMARY② Idempotency — the same input应产出 comparable results: add --no-session to avoidpolluting the session dir; avoid in-prompt phrases like "continue last"依赖历史.
③ Timeout and budget — LLM calls may远慢于普通构建步骤;务必给 steps设置超时 (e.g. timeout-minutes: 10);控制成本用 cheap models (--model <cheap-model>).
④ Failure handling — the official CLI reference makes no承诺 specific exit code semantics; the稳妥做法 is to判业务成败 via JSON mode's stopReason/isError, not rely on shell层返回值:
pi --mode json --no-session -p "$PROMPT" > /tmp/out.jsonl
if jq -e 'select(.type == "message_end") | .message.stopReason == "stop"' /tmp/out.jsonl > /dev/null; then
echo "Task completed"
else
echo "Task异常终止"; exit 1
fi16.6 Chapter Summary
-pprint mode runs once and exits; also合并 pipe stdin into the initial prompt;@fileargs可 with stdin and messages组合输入;--mode jsonoutputs a JSONL event stream:首行 session header,message_endis the authoritative message,stopReasonoferror/abortedindicates failure;- JSON走 stdout, logs走 stderr; extract needed events with
jq select(...); - CI four要素: secret injection, idempotent design, timeout+budget, event-based failure判定.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What happens to content piped in print mode?
2. In --mode json output, which event contains the final authoritative assistant message?
3. In CI, how to可靠地判定 pi task failure?
4. When no saved project trust决定 exists in non-interactive mode, how are project resources默认处理?
🛠️ Hands-on Practice
- Write a pipe command: feed任意 long text to
pi -pto generate a summary under 100 characters; usejqto output only the total token count. - Add an
ai-commitfunction to your shell:git diff --staged | pi -p "Generate a Conventional Commits-formatted commit message based on the diff". 3.搭建 a minimal GitHub Actions workflow: on PR use pi to review the diff and write conclusions to$GITHUB_STEP_SUMMARY; requiretimeout and secret-injected keys.