Compare commits

..

8 Commits

Author SHA1 Message Date
makoMakoGo 55eec1e294 fix(tests): isolate deeplink prompt import home 2026-06-03 21:29:27 +08:00
makoMakoGo e891f5c876 [codex] fix Zhipu coding plan presets (#3524)
* fix(presets): update Zhipu coding plan endpoints

* fix(model-fetch): probe /models on versioned /vN base URLs

The model-list probe assumed any base URL not ending in /v1 needs /v1/models appended. For providers whose base URL already ends in a version segment like /v4 (Zhipu/Z.AI GLM Coding Plan at .../api/coding/paas/v4), this produced .../v4/v1/models which 404s, so the "Fetch models" button always failed.

Detect a trailing /v{N} version segment and probe {base}/models first, keeping /v1/models as a fallback candidate for non-/v1 versions. Fixes Codex/OpenCode/OpenClaw/Hermes GLM presets and any other vN-style endpoint, with no behavior change for /v1 or non-versioned URLs.

---------

Co-authored-by: Jason <farion1231@gmail.com>
2026-06-03 15:53:57 +08:00
Sleepwf 73073454cd fix(i18n): align Chinese VS Code wording with English and Japanese (#3228) 2026-06-03 15:10:06 +08:00
step 7811383b59 fix(codex): use relative filename for model_catalog_json (#3614)
* fix(codex): use relative filename for model_catalog_json

Instead of writing an absolute path (which breaks on WSL/symlink setups),
write only the filename "cc-switch-model-catalog.json" to config.toml.
Codex CLI resolves relative paths from the config directory, and both
files always reside in the same directory (~/.codex/).

This eliminates the need for UNC-to-Linux path translation and makes
the config portable across Windows, WSL, and symlinked directories.

Also simplifies ownership checks in resolve_cc_switch_catalog_path()
and set_codex_model_catalog_json_field() by removing dead string-equality
comparisons that never matched on WSL.

Closes farion1231/cc-switch#3573
Related: farion1231/cc-switch#3569

* style(codex): fix rustfmt violations in model_catalog tests

Wrap a >100-col UNC-path line and remove a trailing blank line that broke 'cargo fmt --check' in Backend CI. Style-only, no logic change.

---------

Co-authored-by: steponeerror <huxaio0207@qq.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-06-03 14:52:57 +08:00
Eter c1dff06625 feat: 新增 ZenMux Token Plan 供应商,支持手动凭证与 USD 额度富展示 (#2709)
* feat(Token plan): 增加 ZenMux 支持

* chore: format code with prettier

* chore: format code with cargo fmt

---------

Co-authored-by: 明桓 <jihaodong.jhd@oceanbase.com>
Co-authored-by: Jason <farion1231@gmail.com>
2026-06-03 14:45:49 +08:00
LaoYueHanNi 43ae1e5f2c fix(coding-plan): 适配 MiniMax 余额查询新接口 + 默认定价 (#3518)
* fix(coding-plan): 适配 MiniMax 余额查询新接口 + 默认定价

## 余额查询修复

/v1/api/openplatform/coding_plan/remains 新接口不再返回
current_*_total_count / current_*_usage_count(恒为 0),
改为返回 current_*_remaining_percent(剩余百分比)。
旧代码会因守卫失败导致 tiers 为空,tray 不再显示用量。

- 切换数据源到 *_remaining_percent,反转为已用百分比
- 过滤 model_name == "general",跳过 video 等非编程模型
- 提取 parse_minimax_tiers 纯函数,便于无 mock 单元测试
- 复用 TIER_FIVE_HOUR / TIER_WEEKLY_LIMIT 常量
- 新增 6 个单元测试覆盖主路径与边界

## 默认定价新增
按 2026-06-01 上线的人民币价格 (CNY/7.0 汇率,口径与 M2.7 反推一致)
换算为 USD,在 seed_model_pricing 数组中追加 minimax-m3:
| model_id     | display_name | input | output | cache_read | cache_creation |
|--------------|--------------|-------|--------|------------|----------------|
| minimax-m3   | MiniMax M3   | 0.60  | 2.40   | 0.12       | 0              |
不取512K上下文定价(参照当前模型标准)

* fix(coding-plan): MiniMax 兼容无周限额套餐 (current_weekly_status=3)
2026-06-03 14:28:05 +08:00
Yeeyzy b4f262c7bd fix(codex): always include output_tokens_details.reasoning_tokens in … (#3514)
* fix(codex): always include output_tokens_details.reasoning_tokens in chat→responses transform

Codex CLI strictly requires reasoning_tokens in the response.completed
usage object. Custom providers using /chat/completions often omit
completion_tokens_details, causing repeated parse failures and retries.

* fix(codex): guard non-object completion_tokens_details before insertion

---------

Co-authored-by: yeeyzy <yeeyzy@yangzhiying05@gmail.com>
2026-06-03 14:10:28 +08:00
makoMakoGo 693c3872f0 docs: refresh user manual for current app support (#3411)
* docs(usage): document pricing model matching rules

* docs: refresh user manual for current app support

* docs: clarify Hermes configuration files

* docs: align feature docs with visible app support
2026-06-02 22:17:42 +08:00
52 changed files with 1172 additions and 299 deletions
+17 -17
View File
@@ -2,7 +2,7 @@
# CC Switch
### The All-in-One Manager for Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
### The All-in-One Manager for Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -152,13 +152,13 @@ Register now via <a href="https://pateway.ai/?ch=etzpm8&aff=WB6M6F67#/">this lin
## Why CC Switch?
Modern AI-powered coding relies on CLI tools like Claude Code, Codex, Gemini CLI, OpenCode, and OpenClaw — but each has its own configuration format. Switching API providers means manually editing JSON, TOML, or `.env` files, and there is no unified way to manage MCP and Skills across multiple tools.
Modern AI-powered coding relies on tools like Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes — but each has its own configuration format. Switching API providers means manually editing JSON, TOML, or `.env` files, and there is no unified way to manage MCP and Skills across multiple tools.
**CC Switch** gives you a single desktop app to manage all five CLI tools. Instead of editing config files by hand, you get a visual interface to import providers with one click, switch between them instantly, with 50+ built-in provider presets, unified MCP and Skills management, and system tray quick switching — all backed by a reliable SQLite database with atomic writes that protect your configs from corruption.
**CC Switch** gives you a single desktop app to manage all supported AI tools. Instead of editing config files by hand, you get a visual interface to import providers with one click, switch between them instantly, with 50+ built-in provider presets, unified MCP and Skills management, and system tray quick switching — all backed by a reliable SQLite database with atomic writes that protect your configs from corruption.
- **One App, Five CLI Tools** — Manage Claude Code, Codex, Gemini CLI, OpenCode, and OpenClaw from a single interface
- **One App, Seven Tools** — Manage Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, and Hermes from a single interface
- **No More Manual Editing** — 50+ provider presets including AWS Bedrock, NVIDIA NIM, and community relays; just pick and switch
- **Unified MCP & Skills Management** — One panel to manage MCP servers and Skills across four apps with bidirectional sync
- **Unified MCP & Skills Management** — One panel to manage MCP servers and Skills across Claude, Codex, Gemini, OpenCode, and Hermes with bidirectional sync
- **System Tray Quick Switch** — Switch providers instantly from the tray menu, no need to open the full app
- **Cloud Sync** — Sync provider data across devices via Dropbox, OneDrive, iCloud, or WebDAV servers
- **Cross-Platform** — Native desktop app for Windows, macOS, and Linux, built with Tauri 2
@@ -172,12 +172,12 @@ Modern AI-powered coding relies on CLI tools like Claude Code, Codex, Gemini CLI
## Features
[Full Changelog](CHANGELOG.md) | [Release Notes](docs/release-notes/v3.15.0-en.md)
[Full Changelog](CHANGELOG.md) | [Release Notes](docs/release-notes/v3.16.0-en.md)
### Provider Management
- **5 CLI tools, 50+ presets** — Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw; copy your key and import with one click
- **Universal providers** — One config syncs to multiple apps (OpenCode, OpenClaw)
- **7 supported tools, 50+ presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, Hermes; copy your key and import with one click
- **Universal providers** — One config syncs to Claude Code, Codex, and Gemini CLI
- One-click switching, system tray quick access, drag-and-drop sorting, import/export
### Proxy & Failover
@@ -187,7 +187,7 @@ Modern AI-powered coding relies on CLI tools like Claude Code, Codex, Gemini CLI
### MCP, Prompts & Skills
- **Unified MCP panel** — Manage MCP servers across 4 apps with bidirectional sync and Deep Link import
- **Unified MCP panel** — Manage MCP servers across Claude, Codex, Gemini, OpenCode, and Hermes with bidirectional sync and Deep Link import
- **Prompts** — Markdown editor with cross-app sync (CLAUDE.md / AGENTS.md / GEMINI.md) and backfill protection
- **Skills** — One-click install from GitHub repos or ZIP files, custom repository management, with symlink and file copy support
@@ -197,21 +197,21 @@ Modern AI-powered coding relies on CLI tools like Claude Code, Codex, Gemini CLI
### Session Manager & Workspace
- Browse, search, and restore conversation history across all apps
- Browse, search, and restore conversation history across supported session sources
- **Workspace editor** (OpenClaw) — Edit agent files (AGENTS.md, SOUL.md, etc.) with Markdown preview
### System & Platform
- **Cloud sync** — Custom config directory (Dropbox, OneDrive, iCloud, NAS) and WebDAV server sync
- **Deep Link** (`ccswitch://`) — Import providers, MCP servers, prompts, and skills via URL
- Dark / Light / System theme, auto-launch, auto-updater, atomic writes, auto-backups, i18n (zh/en/ja)
- Dark / Light / System theme, auto-launch, auto-updater, atomic writes, auto-backups, i18n (zh/zh-TW/en/ja)
## FAQ
<details>
<summary><strong>Which AI CLI tools does CC Switch support?</strong></summary>
<summary><strong>Which AI tools does CC Switch support?</strong></summary>
CC Switch supports five tools: **Claude Code**, **Codex**, **Gemini CLI**, **OpenCode**, and **OpenClaw**. Each tool has dedicated provider presets and configuration management.
CC Switch supports seven tools: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw**, and **Hermes**. Each tool has dedicated provider presets and configuration management.
</details>
@@ -280,8 +280,8 @@ For detailed guides on every feature, check out the **[User Manual](docs/user-ma
- **MCP**: Click the "MCP" button → Add servers via templates or custom config → Toggle per-app sync
- **Prompts**: Click "Prompts" → Create presets with Markdown editor → Activate to sync to live files
- **Skills**: Click "Skills" → Browse GitHub repos → One-click install to all apps
- **Sessions**: Click "Sessions" → Browse, search, and restore conversation history across all apps
- **Skills**: Click "Skills" → Browse GitHub repos → One-click install to supported apps
- **Sessions**: Click "Sessions" → Browse, search, and restore conversation history across supported session sources
> **Note**: On first launch, you can manually import existing CLI tool configs as the default provider.
@@ -372,7 +372,7 @@ Download the latest Linux build from the [Releases](../../releases) page:
- **ProviderService**: Provider CRUD, switching, backfill, sorting
- **McpService**: MCP server management, import/export, live file sync
- **ProxyService**: Local proxy mode with hot-switching and format conversion
- **SessionManager**: Conversation history browsing across all supported apps
- **SessionManager**: Conversation history browsing across supported session sources
- **ConfigService**: Config import/export, backup rotation
- **SpeedtestService**: API endpoint latency measurement
@@ -494,7 +494,7 @@ pnpm test:unit --coverage
│ ├── lib/
│ │ ├── api/ # Tauri API wrapper (type-safe)
│ │ └── query/ # TanStack Query config
│ ├── locales/ # Translations (zh/en/ja)
│ ├── locales/ # Translations (zh/zh-TW/en/ja)
│ ├── config/ # Presets (providers/mcp)
│ └── types/ # TypeScript definitions
├── src-tauri/ # Backend (Rust)
+16 -16
View File
@@ -2,7 +2,7 @@
# CC Switch
### Der All-in-One-Manager für Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
### Der All-in-One-Manager für Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw & Hermes Agent
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -152,13 +152,13 @@ Registrieren Sie sich jetzt über <a href="https://pateway.ai/?ch=etzpm8&aff=WB6
## Warum CC Switch?
Modernes KI-gestütztes Programmieren stützt sich auf CLI-Werkzeuge wie Claude Code, Codex, Gemini CLI, OpenCode und OpenClaw — doch jedes hat sein eigenes Konfigurationsformat. Der Wechsel des API-Anbieters bedeutet, JSON-, TOML- oder `.env`-Dateien von Hand zu bearbeiten, und es gibt keine einheitliche Möglichkeit, MCP und Skills über mehrere Werkzeuge hinweg zu verwalten.
Modernes KI-gestütztes Programmieren stützt sich auf Werkzeuge wie Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw und Hermes — doch jedes hat sein eigenes Konfigurationsformat. Der Wechsel des API-Anbieters bedeutet, JSON-, TOML- oder `.env`-Dateien von Hand zu bearbeiten, und es gibt keine einheitliche Möglichkeit, MCP und Skills über mehrere Werkzeuge hinweg zu verwalten.
**CC Switch** gibt Ihnen eine einzige Desktop-App, um alle fünf CLI-Werkzeuge zu verwalten. Statt Konfigurationsdateien von Hand zu bearbeiten, erhalten Sie eine visuelle Oberfläche, um Anbieter mit einem Klick zu importieren und sofort zwischen ihnen zu wechseln — mit 50+ integrierten Anbieter-Presets, einheitlicher MCP- und Skills-Verwaltung und schnellem Umschalten über das System-Tray. Das Ganze gestützt auf eine zuverlässige SQLite-Datenbank mit atomaren Schreibvorgängen, die Ihre Konfigurationen vor Beschädigung schützen.
**CC Switch** gibt Ihnen eine einzige Desktop-App, um alle unterstützten KI-Werkzeuge zu verwalten. Statt Konfigurationsdateien von Hand zu bearbeiten, erhalten Sie eine visuelle Oberfläche, um Anbieter mit einem Klick zu importieren und sofort zwischen ihnen zu wechseln — mit 50+ integrierten Anbieter-Presets, einheitlicher MCP- und Skills-Verwaltung und schnellem Umschalten über das System-Tray. Das Ganze gestützt auf eine zuverlässige SQLite-Datenbank mit atomaren Schreibvorgängen, die Ihre Konfigurationen vor Beschädigung schützen.
- **Eine App, fünf CLI-Werkzeuge** — Verwalten Sie Claude Code, Codex, Gemini CLI, OpenCode und OpenClaw über eine einzige Oberfläche
- **Eine App, sieben Werkzeuge** — Verwalten Sie Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw und Hermes über eine einzige Oberfläche
- **Kein manuelles Bearbeiten mehr** — 50+ Anbieter-Presets einschließlich AWS Bedrock, NVIDIA NIM und Community-Relays; einfach auswählen und umschalten
- **Einheitliche MCP- & Skills-Verwaltung** — Ein Panel zur Verwaltung von MCP-Servern und Skills über vier Apps hinweg mit bidirektionaler Synchronisierung
- **Einheitliche MCP- & Skills-Verwaltung** — Ein Panel zur Verwaltung von MCP-Servern und Skills für Claude, Codex, Gemini, OpenCode und Hermes mit bidirektionaler Synchronisierung
- **Schnellumschaltung über System-Tray** — Wechseln Sie Anbieter sofort über das Tray-Menü, ohne die vollständige App öffnen zu müssen
- **Cloud-Synchronisierung** — Synchronisieren Sie Anbieterdaten geräteübergreifend über Dropbox, OneDrive, iCloud oder WebDAV-Server
- **Plattformübergreifend** — Native Desktop-App für Windows, macOS und Linux, gebaut mit Tauri 2
@@ -172,12 +172,12 @@ Modernes KI-gestütztes Programmieren stützt sich auf CLI-Werkzeuge wie Claude
## Funktionen
[Vollständiges Changelog](CHANGELOG.md) | [Release Notes](docs/release-notes/v3.15.0-en.md)
[Vollständiges Changelog](CHANGELOG.md) | [Release Notes](docs/release-notes/v3.16.0-en.md)
### Anbieterverwaltung
- **5 CLI-Werkzeuge, 50+ Presets** — Claude Code, Codex, Gemini CLI, OpenCode, OpenClaw; Schlüssel kopieren und mit einem Klick importieren
- **Universelle Anbieter** — Eine Konfiguration synchronisiert sich mit mehreren Apps (OpenCode, OpenClaw)
- **7 unterstützte Werkzeuge, 50+ Presets** — Claude Code, Claude Desktop, Codex, Gemini CLI, OpenCode, OpenClaw, Hermes; Schlüssel kopieren und mit einem Klick importieren
- **Universelle Anbieter** — Eine Konfiguration synchronisiert sich mit Claude Code, Codex und Gemini CLI
- Umschaltung mit einem Klick, Schnellzugriff über System-Tray, Sortierung per Drag-and-drop, Import/Export
### Proxy & Failover
@@ -187,7 +187,7 @@ Modernes KI-gestütztes Programmieren stützt sich auf CLI-Werkzeuge wie Claude
### MCP, Prompts & Skills
- **Einheitliches MCP-Panel** — Verwalten Sie MCP-Server über 4 Apps hinweg mit bidirektionaler Synchronisierung und Deep-Link-Import
- **Einheitliches MCP-Panel** — Verwalten Sie MCP-Server für Claude, Codex, Gemini, OpenCode und Hermes mit bidirektionaler Synchronisierung und Deep-Link-Import
- **Prompts** — Markdown-Editor mit App-übergreifender Synchronisierung (CLAUDE.md / AGENTS.md / GEMINI.md) und Backfill-Schutz
- **Skills** — Installation mit einem Klick aus GitHub-Repositorys oder ZIP-Dateien, Verwaltung eigener Repositorys, mit Unterstützung für Symlinks und Dateikopien
@@ -197,21 +197,21 @@ Modernes KI-gestütztes Programmieren stützt sich auf CLI-Werkzeuge wie Claude
### Session Manager & Workspace
- Durchsuchen, suchen und stellen Sie den Gesprächsverlauf über alle Apps hinweg wieder her
- Gesprächsverlauf aus unterstützten Sitzungsquellen durchsuchen, suchen und wiederherstellen
- **Workspace-Editor** (OpenClaw) — Bearbeiten Sie Agent-Dateien (AGENTS.md, SOUL.md usw.) mit Markdown-Vorschau
### System & Plattform
- **Cloud-Synchronisierung** — Eigenes Konfigurationsverzeichnis (Dropbox, OneDrive, iCloud, NAS) und WebDAV-Server-Synchronisierung
- **Deep Link** (`ccswitch://`) — Importieren Sie Anbieter, MCP-Server, Prompts und Skills per URL
- Dunkles / Helles / System-Theme, automatischer Start, automatischer Updater, atomare Schreibvorgänge, automatische Backups, i18n (zh/en/ja)
- Dunkles / Helles / System-Theme, automatischer Start, automatischer Updater, atomare Schreibvorgänge, automatische Backups, i18n (zh/zh-TW/en/ja)
## FAQ
<details>
<summary><strong>Welche KI-CLI-Werkzeuge unterstützt CC Switch?</strong></summary>
<summary><strong>Welche KI-Werkzeuge unterstützt CC Switch?</strong></summary>
CC Switch unterstützt fünf Werkzeuge: **Claude Code**, **Codex**, **Gemini CLI**, **OpenCode** und **OpenClaw**. Jedes Werkzeug verfügt über dedizierte Anbieter-Presets und Konfigurationsverwaltung.
CC Switch unterstützt sieben Werkzeuge: **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw** und **Hermes**. Jedes Werkzeug verfügt über dedizierte Anbieter-Presets und Konfigurationsverwaltung.
</details>
@@ -280,8 +280,8 @@ Ausführliche Anleitungen zu jeder Funktion finden Sie im **[Benutzerhandbuch](d
- **MCP**: Klicken Sie auf die Schaltfläche „MCP" → Server über Vorlagen oder eigene Konfiguration hinzufügen → Synchronisierung pro App umschalten
- **Prompts**: Klicken Sie auf „Prompts" → Presets mit dem Markdown-Editor erstellen → Aktivieren, um mit den Live-Dateien zu synchronisieren
- **Skills**: Klicken Sie auf „Skills" → GitHub-Repositorys durchsuchen → mit einem Klick in allen Apps installieren
- **Sessions**: Klicken Sie auf „Sessions" → Gesprächsverlauf über alle Apps hinweg durchsuchen, suchen und wiederherstellen
- **Skills**: Klicken Sie auf „Skills" → GitHub-Repositorys durchsuchen → mit einem Klick in unterstützte Apps installieren
- **Sessions**: Klicken Sie auf „Sessions" → Gesprächsverlauf aus unterstützten Sitzungsquellen durchsuchen, suchen und wiederherstellen
> **Hinweis**: Beim Erststart können Sie bestehende CLI-Werkzeug-Konfigurationen manuell als Standardanbieter importieren.
@@ -494,7 +494,7 @@ pnpm test:unit --coverage
│ ├── lib/
│ │ ├── api/ # Tauri-API-Wrapper (typsicher)
│ │ └── query/ # TanStack-Query-Konfiguration
│ ├── locales/ # Übersetzungen (zh/en/ja)
│ ├── locales/ # Übersetzungen (zh/zh-TW/en/ja)
│ ├── config/ # Presets (providers/mcp)
│ └── types/ # TypeScript-Definitionen
├── src-tauri/ # Backend (Rust)
+16 -16
View File
@@ -2,7 +2,7 @@
# CC Switch
### Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes Agent のオールインワン管理ツール
### Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes Agent のオールインワン管理ツール
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -151,13 +151,13 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
## CC Switch を選ぶ理由
最新の AI コーディングは Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw などの CLI ツールに依存していますが、各ツールの設定形式はバラバラです。API プロバイダを切り替えるたびに JSON、TOML、`.env` ファイルを手動で編集する必要があり、複数ツール間で MCP や Skills を統一的に管理する手段もありません。
最新の AI コーディングは Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes などのツールに依存していますが、各ツールの設定形式はバラバラです。API プロバイダを切り替えるたびに JSON、TOML、`.env` ファイルを手動で編集する必要があり、複数ツール間で MCP や Skills を統一的に管理する手段もありません。
**CC Switch** は、5 つの CLI ツールを 1 つのデスクトップアプリで一元管理できます。設定ファイルを手作業で編集する代わりに、ワンクリックでプロバイダをインポートし、瞬時に切り替えられるビジュアルインターフェースを提供します。50 以上の組み込みプリセット、統一 MCP・Skills 管理、システムトレイからの即時切り替え機能を搭載。すべてはアトミック書き込みによる信頼性の高い SQLite データベースに支えられており、設定の破損を防ぎます。
**CC Switch** は、対応する AI ツールを 1 つのデスクトップアプリで一元管理できます。設定ファイルを手作業で編集する代わりに、ワンクリックでプロバイダをインポートし、瞬時に切り替えられるビジュアルインターフェースを提供します。50 以上の組み込みプリセット、統一 MCP・Skills 管理、システムトレイからの即時切り替え機能を搭載。すべてはアトミック書き込みによる信頼性の高い SQLite データベースに支えられており、設定の破損を防ぎます。
- **1 つのアプリで 5 つの CLI ツール** -- Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw を単一インターフェースで管理
- **1 つのアプリで 7 つのツール** -- Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes を単一インターフェースで管理
- **手動編集は不要** -- AWS Bedrock、NVIDIA NIM、コミュニティリレーなど 50 以上のプロバイダプリセットを内蔵。選んで切り替えるだけ
- **統一 MCP・Skills 管理** -- 1 つのパネルで 4 つのアプリの MCP サーバーと Skills を双方向同期で管理
- **統一 MCP・Skills 管理** -- 1 つのパネルで Claude、Codex、Gemini、OpenCode、Hermes の MCP サーバーと Skills を双方向同期で管理
- **システムトレイでクイック切り替え** -- トレイメニューから即座にプロバイダを切り替え。アプリを開く必要なし
- **クラウド同期** -- Dropbox、OneDrive、iCloud、または WebDAV サーバー経由でデバイス間のプロバイダデータを同期
- **クロスプラットフォーム** -- Tauri 2 で構築された Windows、macOS、Linux 対応のネイティブデスクトップアプリ
@@ -171,12 +171,12 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
## 特長
[完全な更新履歴](CHANGELOG.md) | [リリースノート](docs/release-notes/v3.15.0-ja.md)
[完全な更新履歴](CHANGELOG.md) | [リリースノート](docs/release-notes/v3.16.0-ja.md)
### プロバイダ管理
- **5 つの CLI ツール、50 以上のプリセット** -- Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw。キーをコピーしてワンクリックでインポート
- **ユニバーサルプロバイダ** -- 1 つの設定を複数アプリに同期(OpenCode、OpenClaw
- **7 つの対応ツール、50 以上のプリセット** -- Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes。キーをコピーしてワンクリックでインポート
- **ユニバーサルプロバイダ** -- 1 つの設定を Claude Code、Codex、Gemini CLI に同期
- ワンクリック切り替え、システムトレイクイックアクセス、ドラッグ&ドロップ並び替え、インポート/エクスポート
### プロキシ & フェイルオーバー
@@ -186,7 +186,7 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
### MCP、Prompts & Skills
- **統一 MCP パネル** -- 4 つのアプリの MCP サーバーを管理、双方向同期、Deep Link インポート対応
- **統一 MCP パネル** -- Claude、Codex、Gemini、OpenCode、Hermes の MCP サーバーを管理、双方向同期、Deep Link インポート対応
- **Prompts** -- Markdown エディタ、クロスアプリ同期(CLAUDE.md / AGENTS.md / GEMINI.md)、バックフィル保護
- **Skills** -- GitHub リポジトリまたは ZIP ファイルからワンクリックインストール、カスタムリポジトリ管理、シンボリックリンクとファイルコピーに対応
@@ -196,21 +196,21 @@ Claude Code / Codex / Gemini 公式チャンネルが最安で元価格の 38% /
### Session Manager & ワークスペース
- すべてのアプリの会話履歴を閲覧・検索・復元
- 対応するセッションソースの会話履歴を閲覧・検索・復元
- **ワークスペースエディタ**(OpenClaw)-- エージェントファイル(AGENTS.md、SOUL.md など)を Markdown プレビュー付きで編集
### システム & プラットフォーム
- **クラウド同期** -- カスタム設定ディレクトリ(Dropbox、OneDrive、iCloud、NAS)および WebDAV サーバー同期
- **Deep Link** (`ccswitch://`) -- URL 経由でプロバイダ、MCP サーバー、Prompts、Skills をワンクリックインポート
- ダーク / ライト / システムテーマ、自動起動、自動アップデーター、アトミック書き込み、自動バックアップ、多言語対応(/英/日)
- ダーク / ライト / システムテーマ、自動起動、自動アップデーター、アトミック書き込み、自動バックアップ、多言語対応(簡体中文/繁體中文/英/日)
## よくある質問
<details>
<summary><strong>CC Switch はどの AI CLI ツールに対応していますか?</strong></summary>
<summary><strong>CC Switch はどの AI ツールに対応していますか?</strong></summary>
CC Switch は **Claude Code**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw** の 5 つのツールに対応しています。各ツールに専用のプロバイダプリセットと設定管理が用意されています。
CC Switch は **Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw**、**Hermes**7 つのツールに対応しています。各ツールに専用のプロバイダプリセットと設定管理が用意されています。
</details>
@@ -279,8 +279,8 @@ CC Switch は「最小限の介入」という設計原則に従っています
- **MCP**: 「MCP」ボタンをクリック → テンプレートまたはカスタム設定でサーバーを追加 → アプリごとの同期をトグルで切り替え
- **Prompts**: 「Prompts」をクリック → Markdown エディタでプリセットを作成 → 有効化してライブファイルに同期
- **Skills**: 「Skills」をクリック → GitHub リポジトリを閲覧 → ワンクリックですべてのアプリにインストール
- **Sessions**: 「Sessions」をクリック → すべてのアプリの会話履歴を閲覧・検索・復元
- **Skills**: 「Skills」をクリック → GitHub リポジトリを閲覧 → 対応アプリへワンクリックでインストール
- **Sessions**: 「Sessions」をクリック → 対応するセッションソースの会話履歴を閲覧・検索・復元
> **補足**: 初回起動時に、既存の CLI ツール設定を手動でインポートしてデフォルトプロバイダとして使用できます。
@@ -493,7 +493,7 @@ pnpm test:unit --coverage
│ ├── lib/
│ │ ├── api/ # Tauri API ラッパー(型安全)
│ │ └── query/ # TanStack Query 設定
│ ├── locales/ # 翻訳 (zh/en/ja)
│ ├── locales/ # 翻訳 (zh/zh-TW/en/ja)
│ ├── config/ # プリセット (providers/mcp)
│ └── types/ # TypeScript 型定義
├── src-tauri/ # バックエンド (Rust)
+16 -16
View File
@@ -2,7 +2,7 @@
# CC Switch
### Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw 和 Hermes Agent 的全方位管理工具
### Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw 和 Hermes Agent 的全方位管理工具
[![Version](https://img.shields.io/github/v/release/farion1231/cc-switch?color=blue&label=version)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
@@ -152,13 +152,13 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
## 为什么选择 CC Switch
现代 AI 编程依赖于 Claude Code、Codex、Gemini CLI、OpenCodeOpenClaw 等 CLI 工具——但每个工具都有自己的配置格式。切换 API 供应商意味着手动编辑 JSON、TOML 或 `.env` 文件,而在多个工具之间缺乏一个统一管理 MCP, SKILLS 的方式。
现代 AI 编程依赖于 Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCodeOpenClaw 和 Hermes 等工具——但每个工具都有自己的配置格式。切换 API 供应商意味着手动编辑 JSON、TOML 或 `.env` 文件,而在多个工具之间缺乏一个统一管理 MCP, SKILLS 的方式。
**CC Switch** 为你提供一个桌面应用来管理所有五个 CLI 工具。无需手动编辑配置文件,你将获得一个可视化界面,一键将供应商导入应用,一键在不同的供应商之间进行切换,内置 50+ 供应商预设、统一的 MCP, SKILLS 管理以及系统托盘即时切换功能——所有操作都基于可靠的 SQLite 数据库和原子写入机制,保护你的配置不被损坏。
**CC Switch** 为你提供一个桌面应用来管理所有支持的 AI 工具。无需手动编辑配置文件,你将获得一个可视化界面,一键将供应商导入应用,一键在不同的供应商之间进行切换,内置 50+ 供应商预设、统一的 MCP, SKILLS 管理以及系统托盘即时切换功能——所有操作都基于可靠的 SQLite 数据库和原子写入机制,保护你的配置不被损坏。
- **一个应用,五个 CLI 工具** — 在单一界面中管理 Claude Code、Codex、Gemini CLI、OpenCodeOpenClaw
- **一个应用,七个工具** — 在单一界面中管理 Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCodeOpenClaw 和 Hermes
- **告别手动编辑** — 50+ 供应商预设,包括 AWS Bedrock、NVIDIA NIM 和社区中转服务;一键即可切换
- **统一 MCP, SKILLS 管理** — 一个面板管理四个应用的 MCP, SKILLS, 支持双向同步
- **统一 MCP, SKILLS 管理** — 一个面板管理 Claude、Codex、Gemini、OpenCode 和 Hermes 的 MCP, SKILLS, 支持双向同步
- **系统托盘快速切换** — 从托盘菜单即时切换供应商,无需打开完整应用
- **云同步** — 通过 Dropbox、OneDrive、iCloud 或 WebDAV 服务器在不同设备之间同步供应商数据
- **跨平台** — 基于 Tauri 2 构建的原生桌面应用,支持 Windows、macOS 和 Linux
@@ -172,12 +172,12 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
## 功能特性
[完整更新日志](CHANGELOG.md) | [发布说明](docs/release-notes/v3.15.0-zh.md)
[完整更新日志](CHANGELOG.md) | [发布说明](docs/release-notes/v3.16.0-zh.md)
### 供应商管理
- **5 CLI 工具,50+ 预设** — Claude Code、Codex、Gemini CLI、OpenCode、OpenClaw;复制 key 即可一键导入
- **通用供应商** — 一份配置同步到多个应用(OpenCode、OpenClaw
- **7支持工具,50+ 预设** — Claude Code、Claude Desktop、Codex、Gemini CLI、OpenCode、OpenClaw、Hermes;复制 key 即可一键导入
- **通用供应商** — 一份配置同步到 Claude Code、Codex 和 Gemini CLI
- 一键切换、系统托盘快速访问、拖拽排序、导入导出
### 代理与故障转移
@@ -187,7 +187,7 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
### MCP、Prompts 与 Skills
- **统一 MCP 面板** — 管理 4 个应用的 MCP 服务器,双向同步,支持 Deep Link 导入
- **统一 MCP 面板** — 管理 Claude、Codex、Gemini、OpenCode 和 Hermes 的 MCP 服务器,双向同步,支持 Deep Link 导入
- **Prompts** — Markdown 编辑器,跨应用同步(CLAUDE.md / AGENTS.md / GEMINI.md),回填保护
- **Skills** — 从 GitHub 仓库或 ZIP 文件一键安装,自定义仓库管理,支持软连接和文件复制
@@ -197,21 +197,21 @@ Claude Code / Codex / Gemini 官方渠道低至 3.8 / 0.2 / 0.9 折,充值更
### 会话管理器与工作区
- 浏览、搜索和恢复全部应用对话历史
- 浏览、搜索和恢复支持的会话来源
- **工作区编辑器**OpenClaw)— 编辑 Agent 文件(AGENTS.md、SOUL.md 等),支持 Markdown 预览
### 系统与平台
- **云同步** — 自定义配置目录(Dropbox、OneDrive、iCloud、坚果云、NAS)及 WebDAV 服务器同步
- **Deep Link** (`ccswitch://`) — 通过 URL 一键导入供应商、MCP 服务器、提示词和技能
- 深色 / 浅色 / 跟随系统主题、开机自启、自动更新、原子写入、自动备份、国际化(中/英/日)
- 深色 / 浅色 / 跟随系统主题、开机自启、自动更新、原子写入、自动备份、国际化(简中/繁中/英/日)
## 常见问题
<details>
<summary><strong>CC Switch 支持哪些 AI CLI 工具?</strong></summary>
<summary><strong>CC Switch 支持哪些 AI 工具?</strong></summary>
CC Switch 支持个工具:**Claude Code**、**Codex**、**Gemini CLI**、**OpenCode****OpenClaw**。每个工具都有专属的供应商预设和配置管理。
CC Switch 支持个工具:**Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode****OpenClaw** 和 **Hermes**。每个工具都有专属的供应商预设和配置管理。
</details>
@@ -282,8 +282,8 @@ CC Switch macOS 版本已通过 Apple 代码签名和公证,可直接下载安
- **MCP**:点击"MCP"按钮 → 通过模板或自定义配置添加服务器 → 切换各应用同步开关
- **Prompts**:点击"Prompts" → 使用 Markdown 编辑器创建预设 → 激活后同步到 live 文件
- **Skills**:点击"Skills" → 浏览 GitHub 仓库 → 一键安装到全部应用
- **会话**:点击"Sessions" → 浏览搜索和恢复全部应用对话历史
- **Skills**:点击"Skills" → 浏览 GitHub 仓库 → 一键安装到支持的应用
- **会话**:点击"Sessions" → 浏览搜索和恢复支持的会话来源
> **注意**:首次启动可以手动导入现有 CLI 工具配置作为默认供应商。
@@ -496,7 +496,7 @@ pnpm test:unit --coverage
│ ├── lib/
│ │ ├── api/ # Tauri API 封装(类型安全)
│ │ └── query/ # TanStack Query 配置
│ ├── locales/ # 翻译 (zh/en/ja)
│ ├── locales/ # 翻译 (zh/zh-TW/en/ja)
│ ├── config/ # 预设 (providers/mcp)
│ └── types/ # TypeScript 类型定义
├── src-tauri/ # 后端 (Rust)
+4 -4
View File
@@ -1,6 +1,6 @@
# CC Switch User Manual / 用户手册 / ユーザーマニュアル
> Claude Code / Codex / Gemini CLI / OpenCode / OpenClaw
> Claude Code / Claude Desktop / Codex / Gemini CLI / OpenCode / OpenClaw / Hermes
## Language / 语言 / 言語
@@ -12,9 +12,9 @@
## Version / 版本 / バージョン
- Documentation version: v3.15.0
- Last updated: 2026-05-16
- Compatible with CC Switch v3.15.0+
- Documentation version: v3.16.0
- Last updated: 2026-05-29
- Compatible with CC Switch v3.16.0+
## Links
@@ -2,7 +2,7 @@
## What is CC Switch
CC Switch is a cross-platform desktop application designed for developers who use AI coding tools. It helps you centrally manage configurations for **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw**, and **Hermes**.
CC Switch is a cross-platform desktop application designed for developers who use AI tools. It helps you centrally manage configurations for **Claude Code**, **Claude Desktop**, **Codex**, **Gemini CLI**, **OpenCode**, **OpenClaw**, and **Hermes**.
## What Problems Does It Solve
@@ -45,7 +45,7 @@ CC Switch solves these problems through a unified interface.
| **Codex** | OpenAI's code generation tool |
| **Gemini CLI** | Google's AI command-line tool |
| **OpenCode** | Open-source AI coding terminal tool |
| **OpenClaw** | Open-source AI coding assistant (multi-provider gateway) |
| **OpenClaw** | Open-source AI assistant (multi-provider gateway) |
| **Hermes** | Hermes Agent provider, MCP, Skills, and Memory management |
## Supported Platforms
@@ -12,7 +12,7 @@
| 2 | Settings Button | Open the settings page (shortcut `Cmd/Ctrl + ,`) |
| 3 | Proxy Toggle | Start/stop the local proxy service |
| 4 | App Switcher | Switch between Claude / Claude Desktop / Codex / Gemini / OpenCode / OpenClaw / Hermes |
| 5 | Feature Area | Skills / Prompts / MCP entry points |
| 5 | Feature Area | App-specific feature entry points |
| 6 | Add Button | Add a new provider |
### App Switcher
@@ -33,9 +33,9 @@ After switching, the provider list displays the configurations for the selected
| Button | Function | Visibility |
|--------|----------|------------|
| Skills | Skill extension management | Always visible |
| Prompts | System prompt management | Always visible |
| MCP | MCP server management | Always visible |
| Skills | Skill extension management | Claude / Codex / Gemini / OpenCode / Hermes |
| Prompts | System prompt management | Claude / Codex / Gemini / OpenCode |
| MCP | MCP server management | Claude / Codex / Gemini / OpenCode / Hermes |
## Provider Cards
@@ -113,13 +113,14 @@ CC Switch displays an icon in the system tray, providing quick access to operati
### Multi-language Support
The tray menu supports three languages, automatically switching based on settings:
The tray menu supports four languages, automatically switching based on settings:
| Language | Open Main Window | Quit |
|----------|-----------------|------|
| Chinese | Open Main Window | Quit |
| Simplified Chinese | 打开主界面 | 退出 |
| Traditional Chinese | 開啟主介面 | 退出 |
| English | Open main window | Quit |
| Japanese | Open main window | Quit |
| Japanese | メインウィンドウを開く | 終了 |
### Lightweight Mode
@@ -147,7 +148,7 @@ The settings page is divided into multiple tabs:
### General Tab
- Language settings (Chinese/English/Japanese)
- Language settings (Simplified Chinese/Traditional Chinese/English/Japanese)
- Theme settings (System/Light/Dark)
- Window behavior (launch on startup, close behavior)
@@ -9,11 +9,12 @@ This section describes how to configure CC Switch according to your preferences.
## Language Settings
CC Switch supports three languages:
CC Switch supports four languages:
| Language | Description |
|----------|-------------|
| Simplified Chinese | Default language |
| Traditional Chinese | Traditional Chinese interface |
| English | English interface |
| Japanese | Japanese interface |
@@ -124,8 +125,9 @@ You can customize each CLI tool's configuration directory:
| Claude Directory | `~/.claude/` | Claude Code configuration directory |
| Codex Directory | `~/.codex/` | Codex configuration directory |
| Gemini Directory | `~/.gemini/` | Gemini CLI configuration directory |
| OpenCode Directory | `~/.opencode/` | OpenCode configuration directory |
| OpenCode Directory | `~/.config/opencode/` | OpenCode configuration directory |
| OpenClaw Directory | `~/.openclaw/` | OpenClaw configuration directory |
| Hermes Directory | `~/.hermes/` | Hermes configuration directory |
> **Note**: After changing directories, the app must be restarted, and the corresponding CLI tools must also be configured to use the same directory.
@@ -133,14 +135,15 @@ You can customize each CLI tool's configuration directory:
### Export Configuration
Click the "Export" button to save a backup file containing:
Click the "Export" button to save a SQL backup file containing:
- All provider configurations
- MCP server configurations
- Prompt presets
- Usage logs
- App settings
The backup file is in JSON format and can be viewed with a text editor.
The exported file name format is `cc-switch-export-{timestamp}.sql`.
### Import Configuration
+7 -5
View File
@@ -103,15 +103,16 @@ Each MCP server can independently control which apps it is enabled for.
| Claude | Sync to Claude Code | `~/.claude.json`'s `mcpServers` |
| Codex | Sync to Codex | `~/.codex/config.toml`'s `[mcp_servers]` |
| Gemini | Sync to Gemini CLI | `~/.gemini/settings.json`'s `mcpServers` |
| OpenCode | Sync to OpenCode | `~/.opencode/config.json`'s `mcpServers` |
| OpenCode | Sync to OpenCode | `~/.config/opencode/opencode.json`'s `mcp` |
| Hermes | Sync to Hermes | `~/.hermes/config.yaml`'s `mcp_servers` |
> **Note**: OpenClaw does not currently support MCP server management. MCP functionality is currently only supported for Claude, Codex, Gemini, and OpenCode.
> **Note**: OpenClaw and Claude Desktop do not currently support CC Switch MCP sync. MCP functionality is supported for Claude, Codex, Gemini, OpenCode, and Hermes.
### Toggle Implementation
When enabling an app's toggle, CC Switch will:
1. **Update database**: Set the server's `apps.claude/codex/gemini/opencode` status to `true`
1. **Update database**: Set the server's `apps.claude/codex/gemini/opencode/hermes` status to `true`
2. **Sync to live configuration**: Write the server configuration to the corresponding app's configuration file
3. **Take effect immediately**: The new MCP server is automatically loaded the next time the CLI tool starts
@@ -128,7 +129,8 @@ MCP server sync only executes when the corresponding app is installed:
- **Claude**: Requires `~/.claude/` directory or `~/.claude.json` file to exist
- **Codex**: Requires `~/.codex/` directory to exist
- **Gemini**: Requires `~/.gemini/` directory to exist
- **OpenCode**: Requires `~/.opencode/` directory to exist
- **OpenCode**: Requires `~/.config/opencode/` directory to exist
- **Hermes**: Requires `~/.hermes/` directory to exist
> **Tip**: If a CLI tool is not installed, enabling its toggle will not cause an error, but the configuration will not be written.
@@ -154,7 +156,7 @@ After deletion, the configuration is removed from all app configuration files.
If you have already configured MCP servers in CLI tools, you can import them into CC Switch:
1. Click the "Import" button
2. Select the app to import from (Claude/Codex/Gemini/OpenCode)
2. Select the app to import from (Claude/Codex/Gemini/OpenCode/Hermes)
3. CC Switch reads the existing configuration and imports it
## Configuration File Formats
@@ -81,8 +81,7 @@ After activation, the prompt is written to the corresponding app's file:
| Claude | `~/.claude/CLAUDE.md` |
| Codex | `~/.codex/AGENTS.md` |
| Gemini | `~/.gemini/GEMINI.md` |
| OpenCode | `~/.opencode/AGENTS.md` |
| OpenClaw | `~/.openclaw/AGENTS.md` |
| OpenCode | `~/.config/opencode/AGENTS.md` |
## Edit a Preset
@@ -141,7 +140,6 @@ Prompts are managed separately per app:
- When switched to Codex, Codex's presets are shown
- When switched to Gemini, Gemini's presets are shown
- When switched to OpenCode, OpenCode's presets are shown
- When switched to OpenClaw, OpenClaw's presets are shown
To use the same prompt across multiple apps, you need to create them separately.
@@ -12,18 +12,17 @@ Skills exist as folders containing:
## Supported Applications
Skills are supported across all four applications:
Skills are supported across five applications:
- **Claude Code**
- **Codex**
- **Gemini CLI**
- **OpenCode**
- **Hermes**
## Open the Skills Page
Click the **Skills** button in the top navigation bar.
> Note: The Skills button is visible in all app modes.
Click the **Skills** button in the top navigation bar when the selected app supports Skills.
## Page Overview
@@ -93,7 +92,8 @@ Click the "Refresh" button to re-scan repositories for the latest skills.
| Claude | `~/.claude/skills/` |
| Codex | `~/.codex/skills/` |
| Gemini | `~/.gemini/skills/` |
| OpenCode | `~/.opencode/skills/` |
| OpenCode | `~/.config/opencode/skills/` |
| Hermes | `~/.hermes/skills/` |
### Installation Contents
@@ -119,7 +119,7 @@ Installation copies the skill folder to your local machine:
### Uninstall Effect
- **Automatic backup**: Before deletion, the skill is backed up to `~/.cc-switch/skill-backups/`
- Removes the skill from all app directories (Claude, Codex, Gemini, OpenCode)
- Removes the skill from all app directories (Claude, Codex, Gemini, OpenCode, Hermes)
- Removes the skill from the SSOT directory (`~/.cc-switch/skills/`)
- Deletes the skill record from the database
+8 -2
View File
@@ -215,9 +215,11 @@ Set prices for each model (per million tokens):
Before matching pricing, CC Switch normalizes the requested model ID:
- Remove everything before the last `/`
- Remove everything after `:`
- Remove everything before the last `/` and convert to lowercase
- Remove everything after `:` and trim a trailing `[1m]`
- Replace `@` with `-`
- Remove common wrapper prefixes, version suffixes, and date suffixes (`-YYYY-MM-DD`, `-YYYYMMDD`)
- Some model families can match a short ID to a versioned pricing entry
When adding pricing entries, enter the normalized Model ID rather than the full raw model name from the request.
@@ -226,6 +228,10 @@ When adding pricing entries, enter the normalized Model ID rather than the full
| `stepfun-ai/step-3.5-flash` | `step-3.5-flash` | Removes the provider prefix |
| `moonshotai/kimi-k2-0905:exa` | `kimi-k2-0905` | Removes the prefix and the `:` suffix |
| `gpt-5.2-codex@low` | `gpt-5.2-codex-low` | Replaces `@` with `-` |
| `OpenAI/GPT-5.5-2026-05-14` | `gpt-5.5` | Removes the prefix and date suffix |
| `anthropic/claude-opus-4.8` | `claude-opus-4-8` | Removes the prefix and matches dotted format |
| `global.anthropic.claude-opus-4-8-v1:0` | `claude-opus-4-8` | Removes wrapper prefix, version suffix, and `:` suffix |
| `claude-haiku-4-5` | `claude-haiku-4-5-20251001` | Matches a short ID to versioned pricing |
### Operations
+35 -12
View File
@@ -16,7 +16,7 @@ Customizable location in settings (for cloud sync).
├── settings.json # Device-level settings
├── skills/ # Skill SSOT directory
├── skill-backups/ # Skill backups (created on uninstall)
└── db_backup_*.db # Database backups
└── backups/ # Database backups
```
### Database Contents
@@ -51,7 +51,8 @@ Customizable location in settings (for cloud sync).
"codexConfigDir": null,
"geminiConfigDir": null,
"opencodeConfigDir": null,
"openclawConfigDir": null
"openclawConfigDir": null,
"hermesConfigDir": null
}
```
@@ -196,17 +197,41 @@ GEMINI_MODEL=gemini-pro
### Configuration Directory
Default: `~/.opencode/`
Default: `~/.config/opencode/`
### Key Files
```
~/.opencode/
├── config.json # Main configuration file
~/.config/opencode/
├── opencode.json # Main configuration file
├── AGENTS.md # System prompt
└── skills/ # Skills directory
└── ...
```
## Hermes Configuration
### Configuration Directory
Default: `~/.hermes/`
### Key Files
```
~/.hermes/
├── config.yaml # Main settings, providers, and MCP configuration
├── .env # API keys and secrets
├── SOUL.md # Profile identity/persona
├── memories/
│ ├── MEMORY.md # Agent memory
│ └── USER.md # User profile memory
├── skills/ # Active skills directory
├── state.db # SQLite session database
└── sessions/ # Gateway transcripts and optional JSON snapshots
```
### config.yaml
Hermes uses YAML configuration. CC Switch writes MCP servers to `mcp_servers`, writes editable provider entries to `custom_providers`, reads read-only entries from Hermes' `providers` dict, and updates `model.provider` / `model.default` when switching providers.
## OpenClaw Configuration
@@ -219,7 +244,6 @@ Default: `~/.openclaw/`
```
~/.openclaw/
├── openclaw.json # Main configuration file (JSON5 format)
├── AGENTS.md # System prompt
└── skills/ # Skills directory
└── ...
```
@@ -246,18 +270,17 @@ OpenClaw uses a JSON5 format configuration file with the following main sections
env: {
ANTHROPIC_API_KEY: "sk-..."
},
// Agent default model configuration
// Agent default configuration
agents: {
defaults: {
model: {
primary: "provider/model"
}
},
workspace: "~/.openclaw/workspace"
}
},
// Tool configuration
tools: {},
// Workspace file configuration
workspace: {}
tools: {}
}
```
@@ -267,7 +290,7 @@ OpenClaw uses a JSON5 format configuration file with the following main sections
| `env` | Environment variable configuration |
| `agents.defaults` | Agent default model settings |
| `tools` | Tool configuration |
| `workspace` | Workspace file management |
| `agents.defaults.workspace` | Workspace directory path |
## Configuration Priority
+8 -8
View File
@@ -106,15 +106,15 @@ CC Switch User Manual
## Version Information
- Documentation version: v3.15.0
- Last updated: 2026-05-16
- Applicable to CC Switch v3.15.0+
- Documentation version: v3.16.0
- Last updated: 2026-05-29
- Applicable to CC Switch v3.16.0+
### v3.15.0 Highlights
### v3.16.0 Highlights
- **First-class Claude Desktop panel**: supports third-party providers, direct / model mapping modes, Copilot / Codex OAuth reuse, and 3P profile writing. See [2.6 Claude Desktop](./2-providers/2.6-claude-desktop.md)
- **Role-based model mapping**: adapts Claude Desktop model validation with Sonnet / Opus / Haiku routes and `supports1m`
- **Claude Desktop local routing**: provides a local gateway at `127.0.0.1:15721/claude-desktop` for providers that need conversion
- **Codex Chat Completions routing**: route Chat-only providers such as DeepSeek, Kimi, GLM, and MiniMax through Codex. See [2.1 Add Provider](./2-providers/2.1-add.md)
- **Managed CLI tool lifecycle**: install, update, update all, and diagnose Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes from Settings / About. See [1.5 Personalization](./1-getting-started/1.5-settings.md)
- **Provider and model refresh**: new partner presets, refreshed default models and pricing, Claude Opus 4.8 defaults, and GPT 5.5 defaults where applicable
- **Routing support badges**: Claude Code / Codex provider cards indicate whether a provider can be served through Local Routing
- **Codex OAuth live model discovery**: ChatGPT Codex providers fetch available models from the ChatGPT backend on demand
- **Filter-driven Usage Hero**: shows cache-normalized real total tokens and cache hit rate, updating with date / provider / model filters — see [4.4 Usage Statistics](./4-proxy/4.4-usage.md)
@@ -124,7 +124,7 @@ CC Switch User Manual
- **Per-App Tray Submenus**: Claude / Codex / Gemini submenus show the current provider and available usage summaries — see [2.2 Switch Provider](./2-providers/2.2-switch.md)
- **Skills Discovery & Batch Updates**: SHA-256 update detection, batch updates, skills.sh public registry search — see [3.3 Skills Management](./3-extensions/3.3-skills.md)
- **Full URL Endpoint Mode**: Advanced option to treat `base_url` as the full upstream endpoint — see [2.1 Add Provider](./2-providers/2.1-add.md)
- **OpenCode / OpenClaw Stream Check Coverage**: Stream Check covers Claude / Codex / Gemini / OpenCode / OpenClaw — see [4.5 Model Test](./4-proxy/4.5-model-test.md)
- **OpenCode / OpenClaw / Hermes Stream Check Coverage**: Stream Check covers Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes — see [4.5 Model Test](./4-proxy/4.5-model-test.md)
## Contributing
@@ -2,7 +2,7 @@
## CC Switch とは
CC Switch はクロスプラットフォームのデスクトップアプリケーションで、AI プログラミングツールを使用する開発者向けに設計されています。**Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw**、**Hermes** などの管理対象アプリの設定を統一的に管理できます。
CC Switch はクロスプラットフォームのデスクトップアプリケーションで、AI ツールを使用する開発者向けに設計されています。**Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw**、**Hermes** などの管理対象アプリの設定を統一的に管理できます。
## どのような問題を解決するか
@@ -45,7 +45,7 @@ CC Switch は統一されたインターフェースでこれらの問題を解
| **Codex** | OpenAI のコード生成ツール |
| **Gemini CLI** | Google の AI コマンドラインツール |
| **OpenCode** | オープンソース AI プログラミングターミナルツール |
| **OpenClaw** | オープンソース AI プログラミングアシスタント(マルチプロバイダーゲートウェイ) |
| **OpenClaw** | オープンソース AI アシスタント(マルチプロバイダーゲートウェイ) |
| **Hermes** | Hermes Agent のプロバイダー、MCP、Skills、Memory 管理 |
## 対応プラットフォーム
@@ -12,7 +12,7 @@
| ② | 設定ボタン | 設定ページを開く(ショートカット `Cmd/Ctrl + ,` |
| ③ | プロキシスイッチ | ローカルプロキシサービスの起動/停止 |
| ④ | アプリ切り替え | Claude / Claude Desktop / Codex / Gemini / OpenCode / OpenClaw / Hermes を切り替え |
| ⑤ | 機能エリア | Skills / Prompts / MCP の入口 |
| ⑤ | 機能エリア | 現在のアプリで利用できる機能入口 |
| ⑥ | 追加ボタン | 新しいプロバイダーを追加 |
### アプリ切り替え
@@ -33,9 +33,9 @@
| ボタン | 機能 | 表示条件 |
|------|------|----------|
| Skills | スキル拡張管理 | 常に表示 |
| Prompts | システムプロンプト管理 | 常に表示 |
| MCP | MCP サーバー管理 | 常に表示 |
| Skills | スキル拡張管理 | Claude / Codex / Gemini / OpenCode / Hermes |
| Prompts | システムプロンプト管理 | Claude / Codex / Gemini / OpenCode |
| MCP | MCP サーバー管理 | Claude / Codex / Gemini / OpenCode / Hermes |
## プロバイダーカード
@@ -113,11 +113,12 @@ CC Switch はシステムトレイにアイコンを表示し、クイック操
### 多言語対応
トレイメニューは 3 つの言語に対応し、設定に応じて自動的に切り替わります:
トレイメニューは 4 つの言語に対応し、設定に応じて自動的に切り替わります:
| 言語 | メインウィンドウを開く | 終了 |
|------|-----------|------|
| 中文 | 打开主界面 | 退出 |
| 簡体中文 | 打开主界面 | 退出 |
| 繁體中文 | 開啟主介面 | 退出 |
| English | Open main window | Quit |
| 日本語 | メインウィンドウを開く | 終了 |
@@ -147,7 +148,7 @@ CC Switch はシステムトレイにアイコンを表示し、クイック操
### 一般タブ
- 言語設定(中文/English/日本語)
- 言語設定(簡体中文/繁體中文/English/日本語)
- テーマ設定(システムに合わせる/ライト/ダーク)
- ウィンドウ動作(起動時に自動実行、閉じる動作)
@@ -9,11 +9,12 @@
## 言語設定
CC Switch は 3 つの言語に対応しています:
CC Switch は 4 つの言語に対応しています:
| 言語 | 説明 |
| -------- | -------- |
| 簡体中文 | デフォルト言語 |
| 繁體中文 | 繁体字中国語インターフェース |
| English | 英語インターフェース |
| 日本語 | 日本語インターフェース |
@@ -124,8 +125,9 @@ CC Switch 自体のデータの保存場所で、デフォルトは `~/.cc-switc
| Claude ディレクトリ | `~/.claude/` | Claude Code 設定ディレクトリ |
| Codex ディレクトリ | `~/.codex/` | Codex 設定ディレクトリ |
| Gemini ディレクトリ | `~/.gemini/` | Gemini CLI 設定ディレクトリ |
| OpenCode ディレクトリ | `~/.opencode/` | OpenCode 設定ディレクトリ |
| OpenCode ディレクトリ | `~/.config/opencode/` | OpenCode 設定ディレクトリ |
| OpenClaw ディレクトリ | `~/.openclaw/` | OpenClaw 設定ディレクトリ |
| Hermes ディレクトリ | `~/.hermes/` | Hermes 設定ディレクトリ |
> **注意**:ディレクトリを変更した後はアプリの再起動が必要で、対応する CLI ツールも同じディレクトリを設定する必要があります。
@@ -133,14 +135,15 @@ CC Switch 自体のデータの保存場所で、デフォルトは `~/.cc-switc
### 設定のエクスポート
「エクスポート」ボタンをクリックして、以下の内容を含むバックアップファイルを保存します:
「エクスポート」ボタンをクリックして、以下の内容を含む SQL バックアップファイルを保存します:
- すべてのプロバイダー設定
- MCP サーバー設定
- Prompts プリセット
- 使用量ログ
- アプリ設定
バックアップファイルは JSON 形式で、テキストエディタで確認できます。
エクスポートされるファイル名の形式は `cc-switch-export-{timestamp}.sql`す。
### 設定のインポート
+7 -5
View File
@@ -103,15 +103,16 @@ SSE プロトコルでサーバーと通信し、リアルタイムプッシュ
| Claude | Claude Code に同期 | `~/.claude.json``mcpServers` |
| Codex | Codex に同期 | `~/.codex/config.toml``[mcp_servers]` |
| Gemini | Gemini CLI に同期 | `~/.gemini/settings.json``mcpServers` |
| OpenCode | OpenCode に同期 | `~/.opencode/config.json``mcpServers` |
| OpenCode | OpenCode に同期 | `~/.config/opencode/opencode.json``mcp` |
| Hermes | Hermes に同期 | `~/.hermes/config.yaml``mcp_servers` |
> **注意**OpenClaw は現在 MCP サーバー管理に対応していません。MCP 機能は現在 Claude、Codex、Gemini、OpenCode の 4 つのアプリのみサポートしています。
> **注意**OpenClaw と Claude Desktop は現在 CC Switch MCP 同期に対応していません。MCP 機能は Claude、Codex、Gemini、OpenCode、Hermes に対応しています。
### スイッチの動作
あるアプリのスイッチをオンにすると、CC Switch は以下を実行します:
1. **データベースの更新**:サーバーの `apps.claude/codex/gemini/opencode` のステータスを `true` に設定
1. **データベースの更新**:サーバーの `apps.claude/codex/gemini/opencode/hermes` のステータスを `true` に設定
2. **Live 設定に同期**:サーバー設定を対応アプリの設定ファイルに書き込み
3. **即時反映**:次回 CLI ツール起動時に新しい MCP サーバーが自動的にロード
@@ -128,7 +129,8 @@ MCP サーバーの同期は、対応アプリがインストールされてい
- **Claude**`~/.claude/` ディレクトリまたは `~/.claude.json` ファイルが存在する必要あり
- **Codex**`~/.codex/` ディレクトリが存在する必要あり
- **Gemini**`~/.gemini/` ディレクトリが存在する必要あり
- **OpenCode**`~/.opencode/` ディレクトリが存在する必要あり
- **OpenCode**`~/.config/opencode/` ディレクトリが存在する必要あり
- **Hermes**`~/.hermes/` ディレクトリが存在する必要あり
> **ヒント**:CLI ツールがインストールされていない場合、対応するスイッチをオンにしてもエラーにはなりませんが、設定は書き込まれません。
@@ -154,7 +156,7 @@ MCP サーバーの同期は、対応アプリがインストールされてい
CLI ツールで既に MCP サーバーを設定している場合、CC Switch にインポートできます:
1. 「インポート」ボタンをクリック
2. インポートするアプリを選択(Claude/Codex/Gemini/OpenCode
2. インポートするアプリを選択(Claude/Codex/Gemini/OpenCode/Hermes
3. CC Switch が既存の設定を読み取ってインポート
## 設定ファイル形式
@@ -81,8 +81,7 @@ CC Switch を使用すると:
| Claude | `~/.claude/CLAUDE.md` |
| Codex | `~/.codex/AGENTS.md` |
| Gemini | `~/.gemini/GEMINI.md` |
| OpenCode | `~/.opencode/AGENTS.md` |
| OpenClaw | `~/.openclaw/AGENTS.md` |
| OpenCode | `~/.config/opencode/AGENTS.md` |
## プリセットの編集
@@ -141,7 +140,6 @@ Prompts はアプリごとに個別に管理されます:
- Codex に切り替えると、Codex のプリセットが表示
- Gemini に切り替えると、Gemini のプリセットが表示
- OpenCode に切り替えると、OpenCode のプリセットが表示
- OpenClaw に切り替えると、OpenClaw のプリセットが表示
複数のアプリで同じプロンプトを使用する場合は、それぞれで作成する必要があります。
@@ -12,18 +12,17 @@ Skills は再利用可能な機能拡張で、AI ツールに特定分野の専
## 対応アプリ
Skills 機能は以下の 4 つのアプリに対応しています:
Skills 機能は以下の 5 つのアプリに対応しています:
- **Claude Code**
- **Codex**
- **Gemini CLI**
- **OpenCode**
- **Hermes**
## Skills ページを開く
上部ナビゲーションバーの **Skills** ボタンをクリックします。
> 注意:Skills ボタンはすべてのアプリモードで表示されます。
選択中のアプリが Skills に対応している場合、上部ナビゲーションバーの **Skills** ボタンをクリックします。
## ページ概要
@@ -93,7 +92,8 @@ CC Switch は強力な検索とフィルタリング機能を提供していま
| Claude | `~/.claude/skills/` |
| Codex | `~/.codex/skills/` |
| Gemini | `~/.gemini/skills/` |
| OpenCode | `~/.opencode/skills/` |
| OpenCode | `~/.config/opencode/skills/` |
| Hermes | `~/.hermes/skills/` |
### インストール内容
@@ -119,7 +119,7 @@ CC Switch は強力な検索とフィルタリング機能を提供していま
### アンインストールの効果
- **自動バックアップ**:削除前にスキルが `~/.cc-switch/skill-backups/` にバックアップされる
- すべてのアプリディレクトリ(Claude、Codex、Gemini、OpenCode)からスキルを削除
- すべてのアプリディレクトリ(Claude、Codex、Gemini、OpenCode、Hermes)からスキルを削除
- SSOT ディレクトリ(`~/.cc-switch/skills/`)からスキルを削除
- データベースからスキルレコードを削除
+8 -2
View File
@@ -215,9 +215,11 @@ Token 使用量の変化を表示:
料金を照合する前に、CC Switch はリクエスト内のモデル ID を正規化します:
- 最後の `/` より前の接頭辞を削除
- `:` 以降の接尾辞を削除
- 最後の `/` より前の接頭辞を削除し、小文字に変換
- `:` 以降の接尾辞を削除し、末尾の `[1m]` を削除
- `@``-` に置換
- 一般的なラッパー接頭辞、バージョン接尾辞、日付接尾辞(`-YYYY-MM-DD``-YYYYMMDD`)を削除
- 一部のモデルファミリーでは、短い ID からバージョン付き価格エントリに照合できます
料金設定では、リクエスト内の完全な元のモデル名ではなく、正規化後のモデル ID を入力してください。
@@ -226,6 +228,10 @@ Token 使用量の変化を表示:
| `stepfun-ai/step-3.5-flash` | `step-3.5-flash` | プロバイダー接頭辞を削除 |
| `moonshotai/kimi-k2-0905:exa` | `kimi-k2-0905` | 接頭辞と `:` 以降を削除 |
| `gpt-5.2-codex@low` | `gpt-5.2-codex-low` | `@``-` に置換 |
| `OpenAI/GPT-5.5-2026-05-14` | `gpt-5.5` | 接頭辞と日付接尾辞を削除 |
| `anthropic/claude-opus-4.8` | `claude-opus-4-8` | 接頭辞を削除し、ドット形式に照合 |
| `global.anthropic.claude-opus-4-8-v1:0` | `claude-opus-4-8` | ラッパー接頭辞、バージョン接尾辞、`:` 以降を削除 |
| `claude-haiku-4-5` | `claude-haiku-4-5-20251001` | 短い ID からバージョン付き価格に照合 |
### 操作
+35 -12
View File
@@ -16,7 +16,7 @@
├── settings.json # デバイスレベルの設定
├── skills/ # スキル SSOT ディレクトリ
├── skill-backups/ # スキルバックアップ(アンインストール時に作成)
└── db_backup_*.db # データベースバックアップ
└── backups/ # データベースバックアップ
```
### データベースの内容
@@ -51,7 +51,8 @@
"codexConfigDir": null,
"geminiConfigDir": null,
"opencodeConfigDir": null,
"openclawConfigDir": null
"openclawConfigDir": null,
"hermesConfigDir": null
}
```
@@ -196,17 +197,41 @@ GEMINI_MODEL=gemini-pro
### 設定ディレクトリ
デフォルト:`~/.opencode/`
デフォルト:`~/.config/opencode/`
### 主要ファイル
```
~/.opencode/
├── config.json # メイン設定ファイル
~/.config/opencode/
├── opencode.json # メイン設定ファイル
├── AGENTS.md # システムプロンプト
└── skills/ # スキルディレクトリ
└── ...
```
## Hermes の設定
### 設定ディレクトリ
デフォルト:`~/.hermes/`
### 主要ファイル
```
~/.hermes/
├── config.yaml # メイン設定、プロバイダー、MCP 設定
├── .env # API キーとシークレット
├── SOUL.md # Profile identity/persona
├── memories/
│ ├── MEMORY.md # エージェント記憶
│ └── USER.md # ユーザープロファイル記憶
├── skills/ # 有効なスキルディレクトリ
├── state.db # SQLite セッションデータベース
└── sessions/ # Gateway transcript と任意の JSON snapshot
```
### config.yaml
Hermes は YAML 設定を使用します。CC Switch は MCP サーバーを `mcp_servers` に書き込み、編集可能なプロバイダーエントリを `custom_providers` に書き込み、Hermes の `providers` dict にある読み取り専用エントリを読み取り、プロバイダー切り替え時に `model.provider` / `model.default` を更新します。
## OpenClaw の設定
@@ -219,7 +244,6 @@ GEMINI_MODEL=gemini-pro
```
~/.openclaw/
├── openclaw.json # メイン設定ファイル(JSON5 形式)
├── AGENTS.md # システムプロンプト
└── skills/ # スキルディレクトリ
└── ...
```
@@ -246,18 +270,17 @@ OpenClaw は JSON5 形式の設定ファイルを使用し、主に以下のセ
env: {
ANTHROPIC_API_KEY: "sk-..."
},
// Agent デフォルトモデル設定
// Agent デフォルト設定
agents: {
defaults: {
model: {
primary: "provider/model"
}
},
workspace: "~/.openclaw/workspace"
}
},
// ツール設定
tools: {},
// ワークスペースファイル設定
workspace: {}
tools: {}
}
```
@@ -267,7 +290,7 @@ OpenClaw は JSON5 形式の設定ファイルを使用し、主に以下のセ
| `env` | 環境変数設定 |
| `agents.defaults` | Agent デフォルトモデル設定 |
| `tools` | ツール設定 |
| `workspace` | ワークスペースファイル管理 |
| `agents.defaults.workspace` | ワークスペースディレクトリパス |
## 設定の優先順位
+1 -1
View File
@@ -144,7 +144,7 @@ chmod +x CC-Switch-*.AppImage
- バージョンの非互換性
**解決方法**
1. ファイルが CC Switch からエクスポートされた JSON ファイルであることを確認
1. ファイルが CC Switch からエクスポートされた SQL バックアップファイルであることを確認
2. ファイル内容が完全であるか確認
3. テキストエディタで開いてフォーマットを確認
+8 -8
View File
@@ -106,15 +106,15 @@ CC Switch ユーザーマニュアル
## バージョン情報
- ドキュメントバージョン:v3.15.0
- 最終更新:2026-05-16
- CC Switch v3.15.0+ 対応
- ドキュメントバージョン:v3.16.0
- 最終更新:2026-05-29
- CC Switch v3.16.0+ 対応
### v3.15.0 の注目機能
### v3.16.0 の注目機能
- **Claude Desktop の一等管理パネル**:サードパーティプロバイダー、直結 / モデルマッピングの 2 モード、Copilot / Codex OAuth 再利用、3P profile 書き込みに対応 — 詳細は [2.6 Claude Desktop](./2-providers/2.6-claude-desktop.md)
- **役割別モデルマッピング**Sonnet / Opus / Haiku ルートと `supports1m` フラグで Claude Desktop のモデル検証に対応
- **Claude Desktop ローカルルーティング**:変換が必要なプロバイダー向けに `127.0.0.1:15721/claude-desktop` のローカルゲートウェイを提供
- **Codex Chat Completions ルーティング**DeepSeek、Kimi、GLM、MiniMax など Chat 専用プロバイダーを Codex で利用可能 — 詳細は [2.1 プロバイダーの追加](./2-providers/2.1-add.md)
- **管理対象 CLI ツールのライフサイクル**:設定 / About で Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes のインストール、更新、一括更新、診断に対応 — 詳細は [1.5 個人設定](./1-getting-started/1.5-settings.md)
- **プロバイダーとモデルマトリクス更新**:提携プリセットを追加し、既定モデルと価格表を更新。Claude Opus は 4.8、該当する GPT 既定値は 5.5 に更新
- **ルーティング対応バッジ**Claude Code / Codex のプロバイダーカードで Local Routing 対応可否を確認可能
- **Codex OAuth ライブモデル検出**ChatGPT Codex 系プロバイダーは必要に応じて ChatGPT バックエンドから利用可能モデルを取得
- **フィルター連動 Usage Hero**:キャッシュ正規化後の実消費 Token とキャッシュヒット率を表示し、日付 / プロバイダー / モデルフィルターに追従 — 詳細は [4.4 使用量統計](./4-proxy/4.4-usage.md)
@@ -124,7 +124,7 @@ CC Switch ユーザーマニュアル
- **アプリ別トレイサブメニュー**Claude / Codex / Gemini のサブメニューで現在のプロバイダーと使用量サマリーを確認可能 — 詳細は [2.2 プロバイダーの切り替え](./2-providers/2.2-switch.md)
- **Skills の発見と一括更新**:SHA-256 ハッシュによる更新検出、一括更新、skills.sh 公式レジストリ検索 — 詳細は [3.3 Skills スキル管理](./3-extensions/3.3-skills.md)
- **完全URLエンドポイントモード**:高度なオプションで `base_url` を完全なアップストリームエンドポイントとして扱う — 詳細は [2.1 プロバイダーの追加](./2-providers/2.1-add.md)
- **OpenCode / OpenClaw ストリームチェック対応**Stream Check は Claude / Codex / Gemini / OpenCode / OpenClaw をカバー — 詳細は [4.5 モデルテスト](./4-proxy/4.5-model-test.md)
- **OpenCode / OpenClaw / Hermes ストリームチェック対応**Stream Check は Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes をカバー — 詳細は [4.5 モデルテスト](./4-proxy/4.5-model-test.md)
## コントリビュート
@@ -2,7 +2,7 @@
## 什么是 CC Switch
CC Switch 是一款跨平台桌面应用,专为使用 AI 编程工具的开发者设计。它帮助你统一管理 **Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw** 和 **Hermes** 等受管应用的配置。
CC Switch 是一款跨平台桌面应用,专为使用 AI 工具的开发者设计。它帮助你统一管理 **Claude Code**、**Claude Desktop**、**Codex**、**Gemini CLI**、**OpenCode**、**OpenClaw** 和 **Hermes** 等受管应用的配置。
## 解决什么问题
@@ -45,7 +45,7 @@ CC Switch 通过统一的界面解决这些问题。
| **Codex** | OpenAI 的代码生成工具 |
| **Gemini CLI** | Google 的 AI 命令行工具 |
| **OpenCode** | 开源 AI 编程终端工具 |
| **OpenClaw** | 开源 AI 编程助手(多供应商网关) |
| **OpenClaw** | 开源 AI 助手(多供应商网关) |
| **Hermes** | Hermes Agent,支持供应商、MCP、Skills 和 Memory 管理 |
## 支持的平台
@@ -12,7 +12,7 @@
| ② | 设置按钮 | 打开设置页面(快捷键 `Cmd/Ctrl + ,` |
| ③ | 代理开关 | 启动/停止本地代理服务 |
| ④ | 应用切换器 | 切换 Claude / Claude Desktop / Codex / Gemini / OpenCode / OpenClaw / Hermes |
| ⑤ | 功能区 | Skills / Prompts / MCP 入口 |
| ⑤ | 功能区 | 当前应用支持的功能入口 |
| ⑥ | 添加按钮 | 添加新供应商 |
### 应用切换器
@@ -33,9 +33,9 @@
| 按钮 | 功能 | 可见条件 |
|------|------|----------|
| Skills | 技能扩展管理 | 始终可见 |
| Prompts | 系统提示词管理 | 始终可见 |
| MCP | MCP 服务器管理 | 始终可见 |
| Skills | 技能扩展管理 | Claude / Codex / Gemini / OpenCode / Hermes |
| Prompts | 系统提示词管理 | Claude / Codex / Gemini / OpenCode |
| MCP | MCP 服务器管理 | Claude / Codex / Gemini / OpenCode / Hermes |
## 供应商卡片
@@ -113,11 +113,12 @@ CC Switch 在系统托盘显示图标,提供快速操作入口。
### 多语言支持
托盘菜单支持种语言,根据设置自动切换:
托盘菜单支持种语言,根据设置自动切换:
| 语言 | 打开主界面 | 退出 |
|------|-----------|------|
| 中文 | 打开主界面 | 退出 |
| 简体中文 | 打开主界面 | 退出 |
| 繁體中文 | 開啟主介面 | 退出 |
| English | Open main window | Quit |
| 日本語 | メインウィンドウを開く | 終了 |
@@ -147,7 +148,7 @@ CC Switch 在系统托盘显示图标,提供快速操作入口。
### 通用 Tab
- 语言设置(中文/English/日本語)
- 语言设置(简体中文/繁體中文/English/日本語)
- 主题设置(跟随系统/浅色/深色)
- 窗口行为(开机自启、关闭行为)
@@ -9,11 +9,12 @@
## 语言设置
CC Switch 支持种语言:
CC Switch 支持种语言:
| 语言 | 说明 |
| -------- | -------- |
| 简体中文 | 默认语言 |
| 繁體中文 | 繁体中文界面 |
| English | 英文界面 |
| 日本語 | 日文界面 |
@@ -124,8 +125,9 @@ CC Switch 自身数据的存储位置,默认为 `~/.cc-switch/`。
| Claude 目录 | `~/.claude/` | Claude Code 配置目录 |
| Codex 目录 | `~/.codex/` | Codex 配置目录 |
| Gemini 目录 | `~/.gemini/` | Gemini CLI 配置目录 |
| OpenCode 目录 | `~/.opencode/` | OpenCode 配置目录 |
| OpenCode 目录 | `~/.config/opencode/` | OpenCode 配置目录 |
| OpenClaw 目录 | `~/.openclaw/` | OpenClaw 配置目录 |
| Hermes 目录 | `~/.hermes/` | Hermes 配置目录 |
> ⚠️ **注意**:修改目录后需要重启应用,且对应的 CLI 工具也需要配置相同的目录。
@@ -133,14 +135,15 @@ CC Switch 自身数据的存储位置,默认为 `~/.cc-switch/`。
### 导出配置
点击「导出」按钮,保存包含以下内容的备份文件:
点击「导出」按钮,保存包含以下内容的 SQL 备份文件:
- 所有供应商配置
- MCP 服务器配置
- Prompts 预设
- 用量日志
- 应用设置
备份文件格式为 JSON,可以用文本编辑器查看
导出文件格式为 `cc-switch-export-{timestamp}.sql`
### 导入配置
+7 -5
View File
@@ -103,15 +103,16 @@ MCP (Model Context Protocol) 是一种协议,允许 AI 工具访问外部数
| Claude | 同步到 Claude Code | `~/.claude.json``mcpServers` |
| Codex | 同步到 Codex | `~/.codex/config.toml``[mcp_servers]` |
| Gemini | 同步到 Gemini CLI | `~/.gemini/settings.json``mcpServers` |
| OpenCode | 同步到 OpenCode | `~/.opencode/config.json``mcpServers` |
| OpenCode | 同步到 OpenCode | `~/.config/opencode/opencode.json``mcp` |
| Hermes | 同步到 Hermes | `~/.hermes/config.yaml``mcp_servers` |
> ⚠️ **注意**OpenClaw 暂不支持 MCP 服务器管理。MCP 功能目前仅支持 Claude、Codex、GeminiOpenCode 四个应用
> ⚠️ **注意**OpenClaw 和 Claude Desktop 暂不支持 CC Switch MCP 同步。MCP 功能支持 Claude、Codex、GeminiOpenCode 和 Hermes
### 开关实现机制
当开启某个应用的开关时,CC Switch 会:
1. **更新数据库**:将服务器的 `apps.claude/codex/gemini/opencode` 状态设为 `true`
1. **更新数据库**:将服务器的 `apps.claude/codex/gemini/opencode/hermes` 状态设为 `true`
2. **同步到 Live 配置**:将服务器配置写入对应应用的配置文件
3. **即时生效**:下次启动 CLI 工具时自动加载新的 MCP 服务器
@@ -128,7 +129,8 @@ MCP 服务器同步仅在对应应用已安装时执行:
- **Claude**:需存在 `~/.claude/` 目录或 `~/.claude.json` 文件
- **Codex**:需存在 `~/.codex/` 目录
- **Gemini**:需存在 `~/.gemini/` 目录
- **OpenCode**:需存在 `~/.opencode/` 目录
- **OpenCode**:需存在 `~/.config/opencode/` 目录
- **Hermes**:需存在 `~/.hermes/` 目录
> 💡 **提示**:如果某个 CLI 工具未安装,开启对应开关不会报错,但配置不会写入。
@@ -154,7 +156,7 @@ MCP 服务器同步仅在对应应用已安装时执行:
如果你已经在 CLI 工具中配置了 MCP 服务器,可以导入到 CC Switch:
1. 点击「导入」按钮
2. 选择要导入的应用(Claude/Codex/Gemini/OpenCode
2. 选择要导入的应用(Claude/Codex/Gemini/OpenCode/Hermes
3. CC Switch 会读取现有配置并导入
## 配置文件格式
@@ -81,8 +81,7 @@ Prompts 功能用于管理系统提示词预设。系统提示词会影响 AI
| Claude | `~/.claude/CLAUDE.md` |
| Codex | `~/.codex/AGENTS.md` |
| Gemini | `~/.gemini/GEMINI.md` |
| OpenCode | `~/.opencode/AGENTS.md` |
| OpenClaw | `~/.openclaw/AGENTS.md` |
| OpenCode | `~/.config/opencode/AGENTS.md` |
## 编辑预设
@@ -141,7 +140,6 @@ Prompts 是按应用分开管理的:
- 切换到 Codex 时,显示 Codex 的预设
- 切换到 Gemini 时,显示 Gemini 的预设
- 切换到 OpenCode 时,显示 OpenCode 的预设
- 切换到 OpenClaw 时,显示 OpenClaw 的预设
如需在多个应用使用相同的提示词,需要分别创建。
@@ -12,18 +12,17 @@ Skills 是可复用的能力扩展,让 AI 工具获得特定领域的专业能
## 支持的应用
Skills 功能支持所有四种应用:
Skills 功能支持种应用:
- **Claude Code**
- **Codex**
- **Gemini CLI**
- **OpenCode**
- **Hermes**
## 打开 Skills 页面
点击顶部导航栏的 **Skills** 按钮。
> 注意:Skills 按钮在所有应用模式下均可见。
当当前应用支持 Skills 时,点击顶部导航栏的 **Skills** 按钮。
## 页面概览
@@ -93,7 +92,8 @@ CC Switch 提供强大的搜索和过滤功能:
| Claude | `~/.claude/skills/` |
| Codex | `~/.codex/skills/` |
| Gemini | `~/.gemini/skills/` |
| OpenCode | `~/.opencode/skills/` |
| OpenCode | `~/.config/opencode/skills/` |
| Hermes | `~/.hermes/skills/` |
### 安装内容
@@ -119,7 +119,7 @@ CC Switch 提供强大的搜索和过滤功能:
### 卸载效果
- **自动备份**:删除前,技能会被备份到 `~/.cc-switch/skill-backups/`
- 从所有应用目录(Claude、Codex、Gemini、OpenCode)移除技能
- 从所有应用目录(Claude、Codex、Gemini、OpenCode、Hermes)移除技能
- 从 SSOT 目录(`~/.cc-switch/skills/`)移除技能
- 从数据库删除技能记录
+8 -2
View File
@@ -215,9 +215,11 @@ v3.15.0 起,用量页顶部改为筛选驱动的 Hero 卡。切换日期范围
在匹配定价前,CC Switch 会先对请求中的模型 ID 做标准化处理:
- 去掉最后一个 `/` 之前的前缀
- 去掉 `:` 之后的后缀
- 去掉最后一个 `/` 之前的前缀,并转成小写
- 去掉 `:` 之后的后缀,去掉末尾的 `[1m]`
-`@` 替换为 `-`
- 去掉常见包装前缀、版本后缀、日期后缀(`-YYYY-MM-DD``-YYYYMMDD`
- 部分模型族支持短 ID 匹配带版本的定价项
因此,在定价配置中请填写清洗后的模型 ID,而不是请求里的完整原始模型名。
@@ -226,6 +228,10 @@ v3.15.0 起,用量页顶部改为筛选驱动的 Hero 卡。切换日期范围
| `stepfun-ai/step-3.5-flash` | `step-3.5-flash` | 去掉供应商前缀 |
| `moonshotai/kimi-k2-0905:exa` | `kimi-k2-0905` | 去掉前缀和 `:` 后缀 |
| `gpt-5.2-codex@low` | `gpt-5.2-codex-low` | 将 `@` 替换为 `-` |
| `OpenAI/GPT-5.5-2026-05-14` | `gpt-5.5` | 去掉前缀和日期后缀 |
| `anthropic/claude-opus-4.8` | `claude-opus-4-8` | 去掉前缀并匹配点号格式 |
| `global.anthropic.claude-opus-4-8-v1:0` | `claude-opus-4-8` | 去掉包装前缀、版本后缀和 `:` 后缀 |
| `claude-haiku-4-5` | `claude-haiku-4-5-20251001` | 短 ID 匹配带版本定价 |
### 操作
+35 -12
View File
@@ -16,7 +16,7 @@
├── settings.json # 设备级设置
├── skills/ # 技能 SSOT 目录
├── skill-backups/ # 技能备份(卸载时创建)
└── db_backup_*.db # 数据库备份
└── backups/ # 数据库备份
```
### 数据库内容
@@ -51,7 +51,8 @@
"codexConfigDir": null,
"geminiConfigDir": null,
"opencodeConfigDir": null,
"openclawConfigDir": null
"openclawConfigDir": null,
"hermesConfigDir": null
}
```
@@ -196,17 +197,41 @@ GEMINI_MODEL=gemini-pro
### 配置目录
默认:`~/.opencode/`
默认:`~/.config/opencode/`
### 主要文件
```
~/.opencode/
├── config.json # 主配置文件
~/.config/opencode/
├── opencode.json # 主配置文件
├── AGENTS.md # 系统提示词
└── skills/ # 技能目录
└── ...
```
## Hermes 配置
### 配置目录
默认:`~/.hermes/`
### 主要文件
```
~/.hermes/
├── config.yaml # 主设置、供应商与 MCP 配置
├── .env # API keys 与 secrets
├── SOUL.md # Profile 身份/人格
├── memories/
│ ├── MEMORY.md # Agent 记忆
│ └── USER.md # 用户画像记忆
├── skills/ # 活跃技能目录
├── state.db # SQLite 会话数据库
└── sessions/ # Gateway 转录与可选 JSON 快照
```
### config.yaml
Hermes 使用 YAML 配置。CC Switch 将 MCP 服务器写入 `mcp_servers`,将可编辑的供应商条目写入 `custom_providers`,读取 Hermes `providers` 字典中的只读条目,并在切换供应商时更新 `model.provider` / `model.default`
## OpenClaw 配置
@@ -219,7 +244,6 @@ GEMINI_MODEL=gemini-pro
```
~/.openclaw/
├── openclaw.json # 主配置文件(JSON5 格式)
├── AGENTS.md # 系统提示词
└── skills/ # 技能目录
└── ...
```
@@ -246,18 +270,17 @@ OpenClaw 使用 JSON5 格式配置文件,主要包含以下部分:
env: {
ANTHROPIC_API_KEY: "sk-..."
},
// Agent 默认模型配置
// Agent 默认配置
agents: {
defaults: {
model: {
primary: "provider/model"
}
},
workspace: "~/.openclaw/workspace"
}
},
// 工具配置
tools: {},
// 工作区文件配置
workspace: {}
tools: {}
}
```
@@ -267,7 +290,7 @@ OpenClaw 使用 JSON5 格式配置文件,主要包含以下部分:
| `env` | 环境变量配置 |
| `agents.defaults` | Agent 默认模型设置 |
| `tools` | 工具配置 |
| `workspace` | 工作区文件管理 |
| `agents.defaults.workspace` | 工作区目录路径 |
## 配置优先级
+1 -1
View File
@@ -144,7 +144,7 @@ chmod +x CC-Switch-*.AppImage
- 版本不兼容
**解决方法**
1. 确认文件是 CC Switch 导出的 JSON 文件
1. 确认文件是 CC Switch 导出的 SQL 备份文件
2. 检查文件内容是否完整
3. 尝试用文本编辑器打开检查格式
+8 -8
View File
@@ -106,15 +106,15 @@
## 版本信息
- 文档版本:v3.15.0
- 最后更新:2026-05-16
- 适用于 CC Switch v3.15.0+
- 文档版本:v3.16.0
- 最后更新:2026-05-29
- 适用于 CC Switch v3.16.0+
### v3.15.0 亮点
### v3.16.0 亮点
- **Claude Desktop 一等管理面板**:支持第三方供应商、直连 / 模型映射两种模式、Copilot / Codex OAuth 复用与 3P profile 写入 — 详见 [2.6 Claude Desktop](./2-providers/2.6-claude-desktop.md)
- **按角色的模型映射**:用 Sonnet / Opus / Haiku 路由和 `supports1m` 标志适配 Claude Desktop 的模型校验
- **Claude Desktop 本地路由**:通过 `127.0.0.1:15721/claude-desktop` 为需要转换的供应商提供本地网关
- **Codex Chat Completions 路由**DeepSeek、Kimi、GLM、MiniMax 等仅支持 Chat 协议的供应商可通过 Codex 使用 — 详见 [2.1 添加供应商](./2-providers/2.1-add.md)
- **托管 CLI 工具生命周期**:在设置 / 关于页安装、升级、全部升级并诊断 Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes — 详见 [1.5 个性化配置](./1-getting-started/1.5-settings.md)
- **供应商与模型矩阵刷新**:新增合作方预设,刷新默认模型与计费矩阵,Claude Opus 默认升级到 4.8,适用场景下 GPT 默认升级到 5.5
- **路由支持徽章**Claude Code / Codex 供应商卡片会标明是否支持 Local Routing,便于选择可代理的供应商
- **Codex OAuth 实时模型发现**ChatGPT Codex 类供应商按需从 ChatGPT 后端拉取最新模型列表
- **用量看板筛选驱动 Hero**:展示缓存归一化后的真实总 token 与缓存命中率,并跟随日期 / 供应商 / 模型筛选实时更新 — 详见 [4.4 用量统计](./4-proxy/4.4-usage.md)
@@ -124,7 +124,7 @@
- **托盘按应用分级菜单**Claude / Codex / Gemini 独立子菜单,标题展示当前供应商与可用用量摘要 — 详见 [2.2 切换供应商](./2-providers/2.2-switch.md)
- **Skills 发现与批量更新**:SHA-256 更新检测、批量更新、skills.sh 公共注册表搜索 — 详见 [3.3 Skills 技能管理](./3-extensions/3.3-skills.md)
- **完整 URL 端点模式**:高级选项支持将 base_url 视作完整上游端点 — 详见 [2.1 添加供应商](./2-providers/2.1-add.md)
- **OpenCode / OpenClaw 流式检测覆盖**Stream Check 面板覆盖 Claude / Codex / Gemini / OpenCode / OpenClaw — 详见 [4.5 模型检查](./4-proxy/4.5-model-test.md)
- **OpenCode / OpenClaw / Hermes 流式检测覆盖**Stream Check 面板覆盖 Claude / Codex / Gemini / OpenCode / OpenClaw / Hermes — 详见 [4.5 模型检查](./4-proxy/4.5-model-test.md)
## 贡献
+114 -11
View File
@@ -682,20 +682,18 @@ fn set_codex_model_catalog_json_field(
let mut doc = config_text
.parse::<DocumentMut>()
.map_err(|e| AppError::Message(format!("Invalid Codex config.toml: {e}")))?;
let generated_path = get_codex_model_catalog_path();
match catalog_path {
Some(path) => {
doc["model_catalog_json"] = toml_edit::value(path.to_string_lossy().as_ref());
Some(_) => {
doc["model_catalog_json"] = toml_edit::value(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME);
}
None => {
let should_remove = doc
.get("model_catalog_json")
.and_then(|item| item.as_str())
.map(|path| {
path == generated_path.to_string_lossy().as_ref()
|| Path::new(path).file_name().and_then(|name| name.to_str())
== Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME)
Path::new(path).file_name().and_then(|name| name.to_str())
== Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME)
})
.unwrap_or(false);
if should_remove {
@@ -780,9 +778,8 @@ fn resolve_cc_switch_catalog_path(config_text: &str, generated_path: &Path) -> O
.filter(|s| !s.is_empty())?;
let referenced_path = Path::new(catalog_path_str);
let is_cc_switch_owned = catalog_path_str == generated_path.to_string_lossy().as_ref()
|| referenced_path.file_name().and_then(|name| name.to_str())
== Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME);
let is_cc_switch_owned = referenced_path.file_name().and_then(|name| name.to_str())
== Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME);
if !is_cc_switch_owned {
return None;
}
@@ -1791,7 +1788,7 @@ base_url = "https://production.api/v1"
}
#[test]
fn model_catalog_json_field_operates_on_top_level() {
fn model_catalog_json_field_writes_relative_filename() {
let input = r#"model_provider = "any"
[model_providers.any]
@@ -1805,7 +1802,7 @@ name = "any"
parsed
.get("model_catalog_json")
.and_then(|value| value.as_str()),
Some("/tmp/cc-switch-model-catalog.json")
Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME)
);
assert!(
parsed
@@ -2026,4 +2023,110 @@ name = "any"
);
}
}
#[test]
#[cfg(target_os = "windows")]
fn set_catalog_json_field_writes_filename_ignoring_unc_path() {
let input = r#"model_provider = "custom"
model = "glm-5"
"#;
// Simulate a WSL UNC path as cc-switch would see it on Windows;
// the function now writes just the relative filename.
let unc_path =
Path::new(r"\\wsl.localhost\Ubuntu\home\user\.codex\cc-switch-model-catalog.json");
let result = set_codex_model_catalog_json_field(input, Some(unc_path)).unwrap();
let parsed: toml::Value = toml::from_str(&result).unwrap();
let written_path = parsed
.get("model_catalog_json")
.and_then(|v| v.as_str())
.expect("model_catalog_json should be set");
assert_eq!(
written_path, CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME,
"should write only the relative filename, not the UNC path"
);
}
#[test]
fn set_catalog_json_field_writes_filename_for_any_path() {
let input = r#"model_provider = "custom"
model = "glm-5"
"#;
let regular_path = Path::new("/home/user/.codex/cc-switch-model-catalog.json");
let result = set_codex_model_catalog_json_field(input, Some(regular_path)).unwrap();
let parsed: toml::Value = toml::from_str(&result).unwrap();
assert_eq!(
parsed.get("model_catalog_json").and_then(|v| v.as_str()),
Some(CC_SWITCH_CODEX_MODEL_CATALOG_FILENAME),
"should write only the relative filename, not the full path"
);
}
#[test]
fn set_catalog_json_none_removes_cc_switch_owned_by_filename() {
// After the WSL fix, TOML may contain a Linux-style path.
// The None arm must still remove it (file_name match catches any format).
let input = r#"model_catalog_json = "/home/user/.codex/cc-switch-model-catalog.json"
"#;
let result = set_codex_model_catalog_json_field(input, None).unwrap();
let parsed: toml::Value = toml::from_str(&result).unwrap();
assert!(
parsed.get("model_catalog_json").is_none(),
"None arm should remove cc-switch-owned field regardless of path format"
);
}
#[test]
fn set_catalog_json_none_preserves_user_owned_catalog() {
let input = r#"model_catalog_json = "/Users/me/.codex/my-custom-catalog.json"
"#;
let result = set_codex_model_catalog_json_field(input, None).unwrap();
let parsed: toml::Value = toml::from_str(&result).unwrap();
assert_eq!(
parsed.get("model_catalog_json").and_then(|v| v.as_str()),
Some("/Users/me/.codex/my-custom-catalog.json"),
"None arm should NOT remove user-owned catalog"
);
}
#[test]
fn resolve_catalog_finds_relative_filename() {
let config_text = r#"model_provider = "custom"
model_catalog_json = "cc-switch-model-catalog.json"
"#;
let generated_path = PathBuf::from("/home/user/.codex/cc-switch-model-catalog.json");
let result = resolve_cc_switch_catalog_path(config_text, &generated_path);
assert_eq!(
result,
Some(generated_path),
"relative filename should resolve to generated_path for file I/O"
);
}
#[test]
fn resolve_catalog_ignores_user_owned_relative() {
let config_text = r#"model_catalog_json = "my-custom-catalog.json"
"#;
let generated_path = PathBuf::from("/home/user/.codex/cc-switch-model-catalog.json");
let result = resolve_cc_switch_catalog_path(config_text, &generated_path);
assert_eq!(
result, None,
"user-owned catalog should not be claimed by cc-switch"
);
}
#[test]
fn set_catalog_json_none_removes_relative_path() {
let input = r#"model_catalog_json = "cc-switch-model-catalog.json"
"#;
let result = set_codex_model_catalog_json_field(input, None).unwrap();
let parsed: toml::Value = toml::from_str(&result).unwrap();
assert!(
parsed.get("model_catalog_json").is_none(),
"None arm should remove relative cc-switch-owned field"
);
}
}
+142 -5
View File
@@ -417,6 +417,42 @@ fn resolve_native_credentials(app_type: &AppType, provider: Option<&Provider>) -
.unwrap_or_default()
}
fn resolve_coding_plan_credentials(
app_type: &AppType,
provider: Option<&Provider>,
usage_script: Option<&crate::provider::UsageScript>,
) -> (String, String) {
let is_zenmux = usage_script
.and_then(|s| s.coding_plan_provider.as_deref())
.map(|provider| provider.eq_ignore_ascii_case("zenmux"))
.unwrap_or(false);
if !is_zenmux {
return resolve_native_credentials(app_type, provider);
}
let script_base_url = usage_script
.and_then(|s| s.base_url.as_deref())
.unwrap_or("")
.trim_end_matches('/')
.to_string();
let script_api_key = usage_script
.and_then(|s| s.api_key.as_deref())
.unwrap_or("")
.to_string();
if !script_base_url.is_empty() && !script_api_key.is_empty() {
return (script_base_url, script_api_key);
}
let native = resolve_native_credentials(app_type, provider);
if !native.0.is_empty() && !native.1.is_empty() {
native
} else {
(script_base_url, script_api_key)
}
}
async fn query_provider_usage_inner(
state: &AppState,
copilot_state: &CopilotAuthState,
@@ -474,8 +510,8 @@ async fn query_provider_usage_inner(
// ── Coding Plan 专用路径 ──
if template_type == TEMPLATE_TYPE_TOKEN_PLAN {
// 从供应商配置中提取 API Key 和 Base URL(按 app 区分存储格式)
let (base_url, api_key) = resolve_native_credentials(&app_type, provider);
let (base_url, api_key) =
resolve_coding_plan_credentials(&app_type, provider, usage_script);
let quota = crate::services::coding_plan::get_coding_plan_quota(&base_url, &api_key)
.await
@@ -490,6 +526,19 @@ async fn query_provider_usage_inner(
});
}
// ZenMux 的 tier 携带 USD 额度信息,需要编码为 JSON extra
let has_usd = quota
.tiers
.first()
.map(|t| t.used_value_usd.is_some())
.unwrap_or(false);
let plan_label = quota
.credential_message
.as_deref()
.and_then(|msg| msg.split(' ').next())
.map(|tier| format!("ZenMux·{}", tier.to_uppercase()));
let mut first_tier = true;
let data: Vec<crate::provider::UsageData> = quota
.tiers
.iter()
@@ -497,6 +546,26 @@ async fn query_provider_usage_inner(
let total = 100.0;
let used = tier.utilization;
let remaining = total - used;
let extra = if has_usd {
let mut extra_json = serde_json::json!({
"resetsAt": tier.resets_at,
});
if let Some(v) = tier.used_value_usd {
extra_json["usedValueUsd"] = serde_json::json!(v);
}
if let Some(v) = tier.max_value_usd {
extra_json["maxValueUsd"] = serde_json::json!(v);
}
if first_tier {
if let Some(ref label) = plan_label {
extra_json["planLabel"] = serde_json::json!(label);
}
first_tier = false;
}
Some(extra_json.to_string())
} else {
tier.resets_at.clone()
};
crate::provider::UsageData {
plan_name: Some(tier.name.clone()),
remaining: Some(remaining),
@@ -505,7 +574,7 @@ async fn query_provider_usage_inner(
unit: Some("%".to_string()),
is_valid: Some(true),
invalid_message: None,
extra: tier.resets_at.clone(),
extra,
}
})
.collect();
@@ -950,11 +1019,31 @@ mod import_claude_desktop_tests {
#[cfg(test)]
mod native_query_credentials_tests {
use super::resolve_native_credentials;
use super::{resolve_coding_plan_credentials, resolve_native_credentials};
use crate::app_config::AppType;
use crate::provider::Provider;
use crate::provider::{Provider, UsageScript};
use serde_json::json;
fn usage_script(
coding_plan_provider: Option<&str>,
base_url: Option<&str>,
api_key: Option<&str>,
) -> UsageScript {
UsageScript {
enabled: true,
language: "javascript".to_string(),
code: String::new(),
timeout: Some(10),
api_key: api_key.map(str::to_string),
base_url: base_url.map(str::to_string),
access_token: None,
user_id: None,
template_type: Some("token_plan".to_string()),
auto_query_interval: None,
coding_plan_provider: coding_plan_provider.map(str::to_string),
}
}
#[test]
fn delegates_to_provider_for_codex() {
let provider = Provider::with_id(
@@ -979,4 +1068,52 @@ mod native_query_credentials_tests {
assert!(base_url.is_empty());
assert!(api_key.is_empty());
}
#[test]
fn zenmux_coding_plan_uses_script_credentials_first() {
let provider = Provider::with_id(
"test".to_string(),
"Test".to_string(),
json!({
"env": {
"ANTHROPIC_BASE_URL": "https://provider.zenmux.example/v1",
"ANTHROPIC_AUTH_TOKEN": "sk-provider"
}
}),
None,
);
let script = usage_script(
Some("zenmux"),
Some("https://script.zenmux.example/api/usage/"),
Some("sk-script"),
);
let (base_url, api_key) =
resolve_coding_plan_credentials(&AppType::Claude, Some(&provider), Some(&script));
assert_eq!(base_url, "https://script.zenmux.example/api/usage");
assert_eq!(api_key, "sk-script");
}
#[test]
fn zenmux_coding_plan_falls_back_to_provider_credentials() {
let provider = Provider::with_id(
"test".to_string(),
"Test".to_string(),
json!({
"env": {
"ANTHROPIC_BASE_URL": "https://provider.zenmux.example/v1",
"ANTHROPIC_AUTH_TOKEN": "sk-provider"
}
}),
None,
);
let script = usage_script(Some("zenmux"), Some("https://script.zenmux.example"), None);
let (base_url, api_key) =
resolve_coding_plan_credentials(&AppType::Claude, Some(&provider), Some(&script));
assert_eq!(base_url, "https://provider.zenmux.example/v1");
assert_eq!(api_key, "sk-provider");
}
}
+1
View File
@@ -1744,6 +1744,7 @@ impl Database {
"0.06",
"0.375",
),
("minimax-m3", "MiniMax M3", "0.60", "2.40", "0.12", "0"),
// GLM (智谱)
("glm-4.7", "GLM-4.7", "0.39", "1.75", "0.04", "0"),
("glm-4.6", "GLM-4.6", "0.28", "1.11", "0.03", "0"),
+49 -1
View File
@@ -9,7 +9,51 @@ use super::DeepLinkImportRequest;
use crate::AppType;
use crate::{store::AppState, Database};
use base64::prelude::*;
use std::sync::Arc;
use std::{env, ffi::OsString, sync::Arc};
struct TestHomeGuard {
_dir: tempfile::TempDir,
original_home: Option<OsString>,
original_userprofile: Option<OsString>,
original_test_home: Option<OsString>,
}
impl TestHomeGuard {
fn new() -> Self {
let dir = tempfile::tempdir().expect("create isolated test home");
let original_home = env::var_os("HOME");
let original_userprofile = env::var_os("USERPROFILE");
let original_test_home = env::var_os("CC_SWITCH_TEST_HOME");
env::set_var("HOME", dir.path());
env::set_var("USERPROFILE", dir.path());
env::set_var("CC_SWITCH_TEST_HOME", dir.path());
Self {
_dir: dir,
original_home,
original_userprofile,
original_test_home,
}
}
}
impl Drop for TestHomeGuard {
fn drop(&mut self) {
match &self.original_test_home {
Some(value) => env::set_var("CC_SWITCH_TEST_HOME", value),
None => env::remove_var("CC_SWITCH_TEST_HOME"),
}
match &self.original_userprofile {
Some(value) => env::set_var("USERPROFILE", value),
None => env::remove_var("USERPROFILE"),
}
match &self.original_home {
Some(value) => env::set_var("HOME", value),
None => env::remove_var("HOME"),
}
}
}
// =============================================================================
// Parser Tests
@@ -477,8 +521,12 @@ fn test_build_claude_provider_without_config_unchanged() {
// Prompt Tests
// =============================================================================
// Integration-style unit test: prompt import reaches PromptService and resolves
// live config file paths, so HOME must be isolated before it runs.
#[test]
#[serial_test::serial]
fn test_import_prompt_allows_space_in_base64_content() {
let _test_home = TestHomeGuard::new();
let url = "ccswitch://v1/import?resource=prompt&app=codex&name=PromptPlus&content=Pj4+";
let request = parse_deeplink_url(url).unwrap();
@@ -801,7 +801,8 @@ impl ChatToResponsesState {
json!({
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0
"total_tokens": 0,
"output_tokens_details": { "reasoning_tokens": 0 }
})
})
})
@@ -1507,7 +1507,8 @@ pub(crate) fn chat_usage_to_responses_usage(usage: Option<&Value>) -> Value {
return json!({
"input_tokens": 0,
"output_tokens": 0,
"total_tokens": 0
"total_tokens": 0,
"output_tokens_details": { "reasoning_tokens": 0 }
});
};
@@ -1540,8 +1541,17 @@ pub(crate) fn chat_usage_to_responses_usage(usage: Option<&Value>) -> Value {
result["input_tokens_details"] = json!({ "cached_tokens": cached });
}
if let Some(details) = usage.get("completion_tokens_details") {
result["output_tokens_details"] = details.clone();
if let Some(details) = usage
.get("completion_tokens_details")
.filter(|v| v.is_object())
{
let mut details = details.clone();
if details.get("reasoning_tokens").is_none() {
details["reasoning_tokens"] = json!(0);
}
result["output_tokens_details"] = details;
} else {
result["output_tokens_details"] = json!({ "reasoning_tokens": 0 });
}
if let Some(cache_read) = usage.get("cache_read_input_tokens") {
+381 -45
View File
@@ -16,6 +16,7 @@ enum CodingPlanProvider {
ZhipuEn,
MiniMaxCn,
MiniMaxEn,
ZenMux,
}
fn detect_provider(base_url: &str) -> Option<CodingPlanProvider> {
@@ -30,6 +31,8 @@ fn detect_provider(base_url: &str) -> Option<CodingPlanProvider> {
Some(CodingPlanProvider::MiniMaxCn)
} else if url.contains("api.minimax.io") {
Some(CodingPlanProvider::MiniMaxEn)
} else if url.contains("zenmux") {
Some(CodingPlanProvider::ZenMux)
} else {
None
}
@@ -145,6 +148,8 @@ async fn query_kimi(api_key: &str) -> SubscriptionQuota {
name: "five_hour".to_string(),
utilization,
resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
}
@@ -166,6 +171,8 @@ async fn query_kimi(api_key: &str) -> SubscriptionQuota {
name: "weekly_limit".to_string(),
utilization,
resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
@@ -225,6 +232,8 @@ fn parse_zhipu_token_tiers(data: &serde_json::Value) -> Vec<QuotaTier> {
name: name.to_string(),
utilization: percentage,
resets_at,
used_value_usd: None,
max_value_usd: None,
})
})
.collect()
@@ -370,50 +379,8 @@ async fn query_minimax(api_key: &str, is_cn: bool) -> SubscriptionQuota {
}
}
let mut tiers = Vec::new();
if let Some(model_remains) = body.get("model_remains").and_then(|v| v.as_array()) {
// 只取第一个模型(MiniMax-M*,主力编程模型)
if let Some(item) = model_remains.first() {
// usage_count 是剩余量(满额=total,用完=0),需反转为已用百分比
let interval_total = item
.get("current_interval_total_count")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let interval_remaining = item
.get("current_interval_usage_count")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let end_time = item.get("end_time").and_then(|v| v.as_i64());
if interval_total > 0.0 {
tiers.push(QuotaTier {
name: "five_hour".to_string(),
utilization: ((interval_total - interval_remaining) / interval_total) * 100.0,
resets_at: end_time.and_then(millis_to_iso8601),
});
}
// 周额度
let weekly_total = item
.get("current_weekly_total_count")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let weekly_remaining = item
.get("current_weekly_usage_count")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let weekly_end = item.get("weekly_end_time").and_then(|v| v.as_i64());
if weekly_total > 0.0 {
tiers.push(QuotaTier {
name: "weekly_limit".to_string(),
utilization: ((weekly_total - weekly_remaining) / weekly_total) * 100.0,
resets_at: weekly_end.and_then(millis_to_iso8601),
});
}
}
}
// 提取纯函数便于无 mock 单元测试;新接口直接给"剩余百分比",反转为已用百分比
let tiers = parse_minimax_tiers(&body);
SubscriptionQuota {
tool: "coding_plan".to_string(),
@@ -427,6 +394,204 @@ async fn query_minimax(api_key: &str, is_cn: bool) -> SubscriptionQuota {
}
}
// ── ZenMux ──────────────────────────────────────────────────
async fn query_zenmux(base_url: &str, api_key: &str) -> SubscriptionQuota {
let client = crate::proxy::http_client::get();
let resp = client
.get(base_url)
.header("Authorization", format!("Bearer {api_key}"))
.header("Accept", "application/json")
.timeout(std::time::Duration::from_secs(10))
.send()
.await;
let resp = match resp {
Ok(r) => r,
Err(e) => return make_error(format!("Network error: {e}")),
};
let status = resp.status();
if status == reqwest::StatusCode::UNAUTHORIZED || status == reqwest::StatusCode::FORBIDDEN {
return SubscriptionQuota {
tool: "coding_plan".to_string(),
credential_status: CredentialStatus::Expired,
credential_message: Some("Invalid API key".to_string()),
success: false,
tiers: vec![],
extra_usage: None,
error: Some(format!("Authentication failed (HTTP {status})")),
queried_at: Some(now_millis()),
};
}
if !status.is_success() {
let body = resp.text().await.unwrap_or_default();
return make_error(format!("API error (HTTP {status}): {body}"));
}
let body: serde_json::Value = match resp.json().await {
Ok(v) => v,
Err(e) => return make_error(format!("Failed to parse response: {e}")),
};
// 检查业务级别错误
if body.get("success").and_then(|v| v.as_bool()) != Some(true) {
let msg = body
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("Unknown error");
return make_error(format!("API error: {msg}"));
}
let data = match body.get("data") {
Some(d) => d,
None => return make_error("Missing 'data' field in response".to_string()),
};
let mut tiers = Vec::new();
// 5 小时窗口限额
if let Some(q5h) = data.get("quota_5_hour") {
let usage_pct = q5h
.get("usage_percentage")
.and_then(parse_f64)
.unwrap_or(0.0);
let resets_at = q5h
.get("resets_at")
.and_then(|v| v.as_str())
.map(String::from);
let used_usd = q5h.get("used_value_usd").and_then(parse_f64);
let max_usd = q5h.get("max_value_usd").and_then(parse_f64);
tiers.push(QuotaTier {
name: "five_hour".to_string(),
utilization: usage_pct * 100.0,
resets_at,
used_value_usd: used_usd,
max_value_usd: max_usd,
});
}
// 7 天窗口限额
if let Some(q7d) = data.get("quota_7_day") {
let usage_pct = q7d
.get("usage_percentage")
.and_then(parse_f64)
.unwrap_or(0.0);
let resets_at = q7d
.get("resets_at")
.and_then(|v| v.as_str())
.map(String::from);
let used_usd = q7d.get("used_value_usd").and_then(parse_f64);
let max_usd = q7d.get("max_value_usd").and_then(parse_f64);
tiers.push(QuotaTier {
name: "weekly_limit".to_string(),
utilization: usage_pct * 100.0,
resets_at,
used_value_usd: used_usd,
max_value_usd: max_usd,
});
}
// 套餐等级和账户状态存入 credential_message
let plan_tier = data
.get("plan")
.and_then(|p| p.get("tier"))
.and_then(|v| v.as_str())
.unwrap_or("");
let account_status = data
.get("account_status")
.and_then(|v| v.as_str())
.unwrap_or("");
let plan_info = if !plan_tier.is_empty() {
format!("{plan_tier} ({account_status})")
} else {
String::new()
};
SubscriptionQuota {
tool: "coding_plan".to_string(),
credential_status: CredentialStatus::Valid,
credential_message: if plan_info.is_empty() {
None
} else {
Some(plan_info)
},
success: true,
tiers,
extra_usage: None,
error: None,
queried_at: Some(now_millis()),
}
}
/// 从 `/coding_plan/remains` 响应中解析 MiniMax 编程套餐的额度 tier。
///
/// 新接口语义:`current_*_remaining_percent` 是"剩余百分比"(0-100),
/// `model_remains` 数组里有 `general`(编程套餐)和 `video` 等其他模型,
/// 这里只取 `general`,跳过 video。
///
/// 5h 桶始终存在;周桶并非所有套餐都有,靠 `current_weekly_status == 1`
/// 判定激活(无周限额套餐该字段为 3,`remaining_percent` 恒为 100,不应展示)。
fn parse_minimax_tiers(body: &serde_json::Value) -> Vec<QuotaTier> {
let mut tiers = Vec::new();
let Some(model_remains) = body.get("model_remains").and_then(|v| v.as_array()) else {
return tiers;
};
// 只取 model_name == "general" 的条目,跳过 video 等非编程模型
let Some(item) = model_remains.iter().find(|item| {
item.get("model_name")
.and_then(|v| v.as_str())
.map(|s| s == "general")
.unwrap_or(false)
}) else {
return tiers;
};
// 5h 桶:剩余百分比 → 已用百分比
if let Some(remain_pct) = item
.get("current_interval_remaining_percent")
.and_then(|v| v.as_f64())
{
let resets_at = item
.get("end_time")
.and_then(|v| v.as_i64())
.and_then(millis_to_iso8601);
tiers.push(QuotaTier {
name: TIER_FIVE_HOUR.to_string(),
utilization: 100.0 - remain_pct,
resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
// 周桶:仅当 status=1 时激活;status=3 等表示该套餐无周限额,跳过
if item.get("current_weekly_status").and_then(|v| v.as_i64()) == Some(1) {
if let Some(remain_pct) = item
.get("current_weekly_remaining_percent")
.and_then(|v| v.as_f64())
{
let resets_at = item
.get("weekly_end_time")
.and_then(|v| v.as_i64())
.and_then(millis_to_iso8601);
tiers.push(QuotaTier {
name: TIER_WEEKLY_LIMIT.to_string(),
utilization: 100.0 - remain_pct,
resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
}
tiers
}
// ── 公开入口 ────────────────────────────────────────────────
pub async fn get_coding_plan_quota(
@@ -467,6 +632,7 @@ pub async fn get_coding_plan_quota(
CodingPlanProvider::ZhipuCn | CodingPlanProvider::ZhipuEn => query_zhipu(api_key).await,
CodingPlanProvider::MiniMaxCn => query_minimax(api_key, true).await,
CodingPlanProvider::MiniMaxEn => query_minimax(api_key, false).await,
CodingPlanProvider::ZenMux => query_zenmux(base_url, api_key).await,
};
Ok(quota)
@@ -474,7 +640,7 @@ pub async fn get_coding_plan_quota(
#[cfg(test)]
mod tests {
use super::{parse_zhipu_token_tiers, TIER_FIVE_HOUR, TIER_WEEKLY_LIMIT};
use super::{parse_minimax_tiers, parse_zhipu_token_tiers, TIER_FIVE_HOUR, TIER_WEEKLY_LIMIT};
use serde_json::json;
#[test]
@@ -604,4 +770,174 @@ mod tests {
assert_eq!(tiers[0].name, TIER_FIVE_HOUR);
assert_eq!(tiers[1].name, TIER_WEEKLY_LIMIT);
}
// ── MiniMax ──
#[test]
fn minimax_general_two_tiers_from_remaining_percent() {
// 主路径:general 桶 5h 剩 98% / weekly 剩 95% → 已用 2% / 5%
let body = json!({
"model_remains": [
{
"model_name": "general",
"current_interval_remaining_percent": 98.0,
"current_weekly_remaining_percent": 95.0,
"current_interval_status": 1,
"current_weekly_status": 1,
"end_time": 1_780_329_600_000_i64,
"weekly_end_time": 1_780_848_000_000_i64
},
{
"model_name": "video",
"current_interval_remaining_percent": 100.0,
"current_weekly_remaining_percent": 100.0
}
],
"base_resp": { "status_code": 0, "status_msg": "success" }
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 2);
assert_eq!(tiers[0].name, TIER_FIVE_HOUR);
assert_eq!(tiers[0].utilization, 2.0);
assert!(tiers[0].resets_at.is_some());
assert_eq!(tiers[1].name, TIER_WEEKLY_LIMIT);
assert_eq!(tiers[1].utilization, 5.0);
assert!(tiers[1].resets_at.is_some());
}
#[test]
fn minimax_skips_video_and_finds_general_in_any_position() {
// 防御性:即使 video 排在数组前面,general 排在后面,仍应被定位到。
let body = json!({
"model_remains": [
{
"model_name": "video",
"current_interval_remaining_percent": 50.0,
"current_weekly_remaining_percent": 50.0
},
{
"model_name": "general",
"current_interval_remaining_percent": 80.0,
"current_weekly_remaining_percent": 70.0,
"current_interval_status": 1,
"current_weekly_status": 1
}
]
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 2);
// 取的是 general 桶,不是 video(20%/30% 而非 50%/50%)
assert_eq!(tiers[0].utilization, 20.0);
assert_eq!(tiers[1].utilization, 30.0);
}
#[test]
fn minimax_missing_general_returns_empty() {
// model_remains 只有 video / 空 / 缺字段 → 不应崩溃,tiers 为空
let body = json!({
"model_remains": [
{
"model_name": "video",
"current_interval_remaining_percent": 100.0,
"current_weekly_remaining_percent": 100.0
}
]
});
assert!(parse_minimax_tiers(&body).is_empty());
let body_empty: serde_json::Value = json!({ "model_remains": [] });
assert!(parse_minimax_tiers(&body_empty).is_empty());
let body_no_field = json!({});
assert!(parse_minimax_tiers(&body_no_field).is_empty());
}
#[test]
fn minimax_missing_percent_fields_skips_tier() {
// 字段缺失时只跳过对应桶,另一边仍能展示
let body = json!({
"model_remains": [{
"model_name": "general",
"current_interval_remaining_percent": 60.0,
"current_weekly_status": 1
// 缺 current_weekly_remaining_percent
}]
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 1);
assert_eq!(tiers[0].name, TIER_FIVE_HOUR);
assert_eq!(tiers[0].utilization, 40.0);
}
#[test]
fn minimax_negative_percent_passes_through() {
// 防御性:与 parse_zhipu_token_tiers 约定一致,负数 / 超 100 不做范围裁剪
let body = json!({
"model_remains": [{
"model_name": "general",
"current_interval_remaining_percent": -5.0,
"current_weekly_remaining_percent": 150.0,
"current_interval_status": 1,
"current_weekly_status": 1
}]
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 2);
assert_eq!(tiers[0].utilization, 105.0); // 100 - (-5)
assert_eq!(tiers[1].utilization, -50.0); // 100 - 150
}
#[test]
fn minimax_weekly_status_3_skips_weekly_tier() {
// 无周限额套餐:current_weekly_status=3,remaining_percent 恒为 100,
// 不应推 weekly_limit tier(否则会显示"0% 已用"的假周桶)
let body = json!({
"model_remains": [
{
"model_name": "general",
"start_time": 1_780_347_600_000_i64,
"end_time": 1_780_365_600_000_i64,
"remains_time": 4_161_372_i64,
"current_interval_remaining_percent": 99,
"current_interval_status": 1,
"current_weekly_total_count": 0,
"current_weekly_usage_count": 0,
"weekly_start_time": 1_780_243_200_000_i64,
"weekly_end_time": 1_780_848_000_000_i64,
"weekly_remains_time": 486_561_372_i64,
"current_weekly_status": 3,
"current_weekly_remaining_percent": 100
},
{
"model_name": "video",
"current_interval_remaining_percent": 100,
"current_weekly_status": 3,
"current_weekly_remaining_percent": 100
}
],
"base_resp": { "status_code": 0, "status_msg": "success" }
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 1);
assert_eq!(tiers[0].name, TIER_FIVE_HOUR);
assert_eq!(tiers[0].utilization, 1.0);
assert!(tiers[0].resets_at.is_some());
}
#[test]
fn minimax_weekly_status_2_also_skips_weekly_tier() {
// 防御性:除 1 之外的 status 都视为周桶未激活,跳过
let body = json!({
"model_remains": [{
"model_name": "general",
"current_interval_remaining_percent": 80.0,
"current_weekly_remaining_percent": 50.0,
"current_weekly_status": 2
}]
});
let tiers = parse_minimax_tiers(&body);
assert_eq!(tiers.len(), 1);
assert_eq!(tiers[0].name, TIER_FIVE_HOUR);
assert_eq!(tiers[0].utilization, 20.0);
}
}
+14
View File
@@ -32,6 +32,12 @@ pub struct QuotaTier {
pub utilization: f64,
/// ISO 8601 重置时间
pub resets_at: Option<String>,
/// ZenMux: 已用额度(USD
#[serde(skip_serializing_if = "Option::is_none")]
pub used_value_usd: Option<f64>,
/// ZenMux: 窗口上限(USD
#[serde(skip_serializing_if = "Option::is_none")]
pub max_value_usd: Option<f64>,
}
/// 超额使用信息
@@ -377,6 +383,8 @@ async fn query_claude_quota(access_token: &str) -> SubscriptionQuota {
name: tier_name.to_string(),
utilization: util,
resets_at: w.resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
}
@@ -395,6 +403,8 @@ async fn query_claude_quota(access_token: &str) -> SubscriptionQuota {
name: key.clone(),
utilization: util,
resets_at: w.resets_at,
used_value_usd: None,
max_value_usd: None,
});
}
}
@@ -714,6 +724,8 @@ pub(crate) async fn query_codex_quota(
.unwrap_or_else(|| "unknown".to_string()),
utilization: used,
resets_at: window.reset_at.and_then(unix_ts_to_iso),
used_value_usd: None,
max_value_usd: None,
});
}
}
@@ -1179,6 +1191,8 @@ async fn query_gemini_quota(access_token: &str) -> SubscriptionQuota {
name,
utilization: (1.0 - remaining) * 100.0,
resets_at: reset_time,
used_value_usd: None,
max_value_usd: None,
})
.collect();
+2
View File
@@ -902,6 +902,8 @@ mod tests {
name: name.to_string(),
utilization,
resets_at: None,
used_value_usd: None,
max_value_usd: None,
}
}
@@ -309,6 +309,8 @@ export const TierBadge: React.FC<{
: tier.name;
const countdown = countdownStr(tier.resetsAt);
const hasUsd = tier.usedValueUsd != null && tier.maxValueUsd != null;
return (
<div className="flex items-center gap-0.5">
<span className="text-gray-500 dark:text-gray-400">{label}:</span>
@@ -317,6 +319,11 @@ export const TierBadge: React.FC<{
>
{t("subscription.utilization", { value: Math.round(tier.utilization) })}
</span>
{hasUsd && (
<span className="text-muted-foreground/60">
(${tier.usedValueUsd!.toFixed(2)}/${tier.maxValueUsd!.toFixed(2)})
</span>
)}
{countdown && (
<span className="text-muted-foreground/60 ml-0.5 flex items-center gap-px">
<Clock size={10} />
+33 -4
View File
@@ -19,10 +19,26 @@ interface UsageFooterProps {
/** UsageData → QuotaTier 转换(Token Plan 使用) */
function toQuotaTier(data: UsageData): QuotaTier {
const extra = data.extra;
if (extra && extra.startsWith("{")) {
try {
const parsed = JSON.parse(extra);
return {
name: data.planName || "",
utilization: data.used || 0,
resetsAt: parsed.resetsAt || null,
usedValueUsd: parsed.usedValueUsd ?? null,
maxValueUsd: parsed.maxValueUsd ?? null,
planLabel: parsed.planLabel ?? null,
};
} catch {
// fall through to plain string
}
}
return {
name: data.planName || "",
utilization: data.used || 0,
resetsAt: data.extra || null,
resetsAt: extra || null,
};
}
@@ -147,9 +163,22 @@ const UsageFooter: React.FC<UsageFooterProps> = ({
</div>
{/* 第二行:tier 徽章(复用官方订阅的 TierBadge) */}
<div className="flex items-center gap-2">
{usageDataList.map((data, index) => (
<TierBadge key={index} tier={toQuotaTier(data)} t={t} />
))}
{(() => {
const tiers = usageDataList.map((d) => toQuotaTier(d));
const planLabel = tiers[0]?.planLabel;
return (
<>
{planLabel && (
<span className="font-semibold text-muted-foreground">
💰 {planLabel}
</span>
)}
{tiers.map((tier, index) => (
<TierBadge key={index} tier={tier} t={t} />
))}
</>
);
})()}
</div>
</div>
);
+77 -7
View File
@@ -444,8 +444,14 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
// Coding Plan 模板使用专用 API
if (selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN) {
const baseUrl = providerCredentials.baseUrl ?? "";
const apiKey = providerCredentials.apiKey ?? "";
// ZenMux 使用用户在脚本配置中手动填入的 API Key 和 Base URL
const isZenMux = script.codingPlanProvider === "zenmux";
const baseUrl = isZenMux
? (script.baseUrl ?? "")
: (providerCredentials.baseUrl ?? "");
const apiKey = isZenMux
? (script.apiKey ?? "")
: (providerCredentials.apiKey ?? "");
const { subscriptionApi } = await import("@/lib/api/subscription");
const quota = await subscriptionApi.getCodingPlanQuota(baseUrl, apiKey);
if (quota.success && quota.tiers.length > 0) {
@@ -626,15 +632,17 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const autoDetected = detectCodingPlanProvider(
providerCredentials.baseUrl,
);
const provider = script.codingPlanProvider || autoDetected || "kimi";
// ZenMux 允许手动填写 API Key 和 Base URL,不清除
const isZenMux = provider === "zenmux";
setScript({
...script,
code: "",
apiKey: undefined,
baseUrl: undefined,
apiKey: isZenMux ? script.apiKey : undefined,
baseUrl: isZenMux ? script.baseUrl : undefined,
accessToken: undefined,
userId: undefined,
codingPlanProvider:
script.codingPlanProvider || autoDetected || "kimi",
codingPlanProvider: provider,
});
} else if (presetName === TEMPLATE_TYPES.BALANCE) {
// 官方余额查询模板不需要脚本,使用 Rust 原生查询
@@ -653,7 +661,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
const shouldShowCredentialsConfig =
selectedTemplate === TEMPLATE_TYPES.GENERAL ||
selectedTemplate === TEMPLATE_TYPES.NEW_API;
selectedTemplate === TEMPLATE_TYPES.NEW_API ||
(selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN &&
script.codingPlanProvider === "zenmux");
const footer = (
<>
@@ -1050,6 +1060,66 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
</div>
</>
)}
{selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN &&
script.codingPlanProvider === "zenmux" && (
<>
<div className="space-y-2">
<Label htmlFor="usage-zenmux-base-url">
{t("usageScript.baseUrl")}
</Label>
<Input
id="usage-zenmux-base-url"
type="text"
value={script.baseUrl || ""}
onChange={(e) =>
setScript({ ...script, baseUrl: e.target.value })
}
placeholder="https://api.zenmux.com/v1/..."
autoComplete="off"
className="border-white/10"
/>
</div>
<div className="space-y-2">
<Label htmlFor="usage-zenmux-api-key">API Key</Label>
<div className="relative">
<Input
id="usage-zenmux-api-key"
type={showApiKey ? "text" : "password"}
value={script.apiKey || ""}
onChange={(e) =>
setScript({
...script,
apiKey: e.target.value,
})
}
placeholder="sk-..."
autoComplete="off"
className="border-white/10"
/>
{script.apiKey && (
<button
type="button"
onClick={() => setShowApiKey(!showApiKey)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
aria-label={
showApiKey
? t("apiKeyInput.hide")
: t("apiKeyInput.show")
}
>
{showApiKey ? (
<EyeOff size={16} />
) : (
<Eye size={16} />
)}
</button>
)}
</div>
</div>
</>
)}
</div>
</div>
)}
+6 -1
View File
@@ -11,7 +11,7 @@ import { TEMPLATE_TYPES } from "@/config/constants";
export interface CodingPlanProviderEntry {
/** 与后端 QuotaTier 的 `codingPlanProvider` 取值对齐 */
id: "kimi" | "zhipu" | "minimax";
id: "kimi" | "zhipu" | "minimax" | "zenmux";
/** UsageScriptModal 下拉显示用 */
label: string;
/** base_url 匹配规则 */
@@ -30,6 +30,11 @@ export const CODING_PLAN_PROVIDERS: readonly CodingPlanProviderEntry[] = [
label: "MiniMax",
pattern: /api\.minimaxi?\.com|api\.minimax\.io/i,
},
{
id: "zenmux",
label: "ZenMux",
pattern: /zenmux\./i,
},
] as const;
/** 根据 Base URL 自动检测 Coding Plan 供应商;未命中返回 null */
+1 -1
View File
@@ -559,7 +559,7 @@
"useAppWindowControls": "啟用應用程式等級視窗按鈕",
"useAppWindowControlsDescription": "開啟後使用應用程式自建的最小化、最大化/還原、關閉按鈕;關閉後沿用系統視窗模式。",
"enableClaudePluginIntegration": "套用至 Claude Code 外掛程式",
"enableClaudePluginIntegrationDescription": "開啟後 Vscode Claude Code 外掛程式的供應商將隨本軟體切換",
"enableClaudePluginIntegrationDescription": "開啟後 VS Code Claude Code 外掛程式的供應商將隨本軟體切換",
"skipClaudeOnboarding": "跳過 Claude Code 初次安裝確認",
"skipClaudeOnboardingDescription": "開啟後跳過 Claude Code 初次安裝確認",
"codexAuth": "Codex 應用增強",
+1 -1
View File
@@ -559,7 +559,7 @@
"useAppWindowControls": "启用应用级窗口按钮",
"useAppWindowControlsDescription": "开启后使用应用自建的最小化、最大化/还原、关闭按钮;关闭后沿用系统窗口模式。",
"enableClaudePluginIntegration": "应用到 Claude Code 插件",
"enableClaudePluginIntegrationDescription": "开启后 Vscode Claude Code 插件的供应商将随本软件切换",
"enableClaudePluginIntegrationDescription": "开启后 VS Code Claude Code 插件的供应商将随本软件切换",
"skipClaudeOnboarding": "跳过 Claude Code 初次安装确认",
"skipClaudeOnboardingDescription": "开启后跳过 Claude Code 初次安装确认",
"codexAuth": "Codex 应用增强",
File diff suppressed because one or more lines are too long
+7
View File
@@ -306,6 +306,13 @@ export const iconMetadata: Record<string, IconMetadata> = {
keywords: ["minimax"],
defaultColor: "#FF6B6B",
},
zenmux: {
name: "zenmux",
displayName: "ZenMux",
category: "ai-provider",
keywords: ["zenmux", "zen", "mux"],
defaultColor: "#6366F1",
},
mistral: {
name: "mistral",
displayName: "Mistral",
+3
View File
@@ -8,6 +8,9 @@ export interface QuotaTier {
name: string;
utilization: number; // 0-100
resetsAt: string | null;
usedValueUsd?: number | null;
maxValueUsd?: number | null;
planLabel?: string | null;
}
export interface ExtraUsage {