mold-ai 0.13.1

Local AI image generation CLI — FLUX, SDXL, SD3.5, Z-Image diffusion models on your GPU
use anyhow::Result;
use colored::Colorize;
use mold_core::types::GpuWorkerState;

use crate::control::CliContext;
use crate::procinfo;
use crate::theme;

pub async fn run() -> Result<()> {
    let ctx = CliContext::new(None);

    match ctx.client().server_status().await {
        Ok(status) => {
            println!("{} mold server v{}", theme::icon_ok(), status.version);
            println!("{} Uptime: {}s", theme::icon_ok(), status.uptime_secs,);

            // Multi-GPU display: show per-GPU status when available.
            if let Some(gpus) = &status.gpus {
                println!();
                for gpu in gpus {
                    let model = gpu.loaded_model.as_deref().unwrap_or("(none)");
                    let state_str = match gpu.state {
                        GpuWorkerState::Generating => "[generating]".yellow().to_string(),
                        GpuWorkerState::Idle => "[idle]".dimmed().to_string(),
                        GpuWorkerState::Loading => "[loading]".cyan().to_string(),
                        GpuWorkerState::Degraded => "[degraded]".red().to_string(),
                    };
                    let vram_used_gb = gpu.vram_used_bytes as f64 / 1_073_741_824.0;
                    let vram_total_gb = gpu.vram_total_bytes as f64 / 1_073_741_824.0;
                    println!(
                        "GPU {} ({}, {:.0}GB):  {:<20} {}  VRAM: {:.1}/{:.1} GB",
                        gpu.ordinal,
                        gpu.name,
                        vram_total_gb,
                        model.green(),
                        state_str,
                        vram_used_gb,
                        vram_total_gb,
                    );
                }
                if let (Some(depth), Some(capacity)) = (status.queue_depth, status.queue_capacity) {
                    println!("Queue: {}/{}", depth, capacity);
                }
            } else {
                // Single-GPU fallback display.
                if let Some(gpu) = &status.gpu_info {
                    println!(
                        "{} GPU: {} ({}/{} MB VRAM)",
                        theme::icon_ok(),
                        gpu.name,
                        gpu.vram_used_mb,
                        gpu.vram_total_mb,
                    );
                } else {
                    println!("{} GPU: {}", theme::icon_ok(), "not detected".dimmed());
                }

                println!(
                    "{} Busy: {}",
                    theme::icon_ok(),
                    if status.busy {
                        "yes".yellow()
                    } else {
                        "no".dimmed()
                    }
                );
            }

            if let Some(job) = &status.current_generation {
                println!("{} Active model: {}", theme::icon_ok(), job.model);
                println!(
                    "{} Active prompt SHA-256: {}",
                    theme::icon_ok(),
                    job.prompt_sha256.dimmed()
                );
                println!(
                    "{} Active for: {:.1}s",
                    theme::icon_ok(),
                    job.elapsed_ms as f64 / 1000.0
                );
            }

            println!();
            if status.models_loaded.is_empty() {
                println!("{}", "No models loaded.".dimmed());
            } else {
                println!("{}", "Loaded models:".bold());
                for model in &status.models_loaded {
                    println!("  - {}", model.green());
                }
            }
        }
        Err(_) => {
            println!(
                "  {} no mold server running — start with {}",
                theme::prefix_hint(),
                "mold serve".bold()
            );
        }
    }

    // Always scan for running mold processes.
    let procs = procinfo::find_mold_processes();
    if !procs.is_empty() {
        println!();
        println!("{}", "Running mold processes:".bold());
        for p in &procs {
            let args_display = if p.args.is_empty() {
                String::new()
            } else {
                let joined = p.args.join(" ");
                if joined.chars().count() > 60 {
                    let truncated: String = joined.chars().take(57).collect();
                    format!(" {truncated}...")
                } else {
                    format!(" {joined}")
                }
            };
            let threads = if p.thread_count > 1 {
                format!(", {} threads", p.thread_count)
            } else {
                String::new()
            };
            println!(
                "  {} {} {}{} {} ({}{})",
                theme::icon_bullet(),
                format!("[{}]", p.pid).dimmed(),
                p.subcommand.green(),
                args_display.dimmed(),
                procinfo::format_duration(p.run_time_secs).dimmed(),
                procinfo::format_memory_mb(p.memory_bytes).dimmed(),
                threads.dimmed(),
            );
        }
    }

    Ok(())
}