agent_first_http/cli/
mod.rs1pub mod args;
4pub mod cmd;
5pub mod output;
6
7use std::process::ExitCode;
8
9use crate::shared::error::Error;
10
11pub fn run() -> ExitCode {
15 let _stream_redirect =
16 match agent_first_data::stream_redirect::install_from_raw_args(std::env::args()) {
17 Ok(redirect) => redirect,
18 Err(err) => {
19 emit_bootstrap_error(&err.to_string());
20 return ExitCode::from(2);
21 }
22 };
23
24 crate::host::bootstrap::install_rustls_provider();
29
30 if let Some(code) = maybe_render_version() {
33 return code;
34 }
35 if let Some(code) = maybe_render_help() {
38 return code;
39 }
40 match std::thread::Builder::new()
46 .name("afhttp-main".to_string())
47 .stack_size(16 * 1024 * 1024)
48 .spawn(run_blocking)
49 {
50 Ok(handle) => match handle.join() {
51 Ok(code) => code,
52 Err(_) => {
53 emit_bootstrap_error("afhttp worker thread panicked");
54 ExitCode::from(2)
55 }
56 },
57 Err(e) => {
58 emit_bootstrap_error(&format!("spawn worker thread: {e}"));
59 ExitCode::from(2)
60 }
61 }
62}
63
64fn run_blocking() -> ExitCode {
67 let rt = match tokio::runtime::Builder::new_multi_thread()
68 .enable_all()
69 .thread_stack_size(16 * 1024 * 1024)
70 .build()
71 {
72 Ok(rt) => rt,
73 Err(e) => {
74 emit_bootstrap_error(&format!("tokio runtime: {e}"));
75 return ExitCode::from(2);
76 }
77 };
78 let exit = rt.block_on(async {
79 match args::parse() {
80 Ok(parsed) => dispatch(parsed).await,
81 Err(err) => {
82 emit_cli_error(&err);
83 Err(err)
84 }
85 }
86 });
87 match exit {
88 Ok(()) => ExitCode::SUCCESS,
89 Err(_) => ExitCode::from(1),
90 }
91}
92
93fn maybe_render_version() -> Option<ExitCode> {
95 use std::io::Write;
96
97 let raw: Vec<String> = std::env::args().collect();
98 let mut handle = std::io::stdout();
99 match agent_first_data::cli_handle_version_or_continue(
100 &raw,
101 "afhttp",
102 env!("CARGO_PKG_VERSION"),
103 &agent_first_data::VersionConfig::conventional_default(),
104 ) {
105 Ok(Some(version)) => {
106 let _ = write!(handle, "{version}");
107 Some(ExitCode::SUCCESS)
108 }
109 Ok(None) => None,
110 Err(err) => {
111 let _ = writeln!(handle, "{}", agent_first_data::output_json(&err));
112 Some(ExitCode::from(2))
113 }
114 }
115}
116
117fn maybe_render_help() -> Option<ExitCode> {
119 use clap::CommandFactory;
120 use std::io::Write;
121
122 let raw: Vec<String> = std::env::args().collect();
123 let mut handle = std::io::stdout();
124 match agent_first_data::cli_handle_help_or_continue(
125 &raw,
126 &args::Cli::command(),
127 &agent_first_data::HelpConfig::human_cli_default(),
128 ) {
129 Ok(Some(help)) => {
130 let _ = write!(handle, "{help}");
131 Some(ExitCode::SUCCESS)
132 }
133 Ok(None) => None,
134 Err(err) => {
135 let _ = writeln!(handle, "{}", agent_first_data::output_json(&err));
136 Some(ExitCode::from(2))
137 }
138 }
139}
140
141async fn dispatch(parsed: args::Parsed) -> Result<(), Error> {
142 let command = match parsed.command {
143 args::Command::Fetch(a) => {
144 return cmd::fetch::run(*a).await;
147 }
148 command => command,
149 };
150 let res = match command {
151 args::Command::Host(a) => cmd::host::run(a).await,
152 args::Command::Fetch(_) => unreachable!("fetch handled above"),
153 args::Command::Upload(a) => cmd::upload::run(a).await,
154 args::Command::Cdp(a) => cmd::cdp::run(a).await,
155 args::Command::Panel(a) => cmd::panel::run(a).await,
156 args::Command::Health(a) => cmd::health::run(a).await,
157 args::Command::Capabilities(a) => cmd::capabilities::run(a).await,
158 args::Command::Profile(a) => cmd::profile::run(a).await,
159 args::Command::Tabs(a) => cmd::tabs::run(a).await,
160 args::Command::Skill(a) => cmd::skill::run(a).await,
161 args::Command::Container(a) => cmd::container::run(a).await,
162 };
163 if let Err(ref e) = res {
164 emit_cli_error(e);
165 }
166 res
167}
168
169fn emit_cli_error(err: &Error) {
170 let stdout = std::io::stdout();
171 let mut handle = stdout.lock();
172 let _ = crate::shared::envelope::emit_error(&mut handle, err);
173}
174
175fn emit_bootstrap_error(msg: &str) {
176 use std::io::Write;
179 let stdout = std::io::stdout();
180 let mut handle = stdout.lock();
181 let _ = writeln!(
182 handle,
183 "{{\"code\":\"error\",\"error_code\":\"internal_error\",\"error\":{},\"retryable\":false}}",
184 serde_json::Value::String(msg.to_string())
185 );
186}