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 {
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 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 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}