bctx 0.1.7

bctx CLI — intercept CLI commands and compress output for LLM coding agents
use anyhow::Result;
use cloud::client::auth::{clear_token, load_token, save_token, TokenStore};

const DEFAULT_ENDPOINT: &str = "https://api.betterctx.com";
const CLIENT_ID: &str = "bctx-cli";

pub fn handle(endpoint: Option<String>, logout: bool) -> Result<()> {
    if logout {
        clear_token()?;
        println!("Logged out.");
        return Ok(());
    }

    if let Some(t) = load_token() {
        println!("Already authenticated as {} (tier: {})", t.user_id, t.tier);
        println!("Run `bctx login --logout` to sign out.");
        return Ok(());
    }

    let ep = endpoint.unwrap_or_else(|| DEFAULT_ENDPOINT.to_string());

    let rt = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?;

    rt.block_on(async {
        let device = cloud::client::auth::device_flow_start(&ep, CLIENT_ID)
            .await
            .map_err(|e| {
                anyhow::anyhow!(
                    "Could not reach cloud server: {e}\nCheck your network or try again."
                )
            })?;

        println!();
        println!("  Opening browser for authorization...");
        println!("  If the browser doesn't open, visit:");
        println!("  {}", device.verification_uri);
        println!();
        println!("  Your code: {}", device.user_code);
        println!();

        // Try to open the browser; ignore errors (user can open manually)
        let _ = open::that(&device.verification_uri);

        print!("Waiting for authorization");
        std::io::Write::flush(&mut std::io::stdout()).ok();

        let token = cloud::client::auth::device_flow_poll(
            &ep,
            CLIENT_ID,
            &device.device_code,
            device.interval,
        )
        .await?;

        println!();

        let store = TokenStore {
            access_token: token.access_token,
            refresh_token: token.refresh_token,
            expires_at: None,
            endpoint: ep.clone(),
            user_id: token.user_id.clone(),
            tier: token.tier.clone(),
        };
        save_token(&store)?;

        println!("Logged in!");
        println!("  User : {}", token.user_id);
        println!("  Tier : {}", token.tier);
        if token.tier == "free" {
            println!();
            println!("  Upgrade to Beacon for cloud sync: https://betterctx.com/upgrade");
        }
        Ok::<_, anyhow::Error>(())
    })
}

pub fn handle_status() -> Result<()> {
    match load_token() {
        Some(t) => {
            println!("Authenticated");
            println!("  User ID  : {}", t.user_id);
            println!("  Tier     : {}", t.tier);
            println!("  Endpoint : {}", t.endpoint);
            println!();

            // Fetch live account info from server
            let rt = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()?;
            let result = rt.block_on(cloud::client::auth::fetch_account(
                &t.endpoint,
                &t.access_token,
            ));
            match result {
                Ok(info) => {
                    if let Some(quota) = info["quota_tokens"].as_u64() {
                        println!("  Cloud quota : {:>10} tokens/mo", quota);
                    }
                    if let Some(limit) = info["vault_fact_limit"].as_u64() {
                        println!("  Vault limit : {:>10} facts", limit);
                    }
                    let sync = info["cloud_sync_enabled"].as_bool().unwrap_or(false);
                    println!(
                        "  Cloud sync  : {}",
                        if sync { "enabled" } else { "disabled" }
                    );
                }
                Err(e) => {
                    println!("  (Could not reach server: {e})");
                }
            }
        }
        None => {
            println!("Not authenticated. Run `bctx login` to connect.");
        }
    }
    Ok(())
}