Skip to content

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

ccswitch-mobile

cc-switch 的手机遥控面板 + 上游路由器,作为一个 Paseo workspace service 跑在你的 Mac 上。 手机(或任何外网设备)打开一个网页,就能看各个 LLM 应用当前用的是哪个上游、一键切换、测速,还能把 /proxy/<app>/... 当作统一入口转发到当前选中的上游。

零 npm 依赖:只用 Node 内置的 node:http + node:sqlite。


它是干嘛的

cc-switch 桌面应用本身没有 HTTP 控制接口——切换上游是一个只存在于桌面 App 内部的 Tauri IPC 命令(switch_provider),它自带的 :15721 代理还走一套内存里的故障转移状态机。所以光改 SQLite 里的 is_current,并不能确定地把流量切过去。

于是这个 sidecar 自己接管路由:

  • selection.json 是每个 app 当前上游的权威。
  • /proxy/<app>/... 转发到那个上游;开启自动故障转移后,失败会按队列顺序切换(见下节)。
  • cc-switch 的 DB 只是被镜像(is_current)让桌面 UI 显示一致,路由从不依赖它。

数据来自 ~/.cc-switch/cc-switch.db(providers + proxy_request_logs)。

四个 app 页签:claude / codex / gemini / grokbuild。


路由开关 + 故障转移(与 cc-switch 原版一致)

实现了官方文档 4.2 应用路由 / 4.3 故障转移 的完整语义,直接读写同一个 cc-switch.db,所以手机端和桌面版看到的是同一份配置。

路由开关(proxy_enabled)

每个 app 一个开关。关闭时 /proxy/<app>/... 直接返回 503 routing disabled,不转发。 对应桌面版「设置 → 高级 → 路由服务 → 应用路由」。

故障转移队列(in_failover_queue)

把若干备用 provider 加进队列,按 sort_index 排序(序号越小优先级越高)。 节点卡片上的「队列」按钮即加入/移除,对应桌面版供应商卡片的故障转移开关。

自动故障转移(auto_failover_enabled)

状态 行为
关闭 仅记录失败,不自动切换
开启 主节点失败时按队列顺序自动切到下一个,最多 max_retries 次

判定为失败:网络不可达,或上游返回 5xx / 429。切换时同步更新 selection.json。

熔断(circuit breaker)

  • 连续失败达 circuit_failure_threshold → 该节点跳闸,故障转移时跳过它。
  • 跳闸 circuit_timeout_seconds 后进入半开,放一次试通;单次成功即恢复。
  • 健康状态镜像进 provider_health 表(is_healthy / consecutive_failures / last_error)。

所有这些开关写库前都会先自动备份 cc-switch.db。


为什么需要这些(每项的理由)

组成 为什么必须
token 鉴权 /proxy/* 会带着存在 DB 里的各家 API key 转发到上游。一旦把端口开放绑定,等于把钥匙发出去。所以凡是非回环来源必须出示共享 token(timingSafeEqual 比对)。回环(127.0.0.1)被信任,因为那是 Paseo 自己的代理和健康检查。
?token= → Cookie 交换 手机第一次带 token 打开时,服务器 302 把它换成 HttpOnly Cookie,让 token 不再留在浏览器历史和日志里。
selection.json 路由权威。写它=改路由,不依赖 cc-switch 那套不确定的状态机。
写前自动备份 DB 每个进程第一次写 is_current 之前,先把 cc-switch.db 复制成 *.bak-paseo-mobile-<时间戳>。
HOST=0.0.0.0 默认只绑 127.0.0.1。要让手机/局域网能连,必须绑到所有接口。
Tailscale Funnel 手机不在同一局域网时,把 127.0.0.1:16810 安全地暴露到公网,自带 HTTPS。token 校验照常生效。

⚠️ 安全提示:带 token 的完整 URL 就等于登录态,别随手贴到公开渠道。公网暴露后任何拿到该 URL 的人都能操作切换和查看用量。


环境要求

  • Node ≥ 22(用到 node:sqlite 的 DatabaseSync)
  • 已安装 cc-switch,存在 ~/.cc-switch/cc-switch.db(没有就直接退出)
  • 想公网访问才需要 Tailscale

环境变量

变量 默认 说明
PORT / PASEO_PORT 16891 监听端口(Paseo 配置里被覆盖成 16810)
HOST 127.0.0.1 绑定地址;对外要设 0.0.0.0
CC_SWITCH_DB ~/.cc-switch/cc-switch.db cc-switch 数据库路径
CCSWITCH_MOBILE_STATE ~/.paseo/ccswitch-mobile 状态目录(selection.json + token 存这里)
CCSWITCH_MOBILE_TOKEN 自动生成 访问令牌;不设则首次启动生成一个 32 位 hex 存到状态目录
PASEO_URL — 外部访问基础 URL,仅用于日志里打印手机地址

token 存哪:~/.paseo/ccswitch-mobile/token(权限 0600)。~/.cc-switch/、~/.paseo/ 都在 home 目录,不会进这个仓库。


怎么跑

方式 A:Paseo workspace service(推荐,崩溃会自动拉起)

paseo.json(放在 workspace 根目录):

{
  "scripts": {
    "ccswitch-mobile": {
      "type": "service",
      "command": "HOST=0.0.0.0 node paseo-ccswitch-mobile/server.mjs",
      "port": 16810
    }
  }
}

然后在 Paseo 里启动这个 script。它会监听 *:16810,启动日志会直接打印带 token 的局域网/外网 URL。

方式 B:手动

HOST=0.0.0.0 PORT=16810 node server.mjs
# 启动日志会打印:http://<你的IP>:16810/?token=<自动生成的token>

外网访问(Tailscale Funnel)

# 1) 服务已在 127.0.0.1:16810 跑起来
# 2) 第一次要先在浏览器里给该节点开启 Funnel(每个 tailnet 一次)
tailscale funnel --bg --https=443 http://127.0.0.1:16810
#    若提示未启用,打开它给的 https://login.tailscale.com/f/funnel?... 点 Enable
# 3) 拿到公网域名
tailscale funnel status

之后外网/手机直接开:

https://<你的节点>.ts.net/?token=<token>

API

方法 / 路径 作用
GET /api/state 所有 app 总览:节点数、当前选中、厂商启用、路由/自动转移/队列数
GET /api/nodes?app=claude&hours=24 某 app 的节点列表 + 近 N 小时用量(调用数/错误/成本/最近时间)
POST /api/select {app,id} 切换某 app 的上游;写 selection.json 并镜像 is_current
POST /api/ping {app,id} 测上游可达性:GET {base}/v1/models(gemini 用 /v1beta/models),非 200 报 unknown 而非误判为挂
GET /api/vendors 读厂商启用状态(Claude / Codex / Gemini / Grok,可同时开)
POST /api/vendors {app,enabled} 或 {vendors:{claude:true,...}} 开/关某个或一批厂商;关后底部页签隐藏,状态落盘 vendors.json
GET /api/proxy-config[?app=X] 读某 app(或全部)的路由/自动转移/重试/熔断配置
POST /api/proxy-config {app,proxy_enabled?,auto_failover_enabled?,max_retries?} 改路由/自动转移开关或重试次数,写 proxy_config
GET /api/failover-queue?app=X 某 app 的故障转移队列(含熔断/健康状态)
POST /api/failover-queue {app,id,enabled} 把节点加入/移出队列,写 in_failover_queue
GET /api/health {status:"ok", selection}
GET|POST /proxy/<app>/... 转发到当前选中上游,自动带鉴权头(Anthropic 官方走 x-api-key,Gemini 走 x-goog-api-key,其余 Authorization: Bearer);路由关闭返回 503,自动转移开启时失败按队列切换

鉴权(非回环):URL ?token=、x-ccswitch-token 头、或 ccswitch_token Cookie 三选一。


文件

server.mjs          全部服务逻辑(单文件)
public/index.html   手机界面(含设置 → 厂商开关)
public/app.js
public/app.css

状态(不在仓库):~/.paseo/ccswitch-mobile/{selection.json,vendors.json,token}

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages