Skip to content

Commit cfa4e24

Browse files
homanpcursoragent
andauthored
feat: add uninstall command to cli (#20)
* feat: add sus uninstall command for easy removal Adds a self-uninstall command that removes the sus binary from the system. Supports --yes to skip confirmation and --all to also remove project files (.sus-docs/, sus.json). Updated install script to show uninstall instructions. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: bump version to v0.1.6 Co-authored-by: Cursor <cursoragent@cursor.com> * fix: remove sus section from AGENTS.md on uninstall --all Previously uninstall --all only removed .sus-docs/ and sus.json. Now it also removes the sus Docs Index section from AGENTS.md while preserving any other content in the file. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent e97e403 commit cfa4e24

6 files changed

Lines changed: 142 additions & 2 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ members = [
1111
]
1212

1313
[workspace.package]
14-
version = "0.1.5"
14+
version = "0.1.6"
1515
edition = "2021"
1616
authors = ["sus contributors"]
1717
license = "MIT"

crates/cli/src/agents_md.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,6 @@ fn update_agents_md_index_at_path(agents_path: &Path, docs_dir: &Path) -> Result
116116
}
117117

118118
/// Remove sus section from AGENTS.md
119-
#[allow(dead_code)]
120119
pub fn remove_agents_md_index() -> Result<()> {
121120
remove_agents_md_index_at_path(Path::new(AGENTS_MD_PATH))
122121
}

crates/cli/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ pub mod check;
55
pub mod init;
66
pub mod remove;
77
pub mod scan;
8+
pub mod uninstall;
89
pub mod update;
910
pub mod why;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
//! Uninstall command - remove sus from the system
2+
3+
use crate::agents_md;
4+
use anyhow::Result;
5+
use colored::Colorize;
6+
use dialoguer::Confirm;
7+
use std::path::Path;
8+
9+
/// Run the uninstall command
10+
pub async fn run(yes: bool, all: bool) -> Result<()> {
11+
// Get the current executable path
12+
let exe_path = std::env::current_exe()?;
13+
14+
println!();
15+
println!("🗑️ sus uninstaller");
16+
println!();
17+
println!(
18+
" Binary location: {}",
19+
exe_path.display().to_string().cyan()
20+
);
21+
22+
// Check for project-level files
23+
let sus_docs = Path::new(".sus-docs");
24+
let sus_json = Path::new("sus.json");
25+
let agents_md = Path::new("AGENTS.md");
26+
let has_agents_md_section = agents_md.exists()
27+
&& std::fs::read_to_string(agents_md)
28+
.map(|c| c.contains("[sus Docs Index]"))
29+
.unwrap_or(false);
30+
let has_project_files = sus_docs.exists() || sus_json.exists() || has_agents_md_section;
31+
32+
if all && has_project_files {
33+
println!();
34+
println!(" Project files to remove:");
35+
if sus_docs.exists() {
36+
println!(" - {}", ".sus-docs/".cyan());
37+
}
38+
if sus_json.exists() {
39+
println!(" - {}", "sus.json".cyan());
40+
}
41+
if has_agents_md_section {
42+
println!(" - {}", "AGENTS.md (sus section only)".cyan());
43+
}
44+
}
45+
46+
// Confirm unless --yes flag
47+
if !yes {
48+
println!();
49+
let confirm = Confirm::new()
50+
.with_prompt(" Remove sus?")
51+
.default(false)
52+
.interact()?;
53+
54+
if !confirm {
55+
println!();
56+
println!(" {} Uninstall cancelled.", "✗".red());
57+
return Ok(());
58+
}
59+
}
60+
61+
// Remove project-level files if --all flag
62+
if all {
63+
if sus_docs.exists() {
64+
std::fs::remove_dir_all(sus_docs)?;
65+
println!(" {} Removed .sus-docs/", "✓".green());
66+
}
67+
if sus_json.exists() {
68+
std::fs::remove_file(sus_json)?;
69+
println!(" {} Removed sus.json", "✓".green());
70+
}
71+
if has_agents_md_section {
72+
agents_md::remove_agents_md_index()?;
73+
println!(" {} Removed sus section from AGENTS.md", "✓".green());
74+
}
75+
}
76+
77+
// Delete the binary
78+
// Note: On some systems, we can't delete a running executable directly
79+
// So we try a few approaches
80+
#[cfg(unix)]
81+
{
82+
// On Unix, we can usually delete the file while it's running
83+
// The file will be removed when the process exits
84+
std::fs::remove_file(&exe_path)?;
85+
}
86+
87+
#[cfg(windows)]
88+
{
89+
// On Windows, we need to schedule deletion or use a workaround
90+
// For now, we'll try direct deletion which works in some cases
91+
if let Err(_) = std::fs::remove_file(&exe_path) {
92+
// If direct deletion fails, create a batch script to delete after exit
93+
let batch_path = std::env::temp_dir().join("sus_uninstall.bat");
94+
let batch_content = format!(
95+
"@echo off\n\
96+
:loop\n\
97+
del \"{}\" 2>nul\n\
98+
if exist \"{}\" goto loop\n\
99+
del \"%~f0\"\n",
100+
exe_path.display(),
101+
exe_path.display()
102+
);
103+
std::fs::write(&batch_path, batch_content)?;
104+
std::process::Command::new("cmd")
105+
.args(["/C", "start", "/min", batch_path.to_str().unwrap()])
106+
.spawn()?;
107+
}
108+
}
109+
110+
println!();
111+
println!(" {} sus has been uninstalled.", "✓".green());
112+
println!();
113+
114+
// Suggest cleanup if project files exist but --all wasn't used
115+
if !all && has_project_files {
116+
println!(
117+
" {} Project files (.sus-docs/, sus.json) were not removed.",
118+
"note:".yellow()
119+
);
120+
println!(" Run with {} to remove them too.", "--all".cyan());
121+
println!();
122+
}
123+
124+
Ok(())
125+
}

crates/cli/src/main.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ enum Commands {
8484
/// Package to trace
8585
package: String,
8686
},
87+
88+
/// Uninstall sus from this system
89+
Uninstall {
90+
/// Skip confirmation prompt
91+
#[arg(long, short)]
92+
yes: bool,
93+
94+
/// Also remove project-level files (.sus-docs/, sus.json)
95+
#[arg(long)]
96+
all: bool,
97+
},
8798
}
8899

89100
#[tokio::main]
@@ -120,5 +131,7 @@ async fn main() -> anyhow::Result<()> {
120131
Commands::Update { dry_run } => commands::update::run(&client, dry_run).await,
121132

122133
Commands::Why { package } => commands::why::run(&package).await,
134+
135+
Commands::Uninstall { yes, all } => commands::uninstall::run(yes, all).await,
123136
}
124137
}

scripts/install.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ main() {
9191
fi
9292

9393
info "Done! Run 'sus --help' to get started."
94+
echo ""
95+
echo "To uninstall later, run: sus uninstall"
9496
}
9597

9698
main

0 commit comments

Comments
 (0)