Skip to main content

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 self::track_command_with_tier as track_command_tier;
14pub use flush::{flush_analytics, force_flush_sync};
15pub use id::{generate_anon_id, generate_file_hash};
16pub use queue::{track_event, AnalyticsEvent};
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(
53        file_path,
54        command,
55        success,
56        file_created,
57        file_opened,
58        "free",
59    )
60}
61
62/// Track a command execution with user tier info
63pub fn track_command_with_tier(
64    file_path: Option<&str>,
65    command: &str,
66    success: bool,
67    file_created: bool,
68    file_opened: bool,
69    user_tier: &str,
70) {
71    if !is_telemetry_enabled() {
72        return;
73    }
74
75    let anon_id = generate_anon_id(file_path);
76    let file_hash = file_path
77        .map(|p| generate_file_hash(p))
78        .unwrap_or_else(|| "none".to_string());
79
80    let event = AnalyticsEvent {
81        anon_id,
82        file_hash,
83        client: "cli".to_string(),
84        command: command.to_string(),
85        success,
86        timestamp: chrono::Utc::now().to_rfc3339(),
87        file_created,
88        file_opened,
89        user_tier: user_tier.to_string(),
90    };
91
92    // Fire-and-forget queue
93    track_event(event);
94}