Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,26 @@ last-updated: 2025-12-07
- Linux 装 `libwebkit2gtk-4.1-dev`、`libjavascriptcoregtk-4.1-dev`、`patchelf` 等 Tauri v2 依赖;Windows 确保 WebView2 Runtime(先查注册表,winget 安装失败则回退微软官方静默安装包);Node 20.19.0,Rust stable(含 clippy / rustfmt),启用 npm 与 cargo 缓存。
- CI 未通过不得合并;缺少 dist 时会在 `npm run check` 内自动触发 `npm run build` 以满足 Clippy 输入。

## 架构记忆(2025-11-29
## 架构记忆(2025-12-12

- `src-tauri/src/main.rs` 仅保留应用启动与托盘事件注册,所有 Tauri Commands 拆分到 `src-tauri/src/commands/*`,服务实现位于 `services/*`,核心设施放在 `core/*`(HTTP、日志、错误)。
- **配置管理系统(2025-12-12 重构)**:
- `services/config/` 模块化拆分为 6 个子模块:
- `types.rs`:共享类型定义(`CodexSettingsPayload`、`ClaudeSettingsPayload`、`GeminiEnvPayload` 等,60行)
- `utils.rs`:工具函数(`merge_toml_tables` 保留 TOML 注释,85行)
- `claude.rs`:Claude Code 配置管理(4个公共函数,实现 `ToolConfigManager` trait,177行)
- `codex.rs`:Codex 配置管理(支持 config.toml + auth.json,保留 TOML 格式,204行)
- `gemini.rs`:Gemini CLI 配置管理(支持 settings.json + .env 环境变量,199行)
- `watcher.rs`:外部变更检测 + 文件监听(合并原 `config_watcher.rs`,550行)
- 变更检测:`detect_external_changes`、`mark_external_change`、`acknowledge_external_change`
- Profile 导入:`import_external_change`
- 文件监听:`ConfigWatcher`(轮询,跨平台)、`NotifyWatcherManager`(notify,高性能)
- 核心函数:`config_paths`(返回主配置 + 附属文件)、`compute_native_checksum`(SHA256 校验和)
- 统一接口:`ToolConfigManager` trait 定义标准的 `read_settings`、`save_settings`、`get_schema`
- 废弃功能:删除 `ConfigService::save_backup` 系列函数(由 `ProfileManager` 替代)
- 变更检测:与 `ProfileManager` 集成,自动同步激活状态的 dirty 标记和 checksum
- 命令层更新:`commands/config_commands.rs` 使用新模块路径(`config::claude::*`、`config::codex::*`、`config::gemini::*`)
- 测试状态:12 个测试(2 个轮询监听测试通过,10 个标记为 #[ignore],需 ProfileManager 重写)
- **工具管理系统**:
- 多环境架构:支持本地(Local)、WSL、SSH 三种环境的工具实例管理
- 数据模型:`ToolType`(环境类型)、`ToolInstance`(工具实例)存储在 `models/tool.rs`
Expand All @@ -99,6 +116,37 @@ last-updated: 2025-12-07
- `ToolRegistry` 和 `InstallerService` 优先使用 Detector,未注册的工具回退到旧逻辑(向后兼容)
- 新增工具仅需:1) 实现 ToolDetector trait,2) 注册到 DetectorRegistry,3) 添加 Tool 定义
- 每个 Detector 文件包含完整的检测、安装、更新、配置管理逻辑,模块化且易测试
- **命令层模块化重构(2025-12-11)**:
- 原 `commands/tool_commands.rs` (1001行) 按职责拆分为 6 个模块
- 模块结构:
- `tool_commands/installation.rs` - 安装和状态查询(3个命令)
- `tool_commands/detection.rs` - 工具检测(3个命令)
- `tool_commands/validation.rs` - 路径和环境验证(2个命令)
- `tool_commands/update.rs` - 版本更新管理(5个命令)
- `tool_commands/scanner.rs` - 安装器扫描(1个命令)
- `tool_commands/management.rs` - 实例管理(1个命令)
- `tool_commands/mod.rs` - 统一导出
- 架构原则:严格遵守三层架构(Commands → Services → Utils),命令层仅做参数验证,业务逻辑全部在服务层
- 服务层增强:
- `ToolRegistry` 新增 7 个方法:`update_instance`、`check_update_for_instance`、`refresh_all_tool_versions`、`scan_tool_candidates`、`validate_tool_path`、`add_tool_instance`、`detect_single_tool_with_cache`
- `InstallerService` 新增 1 个方法:`update_instance_by_installer`
- `utils/version.rs` 新增模块:统一版本号解析逻辑(含 6 个单元测试)
- 代码质量:命令层从 1001 行减少到 548 行(-45%),平均函数从 62 行减少到 8 行(-87%)
- 重复代码消除:版本解析、命令执行、数据库访问统一化,消除 ~280 行重复代码
- 测试覆盖:新增 11 个单元测试(version.rs: 6个,registry.rs: 5个,installer.rs: 3个)
- 废弃代码清理:删除 `update_tool` 命令(72行),移除 main.rs 中的引用
- **版本解析统一架构(2025-12-12)**:
- **单一数据源**:所有版本解析逻辑统一到 `utils/version.rs` 模块
- **两个公共方法**:
- `parse_version_string(raw: &str) -> String`:提取版本字符串,支持复杂格式(括号、空格分隔、v 前缀)
- `parse_version(raw: &str) -> Option<semver::Version>`:解析为强类型 semver 对象,用于版本比较
- **格式支持**:`2.0.61`、`v1.2.3`、`2.0.61 (Claude Code)`、`codex-cli 0.65.0`、`1.2.3-beta.1`、`rust-v0.55.0` 等
- **调用者统一**:
- `VersionService::parse_version()` → 调用 `utils::parse_version()`(删除内部正则逻辑)
- `Detector::extract_version_default()` → 调用 `utils::parse_version_string()`(删除内部正则逻辑)
- `registry.rs`、`installer.rs`、`detection.rs` 已使用 `utils::parse_version_string()`(保持不变)
- **测试覆盖**:7 个测试函数(6 个字符串提取测试 + 1 个 semver 解析测试,7 个断言),覆盖所有格式
- **代码减少**:删除 `VersionService` 和 `Detector` 中的重复正则定义(约 15 行)
- **透明代理已重构为多工具架构**:
- `ProxyManager` 统一管理三个工具(Claude Code、Codex、Gemini CLI)的代理实例
- `HeadersProcessor` trait 定义工具特定的 headers 处理逻辑(位于 `services/proxy/headers/`)
Expand Down
49 changes: 49 additions & 0 deletions mirror-api-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"tools": [
{
"id": "claude-code",
"name": "Claude Code",
"latest_version": "2.0.61",
"mirror_version": "2.0.61",
"is_stale": false,
"release_date": "2025-12-10T10:30:00Z",
"download_url": "https://registry.npmmirror.com/@anthropic-ai/claude-code/-/claude-code-2.0.61.tgz",
"release_notes_url": "https://github.com/anthropics/claude-code/releases/tag/v2.0.61",
"source": "npm",
"package_name": "@anthropic-ai/claude-code",
"repository": "https://github.com/anthropics/claude-code",
"updated_at": "2025-12-10T10:35:22Z"
},
{
"id": "codex",
"name": "CodeX",
"latest_version": "0.72.0",
"mirror_version": "0.70.0",
"is_stale": true,
"release_date": "2025-12-08T15:20:00Z",
"download_url": "https://registry.npmmirror.com/@openai/codex/-/codex-0.70.0.tgz",
"release_notes_url": "https://github.com/openai/codex/releases/tag/v0.72.0",
"source": "npm",
"package_name": "@openai/codex",
"repository": "https://github.com/openai/codex",
"updated_at": "2025-12-09T08:15:30Z"
},
{
"id": "gemini-cli",
"name": "Gemini CLI",
"latest_version": "1.5.3",
"mirror_version": "1.5.3",
"is_stale": false,
"release_date": "2025-12-05T09:00:00Z",
"download_url": "https://registry.npmmirror.com/@google/gemini-cli/-/gemini-cli-1.5.3.tgz",
"release_notes_url": "https://github.com/google/gemini-cli/releases/tag/v1.5.3",
"source": "npm",
"package_name": "@google/gemini-cli",
"repository": "https://github.com/google/gemini-cli",
"updated_at": "2025-12-05T09:30:45Z"
}
],
"updated_at": "2025-12-11T12:00:00Z",
"status": "success",
"check_duration_ms": 1234
}
36 changes: 15 additions & 21 deletions src-tauri/src/commands/config_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
use serde_json::Value;

use ::duckcoding::services::config::{
ClaudeSettingsPayload, CodexSettingsPayload, ExternalConfigChange, GeminiEnvPayload,
GeminiSettingsPayload, ImportExternalChangeResult,
self, claude, codex, gemini, ClaudeSettingsPayload, CodexSettingsPayload, ExternalConfigChange,
GeminiEnvPayload, GeminiSettingsPayload, ImportExternalChangeResult,
};
use ::duckcoding::utils::config::{
apply_proxy_if_configured, read_global_config, write_global_config,
};
use ::duckcoding::ConfigService;
use ::duckcoding::GlobalConfig;
use ::duckcoding::Tool;

Expand Down Expand Up @@ -52,16 +51,14 @@ fn build_reqwest_client() -> Result<reqwest::Client, String> {
/// 检测外部配置变更
#[tauri::command]
pub async fn get_external_changes() -> Result<Vec<ExternalConfigChange>, String> {
::duckcoding::services::config::ConfigService::detect_external_changes()
.map_err(|e| e.to_string())
config::detect_external_changes().map_err(|e| e.to_string())
}

/// 确认外部变更(清除脏标记并刷新 checksum)
#[tauri::command]
pub async fn ack_external_change(tool: String) -> Result<(), String> {
let tool_obj = Tool::by_id(&tool).ok_or_else(|| format!("❌ 未知的工具: {tool}"))?;
::duckcoding::services::config::ConfigService::acknowledge_external_change(&tool_obj)
.map_err(|e| e.to_string())
config::acknowledge_external_change(&tool_obj).map_err(|e| e.to_string())
}

/// 将外部修改导入集中仓
Expand All @@ -72,10 +69,7 @@ pub async fn import_native_change(
as_new: bool,
) -> Result<ImportExternalChangeResult, String> {
let tool_obj = Tool::by_id(&tool).ok_or_else(|| format!("❌ 未知的工具: {tool}"))?;
::duckcoding::services::config::ConfigService::import_external_change(
&tool_obj, &profile, as_new,
)
.map_err(|e| e.to_string())
config::import_external_change(&tool_obj, &profile, as_new).map_err(|e| e.to_string())
}

#[tauri::command]
Expand Down Expand Up @@ -210,9 +204,9 @@ pub async fn generate_api_key_for_tool(tool: String) -> Result<GenerateApiKeyRes

#[tauri::command]
pub fn get_claude_settings() -> Result<ClaudeSettingsPayload, String> {
ConfigService::read_claude_settings()
claude::read_claude_settings()
.map(|settings| {
let extra = ConfigService::read_claude_extra_config().ok();
let extra = claude::read_claude_extra_config().ok();
ClaudeSettingsPayload {
settings,
extra_config: extra,
Expand All @@ -223,42 +217,42 @@ pub fn get_claude_settings() -> Result<ClaudeSettingsPayload, String> {

#[tauri::command]
pub fn save_claude_settings(settings: Value, extra_config: Option<Value>) -> Result<(), String> {
ConfigService::save_claude_settings(&settings, extra_config.as_ref()).map_err(|e| e.to_string())
claude::save_claude_settings(&settings, extra_config.as_ref()).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_claude_schema() -> Result<Value, String> {
ConfigService::get_claude_schema().map_err(|e| e.to_string())
claude::get_claude_schema().map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_codex_settings() -> Result<CodexSettingsPayload, String> {
ConfigService::read_codex_settings().map_err(|e| e.to_string())
codex::read_codex_settings().map_err(|e| e.to_string())
}

#[tauri::command]
pub fn save_codex_settings(settings: Value, auth_token: Option<String>) -> Result<(), String> {
ConfigService::save_codex_settings(&settings, auth_token).map_err(|e| e.to_string())
codex::save_codex_settings(&settings, auth_token).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_codex_schema() -> Result<Value, String> {
ConfigService::get_codex_schema().map_err(|e| e.to_string())
codex::get_codex_schema().map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_gemini_settings() -> Result<GeminiSettingsPayload, String> {
ConfigService::read_gemini_settings().map_err(|e| e.to_string())
gemini::read_gemini_settings().map_err(|e| e.to_string())
}

#[tauri::command]
pub fn save_gemini_settings(settings: Value, env: GeminiEnvPayload) -> Result<(), String> {
ConfigService::save_gemini_settings(&settings, &env).map_err(|e| e.to_string())
gemini::save_gemini_settings(&settings, &env).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn get_gemini_schema() -> Result<Value, String> {
ConfigService::get_gemini_schema().map_err(|e| e.to_string())
gemini::get_gemini_schema().map_err(|e| e.to_string())
}

// ==================== 单实例模式配置命令 ====================
Expand Down
162 changes: 0 additions & 162 deletions src-tauri/src/commands/proxy_commands_backup.rs

This file was deleted.

Loading
Loading