Compare commits

...

6 Commits

Author SHA1 Message Date
Jason 53dd0a90f3 chore: release v3.10.2 2026-01-24 22:15:21 +08:00
Jason 779fefd86d 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.
2026-01-24 18:24:05 +08:00
Jason 096c1d57c4 feat(partner): add RightCode as official partner
Update RightCode referral link to use CCSWITCH affiliate code and mark
as official partner with promotional messages in all supported languages.
2026-01-23 22:59:48 +08:00
Jason adb868d0cf 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.
2026-01-23 22:43:07 +08:00
Jason a6ad896db0 fix(opencode): preserve extra model fields during serialization
Add `extra` field with serde flatten to OpenCodeModel struct to capture
custom fields like cost, modalities, thinking, and variants that were
previously lost during save operations.
2026-01-23 20:42:50 +08:00
Jason d6cf4390ac fix(form): backfill model fields when editing Claude provider
Use lazy initialization in useState to parse model values from config
on first render, matching the pattern used in useApiKeyState. This
fixes an issue where model fields were not populated in edit mode.
2026-01-23 19:13:38 +08:00
22 changed files with 362 additions and 34 deletions
+19
View File
@@ -9,6 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
## [3.10.2] - 2026-01-24
### Patch Release
This maintenance release adds skill sync options and includes important bug fixes.
### Added
- **Skills**: Add skill sync method setting with symlink/copy options
- **Partners**: Add RightCode as official partner
### Fixed
- **Prompts**: Clear prompt file when all prompts are disabled
- **OpenCode**: Preserve extra model fields during serialization
- **Provider Form**: Backfill model fields when editing Claude provider
---
## [3.10.1] - 2026-01-23
### Patch Release
+2 -2
View File
@@ -2,7 +2,7 @@
# All-in-One Assistant for Claude Code, Codex & Gemini CLI
[![Version](https://img.shields.io/badge/version-3.10.0-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Version](https://img.shields.io/badge/version-3.10.2-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
[![Built with Tauri](https://img.shields.io/badge/built%20with-Tauri%202-orange.svg)](https://tauri.app/)
[![Downloads](https://img.shields.io/endpoint?url=https://api.pinstudios.net/api/badges/downloads/farion1231/cc-switch/total)](https://github.com/farion1231/cc-switch/releases/latest)
@@ -52,7 +52,7 @@ This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.GLM
## Features
### Current Version: v3.10.0 | [Full Changelog](CHANGELOG.md) | [Release Notes](docs/release-note-v3.9.0-en.md)
### Current Version: v3.10.2 | [Full Changelog](CHANGELOG.md) | [Release Notes](docs/release-note-v3.9.0-en.md)
**v3.8.0 Major Update (2025-11-28)**
+2 -2
View File
@@ -2,7 +2,7 @@
# Claude Code / Codex / Gemini CLI オールインワン・アシスタント
[![Version](https://img.shields.io/badge/version-3.10.0-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Version](https://img.shields.io/badge/version-3.10.2-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
[![Built with Tauri](https://img.shields.io/badge/built%20with-Tauri%202-orange.svg)](https://tauri.app/)
[![Downloads](https://img.shields.io/endpoint?url=https://api.pinstudios.net/api/badges/downloads/farion1231/cc-switch/total)](https://github.com/farion1231/cc-switch/releases/latest)
@@ -52,7 +52,7 @@
## 特長
### 現在のバージョン:v3.10.0 | [完全な更新履歴](CHANGELOG.md) | [リリースノート](docs/release-note-v3.9.0-ja.md)
### 現在のバージョン:v3.10.2 | [完全な更新履歴](CHANGELOG.md) | [リリースノート](docs/release-note-v3.9.0-ja.md)
**v3.8.0 メジャーアップデート (2025-11-28)**
+2 -2
View File
@@ -2,7 +2,7 @@
# Claude Code / Codex / Gemini CLI 全方位辅助工具
[![Version](https://img.shields.io/badge/version-3.10.0-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Version](https://img.shields.io/badge/version-3.10.2-blue.svg)](https://github.com/farion1231/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/farion1231/cc-switch/releases)
[![Built with Tauri](https://img.shields.io/badge/built%20with-Tauri%202-orange.svg)](https://tauri.app/)
[![Downloads](https://img.shields.io/endpoint?url=https://api.pinstudios.net/api/badges/downloads/farion1231/cc-switch/total)](https://github.com/farion1231/cc-switch/releases/latest)
@@ -52,7 +52,7 @@
## 功能特性
### 当前版本:v3.10.0 | [完整更新日志](CHANGELOG.md) | [发布说明](docs/release-note-v3.9.0-zh.md)
### 当前版本:v3.10.2 | [完整更新日志](CHANGELOG.md) | [发布说明](docs/release-note-v3.9.0-zh.md)
**v3.8.0 重大更新(2025-11-28**
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cc-switch",
"version": "3.10.1",
"version": "3.10.2",
"description": "All-in-One Assistant for Claude Code, Codex & Gemini CLI",
"type": "module",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "cc-switch"
version = "3.10.1"
version = "3.10.2"
description = "All-in-One Assistant for Claude Code, Codex & Gemini CLI"
authors = ["Jason Young"]
license = "MIT"
+5
View File
@@ -596,6 +596,11 @@ pub struct OpenCodeModel {
/// 模型额外选项(provider 路由等)
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<HashMap<String, Value>>,
/// 额外字段(cost、modalities、thinking、variants 等)
/// 使用 flatten 捕获所有未明确定义的字段
#[serde(flatten, default, skip_serializing_if = "HashMap::is_empty")]
pub extra: HashMap<String, Value>,
}
/// OpenCode 模型限制
+13 -1
View File
@@ -36,10 +36,22 @@ impl PromptService {
state.db.save_prompt(app.as_str(), &prompt)?;
// 如果是已启用的提示词,同步更新到对应的文件
if is_enabled {
// 启用提示词:写入内容到文件
let target_path = prompt_file_path(&app)?;
write_text_file(&target_path, &prompt.content)?;
} else {
// 禁用提示词:检查是否还有其他已启用的提示词
let prompts = state.db.get_prompts(app.as_str())?;
let any_enabled = prompts.values().any(|p| p.enabled);
if !any_enabled {
// 所有提示词都已禁用,清空文件
let target_path = prompt_file_path(&app)?;
if target_path.exists() {
write_text_file(&target_path, "")?;
}
}
}
Ok(())
+111 -13
View File
@@ -21,6 +21,19 @@ use crate::error::format_skill_error;
// ========== 数据结构 ==========
/// Skill 同步方式
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SyncMethod {
/// 自动选择:优先 symlink,失败时回退到 copy
#[default]
Auto,
/// 符号链接(推荐,节省磁盘空间)
Symlink,
/// 文件复制(兼容模式)
Copy,
}
/// 可发现的技能(来自仓库)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiscoverableSkill {
@@ -305,7 +318,7 @@ impl SkillService {
db.save_skill(&installed_skill)?;
// 同步到当前应用目录
Self::copy_to_app(&install_name, current_app)?;
Self::sync_to_app_dir(&install_name, current_app)?;
log::info!(
"Skill {} 安装成功,已启用 {:?}",
@@ -368,7 +381,7 @@ impl SkillService {
// 同步文件
if enabled {
Self::copy_to_app(&skill.directory, app)?;
Self::sync_to_app_dir(&skill.directory, app)?;
} else {
Self::remove_from_app(&skill.directory, app)?;
}
@@ -566,8 +579,41 @@ impl SkillService {
// ========== 文件同步方法 ==========
/// 复制 Skill 到应用目录
pub fn copy_to_app(directory: &str, app: &AppType) -> Result<()> {
/// 创建符号链接(跨平台)
///
/// - Unix: 使用 std::os::unix::fs::symlink
/// - Windows: 使用 std::os::windows::fs::symlink_dir
#[cfg(unix)]
fn create_symlink(src: &Path, dest: &Path) -> Result<()> {
std::os::unix::fs::symlink(src, dest)
.with_context(|| format!("创建符号链接失败: {} -> {}", src.display(), dest.display()))
}
#[cfg(windows)]
fn create_symlink(src: &Path, dest: &Path) -> Result<()> {
std::os::windows::fs::symlink_dir(src, dest)
.with_context(|| format!("创建符号链接失败: {} -> {}", src.display(), dest.display()))
}
/// 检查路径是否为符号链接
fn is_symlink(path: &Path) -> bool {
path.symlink_metadata()
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
}
/// 获取当前同步方式配置
fn get_sync_method() -> SyncMethod {
crate::settings::get_skill_sync_method()
}
/// 同步 Skill 到应用目录(使用 symlink 或 copy
///
/// 根据配置和平台选择最佳同步方式:
/// - Auto: 优先尝试 symlink,失败时回退到 copy
/// - Symlink: 仅使用 symlink
/// - Copy: 仅使用文件复制
pub fn sync_to_app_dir(directory: &str, app: &AppType) -> Result<()> {
let ssot_dir = Self::get_ssot_dir()?;
let source = ssot_dir.join(directory);
@@ -580,25 +626,77 @@ impl SkillService {
let dest = app_dir.join(directory);
// 如果已存在则先删除
if dest.exists() {
fs::remove_dir_all(&dest)?;
// 如果已存在则先删除(无论是 symlink 还是真实目录)
if dest.exists() || Self::is_symlink(&dest) {
Self::remove_path(&dest)?;
}
Self::copy_dir_recursive(&source, &dest)?;
let sync_method = Self::get_sync_method();
log::debug!("Skill {directory} 已复制到 {app:?}");
match sync_method {
SyncMethod::Auto => {
// 优先尝试 symlink
match Self::create_symlink(&source, &dest) {
Ok(()) => {
log::debug!("Skill {directory} 已通过 symlink 同步到 {app:?}");
return Ok(());
}
Err(err) => {
log::warn!(
"Symlink 创建失败,将回退到文件复制: {} -> {}. 错误: {err:#}",
source.display(),
dest.display()
);
}
}
// Fallback 到 copy
Self::copy_dir_recursive(&source, &dest)?;
log::debug!("Skill {directory} 已通过复制同步到 {app:?}");
}
SyncMethod::Symlink => {
Self::create_symlink(&source, &dest)?;
log::debug!("Skill {directory} 已通过 symlink 同步到 {app:?}");
}
SyncMethod::Copy => {
Self::copy_dir_recursive(&source, &dest)?;
log::debug!("Skill {directory} 已通过复制同步到 {app:?}");
}
}
Ok(())
}
/// 从应用目录删除 Skill
/// 复制 Skill 到应用目录(保留用于向后兼容)
#[deprecated(note = "请使用 sync_to_app_dir() 代替")]
pub fn copy_to_app(directory: &str, app: &AppType) -> Result<()> {
Self::sync_to_app_dir(directory, app)
}
/// 删除路径(支持 symlink 和真实目录)
fn remove_path(path: &Path) -> Result<()> {
if Self::is_symlink(path) {
// 符号链接:仅删除链接本身,不影响源文件
#[cfg(unix)]
fs::remove_file(path)?;
#[cfg(windows)]
fs::remove_dir(path)?; // Windows 的目录 symlink 需要用 remove_dir
} else if path.is_dir() {
// 真实目录:递归删除
fs::remove_dir_all(path)?;
} else if path.exists() {
// 普通文件
fs::remove_file(path)?;
}
Ok(())
}
/// 从应用目录删除 Skill(支持 symlink 和真实目录)
pub fn remove_from_app(directory: &str, app: &AppType) -> Result<()> {
let app_dir = Self::get_app_skills_dir(app)?;
let skill_path = app_dir.join(directory);
if skill_path.exists() {
fs::remove_dir_all(&skill_path)?;
if skill_path.exists() || Self::is_symlink(&skill_path) {
Self::remove_path(&skill_path)?;
log::debug!("Skill {directory} 已从 {app:?} 删除");
}
@@ -611,7 +709,7 @@ impl SkillService {
for skill in skills.values() {
if skill.apps.is_enabled_for(app) {
Self::copy_to_app(&skill.directory, app)?;
Self::sync_to_app_dir(&skill.directory, app)?;
}
}
+20
View File
@@ -5,6 +5,7 @@ use std::sync::{OnceLock, RwLock};
use crate::app_config::AppType;
use crate::error::AppError;
use crate::services::skill::SyncMethod;
/// 自定义端点配置(历史兼容,实际存储在 provider.meta.custom_endpoints
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -108,6 +109,11 @@ pub struct AppSettings {
/// 当前 OpenCode 供应商 ID(本地存储,对 OpenCode 可能无意义,但保持结构一致)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub current_provider_opencode: Option<String>,
// ===== Skill 同步设置 =====
/// Skill 同步方式:auto(默认,优先 symlink)、symlink、copy
#[serde(default)]
pub skill_sync_method: SyncMethod,
}
fn default_show_in_tray() -> bool {
@@ -136,6 +142,7 @@ impl Default for AppSettings {
current_provider_codex: None,
current_provider_gemini: None,
current_provider_opencode: None,
skill_sync_method: SyncMethod::default(),
}
}
}
@@ -382,3 +389,16 @@ pub fn get_effective_current_provider(
// Fallback 到数据库的 is_current
db.get_current_provider(app_type.as_str())
}
// ===== Skill 同步方式管理函数 =====
/// 获取 Skill 同步方式配置
pub fn get_skill_sync_method() -> SyncMethod {
settings_store()
.read()
.unwrap_or_else(|e| {
log::warn!("设置锁已毒化,使用恢复值: {e}");
e.into_inner()
})
.skill_sync_method
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "CC Switch",
"version": "3.10.1",
"version": "3.10.2",
"identifier": "com.ccswitch.desktop",
"build": {
"frontendDist": "../dist",
@@ -5,6 +5,42 @@ interface UseModelStateProps {
onConfigChange: (config: string) => void;
}
/**
* Parse model values from settings config JSON
*/
function parseModelsFromConfig(settingsConfig: string) {
try {
const cfg = settingsConfig ? JSON.parse(settingsConfig) : {};
const env = cfg?.env || {};
const model =
typeof env.ANTHROPIC_MODEL === "string" ? env.ANTHROPIC_MODEL : "";
const reasoning =
typeof env.ANTHROPIC_REASONING_MODEL === "string"
? env.ANTHROPIC_REASONING_MODEL
: "";
const small =
typeof env.ANTHROPIC_SMALL_FAST_MODEL === "string"
? env.ANTHROPIC_SMALL_FAST_MODEL
: "";
const haiku =
typeof env.ANTHROPIC_DEFAULT_HAIKU_MODEL === "string"
? env.ANTHROPIC_DEFAULT_HAIKU_MODEL
: small || model;
const sonnet =
typeof env.ANTHROPIC_DEFAULT_SONNET_MODEL === "string"
? env.ANTHROPIC_DEFAULT_SONNET_MODEL
: model || small;
const opus =
typeof env.ANTHROPIC_DEFAULT_OPUS_MODEL === "string"
? env.ANTHROPIC_DEFAULT_OPUS_MODEL
: model || small;
return { model, reasoning, haiku, sonnet, opus };
} catch {
return { model: "", reasoning: "", haiku: "", sonnet: "", opus: "" };
}
}
/**
* 管理模型选择状态
* 支持 ANTHROPIC_MODEL, ANTHROPIC_REASONING_MODEL 和各类型默认模型
@@ -13,11 +49,22 @@ export function useModelState({
settingsConfig,
onConfigChange,
}: UseModelStateProps) {
const [claudeModel, setClaudeModel] = useState("");
const [reasoningModel, setReasoningModel] = useState("");
const [defaultHaikuModel, setDefaultHaikuModel] = useState("");
const [defaultSonnetModel, setDefaultSonnetModel] = useState("");
const [defaultOpusModel, setDefaultOpusModel] = useState("");
// Initialize state by parsing config directly (fixes edit mode backfill)
const [claudeModel, setClaudeModel] = useState(
() => parseModelsFromConfig(settingsConfig).model,
);
const [reasoningModel, setReasoningModel] = useState(
() => parseModelsFromConfig(settingsConfig).reasoning,
);
const [defaultHaikuModel, setDefaultHaikuModel] = useState(
() => parseModelsFromConfig(settingsConfig).haiku,
);
const [defaultSonnetModel, setDefaultSonnetModel] = useState(
() => parseModelsFromConfig(settingsConfig).sonnet,
);
const [defaultOpusModel, setDefaultOpusModel] = useState(
() => parseModelsFromConfig(settingsConfig).opus,
);
const isUserEditingRef = useRef(false);
const lastConfigRef = useRef(settingsConfig);
+7
View File
@@ -35,6 +35,7 @@ import { LanguageSettings } from "@/components/settings/LanguageSettings";
import { ThemeSettings } from "@/components/settings/ThemeSettings";
import { WindowSettings } from "@/components/settings/WindowSettings";
import { AppVisibilitySettings } from "@/components/settings/AppVisibilitySettings";
import { SkillSyncMethodSettings } from "@/components/settings/SkillSyncMethodSettings";
import { DirectorySettings } from "@/components/settings/DirectorySettings";
import { ImportExportSection } from "@/components/settings/ImportExportSection";
import { AboutSection } from "@/components/settings/AboutSection";
@@ -249,6 +250,12 @@ export function SettingsPage({
settings={settings}
onChange={handleAutoSave}
/>
<SkillSyncMethodSettings
value={settings.skillSyncMethod ?? "auto"}
onChange={(method) =>
handleAutoSave({ skillSyncMethod: method })
}
/>
</motion.div>
) : null}
</TabsContent>
@@ -0,0 +1,78 @@
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { SkillSyncMethod } from "@/types";
export interface SkillSyncMethodSettingsProps {
value: SkillSyncMethod;
onChange: (value: SkillSyncMethod) => void;
}
export function SkillSyncMethodSettings({
value,
onChange,
}: SkillSyncMethodSettingsProps) {
const { t } = useTranslation();
// Handle default values: undefined or "auto" defaults to symlink display
const displayValue = value === "copy" ? "copy" : "symlink";
return (
<section className="space-y-2">
<header className="space-y-1">
<h3 className="text-sm font-medium">{t("settings.skillSync.title")}</h3>
<p className="text-xs text-muted-foreground">
{t("settings.skillSync.description")}
</p>
</header>
<div className="inline-flex gap-1 rounded-md border border-border-default bg-background p-1">
<SyncMethodButton
active={displayValue === "symlink"}
onClick={() => onChange("symlink")}
>
{t("settings.skillSync.symlink")}
</SyncMethodButton>
<SyncMethodButton
active={displayValue === "copy"}
onClick={() => onChange("copy")}
>
{t("settings.skillSync.copy")}
</SyncMethodButton>
</div>
{displayValue === "symlink" && (
<p className="text-xs text-muted-foreground">
{t("settings.skillSync.symlinkHint")}
</p>
)}
</section>
);
}
interface SyncMethodButtonProps {
active: boolean;
onClick: () => void;
children: React.ReactNode;
}
function SyncMethodButton({
active,
onClick,
children,
}: SyncMethodButtonProps) {
return (
<Button
type="button"
onClick={onClick}
size="sm"
variant={active ? "default" : "ghost"}
className={cn(
"min-w-[96px]",
active
? "shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-muted",
)}
>
{children}
</Button>
);
}
+3 -1
View File
@@ -452,7 +452,7 @@ export const providerPresets: ProviderPreset[] = [
{
name: "RightCode",
websiteUrl: "https://www.right.codes",
apiKeyUrl: "https://www.right.codes/register?aff=0bdf9bfa",
apiKeyUrl: "https://www.right.codes/register?aff=CCSWITCH",
settingsConfig: {
env: {
ANTHROPIC_BASE_URL: "https://www.right.codes/claude",
@@ -460,6 +460,8 @@ export const providerPresets: ProviderPreset[] = [
},
},
category: "third_party",
isPartner: true,
partnerPromotionKey: "rightcode",
icon: "rc",
iconColor: "#E96B2C",
},
+3 -1
View File
@@ -195,7 +195,7 @@ requires_openai_auth = true`,
{
name: "RightCode",
websiteUrl: "https://www.right.codes",
apiKeyUrl: "https://www.right.codes/register?aff=0bdf9bfa",
apiKeyUrl: "https://www.right.codes/register?aff=CCSWITCH",
auth: generateThirdPartyAuth(""),
config: generateThirdPartyConfig(
"rightcode",
@@ -203,6 +203,8 @@ requires_openai_auth = true`,
"gpt-5.2",
),
category: "third_party",
isPartner: true,
partnerPromotionKey: "rightcode",
icon: "rc",
iconColor: "#E96B2C",
},
+3 -1
View File
@@ -649,7 +649,7 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
{
name: "RightCode",
websiteUrl: "https://www.right.codes",
apiKeyUrl: "https://www.right.codes/register?aff=0bdf9bfa",
apiKeyUrl: "https://www.right.codes/register?aff=CCSWITCH",
settingsConfig: {
npm: "@ai-sdk/openai",
name: "RightCode",
@@ -663,6 +663,8 @@ export const opencodeProviderPresets: OpenCodeProviderPreset[] = [
},
},
category: "third_party",
isPartner: true,
partnerPromotionKey: "rightcode",
icon: "rc",
iconColor: "#E96B2C",
templateValues: {
+9 -1
View File
@@ -280,6 +280,13 @@
"geminiDesc": "Google Gemini CLI",
"opencodeDesc": "OpenCode CLI"
},
"skillSync": {
"title": "Skill Sync Method",
"description": "Choose how to sync Skills files",
"symlink": "Symlink",
"copy": "Copy Files",
"symlinkHint": "Symlinks save disk space and enable real-time sync. Note: May require admin privileges or Developer Mode on Windows"
},
"configDirectoryOverride": "Configuration Directory Override (Advanced)",
"configDirectoryDescription": "When using Claude Code or Codex in environments like WSL, you can manually specify the configuration directory to the one in WSL to keep provider data consistent with the main environment.",
"appConfigDir": "CC Switch Configuration Directory",
@@ -393,7 +400,8 @@
"minimax_en": "MiniMax Coding Plan Black Friday, Starter is now $2/mo (80% OFF!)",
"dmxapi": "Claude Code exclusive model 66% OFF now!",
"cubence": "Cubence is an official partner of CC Switch. Register using this link and enter \"CCSWITCH\" promo code during recharge to get 10% off every top-up",
"aigocode": "AIGoCode is an official partner of CC Switch. Register using this link and get 10% bonus credit on your first top-up!"
"aigocode": "AIGoCode is an official partner of CC Switch. Register using this link and get 10% bonus credit on your first top-up!",
"rightcode": "RightCode is an official partner of CC Switch. Register using this link and get 5% bonus credit on every top-up!"
},
"parameterConfig": "Parameter Config - {{name}} *",
"mainModel": "Main Model (optional)",
+9 -1
View File
@@ -280,6 +280,13 @@
"geminiDesc": "Google Gemini CLI",
"opencodeDesc": "OpenCode CLI"
},
"skillSync": {
"title": "スキル同期方式",
"description": "スキルファイルの同期方法を選択",
"symlink": "シンボリックリンク",
"copy": "ファイルコピー",
"symlinkHint": "シンボリックリンクはディスク容量を節約し、リアルタイム同期を有効にします。注意:Windowsでは管理者権限または開発者モードが必要な場合があります"
},
"configDirectoryOverride": "設定ディレクトリの上書き(詳細)",
"configDirectoryDescription": "WSL などで Claude Code や Codex を使う場合、ここで設定ディレクトリを WSL 側に合わせるとデータを揃えられます。",
"appConfigDir": "CC Switch 設定ディレクトリ",
@@ -393,7 +400,8 @@
"minimax_en": "MiniMax Coding Plan Black Friday、Starter が月額 $280% OFF",
"dmxapi": "Claude Code 専用モデル 66% OFF 実施中!",
"cubence": "Cubence は CC Switch の公式パートナーです。登録後チャージ時に \"CCSWITCH\" を入力すると、毎回 10% オフ",
"aigocode": "AIGoCode は CC Switch の公式パートナーです。このリンクから登録すると、初回チャージ時に 10% のボーナスクレジットがもらえます!"
"aigocode": "AIGoCode は CC Switch の公式パートナーです。このリンクから登録すると、初回チャージ時に 10% のボーナスクレジットがもらえます!",
"rightcode": "RightCode は CC Switch の公式パートナーです。このリンクから登録すると、毎回のチャージに 5% のボーナスクレジットがもらえます!"
},
"parameterConfig": "パラメーター設定 - {{name}} *",
"mainModel": "メインモデル(任意)",
+9 -1
View File
@@ -280,6 +280,13 @@
"geminiDesc": "Google Gemini CLI",
"opencodeDesc": "OpenCode CLI"
},
"skillSync": {
"title": "Skill 同步方式",
"description": "选择 Skills 的文件同步策略",
"symlink": "软连接",
"copy": "文件复制",
"symlinkHint": "软连接节省磁盘空间并支持实时同步。注意:Windows 可能需要管理员权限或开启开发者模式"
},
"configDirectoryOverride": "配置目录覆盖(高级)",
"configDirectoryDescription": "在 WSL 等环境使用 Claude Code 或 Codex 的时候,可手动指定为 WSL 里的配置目录,供应商数据与主环境保持一致。",
"appConfigDir": "CC Switch 配置目录",
@@ -393,7 +400,8 @@
"minimax_en": "MiniMax Coding Plan 黑五特惠,Starter 套餐现仅 $2/月(2折优惠!)",
"dmxapi": "Claude Code 专属模型 3.4 折优惠进行中!",
"cubence": "Cubence 是 CC Switch 的官方合作伙伴,使用此链接注册并在充值时填写 \"CCSWITCH\" 优惠码,每次充值均可享受9折优惠",
"aigocode": "AIGoCode 是 CC Switch 的官方合作伙伴,使用此链接注册首次充值时可以获得10%额度奖励!"
"aigocode": "AIGoCode 是 CC Switch 的官方合作伙伴,使用此链接注册首次充值时可以获得10%额度奖励!",
"rightcode": "RightCode 是 CC Switch 的官方合作伙伴,使用此链接注册每次充值均可赠送5%额外额度!"
},
"parameterConfig": "参数配置 - {{name}} *",
"mainModel": "主模型 (可选)",
+3
View File
@@ -25,6 +25,9 @@ export const settingsSchema = z.object({
currentProviderClaude: z.string().optional(),
currentProviderCodex: z.string().optional(),
currentProviderGemini: z.string().optional(),
// Skill 同步设置
skillSyncMethod: z.enum(["auto", "symlink", "copy"]).optional(),
});
export type SettingsFormData = z.infer<typeof settingsSchema>;
+9
View File
@@ -137,6 +137,9 @@ export interface ProviderMeta {
proxyConfig?: ProviderProxyConfig;
}
// Skill 同步方式
export type SkillSyncMethod = "auto" | "symlink" | "copy";
// 主页面显示的应用配置
export interface VisibleApps {
claude: boolean;
@@ -182,6 +185,10 @@ export interface Settings {
currentProviderCodex?: string;
// 当前 Gemini 供应商 ID(优先于数据库 is_current
currentProviderGemini?: string;
// ===== Skill 同步设置 =====
// Skill 同步方式:auto(默认,优先 symlink)、symlink、copy
skillSyncMethod?: SkillSyncMethod;
}
// MCP 服务器连接参数(宽松:允许扩展字段)
@@ -310,6 +317,8 @@ export interface OpenCodeModel {
output?: number;
};
options?: Record<string, unknown>; // 模型级别额外选项(provider 路由等)
// 支持任意额外字段(cost、modalities、thinking、variants 等)
[key: string]: unknown;
}
// OpenCode 供应商选项