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_help() {
24 return code;
25 }
26 match std::thread::Builder::new()
32 .name("afhttp-main".to_string())
33 .stack_size(16 * 1024 * 1024)
34 .spawn(run_blocking)
35 {
36 Ok(handle) => match handle.join() {
37 Ok(code) => code,
38 Err(_) => {
39 emit_bootstrap_error("afhttp worker thread panicked");
40 ExitCode::from(2)
41 }
42 },
43 Err(e) => {
44 emit_bootstrap_error(&format!("spawn worker thread: {e}"));
45 ExitCode::from(2)
46 }
47 }
48}
49
50fn run_blocking() -> ExitCode {
53 let rt = match tokio::runtime::Builder::new_multi_thread()
54 .enable_all()
55 .thread_stack_size(16 * 1024 * 1024)
56 .build()
57 {
58 Ok(rt) => rt,
59 Err(e) => {
60 emit_bootstrap_error(&format!("tokio runtime: {e}"));
61 return ExitCode::from(2);
62 }
63 };
64 let exit = rt.block_on(async {
65 match args::parse() {
66 Ok(parsed) => dispatch(parsed).await,
67 Err(err) => {
68 emit_cli_error(&err);
69 Err(err)
70 }
71 }
72 });
73 match exit {
74 Ok(()) => ExitCode::SUCCESS,
75 Err(_) => ExitCode::from(1),
76 }
77}
78
79fn maybe_render_help() -> Option<ExitCode> {
81 use clap::CommandFactory;
82 use std::io::Write;
83
84 let raw: Vec<String> = std::env::args().collect();
85 let mut handle = std::io::stdout();
86 match agent_first_data::cli_handle_help_or_continue(
87 &raw,
88 &args::Cli::command(),
89 &agent_first_data::HelpConfig::human_cli_default(),
90 ) {
91 Ok(Some(help)) => {
92 let _ = write!(handle, "{help}");
93 Some(ExitCode::SUCCESS)
94 }
95 Ok(None) => None,
96 Err(err) => {
97 let _ = writeln!(handle, "{}", agent_first_data::output_json(&err));
98 Some(ExitCode::from(2))
99 }
100 }
101}
102
103async fn dispatch(parsed: args::Parsed) -> Result<(), Error> {
104 let command = match parsed.command {
105 args::Command::Fetch(a) => {
106 return cmd::fetch::run(*a).await;
109 }
110 command => command,
111 };
112 let res = match command {
113 args::Command::Host(a) => cmd::host::run(a).await,
114 args::Command::Fetch(_) => unreachable!("fetch handled above"),
115 args::Command::Upload(a) => cmd::upload::run(a).await,
116 args::Command::Cdp(a) => cmd::cdp::run(a).await,
117 args::Command::Ui(a) => cmd::ui::run(a).await,
118 args::Command::Takeover(a) => cmd::takeover::run(a).await,
119 args::Command::Health(a) => cmd::health::run(a).await,
120 args::Command::Capabilities(a) => cmd::capabilities::run(a).await,
121 args::Command::Profile(a) => cmd::profile::run(a).await,
122 args::Command::Tabs(a) => cmd::tabs::run(a).await,
123 args::Command::Skill(a) => cmd::skill::run(a).await,
124 args::Command::Container(a) => cmd::container::run(a).await,
125 };
126 if let Err(ref e) = res {
127 emit_cli_error(e);
128 }
129 res
130}
131
132fn emit_cli_error(err: &Error) {
133 let stdout = std::io::stdout();
134 let mut handle = stdout.lock();
135 let _ = crate::shared::envelope::emit_error(&mut handle, err);
136}
137
138fn emit_bootstrap_error(msg: &str) {
139 use std::io::Write;
142 let stdout = std::io::stdout();
143 let mut handle = stdout.lock();
144 let _ = writeln!(
145 handle,
146 "{{\"code\":\"error\",\"error_code\":\"internal_error\",\"error\":{},\"retryable\":false}}",
147 serde_json::Value::String(msg.to_string())
148 );
149}