Skip to main content

lean_ctx/core/editor_registry/
detect.rs

1use std::path::{Path, PathBuf};
2
3use super::paths::{
4    augment_cli_settings_path, augment_vscode_mcp_path, claude_mcp_json_path, cline_mcp_path,
5    qoder_all_mcp_paths, qoderwork_mcp_path, roo_mcp_path, vscode_mcp_path, zed_config_dir,
6    zed_settings_path,
7};
8use super::types::{ConfigType, EditorTarget};
9
10pub fn build_targets(home: &Path) -> Vec<EditorTarget> {
11    #[cfg(windows)]
12    let opencode_cfg = if let Ok(appdata) = std::env::var("APPDATA") {
13        PathBuf::from(appdata)
14            .join("opencode")
15            .join("opencode.json")
16    } else {
17        home.join(".config/opencode/opencode.json")
18    };
19    #[cfg(not(windows))]
20    let opencode_cfg = home.join(".config/opencode/opencode.json");
21
22    #[cfg(windows)]
23    let opencode_detect = opencode_cfg
24        .parent()
25        .map(|p| p.to_path_buf())
26        .unwrap_or_else(|| home.join(".config/opencode"));
27    #[cfg(not(windows))]
28    let opencode_detect = home.join(".config/opencode");
29
30    let mut targets = vec![
31        EditorTarget {
32            name: "Cursor",
33            agent_key: "cursor".to_string(),
34            config_path: home.join(".cursor/mcp.json"),
35            detect_path: home.join(".cursor"),
36            config_type: ConfigType::McpJson,
37        },
38        EditorTarget {
39            name: "Claude Code",
40            agent_key: "claude".to_string(),
41            config_path: claude_mcp_json_path(home),
42            detect_path: detect_claude_path(),
43            config_type: ConfigType::McpJson,
44        },
45        EditorTarget {
46            name: "Augment CLI",
47            agent_key: "augment".to_string(),
48            config_path: augment_cli_settings_path(home),
49            detect_path: detect_augment_path(home),
50            config_type: ConfigType::McpJson,
51        },
52        EditorTarget {
53            name: "Augment (VS Code)",
54            agent_key: "augment".to_string(),
55            config_path: augment_vscode_mcp_path(home),
56            detect_path: detect_augment_vscode_path(home),
57            config_type: ConfigType::AugmentVsCode,
58        },
59        EditorTarget {
60            name: "Windsurf",
61            agent_key: "windsurf".to_string(),
62            config_path: home.join(".codeium/windsurf/mcp_config.json"),
63            detect_path: home.join(".codeium/windsurf"),
64            config_type: ConfigType::McpJson,
65        },
66        EditorTarget {
67            name: "Codex CLI",
68            agent_key: "codex".to_string(),
69            config_path: crate::core::home::resolve_codex_dir()
70                .unwrap_or_else(|| home.join(".codex"))
71                .join("config.toml"),
72            detect_path: detect_codex_path(home),
73            config_type: ConfigType::Codex,
74        },
75        EditorTarget {
76            name: "Gemini CLI",
77            agent_key: "gemini".to_string(),
78            config_path: home.join(".gemini/settings.json"),
79            detect_path: home.join(".gemini"),
80            config_type: ConfigType::GeminiSettings,
81        },
82        EditorTarget {
83            name: "Antigravity IDE",
84            agent_key: "antigravity".to_string(),
85            config_path: home.join(".gemini/antigravity/mcp_config.json"),
86            detect_path: home.join(".gemini/antigravity"),
87            config_type: ConfigType::McpJson,
88        },
89        EditorTarget {
90            name: "Antigravity CLI",
91            agent_key: "antigravity-cli".to_string(),
92            config_path: home.join(".gemini/antigravity-cli/mcp_config.json"),
93            detect_path: home.join(".gemini/antigravity-cli"),
94            config_type: ConfigType::McpJson,
95        },
96        EditorTarget {
97            name: "Zed",
98            agent_key: "zed".to_string(),
99            config_path: zed_settings_path(home),
100            detect_path: zed_config_dir(home),
101            config_type: ConfigType::Zed,
102        },
103        EditorTarget {
104            name: "VS Code",
105            agent_key: "vscode".to_string(),
106            config_path: vscode_mcp_path(),
107            detect_path: detect_vscode_path(),
108            config_type: ConfigType::VsCodeMcp,
109        },
110        EditorTarget {
111            name: "Copilot CLI",
112            agent_key: "copilot".to_string(),
113            config_path: home.join(".copilot/mcp-config.json"),
114            detect_path: home.join(".copilot"),
115            config_type: ConfigType::CopilotCli,
116        },
117        EditorTarget {
118            name: "OpenCode",
119            agent_key: "opencode".to_string(),
120            config_path: opencode_cfg,
121            detect_path: opencode_detect,
122            config_type: ConfigType::OpenCode,
123        },
124        EditorTarget {
125            name: "Qwen Code",
126            agent_key: "qwen".to_string(),
127            config_path: home.join(".qwen/settings.json"),
128            detect_path: home.join(".qwen"),
129            config_type: ConfigType::McpJson,
130        },
131        EditorTarget {
132            name: "Trae",
133            agent_key: "trae".to_string(),
134            config_path: home.join(".trae/mcp.json"),
135            detect_path: home.join(".trae"),
136            config_type: ConfigType::McpJson,
137        },
138        EditorTarget {
139            name: "Amazon Q Developer",
140            agent_key: "amazonq".to_string(),
141            config_path: home.join(".aws/amazonq/default.json"),
142            detect_path: home.join(".aws/amazonq"),
143            config_type: ConfigType::McpJson,
144        },
145        EditorTarget {
146            name: "JetBrains IDEs",
147            agent_key: "jetbrains".to_string(),
148            config_path: home.join(".jb-mcp.json"),
149            detect_path: detect_jetbrains_path(home),
150            config_type: ConfigType::JetBrains,
151        },
152        EditorTarget {
153            name: "Cline",
154            agent_key: "cline".to_string(),
155            config_path: cline_mcp_path(),
156            detect_path: detect_cline_path(),
157            config_type: ConfigType::McpJson,
158        },
159        EditorTarget {
160            name: "Roo Code",
161            agent_key: "roo".to_string(),
162            config_path: roo_mcp_path(),
163            detect_path: detect_roo_path(),
164            config_type: ConfigType::McpJson,
165        },
166        EditorTarget {
167            name: "AWS Kiro",
168            agent_key: "kiro".to_string(),
169            config_path: home.join(".kiro/settings/mcp.json"),
170            detect_path: home.join(".kiro"),
171            config_type: ConfigType::McpJson,
172        },
173        EditorTarget {
174            name: "Verdent",
175            agent_key: "verdent".to_string(),
176            config_path: home.join(".verdent/mcp.json"),
177            detect_path: home.join(".verdent"),
178            config_type: ConfigType::McpJson,
179        },
180        EditorTarget {
181            name: "Crush",
182            agent_key: "crush".to_string(),
183            config_path: home.join(".config/crush/crush.json"),
184            detect_path: home.join(".config/crush"),
185            config_type: ConfigType::Crush,
186        },
187        EditorTarget {
188            name: "Pi Coding Agent",
189            agent_key: "pi".to_string(),
190            config_path: home.join(".pi/agent/mcp.json"),
191            detect_path: home.join(".pi/agent"),
192            config_type: ConfigType::McpJson,
193        },
194        EditorTarget {
195            name: "Amp",
196            agent_key: "amp".to_string(),
197            config_path: home.join(".config/amp/settings.json"),
198            detect_path: home.join(".config/amp"),
199            config_type: ConfigType::Amp,
200        },
201        EditorTarget {
202            name: "QoderWork",
203            agent_key: "qoderwork".to_string(),
204            config_path: qoderwork_mcp_path(home),
205            detect_path: detect_qoderwork_path(home),
206            config_type: ConfigType::McpJson,
207        },
208        EditorTarget {
209            name: "Hermes Agent",
210            agent_key: "hermes".to_string(),
211            config_path: home.join(".hermes/config.yaml"),
212            detect_path: home.join(".hermes"),
213            config_type: ConfigType::HermesYaml,
214        },
215        EditorTarget {
216            name: "Aider",
217            agent_key: "aider".to_string(),
218            config_path: home.join(".aider/mcp.json"),
219            detect_path: home.join(".aider"),
220            config_type: ConfigType::McpJson,
221        },
222        EditorTarget {
223            name: "Continue",
224            agent_key: "continue".to_string(),
225            config_path: home.join(".continue/mcp.json"),
226            detect_path: home.join(".continue"),
227            config_type: ConfigType::McpJson,
228        },
229        EditorTarget {
230            name: "Neovim (mcphub.nvim)",
231            agent_key: "neovim".to_string(),
232            config_path: home.join(".config/mcphub/servers.json"),
233            detect_path: home.join(".config/nvim"),
234            config_type: ConfigType::McpJson,
235        },
236        EditorTarget {
237            name: "Emacs (mcp.el)",
238            agent_key: "emacs".to_string(),
239            config_path: home.join(".emacs.d/mcp.json"),
240            detect_path: home.join(".emacs.d"),
241            config_type: ConfigType::McpJson,
242        },
243        EditorTarget {
244            name: "Sublime Text",
245            agent_key: "sublime".to_string(),
246            config_path: detect_sublime_mcp_path(home),
247            detect_path: detect_sublime_path(home),
248            config_type: ConfigType::McpJson,
249        },
250        EditorTarget {
251            name: "OpenClaw",
252            agent_key: "openclaw".to_string(),
253            config_path: home.join(".openclaw/openclaw.json"),
254            detect_path: home.join(".openclaw"),
255            config_type: ConfigType::McpJson,
256        },
257    ];
258
259    targets.extend(
260        qoder_all_mcp_paths(home)
261            .into_iter()
262            .map(|config_path| EditorTarget {
263                name: "Qoder",
264                agent_key: "qoder".to_string(),
265                config_path,
266                detect_path: detect_qoder_path(home),
267                config_type: ConfigType::QoderSettings,
268            }),
269    );
270
271    targets
272}
273
274fn detect_qoder_path(home: &Path) -> PathBuf {
275    let qoder_dir = home.join(".qoder");
276    if qoder_dir.exists() {
277        return qoder_dir;
278    }
279    #[cfg(target_os = "macos")]
280    {
281        let app_dir = home.join("Library/Application Support/Qoder");
282        if app_dir.exists() {
283            return app_dir;
284        }
285    }
286    #[cfg(target_os = "windows")]
287    {
288        if let Ok(appdata) = std::env::var("APPDATA") {
289            let app_dir = PathBuf::from(appdata).join("Qoder");
290            if app_dir.exists() {
291                return app_dir;
292            }
293        }
294    }
295    PathBuf::from("/nonexistent")
296}
297
298fn detect_qoderwork_path(home: &Path) -> PathBuf {
299    let dir = home.join(".qoderwork");
300    if dir.exists() {
301        return dir;
302    }
303    #[cfg(target_os = "windows")]
304    {
305        if let Ok(appdata) = std::env::var("APPDATA") {
306            let app_dir = PathBuf::from(appdata).join("QoderWork");
307            if app_dir.exists() {
308                return app_dir;
309            }
310        }
311    }
312    PathBuf::from("/nonexistent")
313}
314
315fn detect_sublime_path(home: &Path) -> PathBuf {
316    #[cfg(target_os = "macos")]
317    {
318        let app_dir = home.join("Library/Application Support/Sublime Text");
319        if app_dir.exists() {
320            return app_dir;
321        }
322    }
323    let xdg_dir = home.join(".config/sublime-text");
324    if xdg_dir.exists() {
325        return xdg_dir;
326    }
327    #[cfg(target_os = "windows")]
328    {
329        if let Ok(appdata) = std::env::var("APPDATA") {
330            let app_dir = PathBuf::from(appdata).join("Sublime Text");
331            if app_dir.exists() {
332                return app_dir;
333            }
334        }
335    }
336    PathBuf::from("/nonexistent")
337}
338
339fn detect_sublime_mcp_path(home: &Path) -> PathBuf {
340    #[cfg(target_os = "macos")]
341    {
342        let app_dir = home.join("Library/Application Support/Sublime Text/Packages/User/mcp.json");
343        if app_dir.parent().is_some_and(std::path::Path::exists) {
344            return app_dir;
345        }
346    }
347    home.join(".config/sublime-text/mcp.json")
348}
349
350pub fn detect_claude_path() -> PathBuf {
351    let which_cmd = if cfg!(windows) { "where" } else { "which" };
352    if let Ok(output) = std::process::Command::new(which_cmd).arg("claude").output() {
353        if output.status.success() {
354            return PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
355        }
356    }
357    if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
358        let dir = dir.trim();
359        if !dir.is_empty() {
360            let p = PathBuf::from(dir);
361            if p.exists() {
362                return p;
363            }
364        }
365    }
366    if let Some(home) = dirs::home_dir() {
367        let claude_json = claude_mcp_json_path(&home);
368        if claude_json.exists() {
369            return claude_json;
370        }
371    }
372    PathBuf::from("/nonexistent")
373}
374
375pub fn detect_augment_path(home: &Path) -> PathBuf {
376    let which_cmd = if cfg!(windows) { "where" } else { "which" };
377    if let Ok(output) = std::process::Command::new(which_cmd).arg("auggie").output() {
378        if output.status.success() {
379            return PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
380        }
381    }
382    let augment_dir = home.join(".augment");
383    if augment_dir.exists() {
384        return augment_dir;
385    }
386    PathBuf::from("/nonexistent")
387}
388
389/// Locate the Augment VS Code extension on disk.
390///
391/// Returns a `PathBuf` that callers use as a "yes/no presence" signal via
392/// `.exists()`. There are three positive-detection paths, in order of
393/// specificity:
394///
395///   1. `mcpServers.json` itself exists → return that file path. Strongest
396///      signal: the user has already configured at least one MCP server.
397///   2. The `augment-global-state/` directory exists (parent-of-parent of
398///      the mcp file) → return that directory. The extension has run at
399///      least once but no MCP servers have been registered yet.
400///   3. The extension's `globalStorage/augment.vscode-augment/` directory
401///      exists → return the (still-nonexistent) `mcpServers.json` path.
402///      The extension is installed but has never written persistent state;
403///      callers will see `.exists() == false` here, which is intentional —
404///      it signals "extension present, file not yet created" so `setup`
405///      can create it.
406///
407/// On no match, returns `/nonexistent` so `.exists()` is unambiguously false.
408pub fn detect_augment_vscode_path(home: &Path) -> PathBuf {
409    let mcp_path = augment_vscode_mcp_path(home);
410    if mcp_path.exists() {
411        return mcp_path;
412    }
413    let extension_state = mcp_path
414        .parent()
415        .and_then(|p| p.parent())
416        .map(Path::to_path_buf);
417    if let Some(path) = extension_state {
418        if path.exists() {
419            return path;
420        }
421    }
422    if detect_extension_installed(home, "augment.vscode-augment") {
423        return mcp_path;
424    }
425    PathBuf::from("/nonexistent")
426}
427
428fn detect_extension_installed(home: &Path, extension_id: &str) -> bool {
429    #[cfg(target_os = "macos")]
430    {
431        if home
432            .join(format!(
433                "Library/Application Support/Code/User/globalStorage/{extension_id}"
434            ))
435            .exists()
436        {
437            return true;
438        }
439    }
440    #[cfg(target_os = "linux")]
441    {
442        if home
443            .join(format!(".config/Code/User/globalStorage/{extension_id}"))
444            .exists()
445        {
446            return true;
447        }
448    }
449    #[cfg(target_os = "windows")]
450    {
451        if let Ok(appdata) = std::env::var("APPDATA") {
452            if PathBuf::from(appdata)
453                .join(format!("Code/User/globalStorage/{extension_id}"))
454                .exists()
455            {
456                return true;
457            }
458        }
459        let _ = home;
460    }
461    false
462}
463
464pub fn detect_codex_path(home: &Path) -> PathBuf {
465    let codex_dir = crate::core::home::resolve_codex_dir().unwrap_or_else(|| home.join(".codex"));
466    if codex_dir.exists() {
467        return codex_dir;
468    }
469    let which_cmd = if cfg!(windows) { "where" } else { "which" };
470    if let Ok(output) = std::process::Command::new(which_cmd).arg("codex").output() {
471        if output.status.success() {
472            return codex_dir;
473        }
474    }
475    PathBuf::from("/nonexistent")
476}
477
478pub fn detect_vscode_path() -> PathBuf {
479    #[cfg(target_os = "macos")]
480    {
481        if let Some(home) = dirs::home_dir() {
482            let vscode = home.join("Library/Application Support/Code/User/settings.json");
483            if vscode.exists() {
484                return vscode;
485            }
486        }
487    }
488    #[cfg(target_os = "linux")]
489    {
490        if let Some(home) = dirs::home_dir() {
491            let vscode = home.join(".config/Code/User/settings.json");
492            if vscode.exists() {
493                return vscode;
494            }
495        }
496    }
497    #[cfg(target_os = "windows")]
498    {
499        if let Ok(appdata) = std::env::var("APPDATA") {
500            let vscode = PathBuf::from(appdata).join("Code/User/settings.json");
501            if vscode.exists() {
502                return vscode;
503            }
504        }
505    }
506    let which_cmd = if cfg!(windows) { "where" } else { "which" };
507    if let Ok(output) = std::process::Command::new(which_cmd).arg("code").output() {
508        if output.status.success() {
509            return PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
510        }
511    }
512    PathBuf::from("/nonexistent")
513}
514
515pub fn detect_jetbrains_path(home: &Path) -> PathBuf {
516    #[cfg(target_os = "macos")]
517    {
518        let lib = home.join("Library/Application Support/JetBrains");
519        if lib.exists() {
520            return lib;
521        }
522    }
523    #[cfg(target_os = "linux")]
524    {
525        let cfg = home.join(".config/JetBrains");
526        if cfg.exists() {
527            return cfg;
528        }
529    }
530    #[cfg(target_os = "windows")]
531    {
532        if let Ok(appdata) = std::env::var("APPDATA") {
533            let jb = std::path::PathBuf::from(appdata).join("JetBrains");
534            if jb.exists() {
535                return jb;
536            }
537        }
538        if let Ok(local) = std::env::var("LOCALAPPDATA") {
539            let jb = std::path::PathBuf::from(local).join("JetBrains");
540            if jb.exists() {
541                return jb;
542            }
543        }
544    }
545    if home.join(".jb-mcp.json").exists() {
546        return home.join(".jb-mcp.json");
547    }
548    PathBuf::from("/nonexistent")
549}
550
551#[allow(unreachable_code)]
552pub fn detect_cline_path() -> PathBuf {
553    #[cfg(target_os = "windows")]
554    {
555        if let Ok(appdata) = std::env::var("APPDATA") {
556            let p = PathBuf::from(appdata).join("Code/User/globalStorage/saoudrizwan.claude-dev");
557            if p.exists() {
558                return p;
559            }
560        }
561        return PathBuf::from("/nonexistent");
562    }
563
564    let Some(home) = dirs::home_dir() else {
565        return PathBuf::from("/nonexistent");
566    };
567    #[cfg(target_os = "macos")]
568    {
569        let p =
570            home.join("Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev");
571        if p.exists() {
572            return p;
573        }
574    }
575    #[cfg(target_os = "linux")]
576    {
577        let p = home.join(".config/Code/User/globalStorage/saoudrizwan.claude-dev");
578        if p.exists() {
579            return p;
580        }
581    }
582    PathBuf::from("/nonexistent")
583}
584
585#[allow(unreachable_code)]
586pub fn detect_roo_path() -> PathBuf {
587    #[cfg(target_os = "windows")]
588    {
589        if let Ok(appdata) = std::env::var("APPDATA") {
590            let p =
591                PathBuf::from(appdata).join("Code/User/globalStorage/rooveterinaryinc.roo-cline");
592            if p.exists() {
593                return p;
594            }
595        }
596        return PathBuf::from("/nonexistent");
597    }
598
599    let Some(home) = dirs::home_dir() else {
600        return PathBuf::from("/nonexistent");
601    };
602    #[cfg(target_os = "macos")]
603    {
604        let p = home
605            .join("Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline");
606        if p.exists() {
607            return p;
608        }
609    }
610    #[cfg(target_os = "linux")]
611    {
612        let p = home.join(".config/Code/User/globalStorage/rooveterinaryinc.roo-cline");
613        if p.exists() {
614            return p;
615        }
616    }
617    PathBuf::from("/nonexistent")
618}
619
620#[cfg(test)]
621mod augment_tests {
622    use super::*;
623    use crate::core::editor_registry::writers::{
624        remove_lean_ctx_server, write_config_with_options, WriteAction, WriteOptions,
625    };
626
627    #[test]
628    fn build_targets_includes_augment_cli_entry() {
629        let home = Path::new("/home/tester");
630        let target = build_targets(home)
631            .into_iter()
632            .find(|t| t.agent_key == "augment")
633            .expect("augment target should be registered");
634        assert_eq!(target.name, "Augment CLI");
635        assert_eq!(target.config_path, home.join(".augment/settings.json"));
636        assert!(matches!(target.config_type, ConfigType::McpJson));
637    }
638
639    #[test]
640    fn build_targets_includes_augment_vscode_entry() {
641        let home = Path::new("/home/tester");
642        let target = build_targets(home)
643            .into_iter()
644            .find(|t| t.name == "Augment (VS Code)")
645            .expect("augment vscode target should be registered");
646        assert_eq!(target.agent_key, "augment");
647        assert_eq!(target.config_path, augment_vscode_mcp_path(home));
648        assert!(matches!(target.config_type, ConfigType::AugmentVsCode));
649    }
650
651    // Writer-layer round-trip: verifies the McpJson writer preserves unrelated
652    // entries when invoked against the Augment settings.json path. This does NOT
653    // exercise the `--agent augment` CLI flow or the setup.rs match arms — those
654    // are covered by the subprocess test in tests/setup_ci_smoke.rs.
655    #[test]
656    fn mcp_json_writer_round_trip_at_augment_settings_path_preserves_other_servers() {
657        let tmp = tempfile::tempdir().expect("tempdir");
658        let cfg = tmp.path().join(".augment").join("settings.json");
659        std::fs::create_dir_all(cfg.parent().unwrap()).unwrap();
660        std::fs::write(
661            &cfg,
662            r#"{ "mcpServers": { "other": { "command": "other-bin", "args": [] } } }"#,
663        )
664        .unwrap();
665
666        let target = EditorTarget {
667            name: "Augment CLI",
668            agent_key: "augment".to_string(),
669            config_path: cfg.clone(),
670            detect_path: PathBuf::from("/nonexistent"),
671            config_type: ConfigType::McpJson,
672        };
673
674        let install =
675            write_config_with_options(&target, "/usr/local/bin/lean-ctx", WriteOptions::default())
676                .expect("install");
677        assert!(matches!(
678            install.action,
679            WriteAction::Created | WriteAction::Updated
680        ));
681        let json: serde_json::Value =
682            serde_json::from_str(&std::fs::read_to_string(&cfg).unwrap()).unwrap();
683        assert_eq!(json["mcpServers"]["other"]["command"], "other-bin");
684        assert_eq!(
685            json["mcpServers"]["lean-ctx"]["command"],
686            "/usr/local/bin/lean-ctx"
687        );
688
689        let uninstall =
690            remove_lean_ctx_server(&target, WriteOptions::default()).expect("uninstall");
691        assert!(matches!(uninstall.action, WriteAction::Updated));
692        let json: serde_json::Value =
693            serde_json::from_str(&std::fs::read_to_string(&cfg).unwrap()).unwrap();
694        assert!(json["mcpServers"].get("lean-ctx").is_none());
695        assert_eq!(json["mcpServers"]["other"]["command"], "other-bin");
696    }
697}
698
699#[cfg(all(test, target_os = "macos"))]
700mod tests {
701    use super::*;
702
703    #[test]
704    #[cfg(target_os = "macos")]
705    fn build_targets_includes_all_qoder_macos_mcp_locations() {
706        let home = Path::new("/Users/tester");
707        let qoder_paths: Vec<_> = build_targets(home)
708            .into_iter()
709            .filter(|target| target.agent_key == "qoder")
710            .map(|target| target.config_path)
711            .collect();
712
713        assert_eq!(
714            qoder_paths,
715            vec![
716                home.join(".qoder/mcp.json"),
717                home.join("Library/Application Support/Qoder/User/mcp.json"),
718                home.join("Library/Application Support/Qoder/SharedClientCache/mcp.json"),
719            ]
720        );
721    }
722}