Skip to main content

cc_switch_lib/
claude_plugin.rs

1use std::fs;
2use std::path::PathBuf;
3
4use crate::app_config::AppType;
5use crate::error::AppError;
6use crate::provider::Provider;
7
8const CLAUDE_DIR: &str = ".claude";
9const CLAUDE_CONFIG_FILE: &str = "config.json";
10
11fn claude_dir() -> Result<PathBuf, AppError> {
12    // 优先使用设置中的覆盖目录
13    if let Some(dir) = crate::settings::get_claude_override_dir() {
14        return Ok(dir);
15    }
16    let home =
17        crate::config::home_dir().ok_or_else(|| AppError::Config("无法获取用户主目录".into()))?;
18    Ok(home.join(CLAUDE_DIR))
19}
20
21pub fn claude_config_path() -> Result<PathBuf, AppError> {
22    Ok(claude_dir()?.join(CLAUDE_CONFIG_FILE))
23}
24
25pub fn ensure_claude_dir_exists() -> Result<PathBuf, AppError> {
26    let dir = claude_dir()?;
27    if !dir.exists() {
28        fs::create_dir_all(&dir).map_err(|e| AppError::io(&dir, e))?;
29    }
30    Ok(dir)
31}
32
33pub fn read_claude_config() -> Result<Option<String>, AppError> {
34    let path = claude_config_path()?;
35    if path.exists() {
36        let content = fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))?;
37        Ok(Some(content))
38    } else {
39        Ok(None)
40    }
41}
42
43pub fn write_claude_config() -> Result<bool, AppError> {
44    // 增量写入:仅设置 primaryApiKey = "any",保留其它字段
45    let path = claude_config_path()?;
46    ensure_claude_dir_exists()?;
47
48    // 尝试读取并解析为对象
49    let mut obj = match read_claude_config()? {
50        Some(existing) => match serde_json::from_str::<serde_json::Value>(&existing) {
51            Ok(serde_json::Value::Object(map)) => serde_json::Value::Object(map),
52            _ => serde_json::json!({}),
53        },
54        None => serde_json::json!({}),
55    };
56
57    let mut changed = false;
58    if let Some(map) = obj.as_object_mut() {
59        let cur = map
60            .get("primaryApiKey")
61            .and_then(|v| v.as_str())
62            .unwrap_or("");
63        if cur != "any" {
64            map.insert(
65                "primaryApiKey".to_string(),
66                serde_json::Value::String("any".to_string()),
67            );
68            changed = true;
69        }
70    }
71
72    if changed || !path.exists() {
73        let serialized = serde_json::to_string_pretty(&obj)
74            .map_err(|e| AppError::JsonSerialize { source: e })?;
75        fs::write(&path, format!("{serialized}\n")).map_err(|e| AppError::io(&path, e))?;
76        Ok(true)
77    } else {
78        Ok(false)
79    }
80}
81
82pub fn clear_claude_config() -> Result<bool, AppError> {
83    let path = claude_config_path()?;
84    if !path.exists() {
85        return Ok(false);
86    }
87
88    let content = match read_claude_config()? {
89        Some(content) => content,
90        None => return Ok(false),
91    };
92
93    let mut value = match serde_json::from_str::<serde_json::Value>(&content) {
94        Ok(value) => value,
95        Err(_) => return Ok(false),
96    };
97
98    let obj = match value.as_object_mut() {
99        Some(obj) => obj,
100        None => return Ok(false),
101    };
102
103    if obj.remove("primaryApiKey").is_none() {
104        return Ok(false);
105    }
106
107    let serialized =
108        serde_json::to_string_pretty(&value).map_err(|e| AppError::JsonSerialize { source: e })?;
109    fs::write(&path, format!("{serialized}\n")).map_err(|e| AppError::io(&path, e))?;
110    Ok(true)
111}
112
113pub fn sync_claude_plugin_on_settings_toggle(enabled: bool) -> Result<(), AppError> {
114    if enabled {
115        let _ = write_claude_config()?;
116    } else {
117        let _ = clear_claude_config()?;
118    }
119    Ok(())
120}
121
122pub fn sync_claude_plugin_on_provider_switch(
123    app_type: &AppType,
124    provider: &Provider,
125) -> Result<(), AppError> {
126    if !matches!(app_type, AppType::Claude) {
127        return Ok(());
128    }
129
130    if !crate::settings::get_enable_claude_plugin_integration() {
131        return Ok(());
132    }
133
134    let is_official = provider.category.as_deref() == Some("official");
135    if is_official {
136        let _ = clear_claude_config()?;
137    } else {
138        let _ = write_claude_config()?;
139    }
140
141    Ok(())
142}