on main help sign in
Merge pull request #1 from robcholz/vibebox-cmd

feat: setup vibebox commands

commit87f99f25711e3528e06f2c119307dcbe34c15a5d authorFinn Sheng <yupbrokendream@gmail.com> dateSat 7 Feb 2026, 14:43 (8 months ago) parentc2dd18815a5199
8 files changed+143−24
docs/implementations.md−1 docs/tasks.md+1−1 src/bin/vibebox-cli.rs+4−3 src/commands.rs+111 src/instance.rs+6−14 src/lib.rs+1 src/ssh.sh+15 src/tui.rs+5−5
docs/implementations.md−1
@@ -68,7 +68,6 @@ In host cli:
6868
6969In vibebox:
7070
71−- use `:new` to prompt user to delete and create a session.
7271- use `:exit` to exit vibebox.
7372
7473### (Shows all the mounts/network/visibility)
docs/tasks.md+1−1
@@ -41,7 +41,7 @@
41414. [x] use vm.lock to ensure process concurrency safety.
42425. [x] wire up SessionManager.
43436. [x] VM should be separated by a per-session VM daemon process (only accepts if to shut down vm and itself).
44−7. [ ] setup vibebox commands
44+7. [x] setup vibebox commands
45458. [ ] setup cli commands.
46469. [ ] fix ui overlap.
474710. [ ] intensive integration test.
src/bin/vibebox-cli.rs+4−3
@@ -9,9 +9,9 @@ use color_eyre::Result;
99use tracing_subscriber::EnvFilter;
1010
1111use vibebox::tui::{AppState, VmInfo};
12−use vibebox::{SessionManager, config, instance, tui, vm, vm_manager};
12+use vibebox::{SessionManager, commands, config, instance, tui, vm, vm_manager};
1313
14−const DEFAULT_AUTO_SHUTDOWN_MS: u64 = 3000;
14+const DEFAULT_AUTO_SHUTDOWN_MS: u64 = 30000;
1515
1616fn main() -> Result<()> {
1717 init_tracing();
@@ -65,7 +65,8 @@ fn main() -> Result<()> {
6565 } else {
6666 tracing::warn!("failed to initialize session manager");
6767 }
68− let app = Arc::new(Mutex::new(AppState::new(cwd.clone(), vm_info)));
68+ let commands = commands::build_commands();
69+ let app = Arc::new(Mutex::new(AppState::new(cwd.clone(), vm_info, commands)));
6970
7071 {
7172 let mut locked = app.lock().expect("app state poisoned");
src/commands.rsnew file+111
@@ -0,0 +1,111 @@
1+use std::collections::HashMap;
2+use std::sync::{Arc, Mutex};
3+
4+use crate::tui::{AppState, VibeboxCommands};
5+use crate::vm::IoControl;
6+
7+#[derive(Clone, Copy)]
8+enum CommandKind {
9+ Help,
10+ Exit,
11+}
12+
13+struct CommandSpec {
14+ name: &'static str,
15+ description: &'static str,
16+ kind: CommandKind,
17+ shell_alias: Option<&'static str>,
18+}
19+
20+const COMMAND_SPECS: &[CommandSpec] = &[
21+ CommandSpec {
22+ name: ":help",
23+ description: "Show Vibebox commands.",
24+ kind: CommandKind::Help,
25+ shell_alias: Some("vibebox_help"),
26+ },
27+ CommandSpec {
28+ name: ":exit",
29+ description: "Exit Vibebox.",
30+ kind: CommandKind::Exit,
31+ shell_alias: Some("exit"),
32+ },
33+];
34+
35+pub struct CommandHandlers {
36+ handlers: HashMap<String, Box<dyn Fn() + Send + Sync>>,
37+}
38+
39+impl CommandHandlers {
40+ fn new() -> Self {
41+ Self {
42+ handlers: HashMap::new(),
43+ }
44+ }
45+
46+ fn register<F>(&mut self, name: &str, handler: F)
47+ where
48+ F: Fn() + Send + Sync + 'static,
49+ {
50+ self.handlers.insert(name.to_string(), Box::new(handler));
51+ }
52+
53+ pub fn handle(&self, line: &str) -> bool {
54+ if let Some(handler) = self.handlers.get(line) {
55+ handler();
56+ true
57+ } else {
58+ false
59+ }
60+ }
61+}
62+
63+pub fn build_commands() -> VibeboxCommands {
64+ let mut commands = VibeboxCommands::new_empty();
65+ for spec in COMMAND_SPECS {
66+ commands.add_command(spec.name, spec.description);
67+ }
68+ commands
69+}
70+
71+pub fn render_shell_script() -> String {
72+ let mut lines = Vec::new();
73+ lines.push("vibebox_help() {".to_string());
74+ lines.push(" cat <<'VIBEBOX_HELP'".to_string());
75+ lines.push("Vibebox Commands".to_string());
76+ for spec in COMMAND_SPECS {
77+ lines.push(format!("{} {}", spec.name, spec.description));
78+ }
79+ lines.push("VIBEBOX_HELP".to_string());
80+ lines.push("}".to_string());
81+ for spec in COMMAND_SPECS {
82+ if let Some(alias) = spec.shell_alias {
83+ lines.push(format!("alias {}='{}'", spec.name, alias));
84+ }
85+ }
86+ lines.join("\n")
87+}
88+
89+pub fn build_handlers(app: Arc<Mutex<AppState>>, io_control: Arc<IoControl>) -> CommandHandlers {
90+ let mut handlers = CommandHandlers::new();
91+ for spec in COMMAND_SPECS {
92+ match spec.kind {
93+ CommandKind::Help => {
94+ let app = app.clone();
95+ handlers.register(spec.name, move || {
96+ if let Ok(mut locked) = app.lock() {
97+ let _ = crate::tui::render_commands_component(&mut locked);
98+ }
99+ });
100+ }
101+ CommandKind::Exit => {
102+ let io_control = io_control.clone();
103+ handlers.register(spec.name, move || {
104+ io_control.request_terminal_restore();
105+ std::process::exit(0);
106+ });
107+ }
108+ }
109+ }
110+ handlers
111+}
src/instance.rs+6−14
@@ -19,8 +19,9 @@ use time::{OffsetDateTime, format_description::well_known::Rfc3339};
1919use uuid::Uuid;
2020
2121use crate::{
22+ commands,
2223 session_manager::{INSTANCE_DIR_NAME, INSTANCE_TOML_FILENAME},
23− tui::{self, AppState},
24+ tui::AppState,
2425 vm::{self, LoginAction, VmInput},
2526};
2627
@@ -344,7 +345,8 @@ pub(crate) fn build_ssh_login_actions(
344345 .replace("__SSH_USER__", &ssh_user)
345346 .replace("__SUDO_PASSWORD__", &sudo_password)
346347 .replace("__PROJECT_NAME__", project_name)
347− .replace("__KEY_PATH__", &key_path);
348+ .replace("__KEY_PATH__", &key_path)
349+ .replace("__VIBEBOX_SHELL_SCRIPT__", &commands::render_shell_script());
348350 let setup = vm::script_command_from_content("ssh_setup", &setup_script)
349351 .expect("ssh setup script contained invalid marker");
350352
@@ -385,18 +387,8 @@ fn spawn_ssh_io(
385387
386388 let mut line_buf = String::new();
387389
388− let on_line = {
389− let app = app.clone();
390− move |line: &str| {
391− if line == ":help" {
392− if let Ok(mut locked) = app.lock() {
393− let _ = tui::render_commands_component(&mut locked);
394− }
395− return true;
396− }
397− false
398− }
399− };
390+ let handlers = commands::build_handlers(app.clone(), io_control.clone());
391+ let on_line = move |line: &str| handlers.handle(line);
400392
401393 let on_output = move |bytes: &[u8]| {
402394 if ssh_connected_for_output.load(Ordering::SeqCst) {
src/lib.rs+1
@@ -1,3 +1,4 @@
1+pub mod commands;
12pub mod instance;
23pub mod session_manager;
34pub mod tui;
src/ssh.sh+15
@@ -77,6 +77,21 @@ if [ ! -e "${USER_HOME}/.claude" ]; then
7777fi
7878chown -h "${SSH_USER}:${SSH_USER}" "${USER_HOME}/.codex" "${USER_HOME}/.claude" 2>/dev/null || true
7979
80+# Vibebox shell commands
81+install -d -m 755 /etc/profile.d
82+cat > /etc/profile.d/vibebox.sh <<'VIBEBOX_SHELL_EOF'
83+__VIBEBOX_SHELL_SCRIPT__
84+VIBEBOX_SHELL_EOF
85+chmod 644 /etc/profile.d/vibebox.sh
86+
87+if ! grep -q "vibebox-aliases" "${USER_HOME}/.bashrc" 2>/dev/null; then
88+ {
89+ echo ""
90+ echo "# vibebox-aliases"
91+ echo ". /etc/profile.d/vibebox.sh"
92+ } >> "${USER_HOME}/.bashrc"
93+fi
94+
8095# Install Mise
8196MISE_BIN="${USER_HOME}/.local/bin/mise"
8297if [ ! -x "$MISE_BIN" ] && ! command -v mise >/dev/null 2>&1; then
src/tui.rs+5−5
@@ -50,11 +50,7 @@ pub struct AppState {
5050}
5151
5252impl AppState {
53− pub fn new(cwd: PathBuf, vm_info: VmInfo) -> Self {
54− let mut commands = VibeboxCommands::default();
55− commands.add_command(":new", "Create a new session.");
56− commands.add_command(":exit", "Exit Vibebox.");
57−
53+ pub fn new(cwd: PathBuf, vm_info: VmInfo, commands: VibeboxCommands) -> Self {
5854 Self {
5955 cwd,
6056 vm_info,
@@ -69,6 +65,10 @@ pub struct VibeboxCommands {
6965}
7066
7167impl VibeboxCommands {
68+ pub fn new_empty() -> Self {
69+ Self { items: Vec::new() }
70+ }
71+
7272 pub fn add_command(&mut self, name: impl Into<String>, description: impl Into<String>) {
7373 self.items.push(VibeboxCommand {
7474 name: name.into(),