use crate::adapters::{json_top_level_keys, HealthFuture};
use crate::health::{base_health, run_status_probe, HealthStatus};
use crate::types::{AdapterId, AuthKind, Capabilities};
use serde_json::Value;
use std::path::Path;
pub(super) const CAPABILITIES: Capabilities = Capabilities {
tool_use: true,
mcp: true,
hooks: true,
sessions: true,
streaming: true,
};
pub(super) fn parse_version(stdout: &str) -> Option<String> {
crate::adapters::first_versionish_token(stdout)
}
pub(super) fn probe_auth(home: &Path) -> AuthKind {
let dir = home.join(".claude");
let candidates = [".credentials.json", "auth.json"];
for candidate in candidates {
let path = dir.join(candidate);
if !path.exists() {
continue;
}
let Some(keys) = json_top_level_keys(&path) else {
return AuthKind::Unknown;
};
let lower: Vec<String> = keys.iter().map(|k| k.to_ascii_lowercase()).collect();
let has_oauth = lower
.iter()
.any(|k| k.contains("oauth") || k.contains("account"));
let has_api_key = lower.iter().any(|k| k.contains("apikey") || k == "api_key");
return match (has_oauth, has_api_key) {
(true, _) => AuthKind::Subscription,
(false, true) => AuthKind::ApiKey,
(false, false) => AuthKind::Unknown,
};
}
if dir.is_dir() {
AuthKind::Unknown
} else {
AuthKind::Unauthenticated
}
}
pub(super) fn health_check(binary_path: &Path) -> HealthFuture {
let bin = binary_path.to_path_buf();
Box::pin(async move {
let mut h = base_health(AdapterId::ClaudeCode);
match run_status_probe(&bin, &["auth", "status"]).await {
Ok((stdout, stderr, code)) if code == 0 => match serde_json::from_str::<Value>(&stdout)
{
Ok(parsed) => {
let logged_in = parsed
.get("loggedIn")
.and_then(Value::as_bool)
.unwrap_or(false);
h.status = if logged_in {
HealthStatus::Ready
} else {
HealthStatus::NotConfigured
};
h.details = parsed;
if !logged_in {
h.reason = Some("Run `claude auth login` to authenticate".to_string());
}
}
Err(e) => {
h.status = HealthStatus::Unknown;
h.reason = Some(format!(
"could not parse `claude auth status` output ({e}); stderr: {stderr}",
));
}
},
Ok((_stdout, stderr, code)) => {
h.status = HealthStatus::NotConfigured;
h.reason = Some(format!(
"`claude auth status` exited {code}; stderr: {}",
stderr.trim()
));
}
Err(e) => {
h.status = HealthStatus::NetworkError;
h.reason = Some(format!("status probe failed: {e}"));
}
}
h
})
}