6 Commits

Author SHA1 Message Date
Jason
14964667ac chore(ci): Linux runner 切换至 ubuntu-22.04(遵循 GitHub 支持范围) 2025-09-11 08:37:01 +08:00
Jason
e143ef30e3 - chore(ci): 固定 runner 版本,移除 macOS-13;升级 actions/cache@v4 与 action-gh-release@v2
- fix(linux): 使用 ubuntu-20.04 构建并可选上传 AppImage,降低 glibc/内核基线
- fix(windows): 安装器启用 WebView2 下载引导(silent),默认不开启 --disable-gpu
- fix(macos): ARM64 构建通用包(zip),覆盖 Intel 与 Apple Silicon
- chore(version): bump to v3.1.2(兼容性热修复)
2025-09-10 22:53:09 +08:00
farion1231
3665a79e50 chore: bump version to v3.1.1
- Update version in package.json, Cargo.toml, and tauri.conf.json
- Add CHANGELOG entries for v3.1.0 and v3.1.1
2025-09-03 16:43:29 +08:00
farion1231
4dce31aff7 Fix the default codex config.toml to match the latest modifications. 2025-09-03 16:33:12 +08:00
Jason
451ca949ec feat(ui): improve provider configuration UX with custom option
- Add explicit "Custom" button in preset selection
- Set "Custom" as default selection when adding new provider
- Update label from "One-click import" to "Choose configuration type"
- Add contextual hints for different configuration modes:
  - Custom mode: "Manually configure provider, complete configuration required"
  - Official preset: "Official login, no API Key required"
  - Other presets: "Use preset configuration, only API Key required"
- Remove redundant "(optional)" text from Codex config.toml hint
- Improve clarity for users who were confused about adding custom providers
2025-09-03 15:58:02 +08:00
Jason
a9ff8ce01c update readme 2025-09-01 15:33:24 +08:00
9 changed files with 181 additions and 30 deletions

View File

@@ -3,7 +3,7 @@ name: Release
on:
push:
tags:
- 'v*'
- "v*"
permissions:
contents: write
@@ -14,9 +14,10 @@ jobs:
strategy:
matrix:
include:
- os: windows-latest
- os: ubuntu-latest
- os: macos-latest
# 固定 runner 版本,避免 latest 漂移导致 ABI 升级
- os: windows-2022
- os: ubuntu-22.04
- os: macos-14
steps:
- name: Checkout
@@ -25,13 +26,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Add macOS targets
if: runner.os == 'macOS'
- name: Add macOS targets (ARM64 only for universal)
if: runner.os == 'macOS' && runner.arch == 'ARM64'
run: |
rustup target add aarch64-apple-darwin x86_64-apple-darwin
@@ -74,7 +75,7 @@ jobs:
run: echo "path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-store.outputs.path }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -83,10 +84,12 @@ jobs:
- name: Install frontend deps
run: pnpm install --frozen-lockfile
- name: Build Tauri App (macOS)
if: runner.os == 'macOS'
- name: Build Tauri App (macOS, universal on ARM64)
if: runner.os == 'macOS' && runner.arch == 'ARM64'
run: pnpm tauri build --target universal-apple-darwin
# macOS 仅保留通用包(在 macOS-14 / ARM64 运行器上构建)
- name: Build Tauri App (Windows)
if: runner.os == 'Windows'
run: pnpm tauri build
@@ -165,7 +168,7 @@ jobs:
run: |
set -euxo pipefail
mkdir -p release-assets
# 仅上传安装包deb
# 优先 DEB同时若存在 AppImage 一并上传
DEB=$(find src-tauri/target/release/bundle -name "*.deb" | head -1 || true)
if [ -n "$DEB" ]; then
cp "$DEB" release-assets/
@@ -174,6 +177,13 @@ jobs:
echo "No .deb found" >&2
exit 1
fi
APPIMAGE=$(find src-tauri/target/release/bundle -name "*.AppImage" | head -1 || true)
if [ -n "$APPIMAGE" ]; then
cp "$APPIMAGE" release-assets/
echo "AppImage copied"
else
echo "No AppImage found (optional)"
fi
- name: List prepared assets
shell: bash
@@ -181,7 +191,7 @@ jobs:
ls -la release-assets || true
- name: Upload Release Assets
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: CC Switch ${{ github.ref_name }}

View File

@@ -5,6 +5,44 @@ All notable changes to CC Switch will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.1.1] - 2025-09-03
### 🐛 Bug Fixes
- Fixed the default codex config.toml to match the latest modifications
- Improved provider configuration UX with custom option
### 📝 Documentation
- Updated README with latest information
## [3.1.0] - 2025-09-01
### ✨ New Features
- **Added Codex application support** - Now supports both Claude Code and Codex configuration management
- Manage auth.json and config.toml for Codex
- Support for backup and restore operations
- Preset providers for Codex (Official, PackyCode)
- API Key auto-write to auth.json when using presets
- **New UI components**
- App switcher with segmented control design
- Dual editor form for Codex configuration
- Pills-style app switcher with consistent button widths
- **Enhanced configuration management**
- Multi-app config v2 structure (claude/codex)
- Automatic v1→v2 migration with backup
- OPENAI_API_KEY validation for non-official presets
- TOML syntax validation for config.toml
### 🔧 Technical Improvements
- Unified Tauri command API with app_type parameter
- Backward compatibility for app/appType parameters
- Added get_config_status/open_config_folder/open_external commands
- Improved error handling for empty config.toml
### 🐛 Bug Fixes
- Fixed config path reporting and folder opening for Codex
- Corrected default import behavior when main config is missing
- Fixed non_snake_case warnings in commands.rs
## [3.0.0] - 2025-08-27
### 🚀 Major Changes

View File

@@ -1,4 +1,4 @@
# Claude Code 供应商切换器
# Claude Code & Codex 供应商切换器
[![Version](https://img.shields.io/badge/version-3.0.0-blue.svg)](https://github.com/jasonyoung/cc-switch/releases)
[![Platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey.svg)](https://github.com/jasonyoung/cc-switch/releases)
@@ -6,12 +6,15 @@
一个用于管理和切换 Claude Code 与 Codex 不同供应商配置的桌面应用。
> **v3.0.0 重大更新**:从 Electron 完全迁移到 Tauri 2.0,应用体积减少 85%(从 ~80MB 降至 ~12MB启动速度提升 10 倍!
> v3.1.0 :新增 Codex 供应商管理与一键切换,支持导入当前 Codex 配置为默认供应商,并在内部配置从 v1 → v2 迁移前自动备份(详见下文““迁移与备份”)。
> v3.0.0 重大更新:从 Electron 完全迁移到 Tauri 2.0,应用体积减少 85%(从 ~80MB 降至 ~12MB启动速度提升 10 倍!
## 功能特性
- **极速启动** - 基于 Tauri 2.0,原生性能,秒开应用
- 一键切换不同供应商
- 同时支持 Claude Code 与 Codex 的供应商切换与导入
- Qwen coder、kimi k2、智谱 GLM、DeepSeek v3.1、packycode 等预设供应商只需要填写 key 即可一键配置
- 支持添加自定义供应商
- 随时切换官方登录
@@ -67,6 +70,22 @@
- API Key 字段:`auth.json` 中使用 `OPENAI_API_KEY`
- 切换策略:将选中供应商的副本覆盖到主配置(`auth.json``config.toml`)。若供应商没有 `config-*.toml`,会创建空的 `config.toml`
- 导入默认:若 `~/.codex/auth.json` 存在,会将当前主配置导入为 `default` 供应商;`config.toml` 不存在时按空处理。
- 官方登录可切换到预设“Codex 官方登录”,重启终端后可选择使用 ChatGPT 账号完成登录。
### Claude Code 说明
- 配置目录:`~/.claude/`
- 主配置文件:`settings.json`(推荐)或 `claude.json`(旧版兼容,若存在则继续使用)
- 供应商副本:`settings-<name>.json`
- API Key 字段:`env.ANTHROPIC_AUTH_TOKEN`
- 切换策略:将选中供应商的副本覆盖到主配置(`settings.json`/`claude.json`)。如当前有配置且存在“当前供应商”,会先将主配置备份回该供应商的副本文件。
- 导入默认:若 `~/.claude/settings.json``~/.claude/claude.json` 存在,会将当前主配置导入为 `default` 供应商副本。
- 官方登录可切换到预设“Claude 官方登录”,重启终端后可使用 `/login` 完成登录。
### 迁移与备份
- cc-switch 自身配置从 v1 → v2 迁移时,将在 `~/.cc-switch/` 目录自动创建时间戳备份:`config.v1.backup.<timestamp>.json`
- 实际生效的应用配置文件(如 `~/.claude/settings.json``~/.codex/auth.json`/`config.toml`)不会被修改,切换仅在用户点击“切换”时按副本覆盖到主配置。
## 开发

View File

@@ -1,7 +1,7 @@
{
"name": "cc-switch",
"version": "3.0.0",
"description": "Claude Code 供应商切换工具",
"version": "3.1.2",
"description": "Claude Code & Codex 供应商切换工具",
"scripts": {
"dev": "pnpm tauri dev",
"build": "pnpm tauri build",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 247 KiB

View File

@@ -1,10 +1,10 @@
[package]
name = "cc-switch"
version = "3.0.0"
description = "Claude Code MCP 服务器配置管理工具"
version = "3.1.2"
description = "Claude Code & Codex 供应商配置管理工具"
authors = ["Jason Young"]
license = "MIT"
repository = "https://github.com/jasonyoung/cc-switch"
repository = "https://github.com/farion1231/cc-switch"
edition = "2024"
rust-version = "1.85.0"

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "CC Switch",
"version": "3.0.0",
"version": "3.1.2",
"identifier": "com.ccswitch.desktop",
"build": {
"frontendDist": "../dist",
@@ -35,6 +35,12 @@
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
],
"windows": {
"webviewInstallMode": {
"type": "downloadBootstrapper",
"silent": true
}
}
}
}

View File

@@ -47,8 +47,9 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const [codexAuth, setCodexAuth] = useState("");
const [codexConfig, setCodexConfig] = useState("");
const [codexApiKey, setCodexApiKey] = useState("");
// -1 表示自定义null 表示未选择,>= 0 表示预设索引
const [selectedCodexPreset, setSelectedCodexPreset] = useState<number | null>(
null,
showPresets && isCodex ? -1 : null,
);
// 初始化 Codex 配置
@@ -71,7 +72,10 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
}, [isCodex, initialData]);
const [error, setError] = useState("");
const [disableCoAuthored, setDisableCoAuthored] = useState(false);
const [selectedPreset, setSelectedPreset] = useState<number | null>(null);
// -1 表示自定义null 表示未选择,>= 0 表示预设索引
const [selectedPreset, setSelectedPreset] = useState<number | null>(
showPresets ? -1 : null
);
const [apiKey, setApiKey] = useState("");
// 初始化时检查禁用签名状态
@@ -216,6 +220,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setDisableCoAuthored(hasCoAuthoredDisabled);
};
// 处理点击自定义按钮
const handleCustomClick = () => {
setSelectedPreset(-1);
setFormData({
name: "",
websiteUrl: "",
settingsConfig: "",
});
setApiKey("");
setDisableCoAuthored(false);
};
// Codex: 应用预设
const applyCodexPreset = (
preset: (typeof codexProviderPresets)[0],
@@ -237,6 +253,19 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
setCodexApiKey("");
};
// Codex: 处理点击自定义按钮
const handleCodexCustomClick = () => {
setSelectedCodexPreset(-1);
setFormData({
name: "",
websiteUrl: "",
settingsConfig: "",
});
setCodexAuth("");
setCodexConfig("");
setCodexApiKey("");
};
// 处理 API Key 输入并自动更新配置
const handleApiKeyChange = (key: string) => {
setApiKey(key);
@@ -244,7 +273,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
const configString = setApiKeyInConfig(
formData.settingsConfig,
key.trim(),
{ createIfMissing: selectedPreset !== null },
{ createIfMissing: selectedPreset !== null && selectedPreset !== -1 },
);
// 更新表单配置
@@ -271,12 +300,15 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
};
// 根据当前配置决定是否展示 API Key 输入框
// 自定义模式(-1)不显示独立的 API Key 输入框
const showApiKey =
selectedPreset !== null || hasApiKeyField(formData.settingsConfig);
(selectedPreset !== null && selectedPreset !== -1) ||
(!showPresets && hasApiKeyField(formData.settingsConfig));
// 判断当前选中的预设是否是官方
const isOfficialPreset =
selectedPreset !== null &&
selectedPreset >= 0 &&
providerPresets[selectedPreset]?.isOfficial === true;
// Codex: 控制显示 API Key 与官方标记
@@ -288,10 +320,13 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
return "";
}
};
// 自定义模式(-1)不显示独立的 API Key 输入框
const showCodexApiKey =
selectedCodexPreset !== null || getCodexAuthApiKey(codexAuth) !== "";
(selectedCodexPreset !== null && selectedCodexPreset !== -1) ||
(!showPresets && getCodexAuthApiKey(codexAuth) !== "");
const isCodexOfficialPreset =
selectedCodexPreset !== null &&
selectedCodexPreset >= 0 &&
codexProviderPresets[selectedCodexPreset]?.isOfficial === true;
// 初始时从配置中同步 API Key编辑模式
@@ -347,8 +382,17 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
{showPresets && !isCodex && (
<div className="presets">
<label> key</label>
<label></label>
<div className="preset-buttons">
<button
type="button"
className={`preset-btn ${
selectedPreset === -1 ? "selected" : ""
}`}
onClick={handleCustomClick}
>
</button>
{providerPresets.map((preset, index) => {
return (
<button
@@ -364,13 +408,34 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
);
})}
</div>
{selectedPreset === -1 && (
<small className="field-hint" style={{ marginTop: "8px", display: "block" }}>
</small>
)}
{selectedPreset !== -1 && selectedPreset !== null && (
<small className="field-hint" style={{ marginTop: "8px", display: "block" }}>
{isOfficialPreset
? "Claude 官方登录,不需要填写 API Key"
: "使用预设配置,只需填写 API Key"}
</small>
)}
</div>
)}
{showPresets && isCodex && (
<div className="presets">
<label> key</label>
<label></label>
<div className="preset-buttons">
<button
type="button"
className={`preset-btn ${
selectedCodexPreset === -1 ? "selected" : ""
}`}
onClick={handleCodexCustomClick}
>
</button>
{codexProviderPresets.map((preset, index) => (
<button
key={index}
@@ -384,6 +449,18 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
</button>
))}
</div>
{selectedCodexPreset === -1 && (
<small className="field-hint" style={{ marginTop: "8px", display: "block" }}>
</small>
)}
{selectedCodexPreset !== -1 && selectedCodexPreset !== null && (
<small className="field-hint" style={{ marginTop: "8px", display: "block" }}>
{isCodexOfficialPreset
? "Codex 官方登录,不需要填写 API Key"
: "使用预设配置,只需填写 API Key"}
</small>
)}
</div>
)}
@@ -448,7 +525,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
}
disabled={isCodexOfficialPreset}
required={
selectedCodexPreset !== null && !isCodexOfficialPreset
selectedCodexPreset !== null && selectedCodexPreset >= 0 && !isCodexOfficialPreset
}
autoComplete="off"
style={
@@ -521,7 +598,7 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
style={{ fontFamily: "monospace", fontSize: "14px" }}
/>
<small className="field-hint">
Codex config.toml
Codex config.toml
</small>
</div>
</>

View File

@@ -30,6 +30,7 @@ export const codexProviderPresets: CodexProviderPreset[] = [
config: `model_provider = "packycode"
model = "gpt-5"
model_reasoning_effort = "high"
disable_response_storage = true
[model_providers.packycode]
name = "packycode"