leviath_cli/commands/
remove.rs1use clap::Args;
4
5#[derive(Args)]
7pub struct RemoveArgs {
8 #[arg(value_name = "NAME")]
10 pub name: String,
11}
12
13pub async fn execute(args: RemoveArgs) -> anyhow::Result<()> {
15 let installer = leviath_package::AgentInstaller::new();
16 remove_agent(&installer, &args.name)
17}
18
19fn remove_agent(installer: &leviath_package::AgentInstaller, name: &str) -> anyhow::Result<()> {
22 remove_agent_with(installer, name, &|n| installer.uninstall(n))
23}
24
25fn remove_agent_with(
34 installer: &leviath_package::AgentInstaller,
35 name: &str,
36 uninstall: &dyn Fn(&str) -> anyhow::Result<()>,
37) -> anyhow::Result<()> {
38 if installer.get_installed(name).is_none() {
39 anyhow::bail!(
40 "Agent '{}' is not installed. Use `lev list` to see installed agents.",
41 name
42 );
43 }
44
45 uninstall(name)?;
46 println!("Removed agent '{}'.", name);
47 Ok(())
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn remove_args_stores_name() {
56 let args = RemoveArgs {
57 name: "my-agent".to_string(),
58 };
59 assert_eq!(args.name, "my-agent");
60 }
61
62 #[test]
63 fn remove_args_accepts_various_names() {
64 for name in &[
65 "simple",
66 "with-dash",
67 "with_underscore",
68 "CamelCase",
69 "v1.0.0",
70 ] {
71 let args = RemoveArgs {
72 name: name.to_string(),
73 };
74 assert_eq!(args.name, *name);
75 }
76 }
77
78 fn install_test_agent(installer: &leviath_package::AgentInstaller, name: &str) {
81 let project_dir = tempfile::tempdir().unwrap();
82 std::fs::write(
83 project_dir.path().join("agent.leviath"),
84 format!(
85 "[agent]\nname = \"{name}\"\nversion = \"1.0.0\"\ndescription = \"test agent\"\n"
86 ),
87 )
88 .unwrap();
89 let bundle = leviath_package::AgentBundler::new()
90 .bundle(project_dir.path())
91 .unwrap();
92 installer.install_from_bytes(name, &bundle).unwrap();
93 }
94
95 #[test]
96 fn remove_agent_not_installed_returns_error() {
97 let dir = tempfile::tempdir().unwrap();
98 let installer = leviath_package::AgentInstaller::with_install_dir(dir.path().to_path_buf());
99
100 let err = remove_agent(&installer, "nonexistent").unwrap_err();
101 assert!(err.to_string().contains("is not installed"));
102 assert!(err.to_string().contains("lev list"));
103 }
104
105 #[test]
106 fn remove_agent_installed_succeeds_and_uninstalls() {
107 let dir = tempfile::tempdir().unwrap();
108 let installer = leviath_package::AgentInstaller::with_install_dir(dir.path().to_path_buf());
109 install_test_agent(&installer, "my-agent");
110 assert!(installer.get_installed("my-agent").is_some());
111
112 remove_agent(&installer, "my-agent").unwrap();
113
114 assert!(installer.get_installed("my-agent").is_none());
115 }
116
117 #[tokio::test]
127 async fn execute_with_nonexistent_agent_returns_error() {
128 let args = RemoveArgs {
129 name: "definitely-nonexistent-agent-for-lev-remove-coverage-xyz".to_string(),
130 };
131 let err = execute(args).await.unwrap_err();
132 assert!(err.to_string().contains("is not installed"));
133 }
134
135 #[test]
136 fn remove_agent_uninstall_error_propagates() {
137 let dir = tempfile::tempdir().unwrap();
138 let installer = leviath_package::AgentInstaller::with_install_dir(dir.path().to_path_buf());
139 install_test_agent(&installer, "err-agent");
140
141 let result = remove_agent_with(&installer, "err-agent", &|_| {
144 Err(anyhow::anyhow!("simulated uninstall failure"))
145 });
146 assert!(result.is_err());
147 }
148}