Improved vt command to allow finding the current session

This commit is contained in:
Armin Ronacher 2025-06-17 09:56:21 +02:00
parent cbe5f87aed
commit eee8a65450
4 changed files with 127 additions and 16 deletions

View file

@ -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}"

View file

@ -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::<String>;
let mut static_path = None::<String>;
let mut password = None::<String>;
@ -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 <path> Where the control folder is located");
println!(" --session-name <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 <I> Operate on this session");
println!(" --send-key <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 {

View file

@ -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<String>,
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,

View file

@ -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<Option<SessionEntryWithId>, 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<u32> {
// 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::<u32>().ok()
} else {
None
}
}
pub fn send_key_to_session(
control_path: &Path,
session_id: &str,