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 "zed" => push(
318 &mut targets,
319 "Zed",
320 crate::core::editor_registry::zed_settings_path(home),
321 ConfigType::Zed,
322 ),
323 "aider" => push(
324 &mut targets,
325 "Aider",
326 home.join(".aider/mcp.json"),
327 ConfigType::McpJson,
328 ),
329 "continue" => push(
330 &mut targets,
331 "Continue",
332 home.join(".continue/mcp.json"),
333 ConfigType::McpJson,
334 ),
335 "neovim" => push(
336 &mut targets,
337 "Neovim (mcphub.nvim)",
338 home.join(".config/mcphub/servers.json"),
339 ConfigType::McpJson,
340 ),
341 "emacs" => push(
342 &mut targets,
343 "Emacs (mcp.el)",
344 home.join(".emacs.d/mcp.json"),
345 ConfigType::McpJson,
346 ),
347 "sublime" => push(
348 &mut targets,
349 "Sublime Text",
350 home.join(".config/sublime-text/mcp.json"),
351 ConfigType::McpJson,
352 ),
353 _ => {
354 return Err(format!("Unknown agent '{agent}'"));
355 }
356 }
357
358 Ok(targets)
359}
360
361pub fn disable_agent_mcp(agent: &str, overwrite_invalid: bool) -> Result<(), String> {
362 let home = dirs::home_dir().ok_or_else(|| "Cannot determine home directory".to_string())?;
363
364 let mut targets = Vec::<EditorTarget>::new();
365
366 let push = |targets: &mut Vec<EditorTarget>,
367 name: &'static str,
368 config_path: PathBuf,
369 config_type: ConfigType| {
370 targets.push(EditorTarget {
371 name,
372 agent_key: agent.to_string(),
373 detect_path: PathBuf::from("/nonexistent"),
374 config_path,
375 config_type,
376 });
377 };
378
379 let pi_cfg = home.join(".pi").join("agent").join("mcp.json");
380
381 match agent {
382 "cursor" => push(
383 &mut targets,
384 "Cursor",
385 home.join(".cursor/mcp.json"),
386 ConfigType::McpJson,
387 ),
388 "claude" | "claude-code" => push(
389 &mut targets,
390 "Claude Code",
391 crate::core::editor_registry::claude_mcp_json_path(&home),
392 ConfigType::McpJson,
393 ),
394 "codebuddy" => push(
395 &mut targets,
396 "CodeBuddy",
397 crate::core::editor_registry::codebuddy_mcp_json_path(&home),
398 ConfigType::McpJson,
399 ),
400 "augment" => {
401 push(
402 &mut targets,
403 "Augment CLI",
404 crate::core::editor_registry::augment_cli_settings_path(&home),
405 ConfigType::McpJson,
406 );
407 push(
408 &mut targets,
409 "Augment (VS Code)",
410 crate::core::editor_registry::augment_vscode_mcp_path(&home),
411 ConfigType::AugmentVsCode,
412 );
413 }
414 "windsurf" => push(
415 &mut targets,
416 "Windsurf",
417 home.join(".codeium/windsurf/mcp_config.json"),
418 ConfigType::McpJson,
419 ),
420 "codex" => {
421 let codex_dir =
422 crate::core::home::resolve_codex_dir().unwrap_or_else(|| home.join(".codex"));
423 push(
424 &mut targets,
425 "Codex CLI",
426 codex_dir.join("config.toml"),
427 ConfigType::Codex,
428 );
429 }
430 "gemini" => {
431 push(
432 &mut targets,
433 "Gemini CLI",
434 home.join(".gemini/settings.json"),
435 ConfigType::GeminiSettings,
436 );
437 push(
438 &mut targets,
439 "Antigravity IDE",
440 home.join(".gemini/antigravity/mcp_config.json"),
441 ConfigType::McpJson,
442 );
443 push(
444 &mut targets,
445 "Antigravity CLI",
446 home.join(".gemini/antigravity-cli/mcp_config.json"),
447 ConfigType::McpJson,
448 );
449 }
450 "antigravity" => push(
451 &mut targets,
452 "Antigravity IDE",
453 home.join(".gemini/antigravity/mcp_config.json"),
454 ConfigType::McpJson,
455 ),
456 "antigravity-cli" => push(
457 &mut targets,
458 "Antigravity CLI",
459 home.join(".gemini/antigravity-cli/mcp_config.json"),
460 ConfigType::McpJson,
461 ),
462 "copilot" => push(
463 &mut targets,
464 "Copilot CLI",
465 home.join(".copilot/mcp-config.json"),
466 ConfigType::CopilotCli,
467 ),
468 "crush" => push(
469 &mut targets,
470 "Crush",
471 home.join(".config/crush/crush.json"),
472 ConfigType::Crush,
473 ),
474 "pi" => push(&mut targets, "Pi Coding Agent", pi_cfg, ConfigType::McpJson),
475 "qoder" => {
476 for path in crate::core::editor_registry::qoder_all_mcp_paths(&home) {
477 push(&mut targets, "Qoder", path, ConfigType::QoderSettings);
478 }
479 }
480 "qoderwork" => push(
481 &mut targets,
482 "QoderWork",
483 crate::core::editor_registry::qoderwork_mcp_path(&home),
484 ConfigType::McpJson,
485 ),
486 "cline" => push(
487 &mut targets,
488 "Cline",
489 crate::core::editor_registry::cline_mcp_path(),
490 ConfigType::McpJson,
491 ),
492 "roo" => push(
493 &mut targets,
494 "Roo Code",
495 crate::core::editor_registry::roo_mcp_path(),
496 ConfigType::McpJson,
497 ),
498 "kiro" => push(
499 &mut targets,
500 "AWS Kiro",
501 home.join(".kiro/settings/mcp.json"),
502 ConfigType::McpJson,
503 ),
504 "verdent" => push(
505 &mut targets,
506 "Verdent",
507 home.join(".verdent/mcp.json"),
508 ConfigType::McpJson,
509 ),
510 "jetbrains" | "amp" | "openclaw" => {
511 }
513 "qwen" => push(
514 &mut targets,
515 "Qwen Code",
516 home.join(".qwen/settings.json"),
517 ConfigType::McpJson,
518 ),
519 "trae" => push(
520 &mut targets,
521 "Trae",
522 home.join(".trae/mcp.json"),
523 ConfigType::McpJson,
524 ),
525 "amazonq" => push(
526 &mut targets,
527 "Amazon Q Developer",
528 home.join(".aws/amazonq/default.json"),
529 ConfigType::McpJson,
530 ),
531 "opencode" => {
532 #[cfg(windows)]
533 let opencode_path = if let Ok(appdata) = std::env::var("APPDATA") {
534 std::path::PathBuf::from(appdata)
535 .join("opencode")
536 .join("opencode.json")
537 } else {
538 home.join(".config/opencode/opencode.json")
539 };
540 #[cfg(not(windows))]
541 let opencode_path = home.join(".config/opencode/opencode.json");
542 push(
543 &mut targets,
544 "OpenCode",
545 opencode_path,
546 ConfigType::OpenCode,
547 );
548 }
549 "hermes" => push(
550 &mut targets,
551 "Hermes Agent",
552 home.join(".hermes/config.yaml"),
553 ConfigType::HermesYaml,
554 ),
555 "vscode" => push(
556 &mut targets,
557 "VS Code",
558 crate::core::editor_registry::vscode_mcp_path(),
559 ConfigType::VsCodeMcp,
560 ),
561 "zed" => push(
562 &mut targets,
563 "Zed",
564 crate::core::editor_registry::zed_settings_path(&home),
565 ConfigType::Zed,
566 ),
567 "aider" => push(
568 &mut targets,
569 "Aider",
570 home.join(".aider/mcp.json"),
571 ConfigType::McpJson,
572 ),
573 "continue" => push(
574 &mut targets,
575 "Continue",
576 home.join(".continue/mcp.json"),
577 ConfigType::McpJson,
578 ),
579 "neovim" => push(
580 &mut targets,
581 "Neovim (mcphub.nvim)",
582 home.join(".config/mcphub/servers.json"),
583 ConfigType::McpJson,
584 ),
585 "emacs" => push(
586 &mut targets,
587 "Emacs (mcp.el)",
588 home.join(".emacs.d/mcp.json"),
589 ConfigType::McpJson,
590 ),
591 "sublime" => push(
592 &mut targets,
593 "Sublime Text",
594 home.join(".config/sublime-text/mcp.json"),
595 ConfigType::McpJson,
596 ),
597 _ => {
598 return Err(format!("Unknown agent '{agent}'"));
599 }
600 }
601
602 for t in &targets {
603 crate::core::editor_registry::remove_lean_ctx_server(
604 t,
605 WriteOptions { overwrite_invalid },
606 )?;
607 }
608
609 Ok(())
610}