vtcode_core/cli/
pods_commands.rs1use 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
8pub 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}