agentflowctl 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jin-Ze Huang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # agentflowctl
2
+
3
+ 一個**跨廠商的 AI 開發 harness**:讓 Claude Code、Codex、Gemini CLI(或任何 agent CLI)在同一條流程裡**輪流實作、互相審查、互相修正**。
4
+
5
+ ```
6
+ 需求 → spec → plan ⇄ plan_review ⇄ plan_fix →(僵持時)仲裁 → implement(測試 A → 實作 B)→ verify ⇄ fix → review ⇄ fix → pr
7
+ ```
8
+
9
+ 從需求到 PR **全程不需要人介入**:計畫和程式碼一樣,由不同公司的模型互相審查、修改;審查僵持不下時交付仲裁。**兩家模型就能完整運作**,有第三家時仲裁會更獨立。
10
+
11
+ 每個步驟都是一次獨立的 agent 執行,用 `.flow/` 裡的檔案交接;是否通過一律由程式實際檢查(跑測試、比對 git diff、驗證 JSON),不相信任何一家模型自己說「完成了」。
12
+
13
+ ## 為什麼要讓不同公司的模型互相循環
14
+
15
+ 同一個模型審查自己的程式碼,很容易對自己的寫法有盲點;測試和實作出自同一個模型,也容易「寫出剛好會過的測試」。agentflowctl 用三條規則打破這種同溫層:
16
+
17
+ | 規則 | 效果 |
18
+ |---|---|
19
+ | **審查者永遠不是最後寫程式的 agent** | 每一輪審查都是「別家」在看 |
20
+ | **同一個任務的測試與實作由不同 agent 負責**(`tddSplit`) | A 寫的測試,B 必須實作到通過,而且不能改測試 |
21
+ | **所有角色沿著同一個輪替順序前進** | 三家輪流扮演作者、審查者、修正者 |
22
+
23
+ 最常見的是兩家,例如輪替順序 `claude → codex`,實際跑出來是很乾淨的乒乓模式:
24
+
25
+ ```
26
+ 計畫:claude 撰寫 → codex 審查(要求修改)→ claude 修改 → codex 審查(核准)
27
+ T-1 測試:claude 實作:codex
28
+ T-2 測試:codex 實作:claude
29
+ 審查:codex(最後作者是 claude)→ 要求修改 → claude 修正 → codex 審查(核准)
30
+ ```
31
+
32
+ 有三家時,修正與審查會一直換人(例如 codex 寫、gemini 審、claude 修),仲裁也能交給完全沒參與的第三方。
33
+
34
+ 每個 commit 的訊息結尾都會標上作者,例如 `feat(T-2): 匯出 [gemini]`,事後可以清楚看到每一段程式碼是哪家模型寫的、哪家審過。
35
+
36
+ ## 在任何環境都能用
37
+
38
+ agentflowctl 本身只需要 **Node.js 22 以上與 git**,其他全部透過各家的 CLI 執行:
39
+
40
+ | 環境 | 說明 |
41
+ |---|---|
42
+ | macOS、Linux、Windows | 專案指令透過系統 shell 執行,git 操作不依賴任何平台專屬路徑 |
43
+ | CI(GitHub Actions 等) | 見 `examples/github-actions.yml`:issue 加上標籤就自動跑完並開 PR |
44
+ | 容器、遠端開發機 | 可丟棄的環境最適合完全無人值守地執行 |
45
+ | 在 Claude Code、Codex 裡面 | 讓它們用 shell 執行 `agentflowctl`,或之後包成 MCP server |
46
+
47
+ 執行前先檢查環境:
48
+
49
+ ```bash
50
+ agentflowctl doctor
51
+ # ✅ claude adapter=claude
52
+ # ✅ codex adapter=codex
53
+ # ❌ gemini adapter=gemini
54
+ # 輪替順序:claude → codex
55
+ ```
56
+
57
+ 沒有設定輪替順序時,agentflowctl 會自動偵測已安裝的 CLI。只裝一家也能運作,只是失去交叉審查的好處。
58
+
59
+ ## 安裝
60
+
61
+ ```bash
62
+ npm install -g agentflowctl
63
+ ```
64
+
65
+ 從原始碼安裝:
66
+
67
+ ```bash
68
+ git clone https://github.com/gogogohuang/agentflowctl.git
69
+ cd agentflowctl
70
+ pnpm install
71
+ pnpm run build
72
+ pnpm link --global
73
+ ```
74
+
75
+ 預設使用各家 CLI 的**訂閱登入**,不會產生額外的 API 費用:先分別執行 `claude`、`codex` 完成登入,再用 `agentflowctl doctor` 確認。詳見下方「訂閱登入與額度」。
76
+
77
+ ## 使用方式
78
+
79
+ 在專案資料夾內執行:
80
+
81
+ ```bash
82
+ agentflowctl run --req "登入表單加上 zod 驗證與錯誤訊息"
83
+ agentflowctl run --req-file ./req.md --cycle codex,claude --max-agent-runs 40
84
+ agentflowctl run --req "..." --manual-plan # 計畫通過 AI 審查後,仍停下來讓你確認
85
+
86
+ agentflowctl approve f-xxxx # 搭配 --manual-plan 時核准計畫
87
+ agentflowctl status f-xxxx # 階段、任務進度、各 agent 的用量
88
+ agentflowctl list
89
+ agentflowctl logs f-xxxx --latest
90
+ agentflowctl resume f-xxxx # 從暫停或失敗處接續,可加 --max-agent-runs
91
+ agentflowctl cancel f-xxxx
92
+ agentflowctl clean f-xxxx # 移除 worktree,分支保留
93
+ ```
94
+
95
+ 每個 run 都在專案內的專用 git worktree(`.agentflowctl/worktrees/<id>`)工作,不會碰到你正在編輯的檔案。
96
+
97
+ ## 設定:`flow.config.json`
98
+
99
+ 放在專案根目錄,完整範例見 `examples/flow.config.json`。
100
+
101
+ ```json
102
+ {
103
+ "cycle": ["claude", "codex"],
104
+ "fixStrategy": "ring",
105
+ "tddSplit": true,
106
+ "tieBreak": "proceed",
107
+ "agents": {
108
+ "codex": { "adapter": "codex", "model": "你要用的模型", "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10 } },
109
+ "aider": { "adapter": "command", "command": ["aider", "--yes-always", "--no-auto-commits", "--message", "{prompt}"] }
110
+ }
111
+ }
112
+ ```
113
+
114
+ | 設定 | 說明 |
115
+ |---|---|
116
+ | `cycle` | 輪替順序;同一家也可以放不同模型,例如定義 `claude-fast` 與 `claude-strong` 兩個 agent |
117
+ | `fixStrategy` | `ring`:審查意見交給審查者的下一位修正(三家時會一直換人);`author`:交回作者修正 |
118
+ | `tddSplit` | 測試與實作是否交給不同 agent |
119
+ | `reviewQuorum` | 程式碼需要幾位不同的審查者都核准;設成 2 就是「兩家都同意才過」 |
120
+ | `planReviewQuorum` | 計畫需要幾位不同的審查者都核准 |
121
+ | `planArbiter` | 計畫審查僵持時是否交付仲裁(預設開啟);關閉的話僵持會直接失敗,等人處理 |
122
+ | `tieBreak` | 仲裁意見分歧時:`proceed`(預設)繼續實作並記錄爭議;`stop` 停下來等人 |
123
+ | `auth` | `subscription`(預設)移除環境中的 API key,只用訂閱登入;`api` 保留 API key |
124
+ | `maxAgentRuns` | 單一 run 最多執行幾次 agent,預設 60 |
125
+ | `agents` | 覆寫內建的 `claude`、`codex`、`gemini`,或用 `command` adapter 接上任何其他 CLI |
126
+
127
+ verify 失敗(型別、lint、建置錯誤)一律交回最後的作者修正,因為這類機械性錯誤由作者處理最快;只有審查意見才依 `fixStrategy` 輪替。
128
+
129
+ ## 計畫審查怎麼做到不需要人
130
+
131
+ 人工確認計畫原本是為了擋住「方向錯了還一路做下去」。agentflowctl 用三層機制取代它:
132
+
133
+ **第一層:確定性檢查。** 每次計畫被撰寫或修改後,都要重新通過格式、任務相依與驗收條件覆蓋率的檢查,沒過就還原。
134
+
135
+ **第二層:跨模型審查。** 審查重點是需求覆蓋、驗收條件能否測試、任務大小與技術方向。審查者只能寫出意見,若偷改規格或計畫,agentflowctl 會把檔案還原;修改者必須在 `plan.md` 的「審查回應」逐條回覆,不同意的意見要寫理由,不能直接忽略。
136
+
137
+ **第三層:僵持時仲裁。** 兩種情況會觸發:審查意見和上一輪完全一樣(修改沒有進展),或已達重試上限。仲裁者只判斷一件事:照這份計畫實作,能不能正確滿足需求。誰來仲裁取決於有幾家:
138
+
139
+ | 情況 | 仲裁方式 | 結果 |
140
+ | --- | --- | --- |
141
+ | 有第三家 | 沒參與討論的第三方單獨仲裁 | 核准就繼續,否則停下 |
142
+ | 只有兩家 | **雙盲交叉仲裁**:兩家各自在全新 context 中判斷 | 一致核准就繼續;都不核准就停下;分歧依 `tieBreak` |
143
+
144
+ 只有兩家時,不能讓一直提反對意見的審查者同時當裁判,所以改成兩家各自仲裁,並且做到**雙盲**:仲裁者看到的只有計畫與一份不含任何模型名稱的爭議清單(`.flow/dispute.md`),看不出誰是作者、誰是審查者;帶有名稱的審查紀錄都移到 worktree 以外。`tieBreak` 預設為 `proceed`,理由是計畫之後還有紅綠燈、驗證與程式碼審查等確定性關卡把關,有瑕疵的計畫很難一路通過到 PR。
145
+
146
+ 裁決結果與每位仲裁者的理由會附在 `plan.md` 最後的「仲裁紀錄」。所有審查與仲裁的原始紀錄保存在 `.agentflowctl/runs/<id>/reviews/`,事後可以完整追溯每一輪誰提了什麼。
147
+
148
+ ## Adapter
149
+
150
+ | adapter | 執行方式 | 權限控制 |
151
+ |---|---|---|
152
+ | `claude` | `claude -p --output-format stream-json` | acceptEdits、禁止 git 寫入指令、內建沙箱(設定檔在 `.agentflowctl/runs/<id>/claude-settings.json`) |
153
+ | `codex` | `codex exec --json --sandbox workspace-write -`(prompt 走 stdin) | 只能修改工作目錄,預設不能連網 |
154
+ | `gemini` | `gemini -p --output-format stream-json --approval-mode yolo` | 沒有細緻權限,建議在 `extraArgs` 加 `--sandbox` 或在可丟棄環境執行 |
155
+ | `command` | 任意指令,`{prompt}` 替換或走 stdin | 取決於該工具 |
156
+
157
+ Codex 的沙箱不能連網,所以 agentflowctl 在建立 worktree 時會先執行 `install` 把相依套件裝好。各家 CLI 的參數與事件格式更新得很快,第一次使用前請先用 `agentflowctl doctor` 與一個小需求實測。
158
+
159
+ 每家 agent 讀取的專案說明檔不同(Claude Code 讀 `CLAUDE.md`、Codex 讀 `AGENTS.md`、Gemini 讀 `GEMINI.md`)。建議把專案慣例寫在 `AGENTS.md`,另外兩個檔案用一行引用它,確保三家看到的規則一致。agentflowctl 的 prompt 本身不依賴任何一家的 skills 或 plugins。
160
+
161
+ ## 各階段與關卡
162
+
163
+ | 階段 | 負責的 agent | 通過條件(由程式判斷) | 失敗時 |
164
+ |---|---|---|---|
165
+ | spec | 輪替順序第 1 位 | 檔案存在、zod 驗證、id 不重複 | 重試 |
166
+ | plan | 輪替順序第 1 位 | zod 驗證、相依存在、無循環、每條驗收條件都有任務負責 | 重試 |
167
+ | plan_review | 非計畫作者的下一位(可多位) | 所有審查者都 `approve` | 進入 plan_fix |
168
+ | plan_fix | 審查者的下一位 | 修改後仍通過 plan 的所有格式與 DAG 檢查 | 還原並重試 |
169
+ | 仲裁 | 有第三家:第三方;只有兩家:兩家雙盲各自仲裁 | 一致核准;分歧依 `tieBreak` | 都不核准(或 `tieBreak: stop`)時 run 失敗,這時才需要人 |
170
+ | 人工確認 | 你(只有 `--manual-plan` 時) | `agentflowctl approve` | — |
171
+ | implement(紅燈) | 第 i 個任務由第 i 位 | 有測試變更,且執行後**失敗** | 還原並重試 |
172
+ | implement(綠燈) | 測試作者的下一位 | 測試檔**完全沒被修改**,且測試通過 | 還原或帶著輸出重試 |
173
+ | verify | — | install 與所有 checks 通過 | 交回作者修正 |
174
+ | review | 非作者的下一位(可多位) | 所有審查者都 `approve` | 依 `fixStrategy` 交給下一位修正 |
175
+ | pr | — | push 成功,有 `gh` 就開 PR | — |
176
+
177
+ ## 訂閱登入與額度
178
+
179
+ agentflowctl 預設 `auth: "subscription"`,只使用各家 CLI 的訂閱登入。
180
+
181
+ **不會意外改走 API 計費。** 環境變數中的 API key 會蓋過訂閱登入,所以 agentflowctl 執行 agent 時,會從子程序環境中移除 `ANTHROPIC_API_KEY`、`ANTHROPIC_AUTH_TOKEN`、`CODEX_API_KEY`、`OPENAI_API_KEY`、`GEMINI_API_KEY`、`GOOGLE_API_KEY`。`agentflowctl doctor` 發現這些變數時也會提醒你。專案指令(install、test、build)不受影響。
182
+
183
+ **用量上限取代金額預算。** 訂閱制下花費不是實際扣款,所以預設以「agent 執行次數」為上限(`maxAgentRuns`,預設 60 次)。Claude Code 回報的美元金額只是換算 API 價格的估計值,僅供參考。
184
+
185
+ **額度用完時,審查停下、其他步驟代打。**
186
+
187
+ | 步驟 | 額度用完時 | 理由 |
188
+ | --- | --- | --- |
189
+ | 計畫審查、程式碼審查、仲裁 | **暫停**,等額度恢復後 `agentflowctl resume` | 由另一家代審,可能變成作者審查自己,失去交叉審查的意義 |
190
+ | 規格、計畫、修改計畫、寫測試、寫實作、修正 | **由另一家代打**,繼續執行 | 撰寫品質之後仍有審查把關 |
191
+ | 所有 agent 的額度都用完 | 暫停 | — |
192
+
193
+ 換人或暫停前,額度用完的 agent 留下的半成品會先被清掉。代打會記錄在 `.agentflowctl/runs/<id>/substitutions.jsonl`,`agentflowctl status` 也會列出;commit 結尾標註的是實際執行的模型。
194
+
195
+ 代打有一個已知的取捨:如果寫實作的一方額度用完,由寫測試的那一家代打,這個任務的測試與實作就會出自同一家,`status` 會特別標註。這也是為什麼審查步驟必須等原本的另一家:它是這種情況下唯一的交叉檢查。
196
+
197
+ 額度相關錯誤是用錯誤訊息比對辨識的(usage limit、rate limit、quota、429 等),只在 agent 執行失敗時才判斷。各家訊息可能隨版本改變,辨識失敗時會被當成一般失敗重試。
198
+
199
+ **使用 API 的情況。** 例如在 CI 中無法使用訂閱登入,在 `flow.config.json` 設定 `"auth": "api"` 保留 API key,並可用 `--budget` 設定估計花費上限。Codex、Gemini 只回報 token,需要在 `agents.<name>.pricing` 設定價格才會算進預算。
200
+
201
+ ## 安全性
202
+
203
+ 沒有容器隔離時,**verify 階段會直接在你的電腦上執行 agent 寫出來的程式碼**,而且各家 CLI 的權限模型強弱不一(Gemini 在無人值守時只能 yolo)。因此:
204
+
205
+ - 在你自己的電腦上:用在自己的專案、需求由你撰寫;剛開始使用時可以加上 `--manual-plan`,確認 AI 審查的品質後再拿掉。
206
+ - 要無人值守或處理外部 issue:放到 CI runner 或容器這類可丟棄的環境。
207
+ - 不要把來路不明的 issue 內容直接交給 agentflowctl 在本機執行,需求文字本身就可能夾帶惡意指示;AI 審查計畫並不能擋住這類攻擊。
208
+
209
+ ## 專案結構
210
+
211
+ ```
212
+ src/
213
+ cli.ts 指令列介面(run、doctor、status……)
214
+ engine.ts 狀態機與各階段邏輯
215
+ roles.ts 誰負責哪個步驟的輪替規則(含計畫修正者與仲裁者)
216
+ runner.ts 執行 agent 並正規化結果、執行專案指令
217
+ agents/ claude、codex、gemini、command 四種 adapter
218
+ git.ts worktree 與安全的 git 操作
219
+ store.ts 以檔案儲存狀態與各 agent 的花費
220
+ tasks.ts 任務 DAG 驗證與排序
221
+ schemas.ts 所有 zod schema
222
+ prompts/ 各階段 prompt(不依賴任何一家的專屬功能)
223
+ examples/ flow.config.json 與 GitHub Actions 範例
224
+ ```
225
+
226
+ ## 開發
227
+
228
+ ```bash
229
+ pnpm run typecheck
230
+ pnpm test # 輪替規則、各 adapter 的事件解析、worktree、儲存、任務 DAG
231
+ ```
232
+
233
+ ## 授權
234
+
235
+ MIT,詳見 [LICENSE](LICENSE)。
@@ -0,0 +1,65 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { num, str, tryJson } from "./types.js";
4
+ /**
5
+ * Claude Code 的權限設定:acceptEdits 只自動允許工作目錄內的修改,
6
+ * 版本控制交給 agentflowctl,並啟用內建沙箱限制 shell 指令。
7
+ * 設定鍵名以 Claude Code 目前的格式撰寫,請依你安裝的版本確認。
8
+ */
9
+ function settingsFile(o) {
10
+ const settings = {
11
+ permissions: {
12
+ allow: ["Read", "Edit", "Write", "Glob", "Grep", "Bash(npm:*)", "Bash(npx:*)", "Bash(pnpm:*)", "Bash(node:*)",
13
+ "Bash(git status:*)", "Bash(git diff:*)", "Bash(git log:*)", "Bash(git show:*)"],
14
+ deny: ["Bash(git commit:*)", "Bash(git push:*)", "Bash(git reset:*)", "Bash(git checkout:*)", "Bash(git switch:*)",
15
+ "Bash(git rebase:*)", "Bash(git merge:*)", "Bash(git worktree:*)", "Bash(git config:*)", "Bash(git stash:*)",
16
+ "Read(~/.ssh/**)", "Read(~/.aws/**)", "Read(~/.config/gh/**)", `Edit(/${join(o.projectRoot, ".git")}/**)`],
17
+ },
18
+ sandbox: { enabled: true, autoAllowBashIfSandboxed: true },
19
+ };
20
+ mkdirSync(o.runDir, { recursive: true });
21
+ const path = join(o.runDir, "claude-settings.json");
22
+ writeFileSync(path, JSON.stringify(settings, null, 2));
23
+ return path;
24
+ }
25
+ export const claude = {
26
+ probe: () => ({ cmd: "claude", args: ["--version"] }),
27
+ invoke: (o) => ({
28
+ cmd: "claude",
29
+ args: [
30
+ "-p", o.prompt,
31
+ "--output-format", "stream-json", "--verbose",
32
+ "--permission-mode", "acceptEdits",
33
+ "--settings", settingsFile(o),
34
+ ...(o.model ? ["--model", o.model] : []),
35
+ ...o.extraArgs,
36
+ ],
37
+ }),
38
+ parse(line) {
39
+ const ev = tryJson(line);
40
+ if (!ev)
41
+ return [];
42
+ const out = [];
43
+ if (ev.type === "assistant") {
44
+ const content = ev.message?.content ?? [];
45
+ for (const b of content) {
46
+ if (b.type === "text" && str(b.text)?.trim())
47
+ out.push({ kind: "text", text: str(b.text) });
48
+ if (b.type === "tool_use")
49
+ out.push({ kind: "tool", name: str(b.name) ?? "tool" });
50
+ }
51
+ }
52
+ else if (ev.type === "result") {
53
+ const usage = (ev.usage ?? {});
54
+ out.push({
55
+ kind: "usage",
56
+ inputTokens: num(usage.input_tokens),
57
+ outputTokens: num(usage.output_tokens),
58
+ costUsd: num(ev.total_cost_usd),
59
+ });
60
+ out.push({ kind: "done", ok: ev.is_error !== true, summary: str(ev.result) });
61
+ }
62
+ return out;
63
+ },
64
+ };
65
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1,39 @@
1
+ import { num, str, tryJson } from "./types.js";
2
+ /**
3
+ * OpenAI Codex CLI:`codex exec --json`,prompt 由 stdin 傳入(`-`)。
4
+ * workspace-write 沙箱只允許修改工作目錄,且預設不能連網,
5
+ * 所以 agentflowctl 會在建立 worktree 時先幫它裝好相依套件。
6
+ */
7
+ export const codex = {
8
+ probe: () => ({ cmd: "codex", args: ["--version"] }),
9
+ invoke: (o) => ({
10
+ cmd: "codex",
11
+ args: ["exec", "--json", "--sandbox", "workspace-write", "-C", o.cwd, ...(o.model ? ["-m", o.model] : []), ...o.extraArgs, "-"],
12
+ input: o.prompt,
13
+ }),
14
+ parse(line) {
15
+ const ev = tryJson(line);
16
+ if (!ev)
17
+ return [];
18
+ const out = [];
19
+ if (ev.type === "item.completed") {
20
+ const item = (ev.item ?? {});
21
+ if (item.type === "agent_message" && str(item.text)?.trim())
22
+ out.push({ kind: "text", text: str(item.text) });
23
+ else if (item.type === "command_execution")
24
+ out.push({ kind: "tool", name: `shell: ${str(item.command) ?? ""}`.slice(0, 80) });
25
+ else if (item.type === "file_change")
26
+ out.push({ kind: "tool", name: "edit" });
27
+ }
28
+ else if (ev.type === "turn.completed") {
29
+ const usage = (ev.usage ?? {});
30
+ out.push({ kind: "usage", inputTokens: num(usage.input_tokens), outputTokens: num(usage.output_tokens) });
31
+ }
32
+ else if (ev.type === "turn.failed" || ev.type === "error") {
33
+ const err = (ev.error ?? {});
34
+ out.push({ kind: "done", ok: false, summary: str(err.message) ?? str(ev.message) ?? "Codex 執行失敗" });
35
+ }
36
+ return out;
37
+ },
38
+ };
39
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 任何其他 agent CLI(aider、opencode、自己寫的腳本……)。
3
+ * 在設定裡提供 command 陣列,`{prompt}` 會被替換成 prompt;沒有 `{prompt}` 時改由 stdin 傳入。
4
+ * 不解析輸出:以 exit code 判斷成功,stdout 最後一段當作摘要。
5
+ */
6
+ export const command = {
7
+ probe: (cmd) => ({ cmd: cmd?.[0] ?? "false", args: ["--version"] }),
8
+ invoke(o) {
9
+ const [cmd, ...rest] = o.command ?? [];
10
+ if (!cmd)
11
+ throw new Error("command adapter 需要設定 command 陣列");
12
+ const hasPlaceholder = rest.some((a) => a.includes("{prompt}"));
13
+ return {
14
+ cmd,
15
+ args: [...rest.map((a) => a.replaceAll("{prompt}", o.prompt)), ...o.extraArgs],
16
+ input: hasPlaceholder ? undefined : o.prompt,
17
+ };
18
+ },
19
+ parse: () => [],
20
+ };
21
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1,41 @@
1
+ import { num, str, tryJson } from "./types.js";
2
+ /**
3
+ * Google Gemini CLI:`gemini -p ... --output-format stream-json`。
4
+ * Gemini CLI 沒有「只允許工作目錄內的 shell 指令」這種細緻權限,
5
+ * 無人值守時只能用 yolo;需要隔離時請在 extraArgs 加上 `--sandbox`,
6
+ * 或在 CI、容器這類可丟棄的環境中執行。
7
+ */
8
+ export const gemini = {
9
+ probe: () => ({ cmd: "gemini", args: ["--version"] }),
10
+ invoke: (o) => ({
11
+ cmd: "gemini",
12
+ args: ["-p", o.prompt, "--output-format", "stream-json", "--approval-mode", "yolo", ...(o.model ? ["-m", o.model] : []), ...o.extraArgs],
13
+ env: { GEMINI_CLI_TRUST_WORKSPACE: "true" },
14
+ }),
15
+ parse(line) {
16
+ const ev = tryJson(line);
17
+ if (!ev)
18
+ return [];
19
+ const out = [];
20
+ if (ev.type === "message" && ev.role === "assistant" && str(ev.content)?.trim()) {
21
+ out.push({ kind: "text", text: str(ev.content) });
22
+ }
23
+ else if (ev.type === "tool_use") {
24
+ out.push({ kind: "tool", name: str(ev.tool_name) ?? "tool" });
25
+ }
26
+ else if (ev.type === "result") {
27
+ const stats = (ev.stats ?? {});
28
+ out.push({
29
+ kind: "usage",
30
+ inputTokens: num(stats.input_tokens) ?? num(stats.inputTokens),
31
+ outputTokens: num(stats.output_tokens) ?? num(stats.outputTokens),
32
+ });
33
+ out.push({ kind: "done", ok: ev.status !== "error", summary: str(ev.response) });
34
+ }
35
+ else if (ev.type === "error") {
36
+ out.push({ kind: "done", ok: false, summary: str(ev.message) ?? "Gemini 執行失敗" });
37
+ }
38
+ return out;
39
+ },
40
+ };
41
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1,8 @@
1
+ import { claude } from "./claude.js";
2
+ import { codex } from "./codex.js";
3
+ import { command } from "./command.js";
4
+ import { gemini } from "./gemini.js";
5
+ export const ADAPTERS = { claude, codex, gemini, command };
6
+ /** 沒有設定時,依序偵測這些已安裝的 CLI 組成輪替順序 */
7
+ export const DEFAULT_CYCLE = ["claude", "codex", "gemini"];
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ export function tryJson(line) {
2
+ try {
3
+ const v = JSON.parse(line);
4
+ return v && typeof v === "object" ? v : undefined;
5
+ }
6
+ catch {
7
+ return undefined;
8
+ }
9
+ }
10
+ export const num = (v) => (typeof v === "number" ? v : undefined);
11
+ export const str = (v) => (typeof v === "string" ? v : undefined);
12
+ //# sourceMappingURL=types.js.map