1use std::path::PathBuf;
2
3struct EditorTarget {
4 name: &'static str,
5 agent_key: &'static str,
6 config_path: PathBuf,
7 detect_path: PathBuf,
8 config_type: ConfigType,
9}
10
11enum ConfigType {
12 McpJson,
13 Zed,
14 Codex,
15 VsCodeMcp,
16 OpenCode,
17}
18
19pub fn run_setup() {
20 use crate::terminal_ui;
21
22 let home = match dirs::home_dir() {
23 Some(h) => h,
24 None => {
25 eprintln!("Cannot determine home directory");
26 std::process::exit(1);
27 }
28 };
29
30 let binary = std::env::current_exe()
31 .map(|p| p.to_string_lossy().to_string())
32 .unwrap_or_else(|_| "lean-ctx".to_string());
33
34 let home_str = home.to_string_lossy().to_string();
35
36 terminal_ui::print_setup_header();
37
38 terminal_ui::print_step_header(1, 5, "Shell Hook");
40 crate::cli::cmd_init(&["--global".to_string()]);
41
42 terminal_ui::print_step_header(2, 5, "AI Tool Detection");
44
45 let targets = build_targets(&home, &binary);
46 let mut newly_configured: Vec<&str> = Vec::new();
47 let mut already_configured: Vec<&str> = Vec::new();
48 let mut not_installed: Vec<&str> = Vec::new();
49 let mut errors: Vec<&str> = Vec::new();
50
51 for target in &targets {
52 let short_path = shorten_path(&target.config_path.to_string_lossy(), &home_str);
53
54 if !target.detect_path.exists() {
55 not_installed.push(target.name);
56 continue;
57 }
58
59 let has_config = target.config_path.exists()
60 && std::fs::read_to_string(&target.config_path)
61 .map(|c| c.contains("lean-ctx"))
62 .unwrap_or(false);
63
64 if has_config {
65 terminal_ui::print_status_ok(&format!(
66 "{:<20} \x1b[2m{short_path}\x1b[0m",
67 target.name
68 ));
69 already_configured.push(target.name);
70 continue;
71 }
72
73 match write_config(target, &binary) {
74 Ok(()) => {
75 terminal_ui::print_status_new(&format!(
76 "{:<20} \x1b[2m{short_path}\x1b[0m",
77 target.name
78 ));
79 newly_configured.push(target.name);
80 }
81 Err(e) => {
82 terminal_ui::print_status_warn(&format!("{}: {e}", target.name));
83 errors.push(target.name);
84 }
85 }
86 }
87
88 let total_ok = newly_configured.len() + already_configured.len();
89 if total_ok == 0 && errors.is_empty() {
90 terminal_ui::print_status_warn(
91 "No AI tools detected. Install one and re-run: lean-ctx setup",
92 );
93 }
94
95 if !not_installed.is_empty() {
96 println!(
97 " \x1b[2m○ {} not detected: {}\x1b[0m",
98 not_installed.len(),
99 not_installed.join(", ")
100 );
101 }
102
103 terminal_ui::print_step_header(3, 5, "Agent Rules");
105 let rules_result = crate::rules_inject::inject_all_rules(&home);
106 for name in &rules_result.injected {
107 terminal_ui::print_status_new(&format!("{name:<20} \x1b[2mrules injected\x1b[0m"));
108 }
109 for name in &rules_result.updated {
110 terminal_ui::print_status_new(&format!("{name:<20} \x1b[2mrules updated\x1b[0m"));
111 }
112 for name in &rules_result.already {
113 terminal_ui::print_status_ok(&format!("{name:<20} \x1b[2mrules up-to-date\x1b[0m"));
114 }
115 for err in &rules_result.errors {
116 terminal_ui::print_status_warn(err);
117 }
118 if rules_result.injected.is_empty()
119 && rules_result.updated.is_empty()
120 && rules_result.already.is_empty()
121 && rules_result.errors.is_empty()
122 {
123 terminal_ui::print_status_skip("No agent rules needed");
124 }
125
126 for target in &targets {
128 if !target.detect_path.exists() || target.agent_key.is_empty() {
129 continue;
130 }
131 crate::hooks::install_agent_hook(target.agent_key, true);
132 }
133
134 terminal_ui::print_step_header(4, 5, "Environment Check");
136 let lean_dir = home.join(".lean-ctx");
137 if !lean_dir.exists() {
138 let _ = std::fs::create_dir_all(&lean_dir);
139 terminal_ui::print_status_new("Created ~/.lean-ctx/");
140 } else {
141 terminal_ui::print_status_ok("~/.lean-ctx/ ready");
142 }
143 crate::doctor::run();
144
145 terminal_ui::print_step_header(5, 5, "Help Improve lean-ctx");
147 println!(" Share anonymous compression stats to make lean-ctx better.");
148 println!(" \x1b[1mNo code, no file names, no personal data — ever.\x1b[0m");
149 println!();
150 print!(" Enable anonymous data sharing? \x1b[1m[Y/n]\x1b[0m ");
151 use std::io::Write;
152 std::io::stdout().flush().ok();
153
154 let mut input = String::new();
155 let contribute = if std::io::stdin().read_line(&mut input).is_ok() {
156 let answer = input.trim().to_lowercase();
157 answer.is_empty() || answer == "y" || answer == "yes"
158 } else {
159 false
160 };
161
162 if contribute {
163 let config_dir = home.join(".lean-ctx");
164 let _ = std::fs::create_dir_all(&config_dir);
165 let config_path = config_dir.join("config.toml");
166 let mut config_content = std::fs::read_to_string(&config_path).unwrap_or_default();
167 if !config_content.contains("[cloud]") {
168 if !config_content.is_empty() && !config_content.ends_with('\n') {
169 config_content.push('\n');
170 }
171 config_content.push_str("\n[cloud]\ncontribute_enabled = true\n");
172 let _ = std::fs::write(&config_path, config_content);
173 }
174 terminal_ui::print_status_ok("Enabled — thank you!");
175 } else {
176 terminal_ui::print_status_skip("Skipped — enable later with: lean-ctx config");
177 }
178
179 println!();
181 println!(
182 " \x1b[1;32m✓ Setup complete!\x1b[0m \x1b[1m{}\x1b[0m configured, \x1b[2m{} already set, {} skipped\x1b[0m",
183 newly_configured.len(),
184 already_configured.len(),
185 not_installed.len()
186 );
187
188 if !errors.is_empty() {
189 println!(
190 " \x1b[33m⚠ {} error{}: {}\x1b[0m",
191 errors.len(),
192 if errors.len() != 1 { "s" } else { "" },
193 errors.join(", ")
194 );
195 }
196
197 let shell = std::env::var("SHELL").unwrap_or_default();
199 let source_cmd = if shell.contains("zsh") {
200 "source ~/.zshrc"
201 } else if shell.contains("fish") {
202 "source ~/.config/fish/config.fish"
203 } else if shell.contains("bash") {
204 "source ~/.bashrc"
205 } else {
206 "Restart your shell"
207 };
208
209 let dim = "\x1b[2m";
210 let bold = "\x1b[1m";
211 let cyan = "\x1b[36m";
212 let yellow = "\x1b[33m";
213 let rst = "\x1b[0m";
214
215 println!();
216 println!(" {bold}Next steps:{rst}");
217 println!();
218 println!(" {cyan}1.{rst} Reload your shell:");
219 println!(" {bold}{source_cmd}{rst}");
220 println!();
221
222 let mut tools_to_restart: Vec<String> =
223 newly_configured.iter().map(|s| s.to_string()).collect();
224 for name in rules_result
225 .injected
226 .iter()
227 .chain(rules_result.updated.iter())
228 {
229 if !tools_to_restart.iter().any(|t| t == name) {
230 tools_to_restart.push(name.clone());
231 }
232 }
233
234 if !tools_to_restart.is_empty() {
235 println!(" {cyan}2.{rst} {yellow}{bold}Restart your IDE / AI tool:{rst}");
236 println!(" {bold}{}{rst}", tools_to_restart.join(", "));
237 println!(
238 " {dim}The MCP connection must be re-established for changes to take effect.{rst}"
239 );
240 println!(" {dim}Close and re-open the application completely.{rst}");
241 } else if !already_configured.is_empty() {
242 println!(
243 " {cyan}2.{rst} {dim}Your tools are already configured — no restart needed.{rst}"
244 );
245 }
246
247 println!();
248 println!(
249 " {dim}After restart, lean-ctx will automatically optimize every AI interaction.{rst}"
250 );
251 println!(" {dim}Verify with:{rst} {bold}lean-ctx gain{rst}");
252
253 println!();
255 terminal_ui::print_logo_animated();
256 terminal_ui::print_command_box();
257}
258
259fn shorten_path(path: &str, home: &str) -> String {
260 if let Some(stripped) = path.strip_prefix(home) {
261 format!("~{stripped}")
262 } else {
263 path.to_string()
264 }
265}
266
267fn build_targets(home: &std::path::Path, _binary: &str) -> Vec<EditorTarget> {
268 vec![
269 EditorTarget {
270 name: "Cursor",
271 agent_key: "cursor",
272 config_path: home.join(".cursor/mcp.json"),
273 detect_path: home.join(".cursor"),
274 config_type: ConfigType::McpJson,
275 },
276 EditorTarget {
277 name: "Claude Code",
278 agent_key: "claude",
279 config_path: home.join(".claude.json"),
280 detect_path: detect_claude_path(),
281 config_type: ConfigType::McpJson,
282 },
283 EditorTarget {
284 name: "Windsurf",
285 agent_key: "windsurf",
286 config_path: home.join(".codeium/windsurf/mcp_config.json"),
287 detect_path: home.join(".codeium/windsurf"),
288 config_type: ConfigType::McpJson,
289 },
290 EditorTarget {
291 name: "Codex CLI",
292 agent_key: "codex",
293 config_path: home.join(".codex/config.toml"),
294 detect_path: detect_codex_path(home),
295 config_type: ConfigType::Codex,
296 },
297 EditorTarget {
298 name: "Gemini CLI",
299 agent_key: "gemini",
300 config_path: home.join(".gemini/settings/mcp.json"),
301 detect_path: home.join(".gemini"),
302 config_type: ConfigType::McpJson,
303 },
304 EditorTarget {
305 name: "Antigravity",
306 agent_key: "gemini",
307 config_path: home.join(".gemini/antigravity/mcp_config.json"),
308 detect_path: home.join(".gemini/antigravity"),
309 config_type: ConfigType::McpJson,
310 },
311 EditorTarget {
312 name: "Zed",
313 agent_key: "",
314 config_path: zed_settings_path(home),
315 detect_path: zed_config_dir(home),
316 config_type: ConfigType::Zed,
317 },
318 EditorTarget {
319 name: "VS Code / Copilot",
320 agent_key: "copilot",
321 config_path: vscode_mcp_path(),
322 detect_path: detect_vscode_path(),
323 config_type: ConfigType::VsCodeMcp,
324 },
325 EditorTarget {
326 name: "OpenCode",
327 agent_key: "",
328 config_path: home.join(".config/opencode/opencode.json"),
329 detect_path: home.join(".config/opencode"),
330 config_type: ConfigType::OpenCode,
331 },
332 EditorTarget {
333 name: "Qwen Code",
334 agent_key: "qwen",
335 config_path: home.join(".qwen/mcp.json"),
336 detect_path: home.join(".qwen"),
337 config_type: ConfigType::McpJson,
338 },
339 EditorTarget {
340 name: "Trae",
341 agent_key: "trae",
342 config_path: home.join(".trae/mcp.json"),
343 detect_path: home.join(".trae"),
344 config_type: ConfigType::McpJson,
345 },
346 EditorTarget {
347 name: "Amazon Q Developer",
348 agent_key: "amazonq",
349 config_path: home.join(".aws/amazonq/mcp.json"),
350 detect_path: home.join(".aws/amazonq"),
351 config_type: ConfigType::McpJson,
352 },
353 EditorTarget {
354 name: "JetBrains IDEs",
355 agent_key: "jetbrains",
356 config_path: home.join(".jb-mcp.json"),
357 detect_path: detect_jetbrains_path(home),
358 config_type: ConfigType::McpJson,
359 },
360 EditorTarget {
361 name: "Cline",
362 agent_key: "cline",
363 config_path: cline_mcp_path(),
364 detect_path: detect_cline_path(),
365 config_type: ConfigType::McpJson,
366 },
367 EditorTarget {
368 name: "Roo Code",
369 agent_key: "roo",
370 config_path: roo_mcp_path(),
371 detect_path: detect_roo_path(),
372 config_type: ConfigType::McpJson,
373 },
374 EditorTarget {
375 name: "AWS Kiro",
376 agent_key: "kiro",
377 config_path: home.join(".kiro/settings/mcp.json"),
378 detect_path: home.join(".kiro"),
379 config_type: ConfigType::McpJson,
380 },
381 EditorTarget {
382 name: "Verdent",
383 agent_key: "verdent",
384 config_path: home.join(".verdent/mcp.json"),
385 detect_path: home.join(".verdent"),
386 config_type: ConfigType::McpJson,
387 },
388 ]
389}
390
391fn detect_claude_path() -> PathBuf {
392 if let Ok(output) = std::process::Command::new("which").arg("claude").output() {
393 if output.status.success() {
394 return PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
395 }
396 }
397 if let Some(home) = dirs::home_dir() {
398 let claude_json = home.join(".claude.json");
399 if claude_json.exists() {
400 return claude_json;
401 }
402 }
403 PathBuf::from("/nonexistent")
404}
405
406fn detect_codex_path(home: &std::path::Path) -> PathBuf {
407 let codex_dir = home.join(".codex");
408 if codex_dir.exists() {
409 return codex_dir;
410 }
411 if let Ok(output) = std::process::Command::new("which").arg("codex").output() {
412 if output.status.success() {
413 return codex_dir;
414 }
415 }
416 PathBuf::from("/nonexistent")
417}
418
419fn zed_settings_path(home: &std::path::Path) -> PathBuf {
420 if cfg!(target_os = "macos") {
421 home.join("Library/Application Support/Zed/settings.json")
422 } else {
423 home.join(".config/zed/settings.json")
424 }
425}
426
427fn zed_config_dir(home: &std::path::Path) -> PathBuf {
428 if cfg!(target_os = "macos") {
429 home.join("Library/Application Support/Zed")
430 } else {
431 home.join(".config/zed")
432 }
433}
434
435fn write_config(target: &EditorTarget, binary: &str) -> Result<(), String> {
436 if let Some(parent) = target.config_path.parent() {
437 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
438 }
439
440 match target.config_type {
441 ConfigType::McpJson => write_mcp_json(target, binary),
442 ConfigType::Zed => write_zed_config(target, binary),
443 ConfigType::Codex => write_codex_config(target, binary),
444 ConfigType::VsCodeMcp => write_vscode_mcp(target, binary),
445 ConfigType::OpenCode => write_opencode_config(target, binary),
446 }
447}
448
449fn lean_ctx_server_entry(binary: &str) -> serde_json::Value {
450 serde_json::json!({
451 "command": binary,
452 "autoApprove": [
453 "ctx_read", "ctx_shell", "ctx_search", "ctx_tree",
454 "ctx_overview", "ctx_compress", "ctx_metrics", "ctx_session",
455 "ctx_knowledge", "ctx_agent", "ctx_analyze", "ctx_benchmark",
456 "ctx_cache", "ctx_discover", "ctx_smart_read", "ctx_delta",
457 "ctx_dedup", "ctx_fill", "ctx_intent", "ctx_response",
458 "ctx_context", "ctx_graph", "ctx_wrapped", "ctx_multi_read",
459 "ctx_semantic_search", "ctx"
460 ]
461 })
462}
463
464fn write_mcp_json(target: &EditorTarget, binary: &str) -> Result<(), String> {
465 if target.config_path.exists() {
466 let content = std::fs::read_to_string(&target.config_path).map_err(|e| e.to_string())?;
467
468 if content.contains("lean-ctx") {
469 return Ok(());
470 }
471
472 if let Ok(mut json) = serde_json::from_str::<serde_json::Value>(&content) {
473 if let Some(obj) = json.as_object_mut() {
474 let servers = obj
475 .entry("mcpServers")
476 .or_insert_with(|| serde_json::json!({}));
477 if let Some(servers_obj) = servers.as_object_mut() {
478 servers_obj.insert("lean-ctx".to_string(), lean_ctx_server_entry(binary));
479 }
480 let formatted = serde_json::to_string_pretty(&json).map_err(|e| e.to_string())?;
481 std::fs::write(&target.config_path, formatted).map_err(|e| e.to_string())?;
482 return Ok(());
483 }
484 }
485 return Err(format!(
486 "Could not parse existing config at {}. Please add lean-ctx manually:\n\
487 Add to \"mcpServers\": \"lean-ctx\": {{ \"command\": \"{}\" }}",
488 target.config_path.display(),
489 binary
490 ));
491 }
492
493 let content = serde_json::to_string_pretty(&serde_json::json!({
494 "mcpServers": {
495 "lean-ctx": lean_ctx_server_entry(binary)
496 }
497 }))
498 .map_err(|e| e.to_string())?;
499
500 std::fs::write(&target.config_path, content).map_err(|e| e.to_string())
501}
502
503fn write_zed_config(target: &EditorTarget, binary: &str) -> Result<(), String> {
504 if target.config_path.exists() {
505 let content = std::fs::read_to_string(&target.config_path).map_err(|e| e.to_string())?;
506
507 if content.contains("lean-ctx") {
508 return Ok(());
509 }
510
511 if let Ok(mut json) = serde_json::from_str::<serde_json::Value>(&content) {
512 if let Some(obj) = json.as_object_mut() {
513 let servers = obj
514 .entry("context_servers")
515 .or_insert_with(|| serde_json::json!({}));
516 if let Some(servers_obj) = servers.as_object_mut() {
517 servers_obj.insert(
518 "lean-ctx".to_string(),
519 serde_json::json!({
520 "source": "custom",
521 "command": binary,
522 "args": [],
523 "env": {}
524 }),
525 );
526 }
527 let formatted = serde_json::to_string_pretty(&json).map_err(|e| e.to_string())?;
528 std::fs::write(&target.config_path, formatted).map_err(|e| e.to_string())?;
529 return Ok(());
530 }
531 }
532 return Err(format!(
533 "Could not parse existing config at {}. Please add lean-ctx manually to \"context_servers\".",
534 target.config_path.display()
535 ));
536 }
537
538 let content = serde_json::to_string_pretty(&serde_json::json!({
539 "context_servers": {
540 "lean-ctx": {
541 "source": "custom",
542 "command": binary,
543 "args": [],
544 "env": {}
545 }
546 }
547 }))
548 .map_err(|e| e.to_string())?;
549
550 std::fs::write(&target.config_path, content).map_err(|e| e.to_string())
551}
552
553fn write_codex_config(target: &EditorTarget, binary: &str) -> Result<(), String> {
554 if target.config_path.exists() {
555 let content = std::fs::read_to_string(&target.config_path).map_err(|e| e.to_string())?;
556
557 if content.contains("lean-ctx") {
558 return Ok(());
559 }
560
561 let mut new_content = content.clone();
562 if !new_content.ends_with('\n') {
563 new_content.push('\n');
564 }
565 new_content.push_str(&format!(
566 "\n[mcp_servers.lean-ctx]\ncommand = \"{}\"\nargs = []\n",
567 binary
568 ));
569 std::fs::write(&target.config_path, new_content).map_err(|e| e.to_string())?;
570 return Ok(());
571 }
572
573 let content = format!(
574 "[mcp_servers.lean-ctx]\ncommand = \"{}\"\nargs = []\n",
575 binary
576 );
577 std::fs::write(&target.config_path, content).map_err(|e| e.to_string())
578}
579
580fn write_vscode_mcp(target: &EditorTarget, binary: &str) -> Result<(), String> {
581 if target.config_path.exists() {
582 let content = std::fs::read_to_string(&target.config_path).map_err(|e| e.to_string())?;
583 if content.contains("lean-ctx") {
584 return Ok(());
585 }
586 if let Ok(mut json) = serde_json::from_str::<serde_json::Value>(&content) {
587 if let Some(obj) = json.as_object_mut() {
588 let servers = obj
589 .entry("servers")
590 .or_insert_with(|| serde_json::json!({}));
591 if let Some(servers_obj) = servers.as_object_mut() {
592 servers_obj.insert(
593 "lean-ctx".to_string(),
594 serde_json::json!({ "command": binary, "args": [] }),
595 );
596 }
597 let formatted = serde_json::to_string_pretty(&json).map_err(|e| e.to_string())?;
598 std::fs::write(&target.config_path, formatted).map_err(|e| e.to_string())?;
599 return Ok(());
600 }
601 }
602 return Err(format!(
603 "Could not parse existing config at {}. Please add lean-ctx manually to \"servers\".",
604 target.config_path.display()
605 ));
606 }
607
608 if let Some(parent) = target.config_path.parent() {
609 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
610 }
611
612 let content = serde_json::to_string_pretty(&serde_json::json!({
613 "servers": {
614 "lean-ctx": {
615 "command": binary,
616 "args": []
617 }
618 }
619 }))
620 .map_err(|e| e.to_string())?;
621
622 std::fs::write(&target.config_path, content).map_err(|e| e.to_string())
623}
624
625fn write_opencode_config(target: &EditorTarget, binary: &str) -> Result<(), String> {
626 if target.config_path.exists() {
627 let content = std::fs::read_to_string(&target.config_path).map_err(|e| e.to_string())?;
628 if content.contains("lean-ctx") {
629 return Ok(());
630 }
631 if let Ok(mut json) = serde_json::from_str::<serde_json::Value>(&content) {
632 if let Some(obj) = json.as_object_mut() {
633 let mcp = obj.entry("mcp").or_insert_with(|| serde_json::json!({}));
634 if let Some(mcp_obj) = mcp.as_object_mut() {
635 mcp_obj.insert(
636 "lean-ctx".to_string(),
637 serde_json::json!({
638 "type": "local",
639 "command": [binary],
640 "enabled": true
641 }),
642 );
643 }
644 let formatted = serde_json::to_string_pretty(&json).map_err(|e| e.to_string())?;
645 std::fs::write(&target.config_path, formatted).map_err(|e| e.to_string())?;
646 return Ok(());
647 }
648 }
649 return Err(format!(
650 "Could not parse existing config at {}. Please add lean-ctx manually:\n\
651 Add to the \"mcp\" section: \"lean-ctx\": {{ \"type\": \"local\", \"command\": [\"{}\"], \"enabled\": true }}",
652 target.config_path.display(),
653 binary
654 ));
655 }
656
657 if let Some(parent) = target.config_path.parent() {
658 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
659 }
660
661 let content = serde_json::to_string_pretty(&serde_json::json!({
662 "$schema": "https://opencode.ai/config.json",
663 "mcp": {
664 "lean-ctx": {
665 "type": "local",
666 "command": [binary],
667 "enabled": true
668 }
669 }
670 }))
671 .map_err(|e| e.to_string())?;
672
673 std::fs::write(&target.config_path, content).map_err(|e| e.to_string())
674}
675
676fn detect_vscode_path() -> PathBuf {
677 #[cfg(target_os = "macos")]
678 {
679 if let Some(home) = dirs::home_dir() {
680 let vscode = home.join("Library/Application Support/Code/User/settings.json");
681 if vscode.exists() {
682 return vscode;
683 }
684 }
685 }
686 #[cfg(target_os = "linux")]
687 {
688 if let Some(home) = dirs::home_dir() {
689 let vscode = home.join(".config/Code/User/settings.json");
690 if vscode.exists() {
691 return vscode;
692 }
693 }
694 }
695 #[cfg(target_os = "windows")]
696 {
697 if let Ok(appdata) = std::env::var("APPDATA") {
698 let vscode = PathBuf::from(appdata).join("Code/User/settings.json");
699 if vscode.exists() {
700 return vscode;
701 }
702 }
703 }
704 if let Ok(output) = std::process::Command::new("which").arg("code").output() {
705 if output.status.success() {
706 return PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
707 }
708 }
709 PathBuf::from("/nonexistent")
710}
711
712fn vscode_mcp_path() -> PathBuf {
713 if let Some(home) = dirs::home_dir() {
714 #[cfg(target_os = "macos")]
715 {
716 return home.join("Library/Application Support/Code/User/mcp.json");
717 }
718 #[cfg(target_os = "linux")]
719 {
720 return home.join(".config/Code/User/mcp.json");
721 }
722 #[cfg(target_os = "windows")]
723 {
724 if let Ok(appdata) = std::env::var("APPDATA") {
725 return PathBuf::from(appdata).join("Code/User/mcp.json");
726 }
727 }
728 #[allow(unreachable_code)]
729 home.join(".config/Code/User/mcp.json")
730 } else {
731 PathBuf::from("/nonexistent")
732 }
733}
734
735fn detect_jetbrains_path(home: &std::path::Path) -> PathBuf {
736 #[cfg(target_os = "macos")]
737 {
738 let lib = home.join("Library/Application Support/JetBrains");
739 if lib.exists() {
740 return lib;
741 }
742 }
743 #[cfg(target_os = "linux")]
744 {
745 let cfg = home.join(".config/JetBrains");
746 if cfg.exists() {
747 return cfg;
748 }
749 }
750 if home.join(".jb-mcp.json").exists() {
751 return home.join(".jb-mcp.json");
752 }
753 PathBuf::from("/nonexistent")
754}
755
756fn cline_mcp_path() -> PathBuf {
757 if let Some(home) = dirs::home_dir() {
758 #[cfg(target_os = "macos")]
759 {
760 return home.join("Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
761 }
762 #[cfg(target_os = "linux")]
763 {
764 return home.join(".config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
765 }
766 #[cfg(target_os = "windows")]
767 {
768 if let Ok(appdata) = std::env::var("APPDATA") {
769 return PathBuf::from(appdata).join("Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
770 }
771 }
772 }
773 PathBuf::from("/nonexistent")
774}
775
776fn detect_cline_path() -> PathBuf {
777 if let Some(home) = dirs::home_dir() {
778 #[cfg(target_os = "macos")]
779 {
780 let p = home
781 .join("Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev");
782 if p.exists() {
783 return p;
784 }
785 }
786 #[cfg(target_os = "linux")]
787 {
788 let p = home.join(".config/Code/User/globalStorage/saoudrizwan.claude-dev");
789 if p.exists() {
790 return p;
791 }
792 }
793 }
794 PathBuf::from("/nonexistent")
795}
796
797fn roo_mcp_path() -> PathBuf {
798 if let Some(home) = dirs::home_dir() {
799 #[cfg(target_os = "macos")]
800 {
801 return home.join("Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
802 }
803 #[cfg(target_os = "linux")]
804 {
805 return home.join(".config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
806 }
807 #[cfg(target_os = "windows")]
808 {
809 if let Ok(appdata) = std::env::var("APPDATA") {
810 return PathBuf::from(appdata).join("Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
811 }
812 }
813 }
814 PathBuf::from("/nonexistent")
815}
816
817fn detect_roo_path() -> PathBuf {
818 if let Some(home) = dirs::home_dir() {
819 #[cfg(target_os = "macos")]
820 {
821 let p = home.join(
822 "Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline",
823 );
824 if p.exists() {
825 return p;
826 }
827 }
828 #[cfg(target_os = "linux")]
829 {
830 let p = home.join(".config/Code/User/globalStorage/rooveterinaryinc.roo-cline");
831 if p.exists() {
832 return p;
833 }
834 }
835 }
836 PathBuf::from("/nonexistent")
837}