mirror of
https://github.com/robcholz/vibebox.git
synced 2026-04-01 00:10:15 +02:00
feat: added explain command to display mounts
This commit is contained in:
@@ -42,11 +42,11 @@
|
||||
5. [x] wire up SessionManager.
|
||||
6. [x] VM should be separated by a per-session VM daemon process (only accepts if to shut down vm and itself).
|
||||
7. [x] setup vibebox commands
|
||||
8. [ ] setup cli commands.
|
||||
8. [x] setup cli commands.
|
||||
1. [x] Organize all the params.
|
||||
2. [x] Remove old cli.
|
||||
3. [x] add an actual config file.
|
||||
4. [ ] set up the cli.
|
||||
4. [x] set up the cli.
|
||||
9. [ ] fix ui overlap, and consistency issue.
|
||||
10. [ ] intensive integration test.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
io::{self, IsTerminal, Write},
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
@@ -32,6 +32,8 @@ enum Command {
|
||||
List,
|
||||
/// Delete the current project's .vibebox directory
|
||||
Clean,
|
||||
/// Explain mounts and mappings
|
||||
Explain,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@@ -42,7 +44,7 @@ fn main() -> Result<()> {
|
||||
let cwd = env::current_dir().map_err(|err| color_eyre::eyre::eyre!(err.to_string()))?;
|
||||
tracing::info!(cwd = %cwd.display(), "starting vibebox cli");
|
||||
if let Some(command) = cli.command {
|
||||
return handle_command(command, &cwd);
|
||||
return handle_command(command, &cwd, cli.config.as_deref());
|
||||
}
|
||||
|
||||
let config_override = cli.config.clone();
|
||||
@@ -111,7 +113,7 @@ fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_command(command: Command, cwd: &PathBuf) -> Result<()> {
|
||||
fn handle_command(command: Command, cwd: &PathBuf, config_override: Option<&Path>) -> Result<()> {
|
||||
match command {
|
||||
Command::List => {
|
||||
let manager = SessionManager::new()?;
|
||||
@@ -164,6 +166,16 @@ fn handle_command(command: Command, cwd: &PathBuf) -> Result<()> {
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Command::Explain => {
|
||||
let config = config::load_config_with_path(cwd, config_override);
|
||||
let rows = build_mount_rows(cwd, &config)?;
|
||||
if rows.is_empty() {
|
||||
println!("No mounts configured.");
|
||||
return Ok(());
|
||||
}
|
||||
tui::render_mounts_table(&rows)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +243,100 @@ 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 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();
|
||||
|
||||
60
src/tui.rs
60
src/tui.rs
@@ -107,6 +107,14 @@ pub struct SessionListRow {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MountListRow {
|
||||
pub host: String,
|
||||
pub guest: String,
|
||||
pub mode: String,
|
||||
pub default_mount: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
struct PageLayout {
|
||||
header: Rect,
|
||||
@@ -235,6 +243,58 @@ pub fn render_sessions_table(rows: &[SessionListRow]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_mounts_table(rows: &[MountListRow]) -> Result<()> {
|
||||
let (width, _) = crossterm::terminal::size()?;
|
||||
if width == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let height = (rows.len() as u16).saturating_add(3);
|
||||
let mut buffer = Buffer::empty(Rect::new(0, 0, width, height));
|
||||
let area = Rect::new(0, 0, width, height);
|
||||
|
||||
let header = Row::new(vec![
|
||||
Cell::from("Host"),
|
||||
Cell::from("Guest"),
|
||||
Cell::from("Mode"),
|
||||
Cell::from(""),
|
||||
Cell::from("Default"),
|
||||
])
|
||||
.style(Style::default().fg(Color::Cyan));
|
||||
|
||||
let table_rows = rows.iter().map(|row| {
|
||||
Row::new(vec![
|
||||
Cell::from(row.host.clone()),
|
||||
Cell::from(row.guest.clone()),
|
||||
Cell::from(row.mode.clone()),
|
||||
Cell::from(""),
|
||||
Cell::from(row.default_mount.clone()),
|
||||
])
|
||||
});
|
||||
|
||||
let table = Table::new(
|
||||
table_rows,
|
||||
[
|
||||
Constraint::Min(24),
|
||||
Constraint::Min(24),
|
||||
Constraint::Length(10),
|
||||
Constraint::Length(1),
|
||||
Constraint::Length(8),
|
||||
],
|
||||
)
|
||||
.header(header)
|
||||
.block(Block::default().title("Mounts").borders(Borders::ALL))
|
||||
.column_spacing(1);
|
||||
|
||||
table.render(area, &mut buffer);
|
||||
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, Clear(ClearType::All), MoveTo(0, 0), Show)?;
|
||||
write_buffer_with_style(&buffer, &mut stdout)?;
|
||||
stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn passthrough_vm_io(
|
||||
app: Arc<Mutex<AppState>>,
|
||||
output_monitor: Arc<vm::OutputMonitor>,
|
||||
|
||||
Reference in New Issue
Block a user