Skip to main content

claude_wrapper/command/
auth.rs

1use crate::Claude;
2use crate::command::ClaudeCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6/// Check authentication status.
7///
8/// # Example
9///
10/// ```no_run
11/// use claude_wrapper::{Claude, ClaudeCommand, AuthStatusCommand};
12///
13/// # async fn example() -> claude_wrapper::Result<()> {
14/// let claude = Claude::builder().build()?;
15/// let status = AuthStatusCommand::new().execute_json(&claude).await?;
16/// println!("logged in: {}", status.logged_in);
17/// # Ok(())
18/// # }
19/// ```
20#[derive(Debug, Clone, Default)]
21pub struct AuthStatusCommand {
22    json: bool,
23}
24
25impl AuthStatusCommand {
26    /// Create a new auth status command.
27    #[must_use]
28    pub fn new() -> Self {
29        Self { json: true }
30    }
31
32    /// Request text output instead of JSON.
33    #[must_use]
34    pub fn text(mut self) -> Self {
35        self.json = false;
36        self
37    }
38
39    /// Execute and parse the JSON result into an [`AuthStatus`](crate::types::AuthStatus).
40    #[cfg(all(feature = "json", feature = "async"))]
41    pub async fn execute_json(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
42        let mut cmd = self.clone();
43        cmd.json = true;
44
45        let output = exec::run_claude(claude, cmd.args()).await?;
46
47        serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
48            message: format!("failed to parse auth status: {e}"),
49            source: e,
50        })
51    }
52
53    /// Blocking mirror of [`AuthStatusCommand::execute_json`].
54    #[cfg(all(feature = "sync", feature = "json"))]
55    pub fn execute_json_sync(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
56        let mut cmd = self.clone();
57        cmd.json = true;
58
59        let output = exec::run_claude_sync(claude, cmd.args())?;
60
61        serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
62            message: format!("failed to parse auth status: {e}"),
63            source: e,
64        })
65    }
66}
67
68impl ClaudeCommand for AuthStatusCommand {
69    type Output = CommandOutput;
70
71    fn args(&self) -> Vec<String> {
72        let mut args = vec!["auth".to_string(), "status".to_string()];
73        if self.json {
74            args.push("--json".to_string());
75        } else {
76            args.push("--text".to_string());
77        }
78        args
79    }
80
81    #[cfg(feature = "async")]
82    async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
83        exec::run_claude(claude, self.args()).await
84    }
85}
86
87/// Authenticate with Claude.
88///
89/// # Example
90///
91/// ```no_run
92/// use claude_wrapper::{Claude, ClaudeCommand, AuthLoginCommand};
93///
94/// # async fn example() -> claude_wrapper::Result<()> {
95/// let claude = Claude::builder().build()?;
96/// AuthLoginCommand::new()
97///     .email("user@example.com")
98///     .execute(&claude)
99///     .await?;
100/// # Ok(())
101/// # }
102/// ```
103#[derive(Debug, Clone, Default)]
104pub struct AuthLoginCommand {
105    email: Option<String>,
106    sso: Option<String>,
107}
108
109impl AuthLoginCommand {
110    /// Create a new auth login command.
111    #[must_use]
112    pub fn new() -> Self {
113        Self::default()
114    }
115
116    /// Set the email address for authentication.
117    #[must_use]
118    pub fn email(mut self, email: impl Into<String>) -> Self {
119        self.email = Some(email.into());
120        self
121    }
122
123    /// Set the SSO provider for authentication.
124    #[must_use]
125    pub fn sso(mut self, provider: impl Into<String>) -> Self {
126        self.sso = Some(provider.into());
127        self
128    }
129}
130
131impl ClaudeCommand for AuthLoginCommand {
132    type Output = CommandOutput;
133
134    fn args(&self) -> Vec<String> {
135        let mut args = vec!["auth".to_string(), "login".to_string()];
136        if let Some(ref email) = self.email {
137            args.push("--email".to_string());
138            args.push(email.clone());
139        }
140        if let Some(ref sso) = self.sso {
141            args.push("--sso".to_string());
142            args.push(sso.clone());
143        }
144        args
145    }
146
147    #[cfg(feature = "async")]
148    async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
149        exec::run_claude(claude, self.args()).await
150    }
151}
152
153/// Deauthenticate from Claude.
154///
155/// # Example
156///
157/// ```no_run
158/// use claude_wrapper::{Claude, ClaudeCommand, AuthLogoutCommand};
159///
160/// # async fn example() -> claude_wrapper::Result<()> {
161/// let claude = Claude::builder().build()?;
162/// AuthLogoutCommand::new().execute(&claude).await?;
163/// # Ok(())
164/// # }
165/// ```
166#[derive(Debug, Clone, Default)]
167pub struct AuthLogoutCommand;
168
169impl AuthLogoutCommand {
170    /// Create a new auth logout command.
171    #[must_use]
172    pub fn new() -> Self {
173        Self
174    }
175}
176
177impl ClaudeCommand for AuthLogoutCommand {
178    type Output = CommandOutput;
179
180    fn args(&self) -> Vec<String> {
181        vec!["auth".to_string(), "logout".to_string()]
182    }
183
184    #[cfg(feature = "async")]
185    async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
186        exec::run_claude(claude, self.args()).await
187    }
188}
189
190/// Set up a long-lived authentication token.
191///
192/// # Example
193///
194/// ```no_run
195/// use claude_wrapper::{Claude, ClaudeCommand, SetupTokenCommand};
196///
197/// # async fn example() -> claude_wrapper::Result<()> {
198/// let claude = Claude::builder().build()?;
199/// SetupTokenCommand::new().execute(&claude).await?;
200/// # Ok(())
201/// # }
202/// ```
203#[derive(Debug, Clone, Default)]
204pub struct SetupTokenCommand;
205
206impl SetupTokenCommand {
207    /// Create a new setup-token command.
208    #[must_use]
209    pub fn new() -> Self {
210        Self
211    }
212}
213
214impl ClaudeCommand for SetupTokenCommand {
215    type Output = CommandOutput;
216
217    fn args(&self) -> Vec<String> {
218        vec!["setup-token".to_string()]
219    }
220
221    #[cfg(feature = "async")]
222    async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
223        exec::run_claude(claude, self.args()).await
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_auth_status_args() {
233        let cmd = AuthStatusCommand::new();
234        assert_eq!(cmd.args(), vec!["auth", "status", "--json"]);
235    }
236
237    #[test]
238    fn test_auth_status_text() {
239        let cmd = AuthStatusCommand::new().text();
240        assert_eq!(cmd.args(), vec!["auth", "status", "--text"]);
241    }
242
243    #[test]
244    fn test_auth_login_default() {
245        let cmd = AuthLoginCommand::new();
246        assert_eq!(cmd.args(), vec!["auth", "login"]);
247    }
248
249    #[test]
250    fn test_auth_login_with_email() {
251        let cmd = AuthLoginCommand::new().email("user@example.com");
252        assert_eq!(
253            cmd.args(),
254            vec!["auth", "login", "--email", "user@example.com"]
255        );
256    }
257
258    #[test]
259    fn test_auth_login_with_sso() {
260        let cmd = AuthLoginCommand::new().sso("okta");
261        assert_eq!(cmd.args(), vec!["auth", "login", "--sso", "okta"]);
262    }
263
264    #[test]
265    fn test_auth_logout() {
266        let cmd = AuthLogoutCommand::new();
267        assert_eq!(cmd.args(), vec!["auth", "logout"]);
268    }
269
270    #[test]
271    fn test_setup_token() {
272        let cmd = SetupTokenCommand::new();
273        assert_eq!(cmd.args(), vec!["setup-token"]);
274    }
275}