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,
};
pub fn cmd_list(verbose: bool, format: &str) -> anyhow::Result<()> {
print_command_header("Cached Models");
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" {
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 {
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(())
}
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!();
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(());
}
}
println!("{}", "Removing...".dimmed());
std::thread::sleep(std::time::Duration::from_millis(200));
print_success(format!("Model removed: {}", model.cyan()));
Ok(())
}
pub fn cmd_show(model: &str, full: bool) -> anyhow::Result<()> {
print_command_header("Model Information");
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(())
}
pub fn cmd_search(query: &str, limit: usize) -> anyhow::Result<()> {
print_command_header("Model Search");
println!("Query: {}", query.cyan());
println!("Limit: {}", limit);
println!();
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(())
}
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(())
}
pub fn cmd_alias(name: &str, target: &str) -> anyhow::Result<()> {
print_command_header("Add Alias");
println!("Alias: {}", name.cyan());
println!("Target: {}", target);
println!();
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(())
}
pub fn cmd_stats() -> anyhow::Result<()> {
print_command_header("Cache Statistics");
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(())
}
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!();
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());
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(())
}
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(())
}
pub fn cmd_unpin(model: &str) -> anyhow::Result<()> {
print_command_header("Unpin Model");
println!("Model: {}", model.cyan());
println!();
print_success("Model unpinned");
Ok(())
}