use std::fs::{self, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::time::SystemTime;
const MAX_TELEMETRY_BYTES: u64 = 10 * 1024 * 1024;
pub fn log_command(
cqs_dir: &Path,
command: &str,
query: Option<&str>,
result_count: Option<usize>,
) {
let path = cqs_dir.join("telemetry.jsonl");
if std::env::var("CQS_TELEMETRY").as_deref() != Ok("1") && !path.exists() {
return;
}
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let entry = serde_json::json!({
"ts": timestamp,
"cmd": command,
"query": query,
"results": result_count,
});
let _ = (|| -> std::io::Result<()> {
let lock_path = cqs_dir.join("telemetry.lock");
let lock_file = OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(&lock_path)?;
if lock_file.try_lock().is_err() {
return Ok(());
}
if let Ok(meta) = fs::metadata(&path) {
if meta.len() > MAX_TELEMETRY_BYTES {
let archive_name = format!("telemetry_{timestamp}.jsonl");
let archive_path = cqs_dir.join(&archive_name);
if let Err(e) = fs::rename(&path, &archive_path) {
tracing::warn!(
error = %e,
"Failed to auto-archive telemetry file"
);
} else {
tracing::info!(
archived = %archive_name,
"Auto-archived telemetry file (exceeded 10 MB)"
);
}
}
}
let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
{
tracing::debug!(path = %path.display(), error = %e, "Failed to set file permissions");
}
}
if let Err(e) = writeln!(file, "{}", entry) {
tracing::warn!(error = %e, "Failed to write telemetry entry");
}
Ok(())
})();
}
pub fn log_routed(
cqs_dir: &Path,
query: &str,
category: &str,
confidence: &str,
strategy: &str,
fallback: bool,
result_count: Option<usize>,
) {
let path = cqs_dir.join("telemetry.jsonl");
if std::env::var("CQS_TELEMETRY").as_deref() != Ok("1") && !path.exists() {
return;
}
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let entry = serde_json::json!({
"ts": timestamp,
"cmd": "search",
"query": query,
"category": category,
"confidence": confidence,
"strategy": strategy,
"fallback": fallback,
"results": result_count,
});
let _ = (|| -> std::io::Result<()> {
let lock_path = cqs_dir.join("telemetry.lock");
let lock_file = OpenOptions::new()
.create(true)
.truncate(false)
.read(true)
.write(true)
.open(&lock_path)?;
if lock_file.try_lock().is_err() {
return Ok(());
}
if let Ok(meta) = fs::metadata(&path) {
if meta.len() > MAX_TELEMETRY_BYTES {
let archive_name = format!("telemetry_{timestamp}.jsonl");
let archive_path = cqs_dir.join(&archive_name);
if let Err(e) = fs::rename(&path, &archive_path) {
tracing::warn!(
error = %e,
"Failed to auto-archive telemetry file"
);
} else {
tracing::info!(
archived = %archive_name,
"Auto-archived telemetry file (exceeded 10 MB)"
);
}
}
}
let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))
{
tracing::debug!(path = %path.display(), error = %e, "Failed to set file permissions");
}
}
if let Err(e) = writeln!(file, "{}", entry) {
tracing::warn!(error = %e, "Failed to write telemetry entry");
}
Ok(())
})();
}
pub fn describe_command(args: &[String]) -> (String, Option<String>) {
use clap::CommandFactory;
let cmd = args.get(1).map(|s| s.as_str()).unwrap_or("unknown");
if !cmd.starts_with('-') && !cmd.is_empty() {
let clap_app = super::definitions::Cli::command();
let is_subcommand = clap_app.get_subcommands().any(|sc| sc.get_name() == cmd);
if is_subcommand {
let query = args.iter().skip(2).find(|a| !a.starts_with('-')).cloned();
return (cmd.to_string(), query);
}
return ("search".to_string(), Some(cmd.to_string()));
}
(cmd.to_string(), None)
}