claude_agent/auth/providers/
cli.rs

1//! Claude Code CLI credential provider.
2
3use 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
10/// Provider that reads credentials from Claude Code CLI.
11pub struct ClaudeCliProvider;
12
13impl ClaudeCliProvider {
14    /// Create a new CLI provider.
15    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}