vx_cli/commands/
global.rs

1//! Global tool management commands
2
3use crate::ui::UI;
4use clap::Subcommand;
5use vx_core::{GlobalToolManager, Result};
6
7#[derive(Subcommand, Clone)]
8pub enum GlobalCommand {
9    /// List all globally installed tools
10    List {
11        /// Show detailed information
12        #[arg(short, long)]
13        verbose: bool,
14    },
15    /// Show information about a specific global tool
16    Info {
17        /// Tool name
18        tool_name: String,
19    },
20    /// Remove a global tool (only if not referenced by any venv)
21    Remove {
22        /// Tool name
23        tool_name: String,
24        /// Force removal even if referenced by virtual environments
25        #[arg(short, long)]
26        force: bool,
27    },
28    /// Show which virtual environments depend on a tool
29    Dependents {
30        /// Tool name
31        tool_name: String,
32    },
33    /// Clean up unused global tools
34    Cleanup {
35        /// Dry run - show what would be removed without actually removing
36        #[arg(short, long)]
37        dry_run: bool,
38    },
39}
40
41/// Handle global tool management commands
42pub 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}