use crate::cli::output::{OutputConfig, OutputFormat};
use crate::cli::TelemetryCommands;
use crate::config;
use crate::error::OlError;
use crate::telemetry::{
config as tconfig,
consent::{resolve, DecidedBy},
consent_file_path,
};
pub fn run(cmd: &TelemetryCommands, output: &OutputConfig) -> Result<(), OlError> {
match cmd {
TelemetryCommands::Status => run_status(output),
TelemetryCommands::Enable => run_enable(output),
TelemetryCommands::Disable => run_disable(output),
TelemetryCommands::Purge => run_purge(output),
TelemetryCommands::Debug => run_debug(output),
}
}
fn run_status(output: &OutputConfig) -> Result<(), OlError> {
let dir = config::openlatch_dir();
let path = consent_file_path(&dir);
let resolved = resolve(&path);
let rule = decided_by_label(resolved.decided_by);
let build_includes = crate::telemetry::build_includes_telemetry();
match output.format {
OutputFormat::Json => {
let json = serde_json::json!({
"enabled": resolved.enabled(),
"decided_by": rule,
"build_includes_telemetry": build_includes,
"consent_file": path.to_string_lossy(),
});
output.print_json(&json);
}
OutputFormat::Human => {
if output.quiet {
return Ok(());
}
let state = if resolved.enabled() {
"enabled"
} else {
"disabled"
};
println!("telemetry: {state}");
println!(" decided by: {rule}");
println!(" build includes telemetry: {build_includes}");
println!(" consent file: {}", path.display());
if !resolved.enabled() {
println!();
println!("To turn on: openlatch telemetry enable");
}
}
}
Ok(())
}
fn run_enable(output: &OutputConfig) -> Result<(), OlError> {
let dir = config::openlatch_dir();
std::fs::create_dir_all(&dir).map_err(|e| {
OlError::new(
crate::error::ERR_TELEMETRY_WRITE_FAILED,
format!("cannot create openlatch directory: {e}"),
)
})?;
let path = consent_file_path(&dir);
tconfig::write_consent(&path, true)?;
if output.format == OutputFormat::Json {
output.print_json(&serde_json::json!({ "enabled": true }));
} else if !output.quiet {
println!("telemetry enabled — thanks for helping shape OpenLatch.");
println!("disable anytime with: openlatch telemetry disable");
}
Ok(())
}
fn run_disable(output: &OutputConfig) -> Result<(), OlError> {
let dir = config::openlatch_dir();
std::fs::create_dir_all(&dir).map_err(|e| {
OlError::new(
crate::error::ERR_TELEMETRY_WRITE_FAILED,
format!("cannot create openlatch directory: {e}"),
)
})?;
let path = consent_file_path(&dir);
tconfig::write_consent(&path, false)?;
if output.format == OutputFormat::Json {
output.print_json(&serde_json::json!({ "enabled": false }));
} else if !output.quiet {
println!("telemetry disabled.");
}
Ok(())
}
fn run_purge(output: &OutputConfig) -> Result<(), OlError> {
run_disable(output)?;
if output.format != OutputFormat::Json && !output.quiet {
println!("in-memory queue will be dropped on next daemon restart.");
}
Ok(())
}
fn run_debug(output: &OutputConfig) -> Result<(), OlError> {
if output.format == OutputFormat::Json {
output.print_json(&serde_json::json!({
"hint": "run any openlatch command with OPENLATCH_TELEMETRY_DEBUG=1 to log event envelopes to stderr"
}));
} else if !output.quiet {
println!("telemetry debug:");
println!(" set OPENLATCH_TELEMETRY_DEBUG=1 before any openlatch command");
println!(" every event that would be captured is printed to stderr as JSON");
println!(" no network is used; debug mode is orthogonal to consent");
}
Ok(())
}
fn decided_by_label(d: DecidedBy) -> &'static str {
match d {
DecidedBy::DoNotTrackEnv => "DO_NOT_TRACK env var",
DecidedBy::OpenlatchDisabledEnv => "OPENLATCH_TELEMETRY_DISABLED env var",
DecidedBy::CiEnvironment => "CI environment detected",
DecidedBy::ConfigFile => "telemetry.json",
DecidedBy::DefaultUnconsented => "default (no consent recorded yet)",
DecidedBy::NoBakedKey => "no baked PostHog key in this build",
}
}