mi6_cli/commands/
uninstall.rs1use std::path::PathBuf;
9use std::process::Command;
10
11use anyhow::{Context, Result};
12
13use mi6_core::{
14 Config, FrameworkAdapter, FrameworkResolutionMode, all_adapters, resolve_frameworks,
15};
16
17use super::disable::disable_framework_silent;
18use super::upgrade::InstallMethod;
19use crate::display::{StderrColors, confirm};
20
21pub struct UninstallOptions {
23 pub confirm: bool,
25 pub keep_data: bool,
27 pub dry_run: bool,
29}
30
31pub fn run_uninstall(options: UninstallOptions) -> Result<()> {
33 let colors = StderrColors::new();
34 let method = InstallMethod::detect()?;
35
36 eprintln!("{}mi6 uninstall{}", colors.bold, colors.reset);
37 eprintln!("Detected installation method: {}", method.name());
38 eprintln!();
39
40 let enabled_frameworks = find_enabled_frameworks();
42 let mi6_dir = get_mi6_dir();
43 let data_exists = mi6_dir.as_ref().is_some_and(|p| p.exists());
44
45 if enabled_frameworks.is_empty() {
47 eprintln!(
48 "{}Step 1:{} No enabled frameworks found",
49 colors.cyan, colors.reset
50 );
51 eprintln!();
52 } else {
53 eprintln!("{}Step 1:{} Disable mi6 hooks", colors.cyan, colors.reset);
54 for adapter in &enabled_frameworks {
55 eprintln!(" - {}", adapter.display_name());
56 }
57 eprintln!();
58 }
59
60 if !options.keep_data && data_exists {
62 eprintln!("{}Step 2:{} Delete mi6 data", colors.cyan, colors.reset);
63 if let Some(ref dir) = mi6_dir
64 && dir.exists()
65 {
66 eprintln!(" - {} (database, config, themes)", dir.display());
67 }
68 eprintln!();
69 } else if options.keep_data {
70 eprintln!(
71 "{}Step 2:{} Keeping mi6 data (--keep-data)",
72 colors.cyan, colors.reset
73 );
74 eprintln!();
75 } else {
76 eprintln!(
77 "{}Step 2:{} No mi6 data to delete",
78 colors.cyan, colors.reset
79 );
80 eprintln!();
81 }
82
83 eprintln!(
85 "{}Step 3:{} Uninstall mi6 binary",
86 colors.cyan, colors.reset
87 );
88 match &method {
89 InstallMethod::Homebrew => eprintln!(" brew uninstall mi6"),
90 InstallMethod::CargoRegistry | InstallMethod::CargoPath(_) => {
91 eprintln!(" cargo uninstall mi6");
92 }
93 InstallMethod::Standalone => {
94 if let Ok(exe_path) = std::env::current_exe() {
95 eprintln!(" rm {}", exe_path.display());
96 } else {
97 eprintln!(" (remove mi6 binary manually)");
98 }
99 }
100 }
101 eprintln!();
102
103 if options.dry_run {
105 eprintln!("{}Dry run:{} No changes made.", colors.yellow, colors.reset);
106 return Ok(());
107 }
108
109 if !options.confirm {
111 eprintln!(
112 "{}This will permanently uninstall mi6.{}",
113 colors.yellow, colors.reset
114 );
115
116 if !options.keep_data && data_exists {
118 eprintln!(
119 "{}All mi6 data (sessions, events, config) will be deleted.{}",
120 colors.yellow, colors.reset
121 );
122 }
123
124 if !confirm("Proceed with uninstall?")? {
125 eprintln!("Aborted.");
126 return Ok(());
127 }
128 eprintln!();
129 }
130
131 if !enabled_frameworks.is_empty() {
133 eprint!("{}Disabling{} mi6 hooks... ", colors.green, colors.reset);
134 for adapter in &enabled_frameworks {
135 disable_framework_silent(*adapter)?;
136 }
137 eprintln!("{}done{}", colors.green, colors.reset);
138 }
139
140 if !options.keep_data
142 && let Some(ref dir) = mi6_dir
143 && dir.exists()
144 {
145 eprint!(
146 "{}Deleting{} {}... ",
147 colors.green,
148 colors.reset,
149 dir.display()
150 );
151 std::fs::remove_dir_all(dir)
152 .with_context(|| format!("failed to delete {}", dir.display()))?;
153 eprintln!("{}done{}", colors.green, colors.reset);
154 }
155
156 eprint!(
158 "{}Uninstalling{} mi6 binary... ",
159 colors.green, colors.reset
160 );
161 match &method {
162 InstallMethod::Homebrew => uninstall_homebrew()?,
163 InstallMethod::CargoRegistry | InstallMethod::CargoPath(_) => uninstall_cargo()?,
164 InstallMethod::Standalone => uninstall_standalone()?,
165 }
166 eprintln!("{}done{}", colors.green, colors.reset);
167
168 eprintln!();
169 eprintln!("{}mi6 has been uninstalled.{}", colors.bold, colors.reset);
170
171 Ok(())
172}
173
174fn find_enabled_frameworks() -> Vec<&'static dyn FrameworkAdapter> {
176 let mode = FrameworkResolutionMode::Active {
178 local: false,
179 settings_local: false,
180 };
181
182 match resolve_frameworks(&[], Some(mode)) {
184 Ok(adapters) => adapters,
185 Err(_) => {
186 all_adapters()
188 .into_iter()
189 .filter(|a| a.has_mi6_hooks(false, false))
190 .collect()
191 }
192 }
193}
194
195fn get_mi6_dir() -> Option<PathBuf> {
197 Config::mi6_dir().ok()
198}
199
200fn uninstall_homebrew() -> Result<()> {
202 let status = Command::new("brew")
203 .args(["uninstall", "mi6"])
204 .status()
205 .context("failed to run brew uninstall")?;
206
207 if !status.success() {
208 anyhow::bail!("brew uninstall failed with exit code: {:?}", status.code());
209 }
210
211 Ok(())
212}
213
214fn uninstall_cargo() -> Result<()> {
216 let status = Command::new("cargo")
217 .args(["uninstall", "mi6"])
218 .status()
219 .context("failed to run cargo uninstall")?;
220
221 if !status.success() {
222 anyhow::bail!("cargo uninstall failed with exit code: {:?}", status.code());
223 }
224
225 Ok(())
226}
227
228fn uninstall_standalone() -> Result<()> {
230 let exe_path = std::env::current_exe().context("failed to get current executable path")?;
231
232 std::fs::remove_file(&exe_path)
235 .with_context(|| format!("failed to delete {}", exe_path.display()))?;
236
237 Ok(())
238}