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
51 changes: 47 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
agents: Codex, Claude-Code, Gemini-Cli
last-updated: 2025-12-07
last-updated: 2025-12-16
---

# DuckCoding 开发协作规范
Expand Down Expand Up @@ -76,6 +76,19 @@ last-updated: 2025-12-07

## 架构记忆(2025-12-12)

- **main.rs 模块化重构(2025-12-15)**:
- **问题**:原 `src-tauri/src/main.rs` 文件过大(652行),包含启动、托盘、窗口、迁移、命令注册等多种职责
- **解决方案**:按启动流程分层,拆分为 `setup/` 模块
- **新架构**(位于 `src-tauri/src/setup/`):
- `tray.rs` (195行):托盘菜单创建、窗口管理(显示/隐藏/聚焦/恢复)、事件处理
- `initialization.rs` (161行):启动初始化流程(日志/迁移/Profile/代理自启动/工具注册表)
- `mod.rs` (9行):模块导出
- **main.rs 重构**(402行,从 652 行减少 -38%):
- 保留:应用启动、状态管理、builder 配置、macOS 事件循环
- 辅助函数:工作目录设置、配置监听、更新检查调度、单实例判断
- 命令注册:保留内联(按功能分组注释),避免宏卫生性问题
- **架构原则**:遵循单一职责原则(SOLID - SRP),按启动流程分层,main() 函数仅保留核心逻辑
- **代码质量**:所有检查通过(ESLint + Clippy + Prettier + fmt),单元测试 199 通过
- `src-tauri/src/main.rs` 仅保留应用启动与托盘事件注册,所有 Tauri Commands 拆分到 `src-tauri/src/commands/*`,服务实现位于 `services/*`,核心设施放在 `core/*`(HTTP、日志、错误)。
- **配置管理系统(2025-12-12 重构)**:
- `services/config/` 模块化拆分为 6 个子模块:
Expand Down Expand Up @@ -147,14 +160,44 @@ last-updated: 2025-12-07
- `registry.rs`、`installer.rs`、`detection.rs` 已使用 `utils::parse_version_string()`(保持不变)
- **测试覆盖**:7 个测试函数(6 个字符串提取测试 + 1 个 semver 解析测试,7 个断言),覆盖所有格式
- **代码减少**:删除 `VersionService` 和 `Detector` 中的重复正则定义(约 15 行)
- **ToolRegistry 模块化拆分(2025-12-13)**:
- **问题**:原 `services/tool/registry.rs` 文件过大(1118行),包含 21 个方法,职责混杂
- **解决方案**:按职责拆分为 5 个子模块,每个文件 < 400 行
- **新架构**(位于 `services/tool/registry/`):
- `mod.rs` (57行):ToolRegistry 结构体定义、初始化方法、ToolDetectionProgress
- `detection.rs` (323行):工具检测与持久化(5个方法:`detect_and_persist_local_tools`、`detect_single_tool_by_detector`、`detect_and_persist_single_tool`、`refresh_local_tools`、`detect_single_tool_with_cache`)
- `instance.rs` (229行):实例 CRUD 操作(4个方法:`add_wsl_instance`、`add_ssh_instance`、`delete_instance`、`add_tool_instance`)
- `version_ops.rs` (239行):版本检查与更新(4个方法:`update_instance`、`check_update_for_instance`、`refresh_all_tool_versions`、`detect_install_methods`)
- `query.rs` (286行):查询与辅助工具(6个方法:`get_all_grouped`、`refresh_all`、`get_local_tool_status`、`refresh_and_get_local_status`、`scan_tool_candidates`、`validate_tool_path`)
- **向后兼容**:保持 `use crate::services::tool::ToolRegistry` 路径不变,调用方无需修改
- **测试迁移**:4 个单元测试随代码迁移到对应子模块(instance.rs: 1个,query.rs: 3个)
- **代码质量**:遵循单一职责原则(SOLID - SRP),每个模块职责明确,易于维护和测试
- **文件大小减少**:最大文件从 1118 行减少到 323 行(-71%),平均文件 227 行
- **透明代理已重构为多工具架构**:
- `ProxyManager` 统一管理三个工具(Claude Code、Codex、Gemini CLI)的代理实例
- `HeadersProcessor` trait 定义工具特定的 headers 处理逻辑(位于 `services/proxy/headers/`)
- `ToolProxyConfig` 存储在 `GlobalConfig.proxy_configs` HashMap 中,每个工具独立配置
- `ToolProxyConfig` 存储在 `ProxyConfigManager` 管理的 `~/.duckcoding/proxy.json` 中,每个工具独立配置
- 支持三个代理同时运行,端口由用户配置(默认: claude-code=8787, codex=8788, gemini-cli=8789)
- 旧的 `transparent_proxy_*` 字段会在读取配置时自动迁移到新结构
- 新命令:`start_tool_proxy`、`stop_tool_proxy`、`get_all_proxy_status`
- 旧命令保持兼容,内部使用新架构实现
- **命令层(2025-12-14 清理遗留代码)**:
- 新架构命令:`start_tool_proxy`、`stop_tool_proxy`、`get_all_proxy_status`、`update_proxy_config`、`get_proxy_config`、`get_all_proxy_configs`
- 旧命令已完全删除:`start_transparent_proxy`、`stop_transparent_proxy`、`get_transparent_proxy_status`、`update_transparent_proxy_config`
- 已删除遗留服务:`TransparentProxyConfigService`(原 `services/proxy/transparent_proxy_config.rs`,563行)
- 已删除前端遗留代码:`useTransparentProxy.ts` hook 和旧 API 包装器
- **代理工具模块化重构(2025-12-16)**:
- **问题**:旧架构 `TransparentProxyService` (454行) 完全未使用,`proxy_instance.rs` (421行) 包含大量重复代码
- **解决方案**:删除旧架构 + 提取通用工具到 `services/proxy/utils/`
- **已删除文件**:
- `services/proxy/transparent_proxy.rs` (454行):单代理实例旧实现(已被 ProxyManager 替代)
- **新建 utils 模块**(位于 `services/proxy/utils/`,消除重复代码 152 行):
- `body.rs` (48行):统一 `BoxBody` 类型定义和 `box_body()` 工厂函数
- `loop_detector.rs` (45行):代理回环检测(`is_proxy_loop` 防止配置指向自身)
- `error_responses.rs` (63行):统一 JSON 错误响应模板(配置缺失、回环检测、未授权、内部错误)
- `mod.rs` (10行):模块导出和常用类型重导出
- **proxy_instance.rs 简化**:从 421 行减少到 269 行(-36%),删除重复的类型定义和错误响应构建逻辑
- **GlobalConfig 清理**:删除 6 个废弃字段(`transparent_proxy_enabled`、`transparent_proxy_port`、`transparent_proxy_api_key`、`transparent_proxy_allow_public`、`transparent_proxy_real_api_key`、`transparent_proxy_real_base_url`)
- **迁移逻辑**:`migrations/proxy_config.rs` 使用 `serde_json::Value` 手动操作 JSON,保持向后兼容
- **代码质量**:遵循 DRY 原则,所有检查通过(Clippy + fmt + ESLint + Prettier),测试 199 通过
- **配置管理机制(2025-12-12)**:
- 代理启动时自动创建内置 Profile(`dc_proxy_*`),通过 `ProfileManager` 切换配置
- 内置 Profile 在 UI 中不可见(列表查询时过滤 `dc_proxy_` 前缀)
Expand Down
11 changes: 11 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pin-project-lite = "0.2"
bytes = "1"
futures-util = "0.3"
async-trait = "0.1"
# 文件锁
fs2 = "0.4"
# 数据库
rusqlite = { version = "0.32", features = ["bundled"] }
# 单例模式
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/commands/balance_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use ::duckcoding::http_client::build_client;
use ::duckcoding::models::{BalanceConfig, BalanceStore};
use ::duckcoding::services::balance::BalanceManager;
use ::duckcoding::utils::config::apply_proxy_if_configured;
use ::duckcoding::services::proxy::config::apply_global_proxy;
use std::collections::HashMap;

/// Tauri command: 通用 API 请求
Expand All @@ -26,7 +26,7 @@ pub async fn fetch_api(
headers: HashMap<String, String>,
timeout_ms: Option<u64>,
) -> Result<serde_json::Value, String> {
apply_proxy_if_configured();
apply_global_proxy().ok();

// 验证 HTTP 方法
let method_normalized = method.to_uppercase();
Expand Down
7 changes: 3 additions & 4 deletions src-tauri/src/commands/config_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ use ::duckcoding::services::config::{
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::services::proxy::config::apply_global_proxy;
use ::duckcoding::utils::config::{read_global_config, write_global_config};
use ::duckcoding::GlobalConfig;
use ::duckcoding::Tool;

Expand Down Expand Up @@ -85,7 +84,7 @@ pub async fn get_global_config() -> Result<Option<GlobalConfig>, String> {
#[tauri::command]
pub async fn generate_api_key_for_tool(tool: String) -> Result<GenerateApiKeyResult, String> {
// 应用代理配置(如果已配置)
apply_proxy_if_configured();
apply_global_proxy().ok();

// 读取全局配置
let global_config = get_global_config()
Expand Down
6 changes: 0 additions & 6 deletions src-tauri/src/commands/onboarding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ fn create_minimal_config() -> GlobalConfig {
proxy_username: None,
proxy_password: None,
proxy_bypass_urls: Vec::new(),
transparent_proxy_enabled: false,
transparent_proxy_port: 8787,
transparent_proxy_api_key: None,
transparent_proxy_real_api_key: None,
transparent_proxy_real_base_url: None,
transparent_proxy_allow_public: false,
proxy_configs: HashMap::new(),
session_endpoint_config_enabled: false,
hide_transparent_proxy_tip: false,
Expand Down
18 changes: 9 additions & 9 deletions src-tauri/src/commands/profile_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ use serde::Deserialize;

/// Profile 输入数据(前端传递)
#[derive(Debug, Deserialize)]
#[serde(untagged)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ProfileInput {
Gemini {
api_key: String,
base_url: String,
#[serde(default)]
model: Option<String>,
},
#[serde(rename = "claude-code")]
Claude { api_key: String, base_url: String },
#[serde(rename = "codex")]
Codex {
api_key: String,
base_url: String,
wire_api: String, // 前端和后端都使用 wire_api
wire_api: String,
},
Claude {
#[serde(rename = "gemini-cli")]
Gemini {
api_key: String,
base_url: String,
#[serde(default)]
model: Option<String>,
},
}

Expand Down
Loading
Loading