use apcore::ModuleError;
use clap::{CommandFactory, Parser};
use tracing_subscriber::EnvFilter;
use apexe::cli::Cli;
use apexe::errors::ApexeError;
fn print_man_page_if_requested(raw_args: &[String]) -> bool {
if !apcore_cli::has_man_flag(raw_args) {
return false;
}
let cmd = Cli::command();
let man = apcore_cli::build_program_man_page(
&cmd,
"apexe",
apexe::VERSION,
Some("Outside-In CLI-to-Agent Bridge"),
apcore_cli::get_docs_url().as_deref(),
);
println!("{man}");
true
}
fn init_logging(cli: &Cli) {
let config_level = apexe::config::load_config(None)
.map(|c| c.log_level)
.unwrap_or_else(|_| "info".to_string());
let fallback_level = cli.effective_log_level(&config_level);
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&fallback_level)),
)
.init();
}
fn render_error(error: anyhow::Error) -> String {
let module_err = match error.downcast::<ModuleError>() {
Ok(module_err) => module_err,
Err(error) => match error.downcast::<ApexeError>() {
Ok(apexe_err) => apexe_err.into(),
Err(error) => return format!("Error: {error}"),
},
};
let mut rendered = format!("Error: {}", module_err.message);
if let Some(ref guidance) = module_err.ai_guidance {
rendered.push_str(&format!("\nSuggestion: {guidance}"));
}
rendered
}
fn report_error(error: anyhow::Error) {
eprintln!("{}", render_error(error));
}
fn main() {
apcore_cli::set_docs_url(Some("https://github.com/aiperceivable/apexe".to_string()));
let raw_args: Vec<String> = std::env::args().collect();
if print_man_page_if_requested(&raw_args) {
return;
}
let cli = Cli::parse();
init_logging(&cli);
if let Err(e) = cli.run() {
report_error(e);
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_report_error_renders_guidance_for_a_module_error() {
let module_err = ModuleError::new(
apcore::ErrorCode::GeneralInternalError,
"failed to create /no/such/dir: Not a directory (os error 20)",
)
.with_ai_guidance("Check that the parent path is a directory you can write to.");
let error: anyhow::Error = module_err.into();
let rendered = render_error(error);
assert!(
rendered.contains("failed to create /no/such/dir"),
"{rendered}"
);
assert!(
rendered.contains("Suggestion: Check that the parent path"),
"{rendered}"
);
assert!(
!rendered.contains("GeneralInternalError"),
"the internal error-code tag must not leak to the user: {rendered}"
);
}
#[test]
fn test_report_error_converts_an_apexe_error_to_module_error_for_rendering() {
let error: anyhow::Error = ApexeError::ToolNotFound {
tool_name: "zzz".to_string(),
}
.into();
let rendered = render_error(error);
assert!(rendered.starts_with("Error:"), "{rendered}");
assert!(
rendered.contains("Suggestion:"),
"ToolNotFound carries ai_guidance: {rendered}"
);
}
#[test]
fn test_report_error_falls_back_to_display_for_an_untyped_anyhow_error() {
let error = anyhow::anyhow!("plain io failure");
let rendered = render_error(error);
assert_eq!(rendered, "Error: plain io failure");
}
}