sunox 0.0.3

Generate AI music from your terminal via direct Suno web workflows
use crate::api::SunoClient;
use crate::app::AppContext;
use crate::auth::{self, AuthState};
use crate::cli::AuthArgs;
use crate::core::CliError;

pub async fn run(args: AuthArgs, _ctx: &AppContext) -> Result<(), CliError> {
    if args.logout {
        AuthState::delete()?;
        eprintln!("Logged out; removed stored Suno authentication");
        return Ok(());
    }

    let mut state = match AuthState::load() {
        Ok(s) => s,
        Err(CliError::AuthMissing) => AuthState::default(),
        Err(e) => return Err(e),
    };

    let has_explicit_auth_input =
        args.login || args.refresh || args.jwt.is_some() || args.cookie.is_some();
    let should_login = args.login
        || (!has_explicit_auth_input && state.jwt.is_none() && state.clerk_client_cookie.is_none());

    if args.refresh {
        let cookie = state.clerk_client_cookie.clone().ok_or_else(|| {
            CliError::Config("no Clerk session cookie stored — run `sunox login` first".into())
        })?;
        let http = reqwest::Client::new();
        eprintln!("Refreshing JWT via Clerk session cookie...");
        let (session_id, jwt) = if let Some(session_id) = state.session_id.clone() {
            (
                session_id.clone(),
                auth::clerk_refresh_jwt(&http, &cookie, &session_id).await?,
            )
        } else {
            auth::clerk_token_exchange(&http, &cookie).await?
        };
        state.session_id = Some(session_id);
        state.jwt = Some(jwt);
        state.save()?;
        eprintln!("JWT refreshed successfully");
    } else if should_login {
        eprintln!("Extracting Suno session from your browser...");
        let browser_auth = auth::extract_browser_auth()?;

        let http = reqwest::Client::new();
        eprintln!("Exchanging for access token via Clerk...");
        let (session_id, jwt) =
            auth::clerk_token_exchange(&http, &browser_auth.clerk_client_cookie).await?;

        state.cookie = Some(browser_auth.cookie_header);
        state.clerk_client_cookie = Some(browser_auth.clerk_client_cookie);
        state.session_id = Some(session_id);
        state.jwt = Some(jwt);
        state.device_id = browser_auth
            .device_id
            .or(state.device_id)
            .or_else(|| Some(uuid::Uuid::new_v4().to_string()));
    } else if let Some(cookie) = args.cookie.as_deref() {
        let browser_auth = auth::normalize_cookie_input(cookie)?;
        let http = reqwest::Client::new();
        eprintln!("Exchanging cookie for access token...");
        let (session_id, jwt) =
            auth::clerk_token_exchange(&http, &browser_auth.clerk_client_cookie).await?;

        state.cookie = Some(browser_auth.cookie_header);
        state.clerk_client_cookie = Some(browser_auth.clerk_client_cookie);
        state.session_id = Some(session_id);
        state.jwt = Some(jwt);
        state.device_id = browser_auth
            .device_id
            .or(state.device_id)
            .or_else(|| Some(uuid::Uuid::new_v4().to_string()));
    } else if let Some(jwt) = args.jwt.clone() {
        state.jwt = Some(jwt);
        if state.device_id.is_none() {
            state.device_id = Some(uuid::Uuid::new_v4().to_string());
        }
    } else {
        eprintln!("Checking existing authentication...");
    }

    if let Some(device) = args.device.as_ref() {
        state.device_id = Some(device.clone());
    }

    let should_save_after_verify = args.refresh
        || should_login
        || args.cookie.is_some()
        || args.jwt.is_some()
        || args.device.is_some();
    let client = SunoClient::new_with_refresh(state.clone()).await?;
    let info = client.billing_info().await?;
    if should_save_after_verify {
        state.save()?;
    }
    eprintln!(
        "Authenticated! Plan: {}, Credits: {}",
        info.plan.name, info.total_credits_left
    );
    Ok(())
}