I am batman

This commit is contained in:
2026-07-10 20:07:29 -04:00
commit 4aac54bff6
175 changed files with 18136 additions and 0 deletions

409
scripts/tmux-session-manager.sh Executable file
View File

@@ -0,0 +1,409 @@
#!/usr/bin/env bash
# Tmux Session Manager - Manage multiple named tmux sessions with persistence
# Usage: tmux-session-manager.sh [session-name] [action]
# Actions: save, restore, list, delete
set -euo pipefail
SESSION_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/tmux/sessions"
RESURRECT_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/tmux/resurrect"
# Create directories if they don't exist
mkdir -p "$SESSION_DIR" "$RESURRECT_DIR"
show_usage() {
cat <<EOF
Tmux Session Manager - Manage multiple named tmux sessions with persistence
Usage: tmux-session-manager [command] [options]
Commands:
list List all saved session configurations
create <name> Create or switch to a named session
save <name> Manually save current session state
restore <name> Restore a specific named session
delete <name> Delete a named session configuration
restore-all Restore all sessions at once
auto-save Automatically save all active sessions (for cron/timer)
Examples:
tmux-session-manager create main # Create/switch to 'main' session
tmux-session-manager save work # Save current session as 'work'
tmux-session-manager restore dev # Restore 'dev' session
tmux-session-manager list # List all sessions
EOF
}
last_snapshot_sessions() {
local last_file="$RESURRECT_DIR/last"
if [ ! -f "$last_file" ]; then
return 0
fi
awk -F '\t' '$1 == "pane" { print $2 }' "$last_file" | sort -u
}
session_path_matches_name() {
local session="$1"
local desired_name="$2"
local desired_basename="$desired_name"
local path=""
if [[ "$desired_name" == _* ]]; then
desired_basename=".${desired_name#_}"
fi
while IFS= read -r path; do
[ -n "$path" ] || continue
if [ "$(basename "$path")" = "$desired_basename" ]; then
return 0
fi
done < <(tmux list-panes -t "=${session}" -F "#{pane_current_path}" 2>/dev/null)
if [ "$desired_name" = "main" ]; then
while IFS= read -r path; do
[ -n "$path" ] || continue
if [ "$path" = "$HOME" ]; then
return 0
fi
done < <(tmux list-panes -t "=${session}" -F "#{pane_current_path}" 2>/dev/null)
fi
return 1
}
run_resurrect_restore() {
local restore_script
restore_script="$(tmux show-options -gv @resurrect-restore-script-path 2>/dev/null || true)"
if [ -z "$restore_script" ]; then
echo "Error: Could not determine resurrect restore script path"
return 1
fi
tmux run-shell "$restore_script"
}
run_resurrect_save() {
local save_script
save_script="$(tmux show-options -gv @resurrect-save-script-path 2>/dev/null || true)"
if [ -z "$save_script" ]; then
echo "Error: Could not determine resurrect save script path"
return 1
fi
tmux run-shell "$save_script quiet"
}
save_session() {
local session_name="$1"
# Get the current session if not specified
if [ -z "$session_name" ]; then
session_name=$(tmux display-message -p '#{session_name}' 2>/dev/null || echo "")
if [ -z "$session_name" ]; then
echo "Error: No active tmux session found"
return 1
fi
fi
# Check if session exists
if ! tmux has-session -t "$session_name" 2>/dev/null; then
echo "Error: Session '$session_name' does not exist"
return 1
fi
# Save session using resurrect script
run_resurrect_save
echo "Saving session '$session_name'..."
sleep 1
# Also create a backup of the session state
local backup_file="$SESSION_DIR/${session_name}.state"
tmux capture-pane -t "$session_name" -p > "$backup_file" || true
echo "Session '$session_name' saved"
}
restore_session() {
local session_name="$1"
local bootstrap_session="__tsm_restore_bootstrap"
local trigger_session=""
local restored=0
local renamed=0
local candidate_session=""
local -a before_sessions=()
local -a after_sessions=()
local -a new_sessions=()
if [ -z "$session_name" ]; then
echo "Error: Session name required"
show_usage
return 1
fi
# Use resurrect to restore the most recent snapshot.
# A target session might not currently exist, so trigger restore from any live session.
echo "Restoring session '$session_name' from latest resurrect save..."
while IFS= read -r session; do
[ -n "$session" ] && before_sessions+=("$session")
done < <(tmux list-sessions -F "#{session_name}" 2>/dev/null || true)
if ! tmux list-sessions >/dev/null 2>&1; then
tmux new-session -d -s "$bootstrap_session"
trigger_session="$bootstrap_session"
else
while IFS= read -r session; do
if [ -n "$session" ]; then
trigger_session="$session"
break
fi
done < <(tmux list-sessions -F "#{session_name}" 2>/dev/null)
fi
if [ -z "$trigger_session" ]; then
echo "Error: Could not find a tmux session to trigger restore"
return 1
fi
run_resurrect_restore
for _ in $(seq 1 20); do
if tmux has-session -t "$session_name" 2>/dev/null; then
restored=1
break
fi
sleep 0.25
done
while IFS= read -r session; do
[ -n "$session" ] && after_sessions+=("$session")
done < <(tmux list-sessions -F "#{session_name}" 2>/dev/null || true)
if [ "$restored" -eq 0 ]; then
for session in "${after_sessions[@]}"; do
local existed=0
for before in "${before_sessions[@]}"; do
if [ "$session" = "$before" ]; then
existed=1
break
fi
done
if [ "$existed" -eq 0 ]; then
new_sessions+=("$session")
fi
done
# Give resurrect a moment to fully materialize pane paths before matching.
for _ in $(seq 1 20); do
candidate_session=""
for session in "${new_sessions[@]}"; do
if session_path_matches_name "$session" "$session_name"; then
candidate_session="$session"
break
fi
done
# If no brand-new session matched, fall back to any current session.
# This handles cases where restore reused existing numeric sessions.
if [ -z "$candidate_session" ]; then
for session in "${after_sessions[@]}"; do
if [ "$session" = "$session_name" ]; then
continue
fi
if session_path_matches_name "$session" "$session_name"; then
candidate_session="$session"
break
fi
done
fi
if [ -n "$candidate_session" ]; then
break
fi
sleep 0.25
done
if [ -z "$candidate_session" ] && [ "${#new_sessions[@]}" -eq 1 ]; then
candidate_session="${new_sessions[0]}"
fi
if [ -n "$candidate_session" ] && ! tmux has-session -t "$session_name" 2>/dev/null; then
tmux rename-session -t "=${candidate_session}" "$session_name"
restored=1
renamed=1
fi
fi
if [ "$trigger_session" = "$bootstrap_session" ] && tmux has-session -t "$bootstrap_session" 2>/dev/null; then
tmux kill-session -t "$bootstrap_session" 2>/dev/null || true
fi
if [ "$restored" -eq 1 ]; then
if [ "$renamed" -eq 1 ]; then
echo "Session '$session_name' restored by renaming '$candidate_session'"
else
echo "Session '$session_name' restored and available"
fi
else
local snapshot_sessions
snapshot_sessions="$(last_snapshot_sessions | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
if [ -n "$snapshot_sessions" ]; then
echo "Restore was triggered, but '$session_name' was not found in the latest snapshot (contains: $snapshot_sessions)"
else
echo "Restore was triggered, but '$session_name' was not found and no snapshot sessions were detected"
fi
return 1
fi
}
create_session() {
local session_name="$1"
if [ -z "$session_name" ]; then
echo "Error: Session name required"
show_usage
return 1
fi
# Check if session already exists
if tmux has-session -t "$session_name" 2>/dev/null; then
echo "Session '$session_name' already exists, attaching..."
tmux attach-session -t "=${session_name}"
else
echo "Creating new session '$session_name'..."
tmux new-session -d -s "$session_name"
tmux send-keys -t "$session_name" "# Session: $session_name" Enter
echo "Session '$session_name' created"
tmux attach-session -t "=${session_name}"
fi
}
list_sessions() {
echo "Available tmux sessions:"
tmux list-sessions -F "#{session_name}: #{session_windows} windows" 2>/dev/null || echo "No tmux sessions found"
echo ""
echo "Session names in latest resurrect snapshot:"
local snapshot_sessions
snapshot_sessions="$(last_snapshot_sessions)"
if [ -n "$snapshot_sessions" ]; then
echo "$snapshot_sessions"
else
echo "No resurrect snapshot found"
fi
echo ""
echo "Saved session labels (local backups):"
if [ -d "$SESSION_DIR" ]; then
ls -1 "$SESSION_DIR" 2>/dev/null | sed 's/.state$//' | sort | uniq || echo "No saved sessions"
else
echo "No saved sessions"
fi
}
delete_session() {
local session_name="$1"
if [ -z "$session_name" ]; then
echo "Error: Session name required"
return 1
fi
# Kill the tmux session if it exists
if tmux has-session -t "$session_name" 2>/dev/null; then
tmux kill-session -t "=${session_name}"
echo "Killed tmux session '$session_name'"
fi
# Remove saved state files
rm -f "$SESSION_DIR/${session_name}.state"
echo "Deleted session configuration '$session_name'"
}
restore_all_sessions() {
local bootstrap_session="__tsm_restore_bootstrap"
local trigger_session=""
echo "Restoring all sessions from latest resurrect save..."
if ! tmux list-sessions >/dev/null 2>&1; then
tmux new-session -d -s "$bootstrap_session"
trigger_session="$bootstrap_session"
else
while IFS= read -r session; do
if [ -n "$session" ]; then
trigger_session="$session"
break
fi
done < <(tmux list-sessions -F "#{session_name}" 2>/dev/null)
fi
if [ -z "$trigger_session" ]; then
echo "Error: Could not find a tmux session to trigger restore"
return 1
fi
run_resurrect_restore
sleep 1
if [ "$trigger_session" = "$bootstrap_session" ] && tmux has-session -t "$bootstrap_session" 2>/dev/null; then
tmux kill-session -t "=${bootstrap_session}" 2>/dev/null || true
fi
echo "Restore triggered for all sessions"
}
auto_save() {
echo "Auto-saving all active tmux sessions..."
while IFS= read -r session; do
save_session "$session" || true
done < <(tmux list-sessions -F "#{session_name}" 2>/dev/null)
echo "All sessions auto-saved"
}
# Main command dispatcher
main() {
local command="${1:-list}"
local arg="${2:-}"
case "$command" in
list)
list_sessions
;;
create)
create_session "$arg"
;;
save)
save_session "$arg"
;;
restore)
restore_session "$arg"
;;
delete)
delete_session "$arg"
;;
restore-all)
restore_all_sessions
;;
auto-save)
auto_save
;;
--help|-h|help)
show_usage
;;
*)
echo "Unknown command: $command"
show_usage
return 1
;;
esac
}
main "$@"