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