rhood-cli 0.1.1

Terminal CLI for the Robinhood trading API
use rhood_core::{RhoodConfig, RhoodError, RobinhoodClient};
use secrecy::ExposeSecret;

pub async fn run_login(config: RhoodConfig) -> anyhow::Result<()> {
    let client = RobinhoodClient::with_config(config.clone())?;

    let username = match config.auth.username {
        Some(ref name) if !name.is_empty() => name.clone(),
        _ => dialoguer::Input::<String>::new()
            .with_prompt("Robinhood username")
            .interact_text()?,
    };

    let password = match config.auth.password {
        Some(ref secret) if !secret.expose_secret().is_empty() => {
            secret.expose_secret().to_string()
        }
        _ => dialoguer::Password::new()
            .with_prompt("Robinhood password")
            .interact()?,
    };

    let credentials_preloaded = config
        .auth
        .username
        .as_ref()
        .is_some_and(|name| !name.is_empty())
        && config
            .auth
            .password
            .as_ref()
            .is_some_and(|secret| !secret.expose_secret().is_empty());

    let mfa = match config.auth.mfa_secret {
        Some(ref secret) if !secret.expose_secret().is_empty() => {
            secret.expose_secret().to_string()
        }
        _ if credentials_preloaded => String::new(),
        _ => dialoguer::Input::new()
            .with_prompt("MFA code (or TOTP secret, empty to skip)")
            .allow_empty(true)
            .interact_text()?,
    };

    let mfa_ref = if mfa.is_empty() {
        None
    } else {
        Some(mfa.as_str())
    };

    match client.login(&username, &password, mfa_ref).await {
        Ok(()) => {
            println!("Login successful.");
            Ok(())
        }
        Err(RhoodError::ChallengeRequired(challenge_type)) => {
            let challenge_id = match client.auth_state().await {
                rhood_core::auth::AuthState::Challenged { challenge_id, .. } => challenge_id,
                _ => anyhow::bail!("Challenge required but no challenge ID available"),
            };
            println!("Verification required via {challenge_type}. Check your device.");
            let code: String = dialoguer::Input::new()
                .with_prompt("Enter verification code")
                .interact_text()?;
            client
                .submit_challenge_response(&challenge_id, &code, &username, &password, mfa_ref)
                .await?;
            println!("Login successful.");
            Ok(())
        }
        Err(err) => Err(err.into()),
    }
}

pub async fn run_logout(config: RhoodConfig) -> anyhow::Result<()> {
    let client = RobinhoodClient::with_config(config)?;
    client.logout().await?;
    println!("Logged out.");
    Ok(())
}