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 =
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 let path = claude_config_path()?;
46 ensure_claude_dir_exists()?;
47
48 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}