bctx 0.1.9

bctx CLI — intercept CLI commands and compress output for LLM coding agents
use std::io::{self, Read};

use weave::lenses::{apply_stack, clarity::ClarityLens, narrow::NarrowLens, LensContext};

/// Read text from a file or stdin, compress it, print JSON to stdout.
/// Called by the Chrome extension native messaging bridge.
///
/// Output:
///   {"compressed":"...","tokens_before":N,"tokens_after":N,"savings_pct":N}
pub fn handle(budget: usize, file: Option<String>) -> anyhow::Result<()> {
    let mut text = String::new();
    if let Some(path) = file {
        text = std::fs::read_to_string(&path)
            .map_err(|e| anyhow::anyhow!("bctx compress: cannot read '{path}': {e}"))?;
    } else {
        io::stdin().read_to_string(&mut text)?;
    }

    if text.trim().is_empty() {
        println!(
            "{}",
            serde_json::json!({
                "compressed": "",
                "tokens_before": 0,
                "tokens_after": 0,
                "savings_pct": 0.0,
            })
        );
        return Ok(());
    }

    let ctx = LensContext::new(budget);
    let stack: Vec<Box<dyn weave::lenses::Lens>> =
        vec![Box::new(ClarityLens), Box::new(NarrowLens)];
    let output = apply_stack(&stack, &text, &ctx);

    println!(
        "{}",
        serde_json::json!({
            "compressed": output.content,
            "tokens_before": output.tokens_before,
            "tokens_after": output.tokens_after,
            "savings_pct": output.savings_pct(),
        })
    );
    Ok(())
}