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