pub mod client;
pub mod config;
pub mod consent;
pub mod events;
pub mod identity;
pub mod network;
pub mod super_props;
pub use client::TelemetryHandle;
pub use consent::{resolve, ConsentState, DecidedBy, Resolved};
pub use events::Event;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
static GLOBAL_HANDLE: OnceLock<TelemetryHandle> = OnceLock::new();
pub fn consent_file_path(openlatch_dir: &Path) -> PathBuf {
openlatch_dir.join("telemetry.json")
}
pub const fn build_includes_telemetry() -> bool {
true
}
pub fn init(
openlatch_dir: &Path,
agent_id: String,
authenticated: bool,
baked_key_present: bool,
) -> TelemetryHandle {
let resolved = resolve(&consent_file_path(openlatch_dir));
let debug_stderr = std::env::var("OPENLATCH_TELEMETRY_DEBUG")
.map(|v| !v.is_empty() && v != "0")
.unwrap_or(false);
let super_props = super_props::SuperProps::new(agent_id, authenticated);
client::start(client::ClientConfig {
resolved,
super_props,
debug_stderr,
baked_key_present,
})
}
pub fn capture(handle: &TelemetryHandle, event: Event) {
handle.capture(event);
}
pub fn install_global(handle: TelemetryHandle) -> bool {
GLOBAL_HANDLE.set(handle).is_ok()
}
pub fn capture_global(event: Event) {
if let Some(h) = GLOBAL_HANDLE.get() {
h.capture(event);
}
}
pub fn capture_hook_source_unknown(source: &str) {
capture_global(Event::hook_source_unknown(source));
}
pub fn capture_hook_type_unknown(type_str: &str) {
capture_global(Event::hook_type_unknown(type_str));
}
pub fn global() -> Option<&'static TelemetryHandle> {
GLOBAL_HANDLE.get()
}
pub async fn shutdown(_handle: TelemetryHandle) {
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_init_without_baked_key_is_noop() {
let tmp = TempDir::new().unwrap();
let handle = init(tmp.path(), "agt_a".into(), false, false);
assert!(!handle.is_enabled());
}
#[test]
fn test_init_with_disabled_consent_is_noop_even_with_key() {
let tmp = TempDir::new().unwrap();
config::write_consent(&consent_file_path(tmp.path()), false).unwrap();
let handle = init(tmp.path(), "agt_a".into(), false, true);
assert!(!handle.is_enabled());
}
#[tokio::test]
async fn test_init_enabled_consent_and_key_produces_live_handle() {
let tmp = TempDir::new().unwrap();
config::write_consent(&consent_file_path(tmp.path()), true).unwrap();
let handle = init(tmp.path(), "agt_a".into(), false, true);
if handle.is_enabled() {
capture(&handle, Event::cli_initialized("claude-code", 1, true));
tokio::task::yield_now().await;
assert_eq!(handle.events_captured(), 1);
shutdown(handle).await;
}
}
}