1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 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)
}