fix: added back explain

This commit is contained in:
robcholz
2026-02-07 16:52:33 -05:00
parent 1451c31a4e
commit dfecd12e12
3 changed files with 151 additions and 117 deletions

View File

@@ -14,7 +14,9 @@ use time::format_description::well_known::Rfc3339;
use tracing_subscriber::EnvFilter;
use vibebox::tui::{AppState, VmInfo};
use vibebox::{SessionManager, commands, config, instance, session_manager, tui, vm, vm_manager};
use vibebox::{
SessionManager, commands, config, explain, instance, session_manager, tui, vm, vm_manager,
};
#[derive(Debug, Parser)]
#[command(name = "vibebox", version, about = "Vibebox CLI")]
@@ -168,8 +170,10 @@ fn handle_command(command: Command, cwd: &PathBuf, config_override: Option<&Path
}
Command::Explain => {
let config = config::load_config_with_path(cwd, config_override);
let mounts = build_mount_rows(cwd, &config)?;
let networks = build_network_rows(cwd)?;
let mounts = explain::build_mount_rows(cwd, &config)
.map_err(|err| color_eyre::eyre::eyre!(err.to_string()))?;
let networks = explain::build_network_rows(cwd)
.map_err(|err| color_eyre::eyre::eyre!(err.to_string()))?;
if mounts.is_empty() && networks.is_empty() {
println!("No mounts or network info available.");
return Ok(());
@@ -244,120 +248,6 @@ fn format_last_active(value: Option<&str>) -> String {
format!("{} day{} ago", days, if days == 1 { "" } else { "s" })
}
fn build_mount_rows(cwd: &Path, config: &config::Config) -> Result<Vec<tui::MountListRow>> {
let mut rows = Vec::new();
rows.extend(default_mounts(cwd)?);
for spec in &config.box_cfg.mounts {
rows.push(parse_mount_spec(cwd, spec, false)?);
}
Ok(rows)
}
fn build_network_rows(cwd: &Path) -> Result<Vec<tui::NetworkListRow>> {
let instance_dir = cwd.join(session_manager::INSTANCE_DIR_NAME);
let mut vm_ip = "-".to_string();
if let Ok(Some(ip)) = instance::read_instance_vm_ip(&instance_dir) {
vm_ip = ip;
}
let host_to_vm = if vm_ip == "-" {
"ssh: <pending>:22".to_string()
} else {
format!("ssh: {vm_ip}:22")
};
let row = tui::NetworkListRow {
network_type: "NAT".to_string(),
vm_ip: vm_ip.clone(),
host_to_vm,
vm_to_host: "none".to_string(),
};
Ok(vec![row])
}
fn default_mounts(cwd: &Path) -> Result<Vec<tui::MountListRow>> {
let project_name = cwd
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("project");
let project_guest = format!("/root/{project_name}");
let project_host = relative_to_home(&cwd.to_path_buf());
let mut rows = vec![tui::MountListRow {
host: project_host,
guest: project_guest,
mode: "read-write".to_string(),
default_mount: "yes".to_string(),
}];
let home = env::var("HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/"));
let cache_home = env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| home.join(".cache"));
let cache_dir = cache_home.join(session_manager::GLOBAL_CACHE_DIR_NAME);
let guest_mise_cache = cache_dir.join(".guest-mise-cache");
rows.push(tui::MountListRow {
host: relative_to_home(&guest_mise_cache),
guest: "/root/.local/share/mise".to_string(),
mode: "read-write".to_string(),
default_mount: "yes".to_string(),
});
Ok(rows)
}
fn parse_mount_spec(cwd: &Path, spec: &str, default_mount: bool) -> Result<tui::MountListRow> {
let parts: Vec<&str> = spec.split(':').collect();
if parts.len() < 2 || parts.len() > 3 {
return Err(color_eyre::eyre::eyre!("invalid mount spec: {spec}"));
}
let host_part = parts[0];
let guest_part = parts[1];
let mode = if parts.len() == 3 {
match parts[2] {
"read-only" => "read-only",
"read-write" => "read-write",
other => {
return Err(color_eyre::eyre::eyre!(
"invalid mount mode '{}'; expected read-only or read-write",
other
));
}
}
} else {
"read-write"
};
let host_path = resolve_host_path(cwd, host_part);
let host_display = relative_to_home(&host_path);
let guest_display = if Path::new(guest_part).is_absolute() {
guest_part.to_string()
} else {
format!("/root/{guest_part}")
};
Ok(tui::MountListRow {
host: host_display,
guest: guest_display,
mode: mode.to_string(),
default_mount: if default_mount { "yes" } else { "no" }.to_string(),
})
}
fn resolve_host_path(cwd: &Path, host: &str) -> PathBuf {
if let Some(stripped) = host.strip_prefix("~/") {
if let Ok(home) = env::var("HOME") {
return PathBuf::from(home).join(stripped);
}
} else if host == "~" {
if let Ok(home) = env::var("HOME") {
return PathBuf::from(home);
}
}
let host_path = PathBuf::from(host);
if host_path.is_absolute() {
host_path
} else {
cwd.join(host_path)
}
}
fn init_tracing() {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
let ansi = std::io::stderr().is_terminal() && env::var("VIBEBOX_LOG_NO_COLOR").is_err();

143
src/explain.rs Normal file
View File

@@ -0,0 +1,143 @@
use std::{
env,
error::Error,
path::{Path, PathBuf},
};
use crate::{config, instance, session_manager, tui};
pub fn build_mount_rows(
cwd: &Path,
config: &config::Config,
) -> Result<Vec<tui::MountListRow>, Box<dyn Error + Send + Sync>> {
let mut rows = Vec::new();
rows.extend(default_mounts(cwd)?);
for spec in &config.box_cfg.mounts {
rows.push(parse_mount_spec(cwd, spec, false)?);
}
Ok(rows)
}
pub fn build_network_rows(
cwd: &Path,
) -> Result<Vec<tui::NetworkListRow>, Box<dyn Error + Send + Sync>> {
let instance_dir = cwd.join(session_manager::INSTANCE_DIR_NAME);
let mut vm_ip = "-".to_string();
if let Ok(Some(ip)) = instance::read_instance_vm_ip(&instance_dir) {
vm_ip = ip;
}
let host_to_vm = if vm_ip == "-" {
"ssh: <pending>:22".to_string()
} else {
format!("ssh: {vm_ip}:22")
};
let row = tui::NetworkListRow {
network_type: "NAT".to_string(),
vm_ip: vm_ip.clone(),
host_to_vm,
vm_to_host: "none".to_string(),
};
Ok(vec![row])
}
fn default_mounts(cwd: &Path) -> Result<Vec<tui::MountListRow>, Box<dyn Error + Send + Sync>> {
let project_name = cwd
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("project");
let project_guest = format!("/root/{project_name}");
let project_host = display_path(cwd);
let mut rows = vec![tui::MountListRow {
host: project_host,
guest: project_guest,
mode: "read-write".to_string(),
default_mount: "yes".to_string(),
}];
let home = env::var("HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/"));
let cache_home = env::var("XDG_CACHE_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| home.join(".cache"));
let cache_dir = cache_home.join(session_manager::GLOBAL_CACHE_DIR_NAME);
let guest_mise_cache = cache_dir.join(".guest-mise-cache");
rows.push(tui::MountListRow {
host: display_path(&guest_mise_cache),
guest: "/root/.local/share/mise".to_string(),
mode: "read-write".to_string(),
default_mount: "yes".to_string(),
});
Ok(rows)
}
fn parse_mount_spec(
cwd: &Path,
spec: &str,
default_mount: bool,
) -> Result<tui::MountListRow, Box<dyn Error + Send + Sync>> {
let parts: Vec<&str> = spec.split(':').collect();
if parts.len() < 2 || parts.len() > 3 {
return Err(format!("invalid mount spec: {spec}").into());
}
let host_part = parts[0];
let guest_part = parts[1];
let mode = if parts.len() == 3 {
match parts[2] {
"read-only" => "read-only",
"read-write" => "read-write",
other => {
return Err(format!(
"invalid mount mode '{}'; expected read-only or read-write",
other
)
.into());
}
}
} else {
"read-write"
};
let host_display = display_host_spec(cwd, host_part);
let guest_display = if Path::new(guest_part).is_absolute() {
guest_part.to_string()
} else {
format!("/root/{guest_part}")
};
Ok(tui::MountListRow {
host: host_display,
guest: guest_display,
mode: mode.to_string(),
default_mount: if default_mount { "yes" } else { "no" }.to_string(),
})
}
fn display_host_spec(cwd: &Path, host: &str) -> String {
if host == "~" || host.starts_with("~/") {
return host.to_string();
}
let host_path = PathBuf::from(host);
if host_path.is_absolute() {
return display_path(&host_path);
}
let candidate = cwd.join(&host_path);
if candidate.is_absolute() {
display_path(&candidate)
} else {
host.to_string()
}
}
fn display_path(path: &Path) -> String {
let Ok(home) = env::var("HOME") else {
return path.display().to_string();
};
let home_path = PathBuf::from(home);
if let Ok(stripped) = path.strip_prefix(&home_path) {
if stripped.components().next().is_none() {
return "~".to_string();
}
return format!("~/{}", stripped.display());
}
path.display().to_string()
}

View File

@@ -1,4 +1,5 @@
pub mod commands;
pub mod explain;
pub mod instance;
pub mod session_manager;
pub mod tui;