1use std::path::PathBuf;
28
29use crate::config::Config;
30use crate::framework::{
31 ConfigFormat, FrameworkResolutionMode, InitOptions, all_adapters, generate_config,
32 initialize_all, resolve_frameworks,
33};
34use crate::model::error::{FrameworkResolutionError, InitError};
35
36#[derive(Debug, Clone)]
38pub struct FrameworkEnablement {
39 pub name: String,
41 pub display_name: String,
43 pub settings_path: PathBuf,
45 pub hooks_config: serde_json::Value,
47}
48
49#[derive(Debug, Clone)]
51pub struct EnableResult {
52 pub frameworks: Vec<FrameworkEnablement>,
54 pub db_path: Option<PathBuf>,
56 pub should_init_db: bool,
58}
59
60#[derive(Debug)]
62pub enum EnableError {
63 NoFrameworks {
65 supported: Vec<String>,
67 },
68 UnknownFramework(String),
70 Config(String),
72 Init(InitError),
74}
75
76impl std::fmt::Display for EnableError {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 match self {
79 Self::NoFrameworks { supported } => {
80 write!(
81 f,
82 "no supported AI coding frameworks detected.\n\
83 Supported frameworks: {}\n\
84 Install one first, or specify explicitly.",
85 supported.join(", ")
86 )
87 }
88 Self::UnknownFramework(name) => write!(f, "unknown framework: {name}"),
89 Self::Config(msg) => write!(f, "configuration error: {msg}"),
90 Self::Init(e) => write!(f, "{e}"),
91 }
92 }
93}
94
95impl std::error::Error for EnableError {
96 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
97 match self {
98 Self::Init(e) => Some(e),
99 _ => None,
100 }
101 }
102}
103
104impl From<InitError> for EnableError {
105 fn from(e: InitError) -> Self {
106 Self::Init(e)
107 }
108}
109
110impl From<FrameworkResolutionError> for EnableError {
111 fn from(e: FrameworkResolutionError) -> Self {
112 match e {
113 FrameworkResolutionError::NoFrameworksFound(_) => Self::NoFrameworks {
114 supported: all_adapters()
115 .iter()
116 .map(|a| a.name().to_string())
117 .collect(),
118 },
119 FrameworkResolutionError::UnknownFramework(name) => Self::UnknownFramework(name),
120 }
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct PreviewResult {
127 pub name: String,
129 pub hooks_config: serde_json::Value,
131 pub config_format: ConfigFormat,
133}
134
135pub fn enable(options: InitOptions) -> Result<EnableResult, EnableError> {
152 let config = Config::load().map_err(|e| EnableError::Config(e.to_string()))?;
153
154 let mut result = EnableResult {
155 frameworks: Vec::new(),
156 db_path: None,
157 should_init_db: !options.hooks_only,
158 };
159
160 if result.should_init_db {
161 result.db_path = config.db_path().ok();
162 }
163
164 if options.db_only {
165 return Ok(result);
166 }
167
168 let adapters = resolve_frameworks(
169 &options.frameworks,
170 Some(FrameworkResolutionMode::Installed),
171 )?;
172
173 let framework_names: Vec<String> = adapters.iter().map(|a| a.name().to_string()).collect();
175 let resolved_options = InitOptions {
176 frameworks: framework_names,
177 ..options
178 };
179
180 let init_results = initialize_all(&config, resolved_options)?;
181
182 for (adapter, init_result) in adapters.iter().zip(init_results) {
183 result.frameworks.push(FrameworkEnablement {
184 name: adapter.name().to_string(),
185 display_name: adapter.display_name().to_string(),
186 settings_path: init_result.settings_path,
187 hooks_config: init_result.hooks_config,
188 });
189 }
190
191 Ok(result)
192}
193
194pub fn preview_enable(options: &InitOptions) -> Result<Vec<PreviewResult>, EnableError> {
198 let config = Config::load().map_err(|e| EnableError::Config(e.to_string()))?;
199 let adapters = resolve_frameworks(
200 &options.frameworks,
201 Some(FrameworkResolutionMode::Installed),
202 )?;
203
204 Ok(adapters
205 .into_iter()
206 .map(|adapter| PreviewResult {
207 name: adapter.name().to_string(),
208 hooks_config: generate_config(adapter, &config, options),
209 config_format: adapter.config_format(),
210 })
211 .collect())
212}
213
214#[derive(Debug, Clone)]
216pub struct FrameworkDisablement {
217 pub name: String,
219 pub display_name: String,
221 pub settings_path: PathBuf,
223 pub hooks_removed: bool,
225}
226
227#[derive(Debug, Clone)]
229pub struct DisableResult {
230 pub frameworks: Vec<FrameworkDisablement>,
232}
233
234pub fn disable(frameworks: &[&str]) -> Result<DisableResult, EnableError> {
252 disable_with_options(frameworks, false, false)
253}
254
255pub fn disable_with_options(
262 frameworks: &[&str],
263 local: bool,
264 settings_local: bool,
265) -> Result<DisableResult, EnableError> {
266 use crate::framework::get_adapter;
267
268 let adapters = if frameworks.is_empty() {
269 all_adapters()
271 .into_iter()
272 .filter(|a| a.has_mi6_hooks(local, settings_local))
273 .collect::<Vec<_>>()
274 } else {
275 let mut adapters = Vec::new();
277 for name in frameworks {
278 let adapter = get_adapter(name)
279 .ok_or_else(|| EnableError::UnknownFramework((*name).to_string()))?;
280 adapters.push(adapter);
281 }
282 adapters
283 };
284
285 let mut result = DisableResult {
286 frameworks: Vec::new(),
287 };
288
289 for adapter in adapters {
290 let settings_path = match adapter.settings_path(local, settings_local) {
291 Ok(path) => path,
292 Err(_) => continue,
293 };
294
295 let hooks_removed = adapter
297 .uninstall_hooks(local, settings_local)
298 .unwrap_or_default();
299
300 result.frameworks.push(FrameworkDisablement {
301 name: adapter.name().to_string(),
302 display_name: adapter.display_name().to_string(),
303 settings_path,
304 hooks_removed,
305 });
306 }
307
308 Ok(result)
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_enable_error_display() {
317 let err = EnableError::NoFrameworks {
318 supported: vec!["claude".into(), "gemini".into()],
319 };
320 assert!(
321 err.to_string()
322 .contains("no supported AI coding frameworks")
323 );
324
325 let err = EnableError::UnknownFramework("foo".into());
326 assert!(err.to_string().contains("unknown framework: foo"));
327 }
328
329 #[test]
330 fn test_enable_error_from_framework_resolution_error() {
331 let err: EnableError = FrameworkResolutionError::UnknownFramework("foo".to_string()).into();
332 assert!(matches!(err, EnableError::UnknownFramework(name) if name == "foo"));
333
334 let err: EnableError =
335 FrameworkResolutionError::NoFrameworksFound("no frameworks".to_string()).into();
336 assert!(matches!(err, EnableError::NoFrameworks { .. }));
337 }
338}