opencode_ralph_loop_cli/commands/
uninstall.rs1use std::path::{Path, PathBuf};
2use std::time::Instant;
3
4use crate::cli::{OutputFormat, UninstallArgs};
5use crate::error::CliError;
6use crate::output::{Action, FileEntry, Report};
7
8pub fn run(args: &UninstallArgs, output: &OutputFormat) -> Result<(), CliError> {
9 let start = Instant::now();
10
11 let target = resolve_target(&args.path)?;
12 let opencode_dir = target.join(".opencode");
13
14 let manifest = if args.force {
15 crate::manifest::load(&opencode_dir)
16 .unwrap_or_else(|_| crate::manifest::Manifest::new("unknown"))
17 } else {
18 crate::manifest::load(&opencode_dir)?
19 };
20
21 let plugin_version = manifest.plugin_version.clone();
22 let mut report = Report::new("uninstall", &plugin_version);
23
24 for file in &manifest.files {
25 if args.keep_state && file.path == "ralph-loop.local.md" {
26 continue;
27 }
28 if args.keep_node_modules && file.path.starts_with("node_modules") {
29 continue;
30 }
31
32 let dest = opencode_dir.join(&file.path);
33
34 let action = if dest.exists() {
35 if !args.dry_run {
36 std::fs::remove_file(&dest)
37 .map_err(|e| CliError::io(dest.to_string_lossy().into_owned(), e))?;
38 }
39 Action::Removed
40 } else {
41 Action::Missing
42 };
43
44 report.files.push(FileEntry {
45 path: file.path.clone(),
46 action,
47 sha256: file.sha256.clone(),
48 expected_sha256: None,
49 size_bytes: file.size_bytes,
50 });
51 }
52
53 if !args.dry_run {
54 crate::manifest::remove(&opencode_dir)?;
55 }
56
57 report.duration_ms = start.elapsed().as_millis() as u64;
58 report.finalize();
59 emit_report(&report, output);
60
61 Ok(())
62}
63
64fn resolve_target(path: &Path) -> Result<PathBuf, CliError> {
65 let resolved = if path.is_absolute() {
66 path.to_path_buf()
67 } else {
68 std::env::current_dir()
69 .map_err(|e| CliError::io("current directory", e))?
70 .join(path)
71 };
72
73 if !resolved.exists() {
74 return Err(CliError::Io {
75 path: resolved.to_string_lossy().to_string(),
76 source: std::io::Error::new(std::io::ErrorKind::NotFound, "directory does not exist"),
77 });
78 }
79
80 Ok(resolved)
81}
82
83fn emit_report(report: &Report, format: &OutputFormat) {
84 match format {
85 OutputFormat::Text => crate::output::text::print_report(report),
86 OutputFormat::Json => crate::output::json::print_report(report),
87 OutputFormat::Ndjson => crate::output::ndjson::print_report(report),
88 OutputFormat::Quiet => {}
89 }
90}