bctx 0.1.15

bctx CLI — intercept CLI commands and compress output for LLM coding agents
use anyhow::{bail, Result};
use atlas::vault_store;
use cloud::client::{auth::load_token, upgrade::Tier};
use vault::fact::MemoryTier;
use vault::retrieval::query::VaultQuery;

pub fn handle(push: bool, pull: bool, project: Option<String>) -> Result<()> {
    let token = load_token()
        .ok_or_else(|| anyhow::anyhow!("Not authenticated. Run `bctx login` first."))?;

    let tier = Tier::parse_tier(&token.tier);
    if !tier.cloud_sync_enabled() {
        bail!("Cloud sync requires the Beacon tier or higher.\nUpgrade at https://betterctx.com/upgrade");
    }

    let project_hash = project.unwrap_or_else(|| {
        let cwd = std::env::current_dir().unwrap_or_default();
        let hash = blake3::hash(cwd.to_string_lossy().as_bytes());
        hash.to_hex()[..16].to_string()
    });

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

    rt.block_on(async {
        if push || !pull {
            // Default: push
            let facts = {
                let vault = vault_store::vault().lock().unwrap();
                vault.query(
                    &VaultQuery::new("")
                        .in_tier(MemoryTier::Crystallized)
                        .top_k(10_000),
                )
            };
            let count = facts.len();
            let resp = cloud::client::sync::push_vault(
                &token.endpoint,
                &token.access_token,
                &project_hash,
                facts,
            )
            .await?;
            println!("Pushed {count} crystallized facts → cloud");
            println!("  Accepted : {}", resp.accepted);
            println!("  Merged   : {}", resp.merged);
            println!("  Version  : {}", resp.server_version);
        }

        if pull {
            let resp = cloud::client::sync::pull_vault(
                &token.endpoint,
                &token.access_token,
                &project_hash,
            )
            .await?;
            let count = resp.facts.len();
            {
                let mut vault = vault_store::vault().lock().unwrap();
                for fact in resp.facts {
                    vault.insert(fact);
                }
            }
            vault_store::flush();
            println!(
                "Pulled {count} facts from cloud (server version: {})",
                resp.server_version
            );
        }

        Ok::<_, anyhow::Error>(())
    })
}