From eee8a6545042a5555df24fa24bff43b1739c347d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 17 Jun 2025 09:56:21 +0200 Subject: [PATCH] Improved vt command to allow finding the current session --- VibeTunnel/Resources/vt | 43 +++++++++++++++++++++-------- tty-fwd/src/main.rs | 28 ++++++++++++++++++- tty-fwd/src/protocol.rs | 11 ++++++-- tty-fwd/src/sessions.rs | 61 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/VibeTunnel/Resources/vt b/VibeTunnel/Resources/vt index b2b1f9c4..f42b4ebc 100755 --- a/VibeTunnel/Resources/vt +++ b/VibeTunnel/Resources/vt @@ -27,6 +27,21 @@ find_claude() { return 1 } +# Get the directory where this script is actually located (Resources folder) +SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_REAL_PATH")" && pwd)" + +# Path to tty-fwd executable in the same Resources directory +TTY_FWD="$SCRIPT_DIR/tty-fwd" + +# Check if tty-fwd exists there, otherwise use the one from ../tty-fwd/target/debug/tty-fwd +if [[ ! -x "$TTY_FWD" ]]; then + TTY_FWD="$SCRIPT_DIR/../tty-fwd/target/debug/tty-fwd" + if [[ ! -x "$TTY_FWD" ]]; then + echo "Error: tty-fwd executable not found at $TTY_FWD" >&2 + exit 1 + fi +fi + # Handle --claude option if [[ "$1" == "--claude" ]]; then shift @@ -51,6 +66,18 @@ if [[ "$1" == "--claude-yolo" ]]; then exec "$0" "$claude_path" --dangerously-skip-permissions "$@" fi +# Handle --show-session-info option +if [[ "$1" == "--show-session-info" ]]; then + shift + exec "$TTY_FWD" --show-session-info "$@" +fi + +# Handle --show-session-id option +if [[ "$1" == "--show-session-id" ]]; then + shift + exec "$TTY_FWD" --show-session-id "$@" +fi + # Handle --shell or -i option (launch current shell) if [[ "$1" == "--shell" || "$1" == "-i" ]]; then shift @@ -76,6 +103,8 @@ USAGE: vt --shell [args...] vt -i [args...] vt --no-shell-wrap [command] [args...] + vt --show-session-info + vt --show-session-id vt -S [command] [args...] vt --help @@ -105,6 +134,8 @@ OPTIONS: --shell, -i Launch current shell (equivalent to vt $SHELL) --no-shell-wrap, -S Execute command directly without shell wrapper --help, -h Show this help message and exit + --show-session-info Show current session info + --show-session-id Show current session ID only NOTE: This script automatically uses the tty-fwd executable bundled with @@ -123,18 +154,6 @@ if [[ -z "$SCRIPT_REAL_PATH" ]]; then done fi -# Get the directory where this script is actually located (Resources folder) -SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_REAL_PATH")" && pwd)" - -# Path to tty-fwd executable in the same Resources directory -TTY_FWD="$SCRIPT_DIR/tty-fwd" - -# Check if tty-fwd exists -if [[ ! -x "$TTY_FWD" ]]; then - echo "Error: tty-fwd executable not found at $TTY_FWD" >&2 - exit 1 -fi - # Function to resolve command through user's shell resolve_command() { local user_shell="${SHELL:-/bin/bash}" diff --git a/tty-fwd/src/main.rs b/tty-fwd/src/main.rs index 08b03493..b080e158 100644 --- a/tty-fwd/src/main.rs +++ b/tty-fwd/src/main.rs @@ -25,6 +25,8 @@ fn main() -> Result<(), anyhow::Error> { let mut stop = false; let mut kill = false; let mut cleanup = false; + let mut show_session_info = false; + let mut show_session_id = false; let mut serve_address = None::; let mut static_path = None::; let mut password = None::; @@ -38,9 +40,16 @@ fn main() -> Result<(), anyhow::Error> { p if p.is_long("list-sessions") => { let control_path: &Path = &control_path; let sessions = sessions::list_sessions(control_path)?; - println!("{}", serde_json::to_string(&sessions)?); + println!("{}", serde_json::to_string_pretty(&sessions)?); return Ok(()); } + p if p.is_long("show-session-info") => { + show_session_info = true; + } + p if p.is_long("show-session-id") => { + show_session_id = true; + show_session_info = true; + } p if p.is_long("session-name") => { session_name = Some(parser.value()?); } @@ -93,6 +102,10 @@ fn main() -> Result<(), anyhow::Error> { println!(" --control-path Where the control folder is located"); println!(" --session-name Names the session when creating"); println!(" --list-sessions List all sessions"); + println!(" --find-session Find session for current process"); + println!( + " --print-id Print session ID only (implies --find-session)" + ); println!(" --session Operate on this session"); println!(" --send-key Send key input to session"); println!(" Keys: arrow_up, arrow_down, arrow_left, arrow_right, escape, enter, ctrl_enter, shift_enter"); @@ -117,6 +130,19 @@ fn main() -> Result<(), anyhow::Error> { } } + // show session info + if show_session_info || show_session_id { + let control_path: &Path = &control_path; + if let Some(entry) = sessions::find_current_session(control_path)? { + if show_session_id { + println!("{}", entry.session_id); + } else { + println!("{}", serde_json::to_string_pretty(&entry)?); + } + } + return Ok(()); + } + // Handle send-key command if let Some(key) = send_key { if let Some(sid) = &session_id { diff --git a/tty-fwd/src/protocol.rs b/tty-fwd/src/protocol.rs index e783e3e3..88cf1b53 100644 --- a/tty-fwd/src/protocol.rs +++ b/tty-fwd/src/protocol.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::tty_spawn::DEFAULT_TERM; -#[derive(Serialize, Deserialize, Default)] +#[derive(Serialize, Deserialize, Default, Debug, Clone)] pub struct SessionInfo { pub cmdline: Vec, pub name: String, @@ -24,7 +24,7 @@ fn get_default_term() -> String { DEFAULT_TERM.to_string() } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct SessionListEntry { #[serde(flatten)] pub session_info: SessionInfo, @@ -35,6 +35,13 @@ pub struct SessionListEntry { pub notification_stream: String, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SessionEntryWithId { + pub session_id: String, + #[serde(flatten)] + pub entry: SessionListEntry, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct AsciinemaHeader { pub version: u32, diff --git a/tty-fwd/src/sessions.rs b/tty-fwd/src/sessions.rs index 74ad4cbc..73c1233f 100644 --- a/tty-fwd/src/sessions.rs +++ b/tty-fwd/src/sessions.rs @@ -11,7 +11,7 @@ use std::process::Command; use std::time::Duration; use uuid::Uuid; -use crate::protocol::{SessionInfo, SessionListEntry}; +use crate::protocol::{SessionEntryWithId, SessionInfo, SessionListEntry}; use crate::tty_spawn::TtySpawn; pub fn list_sessions( @@ -82,6 +82,65 @@ pub fn list_sessions( Ok(sessions) } +pub fn find_current_session( + control_path: &Path, +) -> Result, anyhow::Error> { + let sessions = list_sessions(control_path)?; + + // Get current process PID + let current_pid = std::process::id(); + + // Check each session to see if current process or any parent is part of it + for (session_id, session_entry) in sessions { + if let Some(session_pid) = session_entry.session_info.pid { + // Check if this session PID is in our process ancestry + if is_process_descendant_of(current_pid, session_pid) { + return Ok(Some(SessionEntryWithId { + session_id, + entry: session_entry, + })); + } + } + } + + Ok(None) +} + +fn is_process_descendant_of(mut current_pid: u32, target_pid: u32) -> bool { + // Check if current process is the target or a descendant of target + while current_pid > 1 { + if current_pid == target_pid { + return true; + } + + // Get parent PID + match get_parent_pid(current_pid) { + Some(parent_pid) => current_pid = parent_pid, + None => break, + } + } + + false +} + +fn get_parent_pid(pid: u32) -> Option { + // Use ps command to get parent PID + let output = Command::new("ps") + .arg("-p") + .arg(pid.to_string()) + .arg("-o") + .arg("ppid=") + .output() + .ok()?; + + if output.status.success() { + let ppid_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + ppid_str.parse::().ok() + } else { + None + } +} + pub fn send_key_to_session( control_path: &Path, session_id: &str,