1use std::ffi::OsString;
2use std::path::Path;
3use std::process::ExitCode;
4
5use crate::cli::{print_help, Cli, Command, DaemonCommand};
6use crate::config::Config;
7use crate::error::ViaError;
8use crate::executor;
9use crate::providers::ProviderRegistry;
10
11pub fn run(args: impl IntoIterator<Item = OsString>) -> ExitCode {
12 crate::tls::install_crypto_provider();
13 exit_code_from_result(try_run(args))
14}
15
16fn exit_code_from_result(result: Result<ExitCode, ViaError>) -> ExitCode {
17 match result {
18 Ok(code) => code,
19 Err(ViaError::Clap(error)) => {
20 let exit_code = if error.use_stderr() { 2 } else { 0 };
21 if error.use_stderr() {
22 eprint!("{error}");
23 } else {
24 print!("{error}");
25 }
26 ExitCode::from(exit_code)
27 }
28 Err(error) => {
29 eprintln!("error: {error}");
30 ExitCode::from(error.exit_code())
31 }
32 }
33}
34
35fn try_run(args: impl IntoIterator<Item = OsString>) -> Result<ExitCode, ViaError> {
36 let cli = Cli::parse(args)?;
37 run_cli(cli)
38}
39
40fn run_cli(cli: Cli) -> Result<ExitCode, ViaError> {
41 let config_path = cli.config_path.as_deref();
42 match cli.command {
43 Command::Help => run_help_command(),
44 Command::Version => run_version_command(),
45 Command::Login { provider } => run_login_command(config_path, provider.as_deref()),
46 Command::Capabilities { json } => run_capabilities_command(config_path, json),
47 Command::Config(command) => run_config_command(config_path, command),
48 Command::Daemon(command) => run_daemon_cli_command(command),
49 Command::SkillPrint => run_skill_print_command(config_path),
50 Command::Invoke {
51 service,
52 capability,
53 args,
54 } => run_invoke_command(config_path, service, capability, args),
55 }
56}
57
58fn run_help_command() -> Result<ExitCode, ViaError> {
59 print_help();
60 Ok(ExitCode::SUCCESS)
61}
62
63fn run_version_command() -> Result<ExitCode, ViaError> {
64 println!("via {}", env!("CARGO_PKG_VERSION"));
65 Ok(ExitCode::SUCCESS)
66}
67
68fn run_login_command(
69 config_path: Option<&Path>,
70 provider: Option<&str>,
71) -> Result<ExitCode, ViaError> {
72 crate::login::run(config_path, provider)?;
73 Ok(ExitCode::SUCCESS)
74}
75
76fn run_capabilities_command(config_path: Option<&Path>, json: bool) -> Result<ExitCode, ViaError> {
77 let config = Config::load(config_path)?;
78 crate::capabilities::print(&config, json)?;
79 Ok(ExitCode::SUCCESS)
80}
81
82fn run_config_command(
83 config_path: Option<&Path>,
84 command: crate::cli::ConfigCommand,
85) -> Result<ExitCode, ViaError> {
86 crate::config_command::run(config_path, command)?;
87 Ok(ExitCode::SUCCESS)
88}
89
90fn run_daemon_cli_command(command: DaemonCommand) -> Result<ExitCode, ViaError> {
91 run_daemon_command(command)?;
92 Ok(ExitCode::SUCCESS)
93}
94
95fn run_skill_print_command(config_path: Option<&Path>) -> Result<ExitCode, ViaError> {
96 let config = Config::load(config_path)?;
97 crate::skill::print(&config);
98 Ok(ExitCode::SUCCESS)
99}
100
101fn run_invoke_command(
102 config_path: Option<&Path>,
103 service: String,
104 capability: String,
105 args: Vec<String>,
106) -> Result<ExitCode, ViaError> {
107 let config = Config::load(config_path)?;
108 let providers = ProviderRegistry::from_config(&config)?;
109 executor::invoke(&config, &providers, &service, &capability, args)?;
110 Ok(ExitCode::SUCCESS)
111}
112
113fn run_daemon_command(command: DaemonCommand) -> Result<(), ViaError> {
114 match command {
115 DaemonCommand::Status => crate::daemon::status(),
116 DaemonCommand::Clear => crate::daemon::clear(),
117 DaemonCommand::Stop => crate::daemon::stop(),
118 DaemonCommand::Serve => crate::daemon::serve(),
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn capabilities_command_returns_success() {
128 let code = try_run([
129 OsString::from("via"),
130 OsString::from("--config"),
131 OsString::from("examples/github.toml"),
132 OsString::from("capabilities"),
133 ])
134 .unwrap();
135
136 assert_eq!(code, ExitCode::SUCCESS);
137 }
138
139 #[test]
140 fn version_command_returns_success() {
141 let code = try_run([OsString::from("via"), OsString::from("version")]).unwrap();
142
143 assert_eq!(code, ExitCode::SUCCESS);
144 }
145
146 #[test]
147 fn login_command_without_config_returns_runtime_error() {
148 let code = run([
149 OsString::from("via"),
150 OsString::from("--config"),
151 OsString::from("/definitely/missing/via.toml"),
152 OsString::from("login"),
153 ]);
154
155 assert_eq!(code, ExitCode::from(1));
156 }
157
158 #[test]
159 fn maps_success_result_to_exit_code() {
160 let code = exit_code_from_result(Ok(ExitCode::SUCCESS));
161
162 assert_eq!(code, ExitCode::SUCCESS);
163 }
164
165 #[test]
166 fn run_returns_usage_code_for_cli_error() {
167 let code = run([OsString::from("via"), OsString::from("--unknown")]);
168
169 assert_eq!(code, ExitCode::from(2));
170 }
171
172 #[test]
173 fn run_returns_runtime_code_for_runtime_error() {
174 let code = run([
175 OsString::from("via"),
176 OsString::from("--config"),
177 OsString::from("/definitely/missing/via.toml"),
178 OsString::from("capabilities"),
179 ]);
180
181 assert_eq!(code, ExitCode::from(1));
182 }
183}