#![cfg_attr(docsrs, feature(doc_cfg))]
#![warn(missing_docs)]
#![warn(rustdoc::missing_crate_level_docs)]
#![deny(rustdoc::broken_intra_doc_links)]
#![warn(rustdoc::private_intra_doc_links)]
#![deny(rustdoc::invalid_html_tags)]
#![deny(rustdoc::invalid_rust_codeblocks)]
#![deny(rustdoc::bare_urls)]
#![warn(rustdoc::redundant_explicit_links)]
pub mod browser;
pub mod cache;
pub mod cli;
pub mod color;
pub mod commands_prd;
pub mod constants;
pub mod doctor;
pub mod envelope;
pub mod error;
pub mod find_paths;
pub mod i18n;
pub mod install;
pub mod lifecycle;
pub mod llm_local;
pub mod mitm_local;
pub mod native;
pub mod qr_local;
pub mod residual;
pub mod retry;
pub mod robots;
pub mod scrape_local;
pub mod sg_local;
pub mod sheet_local;
pub mod validation;
pub mod win_job;
pub mod workflow_local;
pub mod xdg;
#[cfg(test)]
#[allow(missing_docs)]
pub mod test_utils;
use std::process::ExitCode;
use clap::{CommandFactory, Parser};
use crate::cli::Cli;
use crate::error::CliError;
use crate::lifecycle::Lifecycle;
#[cfg_attr(doc, aquamarine::aquamarine)]
pub fn run() -> ExitCode {
#[cfg(unix)]
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
#[cfg(windows)]
{
std::env::set_var("MSYS_NO_PATHCONV", "1");
std::env::set_var("MSYS2_ARG_CONV_EXCL", "*");
}
let life = Lifecycle::new();
let cli = match Cli::try_parse() {
Ok(c) => c,
Err(e) => {
let code = e.exit_code();
let wants_json = std::env::args_os().any(|a| a == "--json");
let is_help_or_version = matches!(
e.kind(),
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion
);
if wants_json && !is_help_or_version && code != 0 {
let msg = e.to_string();
let err = crate::error::CliError::with_suggestion(
crate::error::ErrorKind::Usage,
msg.lines().next().unwrap_or("invalid arguments").to_string(),
"Check --help or schema --cmd <name>; pass valid argv",
);
let _ = crate::envelope::print_error_json(&err);
} else {
let _ = e.print();
}
life.finalize();
return ExitCode::from(code as u8);
}
};
let lang = crate::i18n::resolve_lang(cli.globals.lang.as_deref());
crate::i18n::set_effective_lang(lang);
init_tracing(
cli.globals.quiet,
cli.globals.verbose,
cli.globals.debug,
cli.globals.lang.as_deref(),
);
let code = commands_prd::dispatch(cli, &life);
life.finalize();
if code <= 0 {
ExitCode::SUCCESS
} else if code >= 256 {
ExitCode::from(255)
} else {
ExitCode::from(code as u8)
}
}
pub fn command_factory_debug_assert() {
Cli::command().debug_assert();
}
pub fn exit_code_for(err: &CliError) -> u8 {
err.exit_code()
}
fn init_tracing(quiet: bool, verbose: bool, debug: bool, _cli_lang: Option<&str>) {
use tracing_subscriber::EnvFilter;
let xdg_level = crate::xdg::load_config()
.ok()
.and_then(|c| c.log_level)
.filter(|s| !s.trim().is_empty());
let filter = if quiet {
EnvFilter::new("error")
} else if debug {
EnvFilter::new("debug")
} else if verbose {
EnvFilter::new("info")
} else if let Some(level) = xdg_level {
EnvFilter::new(level)
} else {
EnvFilter::new("error")
};
let log_to_file = crate::xdg::load_config()
.ok()
.and_then(|c| c.log_to_file)
.unwrap_or(false);
if log_to_file {
if let Ok(state) = crate::xdg::state_dir() {
let log_dir = state.join("log");
let _ = std::fs::create_dir_all(&log_dir);
let file_appender =
tracing_appender::rolling::daily(&log_dir, "browser-automation-cli");
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
std::mem::forget(_guard);
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(non_blocking)
.with_target(false)
.try_init();
return;
}
}
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.with_target(false)
.try_init();
}