Skip to main content

innate_core/install/
uninstall.rs

1use super::{agents::*, skills::*, ui::*, *};
2
3fn remove_binary_from_path() -> ConfigStatus {
4    let dest = home_dir().join(".local").join("bin").join(binary_name());
5    if !dest.exists() && !dest.is_symlink() {
6        return ConfigStatus::Skipped(format!("~/.local/bin/{} not found", binary_name()));
7    }
8    match std::fs::remove_file(&dest) {
9        Ok(()) => ConfigStatus::Updated(dest),
10        Err(e) => ConfigStatus::Error(e.to_string()),
11    }
12}
13
14fn remove_data_dir() -> ConfigStatus {
15    let data = home_dir().join(".innate");
16    if !data.exists() {
17        return ConfigStatus::Skipped("~/.innate/ not found".into());
18    }
19    match std::fs::remove_dir_all(&data) {
20        Ok(()) => ConfigStatus::Updated(data),
21        Err(e) => ConfigStatus::Error(e.to_string()),
22    }
23}
24
25pub fn run_uninstall(yes: bool, purge_data: bool) -> anyhow::Result<()> {
26    let version = env!("CARGO_PKG_VERSION");
27    box_open(&format!("Innate v{version} — Uninstall"));
28
29    // ── 1. Confirm ─────────────────────────────────────────────────────────
30    if !yes && !prompt_confirm("Remove Innate from your system?", false) {
31        println!("{}", gray("│"));
32        box_close("Cancelled — nothing changed.");
33        return Ok(());
34    }
35
36    // ── 2. Data prompt ─────────────────────────────────────────────────────
37    let remove_data = purge_data
38        || (!yes
39            && prompt_confirm(
40                "Also delete knowledge data (~/.innate/)? This cannot be undone.",
41                false,
42            ));
43
44    // ── 3. Remove agent configs ────────────────────────────────────────────
45    let global_claude = home_dir().join(".claude.json");
46    // Also clean up the old incorrect path for users who installed with older versions.
47    let global_claude_legacy = home_dir().join(".claude").join("settings.json");
48    let project_claude = PathBuf::from(".claude").join("settings.json");
49
50    for (label, path) in &[
51        ("claude (global)", global_claude.as_path()),
52        ("claude (global, legacy)", global_claude_legacy.as_path()),
53        ("claude (project)", project_claude.as_path()),
54    ] {
55        match remove_claude_config(path) {
56            ConfigStatus::Updated(p) => {
57                result_line(&format!(
58                    "{}: Removed MCP config {}",
59                    bold(label),
60                    gray(&tilde_path(&p))
61                ));
62            }
63            ConfigStatus::Error(e) => {
64                warn_line(&format!("{}: \x1b[31mError — {e}\x1b[0m", bold(label)));
65            }
66            _ => {}
67        }
68    }
69
70    match remove_codex_config() {
71        ConfigStatus::Updated(p) => {
72            result_line(&format!(
73                "{}: Removed MCP config {}",
74                bold("codex"),
75                gray(&tilde_path(&p))
76            ));
77        }
78        ConfigStatus::Error(e) => {
79            warn_line(&format!("{}: \x1b[31mError — {e}\x1b[0m", bold("codex")));
80        }
81        _ => {}
82    }
83
84    match remove_opencode_config() {
85        ConfigStatus::Updated(p) => {
86            result_line(&format!(
87                "{}: Removed MCP config {}",
88                bold("opencode"),
89                gray(&tilde_path(&p))
90            ));
91        }
92        ConfigStatus::Error(e) => {
93            warn_line(&format!("{}: \x1b[31mError — {e}\x1b[0m", bold("opencode")));
94        }
95        _ => {}
96    }
97
98    // ── 4. Remove skill + slash commands ──────────────────────────────────
99    match remove_skill() {
100        ConfigStatus::Updated(p) => {
101            result_line(&format!(
102                "{}: Removed skill {}",
103                bold("claude"),
104                gray(&tilde_path(&p))
105            ));
106        }
107        ConfigStatus::Error(e) => {
108            warn_line(&format!(
109                "{}: \x1b[31mSkill error — {e}\x1b[0m",
110                bold("claude")
111            ));
112        }
113        _ => {}
114    }
115
116    for (name, status) in remove_commands() {
117        match status {
118            ConfigStatus::Updated(p) => {
119                result_line(&format!(
120                    "{}: Removed /{name} {}",
121                    bold("claude"),
122                    gray(&tilde_path(&p))
123                ));
124            }
125            ConfigStatus::Error(e) => {
126                warn_line(&format!(
127                    "{}: \x1b[31mCommand /{name} error — {e}\x1b[0m",
128                    bold("claude")
129                ));
130            }
131            _ => {}
132        }
133    }
134
135    // ── 5. Remove binary ───────────────────────────────────────────────────
136    match remove_binary_from_path() {
137        ConfigStatus::Updated(p) => {
138            result_line(&format!("Removed binary {}", gray(&tilde_path(&p))));
139        }
140        ConfigStatus::Error(e) => {
141            warn_line(&format!("\x1b[31mCould not remove binary: {e}\x1b[0m"));
142        }
143        _ => {}
144    }
145
146    // ── 6. Remove data ─────────────────────────────────────────────────────
147    if remove_data {
148        match remove_data_dir() {
149            ConfigStatus::Updated(p) => {
150                result_line(&format!("Removed data {}", gray(&tilde_path(&p))));
151            }
152            ConfigStatus::Error(e) => {
153                warn_line(&format!("\x1b[31mCould not remove data: {e}\x1b[0m"));
154            }
155            _ => {}
156        }
157    } else {
158        info(&gray("Knowledge data (~/.innate/) kept."));
159    }
160
161    sep();
162    box_close("Innate uninstalled. Restart your agents.");
163    Ok(())
164}