# farion1231/cc-switch — codebase unpack

> Canonical page: https://codevetter.com/unpack/cc-switch

This is a deterministic Repo Unpack scan of farion1231/cc-switch — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 09c5d39d8c3cf44b1bdfd0c6432c46495e546578 (2026-09-22)
- Last upstream commit: fix(mcode): refresh provider state after removal (#7578)
- Generated by codevetter 1.12.0 on 2026-09-22
- 1070 files scanned · 17.9 MB

## Languages

- TypeScript: 485 files
- Rust: 245 files
- Markdown: 209 files
- YAML: 17 files
- JSON: 14 files
- JavaScript: 4 files
- HTML: 2 files
- TOML: 2 files

## Repository structure

- src/ — 433 files
- src-tauri/ — 256 files
- docs/ — 197 files
- tests/ — 133 files
- .github/ — 17 files
- flatpak/ — 4 files
- assets/ — 3 files
- scripts/ — 2 files

Probable source entrypoint: `src/` (inferred from the tree, not upstream docs)

## Package

- package.json: cc-switch v3.20.3 — 67 declared dependencies

## Structural health signals

Average score 5.3/10 across 96 files; 86 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 09c5d39d8c3c (2026-09-22) fix(mcode): refresh provider state after removal (#7578)
- 6f6087cdba2d (2026-09-22) fix(lib): reset skip_taskbar before window.show() across entry points (#6348)
- c8e76bbc5ee7 (2026-09-22) fix(proxy): omit missing tool descriptions in the Codex responses-to-chat converter (#7378)
- d6e05152104e (2026-09-22) fix(proxy): preserve max effort for GPT-5.6 and GPT-6 Astra (#7531)
- 701c079ba0c8 (2026-09-22) fix(misc): ignore shell startup output when probing tool versions in WSL (#7348)

## Overview

CC Switch 3.20.3 is a Tauri 2 desktop app (React/Vite renderer, Rust library crate cc_switch_lib) that stores AI-coding-tool provider configs in a local SQLite database and writes them into each tool's live config files. It targets people who switch among Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, Hermes, Pi, and MiniMax Code, plus MCP servers, Skills, prompts, and a local HTTP proxy with failover. Distinctive pieces are exclusive-vs-additive live writes, proxy takeover that rewrites CLI base URLs to 127.0.0.1 while keeping a restore backup, and ccswitch:// deep-link import. Device UI settings stay in ~/.cc-switch/settings.json and are intentionally not part of WebDAV/S3 database sync.

## System map

The process entry is src-tauri/src/main.rs, which only adjusts Linux WebKit environment variables and calls cc_switch_lib::run(). lib.rs owns the Tauri builder: single-instance, deep-link, dialog, updater, window-state, and a very large invoke_handler. Shared state is AppState { db, proxy_service, usage_cache, codex_oauth_manager }. Durable app data is rusqlite at ~/.cc-switch/cc-switch.db (SCHEMA_VERSION 19); device settings are a separate JSON file. The renderer is Vite rooted at src/, dev server port 3000, production output ../dist. CI splits frontend (pnpm) and backend (cargo on three OSes plus a Windows+WSL2 job). Tagged v* pushes build Windows, macOS, and Linux artifacts, including arm64, with a signed updater.
- package.json name cc-switch version 3.20.3 describes the app as an assistant for Claude Code, Codex, and Gemini CLI; scripts are dev (pnpm tauri dev), build (pnpm tauri build), dev:renderer (vite), build:renderer (vite build), typecheck (tsc --noEmit), format/format:check (Prettier on src), and test:unit (vitest run). There is no lint script. — [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json)
- The Rust package cc-switch 3.20.3 (edition 2021, rust-version 1.85, rust-toolchain channel 1.95) builds library cc_switch_lib as staticlib, cdylib, and rlib. Default features are empty; feature test-hooks exists only to expose test hooks. Release profile uses thin LTO, opt-level s, panic=unwind, and strip=symbols. — [src-tauri/Cargo.toml](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/Cargo.toml) [rust-toolchain.toml](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/rust-toolchain.toml)
- Tauri productName is CC Switch, identifier com.ccswitch.desktop, window label main starts hidden at 1000x650 with Overlay title bar. frontendDist is ../dist, devUrl is http://localhost:3000, beforeDevCommand is pnpm run dev:renderer. Deep-link scheme is ccswitch. Updater pubkey is embedded and endpoints are https://dl.ccswitch.io/latest.json and the GitHub latest.json asset. CSP script-src is 'self' plus one sha256; connect-src allows ipc, https, and http. — [src-tauri/tauri.conf.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tauri.conf.json) [vite.config.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/vite.config.ts)
- main() on Linux sets WEBKIT_DISABLE_DMABUF_RENDERER=1 and WEBKIT_DISABLE_COMPOSITING_MODE=1 when unset, and if CC_SWITCH_GDK_BACKEND is non-empty copies it onto GDK_BACKEND, then calls cc_switch_lib::run(). — [src-tauri/src/main.rs#L4-34](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/main.rs#L4-L34)
- run() installs a panic hook writing ~/.cc-switch/crash.log, registers tauri_plugin_single_instance (which can exit lightweight mode, parse ccswitch:// args, and focus the main window), deep-link, process, dialog, opener, store, window-state, and updater. CloseRequested hides to tray when minimize_to_tray_on_close is set, otherwise exits; db_version_too_new recovery always exits. — [src-tauri/src/lib.rs#L347-400](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L347-L400) [src-tauri/src/lib.rs#L418-527](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L418-L527)
- The invoke_handler beginning at lib.rs registers commands for providers, Claude Desktop, settings, MCP, prompts, Pi, profiles, skills, proxy start/stop/takeover, failover, usage, stream check, sessions, WebDAV, S3, deep link, env conflicts, and universal providers. Frontend wrappers in src/lib/api/index.ts re-export providersApi, proxyApi, mcpApi, skillsApi, usageApi, and others that call invoke(). — [src-tauri/src/lib.rs#L1388-1649](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1388-L1649) [src-tauri/src/commands/mod.rs](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/mod.rs) [src/lib/api/index.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/index.ts)
- AppState holds Arc<Database>, ProxyService (constructed with the same CodexOAuthManager), Arc<UsageCache>, and Arc<CodexOAuthManager> rooted at get_app_config_dir(). — [src-tauri/src/store.rs#L7-31](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/store.rs#L7-L31)
- Database::init opens ~/.cc-switch/cc-switch.db via get_app_config_dir(), enables foreign_keys, registers an update hook that notifies WebDAV and S3 auto-sync, creates tables, and migrates when user_version is between 1 and SCHEMA_VERSION (19). A newer on-disk user_version is detected by stored_user_version_exceeds_supported and treated as db_version_too_new. — [src-tauri/src/database/mod.rs#L54-183](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L54-L183) [src-tauri/src/config.rs#L258-291](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L258-L291)
- AppType has ten variants: Claude, ClaudeDesktop, Codex, Gemini, GrokBuild, OpenCode, OpenClaw, Hermes, Pi, Mcode. is_additive_mode is true for OpenCode, OpenClaw, Hermes, Pi, and Mcode. supports_local_proxy is true only for Claude, Codex, Gemini, and GrokBuild. — [src-tauri/src/app_config.rs#L393-448](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L393-L448) [src/config/appConfig.tsx#L19-72](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L19-L72)
- Live config path helpers: Claude ~/.claude/settings.json (legacy claude.json if present), Codex ~/.codex/config.toml and auth.json, Gemini ~/.gemini/settings.json plus .env, Grok ~/.grok/config.toml, OpenCode ~/.config/opencode/opencode.json, OpenClaw ~/.openclaw/openclaw.json, Hermes ~/.hermes/config.yaml, Pi ~/.pi/agent/models.json (override via settings or PI_CODING_AGENT_DIR), MiniMax ~/.minimax/config.yaml or MINIMAX_DATA_DIR/MAVIS_DATA_DIR. Directory overrides exist on AppSettings. — [src-tauri/src/config.rs#L243-256](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L243-L256) [src-tauri/src/codex_config.rs#L462-473](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/codex_config.rs#L462-L473) [src-tauri/src/codex_config.rs#L987-990](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/codex_config.rs#L987-L990) [src-tauri/src/gemini_config.rs#L340-345](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/gemini_config.rs#L340-L345) [src-tauri/src/grok_config.rs#L25-33](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/grok_config.rs#L25-L33) [src-tauri/src/opencode_config.rs#L48-59](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/opencode_config.rs#L48-L59) [src-tauri/src/openclaw_config.rs#L31-48](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/openclaw_config.rs#L31-L48) [src-tauri/src/hermes_config.rs#L98-103](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/hermes_config.rs#L98-L103) [src-tauri/src/pi_config/mod.rs#L34-77](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/pi_config/mod.rs#L34-L77) [src-tauri/src/mcode_config.rs#L12-60](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/mcode_config.rs#L12-L60)
- CI on pull_request and push to main path-filters frontend versus src-tauri. Frontend job runs pnpm install --frozen-lockfile, pnpm typecheck, pnpm format:check, pnpm test:unit on Node 20. Backend matrix is ubuntu-22.04, windows-latest, macos-latest and runs cargo fmt --check, cargo clippy -D warnings, and cargo test. A separate windows-2025 job runs tests against a WSL2 home. Release workflow triggers on tags v* and builds windows-2022, windows-11-arm, ubuntu-22.04, ubuntu-22.04-arm, and macos-14. — [.github/workflows/ci.yml#L1-153](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/ci.yml#L1-L153) [.github/workflows/release.yml#L1-52](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/release.yml#L1-L52)
- The default Tauri capability grants the main window core, opener, updater, log, a short list of window controls, process exit/restart, and dialog. It does not grant shell or fs plugins; filesystem and process work goes through Rust commands. — [src-tauri/capabilities/default.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/capabilities/default.json)

## Feature catalog

The shell in App.tsx is a single window with an app switcher and views for providers, settings, prompts, skills, MCP, agents, universal providers, sessions, workspace files, OpenClaw env/tools/agent defaults, and Hermes memory. Provider management is the primary surface: presets, add/edit/delete, drag sort, tray switching, and per-provider terminals. Four apps can be taken over by a local proxy with failover and circuit breaking. Cross-tool panels cover MCP, Skills, and prompts. Extra utilities include usage scripts, subscription and coding-plan quotas, session browsing, WebDAV and S3 sync, deep-link import, env-conflict checks, and Claude Desktop direct versus proxy routing.
- README positions the app as one manager for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok Build, OpenCode, OpenClaw, Hermes, and MiniMax Code, with provider presets, unified MCP and Skills, tray switching, and cloud sync via Dropbox, OneDrive, iCloud, or WebDAV. The user manual TOC matches providers, MCP, prompts, skills, sessions, workspace, proxy takeover, failover, and usage. — [README.md#L213-249](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/README.md#L213-L249) [docs/user-manual/en/README.md#L1-44](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/docs/user-manual/en/README.md#L1-L44)
- App view union is providers, settings, prompts, skills, skillsDiscovery, mcp, agents, universal, sessions, workspace, openclawEnv, openclawTools, openclawAgents, and hermesMemory. Last app and last view persist in localStorage keys cc-switch-last-app and cc-switch-last-view. Default app is claude. claude-desktop shares feature panels with claude via sharedFeatureApp. — [src/App.tsx#L116-174](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/App.tsx#L116-L174)
- providersApi exposes getAll, getCurrent, add, update, delete, removeFromLiveConfig, switch, importDefault, Claude Desktop import/status, official-provider seed ensure for Claude Desktop, Codex, and Grok Build, tray refresh, sort order, provider-switched events, and openTerminal. OpenCode, OpenClaw, and Hermes also have live-id and import-from-live helpers. — [src/lib/api/providers.ts#L49-199](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/providers.ts#L49-L199)
- Preset catalogs live in src/config as separate modules: claudeProviderPresets, claudeDesktopProviderPresets, codexProviderPresets, geminiProviderPresets, grokBuildProviderPresets, hermesProviderPresets, mcodeProviderPresets, openclawProviderPresets, opencodeProviderPresets, piProviderPresets, plus universalProviderPresets, mcpPresets, and codingPlanProviders. ProviderPreset fields include settingsConfig, apiKeyField, endpointCandidates, category, and partner flags. — [src/config/claudeProviderPresets.ts#L25-50](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/claudeProviderPresets.ts#L25-L50) [src/config/universalProviderPresets.ts#L1-37](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/universalProviderPresets.ts#L1-L37)
- Universal providers are one shared config stored under settings key universal_providers and synced to Claude, Codex, and Gemini. Commands include get_universal_providers, upsert_universal_provider, delete_universal_provider, and sync_universal_provider. — [src-tauri/src/database/dao/universal_providers.rs#L10-39](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/dao/universal_providers.rs#L10-L39) [src/config/universalProviderPresets.ts#L1-6](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/universalProviderPresets.ts#L1-L6) [src-tauri/src/lib.rs#L1644-1649](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1644-L1649)
- The local proxy default listen address is 127.0.0.1:15721 with per-app rows for claude, codex, gemini, and grokbuild. Routes include /v1/messages, /claude-desktop/v1/messages, OpenAI chat/completions and responses (with /codex and /v1/v1 prefixes), /grokbuild/v1/responses, /alpha/search, and image generations. Frontend PROXY_APP_IDS matches those four apps. — [src-tauri/src/database/schema.rs#L127-184](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L127-L184) [src-tauri/src/proxy/server.rs#L291-370](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/server.rs#L291-L370) [src/config/appConfig.tsx#L57-72](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L57-L72)
- Proxy commands split start (no live rewrite) from set_proxy_takeover_for_app, stop_proxy_server (refuses while any takeover flag is set, including opencode and openclaw fields), and stop_proxy_with_restore. Failover commands cover queue membership, auto-failover toggle, circuit-breaker config, reset, and stats. — [src-tauri/src/commands/proxy.rs#L20-72](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/proxy.rs#L20-L72) [src-tauri/src/lib.rs#L1574-1607](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1574-L1607)
- CircuitState is Closed, Open, or HalfOpen. CircuitBreakerConfig uses failure_threshold, success_threshold, timeout_seconds, error_rate_threshold, and min_requests. ProviderRouter::select_providers returns only the current provider when auto-failover is off, otherwise the failover queue. Codex official providers are excluded from failover so an inbound Authorization header is not retried against another account. — [src-tauri/src/proxy/circuit_breaker.rs#L13-49](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/circuit_breaker.rs#L13-L49) [src-tauri/src/proxy/provider_router.rs#L15-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/provider_router.rs#L15-L80)
- Unified MCP commands are get_mcp_servers, upsert_mcp_server, delete_mcp_server, toggle_mcp_app, and import_mcp_from_apps. validate_mcp_command only checks that the binary is on PATH and does not execute it. McpApps has flags for claude, codex, gemini, grokbuild, opencode, hermes, and mcode; OpenClaw, Pi, and Claude Desktop are hard-disabled. — [src-tauri/src/commands/mcp.rs#L38-200](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/mcp.rs#L38-L200) [src-tauri/src/app_config.rs#L8-56](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L8-L56) [src-tauri/src/database/schema.rs#L63-75](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L63-L75)
- Skills use a unified skills table (id, directory, repo owner/name/branch, per-app enabled flags including pi, content_hash). Commands cover install, uninstall, toggle, scan unmanaged, discover, check updates, zip install, and repo list. SkillApps enables the same apps as MCP plus Pi, and not OpenClaw or Claude Desktop. Prompts have get/upsert/delete/enable plus Pi prompt-file commands. — [src-tauri/src/database/schema.rs#L84-118](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L84-L118) [src-tauri/src/app_config.rs#L98-149](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L98-L149) [src-tauri/src/lib.rs#L1474-1557](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1474-L1557)
- Session manager commands are list_sessions, get_session_messages, delete_session(s), launch_session_terminal, get_tool_versions, run_tool_lifecycle_action, and probe_tool_installations. open_provider_terminal launches a terminal with env extracted from that provider's settings_config without requiring it to be current. — [src-tauri/src/commands/session_manager.rs#L5-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/session_manager.rs#L5-L80) [src-tauri/src/commands/misc.rs#L3798-3856](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/misc.rs#L3798-L3856) [src-tauri/src/lib.rs#L1633-1643](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1633-L1643)
- Deep links use resource types provider, prompt, mcp, and skill. DeepLinkImportRequest carries endpoint, apiKey, base64 config, configUrl, and usage-script fields. usageEnabled defaults to unset so a script payload is not itself permission to run it. Parser entry is parse_deeplink_url; import helpers are import_provider_from_deeplink, import_mcp_from_deeplink, import_prompt_from_deeplink, and import_skill_from_deeplink. — [src-tauri/src/deeplink/mod.rs#L1-139](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/deeplink/mod.rs#L1-L139)
- Usage and billing commands include queryProviderUsage, testUsageScript, get_subscription_quota, get_codex_oauth_quota, get_codex_oauth_models, get_xai_oauth_quota, get_xai_oauth_models, get_coding_plan_quota, and get_balance, plus usage summary, trends, request logs, and models.dev pricing sync. Claude plugin commands can apply or clear an onboarding skip and official-vs-third-party plugin config. — [src-tauri/src/lib.rs#L1439-1461](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1439-L1461) [src-tauri/src/lib.rs#L1608-1632](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1608-L1632) [src/hooks/useProviderActions.ts#L57-81](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/hooks/useProviderActions.ts#L57-L81)
- Cloud sync commands are webdav_test_connection, webdav_sync_upload/download, webdav_sync_save_settings, s3_test_connection, s3_sync_upload/download, and s3_sync_save_settings. WebDAV accepts only http/https. S3 uses AWS SigV4, virtual-hosted URLs for amazonaws.com, and path-style URLs for custom endpoints. Local DB backup commands are create/list/restore/rename/delete. — [src-tauri/src/services/webdav.rs#L31-57](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/webdav.rs#L31-L57) [src-tauri/src/services/s3.rs#L21-74](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/s3.rs#L21-L74) [src-tauri/src/lib.rs#L1516-1534](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1516-L1534)
- OpenCode categories omo and omo-slim switch by writing one Oh My OpenCode config and deleting the other. Claude Desktop switches through switch_normal and apply_provider rather than the generic live writer. Pi switch short-circuits to pi::enable. Grok import of an official live config ensures the grokbuild-official seed instead of erroring. — [src-tauri/src/services/provider/mod.rs#L5712-5831](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5712-L5831) [src-tauri/src/commands/provider.rs#L135-174](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L135-L174) [src-tauri/src/claude_desktop_config.rs#L128-128](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/claude_desktop_config.rs#L128-L128)

## Data flow

The renderer never writes CLI configs itself. It calls invoke(); command wrappers parse AppType and delegate to services, which lock the rusqlite connection, update SQLite, and atomically rewrite live files under the user home directory. Exclusive apps backfill the outgoing provider from the live file before the new provider is written. Additive apps keep every provider in one native document. When proxy takeover is active, live files point at the local listener and the real upstream config sits in a live backup. Request bodies hit Axum routes, ProviderRouter, format adapters, then reqwest/hyper upstream. Usage scripts are data: QuickJS builds a request descriptor, Rust performs HTTP, QuickJS extracts the result. SQLite mutations wake WebDAV and S3 auto-sync.
- UI switch path is useProviderActions.switchProvider to providersApi.switch, which invokes switch_provider. The command parses AppType, spawn_blocking's onto switch_provider_internal, which calls ProviderService::switch. — [src/hooks/useProviderActions.ts#L168-180](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/hooks/useProviderActions.ts#L168-L180) [src/lib/api/providers.ts#L90-92](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/providers.ts#L90-L92) [src-tauri/src/commands/provider.rs#L101-132](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L101-L132)
- Exclusive switch order in switch_normal: resolve effective current id, sync common-config snippet from live, strip that snippet, save the outgoing provider (backfill), preflight managed Codex OAuth, then write the target live files and update current pointers. Additive apps skip backfill because all providers already coexist. Codex managed OAuth captures a four-file snapshot and restores it if the live commit fails. — [src-tauri/src/services/provider/mod.rs#L5700-5929](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5700-L5929)
- If a live backup row exists or detect_takeover_in_live_config_for_app is true, switch takes proxy_service.lock_switch_for_app and calls hot_switch_provider_inner instead of writing upstream live config. Official providers that fail official_provider_supports_proxy_takeover are rejected with switch.official_blocked_by_proxy. MCP sync is skipped during takeover. — [src-tauri/src/services/provider/mod.rs#L5739-5797](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5739-L5797)
- read_live_settings dispatches per AppType: Codex reads live TOML/auth and merges the cc-switch model catalog projection; Claude reads settings JSON; Gemini joins .env and settings.json; OpenCode, OpenClaw, Hermes, and Grok read their native documents; Mcode returns custom_provider entries; Pi and Claude Desktop refuse generic live import. — [src-tauri/src/services/provider/live.rs#L1744-1860](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1744-L1860)
- sync_current_to_live walks AppType::all(), skips Pi and Mcode, syncs all providers for additive apps, and for exclusive apps writes only the current provider unless takeover is active (then it updates the restore backup). It then calls McpService::sync_all_enabled and SkillService::sync_to_app, aggregating failures. — [src-tauri/src/services/provider/live.rs#L1689-1741](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1689-L1741)
- atomic_write creates a sibling temp file (pid, timestamp, counter), flushes, then renames over the target. atomic_write_private forces Unix mode 0600 for credential files. MiniMax write_and_commit rolls the previous YAML bytes back if the database commit fails. — [src-tauri/src/config.rs#L382-449](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L382-L449) [src-tauri/src/mcode_config.rs#L29-56](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/mcode_config.rs#L29-L56)
- ProxyServer::start binds listen_address:listen_port, records the actual port for system-proxy detection, and accepts with a manual hyper HTTP/1.1 loop that peeks raw bytes so OriginalHeaderCases can preserve client header casing. ProviderRouter plus FailoverSwitchManager live on ProxyState and survive across requests. GeminiShadowStore and CodexChatHistoryStore hold in-memory protocol bridge state. — [src-tauri/src/proxy/server.rs#L32-120](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/server.rs#L32-L120) [src-tauri/src/proxy/server.rs#L138-180](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/server.rs#L138-L180) [src-tauri/src/proxy/failover_switch.rs#L15-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/failover_switch.rs#L15-L40)
- execute_usage_script substitutes template variables, optionally validates base_url, evals the script in rquickjs to extract a request object, performs the HTTP call in Rust, then calls the script extractor on the response. Runtime limits are 5 seconds, 16 MiB, and a 256 KiB stack. — [src-tauri/src/usage_script.rs#L8-67](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/usage_script.rs#L8-L67) [src-tauri/src/usage_script.rs#L69-120](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/usage_script.rs#L69-L120)
- Every SQLite insert, update, or delete fires register_db_change_hook, which calls webdav_auto_sync::notify_db_changed and s3_auto_sync::notify_db_changed with the table name. AppSettings in settings.json is documented as device-local and not part of that database sync. get_settings_for_frontend clears webdav password and s3 secret_access_key before they reach the UI. — [src-tauri/src/database/mod.rs#L84-94](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L84-L94) [src-tauri/src/settings.rs#L345-348](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L345-L348) [src-tauri/src/settings.rs#L771-793](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L771-L793)
- Startup also pulls models.dev pricing from the renderer via syncModelsDevPricingOnStartup after the React tree mounts, then invalidates usage and models-dev query keys. Failure is logged and does not block boot. Session usage sync runs once immediately (including cost backfill) and then every 60 seconds unless session_auto_sync_enabled is false. — [src/main.tsx#L135-152](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/main.tsx#L135-L152) [src-tauri/src/lib.rs#L1283-1332](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1283-L1332) [src-tauri/src/settings.rs#L381-386](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L381-L386)
- handle_deeplink_url accepts only ccswitch:// strings, parses them, emits deeplink-import or deeplink-error to the webview, and can focus the main window. The single-instance callback does the same for argv. URL logs go through url_for_log, which strips userinfo, query, and fragment and then replaces known secret substrings of length at least 8. — [src-tauri/src/lib.rs#L102-212](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L102-L212) [src-tauri/src/lib.rs#L242-310](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L242-L310) [src-tauri/src/lib.rs#L369-376](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L369-L376)

## Behavior traces

Three flows dominate daily use: process startup (DB open, crash recovery, proxy restore, then the React shell), an exclusive provider switch (backfill then atomic live write, or a proxy hot-switch), and proxy takeover (backup live config, point the CLI at localhost, route through ProviderRouter). Deep-link import and usage-script execution are the two untrusted-input paths that still end in the same database and live-file writers.
- Startup, backend: main sets Linux WebKit vars; run() installs the panic hook; setup refreshes the app-config-dir Store override, starts the log plugin at ~/.cc-switch/logs/cc-switch.log (20 MiB, KeepSome 4), installs the rustls ring provider, opens or migrates cc-switch.db from legacy config.json, and if user_version is newer than the binary records kind db_version_too_new instead of applying DDL. — [src-tauri/src/main.rs#L4-34](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/main.rs#L4-L34) [src-tauri/src/lib.rs#L462-577](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L462-L577) [src-tauri/src/database/mod.rs#L169-183](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L169-L183)
- Startup, after the DB is up: a background task checks has_any_live_backup and detect_takeover_in_live_configs; if either is set it calls proxy_service.recover_from_crash. It then scrubs leaked Gemini common-config credentials, initialize_common_config_snippets, restore_proxy_state_on_startup, runs periodic_backup_if_needed, and arms a 24-hour backup timer plus the 60-second session sync loop. Silent startup hides the main window and, on Windows, sets skip_taskbar. — [src-tauri/src/lib.rs#L1221-1365](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1221-L1365)
- Startup, renderer: main.tsx installs global error handlers, listens for configLoadError, then bootstrap() invokes get_init_error. kind db_version_too_new renders DatabaseUpgrade and returns. Any other path/error shows a dialog and calls plugin-process exit(1). Otherwise it mounts QueryClientProvider, ThemeProvider, UpdateProvider, and App, then fires syncModelsDevPricingOnStartup. — [src/main.tsx#L28-155](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/main.tsx#L28-L155)
- Exclusive provider switch: UI calls providersApi.switch; switch_provider spawn_blocking calls ProviderService::switch; if not taken over, switch_normal backfills via read_live_settings and save_provider, writes the target with write_live helpers, syncs MCP when not in takeover, and returns SwitchResult.warnings (for example backfill_failed:<id>). The UI may then apply Claude plugin config when enableClaudePluginIntegration is on and refresh the tray. — [src/lib/api/providers.ts#L90-139](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/providers.ts#L90-L139) [src-tauri/src/services/provider/mod.rs#L5804-5878](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5804-L5878) [src/hooks/useProviderActions.ts#L57-81](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/hooks/useProviderActions.ts#L57-L81) [src/hooks/useProviderActions.ts#L148-166](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/hooks/useProviderActions.ts#L148-L166)
- Proxy takeover switch: set_proxy_takeover_for_app enables takeover for one app. Later switch_provider sees the backup or live placeholder, refuses unsupported official targets, and calls hot_switch_provider_inner so the proxy's current provider map changes while live files stay pointed at the local listener. Stopping the process with stop_proxy_server errors if any takeover flag remains; stop_proxy_with_restore is the path that puts the original live files back. — [src-tauri/src/commands/proxy.rs#L20-72](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/proxy.rs#L20-L72) [src-tauri/src/services/provider/mod.rs#L5739-5797](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5739-L5797)
- Normal live persistence for Codex is write_codex_live_atomic to ~/.codex/config.toml and auth.json, plus an optional model-catalog sidecar that is never inlined into those files. Claude writes settings JSON through the shared atomic writer. Gemini writes .env and patches security.auth.selectedType in ~/.gemini/settings.json without dropping other keys. Grok writes ~/.grok/config.toml. — [src-tauri/src/codex_config.rs#L471-473](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/codex_config.rs#L471-L473) [src-tauri/src/codex_config.rs#L987-994](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/codex_config.rs#L987-L994) [src-tauri/src/services/provider/live.rs#L1747-1810](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1747-L1810) [src-tauri/src/gemini_config.rs#L347-368](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/gemini_config.rs#L347-L368) [src-tauri/src/grok_config.rs#L25-33](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/grok_config.rs#L25-L33)
- Deep-link flow: OS or second-instance argv hits handle_deeplink_url; parse_deeplink_url fills DeepLinkImportRequest; the webview receives deeplink-import; the user confirms in DeepLinkImportDialog; import commands (import_from_deeplink, import_from_deeplink_unified, merge_deeplink_config) write the provider, MCP, prompt, or skill through the same services as the UI. usageEnabled must be explicitly true before a carried usage script is treated as enabled. — [src-tauri/src/lib.rs#L242-275](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L242-L275) [src-tauri/src/deeplink/mod.rs#L30-139](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/deeplink/mod.rs#L30-L139) [src/App.tsx#L92-93](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/App.tsx#L92-L93) [src-tauri/src/lib.rs#L1535-1539](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1535-L1539)
- Usage query flow: the provider card calls queryProviderUsage or testUsageScript; execute_usage_script builds a sandboxed QuickJS runtime, evals user code to a request config, Rust issues the HTTP request, then the extractor function maps the response JSON. A 5-second interrupt handler aborts runaway scripts. — [src-tauri/src/usage_script.rs#L8-65](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/usage_script.rs#L8-L65) [src-tauri/src/usage_script.rs#L79-120](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/usage_script.rs#L79-L120) [src-tauri/src/lib.rs#L1451-1454](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1451-L1454)
- Failover flow: an upstream failure is recorded on the per-provider CircuitBreaker inside ProviderRouter. When auto_failover_enabled is set, select_providers walks the failover queue and skips open circuits. FailoverSwitchManager dedupes concurrent promotions and emits UI plus tray updates. Codex official cards stay a single route even if failover was left on. — [src-tauri/src/proxy/provider_router.rs#L15-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/provider_router.rs#L15-L80) [src-tauri/src/proxy/circuit_breaker.rs#L13-49](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/circuit_breaker.rs#L13-L49) [src-tauri/src/proxy/failover_switch.rs#L15-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/failover_switch.rs#L15-L40)
- Tray switch: update_tray_menu rebuilds the menu from AppState via tray::create_tray_menu and set_menu on tray id TRAY_ID. Menu text follows the same locale order as the renderer (zh-TW, zh, ja, en, default zh). Provider-switched events from the backend are subscribed in providersApi.onSwitched. — [src-tauri/src/lib.rs#L312-331](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L312-L331) [src-tauri/src/tray.rs#L55-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/tray.rs#L55-L80) [src/lib/api/providers.ts#L133-139](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/providers.ts#L133-L139)

## Testing signals

Frontend tests are Vitest 2 with jsdom, Testing Library, and MSW, configured in vitest.config.ts and rooted in tests/. Backend tests are cargo test: large module-level Rust tests plus integration binaries in src-tauri/tests that drive the library through test-hooks. CI runs both on pull requests when the matching paths change, and always on pushes to main. There is no browser or Tauri-driver end-to-end job. Coverage exists as a Vitest reporter option but is not a CI gate.
- vitest.config.ts uses @vitejs/plugin-react, alias @ to ./src, environment jsdom, globals, setup files tests/setupGlobals.ts and tests/setupTests.ts, and coverage reporters text and lcov. package.json test:unit is vitest run; test:unit:watch is vitest watch. — [vitest.config.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/vitest.config.ts) [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json)
- tests/setupTests.ts starts an MSW server with onUnhandledRequest warn, initializes i18next with empty zh and en catalogs, and after each test runs Testing Library cleanup, resetProviderState, server.resetHandlers, and vi.clearAllMocks. Tauri is mocked from tests/msw/tauriMocks. — [tests/setupTests.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/tests/setupTests.ts)
- Frontend test dirs are tests/components, tests/config, tests/hooks, tests/integration (App.test.tsx and SettingsDialog.test.tsx), tests/lib, tests/msw, tests/types, and tests/utils. Config tests include codexProviderPresets, codingPlanProviders, grokBuildProviderPresets, jiekouProviderPresets, and ppioProviderPresets. — [tests/setupTests.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/tests/setupTests.ts) [src/config/codexProviderPresets.test.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/codexProviderPresets.test.ts)
- Rust dev-dependencies are serial_test and tempfile. Feature test-hooks is off by default. switch_provider_test_hook and import_default_config_test_hook are the integration entry points; they call the same internals as the Tauri commands. src-tauri/tests/provider_commands.rs imports them from cc_switch_lib and uses a process-wide test mutex plus CC_SWITCH_TEST_HOME. — [src-tauri/Cargo.toml#L18-20](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/Cargo.toml#L18-L20) [src-tauri/Cargo.toml#L119-121](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/Cargo.toml#L119-L121) [src-tauri/src/commands/provider.rs#L101-116](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L101-L116) [src-tauri/tests/provider_commands.rs#L1-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/provider_commands.rs#L1-L40)
- Integration test files under src-tauri/tests cover app config load, app type parse, deeplink import, Hermes roundtrip, import/export sync, mcode commands, MCP commands, profile roundtrip, prompt live sync, provider commands, provider service, proxy commands, and skill sync, with a shared support.rs. — [src-tauri/tests/provider_commands.rs](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/provider_commands.rs) [src-tauri/tests/deeplink_import.rs](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/deeplink_import.rs) [src-tauri/tests/proxy_commands.rs](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/proxy_commands.rs) [src-tauri/tests/skill_sync.rs](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/skill_sync.rs)
- Home-directory isolation for tests is CC_SWITCH_TEST_HOME inside get_home_dir(). Windows CI can also set CC_SWITCH_WSL_TEST_DIR. Comments in skill and session-usage tests warn that calling dirs::home_dir() directly bypasses the override and will touch the real user profile. — [src-tauri/src/config.rs#L18-33](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L18-L33) [src-tauri/src/services/skill.rs#L6550-6568](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/skill.rs#L6550-L6568)
- CI frontend job runs typecheck, Prettier, and Vitest, not cargo. Backend jobs run fmt, clippy -D warnings, and cargo test after creating an empty dist/ directory so the Tauri build script has a frontend placeholder. Pull requests skip a side when paths-filter says it did not change. — [.github/workflows/ci.yml#L34-153](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/ci.yml#L34-L153)
- Database::memory() builds an in-memory rusqlite connection with the same schema and change hook for unit tests. schema and DAO behavior also live in src-tauri/src/database/tests.rs. Proxy switch behavior has an in-module test switch_proxy_target_updates_live_backup_when_taken_over. — [src-tauri/src/database/mod.rs#L185-200](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L185-L200) [src-tauri/src/services/proxy.rs#L7571-7571](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/proxy.rs#L7571-L7571)
- CONTRIBUTING lists pnpm lint and locale files at src/locales/{en,zh,ja}/translation.json. package.json has no lint script, and the real catalogs are src/i18n/locales/{en,zh,zh-TW,ja}.json loaded by src/i18n/index.ts. The documented pre-submit command that matches the repo is pnpm typecheck && pnpm format:check && pnpm test:unit, then cargo fmt --check, clippy, and cargo test. — [CONTRIBUTING.md#L45-92](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/CONTRIBUTING.md#L45-L92) [CONTRIBUTING.md#L119-128](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/CONTRIBUTING.md#L119-L128) [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json) [src/i18n/index.ts#L1-11](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/i18n/index.ts#L1-L11)
- No workflow file in the CI or release paths starts a Tauri webview, Playwright, or browser suite. UI behavior of invoke-backed screens is covered only through MSW mocks of the Tauri API. Proxy protocol conversion and live-file takeover are covered by Rust tests, not by a running Axum server in CI. — [.github/workflows/ci.yml#L55-153](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/ci.yml#L55-L153) [tests/setupTests.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/tests/setupTests.ts) [vitest.config.ts](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/vitest.config.ts)

## Risk map

The trust boundary is documented in SECURITY.md: the bundled renderer is trusted, but deep links, proxy listeners, sync payloads, imports, and upstream responses are not. The highest-impact code is the local proxy (optionally non-loopback), QuickJS usage scripts, and shell launches for terminals and session resume. Live config writes sit outside the repo and can brick Claude, Codex, Gemini, or Grok sessions if a takeover backup is mishandled. A few docs and defaults disagree with the code (i18n paths, Hermes visibility, AppType error text).
- SECURITY.md states there is no project cloud backend. The local HTTP proxy listen address is user-configurable and may be non-loopback; inbound requests are in scope. Also in scope: ccswitch:// payloads, WebDAV/S3 restores, SQL and config imports, proxy upstream responses, and credential leakage into logs or shared snippets. DevTools-only IPC calls are explicitly out of scope. — [SECURITY.md#L14-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/SECURITY.md#L14-L80)
- CSP allows connect-src http: and https: plus ipc, and img-src http/https. script-src stays 'self' plus one hash. SECURITY.md says introducing eval, new Function, an iframe, or a wider script-src voids the renderer trust decision. launch_session_terminal documents that it forwards the renderer command string to a shell because of that trust decision, while cwd is shell-escaped. — [src-tauri/tauri.conf.json#L28-34](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tauri.conf.json#L28-L34) [SECURITY.md#L24-62](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/SECURITY.md#L24-L62) [src-tauri/src/commands/session_manager.rs#L27-70](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/session_manager.rs#L27-L70)
- execute_usage_script evals attacker-influenceable JavaScript (deep link or imported provider) inside rquickjs. Mitigations present in code are a 5-second interrupt, 16 MiB memory cap, 256 KiB stack, and base_url validation except for custom templates that embed their own URL. The HTTP request itself is performed by Rust after the script returns a request object. — [src-tauri/src/usage_script.rs#L8-67](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/usage_script.rs#L8-L67) [src-tauri/src/deeplink/mod.rs#L115-138](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/deeplink/mod.rs#L115-L138) [SECURITY.md#L70-79](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/SECURITY.md#L70-L79)
- open_provider_terminal extracts env vars, including API keys, from provider settings_config and passes them to launch_terminal_with_env. That function uses std::process::Command for bash, cmd, wsl.exe, osascript, and user-selected terminal binaries. MCP server commands are stored and later executed by the external CLI, not by validate_mcp_command, which only does a PATH lookup. — [src-tauri/src/commands/misc.rs#L3798-3856](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/misc.rs#L3798-L3856) [src-tauri/src/commands/mcp.rs#L38-42](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/mcp.rs#L38-L42) [SECURITY.md#L90-93](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/SECURITY.md#L90-L93)
- macOS subscription quota reads credentials by spawning the security CLI. Device names for sync can come from CC_SWITCH_DEVICE_NAME or a hostname command. These are local process launches with the user's privileges. — [src-tauri/src/services/subscription.rs#L133-133](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/subscription.rs#L133-L133) [src-tauri/src/services/sync_protocol.rs#L402-411](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/sync_protocol.rs#L402-L411)
- Provider API keys and live auth tokens are stored in SQLite providers.settings_config and in CLI files such as ~/.codex/auth.json. settings.json is written mode 0600 on Unix and secrets are stripped only in get_settings_for_frontend. Log redaction is exact-string based (minimum 8 characters) plus URL userinfo/query stripping, not a general secret scanner. — [src-tauri/src/settings.rs#L692-720](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L692-L720) [src-tauri/src/settings.rs#L771-779](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L771-L779) [src-tauri/src/lib.rs#L132-178](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L132-L178) [src-tauri/src/database/schema.rs#L26-43](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L26-L43)
- Switching while a takeover backup exists must not overwrite the stub live file with the real upstream URL. recover_from_crash on the next launch is what restores live configs after a crash. A bug in hot_switch_provider_inner or the backup row can leave Claude/Codex/Gemini/Grok pointed at a dead local port or lose the pre-takeover file. — [src-tauri/src/services/provider/mod.rs#L5739-5801](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5739-L5801) [src-tauri/src/lib.rs#L1221-1244](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1221-L1244) [src-tauri/src/services/provider/live.rs#L1704-1711](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1704-L1711)
- AppType::from_str accepts mcode but the localized error string lists only claude, claude-desktop, codex, gemini, grokbuild, opencode, openclaw, hermes, and pi. Backend VisibleApps::default sets hermes to false while src/config/appConfig.tsx DEFAULT_VISIBLE_APPS sets hermes to true, so a missing settings value can disagree across the IPC boundary. — [src-tauri/src/app_config.rs#L467-488](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L467-L488) [src-tauri/src/settings.rs#L47-68](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L47-L68) [src/config/appConfig.tsx#L32-43](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L32-L43)
- CONTRIBUTING.md tells contributors to edit src/locales/*/translation.json and to run pnpm lint. Neither the path nor the script exists. Locale edits that follow the doc will not ship; the loaded files are src/i18n/locales/en.json, zh.json, zh-TW.json, and ja.json. package.json's description still names only Claude, Codex, and Gemini. — [CONTRIBUTING.md#L66-128](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/CONTRIBUTING.md#L66-L128) [src/i18n/index.ts#L4-7](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/i18n/index.ts#L4-L7) [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json)
- The SQLite connection is a single Mutex<Connection>. Provider switches, MCP sync, and the 60-second session scanner all take that lock, sometimes inside spawn_blocking or block_on from async commands. Long writes (skill sync, usage rollup) stall every other command. — [src-tauri/src/database/mod.rs#L76-81](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L76-L81) [src-tauri/src/services/provider/mod.rs#L5743-5746](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5743-L5746) [src-tauri/src/lib.rs#L1283-1316](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1283-L1316)
- God-module concentration: ProviderService::switch is in services/provider/mod.rs past line 5700, open_provider_terminal is in commands/misc.rs past line 3800, and proxy takeover lives in services/proxy.rs (a test at line 7571). A drive-by edit in those files can change every app's live write. — [src-tauri/src/services/provider/mod.rs#L5712-5801](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5712-L5801) [src-tauri/src/commands/misc.rs#L3803-3826](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/misc.rs#L3803-L3826) [src-tauri/src/services/proxy.rs#L3416-3416](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/proxy.rs#L3416-L3416)
- Legacy compatibility still in the boot path: JSON config.json migration when no database exists, Windows HOME/.cc-switch fallback when the default db is missing, Claude settings.json versus claude.json, and deprecated get_mcp_config marked allow(deprecated). Official seed ids are special-cased so an import does not resurrect deleted official rows on ordinary startup, but a manual import does re-seed them. — [src-tauri/src/lib.rs#L534-567](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L534-L567) [src-tauri/src/config.rs#L258-288](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L258-L288) [src-tauri/src/config.rs#L243-255](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L243-L255) [src-tauri/src/commands/provider.rs#L135-174](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L135-L174) [src-tauri/src/commands/mcp.rs#L53-55](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/mcp.rs#L53-L55)

## Extension points

New coding tools plug in through AppType plus a live-config module and a frontend preset file. New proxy vendors plug in through ProviderAdapter and a route on the Axum router. New UI features plug in as a Tauri command in commands/, a line in invoke_handler, and a wrapper in src/lib/api. Deep links, MCP/Skill app flags, and the settings KV table are the other registries. Schema changes must bump SCHEMA_VERSION and add a migration.
- Adding a tool requires a new AppType variant, as_str, FromStr, all(), is_additive_mode or supports_local_proxy, and the matching frontend AppId in APP_IDS. MCP and Skills need a new bool on McpApps and SkillApps plus an enabled_* column. VisibleApps on both the Rust settings struct and the TypeScript default must be updated together. — [src-tauri/src/app_config.rs#L8-214](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L8-L214) [src-tauri/src/app_config.rs#L393-465](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L393-L465) [src/config/appConfig.tsx#L19-89](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L19-L89) [src-tauri/src/settings.rs#L24-88](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L24-L88)
- Live IO for each tool is a dedicated module: claude via config.rs, claude_desktop_config.rs, codex_config.rs, gemini_config.rs, grok_config.rs, opencode_config.rs, openclaw_config.rs, hermes_config.rs, mcode_config.rs, and pi_config/mod.rs. read_live_settings and the write path in services/provider/live.rs are the dispatch tables a new app must join. — [src-tauri/src/services/provider/live.rs#L1744-1860](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1744-L1860) [src-tauri/src/pi_config/mod.rs#L1-7](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/pi_config/mod.rs#L1-L7) [src-tauri/src/mcode_config.rs#L1-60](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/mcode_config.rs#L1-L60)
- ProviderAdapter in proxy/providers/adapter.rs is the proxy plugin trait: name, extract_base_url, extract_auth, build_url, get_auth_headers, and optional transform_request/transform_response. Concrete adapters live under proxy/providers (claude, codex, gemini, copilot, xAI OAuth, streaming transforms). auth_header_value rejects CR/LF in credentials instead of panicking. — [src-tauri/src/proxy/providers/adapter.rs#L16-69](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/providers/adapter.rs#L16-L69)
- New proxy endpoints are registered in ProxyServer::build_router. App-specific prefixes already used are /claude, /claude-desktop, /codex, and /grokbuild. A fifth proxied app would also need a proxy_config CHECK constraint change, a seed row, supports_local_proxy, and PROXY_APP_IDS. — [src-tauri/src/proxy/server.rs#L291-370](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/proxy/server.rs#L291-L370) [src-tauri/src/database/schema.rs#L127-184](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L127-L184) [src/config/appConfig.tsx#L57-68](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L57-L68)
- The command table is tauri::generate_handler! in lib.rs. Each command module is declared and star-exported from commands/mod.rs. Frontend call sites must use the Rust function name; several parameters are intentionally camelCase (addToLive, originalId, providerId) under allow(non_snake_case). — [src-tauri/src/lib.rs#L1388-1400](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1388-L1400) [src-tauri/src/commands/mod.rs#L1-74](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/mod.rs#L1-L74) [src-tauri/src/commands/provider.rs#L38-44](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L38-L44)
- Provider presets are plain TypeScript arrays in src/config/*ProviderPresets.ts, not a runtime plugin loader. Universal providers are a second registry: UniversalProviderPreset in universalProviderPresets.ts and the universal_providers JSON blob in the settings table, with sync_universal_provider pushing one record into Claude, Codex, and Gemini. — [src/config/claudeProviderPresets.ts#L25-50](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/claudeProviderPresets.ts#L25-L50) [src/config/universalProviderPresets.ts#L15-37](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/universalProviderPresets.ts#L15-L37) [src-tauri/src/database/dao/universal_providers.rs#L10-14](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/dao/universal_providers.rs#L10-L14)
- Deep link resources are a string discriminant on DeepLinkImportRequest (provider, prompt, mcp, skill) parsed in deeplink/parser.rs and imported by per-resource modules. New resource types need a parser arm, an import function, and a frontend confirm dialog branch. — [src-tauri/src/deeplink/mod.rs#L11-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/deeplink/mod.rs#L11-L40) [src-tauri/src/deeplink/mod.rs#L104-114](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/deeplink/mod.rs#L104-L114)
- Schema evolution is SCHEMA_VERSION in database/mod.rs, currently 19, with migrations in database/schema.rs. New databases get CREATE TABLE IF NOT EXISTS; existing databases run apply_schema_migrations only when the stored user_version is lower. A higher on-disk version aborts into the upgrade UI instead of writing DDL. — [src-tauri/src/database/mod.rs#L54-56](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L54-L56) [src-tauri/src/database/mod.rs#L126-183](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/mod.rs#L126-L183) [src-tauri/src/database/schema.rs#L16-24](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/database/schema.rs#L16-L24)
- i18n resources are four JSON files registered in src/i18n/index.ts. getInitialLanguage reads localStorage language, then navigator, with default zh. Tray locale mapping in tray.rs is documented to mirror that order. User-facing Rust errors use AppError::localized(key, zh, en). — [src/i18n/index.ts#L4-60](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/i18n/index.ts#L4-L60) [src-tauri/src/tray.rs#L68-80](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/tray.rs#L68-L80) [src-tauri/src/app_config.rs#L483-487](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L483-L487)
- OMO is an OpenCode category pair (omo and omo-slim) handled inside switch_normal by OmoService::write_config_to_file and delete_config_file, not by the generic provider writer. A similar exclusive sidecar should follow that branch rather than is_additive_mode. — [src-tauri/src/services/provider/mod.rs#L5723-5831](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5723-L5831)
- Per-app config directory overrides are fields on AppSettings (claude_config_dir, codex_config_dir, gemini_config_dir, grok_config_dir, opencode_config_dir, openclaw_config_dir, hermes_config_dir, pi_config_dir) resolved by the matching get_*_dir helpers. The whole ~/.cc-switch directory can be moved via the Tauri Store override read in app_store::refresh_app_config_dir_override before the database opens. — [src-tauri/src/settings.rs#L549-556](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L549-L556) [src-tauri/src/lib.rs#L465-467](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L465-L467) [src-tauri/src/config.rs#L258-263](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L258-L263) [src-tauri/src/openclaw_config.rs#L31-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/openclaw_config.rs#L31-L40)

## Contributor handoff

Read the AppType dispatch, the switch function, and the live-path module for the tool you are touching before editing. Keep frontend AppId lists and Rust enums in lockstep, and update all four locale files for UI strings. Run the narrow Vitest file plus cargo test for the module; home-dir tests must set CC_SWITCH_TEST_HOME. Do not rewrite live CLI files while a takeover backup exists, do not relax CSP, and do not log raw provider settings. CONTRIBUTING.md is partly stale.
- First files for any change: src-tauri/src/app_config.rs (AppType), src-tauri/src/services/provider/mod.rs ProviderService::switch, src-tauri/src/services/provider/live.rs, src/config/appConfig.tsx, src/lib/api/providers.ts, and src/App.tsx. Proxy work also starts at src-tauri/src/proxy/server.rs, provider_router.rs, and services/proxy.rs. — [src-tauri/src/app_config.rs#L393-448](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L393-L448) [src-tauri/src/services/provider/mod.rs#L5700-5801](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5700-L5801) [src/config/appConfig.tsx#L19-72](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/config/appConfig.tsx#L19-L72) [src/App.tsx#L116-174](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/App.tsx#L116-L174)
- Safe UI edits are components under src/components and hooks under src/hooks, as long as invoke names stay aligned with commands. Safe backend edits for one tool stay inside that tool's *_config.rs module plus its preset TS file. Cross-cutting switch, takeover, and schema changes are not safe to make without the recovery and backfill paths. — [src/hooks/useProviderActions.ts#L44-92](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/hooks/useProviderActions.ts#L44-L92) [src-tauri/src/commands/provider.rs#L118-132](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L118-L132) [src-tauri/src/services/provider/live.rs#L1696-1711](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1696-L1711)
- Command names registered with Tauri are the Rust function names (snake_case), despite CONTRIBUTING saying camelCase. Match existing argument renaming: the frontend passes app, id, provider, addToLive, originalId, providerId. New commands must be added both in commands/*.rs and in the generate_handler list. — [CONTRIBUTING.md#L66-71](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/CONTRIBUTING.md#L66-L71) [src-tauri/src/lib.rs#L1388-1396](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L1388-L1396) [src/lib/api/providers.ts#L49-92](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/lib/api/providers.ts#L49-L92) [src-tauri/src/commands/provider.rs#L38-44](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L38-L44)
- i18n edits belong in src/i18n/locales/en.json, zh.json, zh-TW.json, and ja.json. The renderer defaults to zh. Rust user-visible errors should use AppError::localized so both zh and en exist even when the key is missing on one side. — [src/i18n/index.ts#L4-11](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/i18n/index.ts#L4-L11) [CONTRIBUTING.md#L119-128](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/CONTRIBUTING.md#L119-L128) [src-tauri/src/commands/provider.rs#L135-141](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/provider.rs#L135-L141)
- Tests to run: pnpm test:unit for TS, filtered to the nearest tests/** file; cargo test --manifest-path src-tauri/Cargo.toml <name> for Rust. Integration tests that import switch_provider_test_hook need the library built with the test hooks that those tests already reference. Set CC_SWITCH_TEST_HOME and take the serial mutex before touching ~/.claude or ~/.codex. cargo fmt and clippy -D warnings match CI. — [src-tauri/tests/provider_commands.rs#L1-40](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/tests/provider_commands.rs#L1-L40) [src-tauri/src/config.rs#L18-33](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L18-L33) [.github/workflows/ci.yml#L145-153](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/ci.yml#L145-L153) [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json)
- Do not point tests or manual runs at the developer's real home. get_home_dir honors CC_SWITCH_TEST_HOME only; dirs::home_dir and a raw HOME on Windows are explicitly called out as ways to split or leak the database. get_app_config_dir on Windows will adopt a legacy HOME/.cc-switch database if the default file is absent. — [src-tauri/src/config.rs#L9-33](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L9-L33) [src-tauri/src/config.rs#L258-288](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L258-L288) [src-tauri/src/services/skill.rs#L6554-6568](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/skill.rs#L6554-L6568)
- Exclusive versus additive is the main behavioral trap. OpenCode, OpenClaw, Hermes, Pi, and Mcode must not go through backfill-and-replace. remove_provider_from_live_config exists for additive apps and refuses other app types. Pi is not in the generic live sync loop. — [src-tauri/src/app_config.rs#L430-440](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/app_config.rs#L430-L440) [src-tauri/src/services/provider/mod.rs#L5674-5714](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5674-L5714) [src-tauri/src/services/provider/live.rs#L1699-1703](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/live.rs#L1699-L1703)
- Proxy takeover owns the live file. Edits that call write_live while a backup row or placeholder is present will clobber the stub or the backup. Use lock_switch_for_app and the hot-switch path. Official Codex can pass through native auth; other official providers are blocked while takeover is on. — [src-tauri/src/services/provider/mod.rs#L5739-5797](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/services/provider/mod.rs#L5739-L5797) [src-tauri/src/commands/proxy.rs#L28-50](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/commands/proxy.rs#L28-L50)
- Logging and error strings must not include raw API keys. Use url_for_log / redact_known_secrets. Frontend settings reads must keep using get_settings_for_frontend, which clears WebDAV and S3 secrets. Credential files should use atomic_write_private (0600), not a plain fs::write. — [src-tauri/src/lib.rs#L116-178](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/lib.rs#L116-L178) [src-tauri/src/settings.rs#L771-779](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L771-L779) [src-tauri/src/config.rs#L382-390](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/config.rs#L382-L390)
- Before finishing a user-facing flow, check db_version_too_new still renders DatabaseUpgrade, configLoadError still exits, and Hermes visibility defaults if you touched VisibleApps. Prettier only formats src/**, not tests/, so a format:check pass does not prove tests are formatted. Clippy denies warnings on the whole crate. — [src/main.tsx#L88-111](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src/main.tsx#L88-L111) [src-tauri/src/settings.rs#L47-68](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/src-tauri/src/settings.rs#L47-L68) [package.json](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/package.json) [.github/workflows/ci.yml#L145-150](https://github.com/farion1231/cc-switch/blob/09c5d39d8c3cf44b1bdfd0c6432c46495e546578/.github/workflows/ci.yml#L145-L150)

Source repository: https://github.com/farion1231/cc-switch
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/cc-switch.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
