aprender-orchestrate 0.31.2

Sovereign AI orchestration: autonomous agents, ML serving, code analysis, and transpilation pipelines
Documentation
//! Pacha Command Handlers
//!
//! Implementations for list, show, search, aliases, stats, prune, and pin/unpin commands.

use crate::ansi_colors::Colorize;
use std::io::{self, Write};

use super::helpers::{
    format_size, get_cached_models, print_command_header, print_success, resolve_model_ref,
};

// ============================================================================
// PACHA-CLI-004: List Command
// ============================================================================

/// List cached models
pub fn cmd_list(verbose: bool, format: &str) -> anyhow::Result<()> {
    print_command_header("Cached Models");

    // Get cached models (simulation)
    let models = get_cached_models();

    if models.is_empty() {
        println!("{}", "No models cached.".dimmed());
        println!();
        println!("Pull a model with: {}", "batuta pacha pull llama3".cyan());
        return Ok(());
    }

    if format == "json" {
        // JSON output
        let json = serde_json::json!({
            "models": models.iter().map(|m| {
                serde_json::json!({
                    "name": m.0,
                    "size": m.1,
                    "modified": m.2
                })
            }).collect::<Vec<_>>()
        });
        println!("{}", serde_json::to_string_pretty(&json)?);
    } else {
        // Table output
        println!("{:<30} {:>12} {:>20}", "NAME".dimmed(), "SIZE".dimmed(), "MODIFIED".dimmed());
        println!("{}", "-".repeat(62).dimmed());

        for (name, size, modified) in &models {
            let size_str = format_size(*size);
            println!("{:<30} {:>12} {:>20}", name.cyan(), size_str, modified.dimmed());
        }
    }

    if verbose {
        println!();
        println!("{}", "-".repeat(62).dimmed());
        let total_size: u64 = models.iter().map(|m| m.1).sum();
        println!(
            "Total: {} models, {}",
            models.len().to_string().cyan(),
            format_size(total_size).yellow()
        );
    }

    Ok(())
}

// ============================================================================
// PACHA-CLI-005: Remove Command
// ============================================================================

/// Remove a model from cache
pub fn cmd_rm(model: &str, all: bool, yes: bool) -> anyhow::Result<()> {
    print_command_header("Remove Model");

    println!("Model: {}", model.cyan());
    if all {
        println!("Mode:  {}", "Remove all versions".yellow());
    }
    println!();

    // Confirm unless -y flag
    if !yes {
        print!("Are you sure? [y/N] ");
        io::stdout().flush()?;

        let mut input = String::new();
        io::stdin().read_line(&mut input)?;

        if !input.trim().eq_ignore_ascii_case("y") {
            println!("{}", "Cancelled.".dimmed());
            return Ok(());
        }
    }

    // Simulate removal
    println!("{}", "Removing...".dimmed());
    std::thread::sleep(std::time::Duration::from_millis(200));

    print_success(format!("Model removed: {}", model.cyan()));

    Ok(())
}

// ============================================================================
// PACHA-CLI-006: Show Command
// ============================================================================

/// Show model information
pub fn cmd_show(model: &str, full: bool) -> anyhow::Result<()> {
    print_command_header("Model Information");

    // Resolve and show info
    let resolved = resolve_model_ref(model, None)?;

    println!("Name:         {}", model.cyan());
    println!("Resolved:     {}", resolved);
    println!("Format:       {}", "GGUF".yellow());
    println!("Quantization: {}", "Q4_K_M".yellow());
    println!("Size:         {}", "4.37 GB".yellow());
    println!("Parameters:   {}", "8B".yellow());
    println!();

    println!("{}", "Architecture".bright_white().bold());
    println!("  Type:       LlamaForCausalLM");
    println!("  Context:    8192 tokens");
    println!("  Embedding:  4096");
    println!("  Layers:     32");
    println!("  Heads:      32");
    println!("  Vocab:      32000");

    if full {
        println!();
        println!("{}", "Tensors (first 10)".bright_white().bold());
        let tensors = [
            ("token_embd.weight", "[32000, 4096]", "Q4_K"),
            ("blk.0.attn_q.weight", "[4096, 4096]", "Q4_K"),
            ("blk.0.attn_k.weight", "[4096, 4096]", "Q4_K"),
            ("blk.0.attn_v.weight", "[4096, 4096]", "Q4_K"),
            ("blk.0.attn_output.weight", "[4096, 4096]", "Q4_K"),
        ];
        for (name, shape, quant) in &tensors {
            println!("  {} {} {}", name, shape.dimmed(), quant.dimmed());
        }
        println!("  ...");
    }

    Ok(())
}

// ============================================================================
// PACHA-CLI-007: Search Command
// ============================================================================

/// Search for models
pub fn cmd_search(query: &str, limit: usize) -> anyhow::Result<()> {
    print_command_header("Model Search");

    println!("Query: {}", query.cyan());
    println!("Limit: {}", limit);
    println!();

    // Simulate search results
    let results = [
        ("llama3", "Meta Llama 3 8B Instruct", "8B params"),
        ("llama3:70b", "Meta Llama 3 70B Instruct", "70B params"),
        ("mistral", "Mistral 7B Instruct v0.2", "7B params"),
        ("mixtral", "Mixtral 8x7B Instruct", "46.7B params"),
        ("phi3", "Microsoft Phi-3 Mini", "3.8B params"),
    ];

    let filtered: Vec<_> = results
        .iter()
        .filter(|(name, desc, _)| {
            name.to_lowercase().contains(&query.to_lowercase())
                || desc.to_lowercase().contains(&query.to_lowercase())
        })
        .take(limit)
        .collect();

    if filtered.is_empty() {
        println!("{}", "No results found.".dimmed());
        return Ok(());
    }

    println!("{:<20} {:<35} {:>12}", "NAME".dimmed(), "DESCRIPTION".dimmed(), "SIZE".dimmed());
    println!("{}", "-".repeat(70).dimmed());

    for (name, desc, size) in filtered {
        println!("{:<20} {:<35} {:>12}", name.cyan(), desc, size.dimmed());
    }

    println!();
    println!("Pull with: {}", format!("batuta pacha pull {}", query).cyan());

    Ok(())
}

// ============================================================================
// PACHA-CLI-008: Aliases Command
// ============================================================================

/// Show available aliases
pub fn cmd_aliases(pattern: Option<&str>) -> anyhow::Result<()> {
    print_command_header("Model Aliases");

    let aliases = [
        ("llama3", "hf://meta-llama/Meta-Llama-3-8B-Instruct"),
        ("llama3:70b", "hf://meta-llama/Meta-Llama-3-70B-Instruct"),
        ("llama3.1", "hf://meta-llama/Llama-3.1-8B-Instruct"),
        ("mistral", "hf://mistralai/Mistral-7B-Instruct-v0.2"),
        ("mixtral", "hf://mistralai/Mixtral-8x7B-Instruct-v0.1"),
        ("phi3", "hf://microsoft/Phi-3-mini-4k-instruct"),
        ("gemma", "hf://google/gemma-7b-it"),
        ("qwen2", "hf://Qwen/Qwen2-7B-Instruct"),
        ("codellama", "hf://codellama/CodeLlama-7b-Instruct-hf"),
    ];

    let filtered: Vec<_> = if let Some(p) = pattern {
        aliases.iter().filter(|(name, _)| name.contains(p)).collect()
    } else {
        aliases.iter().collect()
    };

    println!("{:<15} {}", "ALIAS".dimmed(), "TARGET".dimmed());
    println!("{}", "-".repeat(70).dimmed());

    for (alias, target) in filtered {
        println!("{:<15} {}", alias.cyan(), target);
    }

    println!();
    println!("Add custom: {}", "batuta pacha alias mymodel hf://org/model".cyan());

    Ok(())
}

// ============================================================================
// PACHA-CLI-009: Alias Command (Add)
// ============================================================================

/// Add a custom alias
pub fn cmd_alias(name: &str, target: &str) -> anyhow::Result<()> {
    print_command_header("Add Alias");

    println!("Alias:  {}", name.cyan());
    println!("Target: {}", target);
    println!();

    // Validate target
    if !target.starts_with("hf://")
        && !target.starts_with("pacha://")
        && !target.starts_with("file://")
    {
        println!("{} Target should start with hf://, pacha://, or file://", "Warning:".yellow());
    }

    print_success(format!("Alias added: {} -> {}", name.cyan(), target));

    Ok(())
}

// ============================================================================
// PACHA-CLI-010: Stats Command
// ============================================================================

/// Show cache statistics
pub fn cmd_stats() -> anyhow::Result<()> {
    print_command_header("Cache Statistics");

    // Simulated stats
    println!("{}", "Storage".bright_white().bold());
    println!("  Total Size:     {}", "23.5 GB".yellow());
    println!("  Max Size:       {}", "50.0 GB".dimmed());
    println!("  Usage:          {}", "47%".yellow());
    println!("  Available:      {}", "26.5 GB".dimmed());
    println!();

    println!("{}", "Models".bright_white().bold());
    println!("  Cached:         {}", "5".cyan());
    println!("  Pinned:         {}", "2".cyan());
    println!();

    println!("{}", "Performance".bright_white().bold());
    println!("  Cache Hits:     {}", "127".cyan());
    println!("  Cache Misses:   {}", "12".dimmed());
    println!("  Hit Rate:       {}", "91.4%".bright_green());
    println!();

    println!("{}", "Age".bright_white().bold());
    println!("  Oldest Entry:   {}", "14 days ago".dimmed());
    println!("  Most Accessed:  {}", "llama3:8b".cyan());

    Ok(())
}

// ============================================================================
// PACHA-CLI-011: Prune Command
// ============================================================================

/// Clean up old/unused models
pub fn cmd_prune(days: u64, dry_run: bool) -> anyhow::Result<()> {
    print_command_header("Prune Cache");

    println!("Max Age:  {} days", days);
    if dry_run {
        println!("Mode:     {}", "Dry run (no changes)".yellow());
    }
    println!();

    // Simulated entries to prune
    let to_prune = [("mistral:7b-q4", "2.1 GB", "45 days ago"), ("phi2", "1.8 GB", "38 days ago")];

    if to_prune.is_empty() {
        println!("{}", "No models to prune.".dimmed());
        return Ok(());
    }

    println!("{:<25} {:>10} {:>15}", "MODEL".dimmed(), "SIZE".dimmed(), "LAST ACCESS".dimmed());
    println!("{}", "-".repeat(50).dimmed());

    let mut total_size = 0u64;
    for (name, size, last_access) in &to_prune {
        println!("{:<25} {:>10} {:>15}", name.red(), size, last_access.dimmed());
        // Parse size for total
        if size.ends_with(" GB") {
            if let Ok(gb) = size.trim_end_matches(" GB").parse::<f64>() {
                total_size += (gb * 1024.0 * 1024.0 * 1024.0) as u64;
            }
        }
    }

    println!();
    println!("Would free: {}", format_size(total_size).yellow());

    if !dry_run {
        println!();
        print_success(format!("Pruned {} models", to_prune.len()));
    }

    Ok(())
}

// ============================================================================
// PACHA-CLI-012: Pin/Unpin Commands
// ============================================================================

/// Pin a model (prevent eviction)
pub fn cmd_pin(model: &str) -> anyhow::Result<()> {
    print_command_header("Pin Model");

    println!("Model: {}", model.cyan());
    println!();
    print_success("Model pinned (won't be evicted)");

    Ok(())
}

/// Unpin a model
pub fn cmd_unpin(model: &str) -> anyhow::Result<()> {
    print_command_header("Unpin Model");

    println!("Model: {}", model.cyan());
    println!();
    print_success("Model unpinned");

    Ok(())
}