Jeffery jiantw83

cliproxyapi (0.1.7)

Published 2026-08-05 10:20:36 +00:00 by jiantw83

Installation

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

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 18:19:56

系統定位

面向 內容
對使用者 一個本機管理介面,看得到每個 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(登入)— 只有這個狀態會參與派工

執行檔位置除了 PATH,還會查常見的使用者層級目錄(~/.local/bin~/bin/usr/local/bin/opt/homebrew/bin)。安裝器常裝到 ~/.local/bin 並自行提醒 「不在你目前的 PATH 中」,而服務或 runner daemon 的 PATH 是啟動當下決定的—— 少了這層 fallback,透過管理介面安裝完成後會出現「裝好了卻偵測不到」。

認證判定依序採用:狀態指令輸出憑證檔存在(並讀取到期時間等非機密欄位)→ 環境變數 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 時,只有實測成功的組合才會進派工池。

安裝

前置條件只有 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.7

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_HOSTCLIPROXY_PORTCLIPROXY_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 更新指令、優先序、額外參數、環境變數、isolationoverrides
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 實測「工具 × 模型」;可用 toolsmodelsconcurrency 縮小範圍
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/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 },
  "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.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                設定檔讀寫、資料目錄、工具執行目錄
├── 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.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. 定期自動重測暫時性失敗的組合(目前需手動觸發)。
Details
npm
2026-08-05 10:20:36 +00:00
1
UNLICENSED
168 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