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