Initial commit
This commit is contained in:
262
tools/find-sessions.sh
Executable file
262
tools/find-sessions.sh
Executable file
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# find-sessions.sh - Discover and list tmux sessions on sockets
|
||||
#
|
||||
# PURPOSE:
|
||||
# Find and display information about tmux sessions, either on a specific
|
||||
# socket or by scanning all sockets in a directory. Useful for discovering
|
||||
# what agent sessions are currently running.
|
||||
#
|
||||
# HOW IT WORKS:
|
||||
# 1. Identify target socket(s) based on command-line options
|
||||
# 2. Query each socket for running tmux sessions
|
||||
# 3. Display session info: name, attach status, creation time
|
||||
# 4. Optionally filter by session name substring
|
||||
#
|
||||
# USE CASES:
|
||||
# - List all agent sessions across multiple sockets
|
||||
# - Find a specific session by name (partial matching)
|
||||
# - Check if a session is attached or detached
|
||||
# - See when sessions were created
|
||||
# - Enumerate sessions before cleanup
|
||||
#
|
||||
# EXAMPLES:
|
||||
# # List sessions on default tmux socket
|
||||
# ./find-sessions.sh
|
||||
#
|
||||
# # List sessions on specific socket by name
|
||||
# ./find-sessions.sh -L mysocket
|
||||
#
|
||||
# # List sessions on specific socket by path
|
||||
# ./find-sessions.sh -S /tmp/claude-tmux-sockets/claude.sock
|
||||
#
|
||||
# # Scan all sockets in directory
|
||||
# ./find-sessions.sh --all
|
||||
#
|
||||
# # Find sessions with "python" in the name
|
||||
# ./find-sessions.sh --all -q python
|
||||
#
|
||||
# DEPENDENCIES:
|
||||
# - bash (with arrays, [[, functions)
|
||||
# - tmux (for list-sessions)
|
||||
# - grep (for filtering by query)
|
||||
#
|
||||
|
||||
# Bash strict mode:
|
||||
# -e: Exit immediately if any command fails
|
||||
# -u: Treat unset variables as errors
|
||||
# -o pipefail: Pipe fails if any command in pipeline fails
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern]
|
||||
|
||||
List tmux sessions on a socket (default tmux socket if none provided).
|
||||
|
||||
Options:
|
||||
-L, --socket tmux socket name (passed to tmux -L)
|
||||
-S, --socket-path tmux socket path (passed to tmux -S)
|
||||
-A, --all scan all sockets under CLAUDE_TMUX_SOCKET_DIR
|
||||
-q, --query case-insensitive substring to filter session names
|
||||
-h, --help show this help
|
||||
USAGE
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Default Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Socket specification (mutually exclusive)
|
||||
socket_name="" # tmux socket name (for tmux -L)
|
||||
socket_path="" # tmux socket path (for tmux -S)
|
||||
|
||||
# Filtering and scanning options
|
||||
query="" # substring to filter session names (case-insensitive)
|
||||
scan_all=false # whether to scan all sockets in socket_dir
|
||||
|
||||
# Directory containing agent tmux sockets
|
||||
# Priority: CLAUDE_TMUX_SOCKET_DIR env var > TMPDIR/claude-tmux-sockets > /tmp/claude-tmux-sockets
|
||||
socket_dir="${CLAUDE_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/claude-tmux-sockets}"
|
||||
|
||||
# ============================================================================
|
||||
# Parse Command-Line Arguments
|
||||
# ============================================================================
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-L|--socket) socket_name="${2-}"; shift 2 ;; # Socket name mode
|
||||
-S|--socket-path) socket_path="${2-}"; shift 2 ;; # Socket path mode
|
||||
-A|--all) scan_all=true; shift ;; # Scan all mode
|
||||
-q|--query) query="${2-}"; shift 2 ;; # Filter by name
|
||||
-h|--help) usage; exit 0 ;; # Show help
|
||||
*) echo "Unknown option: $1" >&2; usage; exit 1 ;; # Error on unknown
|
||||
esac
|
||||
done
|
||||
|
||||
# ============================================================================
|
||||
# Validate Options
|
||||
# ============================================================================
|
||||
|
||||
# Cannot use --all with specific socket options (they're mutually exclusive)
|
||||
if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then
|
||||
echo "Cannot combine --all with -L or -S" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cannot use both -L and -S at the same time (different socket types)
|
||||
if [[ -n "$socket_name" && -n "$socket_path" ]]; then
|
||||
echo "Use either -L or -S, not both" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check that tmux is installed and available in PATH
|
||||
if ! command -v tmux >/dev/null 2>&1; then
|
||||
echo "tmux not found in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Function: list_sessions
|
||||
# ============================================================================
|
||||
# Query a tmux socket for sessions and display formatted output
|
||||
#
|
||||
# Arguments:
|
||||
# $1: Label describing the socket (for display purposes)
|
||||
# $@: Remaining args are passed to tmux command (e.g., -L name or -S path)
|
||||
#
|
||||
# Returns:
|
||||
# 0 if sessions found (or no sessions after filtering)
|
||||
# 1 if tmux server not running on this socket
|
||||
#
|
||||
# Output format:
|
||||
# Sessions on <label>:
|
||||
# - session-name (attached|detached, started <timestamp>)
|
||||
#
|
||||
list_sessions() {
|
||||
# Store label for display, then shift to get remaining args
|
||||
local label="$1"; shift
|
||||
# Build tmux command array with remaining args (socket options)
|
||||
local tmux_cmd=(tmux "$@")
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Query tmux for session information
|
||||
# --------------------------------------------------------------------------
|
||||
# tmux list-sessions -F specifies output format:
|
||||
# #{session_name}: Name of the session
|
||||
# #{session_attached}: 1 if attached, 0 if detached
|
||||
# #{session_created_string}: Human-readable creation timestamp
|
||||
# Tab-separated output for easy parsing
|
||||
# 2>/dev/null: Suppress errors if no server running
|
||||
# if !: Check if command failed (no server = exit code 1)
|
||||
#
|
||||
if ! sessions="$("${tmux_cmd[@]}" list-sessions -F '#{session_name}\t#{session_attached}\t#{session_created_string}' 2>/dev/null)"; then
|
||||
echo "No tmux server found on $label" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Filter sessions by query if provided
|
||||
# --------------------------------------------------------------------------
|
||||
# -i: Case-insensitive search
|
||||
# --: End of options (allows query starting with -)
|
||||
# || true: Don't fail if grep finds no matches (returns exit 1)
|
||||
#
|
||||
if [[ -n "$query" ]]; then
|
||||
sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)"
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Handle case where no sessions match query
|
||||
# --------------------------------------------------------------------------
|
||||
if [[ -z "$sessions" ]]; then
|
||||
echo "No sessions found on $label"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Format and display session information
|
||||
# --------------------------------------------------------------------------
|
||||
echo "Sessions on $label:"
|
||||
# Parse tab-separated values into name, attached, created
|
||||
# IFS=$'\t': Set field separator to tab character
|
||||
# read -r: Don't interpret backslashes (raw input)
|
||||
printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do
|
||||
# Convert attached flag (1/0) to human-readable label
|
||||
attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached")
|
||||
# Display formatted session info
|
||||
printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Execution: Scan All Mode
|
||||
# ============================================================================
|
||||
# Scan all socket files in socket_dir and list sessions on each
|
||||
|
||||
if [[ "$scan_all" == true ]]; then
|
||||
# Verify socket directory exists
|
||||
if [[ ! -d "$socket_dir" ]]; then
|
||||
echo "Socket directory not found: $socket_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Enumerate all files in socket directory
|
||||
# --------------------------------------------------------------------------
|
||||
# shopt -s nullglob: If no matches, glob expands to empty array (not literal *)
|
||||
# This prevents errors when directory is empty
|
||||
shopt -s nullglob
|
||||
sockets=("$socket_dir"/*)
|
||||
shopt -u nullglob # Restore default behavior
|
||||
|
||||
# Check if any files were found
|
||||
if [[ "${#sockets[@]}" -eq 0 ]]; then
|
||||
echo "No sockets found under $socket_dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Iterate through all socket files and list sessions
|
||||
# --------------------------------------------------------------------------
|
||||
# Track exit code: 0 = all succeeded, 1 = at least one failed
|
||||
exit_code=0
|
||||
for sock in "${sockets[@]}"; do
|
||||
# -S test: Check if file is a socket (not a regular file or directory)
|
||||
# Skip non-socket files (e.g., .DS_Store, temp files)
|
||||
if [[ ! -S "$sock" ]]; then
|
||||
continue
|
||||
fi
|
||||
# Call list_sessions for this socket
|
||||
# || exit_code=$?: Capture failure exit code but continue loop
|
||||
list_sessions "socket path '$sock'" -S "$sock" || exit_code=$?
|
||||
done
|
||||
# Exit with captured exit code (0 if all succeeded, 1 if any failed)
|
||||
exit "$exit_code"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Main Execution: Single Socket Mode
|
||||
# ============================================================================
|
||||
# List sessions on a specific socket (or default socket)
|
||||
|
||||
# Start with base tmux command
|
||||
tmux_cmd=(tmux)
|
||||
socket_label="default socket"
|
||||
|
||||
# Add socket-specific options based on user input
|
||||
if [[ -n "$socket_name" ]]; then
|
||||
# -L mode: Named socket (e.g., tmux -L mysocket)
|
||||
tmux_cmd+=(-L "$socket_name")
|
||||
socket_label="socket name '$socket_name'"
|
||||
elif [[ -n "$socket_path" ]]; then
|
||||
# -S mode: Socket path (e.g., tmux -S /tmp/my.sock)
|
||||
tmux_cmd+=(-S "$socket_path")
|
||||
socket_label="socket path '$socket_path'"
|
||||
fi
|
||||
# If neither set, use default tmux socket (no additional flags)
|
||||
|
||||
# Call list_sessions with constructed command
|
||||
# ${tmux_cmd[@]:1}: Array slice starting at index 1 (skips "tmux" itself)
|
||||
# This passes only the flags (e.g., "-L mysocket" or "-S /path")
|
||||
list_sessions "$socket_label" "${tmux_cmd[@]:1}"
|
||||
Reference in New Issue
Block a user