use anyhow::Result;
use std::collections::HashMap;
use std::path::PathBuf;
use weave::mesh::registry::FilterMesh;
fn avg_compression_pct(program: &str) -> u64 {
match program {
"git" => 84,
"cargo" => 79,
"kubectl" => 68,
"npm" | "pnpm" | "yarn" | "bun" => 71,
"docker" | "docker-compose" => 65,
"terraform" | "tofu" => 60,
"aws" => 63,
"go" => 72,
"make" | "cmake" | "bazel" => 58,
"pytest" | "jest" | "vitest" => 75,
"ruff" | "mypy" | "eslint" | "clippy" => 77,
"psql" | "mysql" | "prisma" => 62,
"helm" | "ansible" => 60,
_ => 55,
}
}
fn avg_tokens_per_run(program: &str) -> u64 {
match program {
"git" => 2_000,
"cargo" => 6_000,
"kubectl" => 3_000,
"npm" | "pnpm" | "yarn" | "bun" => 4_000,
"docker" | "docker-compose" => 3_500,
"terraform" | "tofu" => 4_000,
"aws" => 3_000,
"go" => 3_500,
"make" | "cmake" | "bazel" => 5_000,
"pytest" | "jest" | "vitest" => 5_000,
"ruff" | "mypy" | "eslint" | "clippy" => 2_500,
"psql" | "mysql" | "prisma" => 2_000,
_ => 1_500,
}
}
pub fn missed_tokens_for(program: &str, count: u64) -> u64 {
count * avg_tokens_per_run(program) * avg_compression_pct(program) / 100
}
pub fn is_builtin(program: &str) -> bool {
matches!(
program,
"cd" | "ls"
| "echo"
| "export"
| "source"
| "."
| "bctx"
| "exit"
| "history"
| "alias"
| "unset"
| "set"
| "eval"
| "exec"
| "pwd"
| "type"
| "which"
| "man"
)
}
pub fn parse_history_line(
line: &str,
is_zsh_extended: bool,
cutoff: u64,
) -> Option<(String, String)> {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
return None;
}
let (ts, cmd): (u64, &str) = if is_zsh_extended && line.starts_with(": ") {
let semi = line.find(';')?;
let ts_val: u64 = line[2..semi]
.split(':')
.next()
.unwrap_or("0")
.trim()
.parse()
.unwrap_or(0);
(ts_val, line[semi + 1..].trim())
} else if let Some(rest) = line.strip_prefix("- cmd: ") {
(0, rest.trim())
} else {
(0, line)
};
if ts > 0 && ts < cutoff {
return None;
}
let mut parts = cmd.split_whitespace();
let program = parts.next()?;
if is_builtin(program) {
return None;
}
let sub = parts.next().unwrap_or("").to_string();
Some((program.to_string(), sub))
}
pub fn parse_history_content(
content: &str,
is_zsh_extended: bool,
cutoff: u64,
) -> Vec<(String, String)> {
content
.lines()
.filter_map(|l| parse_history_line(l, is_zsh_extended, cutoff))
.collect()
}
pub fn count_supported(entries: &[(String, String)], mesh: &FilterMesh) -> HashMap<String, u64> {
let mut counts: HashMap<String, u64> = HashMap::new();
for (program, sub) in entries {
let args: Vec<String> = if sub.is_empty() {
vec![]
} else {
vec![sub.clone()]
};
if mesh.find(program, &args).is_some() {
*counts.entry(program.clone()).or_insert(0) += 1;
}
}
counts
}
pub fn rank_missed(counts: HashMap<String, u64>) -> Vec<(String, u64, u64)> {
let mut ranked: Vec<(String, u64, u64)> = counts
.into_iter()
.map(|(prog, count)| {
let missed = missed_tokens_for(&prog, count);
(prog, count, missed)
})
.collect();
ranked.sort_by_key(|b| std::cmp::Reverse(b.2));
ranked.truncate(10);
ranked
}
fn read_shell_history(days: u32) -> Vec<(String, String)> {
let home = super::home_dir();
let cutoff = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
.saturating_sub(days as u64 * 86_400);
let sources: Vec<(PathBuf, bool)> = vec![
(PathBuf::from(&home).join(".zsh_history"), true),
(PathBuf::from(&home).join(".bash_history"), false),
(
PathBuf::from(&home).join(".local/share/fish/fish_history"),
false,
),
];
let mut entries = Vec::new();
for (path, is_zsh_extended) in sources {
let Ok(content) = std::fs::read_to_string(&path) else {
continue;
};
entries.extend(parse_history_content(&content, is_zsh_extended, cutoff));
}
entries
}
pub fn handle(days: u32) -> Result<()> {
let mesh = FilterMesh::new();
let history = read_shell_history(days);
if history.is_empty() {
println!();
println!(" No shell history found.");
println!(" Checked: ~/.zsh_history ~/.bash_history ~/.local/share/fish/fish_history");
println!();
return Ok(());
}
let counts = count_supported(&history, &mesh);
if counts.is_empty() {
println!();
println!(" No uncompressed commands found in the last {days} days.");
let home = super::home_dir();
let hook_active = [
format!("{home}/.zshrc"),
format!("{home}/.bashrc"),
format!("{home}/.bash_profile"),
]
.iter()
.any(|rc| {
std::fs::read_to_string(rc)
.map(|c| c.contains("# bctx"))
.unwrap_or(false)
});
if hook_active {
println!(" Shell hook is active — all recent commands ran through bctx.");
} else {
println!(" Run `bctx init` to wire the shell hook and start saving tokens.");
}
println!();
return Ok(());
}
let ranked = rank_missed(counts);
let total_missed: u64 = ranked.iter().map(|(_, _, t)| t).sum();
let cost = total_missed as f64 * 0.000_015;
let max_missed = ranked.iter().map(|(_, _, t)| *t).max().unwrap_or(1);
let sep = " ─────────────────────────────────────────────────────";
println!();
println!(" UNCOMPRESSED COMMANDS (last {days} days)");
println!("{sep}");
for (prog, count, missed_tokens) in &ranked {
let bar = savings_bar(*missed_tokens, max_missed, 14);
println!(
" {:<12} {} ran {:>4}× · est. missed ~{}",
prog,
bar,
count,
fmt_tokens(*missed_tokens),
);
}
println!("{sep}");
println!(
" Total estimated missed savings: ~{} tokens (~${:.2})",
fmt_tokens(total_missed),
cost,
);
println!();
println!(" These commands ran without bctx interception.");
println!(" Run `bctx init` to wire the shell hook and capture them automatically.");
println!();
Ok(())
}
fn fmt_tokens(n: u64) -> String {
if n >= 1_000_000 {
format!("{:.1}M", n as f64 / 1_000_000.0)
} else if n >= 1_000 {
format!("{:.0}K", n as f64 / 1_000.0)
} else {
n.to_string()
}
}
fn savings_bar(value: u64, max: u64, width: usize) -> String {
use std::io::IsTerminal;
let filled = if max == 0 {
0
} else {
((value as usize) * width / (max as usize)).min(width)
};
let empty = width - filled;
let blocks = "█".repeat(filled);
let shade = "░".repeat(empty);
if std::io::stdout().is_terminal() {
format!("\x1b[33m{blocks}\x1b[2m{shade}\x1b[0m")
} else {
format!("{blocks}{shade}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fmt_tokens_millions() {
assert_eq!(fmt_tokens(2_400_000), "2.4M");
}
#[test]
fn fmt_tokens_thousands() {
assert_eq!(fmt_tokens(94_000), "94K");
}
#[test]
fn fmt_tokens_small() {
assert_eq!(fmt_tokens(0), "0");
assert_eq!(fmt_tokens(42), "42");
assert_eq!(fmt_tokens(999), "999");
}
#[test]
fn savings_bar_full() {
let b = savings_bar(16, 16, 8);
assert!(b.contains("████████"));
}
#[test]
fn savings_bar_zero_max_no_panic() {
let b = savings_bar(0, 0, 8);
assert!(b.contains("░"));
}
#[test]
fn savings_bar_half() {
let b = savings_bar(4, 8, 8);
assert!(b.contains("████"));
assert!(b.contains("░░░░"));
}
#[test]
fn avg_compression_pct_known() {
assert_eq!(avg_compression_pct("git"), 84);
assert_eq!(avg_compression_pct("cargo"), 79);
assert_eq!(avg_compression_pct("kubectl"), 68);
}
#[test]
fn avg_compression_pct_fallback() {
assert_eq!(avg_compression_pct("some_unknown_tool"), 55);
}
#[test]
fn builtins_are_filtered() {
for b in &["cd", "ls", "echo", "export", "source", "exit", "bctx"] {
assert!(is_builtin(b), "{b} should be a builtin");
}
}
#[test]
fn supported_programs_are_not_builtins() {
for p in &["git", "cargo", "kubectl", "npm", "docker", "terraform"] {
assert!(!is_builtin(p), "{p} should not be a builtin");
}
}
#[test]
fn parse_plain_bash_line() {
let result = parse_history_line("git log --oneline -20", false, 0);
assert_eq!(result, Some(("git".into(), "log".into())));
}
#[test]
fn parse_plain_bash_no_subcommand() {
let result = parse_history_line("docker", false, 0);
assert_eq!(result, Some(("docker".into(), "".into())));
}
#[test]
fn parse_zsh_extended_line() {
let result = parse_history_line(": 1715000000:0;cargo build --release", true, 0);
assert_eq!(result, Some(("cargo".into(), "build".into())));
}
#[test]
fn parse_zsh_extended_old_entry_filtered_by_cutoff() {
let result = parse_history_line(": 1000:0;git status", true, 9_999_999_999);
assert_eq!(result, None);
}
#[test]
fn parse_zsh_extended_recent_entry_kept() {
let result = parse_history_line(": 9999999999:0;git status", true, 0);
assert_eq!(result, Some(("git".into(), "status".into())));
}
#[test]
fn parse_fish_line() {
let result = parse_history_line("- cmd: kubectl get pods", false, 0);
assert_eq!(result, Some(("kubectl".into(), "get".into())));
}
#[test]
fn parse_empty_line_returns_none() {
assert_eq!(parse_history_line("", false, 0), None);
assert_eq!(parse_history_line(" ", false, 0), None);
}
#[test]
fn parse_comment_line_returns_none() {
assert_eq!(parse_history_line("# this is a comment", false, 0), None);
}
#[test]
fn parse_builtin_returns_none() {
assert_eq!(parse_history_line("cd /home/user", false, 0), None);
assert_eq!(parse_history_line("bctx git log", false, 0), None);
assert_eq!(parse_history_line("echo hello", false, 0), None);
}
#[test]
fn parse_history_content_bash_multiline() {
let content = "git log\ncargo test\ncd /tmp\ngit status\n";
let result = parse_history_content(content, false, 0);
assert_eq!(result.len(), 3); assert!(result.iter().any(|(p, _)| p == "git"));
assert!(result.iter().any(|(p, _)| p == "cargo"));
assert!(!result.iter().any(|(p, _)| p == "cd"));
}
#[test]
fn parse_history_content_zsh_filters_old_entries() {
let content = ": 100:0;git log\n: 9999999999:0;cargo build\n";
let result = parse_history_content(content, true, 1_000_000);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "cargo");
}
#[test]
fn parse_history_content_skips_blank_and_comments() {
let content = "\n# comment\ngit status\n\n";
let result = parse_history_content(content, false, 0);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "git");
}
#[test]
fn count_supported_ignores_unknown_programs() {
let mesh = FilterMesh::default_mesh();
let entries = vec![
("my_obscure_tool_xyz".into(), "".into()),
("another_unknown_999".into(), "run".into()),
];
let counts = count_supported(&entries, &mesh);
assert!(counts.is_empty(), "unknown tools must not appear in counts");
}
#[test]
fn count_supported_counts_git_occurrences() {
let mesh = FilterMesh::default_mesh();
let entries = vec![
("git".into(), "log".into()),
("git".into(), "status".into()),
("git".into(), "diff".into()),
];
let counts = count_supported(&entries, &mesh);
assert_eq!(counts.get("git"), Some(&3));
}
#[test]
fn count_supported_counts_multiple_programs() {
let mesh = FilterMesh::default_mesh();
let entries = vec![
("git".into(), "log".into()),
("cargo".into(), "build".into()),
("git".into(), "status".into()),
];
let counts = count_supported(&entries, &mesh);
assert_eq!(counts.get("git"), Some(&2));
assert_eq!(counts.get("cargo"), Some(&1));
}
#[test]
fn count_supported_mixed_known_and_unknown() {
let mesh = FilterMesh::default_mesh();
let entries = vec![
("git".into(), "log".into()),
("totally_fake_tool_xyz".into(), "run".into()),
("cargo".into(), "test".into()),
];
let counts = count_supported(&entries, &mesh);
assert_eq!(counts.len(), 2);
assert!(!counts.contains_key("totally_fake_tool_xyz"));
}
#[test]
fn rank_missed_sorted_by_tokens_descending() {
let mut counts = HashMap::new();
counts.insert("git".into(), 1u64); counts.insert("cargo".into(), 1u64); let ranked = rank_missed(counts);
assert_eq!(ranked[0].0, "cargo");
assert_eq!(ranked[1].0, "git");
}
#[test]
fn rank_missed_capped_at_ten() {
let counts: HashMap<String, u64> = (0..15).map(|i| (format!("tool_{i}"), 1u64)).collect();
let ranked = rank_missed(counts);
assert!(ranked.len() <= 10);
}
#[test]
fn rank_missed_token_estimate_correct() {
let mut counts = HashMap::new();
counts.insert("git".into(), 2u64); let ranked = rank_missed(counts);
assert_eq!(ranked[0].2, 3_360);
}
#[test]
fn missed_tokens_for_git_single_run() {
assert_eq!(missed_tokens_for("git", 1), 1_680);
}
#[test]
fn missed_tokens_for_cargo_multiple_runs() {
assert_eq!(missed_tokens_for("cargo", 3), 14_220);
}
#[test]
fn missed_tokens_for_zero_runs() {
assert_eq!(missed_tokens_for("git", 0), 0);
}
}