mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-26 14:35:22 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c229c47c00 | |||
| 6c477a60f9 | |||
| 7eac809689 | |||
| dafa77897b | |||
| 0f959112b1 | |||
| f4c284f86c | |||
| 3878a16c4f |
@@ -5,6 +5,46 @@ All notable changes to CC Switch will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.8.0] - 2025-11-28
|
||||||
|
|
||||||
|
### Major Updates
|
||||||
|
|
||||||
|
- **Persistence architecture upgrade** - Moved from single JSON storage to SQLite + JSON dual-layer; added schema versioning, transactions, and SQL import/export; first launch auto-migrates `config.json` to SQLite while keeping originals safe.
|
||||||
|
- **Brand new UI** - Full layout redesign, unified component/ConfirmDialog styles, smoother animations, overscroll disabled; Tailwind CSS downgraded to v3.4 for compatibility.
|
||||||
|
- **Japanese language support** - UI now localized in Chinese/English/Japanese.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Skills recursive scanning** - Discovers nested `SKILL.md` files across multi-level directories; same-name skills allowed by full-path dedup.
|
||||||
|
- **Provider icons** - Presets ship with default icons; custom icon colors; icons retained when duplicating providers.
|
||||||
|
- **Auto launch on startup** - One-click enable/disable using Registry/LaunchAgent/XDG autostart.
|
||||||
|
- **Provider preset** - Added MiniMax partner preset.
|
||||||
|
- **Form validation** - Required fields get real-time validation and unified toast messaging.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Custom endpoints loss** - Switched provider updates to `UPDATE` to avoid cascade deletes from `INSERT OR REPLACE`.
|
||||||
|
- **Gemini config writing** - Correctly writes custom env vars to `.env` and keeps auth configs isolated.
|
||||||
|
- **Provider validation** - Handles missing current provider IDs and preserves icon fields on duplicate.
|
||||||
|
- **Linux rendering** - Fixed WebKitGTK DMA-BUF rendering and preserved user `.desktop` customizations.
|
||||||
|
- **Misc** - Removed redundant usage queries; corrected DMXAPI auth token field; restored missing deeplink translations; fixed usage script template init.
|
||||||
|
|
||||||
|
### Technical
|
||||||
|
|
||||||
|
- **Database modules** - Added `schema`, `backup`, `migration`, and DAO layers for providers/MCP/prompts/skills/settings.
|
||||||
|
- **Service modularization** - Split provider service into live/auth/endpoints/usage modules; deeplink parsing/import logic modularized.
|
||||||
|
- **Code cleanup** - Removed legacy JSON-era import/export, unused MCP types; unified error handling; tests migrated to SQLite backend and MSW handlers updated.
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
|
||||||
|
- First launch auto-migrates data from `config.json` to SQLite and device settings to `settings.json`; originals kept; error dialog on failure; dry-run supported.
|
||||||
|
|
||||||
|
### Stats
|
||||||
|
|
||||||
|
- 51 commits since v3.7.1; 207 files changed; +17,297 / -6,870 lines. See [release-note-v3.8.0](docs/release-note-v3.8.0-en.md) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [3.7.1] - 2025-11-22
|
## [3.7.1] - 2025-11-22
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# All-in-One Assistant for Claude Code, Codex & Gemini CLI
|
# All-in-One Assistant for Claude Code, Codex & Gemini CLI
|
||||||
|
|
||||||
[](https://github.com/farion1231/cc-switch/releases)
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
[](https://github.com/trending/typescript)
|
[](https://github.com/trending/typescript)
|
||||||
[](https://github.com/farion1231/cc-switch/releases)
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
[](https://tauri.app/)
|
[](https://tauri.app/)
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<a href="https://trendshift.io/repositories/15372" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/15372" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
English | [中文](README_ZH.md) | [Changelog](CHANGELOG.md)
|
English | [中文](README_ZH.md) | [日本語](README_JA.md) | [Changelog](CHANGELOG.md)
|
||||||
|
|
||||||
**From Provider Switcher to All-in-One AI CLI Management Platform**
|
**From Provider Switcher to All-in-One AI CLI Management Platform**
|
||||||
|
|
||||||
@@ -51,9 +51,42 @@ Get 10% OFF the GLM CODING PLAN with [this link](https://z.ai/subscribe?ic=8JVLJ
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Current Version: v3.7.0 | [Full Changelog](CHANGELOG.md) | [📋 Release Notes](docs/release-note-v3.7.0-en.md)
|
### Current Version: v3.8.0 | [Full Changelog](CHANGELOG.md) | [Release Notes](docs/release-note-v3.8.0-en.md)
|
||||||
|
|
||||||
**v3.7.0 Major Update (2025-11-19)**
|
**v3.8.0 Major Update (2025-11-28)**
|
||||||
|
|
||||||
|
**Persistence Architecture Upgrade & Brand New UI**
|
||||||
|
|
||||||
|
- **SQLite + JSON Dual-layer Architecture**
|
||||||
|
- Migrated from JSON file storage to SQLite + JSON dual-layer structure
|
||||||
|
- Syncable data (providers, MCP, Prompts, Skills) stored in SQLite
|
||||||
|
- Device-level data (window state, local paths) stored in JSON
|
||||||
|
- Lays the foundation for future cloud sync functionality
|
||||||
|
- Schema version management for database migrations
|
||||||
|
|
||||||
|
- **Brand New User Interface**
|
||||||
|
- Completely redesigned interface layout
|
||||||
|
- Unified component styles and smoother animations
|
||||||
|
- Optimized visual hierarchy
|
||||||
|
- Tailwind CSS downgraded from v4 to v3.4 for better browser compatibility
|
||||||
|
|
||||||
|
- **Japanese Language Support**
|
||||||
|
- Added Japanese interface support (now supports Chinese/English/Japanese)
|
||||||
|
|
||||||
|
- **Auto Launch on Startup**
|
||||||
|
- One-click enable/disable in settings
|
||||||
|
- Platform-native APIs (Registry/LaunchAgent/XDG autostart)
|
||||||
|
|
||||||
|
- **Skills Recursive Scanning**
|
||||||
|
- Support for multi-level directory structures
|
||||||
|
- Allow same-named skills from different repositories
|
||||||
|
|
||||||
|
- **Critical Bug Fixes**
|
||||||
|
- Fixed custom endpoints lost when updating providers
|
||||||
|
- Fixed Gemini configuration write issues
|
||||||
|
- Fixed Linux WebKitGTK rendering issues
|
||||||
|
|
||||||
|
**v3.7.0 Highlights**
|
||||||
|
|
||||||
**Six Core Features, 18,000+ Lines of New Code**
|
**Six Core Features, 18,000+ Lines of New Code**
|
||||||
|
|
||||||
@@ -226,10 +259,10 @@ Download the latest `CC-Switch-v{version}-Linux.deb` package or `CC-Switch-v{ver
|
|||||||
- MCP servers: `~/.gemini/settings.json` → `mcpServers`
|
- MCP servers: `~/.gemini/settings.json` → `mcpServers`
|
||||||
- Tray quick switch: Each provider switch rewrites `~/.gemini/.env`, no need to restart Gemini CLI
|
- Tray quick switch: Each provider switch rewrites `~/.gemini/.env`, no need to restart Gemini CLI
|
||||||
|
|
||||||
**CC Switch Storage**
|
**CC Switch Storage (v3.8.0 New Architecture)**
|
||||||
|
|
||||||
- Main config (SSOT): `~/.cc-switch/config.json` (includes providers, MCP, Prompts presets, etc.)
|
- Database (SSOT): `~/.cc-switch/cc-switch.db` (SQLite, stores providers, MCP, Prompts, Skills)
|
||||||
- Settings: `~/.cc-switch/settings.json`
|
- Local settings: `~/.cc-switch/settings.json` (device-level settings)
|
||||||
- Backups: `~/.cc-switch/backups/` (auto-rotate, keep 10)
|
- Backups: `~/.cc-switch/backups/` (auto-rotate, keep 10)
|
||||||
|
|
||||||
### Cloud Sync Setup
|
### Cloud Sync Setup
|
||||||
@@ -265,11 +298,12 @@ Download the latest `CC-Switch-v{version}-Linux.deb` package or `CC-Switch-v{ver
|
|||||||
|
|
||||||
**Core Design Patterns**
|
**Core Design Patterns**
|
||||||
|
|
||||||
- **SSOT** (Single Source of Truth): All provider configs stored in `~/.cc-switch/config.json`
|
- **SSOT** (Single Source of Truth): All data stored in `~/.cc-switch/cc-switch.db` (SQLite)
|
||||||
|
- **Dual-layer Storage**: SQLite for syncable data, JSON for device-level settings
|
||||||
- **Dual-way Sync**: Write to live files on switch, backfill from live when editing active provider
|
- **Dual-way Sync**: Write to live files on switch, backfill from live when editing active provider
|
||||||
- **Atomic Writes**: Temp file + rename pattern prevents config corruption
|
- **Atomic Writes**: Temp file + rename pattern prevents config corruption
|
||||||
- **Concurrency Safe**: RwLock with scoped guards avoids deadlocks
|
- **Concurrency Safe**: Mutex-protected database connection avoids race conditions
|
||||||
- **Layered Architecture**: Clear separation (Commands → Services → Models)
|
- **Layered Architecture**: Clear separation (Commands → Services → DAO → Database)
|
||||||
|
|
||||||
**Key Components**
|
**Key Components**
|
||||||
|
|
||||||
|
|||||||
+476
@@ -0,0 +1,476 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
# Claude Code / Codex / Gemini CLI オールインワン・アシスタント
|
||||||
|
|
||||||
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
|
[](https://github.com/trending/typescript)
|
||||||
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
|
[](https://tauri.app/)
|
||||||
|
[](https://github.com/farion1231/cc-switch/releases/latest)
|
||||||
|
|
||||||
|
<a href="https://trendshift.io/repositories/15372" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
|
[English](README.md) | [中文](README_ZH.md) | 日本語 | [Changelog](CHANGELOG.md) | [v3.8.0 リリースノート](docs/release-note-v3.8.0-en.md)
|
||||||
|
|
||||||
|
**プロバイダスイッチャーから AI CLI 一体型管理プラットフォームへ**
|
||||||
|
|
||||||
|
Claude Code・Codex・Gemini CLI のプロバイダ設定、MCP サーバー、Skills 拡張、システムプロンプトを統合管理。
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## ❤️スポンサー
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
本プロジェクトは Z.ai の GLM CODING PLAN による支援を受けています。
|
||||||
|
|
||||||
|
GLM CODING PLAN は AI コーディング向けのサブスクリプションで、月額わずか 3 ドルから。Claude Code、Cline、Roo Code など 10 以上の人気 AI コーディングツールでフラッグシップモデル GLM-4.6 を利用でき、速く安定した開発体験を提供します。
|
||||||
|
|
||||||
|
[このリンク](https://z.ai/subscribe?ic=8JVLJQFSKB) から申し込むと 10% オフになります!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td width="180"><img src="assets/partners/logos/packycode.png" alt="PackyCode" width="150"></td>
|
||||||
|
<td>PackyCode のご支援に感謝します!PackyCode は Claude Code、Codex、Gemini などのリレーサービスを提供する信頼性の高い API 中継プラットフォームです。本ソフト利用者向けに特別割引があります:<a href="https://www.packyapi.com/register?aff=cc-switch">このリンク</a>で登録し、チャージ時に「cc-switch」クーポンを入力すると 10% オフになります。</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td width="180"><img src="assets/partners/logos/sds-en.png" alt="ShanDianShuo" width="150"></td>
|
||||||
|
<td>ShanDianShuo のご支援に感謝します!ShanDianShuo はローカルファーストの音声入力ツールで、ミリ秒遅延・データは端末から外に出ず・キーボード入力の 4 倍の速度・AI 自動補正・プライバシー優先で完全無料。Claude Code と組み合わせればコーディング効率が倍増します。<a href="https://www.shandianshuo.cn">Mac/Win 版を無料ダウンロード</a></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
## スクリーンショット
|
||||||
|
|
||||||
|
| メイン画面 | プロバイダ追加 |
|
||||||
|
| :-------------------------------------------: | :---------------------------------------------: |
|
||||||
|
|  |  |
|
||||||
|
|
||||||
|
## 特長
|
||||||
|
|
||||||
|
### 現在のバージョン:v3.8.0 | [完全な更新履歴](CHANGELOG.md) | [リリースノート](docs/release-note-v3.8.0-en.md)
|
||||||
|
|
||||||
|
**v3.8.0 メジャーアップデート (2025-11-28)**
|
||||||
|
|
||||||
|
**永続化アーキテクチャ刷新 & 新 UI**
|
||||||
|
|
||||||
|
- **SQLite + JSON 二層構造**
|
||||||
|
- JSON 単独保存から SQLite + JSON の二層構造へ移行
|
||||||
|
- 同期対象データ(プロバイダ、MCP、Prompts、Skills)は SQLite に保存
|
||||||
|
- デバイス固有データ(ウィンドウ状態、ローカルパス)は JSON に保存
|
||||||
|
- 将来のクラウド同期の土台を用意
|
||||||
|
- DB マイグレーション用にスキーマバージョンを管理
|
||||||
|
|
||||||
|
- **新しいユーザーインターフェース**
|
||||||
|
- レイアウトを全面再設計
|
||||||
|
- コンポーネントスタイルとアニメーションを統一
|
||||||
|
- 視覚的な階層を最適化
|
||||||
|
- ブラウザ互換性向上のため Tailwind CSS を v4 から v3.4 にダウングレード
|
||||||
|
|
||||||
|
- **日本語対応**
|
||||||
|
- UI が中国語/英語/日本語の 3 言語対応に
|
||||||
|
|
||||||
|
- **自動起動**
|
||||||
|
- 設定画面でワンクリック ON/OFF
|
||||||
|
- プラットフォームネイティブ API(Registry/LaunchAgent/XDG autostart)を使用
|
||||||
|
|
||||||
|
- **Skills 再帰スキャン**
|
||||||
|
- 多階層ディレクトリをサポート
|
||||||
|
- リポジトリが異なる同名スキルを許可
|
||||||
|
|
||||||
|
- **重要なバグ修正**
|
||||||
|
- プロバイダ更新時にカスタムエンドポイントが失われる問題を修正
|
||||||
|
- Gemini 設定の書き込み問題を修正
|
||||||
|
- Linux WebKitGTK の描画問題を修正
|
||||||
|
|
||||||
|
**v3.7.0 ハイライト**
|
||||||
|
|
||||||
|
**6 つのコア機能、18,000 行超の新コード**
|
||||||
|
|
||||||
|
- **Gemini CLI 統合**
|
||||||
|
- Claude Code / Codex / Gemini の 3 番目のサポート AI CLI
|
||||||
|
- 2 つの設定ファイル(`.env` + `settings.json`)に対応
|
||||||
|
- MCP サーバー管理を完備
|
||||||
|
- プリセット:Google 公式(OAuth)/ PackyCode / カスタム
|
||||||
|
|
||||||
|
- **Claude Skills 管理システム**
|
||||||
|
- GitHub リポジトリを自動スキャン(3 つのキュレーション済みリポジトリを同梱)
|
||||||
|
- `~/.claude/skills/` へワンクリックでインストール/アンインストール
|
||||||
|
- カスタムリポジトリ + サブディレクトリスキャンをサポート
|
||||||
|
- ライフサイクル管理(検出/インストール/更新)を完備
|
||||||
|
|
||||||
|
- **Prompts 管理システム**
|
||||||
|
- 無制限のシステムプロンプトプリセットを作成
|
||||||
|
- Markdown エディタ(CodeMirror 6 + リアルタイムプレビュー)付き
|
||||||
|
- スマートなバックフィル保護で手動変更を保持
|
||||||
|
- 複数アプリに同時対応(Claude: `CLAUDE.md` / Codex: `AGENTS.md` / Gemini: `GEMINI.md`)
|
||||||
|
|
||||||
|
- **MCP v3.7.0 統合アーキテクチャ**
|
||||||
|
- 1 つのパネルで 3 アプリの MCP を管理
|
||||||
|
- 新たに SSE(Server-Sent Events)トランスポートを追加
|
||||||
|
- スマート JSON パーサー + Codex TOML 自動修正
|
||||||
|
- 双方向のインポート/エクスポート + 双方向同期
|
||||||
|
|
||||||
|
- **ディープリンクプロトコル**
|
||||||
|
- `ccswitch://` を全プラットフォームで登録
|
||||||
|
- 共有リンクからプロバイダ設定をワンクリックでインポート
|
||||||
|
- セキュリティ検証 + ライフサイクル統合
|
||||||
|
|
||||||
|
- **環境変数の競合検知**
|
||||||
|
- Claude/Codex/Gemini/MCP 間の設定競合を自動検出
|
||||||
|
- 競合表示 + 解決ガイド
|
||||||
|
- 上書き前の警告 + バックアップ
|
||||||
|
|
||||||
|
**コア機能**
|
||||||
|
|
||||||
|
- **プロバイダ管理**:Claude Code、Codex、Gemini の API 設定をワンクリックで切り替え
|
||||||
|
- **速度テスト**:エンドポイント遅延を計測し、品質を可視化
|
||||||
|
- **インポート/エクスポート**:設定をバックアップ・復元(最新 10 件を自動ローテーション)
|
||||||
|
- **多言語対応**:UI/エラー/トレイを含む中国語・英語・日本語ローカライズ
|
||||||
|
- **Claude プラグイン同期**:Claude プラグイン設定をワンクリックで適用/復元
|
||||||
|
|
||||||
|
**v3.6 ハイライト**
|
||||||
|
|
||||||
|
- プロバイダの複製とドラッグ&ドロップ並び替え
|
||||||
|
- 複数エンドポイント管理とカスタム設定ディレクトリ(クラウド同期準備済み)
|
||||||
|
- 4 階層のモデル設定(Haiku/Sonnet/Opus/Custom)
|
||||||
|
- WSL 環境をサポートし、ディレクトリ変更時に自動同期
|
||||||
|
- Hooks テスト 100% カバレッジ + アーキテクチャ全面リファクタ
|
||||||
|
|
||||||
|
**システム機能**
|
||||||
|
|
||||||
|
- クイックスイッチ付きシステムトレイ
|
||||||
|
- シングルインスタンス常駐
|
||||||
|
- ビルトイン自動アップデータ
|
||||||
|
- ロールバック保護付きのアトミック書き込み
|
||||||
|
|
||||||
|
## ダウンロード & インストール
|
||||||
|
|
||||||
|
### システム要件
|
||||||
|
|
||||||
|
- **Windows**: Windows 10 以上
|
||||||
|
- **macOS**: macOS 10.15 (Catalina) 以上
|
||||||
|
- **Linux**: Ubuntu 22.04+ / Debian 11+ / Fedora 34+ など主要ディストリビューション
|
||||||
|
|
||||||
|
### Windows ユーザー
|
||||||
|
|
||||||
|
[Releases](../../releases) ページから最新版の `CC-Switch-v{version}-Windows.msi` インストーラー、またはポータブル版 `CC-Switch-v{version}-Windows-Portable.zip` をダウンロード。
|
||||||
|
|
||||||
|
### macOS ユーザー
|
||||||
|
|
||||||
|
**方法 1: Homebrew でインストール(推奨)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap farion1231/ccswitch
|
||||||
|
brew install --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
アップデート:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew upgrade --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
**方法 2: 手動ダウンロード**
|
||||||
|
|
||||||
|
[Releases](../../releases) から `CC-Switch-v{version}-macOS.zip` をダウンロードして展開。
|
||||||
|
|
||||||
|
> **注意**: 開発者アカウント未登録のため、初回起動時に「開発元を確認できません」と表示される場合があります。一度閉じてから「システム設定」→「プライバシーとセキュリティ」→「このまま開く」をクリックしてください。以降は通常通り起動できます。
|
||||||
|
|
||||||
|
### ArchLinux ユーザー
|
||||||
|
|
||||||
|
**paru でインストール(推奨)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
paru -S cc-switch-bin
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux ユーザー
|
||||||
|
|
||||||
|
[Releases](../../releases) から最新版の `CC-Switch-v{version}-Linux.deb` または `CC-Switch-v{version}-Linux.AppImage` をダウンロード。
|
||||||
|
|
||||||
|
## クイックスタート
|
||||||
|
|
||||||
|
### 基本的な使い方
|
||||||
|
|
||||||
|
1. **プロバイダ追加**:「Add Provider」をクリック → プリセットを選ぶかカスタム設定を作成
|
||||||
|
2. **プロバイダ切り替え**:
|
||||||
|
- メイン UI: プロバイダを選択 → 「Enable」をクリック
|
||||||
|
- システムトレイ: プロバイダ名をクリック(即時反映)
|
||||||
|
3. **反映**: ターミナルや Claude Code / Codex / Gemini クライアントを再起動して適用
|
||||||
|
4. **公式設定に戻す**: 「Official Login」プリセット(Claude/Codex)または「Google Official」プリセット(Gemini)を選び、対応クライアントを再起動してログイン/OAuth を実行
|
||||||
|
|
||||||
|
### MCP 管理
|
||||||
|
|
||||||
|
- **入口**: 右上の「MCP」ボタンをクリック
|
||||||
|
- **サーバー追加**:
|
||||||
|
- 組み込みテンプレート(mcp-fetch、mcp-filesystem など)を使用
|
||||||
|
- stdio / http / sse の各トランスポートをサポート
|
||||||
|
- アプリごとに独立した MCP を設定可能
|
||||||
|
- **有効/無効**: トグルでライブ設定への同期を切り替え
|
||||||
|
- **同期**: 有効なサーバーは各アプリのライブファイルへ自動同期
|
||||||
|
- **インポート/エクスポート**: Claude/Codex/Gemini の設定ファイルから既存 MCP を取り込み
|
||||||
|
|
||||||
|
### Skills 管理 (v3.7.0 新機能)
|
||||||
|
|
||||||
|
- **入口**: 右上の「Skills」ボタンをクリック
|
||||||
|
- **スキル探索**:
|
||||||
|
- 事前設定済みの GitHub リポジトリを自動スキャン(Anthropic 公式、ComposioHQ、コミュニティなど)
|
||||||
|
- カスタムリポジトリを追加(サブディレクトリスキャン対応)
|
||||||
|
- **インストール**: 「Install」を押すだけで `~/.claude/skills/` に配置
|
||||||
|
- **アンインストール**: 「Uninstall」で安全に削除と状態クリーンアップ
|
||||||
|
- **リポジトリ管理**: カスタム GitHub リポジトリを追加/削除
|
||||||
|
|
||||||
|
### Prompts 管理 (v3.7.0 新機能)
|
||||||
|
|
||||||
|
- **入口**: 右上の「Prompts」ボタンをクリック
|
||||||
|
- **プリセット作成**:
|
||||||
|
- 無制限のシステムプロンプトプリセットを作成
|
||||||
|
- Markdown エディタで記述(シンタックスハイライト + リアルタイムプレビュー)
|
||||||
|
- **プリセット切り替え**: プリセットを選択 → 「Activate」で即適用
|
||||||
|
- **同期先**:
|
||||||
|
- Claude: `~/.claude/CLAUDE.md`
|
||||||
|
- Codex: `~/.codex/AGENTS.md`
|
||||||
|
- Gemini: `~/.gemini/GEMINI.md`
|
||||||
|
- **保護機構**: 切り替え前に現在の内容を自動保存し、手動変更を保持
|
||||||
|
|
||||||
|
### 設定ファイルパス
|
||||||
|
|
||||||
|
**Claude Code**
|
||||||
|
|
||||||
|
- ライブ設定: `~/.claude/settings.json`(または `claude.json`)
|
||||||
|
- API キーフィールド: `env.ANTHROPIC_AUTH_TOKEN` または `env.ANTHROPIC_API_KEY`
|
||||||
|
- MCP サーバー: `~/.claude.json` → `mcpServers`
|
||||||
|
|
||||||
|
**Codex**
|
||||||
|
|
||||||
|
- ライブ設定: `~/.codex/auth.json`(必須)+ `config.toml`(任意)
|
||||||
|
- API キーフィールド: `auth.json` 内の `OPENAI_API_KEY`
|
||||||
|
- MCP サーバー: `~/.codex/config.toml` → `[mcp_servers]` テーブル
|
||||||
|
|
||||||
|
**Gemini**
|
||||||
|
|
||||||
|
- ライブ設定: `~/.gemini/.env`(API キー)+ `~/.gemini/settings.json`(認証モード)
|
||||||
|
- API キーフィールド: `.env` 内の `GEMINI_API_KEY` または `GOOGLE_GEMINI_API_KEY`
|
||||||
|
- 環境変数: `GOOGLE_GEMINI_BASE_URL`、`GEMINI_MODEL` などをサポート
|
||||||
|
- MCP サーバー: `~/.gemini/settings.json` → `mcpServers`
|
||||||
|
- トレイでのクイックスイッチ: プロバイダ切り替えごとに `~/.gemini/.env` を書き換えるため Gemini CLI の再起動は不要
|
||||||
|
|
||||||
|
**CC Switch 保存先 (v3.8.0 新アーキテクチャ)**
|
||||||
|
|
||||||
|
- データベース (SSOT): `~/.cc-switch/cc-switch.db`(SQLite。プロバイダ、MCP、Prompts、Skills を保存)
|
||||||
|
- ローカル設定: `~/.cc-switch/settings.json`(デバイスレベル設定)
|
||||||
|
- バックアップ: `~/.cc-switch/backups/`(自動ローテーション、最新 10 件を保持)
|
||||||
|
|
||||||
|
### クラウド同期の設定
|
||||||
|
|
||||||
|
1. 設定 → 「Custom Configuration Directory」へ進む
|
||||||
|
2. クラウド同期フォルダ(Dropbox、OneDrive、iCloud など)を選択
|
||||||
|
3. アプリを再起動して反映
|
||||||
|
4. 他のデバイスでも同じフォルダを指定すればクロスデバイス同期が有効に
|
||||||
|
|
||||||
|
> **補足**: 初回起動時に既存の Claude/Codex 設定をデフォルトプロバイダとして自動インポートします。
|
||||||
|
|
||||||
|
## アーキテクチャ概要
|
||||||
|
|
||||||
|
### 設計原則
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Frontend (React + TS) │
|
||||||
|
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ Components │ │ Hooks │ │ TanStack Query │ │
|
||||||
|
│ │ (UI) │──│ (Bus. Logic) │──│ (Cache/Sync) │ │
|
||||||
|
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
|
||||||
|
└────────────────────────┬────────────────────────────────────┘
|
||||||
|
│ Tauri IPC
|
||||||
|
┌────────────────────────▼────────────────────────────────────┐
|
||||||
|
│ Backend (Tauri + Rust) │
|
||||||
|
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
|
||||||
|
│ │ Commands │ │ Services │ │ Models/Config │ │
|
||||||
|
│ │ (API Layer) │──│ (Bus. Layer) │──│ (Data) │ │
|
||||||
|
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**コア設計パターン**
|
||||||
|
|
||||||
|
- **SSOT** (Single Source of Truth): すべてのデータを `~/.cc-switch/cc-switch.db`(SQLite)に集約
|
||||||
|
- **二層ストレージ**: 同期データは SQLite、デバイスデータは JSON
|
||||||
|
- **双方向同期**: 切り替え時はライブファイルへ書き込み、編集時はアクティブプロバイダから逆同期
|
||||||
|
- **アトミック書き込み**: 一時ファイル + rename パターンで設定破損を防止
|
||||||
|
- **並行安全**: Mutex で保護された DB 接続でレースを防ぐ
|
||||||
|
- **レイヤードアーキテクチャ**: Commands → Services → DAO → Database を明確に分離
|
||||||
|
|
||||||
|
**主要コンポーネント**
|
||||||
|
|
||||||
|
- **ProviderService**: プロバイダの CRUD、切り替え、バックフィル、ソート
|
||||||
|
- **McpService**: MCP サーバー管理、インポート/エクスポート、ライブファイル同期
|
||||||
|
- **ConfigService**: 設定のインポート/エクスポート、バックアップローテーション
|
||||||
|
- **SpeedtestService**: API エンドポイントの遅延計測
|
||||||
|
|
||||||
|
**v3.6 リファクタリング**
|
||||||
|
|
||||||
|
- バックエンド: エラーハンドリング → コマンド分割 → テスト → サービス層 → 並行性の 5 フェーズ
|
||||||
|
- フロントエンド: テスト基盤 → hooks → コンポーネント → クリーンアップの 4 ステージ
|
||||||
|
- テスト: hooks 100% カバレッジ + 統合テスト(vitest + MSW)
|
||||||
|
|
||||||
|
## 開発
|
||||||
|
|
||||||
|
### 開発環境
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- pnpm 8+
|
||||||
|
- Rust 1.85+
|
||||||
|
- Tauri CLI 2.8+
|
||||||
|
|
||||||
|
### 開発コマンド
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 依存関係をインストール
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# ホットリロード付き開発モード
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# 型チェック
|
||||||
|
pnpm typecheck
|
||||||
|
|
||||||
|
# コード整形
|
||||||
|
pnpm format
|
||||||
|
|
||||||
|
# フォーマット検証
|
||||||
|
pnpm format:check
|
||||||
|
|
||||||
|
# フロントエンド単体テスト
|
||||||
|
pnpm test:unit
|
||||||
|
|
||||||
|
# ウォッチモード(開発に推奨)
|
||||||
|
pnpm test:unit:watch
|
||||||
|
|
||||||
|
# アプリをビルド
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# デバッグビルド
|
||||||
|
pnpm tauri build --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rust バックエンド開発
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src-tauri
|
||||||
|
|
||||||
|
# Rust コード整形
|
||||||
|
cargo fmt
|
||||||
|
|
||||||
|
# clippy チェック
|
||||||
|
cargo clippy
|
||||||
|
|
||||||
|
# バックエンドテスト
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# 特定テストのみ実行
|
||||||
|
cargo test test_name
|
||||||
|
|
||||||
|
# test-hooks フィーチャー付きでテスト
|
||||||
|
cargo test --features test-hooks
|
||||||
|
```
|
||||||
|
|
||||||
|
### テストガイド (v3.6)
|
||||||
|
|
||||||
|
**フロントエンドテスト**:
|
||||||
|
|
||||||
|
- テストフレームワークに **vitest** を使用
|
||||||
|
- **MSW (Mock Service Worker)** で Tauri API 呼び出しをモック
|
||||||
|
- コンポーネントテストに **@testing-library/react** を採用
|
||||||
|
|
||||||
|
**テストカバレッジ**:
|
||||||
|
|
||||||
|
- Hooks の単体テスト(100% カバレッジ)
|
||||||
|
- `useProviderActions` - プロバイダ操作
|
||||||
|
- `useMcpActions` - MCP 管理
|
||||||
|
- `useSettings` 系 - 設定管理
|
||||||
|
- `useImportExport` - インポート/エクスポート
|
||||||
|
- 統合テスト
|
||||||
|
- アプリのメインフロー
|
||||||
|
- SettingsDialog の一連操作
|
||||||
|
- MCP パネルの機能
|
||||||
|
|
||||||
|
**テスト実行**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 全テストを実行
|
||||||
|
pnpm test:unit
|
||||||
|
|
||||||
|
# ウォッチモード(自動再実行)
|
||||||
|
pnpm test:unit:watch
|
||||||
|
|
||||||
|
# カバレッジレポート付き
|
||||||
|
pnpm test:unit --coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技術スタック
|
||||||
|
|
||||||
|
**フロントエンド**: React 18 · TypeScript · Vite · TailwindCSS 4 · TanStack Query v5 · react-i18next · react-hook-form · zod · shadcn/ui · @dnd-kit
|
||||||
|
|
||||||
|
**バックエンド**: Tauri 2.8 · Rust · serde · tokio · thiserror · tauri-plugin-updater/process/dialog/store/log
|
||||||
|
|
||||||
|
**テスト**: vitest · MSW · @testing-library/react
|
||||||
|
|
||||||
|
## プロジェクト構成
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/ # フロントエンド (React + TypeScript)
|
||||||
|
│ ├── components/ # UI コンポーネント (providers/settings/mcp/ui)
|
||||||
|
│ ├── hooks/ # ビジネスロジック用カスタムフック
|
||||||
|
│ ├── lib/
|
||||||
|
│ │ ├── api/ # Tauri API ラッパー (型安全)
|
||||||
|
│ │ └── query/ # TanStack Query 設定
|
||||||
|
│ ├── i18n/locales/ # 翻訳 (zh/en)
|
||||||
|
│ ├── config/ # プリセット (providers/mcp)
|
||||||
|
│ └── types/ # TypeScript 型定義
|
||||||
|
├── src-tauri/ # バックエンド (Rust)
|
||||||
|
│ └── src/
|
||||||
|
│ ├── commands/ # Tauri コマンド層 (ドメイン別)
|
||||||
|
│ ├── services/ # ビジネスロジック層
|
||||||
|
│ ├── app_config.rs # 設定モデル
|
||||||
|
│ ├── provider.rs # プロバイダドメインモデル
|
||||||
|
│ ├── mcp.rs # MCP 同期 & 検証
|
||||||
|
│ └── lib.rs # アプリエントリ & トレイメニュー
|
||||||
|
├── tests/ # フロントエンドテスト
|
||||||
|
│ ├── hooks/ # 単体テスト
|
||||||
|
│ └── components/ # 統合テスト
|
||||||
|
└── assets/ # スクリーンショット & スポンサーリソース
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更新履歴
|
||||||
|
|
||||||
|
詳細は [CHANGELOG.md](CHANGELOG.md) をご覧ください。
|
||||||
|
|
||||||
|
## 旧 Electron 版
|
||||||
|
|
||||||
|
[Releases](../../releases) に v2.0.3 の Electron 旧版を残しています。
|
||||||
|
|
||||||
|
旧版コードが必要な場合は `electron-legacy` ブランチを取得してください。
|
||||||
|
|
||||||
|
## 貢献
|
||||||
|
|
||||||
|
Issue や提案を歓迎します!
|
||||||
|
|
||||||
|
PR を送る前に以下をご確認ください:
|
||||||
|
|
||||||
|
- 型チェック: `pnpm typecheck`
|
||||||
|
- フォーマットチェック: `pnpm format:check`
|
||||||
|
- 単体テスト: `pnpm test:unit`
|
||||||
|
- 💡 新機能の場合は、事前に Issue でディスカッションしていただけると助かります
|
||||||
|
|
||||||
|
## Star History
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#farion1231/cc-switch&Date)
|
||||||
|
|
||||||
|
## ライセンス
|
||||||
|
|
||||||
|
MIT © Jason Young
|
||||||
+44
-10
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Claude Code / Codex / Gemini CLI 全方位辅助工具
|
# Claude Code / Codex / Gemini CLI 全方位辅助工具
|
||||||
|
|
||||||
[](https://github.com/farion1231/cc-switch/releases)
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
[](https://github.com/trending/typescript)
|
[](https://github.com/trending/typescript)
|
||||||
[](https://github.com/farion1231/cc-switch/releases)
|
[](https://github.com/farion1231/cc-switch/releases)
|
||||||
[](https://tauri.app/)
|
[](https://tauri.app/)
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<a href="https://trendshift.io/repositories/15372" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
<a href="https://trendshift.io/repositories/15372" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15372" alt="farion1231%2Fcc-switch | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||||
|
|
||||||
[English](README.md) | 中文 | [更新日志](CHANGELOG.md) | [📋 v3.7.0 发布说明](docs/release-note-v3.7.0-zh.md)
|
[English](README.md) | 中文 | [日本語](README_JA.md) | [更新日志](CHANGELOG.md) | [v3.8.0 发布说明](docs/release-note-v3.8.0-zh.md)
|
||||||
|
|
||||||
**从供应商切换器到 AI CLI 一体化管理平台**
|
**从供应商切换器到 AI CLI 一体化管理平台**
|
||||||
|
|
||||||
@@ -51,9 +51,42 @@ CC Switch 已经预设了智谱GLM,只需要填写 key 即可一键导入编
|
|||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
### 当前版本:v3.7.0 | [完整更新日志](CHANGELOG.md)
|
### 当前版本:v3.8.0 | [完整更新日志](CHANGELOG.md)
|
||||||
|
|
||||||
**v3.7.0 重大更新(2025-11-19)**
|
**v3.8.0 重大更新(2025-11-28)**
|
||||||
|
|
||||||
|
**持久化架构升级 & 全新用户界面**
|
||||||
|
|
||||||
|
- **SQLite + JSON 双层架构**
|
||||||
|
- 从 JSON 文件存储迁移到 SQLite + JSON 双层结构
|
||||||
|
- 可同步数据(供应商、MCP、Prompts、Skills)存入 SQLite
|
||||||
|
- 设备级数据(窗口状态、本地路径)保留在 JSON
|
||||||
|
- 为未来云同步功能奠定基础
|
||||||
|
- Schema 版本管理支持数据库迁移
|
||||||
|
|
||||||
|
- **全新用户界面**
|
||||||
|
- 完全重新设计的界面布局
|
||||||
|
- 统一的组件样式和更流畅的动画
|
||||||
|
- 优化的视觉层次
|
||||||
|
- Tailwind CSS 从 v4 降级到 v3.4 以提升浏览器兼容性
|
||||||
|
|
||||||
|
- **日语支持**
|
||||||
|
- 新增日语界面支持(现支持中文/英文/日语)
|
||||||
|
|
||||||
|
- **开机自启**
|
||||||
|
- 在设置中一键开启/关闭
|
||||||
|
- 使用平台原生 API(注册表/LaunchAgent/XDG autostart)
|
||||||
|
|
||||||
|
- **Skills 递归扫描**
|
||||||
|
- 支持多层目录结构
|
||||||
|
- 允许不同仓库的同名技能
|
||||||
|
|
||||||
|
- **关键 Bug 修复**
|
||||||
|
- 修复更新供应商时自定义端点丢失问题
|
||||||
|
- 修复 Gemini 配置写入问题
|
||||||
|
- 修复 Linux WebKitGTK 渲染问题
|
||||||
|
|
||||||
|
**v3.7.0 亮点**
|
||||||
|
|
||||||
**六大核心功能,18,000+ 行新增代码**
|
**六大核心功能,18,000+ 行新增代码**
|
||||||
|
|
||||||
@@ -226,10 +259,10 @@ paru -S cc-switch-bin
|
|||||||
- MCP 服务器:`~/.gemini/settings.json` → `mcpServers`
|
- MCP 服务器:`~/.gemini/settings.json` → `mcpServers`
|
||||||
- 托盘快速切换:每次切换供应商都会重写 `~/.gemini/.env`,无需重启 Gemini CLI 即可生效
|
- 托盘快速切换:每次切换供应商都会重写 `~/.gemini/.env`,无需重启 Gemini CLI 即可生效
|
||||||
|
|
||||||
**CC Switch 存储**
|
**CC Switch 存储(v3.8.0 新架构)**
|
||||||
|
|
||||||
- 主配置(SSOT):`~/.cc-switch/config.json`(包含供应商、MCP、Prompts 预设等)
|
- 数据库(SSOT):`~/.cc-switch/cc-switch.db`(SQLite,存储供应商、MCP、Prompts、Skills)
|
||||||
- 设置:`~/.cc-switch/settings.json`
|
- 本地设置:`~/.cc-switch/settings.json`(设备级设置)
|
||||||
- 备份:`~/.cc-switch/backups/`(自动轮换,保留 10 个)
|
- 备份:`~/.cc-switch/backups/`(自动轮换,保留 10 个)
|
||||||
|
|
||||||
### 云同步设置
|
### 云同步设置
|
||||||
@@ -265,11 +298,12 @@ paru -S cc-switch-bin
|
|||||||
|
|
||||||
**核心设计模式**
|
**核心设计模式**
|
||||||
|
|
||||||
- **SSOT**(单一事实源):所有供应商配置存储在 `~/.cc-switch/config.json`
|
- **SSOT**(单一事实源):所有数据存储在 `~/.cc-switch/cc-switch.db`(SQLite)
|
||||||
|
- **双层存储**:SQLite 存储可同步数据,JSON 存储设备级设置
|
||||||
- **双向同步**:切换时写入 live 文件,编辑当前供应商时从 live 回填
|
- **双向同步**:切换时写入 live 文件,编辑当前供应商时从 live 回填
|
||||||
- **原子写入**:临时文件 + 重命名模式防止配置损坏
|
- **原子写入**:临时文件 + 重命名模式防止配置损坏
|
||||||
- **并发安全**:RwLock 与作用域守卫避免死锁
|
- **并发安全**:Mutex 保护的数据库连接避免竞态条件
|
||||||
- **分层架构**:清晰分离(Commands → Services → Models)
|
- **分层架构**:清晰分离(Commands → Services → DAO → Database)
|
||||||
|
|
||||||
**核心组件**
|
**核心组件**
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,369 @@
|
|||||||
|
# CC Switch v3.8.0
|
||||||
|
|
||||||
|
> Persistence Architecture Upgrade, Laying the Foundation for Cloud Sync
|
||||||
|
|
||||||
|
**[中文版 →](release-note-v3.8.0-zh.md) | [日本語版 →](release-note-v3.8.0-ja.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
CC Switch v3.8.0 is a major architectural upgrade that restructures the data persistence layer and user interface, laying the foundation for future cloud sync and local proxy features.
|
||||||
|
|
||||||
|
**Release Date**: 2025-11-28
|
||||||
|
**Commits**: 51 commits since v3.7.1
|
||||||
|
**Code Changes**: 207 files, +17,297 / -6,870 lines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Major Updates
|
||||||
|
|
||||||
|
### Persistence Architecture Upgrade
|
||||||
|
|
||||||
|
Migrated from single JSON file storage to SQLite + JSON dual-layer architecture for hierarchical data management.
|
||||||
|
|
||||||
|
**Architecture Changes**:
|
||||||
|
|
||||||
|
```
|
||||||
|
v3.7.x (Old) v3.8.0 (New)
|
||||||
|
┌─────────────────┐ ┌─────────────────────────────────┐
|
||||||
|
│ config.json │ │ SQLite (Syncable Data) │
|
||||||
|
│ ┌───────────┐ │ │ ├─ providers Provider cfg │
|
||||||
|
│ │ providers │ │ │ ├─ mcp_servers MCP servers │
|
||||||
|
│ │ mcp │ │ ──> │ ├─ prompts Prompts │
|
||||||
|
│ │ prompts │ │ │ ├─ skills Skills │
|
||||||
|
│ │ settings │ │ │ └─ settings General cfg │
|
||||||
|
│ └───────────┘ │ ├─────────────────────────────────┤
|
||||||
|
└─────────────────┘ │ JSON (Device-level Data) │
|
||||||
|
│ └─ settings.json Local settings│
|
||||||
|
│ ├─ Window position │
|
||||||
|
│ ├─ Path overrides │
|
||||||
|
│ └─ Current provider ID │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dual-layer Structure Design**:
|
||||||
|
|
||||||
|
| Layer | Storage | Data Types | Sync Strategy |
|
||||||
|
| ---------- | ------- | ------------------------------- | --------------- |
|
||||||
|
| Cloud Sync | SQLite | Providers, MCP, Prompts, Skills | Future syncable |
|
||||||
|
| Device | JSON | Window state, local paths | Stays local |
|
||||||
|
|
||||||
|
**Technical Implementation**:
|
||||||
|
|
||||||
|
- **Schema Version Management** - Supports database structure upgrade migrations
|
||||||
|
- **SQL Import/Export** - `backup.rs` supports SQL dump for cloud storage
|
||||||
|
- **Transaction Support** - SQLite native transactions ensure data consistency
|
||||||
|
- **Auto Migration** - Automatically migrates from `config.json` on first launch
|
||||||
|
|
||||||
|
**Modular Refactoring**:
|
||||||
|
|
||||||
|
```
|
||||||
|
database/
|
||||||
|
├── mod.rs Core Database struct and initialization
|
||||||
|
├── schema.rs Table definitions, schema version migrations
|
||||||
|
├── backup.rs SQL import/export, binary snapshot backup
|
||||||
|
├── migration.rs JSON → SQLite data migration engine
|
||||||
|
└── dao/ Data Access Object layer
|
||||||
|
├── providers.rs Provider CRUD
|
||||||
|
├── mcp.rs MCP server CRUD
|
||||||
|
├── prompts.rs Prompts CRUD
|
||||||
|
├── skills.rs Skills CRUD
|
||||||
|
└── settings.rs Key-value settings storage
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Brand New User Interface
|
||||||
|
|
||||||
|
Completely redesigned UI providing a more modern visual experience.
|
||||||
|
|
||||||
|
**Visual Improvements**:
|
||||||
|
|
||||||
|
- Redesigned interface layout
|
||||||
|
- Unified component styles
|
||||||
|
- Smoother transition animations
|
||||||
|
- Optimized visual hierarchy
|
||||||
|
|
||||||
|
**Interaction Enhancements**:
|
||||||
|
|
||||||
|
- Redesigned header toolbar
|
||||||
|
- Unified ConfirmDialog styling
|
||||||
|
- Disabled overscroll bounce effect on main view
|
||||||
|
- Improved form validation feedback
|
||||||
|
|
||||||
|
**Compatibility Adjustments**:
|
||||||
|
|
||||||
|
- Downgraded Tailwind CSS from v4 to v3.4 for better browser compatibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Japanese Language Support
|
||||||
|
|
||||||
|
Added Japanese interface support, expanding internationalization to three languages.
|
||||||
|
|
||||||
|
**Supported Languages**:
|
||||||
|
|
||||||
|
- Simplified Chinese
|
||||||
|
- English
|
||||||
|
- Japanese (New)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
### Skills Recursive Scanning
|
||||||
|
|
||||||
|
Skills management system now supports recursive scanning of repository directories, automatically discovering nested skill files.
|
||||||
|
|
||||||
|
**Improvements**:
|
||||||
|
|
||||||
|
- Support for multi-level directory structures
|
||||||
|
- Automatic discovery of all `SKILL.md` files
|
||||||
|
- Allow same-named skills from different repositories (using full path for deduplication)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Provider Icon Configuration
|
||||||
|
|
||||||
|
Provider presets now support custom icon configuration.
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- Preset providers include default icons
|
||||||
|
- Icon settings preserved when duplicating providers
|
||||||
|
- Custom icon colors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Enhanced Form Validation
|
||||||
|
|
||||||
|
Provider forms now include required field validation with friendlier error messages.
|
||||||
|
|
||||||
|
**Improvements**:
|
||||||
|
|
||||||
|
- Real-time validation for required fields
|
||||||
|
- Unified Toast notifications for validation errors
|
||||||
|
- Clearer error messages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Auto Launch on Startup
|
||||||
|
|
||||||
|
Added auto-launch functionality supporting Windows, macOS, and Linux platforms.
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
|
||||||
|
- One-click enable/disable in settings
|
||||||
|
- Implemented using platform-native APIs
|
||||||
|
- Windows uses Registry, macOS uses LaunchAgent, Linux uses XDG autostart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### New Provider Presets
|
||||||
|
|
||||||
|
- **MiniMax** - Official partner
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
### Critical Fixes
|
||||||
|
|
||||||
|
**Custom Endpoints Lost Issue**
|
||||||
|
|
||||||
|
Fixed an issue where custom request URLs were unexpectedly lost when updating providers.
|
||||||
|
|
||||||
|
- Root Cause: `INSERT OR REPLACE` executes `DELETE + INSERT` under the hood in SQLite, triggering foreign key cascade deletion
|
||||||
|
- Fix: Changed to use `UPDATE` statement for existing providers
|
||||||
|
|
||||||
|
**Gemini Configuration Issues**
|
||||||
|
|
||||||
|
- Fixed custom provider environment variables not correctly written to `.env` file
|
||||||
|
- Fixed security auth config incorrectly written to other config files
|
||||||
|
|
||||||
|
**Provider Validation Issues**
|
||||||
|
|
||||||
|
- Fixed validation error when current provider ID doesn't exist
|
||||||
|
- Fixed icon fields lost when duplicating providers
|
||||||
|
|
||||||
|
### Platform Compatibility
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
|
||||||
|
- Resolved WebKitGTK DMA-BUF rendering issue
|
||||||
|
- Preserve user `.desktop` file customizations
|
||||||
|
|
||||||
|
### Other Fixes
|
||||||
|
|
||||||
|
- Fixed redundant usage queries when switching apps
|
||||||
|
- Fixed DMXAPI preset using wrong auth token field
|
||||||
|
- Fixed missing translation keys in deeplink components
|
||||||
|
- Fixed usage script template initialization logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Improvements
|
||||||
|
|
||||||
|
### Architecture Refactoring
|
||||||
|
|
||||||
|
**Provider Service Modularization**:
|
||||||
|
|
||||||
|
```
|
||||||
|
services/provider/
|
||||||
|
├── mod.rs Core service - add/update/delete/switch/validate
|
||||||
|
├── live.rs Live config file operations
|
||||||
|
├── gemini_auth.rs Gemini auth type detection
|
||||||
|
├── endpoints.rs Custom endpoint management
|
||||||
|
└── usage.rs Usage script execution
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deeplink Modularization**:
|
||||||
|
|
||||||
|
```
|
||||||
|
deeplink/
|
||||||
|
├── mod.rs Module exports
|
||||||
|
├── parser.rs URL parsing
|
||||||
|
├── provider.rs Provider import logic
|
||||||
|
├── mcp.rs MCP import logic
|
||||||
|
├── prompt.rs Prompt import
|
||||||
|
├── skill.rs Skills import
|
||||||
|
└── utils.rs Utility functions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
**Cleanup**:
|
||||||
|
|
||||||
|
- Removed legacy JSON-era import/export dead code
|
||||||
|
- Removed unused MCP type exports
|
||||||
|
- Unified error handling approach
|
||||||
|
|
||||||
|
**Test Updates**:
|
||||||
|
|
||||||
|
- Migrated tests to SQLite database architecture
|
||||||
|
- Updated component tests to match current implementation
|
||||||
|
- Fixed MSW handlers to adapt to new API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Statistics
|
||||||
|
|
||||||
|
```
|
||||||
|
Overall Changes:
|
||||||
|
- Commits: 51
|
||||||
|
- Files: 207 files changed
|
||||||
|
- Additions: +17,297 lines
|
||||||
|
- Deletions: -6,870 lines
|
||||||
|
- Net: +10,427 lines
|
||||||
|
|
||||||
|
Commit Type Distribution:
|
||||||
|
- fix: 25 (Bug fixes)
|
||||||
|
- refactor: 11 (Code refactoring)
|
||||||
|
- feat: 9 (New features)
|
||||||
|
- test: 1 (Testing)
|
||||||
|
- other: 5
|
||||||
|
|
||||||
|
Change Area Distribution:
|
||||||
|
- Frontend source: 112 files
|
||||||
|
- Rust backend: 63 files
|
||||||
|
- Test files: 20 files
|
||||||
|
- i18n files: 3 files
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Guide
|
||||||
|
|
||||||
|
### Upgrading from v3.7.x
|
||||||
|
|
||||||
|
**Auto Migration** - Executes automatically on first launch:
|
||||||
|
|
||||||
|
1. Detects if `config.json` exists
|
||||||
|
2. Migrates all data to SQLite within a transaction
|
||||||
|
3. Migrates device-level settings to `settings.json`
|
||||||
|
4. Shows migration success notification
|
||||||
|
|
||||||
|
**Data Safety**:
|
||||||
|
|
||||||
|
- Original `config.json` file is preserved (not deleted)
|
||||||
|
- Error dialog displayed on migration failure, `config.json` preserved
|
||||||
|
- Supports Dry-run mode to verify migration logic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Download & Installation
|
||||||
|
|
||||||
|
### System Requirements
|
||||||
|
|
||||||
|
- **Windows**: Windows 10+
|
||||||
|
- **macOS**: macOS 10.15 (Catalina)+
|
||||||
|
- **Linux**: Ubuntu 22.04+ / Debian 11+ / Fedora 34+
|
||||||
|
|
||||||
|
### Download Links
|
||||||
|
|
||||||
|
Visit [Releases](https://github.com/farion1231/cc-switch/releases/latest) to download:
|
||||||
|
|
||||||
|
- **Windows**: `CC-Switch-v3.8.0-Windows.msi` or `-Portable.zip`
|
||||||
|
- **macOS**: `CC-Switch-v3.8.0-macOS.tar.gz` or `.zip`
|
||||||
|
- **Linux**: `CC-Switch-v3.8.0-Linux.AppImage` or `.deb`
|
||||||
|
|
||||||
|
### Homebrew (macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap farion1231/ccswitch
|
||||||
|
brew install --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
Update:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew upgrade --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
|
||||||
|
Thanks to all contributors who made this release possible:
|
||||||
|
|
||||||
|
- [@YoVinchen](https://github.com/YoVinchen) - UI and database refactoring
|
||||||
|
- [@farion1231](https://github.com/farion1231) - Bug fixes and feature enhancements
|
||||||
|
- Community members for testing and feedback
|
||||||
|
|
||||||
|
### Sponsors
|
||||||
|
|
||||||
|
**Zhipu AI** - GLM CODING PLAN Sponsor
|
||||||
|
[Get 10% off with this link](https://z.ai/subscribe?ic=8JVLJQFSKB)
|
||||||
|
|
||||||
|
**PackyCode** - API Relay Service Partner
|
||||||
|
[Use code "cc-switch" for 10% off registration](https://www.packyapi.com/register?aff=cc-switch)
|
||||||
|
|
||||||
|
**ShandianShuo** - Local-first AI Voice Input
|
||||||
|
[Free download](https://shandianshuo.cn) for Mac/Windows
|
||||||
|
|
||||||
|
**MiniMax** - MiniMax M2 CODING PLAN Sponsor
|
||||||
|
[Black Friday sale, plans starting at $2](https://platform.minimax.io/subscribe/coding-plan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feedback & Support
|
||||||
|
|
||||||
|
- **Issue Reports**: [GitHub Issues](https://github.com/farion1231/cc-switch/issues)
|
||||||
|
- **Discussions**: [GitHub Discussions](https://github.com/farion1231/cc-switch/discussions)
|
||||||
|
- **Documentation**: [README](../README.md)
|
||||||
|
- **Changelog**: [CHANGELOG.md](../CHANGELOG.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Future Roadmap
|
||||||
|
|
||||||
|
**v3.9.0 Preview** (Tentative):
|
||||||
|
|
||||||
|
- Local proxy feature
|
||||||
|
|
||||||
|
Stay tuned for more updates!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Coding!**
|
||||||
@@ -0,0 +1,315 @@
|
|||||||
|
# CC Switch v3.8.0
|
||||||
|
|
||||||
|
> 永続化アーキテクチャを刷新し、クラウド同期の土台を構築
|
||||||
|
|
||||||
|
**[English →](release-note-v3.8.0-en.md) | [中文版 →](release-note-v3.8.0-zh.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
CC Switch v3.8.0 はデータ永続化レイヤーと UI を大幅に作り替え、今後のクラウド同期やローカルプロキシ機能に向けた基盤を整えたメジャーアップデートです。
|
||||||
|
|
||||||
|
**リリース日**: 2025-11-28
|
||||||
|
**コミット数**: v3.7.1 以降 51 commits
|
||||||
|
**変更量**: 207 files, +17,297 / -6,870 lines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 主要アップデート
|
||||||
|
|
||||||
|
### 永続化アーキテクチャの刷新
|
||||||
|
|
||||||
|
単一の JSON 保存から、階層化された SQLite + JSON の二層構造へ移行。
|
||||||
|
|
||||||
|
**アーキテクチャ変更**:
|
||||||
|
|
||||||
|
```
|
||||||
|
v3.7.x (旧) v3.8.0 (新)
|
||||||
|
┌─────────────────┐ ┌─────────────────────────────────┐
|
||||||
|
│ config.json │ │ SQLite (同期対象データ) │
|
||||||
|
│ ┌───────────┐ │ │ ├─ providers プロバイダ設定 │
|
||||||
|
│ │ providers │ │ │ ├─ mcp_servers MCP サーバー │
|
||||||
|
│ │ mcp │ │ ──> │ ├─ prompts プロンプト │
|
||||||
|
│ │ prompts │ │ │ ├─ skills Skills │
|
||||||
|
│ │ settings │ │ │ └─ settings 汎用設定 │
|
||||||
|
│ └───────────┘ │ ├─────────────────────────────────┤
|
||||||
|
└─────────────────┘ │ JSON (デバイス固有データ) │
|
||||||
|
│ └─ settings.json ローカル設定 │
|
||||||
|
│ ├─ ウィンドウ位置 │
|
||||||
|
│ ├─ パスの上書き │
|
||||||
|
│ └─ 現在のプロバイダ ID │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**二層構造の設計**:
|
||||||
|
|
||||||
|
| レイヤー | ストレージ | データ種別 | 同期戦略 |
|
||||||
|
| -------- | ---------- | ----------------------------------- | ---------------- |
|
||||||
|
| クラウド | SQLite | Providers, MCP, Prompts, Skills | 将来同期対象 |
|
||||||
|
| デバイス | JSON | ウィンドウ状態、ローカルパス | ローカル保持 |
|
||||||
|
|
||||||
|
**実装ポイント**:
|
||||||
|
|
||||||
|
- **スキーマバージョン管理**: DB 構造のマイグレーションに対応
|
||||||
|
- **SQL インポート/エクスポート**: `backup.rs` が SQL ダンプをサポート
|
||||||
|
- **トランザクション対応**: SQLite ネイティブで整合性を確保
|
||||||
|
- **自動マイグレーション**: 初回起動で `config.json` から自動移行
|
||||||
|
|
||||||
|
**モジュール分割**:
|
||||||
|
|
||||||
|
```
|
||||||
|
database/
|
||||||
|
├── mod.rs Database 構造体と初期化
|
||||||
|
├── schema.rs テーブル定義とスキーマ移行
|
||||||
|
├── backup.rs SQL インポート/エクスポートとスナップショット
|
||||||
|
├── migration.rs JSON → SQLite 変換エンジン
|
||||||
|
└── dao/ DAO レイヤー
|
||||||
|
├── providers.rs プロバイダ CRUD
|
||||||
|
├── mcp.rs MCP CRUD
|
||||||
|
├── prompts.rs プロンプト CRUD
|
||||||
|
├── skills.rs Skills CRUD
|
||||||
|
└── settings.rs 設定 Key-Value 保存
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 新しいユーザーインターフェース
|
||||||
|
|
||||||
|
よりモダンな見た目と操作感に再設計。
|
||||||
|
|
||||||
|
- レイアウト全面刷新、コンポーネントスタイルを統一
|
||||||
|
- トランジションを滑らかにし、視覚的階層を最適化
|
||||||
|
- メインビューのオーバースクロールバウンスを無効化
|
||||||
|
- ブラウザ互換性向上のため Tailwind CSS を v4→v3.4 にダウングレード
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 日語化
|
||||||
|
|
||||||
|
UI が日本語に対応し、国際化が 3 言語(中/英/日)へ拡大。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 新機能
|
||||||
|
|
||||||
|
### Skills 递帰スキャン
|
||||||
|
|
||||||
|
Skills 管理がリポジトリを再帰的に走査し、ネストされた `SKILL.md` を自動検出。
|
||||||
|
|
||||||
|
- 複数階層のディレクトリに対応
|
||||||
|
- すべての `SKILL.md` を自動発見
|
||||||
|
- パスをキーにした重複排除で同名スキルを許容
|
||||||
|
|
||||||
|
### プロバイダアイコン設定
|
||||||
|
|
||||||
|
プリセットがデフォルトアイコンを持ち、複製してもアイコンを保持。カスタム色も設定可能。
|
||||||
|
|
||||||
|
### フォームバリデーション強化
|
||||||
|
|
||||||
|
必須項目にリアルタイム検証と分かりやすいエラーメッセージを追加し、トースト通知を統一。
|
||||||
|
|
||||||
|
### 自動起動
|
||||||
|
|
||||||
|
Windows/macOS/Linux で自動起動をサポート。
|
||||||
|
|
||||||
|
- 設定画面からワンクリックで ON/OFF
|
||||||
|
- Registry / LaunchAgent / XDG autostart を使用
|
||||||
|
|
||||||
|
### 新プロバイダプリセット
|
||||||
|
|
||||||
|
- **MiniMax** - 公式パートナー
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## バグ修正
|
||||||
|
|
||||||
|
### 重要修正
|
||||||
|
|
||||||
|
**カスタムエンドポイント消失**
|
||||||
|
|
||||||
|
- 原因: SQLite の `INSERT OR REPLACE` が内部で `DELETE + INSERT` を実行し、外部キーのカスケード削除が発生
|
||||||
|
- 対応: 既存プロバイダ更新を `UPDATE` に変更
|
||||||
|
|
||||||
|
**Gemini 設定**
|
||||||
|
|
||||||
|
- カスタム環境変数が `.env` に正しく書き込まれない問題を修正
|
||||||
|
- 認証設定が他ファイルに誤って書き込まれる問題を修正
|
||||||
|
|
||||||
|
**プロバイダ検証**
|
||||||
|
|
||||||
|
- 現在プロバイダ ID が欠落している場合のバリデーションエラーを修正
|
||||||
|
- 複製時にアイコンフィールドが失われる問題を修正
|
||||||
|
|
||||||
|
### プラットフォーム互換性
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
|
||||||
|
- WebKitGTK の DMA-BUF 描画問題を解消
|
||||||
|
- ユーザーの `.desktop` カスタマイズを保持
|
||||||
|
|
||||||
|
### その他修正
|
||||||
|
|
||||||
|
- アプリ切り替え時の不要な使用量クエリを削減
|
||||||
|
- DMXAPI プリセットの誤ったトークンフィールドを修正
|
||||||
|
- Deeplink コンポーネントの欠損翻訳キーを補完
|
||||||
|
- 使用量スクリプトテンプレート初期化を修正
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技術的改善
|
||||||
|
|
||||||
|
### アーキテクチャ再編
|
||||||
|
|
||||||
|
**Provider Service のモジュール化**:
|
||||||
|
|
||||||
|
```
|
||||||
|
services/provider/
|
||||||
|
├── mod.rs 追加/更新/削除/切替/検証の中核
|
||||||
|
├── live.rs ライブ設定ファイル操作
|
||||||
|
├── gemini_auth.rs Gemini 認証タイプ検出
|
||||||
|
├── endpoints.rs カスタムエンドポイント管理
|
||||||
|
└── usage.rs 使用量スクリプト実行
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deeplink のモジュール化**:
|
||||||
|
|
||||||
|
```
|
||||||
|
deeplink/
|
||||||
|
├── mod.rs エクスポート
|
||||||
|
├── parser.rs URL パース
|
||||||
|
├── provider.rs プロバイダ取り込み
|
||||||
|
├── mcp.rs MCP 取り込み
|
||||||
|
├── prompt.rs プロンプト取り込み
|
||||||
|
├── skill.rs Skills 取り込み
|
||||||
|
└── utils.rs ユーティリティ
|
||||||
|
```
|
||||||
|
|
||||||
|
### コード品質
|
||||||
|
|
||||||
|
- レガシーな JSON 時代のインポート/エクスポート死蔵コードを削除
|
||||||
|
- 使われていない MCP 型を削除し、エラーハンドリングを統一
|
||||||
|
- テストを SQLite バックエンドに移行し、MSW ハンドラを最新 API に合わせて更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技術統計
|
||||||
|
|
||||||
|
```
|
||||||
|
全体変更:
|
||||||
|
- コミット: 51
|
||||||
|
- 変更ファイル: 207
|
||||||
|
- 追加: +17,297 行
|
||||||
|
- 削除: -6,870 行
|
||||||
|
- 純増: +10,427 行
|
||||||
|
|
||||||
|
コミット種別:
|
||||||
|
- fix: 25
|
||||||
|
- refactor: 11
|
||||||
|
- feat: 9
|
||||||
|
- test: 1
|
||||||
|
- other: 5
|
||||||
|
|
||||||
|
変更箇所:
|
||||||
|
- フロントエンド: 112 files
|
||||||
|
- Rust バックエンド: 63 files
|
||||||
|
- テスト: 20 files
|
||||||
|
- i18n: 3 files
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## マイグレーションガイド
|
||||||
|
|
||||||
|
### v3.7.x からのアップグレード
|
||||||
|
|
||||||
|
**自動マイグレーション**(初回起動時):
|
||||||
|
|
||||||
|
1. `config.json` の存在を検出
|
||||||
|
2. 全データをトランザクションで SQLite に移行
|
||||||
|
3. デバイス設定を `settings.json` へ移行
|
||||||
|
4. 移行成功の通知を表示
|
||||||
|
|
||||||
|
**データ保護**:
|
||||||
|
|
||||||
|
- 元の `config.json` は保持(削除しない)
|
||||||
|
- 失敗時はエラーダイアログを表示し、`config.json` を温存
|
||||||
|
- Dry-run モードで検証可能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ダウンロード & インストール
|
||||||
|
|
||||||
|
### システム要件
|
||||||
|
|
||||||
|
- **Windows**: Windows 10+
|
||||||
|
- **macOS**: macOS 10.15 (Catalina)+
|
||||||
|
- **Linux**: Ubuntu 22.04+ / Debian 11+ / Fedora 34+
|
||||||
|
|
||||||
|
### ダウンロード
|
||||||
|
|
||||||
|
[Releases](https://github.com/farion1231/cc-switch/releases/latest) から入手:
|
||||||
|
|
||||||
|
- **Windows**: `CC-Switch-v3.8.0-Windows.msi` または `-Portable.zip`
|
||||||
|
- **macOS**: `CC-Switch-v3.8.0-macOS.tar.gz` または `.zip`
|
||||||
|
- **Linux**: `CC-Switch-v3.8.0-Linux.AppImage` または `.deb`
|
||||||
|
|
||||||
|
### Homebrew (macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap farion1231/ccswitch
|
||||||
|
brew install --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
アップデート:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew upgrade --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 謝辞
|
||||||
|
|
||||||
|
### コントリビューター
|
||||||
|
|
||||||
|
- [@YoVinchen](https://github.com/YoVinchen) - UI とデータベースリファクタ
|
||||||
|
- [@farion1231](https://github.com/farion1231) - バグ修正と機能拡張
|
||||||
|
- コミュニティの皆さん - テストとフィードバック
|
||||||
|
|
||||||
|
### スポンサー
|
||||||
|
|
||||||
|
**Zhipu AI** - GLM CODING PLAN スポンサー
|
||||||
|
[10% オフリンク](https://z.ai/subscribe?ic=8JVLJQFSKB)
|
||||||
|
|
||||||
|
**PackyCode** - API リレーサービスパートナー
|
||||||
|
[登録時に「cc-switch」で 10% オフ](https://www.packyapi.com/register?aff=cc-switch)
|
||||||
|
|
||||||
|
**ShandianShuo** - ローカルファースト音声入力
|
||||||
|
[Mac/Windows 無料ダウンロード](https://shandianshuo.cn)
|
||||||
|
|
||||||
|
**MiniMax** - MiniMax M2 CODING PLAN スポンサー
|
||||||
|
[ブラックフライデーセール中、$2 から](https://platform.minimax.io/subscribe/coding-plan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## フィードバック & サポート
|
||||||
|
|
||||||
|
- **Issue**: [GitHub Issues](https://github.com/farion1231/cc-switch/issues)
|
||||||
|
- **Discussions**: [GitHub Discussions](https://github.com/farion1231/cc-switch/discussions)
|
||||||
|
- **ドキュメント**: [README](../README.md)
|
||||||
|
- **更新履歴**: [CHANGELOG.md](../CHANGELOG.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 今後のロードマップ
|
||||||
|
|
||||||
|
**v3.9.0 予告(予定)**:
|
||||||
|
|
||||||
|
- ローカルプロキシ機能
|
||||||
|
|
||||||
|
続報にご期待ください!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Coding!**
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
# CC Switch v3.8.0
|
||||||
|
|
||||||
|
> 持久化架构升级,为云同步奠定基础
|
||||||
|
|
||||||
|
**[English Version →](release-note-v3.8.0-en.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
|
CC Switch v3.8.0 是一次重大的架构升级版本,重构了数据持久化层和用户界面,为未来的云同步和本地代理功能奠定基础。
|
||||||
|
|
||||||
|
**发布日期**:2025-11-28
|
||||||
|
**提交数量**:从 v3.7.1 开始 51 个提交
|
||||||
|
**代码变更**:207 个文件,+17,297 / -6,870 行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 重大更新
|
||||||
|
|
||||||
|
### 持久化架构升级
|
||||||
|
|
||||||
|
从单一 JSON 文件存储迁移到 SQLite + JSON 双层架构,实现数据分层管理。
|
||||||
|
|
||||||
|
**架构变更**:
|
||||||
|
|
||||||
|
```
|
||||||
|
v3.7.x (旧) v3.8.0 (新)
|
||||||
|
┌─────────────────┐ ┌─────────────────────────────────┐
|
||||||
|
│ config.json │ │ SQLite (可同步数据) │
|
||||||
|
│ ┌───────────┐ │ │ ├─ providers 供应商配置 │
|
||||||
|
│ │ providers │ │ │ ├─ mcp_servers MCP 服务器 │
|
||||||
|
│ │ mcp │ │ ──> │ ├─ prompts 提示词 │
|
||||||
|
│ │ prompts │ │ │ ├─ skills 技能 │
|
||||||
|
│ │ settings │ │ │ └─ settings 通用设置 │
|
||||||
|
│ └───────────┘ │ ├─────────────────────────────────┤
|
||||||
|
└─────────────────┘ │ JSON (设备级数据) │
|
||||||
|
│ └─ settings.json 本地设置 │
|
||||||
|
│ ├─ 窗口位置 │
|
||||||
|
│ ├─ 路径覆盖 │
|
||||||
|
│ └─ 当前选中供应商 ID │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**双层结构设计**:
|
||||||
|
|
||||||
|
| 层级 | 存储方式 | 数据类型 | 同步策略 |
|
||||||
|
| -------- | -------- | ---------------------------- | ---------- |
|
||||||
|
| 云同步层 | SQLite | 供应商、MCP、Prompts、Skills | 未来可同步 |
|
||||||
|
| 设备层 | JSON | 窗口状态、本地路径、当前选择 | 保持本地 |
|
||||||
|
|
||||||
|
**技术实现**:
|
||||||
|
|
||||||
|
- **Schema 版本管理** - 支持数据库结构升级迁移
|
||||||
|
- **SQL 导入导出** - `backup.rs` 支持 SQL dump,便于云端存储
|
||||||
|
- **事务支持** - SQLite 原生事务保证数据一致性
|
||||||
|
- **自动迁移** - 首次启动自动从 `config.json` 迁移数据
|
||||||
|
|
||||||
|
**模块化重构**:
|
||||||
|
|
||||||
|
```
|
||||||
|
database/
|
||||||
|
├── mod.rs 核心 Database 结构体和初始化
|
||||||
|
├── schema.rs 表结构定义、Schema 版本迁移
|
||||||
|
├── backup.rs SQL 导入导出、二进制快照备份
|
||||||
|
├── migration.rs JSON → SQLite 数据迁移引擎
|
||||||
|
└── dao/ 数据访问对象层
|
||||||
|
├── providers.rs 供应商 CRUD
|
||||||
|
├── mcp.rs MCP 服务器 CRUD
|
||||||
|
├── prompts.rs 提示词 CRUD
|
||||||
|
├── skills.rs Skills CRUD
|
||||||
|
└── settings.rs 键值对设置存储
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 全新用户界面
|
||||||
|
|
||||||
|
完整重构的 UI 设计,提供更现代化的视觉体验。
|
||||||
|
|
||||||
|
**视觉改进**:
|
||||||
|
|
||||||
|
- 重新设计的界面布局
|
||||||
|
- 统一的组件样式
|
||||||
|
- 更流畅的过渡动画
|
||||||
|
- 优化的视觉层次
|
||||||
|
|
||||||
|
**交互优化**:
|
||||||
|
|
||||||
|
- Header toolbar 重新设计
|
||||||
|
- ConfirmDialog 样式统一
|
||||||
|
- 禁用主视图 overscroll 弹跳效果
|
||||||
|
- 改进的表单验证反馈
|
||||||
|
|
||||||
|
**兼容性调整**:
|
||||||
|
|
||||||
|
- Tailwind CSS 从 v4 降级到 v3.4,提升浏览器兼容性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 日语支持
|
||||||
|
|
||||||
|
新增日语(日本語)界面支持,国际化语言扩展到三种。
|
||||||
|
|
||||||
|
**支持语言**:
|
||||||
|
|
||||||
|
- 简体中文
|
||||||
|
- English
|
||||||
|
- 日本語(新增)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 新增功能
|
||||||
|
|
||||||
|
### Skills 递归扫描
|
||||||
|
|
||||||
|
Skills 管理系统支持递归扫描仓库目录,自动发现嵌套的技能文件。
|
||||||
|
|
||||||
|
**改进内容**:
|
||||||
|
|
||||||
|
- 支持多层目录结构
|
||||||
|
- 自动发现所有 `SKILL.md` 文件
|
||||||
|
- 允许不同仓库的同名技能(使用完整路径去重)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 供应商图标配置
|
||||||
|
|
||||||
|
供应商预设支持自定义图标配置。
|
||||||
|
|
||||||
|
**功能特性**:
|
||||||
|
|
||||||
|
- 预设供应商包含默认图标
|
||||||
|
- 复制供应商时保留图标设置
|
||||||
|
- 图标颜色自定义
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 表单验证增强
|
||||||
|
|
||||||
|
供应商表单新增必填字段验证,提供更友好的错误提示。
|
||||||
|
|
||||||
|
**改进内容**:
|
||||||
|
|
||||||
|
- 必填字段实时校验
|
||||||
|
- 统一使用 Toast 通知显示验证错误
|
||||||
|
- 更清晰的错误信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 开机自启
|
||||||
|
|
||||||
|
新增开机自动启动功能,支持 Windows、macOS 和 Linux 三个平台。
|
||||||
|
|
||||||
|
**功能特性**:
|
||||||
|
|
||||||
|
- 在设置中一键开启/关闭
|
||||||
|
- 使用平台原生 API 实现
|
||||||
|
- Windows 使用注册表、macOS 使用 LaunchAgent、Linux 使用 XDG autostart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 新增供应商预设
|
||||||
|
|
||||||
|
- **MiniMax** - 官方合作伙伴
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug 修复
|
||||||
|
|
||||||
|
### 关键修复
|
||||||
|
|
||||||
|
**自定义端点丢失问题**
|
||||||
|
|
||||||
|
修复更新供应商时自定义请求地址意外丢失的问题。
|
||||||
|
|
||||||
|
- 根因:`INSERT OR REPLACE` 在 SQLite 底层执行 `DELETE + INSERT`,触发外键级联删除
|
||||||
|
- 修复:改用 `UPDATE` 语句更新已存在的供应商
|
||||||
|
|
||||||
|
**Gemini 配置问题**
|
||||||
|
|
||||||
|
- 修复自定义供应商环境变量未正确写入 `.env` 文件
|
||||||
|
- 修复安全认证配置错误写入到其他配置文件
|
||||||
|
|
||||||
|
**供应商验证问题**
|
||||||
|
|
||||||
|
- 修复当前供应商 ID 不存在时的验证错误
|
||||||
|
- 修复供应商复制时图标字段丢失
|
||||||
|
|
||||||
|
### 平台兼容性
|
||||||
|
|
||||||
|
**Linux**
|
||||||
|
|
||||||
|
- 解决 WebKitGTK DMA-BUF 渲染问题
|
||||||
|
- 保留用户 `.desktop` 文件自定义
|
||||||
|
|
||||||
|
### 其他修复
|
||||||
|
|
||||||
|
- 修复切换应用时的冗余用量查询
|
||||||
|
- 修复 DMXAPI 预设使用错误的认证令牌字段
|
||||||
|
- 修复深链接组件缺少翻译键
|
||||||
|
- 修复用量脚本模板初始化逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术改进
|
||||||
|
|
||||||
|
### 架构重构
|
||||||
|
|
||||||
|
**供应商服务模块化**:
|
||||||
|
|
||||||
|
```
|
||||||
|
services/provider/
|
||||||
|
├── mod.rs 核心服务 - add/update/delete/switch/validate
|
||||||
|
├── live.rs Live 配置文件操作
|
||||||
|
├── gemini_auth.rs Gemini 认证类型检测
|
||||||
|
├── endpoints.rs 自定义端点管理
|
||||||
|
└── usage.rs 用量脚本执行
|
||||||
|
```
|
||||||
|
|
||||||
|
**深链接模块化**:
|
||||||
|
|
||||||
|
```
|
||||||
|
deeplink/
|
||||||
|
├── mod.rs 模块导出
|
||||||
|
├── parser.rs URL 解析
|
||||||
|
├── provider.rs 供应商导入逻辑
|
||||||
|
├── mcp.rs MCP 导入逻辑
|
||||||
|
├── prompt.rs 提示词导入
|
||||||
|
├── skill.rs Skills 导入
|
||||||
|
└── utils.rs 工具函数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码质量
|
||||||
|
|
||||||
|
**清理工作**:
|
||||||
|
|
||||||
|
- 移除 JSON 时代遗留的导入导出死代码
|
||||||
|
- 移除未使用的 MCP 类型导出
|
||||||
|
- 统一错误处理方式
|
||||||
|
|
||||||
|
**测试更新**:
|
||||||
|
|
||||||
|
- 迁移测试到 SQLite 数据库架构
|
||||||
|
- 更新组件测试匹配当前实现
|
||||||
|
- 修复 MSW handlers 适配新 API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术统计
|
||||||
|
|
||||||
|
```
|
||||||
|
总体变更:
|
||||||
|
- 提交数:51
|
||||||
|
- 文件数:207 个文件变更
|
||||||
|
- 新增:+17,297 行
|
||||||
|
- 删除:-6,870 行
|
||||||
|
- 净增:+10,427 行
|
||||||
|
|
||||||
|
提交类型分布:
|
||||||
|
- fix:25 个(Bug 修复)
|
||||||
|
- refactor:11 个(代码重构)
|
||||||
|
- feat:9 个(新功能)
|
||||||
|
- test:1 个(测试)
|
||||||
|
- 其他:5 个
|
||||||
|
|
||||||
|
改动区域分布:
|
||||||
|
- 前端源码:112 个文件
|
||||||
|
- Rust 后端:63 个文件
|
||||||
|
- 测试文件:20 个文件
|
||||||
|
- 国际化文件:3 个文件
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 迁移说明
|
||||||
|
|
||||||
|
### 从 v3.7.x 升级
|
||||||
|
|
||||||
|
**自动迁移** - 首次启动时自动执行:
|
||||||
|
|
||||||
|
1. 检测 `config.json` 是否存在
|
||||||
|
2. 在事务中迁移所有数据到 SQLite
|
||||||
|
3. 设备级设置迁移到 `settings.json`
|
||||||
|
4. 显示迁移成功通知
|
||||||
|
|
||||||
|
**数据安全**:
|
||||||
|
|
||||||
|
- 原 `config.json` 文件保留不删除
|
||||||
|
- 迁移失败时显示错误对话框,保留`config.json`
|
||||||
|
- 支持 Dry-run 模式验证迁移逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 下载与安装
|
||||||
|
|
||||||
|
### 系统要求
|
||||||
|
|
||||||
|
- **Windows**:Windows 10+
|
||||||
|
- **macOS**:macOS 10.15(Catalina)+
|
||||||
|
- **Linux**:Ubuntu 22.04+ / Debian 11+ / Fedora 34+
|
||||||
|
|
||||||
|
### 下载链接
|
||||||
|
|
||||||
|
访问 [Releases](https://github.com/farion1231/cc-switch/releases/latest) 下载:
|
||||||
|
|
||||||
|
- **Windows**:`CC-Switch-v3.8.0-Windows.msi` 或 `-Portable.zip`
|
||||||
|
- **macOS**:`CC-Switch-v3.8.0-macOS.tar.gz` 或 `.zip`
|
||||||
|
- **Linux**:`CC-Switch-v3.8.0-Linux.AppImage` 或 `.deb`
|
||||||
|
|
||||||
|
### Homebrew(macOS)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew tap farion1231/ccswitch
|
||||||
|
brew install --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
更新:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew upgrade --cask cc-switch
|
||||||
|
```
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
### 贡献者
|
||||||
|
|
||||||
|
感谢所有让这个版本成为可能的贡献者:
|
||||||
|
|
||||||
|
- [@YoVinchen](https://github.com/YoVinchen) - UI 和数据库重构
|
||||||
|
- [@farion1231](https://github.com/farion1231) - BUG 修复和功能增强
|
||||||
|
- 社区成员的测试和反馈
|
||||||
|
|
||||||
|
### 赞助商
|
||||||
|
|
||||||
|
**智谱AI** - GLM CODING PLAN 赞助商
|
||||||
|
[使用此链接购买可享九折优惠](https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII)
|
||||||
|
|
||||||
|
**PackyCode** - API 中转服务合作伙伴
|
||||||
|
[使用 "cc-switch" 优惠码注册享 9 折优惠](https://www.packyapi.com/register?aff=cc-switch)
|
||||||
|
|
||||||
|
**闪电说** - 本地优先的 AI 语音输入法
|
||||||
|
[免费下载](https://shandianshuo.cn) Mac/Win 双平台
|
||||||
|
|
||||||
|
**MiniMax** - MiniMax M2 CODING PLAN 赞助商
|
||||||
|
[黑五优惠进行中,套餐9.9元起](https://platform.minimaxi.com/subscribe/coding-plan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 反馈与支持
|
||||||
|
|
||||||
|
- **问题反馈**:[GitHub Issues](https://github.com/farion1231/cc-switch/issues)
|
||||||
|
- **讨论**:[GitHub Discussions](https://github.com/farion1231/cc-switch/discussions)
|
||||||
|
- **文档**:[README](../README_ZH.md)
|
||||||
|
- **更新日志**:[CHANGELOG.md](../CHANGELOG.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 未来展望
|
||||||
|
|
||||||
|
**v3.9.0 预览**(暂定):
|
||||||
|
|
||||||
|
- 本地代理功能
|
||||||
|
|
||||||
|
敬请期待更多更新!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy Coding!**
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cc-switch",
|
"name": "cc-switch",
|
||||||
"version": "3.7.1",
|
"version": "3.8.0",
|
||||||
"description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI",
|
"description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm tauri dev",
|
"dev": "pnpm tauri dev",
|
||||||
|
|||||||
Generated
+1
-1
@@ -618,7 +618,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc-switch"
|
name = "cc-switch"
|
||||||
version = "3.7.1"
|
version = "3.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"auto-launch",
|
"auto-launch",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "cc-switch"
|
name = "cc-switch"
|
||||||
version = "3.7.1"
|
version = "3.8.0"
|
||||||
description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI"
|
description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI"
|
||||||
authors = ["Jason Young"]
|
authors = ["Jason Young"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
use auto_launch::AutoLaunch;
|
use auto_launch::{AutoLaunch, AutoLaunchBuilder};
|
||||||
|
|
||||||
/// 初始化 AutoLaunch 实例
|
/// 初始化 AutoLaunch 实例
|
||||||
fn get_auto_launch() -> Result<AutoLaunch, AppError> {
|
fn get_auto_launch() -> Result<AutoLaunch, AppError> {
|
||||||
@@ -7,13 +7,15 @@ fn get_auto_launch() -> Result<AutoLaunch, AppError> {
|
|||||||
let app_path =
|
let app_path =
|
||||||
std::env::current_exe().map_err(|e| AppError::Message(format!("无法获取应用路径: {e}")))?;
|
std::env::current_exe().map_err(|e| AppError::Message(format!("无法获取应用路径: {e}")))?;
|
||||||
|
|
||||||
// Windows 平台的 AutoLaunch::new 只接受 3 个参数
|
// 使用 AutoLaunchBuilder 消除平台差异
|
||||||
// Linux/macOS 平台需要 4 个参数(包含 hidden 参数)
|
// Windows/Linux: new() 接受 3 参数
|
||||||
#[cfg(target_os = "windows")]
|
// macOS: new() 接受 4 参数(含 hidden 参数)
|
||||||
let auto_launch = AutoLaunch::new(app_name, &app_path.to_string_lossy(), &[] as &[&str]);
|
// Builder 模式自动处理这些差异
|
||||||
|
let auto_launch = AutoLaunchBuilder::new()
|
||||||
#[cfg(not(target_os = "windows"))]
|
.set_app_name(app_name)
|
||||||
let auto_launch = AutoLaunch::new(app_name, &app_path.to_string_lossy(), false, &[] as &[&str]);
|
.set_app_path(&app_path.to_string_lossy())
|
||||||
|
.build()
|
||||||
|
.map_err(|e| AppError::Message(format!("创建 AutoLaunch 失败: {e}")))?;
|
||||||
|
|
||||||
Ok(auto_launch)
|
Ok(auto_launch)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ pub async fn install_skill(
|
|||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "main".to_string()),
|
.unwrap_or_else(|| "main".to_string()),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skills_path: skill.skills_path.clone(), // 使用技能记录的 skills_path
|
|
||||||
};
|
};
|
||||||
|
|
||||||
service
|
service
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ impl Database {
|
|||||||
pub fn get_skill_repos(&self) -> Result<Vec<SkillRepo>, AppError> {
|
pub fn get_skill_repos(&self) -> Result<Vec<SkillRepo>, AppError> {
|
||||||
let conn = lock_conn!(self.conn);
|
let conn = lock_conn!(self.conn);
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT owner, name, branch, enabled, skills_path FROM skill_repos ORDER BY owner ASC, name ASC")
|
.prepare(
|
||||||
|
"SELECT owner, name, branch, enabled FROM skill_repos ORDER BY owner ASC, name ASC",
|
||||||
|
)
|
||||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||||
|
|
||||||
let repo_iter = stmt
|
let repo_iter = stmt
|
||||||
@@ -68,7 +70,6 @@ impl Database {
|
|||||||
name: row.get(1)?,
|
name: row.get(1)?,
|
||||||
branch: row.get(2)?,
|
branch: row.get(2)?,
|
||||||
enabled: row.get(3)?,
|
enabled: row.get(3)?,
|
||||||
skills_path: row.get(4)?,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||||
@@ -84,8 +85,8 @@ impl Database {
|
|||||||
pub fn save_skill_repo(&self, repo: &SkillRepo) -> Result<(), AppError> {
|
pub fn save_skill_repo(&self, repo: &SkillRepo) -> Result<(), AppError> {
|
||||||
let conn = lock_conn!(self.conn);
|
let conn = lock_conn!(self.conn);
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled, skills_path) VALUES (?1, ?2, ?3, ?4, ?5)",
|
"INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)",
|
||||||
params![repo.owner, repo.name, repo.branch, repo.enabled, repo.skills_path],
|
params![repo.owner, repo.name, repo.branch, repo.enabled],
|
||||||
).map_err(|e| AppError::Database(e.to_string()))?;
|
).map_err(|e| AppError::Database(e.to_string()))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,8 +202,8 @@ impl Database {
|
|||||||
|
|
||||||
for repo in &config.skills.repos {
|
for repo in &config.skills.repos {
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled, skills_path) VALUES (?1, ?2, ?3, ?4, ?5)",
|
"INSERT OR REPLACE INTO skill_repos (owner, name, branch, enabled) VALUES (?1, ?2, ?3, ?4)",
|
||||||
params![repo.owner, repo.name, repo.branch, repo.enabled, repo.skills_path],
|
params![repo.owner, repo.name, repo.branch, repo.enabled],
|
||||||
).map_err(|e| AppError::Database(format!("Migrate skill repo failed: {e}")))?;
|
).map_err(|e| AppError::Database(format!("Migrate skill repo failed: {e}")))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ impl Database {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
branch TEXT NOT NULL DEFAULT 'main',
|
branch TEXT NOT NULL DEFAULT 'main',
|
||||||
enabled BOOLEAN NOT NULL DEFAULT 1,
|
enabled BOOLEAN NOT NULL DEFAULT 1,
|
||||||
skills_path TEXT,
|
|
||||||
PRIMARY KEY (owner, name)
|
PRIMARY KEY (owner, name)
|
||||||
)",
|
)",
|
||||||
[],
|
[],
|
||||||
@@ -233,7 +232,7 @@ impl Database {
|
|||||||
"TEXT NOT NULL DEFAULT 'main'",
|
"TEXT NOT NULL DEFAULT 'main'",
|
||||||
)?;
|
)?;
|
||||||
Self::add_column_if_missing(conn, "skill_repos", "enabled", "BOOLEAN NOT NULL DEFAULT 1")?;
|
Self::add_column_if_missing(conn, "skill_repos", "enabled", "BOOLEAN NOT NULL DEFAULT 1")?;
|
||||||
Self::add_column_if_missing(conn, "skill_repos", "skills_path", "TEXT")?;
|
// 注意: skills_path 字段已被移除,因为现在支持全仓库递归扫描
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,12 +100,8 @@ pub struct DeepLinkImportRequest {
|
|||||||
/// Skill directory name
|
/// Skill directory name
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub directory: Option<String>,
|
pub directory: Option<String>,
|
||||||
/// Repository branch (default: "main")
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub branch: Option<String>,
|
pub branch: Option<String>,
|
||||||
/// Skills subdirectory path (e.g., "skills")
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub skills_path: Option<String>,
|
|
||||||
|
|
||||||
// ============ Config file fields (v3.8+) ============
|
// ============ Config file fields (v3.8+) ============
|
||||||
/// Base64 encoded config content
|
/// Base64 encoded config content
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ fn parse_provider_deeplink(
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
config,
|
config,
|
||||||
config_format,
|
config_format,
|
||||||
config_url,
|
config_url,
|
||||||
@@ -204,7 +203,6 @@ fn parse_prompt_deeplink(
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
config: None,
|
config: None,
|
||||||
config_format: None,
|
config_format: None,
|
||||||
config_url: None,
|
config_url: None,
|
||||||
@@ -262,7 +260,6 @@ fn parse_mcp_deeplink(
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
config_url: None,
|
config_url: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -287,10 +284,6 @@ fn parse_skill_deeplink(
|
|||||||
|
|
||||||
let directory = params.get("directory").cloned();
|
let directory = params.get("directory").cloned();
|
||||||
let branch = params.get("branch").cloned();
|
let branch = params.get("branch").cloned();
|
||||||
let skills_path = params
|
|
||||||
.get("skills_path")
|
|
||||||
.or_else(|| params.get("skillsPath"))
|
|
||||||
.cloned();
|
|
||||||
|
|
||||||
Ok(DeepLinkImportRequest {
|
Ok(DeepLinkImportRequest {
|
||||||
version,
|
version,
|
||||||
@@ -298,7 +291,6 @@ fn parse_skill_deeplink(
|
|||||||
repo: Some(repo),
|
repo: Some(repo),
|
||||||
directory,
|
directory,
|
||||||
branch,
|
branch,
|
||||||
skills_path,
|
|
||||||
icon: None,
|
icon: None,
|
||||||
app: Some("claude".to_string()), // Skills are Claude-only
|
app: Some("claude".to_string()), // Skills are Claude-only
|
||||||
name: None,
|
name: None,
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ pub fn import_skill_from_deeplink(
|
|||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
branch: request.branch.unwrap_or_else(|| "main".to_string()),
|
branch: request.branch.unwrap_or_else(|| "main".to_string()),
|
||||||
enabled: request.enabled.unwrap_or(true),
|
enabled: request.enabled.unwrap_or(true),
|
||||||
skills_path: request.skills_path,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save using Database
|
// Save using Database
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ fn test_build_gemini_provider_with_model() {
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
content: None,
|
content: None,
|
||||||
description: None,
|
description: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
@@ -189,7 +188,6 @@ fn test_build_gemini_provider_without_model() {
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
content: None,
|
content: None,
|
||||||
description: None,
|
description: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
@@ -231,7 +229,6 @@ fn test_parse_and_merge_config_claude() {
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
content: None,
|
content: None,
|
||||||
description: None,
|
description: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
@@ -275,7 +272,6 @@ fn test_parse_and_merge_config_url_override() {
|
|||||||
repo: None,
|
repo: None,
|
||||||
directory: None,
|
directory: None,
|
||||||
branch: None,
|
branch: None,
|
||||||
skills_path: None,
|
|
||||||
content: None,
|
content: None,
|
||||||
description: None,
|
description: None,
|
||||||
enabled: None,
|
enabled: None,
|
||||||
@@ -372,12 +368,11 @@ fn test_parse_mcp_deeplink() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_skill_deeplink() {
|
fn test_parse_skill_deeplink() {
|
||||||
let url = "ccswitch://v1/import?resource=skill&repo=owner/repo&directory=skills&branch=dev&skills_path=src";
|
let url = "ccswitch://v1/import?resource=skill&repo=owner/repo&directory=skills&branch=dev";
|
||||||
let request = parse_deeplink_url(&url).unwrap();
|
let request = parse_deeplink_url(&url).unwrap();
|
||||||
|
|
||||||
assert_eq!(request.resource, "skill");
|
assert_eq!(request.resource, "skill");
|
||||||
assert_eq!(request.repo.unwrap(), "owner/repo");
|
assert_eq!(request.repo.unwrap(), "owner/repo");
|
||||||
assert_eq!(request.directory.unwrap(), "skills");
|
assert_eq!(request.directory.unwrap(), "skills");
|
||||||
assert_eq!(request.branch.unwrap(), "dev");
|
assert_eq!(request.branch.unwrap(), "dev");
|
||||||
assert_eq!(request.skills_path.unwrap(), "src");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ impl TrayTexts {
|
|||||||
},
|
},
|
||||||
"ja" => Self {
|
"ja" => Self {
|
||||||
show_main: "メインウィンドウを開く",
|
show_main: "メインウィンドウを開く",
|
||||||
no_provider_hint: " (プロバイダーがまだありません。メイン画面から追加してください)",
|
no_provider_hint:
|
||||||
|
" (プロバイダーがまだありません。メイン画面から追加してください)",
|
||||||
quit: "終了",
|
quit: "終了",
|
||||||
},
|
},
|
||||||
_ => Self {
|
_ => Self {
|
||||||
@@ -521,7 +522,8 @@ pub fn run() {
|
|||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
use objc2::msg_send;
|
use objc2::msg_send;
|
||||||
let _: () = msg_send![&*ns_window, setBackgroundColor: &*bg_color];
|
let _: () =
|
||||||
|
msg_send![&*ns_window, setBackgroundColor: &*bg_color];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Failed to retain NSWindow reference");
|
log::warn!("Failed to retain NSWindow reference");
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ pub struct Skill {
|
|||||||
/// 分支名称
|
/// 分支名称
|
||||||
#[serde(rename = "repoBranch")]
|
#[serde(rename = "repoBranch")]
|
||||||
pub repo_branch: Option<String>,
|
pub repo_branch: Option<String>,
|
||||||
/// 技能所在的子目录路径 (可选, 如 "skills")
|
|
||||||
#[serde(rename = "skillsPath")]
|
|
||||||
pub skills_path: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 仓库配置
|
/// 仓库配置
|
||||||
@@ -50,9 +47,6 @@ pub struct SkillRepo {
|
|||||||
pub branch: String,
|
pub branch: String,
|
||||||
/// 是否启用
|
/// 是否启用
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// 技能所在的子目录路径 (可选, 如 "skills", "my-skills/subdir")
|
|
||||||
#[serde(rename = "skillsPath")]
|
|
||||||
pub skills_path: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 技能安装状态
|
/// 技能安装状态
|
||||||
@@ -84,21 +78,18 @@ impl Default for SkillStore {
|
|||||||
name: "awesome-claude-skills".to_string(),
|
name: "awesome-claude-skills".to_string(),
|
||||||
branch: "main".to_string(),
|
branch: "main".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skills_path: None, // 扫描根目录
|
|
||||||
},
|
},
|
||||||
SkillRepo {
|
SkillRepo {
|
||||||
owner: "anthropics".to_string(),
|
owner: "anthropics".to_string(),
|
||||||
name: "skills".to_string(),
|
name: "skills".to_string(),
|
||||||
branch: "main".to_string(),
|
branch: "main".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skills_path: None, // 扫描根目录
|
|
||||||
},
|
},
|
||||||
SkillRepo {
|
SkillRepo {
|
||||||
owner: "cexll".to_string(),
|
owner: "cexll".to_string(),
|
||||||
name: "myclaude".to_string(),
|
name: "myclaude".to_string(),
|
||||||
branch: "master".to_string(),
|
branch: "master".to_string(),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skills_path: Some("skills".to_string()), // 扫描 skills 子目录
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -194,25 +185,8 @@ impl SkillService {
|
|||||||
})??;
|
})??;
|
||||||
let mut skills = Vec::new();
|
let mut skills = Vec::new();
|
||||||
|
|
||||||
// 确定要扫描的目录路径
|
// 扫描仓库根目录(支持全仓库递归扫描)
|
||||||
let scan_dir = if let Some(ref skills_path) = repo.skills_path {
|
let scan_dir = temp_dir.clone();
|
||||||
// 如果指定了 skillsPath,则扫描该子目录
|
|
||||||
let subdir = temp_dir.join(skills_path.trim_matches('/'));
|
|
||||||
if !subdir.exists() {
|
|
||||||
log::warn!(
|
|
||||||
"仓库 {}/{} 中指定的技能路径 '{}' 不存在",
|
|
||||||
repo.owner,
|
|
||||||
repo.name,
|
|
||||||
skills_path
|
|
||||||
);
|
|
||||||
let _ = fs::remove_dir_all(&temp_dir);
|
|
||||||
return Ok(skills);
|
|
||||||
}
|
|
||||||
subdir
|
|
||||||
} else {
|
|
||||||
// 否则扫描仓库根目录
|
|
||||||
temp_dir.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 递归扫描目录查找所有技能
|
// 递归扫描目录查找所有技能
|
||||||
self.scan_dir_recursive(&scan_dir, &scan_dir, repo, &mut skills)?;
|
self.scan_dir_recursive(&scan_dir, &scan_dir, repo, &mut skills)?;
|
||||||
@@ -284,11 +258,7 @@ impl SkillService {
|
|||||||
let meta = self.parse_skill_metadata(skill_md)?;
|
let meta = self.parse_skill_metadata(skill_md)?;
|
||||||
|
|
||||||
// 构建 README URL
|
// 构建 README URL
|
||||||
let readme_path = if let Some(ref skills_path) = repo.skills_path {
|
let readme_path = directory.to_string();
|
||||||
format!("{}/{}", skills_path.trim_matches('/'), directory)
|
|
||||||
} else {
|
|
||||||
directory.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Skill {
|
Ok(Skill {
|
||||||
key: format!("{}/{}:{}", repo.owner, repo.name, directory),
|
key: format!("{}/{}:{}", repo.owner, repo.name, directory),
|
||||||
@@ -303,7 +273,6 @@ impl SkillService {
|
|||||||
repo_owner: Some(repo.owner.clone()),
|
repo_owner: Some(repo.owner.clone()),
|
||||||
repo_name: Some(repo.name.clone()),
|
repo_name: Some(repo.name.clone()),
|
||||||
repo_branch: Some(repo.branch.clone()),
|
repo_branch: Some(repo.branch.clone()),
|
||||||
skills_path: repo.skills_path.clone(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +374,6 @@ impl SkillService {
|
|||||||
repo_owner: None,
|
repo_owner: None,
|
||||||
repo_name: None,
|
repo_name: None,
|
||||||
repo_branch: None,
|
repo_branch: None,
|
||||||
skills_path: None,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,16 +542,8 @@ impl SkillService {
|
|||||||
))
|
))
|
||||||
})??;
|
})??;
|
||||||
|
|
||||||
// 根据 skills_path 确定源目录路径
|
// 确定源目录路径(技能相对于仓库根目录的路径)
|
||||||
let source = if let Some(ref skills_path) = repo.skills_path {
|
let source = temp_dir.join(&directory);
|
||||||
// 如果指定了 skills_path,源路径为: temp_dir/skills_path/directory
|
|
||||||
temp_dir
|
|
||||||
.join(skills_path.trim_matches('/'))
|
|
||||||
.join(&directory)
|
|
||||||
} else {
|
|
||||||
// 否则源路径为: temp_dir/directory
|
|
||||||
temp_dir.join(&directory)
|
|
||||||
};
|
|
||||||
|
|
||||||
if !source.exists() {
|
if !source.exists() {
|
||||||
let _ = fs::remove_dir_all(&temp_dir);
|
let _ = fs::remove_dir_all(&temp_dir);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "CC Switch",
|
"productName": "CC Switch",
|
||||||
"version": "3.7.1",
|
"version": "3.8.0",
|
||||||
"identifier": "com.ccswitch.desktop",
|
"identifier": "com.ccswitch.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
+2
-2
@@ -391,7 +391,7 @@ function App() {
|
|||||||
t("prompts.title", { appName: t(`apps.${activeApp}`) })}
|
t("prompts.title", { appName: t(`apps.${activeApp}`) })}
|
||||||
{currentView === "skills" && t("skills.title")}
|
{currentView === "skills" && t("skills.title")}
|
||||||
{currentView === "mcp" && t("mcp.unifiedPanel.title")}
|
{currentView === "mcp" && t("mcp.unifiedPanel.title")}
|
||||||
{currentView === "agents" && "Agents"}
|
{currentView === "agents" && t("agents.title")}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -512,7 +512,7 @@ function App() {
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setCurrentView("mcp")}
|
onClick={() => setCurrentView("mcp")}
|
||||||
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
className="text-muted-foreground hover:text-foreground hover:bg-black/5 dark:hover:bg-white/5"
|
||||||
title="MCP"
|
title={t("mcp.title")}
|
||||||
>
|
>
|
||||||
<Server className="h-4 w-4" />
|
<Server className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -30,22 +30,11 @@ export function SkillConfirmation({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div>
|
||||||
<div>
|
<label className="block text-sm font-medium text-muted-foreground">
|
||||||
<label className="block text-sm font-medium text-muted-foreground">
|
{t("deeplink.skill.branch")}
|
||||||
{t("deeplink.skill.branch")}
|
</label>
|
||||||
</label>
|
<div className="mt-1 text-sm">{request.branch || "main"}</div>
|
||||||
<div className="mt-1 text-sm">{request.branch || "main"}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{request.skillsPath && (
|
|
||||||
<div>
|
|
||||||
<label className="block text-sm font-medium text-muted-foreground">
|
|
||||||
{t("deeplink.skill.skillsPath")}
|
|
||||||
</label>
|
|
||||||
<div className="mt-1 text-sm">{request.skillsPath}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-blue-600 dark:text-blue-400 text-sm bg-blue-50 dark:bg-blue-950/30 p-3 rounded border border-blue-200 dark:border-blue-800">
|
<div className="text-blue-600 dark:text-blue-400 text-sm bg-blue-50 dark:bg-blue-950/30 p-3 rounded border border-blue-200 dark:border-blue-800">
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export function RepoManager({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [repoUrl, setRepoUrl] = useState("");
|
const [repoUrl, setRepoUrl] = useState("");
|
||||||
const [branch, setBranch] = useState("");
|
const [branch, setBranch] = useState("");
|
||||||
const [skillsPath, setSkillsPath] = useState("");
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const getSkillCount = (repo: SkillRepo) =>
|
const getSkillCount = (repo: SkillRepo) =>
|
||||||
@@ -80,12 +79,10 @@ export function RepoManager({
|
|||||||
name: parsed.name,
|
name: parsed.name,
|
||||||
branch: branch || "main",
|
branch: branch || "main",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skillsPath: skillsPath.trim() || undefined, // 仅在有值时传递
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setRepoUrl("");
|
setRepoUrl("");
|
||||||
setBranch("");
|
setBranch("");
|
||||||
setSkillsPath("");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : t("skills.repo.addFailed"));
|
setError(e instanceof Error ? e.message : t("skills.repo.addFailed"));
|
||||||
}
|
}
|
||||||
@@ -130,13 +127,6 @@ export function RepoManager({
|
|||||||
onChange={(e) => setBranch(e.target.value)}
|
onChange={(e) => setBranch(e.target.value)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<Input
|
|
||||||
id="skills-path"
|
|
||||||
placeholder={t("skills.repo.pathPlaceholder")}
|
|
||||||
value={skillsPath}
|
|
||||||
onChange={(e) => setSkillsPath(e.target.value)}
|
|
||||||
className="flex-1"
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
className="w-full sm:w-auto sm:px-4"
|
className="w-full sm:w-auto sm:px-4"
|
||||||
@@ -171,12 +161,6 @@ export function RepoManager({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-xs text-muted-foreground">
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
{t("skills.repo.branch")}: {repo.branch || "main"}
|
{t("skills.repo.branch")}: {repo.branch || "main"}
|
||||||
{repo.skillsPath && (
|
|
||||||
<>
|
|
||||||
<span className="mx-2">•</span>
|
|
||||||
{t("skills.repo.path")}: {repo.skillsPath}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span className="ml-3 inline-flex items-center rounded-full border border-border-default px-2 py-0.5 text-[11px]">
|
<span className="ml-3 inline-flex items-center rounded-full border border-border-default px-2 py-0.5 text-[11px]">
|
||||||
{t("skills.repo.skillCount", {
|
{t("skills.repo.skillCount", {
|
||||||
count: getSkillCount(repo),
|
count: getSkillCount(repo),
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function RepoManagerPanel({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [repoUrl, setRepoUrl] = useState("");
|
const [repoUrl, setRepoUrl] = useState("");
|
||||||
const [branch, setBranch] = useState("");
|
const [branch, setBranch] = useState("");
|
||||||
const [skillsPath, setSkillsPath] = useState("");
|
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const getSkillCount = (repo: SkillRepo) =>
|
const getSkillCount = (repo: SkillRepo) =>
|
||||||
@@ -67,12 +66,10 @@ export function RepoManagerPanel({
|
|||||||
name: parsed.name,
|
name: parsed.name,
|
||||||
branch: branch || "main",
|
branch: branch || "main",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
skillsPath: skillsPath.trim() || undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setRepoUrl("");
|
setRepoUrl("");
|
||||||
setBranch("");
|
setBranch("");
|
||||||
setSkillsPath("");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : t("skills.repo.addFailed"));
|
setError(e instanceof Error ? e.message : t("skills.repo.addFailed"));
|
||||||
}
|
}
|
||||||
@@ -110,31 +107,17 @@ export function RepoManagerPanel({
|
|||||||
className="mt-2"
|
className="mt-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div>
|
||||||
<div>
|
<Label htmlFor="branch" className="text-foreground">
|
||||||
<Label htmlFor="branch" className="text-foreground">
|
{t("skills.repo.branch")}
|
||||||
{t("skills.repo.branch")}
|
</Label>
|
||||||
</Label>
|
<Input
|
||||||
<Input
|
id="branch"
|
||||||
id="branch"
|
placeholder={t("skills.repo.branchPlaceholder")}
|
||||||
placeholder={t("skills.repo.branchPlaceholder")}
|
value={branch}
|
||||||
value={branch}
|
onChange={(e) => setBranch(e.target.value)}
|
||||||
onChange={(e) => setBranch(e.target.value)}
|
className="mt-2"
|
||||||
className="mt-2"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label htmlFor="skills-path" className="text-foreground">
|
|
||||||
{t("skills.repo.path")}
|
|
||||||
</Label>
|
|
||||||
<Input
|
|
||||||
id="skills-path"
|
|
||||||
placeholder={t("skills.repo.pathPlaceholder")}
|
|
||||||
value={skillsPath}
|
|
||||||
onChange={(e) => setSkillsPath(e.target.value)}
|
|
||||||
className="mt-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||||
@@ -174,12 +157,6 @@ export function RepoManagerPanel({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-1 text-xs text-muted-foreground">
|
<div className="mt-1 text-xs text-muted-foreground">
|
||||||
{t("skills.repo.branch")}: {repo.branch || "main"}
|
{t("skills.repo.branch")}: {repo.branch || "main"}
|
||||||
{repo.skillsPath && (
|
|
||||||
<>
|
|
||||||
<span className="mx-2">•</span>
|
|
||||||
{t("skills.repo.path")}: {repo.skillsPath}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<span className="ml-3 inline-flex items-center rounded-full border border-border-default px-2 py-0.5 text-[11px]">
|
<span className="ml-3 inline-flex items-center rounded-full border border-border-default px-2 py-0.5 text-[11px]">
|
||||||
{t("skills.repo.skillCount", {
|
{t("skills.repo.skillCount", {
|
||||||
count: getSkillCount(repo),
|
count: getSkillCount(repo),
|
||||||
|
|||||||
@@ -184,9 +184,7 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "ZhipuAI/GLM-4.6",
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "ZhipuAI/GLM-4.6",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "aggregator",
|
category: "aggregator"
|
||||||
icon: "alibaba",
|
|
||||||
iconColor: "#FF6A00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "KAT-Coder",
|
name: "KAT-Coder",
|
||||||
@@ -249,7 +247,33 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
},
|
},
|
||||||
category: "cn_official",
|
category: "cn_official",
|
||||||
isPartner: true,
|
isPartner: true,
|
||||||
partnerPromotionKey: "minimax",
|
partnerPromotionKey: "minimax_cn",
|
||||||
|
theme: {
|
||||||
|
backgroundColor: "#f64551",
|
||||||
|
textColor: "#FFFFFF",
|
||||||
|
},
|
||||||
|
icon: "minimax",
|
||||||
|
iconColor: "#FF6B6B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MiniMax en",
|
||||||
|
websiteUrl: "https://platform.minimaxi.io",
|
||||||
|
apiKeyUrl: "https://platform.minimax.io/subscribe/coding-plan",
|
||||||
|
settingsConfig: {
|
||||||
|
env: {
|
||||||
|
ANTHROPIC_BASE_URL: "https://api.minimaxi.io/anthropic",
|
||||||
|
ANTHROPIC_AUTH_TOKEN: "",
|
||||||
|
API_TIMEOUT_MS: "3000000",
|
||||||
|
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
|
||||||
|
ANTHROPIC_MODEL: "MiniMax-M2",
|
||||||
|
ANTHROPIC_DEFAULT_SONNET_MODEL: "MiniMax-M2",
|
||||||
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "MiniMax-M2",
|
||||||
|
ANTHROPIC_DEFAULT_HAIKU_MODEL: "MiniMax-M2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
category: "cn_official",
|
||||||
|
isPartner: true,
|
||||||
|
partnerPromotionKey: "minimax_en",
|
||||||
theme: {
|
theme: {
|
||||||
backgroundColor: "#f64551",
|
backgroundColor: "#f64551",
|
||||||
textColor: "#FFFFFF",
|
textColor: "#FFFFFF",
|
||||||
@@ -289,9 +313,7 @@ export const providerPresets: ProviderPreset[] = [
|
|||||||
ANTHROPIC_DEFAULT_OPUS_MODEL: "Ling-1T",
|
ANTHROPIC_DEFAULT_OPUS_MODEL: "Ling-1T",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
category: "cn_official",
|
category: "cn_official"
|
||||||
icon: "alibaba",
|
|
||||||
iconColor: "#FF6A00",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AiHubMix",
|
name: "AiHubMix",
|
||||||
|
|||||||
@@ -263,7 +263,8 @@
|
|||||||
"partnerPromotion": {
|
"partnerPromotion": {
|
||||||
"zhipu": "Zhipu GLM is an official partner of CC Switch. Use this link to top up and get a 10% discount",
|
"zhipu": "Zhipu GLM is an official partner of CC Switch. Use this link to top up and get a 10% discount",
|
||||||
"packycode": "PackyCode is an official partner of CC Switch. Register using this link and enter \"cc-switch\" promo code during recharge to get 10% off",
|
"packycode": "PackyCode is an official partner of CC Switch. Register using this link and enter \"cc-switch\" promo code during recharge to get 10% off",
|
||||||
"minimax": "MiniMax Coding Plan Black Friday, Starter is now $2/mo (80% OFF!)"
|
"minimax_cn": "MiniMax Coding Plan Special Offer, Starter from ¥9.9",
|
||||||
|
"minimax_en": "MiniMax Coding Plan Black Friday, Starter is now $2/mo (80% OFF!)"
|
||||||
},
|
},
|
||||||
"parameterConfig": "Parameter Config - {{name}} *",
|
"parameterConfig": "Parameter Config - {{name}} *",
|
||||||
"mainModel": "Main Model (optional)",
|
"mainModel": "Main Model (optional)",
|
||||||
@@ -833,5 +834,11 @@
|
|||||||
"colorLabel": "Icon Color",
|
"colorLabel": "Icon Color",
|
||||||
"selectIcon": "Select Icon",
|
"selectIcon": "Select Icon",
|
||||||
"preview": "Preview"
|
"preview": "Preview"
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"success": "Configuration migrated successfully"
|
||||||
|
},
|
||||||
|
"agents": {
|
||||||
|
"title": "Agents"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,8 @@
|
|||||||
"partnerPromotion": {
|
"partnerPromotion": {
|
||||||
"zhipu": "Zhipu GLM は CC Switch の公式パートナーです。リンク経由でチャージすると 10% 割引",
|
"zhipu": "Zhipu GLM は CC Switch の公式パートナーです。リンク経由でチャージすると 10% 割引",
|
||||||
"packycode": "PackyCode は CC Switch の公式パートナーです。登録後チャージ時に \"cc-switch\" を入力すると 10% オフ",
|
"packycode": "PackyCode は CC Switch の公式パートナーです。登録後チャージ時に \"cc-switch\" を入力すると 10% オフ",
|
||||||
"minimax": "MiniMax Coding Plan Black Friday、Starter が月額 $2(80% OFF)"
|
"minimax_cn": "MiniMax Coding Plan 特別価格、Starter ¥9.9 から",
|
||||||
|
"minimax_en": "MiniMax Coding Plan Black Friday、Starter が月額 $2(80% OFF)"
|
||||||
},
|
},
|
||||||
"parameterConfig": "パラメーター設定 - {{name}} *",
|
"parameterConfig": "パラメーター設定 - {{name}} *",
|
||||||
"mainModel": "メインモデル(任意)",
|
"mainModel": "メインモデル(任意)",
|
||||||
@@ -833,5 +834,11 @@
|
|||||||
"colorLabel": "アイコンカラー",
|
"colorLabel": "アイコンカラー",
|
||||||
"selectIcon": "アイコンを選択",
|
"selectIcon": "アイコンを選択",
|
||||||
"preview": "プレビュー"
|
"preview": "プレビュー"
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"success": "設定の移行が完了しました"
|
||||||
|
},
|
||||||
|
"agents": {
|
||||||
|
"title": "エージェント"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,7 +263,8 @@
|
|||||||
"partnerPromotion": {
|
"partnerPromotion": {
|
||||||
"zhipu": "智谱 GLM 是 CC Switch 的官方合作伙伴,使用此链接充值可以获得9折优惠",
|
"zhipu": "智谱 GLM 是 CC Switch 的官方合作伙伴,使用此链接充值可以获得9折优惠",
|
||||||
"packycode": "PackyCode 是 CC Switch 的官方合作伙伴,使用此链接注册并在充值时填写 \"cc-switch\" 优惠码,可以享受9折优惠",
|
"packycode": "PackyCode 是 CC Switch 的官方合作伙伴,使用此链接注册并在充值时填写 \"cc-switch\" 优惠码,可以享受9折优惠",
|
||||||
"minimax": "MiniMax Coding Plan 黑五特惠,Starter 套餐现仅 $2/月(2折优惠!)"
|
"minimax_cn": "MiniMax Coding Plan 特惠,Starter 套餐 9.9 元起",
|
||||||
|
"minimax_en": "MiniMax Coding Plan 黑五特惠,Starter 套餐现仅 $2/月(2折优惠!)"
|
||||||
},
|
},
|
||||||
"parameterConfig": "参数配置 - {{name}} *",
|
"parameterConfig": "参数配置 - {{name}} *",
|
||||||
"mainModel": "主模型 (可选)",
|
"mainModel": "主模型 (可选)",
|
||||||
@@ -833,5 +834,11 @@
|
|||||||
"colorLabel": "图标颜色",
|
"colorLabel": "图标颜色",
|
||||||
"selectIcon": "选择图标",
|
"selectIcon": "选择图标",
|
||||||
"preview": "预览"
|
"preview": "预览"
|
||||||
|
},
|
||||||
|
"migration": {
|
||||||
|
"success": "配置迁移成功"
|
||||||
|
},
|
||||||
|
"agents": {
|
||||||
|
"title": "智能体"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export interface DeepLinkImportRequest {
|
|||||||
repo?: string;
|
repo?: string;
|
||||||
directory?: string;
|
directory?: string;
|
||||||
branch?: string;
|
branch?: string;
|
||||||
skillsPath?: string;
|
|
||||||
|
|
||||||
// Config file fields
|
// Config file fields
|
||||||
config?: string;
|
config?: string;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export interface Skill {
|
|||||||
repoOwner?: string;
|
repoOwner?: string;
|
||||||
repoName?: string;
|
repoName?: string;
|
||||||
repoBranch?: string;
|
repoBranch?: string;
|
||||||
skillsPath?: string; // 技能所在的子目录路径,如 "skills"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SkillRepo {
|
export interface SkillRepo {
|
||||||
@@ -18,7 +17,6 @@ export interface SkillRepo {
|
|||||||
name: string;
|
name: string;
|
||||||
branch: string;
|
branch: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
skillsPath?: string; // 可选:技能所在的子目录路径,如 "skills"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const skillsApi = {
|
export const skillsApi = {
|
||||||
|
|||||||
@@ -32,17 +32,17 @@ describe("ImportExportSection Component", () => {
|
|||||||
it("should disable import button and show placeholder when no file selected", () => {
|
it("should disable import button and show placeholder when no file selected", () => {
|
||||||
render(<ImportExportSection {...baseProps} />);
|
render(<ImportExportSection {...baseProps} />);
|
||||||
|
|
||||||
expect(screen.getByText("settings.noFileSelected")).toBeInTheDocument();
|
// When no file selected, button shows "selectConfigFile" and clicking it opens file dialog
|
||||||
expect(
|
expect(
|
||||||
screen.getByRole("button", { name: "settings.import" }),
|
screen.getByRole("button", { name: /settings\.selectConfigFile/ }),
|
||||||
).toBeDisabled();
|
).toBeInTheDocument();
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
screen.getByRole("button", { name: "settings.exportConfig" }),
|
screen.getByRole("button", { name: "settings.exportConfig" }),
|
||||||
);
|
);
|
||||||
expect(baseProps.onExport).toHaveBeenCalledTimes(1);
|
expect(baseProps.onExport).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
screen.getByRole("button", { name: "settings.selectConfigFile" }),
|
screen.getByRole("button", { name: /settings\.selectConfigFile/ }),
|
||||||
);
|
);
|
||||||
expect(baseProps.onSelectFile).toHaveBeenCalledTimes(1);
|
expect(baseProps.onSelectFile).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
@@ -55,15 +55,15 @@ describe("ImportExportSection Component", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.getByText("config.json")).toBeInTheDocument();
|
expect(screen.getByText(/config\.json/)).toBeInTheDocument();
|
||||||
const importButton = screen.getByRole("button", {
|
const importButton = screen.getByRole("button", {
|
||||||
name: "settings.import",
|
name: /settings\.import/,
|
||||||
});
|
});
|
||||||
expect(importButton).toBeEnabled();
|
expect(importButton).toBeEnabled();
|
||||||
fireEvent.click(importButton);
|
fireEvent.click(importButton);
|
||||||
expect(baseProps.onImport).toHaveBeenCalledTimes(1);
|
expect(baseProps.onImport).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "common.clear" }));
|
fireEvent.click(screen.getByRole("button", { name: "Clear selection" }));
|
||||||
expect(baseProps.onClear).toHaveBeenCalledTimes(1);
|
expect(baseProps.onClear).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,17 @@ vi.mock("@/components/ui/textarea", () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/JsonEditor", () => ({
|
||||||
|
default: ({ value, onChange, placeholder, ...rest }: any) => (
|
||||||
|
<textarea
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={(event) => onChange?.(event.target.value)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("@/components/ui/checkbox", () => ({
|
vi.mock("@/components/ui/checkbox", () => ({
|
||||||
Checkbox: ({ id, checked, onCheckedChange, ...rest }: any) => (
|
Checkbox: ({ id, checked, onCheckedChange, ...rest }: any) => (
|
||||||
<input
|
<input
|
||||||
@@ -256,7 +267,7 @@ describe("McpFormModal", () => {
|
|||||||
await waitFor(() => expect(toastErrorMock).toHaveBeenCalled());
|
await waitFor(() => expect(toastErrorMock).toHaveBeenCalled());
|
||||||
expect(upsertMock).not.toHaveBeenCalled();
|
expect(upsertMock).not.toHaveBeenCalled();
|
||||||
const [message] = toastErrorMock.mock.calls.at(-1) ?? [];
|
const [message] = toastErrorMock.mock.calls.at(-1) ?? [];
|
||||||
expect(message).toBe("mcp.error.jsonInvalid");
|
expect(message).toBe("mcp.error.commandRequired");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("支持向导生成配置并自动填充 ID", async () => {
|
it("支持向导生成配置并自动填充 ID", async () => {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ interface SettingsMock {
|
|||||||
resetDirectory: ReturnType<typeof vi.fn>;
|
resetDirectory: ReturnType<typeof vi.fn>;
|
||||||
resetAppConfigDir: ReturnType<typeof vi.fn>;
|
resetAppConfigDir: ReturnType<typeof vi.fn>;
|
||||||
saveSettings: ReturnType<typeof vi.fn>;
|
saveSettings: ReturnType<typeof vi.fn>;
|
||||||
|
autoSaveSettings: ReturnType<typeof vi.fn>;
|
||||||
resetSettings: ReturnType<typeof vi.fn>;
|
resetSettings: ReturnType<typeof vi.fn>;
|
||||||
acknowledgeRestart: ReturnType<typeof vi.fn>;
|
acknowledgeRestart: ReturnType<typeof vi.fn>;
|
||||||
}
|
}
|
||||||
@@ -66,6 +67,7 @@ const createSettingsMock = (overrides: Partial<SettingsMock> = {}) => {
|
|||||||
resetDirectory: vi.fn(),
|
resetDirectory: vi.fn(),
|
||||||
resetAppConfigDir: vi.fn(),
|
resetAppConfigDir: vi.fn(),
|
||||||
saveSettings: vi.fn().mockResolvedValue({ requiresRestart: false }),
|
saveSettings: vi.fn().mockResolvedValue({ requiresRestart: false }),
|
||||||
|
autoSaveSettings: vi.fn().mockResolvedValue({ requiresRestart: false }),
|
||||||
resetSettings: vi.fn(),
|
resetSettings: vi.fn(),
|
||||||
acknowledgeRestart: vi.fn(),
|
acknowledgeRestart: vi.fn(),
|
||||||
};
|
};
|
||||||
@@ -247,7 +249,8 @@ describe("SettingsPage Component", () => {
|
|||||||
render(<SettingsPage open={true} onOpenChange={vi.fn()} />);
|
render(<SettingsPage open={true} onOpenChange={vi.fn()} />);
|
||||||
|
|
||||||
expect(screen.queryByText("language:zh")).not.toBeInTheDocument();
|
expect(screen.queryByText("language:zh")).not.toBeInTheDocument();
|
||||||
expect(screen.getByText("settings.title")).toBeInTheDocument();
|
// 加载状态下显示 spinner 而不是表单内容
|
||||||
|
expect(document.querySelector(".animate-spin")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reset import/export status when dialog transitions to open", () => {
|
it("should reset import/export status when dialog transitions to open", () => {
|
||||||
@@ -264,6 +267,7 @@ describe("SettingsPage Component", () => {
|
|||||||
|
|
||||||
it("should render general and advanced tabs and trigger child callbacks", () => {
|
it("should render general and advanced tabs and trigger child callbacks", () => {
|
||||||
const onOpenChange = vi.fn();
|
const onOpenChange = vi.fn();
|
||||||
|
// 设置 selectedFile 后,按钮显示 settings.import(可执行导入)
|
||||||
importExportMock = createImportExportMock({
|
importExportMock = createImportExportMock({
|
||||||
selectedFile: "/tmp/config.json",
|
selectedFile: "/tmp/config.json",
|
||||||
});
|
});
|
||||||
@@ -284,21 +288,20 @@ describe("SettingsPage Component", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("settings.tabAdvanced"));
|
fireEvent.click(screen.getByText("settings.tabAdvanced"));
|
||||||
fireEvent.click(
|
|
||||||
screen.getByRole("button", { name: "settings.selectConfigFile" }),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(importExportMock.selectImportFile).toHaveBeenCalled();
|
// 有文件时,点击导入按钮执行 importConfig
|
||||||
|
fireEvent.click(
|
||||||
|
screen.getByRole("button", { name: /settings\.import/ }),
|
||||||
|
);
|
||||||
|
expect(importExportMock.importConfig).toHaveBeenCalled();
|
||||||
|
|
||||||
fireEvent.click(
|
fireEvent.click(
|
||||||
screen.getByRole("button", { name: "settings.exportConfig" }),
|
screen.getByRole("button", { name: "settings.exportConfig" }),
|
||||||
);
|
);
|
||||||
expect(importExportMock.exportConfig).toHaveBeenCalled();
|
expect(importExportMock.exportConfig).toHaveBeenCalled();
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "settings.import" }));
|
// 清除选择按钮
|
||||||
expect(importExportMock.importConfig).toHaveBeenCalled();
|
fireEvent.click(screen.getByRole("button", { name: "Clear selection" }));
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "common.clear" }));
|
|
||||||
expect(importExportMock.clearSelection).toHaveBeenCalled();
|
expect(importExportMock.clearSelection).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -330,7 +333,9 @@ describe("SettingsPage Component", () => {
|
|||||||
|
|
||||||
render(<SettingsPage open={true} onOpenChange={onOpenChange} />);
|
render(<SettingsPage open={true} onOpenChange={onOpenChange} />);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("common.save"));
|
// 保存按钮在 advanced tab 中
|
||||||
|
fireEvent.click(screen.getByText("settings.tabAdvanced"));
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: /common\.save/ }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(settingsMock.saveSettings).toHaveBeenCalledTimes(1);
|
expect(settingsMock.saveSettings).toHaveBeenCalledTimes(1);
|
||||||
@@ -341,20 +346,6 @@ describe("SettingsPage Component", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should reset settings and close dialog when clicking cancel", () => {
|
|
||||||
const onOpenChange = vi.fn();
|
|
||||||
|
|
||||||
render(<SettingsPage open={true} onOpenChange={onOpenChange} />);
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("common.cancel"));
|
|
||||||
|
|
||||||
expect(settingsMock.resetSettings).toHaveBeenCalledTimes(1);
|
|
||||||
expect(settingsMock.acknowledgeRestart).toHaveBeenCalledTimes(1);
|
|
||||||
expect(importExportMock.clearSelection).toHaveBeenCalledTimes(1);
|
|
||||||
expect(importExportMock.resetStatus).toHaveBeenCalledTimes(2);
|
|
||||||
expect(onOpenChange).toHaveBeenCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show restart prompt and allow immediate restart after save", async () => {
|
it("should show restart prompt and allow immediate restart after save", async () => {
|
||||||
settingsMock = createSettingsMock({
|
settingsMock = createSettingsMock({
|
||||||
requiresRestart: true,
|
requiresRestart: true,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const useSettingsQueryMock = vi.fn();
|
|||||||
const setAppConfigDirOverrideMock = vi.fn();
|
const setAppConfigDirOverrideMock = vi.fn();
|
||||||
const applyClaudePluginConfigMock = vi.fn();
|
const applyClaudePluginConfigMock = vi.fn();
|
||||||
const syncCurrentProvidersLiveMock = vi.fn();
|
const syncCurrentProvidersLiveMock = vi.fn();
|
||||||
|
const updateTrayMenuMock = vi.fn();
|
||||||
const toastErrorMock = vi.fn();
|
const toastErrorMock = vi.fn();
|
||||||
const toastSuccessMock = vi.fn();
|
const toastSuccessMock = vi.fn();
|
||||||
|
|
||||||
@@ -52,6 +53,9 @@ vi.mock("@/lib/api", () => ({
|
|||||||
syncCurrentProvidersLive: (...args: unknown[]) =>
|
syncCurrentProvidersLive: (...args: unknown[]) =>
|
||||||
syncCurrentProvidersLiveMock(...args),
|
syncCurrentProvidersLiveMock(...args),
|
||||||
},
|
},
|
||||||
|
providersApi: {
|
||||||
|
updateTrayMenu: (...args: unknown[]) => updateTrayMenuMock(...args),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const createSettingsFormMock = (overrides: Record<string, unknown> = {}) => ({
|
const createSettingsFormMock = (overrides: Record<string, unknown> = {}) => ({
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ vi.mock("@/components/ConfirmDialog", () => ({
|
|||||||
) : null,
|
) : null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/components/settings/SettingsDialog", () => ({
|
vi.mock("@/components/settings/SettingsPage", () => ({
|
||||||
SettingsDialog: ({ open, onOpenChange, onImportSuccess }: any) =>
|
SettingsPage: ({ open, onOpenChange, onImportSuccess }: any) =>
|
||||||
open ? (
|
open ? (
|
||||||
<div data-testid="settings-dialog">
|
<div data-testid="settings-dialog">
|
||||||
<button onClick={() => onImportSuccess?.()}>
|
<button onClick={() => onImportSuccess?.()}>
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ describe("SettingsPage integration", () => {
|
|||||||
await waitFor(() => expect(toastErrorMock).toHaveBeenCalled());
|
await waitFor(() => expect(toastErrorMock).toHaveBeenCalled());
|
||||||
const cancelMessage = toastErrorMock.mock.calls.at(-1)?.[0] as string;
|
const cancelMessage = toastErrorMock.mock.calls.at(-1)?.[0] as string;
|
||||||
expect(cancelMessage).toMatch(
|
expect(cancelMessage).toMatch(
|
||||||
/settings\.selectFileFailed|选择保存位置失败/,
|
/settings\.selectFileFailed|请选择.*保存路径/,
|
||||||
);
|
);
|
||||||
|
|
||||||
toastErrorMock.mockClear();
|
toastErrorMock.mockClear();
|
||||||
|
|||||||
Reference in New Issue
Block a user