Skip to content

Chapter 16 · Print/JSON Mode and CI Automation ​

Chapter goals: Master -p print mode and --mode json event 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:

bash
# 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:

bash
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非常顺手:

bash
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:

bash
pi --mode json "List files" 2>/dev/null | jq -c 'select(.type == "message_end")'

Event stream structure (based on the official docs/json.md):

jsonc
// 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必须理解:

  1. message_update contains only increments (delta), no cumulative snapshot;流体积与文本量线性相关; to get the final authoritative content取 message_end;
  2. Tool execution有独立事件 (tool_execution_start/update/end); end has an isError field可用于统计失败的工具调用;
  3. Failure判定看 stopReason: assistant message stopReason取值包括 "stop", "length", "toolUse", "error", "aborted" — in CI treat error/aborted as任务失败, not just看进程是否退出.

16.4 Consuming pi in Pipes ​

Combined with jq you can write各种 "one-liners". E.g. extract the final回答 text:

bash
# 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 40

Another example:统计本次任务的 token 成本 (usage挂 in each assistant message):

bash
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):

yaml
# .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层返回值:

bash
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
fi

16.6 Chapter Summary ​

  • -p print mode runs once and exits; also合并 pipe stdin into the initial prompt;
  • @file args可 with stdin and messages组合输入;
  • --mode json outputs a JSONL event stream:首行 session header, message_end is the authoritative message, stopReason of error/aborted indicates 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 ​

  1. Write a pipe command: feed任意 long text to pi -p to generate a summary under 100 characters; use jq to output only the total token count.
  2. Add an ai-commit function 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.