claude_wrapper/command/
auth.rs1use crate::Claude;
2use crate::command::ClaudeCommand;
3use crate::error::Result;
4use crate::exec::{self, CommandOutput};
5
6#[derive(Debug, Clone, Default)]
21pub struct AuthStatusCommand {
22 json: bool,
23}
24
25impl AuthStatusCommand {
26 #[must_use]
28 pub fn new() -> Self {
29 Self { json: true }
30 }
31
32 #[must_use]
34 pub fn text(mut self) -> Self {
35 self.json = false;
36 self
37 }
38
39 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum LoginMode {
92 Claudeai,
96 Console,
101}
102
103impl LoginMode {
104 fn as_arg(self) -> &'static str {
105 match self {
106 Self::Claudeai => "--claudeai",
107 Self::Console => "--console",
108 }
109 }
110}
111
112#[derive(Debug, Clone, Default)]
139pub struct AuthLoginCommand {
140 email: Option<String>,
141 mode: Option<LoginMode>,
142 force_sso: bool,
143 #[deprecated(
144 since = "0.10.0",
145 note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x; \
146 the value passed via the deprecated `.sso(provider)` was being \
147 emitted as an extra positional and silently doing the wrong thing. \
148 Use `force_sso()` to set the boolean flag instead."
149 )]
150 legacy_sso_value: Option<String>,
151}
152
153impl AuthLoginCommand {
154 #[must_use]
156 pub fn new() -> Self {
157 Self::default()
158 }
159
160 #[must_use]
162 pub fn email(mut self, email: impl Into<String>) -> Self {
163 self.email = Some(email.into());
164 self
165 }
166
167 #[must_use]
172 pub fn mode(mut self, mode: LoginMode) -> Self {
173 self.mode = Some(mode);
174 self
175 }
176
177 #[must_use]
182 pub fn force_sso(mut self) -> Self {
183 self.force_sso = true;
184 self
185 }
186
187 #[deprecated(
200 since = "0.10.0",
201 note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x. \
202 Use `force_sso()` instead. The value passed here is ignored at \
203 emit time; the boolean intent is preserved."
204 )]
205 #[must_use]
206 pub fn sso(mut self, provider: impl Into<String>) -> Self {
207 self.force_sso = true;
212 #[allow(deprecated)]
213 {
214 self.legacy_sso_value = Some(provider.into());
215 }
216 self
217 }
218}
219
220impl ClaudeCommand for AuthLoginCommand {
221 type Output = CommandOutput;
222
223 fn args(&self) -> Vec<String> {
224 let mut args = vec!["auth".to_string(), "login".to_string()];
225 if let Some(mode) = self.mode {
226 args.push(mode.as_arg().to_string());
227 }
228 if let Some(ref email) = self.email {
229 args.push("--email".to_string());
230 args.push(email.clone());
231 }
232 if self.force_sso {
233 args.push("--sso".to_string());
234 }
235 args
236 }
237
238 #[cfg(feature = "async")]
239 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
240 exec::run_claude(claude, self.args()).await
241 }
242}
243
244#[derive(Debug, Clone, Default)]
258pub struct AuthLogoutCommand;
259
260impl AuthLogoutCommand {
261 #[must_use]
263 pub fn new() -> Self {
264 Self
265 }
266}
267
268impl ClaudeCommand for AuthLogoutCommand {
269 type Output = CommandOutput;
270
271 fn args(&self) -> Vec<String> {
272 vec!["auth".to_string(), "logout".to_string()]
273 }
274
275 #[cfg(feature = "async")]
276 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
277 exec::run_claude(claude, self.args()).await
278 }
279}
280
281#[derive(Debug, Clone, Default)]
295pub struct SetupTokenCommand;
296
297impl SetupTokenCommand {
298 #[must_use]
300 pub fn new() -> Self {
301 Self
302 }
303}
304
305impl ClaudeCommand for SetupTokenCommand {
306 type Output = CommandOutput;
307
308 fn args(&self) -> Vec<String> {
309 vec!["setup-token".to_string()]
310 }
311
312 #[cfg(feature = "async")]
313 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
314 exec::run_claude(claude, self.args()).await
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_auth_status_args() {
324 let cmd = AuthStatusCommand::new();
325 assert_eq!(cmd.args(), vec!["auth", "status", "--json"]);
326 }
327
328 #[test]
329 fn test_auth_status_text() {
330 let cmd = AuthStatusCommand::new().text();
331 assert_eq!(cmd.args(), vec!["auth", "status", "--text"]);
332 }
333
334 #[test]
335 fn test_auth_login_default() {
336 let cmd = AuthLoginCommand::new();
337 assert_eq!(cmd.args(), vec!["auth", "login"]);
338 }
339
340 #[test]
341 fn test_auth_login_with_email() {
342 let cmd = AuthLoginCommand::new().email("user@example.com");
343 assert_eq!(
344 cmd.args(),
345 vec!["auth", "login", "--email", "user@example.com"]
346 );
347 }
348
349 #[test]
350 fn test_auth_login_with_force_sso() {
351 let cmd = AuthLoginCommand::new().force_sso();
352 assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
353 }
354
355 #[test]
356 #[allow(deprecated)]
357 fn test_auth_login_deprecated_sso_emits_boolean_only() {
358 let cmd = AuthLoginCommand::new().sso("okta");
363 assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
364 }
365
366 #[test]
367 fn test_auth_login_with_mode_claudeai() {
368 let cmd = AuthLoginCommand::new().mode(LoginMode::Claudeai);
369 assert_eq!(cmd.args(), vec!["auth", "login", "--claudeai"]);
370 }
371
372 #[test]
373 fn test_auth_login_with_mode_console() {
374 let cmd = AuthLoginCommand::new().mode(LoginMode::Console);
375 assert_eq!(cmd.args(), vec!["auth", "login", "--console"]);
376 }
377
378 #[test]
379 fn test_auth_login_console_with_email() {
380 let cmd = AuthLoginCommand::new()
381 .mode(LoginMode::Console)
382 .email("ops@example.com");
383 assert_eq!(
384 cmd.args(),
385 vec!["auth", "login", "--console", "--email", "ops@example.com"]
386 );
387 }
388
389 #[test]
390 fn test_auth_logout() {
391 let cmd = AuthLogoutCommand::new();
392 assert_eq!(cmd.args(), vec!["auth", "logout"]);
393 }
394
395 #[test]
396 fn test_setup_token() {
397 let cmd = SetupTokenCommand::new();
398 assert_eq!(cmd.args(), vec!["setup-token"]);
399 }
400}