bctx 0.1.5

bctx CLI โ€” intercept CLI commands and compress output for LLM coding agents
mod commands;
mod tui;

use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(
    name = "bctx",
    about = "better-ctx: context-aware execution runtime for LLM agents",
    version
)]
struct Cli {
    #[command(subcommand)]
    command: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Intercept a domain command (e.g. bctx git log -20)
    #[command(external_subcommand)]
    Run(Vec<String>),

    /// Start the MCP/Nexus gateway server
    Mcp {
        #[arg(long, help = "Use HTTP transport instead of stdio")]
        http: bool,
        #[arg(long, default_value = "3000", help = "HTTP port (when --http)")]
        port: u16,
    },

    /// Build/update the project code index
    Index {
        #[arg(default_value = ".", help = "Project root to index")]
        path: String,
        #[arg(long, help = "Force full re-index")]
        force: bool,
    },

    /// Search the project code index
    Search {
        #[arg(help = "Search query")]
        query: String,
        #[arg(long, default_value = "10", help = "Max results")]
        top_k: usize,
    },

    /// Show token savings summary (local ยท offline)
    Gain,

    /// Open the local token savings dashboard (TUI)
    Dashboard,

    /// Initialize bctx in the current project
    Init {
        #[arg(long, default_value = "claude", help = "Agent to configure hooks for")]
        agent: String,
    },

    /// Diagnose bctx installation and health
    Doctor,

    /// Query the Vault for remembered facts
    Recall {
        #[arg(help = "Query string")]
        query: String,
        #[arg(long, default_value = "5", help = "Max facts to return")]
        top_k: usize,
    },

    /// Log in to the better-ctx cloud (device flow)
    Login {
        #[arg(long, help = "Custom cloud endpoint URL")]
        endpoint: Option<String>,
        #[arg(long, help = "Log out and remove stored credentials")]
        logout: bool,
    },

    /// Show current authentication status
    Status,

    /// Sync Vault facts with the cloud
    Sync {
        #[arg(
            long,
            help = "Push local crystallized facts to cloud (default when neither flag set)"
        )]
        push: bool,
        #[arg(long, help = "Pull facts from cloud and merge into local Vault")]
        pull: bool,
        #[arg(long, help = "Project identifier (default: hash of current directory)")]
        project: Option<String>,
    },
}

fn main() {
    // Pre-warm BPE tokenizer in background so first lens application is instant.
    // Warm BPE tokenizer in background โ€” count_nonblocking() uses fast path
    // until this finishes, then upgrades to BPE automatically for free.
    std::thread::spawn(forge::budget::estimator::TokenEstimator::warmup);

    let cli = Cli::parse();
    let result = match cli.command {
        Cmd::Run(args) => commands::run::handle(args),
        Cmd::Mcp { http, port } => commands::mcp::handle(http, port),
        Cmd::Index { path, force } => commands::index::handle(path, force),
        Cmd::Search { query, top_k } => commands::search::handle(query, top_k),
        Cmd::Gain => commands::gain::handle(),
        Cmd::Dashboard => commands::dashboard::handle(),
        Cmd::Init { agent } => commands::init::handle(agent),
        Cmd::Doctor => commands::doctor::handle(),
        Cmd::Recall { query, top_k } => commands::recall::handle(query, top_k),
        Cmd::Login { endpoint, logout } => commands::login::handle(endpoint, logout),
        Cmd::Status => commands::login::handle_status(),
        Cmd::Sync {
            push,
            pull,
            project,
        } => commands::sync::handle(push, pull, project),
    };
    if let Err(e) = result {
        eprintln!("error: {e}");
        std::process::exit(1);
    }
}