1use 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
21pub use cli::Cli;
23pub use tracing_setup::setup_tracing;
24
25pub async fn main() -> anyhow::Result<()> {
28 setup_tracing();
30
31 let registry = PluginRegistry::new();
33
34 let _ = registry
36 .register_plugin(Box::new(vx_tool_node::NodePlugin::new()))
37 .await;
38
39 let _ = registry
41 .register_plugin(Box::new(vx_tool_go::GoPlugin::new()))
42 .await;
43
44 let _ = registry
46 .register_plugin(Box::new(vx_tool_rust::RustPlugin::new()))
47 .await;
48
49 let _ = registry
51 .register_plugin(Box::new(vx_tool_uv::UvPlugin::new()))
52 .await;
53
54 let cli = VxCli::new(registry);
56 cli.run().await
57}
58
59pub struct VxCli {
61 registry: PluginRegistry,
62}
63
64impl VxCli {
65 pub fn new(registry: PluginRegistry) -> Self {
67 Self { registry }
68 }
69
70 pub async fn run(self) -> Result<()> {
72 let cli = Cli::parse();
73
74 if cli.verbose {
76 }
78
79 match &cli.command {
81 Some(command) => self.handle_command(command.clone(), &cli).await,
82 None => {
83 if cli.args.is_empty() {
85 Cli::parse_from(["vx", "--help"]);
87 Ok(())
88 } else {
89 self.execute_tool(&cli.args, cli.use_system_path).await
91 }
92 }
93 }
94 }
95
96 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 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 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}