use clap::{Args, Subcommand};
use std::process::Command;
#[derive(Args)]
pub struct ListArgs {
#[command(subcommand)]
pub target: ListTarget,
}
#[derive(Subcommand)]
pub enum ListTarget {
Benchmarks,
Agents,
Models,
Evals {
#[arg(long)]
benchmark: Option<String>,
#[arg(long)]
agent: Option<String>,
},
}
pub fn execute(registry: &str, args: ListArgs) -> Result<(), String> {
match args.target {
ListTarget::Benchmarks => {
let images = get_images(registry, "benchmarks")?;
if images.is_empty() {
eprintln!("no benchmark images found");
return Ok(());
}
println!(
"{:<30} {:<50} {:<6} {:<10} INTERNET",
"IMAGE", "DESCRIPTION", "TASKS", "TYPE"
);
println!("{}", "-".repeat(110));
for image in &images {
let desc = get_label(image, "eval.benchmark.description");
let tasks = get_label(image, "eval.benchmark.tasks");
let env = get_label(image, "eval.benchmark.env");
let internet = get_label(image, "eval.benchmark.internet");
println!("{image:<30} {desc:<50} {tasks:<6} {env:<10} {internet}");
}
Ok(())
}
ListTarget::Agents => {
let images = get_images(registry, "agents")?;
if images.is_empty() {
eprintln!("no agent images found");
return Ok(());
}
println!("{:<30} {:<40} RUNTIME", "IMAGE", "DESCRIPTION");
println!("{}", "-".repeat(80));
for image in &images {
let desc = get_label(image, "eval.agent.description");
let runtime = get_label(image, "eval.agent.runtime");
println!("{image:<30} {desc:<40} {runtime}");
}
Ok(())
}
ListTarget::Models => {
let images = get_images(registry, "models")?;
if images.is_empty() {
eprintln!("no model images found");
return Ok(());
}
println!("{:<30} PROVIDER", "IMAGE");
println!("{}", "-".repeat(45));
for image in &images {
let provider = get_label(image, "eval.model.provider");
println!("{image:<30} {provider}");
}
Ok(())
}
ListTarget::Evals { benchmark, agent } => {
let filter = match (benchmark, agent) {
(Some(b), Some(a)) => format!("{b}--{a}"),
(Some(b), None) => format!("{b}--"),
(None, Some(a)) => format!("--{a}"),
(None, None) => String::new(),
};
let reference = format!("{registry}/evals/*{filter}*");
let fmt = "{{.Repository}}:{{.Tag}}";
eprintln!("$ docker images --format '{fmt}' {reference}");
let output = Command::new("docker")
.args(["images", "--format", fmt, &reference])
.output()
.map_err(|e| format!("failed to run docker: {e}"))?;
print!("{}", String::from_utf8_lossy(&output.stdout));
Ok(())
}
}
}
fn get_images(registry: &str, category: &str) -> Result<Vec<String>, String> {
let reference = format!("{registry}/{category}/*");
let fmt = "{{.Repository}}:{{.Tag}}";
eprintln!("$ docker images --format '{fmt}' {reference}");
let output = Command::new("docker")
.args(["images", "--format", fmt, &reference])
.output()
.map_err(|e| format!("failed to run docker: {e}"))?;
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(stdout
.lines()
.map(|s| s.to_string())
.filter(|s| !s.is_empty())
.collect())
}
fn get_label(image: &str, label: &str) -> String {
let format_str = format!("{{{{index .Config.Labels \"{label}\"}}}}");
Command::new("docker")
.args(["inspect", "--format", &format_str, image])
.output()
.ok()
.and_then(|o| {
let s = String::from_utf8_lossy(&o.stdout).trim().to_string();
if s.is_empty() || s == "<no value>" {
None
} else {
Some(s)
}
})
.unwrap_or_else(|| "-".to_string())
}