vx_cli/commands/
global.rs1use crate::ui::UI;
4use clap::Subcommand;
5use vx_core::{GlobalToolManager, Result};
6
7#[derive(Subcommand, Clone)]
8pub enum GlobalCommand {
9 List {
11 #[arg(short, long)]
13 verbose: bool,
14 },
15 Info {
17 tool_name: String,
19 },
20 Remove {
22 tool_name: String,
24 #[arg(short, long)]
26 force: bool,
27 },
28 Dependents {
30 tool_name: String,
32 },
33 Cleanup {
35 #[arg(short, long)]
37 dry_run: bool,
38 },
39}
40
41pub async fn handle(command: GlobalCommand) -> Result<()> {
43 let global_manager = GlobalToolManager::new()?;
44
45 match command {
46 GlobalCommand::List { verbose } => {
47 let tools = global_manager.list_global_tools().await?;
48
49 if tools.is_empty() {
50 UI::info("No global tools installed");
51 UI::hint(
52 "Install tools with 'vx install <tool>' or run 'vx <tool>' to auto-install",
53 );
54 return Ok(());
55 }
56
57 UI::info(&format!("Global tools ({} installed):", tools.len()));
58
59 for tool in tools {
60 if verbose {
61 UI::detail(&format!("📦 {} v{}", tool.name, tool.version));
62 UI::detail(&format!(" Path: {}", tool.install_path.display()));
63 UI::detail(&format!(
64 " Installed: {}",
65 tool.installed_at.format("%Y-%m-%d %H:%M:%S")
66 ));
67
68 if !tool.referenced_by.is_empty() {
69 let refs: Vec<String> = tool.referenced_by.iter().cloned().collect();
70 UI::detail(&format!(" Referenced by: {}", refs.join(", ")));
71 } else {
72 UI::detail(" Referenced by: none");
73 }
74 println!();
75 } else {
76 let refs = if tool.referenced_by.is_empty() {
77 "".to_string()
78 } else {
79 format!(" (used by {})", tool.referenced_by.len())
80 };
81 UI::detail(&format!("📦 {} v{}{}", tool.name, tool.version, refs));
82 }
83 }
84 }
85
86 GlobalCommand::Info { tool_name } => {
87 if let Some(tool) = global_manager.get_tool_info(&tool_name).await? {
88 UI::info(&format!("Global tool: {}", tool.name));
89 UI::detail(&format!("Version: {}", tool.version));
90 UI::detail(&format!("Install path: {}", tool.install_path.display()));
91 UI::detail(&format!(
92 "Installed at: {}",
93 tool.installed_at.format("%Y-%m-%d %H:%M:%S")
94 ));
95
96 if tool.referenced_by.is_empty() {
97 UI::detail("Referenced by: none (can be safely removed)");
98 } else {
99 UI::detail("Referenced by virtual environments:");
100 for venv in &tool.referenced_by {
101 UI::detail(&format!(" - {}", venv));
102 }
103 }
104 } else {
105 UI::error(&format!("Global tool '{}' not found", tool_name));
106 UI::hint("Run 'vx global list' to see all installed global tools");
107 }
108 }
109
110 GlobalCommand::Remove { tool_name, force } => {
111 if !global_manager.is_tool_installed(&tool_name).await? {
112 UI::error(&format!("Global tool '{}' is not installed", tool_name));
113 return Ok(());
114 }
115
116 if !force && !global_manager.can_remove_tool(&tool_name).await? {
117 let dependents = global_manager.get_tool_dependents(&tool_name).await?;
118 UI::error(&format!(
119 "Cannot remove tool '{}' - it is referenced by virtual environments:",
120 tool_name
121 ));
122 for venv in dependents {
123 UI::detail(&format!(" - {}", venv));
124 }
125 UI::hint("Use --force to remove anyway, or remove the tool from virtual environments first");
126 return Ok(());
127 }
128
129 if force {
130 UI::warn(&format!("Force removing global tool '{}'...", tool_name));
131 } else {
132 UI::info(&format!("Removing global tool '{}'...", tool_name));
133 }
134
135 global_manager.remove_global_tool(&tool_name).await?;
136 UI::success(&format!("Successfully removed global tool '{}'", tool_name));
137 }
138
139 GlobalCommand::Dependents { tool_name } => {
140 let dependents = global_manager.get_tool_dependents(&tool_name).await?;
141
142 if dependents.is_empty() {
143 UI::info(&format!(
144 "Tool '{}' is not referenced by any virtual environments",
145 tool_name
146 ));
147 UI::hint("This tool can be safely removed");
148 } else {
149 UI::info(&format!(
150 "Tool '{}' is referenced by {} virtual environment(s):",
151 tool_name,
152 dependents.len()
153 ));
154 for venv in dependents {
155 UI::detail(&format!(" - {}", venv));
156 }
157 }
158 }
159
160 GlobalCommand::Cleanup { dry_run } => {
161 let tools = global_manager.list_global_tools().await?;
162 let mut removable_tools = Vec::new();
163
164 for tool in tools {
165 if tool.referenced_by.is_empty() {
166 removable_tools.push(tool);
167 }
168 }
169
170 if removable_tools.is_empty() {
171 UI::info("No unused global tools found");
172 return Ok(());
173 }
174
175 if dry_run {
176 UI::info(&format!(
177 "Would remove {} unused global tool(s):",
178 removable_tools.len()
179 ));
180 for tool in removable_tools {
181 UI::detail(&format!(" - {} v{}", tool.name, tool.version));
182 }
183 UI::hint("Run without --dry-run to actually remove these tools");
184 } else {
185 UI::info(&format!(
186 "Removing {} unused global tool(s)...",
187 removable_tools.len()
188 ));
189
190 let mut removed_count = 0;
191 for tool in removable_tools {
192 match global_manager.remove_global_tool(&tool.name).await {
193 Ok(()) => {
194 UI::detail(&format!("✓ Removed {} v{}", tool.name, tool.version));
195 removed_count += 1;
196 }
197 Err(e) => {
198 UI::error(&format!("✗ Failed to remove {}: {}", tool.name, e));
199 }
200 }
201 }
202
203 UI::success(&format!(
204 "Successfully removed {} global tool(s)",
205 removed_count
206 ));
207 }
208 }
209 }
210
211 Ok(())
212}