use anyhow::Result;
use clap::{Args as ClapArgs, Subcommand};
use console::style;
use stout_state::{Config, Paths};
#[derive(ClapArgs)]
pub struct Args {
#[command(subcommand)]
pub command: Option<AnalyticsCommand>,
}
#[derive(Subcommand)]
pub enum AnalyticsCommand {
On,
Off,
Status,
What,
}
pub async fn run(args: Args) -> Result<()> {
match args.command {
Some(AnalyticsCommand::On) => run_on().await,
Some(AnalyticsCommand::Off) => run_off().await,
Some(AnalyticsCommand::Status) | None => run_status().await,
Some(AnalyticsCommand::What) => run_what().await,
}
}
async fn run_on() -> Result<()> {
let paths = Paths::default();
let mut config = Config::load(&paths)?;
config.analytics.enabled = true;
config.save(&paths)?;
println!("\n{} Analytics enabled\n", style("✓").green().bold());
println!("Thank you for helping improve stout!");
println!(
"Run '{}' to see what data is collected.\n",
style("stout analytics what").cyan()
);
Ok(())
}
async fn run_off() -> Result<()> {
let paths = Paths::default();
let mut config = Config::load(&paths)?;
config.analytics.enabled = false;
config.save(&paths)?;
println!("\n{} Analytics disabled\n", style("✓").green().bold());
Ok(())
}
async fn run_status() -> Result<()> {
let paths = Paths::default();
let config = Config::load(&paths)?;
println!("\n{}\n", style("Analytics Status").cyan().bold());
if config.analytics.enabled {
println!(" Status: {}", style("Enabled").green());
println!(
"\n Run '{}' to disable.",
style("stout analytics off").cyan()
);
} else {
println!(" Status: {}", style("Disabled").dim());
println!(
"\n Run '{}' to enable.",
style("stout analytics on").cyan()
);
}
println!(
" Run '{}' to see what data is collected.\n",
style("stout analytics what").cyan()
);
Ok(())
}
async fn run_what() -> Result<()> {
println!(
"\n{}\n",
style("What stout collects (when enabled)").cyan().bold()
);
println!("{}:", style("Anonymous usage data").bold());
println!(" • Commands used (install, search, update, etc.)");
println!(" • Package names installed/searched");
println!(" • stout version");
println!(" • Operating system and architecture");
println!(" • Success/failure status of operations");
println!();
println!("{}:", style("What we DO NOT collect").bold());
println!(" • IP addresses (not logged)");
println!(" • User identifiers");
println!(" • File paths or contents");
println!(" • Custom tap information");
println!(" • Environment variables");
println!(" • Any personal information");
println!();
println!("{}:", style("Why we collect this").bold());
println!(" • Understand which packages are most popular");
println!(" • Identify common errors to fix");
println!(" • Prioritize development efforts");
println!();
println!("{}:", style("Data handling").bold());
println!(" • Data is aggregated and anonymized");
println!(" • No individual usage is tracked");
println!(
" • You can disable at any time with '{}'\n",
style("stout analytics off").cyan()
);
Ok(())
}
#[allow(dead_code)]
pub async fn record_event(_event_type: &str, _data: &str) -> Result<()> {
let paths = Paths::default();
let config = Config::load(&paths)?;
if !config.analytics.enabled {
return Ok(());
}
#[cfg(debug_assertions)]
{
tracing::debug!("Analytics event: {} - {}", _event_type, _data);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_analytics_command_variants() {
let _on = AnalyticsCommand::On;
let _off = AnalyticsCommand::Off;
let _status = AnalyticsCommand::Status;
let _what = AnalyticsCommand::What;
}
#[tokio::test]
async fn test_record_event_disabled() {
let result = record_event("test", "data").await;
assert!(result.is_ok() || result.is_err());
}
}