Merge pull request #1 from robcholz/vibebox-cmd

feat: setup vibebox commands
This commit is contained in:
Finn Sheng
2026-02-07 14:43:30 -05:00
committed by GitHub
8 changed files with 143 additions and 24 deletions
-1
View File
@@ -68,7 +68,6 @@ In host cli:
In vibebox:
- use `:new` to prompt user to delete and create a session.
- use `:exit` to exit vibebox.
### (Shows all the mounts/network/visibility)
+1 -1
View File
@@ -41,7 +41,7 @@
4. [x] use vm.lock to ensure process concurrency safety.
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. [ ] setup vibebox commands
7. [x] setup vibebox commands
8. [ ] setup cli commands.
9. [ ] fix ui overlap.
10. [ ] intensive integration test.
+4 -3
View File
@@ -9,9 +9,9 @@ use color_eyre::Result;
use tracing_subscriber::EnvFilter;
use vibebox::tui::{AppState, VmInfo};
use vibebox::{SessionManager, config, instance, tui, vm, vm_manager};
use vibebox::{SessionManager, commands, config, instance, tui, vm, vm_manager};
const DEFAULT_AUTO_SHUTDOWN_MS: u64 = 3000;
const DEFAULT_AUTO_SHUTDOWN_MS: u64 = 30000;
fn main() -> Result<()> {
init_tracing();
@@ -65,7 +65,8 @@ fn main() -> Result<()> {
} else {
tracing::warn!("failed to initialize session manager");
}
let app = Arc::new(Mutex::new(AppState::new(cwd.clone(), vm_info)));
let commands = commands::build_commands();
let app = Arc::new(Mutex::new(AppState::new(cwd.clone(), vm_info, commands)));
{
let mut locked = app.lock().expect("app state poisoned");
+111
View File
@@ -0,0 +1,111 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::tui::{AppState, VibeboxCommands};
use crate::vm::IoControl;
#[derive(Clone, Copy)]
enum CommandKind {
Help,
Exit,
}
struct CommandSpec {
name: &'static str,
description: &'static str,
kind: CommandKind,
shell_alias: Option<&'static str>,
}
const COMMAND_SPECS: &[CommandSpec] = &[
CommandSpec {
name: ":help",
description: "Show Vibebox commands.",
kind: CommandKind::Help,
shell_alias: Some("vibebox_help"),
},
CommandSpec {
name: ":exit",
description: "Exit Vibebox.",
kind: CommandKind::Exit,
shell_alias: Some("exit"),
},
];
pub struct CommandHandlers {
handlers: HashMap<String, Box<dyn Fn() + Send + Sync>>,
}
impl CommandHandlers {
fn new() -> Self {
Self {
handlers: HashMap::new(),
}
}
fn register<F>(&mut self, name: &str, handler: F)
where
F: Fn() + Send + Sync + 'static,
{
self.handlers.insert(name.to_string(), Box::new(handler));
}
pub fn handle(&self, line: &str) -> bool {
if let Some(handler) = self.handlers.get(line) {
handler();
true
} else {
false
}
}
}
pub fn build_commands() -> VibeboxCommands {
let mut commands = VibeboxCommands::new_empty();
for spec in COMMAND_SPECS {
commands.add_command(spec.name, spec.description);
}
commands
}
pub fn render_shell_script() -> String {
let mut lines = Vec::new();
lines.push("vibebox_help() {".to_string());
lines.push(" cat <<'VIBEBOX_HELP'".to_string());
lines.push("Vibebox Commands".to_string());
for spec in COMMAND_SPECS {
lines.push(format!("{} {}", spec.name, spec.description));
}
lines.push("VIBEBOX_HELP".to_string());
lines.push("}".to_string());
for spec in COMMAND_SPECS {
if let Some(alias) = spec.shell_alias {
lines.push(format!("alias {}='{}'", spec.name, alias));
}
}
lines.join("\n")
}
pub fn build_handlers(app: Arc<Mutex<AppState>>, io_control: Arc<IoControl>) -> CommandHandlers {
let mut handlers = CommandHandlers::new();
for spec in COMMAND_SPECS {
match spec.kind {
CommandKind::Help => {
let app = app.clone();
handlers.register(spec.name, move || {
if let Ok(mut locked) = app.lock() {
let _ = crate::tui::render_commands_component(&mut locked);
}
});
}
CommandKind::Exit => {
let io_control = io_control.clone();
handlers.register(spec.name, move || {
io_control.request_terminal_restore();
std::process::exit(0);
});
}
}
}
handlers
}
+6 -14
View File
@@ -19,8 +19,9 @@ use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use uuid::Uuid;
use crate::{
commands,
session_manager::{INSTANCE_DIR_NAME, INSTANCE_TOML_FILENAME},
tui::{self, AppState},
tui::AppState,
vm::{self, LoginAction, VmInput},
};
@@ -344,7 +345,8 @@ pub(crate) fn build_ssh_login_actions(
.replace("__SSH_USER__", &ssh_user)
.replace("__SUDO_PASSWORD__", &sudo_password)
.replace("__PROJECT_NAME__", project_name)
.replace("__KEY_PATH__", &key_path);
.replace("__KEY_PATH__", &key_path)
.replace("__VIBEBOX_SHELL_SCRIPT__", &commands::render_shell_script());
let setup = vm::script_command_from_content("ssh_setup", &setup_script)
.expect("ssh setup script contained invalid marker");
@@ -385,18 +387,8 @@ fn spawn_ssh_io(
let mut line_buf = String::new();
let on_line = {
let app = app.clone();
move |line: &str| {
if line == ":help" {
if let Ok(mut locked) = app.lock() {
let _ = tui::render_commands_component(&mut locked);
}
return true;
}
false
}
};
let handlers = commands::build_handlers(app.clone(), io_control.clone());
let on_line = move |line: &str| handlers.handle(line);
let on_output = move |bytes: &[u8]| {
if ssh_connected_for_output.load(Ordering::SeqCst) {
+1
View File
@@ -1,3 +1,4 @@
pub mod commands;
pub mod instance;
pub mod session_manager;
pub mod tui;
+15
View File
@@ -77,6 +77,21 @@ if [ ! -e "${USER_HOME}/.claude" ]; then
fi
chown -h "${SSH_USER}:${SSH_USER}" "${USER_HOME}/.codex" "${USER_HOME}/.claude" 2>/dev/null || true
# Vibebox shell commands
install -d -m 755 /etc/profile.d
cat > /etc/profile.d/vibebox.sh <<'VIBEBOX_SHELL_EOF'
__VIBEBOX_SHELL_SCRIPT__
VIBEBOX_SHELL_EOF
chmod 644 /etc/profile.d/vibebox.sh
if ! grep -q "vibebox-aliases" "${USER_HOME}/.bashrc" 2>/dev/null; then
{
echo ""
echo "# vibebox-aliases"
echo ". /etc/profile.d/vibebox.sh"
} >> "${USER_HOME}/.bashrc"
fi
# Install Mise
MISE_BIN="${USER_HOME}/.local/bin/mise"
if [ ! -x "$MISE_BIN" ] && ! command -v mise >/dev/null 2>&1; then
+5 -5
View File
@@ -50,11 +50,7 @@ pub struct AppState {
}
impl AppState {
pub fn new(cwd: PathBuf, vm_info: VmInfo) -> Self {
let mut commands = VibeboxCommands::default();
commands.add_command(":new", "Create a new session.");
commands.add_command(":exit", "Exit Vibebox.");
pub fn new(cwd: PathBuf, vm_info: VmInfo, commands: VibeboxCommands) -> Self {
Self {
cwd,
vm_info,
@@ -69,6 +65,10 @@ pub struct VibeboxCommands {
}
impl VibeboxCommands {
pub fn new_empty() -> Self {
Self { items: Vec::new() }
}
pub fn add_command(&mut self, name: impl Into<String>, description: impl Into<String>) {
self.items.push(VibeboxCommand {
name: name.into(),