Remove a previously installed integration, guided by its receipt. Files the user modified since install are kept (and reported). Settings entries are removed by `(target, hooks_key, command_prefix)` without needing the original package.
(agent: &str)
| 218 | /// entries are removed by `(target, hooks_key, command_prefix)` without |
| 219 | /// needing the original package. |
| 220 | pub fn uninstall(agent: &str) -> AgentResult<UninstallOutcome> { |
| 221 | let receipt = Receipt::load(agent)?.ok_or_else(|| AgentError::Integration { |
| 222 | agent: agent.to_string(), |
| 223 | reason: "not installed (no receipt found)".to_string(), |
| 224 | })?; |
| 225 | |
| 226 | let mut removed = Vec::new(); |
| 227 | let mut kept_modified = Vec::new(); |
| 228 | |
| 229 | for file in &receipt.files { |
| 230 | if !file.dst.exists() { |
| 231 | continue; |
| 232 | } |
| 233 | let current = hash_file(&file.dst)?; |
| 234 | if current == file.blake3 { |
| 235 | std::fs::remove_file(&file.dst)?; |
| 236 | // Best-effort: drop the parent dir if we just emptied it. |
| 237 | if let Some(parent) = file.dst.parent() { |
| 238 | let _ = std::fs::remove_dir(parent); |
| 239 | } |
| 240 | removed.push(file.dst.clone()); |
| 241 | } else { |
| 242 | kept_modified.push(file.dst.clone()); |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | let mut settings_hooks_removed = 0; |
| 247 | for settings in &receipt.settings { |
| 248 | let outcome = hooks_manifest::uninstall_prefixed( |
| 249 | &settings.target, |
| 250 | &settings.hooks_key, |
| 251 | &settings.command_prefix, |
| 252 | )?; |
| 253 | settings_hooks_removed += outcome.removed; |
| 254 | } |
| 255 | |
| 256 | Receipt::remove(agent)?; |
| 257 | |
| 258 | Ok(UninstallOutcome { |
| 259 | agent: agent.to_string(), |
| 260 | removed, |
| 261 | kept_modified, |
| 262 | settings_hooks_removed, |
| 263 | }) |
| 264 | } |
| 265 | |
| 266 | enum Decision { |
| 267 | Install, |
no test coverage detected