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