Commit Graph

149 Commits

  • fix(proxy): remove hardcoded ?beta=true injection, passthrough client query params
    - handlers.rs: add `uri` extractor to capture client's full path+query
    - claude.rs: remove ?beta=true injection from build_url(), update tests
    - stream_check.rs: remove ?beta=true from health check URL
    
    The proxy no longer injects ?beta=true into API URLs. Instead, query
    parameters sent by the Claude Code client are transparently passed through.
    This fixes duplicate ?beta=true issues and aligns with upstream behavior
    where the client already sends this parameter.
  • fix(opencode): add missing omo-slim category checks across add/form/mutation paths
    Several code paths only checked for "omo" category but missed "omo-slim",
    causing OMO Slim providers to be treated as regular OpenCode providers
    (triggering invalid write_live_snapshot, requiring manual provider key,
    and showing wrong form fields).
  • refactor(provider): switch from full config overwrite to partial key-field merging (#1098)
    * refactor(provider): switch from full config overwrite to partial key-field merging
    
    Replace the provider switching mechanism for Claude/Codex/Gemini from
    full settings_config overwrite to partial key-field replacement, preserving
    user's non-provider settings (plugins, MCP, permissions, etc.) across switches.
    
    - Add write_live_partial() with per-app implementations for Claude (JSON env
      merge), Codex (auth replace + TOML partial merge), and Gemini (env merge)
    - Add backfill_key_fields() to extract only provider-specific fields when
      saving live config back to provider entries
    - Update switch_normal, sync_current_to_live, add, update to use partial merge
    - Remove common config snippet feature for Claude/Codex/Gemini (no longer
      needed with partial merging); preserve OMO common config
    - Delete 6 frontend files (3 components + 3 hooks), clean up 11 modified files
    - Remove backend extract_common_config_* methods, 3 Tauri commands,
      CommonConfigSnippets struct, and related migration code
    - Update integration tests to validate key-field-only backfill behavior
    
    * refactor(cleanup): remove dead code and redundant MCP sync after partial-merge refactor
    
    - Remove ConfigService legacy full-overwrite sync methods (~150 lines)
    - Remove redundant McpService::sync_all_enabled from switch_normal
    - Switch proxy fallback recovery from write_live_snapshot to write_live_partial
    - Remove dead ProviderService::write_gemini_live wrapper
    - Update tests to reflect partial-merge behavior (MCP preserved, not re-synced)
    
    * feat(claude): add Quick Toggles for common Claude Code preferences
    
    Add checkbox toggles for hideAttribution, alwaysThinking, and
    enableTeammates that write directly to the live settings file via
    RFC 7396 JSON Merge Patch. Mirror changes to the form editor using
    form.watch for reactive updates.
    
    * fix(provider): add missing key fields to partial-merge constants
    
    Add provider-specific fields verified against official docs to prevent
    key residue or loss during provider switching:
    
    - Claude: CLAUDE_CODE_SUBAGENT_MODEL (env), model (top-level)
    - Codex: review_model, plan_mode_reasoning_effort
    - Gemini: GOOGLE_API_KEY (official alternative to GEMINI_API_KEY)
    
    * fix(provider): expand partial-merge key fields for Bedrock, Vertex, Foundry and behavior settings
    
    Add missing env/top-level fields to CLAUDE_KEY_ENV_FIELDS and
    CLAUDE_KEY_TOP_LEVEL so that provider switching correctly replaces
    (and clears) credentials and flags for AWS Bedrock, Google Vertex AI,
    Microsoft Foundry, and provider behavior overrides like max output
    tokens and prompt caching.
    
    * feat(provider): add auth field selector for Claude providers (AUTH_TOKEN / API_KEY)
    
    Allow users to choose between ANTHROPIC_AUTH_TOKEN and ANTHROPIC_API_KEY
    when creating or editing custom Claude providers, persisted in meta.apiKeyField.
    
    * refactor(preset): remove AiHubMix hardcoded API_KEY in favor of generic auth selector
    
    AiHubMix was the only preset that hardcoded ANTHROPIC_API_KEY before the
    generic auth field selector was introduced. Now that users can freely
    choose between AUTH_TOKEN and API_KEY via the UI, remove the special-case
    and default AiHubMix to the standard ANTHROPIC_AUTH_TOKEN.
  • feat(backup): add pre-migration backup, periodic backup, backfill warning, and backup management UI
    Four improvements to the database backup mechanism:
    
    1. Auto backup before schema migration - creates a snapshot when
       upgrading from an older database version, providing a safety net
       beyond the existing SAVEPOINT rollback mechanism.
    
    2. Periodic startup backup - checks on app launch whether the latest
       backup is older than 24 hours and creates a new one if needed,
       ensuring all users have recent backups regardless of usage patterns.
    
    3. Backfill failure notification - switch now returns SwitchResult with
       warnings instead of silently ignoring backfill errors, so users are
       informed when their manual config changes may not have been saved.
    
    4. Backup management UI - new BackupListSection in Settings > Data
       Management showing all backup snapshots with restore capability,
       including a confirmation dialog and automatic safety backup before
       restore.
  • refactor(omo): deduplicate OMO/OMO Slim via OmoVariant parameterization
    Introduce OmoVariant struct with STANDARD/SLIM constants to eliminate
    ~250 lines of copy-pasted code across DAO, service, commands, and
    frontend layers. Adding a new OMO variant now requires only a single
    const declaration instead of duplicating ~400 lines.
  • feat(omo): add OMO Slim (oh-my-opencode-slim) support
    Implement full OMO Slim profile management to align with ai-toolbox:
    - Backend: Slim service methods, DAO, Tauri commands, plugin conflict handling
    - Frontend: types, API, query hooks, form integration with isSlim parameterization
    - Slim variant: 6 agents (no categories), separate config file and plugin name
    - Mutual exclusion: standard OMO and Slim cannot coexist as plugins
    - i18n: zh/en/ja translations for all Slim agent descriptions
  • feat(webdav): follow-up 补齐自动同步与大文件防护 (#1043)
    * feat(webdav): add robust auto sync with failure feedback
    
    (cherry picked from commit bb6760124a62a964b36902c004e173534910728f)
    
    * fix(webdav): enforce bounded download and extraction size
    
    (cherry picked from commit 7777d6ec2b9bba07c8bbba9b04fe3ea6b15e0e79)
    
    * fix(webdav): only show auto-sync callout for auto-source errors
    
    * refactor(webdav): remove services->commands auto-sync dependency
  • Fix/skill zip symlink resolution (#1040)
    * fix(skill): resolve symlinks in ZIP extraction for GitHub repos (#1001)
    
    - detect symlink entries via is_symlink() during ZIP extraction and collect target paths
    
    - add resolve_symlinks_in_dir() to copy symlink target content into link location
    
    - canonicalize base_dir to fix macOS /tmp → /private/tmp path comparison issue
    
    - add path traversal safety check to block symlinks pointing outside repo boundary
    
    - apply symlink resolution to both download_and_extract and extract_local_zip paths
    
    Closes https://github.com/farion1231/cc-switch/issues/1001
    
    * fix(skill): change search to match name and repo instead of description
    
    * feat(skill): support importing skills from ~/.agents/skills/ directory
    
    - Scan ~/.agents/skills/ in scan_unmanaged() for skill discovery
    - Parse ~/.agents/.skill-lock.json to extract repo owner/name metadata
    - Auto-add discovered repos to skill_repos management on import
    - Add path field to UnmanagedSkill to show discovered location in UI
    
    Closes #980
    
    * fix(skill): use metadata name or ZIP filename for root-level SKILL.md imports (#1000)
    
    When a ZIP contains SKILL.md at the root without a wrapper directory,
    the install name was derived from the temp directory name (e.g. .tmpDZKGpF).
    Now falls back to SKILL.md frontmatter name, then ZIP filename stem.
    
    * feat(skill): scan ~/.cc-switch/skills/ for unmanaged skill discovery and import
    
    * refactor(skill): unify scan/import logic with lock file skillPath and repo saving
    
    - Deduplicate scan_unmanaged and import_from_apps using shared source list
    - Replace hand-written AppType match with as_str() and AppType::all()
    - Extract read_skill_name_desc, build_repo_info_from_lock, save_repos_from_lock helpers
    - Add SkillApps::from_labels for building enable state from source labels
    - Parse skillPath from .skill-lock.json for correct readme URLs
    - Save skill repos to skill_repos table in both import and migration paths
    
    * fix(skill): resolve symlink and path traversal issues in ZIP skill import
    
    * fix(skill): separate source path validation and add canonicalization for symlink safety
  • fix(openclaw): address code review findings across P0-P3 issues
    - Add 25 missing i18n keys for OpenClawFormFields in all 3 locales (P0)
    - Replace key={index} with stable crypto.randomUUID() keys in EnvPanel,
      ToolsPanel, and OpenClawFormFields to prevent list state bugs (P1)
    - Exclude openclaw from ProxyToggle/FailoverToggle in App.tsx (P1)
    - Add merge_additive_config() for openclaw/opencode deep link imports (P1)
    - Normalize serde(flatten) field naming to `extra` + HashMap (P2)
    - Add directory existence check in remove_openclaw_provider_from_live (P2)
    - Remove dead code in import_default_config and openclaw API methods (P2)
    - Add duplicate key validation in EnvPanel before save (P2)
    - Add openclawConfigDir to Settings type (P2)
    - Add staleTime to OpenClaw query hooks (P3)
    - Fix type-unsafe delete via destructuring in mutations.ts (P3)
  • fix(openclaw): address code review findings for robustness
    - Fix EnvPanel visibleKeys using entry key name instead of array index
      to prevent visibility state corruption after deletion
    - Add NaN guard in AgentsDefaultsPanel numeric field parsing
    - Validate provider id and models before importing from live config
    - Upgrade import failure log level from debug to warn for OpenCode/OpenClaw
  • feat(openclaw): add Env/Tools/Agents config panels
    - Migrate OpenClaw commands from provider.rs to dedicated commands/openclaw.rs
    - Add backend types and read/write for env, tools, agents.defaults sections
    - Create EnvPanel (API key + custom vars KV editor)
    - Create ToolsPanel (profile selector + allow/deny lists)
    - Create AgentsDefaultsPanel (default model + runtime parameters)
    - Extend App.tsx menu bar with Env/Tools/Agents buttons
    - Remove Prompts button for OpenClaw (overlaps with Workspace AGENTS.md)
  • fix(openclaw): prevent creating default provider on first launch
    Add additive mode guard in import_default_config() to skip OpenCode
    and OpenClaw apps, which should use their dedicated import functions.
  • feat(services): add OpenClaw branches to backend services
    - Add OpenClaw branches to proxy service (not supported)
    - Add OpenClaw to MCP service (skip sync, MCP still in development)
    - Add OpenClaw skills directory path
    - Update ProxyTakeoverStatus with openclaw field
    - Add OpenClaw to stream check (not supported)
  • feat(provider): add OpenClaw provider service support
    - Add import_openclaw_providers_from_live() function
    - Add remove_openclaw_provider_from_live() function
    - Update write_live_snapshot() for OpenClaw
    - Add openclaw fields to VisibleApps and AppSettings
    - Add get_openclaw_override_dir() function
  • Webdav (#923)
    * feat: WebDAV backup/restore
    
    - Add WebDAV test/backup/restore commands and settings\n- Fix ja i18n missing keys; decode PROPFIND href as UTF-8\n- Stabilize Windows prompt auto-import tests via CC_SWITCH_TEST_HOME
    
    * chore: format and minor cleanups
    
    * fix: update build config
    
    * feat(webdav): unify sync UX and hardening fixes
    
    * fix(webdav): harden sync flow and stabilize sync UX/tests
    
    * fix(webdav): add resource limits to skills.zip extraction
    
    Prevent zip bomb / resource exhaustion by enforcing:
    - MAX_EXTRACT_ENTRIES (10,000 files)
    - MAX_EXTRACT_BYTES (512 MB cumulative)
    
    * refactor(webdav): drop deviceId and display deviceName only
    
    ---------
    
    Co-authored-by: small-lovely-cat <77799160+small-lovely-cat@users.noreply.github.com>
    Co-authored-by: saladday <1203511142@qq.com>
  • feat(omo): improve agent model selection UX and fix lowercase keys (#1004)
    * fix(omo): use lowercase keys for builtin agent definitions
    
    OMO config schema expects all agent keys to be lowercase.
    Updated OMO_BUILTIN_AGENTS keys (Sisyphus → sisyphus, Hephaestus →
    hephaestus, etc.) and aligned Rust test fixtures accordingly.
    
    * feat(omo): add i18n support and tooltips for agent/category descriptions
    
    * feat(omo): add preset model variants for thinking level support
    
    Add OPENCODE_PRESET_MODEL_VARIANTS constant with variant definitions
    for Google, OpenAI, and Anthropic models. The omoModelVariantsMap
    builder now falls back to presets when config-defined variants are
    absent, enabling the variant selector for supported models.
    
    * feat(omo): replace model select with searchable combobox and improve fallback handling
    
    * feat(omo): enrich preset model defaults and metadata fallback
    
    * fix(omo): preserve custom fields and align otherFields import/validation
    
    * fix: resolve omo clippy warnings and include app update
  • fix(skill): correct skill doc URL branch and path resolution (#977)
    Use the actual branch returned by download_repo instead of the
    configured branch, fixing 404s when repos default to master but
    the URL was hardcoded to main. Also switch URL format from /tree/
    to /blob/ and always point to the SKILL.md file.
    
    Closes farion1231/cc-switch#968
  • feat(omo): integrate Oh My OpenCode profile management (#972)
    * feat(omo): integrate Oh My OpenCode profile management into Provider system
    
    Adds full-stack OMO support: backend config read/write/import, OMO-specific
    provider CRUD with exclusive switching, frontend profile editor with
    agent/category/model configuration, global config management, and i18n support.
    
    * feat(omo): add model/variant dropdowns from enabled providers
    
    Replace model text inputs with Select dropdowns sourced from enabled
    OpenCode providers, add thinking-level variant selection, and prevent
    auto-enabling newly added OMO providers.
    
    * fix(omo): use standard provider action styles for OMO switch button
    
    * fix(omo): replace hardcoded isZh strings with proper i18n t() calls
  • feat(usage): enhance dashboard with auto-refresh control and robust formatting (#942)
    * style: format code and apply clippy lint fixes
    
    * feat(usage): enhance dashboard with auto-refresh control and robust formatting
    
    - Add configurable auto-refresh interval toggle (off/5s/10s/30s/60s) to usage dashboard
    - Extract shared format utilities (fmtUsd, fmtInt, parseFiniteNumber, getLocaleFromLanguage)
    - Refactor request log time filtering to rolling vs fixed mode with validation
    - Use stable serializable query keys instead of filter objects
    - Handle NaN/Infinity safely in number formatting across all usage components
    - Use RFC 3339 date format in backend trend data
  • fix(stream_check): respect auth_mode for Claude health checks (#824)
    Previously, check_claude_stream always added the x-api-key header,
    ignoring the provider's auth_mode setting. This caused health check
    failures for proxy services that only support Bearer authentication.
    
    Now the function respects the auth.strategy field:
    - AuthStrategy::Anthropic: Authorization Bearer + x-api-key
    - AuthStrategy::ClaudeAuth: Authorization Bearer only
    - AuthStrategy::Bearer: Authorization Bearer only
    
    This aligns with the behavior of ClaudeAdapter::add_auth_headers
    and fixes health checks for proxy providers with auth_mode="bearer_only".
    
    Changes:
    - Modified check_claude_stream to conditionally add x-api-key header
    - Added AuthStrategy import
    - Added test_auth_strategy_imports unit test
    
    Tests: All passing (7/7 for stream_check module)
    
    Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
  • refactor(claude): migrate api_format from settings_config to meta
    Move api_format storage from settings_config to ProviderMeta to prevent
    polluting ~/.claude/settings.json when switching providers.
    
    - Add api_format field to ProviderMeta (Rust + TypeScript)
    - Update ProviderForm to read/write apiFormat from meta
    - Maintain backward compatibility for legacy settings_config.api_format
      and openrouter_compat_mode fields (read-only fallback)
    - Strip api_format from settings_config before writing to live config
  • feat(skills): add install from ZIP file feature
    - Add open_zip_file_dialog command for selecting ZIP files
    - Add install_from_zip service method with recursive skill scanning
    - Add install_skills_from_zip Tauri command
    - Add frontend API methods and useInstallSkillsFromZip hook
    - Add "Install from ZIP" button in Skills management page
    - Support local skill ID format: local:{directory}
    - Add i18n translations for new feature and error messages
  • fix(codex): fix 404 errors and connection timeout with custom base_url (#760)
    * fix(proxy): fix Codex 404 errors with custom base_url prefixes
    
    - handlers.rs:268: Remove hardcoded /v1 prefix in Codex forwarding
    - codex.rs:140: Only add /v1 for origin-only base_urls, dedupe /v1/v1
    - stream_check.rs:364: Try /responses first, fallback to /v1/responses
    - provider.rs:427: Don't force /v1 for custom prefix base_urls
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(codex): always add /v1 for custom prefix base_urls
    
    Changed logic to always add /v1 prefix unless base_url already ends with /v1.
    This fixes 504 timeout errors with relay services that expect /v1 in the path.
    
    - Most relay services follow OpenAI standard format: /v1/responses
    - Users can opt-out by adding /v1 to their base_url configuration
    - Updated test case to reflect new behavior
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): allow system proxy on localhost with different ports
    
    - Only bypass system proxy if it points to CC Switch's own port (15721)
    - Allow other localhost proxies (e.g., Clash on 7890) to be used
    - Add INFO level logging for request URLs to aid debugging
    
    This fixes connection timeout issues when using local proxy tools.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(codex): don't add /v1 for custom prefix base_urls
    
    Reverted logic to not add /v1 for base_urls with custom prefixes.
    Many relay services use custom paths without /v1.
    
    - Pure origin (e.g., https://api.openai.com) → adds /v1
    - With /v1 (e.g., https://api.openai.com/v1) → no change
    - Custom prefix (e.g., https://example.com/openai) → no /v1
    
    This fixes 404 errors with relay services that don't use /v1 in their paths.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): use dynamic port for system proxy detection
    
    Instead of hardcoding port 15721, now uses the actual configured
    listen_port from proxy settings.
    
    - Added set_proxy_port() to update the port when proxy server starts
    - Added get_proxy_port() to retrieve current port for detection
    - Updated server.rs to call set_proxy_port() on startup
    - Updated tests to reflect new behavior
    
    This allows users to change the proxy port in settings without
    breaking the system proxy detection logic.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): change default proxy port from 15721 to 5000
    
    Update the default fallback port in get_proxy_port() from 15721 to 5000
    to match the project's standard default port configuration.
    
    Also updated test cases to use port 5000 consistently.
    
    Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
    
    * fix(proxy): revert default port back to 15721
    
    Revert the default fallback port in get_proxy_port() from 5000 back to 15721
    to align with the project's updated default port configuration.
    
    Also updated test cases to use port 15721 consistently.
    
    Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: ozbombor <ozbombor@users.noreply.github.com>
    Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
  • Feat/pricing config enhancement (#781)
    * feat(db): add pricing config fields to proxy_config table
    
    - Add default_cost_multiplier field per app type
    - Add pricing_model_source field (request/response)
    - Add request_model field to proxy_request_logs table
    - Implement schema migration v5
    
    * feat(api): add pricing config commands and provider meta fields
    
    - Add get/set commands for default cost multiplier
    - Add get/set commands for pricing model source
    - Extend ProviderMeta with cost_multiplier and pricing_model_source
    - Register new commands in Tauri invoke handler
    
    * fix(proxy): apply cost multiplier to total cost only
    
    - Move multiplier calculation from per-item to total cost
    - Add resolve_pricing_config for provider-level override
    - Include request_model and cost_multiplier in usage logs
    - Return new fields in get_request_logs API
    
    * feat(ui): add pricing config UI and usage log enhancements
    
    - Add pricing config section to provider advanced settings
    - Refactor PricingConfigPanel to compact table layout
    - Display all three apps (Claude/Codex/Gemini) in one view
    - Add multiplier column and request model display to logs
    - Add frontend API wrappers for pricing config
    
    * feat(i18n): add pricing config translations
    
    - Add zh/en/ja translations for pricing defaults config
    - Add translations for multiplier, requestModel, responseModel
    - Add provider pricing config translations
    
    * fix(pricing): align backfill cost calculation with real-time logic
    
    - Fix backfill to deduct cache_read_tokens from input (avoid double billing)
    - Apply multiplier only to total cost, not to each item
    - Add multiplier display in request detail panel with i18n support
    - Use AppError::localized for backend error messages
    - Fix init_proxy_config_rows to use per-app default values
    - Fix silent failure in set_default_cost_multiplier/set_pricing_model_source
    - Add clippy allow annotation for test mutex across await
    
    * style: format code with cargo fmt and prettier
    
    * fix(tests): correct error type assertions in proxy DAO tests
    
    The tests expected AppError::InvalidInput but the DAO functions use
    AppError::localized() which returns AppError::Localized variant.
    Updated assertions to match the correct error type with key validation.
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat(opencode): sync all providers to live config on directory change
    Add additive mode support for OpenCode in sync_current_to_live:
    - Add AppType::is_additive_mode() to distinguish switch vs additive mode
    - Add AppType::all() iterator to avoid hardcoding app lists
    - Add sync_all_providers_to_live() for additive mode apps
    - Refactor sync_current_to_live to handle both modes
    
    Frontend changes (directory settings):
    - Track opencodeDirChanged in useDirectorySettings
    - Trigger syncCurrentProvidersLiveSafe when OpenCode dir changes
    - Add i18n strings for OpenCode directory settings
  • fix(skills): prevent duplicate skill installation from different repos (#778)
    - Add directory conflict detection before installation
    - Fix installed status check to match repo owner and name
    - Add i18n translations for conflict error messages
  • feat(skills): add skill sync method setting (symlink/copy)
    - Add SyncMethod enum (Auto/Symlink/Copy) in Rust backend
    - Implement sync_to_app_dir with symlink support (cross-platform)
    - Add SkillSyncMethodSettings UI component (simplified 2-button selector)
    - Add i18n support for zh/en/ja
    - Replace copy_to_app with configurable sync_to_app_dir
    - Add skill_sync_method field to AppSettings
    
    User can now choose between symlink (disk space saving) or copy (best compatibility) in Settings > General.
  • fix(prompt): clear prompt file when all prompts are disabled
    When disabling a prompt, check if any other prompts remain enabled.
    If all prompts are disabled, clear the prompt file to ensure UI state
    matches the actual configuration that Claude Code reads.
  • feat(skills): add baoyu-skills preset repo and auto-supplement missing defaults
    - Add JimLiu/baoyu-skills to default skill repositories
    - Change init_default_skill_repos() from "first-run only" to "supplement missing"
    - New preset repos will now auto-appear for existing users on upgrade
  • fix(failover): switch to P1 immediately when enabling auto failover
    Previously, enabling auto failover kept using the current provider until
    the first failure, causing inconsistency when the current provider was
    not in the failover queue. When stopping proxy, the restored config
    would not match user expectations.
    
    New behavior:
    - Enable auto failover = immediately switch to queue P1
    - Subsequent routing follows queue order (P1→P2→...)
    - Auto-add current provider to queue if queue is empty
    
    Changes:
    - Add switch_proxy_target() for hot-switching during proxy mode
    - Update provider_router to use queue order when failover enabled
    - Sync tray menu Auto click with the same logic
    - Update UI tooltips to reflect new semantics
    - Add tests for queue-only routing scenario
  • Feat/provider individual config (#663)
    * refactor(ui): simplify UpdateBadge to minimal dot indicator
    
    * feat(provider): add individual test and proxy config for providers
    
    Add support for provider-specific model test and proxy configurations:
    
    - Add ProviderTestConfig and ProviderProxyConfig types in Rust and TypeScript
    - Create ProviderAdvancedConfig component with collapsible panels
    - Update stream_check service to merge provider config with global config
    - Proxy config UI follows global proxy style (single URL input)
    
    Provider-level configs stored in meta field, no database schema changes needed.
    
    * feat(ui): add failover toggle and improve proxy controls
    
    - Add FailoverToggle component with slide animation
    - Simplify ProxyToggle style to match FailoverToggle
    - Add usage statistics button when proxy is active
    - Fix i18n parameter passing for failover messages
    - Add missing failover translation keys (inQueue, addQueue, priority)
    - Replace AboutSection icon with app logo
    
    * fix(proxy): support system proxy fallback and provider-level proxy config
    
    - Remove no_proxy() calls in http_client.rs to allow system proxy fallback
    - Add get_for_provider() to build HTTP client with provider-specific proxy
    - Update forwarder.rs and stream_check.rs to use provider proxy config
    - Fix EditProviderDialog.tsx to include provider.meta in useMemo deps
    - Add useEffect in ProviderAdvancedConfig.tsx to sync expand state
    
    Fixes #636
    Fixes #583
    
    * fix(ui): sync toast theme with app setting
    
    * feat(settings): add log config management
    
    Fixes #612
    Fixes #514
    
    * fix(proxy): increase request body size limit to 200MB
    
    Fixes #666
    
    * docs(proxy): update timeout config descriptions and defaults
    
    Fixes #612
    
    * fix(proxy): filter x-goog-api-key header to prevent duplication
    
    * fix(proxy): prevent proxy recursion when system proxy points to localhost
    
    Detect if HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY environment variables
    point to loopback addresses (localhost, 127.0.0.1), and bypass system
    proxy in such cases to avoid infinite request loops.
    
    * fix(i18n): add providerAdvanced i18n keys and fix failover toast parameter
    
    - Add providerAdvanced.* i18n keys to en.json, zh.json, and ja.json
    - Fix failover toggleFailed toast to pass detail parameter
    - Remove Chinese fallback text from UI for English/Japanese users
    
    * fix(tray): restore tray-provider events and enable Auto failover properly
    
    - Emit provider-switched event on tray provider click (backward compatibility)
    - Auto button now: starts proxy, takes over live config, enables failover
    
    * fix(log): enable dynamic log level and single file mode
    
    - Initialize log at Trace level for dynamic adjustment
    - Change rotation strategy to KeepSome(1) for single file
    - Set max file size to 1GB
    - Delete old log file on startup for clean start
    
    * fix(tray): fix clippy uninlined format args warning
    
    Use inline format arguments: {app_type_str} instead of {}
    
    * fix(provider): allow typing :// in endpoint URL inputs
    
    Change input type from "url" to "text" to prevent browser
    URL validation from blocking :// input.
    
    Closes #681
    
    * fix(stream-check): use Gemini native streaming API format
    
    - Change endpoint from OpenAI-compatible to native streamGenerateContent
    - Add alt=sse parameter for SSE format response
    - Use x-goog-api-key header instead of Bearer token
    - Convert request body to Gemini contents/parts format
    
    * feat(proxy): add request logging for debugging
    
    Add debug logs for outgoing requests including URL and body content
    with byte size, matching the existing response logging format.
    
    * fix(log): prevent usize underflow in KeepSome rotation strategy
    
    KeepSome(n) internally computes n-2, so n=1 causes underflow.
    Use KeepSome(2) as the minimum safe value.
  • fix(opencode): fix add/remove provider flow and toast messages
    - Create separate removeFromLiveConfig API for additive mode apps
      (remove only removes from live config, not database)
    - Fix useSwitchProviderMutation to invalidate opencodeLiveProviderIds
      cache so button state updates correctly after add operation
    - Show appropriate toast messages:
      - Add: "已添加到配置" / "Added to config"
      - Remove: "已从配置移除" / "Removed from config"
    - Add i18n texts for addToConfigSuccess and removeFromConfigSuccess
  • fix(opencode): remove current provider concept for additive mode
    OpenCode uses additive mode where all providers coexist in config file,
    so there's no "current" provider concept. This commit:
    
    - Skip setting is_current in switch_normal for OpenCode
    - Return empty string from ProviderService::current for OpenCode
    - Disable active provider highlight in ProviderCard for OpenCode
  • fix(opencode): prevent config nesting and use slugified provider IDs
    - Skip backfill logic for OpenCode (additive mode doesn't need it)
    - Add defensive check in write_live_snapshot to extract provider fragment
    - Use slugified name as provider ID for readable config keys
  • fix(opencode): add OpenCode support to skills functionality
    Add missing OpenCode branch in parse_app_type() and include OpenCode
    in all app iteration loops for skills operations (uninstall, scan,
    import, migrate).
  • fix(opencode): address issues found during OpenCode integration review
    - Fix MCP server not removed from opencode.json when unchecked in edit modal
    - Fix Windows atomic write failure when opencode.json already exists
    - Fix i18n keys mismatch in OpenCodeFormFields (use opencode.* namespace)
    - Fix unit test missing apps.opencode field assertion
  • feat(opencode): Phase 6 - Tauri command extensions for OpenCode
    - Add import_opencode_providers_from_live command to provider.rs
    - Register new command in lib.rs invoke_handler
    - Update commands/mcp.rs: include OpenCode in sync_other_side logic
    - Add McpService::import_from_opencode to import_mcp_from_apps
    - Implement MCP sync/remove for OpenCode in services/mcp.rs
      - sync_server_to_app_no_config now calls sync_single_server_to_opencode
      - remove_server_from_app now calls remove_server_from_opencode
  • feat(opencode): complete Phase 5 - provider service layer
    Implement OpenCode-specific provider service logic with additive mode:
    - add(): Always write to live config (no is_current check needed)
    - update(): Always sync changes to live config
    - delete(): Remove from both DB and live config (no is_current check)
    
    New helper functions in live.rs:
    - write_live_snapshot(): Write provider to opencode.json provider section
    - remove_opencode_provider_from_live(): Remove provider from live config
    - import_opencode_providers_from_live(): Import existing providers from
      ~/.config/opencode/opencode.json into CC Switch database
    
    Key design: OpenCode uses additive mode where all providers coexist
    in the config file, unlike Claude/Codex/Gemini which use replacement
    mode with a single active provider.
  • feat(opencode): Phase 1 - Backend data structure expansion for OpenCode support
    Add OpenCode as the 4th supported application with additive provider management:
    
    - Add OpenCode variant to AppType enum with all related match statements
    - Add enabled_opencode field to McpApps and SkillApps structures
    - Add opencode field to McpRoot and PromptRoot
    - Add database schema migration v3→v4 with enabled_opencode columns
    - Add settings.rs support for opencode_config_dir and current_provider_opencode
    - Create opencode_config.rs module for config file I/O operations
    - Update all services (proxy, mcp, skill, provider, stream_check) for OpenCode
    - Add OpenCode support to deeplink provider and MCP parsing
    - Update commands/config.rs for OpenCode config status and paths
    
    Key design decisions:
    - OpenCode uses additive mode (no is_current needed, no proxy support)
    - Config path: ~/.config/opencode/opencode.json
    - MCP format: stdio→local, sse/http→remote conversion planned
    - Stream check returns error (not yet implemented for OpenCode)
  • feat(usage): improve custom template system with variable hints and validation fixes (#628)
    * feat(usage): improve custom template with variables display and explicit type detection
    
    Combine two feature improvements:
    1. Display supported variables ({{baseUrl}}, {{apiKey}}) with actual values in custom template mode
    2. Add explicit templateType field for accurate template mode detection
    
    ## Changes
    
    ### Frontend
    - Display template variables with actual values extracted from provider settings
    - Add templateType field to UsageScript for explicit mode detection
    - Support template mode persistence across sessions
    
    ### Backend
    - Add template_type field to UsageScript struct
    - Improve validation logic based on explicit template type
    - Maintain backward compatibility with type inference
    
    ### I18n
    - Add "Supported Variables" section translation (zh/en/ja)
    
    ### Benefits
    - More accurate template mode detection (no more guessing)
    - Better user experience with variable hints
    - Clearer validation rules per template type
    
    * fix(usage): resolve custom template cache and validation issues
    
    Combine three bug fixes to make custom template mode work correctly:
    
    1. **Update cache after test**: Testing usage script successfully now updates the main list cache immediately
    2. **Fix same-origin check**: Custom template mode can now access different domains (SSRF protection still active)
    3. **Fix field naming**: Unified to use autoQueryInterval consistently between frontend and backend
    
    ## Problems Solved
    
    - Main provider list showing "Query failed" after successful test
    - Custom templates blocked by overly strict same-origin validation
    - Auto-query intervals not saved correctly due to inconsistent naming
    
    ## Changes
    
    ### Frontend (UsageScriptModal)
    - Import useQueryClient and update cache after successful test
    - Invalidate usage cache when saving script configuration
    - Use standardized autoQueryInterval field name
    
    ### Backend (usage_script.rs)
    - Allow custom template mode to bypass same-origin checks
    - Maintain SSRF protection for all modes
    
    ### Hooks (useProviderActions)
    - Invalidate usage query cache when saving script
    
    ## Impact
    
    Users can now use custom templates freely while security validations remain intact for general templates.
    
    * fix(usage): correct provider credential field names
    
    - Claude: support both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN
    - Gemini: use GEMINI_API_KEY instead of GOOGLE_GEMINI_API_KEY
    - Codex: use OPENAI_API_KEY and parse base_url from TOML config string
    
    Addresses review feedback from PR #628
    
    * style: format code
    
    ---------
    
    Co-authored-by: Jason <farion1231@gmail.com>
  • feat(stream-check): enhance health check with configurable prompt and CLI-compatible requests (#623)
    - Add configurable test prompt field to StreamCheckConfig
    - Implement Claude CLI-compatible request format with proper headers:
      - Authorization + x-api-key dual auth
      - anthropic-beta, anthropic-version headers
      - x-stainless-* SDK headers with dynamic OS/arch detection
      - URL with ?beta=true parameter
    - Implement Codex CLI-compatible Responses API format:
      - /v1/responses endpoint
      - input array format with reasoning effort support
      - codex_cli_rs user-agent and originator headers
    - Add dynamic OS name and CPU architecture detection
    - Internationalize error messages (Chinese -> English)
    - Add test prompt Textarea UI component with i18n support
    - Remove obsolete testPromptDesc translation key
  • Feature/global proxy (#596)
    * refactor(proxy): simplify logging for better readability
    
    - Delete 17 verbose debug logs from handlers, streaming, and response_processor
    - Convert excessive INFO logs to DEBUG level for internal processing details
    - Add 2 critical INFO logs in forwarder.rs for failover scenarios:
      - Log when switching to next provider after failure
      - Log when all providers have been exhausted
    - Fix clippy uninlined_format_args warning
    
    This reduces log noise while maintaining visibility into key user-facing decisions.
    
    * fix: replace unsafe unwrap() calls with proper error handling
    
    - database/dao/mcp.rs: Use map_err for serde_json serialization
    - database/dao/providers.rs: Use map_err for settings_config and meta serialization
    - commands/misc.rs: Use expect() for compile-time regex pattern
    - services/prompt.rs: Use unwrap_or_default() for SystemTime
    - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks
    
    Reduces potential panic points from 26 to 1 (static regex init, safe).
    
    * refactor(proxy): simplify verbose logging output
    
    - Remove response JSON full output logging in response_processor
    - Remove per-request INFO logs in provider_router (failover status, provider selection)
    - Change model mapping log from INFO to DEBUG
    - Change usage logging failure from INFO to WARN
    - Remove redundant debug logs for circuit breaker operations
    
    Reduces log noise significantly while preserving important warnings and errors.
    
    * feat(proxy): add structured log codes for i18n support
    
    Add error code system to proxy module logs for multi-language support:
    
    - CB-001~006: Circuit breaker state transitions and triggers
    - SRV-001~004: Proxy server lifecycle events
    - FWD-001~002: Request forwarding and failover
    - FO-001~005: Failover switch operations
    - USG-001~002: Usage logging errors
    
    Log format: [CODE] Chinese message
    Frontend/log tools can map codes to any language.
    
    New file: src/proxy/log_codes.rs - centralized code definitions
    
    * chore: bump version to 3.9.1
    
    * style: format code with prettier and rustfmt
    
    * fix(ui): allow number inputs to be fully cleared before saving
    
    - Convert numeric state to string type for controlled inputs
    - Use isNaN() check instead of || fallback to allow 0 values
    - Apply fix to ProxyPanel, CircuitBreakerConfigPanel,
      AutoFailoverConfigPanel, and ModelTestConfigPanel
    
    * feat(pricing): support @ separator in model name matching
    
    - Refactor model name cleaning into chained method calls
    - Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low)
    - Add test case for @ separator matching
    
    * feat(proxy): add global proxy settings support
    
    Add ability to configure a global HTTP/HTTPS proxy for all outbound
    requests including provider API calls, speed tests, and stream checks.
    
    * fix(proxy): improve validation and error handling in proxy config panels
    
    - Add StopTimeout/StopFailed error types for proper stop() error reporting
    - Replace silent clamp with validation-and-block in config panels
    - Add listenAddress format validation in ProxyPanel
    - Use log_codes constants instead of hardcoded strings
    - Use once_cell::Lazy for regex precompilation
    
    * fix(proxy): harden error handling and input validation
    
    - Handle RwLock poisoning in settings.rs with unwrap_or_else
    - Add fallback for dirs::home_dir() in config modules
    - Normalize localhost to 127.0.0.1 in ProxyPanel
    - Format IPv6 addresses with brackets for valid URLs
    - Strict port validation with pure digit regex
    - Treat NaN as validation failure in config panels
    - Log warning on cost_multiplier parse failure
    - Align timeoutSeconds range to [0, 300] across all panels
    
    * feat(proxy): add local proxy auto-scan and fix hot-reload
    
    - Add scan_local_proxies command to detect common proxy ports
    - Fix SkillService not using updated proxy after hot-reload
    - Move global proxy settings to advanced tab
    - Add error handling for scan failures
    
    * fix(proxy): allow localhost input in proxy address field
    
    * fix(proxy): restore request timeout and fix proxy hot-reload issues
    
    - Add URL scheme validation in build_client (http/https/socks5/socks5h)
    - Restore per-request timeout for speedtest, stream_check, usage_script, forwarder
    - Fix set_global_proxy_url to validate before persisting to DB
    - Mask proxy credentials in all log outputs
    - Fix forwarder hot-reload by fetching client on each request
    
    * style: format code with prettier
    
    * fix(proxy): improve global proxy stability and error handling
    
    - Fix RwLock silent failures with explicit error propagation
    - Handle init() duplicate calls gracefully with warning log
    - Align fallback client config with build_client settings
    - Make scan_local_proxies async to avoid UI blocking
    - Add mixed mode support for Clash 7890 port (http+socks5)
    - Use multiple test targets for better proxy connectivity test
    - Clear invalid proxy config on init failure
    - Restore timeout constraints in usage_script
    - Fix mask_url output for URLs without port
    - Add structured error codes [GP-001 to GP-009]
    
    * feat(proxy): add username/password authentication support
    
    - Add separate username and password input fields
    - Implement password visibility toggle with eye icon
    - Add clear button to reset all proxy fields
    - Auto-extract auth info from saved URL and merge on save
    - Update i18n translations (zh/en/ja)
    
    * fix(proxy): fix double encoding issue in proxy auth and add debug logs
    
    - Remove encodeURIComponent in mergeAuth() since URL object's
      username/password setters already do percent-encoding automatically
    - Add GP-010 debug log for database read operations
    - Add GP-011 debug log to track incoming URL info (length, has_auth)
    - Fix username.trim() in fallback branch for consistent behavior
  • Feat/deeplink multi endpoints (#597)
    * feat(deeplink): support comma-separated multiple endpoints in URL
    
    Allow importing multiple API endpoints via single endpoint parameter.
    First URL becomes primary endpoint, rest are added as custom endpoints.
    
    * feat(deeplink): add usage query fields to deeplink generator
    
    Add form fields for usage query configuration in deeplink HTML generator:
    - usageEnabled, usageBaseUrl, usageApiKey
    - usageScript, usageAutoInterval
    - usageAccessToken, usageUserId
    
    * fix(deeplink): auto-infer homepage and improve multi-endpoint display
    
    - Auto-infer homepage from primary endpoint when not provided
    - Display multiple endpoints as list in import dialog (primary marked)
    - Update deeplink parser in deplink.html to show multi-endpoint info
    - Add test for homepage inference from endpoint
    - Minor log format fix in live.rs
    
    * fix(deeplink): use primary endpoint for usage script base_url
    
    - Fix usage_script.base_url getting comma-separated string when multiple endpoints
    - Add i18n support for primary endpoint label in DeepLinkImportDialog
  • Refactor/simplify proxy logs (#585)
    * refactor(proxy): simplify logging for better readability
    
    - Delete 17 verbose debug logs from handlers, streaming, and response_processor
    - Convert excessive INFO logs to DEBUG level for internal processing details
    - Add 2 critical INFO logs in forwarder.rs for failover scenarios:
      - Log when switching to next provider after failure
      - Log when all providers have been exhausted
    - Fix clippy uninlined_format_args warning
    
    This reduces log noise while maintaining visibility into key user-facing decisions.
    
    * fix: replace unsafe unwrap() calls with proper error handling
    
    - database/dao/mcp.rs: Use map_err for serde_json serialization
    - database/dao/providers.rs: Use map_err for settings_config and meta serialization
    - commands/misc.rs: Use expect() for compile-time regex pattern
    - services/prompt.rs: Use unwrap_or_default() for SystemTime
    - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks
    
    Reduces potential panic points from 26 to 1 (static regex init, safe).
    
    * refactor(proxy): simplify verbose logging output
    
    - Remove response JSON full output logging in response_processor
    - Remove per-request INFO logs in provider_router (failover status, provider selection)
    - Change model mapping log from INFO to DEBUG
    - Change usage logging failure from INFO to WARN
    - Remove redundant debug logs for circuit breaker operations
    
    Reduces log noise significantly while preserving important warnings and errors.
    
    * feat(proxy): add structured log codes for i18n support
    
    Add error code system to proxy module logs for multi-language support:
    
    - CB-001~006: Circuit breaker state transitions and triggers
    - SRV-001~004: Proxy server lifecycle events
    - FWD-001~002: Request forwarding and failover
    - FO-001~005: Failover switch operations
    - USG-001~002: Usage logging errors
    
    Log format: [CODE] Chinese message
    Frontend/log tools can map codes to any language.
    
    New file: src/proxy/log_codes.rs - centralized code definitions
    
    * chore: bump version to 3.9.1
    
    * style: format code with prettier and rustfmt
    
    * fix(ui): allow number inputs to be fully cleared before saving
    
    - Convert numeric state to string type for controlled inputs
    - Use isNaN() check instead of || fallback to allow 0 values
    - Apply fix to ProxyPanel, CircuitBreakerConfigPanel,
      AutoFailoverConfigPanel, and ModelTestConfigPanel
    
    * feat(pricing): support @ separator in model name matching
    
    - Refactor model name cleaning into chained method calls
    - Add @ to - replacement (e.g., gpt-5.2-codex@low → gpt-5.2-codex-low)
    - Add test case for @ separator matching
    
    * fix(proxy): improve validation and error handling in proxy config panels
    
    - Add StopTimeout/StopFailed error types for proper stop() error reporting
    - Replace silent clamp with validation-and-block in config panels
    - Add listenAddress format validation in ProxyPanel
    - Use log_codes constants instead of hardcoded strings
    - Use once_cell::Lazy for regex precompilation
    
    * fix(proxy): harden error handling and input validation
    
    - Handle RwLock poisoning in settings.rs with unwrap_or_else
    - Add fallback for dirs::home_dir() in config modules
    - Normalize localhost to 127.0.0.1 in ProxyPanel
    - Format IPv6 addresses with brackets for valid URLs
    - Strict port validation with pure digit regex
    - Treat NaN as validation failure in config panels
    - Log warning on cost_multiplier parse failure
    - Align timeoutSeconds range to [0, 300] across all panels
  • fix(live): sync skills to app directories on config path change
    When users change app config directories (claudeConfigDir, codexConfigDir,
    geminiConfigDir), MCP servers were being synced to the new paths but Skills
    were not. This adds Skill synchronization to sync_current_to_live() to ensure
    installed Skills are also copied to the new app directories.
  • Fix/Resolve panic issues in proxy-related code (#560)
    * fix(proxy): change default port from 5000 to 15721
    
    Port 5000 conflicts with AirPlay Receiver on macOS 12+.
    Also adds error handling for proxy toggle and i18n placeholder updates.
    
    * fix(proxy): replace unwrap/expect with graceful error handling
    
    - Handle HTTP client initialization failure with no_proxy fallback
    - Fix potential panic on Unicode slicing in API key preview
    - Add proper error handling for response body builder
    - Handle edge case where SystemTime is before UNIX_EPOCH
    
    * fix(proxy): handle UTF-8 char boundary when truncating request body log
    
    Rust strings are UTF-8 encoded, slicing at a fixed byte index may cut
    in the middle of a multi-byte character (e.g., Chinese, emoji), causing
    a panic. Use is_char_boundary() to find the nearest safe cut point.
    
    * fix(proxy): improve robustness and prevent panics
    
    - Add reqwest socks feature to support SOCKS proxy environments
    - Fix UTF-8 safety in masked_key/masked_access_token (use chars() instead of byte slicing)
    - Fix UTF-8 boundary check in usage_script HTTP response truncation
    - Add defensive checks for JSON operations in proxy service
    - Remove verbose debug logs that could trigger panic-prone code paths
  • Feat/proxy header improvements (#538)
    * fix(proxy): improve header handling for Claude API compatibility
    
    - Streamline header blacklist by removing overly aggressive filtering
      (browser-specific headers like sec-fetch-*, accept-language)
    - Ensure anthropic-beta header always includes 'claude-code-20250219'
      marker required by upstream services for request validation
    - Centralize anthropic-version header handling in forwarder to prevent
      duplicate headers across different auth strategies
    - Add ?beta=true query parameter to /v1/messages endpoint for
      compatibility with certain upstream services (e.g., DuckCoding)
    - Remove redundant anthropic-version from ClaudeAdapter auth headers
      as it's now managed exclusively by the forwarder
    
    This improves proxy reliability with various Claude API endpoints
    and third-party services that have specific header requirements.
    
    * style(services): use inline format arguments in format strings
    
    Apply Rust 1.58+ format string syntax across provider and skill
    services. This replaces format!("msg {}", var) with format!("msg {var}")
    for improved readability and consistency with modern Rust idioms.
    
    Changed files:
    - services/provider/mod.rs: 1 format string
    - services/skill.rs: 10 format strings (error messages, log statements)
    
    No functional changes, purely stylistic improvement.
    
    * fix(proxy): restrict Anthropic headers to Claude adapter only
    
    - Move anthropic-beta and anthropic-version header handling inside
      Claude-specific condition to avoid sending unnecessary headers
      to Codex and Gemini APIs
    - Update test cases to reflect ?beta=true query parameter behavior
    - Add edge case tests for non-messages endpoints and existing queries
  • fix(proxy): clean up model override env vars when switching providers in takeover mode
    When proxy takeover is enabled, switching providers no longer writes to
    the Live config. However, if model override fields (ANTHROPIC_MODEL,
    ANTHROPIC_REASONING_MODEL, etc.) remain in the Live config, Claude Code
    continues sending requests with the old model name, causing failures
    when the new provider doesn't support that model.
    
    This fix:
    - Removes model override env keys from Claude Live config during takeover
    - Adds cleanup when switching providers in takeover mode
    - Fixes has_mapping() to include reasoning_model in the check
    - Adds test coverage for reasoning-only model mapping scenarios
  • fix(codex): remove entire model_providers table from common config extraction
    Previously only removed base_url from model_providers.* tables. Now removes
    the entire model_providers section since all its fields (name, base_url,
    wire_api, requires_openai_auth) are provider-specific configuration.
    
    MCP servers configuration remains preserved as it's provider-agnostic.
  • refactor(common-config): extract snippet from editor content instead of active provider
    - Change extraction source from current active provider to editor's live content
    - Add i18n support for JSON parse error messages via invalid_json_format_error()
    - Simplify API by removing unused providerId parameter
    - Update button labels and error messages in zh/en/ja locales