claude_agent/auth/providers/
cli.rs1use async_trait::async_trait;
4use tokio::process::Command;
5
6use crate::auth::storage::load_cli_credentials;
7use crate::auth::{Credential, CredentialProvider};
8use crate::{Error, Result};
9
10pub struct ClaudeCliProvider;
12
13impl ClaudeCliProvider {
14 pub fn new() -> Self {
16 Self
17 }
18
19 async fn refresh_via_cli() -> Result<Credential> {
20 let output = Command::new("claude")
21 .args(["auth", "refresh"])
22 .output()
23 .await
24 .map_err(|e| Error::auth(format!("Failed to run claude auth refresh: {}", e)))?;
25
26 if !output.status.success() {
27 let stderr = String::from_utf8_lossy(&output.stderr);
28 return Err(Error::auth(format!("Token refresh failed: {}", stderr)));
29 }
30
31 let creds = load_cli_credentials()
32 .await?
33 .ok_or_else(|| Error::auth("Credentials not found after refresh"))?;
34
35 let oauth = creds
36 .oauth()
37 .ok_or_else(|| Error::auth("No OAuth credentials after refresh"))?;
38
39 if oauth.is_expired() {
40 return Err(Error::auth("Token still expired after refresh"));
41 }
42
43 Ok(Credential::OAuth(oauth.clone()))
44 }
45}
46
47impl Default for ClaudeCliProvider {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53#[async_trait]
54impl CredentialProvider for ClaudeCliProvider {
55 fn name(&self) -> &str {
56 "claude_cli"
57 }
58
59 async fn resolve(&self) -> Result<Credential> {
60 let creds = load_cli_credentials().await?.ok_or_else(|| {
61 Error::auth("Claude Code CLI credentials not found. Run 'claude login' first.")
62 })?;
63
64 let oauth = creds
65 .oauth()
66 .ok_or_else(|| Error::auth("No OAuth credentials in Claude Code CLI config"))?;
67
68 if oauth.is_expired() {
69 return self.refresh().await;
70 }
71
72 Ok(Credential::OAuth(oauth.clone()))
73 }
74
75 async fn refresh(&self) -> Result<Credential> {
76 Self::refresh_via_cli().await
77 }
78
79 fn supports_refresh(&self) -> bool {
80 true
81 }
82}