use anyhow::Result;
use forge::tracker::store::ExecutionStore;
pub fn handle() -> Result<()> {
let home = std::env::var("HOME").unwrap_or_default();
let db_path = std::path::PathBuf::from(&home)
.join(".bctx")
.join("executions.db");
if !db_path.exists() {
println!();
println!(" No executions recorded yet.");
println!(" Run bctx git log or bctx cargo build to start tracking.");
println!();
return Ok(());
}
let store = ExecutionStore::open(&db_path)?;
let total_saved = store.total_saved()?;
let summary = store.summary()?;
let recent = store.recent(100_000)?;
let total_sent: usize = recent.iter().map(|r| r.tokens_after).sum();
let total_before: usize = recent.iter().map(|r| r.tokens_before).sum();
let cmd_count = recent.len();
let compression_pct = total_saved
.checked_mul(100)
.and_then(|v| v.checked_div(total_before))
.unwrap_or(0);
let cost_avoided = total_saved as f64 * 0.000_015;
let sep = " ─────────────────────────────────────────────────";
println!();
println!(" TOKEN SAVINGS (local · all time)");
println!("{sep}");
println!(
" Tokens saved {:>10} Compression {:>3}%",
fmt_tokens(total_saved),
compression_pct
);
println!(
" Tokens sent {:>10} Commands run {:>6}",
fmt_tokens(total_sent),
cmd_count
);
println!(" Cost avoided {:>10}", format!("${cost_avoided:.2}"));
println!();
if !summary.is_empty() {
println!(" TOP COMMANDS");
let max_saved = summary
.iter()
.map(|(_, tb, ta, _)| tb.saturating_sub(*ta))
.max()
.unwrap_or(1);
for (prog, tb, ta, pct) in summary.iter().take(8) {
let saved = tb.saturating_sub(*ta);
let bar = bar(saved, max_saved, 16);
println!(
" {:<10} {} {:>8} saved {:>3.0}%",
prog,
bar,
fmt_tokens(saved),
pct
);
}
println!();
}
println!("{sep}");
if let Some(token) = cloud::client::auth::load_token() {
if let Ok(gauge) = fetch_gauge(&token.endpoint, &token.access_token) {
let used = gauge["used_tokens"].as_u64().unwrap_or(0);
let quota = gauge["quota_tokens"].as_u64().unwrap_or(0);
let reset = gauge["reset_at"].as_str().unwrap_or("—");
if quota > 0 {
let pct = used
.checked_mul(100)
.and_then(|v| v.checked_div(quota))
.unwrap_or(0);
println!(
" Cloud quota {:>6} / {} used ({}%) resets {}",
fmt_tokens(used as usize),
fmt_tokens(quota as usize),
pct,
reset
);
} else {
println!(
" Cloud quota {} used (unlimited) resets {}",
fmt_tokens(used as usize),
reset
);
}
println!("{sep}");
}
}
println!(" Run `bctx dashboard` to open the full dashboard");
println!();
Ok(())
}
fn fmt_tokens(n: usize) -> String {
if n >= 1_000_000 {
format!("{:.1}M", n as f64 / 1_000_000.0)
} else if n >= 1_000 {
format!("{:.1}K", n as f64 / 1_000.0)
} else {
n.to_string()
}
}
fn bar(value: usize, max: usize, width: usize) -> String {
use std::io::IsTerminal;
let filled = value
.checked_mul(width)
.and_then(|v| v.checked_div(max))
.unwrap_or(0)
.min(width);
let empty = width - filled;
let blocks = "█".repeat(filled);
let shade = "░".repeat(empty);
if std::io::stdout().is_terminal() {
format!("\x1b[32m{blocks}\x1b[2m{shade}\x1b[0m")
} else {
format!("{blocks}{shade}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fmt_tokens_formats_millions() {
assert_eq!(fmt_tokens(1_000_000), "1.0M");
assert_eq!(fmt_tokens(2_400_000), "2.4M");
}
#[test]
fn fmt_tokens_formats_thousands() {
assert_eq!(fmt_tokens(1_000), "1.0K");
assert_eq!(fmt_tokens(890_000), "890.0K");
}
#[test]
fn fmt_tokens_formats_small() {
assert_eq!(fmt_tokens(0), "0");
assert_eq!(fmt_tokens(42), "42");
assert_eq!(fmt_tokens(999), "999");
}
#[test]
fn bar_full_when_value_equals_max() {
let b = bar(16, 16, 16);
assert!(b.contains("████████████████"));
assert!(!b.contains("░"));
}
#[test]
fn bar_empty_when_value_is_zero() {
let b = bar(0, 16, 16);
assert!(b.contains("░░░░░░░░░░░░░░░░"));
}
#[test]
fn bar_half_fill() {
let b = bar(8, 16, 16);
assert!(b.contains("████████"));
assert!(b.contains("░░░░░░░░"));
}
#[test]
fn bar_zero_max_does_not_panic() {
let b = bar(5, 0, 16);
assert!(b.contains("░"));
}
}
fn fetch_gauge(endpoint: &str, access_token: &str) -> Result<serde_json::Value> {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?;
rt.block_on(async {
let resp = reqwest::Client::new()
.get(format!("{endpoint}/dashboard/gauge"))
.bearer_auth(access_token)
.send()
.await?;
let val = resp.json::<serde_json::Value>().await?;
Ok(val)
})
}