gemini-tts-cli 0.1.1

Agent-friendly Gemini text-to-speech CLI for expressive scripts, voices, tags, and audio files
use serde::Serialize;

use crate::config::{self, AppConfig};
use crate::error::AppError;
use crate::output::{self, Ctx};

#[derive(Serialize)]
struct AuthResult {
    configured: bool,
    source: String,
    api_key: String,
    path: String,
}

pub fn set(ctx: Ctx, api_key: String) -> Result<(), AppError> {
    validate_key(&api_key)?;
    let config = config::set_value("keys.api_key", &api_key)?;
    let result = auth_result(&config);
    output::print_success_or(ctx, &result, |r| {
        println!("Saved Gemini API key to {}", r.path);
    });
    Ok(())
}

pub fn import_env(ctx: Ctx) -> Result<(), AppError> {
    let Some((source, key)) = config::api_key_from_env() else {
        return Err(AppError::Config(
            "no key found in GEMINI_API_KEY, GOOGLE_API_KEY, or GOOGLE_AI_API_KEY".into(),
        ));
    };
    validate_key(&key)?;
    let config = config::set_value("keys.api_key", &key)?;
    let mut result = auth_result(&config);
    result.source = source.into();
    output::print_success_or(ctx, &result, |r| {
        println!("Imported {} into {}", r.source, r.path);
    });
    Ok(())
}

pub fn status(ctx: Ctx, config: &AppConfig) -> Result<(), AppError> {
    let result = auth_result(config);
    output::print_success_or(ctx, &result, |r| {
        if r.configured {
            println!("Gemini API key configured: {} ({})", r.api_key, r.source);
        } else {
            println!("Gemini API key not configured");
        }
    });
    Ok(())
}

fn auth_result(config: &AppConfig) -> AuthResult {
    let key = config::api_key(config);
    AuthResult {
        configured: key.is_some(),
        source: key
            .as_ref()
            .map(|(source, _)| source.clone())
            .unwrap_or_else(|| "none".into()),
        api_key: key
            .as_ref()
            .map(|(_, value)| config::mask_secret(value))
            .unwrap_or_else(|| "not_set".into()),
        path: config::config_path().display().to_string(),
    }
}

fn validate_key(api_key: &str) -> Result<(), AppError> {
    let trimmed = api_key.trim();
    if trimmed.len() < 20 {
        return Err(AppError::InvalidInput(
            "Gemini API key looks too short".into(),
        ));
    }
    if trimmed.contains(char::is_whitespace) {
        return Err(AppError::InvalidInput(
            "Gemini API key must not contain whitespace".into(),
        ));
    }
    Ok(())
}