pub mod args;
pub mod cmd;
pub mod output;
use std::process::ExitCode;
use crate::shared::error::Error;
pub fn run() -> ExitCode {
crate::host::bootstrap::install_rustls_provider();
if let Some(code) = maybe_render_version() {
return code;
}
if let Some(code) = maybe_render_help() {
return code;
}
match std::thread::Builder::new()
.name("afhttp-main".to_string())
.stack_size(16 * 1024 * 1024)
.spawn(run_blocking)
{
Ok(handle) => match handle.join() {
Ok(code) => code,
Err(_) => {
emit_bootstrap_error("afhttp worker thread panicked");
ExitCode::from(2)
}
},
Err(e) => {
emit_bootstrap_error(&format!("spawn worker thread: {e}"));
ExitCode::from(2)
}
}
}
fn run_blocking() -> ExitCode {
let rt = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_stack_size(16 * 1024 * 1024)
.build()
{
Ok(rt) => rt,
Err(e) => {
emit_bootstrap_error(&format!("tokio runtime: {e}"));
return ExitCode::from(2);
}
};
let exit = rt.block_on(async {
match args::parse() {
Ok(parsed) => dispatch(parsed).await,
Err(err) => {
emit_cli_error(&err);
Err(err)
}
}
});
match exit {
Ok(()) => ExitCode::SUCCESS,
Err(_) => ExitCode::from(1),
}
}
fn maybe_render_version() -> Option<ExitCode> {
use std::io::Write;
let raw: Vec<String> = std::env::args().collect();
let mut handle = std::io::stdout();
match agent_first_data::cli_handle_version_or_continue(
&raw,
"afhttp",
env!("CARGO_PKG_VERSION"),
&agent_first_data::VersionConfig::conventional_default(),
) {
Ok(Some(version)) => {
let _ = write!(handle, "{version}");
Some(ExitCode::SUCCESS)
}
Ok(None) => None,
Err(err) => {
let _ = writeln!(handle, "{}", agent_first_data::output_json(&err));
Some(ExitCode::from(2))
}
}
}
fn maybe_render_help() -> Option<ExitCode> {
use clap::CommandFactory;
use std::io::Write;
let raw: Vec<String> = std::env::args().collect();
let mut handle = std::io::stdout();
match agent_first_data::cli_handle_help_or_continue(
&raw,
&args::Cli::command(),
&agent_first_data::HelpConfig::human_cli_default(),
) {
Ok(Some(help)) => {
let _ = write!(handle, "{help}");
Some(ExitCode::SUCCESS)
}
Ok(None) => None,
Err(err) => {
let _ = writeln!(handle, "{}", agent_first_data::output_json(&err));
Some(ExitCode::from(2))
}
}
}
async fn dispatch(parsed: args::Parsed) -> Result<(), Error> {
let command = match parsed.command {
args::Command::Fetch(a) => {
return cmd::fetch::run(*a).await;
}
command => command,
};
let res = match command {
args::Command::Host(a) => cmd::host::run(a).await,
args::Command::Fetch(_) => unreachable!("fetch handled above"),
args::Command::Upload(a) => cmd::upload::run(a).await,
args::Command::Cdp(a) => cmd::cdp::run(a).await,
args::Command::Panel(a) => cmd::panel::run(a).await,
args::Command::Health(a) => cmd::health::run(a).await,
args::Command::Capabilities(a) => cmd::capabilities::run(a).await,
args::Command::Profile(a) => cmd::profile::run(a).await,
args::Command::Tabs(a) => cmd::tabs::run(a).await,
args::Command::Skill(a) => cmd::skill::run(a).await,
args::Command::Container(a) => cmd::container::run(a).await,
};
if let Err(ref e) = res {
emit_cli_error(e);
}
res
}
fn emit_cli_error(err: &Error) {
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let _ = crate::shared::envelope::emit_error(&mut handle, err);
}
fn emit_bootstrap_error(msg: &str) {
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
let _ = writeln!(
handle,
"{{\"code\":\"error\",\"error_code\":\"internal_error\",\"error\":{},\"retryable\":false}}",
serde_json::Value::String(msg.to_string())
);
}