1use 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
12pub use cli::Cli;
14pub use tracing_setup::setup_tracing;
15
16pub async fn main() -> anyhow::Result<()> {
19 setup_tracing();
21
22 let mut registry = vx_core::PluginRegistry::new();
24
25 let _ = registry.register(Box::new(vx_tool_node::NodePlugin::new()));
27
28 let _ = registry.register(Box::new(vx_tool_go::GoPlugin::new()));
30
31 let _ = registry.register(Box::new(vx_tool_rust::RustPlugin::new()));
33
34 let _ = registry.register(Box::new(vx_tool_uv::UvPlugin::new()));
36
37 let cli = VxCli::new(registry);
39 cli.run().await
40}
41
42pub struct VxCli {
44 registry: PluginRegistry,
45}
46
47impl VxCli {
48 pub fn new(registry: PluginRegistry) -> Self {
50 Self { registry }
51 }
52
53 pub async fn run(self) -> Result<()> {
55 let cli = Cli::parse();
56
57 if cli.verbose {
59 }
61
62 match &cli.command {
64 Some(command) => self.handle_command(command.clone(), &cli).await,
65 None => {
66 if cli.args.is_empty() {
68 Cli::parse_from(["vx", "--help"]);
70 Ok(())
71 } else {
72 self.execute_tool(&cli.args, cli.use_system_path).await
74 }
75 }
76 }
77 }
78
79 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 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 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}