use crate::Claude;
use crate::command::ClaudeCommand;
use crate::error::Result;
use crate::exec::{self, CommandOutput};
#[derive(Debug, Clone, Default)]
pub struct AuthStatusCommand {
json: bool,
}
impl AuthStatusCommand {
#[must_use]
pub fn new() -> Self {
Self { json: true }
}
#[must_use]
pub fn text(mut self) -> Self {
self.json = false;
self
}
#[cfg(all(feature = "json", feature = "async"))]
pub async fn execute_json(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
let mut cmd = self.clone();
cmd.json = true;
let output = exec::run_claude(claude, cmd.args()).await?;
serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
message: format!("failed to parse auth status: {e}"),
source: e,
})
}
#[cfg(all(feature = "sync", feature = "json"))]
pub fn execute_json_sync(&self, claude: &Claude) -> Result<crate::types::AuthStatus> {
let mut cmd = self.clone();
cmd.json = true;
let output = exec::run_claude_sync(claude, cmd.args())?;
serde_json::from_str(&output.stdout).map_err(|e| crate::error::Error::Json {
message: format!("failed to parse auth status: {e}"),
source: e,
})
}
}
impl ClaudeCommand for AuthStatusCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["auth".to_string(), "status".to_string()];
if self.json {
args.push("--json".to_string());
} else {
args.push("--text".to_string());
}
args
}
#[cfg(feature = "async")]
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoginMode {
Claudeai,
Console,
}
impl LoginMode {
fn as_arg(self) -> &'static str {
match self {
Self::Claudeai => "--claudeai",
Self::Console => "--console",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct AuthLoginCommand {
email: Option<String>,
mode: Option<LoginMode>,
force_sso: bool,
#[deprecated(
since = "0.10.0",
note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x; \
the value passed via the deprecated `.sso(provider)` was being \
emitted as an extra positional and silently doing the wrong thing. \
Use `force_sso()` to set the boolean flag instead."
)]
legacy_sso_value: Option<String>,
}
impl AuthLoginCommand {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn email(mut self, email: impl Into<String>) -> Self {
self.email = Some(email.into());
self
}
#[must_use]
pub fn mode(mut self, mode: LoginMode) -> Self {
self.mode = Some(mode);
self
}
#[must_use]
pub fn force_sso(mut self) -> Self {
self.force_sso = true;
self
}
#[deprecated(
since = "0.10.0",
note = "the `--sso` flag is a boolean since at least Claude Code 2.1.x. \
Use `force_sso()` instead. The value passed here is ignored at \
emit time; the boolean intent is preserved."
)]
#[must_use]
pub fn sso(mut self, provider: impl Into<String>) -> Self {
self.force_sso = true;
#[allow(deprecated)]
{
self.legacy_sso_value = Some(provider.into());
}
self
}
}
impl ClaudeCommand for AuthLoginCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
let mut args = vec!["auth".to_string(), "login".to_string()];
if let Some(mode) = self.mode {
args.push(mode.as_arg().to_string());
}
if let Some(ref email) = self.email {
args.push("--email".to_string());
args.push(email.clone());
}
if self.force_sso {
args.push("--sso".to_string());
}
args
}
#[cfg(feature = "async")]
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Default)]
pub struct AuthLogoutCommand;
impl AuthLogoutCommand {
#[must_use]
pub fn new() -> Self {
Self
}
}
impl ClaudeCommand for AuthLogoutCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
vec!["auth".to_string(), "logout".to_string()]
}
#[cfg(feature = "async")]
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[derive(Debug, Clone, Default)]
pub struct SetupTokenCommand;
impl SetupTokenCommand {
#[must_use]
pub fn new() -> Self {
Self
}
}
impl ClaudeCommand for SetupTokenCommand {
type Output = CommandOutput;
fn args(&self) -> Vec<String> {
vec!["setup-token".to_string()]
}
#[cfg(feature = "async")]
async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
exec::run_claude(claude, self.args()).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auth_status_args() {
let cmd = AuthStatusCommand::new();
assert_eq!(cmd.args(), vec!["auth", "status", "--json"]);
}
#[test]
fn test_auth_status_text() {
let cmd = AuthStatusCommand::new().text();
assert_eq!(cmd.args(), vec!["auth", "status", "--text"]);
}
#[test]
fn test_auth_login_default() {
let cmd = AuthLoginCommand::new();
assert_eq!(cmd.args(), vec!["auth", "login"]);
}
#[test]
fn test_auth_login_with_email() {
let cmd = AuthLoginCommand::new().email("user@example.com");
assert_eq!(
cmd.args(),
vec!["auth", "login", "--email", "user@example.com"]
);
}
#[test]
fn test_auth_login_with_force_sso() {
let cmd = AuthLoginCommand::new().force_sso();
assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
}
#[test]
#[allow(deprecated)]
fn test_auth_login_deprecated_sso_emits_boolean_only() {
let cmd = AuthLoginCommand::new().sso("okta");
assert_eq!(cmd.args(), vec!["auth", "login", "--sso"]);
}
#[test]
fn test_auth_login_with_mode_claudeai() {
let cmd = AuthLoginCommand::new().mode(LoginMode::Claudeai);
assert_eq!(cmd.args(), vec!["auth", "login", "--claudeai"]);
}
#[test]
fn test_auth_login_with_mode_console() {
let cmd = AuthLoginCommand::new().mode(LoginMode::Console);
assert_eq!(cmd.args(), vec!["auth", "login", "--console"]);
}
#[test]
fn test_auth_login_console_with_email() {
let cmd = AuthLoginCommand::new()
.mode(LoginMode::Console)
.email("ops@example.com");
assert_eq!(
cmd.args(),
vec!["auth", "login", "--console", "--email", "ops@example.com"]
);
}
#[test]
fn test_auth_logout() {
let cmd = AuthLogoutCommand::new();
assert_eq!(cmd.args(), vec!["auth", "logout"]);
}
#[test]
fn test_setup_token() {
let cmd = SetupTokenCommand::new();
assert_eq!(cmd.args(), vec!["setup-token"]);
}
}