vx_cli/
lib.rs

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