cc_switch_lib/
claude_plugin.rs1use 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 if let Some(dir) = crate::settings::get_claude_override_dir() {
14 return Ok(dir);
15 }
16 let home = dirs::home_dir().ok_or_else(|| AppError::Config("无法获取用户主目录".into()))?;
17 Ok(home.join(CLAUDE_DIR))
18}
19
20pub fn claude_config_path() -> Result<PathBuf, AppError> {
21 Ok(claude_dir()?.join(CLAUDE_CONFIG_FILE))
22}
23
24pub fn ensure_claude_dir_exists() -> Result<PathBuf, AppError> {
25 let dir = claude_dir()?;
26 if !dir.exists() {
27 fs::create_dir_all(&dir).map_err(|e| AppError::io(&dir, e))?;
28 }
29 Ok(dir)
30}
31
32pub fn read_claude_config() -> Result<Option<String>, AppError> {
33 let path = claude_config_path()?;
34 if path.exists() {
35 let content = fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))?;
36 Ok(Some(content))
37 } else {
38 Ok(None)
39 }
40}
41
42fn is_managed_config(content: &str) -> bool {
43 match serde_json::from_str::<serde_json::Value>(content) {
44 Ok(value) => value
45 .get("primaryApiKey")
46 .and_then(|v| v.as_str())
47 .map(|val| val == "any")
48 .unwrap_or(false),
49 Err(_) => false,
50 }
51}
52
53pub fn write_claude_config() -> Result<bool, AppError> {
54 let path = claude_config_path()?;
56 ensure_claude_dir_exists()?;
57
58 let mut obj = match read_claude_config()? {
60 Some(existing) => match serde_json::from_str::<serde_json::Value>(&existing) {
61 Ok(serde_json::Value::Object(map)) => serde_json::Value::Object(map),
62 _ => serde_json::json!({}),
63 },
64 None => serde_json::json!({}),
65 };
66
67 let mut changed = false;
68 if let Some(map) = obj.as_object_mut() {
69 let cur = map
70 .get("primaryApiKey")
71 .and_then(|v| v.as_str())
72 .unwrap_or("");
73 if cur != "any" {
74 map.insert(
75 "primaryApiKey".to_string(),
76 serde_json::Value::String("any".to_string()),
77 );
78 changed = true;
79 }
80 }
81
82 if changed || !path.exists() {
83 let serialized = serde_json::to_string_pretty(&obj)
84 .map_err(|e| AppError::JsonSerialize { source: e })?;
85 fs::write(&path, format!("{serialized}\n")).map_err(|e| AppError::io(&path, e))?;
86 Ok(true)
87 } else {
88 Ok(false)
89 }
90}
91
92pub fn clear_claude_config() -> Result<bool, AppError> {
93 let path = claude_config_path()?;
94 if !path.exists() {
95 return Ok(false);
96 }
97
98 let content = match read_claude_config()? {
99 Some(content) => content,
100 None => return Ok(false),
101 };
102
103 let mut value = match serde_json::from_str::<serde_json::Value>(&content) {
104 Ok(value) => value,
105 Err(_) => return Ok(false),
106 };
107
108 let obj = match value.as_object_mut() {
109 Some(obj) => obj,
110 None => return Ok(false),
111 };
112
113 if obj.remove("primaryApiKey").is_none() {
114 return Ok(false);
115 }
116
117 let serialized =
118 serde_json::to_string_pretty(&value).map_err(|e| AppError::JsonSerialize { source: e })?;
119 fs::write(&path, format!("{serialized}\n")).map_err(|e| AppError::io(&path, e))?;
120 Ok(true)
121}
122
123pub fn claude_config_status() -> Result<(bool, PathBuf), AppError> {
124 let path = claude_config_path()?;
125 Ok((path.exists(), path))
126}
127
128pub fn is_claude_config_applied() -> Result<bool, AppError> {
129 match read_claude_config()? {
130 Some(content) => Ok(is_managed_config(&content)),
131 None => Ok(false),
132 }
133}
134
135pub fn sync_claude_plugin_on_settings_toggle(enabled: bool) -> Result<(), AppError> {
136 if enabled {
137 let _ = write_claude_config()?;
138 } else {
139 let _ = clear_claude_config()?;
140 }
141 Ok(())
142}
143
144pub fn sync_claude_plugin_on_provider_switch(
145 app_type: &AppType,
146 provider: &Provider,
147) -> Result<(), AppError> {
148 if !matches!(app_type, AppType::Claude) {
149 return Ok(());
150 }
151
152 if !crate::settings::get_enable_claude_plugin_integration() {
153 return Ok(());
154 }
155
156 let is_official = provider.category.as_deref() == Some("official");
157 if is_official {
158 let _ = clear_claude_config()?;
159 } else {
160 let _ = write_claude_config()?;
161 }
162
163 Ok(())
164}