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