cliproxyapi (0.1.5)
Installation
registry=https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/npm install cliproxyapi@0.1.5"cliproxyapi": "0.1.5"About this package
CLIProxyAPI
統一管理多個透過 OAuth 登入的 CLI 工具(Antigravity agy / Codex / Copilot / Claude Code),
並以 OpenAI 相容 API 對外提供服務:收到請求後依模型挑選最適合的 CLI 工具執行,失敗自動換手。
- 執行環境:Node.js 20.11 以上
- 外部依賴:無(只使用 Node 內建模組,
npm install不會下載任何套件) - 安裝:一行
npm install -g .取得cliproxyapi指令(見 安裝) - 更新時間:2026/08/05 17:54:00
系統定位
| 面向 | 內容 |
|---|---|
| 對使用者 | 一個本機管理介面,看得到每個 CLI 工具的狀態,並能安裝、啟用/停用、OAuth 登入、登出、直接下提示測試 |
| 對其他系統 | 一個 OpenAI 相容端點,只要把 SDK 的 base_url 指過來即可使用 |
| 對維運 | 定時檢查工具狀態,偵測「登入失效」與「有新版本」;每次派工都留下嘗試軌跡 |
工具三態
stateDiagram-v2
[*] --> 停用
停用 --> 啟用: 安裝完成且啟用
啟用 --> 登入: 完成 OAuth 登入
登入 --> 啟用: 憑證到期/登入失效
啟用 --> 停用: 使用者停用或移除安裝
登入 --> 停用: 使用者停用
note right of 停用
可詢問是否安裝
蒐集:安裝方式、最新版本、官方文件
end note
note right of 啟用
引導 OAuth 登入
蒐集:執行檔路徑、版本、可更新、憑證檔位置、環境變數
end note
note right of 登入
加入 API 派工池
蒐集:認證方式、憑證到期時間、方案、支援模型、用量與健康度
end note
判定規則:
| 條件 | 狀態 |
|---|---|
| 未安裝,或被使用者停用 | disabled(停用) |
| 已安裝且啟用,但未通過認證 | enabled(啟用) |
| 已安裝、啟用且通過認證 | logged_in(登入)— 只有這個狀態會參與派工 |
認證判定依序採用:狀態指令輸出 → 憑證檔存在(並讀取到期時間等非機密欄位)→ 環境變數 token。
憑證檔只讀取定義中明確列出的非機密欄位(例如 expiresAt、方案名稱),不會讀取或回傳任何 token。
派工規則
flowchart LR
A[POST /v1/chat/completions<br/>model=gemini-3.5-flash] --> B{模型 → 工具索引}
B --> C[antigravity P10]
C -->|失敗或不可用| D[copilot P20]
D -->|失敗| E[下一個候選…]
E -->|全部失敗| F[502 dispatch_failed<br/>附完整嘗試軌跡]
C -->|成功| G[200 OpenAI 格式回應<br/>x_cliproxy.tool=antigravity]
- 候選來源:每個工具宣告自己支援的模型,聯集成「模型 → 工具」索引;只有
logged_in且未進入冷卻的工具會成為候選。 - 順序:預設
dispatch.strategy = latency-aware,依近期首字時間、總耗時與失敗率排序;優先使用「工具 × 模型」統計,沒有資料時退回工具整體平均,速度同分才用priority當備援排序。也可改成round-robin或priority。 - 失敗換手:任一候選失敗(非零退出、逾時、輸出為空、或輸出中夾帶錯誤)就換下一個,最多
dispatch.maxAttempts次。 - 首字逾時:串流請求遇到支援串流的 CLI 時,若超過
dispatch.firstTokenTimeoutSeconds仍沒有 stdout,會視為本次嘗試失敗並換手;一次性輸出的 CLI 不套用此規則。 - 回覆快取:
dispatch.responseCache.enabled開啟時,完全相同的非串流請求會在 TTL 內直接回傳記憶體快取;快取 key 只存 SHA-256,不保存原始 prompt。 - 冷卻:連續失敗會指數退避(60s → 120s → …,上限 15 分鐘),期間不列入候選。
- 串流限制:若工具在還沒吐出任何內容前失敗,會靜默換手;已經吐出內容才失敗則無法回溯,會以錯誤事件結束該次串流。
- 模型名稱轉換:對外模型名稱與 CLI 實際接受的名稱分開(
models[].cliModel)。例如對外claude-opus-4.6,傳給agy是claude-opus-4-6-thinking;cliModel: null代表不傳--model,改用工具自己的預設模型。 - 名稱正規化:各家對同一個模型的寫法不同(
agy用claude-sonnet-4-6、Copilot 用claude-sonnet-4.6),索引鍵會把.統一成-,這兩種寫法才會落在同一個候選清單裡互相 failover。
模型盤點與實測
「這個帳號到底能用哪些模型」不能靠猜,系統分兩步確認:
| 步驟 | 做法 | 產出 |
|---|---|---|
| 盤點(discovery) | 登入完成與重新檢查時自動執行(check.discoverModelsOnRefresh 可關閉):Codex 執行 codex debug models(只取 visibility: list)、Antigravity 執行 agy models;Copilot 與 Claude Code 沒有穩定非互動盤點介面,沿用靜態清單 |
state.discoveredModels |
| 實測(probe) | 對每個「工具 × 模型」實際下一句最短提示(只輸出兩個字元:ok),真的回話才算成功 |
~/.cliproxyapi/model-probe.json |
失敗會分成兩類,這個區分很重要:
- 永久性:模型不存在、名稱寫錯、方案不支援 → 直接從派工池排除。
- 暫時性:額度用盡、容量不足、限流、伺服器忙碌(訊息含 quota/capacity/rate limit…)→ 保留在池中,等恢復即可繼續派工。
node scripts/probe-models.mjs # 盤點 + 實測所有登入中的工具(或 npm run probe)
node scripts/probe-models.mjs --list # 只盤點,不花額度(或 npm run probe -- --list)
node scripts/probe-models.mjs codex claude # 只測指定工具
node scripts/probe-models.mjs --concurrency 3
管理介面「模型路由」頁提供兩個分開的操作:「刷新模型清單」對應 POST /api/models/discover,可手動重跑盤點且不做推論實測;「實測所有可用組合」對應 POST /api/models/probe,會真的發短提示。
dispatch.requireProbe 設為 true 時,只有實測成功的組合才會進派工池。
安裝
前置條件只有 Node.js 20.11 以上(node -v 確認)。專案零依賴,安裝過程不會向 registry 下載任何套件。
一鍵安裝(推薦,取得全域指令)
git clone https://gitea.jsc.idv.tw/jiantw83/CLIProxyAPI.git && cd CLIProxyAPI && npm install -g .
已經 clone 過的話,在專案目錄下這行就夠了:
npm run install:global
安裝完成後多了兩個等價的全域指令:
| 指令 | 說明 |
|---|---|
cliproxyapi |
啟動服務(Ctrl+C 結束) |
cliproxy |
同上,短別名 |
cliproxyapi --help |
顯示用途、管理介面/API 位址、設定檔位置、環境變數 |
cliproxyapi --version |
顯示版本 |
從 Gitea npm 套件庫安裝
已發布版本可直接從 Gitea npm 套件庫安裝:
npm install -g cliproxyapi --registry=https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/
若套件庫需要認證,先把具備 package 權限的 token 設到 npm 設定;不要把 token 直接寫進安裝指令或 shell 歷史:
npm config set -- '//gitea.jsc.idv.tw/api/packages/jiantw83/npm/:_authToken' "$GITEA_TOKEN"
npm config set registry https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/
npm install -g cliproxyapi
安裝完成後啟動服務:
cliproxyapi
也可以使用短別名:
cliproxy
啟動後開啟管理介面:
http://127.0.0.1:8317/ui/
目前套件頁:
https://gitea.jsc.idv.tw/jiantw83/-/packages/npm/cliproxyapi/0.1.5
Gitea npm registry 的 URL 必須包含 owner 與 /npm/:
https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/
不要使用 https://gitea.jsc.idv.tw/api/packages/jiant/cliproxyapi 這類缺少 /npm/ 的路徑,npm 會回 E404 Not Found。
直接從 Gitea 安裝(不保留原始碼)
npm install -g "git+https://gitea.jsc.idv.tw/jiantw83/CLIProxyAPI.git#develop"
此 repo 非公開,需要先有 Gitea 存取權(已設定 git credential helper 或 SSH key); 不要把 token 直接寫在指令裡,避免留在 shell 歷史紀錄。沒有存取權時請用上面的 clone 方式。
不安裝,直接在專案目錄執行
npm start # 等同 node src/index.js
npm run dev # 檔案變更自動重啟
npm 指令一覽
| 指令 | 用途 |
|---|---|
npm start |
啟動服務 |
npm run dev |
開發模式(node --watch) |
npm run install:global |
把目前原始碼安裝成全域指令 |
npm run uninstall:global |
移除全域指令 |
npm run probe |
模型盤點與實測(參數用 npm run probe -- --list) |
npm run icons |
重新產生所有圖示 |
更新與移除
| 動作 | 指令 |
|---|---|
| 更新 | git pull && npm run install:global |
| 移除 | npm run uninstall:global(或任意目錄下 npm uninstall -g cliproxyapi) |
全域安裝只放置程式碼與指令;設定與狀態一律留在 ~/.cliproxyapi/,
因此移除套件不會刪掉設定、實測結果與工作目錄,重新安裝即可接續使用。
隔離 CLI 執行環境
若開發主機上的 Codex / Claude / Copilot 已安裝大量 skill、MCP 或個人設定,CLI 啟動時間可能變長。
可針對單一工具啟用 tools.<id>.isolation,讓代理使用獨立 HOME / XDG / CODEX_HOME / CLAUDE_CONFIG_DIR 啟動 CLI,
避免讀取開發主機的 ~/.codex、~/.claude 等設定。
隔離模式一律只使用 ~/.cliproxyapi/tools/<tool> 或隔離 HOME 內安裝的 binary;若尚未安裝,工具會顯示為未安裝。
{
"tools": {
"codex": {
"isolation": {
"enabled": true,
"cleanEnv": true
}
}
}
}
| 設定 | 效果 |
|---|---|
enabled: true |
使用隔離安裝版 binary 與 ~/.cliproxyapi/isolated/<tool>/home 作為 CLI HOME,避免讀取開發主機工具與設定 |
cleanEnv: true |
只保留 PATH / LANG / TERM 等最小環境,再套用工具自己的 env |
啟用隔離後,該工具必須先安裝到隔離目錄,並需要在隔離 HOME 內重新登入一次;這是預期行為。
對 npm 安裝的 CLI,管理介面的「安裝」會裝到 ~/.cliproxyapi/tools/<tool>。
快速開始
cliproxyapi # 全域安裝後,任意目錄都可啟動
npm start # 或在專案目錄下執行(等同 node src/index.js)
啟動後:
| 位置 | 說明 |
|---|---|
http://127.0.0.1:8317/ui/ |
管理介面 |
http://127.0.0.1:8317/v1 |
OpenAI 相容端點(SDK 的 base_url) |
~/.cliproxyapi/config.json |
設定檔(首次啟動採用預設值,透過介面或 API 修改後才寫檔) |
~/.cliproxyapi/work/ |
CLI 工具的執行目錄(與呼叫端專案隔離) |
環境變數:CLIPROXY_HOME(資料目錄)、CLIPROXY_HOST、CLIPROXY_PORT、CLIPROXY_LOG_LEVEL(TRC/DBG/INF/WRN/ERR)。
以 OpenAI SDK 呼叫:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8317/v1", api_key="unused")
print(client.chat.completions.create(
model="claude-haiku-4.5",
messages=[{"role": "user", "content": "台北今天天氣如何?"}],
).choices[0].message.content)
管理介面
五個頁籤:
| 頁籤 | 功能 |
|---|---|
| CLI 工具 | 每個工具一張卡片:狀態徽章、版本/最新版、認證方式、憑證到期、憑證檔數、支援模型、用量統計;按鈕可安裝、OAuth 登入、登出、啟用/停用、重新檢查;可展開原始檢查資訊 |
| 模型路由 | 模型 → 工具矩陣,顯示派工順序與各工具當下是否可用 |
| 測試呼叫 | 直接對 /v1/chat/completions 發一次請求,顯示由哪個工具完成與 failover 軌跡 |
| 派工紀錄 | 近期請求的模型、結果、逐一嘗試的工具與耗時 |
| 系統日誌 | 即時日誌(SSE),格式 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息 |
安裝與登入都以「作業(job)」呈現,輸出即時串流到對話框,並自動把偵測到的 OAuth 授權網址變成可點連結。
需要互動終端才能登入的工具(Claude Code、Copilot CLI)會在管理介面開啟互動作業:
後端會以 pseudo-terminal 啟動 CLI、自動送入 /login,並把 OAuth URL 顯示成可點連結;必要時也可在作業對話框輸入內容送回 CLI。
登入作業會覆寫 BROWSER=true,避免 CLI 自動彈出瀏覽器;使用者需在管理介面明確點「開啟授權頁」。
API
管理 API(/api/*)
| 方法 | 路徑 | 說明 |
|---|---|---|
| GET | /api/health |
健康檢查與狀態摘要 |
| GET | /api/state |
摘要 + 工具清單 + 模型矩陣 |
| GET | /api/tools、/api/tools/:id |
工具列表/詳情 |
| PUT | /api/tools/:id |
更新指令、優先序、額外參數、環境變數、isolation、overrides |
| POST | /api/tools/:id/enable |
啟用/停用 |
| POST | /api/tools/:id/install |
安裝(不可自動安裝者回傳手動指引) |
| POST | /api/tools/:id/login、/logout |
OAuth 登入引導/登出 |
| POST | /api/tools/:id/credentials/export |
匯出 OAuth 憑證檔,需 confirm: "EXPORT_OAUTH_SECRETS",回傳檔案 base64 與整包 bundleBase64 |
| POST | /api/tools/:id/refresh、/api/refresh |
重新檢查單一/全部工具 |
| POST | /api/tools/:id/run |
指定工具直接執行一次提示(繞過派工) |
| POST | /api/dispatch/test |
走完整派工流程測試 |
| POST | /api/performance/warmup |
每個可派工 CLI 各發一次短提示,預熱 CLI 初始化並更新速度統計 |
| GET | /api/jobs/:id、/api/jobs/:id/stream、POST /api/jobs/:id/input、POST /api/jobs/:id/cancel |
作業狀態/輸出串流/互動輸入/取消 |
| GET | /api/models、PUT /api/models/:model |
模型矩陣/模型層級覆寫(限定工具、停用模型) |
| POST | /api/models/discover |
手動刷新模型清單;Codex 跑 debug models、Antigravity 跑 list 指令,Claude/Copilot 使用靜態清單 |
| POST | /api/models/probe |
實測「工具 × 模型」;可用 tools/models/concurrency 縮小範圍 |
| POST | /api/models/probe/job |
建立背景實測任務,搭配 /api/model-tasks/:id/stream 即時顯示逐項進度 |
| POST | /api/performance/warmup/job |
建立背景預熱任務,搭配 /api/model-tasks/:id/stream 即時顯示逐工具進度 |
| GET | /api/model-tasks/:id、/api/model-tasks/:id/stream |
模型背景任務狀態/SSE 進度串流 |
| GET | /api/config、PUT /api/config |
讀取/更新設定(金鑰遮蔽) |
| GET | /api/requests、/api/logs |
近期派工紀錄/日誌 |
| GET | /api/events |
SSE:工具狀態、日誌、派工事件 |
OpenAI 相容 API(/v1/*)
| 狀態 | 端點 |
|---|---|
| 已實作 | GET /v1/models、GET /v1/models/{model}、POST /v1/chat/completions(含 stream)、POST /v1/completions(含 stream)、POST /v1/responses(含 stream) |
| 尚未實作 | 其餘 /v1/* 一律回 501 + 標準錯誤物件,並在 x_cliproxy.implemented 附上目前支援清單 |
相容性細節:Bearer 驗證(config.apiKeys 為空時不驗證)、OpenAI 風格錯誤物件、SSE 以 [DONE] 結束、CORS。
每個回應額外附非標準欄位 x_cliproxy(request_id、tool、attempts),並在 header 回 x-cliproxy-tool。
usage 的 token 數是以字元數估算(標記 x_estimated: true),不是工具實際計費 token。
設定檔
{
"server": { "host": "127.0.0.1", "port": 8317, "shutdownGraceSeconds": 120 },
"apiKeys": [], // 非空時 /v1 需帶 Bearer
"check": { "intervalMinutes": 15, "runOnStart": true, "checkLatestVersion": true },
"dispatch": {
"strategy": "latency-aware", // 或 "round-robin" / "priority"
"maxAttempts": 2,
"requireProbe": false, // true = 只有實測成功的組合可派工
"timeoutSeconds": 300,
"firstTokenTimeoutSeconds": 30, // 串流 CLI 首次 stdout 逾時,0 = 停用
"fallbackOnNoOutput": true,
"warmupOnStart": false, // true = 啟動後預熱可派工工具,會消耗少量額度
"warmupTimeoutSeconds": 45,
"responseCache": {
"enabled": false, // true = 相同非串流請求在 TTL 內直接回覆
"ttlSeconds": 300,
"maxEntries": 100
},
"cooldownSeconds": 60,
"maxCooldownSeconds": 900,
"workDir": "~/.cliproxyapi/work"
},
"tools": {
"claude": {
"enabled": true,
"command": "claude",
"priority": 40,
"extraArgs": [],
"env": {},
"isolation": {
"enabled": false,
"homeDir": "~/.cliproxyapi/isolated/claude/home",
"installDir": "~/.cliproxyapi/tools/claude",
"cleanEnv": true
},
"overrides": { "exec": { "args": ["-p", "{prompt}", "--model", "{model}", "--output-format", "json"] } }
}
},
"models": { "gemini-3.5-flash": { "tools": ["antigravity", "copilot"], "enabled": true } }
}
各 CLI 的參數會隨版本改變,因此工具行為全部以資料描述在 src/tools/definitions.js,
並可用 tools.<id>.overrides 逐欄覆寫,不需要改程式碼。定義中 verified: false 表示指令樣板尚未實測。
圖示
設計概念:終端機提示符號 > 代表 CLI,右側扇出的三個節點代表被統一管理、依序輪巡的多個工具,
整體是一枚深色終端機徽章;節點顏色沿用管理介面的 accent 色系。
npm run icons # 重新產生所有圖示
| 檔案 | 用途 |
|---|---|
assets/icon.svg |
主圖(512 設計稿,含漸層與內圈) |
assets/icon-compact.svg |
小尺寸幾何:去掉連線、加粗符號、放大節點 |
assets/icon-mono.svg |
單色版(currentColor),可用於文件、徽章、深淺底 |
assets/icon-{16,32,48,64,128,180,256,512}.png |
點陣輸出;≤32px 自動採用 compact 幾何 |
assets/favicon.ico |
內含 16/32/48 三種尺寸 |
src/ui/icon.svg、favicon.ico、icon-180.png |
管理介面直接引用的副本 |
環境中沒有 librsvg/ImageMagick/sharp,因此 scripts/generate-icons.mjs 自行完成光柵化與編碼:
幾何只定義一次,SVG 與 PNG 由同一份定義輸出(PNG 走有號距離場 + 2×2 超取樣,
再以 node:zlib 編成 PNG/ICO),避免兩種格式外觀不一致,也維持專案零依賴。
目錄結構
package.json npm 設定:全域指令(cliproxyapi / cliproxy)與 start/dev/probe/icons
assets/ 圖示輸出(SVG / PNG / ICO)
scripts/
├── generate-icons.mjs 圖示產生器(含自製 PNG/ICO 編碼器)
└── probe-models.mjs 模型盤點與實測(輸出結果表)
src/
├── index.js 進入點:啟動 HTTP 服務與狀態檢查
├── config.js 設定檔讀寫、資料目錄、工具執行目錄
├── logger.js 日誌(台灣時區、環形緩衝、SSE 廣播)
├── util.js 識別碼、遮蔽、路徑展開、參數樣板
├── tools/
│ ├── definitions.js CLI 工具目錄(宣告式:版本/登入/安裝/執行/模型/盤點)
│ ├── exec.js 子行程執行與串流
│ ├── model-discovery.js 向工具問出可用模型(讀快取檔或跑列出指令)
│ └── registry.js 三態偵測、資訊蒐集、安裝/登入/登出、定時檢查
├── routing/
│ ├── run-tool.js 單一工具執行 + 輸出萃取 + 失敗判讀
│ ├── model-probe.js 模型實測與結果保存、暫時/永久失敗分類
│ └── dispatcher.js 模型索引、候選排序、失敗換手
├── http/
│ ├── server.js 極簡 HTTP 框架(路由、SSE、靜態檔、CORS)
│ ├── routes-mgmt.js 管理 API
│ └── routes-openai.js OpenAI 相容 API
└── ui/ 管理介面(原生 HTML/CSS/JS)
安全性
- 預設綁定
127.0.0.1。若要對外開放,務必先設定apiKeys。 - CLI 工具在
dispatch.workDir下執行,與呼叫端的專案目錄隔離。 - Copilot CLI 的非互動模式必須
--allow-all-tools,因此預設額外加上--deny-tool=shell,避免呼叫端取得主機的任意指令執行能力。需要完整 agentic 行為請自行在overrides.exec.args放寬。 - 日誌、作業輸出與 API 回應都會過濾常見機密樣式(
gh*_、sk-、ya29.、Bearer、*_token),帳號類識別字串一律遮蔽後才輸出。
本機驗證結果(2026/07/28)
| 工具 | 執行檔 | 偵測狀態 | 版本 | 判定依據 |
|---|---|---|---|---|
| Antigravity CLI | agy |
登入 | 1.1.8 | agy models 列得出模型(憑證在 OS keyring,沒有檔案可查) |
| Copilot CLI | copilot |
啟用 | 1.0.75 | 已安裝但未登入(需 gh auth login 或互動 /login) |
| Codex CLI | codex |
登入 | 0.145.0 | codex login status |
| Claude Code | claude |
登入 | 2.1.220 | 憑證檔並讀出到期時間 |
實測 30 個「工具 × 模型」組合,20 個成功:
| 工具 | 實測成功的對外模型名稱 | 傳給 CLI 的值 |
|---|---|---|
| antigravity | gemini-3.6-flash / gemini-3.5-flash / gemini-3.1-pro |
對應 -medium / -high slug |
| antigravity | claude-sonnet-4.6、claude-sonnet-4-6 |
claude-sonnet-4-6 |
| antigravity | claude-opus-4.6、claude-opus-4-6-thinking |
claude-opus-4-6-thinking |
| antigravity | gpt-oss-120b-medium |
同名 |
| codex | gpt-5.6-sol、gpt-5.6-terra、gpt-5.6-luna、gpt-5.5、gpt-5.4、gpt-5.4-mini |
同名 |
| codex | codex-default |
不傳 --model(用 config.toml 預設) |
| claude | claude-fable-5、claude-opus-5、claude-sonnet-5、claude-haiku-4-5、claude-opus-4-8 |
同名 |
10 個失敗全部是暫時性(Antigravity 的 gemini 系列額度用盡、gpt-5.3-codex-spark 容量不足),
不是模型不存在,因此仍留在派工池中。Copilot 的 6 個模型因未登入而無法實測。
已實測:模型盤點(agy models 11 個、Codex 快取 7 個)、模型實測與失敗分類、
/v1/models、/v1/chat/completions(非串流與串流)、失敗換手軌跡、501/404/400 錯誤格式、
管理介面與 SSE、POST /api/models/probe。
尚未實測:兩個工具皆登入時的「A 失敗 → B 成功」完整換手(Copilot 登入後 claude-sonnet-4.6、
gpt-5.4、claude-haiku-4.5 就會各有兩個候選)。
後續項目
- 補齊其餘 OpenAI 端點(Files / Vector Store / Batch / Embeddings / Audio / Images / Realtime / Admin…)。
- Copilot 登入後校正其模型清單與指令樣板(目前
verified: false)。 tools/tool_choice函式呼叫與結構化輸出(response_format)對映到各 CLI 的能力。- 依工具實際回報的 token 用量取代估算值。
- 定期自動重測暫時性失敗的組合(目前需手動觸發)。