stynx_code_auth/infrastructure/
file_provider.rs1use stynx_code_errors::{AppError, AppResult};
2
3use crate::domain::Credential;
4
5pub fn resolve_file_oauth() -> AppResult<Credential> {
6 let home = std::env::var("HOME")
7 .or_else(|_| std::env::var("USERPROFILE"))
8 .map_err(|_| AppError::Provider("cannot determine home directory".to_string()))?;
9
10 let path = std::path::PathBuf::from(home)
11 .join(".claude")
12 .join(".credentials.json");
13
14 let contents = std::fs::read_to_string(&path)
15 .map_err(|e| AppError::Provider(format!("cannot read {}: {e}", path.display())))?;
16
17 let parsed: serde_json::Value = serde_json::from_str(&contents)
18 .map_err(|e| AppError::Provider(format!("failed to parse credentials JSON: {e}")))?;
19
20 let oauth = parsed
21 .get("claudeAiOauth")
22 .ok_or_else(|| AppError::Provider("no claudeAiOauth in credentials file".to_string()))?;
23
24 let access_token = oauth
25 .get("accessToken")
26 .and_then(|v| v.as_str())
27 .ok_or_else(|| AppError::Provider("no accessToken in OAuth data".to_string()))?
28 .to_string();
29
30 let expires_at = oauth.get("expiresAt").and_then(|v| v.as_u64()).unwrap_or(0);
31
32 if expires_at > 0 {
33 let now_ms = std::time::SystemTime::now()
34 .duration_since(std::time::UNIX_EPOCH)
35 .map(|d| d.as_millis() as u64)
36 .unwrap_or(0);
37
38 if now_ms > expires_at {
39 return Err(AppError::Provider(
40 "Claude Code OAuth token expired. Run `claude` to refresh your session."
41 .to_string(),
42 ));
43 }
44 }
45
46 Ok(Credential::ClaudeCodeOAuth {
47 access_token,
48 expires_at,
49 })
50}
51
52pub fn resolve_settings_json() -> AppResult<Credential> {
53 let home = std::env::var("HOME")
54 .or_else(|_| std::env::var("USERPROFILE"))
55 .map_err(|_| AppError::Provider("cannot determine home directory".to_string()))?;
56
57 let path = std::path::PathBuf::from(home)
58 .join(".stynx")
59 .join("settings.json");
60
61 let contents = std::fs::read_to_string(&path)
62 .map_err(|e| AppError::Provider(format!("cannot read {}: {e}", path.display())))?;
63
64 let parsed: serde_json::Value = serde_json::from_str(&contents)
65 .map_err(|e| AppError::Provider(format!("failed to parse settings.json: {e}")))?;
66
67 let env = parsed
68 .get("env")
69 .ok_or_else(|| AppError::Provider("no env in settings.json".to_string()))?;
70
71 let token = env
72 .get("ANTHROPIC_AUTH_TOKEN")
73 .and_then(|v| v.as_str())
74 .ok_or_else(|| AppError::Provider("no ANTHROPIC_AUTH_TOKEN in settings.json".to_string()))?
75 .to_string();
76
77 let base_url = env
78 .get("ANTHROPIC_BASE_URL")
79 .and_then(|v| v.as_str())
80 .map(str::trim)
81 .filter(|s| !s.is_empty())
82 .unwrap_or("https://api.anthropic.com")
83 .to_string();
84
85 tracing::debug!("using ANTHROPIC_AUTH_TOKEN from settings.json");
86
87 Ok(Credential::AuthToken { token, base_url })
88}