Skip to main content

lean_ctx/wrap/
unwrap.rs

1//! `lean-ctx unwrap <agent>` — restore pre-wrap state from snapshot.
2
3use super::snapshot::WrapSnapshot;
4use std::path::Path;
5
6pub fn run_unwrap(args: &[String]) {
7    if args.iter().any(|a| a == "--help" || a == "-h") {
8        println!("Usage: lean-ctx unwrap <agent>");
9        println!();
10        println!("Restore the agent's configuration to its pre-wrap state.");
11        println!("Removes lean-ctx MCP registration, hooks, and shell integration");
12        println!("that were installed by `lean-ctx wrap <agent>`.");
13        return;
14    }
15
16    let agent_key = match args.first() {
17        Some(a) if !a.starts_with('-') => a.as_str(),
18        _ => {
19            eprintln!("Usage: lean-ctx unwrap <agent>");
20            eprintln!("Example: lean-ctx unwrap cursor");
21            std::process::exit(1);
22        }
23    };
24
25    let snap = match WrapSnapshot::load(agent_key) {
26        Ok(s) => s,
27        Err(e) => {
28            eprintln!("{e}");
29            std::process::exit(1);
30        }
31    };
32
33    eprintln!(
34        "Restoring {agent_key} to pre-wrap state (snapshot from {})...",
35        snap.timestamp
36    );
37
38    let mut restored = 0u32;
39    let mut errors = 0u32;
40
41    for (path_str, record) in &snap.files {
42        let path = Path::new(path_str);
43
44        if !record.existed {
45            if path.exists() {
46                match std::fs::remove_file(path) {
47                    Ok(()) => {
48                        eprintln!("  Removed: {path_str}");
49                        restored += 1;
50                    }
51                    Err(e) => {
52                        eprintln!("  Error removing {path_str}: {e}");
53                        errors += 1;
54                    }
55                }
56            }
57            continue;
58        }
59
60        if record.backup_path.is_empty() {
61            eprintln!("  Skipped: {path_str} (no backup available)");
62            continue;
63        }
64
65        let backup = Path::new(&record.backup_path);
66        if !backup.exists() {
67            eprintln!("  Skipped: {path_str} (backup file missing)");
68            continue;
69        }
70
71        match std::fs::copy(backup, path) {
72            Ok(_) => {
73                eprintln!("  Restored: {path_str}");
74                restored += 1;
75            }
76            Err(e) => {
77                eprintln!("  Error restoring {path_str}: {e}");
78                errors += 1;
79            }
80        }
81    }
82
83    // Remove MCP registration for the agent
84    eprintln!("  Removing MCP registration...");
85    if let Some(home) = dirs::home_dir() {
86        let targets = crate::core::editor_registry::build_targets(&home);
87        for target in targets.iter().filter(|t| t.agent_key == agent_key) {
88            let _ = crate::core::editor_registry::remove_lean_ctx_mcp_server(
89                &target.config_path,
90                crate::core::editor_registry::WriteOptions {
91                    overwrite_invalid: true,
92                },
93            );
94        }
95    }
96
97    // Clean up snapshot dir
98    if let Ok(state) = crate::core::paths::state_dir() {
99        let snap_dir = state.join("snapshots").join(agent_key);
100        let _ = std::fs::remove_dir_all(&snap_dir);
101    }
102
103    eprintln!();
104    if errors == 0 {
105        eprintln!(
106            "\x1b[1;32mlean-ctx unwrapped {agent_key} successfully.\x1b[0m ({restored} files restored)"
107        );
108    } else {
109        eprintln!(
110            "\x1b[1;33mlean-ctx unwrap completed with {errors} error(s).\x1b[0m ({restored} files restored)"
111        );
112    }
113    eprintln!();
114    eprintln!("  Restart {agent_key} to complete the removal.");
115}