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