#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
use clap::Parser;
use openlatch_client::cli;
use openlatch_client::cli::Commands;
use openlatch_client::error::OlError;
use openlatch_client::telemetry::{self, Event};
use openlatch_client::update;
enum RollbackEvent {
Success { from: String, to: String },
Failed,
}
#[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 is_daemon_entry = std::env::args().any(|a| a == "--foreground");
let rollback_event = if is_daemon_entry && update::should_rollback() {
let (from, to) = update::read_sentinel()
.map(|s| (s.from, s.to))
.unwrap_or_else(|| ("(unknown)".into(), "(unknown)".into()));
match update::rollback_from_bak() {
Ok(()) => {
eprintln!("[openlatch] rolled back update due to supervisor restart loop");
Some(RollbackEvent::Success { from, to })
}
Err(e) => {
eprintln!("[openlatch] rollback failed: {e}");
Some(RollbackEvent::Failed)
}
}
} else {
None
};
let _ = ctrlc::set_handler(|| {
#[cfg(feature = "crash-report")]
openlatch_client::crash_report::flush(std::time::Duration::from_secs(2));
std::process::exit(130);
});
#[cfg(feature = "crash-report")]
let _crash_guard = init_crash_report();
init_telemetry();
install_panic_hook();
if let Some(RollbackEvent::Success { from, to }) = rollback_event {
telemetry::capture_global(Event::update_completed(
&from,
&to,
"normal",
"in_process",
false,
None,
true,
));
}
let cli_args = cli::Cli::parse();
let output = cli::build_output_config(&cli_args);
let Some(command) = cli_args.command.as_ref() else {
use clap::CommandFactory;
cli::header::print_full_banner(&output);
let _ = cli::Cli::command().print_help();
println!();
return;
};
let started_at = std::time::Instant::now();
let (command_label, subcommand_label) = command_labels(command);
#[cfg(feature = "crash-report")]
openlatch_client::crash_report::enrich_cli_scope(command_label);
let result = dispatch(command, &output);
let exit_code = result.as_ref().err().map_or(0, OlError::exit_code);
let duration_ms = started_at.elapsed().as_millis().min(u128::from(u64::MAX)) as u64;
telemetry::capture_global(Event::command_invoked(
command_label,
subcommand_label,
exit_code,
duration_ms,
));
if let Err(e) = result {
output.print_error(&e);
std::process::exit(exit_code);
}
}
#[cfg(feature = "crash-report")]
fn init_crash_report() -> Option<sentry::ClientInitGuard> {
let dir = openlatch_client::config::openlatch_dir();
openlatch_client::crash_report::init(&dir)
}
fn init_telemetry() {
let dir = openlatch_client::config::openlatch_dir();
let agent_id =
openlatch_client::config::sniff_agent_id(&dir).unwrap_or_else(|| "agt_unknown".to_string());
let baked_key_present = openlatch_client::telemetry::network::key_is_present();
let handle = telemetry::init(&dir, agent_id, false, baked_key_present);
let _ = telemetry::install_global(handle);
}
fn install_panic_hook() {
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let location = info
.location()
.map(|l| format!("{}:{}", l.file(), l.line()))
.unwrap_or_else(|| "unknown".to_string());
telemetry::capture_global(Event::daemon_crashed(&location, 0));
prev(info);
}));
}
fn command_labels(cmd: &Commands) -> (&'static str, Option<&'static str>) {
match cmd {
Commands::Init(_) => ("init", None),
Commands::Status => ("status", None),
Commands::Start(_) => ("start", None),
Commands::Stop => ("stop", None),
Commands::Restart => ("restart", None),
Commands::Logs(_) => ("logs", None),
Commands::Doctor(_) => ("doctor", None),
Commands::Uninstall(_) => ("uninstall", None),
Commands::Docs => ("docs", None),
Commands::Hooks { cmd } => (
"hooks",
Some(match cmd {
cli::HooksCommands::Install(_) => "install",
cli::HooksCommands::Uninstall(_) => "uninstall",
cli::HooksCommands::Status => "status",
}),
),
Commands::Daemon { cmd } => (
"daemon",
Some(match cmd {
cli::DaemonCommands::Start(_) => "start",
cli::DaemonCommands::Stop => "stop",
cli::DaemonCommands::Restart => "restart",
}),
),
Commands::Auth { cmd } => (
"auth",
Some(match cmd {
cli::AuthCommands::Login(_) => "login",
cli::AuthCommands::Logout => "logout",
cli::AuthCommands::Status => "status",
}),
),
Commands::Telemetry { cmd } => (
"telemetry",
Some(match cmd {
cli::TelemetryCommands::Status => "status",
cli::TelemetryCommands::Enable => "enable",
cli::TelemetryCommands::Disable => "disable",
cli::TelemetryCommands::Purge => "purge",
cli::TelemetryCommands::Debug => "debug",
}),
),
Commands::Supervision { cmd } => (
"supervision",
Some(match cmd {
cli::SupervisionCommands::Install => "install",
cli::SupervisionCommands::Uninstall => "uninstall",
cli::SupervisionCommands::Status => "status",
cli::SupervisionCommands::Enable => "enable",
cli::SupervisionCommands::Disable => "disable",
}),
),
Commands::Update(_) => ("update", None),
Commands::Inventory { cmd } => (
"inventory",
Some(match cmd {
cli::InventoryCommands::List(_) => "list",
cli::InventoryCommands::Log(_) => "log",
cli::InventoryCommands::Rescan(_) => "rescan",
cli::InventoryCommands::Status(_) => "status",
cli::InventoryCommands::Inspect(_) => "inspect",
cli::InventoryCommands::Projects(_) => "projects",
cli::InventoryCommands::Ack(_) => "ack",
}),
),
Commands::SpikeUpdate(_) => ("__spike-update", None),
Commands::Boundary { cmd } => (
"boundary",
Some(match cmd {
cli::BoundaryCommands::Status => "status",
cli::BoundaryCommands::Explain { .. } => "explain",
}),
),
Commands::Bench { cmd } => (
"bench",
Some(match cmd {
cli::BenchCommands::PanicIsolation(_) => "panic-isolation",
cli::BenchCommands::PortStability => "port-stability",
cli::BenchCommands::ExportFixtures => "export-fixtures",
cli::BenchCommands::TransformEgress => "transform-egress",
cli::BenchCommands::Replay(_) => "replay",
cli::BenchCommands::CacheBaseline(_) => "cache-baseline",
cli::BenchCommands::Compare(_) => "compare",
}),
),
}
}
fn dispatch(
command: &Commands,
output: &openlatch_client::cli::output::OutputConfig,
) -> Result<(), openlatch_client::error::OlError> {
match command {
Commands::Init(args) => openlatch_client::cli::commands::init::run_init(args, output),
Commands::Status => openlatch_client::cli::commands::status::run_status(output),
Commands::Start(args) => {
openlatch_client::cli::commands::lifecycle::run_start(args, output)
}
Commands::Stop => openlatch_client::cli::commands::lifecycle::run_stop(output),
Commands::Restart => openlatch_client::cli::commands::lifecycle::run_restart(output),
Commands::Logs(args) => openlatch_client::cli::commands::logs::run_logs(args, output),
Commands::Doctor(args) => openlatch_client::cli::commands::doctor::run_doctor(args, output),
Commands::Uninstall(args) => {
openlatch_client::cli::commands::uninstall::run_uninstall(args, output)
}
Commands::Docs => openlatch_client::cli::commands::docs::run_docs(output),
Commands::Hooks { cmd } => match cmd {
cli::HooksCommands::Install(args) => {
openlatch_client::cli::commands::init::run_init(args, output)
}
cli::HooksCommands::Uninstall(args) => {
openlatch_client::cli::commands::uninstall::run_uninstall(args, output)
}
cli::HooksCommands::Status => {
openlatch_client::cli::commands::status::run_status(output)
}
},
Commands::Daemon { cmd } => match cmd {
cli::DaemonCommands::Start(args) => {
openlatch_client::cli::commands::lifecycle::run_start(args, output)
}
cli::DaemonCommands::Stop => {
openlatch_client::cli::commands::lifecycle::run_stop(output)
}
cli::DaemonCommands::Restart => {
openlatch_client::cli::commands::lifecycle::run_restart(output)
}
},
Commands::Auth { cmd } => match cmd {
cli::AuthCommands::Login(args) => {
openlatch_client::cli::commands::auth::run_login(args, output)
}
cli::AuthCommands::Logout => openlatch_client::cli::commands::auth::run_logout(output),
cli::AuthCommands::Status => openlatch_client::cli::commands::auth::run_status(output),
},
Commands::Telemetry { cmd } => openlatch_client::cli::commands::telemetry::run(cmd, output),
Commands::Supervision { cmd } => {
openlatch_client::cli::commands::supervision::run(cmd, output)
}
Commands::Update(args) => {
let code = openlatch_client::cli::commands::update::run(args, output);
std::process::exit(code);
}
Commands::Inventory { cmd } => {
openlatch_client::cli::commands::inventory::run_inventory(cmd, output)
}
Commands::SpikeUpdate(args) => {
let success = openlatch_client::cli::commands::spike_update::run(args);
std::process::exit(if success { 0 } else { 1 });
}
Commands::Boundary { cmd } => openlatch_client::cli::commands::boundary::run(cmd, output),
Commands::Bench { cmd } => match cmd {
cli::BenchCommands::PanicIsolation(args) => {
openlatch_client::boundary::bench::run_panic_isolation(&args.inject)
}
cli::BenchCommands::PortStability => {
openlatch_client::boundary::bench::run_port_stability()
}
cli::BenchCommands::ExportFixtures => {
openlatch_client::boundary::bench::run_export_fixtures()
}
cli::BenchCommands::TransformEgress => {
openlatch_client::boundary::bench::run_transform_egress()
}
cli::BenchCommands::Replay(args) => {
openlatch_client::boundary::bench::run_replay(&args.rule, args.runs)
}
cli::BenchCommands::CacheBaseline(args) => {
let out = args.out.clone().or_else(|| args.out_positional.clone());
openlatch_client::boundary::bench::run_cache_baseline(
args.turns,
args.with_mcp,
args.direct,
out.as_deref(),
)
}
cli::BenchCommands::Compare(args) => {
openlatch_client::boundary::bench::run_compare(&args.a, &args.b)
}
},
}
}