memvid_cli/analytics/
mod.rs

1//! Analytics Module for Memvid CLI
2//!
3//! Provides anonymous telemetry tracking with:
4//! - Zero latency impact (async fire-and-forget)
5//! - Local JSONL queue with background flush
6//! - SHA256-based anonymous IDs
7//! - Opt-out via MEMVID_TELEMETRY=0
8
9mod flush;
10mod id;
11mod queue;
12
13pub use flush::{flush_analytics, force_flush_sync};
14pub use id::{generate_anon_id, generate_file_hash};
15pub use queue::{track_event, AnalyticsEvent};
16pub use self::track_command_with_tier as track_command_tier;
17
18use std::sync::atomic::{AtomicBool, Ordering};
19
20/// Global flag to check if telemetry is enabled
21static TELEMETRY_ENABLED: AtomicBool = AtomicBool::new(true);
22
23/// Check if telemetry is enabled
24/// Set MEMVID_TELEMETRY=0 to disable
25pub fn is_telemetry_enabled() -> bool {
26    TELEMETRY_ENABLED.load(Ordering::Relaxed)
27}
28
29/// Initialize analytics (called once at startup)
30/// Checks environment variable and starts background flush
31pub fn init_analytics() {
32    // Check opt-out
33    if let Ok(val) = std::env::var("MEMVID_TELEMETRY") {
34        if val == "0" || val.to_lowercase() == "false" {
35            TELEMETRY_ENABLED.store(false, Ordering::Relaxed);
36            return;
37        }
38    }
39
40    // Start background flush task
41    flush::start_background_flush();
42}
43
44/// Convenience function to track a command execution
45pub fn track_command(
46    file_path: Option<&str>,
47    command: &str,
48    success: bool,
49    file_created: bool,
50    file_opened: bool,
51) {
52    track_command_with_tier(file_path, command, success, file_created, file_opened, "free")
53}
54
55/// Track a command execution with user tier info
56pub fn track_command_with_tier(
57    file_path: Option<&str>,
58    command: &str,
59    success: bool,
60    file_created: bool,
61    file_opened: bool,
62    user_tier: &str,
63) {
64    if !is_telemetry_enabled() {
65        return;
66    }
67
68    let anon_id = generate_anon_id(file_path);
69    let file_hash = file_path
70        .map(|p| generate_file_hash(p))
71        .unwrap_or_else(|| "none".to_string());
72
73    let event = AnalyticsEvent {
74        anon_id,
75        file_hash,
76        client: "cli".to_string(),
77        command: command.to_string(),
78        success,
79        timestamp: chrono::Utc::now().to_rfc3339(),
80        file_created,
81        file_opened,
82        user_tier: user_tier.to_string(),
83    };
84
85    // Fire-and-forget queue
86    track_event(event);
87}