1#[allow(clippy::wildcard_imports)]
7use super::*;
8
9#[derive(Debug, Default)]
11pub struct AgentSetupResult {
12 pub mcp_ok: bool,
13 pub mcp_skipped: bool,
16 pub rules: crate::rules_inject::InjectResult,
17 pub skill_installed: bool,
18 pub errors: Vec<String>,
19}
20
21pub fn setup_single_agent(
24 agent_name: &str,
25 global: bool,
26 mode: crate::hooks::HookMode,
27) -> AgentSetupResult {
28 let home = dirs::home_dir().unwrap_or_default();
29 let mut result = AgentSetupResult::default();
30
31 crate::hooks::install_agent_hook_with_mode(agent_name, global, mode);
32
33 if crate::core::config::Config::load()
37 .setup
38 .should_update_mcp()
39 {
40 match configure_agent_mcp(agent_name) {
41 Ok(()) => result.mcp_ok = true,
42 Err(e) => result.errors.push(format!("MCP config: {e}")),
43 }
44 } else {
45 result.mcp_skipped = true;
46 }
47
48 result.rules = crate::rules_inject::inject_rules_for_agent(&home, agent_name);
49
50 if let Ok(path) = crate::rules_inject::install_skill_for_agent(&home, agent_name) {
51 result.skill_installed = path.exists();
52 }
53
54 result
55}
56
57pub fn configure_agent_mcp(agent: &str) -> Result<(), String> {
58 let home = dirs::home_dir().ok_or_else(|| "Cannot determine home directory".to_string())?;
59 let binary = resolve_portable_binary();
60
61 let targets = agent_mcp_targets(agent, &home)?;
62
63 let mut errors = Vec::new();
64 for t in &targets {
65 if let Err(e) = crate::core::editor_registry::write_config_with_options(
66 t,
67 &binary,
68 WriteOptions {
69 overwrite_invalid: true,
70 },
71 ) {
72 eprintln!(
73 "\x1b[33m⚠\x1b[0m Could not configure {}: {}",
74 t.config_path.display(),
75 e
76 );
77 errors.push(e);
78 }
79 }
80
81 if agent == "kiro" {
82 install_kiro_steering(&home);
83 }
84
85 if agent == "vscode" || agent == "copilot" {
86 if let Err(e) = crate::core::editor_registry::plan_mode::write_vscode_plan_settings() {
87 eprintln!("\x1b[33m⚠\x1b[0m VS Code plan mode: {e}");
88 }
89 }
90 if agent == "claude" || agent == "claude-code" {
91 if let Err(e) =
92 crate::core::editor_registry::plan_mode::write_claude_code_plan_permissions()
93 {
94 eprintln!("\x1b[33m⚠\x1b[0m Claude Code plan mode: {e}");
95 }
96 }
97 if agent == "codebuddy" {
98 if let Err(e) =
99 crate::core::editor_registry::plan_mode::write_claude_code_plan_permissions()
100 {
101 eprintln!("\x1b[33m⚠\x1b[0m CodeBuddy plan mode: {e}");
102 }
103 }
104
105 if errors.is_empty() {
106 Ok(())
107 } else {
108 Err(format!(
109 "{} config(s) could not be written. See warnings above.",
110 errors.len()
111 ))
112 }
113}
114
115pub(crate) fn agent_mcp_targets(
116 agent: &str,
117 home: &std::path::Path,
118) -> Result<Vec<EditorTarget>, String> {
119 let mut targets = Vec::<EditorTarget>::new();
120
121 let push = |targets: &mut Vec<EditorTarget>,
122 name: &'static str,
123 config_path: PathBuf,
124 config_type: ConfigType| {
125 targets.push(EditorTarget {
126 name,
127 agent_key: agent.to_string(),
128 detect_path: PathBuf::from("/nonexistent"), config_path,
130 config_type,
131 });
132 };
133
134 match agent {
135 "cursor" => push(
136 &mut targets,
137 "Cursor",
138 home.join(".cursor/mcp.json"),
139 ConfigType::McpJson,
140 ),
141 "claude" | "claude-code" => push(
142 &mut targets,
143 "Claude Code",
144 crate::core::editor_registry::claude_mcp_json_path(home),
145 ConfigType::McpJson,
146 ),
147 "codebuddy" => push(
148 &mut targets,
149 "CodeBuddy",
150 crate::core::editor_registry::codebuddy_mcp_json_path(home),
151 ConfigType::McpJson,
152 ),
153 "augment" => {
154 push(
155 &mut targets,
156 "Augment CLI",
157 crate::core::editor_registry::augment_cli_settings_path(home),
158 ConfigType::McpJson,
159 );
160 push(
161 &mut targets,
162 "Augment (VS Code)",
163 crate::core::editor_registry::augment_vscode_mcp_path(home),
164 ConfigType::AugmentVsCode,
165 );
166 }
167 "windsurf" => push(
168 &mut targets,
169 "Windsurf",
170 home.join(".codeium/windsurf/mcp_config.json"),
171 ConfigType::McpJson,
172 ),
173 "codex" => {
174 let codex_dir =
175 crate::core::home::resolve_codex_dir().unwrap_or_else(|| home.join(".codex"));
176 push(
177 &mut targets,
178 "Codex CLI",
179 codex_dir.join("config.toml"),
180 ConfigType::Codex,
181 );
182 }
183 "gemini" => {
184 push(
185 &mut targets,
186 "Gemini CLI",
187 home.join(".gemini/settings.json"),
188 ConfigType::GeminiSettings,
189 );
190 push(
191 &mut targets,
192 "Antigravity IDE",
193 home.join(".gemini/antigravity/mcp_config.json"),
194 ConfigType::McpJson,
195 );
196 push(
197 &mut targets,
198 "Antigravity CLI",
199 home.join(".gemini/antigravity-cli/mcp_config.json"),
200 ConfigType::McpJson,
201 );
202 }
203 "antigravity" => push(
204 &mut targets,
205 "Antigravity IDE",
206 home.join(".gemini/antigravity/mcp_config.json"),
207 ConfigType::McpJson,
208 ),
209 "antigravity-cli" => push(
210 &mut targets,
211 "Antigravity CLI",
212 home.join(".gemini/antigravity-cli/mcp_config.json"),
213 ConfigType::McpJson,
214 ),
215 "copilot" => push(
216 &mut targets,
217 "Copilot CLI",
218 home.join(".copilot/mcp-config.json"),
219 ConfigType::CopilotCli,
220 ),
221 "crush" => push(
222 &mut targets,
223 "Crush",
224 home.join(".config/crush/crush.json"),
225 ConfigType::Crush,
226 ),
227 "qoder" => {
228 for path in crate::core::editor_registry::qoder_all_mcp_paths(home) {
229 push(&mut targets, "Qoder", path, ConfigType::QoderSettings);
230 }
231 }
232 "qoderwork" => push(
233 &mut targets,
234 "QoderWork",
235 crate::core::editor_registry::qoderwork_mcp_path(home),
236 ConfigType::McpJson,
237 ),
238 "cline" => push(
239 &mut targets,
240 "Cline",
241 crate::core::editor_registry::cline_mcp_path(),
242 ConfigType::McpJson,
243 ),
244 "roo" => push(
245 &mut targets,
246 "Roo Code",
247 crate::core::editor_registry::roo_mcp_path(),
248 ConfigType::McpJson,
249 ),
250 "kiro" => push(
251 &mut targets,
252 "AWS Kiro",
253 home.join(".kiro/settings/mcp.json"),
254 ConfigType::McpJson,
255 ),
256 "verdent" => push(
257 &mut targets,
258 "Verdent",
259 home.join(".verdent/mcp.json"),
260 ConfigType::McpJson,
261 ),
262 "pi" | "jetbrains" | "amp" | "openclaw" => {
268 }
271 "qwen" => push(
272 &mut targets,
273 "Qwen Code",
274 home.join(".qwen/settings.json"),
275 ConfigType::McpJson,
276 ),
277 "trae" => push(
278 &mut targets,
279 "Trae",
280 home.join(".trae/mcp.json"),
281 ConfigType::McpJson,
282 ),
283 "amazonq" => push(
284 &mut targets,
285 "Amazon Q Developer",
286 home.join(".aws/amazonq/default.json"),
287 ConfigType::McpJson,
288 ),
289 "opencode" => {
290 #[cfg(windows)]
291 let opencode_path = if let Ok(appdata) = std::env::var("APPDATA") {
292 std::path::PathBuf::from(appdata)
293 .join("opencode")
294 .join("opencode.json")
295 } else {
296 home.join(".config/opencode/opencode.json")
297 };
298 #[cfg(not(windows))]
299 let opencode_path = home.join(".config/opencode/opencode.json");
300 push(
301 &mut targets,
302 "OpenCode",
303 opencode_path,
304 ConfigType::OpenCode,
305 );
306 }
307 "hermes" => push(
308 &mut targets,
309 "Hermes Agent",
310 home.join(".hermes/config.yaml"),
311 ConfigType::HermesYaml,
312 ),
313 "vscode" => push(
314 &mut targets,
315 "VS Code",
316 crate::core::editor_registry::vscode_mcp_path(),
317 ConfigType::VsCodeMcp,
318 ),
319 "zed" => push(
320 &mut targets,
321 "Zed",
322 crate::core::editor_registry::zed_settings_path(home),
323 ConfigType::Zed,
324 ),
325 "aider" => push(
326 &mut targets,
327 "Aider",
328 home.join(".aider/mcp.json"),
329 ConfigType::McpJson,
330 ),
331 "continue" => push(
332 &mut targets,
333 "Continue",
334 home.join(".continue/mcp.json"),
335 ConfigType::McpJson,
336 ),
337 "neovim" => push(
338 &mut targets,
339 "Neovim (mcphub.nvim)",
340 home.join(".config/mcphub/servers.json"),
341 ConfigType::McpJson,
342 ),
343 "emacs" => push(
344 &mut targets,
345 "Emacs (mcp.el)",
346 home.join(".emacs.d/mcp.json"),
347 ConfigType::McpJson,
348 ),
349 "sublime" => push(
350 &mut targets,
351 "Sublime Text",
352 home.join(".config/sublime-text/mcp.json"),
353 ConfigType::McpJson,
354 ),
355 _ => {
356 return Err(format!("Unknown agent '{agent}'"));
357 }
358 }
359
360 Ok(targets)
361}
362
363pub fn disable_agent_mcp(agent: &str, overwrite_invalid: bool) -> Result<(), String> {
364 let home = dirs::home_dir().ok_or_else(|| "Cannot determine home directory".to_string())?;
365
366 let mut targets = Vec::<EditorTarget>::new();
367
368 let push = |targets: &mut Vec<EditorTarget>,
369 name: &'static str,
370 config_path: PathBuf,
371 config_type: ConfigType| {
372 targets.push(EditorTarget {
373 name,
374 agent_key: agent.to_string(),
375 detect_path: PathBuf::from("/nonexistent"),
376 config_path,
377 config_type,
378 });
379 };
380
381 let pi_cfg = home.join(".pi").join("agent").join("mcp.json");
382
383 match agent {
384 "cursor" => push(
385 &mut targets,
386 "Cursor",
387 home.join(".cursor/mcp.json"),
388 ConfigType::McpJson,
389 ),
390 "claude" | "claude-code" => push(
391 &mut targets,
392 "Claude Code",
393 crate::core::editor_registry::claude_mcp_json_path(&home),
394 ConfigType::McpJson,
395 ),
396 "codebuddy" => push(
397 &mut targets,
398 "CodeBuddy",
399 crate::core::editor_registry::codebuddy_mcp_json_path(&home),
400 ConfigType::McpJson,
401 ),
402 "augment" => {
403 push(
404 &mut targets,
405 "Augment CLI",
406 crate::core::editor_registry::augment_cli_settings_path(&home),
407 ConfigType::McpJson,
408 );
409 push(
410 &mut targets,
411 "Augment (VS Code)",
412 crate::core::editor_registry::augment_vscode_mcp_path(&home),
413 ConfigType::AugmentVsCode,
414 );
415 }
416 "windsurf" => push(
417 &mut targets,
418 "Windsurf",
419 home.join(".codeium/windsurf/mcp_config.json"),
420 ConfigType::McpJson,
421 ),
422 "codex" => {
423 let codex_dir =
424 crate::core::home::resolve_codex_dir().unwrap_or_else(|| home.join(".codex"));
425 push(
426 &mut targets,
427 "Codex CLI",
428 codex_dir.join("config.toml"),
429 ConfigType::Codex,
430 );
431 }
432 "gemini" => {
433 push(
434 &mut targets,
435 "Gemini CLI",
436 home.join(".gemini/settings.json"),
437 ConfigType::GeminiSettings,
438 );
439 push(
440 &mut targets,
441 "Antigravity IDE",
442 home.join(".gemini/antigravity/mcp_config.json"),
443 ConfigType::McpJson,
444 );
445 push(
446 &mut targets,
447 "Antigravity CLI",
448 home.join(".gemini/antigravity-cli/mcp_config.json"),
449 ConfigType::McpJson,
450 );
451 }
452 "antigravity" => push(
453 &mut targets,
454 "Antigravity IDE",
455 home.join(".gemini/antigravity/mcp_config.json"),
456 ConfigType::McpJson,
457 ),
458 "antigravity-cli" => push(
459 &mut targets,
460 "Antigravity CLI",
461 home.join(".gemini/antigravity-cli/mcp_config.json"),
462 ConfigType::McpJson,
463 ),
464 "copilot" => push(
465 &mut targets,
466 "Copilot CLI",
467 home.join(".copilot/mcp-config.json"),
468 ConfigType::CopilotCli,
469 ),
470 "crush" => push(
471 &mut targets,
472 "Crush",
473 home.join(".config/crush/crush.json"),
474 ConfigType::Crush,
475 ),
476 "pi" => push(&mut targets, "Pi Coding Agent", pi_cfg, ConfigType::McpJson),
477 "qoder" => {
478 for path in crate::core::editor_registry::qoder_all_mcp_paths(&home) {
479 push(&mut targets, "Qoder", path, ConfigType::QoderSettings);
480 }
481 }
482 "qoderwork" => push(
483 &mut targets,
484 "QoderWork",
485 crate::core::editor_registry::qoderwork_mcp_path(&home),
486 ConfigType::McpJson,
487 ),
488 "cline" => push(
489 &mut targets,
490 "Cline",
491 crate::core::editor_registry::cline_mcp_path(),
492 ConfigType::McpJson,
493 ),
494 "roo" => push(
495 &mut targets,
496 "Roo Code",
497 crate::core::editor_registry::roo_mcp_path(),
498 ConfigType::McpJson,
499 ),
500 "kiro" => push(
501 &mut targets,
502 "AWS Kiro",
503 home.join(".kiro/settings/mcp.json"),
504 ConfigType::McpJson,
505 ),
506 "verdent" => push(
507 &mut targets,
508 "Verdent",
509 home.join(".verdent/mcp.json"),
510 ConfigType::McpJson,
511 ),
512 "jetbrains" | "amp" | "openclaw" => {
513 }
515 "qwen" => push(
516 &mut targets,
517 "Qwen Code",
518 home.join(".qwen/settings.json"),
519 ConfigType::McpJson,
520 ),
521 "trae" => push(
522 &mut targets,
523 "Trae",
524 home.join(".trae/mcp.json"),
525 ConfigType::McpJson,
526 ),
527 "amazonq" => push(
528 &mut targets,
529 "Amazon Q Developer",
530 home.join(".aws/amazonq/default.json"),
531 ConfigType::McpJson,
532 ),
533 "opencode" => {
534 #[cfg(windows)]
535 let opencode_path = if let Ok(appdata) = std::env::var("APPDATA") {
536 std::path::PathBuf::from(appdata)
537 .join("opencode")
538 .join("opencode.json")
539 } else {
540 home.join(".config/opencode/opencode.json")
541 };
542 #[cfg(not(windows))]
543 let opencode_path = home.join(".config/opencode/opencode.json");
544 push(
545 &mut targets,
546 "OpenCode",
547 opencode_path,
548 ConfigType::OpenCode,
549 );
550 }
551 "hermes" => push(
552 &mut targets,
553 "Hermes Agent",
554 home.join(".hermes/config.yaml"),
555 ConfigType::HermesYaml,
556 ),
557 "vscode" => push(
558 &mut targets,
559 "VS Code",
560 crate::core::editor_registry::vscode_mcp_path(),
561 ConfigType::VsCodeMcp,
562 ),
563 "zed" => push(
564 &mut targets,
565 "Zed",
566 crate::core::editor_registry::zed_settings_path(&home),
567 ConfigType::Zed,
568 ),
569 "aider" => push(
570 &mut targets,
571 "Aider",
572 home.join(".aider/mcp.json"),
573 ConfigType::McpJson,
574 ),
575 "continue" => push(
576 &mut targets,
577 "Continue",
578 home.join(".continue/mcp.json"),
579 ConfigType::McpJson,
580 ),
581 "neovim" => push(
582 &mut targets,
583 "Neovim (mcphub.nvim)",
584 home.join(".config/mcphub/servers.json"),
585 ConfigType::McpJson,
586 ),
587 "emacs" => push(
588 &mut targets,
589 "Emacs (mcp.el)",
590 home.join(".emacs.d/mcp.json"),
591 ConfigType::McpJson,
592 ),
593 "sublime" => push(
594 &mut targets,
595 "Sublime Text",
596 home.join(".config/sublime-text/mcp.json"),
597 ConfigType::McpJson,
598 ),
599 _ => {
600 return Err(format!("Unknown agent '{agent}'"));
601 }
602 }
603
604 for t in &targets {
605 crate::core::editor_registry::remove_lean_ctx_server(
606 t,
607 WriteOptions { overwrite_invalid },
608 )?;
609 }
610
611 Ok(())
612}