claude_agent/auth/storage/
mod.rs1mod file;
4#[cfg(target_os = "macos")]
5mod keychain;
6
7use serde::{Deserialize, Serialize};
8
9use super::OAuthCredential;
10use crate::Result;
11
12pub use file::FileStorage;
13#[cfg(target_os = "macos")]
14pub use keychain::KeychainStorage;
15
16#[derive(Clone, Debug, Default, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct CliCredentials {
20 #[serde(skip_serializing_if = "Option::is_none")]
22 pub claude_ai_oauth: Option<OAuthCredential>,
23}
24
25impl CliCredentials {
26 pub fn oauth(&self) -> Option<&OAuthCredential> {
28 self.claude_ai_oauth.as_ref()
29 }
30}
31
32pub async fn load_cli_credentials() -> Result<Option<CliCredentials>> {
34 #[cfg(target_os = "macos")]
35 {
36 if let Some(creds) = KeychainStorage::load().await? {
37 return Ok(Some(creds));
38 }
39 }
40
41 FileStorage::load().await
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use secrecy::ExposeSecret;
48
49 #[test]
50 fn test_cli_credentials_parse() {
51 let json = r#"{
52 "claudeAiOauth": {
53 "accessToken": "sk-ant-oat01-test",
54 "refreshToken": "sk-ant-ort01-test",
55 "expiresAt": 1234567890,
56 "scopes": ["user:inference"],
57 "subscriptionType": "pro"
58 }
59 }"#;
60
61 let creds: CliCredentials = serde_json::from_str(json).unwrap();
62 let oauth = creds.oauth().unwrap();
63 assert_eq!(oauth.access_token.expose_secret(), "sk-ant-oat01-test");
64 assert_eq!(oauth.subscription_type, Some("pro".to_string()));
65 }
66}