use std::process::Command;
use super::{parse_credential_json, KEYCHAIN_SERVICE};
use crate::error::CredentialError;
pub fn get_token_macos() -> Result<String, CredentialError> {
let username = get_current_username()?;
let output = Command::new("/usr/bin/security")
.args([
"find-generic-password",
"-s",
KEYCHAIN_SERVICE,
"-a",
&username,
"-w", ])
.output()
.map_err(|_| CredentialError::NotFound)?;
if output.status.success() {
let content = String::from_utf8(output.stdout)
.map_err(|_| CredentialError::Parse("Invalid UTF-8 in credentials".to_string()))?
.trim()
.to_string();
parse_credential_json(&content)
} else {
Err(CredentialError::NotFound)
}
}
fn get_current_username() -> Result<String, CredentialError> {
std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.map_err(|_| CredentialError::NotFound)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_keychain_service_name() {
assert_eq!(KEYCHAIN_SERVICE, "Claude Code-credentials");
}
#[test]
#[ignore = "requires real Keychain credentials"]
fn test_get_token_macos_integration() {
let result = get_token_macos();
match result {
Ok(token) => {
assert!(token.starts_with("sk-ant-oat01-"));
println!("Token retrieved successfully (first 20 chars hidden)");
}
Err(CredentialError::NotFound) => {
println!("No credentials found - expected if not logged in");
}
Err(e) => {
panic!("Unexpected error: {}", e);
}
}
}
}