---
title: "06 - CLI 與自動化基礎"
type: note
specialty: Programming
tags: [herdr從0開始使用教學, 06-cli, automation, jq]
created: 2026-08-04
updated: "2026"
modified: 2026-08-04
---

# 06 - CLI 與自動化基礎

← [[05-Agent偵測與狀態機制]] | [[00-Index]] | 下一篇 → [[07-Agent-Skill讓AI自己操控Herdr]]

---

## 心智模型：三種基本元件

| 元件 | 職責 |
|---|---|
| **Layout**（`workspace` / `tab` / pane 拓撲）| 建立與組織「終端機的位置」|
| **Pane** | 控制一個原始終端：跑指令、送輸入、讀輸出、等輸出 |
| **Agent** | 用名字或 pane 控制一個被認出來的 coding agent，含 lifecycle 狀態 |

**選錯元件是自動化最常見的錯誤。** 判斷準則：

- 一般 shell、測試、server、CI watcher → **pane** 指令
- 需要 Herdr 理解「這是哪個 agent」「它現在什麼狀態」 → **agent** 指令

---

## 鐵則一：ID 從 JSON 讀，不要猜

建立類指令都會印 JSON。**永遠從回應裡抓 ID。**

```bash
created=$(herdr workspace create --cwd ~/Taiwan_IM_board --label board --no-focus)
ws=$(printf '%s\n' "$created" | jq -r '.result.workspace.workspace_id')
pane=$(printf '%s\n' "$created" | jq -r '.result.root_pane.pane_id')

split=$(herdr pane split "$pane" --direction right --no-focus)
worker=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id')
```

回應路徑速查：

| 指令 | 主要回傳路徑 |
|---|---|
| `workspace create` | `.result.workspace` / `.result.tab` / `.result.root_pane` |
| `tab create` | `.result.tab` / `.result.root_pane` |
| `pane split` | `.result.pane` |
| `pane move` | `.result.move_result.pane.pane_id`（舊值在 `.previous_pane_id`）|
| `agent start` / `agent prompt` / `agent wait` | `.result.agent` |
| `pane wait-output` | `.result.pane_id` / `.result.matched_line` / `.result.read` |
| `pane read` / `agent read`（socket API）| `.result.read.text` |

---

## 鐵則二：明確指定目標

```bash
herdr pane split --current --direction right --cwd "$PWD" --no-focus   # ✅
herdr pane split --direction right                                      # ❌ 用 UI 目前 focus 的 pane
```

省略目標時 Herdr 會用 **UI 目前 focus 的 pane**——那可能是使用者剛切過去的、或另一個 client 的。多 agent 環境下這一定會出事。

---

## 建立版面

```bash
# workspace（順便有 tab + root pane）
herdr workspace create --cwd ~/project --label api --no-focus

# tab
herdr tab create --workspace w1 --label validate --no-focus

# 分割 pane
herdr pane split --current --direction right --cwd "$PWD" --no-focus
herdr pane split w1:p2 --direction down --ratio 0.4 --no-focus
```

分割方向的幾何規則：**寬的往右切、窄或高的往下切**，避免連續同方向切出無法使用的細長條。先看形狀：

```bash
herdr pane layout --pane "$HERDR_PANE_ID"
```

---

## 在 pane 裡跑普通指令

```bash
herdr pane run w1:p3 "python3 tools/validate.py"
herdr pane send-text w1:p3 "some literal text"    # 不送 Enter
herdr pane send-keys w1:p3 ctrl+c                 # 送按鍵
```

- **`pane run` 會把指令文字和 Enter 一起原子送出**，而且尊重 pane 目前的 bracketed-paste 模式。跑指令一律用它。
- `send-text` + `send-keys enter` 是低階做法，兩次操作分開，不保證原子性。

按鍵語法：普通字元 `a`、特殊鍵 `enter` `tab` `esc` `backspace` `left` `right` `up` `down`、和弦 `ctrl+h` `alt+x` `shift+tab`、功能鍵 `f1`、有名字的標點 `minus` `plus` `backtick`。`escape` 是 `esc` 的別名；舊式 `C-c` 也接受。

---

## 讀輸出

```bash
herdr pane read w1:p3 --source recent-unwrapped --lines 120
herdr agent read writer-a --source recent-unwrapped --lines 200
herdr pane read w1:p3 --source visible --ansi
```

四種 source：

| Source | 意思 | 適合 |
|---|---|---|
| `visible` | 目前渲染的畫面 | UI 回饋迴圈 |
| `recent` | 近期輸出，**含終端換行折行** | 一般 |
| `recent-unwrapped` | 近期輸出，**折行接回去** | **log、transcript 首選** |
| `detection` | agent 偵測用的底部純文字快照 | 除錯偵測 |

行為細節：

- 預設輸出 UTF-8 純文字、**剝掉 ANSI escape**。要保留顏色用 `--format ansi` 或 `--ansi`（`detection` 永遠是純文字）。
- `recent` 系列：`--lines N` 取「最後 N 個渲染終端列」；不給預設 **80 列**。
- `visible` 和 `detection`：不給 `--lines` 回傳整份快照；給了則保留最後 N 行。

### ⚠️ Alternate screen 的坑（Claude Code / OpenCode）

全螢幕型 agent（**Claude Code**、OpenCode）把 transcript 畫在終端的 **alternate screen**，不是 Herdr 的 host scrollback。

Herdr 有做了一件聰明事：對一個**閒置、被認出來、而且停在 transcript 底部**的 agent，`recent` / `recent-unwrapped` 且 `--lines` 超過可見畫面時，會**自動用 agent 的滑鼠捲動介面**去收集重疊的頁面，讀完再把 viewport 放回底部。

但這條路徑有條件：

- agent 正在 `working` / `blocked` / `unknown` 時，明確指定 `--lines N` 的 alternate-screen 歷史讀取會回 **`agent_not_idle`**。→ 等它 idle 再讀，或改用 `--source visible`。
- 使用者手動捲動過、有 direct attach、或那個程式不回報滑鼠滾輪時，Herdr **不會**動 viewport。
- `visible`、`detection`、ANSI 讀取、輸出等待與訂閱**一律是被動的**，不會移動 viewport。

**官方給的 fallback，也是實務上最好用的一招**：

> 叫 agent 把完整回應寫成 Markdown 存到暫存目錄，**只回傳檔案路徑**，然後你直接讀那個檔案。

⚠️ 但這是 fallback，**不要一開始就在 prompt 裡要求寫檔**（會污染任務）。不過在詳解 / 筆記量產這種**本來就要寫檔**的任務裡，寫檔是任務本體，所以天然沒有這個問題——見 [[09-實戰-刷題網站Taiwan-IM-board]]。

---

## 等待

### 等普通指令的輸出

```bash
herdr pane wait-output w1:p3 --match "OK" --timeout 120000
herdr pane wait-output w1:p3 --regex "passed|failed" --timeout 120000
```

- **它會立刻搜尋目前的快照**，所以**已經存在的輸出也會命中**。
- 預設 source 名稱是 `recent`，比對時當成「最新 80 個渲染列的 unwrapped 近期輸出」；`--lines` 可改。
- `--match` 是**單行內的字面子字串**；`--regex` 是 Rust 正規表示式，**一次比對一行**。
- **它完全不解讀 agent lifecycle。**

### 等 agent 狀態

```bash
herdr agent wait writer-a --timeout 900000
herdr agent wait writer-a --until blocked --timeout 120000
herdr agent wait writer-a --until idle --until done --timeout 900000
```

- 不給 `--until` 時，預設接受 `idle` / `done` / `blocked`。
- `agent wait` 是**觀察目前狀態**：如果現在就符合，**立刻回傳**。
- 要接受 `unknown` 必須明確寫 `--until unknown`。

### 送 prompt 並等它

```bash
herdr agent prompt writer-a "把 §Diagnosis 依 2026 guideline 改寫" --wait --timeout 900000
```

規則（**這段一定要看懂，不然會誤判**）：

- `agent prompt` 把**文字 + 編碼過的 Enter 原子送出**，尊重終端目前的 bracketed-paste 模式。**agent 正在工作時也可以送。**
- `--wait` 是**立刻送出**，然後：
  - 如果 agent 從**非 working 狀態**開始，Herdr 要求**五秒內觀察到 lifecycle 變化**。沒有的話回 **`agent_prompt_stalled`**，而不是無限等下去。（caller `--timeout` ≤ 5 秒時走一般的 `timeout` 錯誤。）
  - 觀察到活動之後，才等你要的 settled 狀態。
- ⚠️ **它追的是 lifecycle 狀態，不是「這一輪對話」**。如果 agent 本來就在工作，**目前那一輪結束就可能滿足這個 wait**。
- `--until` 只有在同時有 `--wait` 時才允許。

> 🔑 **實務結論**：`agent prompt --wait` 回來 **不等於「我要的任務做完了」**。真正的驗收要靠：**檢查它應該產生的檔案是否存在且正確**，這比讀畫面可靠一百倍。

### Timeout 與錯誤

- `pane wait-output`、`agent wait`、`agent prompt --wait` **不給 `--timeout` 就會無限等**。
- Timeout 或 server 錯誤 → JSON 印到 **stderr**，**exit code 1**。
- CLI 語法錯誤 → **exit code 2**。

---

## 啟動 agent

```bash
# 1. 先要有一個停在 shell prompt 的 pane
split=$(herdr pane split --current --direction right --cwd "$PWD" --no-focus)
p=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id')

# 2. 才能啟動 agent
herdr agent start writer-a --kind codex --pane "$p"

# 3. 需要傳給 agent 本身的參數放在 -- 之後
herdr agent start reviewer --kind codex --pane "$p" -- --profile worker
```

> ⚠️ **不要在編排腳本裡寫死模型名。** 官方文件的範例寫的是 `-m gpt-5.4`，但**該模型已於 2026-08-31 從 ChatGPT 登入的 Codex 退場**。
> 讓 `~/.codex/config.toml` 的 profile 決定模型（見 [[Programming/Codex/Codex-CLI從0開始使用教學/05-AGENTS-md與config-toml|Codex CLI 05]]），模型換代時腳本才不會跟著壞。

規則：

- **「可用的 shell pane」= 停在互動 shell prompt**：shell 自己擁有前景，沒有前景指令、編輯器或 agent 在跑。
- `agent start` **只在 Herdr 偵測到預期的 agent 在同一個終端、而且準備好接受互動輸入之後才回傳**。
- 預設啟動 timeout **30000 ms**；明確給的值必須 **> 3000 且 ≤ 300000**。
- `--kind` 選的是 Herdr 認定的標準執行檔；`--` 之後的參數原封不動傳給它。
- 手動啟動的 agent 也會被自動偵測，用 pane ID 定址；想要穩定的名字就 `agent rename`。

---

## 送按鍵給 agent 的互動 UI

```bash
herdr agent send-keys writer-a esc
herdr agent send-keys writer-a ctrl+c
herdr agent send-keys writer-a up enter
```

Herdr **會先驗證所有按鍵，才寫出任何 byte**。

**pane 輸入 vs agent 輸入的差別**：

- **pane 輸入**：不管目前誰佔用，直接對那個終端下手。
- **agent 輸入**：先解析出存活的 agent；**如果那個 agent 已經不再控制該 pane，操作會被拒絕**。

👉 編排時用 agent 輸入（比較安全）；只有在你**刻意**要原始終端控制時才用 pane 輸入。

---

## 一段完整的最小編排範例

```bash
#!/usr/bin/env bash
set -euo pipefail

# 1. 開一個背景 pane
split=$(herdr pane split --current --direction right --cwd "$PWD" --no-focus)
p=$(printf '%s\n' "$split" | jq -r '.result.pane.pane_id')

# 2. 啟動 codex
herdr agent start reviewer --kind codex --pane "$p"

# 3. 丟任務並等它
herdr agent prompt reviewer \
  "審查目前的 git diff，只回報可執行的問題。" \
  --wait --timeout 600000

# 4. 讀結果
herdr agent read reviewer --source recent-unwrapped --lines 200
```

等一個 agent 來問你問題、看一下、然後回它：

```bash
herdr agent wait reviewer --until blocked --timeout 120000
herdr agent read reviewer --source recent-unwrapped --lines 80
herdr agent send-keys reviewer esc
```

跑一個普通 process 並等它的輸出（**不當成 agent**）：

```bash
herdr pane run w1:p3 "npm run validate"
herdr pane wait-output w1:p3 --regex "通過|FAIL|Error" --timeout 300000
herdr pane read w1:p3 --source recent-unwrapped --lines 60
```

---

## 其他常用指令

```bash
# 狀態
herdr status
herdr status server
herdr status client

# session
herdr session list --json

# git worktree（見第 09、10 章的隔離工作流）
herdr worktree list --cwd .
herdr worktree create --cwd . --branch feat/詳解-114 --no-focus
herdr worktree remove --workspace w2 --force

# 通知（可以在腳本結束時叫自己）
herdr notification show "詳解量產完成" --body "114 心臟 24 題已產出" --sound done

# API schema（要寫工具時）
herdr api schema --json --output herdr-api.schema.json
```

---

## 🔗 相關筆記

- [[05-Agent偵測與狀態機制]] — 狀態的來源
- [[07-Agent-Skill讓AI自己操控Herdr]] — 下一步：把這些指令交給 Claude 自己下
- [[08-Claude當大腦-Codex當雙手]] — 完整編排腳本
- [[13-疑難排解與術語速查表]] — 指令 cheat sheet
- [[Programming/Python/07-實戰自動化範例|Python 07 - 實戰自動化範例]] — 刷題網站的 tools/ pipeline，正是被編排的對象

---

*最後更新：2026-08-04*
