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!();
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(),
email: token.email.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!();
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.");
}
}
println!();
let home = std::env::var("HOME").unwrap_or_default();
let hook_marker = "# bctx";
let shell_configs = [
format!("{home}/.zshrc"),
format!("{home}/.bashrc"),
format!("{home}/.bash_profile"),
format!("{home}/.config/fish/config.fish"),
];
let hook_rc = shell_configs.iter().find_map(|rc| {
std::fs::read_to_string(rc)
.ok()
.filter(|c| c.contains(hook_marker))
.map(|_| rc.replace(&home, "~"))
});
match &hook_rc {
Some(rc) => println!("Shell hook : active ({})", rc),
None => println!("Shell hook : not installed — run `bctx init` to set up"),
}
let agents = [
("Claude Code", format!("{home}/.claude/settings.json")),
("Cursor", format!("{home}/.cursor/settings.json")),
("Windsurf", format!("{home}/.windsurf/settings.json")),
("Zed", format!("{home}/.config/zed/settings.json")),
];
let found: Vec<&str> = agents
.iter()
.filter(|(_, p)| {
std::fs::read_to_string(p)
.map(|c| c.contains("bctx"))
.unwrap_or(false)
})
.map(|(name, _)| *name)
.collect();
if found.is_empty() {
println!("Agents : none configured — run `bctx init --agent <name>`");
} else {
println!("Agents : {}", found.join(", "));
}
Ok(())
}