tb-npmswitch
v0.6.0
Published
Echo desktop plugin: install and update AI CLIs, manage Claude Code / Codex / Gemini CLI providers, and report token usage per Echo message.
Readme
tb-npmswitch
从 tb_switch(CC Switch 分支)抽取的核心能力,按 Echo 插件协议打包成一个 npm 包。包里同时有两部分:
- 插件前端(
dist/renderer.js+dist/renderer.css):Echo 桌面端里的「AI 工具」页面,包括概览、AI CLI、供应商、Token 用量四个标签页和一个设置分区。 - 本机服务(
dist/service.js由 Echo 桌面端运行,或终端里的tb-switch serve):在用户电脑上运行,负责检测和更新 AI CLI、写各 CLI 的配置文件、用本地代理和 Codex 会话日志统计 Token。
Token 用量最终汇总到 echo_admin(/api/token-usage/*、/api/workspaces/:id/analytics/tokens*),按工作区、项目、工位、成员、模型、供应商统计。
数据目录是 ~/.tb-switch,和桌面版的 ~/.cc-switch 互不影响。
架构
flowchart LR
subgraph Echo 桌面端
UI[插件前端<br/>dist/renderer.js]
end
subgraph 用户电脑
SVC[本机服务<br/>tb-switch serve]
CLI[Claude Code / Codex / Gemini CLI]
end
API[(echo_admin)]
UI -- "HTTP JSON-RPC + SSE<br/>127.0.0.1:15822(桌面端令牌 / 连接码)" --> SVC
CLI -- "请求经本地代理 :15821" --> SVC
UI -- "ctx.core.request(用户登录态)<br/>POST /token-usage/reports" --> API
UI -- "GET /workspaces/:id/analytics/tokens" --> API- 本机模式(默认):清单声明
service:local和entries.service: ./dist/service.js(esbuild 打成单文件,依赖全打进去)。插件在插件中心启用后,前端通过ctx.core.service.start()让桌面端用自带的 Node 在后台运行它,不用开终端、不用连接码:- 前端用
status().token(桌面端每次启动生成、通过TOC_PLUGIN_SERVICE_TOKEN传给服务)访问本机 HTTP 接口;服务读完即从环境变量删除。终端里的tb-switch命令仍用~/.tb-switch/api-token,两个令牌都有效。 - 跟随桌面端:stdin 关闭或父进程(
TOC_PLUGIN_PARENT_PID)退出时停止并恢复 CLI 配置;插件被禁用 / 卸载时桌面端也会停止它。开关状态存在ctx.storage的service.local(on/off,默认on),关掉后不再随桌面端启动。 - 一台电脑只跑一个:端口被占用(终端里已有
tb-switch serve)时,它在接管任何配置之前以退出码3退出,前端改用连接码连接那个服务;那个服务关掉后自动改用桌面端运行的服务。 - 由桌面端管理时不支持
system.restart(服务和插件同包同版本,不会出现版本不一致)。
- 前端用
- 旧版桌面端(没有
ctx.core.service):仍按原方式,在终端运行启动命令,把终端显示的 6 位连接码输入插件。令牌保存在ctx.secureStorage的sidecar.http里,之后自动重连。 - 清单里的
entries.node和sidecar是更早预留的侧车字段,当前宿主会忽略它们。
安装与构建
依赖 echo_web 仓库里的插件 SDK(file:../../echo/echo_web/packages/plugin-sdk),构建前需要把两个仓库放在约定的相对位置。
npm install --ignore-scripts
npm run typecheck # Node 部分 + 前端部分
npm run build # esbuild 打包 + 类型声明 + 语言包检查 + SDK 包校验
npm run validate # 只做清单和语言包校验
npm run dev:renderer # 前端监听构建前端的 React、React Query、React Router 和 SDK UI 组件由宿主共享(SDK 的 sharedModulesPlugin),不会打进包里;用到的 lucide 图标按需打包。发布的包只有 dist/、assets/、locales/,没有运行时依赖,也没有安装脚本。
插件清单
| 字段 | 内容 |
|------|------|
| id | official.tb-switch,apiVersion 1.x,只支持 desktop |
| 权限 | storage:plugin、secureStorage:plugin、notifications:publish、api:user、service:local |
| 本机服务 | entries.service: ./dist/service.js(需要支持 service:local 的 Echo 桌面端;先发桌面端再发插件) |
| 菜单与路由 | tbswitch.nav / tbswitch.home → /plugins/tb-switch(/tools、/providers、/usage 子路径对应标签页) |
| 设置 | tbswitch.settings:本机服务连接、自动上报、统计方式 |
| 命令 | tbswitch.open、tbswitch.tools.detect、tbswitch.report.flush |
| 语言 | zh-CN、en,清单里的 titleKey 都在语言包里 |
命令行
npx -y tb-npmswitch@latest serve # 启动本机服务,终端显示连接码
tb-switch pair # 连接码过期后获取新码(服务需在运行)
tb-switch tools list # 已装 / 最新版本
tb-switch tools install codex
tb-switch tools update claude [--yes] # 多处安装时需要 --yes
tb-switch provider add claude --name 中转A --base-url https://x.example.com --api-key sk-... [--model ...] [--switch]
tb-switch provider list claude
tb-switch provider switch claude <id>
tb-switch provider import codex # 把当前配置导入为一个供应商
tb-switch proxy restore # 服务异常退出后,把 CLI 配置恢复为真实地址
tb-switch report status | messages | flush | retry <msgId>
tb-switch mcp set public --url echoadmin.paperfold.cn --token <access token>
tb-switch mcp set lan --url 192.168.89.88:3000 --token <access token>
tb-switch mcp switch lan # 删除各 CLI 的 doer,再按局域网重新添加
tb-switch mcp status
tb-switch settings set report.delivery direct
tb-switch rpc <method> '<paramsJson>' # 直接调用任意接口API Key 也可以用环境变量 TB_SWITCH_API_KEY 传入,避免留在 shell 历史里。
MCP 一键切换
每个 CLI 只配置一个名为 doer 的 MCP,指向 Echo 后台的 /mcp。插件的「MCP」标签页里分别填公网、局域网的地址和 Access Token(令牌在各自服务器的「安全与访问」里生成),点「公网 / 局域网」即可切换。
- 切换时对每个勾选的 CLI 先删除原来的
doer条目,再按所选服务器重新添加(url+Authorization: Bearer),其他 MCP 和配置不动;写入前备份到~/.tb-switch/backups/mcp-<cli>/。 - 地址可以写
echoadmin.paperfold.cn、192.168.89.88:3000或完整 URL;没写协议时,局域网地址用 http,其余用 https,末尾自动补/mcp。 - 两套令牌保存在
~/.tb-switch/mcp.json(权限 0600),接口只返回「是否已保存」,不返回令牌本身。 - 目前支持 Codex(
~/.codex/config.toml)。其他 CLI 通过src/core/mcp/adapters里的McpClientAdapter(read / remove / add)接入,并在MCP_CLIENT_IDS里登记。 - 已打开的 CLI 会话不会重新读取配置,新开的会话生效。
切换时会一起改掉本机所有保存过这个 MCP 的地方,不只是 CLI 的 live 配置:
- CC Switch / TB Switch 桌面版的数据库(
~/.cc-switch/cc-switch.db):每个 Codex 供应商保存的整份config.toml、接管前的 live 备份、它自己的 MCP 表、profiles 和 opencode 公共配置。这些内容会在那边切换供应商时覆盖 CLI 配置,所以不一起改就会切回旧地址。 - TB Switch 插件自己保存的供应商(
~/.tb-switch/providers.json)。
只改已存在的条目,不新增;其他 MCP 和供应商配置不动;写库前把数据库(含 -wal/-shm)备份到 ~/.tb-switch/backups/mcp-ccswitch/。「MCP」页会列出每处有多少条、分别指向哪个环境,可以单独取消勾选。
地址和 Token 旁边有「测试连通」:用当前填写的值向 POST <地址>/mcp 发一次 MCP initialize,区分四种结果——连通正常(显示服务端名称和耗时)、Token 无效(401/403)、地址上没有 MCP 接口(404/405,反向代理没把 POST 透传给后端时也是这个)、无法连通(DNS / 连接 / TLS / 超时)。
接口:mcp.get、mcp.test({ profile, url?, token? })、mcp.update({ profiles?: { public|lan: { url?, token? } }, clients?: { codex: boolean } })、mcp.switch({ profile: "public"|"lan", clients? }),事件 mcp.changed。
获取模型列表
供应商表单和多供应商路由里的模型输入框都带「获取模型列表」按钮,逻辑移植自 tb_switch(CC Switch)的 model_fetch.rs:
- 调用 OpenAI 兼容的
GET /v1/models。地址已以/v{N}结尾时改拼/models(非/v1再补/v1/models兜底);以/anthropic、/api/coding、/claude等兼容子路径结尾时,追加剥离后缀的候选。按顺序尝试,404/405 换下一个。 - 默认
Authorization: Bearer,apiFormat为anthropic-messages/google-generative-ai时改用x-api-key/x-goog-api-key;单次 15 秒超时;结果按 id 排序,按owned_by分组、可搜索。 - 编辑已保存的供应商时不必重填 Key:传
{ app, providerId },由本机服务用保存的 Key 请求,Key 不回传前端。错误信息里的 Key 会被替换成[REDACTED]。
测试连接
供应商列表每行和新增 / 编辑弹窗里都有「测试连接」,分两步(第一步移植自 tb_switch stream_check.rs):
- 地址可达:GET base_url,收到任意 HTTP 响应即算可达,延迟按响应头到达计时;只有 DNS / 连接被拒 / TLS / 超时算不通。8 秒超时,超时类失败重试一次,超过 6000ms 标「较慢」。
- 密钥有效:带 API Key 请求模型列表接口。2xx 为有效(并显示可用模型数),401/403 为无效,没有该接口则为「无法确认」。
结果既弹提示也留在行上。官方登录的供应商不显示这个按钮(没有可探测的地址)。
接口:providers.test({ app, id? | baseUrl?, apiKey? })。
本地代理(与 tb_switch 对齐)
转发逻辑按 tb_switch(CC Switch)的代理流程移植,分四层,所有 CLI 共用,差异只在适配器:
- 路由表(
src/core/proxy/adapters/<app>.ts的route):Codex 的/responses、/v1/responses、/v1/v1/responses等别名统一成/responses,另有/responses/compact、/chat/completions、/alpha/search、/images/generations|edits;/models本地返回模型目录,不打上游。WebSocket 升级和方法不对返回 405,Codex 会改用 HTTP + SSE。 - 请求上下文(
controller.resolve):多供应商模式下,请求模型等于某条路由的客户端模型名时,固定到该路由的供应商并换成实际模型;否则走当前供应商、模型不变。当前供应商是官方登录无法代理时,退回默认路由。 - 适配器(
ProxyAdapter):取供应商地址和 Key、拼上游 URL(只填域名时补/v1,已带/v1或自定义前缀原样拼接)、生成认证头。 - 转发器(
forwarder.ts):- 请求头保持客户端顺序:认证头原位替换成供应商 Key,丢掉代理 / CDN / 链路追踪头;流式请求要求上游
accept-encoding: identity。 - 请求体:先解压(Codex 登录 ChatGPT 后发 zstd),再换路由模型、去掉
[1m]后缀、过滤_开头的私有字段、按键名排序。 - 响应:SSE 原样回传,去掉逐跳头;上游 4xx/5xx 按 Codex 的错误格式返回,写明供应商、模型和上游状态码。
- 请求头保持客户端顺序:认证头原位替换成供应商 Key,丢掉代理 / CDN / 链路追踪头;流式请求要求上游
Claude、Gemini 目前走透传适配器(路径原样、使用客户端自己的 Key),后续按同样方式移植 tb_switch 的适配器即可,Grok 也是加一个适配器。
检查:npm run smoke 用本地假上游验证以上规则(发布前自动运行);RELAY_URL=… RELAY_KEY=… RELAY_MODEL=… npm run check:relay 经系统代理对真实中转站做测试连接、获取模型和一次多供应商流式对话。
出站代理
获取模型、测试连接、MCP 测试、npm 版本查询、用量上报和转发代理的上游请求,都走同一个出站通道。Node 的 fetch 不认 HTTPS_PROXY,所以这里自己实现了代理支持:
- 默认跟随
HTTPS_PROXY/HTTP_PROXY/ALL_PROXY(遵守NO_PROXY),都没有时读 macOS 系统代理。 settings.network.proxyUrl可以显式指定(如http://127.0.0.1:7890),填off强制直连。只支持 http(s) 代理,不支持 socks。- 本机和内网地址(127.x、10.x、172.16-31.x、192.168.x、*.local)永不走代理。
- https 通过代理的
CONNECT隧道转发,TLS 仍然是本机到上游端到端。
版本一致性
本机服务是独立的长驻进程,和插件界面分别更新。两者版本不一致时新接口会报 Unknown method,所以插件顶部会显示提示,并提供「更新本机服务」按钮:
- 优先用开发者模式安装的本地构建启动;
- 否则用 npm 上的同版本(
npx -y tb-npmswitch@<版本> serve); - 旧服务太旧(没有
system.restart)时给出手动命令。
重启由本机服务自己完成:先优雅退出(恢复被接管的 live 配置、保存待上报数据),再由一个脱离终端的进程拉起新版本,日志写到 ~/.tb-switch/logs/serve.log。连接页给出的启动命令也固定为插件自身版本,避免 @latest 的 npx 缓存拿到旧版本。
接口:system.restart({ version? })。
接口:models.fetch({ baseUrl?, apiKey?, app?, providerId?, isFullUrl?, modelsUrl?, customUserAgent?, apiFormat?, requestHeaders? } → [{ id, ownedBy }])。
单供应商 / 多供应商
「供应商」页每个 CLI 上方可以切换工作方式:
- 单供应商:和以前一样,启用哪个供应商就把它写进 CLI 配置。
- 多供应商:CLI 指向本机路由服务,模型列表由「模型路由」组成,每条路由 = 供应商 + 实际模型 + 客户端模型名 + 显示名称。请求按模型名转发到对应供应商,并换上该供应商的 Key 和实际模型名。
Codex(移植自 tb_switch「Codex 模型路由」):
config.toml写入model_provider = "custom",[model_providers.custom]指向http://127.0.0.1:15821/codex、wire_api = "responses"、experimental_bearer_token = "PROXY_MANAGED";model为默认路由的客户端模型名;model_catalog_json = "tb-switch-model-catalog.json"(以 Codexmodels_cache.json里的 gpt-5.5 为模板,没有时用内置模板)。- 客户端模型名只能用官方名(
gpt-5.6-sol、gpt-5.6-terra、gpt-5.6-luna、gpt-5.5、gpt-5.4、gpt-5.4-mini、gpt-5.2,最多 7 条),因为 ChatGPT 桌面端会隐藏非官方名;它只作路由键,不影响实际模型。 auth.json不动,ChatGPT 登录保留;chatgpt-account-id等头不会转发给第三方。未匹配的模型走默认路由。- 需要本机服务一直运行(多供应商模式下启动时会自动开启代理)。切回单供应商时删除路由目录、恢复当前供应商配置,代理恢复到切换前的状态;写入前备份到
~/.tb-switch/backups/multi-codex/。 - 被路由引用的供应商不能删除;编辑供应商的 Key 立即生效。模式与路由保存在
~/.tb-switch/routes.json(0600)。
其他 CLI:实现 src/core/multi/types.ts 的 MultiProviderAdapter(normalize / apply / release / modelsResponse / upstreamHeaders),在 src/core/multi/adapters/index.ts 登记即可,接口和前端组件通用。
接口:multi.get({ app })、multi.save({ app, routes, currentRouteId? })、multi.setMode({ app, mode: "single"|"multi" }),事件 multi.changed。
Token 上报
统计
- 代理:
proxy.enabled默认开启,接管claude、gemini。代理运行期间,当前供应商的地址会改写成http://127.0.0.1:15821/<app>,请求原样转发。密钥仍由 CLI 自己发送,代理不保存也不注入密钥。停止服务时恢复真实地址;异常退出后执行tb-switch proxy restore,或者下次启动时自动恢复。官方登录的供应商不会被接管。 - Codex 会话:读取
~/.codex/sessions和archived_sessions下的 JSONL,首次启动只回放最近replayDays天。Codex 走代理时会自动停用会话来源,避免重复计数。 - msg_id 识别:依次看请求头
x-echo-msg-id、请求体字段、最新一条真实用户消息(跳过 tool_result)里的---payload---{"msg_id":...}。msg_id 由 echo_admin 下发任务时生成。
投递方式(report.delivery)
| 方式 | 说明 |
|------|------|
| host(默认) | 插件前端每 15 秒调用 report.pending 领取一批快照(90 秒租约),用 ctx.core.request 以当前登录用户身份 POST 到 echo_admin,再调用 report.ack 回写结果 |
| direct | 本机服务自己 POST 到 report.endpoint,带 Authorization: Bearer <report.token>。用于没有打开 Echo 桌面端的场景;也可以用环境变量 TB_SWITCH_REPORT_ENDPOINT / TB_SWITCH_REPORT_TOKEN 注入 |
同一个 msg_id 静默 quietSeconds(默认 30 秒)后生成完整快照,report_version 递增。echo_admin 按消息整份替换,旧版本返回 stale 并视为已送达。失败按指数退避重试;rejected(如消息不存在或不属于当前用户)不再自动重试,可在「Token 用量 › 本机上报」里手动重新上报。
echo_admin 接口
// POST /api/token-usage/reports(最多 50 份,每份最多 100 个分桶)
{ "reports": [{
"msg_id": "…", "report_version": 3, "source": "tb_switch",
"buckets": [{ "provider_id": "…", "provider_name": "智谱", "provider_type": "custom", "provider_host": "open.bigmodel.cn",
"app_type": "claude", "model": "glm-4.6", "request_count": 4,
"input_tokens": 1200, "output_tokens": 300, "cache_read_tokens": 800, "cache_write_tokens": 0,
"cost_usd": null, "first_request_at": "…", "last_request_at": "…" }]
}]}
// → data: { results: [{ msg_id, report_version, status: "applied|stale|rejected", code?, message? }], applied, stale, rejected }GET /api/token-usage/messages?ids=a,b/GET /api/token-usage/messages/:message_id:单条消息的用量。GET /api/workspaces/:workspace_id/analytics/tokens:汇总、趋势、按 CLI / 模型 / 供应商 / 项目 / 工位 / 成员拆分。成员只能看自己的用量,工作区所有者和管理员可以看全部(mine=true或user_id缩小范围)。GET /api/workspaces/:workspace_id/analytics/tokens/messages:按任务消息分页。
完整参数见 echo_admin 的 docs/API.md「AI Token 用量」一节。
对接接口(JSON-RPC 2.0)
所有通道使用同一套消息格式:
{ "jsonrpc": "2.0", "id": 1, "method": "providers.switch", "params": { "app": "claude", "id": "..." } }
{ "jsonrpc": "2.0", "id": 1, "result": { ... } }
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32001, "message": "...", "data": { ... } } }
// 服务主动推送
{ "jsonrpc": "2.0", "method": "ready", "params": { "version": "0.2.0", "protocolVersion": "1.0", "pid": 123, "api": null } }
{ "jsonrpc": "2.0", "method": "event", "params": { "type": "tools.job.log", "data": { ... } } }通道
| 通道 | 启动方式 | 说明 |
|------|----------|------|
| HTTP | tb-switch serve 默认开启,或 --http / settings.api.enabled | 当前插件前端使用的通道 |
| Electron utilityProcess | utilityProcess.fork(dist/node.js) | 预留给宿主侧车,自动识别 process.parentPort |
| Node IPC | child_process.fork(dist/node.js) | 自动识别 process.send |
| stdio | node dist/node.js --stdio | 每行一个 JSON,日志走 stderr |
服务参数:--transport=auto|parentPort|ipc|stdio|none、--http、--http-port=N、--no-proxy、--no-codex-sessions。
HTTP 接口
- 只监听
127.0.0.1,默认端口 15822,地址写在~/.tb-switch/api.json。校验 Host 头,防止 DNS 重绑定。 - 令牌优先取
TB_SWITCH_API_TOKEN,否则读~/.tb-switch/api-token(首次启动自动生成,权限 0600)。 GET /health:免鉴权,返回版本和是否支持配对。POST /pair:{ "code": "123456" }换取{ token, url }。连接码 10 分钟有效,同一时间只有一个,输错 5 次作废,成功后立即失效;错误返回 403code_wrong/code_expired。POST /rpc:Authorization: Bearer <token>,支持单条或批量。GET /events:SSE 事件流,每帧data是{type, data};鉴权同上,也支持?token=(给 EventSource 用)。- 跨域:默认放行
http://127.0.0.1|localhost|[::1]:<端口>来源(Echo 桌面端从http://127.0.0.1加载页面),其他来源需要加到api.allowedOrigins。放行跨域不代替鉴权,除/health和/pair外都要令牌。
方法
| 方法 | 参数 | 返回 |
|------|------|------|
| system.info / system.health / system.ping | – | 版本与通道 / 健康检查 / pong |
| system.pairingCode | – | 新连接码 { code, expiresAt } |
| system.shutdown | – | 优雅退出:先恢复被代理接管的配置,再保存待上报数据 |
| settings.get / settings.update | – / 部分设置 | 设置(令牌显示为 ***) |
| tools.list | { tools?, withLatest?, refresh? } | ToolStatus[] |
| tools.probe | { tool } | 所有安装位置和将要执行的更新命令 |
| tools.install / tools.update | { tool } / { tool, confirm? } | ToolJob(立即返回,进度走事件) |
| tools.job / tools.jobs / tools.cancel | { jobId } | ToolJob |
| providers.list / get / current | { app, id?, includeSecrets? } | 默认遮蔽密钥 |
| providers.add | { app, name, quick? \| settingsConfig? \| category:"official", switchTo? } | Provider |
| providers.update | { app, id, name?, quick?, settingsConfig? } | 被遮蔽的值原样传回时保留原值 |
| providers.delete / switch | { app, id } | 当前供应商不能删除 |
| providers.importLive / providers.live | { app, name? } / { app } | 导入现有配置 / 当前配置摘要 |
| proxy.status / proxy.start / proxy.stop | { apps? } | ProxyStatus |
| report.status / report.messages / report.retry / report.flush | 分页、msgId | 上报状态与本机记录 |
| report.pending / report.ack | { limit? } / { leaseId, results } | 宿主转发模式:领取快照 / 回写 echo_admin 的结果 |
| usage.recent | { limit?, msgId? } | 最近的用量记录 |
| codexSessions.status / codexSessions.scan | – | Codex 会话扫描状态 |
| models.fetch | { baseUrl?, apiKey?, app?, providerId?, ... } | [{ id, ownedBy }] |
| multi.get / multi.save / multi.setMode | { app } / { app, routes, currentRouteId? } / { app, mode } | 工作方式与模型路由 |
| providers.test | { app, id? , baseUrl?, apiKey? } | 地址可达性 + API Key 有效性 |
| mcp.test | { profile, url?, token? } | MCP initialize 探测 |
| system.restart | { version? } | 以指定版本重启本机服务 |
完整类型见 src/shared/protocol.ts(TbSwitchMethods)和 src/shared/types.ts。quick 是最常用的「地址 + 密钥」写法:{ baseUrl, apiKey, model?, haikuModel?, sonnetModel?, opusModel?, reasoningEffort?, wireApi? }。
事件:tools.job、tools.job.log、providers.changed、proxy.status、usage.recorded、report.status、settings.changed、service.health。
错误码
| 码 | 含义 |
|----|------|
| -32602 | 参数错误 |
| -32601 | 方法不存在 |
| -32001 | 冲突(如删除当前供应商、代理端口被占用) |
| -32002 | 不存在 |
| -32003 | 需要确认:tools.update 遇到多处安装,data 里是安装清单,带 confirm: true 重试 |
| -32004 | 同一工具已有任务在执行 |
| -32005 | HTTP 令牌无效 |
| -32006 | 上游不可用(npm 仓库、上报接口) |
| -32007 | 本地配置文件读写失败 |
自己写前端
import { createClient, createHttpTransport, pairLocalService } from "tb-npmswitch/client";
const { token, url } = await pairLocalService("http://127.0.0.1:15822", "123456");
const client = createClient(createHttpTransport({ baseUrl: url, token }));
const tools = await client.tools.list();
const job = await client.tools.update("claude", true);
await client.tools.waitForJob(job.id, line => console.log(line.line));
await client.providers.add({ app: "codex", name: "中转A", quick: { baseUrl: "https://x/v1", apiKey: "sk-..." }, switchTo: true });
client.on("usage.recorded", record => console.log(record.msgId));注意事项
- 切换供应商前,会把 CLI 配置备份到
~/.tb-switch/backups/<app>/。写入时只替换供应商相关的字段,其他用户设置保留。Codex 的config.toml会被重新格式化,注释不会保留。 - 从官方登录切到第三方时,Codex 的 ChatGPT 登录信息会先保存到
~/.tb-switch/codex/official-auth.json,切回官方时恢复。 - 预设供应商只收录能直接透传的服务:代理不做协议转换,Codex 自定义供应商必须支持 Responses 接口。
- Node 自带的
fetch不读取HTTP(S)_PROXY。上游必须走代理时,启动服务前设置NODE_USE_ENV_PROXY=1(Node ≥ 22.21)。 - 不要同时运行桌面版 CC Switch 的代理和上报,否则同一台电脑的用量会被统计两次。
- Windows 下的检测和执行逻辑已移植,但还没有在真机上验证。
