#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "console")]
use clap::{CommandFactory, Parser};
use saferskills::cli::output::OutputConfig;
use saferskills::cli::{self, Cli, Commands};
use saferskills::commands;
use saferskills::core::config::Config;
use saferskills::core::crash_report;
use saferskills::core::error::SsError;
use saferskills::core::telemetry;
#[cfg(windows)]
fn attach_parent_console_if_any() {
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};
unsafe {
let _ = AttachConsole(ATTACH_PARENT_PROCESS);
}
}
#[cfg(not(windows))]
fn attach_parent_console_if_any() {}
fn main() {
attach_parent_console_if_any();
let _crash_guard = crash_report::init();
crash_report::install_panic_hook();
let _ = ctrlc::set_handler(|| {
saferskills::tui::terminal::restore_on_signal();
crash_report::flush(std::time::Duration::from_secs(2));
std::process::exit(130);
});
let exit_code = match tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
{
Ok(rt) => rt.block_on(run()),
Err(e) => {
eprintln!("Error: failed to start async runtime: {e} (SS-E-9999)");
1
}
};
std::process::exit(exit_code);
}
async fn run() -> i32 {
let cli = Cli::parse();
let output = cli::build_output_config(&cli);
let inter = cli::interaction(&cli);
let machine_output = matches!(
cli.command,
Some(Commands::Completion { .. }) | Some(Commands::Man)
);
if !machine_output {
cli::header::print(&output);
}
let config = Config::load().unwrap_or_default();
let telemetry_on = telemetry::is_enabled(telemetry::resolve_telemetry_consent(
&output,
&config,
inter.non_interactive || machine_output,
));
let Some(command) = cli.command.as_ref() else {
let _ = Cli::command().print_help();
return 0;
};
let (cmd_label, sub_label) = cli::command_label(command);
crash_report::enrich_cli_scope(cmd_label, sub_label);
let started = std::time::Instant::now();
commands::audit::maybe_first_run_audit(inter, &output).await;
let result = dispatch(command, inter, &output).await;
let exit_code = match &result {
Ok(()) => 0,
Err(e) => e.exit_code(),
};
let duration_ms = started.elapsed().as_millis().min(u128::from(u64::MAX)) as u64;
telemetry::capture_command_invoked(cmd_label, sub_label, exit_code, duration_ms, telemetry_on)
.await;
if let Err(e) = result {
if output.verbose && !output.is_json() {
eprintln!("{:?}", miette::Report::new(e));
} else {
output.print_error(&e);
}
}
exit_code
}
async fn dispatch(
command: &Commands,
inter: cli::Interaction,
output: &OutputConfig,
) -> Result<(), SsError> {
match command {
Commands::Info(args) => commands::info::run_info(args, output).await,
Commands::Install(args) => commands::install::run_install(args, inter, output).await,
Commands::Uninstall(args) => commands::uninstall::run_uninstall(args, inter, output).await,
Commands::Update(args) => commands::update::run_update(args, inter, output).await,
Commands::List(args) => commands::list::run_list(args, inter, output).await,
Commands::Search(args) => commands::search::run_search(args, inter, output).await,
Commands::Capability(args) => commands::capability::run_capability(args, output).await,
Commands::Agent(args) => commands::agent::run_agent(args, inter, output).await,
Commands::Doctor(args) => commands::doctor::run_doctor(args, inter, output).await,
Commands::Completion { shell } => commands::completion::run_completion(*shell, output),
Commands::Man => commands::completion::run_man(output),
}
}