Remove Atomic's hooks, plugin manifest, and import registration.
(config_dir: &Path)
| 482 | |
| 483 | /// Remove Atomic's hooks, plugin manifest, and import registration. |
| 484 | fn uninstall_from(config_dir: &Path) -> AgentResult<()> { |
| 485 | let hooks_path = Self::hooks_path(config_dir); |
| 486 | |
| 487 | if hooks_path.exists() { |
| 488 | let mut groups = Self::read_hooks_file(&hooks_path)?; |
| 489 | |
| 490 | if let Some(group) = groups.get_mut(ATOMIC_GROUP) { |
| 491 | if group.is_object() { |
| 492 | remove_atomic_commands(group); |
| 493 | if group_is_empty(group) { |
| 494 | groups.remove(ATOMIC_GROUP); |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | if groups.is_empty() { |
| 500 | // No groups left — remove the hooks file entirely. |
| 501 | std::fs::remove_file(&hooks_path).map_err(|e| AgentError::ConfigError { |
| 502 | operation: "remove hooks file".to_string(), |
| 503 | path: hooks_path.clone(), |
| 504 | reason: e.to_string(), |
| 505 | })?; |
| 506 | } else { |
| 507 | Self::write_json(&hooks_path, &Value::Object(groups))?; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | // Remove the plugin manifest if it is the one Atomic wrote. |
| 512 | let manifest_path = Self::plugin_dir(config_dir).join(PLUGIN_MANIFEST_FILE); |
| 513 | if manifest_path.exists() { |
| 514 | let is_ours = std::fs::read_to_string(&manifest_path) |
| 515 | .map(|content| content.contains("\"atomic\"")) |
| 516 | .unwrap_or(false); |
| 517 | if is_ours { |
| 518 | std::fs::remove_file(&manifest_path).map_err(|e| AgentError::ConfigError { |
| 519 | operation: "remove plugin manifest".to_string(), |
| 520 | path: manifest_path.clone(), |
| 521 | reason: e.to_string(), |
| 522 | })?; |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | // Remove the bundled skills (they are ours — refreshed on install). |
| 527 | let skills_dir = Self::plugin_dir(config_dir).join("skills"); |
| 528 | for (name, _) in PLUGIN_SKILLS { |
| 529 | let path = skills_dir.join(name); |
| 530 | if path.exists() { |
| 531 | let _ = std::fs::remove_file(path); |
| 532 | } |
| 533 | } |
| 534 | if skills_dir.is_dir() { |
| 535 | let is_empty = std::fs::read_dir(&skills_dir) |
| 536 | .map(|mut entries| entries.next().is_none()) |
| 537 | .unwrap_or(false); |
| 538 | if is_empty { |
| 539 | let _ = std::fs::remove_dir(&skills_dir); |
| 540 | } |
| 541 | } |
nothing calls this directly
no test coverage detected