use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::format::endpoint_label;
use crate::profile::{AppConfig, ClaudeCredentials, Profile, home_dir, load_config};
pub(crate) fn run(json: bool) -> Result<()> {
let path = resolve_credentials_path()?;
let creds = read_credentials(&path);
let config = load_config()?;
let matched = creds
.as_ref()
.and_then(ClaudeCredentials::refresh_token)
.and_then(|rt| match_by_refresh_token(&config, rt));
if json {
emit_json(&config, matched);
} else {
emit_plain(matched);
}
Ok(())
}
fn resolve_credentials_path() -> Result<PathBuf> {
if let Ok(dir) = std::env::var("CLAUDE_CONFIG_DIR") {
return Ok(PathBuf::from(dir).join(".credentials.json"));
}
Ok(home_dir()?.join(".claude").join(".credentials.json"))
}
fn read_credentials(path: &Path) -> Option<ClaudeCredentials> {
let content = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&content).ok()
}
fn match_by_refresh_token<'a>(config: &'a AppConfig, refresh_token: &str) -> Option<&'a str> {
let active = config.state.active_profile.as_deref();
let mut fallback = None;
for p in &config.profiles {
if p.refresh_token() != Some(refresh_token) {
continue;
}
if Some(p.name.as_str()) == active {
return Some(p.name.as_str());
}
fallback.get_or_insert(p.name.as_str());
}
fallback
}
fn emit_plain(matched: Option<&str>) {
match matched {
Some(name) => println!("{name}"),
None => println!("unknown"),
}
}
fn emit_json(config: &AppConfig, matched: Option<&str>) {
let profile = matched.and_then(|n| config.find(n));
let value = serde_json::json!({
"profile": profile.map(|p| &p.name),
"tier": profile.map(endpoint_label),
"oauth": profile.map(Profile::is_oauth),
"active": profile.is_some_and(|p| config.is_active(&p.name)),
});
println!("{value}");
}
#[cfg(test)]
#[path = "../tests/inline/which.rs"]
mod tests;