Jeffery jiantw83

cliproxyapi (0.0.8)

Published 2026-08-05 04:10:27 +00:00 by jiantw83

Installation

registry=https://gitea.jsc.idv.tw/api/packages/jiantw83/npm/
npm install cliproxyapi@0.0.8
"cliproxyapi": "0.0.8"

About this package

統一管理多個透過 OAuth 登入的 CLI 工具,並以 OpenAI 相容 API 對外派工

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 12:10:03

系統定位

面向 內容
對使用者 一個本機管理介面,看得到每個 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-robinpriority
  • 失敗換手:任一候選失敗(非零退出、逾時、輸出為空、或輸出中夾帶錯誤)就換下一個,最多 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,傳給 agyclaude-opus-4-6-thinkingcliModel: null 代表不傳 --model,改用工具自己的預設模型。
  • 名稱正規化:各家對同一個模型的寫法不同(agyclaude-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 時,只有實測成功的組合才會進派工池。

停用模型

「模型路由」頁每一列都有停用/啟用按鈕(對應 PUT /api/models/:modelenabled)。 停用後這個模型:

行為 結果
派工 不列入候選;直接指定會回 model_not_found
/v1/models 不列出
實測(probe) 跳過,不消耗額度
預熱(warmup) 跳過
模型路由頁 仍然列出(淡化並標示「已停用」),才能重新啟用

模型別名也會一併排除,不會出現「用別名還是叫得動已停用模型」的漏洞。

盤點結果(state.discoveredModels刻意保留完整,不因停用而刪除:盤點是一次列出全部模型的 CLI 呼叫,逐一過濾省不到成本,保留下來反而讓重新啟用不必再跑一次盤點。真正花額度的是實測與預熱, 這兩項才是停用要擋掉的。

安裝

前置條件只有 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 顯示版本
cliproxy runner register 向 server 註冊這台機器為遠端 runner(見 Runner
cliproxy runner daemon 啟動遠端 runner daemon(long polling 取任務)
cliproxy runner status 顯示遠端 runner 狀態
cliproxy runner --help 顯示 runner 子指令的參數

從 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.0.8

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/, 因此移除套件不會刪掉設定、實測結果與工作目錄,重新安裝即可接續使用。

Runner

派工不直接呼叫 CLI,而是先變成一個 runner taskqueued → running → succeeded/failed/canceled), 再交給 runner 執行。預設只有一個內嵌的 Local Runner(就在 server process 內), 每個任務的生命週期都可從 /api/tasks 或管理介面查到。

flowchart LR
    A[派工請求] --> B[task queue<br/>queued]
    B --> C{runner registry}
    C --> D[Local Runner<br/>同一個 process]
    C --> E[Remote Runner<br/>另一台機器的 daemon]
    D --> F[executor:host 或 isolated]
    F --> G[實際執行 CLI]

這層抽象是為了讓 CLI 能跑在別台機器上(例如把額度分散到多台、或在沒有 GUI 的伺服器上跑)。

註冊遠端 runner

步驟 1:在 server 端取得註冊金鑰

最簡單的方式是開管理介面的**「金鑰」頁 → Runner 註冊金鑰**:預設遮蔽顯示, 可按「顯示」查看、「複製」複製完整金鑰,或「重新產生」輪替。

也可以用 API(需要管理金鑰):

# 查看目前的註冊金鑰
curl -H 'x-admin-key: <adminKey>' \
  http://<server>:8317/api/runners/registration-token
# → {"exists":true,"token":"rreg-…","masked":"rr***…"}

# 尚未產生時建立一把(已存在則直接回傳原本那把)
curl -H 'x-admin-key: <adminKey>' -X POST \
  http://<server>:8317/api/runners/registration-token

# 輪替(舊金鑰立即失效)
curl -H 'x-admin-key: <adminKey>' -X POST \
  http://<server>:8317/api/runners/registration-token/rotate

同一把註冊金鑰可重複使用,多台 runner 共用即可。

註冊金鑰與 apiKeysadminKeys 不同,是以明文保存的:server 必須拿原值和 runner 送來的值比對,沒有「只存雜湊」的空間,因此它可以隨時查看與複製。 輪替只影響「之後的註冊」,已註冊的 runner 用各自的 runner token,不受影響

步驟 2:在要當 runner 的機器上註冊

該機器需先安裝本套件(npm install -g cliproxyapi …),並確保連得到 server (server 需綁 0.0.0.0,見環境變數):

cliproxy runner register \
  --server http://<server>:8317 \
  --token <registration-token> \
  --name lab-01 \
  --label "os:linux,gpu:no"     # 選填,預設帶 os / arch
參數 說明
--server server 位址,預設 http://127.0.0.1:8317;也可用 CLIPROXY_SERVER
--token 步驟 1 的註冊 token;也可用 CLIPROXY_RUNNER_REGISTRATION_TOKEN
--name runner 名稱,預設為主機名稱
--label 逗號分隔的標籤,供後續挑選候選使用

註冊成功後會把 runner id 與 runner token 寫進 ~/.cliproxyapi/runner.json(權限 0600)。 runner 只持有這把 token,不需要也不應該拿到管理金鑰——否則等於把主機管理權限發給每台 runner。

步驟 3:啟動 daemon 並確認狀態

cliproxy runner daemon --server http://<server>:8317 [--poll-timeout 30]
cliproxy runner status

daemon 會在註冊時與每次心跳回報本機偵測到的 CLI 工具(5 分鐘 TTL 快取), 因此管理介面「Runner」頁看得到那台機器上有哪些工具、版本與登入狀態。

步驟 4:在管理介面查看與管理

「Runner」頁列出所有 runner,可切換檢視、啟用/停用,遠端 runner 也可移除。 移除只是把它從清單拿掉;該機器的 daemon 若仍在執行,心跳會開始失敗, 需要重新 register 才會再出現。

daemon 以 long polling 維持心跳(heartbeat <時間> tasks=<數量>),Ctrl+C 結束。 runner status 會顯示本地設定(token 已遮蔽)與 server 端看到的狀態。

注意事項

沒有 HTTPS:runner token 以明文經 HTTP 傳輸,只適合在信任的內部網段使用。

註冊只是握手,daemon 才是上線條件。 register 做的是換取 runner token、 把當下的工具清單送給 server;真正讓這台機器參與派工的是 daemon

狀態 條件 是否參與派工
offline 已註冊但 daemon 未執行(尚未收到心跳),或心跳中斷超過 60 秒
online daemon 正在 long polling
disabled 在管理介面停用

只有收到過心跳才算 online——註冊當下不算。否則會有一段時間 runner 看起來線上、 請求被派過去,卻要等到領取逾時才換手。

註冊資料保存在 ~/.cliproxyapi/runners.json(含 runner token,權限 0600), server 重啟後會自動還原,不需要重新註冊;但重啟後狀態一律是 offline, 直到該機器的 daemon 再次 poll——server 無從得知對方 daemon 是否還活著。

遠端 runner 如何參與派工

遠端 runner 會與 Local Runner 一起成為候選,同一個模型可以同時有本機與遠端兩個候選:

sequenceDiagram
    participant C as 呼叫端
    participant S as Server
    participant D as Remote daemon
    C->>S: POST /v1/chat/completions
    S->>S: 候選排序(本機 + 遠端)
    S->>S: task 放入該 runner 的待領佇列
    D->>S: poll(long polling,同時是心跳)
    S-->>D: 回傳 task
    D->>D: 執行 CLI
    D->>S: report 結果
    S-->>C: 200 回應(x_cliproxy.runner 標示執行者)

判斷「有沒有執行者」時看的是所有線上 runner,而不是 server 本機: server 本身沒安裝該 CLI,只要有遠端 runner 裝了並登入,該模型一樣可派工。 每次嘗試的軌跡(x_cliproxy.attempts)都會帶上 runner 欄位,看得出是誰執行的。

情境 行為
本機與遠端都可用 兩者都成為候選,依 dispatch.strategy 排序,失敗自動換手
本機工具冷卻中 只排除本機候選;同一個工具在遠端未必有問題,遠端照常參與
daemon 未執行 任務 20 秒內沒被領取就失敗換手,不會空等到執行逾時
runner 被停用或移除 等待中的任務立即失敗換手,不會卡住請求
執行逾時 dispatch.timeoutSeconds + 30 秒緩衝

daemon 預設同時只跑一個任務(--concurrency 可調)。任務在背景執行、心跳不中斷—— 否則 CLI 跑超過 60 秒就會被 server 判定離線。

串流:daemon 邊執行邊把片段 POST 回 /api/runners/:id/tasks/:taskId/delta, server 再轉交給等待中的串流回呼,因此遠端執行也有逐字輸出。 片段會先累積到一定長度才送出,避免每個 token 一個 HTTP 請求。 實測片段提前約 2.8 秒送達(首塊 7.8 秒 vs 結束 10.6 秒); 實際片段數取決於該 CLI 的輸出粒度,streamable: false 的工具(如 Claude Code)本來就只有一次輸出。

盤點、實測與預熱都會在實際執行的那台機器上跑

動作 行為
盤點(discover) 遠端工具以 kind: discover 的 task 派過去執行,結果存回該 runner 的工具資料
實測(probe) 對「runner × 工具 × 模型」逐一實測;結果依 runner 分開保存在 model-probe.jsonrunners 維度
預熱(warmup) 對每個線上 runner 的可派工工具各發一次短提示

本機測出來的結果不代表遠端那台的狀況——可能根本沒裝,或登入的是不同帳號與方案。 遠端沒有自己的實測紀錄時,會退回沿用本機結果(「模型不存在」這類永久性失敗與機器無關)。

Executor

Runner 決定「在哪台機器執行」,executor 決定「用什麼環境執行」:

Executor 環境
host 目前主機的 PATH / HOME
isolated 工具專屬的 HOME / PATH / installDir(見隔離 CLI 執行環境

介面固定,後續要加 container / ssh executor 只需補上同一組方法。

隔離 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>; 以官方腳本安裝的 CLI(Antigravity)則透過該腳本的 --dir 參數指定同一個位置 (定義中的 install.isolatedCommand)。

快速開始

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 資料目錄 ~/.cliproxyapi
CLIPROXY_HOST 綁定位址 127.0.0.1
CLIPROXY_PORT 連接埠 8317
CLIPROXY_LOG_LEVEL 日誌等級(TRC/DBG/INF/WRN/ERR) INF

CLIPROXY_HOSTCLIPROXY_PORT 屬於「部署環境」而非使用者偏好,因此:

  • 優先序高於 config.json——設定檔已有 server.host 時仍以環境變數為準,啟動日誌會印出覆寫來源。
  • 不會被寫回 config.json——即使之後從管理介面存檔,設定檔仍保留原本的值, 不會把當下環境的綁定位址固化進去(拿掉環境變數就回到設定檔的值)。

對外開放(監聽所有介面):

CLIPROXY_HOST=0.0.0.0 cliproxyapi
CLIPROXY_HOST=0.0.0.0 npm start        # 或在專案目錄下

開放前請先讀存取控制/v1 要設 apiKeys,管理介面要設 adminKeys。 綁定非本機位址而金鑰未設定時,啟動日誌會印出 WRN 警告。

存取控制

兩組金鑰,權限範圍刻意分開——「叫用模型」和「管理這台主機上的 CLI 工具」風險差很多, /v1 的金鑰若同時是管理金鑰,等於把 OAuth 憑證匯出、工具登出、改設定的權限一起交出去。

金鑰 保護範圍 為空時的行為
apiKeys /v1/*(OpenAI 相容端點) 完全不驗證,任何人都能叫用模型
adminKeys /api/*(管理 API) 只接受本機(loopback)來源,遠端一律 401

adminKeys 為空時遠端直接拒絕,因此「綁 0.0.0.0 卻忘了設金鑰」不會讓管理 API 裸奔; 代價是遠端管理介面在設定 adminKeys 前無法使用(本機仍可正常操作)。

首次啟動自動產生管理金鑰

adminKeys 為空時,服務啟動會自動產生一把並寫入設定檔,明文只印在啟動主控台一次

────────────────────────────────────────────────────────────────
  已自動產生管理金鑰(只會顯示這一次,設定檔僅保存雜湊)

    cpadm-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

  用途:管理介面登入、以及 /api/* 的 Authorization: Bearer / x-admin-key
────────────────────────────────────────────────────────────────

明文不會進入日誌緩衝區,因此事後從 /api/logs、系統日誌頁或 SSE 都撈不回來—— 緩衝區只留一則「已自動產生管理金鑰」的提示。這讓「只顯示一次」是真的, 而不是顯示層遮蔽而已。錯過了不必緊張:在本機用管理介面的「金鑰」頁再建一把即可。

金鑰管理

管理介面「金鑰」頁可以建立與刪除兩種金鑰;對應 API:

方法 路徑 說明
GET /api/keys 列出兩種金鑰的摘要(id/備註/提示/建立時間),不含明文與雜湊
POST /api/keys 建立金鑰,body { "type": "api"|"admin", "label": "備註" }回應是唯一一次拿到明文的機會
DELETE /api/keys/:id?type=api|admin 刪除金鑰

設定檔存的是 SHA-256 雜湊,不是明文:

{
  "adminKeys": [
    { "id": "key-…", "label": "首次啟動自動產生", "hint": "cpadm-RByamkOqBp…dAYb",
      "hash": "f8b16fd3…", "createdAt": "2026-08-04T10:18:28.331Z" }
  ]
}

因此金鑰弄丟就只能重建,這是刻意的設計——設定檔外流也拿不回金鑰。 hint 只保留頭尾各一小段,供介面辨認是哪一把。

手動設定的明文金鑰仍然可用:直接在陣列裡放字串即可(顯示為「明文」), 方便手動編輯 config.json 或沿用舊設定,驗證時兩種格式都會比對。

管理金鑰可用三種方式提供:

curl -H 'Authorization: Bearer <adminKey>' http://<host>:8317/api/state
curl -H 'x-admin-key: <adminKey>'          http://<host>:8317/api/state

第三種是瀏覽器用的 session cookie:管理介面偵測到 401 會跳出登入框, 輸入金鑰後呼叫 POST /api/session 換取 HttpOnly + SameSite=Strict cookie(有效 12 小時,服務重啟即失效)。 之所以需要 cookie,是因為即時日誌與作業輸出走 EventSource,而它無法自訂請求標頭。

GET /api/config 回傳的金鑰只有摘要、沒有明文;PUT /api/configapiKeys / adminKeys 只接受明文字串陣列(手動設定用),送回摘要物件會被擋下(回 400)。 新增或刪除金鑰請用 /api/keys,省略該欄位即保留原值。

豁免驗證的端點GET/POST/DELETE /api/session(登入本身), 以及遠端 runner 用 runner token 自我驗證的 POST /api/runners/register/api/runners/:id/poll/api/runners/:id/tasks/:taskId/reportGET /api/runners/:id/api/runners 清單仍需管理金鑰)。/ui/ 靜態檔本身不含機密,不設限;沒有金鑰時介面只會停在登入框。

以 OpenAI SDK 呼叫:

from openai import OpenAI
# api_key 對應 config.apiKeys 其中一組;apiKeys 為空時不驗證,隨便填即可
client = OpenAI(base_url="http://127.0.0.1:8317/v1", api_key="sk-...")
print(client.chat.completions.create(
    model="claude-haiku-4.5",
    messages=[{"role": "user", "content": "台北今天天氣如何?"}],
).choices[0].message.content)

管理介面

七個頁籤(介面文案一律繁體中文;候選標籤等英文 slug 只在顯示層轉換,原值保留在 tooltip):

頁籤 功能
CLI 工具 針對單一 Runner(頁內有 Runner 選擇器)。每個工具一張卡片:狀態徽章、版本/最新版、認證方式、憑證到期、憑證檔數、支援模型、用量統計;按鈕可安裝、OAuth 登入、登出、啟用/停用;可展開原始檢查資訊
模型路由 模型 → 工具矩陣,顯示派工順序與各工具當下是否可用;可逐一停用/啟用模型
測試呼叫 直接對 /v1/chat/completions 發一次請求,顯示由哪個工具完成與 failover 軌跡
執行者(Runner) 每個 runner 一張卡片:狀態、主機/OS、版本、心跳、任務數、可派工模型、回報的 CLI 工具與最近任務;可切換檢視、啟用/停用,遠端 runner 可移除
派工紀錄 近期請求的模型、結果、逐一嘗試的工具與耗時
金鑰 建立/刪除 API 金鑰與管理金鑰(新建的只顯示一次);查看/複製/輪替 Runner 註冊金鑰
系統日誌 即時日誌(SSE),格式 [yyyy/MM/dd HH:mm:ss][階段][等級]: 訊息

頂端只保留工具狀態摘要(涵蓋所有 Runner)。系統資源列移到「CLI 工具」頁, 跟著選定的 Runner 走:本機向 server 取樣,遠端 runner 的資源由它自己的 daemon 隨心跳回報,daemon 未執行時會明確顯示「資源尚未回報」而不是空白數字。

Runner 切換只在「CLI 工具」頁:CLI 工具是安裝在特定機器上的,所以那一頁必須指定操作對象; 其餘頁面(模型路由、測試呼叫、派工紀錄、頂端摘要)一律呈現所有 Runner 的整合結果, 模型候選會標示來自哪台機器(例如 codex@lab-01)。

Runner 的啟用/停用/移除只在「Runner」頁操作,工具頁不提供——同一件事散落兩處容易誤觸, 在工具頁按下去會讓整台機器退出派工。

選定遠端 Runner 時,工具卡片的安裝/登入/登出/啟停用按鈕會隱藏—— 管理 API 沒有遠端執行這些動作的通道,照常顯示會讓按鈕默默作用到 server 本機。 這些操作請直接在該機器上進行。

畫面自動保持最新,沒有手動「重新檢查」按鈕,更新分三層:

層次 時機 成本
SSE 事件推送 工具狀態變更、派工、任務、日誌
狀態輪詢 每 5 秒讀一次 /api/state 低(純記憶體讀取)
CLI 偵測 伺服器定時檢查(check.intervalMinutes,預設 5 分鐘)、切換到「CLI 工具」頁時(至少間隔 60 秒)、以及安裝/登入/登出/啟停用完成後 高(會啟動 CLI 子行程)

真正花時間的是最後一層,因此才做節流;前兩層足以讓已知狀態即時反映在畫面上。

設有 adminKeys 時,介面開啟後會先跳出登入框,通過驗證才會載入資料與建立 SSE 連線; session 過期或被登出時,任何一支 API 回 401 都會重新跳出登入框。

安裝與登入都以「作業(job)」呈現,輸出即時串流到對話框,並自動把偵測到的 OAuth 授權網址變成可點連結。 需要互動終端才能登入的工具(Claude Code、Copilot CLI)會在管理介面開啟互動作業: 後端會以 pseudo-terminal 啟動 CLI、自動送入 /login,並把 OAuth URL 顯示成可點連結;必要時也可在作業對話框輸入內容送回 CLI。 登入作業會覆寫 BROWSER=true,避免 CLI 自動彈出瀏覽器;使用者需在管理介面明確點「開啟授權頁」。

API

管理 API(/api/*

整組 /api/*存取控制保護(adminKeys 為空時僅限本機來源)。

方法 路徑 說明
GET/POST/DELETE /api/session 查詢驗證狀態/以管理金鑰登入/登出(免驗證)
GET/POST /api/keys、DELETE /api/keys/:id 金鑰清單/建立(明文只回傳一次)/刪除
GET /api/health 健康檢查與狀態摘要
GET /api/state 摘要 + 工具清單 + 模型矩陣
GET /api/system/resources 本機資源取樣(CPU/記憶體/磁碟/程序/網路)
GET /api/tools/api/tools/:id 工具列表/詳情
PUT /api/tools/:id 更新指令、優先序、額外參數、環境變數、isolationoverrides
POST /api/tools/:id/enable 啟用/停用
POST /api/tools/:id/install/update 安裝(不可自動安裝者回傳手動指引)/更新到最新版
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/tools/:id/shell 以 pseudo-terminal 開啟真正的互動 CLI session
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 實測「runner × 工具 × 模型」;可用 runnerstoolsmodelsconcurrency 縮小範圍
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:工具狀態、日誌、派工事件
GET /api/runners Runner 狀態清單
GET/POST /api/runners/registration-token 查看/產生 runner 註冊金鑰
POST /api/runners/registration-token/rotate 輪替註冊金鑰(舊的立即失效,已註冊 runner 不受影響)
POST /api/runners/register/api/runners/:id/poll/api/runners/:id/tasks/:taskId/report/delta 遠端 runner 註冊/long polling 取任務/回報結果/串流片段(以 runner token 自我驗證)
GET /api/runners/:id 單一 runner 狀態;以 runner token 自我驗證(runner status 用)
POST /api/runners/:id/enable 啟用/停用 runner
DELETE /api/runners/:id 移除遠端 runner(Local Runner 不可移除)
GET /api/tasks/api/tasks/:id/api/tasks/:id/stream Runner task 列表/詳情/SSE
POST /api/tasks/:id/cancel 取消 runner task

OpenAI 相容 API(/v1/*

狀態 端點
已實作 GET /v1/modelsGET /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_cliproxyrequest_idtoolattempts),並在 header 回 x-cliproxy-toolusage 的 token 數是以字元數估算(標記 x_estimated: true),不是工具實際計費 token。

設定檔

{
  "server": { "host": "127.0.0.1", "port": 8317, "shutdownGraceSeconds": 120 },
                                       // host / port 可被 CLIPROXY_HOST / CLIPROXY_PORT 覆寫
  "apiKeys": [],                       // 非空時 /v1 需帶 Bearer;元素為雜湊記錄或明文字串
  "adminKeys": [],                     // /api/* 的管理金鑰;為空時首次啟動會自動產生一把
  "check": { "intervalMinutes": 5, "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 } }
                                       // enabled: false = 停用;不派工、不實測、不預熱
}

各 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.svgfavicon.icoicon-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                設定檔讀寫、資料目錄、環境變數覆寫
├── keys.js                  金鑰模型:雜湊保存、驗證、建立(明文只回傳一次)
├── logger.js                日誌(台灣時區、環形緩衝、SSE 廣播)
├── util.js                  識別碼、遮蔽、路徑展開、參數樣板
├── tools/
│   ├── definitions.js       CLI 工具目錄(宣告式:版本/登入/安裝/執行/模型/盤點)
│   ├── exec.js              子行程執行與串流
│   ├── model-discovery.js   向工具問出可用模型(讀快取檔或跑列出指令)
│   ├── isolation.js         executor 層的相容匯出(新程式碼請直接用 executors/)
│   └── registry.js          三態偵測、資訊蒐集、安裝/登入/登出、定時檢查
├── routing/
│   ├── run-tool.js          單一工具執行 + 輸出萃取 + 失敗判讀
│   ├── model-probe.js       模型實測與結果保存、暫時/永久失敗分類、停用模型過濾
│   └── dispatcher.js        模型索引、候選排序、失敗換手
├── runner/
│   ├── types.js             runner/task 共用狀態常數與 DTO
│   ├── task-queue.js        task 生命週期(queued → running → succeeded/failed/canceled)
│   ├── local-runner.js      內嵌 runner:把 task 轉交給 run-tool
│   ├── registry.js          runner 註冊(持久化)、心跳、候選選擇、遠端執行與串流轉交
│   └── daemon-cli.js        遠端 runner CLI:註冊、心跳、領取並執行 task、回報與串流片段
├── executors/
│   └── index.js             執行環境分層:host(主機 PATH/HOME)與 isolated(工具專屬 HOME)
├── system/
│   └── resources.js         本機資源取樣(Linux 讀 /proc,其他平台退回 os API)
├── http/
│   ├── server.js            極簡 HTTP 框架(路由、中介層、SSE、靜態檔、CORS)
│   ├── auth.js              管理 API 存取控制(adminKeys、session cookie、loopback 規則)
│   ├── routes-mgmt.js       管理 API
│   └── routes-openai.js     OpenAI 相容 API
└── ui/                      管理介面(原生 HTML/CSS/JS)

安全性

  • 預設綁定 127.0.0.1。若要對外開放,務必先設定 apiKeysadminKeys(見存取控制)。
  • CLI 工具在 dispatch.workDir 下執行,與呼叫端的專案目錄隔離。
  • Copilot CLI 的非互動模式必須 --allow-all-tools,因此預設額外加上 --deny-tool=shell,避免呼叫端取得主機的任意指令執行能力。需要完整 agentic 行為請自行在 overrides.exec.args 放寬。
  • 日誌、作業輸出與 API 回應都會過濾常見機密樣式(gh*_sk-ya29.Bearer*_token),帳號類識別字串一律遮蔽後才輸出。

本機驗證結果

工具狀態(2026/08/04)

工具 執行檔 偵測狀態 版本 判定依據
Antigravity CLI agy 登入 1.1.10 agy models 列得出模型(憑證在 OS keyring,沒有檔案可查)
Copilot CLI copilot 啟用 1.0.78 已安裝但未登入(需 gh auth login 或互動 /login
Codex CLI codex 登入 0.146.0 codex login status
Claude Code claude 登入 2.1.221 憑證檔並讀出到期時間

遠端 runner 完整功能(2026/08/05)

除派工外,另實測串流、盤點、實測、預熱與註冊持久化:

檢查 結果
註冊資料寫入 runners.json(權限 600、含 token)
server 重啟後自動還原註冊,舊 runner token 直接可用
串流:片段提前送達(首塊 7.8 秒 vs 結束 10.6 秒)
遠端盤點 codex 取得 7 個模型
遠端實測 codex × gpt-5.4 成功,結果存進 runners 維度
遠端預熱 codex 成功(10.3 秒)

遠端 runner 派工(2026/08/05)

在 server 本機停用全部 4 個 CLI 工具(可派工工具數 0)的情況下實測:

檢查 結果
遠端 runner 回報 4 個工具,模型矩陣出現 29/31 個可派工模型
POST /v1/chat/completions 由遠端完成,回傳 ok 11.2 秒
軌跡標示 runner: runner-…executor: host
CLI 執行期間 daemon 持續心跳(running=1),未被判離線
daemon 停止時,20 秒內失敗換手(非等到 300 秒執行逾時)
執行中停用 runner,3 秒內立即失敗
本機與遠端同時可用時,同一模型出現兩個候選

存取控制(2026/08/04)

30 項端到端測試全數通過,涵蓋:遠端無金鑰/錯誤金鑰一律 401(含憑證匯出)、 三種憑證方式(Authorization: Bearerx-admin-key/session cookie)皆可通過、 apiKeysadminKeys 互打對方端點皆遭拒、SSE 帶 cookie 才通、 runner 自我驗證端點正確豁免而 registration-token 仍受保護、 adminKeys 為空時本機可用而遠端 401、環境變數覆寫優先且不寫回設定檔。

模型實測(2026/07/28)

實測 30 個「工具 × 模型」組合,20 個成功

工具 實測成功的對外模型名稱 傳給 CLI 的值
antigravity gemini-3.6-flash / gemini-3.5-flash / gemini-3.1-pro 對應 -medium / -high slug
antigravity claude-sonnet-4.6claude-sonnet-4-6 claude-sonnet-4-6
antigravity claude-opus-4.6claude-opus-4-6-thinking claude-opus-4-6-thinking
antigravity gpt-oss-120b-medium 同名
codex gpt-5.6-solgpt-5.6-terragpt-5.6-lunagpt-5.5gpt-5.4gpt-5.4-mini 同名
codex codex-default 不傳 --model(用 config.toml 預設)
claude claude-fable-5claude-opus-5claude-sonnet-5claude-haiku-4-5claude-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.6gpt-5.4claude-haiku-4.5 就會各有兩個候選)。

後續項目

  1. 補齊其餘 OpenAI 端點(Files / Vector Store / Batch / Embeddings / Audio / Images / Realtime / Admin…)。
  2. Copilot 登入後校正其模型清單與指令樣板(目前 verified: false)。
  3. tools / tool_choice 函式呼叫與結構化輸出(response_format)對映到各 CLI 的能力。
  4. 依工具實際回報的 token 用量取代估算值。
  5. 定期自動重測暫時性失敗的組合(目前需手動觸發)。
  6. 遠端 runner 的 HTTPS 傳輸(目前 runner token 走明文 HTTP,只適合信任網段)。
Details
npm
2026-08-05 04:10:27 +00:00
2
UNLICENSED
217 KiB
Assets (1)
Versions (18) View all
0.1.8 2026-08-05
0.1.7 2026-08-05
0.1.6 2026-08-05
0.1.5 2026-08-05
0.1.4 2026-08-05