Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

nushell/modules/minimax.nu

Raw
# MiniMax AI integration — image generation, text-to-speech, and usage.
# Set MINIMAX_API_KEY in secrets.nuon (unified sk-cp- token covers all models).
export def usage [] {
    let token = $env | get --optional MINIMAX_API_KEY
    if ($token | is-empty) {
        error make {
            msg: "Token for MiniMax missing in environment"
            label: { text: "Set MINIMAX_API_KEY in your secrets.nuon file" }
        }
    }
    let headers = {
        Authorization: $'Bearer ($token)'
        Content-Type: 'application/json'
        User-Agent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
    }
    let response = http get --headers $headers 'https://www.minimax.io/v1/api/openplatform/coding_plan/remains'
    let base = $response.base_resp

    if ($base.status_code != 0) {
        error make {
            msg: $base.status_msg
            label: { text: $"MiniMax API error — check MINIMAX_API_KEY" }
        }
    }

    $response.model_remains
    | each { |row|
        let interval_pct = if $row.current_interval_total_count > 0 {
            $row.current_interval_usage_count * 100 // $row.current_interval_total_count
        } else { 0 }
        let weekly_pct = if $row.current_weekly_total_count > 0 {
            $row.current_weekly_usage_count * 100 // $row.current_weekly_total_count
        } else { 0 }
        let remains_h = $row.remains_time // 3600000
        let remains_m = ($row.remains_time mod 3600000) // 60000
        {
            model: $row.model_name
            interval: $"($row.current_interval_usage_count) / ($row.current_interval_total_count) (($interval_pct)%)"
            weekly: $"($row.current_weekly_usage_count) / ($row.current_weekly_total_count) (($weekly_pct)%)"
            resets_in: $"($remains_h)h ($remains_m)m"
        }
    }
}

# `minimax` — MiniMax AI: image generation, text-to-speech, voice list, and usage.
#
# # Subcommands
# `minimax usage` — check token quota and usage
# `minimax image` — generate images with image-01 model
# `minimax speech` — synthesize speech with speech-2.8-hd model
# `minimax voices` — list all 332 available system voices
export def main [] {
    print "MiniMax AI integration"
    print "  minimax usage   — check token quota and usage"
    print "  minimax image   — generate images (image-01 model)"
    print "  minimax speech  — synthesize speech (speech-2.8-hd model)"
    print "  minimax voices  — list all 332 system voices"
    print ""
    print "Run a subcommand with --help for details."
}

# ── Private helpers ─────────────────────────────────────────────────────────────

def get-token [] {
    let token = $env | get --optional MINIMAX_API_KEY
    if ($token | is-empty) {
        error make {
            msg: "MINIMAX_API_KEY is not set"
            label: { text: "Set MINIMAX_API_KEY in secrets.nuon" }
        }
    }
    $token
}

def check-base-resp [resp: record] {
    let code = $resp.base_resp.status_code
    if $code == 0 { return }
    let msg = $resp.base_resp.status_msg
    let human = {
        "1002": "Rate limit exceeded — pause before retrying"
        "1004": "Authentication failed — check MINIMAX_API_KEY"
        "1008": "Quota exhausted — check your plan limits"
        "1026": "Content blocked by policy — revise prompt"
        "2013": "Invalid parameters — check API call"
        "2049": "Bad API key — verify token in secrets.nuon"
        "2061": "Model not in plan — Plus-Highspeed includes image-01 and speech-2.8-hd"
    } | get --optional ($code | into string) | default $"MiniMax API error ($code): ($msg)"
    error make { msg: $human }
}

def prompt-view [path: string] {
    if (which wezterm | is-empty) {
        print $'(ansi y)  wezterm not found — install it to display images inline(ansi rst)'
        return
    }
    print $"  Display image? y/N: ($path)"
    let answer = input
    if $answer == "y" or $answer == "Y" {
        print $'(ansi d)  Running wezterm imgcat ($path)(ansi rst)'
        wezterm imgcat $path
    }
}

def prompt-play [path: string] {
    let player = if ((which mpv | is-empty) == false) { "mpv" } else if ((which paplay | is-empty) == false) { "paplay" } else if ((which aplay | is-empty) == false) { "aplay" } else { null }
    if ($player | is-empty) {
        print $'(ansi y)  No audio player found (tried mpv, paplay, aplay) — skipping playback(ansi rst)'
        return
    }
    print $"  Play audio? y/N: ($path)"
    let answer = input
    if $answer == "y" or $answer == "Y" {
        print $'(ansi d)  Playing with ($player) ($path)(ansi rst)'
        match $player {
            "mpv" => { mpv --no-video --quiet $path }
            "paplay" => { paplay $path }
            "aplay" => { aplay $path }
        }
    }
}

# ── Completions ─────────────────────────────────────────────────────────────────

def parse-voice-gender [voice_name: string] {
    let female_markers = ["girl", "lady", "woman", "queen", "sister", "mother", "wife", "aunt", "narrator", "host", "hostess", "antie", "belle", "maiden"]
    let male_markers = ["man", "boy", "father", "husband", "king", "brother", "uncle", "bloke", "guy", "dude", "fellow", "lad", "gentleman"]
    if ($female_markers | any { |m| $voice_name | str contains --ignore-case $m }) {
        "Female"
    } else if ($male_markers | any { |m| $voice_name | str contains --ignore-case $m }) {
        "Male"
    } else {
        "Neutral"
    }
}

def voice-id-completions [] {
    [
        { value: "English_Graceful_Lady", description: "Female · English · Graceful Lady" }
        { value: "English_expressive_narrator", description: "Female · English · Expressive Narrator" }
        { value: "English_CalmWoman", description: "Female · English · Calm Woman" }
        { value: "English_Upbeat_Woman", description: "Female · English · Upbeat Woman" }
        { value: "English_Trustworth_Man", description: "Male · English · Trustworthy Man" }
        { value: "English_Compelling_lady1", description: "Female · English · Compelling Lady" }
        { value: "English_captivating_female1", description: "Female · English · Captivating Female" }
        { value: "English_Aussie_Bloke", description: "Male · English · Aussie Bloke" }
        { value: "English_ConfidentWoman", description: "Female · English · Confident Woman" }
        { value: "English_SereneWoman", description: "Female · English · Serene Woman" }
        { value: "English_Soft-spokenGirl", description: "Female · English · Soft-spoken Girl" }
        { value: "English_PlayfulGirl", description: "Female · English · Playful Girl" }
        { value: "English_SentimentalLady", description: "Female · English · Sentimental Lady" }
        { value: "English_Wiselady", description: "Female · English · Wise Lady" }
        { value: "English_Kind-heartedGirl", description: "Female · English · Kind-hearted Girl" }
        { value: "English_LovelyGirl", description: "Female · English · Lovely Girl" }
        { value: "English_WhimsicalGirl", description: "Female · English · Whimsical Girl" }
        { value: "English_UpsetGirl", description: "Female · English · Upset Girl" }
        { value: "English_StressedLady", description: "Female · English · Stressed Lady" }
        { value: "English_AssertiveQueen", description: "Female · English · Assertive Queen" }
        { value: "English_magnetic_voiced_man", description: "Male · English · Magnetic-voiced Male" }
        { value: "English_Gentle-voiced_man", description: "Male · English · Gentle-voiced Man" }
        { value: "English_Diligent_Man", description: "Male · English · Diligent Man" }
        { value: "English_ManWithDeepVoice", description: "Male · English · Man With Deep Voice" }
        { value: "English_Deep-VoicedGentleman", description: "Male · English · Deep-voiced Gentleman" }
        { value: "English_ReservedYoungMan", description: "Male · English · Reserved Young Man" }
        { value: "English_DecentYoungMan", description: "Male · English · Decent Young Man" }
        { value: "English_PatientMan", description: "Male · English · Patient Man" }
        { value: "English_Jovialman", description: "Male · English · Jovial Man" }
        { value: "English_Strong-WilledBoy", description: "Male · English · Strong-willed Boy" }
        { value: "English_CaptivatingStoryteller", description: "Neutral · English · Captivating Storyteller" }
        { value: "English_WiseScholar", description: "Neutral · English · Wise Scholar" }
        { value: "English_PassionateWarrior", description: "Neutral · English · Passionate Warrior" }
        { value: "English_MaturePartner", description: "Neutral · English · Mature Partner" }
        { value: "English_FriendlyPerson", description: "Neutral · English · Friendly Guy" }
        { value: "English_MatureBoss", description: "Neutral · English · Bossy Lady" }
        { value: "English_Debator", description: "Neutral · English · Male Debater" }
        { value: "English_Steadymentor", description: "Neutral · English · Reliable Man" }
        { value: "English_ImposingManner", description: "Neutral · English · Imposing Queen" }
        { value: "English_SadTeen", description: "Neutral · English · Teen Boy" }
        { value: "English_Comedian", description: "Neutral · English · Comedian" }
        { value: "English_BossyLeader", description: "Neutral · English · Bossy Leader" }
        { value: "English_AnimeCharacter", description: "Female · English · Female Narrator" }
        { value: "English_radiant_girl", description: "Female · English · Radiant Girl" }
        { value: "Chinese (Mandarin)_News_Anchor", description: "Neutral · Chinese (Mandarin) · News Anchor" }
        { value: "Chinese (Mandarin)_Reliable_Executive", description: "Neutral · Chinese (Mandarin) · Reliable Executive" }
        { value: "Chinese (Mandarin)_Gentleman", description: "Male · Chinese (Mandarin) · Gentleman" }
        { value: "Chinese (Mandarin)_Sweet_Lady", description: "Female · Chinese (Mandarin) · Sweet Lady" }
        { value: "Chinese (Mandarin)_Mature_Woman", description: "Female · Chinese (Mandarin) · Mature Woman" }
        { value: "Chinese (Mandarin)_Kind-hearted_Antie", description: "Female · Chinese (Mandarin) · Kind-hearted Antie" }
        { value: "Chinese (Mandarin)_Warm-HeartedAunt", description: "Female · Chinese (Mandarin) · Warm-hearted Aunt" }
        { value: "Chinese (Mandarin)_Warm_Bestie", description: "Female · Chinese (Mandarin) · Warm Bestie" }
        { value: "Chinese (Mandarin)_Warm_Girl", description: "Female · Chinese (Mandarin) · Warm Girl" }
        { value: "Chinese (Mandarin)_Wise_Women", description: "Female · Chinese (Mandarin) · Wise Women" }
        { value: "Chinese (Mandarin)_Soft_Girl", description: "Female · Chinese (Mandarin) · Soft Girl" }
        { value: "Chinese (Mandarin)_Crisp_Girl", description: "Female · Chinese (Mandarin) · Crisp Girl" }
        { value: "Chinese (Mandarin)_BashfulGirl", description: "Female · Chinese (Mandarin) · Bashful Girl" }
        { value: "Chinese (Mandarin)_IntellectualGirl", description: "Female · Chinese (Mandarin) · Intellectual Girl" }
        { value: "Chinese (Mandarin)_Warm_HeartedGirl", description: "Female · Chinese (Mandarin) · Warm-hearted Girl" }
        { value: "Chinese (Mandarin)_Laid_BackGirl", description: "Female · Chinese (Mandarin) · Laid-back Girl" }
        { value: "Chinese (Mandarin)_ExplorativeGirl", description: "Female · Chinese (Mandarin) · Explorative Girl" }
        { value: "Chinese (Mandarin)_Arrogant_Miss", description: "Female · Chinese (Mandarin) · Arrogant Miss" }
        { value: "Chinese (Mandarin)_Unrestrained_Young_Man", description: "Male · Chinese (Mandarin) · Unrestrained Young Man" }
        { value: "Chinese (Mandarin)_Southern_Young_Man", description: "Male · Chinese (Mandarin) · Southern Young Man" }
        { value: "Chinese (Mandarin)_Male_Announcer", description: "Male · Chinese (Mandarin) · Male Announcer" }
        { value: "Chinese (Mandarin)_Straightforward_Boy", description: "Male · Chinese (Mandarin) · Straightforward Boy" }
        { value: "Chinese (Mandarin)_Pure-hearted_Boy", description: "Male · Chinese (Mandarin) · Pure-hearted Boy" }
        { value: "Chinese (Mandarin)_HK_Flight_Attendant", description: "Neutral · Chinese (Mandarin) · HK Flight Attendant" }
        { value: "Chinese (Mandarin)_Humorous_Elder", description: "Neutral · Chinese (Mandarin) · Humorous Elder" }
        { value: "Chinese (Mandarin)_Stubborn_Friend", description: "Neutral · Chinese (Mandarin) · Stubborn Friend" }
        { value: "Chinese (Mandarin)_Gentle_Youth", description: "Neutral · Chinese (Mandarin) · Gentle Youth" }
        { value: "Chinese (Mandarin)_Kind-hearted_Elder", description: "Neutral · Chinese (Mandarin) · Kind-hearted Elder" }
        { value: "Chinese (Mandarin)_Cute_Spirit", description: "Neutral · Chinese (Mandarin) · Cute Spirit" }
        { value: "Chinese (Mandarin)_Radio_Host", description: "Neutral · Chinese (Mandarin) · Radio Host" }
        { value: "Chinese (Mandarin)_Lyrical_Voice", description: "Neutral · Chinese (Mandarin) · Lyrical Voice" }
        { value: "Chinese (Mandarin)_Sincere_Adult", description: "Neutral · Chinese (Mandarin) · Sincere Adult" }
        { value: "Chinese (Mandarin)_Gentle_Senior", description: "Neutral · Chinese (Mandarin) · Gentle Senior" }
        { value: "Robot_Armor", description: "Neutral · Chinese (Mandarin) · Robot Armor" }
        { value: "Japanese_KindLady", description: "Female · Japanese · Kind Lady" }
        { value: "Japanese_CalmLady", description: "Female · Japanese · Calm Lady" }
        { value: "Japanese_DecisivePrincess", description: "Female · Japanese · Decisive Princess" }
        { value: "Japanese_DependableWoman", description: "Female · Japanese · Dependable Woman" }
        { value: "Japanese_GracefulMaiden", description: "Female · Japanese · Graceful Maiden" }
        { value: "Japanese_ColdQueen", description: "Female · Japanese · Cold Queen" }
        { value: "Japanese_LoyalKnight", description: "Male · Japanese · Loyal Knight" }
        { value: "Japanese_GentleButler", description: "Male · Japanese · Gentle Butler" }
        { value: "Japanese_DominantMan", description: "Male · Japanese · Dominant Man" }
        { value: "Japanese_InnocentBoy", description: "Male · Japanese · Innocent Boy" }
        { value: "Japanese_IntellectualSenior", description: "Neutral · Japanese · Intellectual Senior" }
        { value: "Japanese_SeriousCommander", description: "Neutral · Japanese · Serious Commander" }
        { value: "Japanese_OptimisticYouth", description: "Neutral · Japanese · Optimistic Youth" }
        { value: "Japanese_GenerousIzakayaOwner", description: "Neutral · Japanese · Generous Izakaya Owner" }
        { value: "Japanese_SportyStudent", description: "Neutral · Japanese · Sporty Student" }
        { value: "Korean_CalmLady", description: "Female · Korean · Calm Lady" }
        { value: "Korean_GentleWoman", description: "Female · Korean · Gentle Woman" }
        { value: "Korean_SoothingLady", description: "Female · Korean · Soothing Lady" }
        { value: "Korean_SweetGirl", description: "Female · Korean · Sweet Girl" }
        { value: "Korean_ShyGirl", description: "Female · Korean · Shy Girl" }
        { value: "Korean_SassyGirl", description: "Female · Korean · Sassy Girl" }
        { value: "Korean_QuirkyGirl", description: "Female · Korean · Quirky Girl" }
        { value: "Korean_ColdGirl", description: "Female · Korean · Cold Girl" }
        { value: "Korean_AirheadedGirl", description: "Female · Korean · Airheaded Girl" }
        { value: "Korean_AthleticGirl", description: "Female · Korean · Athletic Girl" }
        { value: "Korean_BraveFemaleWarrior", description: "Female · Korean · Brave Female Warrior" }
        { value: "Korean_CharmingElderSister", description: "Female · Korean · Charming Elder Sister" }
        { value: "Korean_CharmingSister", description: "Female · Korean · Charming Sister" }
        { value: "Korean_CheerfulLittleSister", description: "Female · Korean · Cheerful Little Sister" }
        { value: "Korean_ChildhoodFriendGirl", description: "Female · Korean · Childhood Friend Girl" }
        { value: "Korean_FriendlyBigSister", description: "Female · Korean · Friendly Big Sister" }
        { value: "Korean_HaughtyLady", description: "Female · Korean · Haughty Lady" }
        { value: "Korean_MysteriousGirl", description: "Female · Korean · Mysterious Girl" }
        { value: "Korean_CaringWoman", description: "Female · Korean · Caring Woman" }
        { value: "Korean_ThoughtfulWoman", description: "Female · Korean · Thoughtful Woman" }
        { value: "Korean_DecisiveQueen", description: "Female · Korean · Decisive Queen" }
        { value: "Korean_ElegantPrincess", description: "Female · Korean · Elegant Princess" }
        { value: "Korean_EncharmingSister", description: "Female · Korean · Enchanting Sister" }
        { value: "Korean_ReliableSister", description: "Female · Korean · Reliable Sister" }
        { value: "Korean_CalmGentleman", description: "Male · Korean · Calm Gentleman" }
        { value: "Korean_ColdYoungMan", description: "Male · Korean · Cold Young Man" }
        { value: "Korean_InnocentBoy", description: "Male · Korean · Innocent Boy" }
        { value: "Korean_IntellectualMan", description: "Male · Korean · Intellectual Man" }
        { value: "Korean_CockyGuy", description: "Male · Korean · Cocky Guy" }
        { value: "Korean_CheerfulBoyfriend", description: "Male · Korean · Cheerful Boyfriend" }
        { value: "Korean_PossessiveMan", description: "Male · Korean · Possessive Man" }
        { value: "Korean_DominantMan", description: "Male · Korean · Dominant Man" }
        { value: "Korean_AthleticStudent", description: "Neutral · Korean · Athletic Student" }
        { value: "Korean_BraveAdventurer", description: "Neutral · Korean · Brave Adventurer" }
        { value: "Korean_BraveYouth", description: "Neutral · Korean · Brave Youth" }
        { value: "Korean_CheerfulCoolJunior", description: "Neutral · Korean · Cheerful Cool Junior" }
        { value: "Korean_ConfidentBoss", description: "Neutral · Korean · Confident Boss" }
        { value: "Korean_ConsiderateSenior", description: "Neutral · Korean · Considerate Senior" }
        { value: "Korean_EnthusiasticTeen", description: "Neutral · Korean · Enthusiastic Teen" }
        { value: "Korean_GentleBoss", description: "Neutral · Korean · Gentle Boss" }
        { value: "Korean_IntellectualSenior", description: "Neutral · Korean · Intellectual Senior" }
        { value: "Korean_LonelyWarrior", description: "Neutral · Korean · Lonely Warrior" }
        { value: "Korean_OptimisticYouth", description: "Neutral · Korean · Optimistic Youth" }
        { value: "Korean_PlayboyCharmer", description: "Neutral · Korean · Playboy Charmer" }
        { value: "Korean_ReliableYouth", description: "Neutral · Korean · Reliable Youth" }
        { value: "Korean_StrictBoss", description: "Neutral · Korean · Strict Boss" }
        { value: "Korean_WiseElf", description: "Neutral · Korean · Wise Elf" }
        { value: "Korean_WiseTeacher", description: "Neutral · Korean · Wise Teacher" }
        { value: "Spanish_Narrator", description: "Female · Spanish · Narrator" }
        { value: "Spanish_SereneWoman", description: "Female · Spanish · Serene Woman" }
        { value: "Spanish_ConfidentWoman", description: "Female · Spanish · Confident Woman" }
        { value: "Spanish_Kind-heartedGirl", description: "Female · Spanish · Kind-hearted Girl" }
        { value: "Spanish_SophisticatedLady", description: "Female · Spanish · Sophisticated Lady" }
        { value: "Spanish_Wiselady", description: "Female · Spanish · Wise Lady" }
        { value: "Spanish_Fussyhostess", description: "Female · Spanish · Fussy Hostess" }
        { value: "Spanish_FrankLady", description: "Female · Spanish · Frank Lady" }
        { value: "Spanish_ThoughtfulLady", description: "Female · Spanish · Thoughtful Lady" }
        { value: "Spanish_ChattyGirl", description: "Female · Spanish · Chatty Girl" }
        { value: "Spanish_CompellingGirl", description: "Female · Spanish · Compelling Girl" }
        { value: "Spanish_CaringGirlfriend", description: "Female · Spanish · Caring Girlfriend" }
        { value: "Spanish_AssertiveQueen", description: "Female · Spanish · Assertive Queen" }
        { value: "Spanish_IntonateGirl", description: "Female · Spanish · Intonate Girl" }
        { value: "Spanish_WhimsicalGirl", description: "Female · Spanish · Whimsical Girl" }
        { value: "Spanish_ReservedYoungMan", description: "Male · Spanish · Reserved Young Man" }
        { value: "Spanish_ThoughtfulMan", description: "Male · Spanish · Thoughtful Man" }
        { value: "Spanish_Strong-WilledBoy", description: "Male · Spanish · Strong-willed Boy" }
        { value: "Spanish_RationalMan", description: "Male · Spanish · Rational Man" }
        { value: "Spanish_Deep-tonedMan", description: "Male · Spanish · Deep-toned Man" }
        { value: "Spanish_Jovialman", description: "Male · Spanish · Jovial Man" }
        { value: "Spanish_ReliableMan", description: "Male · Spanish · Reliable Man" }
        { value: "Spanish_AngryMan", description: "Male · Spanish · Angry Man" }
        { value: "Spanish_RomanticHusband", description: "Male · Spanish · Romantic Husband" }
        { value: "Spanish_EnergeticBoy", description: "Male · Spanish · Energetic Boy" }
        { value: "Spanish_SantaClaus", description: "Male · Spanish · Santa Claus" }
        { value: "Spanish_Rudolph", description: "Male · Spanish · Rudolph" }
        { value: "Spanish_Arnold", description: "Neutral · Spanish · Arnold" }
        { value: "Spanish_MaturePartner", description: "Neutral · Spanish · Mature Partner" }
        { value: "Spanish_CaptivatingStoryteller", description: "Neutral · Spanish · Captivating Storyteller" }
        { value: "Spanish_WiseScholar", description: "Neutral · Spanish · Wise Scholar" }
        { value: "Spanish_DeterminedManager", description: "Neutral · Spanish · Determined Manager" }
        { value: "Spanish_BossyLeader", description: "Neutral · Spanish · Bossy Leader" }
        { value: "Spanish_AnimeCharacter", description: "Neutral · Spanish · Anime Character" }
        { value: "Spanish_SincereTeen", description: "Neutral · Spanish · Sincere Teen" }
        { value: "Spanish_Comedian", description: "Neutral · Spanish · Comedian" }
        { value: "Spanish_Debator", description: "Neutral · Spanish · Debator" }
        { value: "Spanish_ToughBoss", description: "Neutral · Spanish · Tough Boss" }
        { value: "Spanish_Steadymentor", description: "Neutral · Spanish · Steady Mentor" }
        { value: "Spanish_Ghost", description: "Neutral · Spanish · Ghost" }
        { value: "Spanish_HumorousElder", description: "Neutral · Spanish · Humorous Elder" }
        { value: "Spanish_StrictBoss", description: "Neutral · Spanish · Strict Boss" }
        { value: "Spanish_SereneElder", description: "Neutral · Spanish · Serene Elder" }
        { value: "Spanish_PowerfulSoldier", description: "Neutral · Spanish · Powerful Soldier" }
        { value: "Spanish_PassionateWarrior", description: "Neutral · Spanish · Passionate Warrior" }
        { value: "Spanish_PowerfulVeteran", description: "Neutral · Spanish · Powerful Veteran" }
        { value: "Spanish_SensibleManager", description: "Neutral · Spanish · Sensible Manager" }
        { value: "Portuguese_Narrator", description: "Female · Portuguese · Narrator" }
        { value: "Portuguese_SentimentalLady", description: "Female · Portuguese · Sentimental Lady" }
        { value: "Portuguese_Wiselady", description: "Female · Portuguese · Wise Lady" }
        { value: "Portuguese_UpsetGirl", description: "Female · Portuguese · Upset Girl" }
        { value: "Portuguese_ConfidentWoman", description: "Female · Portuguese · Confident Woman" }
        { value: "Portuguese_SmartYoungGirl", description: "Female · Portuguese · Smart Young Girl" }
        { value: "Portuguese_Kind-heartedGirl", description: "Female · Portuguese · Kind-hearted Girl" }
        { value: "Portuguese_Pompouslady", description: "Female · Portuguese · Pompous Lady" }
        { value: "Portuguese_SweetGirl", description: "Female · Portuguese · Sweet Girl" }
        { value: "Portuguese_AttractiveGirl", description: "Female · Portuguese · Attractive Girl" }
        { value: "Portuguese_PlayfulGirl", description: "Female · Portuguese · Playful Girl" }
        { value: "Portuguese_GorgeousLady", description: "Female · Portuguese · Gorgeous Lady" }
        { value: "Portuguese_LovelyLady", description: "Female · Portuguese · Lovely Lady" }
        { value: "Portuguese_SereneWoman", description: "Female · Portuguese · Serene Woman" }
        { value: "Portuguese_NaughtySchoolgirl", description: "Female · Portuguese · Naughty Schoolgirl" }
        { value: "Portuguese_Fussyhostess", description: "Female · Portuguese · Fussy Hostess" }
        { value: "Portuguese_CharmingQueen", description: "Female · Portuguese · Charming Queen" }
        { value: "Portuguese_CharmingLady", description: "Female · Portuguese · Charming Lady" }
        { value: "Portuguese_WhimsicalGirl", description: "Female · Portuguese · Whimsical Girl" }
        { value: "Portuguese_StressedLady", description: "Female · Portuguese · Stressed Lady" }
        { value: "Portuguese_CaringGirlfriend", description: "Female · Portuguese · Caring Girlfriend" }
        { value: "Portuguese_InspiringLady", description: "Female · Portuguese · Inspiring Lady" }
        { value: "Portuguese_ElegantGirl", description: "Female · Portuguese · Elegant Girl" }
        { value: "Portuguese_CompellingGirl", description: "Female · Portuguese · Compelling Girl" }
        { value: "Portuguese_ThoughtfulLady", description: "Female · Portuguese · Thoughtful Lady" }
        { value: "Portuguese_FrankLady", description: "Female · Portuguese · Frank Lady" }
        { value: "Portuguese_Strong-WilledBoy", description: "Male · Portuguese · Strong-willed Boy" }
        { value: "Portuguese_Deep-VoicedGentleman", description: "Male · Portuguese · Deep-voiced Gentleman" }
        { value: "Portuguese_AngryMan", description: "Male · Portuguese · Angry Man" }
        { value: "Portuguese_Godfather", description: "Male · Portuguese · Godfather" }
        { value: "Portuguese_ReservedYoungMan", description: "Male · Portuguese · Reserved Young Man" }
        { value: "Portuguese_ThoughtfulMan", description: "Male · Portuguese · Thoughtful Man" }
        { value: "Portuguese_Jovialman", description: "Male · Portuguese · Jovial Man" }
        { value: "Portuguese_Grinch", description: "Male · Portuguese · Grinch" }
        { value: "Portuguese_EnergeticBoy", description: "Male · Portuguese · Energetic Boy" }
        { value: "Portuguese_ReliableMan", description: "Male · Portuguese · Reliable Man" }
        { value: "Portuguese_SantaClaus", description: "Male · Portuguese · Santa Claus" }
        { value: "Portuguese_Rudolph", description: "Male · Portuguese · Rudolph" }
        { value: "Portuguese_Arnold", description: "Neutral · Portuguese · Arnold" }
        { value: "Portuguese_CharmingSanta", description: "Male · Portuguese · Charming Santa" }
        { value: "Portuguese_FascinatingBoy", description: "Male · Portuguese · Fascinating Boy" }
        { value: "Portuguese_RomanticHusband", description: "Male · Portuguese · Romantic Husband" }
        { value: "Portuguese_FragileBoy", description: "Male · Portuguese · Fragile Boy" }
        { value: "Portuguese_RationalMan", description: "Male · Portuguese · Rational Man" }
        { value: "Portuguese_TheatricalActor", description: "Male · Portuguese · Theatrical Actor" }
        { value: "Portuguese_PassionateWarrior", description: "Neutral · Portuguese · Passionate Warrior" }
        { value: "Portuguese_AnimeCharacter", description: "Neutral · Portuguese · Anime Character" }
        { value: "Portuguese_CaptivatingStoryteller", description: "Neutral · Portuguese · Captivating Storyteller" }
        { value: "Portuguese_BossyLeader", description: "Neutral · Portuguese · Bossy Leader" }
        { value: "Portuguese_SadTeen", description: "Neutral · Portuguese · Sad Teen" }
        { value: "Portuguese_MaturePartner", description: "Neutral · Portuguese · Mature Partner" }
        { value: "Portuguese_Comedian", description: "Neutral · Portuguese · Comedian" }
        { value: "Portuguese_ToughBoss", description: "Neutral · Portuguese · Tough Boss" }
        { value: "Portuguese_Dramatist", description: "Neutral · Portuguese · Dramatist" }
        { value: "Portuguese_Steadymentor", description: "Neutral · Portuguese · Steady Mentor" }
        { value: "Portuguese_Ghost", description: "Neutral · Portuguese · Ghost" }
        { value: "Portuguese_HumorousElder", description: "Neutral · Portuguese · Humorous Elder" }
        { value: "Portuguese_CalmLeader", description: "Neutral · Portuguese · Calm Leader" }
        { value: "Portuguese_GentleTeacher", description: "Neutral · Portuguese · Gentle Teacher" }
        { value: "Portuguese_SereneElder", description: "Neutral · Portuguese · Serene Elder" }
        { value: "Portuguese_GrimReaper", description: "Neutral · Portuguese · Grim Reaper" }
        { value: "Portuguese_FriendlyNeighbor", description: "Neutral · Portuguese · Friendly Neighbor" }
        { value: "Portuguese_PowerfulSoldier", description: "Neutral · Portuguese · Powerful Soldier" }
        { value: "Portuguese_StrictBoss", description: "Neutral · Portuguese · Strict Boss" }
        { value: "Portuguese_PlayfulSpirit", description: "Neutral · Portuguese · Playful Spirit" }
        { value: "Portuguese_PowerfulVeteran", description: "Neutral · Portuguese · Powerful Veteran" }
        { value: "Portuguese_SensibleManager", description: "Neutral · Portuguese · Sensible Manager" }
        { value: "Portuguese_Debator", description: "Neutral · Portuguese · Debator" }
        { value: "Portuguese_Conscientiousinstructor", description: "Neutral · Portuguese · Conscientious Instructor" }
        { value: "Portuguese_WiseScholar", description: "Neutral · Portuguese · Wise Scholar" }
        { value: "Portuguese_DeterminedManager", description: "Neutral · Portuguese · Determined Manager" }
        { value: "French_Female_News_Anchor", description: "Female · French · Patient Female Presenter" }
        { value: "French_MovieLeadFemale", description: "Female · French · Movie Lead Female" }
        { value: "French_FemaleAnchor", description: "Female · French · Female Anchor" }
        { value: "French_Male_Speech_New", description: "Male · French · Level-headed Man" }
        { value: "French_CasualMan", description: "Male · French · Casual Man" }
        { value: "French_MaleNarrator", description: "Male · French · Male Narrator" }
        { value: "Indonesian_CalmWoman", description: "Female · Indonesian · Calm Woman" }
        { value: "Indonesian_SweetGirl", description: "Female · Indonesian · Sweet Girl" }
        { value: "Indonesian_CharmingGirl", description: "Female · Indonesian · Charming Girl" }
        { value: "Indonesian_ConfidentWoman", description: "Female · Indonesian · Confident Woman" }
        { value: "Indonesian_GentleGirl", description: "Female · Indonesian · Gentle Girl" }
        { value: "Indonesian_ReservedYoungMan", description: "Male · Indonesian · Reserved Young Man" }
        { value: "Indonesian_CaringMan", description: "Male · Indonesian · Caring Man" }
        { value: "Indonesian_DeterminedBoy", description: "Male · Indonesian · Determined Boy" }
        { value: "Indonesian_BossyLeader", description: "Neutral · Indonesian · Bossy Leader" }
        { value: "German_SweetLady", description: "Female · German · Sweet Lady" }
        { value: "German_FriendlyMan", description: "Male · German · Friendly Man" }
        { value: "German_PlayfulMan", description: "Male · German · Playful Man" }
        { value: "Russian_AmbitiousWoman", description: "Female · Russian · Ambitious Woman" }
        { value: "Russian_CrazyQueen", description: "Female · Russian · Crazy Girl" }
        { value: "Russian_PessimisticGirl", description: "Female · Russian · Pessimistic Girl" }
        { value: "Russian_ReliableMan", description: "Male · Russian · Reliable Man" }
        { value: "Russian_AttractiveGuy", description: "Male · Russian · Attractive Guy" }
        { value: "Russian_Bad-temperedBoy", description: "Male · Russian · Bad-tempered Boy" }
        { value: "Russian_HandsomeChildhoodFriend", description: "Neutral · Russian · Handsome Childhood Friend" }
        { value: "Russian_BrightHeroine", description: "Female · Russian · Bright Queen" }
        { value: "Italian_BraveHeroine", description: "Female · Italian · Brave Heroine" }
        { value: "Italian_WanderingSorcerer", description: "Male · Italian · Wandering Sorcerer" }
        { value: "Italian_Narrator", description: "Neutral · Italian · Narrator" }
        { value: "Italian_DiligentLeader", description: "Neutral · Italian · Diligent Leader" }
        { value: "Dutch_kindhearted_girl", description: "Female · Dutch · Kind-hearted Girl" }
        { value: "Dutch_bossy_leader", description: "Neutral · Dutch · Bossy Leader" }
        { value: "Vietnamese_kindhearted_girl", description: "Female · Vietnamese · Kind-hearted Girl" }
        { value: "Arabic_CalmWoman", description: "Female · Arabic · Calm Woman" }
        { value: "Arabic_FriendlyGuy", description: "Male · Arabic · Friendly Guy" }
        { value: "Turkish_CalmWoman", description: "Female · Turkish · Calm Woman" }
        { value: "Turkish_Trustworthyman", description: "Male · Turkish · Trustworthy Man" }
        { value: "Ukrainian_CalmWoman", description: "Female · Ukrainian · Calm Woman" }
        { value: "Ukrainian_WiseScholar", description: "Neutral · Ukrainian · Wise Scholar" }
        { value: "Thai_female_1_sample1", description: "Female · Thai · Confident Woman" }
        { value: "Thai_female_2_sample2", description: "Female · Thai · Energetic Woman" }
        { value: "Thai_male_1_sample8", description: "Male · Thai · Serene Man" }
        { value: "Thai_male_2_sample2", description: "Male · Thai · Friendly Man" }
        { value: "Polish_female_1_sample1", description: "Female · Polish · Calm Woman" }
        { value: "Polish_female_2_sample3", description: "Female · Polish · Casual Woman" }
        { value: "Polish_male_1_sample4", description: "Male · Polish · Male Narrator" }
        { value: "Polish_male_2_sample3", description: "Male · Polish · Male Anchor" }
        { value: "Romanian_female_1_sample4", description: "Female · Romanian · Optimistic Youth" }
        { value: "Romanian_female_2_sample1", description: "Female · Romanian · Gentle Woman" }
        { value: "Romanian_male_1_sample2", description: "Male · Romanian · Reliable Man" }
        { value: "Romanian_male_2_sample1", description: "Neutral · Romanian · Energetic Youth" }
        { value: "Greek_female_1_sample1", description: "Female · Greek · Gentle Lady" }
        { value: "Greek_female_2_sample3", description: "Female · Greek · Girl Next Door" }
        { value: "Greek_male_1a_v1", description: "Male · Greek · Thoughtful Mentor" }
        { value: "czech_female_5_v7", description: "Female · Czech · Steadfast Narrator" }
        { value: "czech_female_2_v2", description: "Female · Czech · Elegant Lady" }
        { value: "czech_male_1_v1", description: "Male · Czech · Assured Presenter" }
        { value: "finnish_female_4_v1", description: "Female · Finnish · Assertive Woman" }
        { value: "finnish_male_1_v2", description: "Male · Finnish · Friendly Boy" }
        { value: "finnish_male_3_v1", description: "Male · Finnish · Upbeat Man" }
        { value: "hindi_female_1_v2", description: "Female · Hindi · News Anchor" }
        { value: "hindi_female_2_v1", description: "Female · Hindi · Tranquil Woman" }
        { value: "hindi_male_1_v2", description: "Male · Hindi · Trustworthy Advisor" }
        { value: "Cantonese_GentleLady", description: "Female · Cantonese · Gentle Lady" }
        { value: "Cantonese_CuteGirl", description: "Female · Cantonese · Cute Girl" }
        { value: "Cantonese_KindWoman", description: "Female · Cantonese · Kind Woman" }
        { value: "Cantonese_PlayfulMan", description: "Male · Cantonese · Playful Man" }
        { value: "Cantonese_ProfessionalHost", description: "Neutral · Cantonese · Professional Host" }
    ]
}

def aspect-ratio-completions [] {
    [
        { value: "1:1", description: "Square (1:1) — 1024×1024" }
        { value: "16:9", description: "Landscape (16:9) — 1024×576" }
        { value: "9:16", description: "Portrait (9:16) — 576×1024" }
        { value: "4:3", description: "Standard (4:3) — 1024×768" }
        { value: "3:2", description: "Classic photo (3:2) — 1024×682" }
        { value: "2:3", description: "Portrait photo (2:3) — 682×1024" }
    ]
}

def speech-model-completions [] {
    [
        { value: "speech-2.8-hd", description: "2.8-hd — High quality, natural expressiveness (recommended)" }
        { value: "speech-2.8-turbo", description: "2.8-turbo — Faster synthesis, slightly lower quality" }
        { value: "speech-2.6-hd", description: "2.6-hd — Legacy HD model (may not be in plan)" }
        { value: "speech-2.6-turbo", description: "2.6-turbo — Legacy fast model (may not be in plan)" }
        { value: "speech-02-hd", description: "02-hd — Older HD model (may not be in plan)" }
        { value: "speech-01-turbo", description: "01-turbo — Older fast model (may not be in plan)" }
    ]
}

# ── Subcommands ─────────────────────────────────────────────────────────────────

# `minimax voices` — list all available MiniMax TTS system voices.
#
# Voices are grouped by language. Each voice has a unique ID (use with --voice),
# plus gender and description.
#
# # Example
# minimax voices          # list all voices
# minimax speech --voice male-qn-johnny "Hello"  # use a specific voice
export def voices [] {
    let voices = voice-id-completions
    let by_lang = $voices
        | group-by { |v|
            let parsed = ($v.description | parse "{gender} · {lang} ·{rest}")
            if ($parsed | is-empty) { "Other" } else { $parsed | get 0 | get --optional lang | default "Other" }
        }

    print "MiniMax system voices — 332 voices across 15 languages"
    print ""

    for lang_name in ["Chinese (Mandarin)", "Cantonese", "English", "Japanese", "Korean", "Spanish", "Portuguese", "French", "Indonesian", "German", "Russian", "Italian", "Dutch", "Vietnamese", "Arabic", "Turkish", "Ukrainian", "Thai", "Polish", "Romanian", "Greek", "Czech", "Finnish", "Hindi", "Other"] {
        if ($by_lang | get --optional $lang_name | is-empty) { continue }
        let lang_voices = $by_lang | get $lang_name
        let count = ($lang_voices | length)
        print $'(ansi cb)◆ ($lang_name)(ansi rst) — ($count) voices'
        $lang_voices
        | each { |v|
            print $'  (ansi d)($v.value)(ansi rst)  ($v.description)'
        }
        print ""
    }
}

# `minimax image` — generate an image with MiniMax image-01 model.
#
# # Parameters
# `prompt` # The text description of the image to generate (required, non-empty)
# `--aspect` # Aspect ratio: 1:1 (default), 16:9, 9:16, 4:3, 3:2, 2:3
# `--model` # Model to use: image-01 (default) or image-01-pro
# `--seed` # Random seed for reproducible results (optional)
# `--n` # Number of images to generate (1-4, default 1)
# `--name` # Output filename without extension (default: minimax_<timestamp>)
# `--dir` # Output directory (default: current directory)
# `--no-view` # Skip the terminal display prompt (image is still saved)
export def image [
    # The text description of the image to generate.
    prompt: string
    # Aspect ratio of the output image.
    --aspect (-a): string@aspect-ratio-completions = "1:1"
    # Image model to use.
    --model (-m): string = "image-01"
    # Random seed for reproducible results (0 = random).
    --seed (-s): int = 0
    # Number of images to generate (1–4).
    --n (-n): int = 1
    # Output filename without extension (timestamp is added).
    --name (-N): string = ""
    # Output directory.
    --dir (-d): string = "."
    # Skip the terminal display prompt.
    --no-view (-V)
] {
    if ($prompt | str trim | is-empty) {
        error make {
            msg: "Prompt cannot be empty"
            label: { text: "Provide a non-empty prompt string" }
        }
    }
    if $n < 1 or $n > 4 {
        error make {
            msg: $"n must be 1–4, got ($n)"
            label: { text: "Use --n with a value between 1 and 4" }
        }
    }

    let token = get-token
    let out_dir = if ($dir | path expand) == "." { $env.PWD } else { $dir | path expand }
    if not (($out_dir | path type) == "dir") {
        error make {
            msg: $"Output directory does not exist: ($out_dir)"
            label: { text: "Use an existing directory, or omit --dir" }
        }
    }

    let timestamp = (date now | format date "%Y%m%d_%H%M%S")
    let base_name = if $name == "" { $"minimax_($timestamp)" } else { $name }

    let seed_val = if $seed == 0 { null } else { $seed }
    let payload = {
        model: $model
        prompt: $prompt
        aspect_ratio: $aspect
    }
    let payload = if $seed != 0 { $payload | merge { seed: $seed } } else { $payload }
    let payload = $payload | merge { n: $n }

    let headers = {
        Authorization: $'Bearer ($token)'
        Content-Type: "application/json"
    }

    print $'(ansi d)Generating ($n) images with ($model) at ($aspect)…(ansi rst)'
    let resp = http post --headers $headers $"https://api.minimax.io/v1/image_generation" ($payload | to json)

    if ($resp.base_resp.status_code != 0) {
        check-base-resp $resp
    }

    if not ($resp.data | columns | any { |c| $c == "image_urls" }) {
        error make {
            msg: "No image URLs in API response — content may have been blocked"
            label: { text: "Try a different prompt; NSFW content is not allowed" }
        }
    }

    let image_urls = $resp.data.image_urls
    let items = $image_urls | enumerate | each { |pair|
        let idx = $pair.index
        let img_url = $pair.item
        let parsed_ext = ($img_url | parse --regex "\\.(\\w+)\\?")
        let ext = if ($parsed_ext | is-empty) { "png" } else { ($parsed_ext | get 0 | get capture0) }
        let filename = if $n > 1 { $"($base_name)_($idx).($ext)" } else { $"($base_name).($ext)" }
        let out_path = $out_dir | path join $filename

        print $'(ansi d)Downloading ($filename)…(ansi rst)'
        let download_err = (try {
            curl -L -s -o $out_path $img_url
            null
        } catch { |e| $e.raw })
        if $download_err != null {
            error make {
                msg: $"Failed to download image: ($download_err)"
                label: { text: "Check network and image URL" }
            }
        }

        {
            index: $idx
            filename: $filename
            path: $out_path
            url: $img_url
        }
    }

    print ""
    print $'(ansi gb)◆ Images saved(ansi rst)'
    $items | table -e

    if not $no_view {
        for item in $items {
            prompt-view $item.path
        }
    }
}

# `minimax speech` — synthesize text to speech with MiniMax speech-2.8-hd model.
#
# # Parameters
# `text` # The text to synthesize (required, non-empty)
# `--voice` # Voice ID from `minimax voices` (default: English_Graceful_Lady)
# `--model` # Speech model: speech-2.8-hd (default, recommended) or speech-2.8-turbo
# `--speed` # Speech speed as a multiplier (0.5–2.0, default 1.0)
# `--vol` # Volume as a multiplier (0.5–2.0, default 1.0)
# `--pitch` # Pitch adjustment in semitones (-12 to 12, default 0)
# `--name` # Output filename without extension (default: minimax_<timestamp>)
# `--dir` # Output directory (default: current directory)
# `--no-play` # Skip the audio playback prompt (file is still saved)
export def speech [
    # The text to synthesize into speech.
    text: string
    # Voice ID — run `minimax voices` for the full list.
    --voice (-v): string@voice-id-completions = "English_Graceful_Lady"
    # Speech synthesis model.
    --model (-m): string@speech-model-completions = "speech-2.8-hd"
    # Speech speed as a multiplier (0.5–2.0).
    --speed: float = 1.0
    # Volume as a multiplier (0.5–2.0).
    --vol: float = 1.0
    # Pitch adjustment in semitones (-12 to 12).
    --pitch: int = 0
    # Output filename without extension (timestamp is added).
    --name (-N): string = ""
    # Output directory.
    --dir (-d): string = "."
    # Skip the audio playback prompt.
    --no-play (-P)
] {
    if ($text | str trim | is-empty) {
        error make {
            msg: "Text to synthesize cannot be empty"
            label: { text: "Provide a non-empty text string" }
        }
    }
    if $speed < 0.5 or $speed > 2.0 {
        error make {
            msg: $"speed must be 0.5–2.0, got ($speed)"
            label: { text: "Use --speed with a value between 0.5 and 2.0" }
        }
    }
    if $vol < 0.5 or $vol > 2.0 {
        error make {
            msg: $"vol must be 0.5–2.0, got ($vol)"
            label: { text: "Use --vol with a value between 0.5 and 2.0" }
        }
    }
    if $pitch < -12 or $pitch > 12 {
        error make {
            msg: $"pitch must be -12 to 12, got ($pitch)"
            label: { text: "Use --pitch with a value between -12 and 12" }
        }
    }

    let token = get-token
    let out_dir = if ($dir | path expand) == "." { $env.PWD } else { $dir | path expand }
    if not (($out_dir | path type) == "dir") {
        error make {
            msg: $"Output directory does not exist: ($out_dir)"
            label: { text: "Use an existing directory, or omit --dir" }
        }
    }

    let timestamp = (date now | format date "%Y%m%d_%H%M%S")
    let base_name = if $name == "" { $"minimax_($timestamp)" } else { $name }
    let filename = $"($base_name).mp3"
    let out_path = $out_dir | path join $filename

    let payload = {
        model: $model
        text: $text
        voice_setting: { voice_id: $voice }
        audio_setting: {
            sample_rate: 32000
            speed: $speed
            vol: $vol
            pitch: $pitch
        }
    }

    let headers = {
        Authorization: $'Bearer ($token)'
        Content-Type: "application/json"
    }

    print $'(ansi d)Synthesizing speech with ($voice) at ($model)…(ansi rst)'
    let resp = http post --headers $headers $"https://api.minimax.io/v1/t2a_v2" ($payload | to json)

    if ($resp.base_resp.status_code != 0) {
        check-base-resp $resp
    }

    let audio_hex = $resp.data.audio
    if ($audio_hex | is-empty) {
        error make {
            msg: "No audio data in API response"
            label: { text: "Check API response or try again" }
        }
    }

    print $'(ansi d)Decoding and saving audio…(ansi rst)'
    let decode_err = (try {
        $audio_hex | decode hex | save -f $out_path
        null
    } catch { |e| $e.raw })
    if $decode_err != null {
        error make {
            msg: $"Failed to decode and save audio: ($decode_err)"
            label: { text: "Check audio data integrity" }
        }
    }

    let chars = $resp.extra_info.usage_characters
    print ""
    print $'(ansi gb)? Audio saved(ansi rst)'
    print $'(ansi d)  File: ($out_path)(ansi rst)'
    if not ($chars | is-empty) {
        print $'(ansi d)  Characters synthesised: ($chars)(ansi rst)'
    }

    if not $no_play {
        prompt-play $out_path
    }
}