Skip to main content

lean_ctx/core/editor_registry/
paths.rs

1use std::path::{Path, PathBuf};
2
3pub fn zed_settings_path(home: &std::path::Path) -> PathBuf {
4    if cfg!(target_os = "macos") {
5        home.join("Library/Application Support/Zed/settings.json")
6    } else {
7        home.join(".config/zed/settings.json")
8    }
9}
10
11pub fn zed_config_dir(home: &std::path::Path) -> PathBuf {
12    if cfg!(target_os = "macos") {
13        home.join("Library/Application Support/Zed")
14    } else {
15        home.join(".config/zed")
16    }
17}
18
19pub fn vscode_mcp_path() -> PathBuf {
20    if let Some(home) = dirs::home_dir() {
21        #[cfg(target_os = "macos")]
22        {
23            return home.join("Library/Application Support/Code/User/mcp.json");
24        }
25        #[cfg(target_os = "linux")]
26        {
27            return home.join(".config/Code/User/mcp.json");
28        }
29        #[cfg(target_os = "windows")]
30        {
31            if let Ok(appdata) = std::env::var("APPDATA") {
32                return PathBuf::from(appdata).join("Code/User/mcp.json");
33            }
34        }
35        #[allow(unreachable_code)]
36        home.join(".config/Code/User/mcp.json")
37    } else {
38        PathBuf::from("/nonexistent")
39    }
40}
41
42pub fn qoder_mcp_path(home: &Path) -> PathBuf {
43    #[cfg(target_os = "windows")]
44    {
45        if let Ok(appdata) = std::env::var("APPDATA") {
46            return PathBuf::from(appdata)
47                .join("Qoder")
48                .join("SharedClientCache")
49                .join("mcp.json");
50        }
51    }
52    home.join(".qoder").join("mcp.json")
53}
54
55#[cfg(target_os = "macos")]
56pub fn qoder_mcp_paths(home: &Path) -> Vec<PathBuf> {
57    let mut paths = vec![qoder_mcp_path(home)];
58    paths.push(home.join("Library/Application Support/Qoder/User/mcp.json"));
59    paths.push(home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"));
60    paths
61}
62
63#[cfg(not(target_os = "macos"))]
64pub fn qoder_mcp_paths(home: &Path) -> Vec<PathBuf> {
65    vec![qoder_mcp_path(home)]
66}
67
68#[allow(unreachable_code)]
69pub fn cline_mcp_path() -> PathBuf {
70    #[cfg(target_os = "windows")]
71    {
72        if let Ok(appdata) = std::env::var("APPDATA") {
73            return PathBuf::from(appdata).join(
74                "Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
75            );
76        }
77        return PathBuf::from("/nonexistent");
78    }
79
80    let Some(home) = dirs::home_dir() else {
81        return PathBuf::from("/nonexistent");
82    };
83    #[cfg(target_os = "macos")]
84    {
85        return home.join("Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
86    }
87    #[cfg(target_os = "linux")]
88    {
89        return home.join(".config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json");
90    }
91    PathBuf::from("/nonexistent")
92}
93
94#[allow(unreachable_code)]
95pub fn roo_mcp_path() -> PathBuf {
96    #[cfg(target_os = "windows")]
97    {
98        if let Ok(appdata) = std::env::var("APPDATA") {
99            return PathBuf::from(appdata)
100                .join("Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
101        }
102        return PathBuf::from("/nonexistent");
103    }
104
105    let Some(home) = dirs::home_dir() else {
106        return PathBuf::from("/nonexistent");
107    };
108    #[cfg(target_os = "macos")]
109    {
110        return home.join("Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
111    }
112    #[cfg(target_os = "linux")]
113    {
114        return home.join(".config/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/cline_mcp_settings.json");
115    }
116    PathBuf::from("/nonexistent")
117}
118
119pub fn qoder_settings_path(home: &Path) -> PathBuf {
120    #[cfg(target_os = "windows")]
121    {
122        if let Ok(appdata) = std::env::var("APPDATA") {
123            return PathBuf::from(appdata)
124                .join("Qoder")
125                .join("SharedClientCache")
126                .join("mcp.json");
127        }
128    }
129    home.join(".qoder/mcp.json")
130}
131
132pub fn qoder_all_mcp_paths(home: &Path) -> Vec<PathBuf> {
133    let paths = vec![qoder_settings_path(home)];
134    #[cfg(target_os = "macos")]
135    let paths = {
136        let mut paths = paths;
137        paths.push(home.join("Library/Application Support/Qoder/User/mcp.json"));
138        paths.push(home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"));
139        paths
140    };
141    paths
142}
143
144pub fn qoderwork_mcp_path(home: &Path) -> PathBuf {
145    home.join(".qoderwork/mcp.json")
146}
147
148pub fn claude_mcp_json_path(home: &Path) -> PathBuf {
149    if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
150        let dir = dir.trim();
151        if !dir.is_empty() {
152            return PathBuf::from(dir).join(".claude.json");
153        }
154    }
155    home.join(".claude.json")
156}
157
158pub fn claude_state_dir(home: &Path) -> PathBuf {
159    if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
160        let dir = dir.trim();
161        if !dir.is_empty() {
162            return PathBuf::from(dir);
163        }
164    }
165    home.join(".claude")
166}
167
168pub fn claude_rules_dir(home: &Path) -> PathBuf {
169    claude_state_dir(home).join("rules")
170}
171
172pub fn augment_cli_settings_path(home: &Path) -> PathBuf {
173    home.join(".augment/settings.json")
174}
175
176/// MCP server list for the Augment VS Code extension.
177///
178/// The extension persists registered MCP servers as a top-level JSON array in
179/// its globalStorage directory. Confirmed empirically against
180/// `augment.vscode-augment` build shipped on 2026-05-21 (see PR description).
181///
182/// On Windows the User dir lives under `%APPDATA%/Code` rather than the
183/// user's home, so we honour that when the env var is set; we fall back to
184/// the home-relative path for tests and unusual setups.
185pub fn augment_vscode_mcp_path(home: &Path) -> PathBuf {
186    const TAIL: &str = "globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json";
187
188    #[cfg(target_os = "macos")]
189    {
190        return home
191            .join("Library/Application Support/Code/User")
192            .join(TAIL);
193    }
194    #[cfg(target_os = "linux")]
195    {
196        return home.join(".config/Code/User").join(TAIL);
197    }
198    #[cfg(target_os = "windows")]
199    {
200        if let Ok(appdata) = std::env::var("APPDATA") {
201            return PathBuf::from(appdata).join("Code/User").join(TAIL);
202        }
203    }
204    #[allow(unreachable_code)]
205    home.join(".config/Code/User").join(TAIL)
206}
207
208#[cfg(test)]
209mod augment_tests {
210    use super::*;
211
212    #[test]
213    fn augment_cli_settings_path_is_under_dot_augment() {
214        let home = Path::new("/home/tester");
215        assert_eq!(
216            augment_cli_settings_path(home),
217            home.join(".augment").join("settings.json")
218        );
219    }
220
221    #[test]
222    #[cfg(target_os = "linux")]
223    fn augment_vscode_mcp_path_uses_linux_globalstorage() {
224        let home = Path::new("/home/tester");
225        assert_eq!(
226            augment_vscode_mcp_path(home),
227            home.join(".config/Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json")
228        );
229    }
230
231    #[test]
232    #[cfg(target_os = "macos")]
233    fn augment_vscode_mcp_path_uses_macos_application_support() {
234        let home = Path::new("/Users/tester");
235        assert_eq!(
236            augment_vscode_mcp_path(home),
237            home.join("Library/Application Support/Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json")
238        );
239    }
240
241    /// On Windows we honour `%APPDATA%` when set, falling back to a
242    /// home-relative path only when it is missing. We can't reliably mutate
243    /// process-wide env vars in a parallel test runner, so this test only
244    /// asserts the invariant tail (which is platform-agnostic) and that the
245    /// final segment is the expected file name. Both branches share that tail.
246    #[test]
247    #[cfg(target_os = "windows")]
248    fn augment_vscode_mcp_path_ends_with_globalstorage_tail() {
249        let home = Path::new("C:/Users/tester");
250        let path = augment_vscode_mcp_path(home);
251        let s = path.to_string_lossy().replace('\\', "/");
252        assert!(
253            s.ends_with(
254                "Code/User/globalStorage/augment.vscode-augment/augment-global-state/mcpServers.json"
255            ),
256            "unexpected windows path: {s}"
257        );
258    }
259}
260
261#[cfg(all(test, target_os = "macos"))]
262mod tests {
263    use super::*;
264
265    #[test]
266    #[cfg(target_os = "macos")]
267    fn qoder_mcp_paths_include_macos_user_and_shared_cache_locations() {
268        let home = Path::new("/Users/tester");
269        let paths = qoder_mcp_paths(home);
270
271        assert_eq!(
272            paths,
273            vec![
274                home.join(".qoder/mcp.json"),
275                home.join("Library/Application Support/Qoder/User/mcp.json"),
276                home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"),
277            ]
278        );
279    }
280}