claude_wrapper/command/
auth.rs1#[cfg(any(feature = "async", all(feature = "sync", feature = "json")))]
9use crate::Claude;
10use crate::command::ClaudeCommand;
11#[cfg(any(feature = "async", all(feature = "sync", feature = "json")))]
12use crate::error::Result;
13#[cfg(any(feature = "async", all(feature = "sync", feature = "json")))]
14use crate::exec;
15use crate::exec::CommandOutput;
16
17#[derive(Debug, Clone, Default)]
32pub struct AuthStatusCommand {
33 json: bool,
34}
35
36impl AuthStatusCommand {
37 #[must_use]
39 pub fn new() -> Self {
40 Self { json: true }
41 }
42
43 #[must_use]
45 pub fn text(mut self) -> Self {
46 self.json = false;
47 self
48 }
49
50 #[cfg(all(feature = "json", feature = "async"))]
52 pub async fn execute_json(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
53 let mut cmd = self.clone();
54 cmd.json = true;
55
56 let output = exec::run_claude(claude, cmd.args()).await?;
57
58 serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
59 message: format!("failed to parse auth status: {e}"),
60 source: e,
61 })
62 }
63
64 #[cfg(all(feature = "sync", feature = "json"))]
66 pub fn execute_json_sync(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
67 let mut cmd = self.clone();
68 cmd.json = true;
69
70 let output = exec::run_claude_sync(claude, cmd.args())?;
71
72 serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
73 message: format!("failed to parse auth status: {e}"),
74 source: e,
75 })
76 }
77}
78
79impl ClaudeCommand for AuthStatusCommand {
80 type Output = CommandOutput;
81
82 fn args(&self) -> Vec<String> {
83 let mut args = vec!["auth".to_string(), "status".to_string()];
84 if self.json {
85 args.push("--json".to_string());
86 } else {
87 args.push("--text".to_string());
88 }
89 args
90 }
91
92 #[cfg(feature = "async")]
93 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
94 exec::run_claude(claude, self.args()).await
95 }
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub enum LoginMode {
103 Claudeai,
107 Console,
112}
113
114impl LoginMode {
115 fn as_arg(self) -> &'static str {
116 match self {
117 Self::Claudeai => "--claudeai",
118 Self::Console => "--console",
119 }
120 }
121}
122
123#[derive(Debug, Clone, Default)]
150pub struct AuthLoginCommand {
151 email: Option<String>,
152 mode: Option<LoginMode>,
153 force_sso: bool,
154 #[deprecated(
155 since = "0.10.0",
156 note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x; \
157 the value passed via the deprecated `.sso(provider)` was being \
158 emitted as an extra positional and silently doing the wrong thing. \
159 Use `force_sso()` to set the boolean flag instead."
160 )]
161 legacy_sso_value: Option<String>,
162}
163
164impl AuthLoginCommand {
165 #[must_use]
167 pub fn new() -> Self {
168 Self::default()
169 }
170
171 #[must_use]
173 pub fn email(mut self, email: impl Into<String>) -> Self {
174 self.email = Some(email.into());
175 self
176 }
177
178 #[must_use]
183 pub fn mode(mut self, mode: LoginMode) -> Self {
184 self.mode = Some(mode);
185 self
186 }
187
188 #[must_use]
193 pub fn force_sso(mut self) -> Self {
194 self.force_sso = true;
195 self
196 }
197
198 #[deprecated(
211 since = "0.10.0",
212 note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x. \
213 Use `force_sso()` instead. The value passed here is ignored at \
214 emit time; the boolean intent is preserved."
215 )]
216 #[must_use]
217 pub fn sso(mut self, provider: impl Into<String>) -> Self {
218 self.force_sso = true;
223 #[allow(deprecated)]
224 {
225 self.legacy_sso_value = Some(provider.into());
226 }
227 self
228 }
229}
230
231impl ClaudeCommand for AuthLoginCommand {
232 type Output = CommandOutput;
233
234 fn args(&self) -> Vec<String> {
235 let mut args = vec!["auth".to_string(), "login".to_string()];
236 if let Some(mode) = self.mode {
237 args.push(mode.as_arg().to_string());
238 }
239 if let Some(ref email) = self.email {
240 args.push("--email".to_string());
241 args.push(email.clone());
242 }
243 if self.force_sso {
244 args.push("--sso".to_string());
245 }
246 args
247 }
248
249 #[cfg(feature = "async")]
250 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
251 exec::run_claude(claude, self.args()).await
252 }
253}
254
255#[derive(Debug, Clone, Default)]
269pub struct AuthLogoutCommand;
270
271impl AuthLogoutCommand {
272 #[must_use]
274 pub fn new() -> Self {
275 Self
276 }
277}
278
279impl ClaudeCommand for AuthLogoutCommand {
280 type Output = CommandOutput;
281
282 fn args(&self) -> Vec<String> {
283 vec!["auth".to_string(), "logout".to_string()]
284 }
285
286 #[cfg(feature = "async")]
287 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
288 exec::run_claude(claude, self.args()).await
289 }
290}
291
292#[derive(Debug, Clone, Default)]
306pub struct SetupTokenCommand;
307
308impl SetupTokenCommand {
309 #[must_use]
311 pub fn new() -> Self {
312 Self
313 }
314}
315
316impl ClaudeCommand for SetupTokenCommand {
317 type Output = CommandOutput;
318
319 fn args(&self) -> Vec<String> {
320 vec!["setup-token".to_string()]
321 }
322
323 #[cfg(feature = "async")]
324 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
325 exec::run_claude(claude, self.args()).await
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_auth_status_args() {
335 let cmd = AuthStatusCommand::new();
336 assert_eq!(cmd.args(), vec!["auth", "status", "--json"]);
337 }
338
339 #[test]
340 fn test_auth_status_text() {
341 let cmd = AuthStatusCommand::new().text();
342 assert_eq!(cmd.args(), vec!["auth", "status", "--text"]);
343 }
344
345 #[test]
346 fn test_auth_login_default() {
347 let cmd = AuthLoginCommand::new();
348 assert_eq!(cmd.args(), vec!["auth", "login"]);
349 }
350
351 #[test]
352 fn test_auth_login_with_email() {
353 let cmd = AuthLoginCommand::new().email("user@example.com");
354 assert_eq!(
355 cmd.args(),
356 vec!["auth", "login", "--email", "user@example.com"]
357 );
358 }
359
360 #[test]
361 fn test_auth_login_with_force_sso() {
362 let cmd = AuthLoginCommand::new().force_sso();
363 assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
364 }
365
366 #[test]
367 #[allow(deprecated)]
368 fn test_auth_login_deprecated_sso_emits_boolean_only() {
369 let cmd = AuthLoginCommand::new().sso("okta");
374 assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
375 }
376
377 #[test]
378 fn test_auth_login_with_mode_claudeai() {
379 let cmd = AuthLoginCommand::new().mode(LoginMode::Claudeai);
380 assert_eq!(cmd.args(), vec!["auth", "login", "--claudeai"]);
381 }
382
383 #[test]
384 fn test_auth_login_with_mode_console() {
385 let cmd = AuthLoginCommand::new().mode(LoginMode::Console);
386 assert_eq!(cmd.args(), vec!["auth", "login", "--console"]);
387 }
388
389 #[test]
390 fn test_auth_login_console_with_email() {
391 let cmd = AuthLoginCommand::new()
392 .mode(LoginMode::Console)
393 .email("ops@example.com");
394 assert_eq!(
395 cmd.args(),
396 vec!["auth", "login", "--console", "--email", "ops@example.com"]
397 );
398 }
399
400 #[test]
401 fn test_auth_logout() {
402 let cmd = AuthLogoutCommand::new();
403 assert_eq!(cmd.args(), vec!["auth", "logout"]);
404 }
405
406 #[test]
407 fn test_setup_token() {
408 let cmd = SetupTokenCommand::new();
409 assert_eq!(cmd.args(), vec!["setup-token"]);
410 }
411}