The handleExtract function assumed codexConfig was JSON format, but it's
actually a pure TOML string from useCodexConfigState. JSON.parse() failed
silently, causing onConfigChange not to be called.
Changes:
- Handle both pure TOML string and legacy JSON format
- Add missing i18n key for zh.json
- 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
- Move ~/.local/bin to first position in version scan search paths
- Update one-click install commands to use official native installation
(curl script) instead of npm for Claude Code
* 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>
- Change handleExtract and handleCommonConfigSnippetChange to use delayed save pattern
- Add hasUnsavedCommonConfig state to track pending changes
- Add getPendingCommonConfigSnippet and markCommonConfigSaved helper functions
- Remove immediate backend API calls, save only on form submit
- Add extractSuccessNeedSave and saveCommonConfigFailed i18n keys
Frontend fallback value for visibleApps had gemini: false, which was
inconsistent with backend default (gemini: true). This caused Gemini
to be hidden by default on first install.
Fixes#817
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Update Moonshot preset models from kimi-k2-thinking to kimi-k2.5
- Update OpenCode Kimi preset name and model to k2.5
- Remove explicit model config from Kimi preset (use API defaults)
- Fix backfill pollution: extract custom config from live when common config enabled
- Fix proxy backup: merge common config before backup to preserve full config
- Unify null override semantics: null no longer overrides in both frontend and backend
- Add missing i18n keys for common config UI
- Hide format button in readonly JsonEditor
- Add mapGeminiWarningToI18n for user-friendly warning messages
- Establish proper flexbox height chain from App to SettingsPage
- Move save button to fixed footer outside scrollable area
- Align footer styling with FullScreenPanel for consistency
* 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>
- Combine doc comments from both branches
- Keep write_live_snapshot_with_merge for common config runtime merge
- Fix indentation to match main branch style
Address multiple audit findings:
1. config_merge.rs:701 - Gemini merge now initializes env from common
config when custom config has no env field, ensuring common API keys
and base URLs are applied correctly.
2. Remove duplicate logging - merge functions now only return warnings,
callers (live.rs, proxy.rs) handle logging in one place.
3. live.rs:169 - Unify error message style to plain English descriptions
instead of error codes embedded in messages.
Changes:
- Add fallback to initialize env from common_env when missing
- Remove log::warn! calls from merge functions
- Use descriptive error messages consistently
Address audit finding: EditProviderDialog.tsx:121 only parsed flat JSON
format, not the wrapped {"env": {...}} format.
Changes:
- Detect if common config is wrapped or flat format
- Extract env object from either format
- Properly compute difference for live settings extraction
This ensures backward compatibility with existing wrapped-format data.
Address audit finding: ProviderForm.tsx:944 was using envStringToObj()
(KEY=VALUE format) to parse Gemini common config, but it's stored as JSON.
Changes:
- Add parseGeminiCommonConfig() helper function
- Support both flat {"KEY": "VALUE"} and wrapped {"env": {...}} formats
- Properly extract difference between custom and common env
This prevents common config values from being incorrectly saved as
custom config when the formats don't match.
Address code audit finding #10:
Replace unsafe type assertion with proper type guard when extracting
custom env config. Convert all values to strings explicitly using
String() to prevent runtime type errors when env contains non-string
values (numbers, booleans, etc).
Before: envObj = customConfig as Record<string, string>
After: Iterate and convert each value with String(value)
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
Address code audit finding #4:
The frontend was incorrectly parsing Gemini common config as KEY=VALUE
format while the backend expects JSON format. Unify to use JSON parsing
{"KEY": "VALUE"} which matches the format stored by useGeminiCommonConfig.
Also add type guard when converting to string record to prevent type
assertion issues with non-string values.
Address code audit findings #1, #2, #6, #7, #11:
- Extract merge logic to merge_config_for_live() in config_merge.rs
- Add is_common_config_enabled() helper function
- Add MergeResult struct with config and optional warning
- Update live.rs and proxy.rs to use the shared function
- Add logging for JSON parse failures instead of silent fallback
- Convert Chinese error messages to error codes (CODEX_CONFIG_*)
- Support both flat and wrapped formats for Gemini common config
This eliminates code duplication between live.rs and proxy.rs,
making future maintenance easier and preventing behavioral drift.
Update EditProviderDialog to extract custom configuration from live
settings when common config is enabled, ensuring the edit form shows
only the provider-specific values.
When loading live settings for editing:
- Check if common config is enabled for the provider and app type
- Fetch the common config snippet from database
- Extract the difference (custom config) from live settings
- Handle all three formats: Claude (JSON), Codex (TOML), Gemini (env)
This prevents the merged common config values from appearing in the
edit form, which would cause them to be saved as custom config and
result in duplicate values after the next merge.
Update ProviderForm to extract only the custom (provider-specific)
configuration when saving, removing common config values that would
be merged at runtime.
Changes:
- Import configMerge utilities (extractDifference, extractTomlDifference)
- Pass finalConfig from hooks to config editor components
- On save, extract difference between current config and common snippet
- Apply extraction for all three app types (Claude JSON, Codex TOML, Gemini env)
This ensures the database stores only the provider's unique configuration,
while the common config is stored separately and merged at runtime when
writing to live files.
Add real-time merge preview functionality to all config editor components
(Claude, Codex, Gemini) that shows the final merged configuration when
common config is enabled.
UI changes:
- Add finalConfig/finalEnv prop to display merged result
- Add toggle button to show/hide merge preview (Eye/EyeOff icons)
- Auto-show preview when common config is enabled
- Display custom config editor with label when preview is visible
- Show read-only merged preview below custom config
- Add visual indicators: "只读" badge, green hint text
The preview helps users understand how their custom configuration
combines with the shared common config template before saving.
Refactor all three common config hooks (Claude, Codex, Gemini) to support
the new runtime merge architecture where:
- settingsConfig stores only custom (provider-specific) configuration
- commonConfigSnippet stores shared template configuration in database
- finalConfig is computed at runtime: merge(commonConfig, customConfig)
- Toggling common config only changes enabled state, not settingsConfig
Key changes across all hooks:
- Add finalConfig/finalEnv return value for merge preview
- Use configMerge utilities (computeFinalConfig, extractDifference)
- Remove direct config manipulation on toggle (no more inject/remove)
- Add proper TypeScript return type interfaces
- Simplify state management by removing unnecessary refs
- Improve code organization with clear section comments
This refactor enables:
- Clean separation between custom and common configuration
- Real-time merge preview in the UI
- Consistent behavior across Claude (JSON), Codex (TOML), and Gemini (env)
Add a readOnly prop to the JsonEditor component that:
- Enables CodeMirror's EditorState.readOnly extension
- Applies visual styling (reduced opacity, default cursor)
- Prevents user modifications to the editor content
This feature is needed for displaying merged config previews where
users should see the final result but not edit it directly.
Set common_config_enabled to false by default for providers imported
via deeplink URLs. This prevents unexpected configuration merging when
users import providers from external sources.
Deeplink imported providers should use their settings_config directly
without merging with the global common config snippet, as the imported
configuration is expected to be complete and self-contained.
Changes:
- Always return ProviderMeta with common_config_enabled = false
- Refactor build_provider_meta to handle usage_script conditionally
- Add documentation explaining the design decision
Extend the proxy service's restore_live_config function to support
common config runtime merge when restoring live configuration after
proxy takeover ends.
This ensures that when the proxy releases control and restores the
original provider configuration, the common config merge is applied
consistently with the main provider switching logic.
The merge follows the same pattern as live.rs:
- Check common_config_enabled flag per app type
- Merge common snippet with provider's custom config
- customConfig overrides commonConfig
Implement runtime merge of common config snippet with provider's custom
config when writing to live configuration files. This enables shared
configuration templates across providers while preserving provider-specific
overrides.
Key changes:
- Add write_live_snapshot_with_merge() function that performs runtime merge
- Support JSON merge for Claude, TOML merge for Codex, and env merge for Gemini
- Update sync_current_to_live() to use the new merge function
- Update provider switch and update operations to use merge function
- Merge rule: customConfig (provider-specific) overrides commonConfig (shared)
The merge is only performed when common_config_enabled is true for the
provider and the corresponding app type.
Add comprehensive config merge utilities for both Rust backend and TypeScript frontend
to support the new common config architecture where customConfig overrides commonConfig.
Backend (Rust):
- Add config_merge.rs module with JSON and TOML merge functions
- Support deep merge where source overrides target for nested objects
- Add extract_difference functions to identify custom-only keys
- Include compute_final_*_config functions for runtime merge calculation
- Register module in lib.rs
Frontend (TypeScript):
- Add configMerge.ts with isPlainObject, deepClone, deepEqual, deepMerge utilities
- Implement computeFinalConfig for merging common + custom configs
- Add extractDifference to identify keys unique to live config
- Add tomlConfigMerge.ts with TOML-specific merge/extract functions
- Use smol-toml for parsing and serialization
These utilities form the foundation for the refactored common config system
where providers store only custom config and merge with shared templates at runtime.
Follow-up to #644: Extract duplicate get_home_dir() implementations
into a single pub fn in config.rs, reducing code duplication across
codex_config.rs, gemini_config.rs, and settings.rs.
Also adds documentation comments to build.rs explaining the Windows
manifest workaround for test binaries.
Merge macOS Alacritty/Kitty/Ghostty launchers into a single generic
`launch_macos_open_app` function with a `use_e_flag` parameter.
Replace Windows cmd/PowerShell/wt launchers with a shared
`run_windows_start_command` helper. Reduces ~98 lines of duplicate code.
- Add directory conflict detection before installation
- Fix installed status check to match repo owner and name
- Add i18n translations for conflict error messages