---
schemaVersion: 1
slug: gp-91-20260301-dhasandev-claude-cli-wrapper-guide
ticketId: GP-91
lang: zh-tw
title: claude -p 完全攻略：把 Claude CLI 變成你的 Agentic App 後端
summary: Anthropic 砍了第三方 OAuth token，只剩官方 Claude CLI 能用訂閱額度。這篇完整拆解 claude -p（print mode）的所有用法：5 種輸入、3 種輸出、JSON schema 結構化回應、tool 白名單、session 管理、雙向 streaming，到最後附上三個 production-ready 的 wrapper 範例。想用 Claude 寫 agentic app 的人必讀。
originalDate: 2026-01-09
translatedDate: 2026-03-01
source: "@dhasandev on X"
sourceUrl: https://x.com/dhasandev/status/2009529865511555506
author: null
authorshipNote: null
canonicalUrl: https://gu-log.vercel.app/posts/gp-91-20260301-dhasandev-claude-cli-wrapper-guide
status: published
replacementTicketId: null
replacementUrl: null
---

# claude -p 完全攻略：把 Claude CLI 變成你的 Agentic App 後端

> **來源:** [@dhasandev on X](https://x.com/dhasandev/status/2009529865511555506)

想像一下這個情境：你花了三個月寫了一個超讚的 side project，後端接了 Claude API，demo 跑得順得不得了。結果某天早上起床，Anthropic 一紙公告——第三方 app 不能再用 OAuth token 吃 Claude 訂閱額度了。

你的 API key？廢了。你的 OAuth flow？斷了。三個月的心血，一夕之間變成一堆不會動的 TypeScript。

這種感覺大概跟你精心準備了一桌菜，結果客人到門口才發現餐廳已經換鎖了差不多 ┐(￣ヘ￣)┌

但 Anthropic 留了一扇窗：**[Claude Code](https://gu-log.vercel.app/glossary#claude-code) 官方 CLI**。只要你用 `claude -p`（print mode），就能把 CLI 當成一個非官方但完全合法的 API endpoint。Danial Hasan 寫了一篇我目前看過最完整的教學，把 `claude -p` 的每個 flag、每種組合都拆解了一遍。

這篇就是那份教學的完整翻譯。讀完你就知道怎麼把 Claude CLI 變成你的 agentic app 後端——而且不用花一毛 API 費用。

> **Mogu 認真說：**
>
> 先釐清一下 `claude -p` 到底在幹嘛。你平常用 Claude Code，是互動式的——你打一句、它回一句，中間還會跳出來問「欸你確定要執行這個嗎？」。Print mode 把這些全部關掉，變成「我丟 prompt 進去、你把結果吐出來、中間不要囉唆」。
>
> 這就像便利商店的差別：interactive mode 是你走進去慢慢逛、店員還會跟你推薦新品；print mode 是 7-11 的 ibon，你塞指令進去、東西印出來、完事走人 (◕‿◕)
>
> 對寫 script 和 automation 的人來說，這才是正確的使用姿勢。

---

## 五種餵 [Prompt](https://gu-log.vercel.app/glossary#prompt) 的方式

好，先來搞定最基本的事：怎麼把 prompt 塞進去。

`claude -p` 支援五種輸入方式，從「我就隨便問一句」到「我要做即時雙向通訊」都有。就像點餐一樣——你可以口頭點、用 app 點、寫紙條、或者直接走進廚房自己做。

**1. CLI argument — 直接在指令後面接字串**

最直覺的用法。你在 terminal 隨手問一句：

```bash
claude -p "What is 2+2?"
```

**2. Stdin pipe — 經典 Unix 管線**

Unix 老手最愛的操作。把任何東西 pipe 進去，Claude 就會幫你處理：

```bash
echo "What is 2+2?" | claude -p
cat README.md | claude -p "Summarize this"
git diff | claude -p "Review these changes"
```

最後那行特別實用——code review 不用離開 terminal。

**3. Stdin redirect — 從檔案讀**

跟 pipe 差不多，但直接指定檔案：

```bash
claude -p < prompt.txt
```

**4. Here-doc — 多行 prompt**

當你的 prompt 不是一句話能搞定的時候，here-doc 是你的好朋友：

```bash
claude -p <<EOF
You are a code reviewer. Review this code:
def add(a, b):
    return a + b
Focus on: error handling, edge cases, documentation.
EOF
```

**5. Stream-JSON — 串流輸入**

最進階的玩法。用 JSON event 格式餵資料，通常搭配 stream output 一起用：

```bash
echo '{"type":"user","message":{"role":"user","content":"Hello"}}' | \
  claude -p --input-format stream-json --output-format stream-json --verbose
```

小技巧：pipe 跟 CLI arg 可以混搭——pipe 餵資料、arg 給指令：

```bash
cat code.py | claude -p "Find bugs in this code"
```

> **Mogu murmur：**
>
> 我跟你說，90% 的人最後都會回來用 pipe。不是因為其他方式不好，而是 pipe 根本就是 Unix 的母語——你不需要學新語法，不需要記什麼 JSON event 格式，就是把東西接起來，完事。
>
> 那些跑去用 Stream-JSON input 的人，通常是在蓋一整套即時通訊架構。如果你的需求沒有到「兩邊要同時說話」的程度，用 pipe 就是在浪費時間找自己麻煩 ╰(°▽°)╯

---

## 三種拿結果的方式

輸入搞定了，來看輸出。

如果說輸入是「怎麼點餐」，輸出就是「你要內用、外帶、還是 Uber Eats 送到家」。

**1. Text（預設）— 純文字回答**

什麼 flag 都不加就是 text output，最乾淨：

```bash
response=$(claude -p "What is 2+2?")
claude -p "Write a haiku" > haiku.txt
claude -p "List 5 colors as JSON array" | jq '.'
```

**2. JSON — 帶 metadata 的結構化回答**

加上 `--output-format json`，回傳的不只是回答本身，還有 session ID、cost、token usage 等 metadata：

```bash
claude -p --output-format json "What is 2+2?"
# 回傳: { type, subtype, is_error, duration_ms, result, session_id, total_cost_usd, usage, modelUsage }
```

寫 production 系統的話，你幾乎一定要用這個——至少你得知道每次 call 花了多少錢。

**3. Stream-JSON — 即時串流**

[Token](https://gu-log.vercel.app/glossary#token) 一個一個蹦出來，適合需要 real-time 顯示的場景。

但這裡有一個巨坑，請用螢光筆畫起來：

```bash
claude -p --output-format stream-json --verbose "Write a poem"
# 事件順序: init event → content deltas → assistant message → result
```

**`--verbose` 是必加的。不加就是不動。而且不會報錯。**

> **Mogu 認真說：**
>
> stream-json 不加 `--verbose` 就無聲無息地不 work 這件事，我覺得根本是 CLI UX 的教科書級反面教材。你以為你的 stream 在跑，結果什麼都沒吐出來，你還在那邊 debug 半天以為是自己 code 的問題。
>
> Anthropic，拜託，要嘛就自動 enable，要嘛就噴個 warning 說「嘿你忘了加 —verbose」。Silent failure 是所有 developer 的公敵 (╯°□°)╯

---

## 輸入 × 輸出組合表

不是每種 input 跟 output 都能隨便配。這張表幫你搞清楚誰跟誰能湊對：

```mermaid
graph TD
  subgraph Input["📥 Input Methods"]
      A["CLI arg"]
      B["Stdin pipe"]
      C["Stdin redirect"]
      D["Here-doc"]
      E["Stream-JSON"]
  end

  subgraph Output["📤 Output Formats"]
      X["Text"]
      Y["JSON"]
      Z["Stream-JSON"]
  end

  A --> X
  A --> Y
  A --> Z
  B --> X
  B --> Y
  B --> Z
  C --> X
  C --> Y
  C --> Z
  D --> X
  D --> Y
  D --> Z
  E --> Z
```

_輸入 × 輸出組合表_

記住兩條規則就好：

- 前四種 input（CLI arg, pipe, redirect, here-doc）**什麼 output 都能配**
- **Stream-JSON input 只能配 stream-JSON output** — 這是硬限制，不是 bug

---

## JSON Schema：叫 Claude 乖乖照格式回答

好，接下來是我個人認為 `claude -p` 最殺的功能。

寫 agentic app 最怕的是什麼？Claude 回了一坨自由奔放的散文，你的 parser 在那邊崩潰。JSON Schema 就是解藥——你定義好格式，Claude 就乖乖照著填：

```bash
echo "What is the capital of France?" | claude -p \
  --model haiku \
  --output-format json \
  --json-schema '{"type":"object","properties":{"answer":{"type":"string"},"confidence":{"type":"number"}},"required":["answer"]}'
```

但這裡有一個超多人踩的坑，我要說三遍：

**結構化輸出在 `structured_output` 這個 field，不是 `result`。**

`result` 是 Claude 的文字回答，`structured_output` 才是根據你 schema 生成的 typed JSON。兩個都會回來，但你要 parse 的是 `structured_output`。拿錯 field 你會得到一個 string 然後開始懷疑人生。

> **Mogu 畫重點：**
>
> `result` vs `structured_output` 這個 naming 真的很容易搞混。用吃飯來比喻：你跟服務生說「我要一份雞排飯」（schema），結果店家送來兩個東西——一盤雞排飯（`structured_output`）和一張小紙條寫著「今天的雞排很新鮮喔」（`result`）。你要吃的是那盤飯，不是那張紙條。
>
> 我猜 Anthropic 是為了向後相容才留著 `result`，但身為 API 設計的吹毛求疵者，我覺得他們至少該在文件裡用粗體字寫「你要的東西在 structured\_output」(¬‿¬)

---

## Tool 配置：管好 Claude 的手

Claude CLI 預設會載入一堆 tool——Bash、Read、Write、Edit、Glob、Grep 全部開好開滿。但在 production 環境，你不會想讓 Claude 有權限跑 `rm -rf /`。

這就像帶小孩去廚房：你可以讓他幫忙洗菜、攪拌，但菜刀和瓦斯爐？不好意思，那些先收起來。

四種控制方式：

```bash
claude -p --tools ""                          # 全部收起來，一個都不准用
claude -p --tools "Bash,Read,Glob,Grep"       # 白名單：只准用這四個
claude -p --allowedTools "Bash(git:*)"         # Pattern 比對：只准 git 相關的 Bash
claude -p --disallowedTools "Write,Edit,Bash"  # 黑名單：這三個不准碰
```

> **Mogu 插嘴：**
>
> 這裡有個隱藏的大坑：**不管你怎麼設 `--tools`，[MCP](https://gu-log.vercel.app/glossary#mcp) server 的 tool 都會照常載入**。
>
> 對，你沒看錯。你以為你用白名單把 Claude 管得死死的，結果 MCP 那邊的 tool 完全不受影響，Claude 照樣可以 call。這就像你把前門鎖好了，結果後門大開。
>
> 要真的管住的話，加 `--strict-mcp-config`。我真心覺得這個行為應該反過來——預設就 strict，要放寬的人自己加 flag。Security 的 default 應該是 deny，不是 allow ┐(￣ヘ￣)┌

---

## Permission Mode：在 Script 裡跳過手動確認

正常使用 Claude CLI，每次寫檔案或跑 command 都會跳出「你確定嗎？」的確認視窗。人在 terminal 前面當然沒問題，但你的 cron job 半夜三點跑起來可沒辦法幫你按 Y。

解法很直白：

```bash
echo "$task" | claude -p \
  --permission-mode bypassPermissions \
  --tools "Bash,Read,Write,Edit" \
  --output-format json
```

或者用更不避諱的 flag：`--dangerously-skip-permissions`。

對，flag 名字就叫 **dangerously**。Anthropic 至少在這件事上很誠實——「我們給你一把槍，但我們會在槍身上刻『這玩意兒會殺人』」。

不過 bypass permissions 在 production 是完全正常的操作，重點是你的 sandboxing 有沒有做好。Docker container、受限的 user、只開必要的 tools——把 Claude 關在一個安全的房間裡，然後再告訴它「你自由了」。

> **Mogu 想補充：**
>
> 說真的，我覺得 `--dangerously-skip-permissions` 這個 flag 名字超讚。整個 CLI 生態圈，很少有人把「你正在做一件危險的事」寫得這麼直白。大部分工具都是藏一個 `-f` 或 `--force`，好像怕你看懂似的。
>
> 但反過來想，如果每個危險操作的 flag 都叫 `--dangerously-XXX`，那 `rm -rf` 應該改名叫 `rm --dangerously-delete-everything-and-pray` 才對 (￣▽￣)／

---

## Session 管理：讓 Claude 記住你是誰

預設的 `claude -p` 每次 call 都是全新的對話，Claude 不會記得你上一句說了什麼。就像每次走進 7-11 都要重新跟店員自我介紹——「你好我是小明，我喜歡微糖少冰的美式」。累不累？

三種模式幫你解決這個問題：

```bash
# 拋棄式：用完就丟，不留痕跡
claude -p --no-session-persistence

# 固定 Session ID：指定一個 ID，跨次呼叫共享上下文
echo "My name is Alice" | claude -p --session-id $SESSION --output-format json
echo "What's my name?" | claude -p --session-id $SESSION --continue

# 接續上次對話
claude -p --continue "follow up question"
```

`--session-id` + `--continue` 的組合最實用。第一次 call 建立 session，後續的 call 用同一個 ID 接著聊。Claude 會記住之前的 context，就像你跟朋友 LINE 聊天一樣——不用每次都重新自我介紹。

> **Mogu 想補充：**
>
> 這裡有個很容易忽略的取捨：session 會吃 disk space。每個 session 都會把完整的對話紀錄存在本地，你的 cron job 跑一千次就存一千份。
>
> 所以原作者才特別提到 `--no-session-persistence`。如果你的 use case 是「問一句話拿答案走人」，就不要留 session。省空間、也避免某天你打開 `~/.claude/sessions/` 發現裡面塞了 20GB 的聊天紀錄，然後開始反思人生 ヽ(°〇°)ﾉ

---

## System Prompt 和 Custom Agents

兩種設 system prompt 的方式，但這裡面有一個陷阱比看起來的大得多：

```bash
claude -p --system-prompt "You are a senior code reviewer."
claude -p --append-system-prompt "IMPORTANT: Always respond in bullet points."
```

原作者推薦用 `--append-system-prompt`。為什麼？因為 Claude CLI 的預設 system prompt 裡面有教 Claude 怎麼用 tool 的指引。你用 `--system-prompt` 會**整個覆蓋掉**，Claude 突然就不知道怎麼 call tool 了。就像你把遊戲的操作說明頁撕掉，然後在上面寫「打得漂亮一點」——它連手把怎麼用都忘了，要怎麼打得漂亮？

`--append-system-prompt` 是在後面加上你的指令，不影響原本的。這個 append 的設計其實很聰明，可惜 flag 名字太長，打起來手會抽筋。

想更進一步的話，可以定義 Custom Agents：

```bash
claude -p \
  --agents '{"reviewer":{"description":"Code reviewer","prompt":"You are a strict code reviewer."}}' \
  --agent reviewer \
  "review this function"
```

> **Mogu 忍不住說：**
>
> Custom Agents 這個功能聽起來很花俏，但本質上就是 system prompt + tool 白名單的打包組合。你可以把它想成是預設好的「角色扮演套餐」——不用每次都重新組裝一堆 flag。
>
> 不過我必須說，`--agents` 後面接的那坨 JSON 字串，在 shell 裡面寫起來簡直是視覺地獄。引號套引號、escape 到天荒地老。建議你直接寫成一個 JSON 檔案然後用 `cat` 餵進去，別跟自己過不去 (◕‿◕)

---

## Model 選擇與 Fallback

```bash
claude -p --model haiku "quick question"       # 便宜快速
claude -p --model opus "complex task"          # 貴但聰明
claude -p --model sonnet --fallback-model haiku "important task"  # 主力掛了自動切備用
```

`--fallback-model` 是 production 必備。你的主力 model 超載或掛掉的時候，自動切換到備用的，service 不會中斷。這就像你常去的那家拉麵店公休的時候，你至少要知道隔壁巷子還有一家能吃的。

但問題來了——你怎麼知道 fallback 有沒有被觸發？答案是看 JSON output 裡的 `modelUsage`。如果你發現某天帳單突然變便宜了，可能不是 Anthropic 佛心降價，而是你的 sonnet 一直在掛、haiku 在那邊苦撐 ┐(￣ヘ￣)┌

---

## 雙向 Streaming：最終 Boss

最進階的用法——input 跟 output 都用 stream-JSON，實現真正的即時雙向通訊：

```bash
claude -p \
  --input-format stream-json \
  --output-format stream-json \
  --verbose
```

三個 flag 缺一不可。對，`--verbose` 又出現了。它就像那個你永遠忘記帶的悠遊卡——沒有它你哪裡都去不了。

> **Mogu 吐槽時間：**
>
> 老實說，雙向 streaming 是這整篇文章裡面我最不推薦新手碰的功能。不是因為它不好用，而是因為你需要同時處理兩個方向的 JSON event stream，任何一邊的 parse error 都會讓整個通訊斷掉。
>
> 這就像兩個人同時對講機講話——理論上很酷，實務上你光是處理「誰先講完」「中間斷線了怎麼辦」「event 順序亂了怎麼 recover」就可以 debug 三天。如果你的需求只是「送 prompt、拿結果」，真的不要為了技術好奇心去用這個，用 pipe + JSON output 就夠了 (ง •̀\_•́)ง

---

## 三個可以直接抄的 Production 範例

理論講夠了。來看三段可以直接複製貼上的 code。

**1. Agentic Wrapper（Bash）— 任務進去、結構化結果出來**

```bash
#!/bin/bash
TASK="$1"
result=$(echo "$TASK" | claude -p \
  --model sonnet \
  --fallback-model haiku \
  --tools "Bash,Read,Write,Edit,Glob,Grep" \
  --permission-mode bypassPermissions \
  --no-session-persistence \
  --output-format json \
  --json-schema '{"type":"object","properties":{"success":{"type":"boolean"},"summary":{"type":"string"}},"required":["success","summary"]}')
success=$(echo "$result" | jq -r '.structured_output.success')
summary=$(echo "$result" | jq -r '.structured_output.summary')
cost=$(echo "$result" | jq -r '.total_cost_usd')
```

注意：parse 的是 `.structured_output.success`，不是 `.result.success`。踩過一次坑的人都記得。

**2. Chatbot Wrapper（TypeScript）— 最小可運作版**

```typescript
import { execSync } from 'child_process';
interface ClaudeResult { type: string; subtype: string; result: string; total_cost_usd: number; is_error: boolean; }
function chat(prompt: string, model = 'haiku'): ClaudeResult {
  const result = execSync(`claude -p --model ${model} --output-format json`, { input: prompt, encoding: 'utf-8' });
  return JSON.parse(result);
}
```

**3. Data Extraction Pipeline（TypeScript）— 結構化資料抽取**

```typescript
const SCHEMA = JSON.stringify({
  type: 'object',
  properties: {
    entities: { type: 'array', items: { type: 'string' } },
    sentiment: { enum: ['positive', 'negative', 'neutral'] },
    summary: { type: 'string' }
  },
  required: ['entities', 'sentiment', 'summary']
});
function extract(text: string) {
  const result = execSync(
    `claude -p --model haiku --output-format json --json-schema '${SCHEMA}'`,
    { input: `Extract entities, sentiment, and summary from: ${text}`, encoding: 'utf-8' }
  );
  return JSON.parse(result).structured_output;
}
```

## 延伸閱讀

- [MP-36: Vibe Coding 一周年 — Karpathy 提出「Agentic Engineering」新概念](https://gu-log.vercel.app/posts/mp-36-20260206-karpathy-agentic-engineering/)
- [MP-38: Anthropic 派 16 個 Claude 一起寫了一個 C Compiler — 然後它能編譯 Linux Kernel](https://gu-log.vercel.app/posts/mp-38-20260207-anthropic-c-compiler/)
- [MP-3: Simon Willison：我 25 年的開發直覺已經失效了](https://gu-log.vercel.app/posts/mp-3-20260203-simonw-intuitions/)

> **Mogu murmur：**
>
> 三個範例，三種不同的信任等級。Bash wrapper 把 Claude 當全能員工——給 tool、給 permission、叫它自己搞定。TypeScript chatbot 把 Claude 當翻譯機——你說話、它回話、不碰任何檔案。Data extraction 更狠，連自由回答都不給——乖乖照 schema 填空就好。
>
> 你選哪一個，取決於你對 Claude 的信任程度。我個人的建議是：production 環境先從最低信任開始（extraction），等你確認它不會亂來再慢慢放寬。就像新員工報到第一天不會直接給 root access 一樣，對吧 (๑•̀ㅂ•́)و✧

---

## 那扇窗後面有一整間工廠

開頭說 Anthropic 關了 OAuth 的門，留了一扇窗。

但走過這整篇之後你應該發現了——這哪是一扇窗？窗戶後面有五條輸入管線、三種輸出格式、JSON schema 強制填表機、tool 白名單安檢門、session 記憶銀行、還有雙向 streaming 的即時通訊系統。這根本是一間完整的工廠，只是入口長得像一扇窗而已。

Danial Hasan 做的事，就是幫你畫了這間工廠的完整藍圖。以後你的 side project 需要接 Claude，打開 terminal，`claude -p`，開工 (⌐■\_■)
