Skip to main content

vtcode_core/cli/
pods_commands.rs

1//! CLI commands for GPU pod management.
2
3use crate::cli::PodsCommands;
4use crate::pods::{PodGpu, PodManager, PodStartRequest, PodState, PodStatusDetail, PodStatusReport};
5use crate::utils::colors::{bold, cyan, green, underline, yellow};
6use anyhow::{Result, anyhow};
7
8/// Handle GPU pod commands.
9pub async fn handle_pods_command(command: PodsCommands) -> Result<()> {
10    let manager = PodManager::new()?;
11
12    match command {
13        PodsCommands::Start {
14            name,
15            model,
16            pod_name,
17            ssh,
18            gpus,
19            models_path,
20            profile,
21            gpus_count,
22            memory,
23            context,
24        } => {
25            let request = PodStartRequest {
26                pod_name,
27                ssh,
28                gpus: parse_gpu_entries(&gpus)?,
29                models_path,
30                name,
31                model,
32                profile,
33                requested_gpu_count: gpus_count,
34                memory,
35                context,
36            };
37            let result = manager.start_model(request).await?;
38            print_start_result(&result);
39        }
40        PodsCommands::Stop { name } => {
41            let stopped = manager
42                .stop_model(&name)
43                .await?
44                .ok_or_else(|| anyhow!("unknown model '{name}'"))?;
45            println!("{} Stopped {} (pid {})", green("✓"), cyan(&name), stopped.pid);
46        }
47        PodsCommands::StopAll => {
48            let count = manager.stop_all_models().await?;
49            println!("{} Stopped {} model(s)", green("✓"), count);
50        }
51        PodsCommands::List => {
52            let report = manager.list_models().await?;
53            print_list_report(&report);
54        }
55        PodsCommands::Logs { name } => {
56            manager.stream_logs(&name).await?;
57        }
58        PodsCommands::KnownModels => {
59            let report = manager.known_models().await?;
60            print_known_models(&report, manager.load_state().await?.active_pod.as_ref());
61        }
62    }
63
64    Ok(())
65}
66
67fn parse_gpu_entries(raw: &[String]) -> Result<Vec<PodGpu>> {
68    raw.iter()
69        .map(|entry| {
70            let (id, name) = entry
71                .split_once(':')
72                .or_else(|| entry.split_once('='))
73                .ok_or_else(|| anyhow!("invalid GPU entry '{entry}'; expected ID:NAME"))?;
74            let id = id
75                .trim()
76                .parse::<u32>()
77                .map_err(|e| anyhow!("invalid GPU id '{}': {e}", id.trim()))?;
78            let name = name.trim();
79            if name.is_empty() {
80                return Err(anyhow!("GPU name cannot be empty"));
81            }
82            Ok(PodGpu { id, name: name.to_string() })
83        })
84        .collect()
85}
86
87fn print_start_result(result: &crate::pods::PodStartResult) {
88    println!("{} Started {}", green("✓"), bold(&result.entry.model));
89    println!("  Pod: {}", cyan(&result.pod.name));
90    println!("  Profile: {}", cyan(&result.entry.profile));
91    println!("  PID: {}", cyan(&result.entry.pid.to_string()));
92    println!("  Port: {}", cyan(&result.entry.port.to_string()));
93    println!("  GPUs: {}", cyan(&result.entry.gpu_ids.iter().map(u32::to_string).collect::<Vec<_>>().join(", ")));
94    println!("  Launch: {}", yellow(&result.launch_command));
95}
96
97fn print_list_report(report: &PodStatusReport) {
98    println!("{}", underline(&bold("Active Pod")));
99    println!("Pod: {}", cyan(&report.pod_name));
100    println!();
101
102    if report.entries.is_empty() {
103        println!("{}", yellow("No running models"));
104        return;
105    }
106
107    for entry in &report.entries {
108        println!(
109            "{} {} | model={} | port={} | pid={} | gpus={}",
110            status_symbol(entry.status),
111            bold(&entry.name),
112            entry.model,
113            entry.port,
114            entry.pid,
115            entry.gpu_ids.iter().map(u32::to_string).collect::<Vec<_>>().join(", ")
116        );
117    }
118}
119
120fn print_known_models(report: &crate::pods::KnownModelsReport, active_pod: Option<&PodState>) {
121    println!("{}", underline(&bold("Known Models")));
122    if let Some(pod) = active_pod {
123        println!("Pod: {}", cyan(&pod.name));
124        println!("GPUs: {}", cyan(&pod.gpu_count().to_string()));
125    }
126    println!();
127
128    println!("{}", green("Compatible"));
129    for model in &report.compatible {
130        print_model_detail(model, green("  ✓"));
131    }
132
133    println!();
134    println!("{}", yellow("Incompatible"));
135    for model in &report.incompatible {
136        print_model_detail(model, yellow("  •"));
137    }
138}
139
140fn print_model_detail(model: &PodStatusDetail, prefix: impl std::fmt::Display) {
141    println!(
142        "{} {} ({}, {} GPU{})",
143        prefix,
144        cyan(&model.name),
145        model.model,
146        model.gpu_count,
147        if model.gpu_count == 1 { "" } else { "s" }
148    );
149}
150
151fn status_symbol(status: crate::pods::PodHealth) -> &'static str {
152    match status {
153        crate::pods::PodHealth::Running => "✓",
154        crate::pods::PodHealth::Starting => "…",
155        crate::pods::PodHealth::Crashed => "✗",
156        crate::pods::PodHealth::Dead => "•",
157    }
158}