vx_cli/
lib.rs

1//! VX CLI - Command Line Interface for VX Tool Manager
2
3use anyhow::Result;
4use clap::Parser;
5use vx_plugin::PluginRegistry;
6
7pub mod cli;
8pub mod commands;
9pub mod tracing_setup;
10pub mod ui;
11
12#[cfg(test)]
13pub mod test_utils;
14
15#[cfg(test)]
16mod cli_tests;
17
18#[cfg(test)]
19mod plugin_tests;
20
21// Re-export for convenience
22pub use cli::Cli;
23pub use tracing_setup::setup_tracing;
24
25/// Main entry point for the VX CLI application
26/// This function sets up the plugin registry and runs the CLI
27pub async fn main() -> anyhow::Result<()> {
28    // Setup tracing
29    setup_tracing();
30
31    // Create plugin registry with all available plugins
32    let registry = PluginRegistry::new();
33
34    // Register Node.js plugin
35    let _ = registry
36        .register_plugin(Box::new(vx_tool_node::NodePlugin::new()))
37        .await;
38
39    // Register Go plugin
40    let _ = registry
41        .register_plugin(Box::new(vx_tool_go::GoPlugin::new()))
42        .await;
43
44    // Register Rust plugin
45    let _ = registry
46        .register_plugin(Box::new(vx_tool_rust::RustPlugin::new()))
47        .await;
48
49    // Register UV plugin
50    let _ = registry
51        .register_plugin(Box::new(vx_tool_uv::UvPlugin::new()))
52        .await;
53
54    // Create and run CLI
55    let cli = VxCli::new(registry);
56    cli.run().await
57}
58
59/// Main CLI application structure
60pub struct VxCli {
61    registry: PluginRegistry,
62}
63
64impl VxCli {
65    /// Create a new VxCli instance with the given plugin registry
66    pub fn new(registry: PluginRegistry) -> Self {
67        Self { registry }
68    }
69
70    /// Run the CLI application
71    pub async fn run(self) -> Result<()> {
72        let cli = Cli::parse();
73
74        // Handle global flags
75        if cli.verbose {
76            // Verbose logging is already set up in tracing_setup
77        }
78
79        // Route to appropriate command handler
80        match &cli.command {
81            Some(command) => self.handle_command(command.clone(), &cli).await,
82            None => {
83                // No subcommand provided, try to execute as tool
84                if cli.args.is_empty() {
85                    // Show help if no arguments
86                    Cli::parse_from(["vx", "--help"]);
87                    Ok(())
88                } else {
89                    // Execute tool
90                    self.execute_tool(&cli.args, cli.use_system_path).await
91                }
92            }
93        }
94    }
95
96    /// Handle a specific command
97    async fn handle_command(&self, command: cli::Commands, _cli: &Cli) -> Result<()> {
98        use cli::Commands;
99
100        match command {
101            Commands::Version => commands::version::handle().await,
102            Commands::List {
103                tool,
104                status,
105                installed: _,
106                available: _,
107            } => commands::list::handle(&self.registry, tool.as_deref(), status).await,
108            Commands::Install {
109                tool,
110                version,
111                force,
112            } => commands::install::handle(&self.registry, &tool, version.as_deref(), force).await,
113            Commands::Update { tool, apply } => {
114                commands::update::handle(&self.registry, tool.as_deref(), apply).await
115            }
116
117            Commands::SelfUpdate { check, version } => {
118                commands::self_update::handle(check, version.as_deref()).await
119            }
120
121            Commands::Uninstall {
122                tool,
123                version,
124                force,
125            } => commands::remove::handle(&self.registry, &tool, version.as_deref(), force).await,
126
127            Commands::Which { tool, all } => {
128                commands::where_cmd::handle(&self.registry, &tool, all).await
129            }
130
131            Commands::Versions {
132                tool,
133                latest,
134                prerelease,
135                detailed,
136                interactive,
137            } => {
138                commands::fetch::handle(
139                    &self.registry,
140                    &tool,
141                    latest,
142                    detailed,
143                    interactive,
144                    prerelease,
145                )
146                .await
147            }
148            Commands::Switch {
149                tool_version,
150                global,
151            } => commands::switch::handle(&self.registry, &tool_version, global).await,
152            Commands::Config { command } => match command {
153                Some(cli::ConfigCommand::Show) | None => commands::config::handle().await,
154                Some(cli::ConfigCommand::Set { key, value }) => {
155                    commands::config::handle_set(&key, &value).await
156                }
157                Some(cli::ConfigCommand::Get { key }) => commands::config::handle_get(&key).await,
158                Some(cli::ConfigCommand::Reset { key }) => {
159                    commands::config::handle_reset(key.clone()).await
160                }
161                Some(cli::ConfigCommand::Edit) => commands::config::handle_edit().await,
162            },
163            Commands::Init {
164                interactive,
165                template,
166                tools,
167                force,
168                dry_run,
169                list_templates,
170            } => {
171                commands::init::handle(interactive, template, tools, force, dry_run, list_templates)
172                    .await
173            }
174
175            Commands::Clean {
176                dry_run,
177                cache,
178                orphaned,
179                all,
180                force,
181                older_than,
182                verbose,
183            } => {
184                // Map new clean options to cleanup options
185                let cache_only = cache && !all;
186                let orphaned_only = orphaned && !all;
187                commands::cleanup::handle(
188                    dry_run,
189                    cache_only,
190                    orphaned_only,
191                    force,
192                    older_than,
193                    verbose,
194                )
195                .await
196            }
197            Commands::Stats => commands::stats::handle(&self.registry).await,
198            Commands::Plugin { command } => commands::plugin::handle(&self.registry, command).await,
199            Commands::Venv { command } => commands::venv_cmd::handle(command).await,
200            Commands::Global { command } => commands::global::handle(command).await,
201            Commands::Search {
202                query,
203                category,
204                installed_only,
205                available_only,
206                format,
207                verbose,
208            } => {
209                commands::search::handle(
210                    &self.registry,
211                    query,
212                    category,
213                    installed_only,
214                    available_only,
215                    format,
216                    verbose,
217                )
218                .await
219            }
220            Commands::Sync {
221                check,
222                force,
223                dry_run,
224                verbose,
225                no_parallel,
226                no_auto_install,
227            } => {
228                commands::sync::handle(
229                    &self.registry,
230                    check,
231                    force,
232                    dry_run,
233                    verbose,
234                    no_parallel,
235                    no_auto_install,
236                )
237                .await
238            }
239
240            Commands::Shell { command } => {
241                use crate::cli::ShellCommand;
242                match command {
243                    ShellCommand::Init { shell } => {
244                        commands::shell::handle_shell_init(shell.clone()).await
245                    }
246                    ShellCommand::Completions { shell } => {
247                        commands::shell::handle_completion(shell.clone()).await
248                    }
249                }
250            }
251        }
252    }
253
254    /// Execute a tool with the given arguments
255    async fn execute_tool(&self, args: &[String], use_system_path: bool) -> Result<()> {
256        if args.is_empty() {
257            return Err(anyhow::anyhow!("No tool specified"));
258        }
259
260        let tool_name = &args[0];
261        let tool_args = &args[1..];
262
263        commands::execute::handle(&self.registry, tool_name, tool_args, use_system_path).await
264    }
265}