mcpkill 0.1.0

Universal MCP proxy — semantic cache + chunking to kill token waste
Documentation
use anyhow::Result;

use crate::cache::Cache;

/// Print a formatted stats table to stderr.
pub fn print_stats(db_path: &str) -> Result<()> {
    let s = Cache::new(db_path)?.stats(db_path)?;

    let saved = s.tokens_saved();
    let rate = s.hit_rate();
    let db_mb = s.db_bytes as f64 / (1024.0 * 1024.0);
    let reduction = if s.tokens_original > 0 {
        (1.0 - s.tokens_returned as f64 / s.tokens_original as f64) * 100.0
    } else {
        0.0
    };

    eprintln!();
    eprintln!("  ┌─────────────────────────────────────────┐");
    eprintln!("  │           mcpkill cache stats            │");
    eprintln!("  ├─────────────────────────────────────────┤");
    eprintln!("  │  Cache entries    {:<22} │", s.entries);
    eprintln!("  │  Stored chunks    {:<22} │", s.total_chunks);
    eprintln!("  │  Cache hits       {:<22} │", s.cache_hits);
    eprintln!("  │  Hit rate         {:<22} │", format!("{rate:.1}%"));
    eprintln!("  ├─────────────────────────────────────────┤");
    eprintln!("  │  Tokens (original){:<22} │", s.tokens_original);
    eprintln!("  │  Tokens (returned){:<22} │", s.tokens_returned);
    eprintln!(
        "  │  Tokens saved     {:<22} │",
        format!("~{saved}  ({reduction:.0}%)")
    );
    eprintln!("  ├─────────────────────────────────────────┤");
    eprintln!("  │  DB size          {:<22} │", format!("{db_mb:.2} MB"));
    eprintln!("  │  DB path          {:<22} │", truncate(db_path, 22));
    eprintln!("  └─────────────────────────────────────────┘");
    eprintln!();

    Ok(())
}

/// Print stats as JSON to stdout (for scripting / CI dashboards).
pub fn print_stats_json(db_path: &str) -> Result<()> {
    let s = Cache::new(db_path)?.stats(db_path)?;

    // Augment the serialisable struct with derived fields.
    let json = serde_json::json!({
        "entries":         s.entries,
        "chunks":          s.total_chunks,
        "cache_hits":      s.cache_hits,
        "hit_rate_pct":    format!("{:.1}", s.hit_rate()),
        "tokens_original": s.tokens_original,
        "tokens_returned": s.tokens_returned,
        "tokens_saved":    s.tokens_saved(),
        "db_bytes":        s.db_bytes,
        "db_path":         db_path,
    });

    println!("{}", serde_json::to_string_pretty(&json)?);
    Ok(())
}

fn truncate(s: &str, max: usize) -> String {
    if s.len() <= max {
        s.to_string()
    } else {
        format!("{}", &s[s.len().saturating_sub(max - 1)..])
    }
}