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 {
118                check,
119                version: _,
120                token,
121                prerelease,
122                force,
123            } => commands::self_update::handle(token.as_deref(), prerelease, force, check).await,
124
125            Commands::Uninstall {
126                tool,
127                version,
128                force,
129            } => commands::remove::handle(&self.registry, &tool, version.as_deref(), force).await,
130
131            Commands::Which { tool, all } => {
132                commands::where_cmd::handle(&self.registry, &tool, all).await
133            }
134
135            Commands::Versions {
136                tool,
137                latest,
138                prerelease,
139                detailed,
140                interactive,
141            } => {
142                commands::fetch::handle(
143                    &self.registry,
144                    &tool,
145                    latest,
146                    detailed,
147                    interactive,
148                    prerelease,
149                )
150                .await
151            }
152            Commands::Switch {
153                tool_version,
154                global,
155            } => commands::switch::handle(&self.registry, &tool_version, global).await,
156            Commands::Config { command } => match command {
157                Some(cli::ConfigCommand::Show) | None => commands::config::handle().await,
158                Some(cli::ConfigCommand::Set { key, value }) => {
159                    commands::config::handle_set(&key, &value).await
160                }
161                Some(cli::ConfigCommand::Get { key }) => commands::config::handle_get(&key).await,
162                Some(cli::ConfigCommand::Reset { key }) => {
163                    commands::config::handle_reset(key.clone()).await
164                }
165                Some(cli::ConfigCommand::Edit) => commands::config::handle_edit().await,
166            },
167            Commands::Init {
168                interactive,
169                template,
170                tools,
171                force,
172                dry_run,
173                list_templates,
174            } => {
175                commands::init::handle(interactive, template, tools, force, dry_run, list_templates)
176                    .await
177            }
178
179            Commands::Clean {
180                dry_run,
181                cache,
182                orphaned,
183                all,
184                force,
185                older_than,
186                verbose,
187            } => {
188                // Map new clean options to cleanup options
189                let cache_only = cache && !all;
190                let orphaned_only = orphaned && !all;
191                commands::cleanup::handle(
192                    dry_run,
193                    cache_only,
194                    orphaned_only,
195                    force,
196                    older_than,
197                    verbose,
198                )
199                .await
200            }
201            Commands::Stats => commands::stats::handle(&self.registry).await,
202            Commands::Plugin { command } => commands::plugin::handle(&self.registry, command).await,
203            Commands::Venv { command } => commands::venv_cmd::handle(command).await,
204            Commands::Global { command } => commands::global::handle(command).await,
205            Commands::Search {
206                query,
207                category,
208                installed_only,
209                available_only,
210                format,
211                verbose,
212            } => {
213                commands::search::handle(
214                    &self.registry,
215                    query,
216                    category,
217                    installed_only,
218                    available_only,
219                    format,
220                    verbose,
221                )
222                .await
223            }
224            Commands::Sync {
225                check,
226                force,
227                dry_run,
228                verbose,
229                no_parallel,
230                no_auto_install,
231            } => {
232                commands::sync::handle(
233                    &self.registry,
234                    check,
235                    force,
236                    dry_run,
237                    verbose,
238                    no_parallel,
239                    no_auto_install,
240                )
241                .await
242            }
243
244            Commands::Shell { command } => {
245                use crate::cli::ShellCommand;
246                match command {
247                    ShellCommand::Init { shell } => {
248                        commands::shell::handle_shell_init(shell.clone()).await
249                    }
250                    ShellCommand::Completions { shell } => {
251                        commands::shell::handle_completion(shell.clone()).await
252                    }
253                }
254            }
255        }
256    }
257
258    /// Execute a tool with the given arguments
259    async fn execute_tool(&self, args: &[String], use_system_path: bool) -> Result<()> {
260        if args.is_empty() {
261            return Err(anyhow::anyhow!("No tool specified"));
262        }
263
264        let tool_name = &args[0];
265        let tool_args = &args[1..];
266
267        commands::execute::handle(&self.registry, tool_name, tool_args, use_system_path).await
268    }
269}