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