openlatch-provider 0.1.0

Self-service onboarding CLI + runtime daemon for OpenLatch Editors and Providers
// Binary entry point. `anyhow` is allowed only here; library code uses
// `thiserror` per `.claude/rules/error-handling.md`.

use anyhow::Result;
use openlatch_provider as lib;
use std::process::ExitCode;
use std::time::Instant;

#[tokio::main]
async fn main() -> Result<ExitCode> {
    // Bootstrap config + machine_id (telemetry identity). Failures here are
    // non-fatal — telemetry just stays disabled if the config can't be read.
    let cfg = lib::config::Config::load().unwrap_or_default();
    let machine_id = lib::config::machine_id_or_init().ok();

    // Sentry first — its panic hook needs to be installed before anything
    // that might panic. Hold the guard for the lifetime of `main`.
    let _sentry_guard = lib::telemetry::sentry::init_if_enabled(&cfg);

    // PostHog handle — opt-in. When consent is missing or the baked key is
    // empty, this is a no-op handle so `capture_global` is free.
    let provider_dir = lib::config::provider_dir();
    let handle = match machine_id.as_ref() {
        Some(id) => lib::telemetry::init(&provider_dir, id.clone(), false),
        None => lib::telemetry::TelemetryHandle::disabled("mach_unknown".into(), false),
    };
    lib::telemetry::install_global(handle.clone());

    // Run the requested command and surface the resulting exit code.
    let started = Instant::now();
    let result = lib::cli::dispatch().await;
    let duration_ms = started.elapsed().as_millis() as u64;

    let exit = match &result {
        Ok(()) => ExitCode::SUCCESS,
        Err(err) => {
            eprintln!("{err}");
            if let Some(suggestion) = &err.suggestion {
                eprintln!("\n  {} {}", lib::ui::color::ARROW, suggestion);
            }
            // Fire-and-forget telemetry for the error code.
            lib::telemetry::capture_global(lib::telemetry::Event::error_emitted(
                err.code.code,
                "cli",
            ));
            ExitCode::from(i32::from(err.exit_code()) as u8)
        }
    };

    // The dispatcher records the per-command name in its own `cli_command_invoked`
    // event (see cli/mod.rs); here we only stamp the wallclock duration as a
    // safety net so the metric is never missing.
    lib::telemetry::capture_global(lib::telemetry::Event::cli_command_invoked(
        "<global>",
        "auto",
        match &result {
            Ok(()) => 0,
            Err(e) => i32::from(e.exit_code()),
        },
        duration_ms,
    ));

    lib::telemetry::shutdown(handle).await;
    Ok(exit)
}