diff options
| author | Craig Jennings <c@cjennings.net> | 2026-05-19 12:24:40 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-05-19 12:24:40 -0500 |
| commit | 5d8d9df7d1b95c1a6a7bf25ddf57652c86f110b3 (patch) | |
| tree | 6c4a5c1fe10db7ea2413dc514fb3e64ed45df71c /dotfiles/common | |
| parent | 7e5651e3b074366ab35321b550ac06f1823b0b3d (diff) | |
| download | archsetup-5d8d9df7d1b95c1a6a7bf25ddf57652c86f110b3.tar.gz archsetup-5d8d9df7d1b95c1a6a7bf25ddf57652c86f110b3.zip | |
feat(tmux-util): add script skeleton and reap subcommand
A new utility in dotfiles/common/.local/bin/ for managing tmux sessions. The eventual plan covers six subcommands (go, pick, ls, find, reap, rename). This commit ships the skeleton, the dispatch + help, and the first subcommand: reap.
reap walks every unattached tmux session whose name doesn't match $TMUX_UTIL_REAP_SKIP (default `^aiv-`), sends SIGHUP to each pane's PID (the same signal that fires when you close a terminal window), waits up to three seconds for the session to wind down, and falls back to `tmux kill-session` if anything's still alive.
Tests live under tests/tmux-util/ with the same fake-binary-on-PATH pattern layout-navigate uses. fake-tmux reads canned session state from a file and records every invocation. fake-kill records signal calls without sending them. fake-sleep is a no-op so tests don't actually wait. 14 tests cover Normal / Boundary cases for dispatch + reap. Run them with:
cd tests && python3 -m unittest tmux-util.test_tmux_util
The other five subcommands stub out for now and exit non-zero with "not implemented yet" so future TDD turns can drop them in one at a time.
Diffstat (limited to 'dotfiles/common')
| -rwxr-xr-x | dotfiles/common/.local/bin/tmux-util | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/dotfiles/common/.local/bin/tmux-util b/dotfiles/common/.local/bin/tmux-util new file mode 100755 index 0000000..10c6fbb --- /dev/null +++ b/dotfiles/common/.local/bin/tmux-util @@ -0,0 +1,107 @@ +#!/bin/bash +# tmux-util — small utilities for managing tmux sessions. +# +# Subcommands: +# go <name> attach to <name> if it exists, otherwise create it +# pick fzf-driven session switcher +# ls opinionated session listing (attached, idle, windows, cwd) +# find <pattern> locate panes whose foreground process matches <pattern> +# reap gracefully exit every unattached session (skipping aiv-*) +# rename fzf-pick a session, prompt for a new name, rename it +# +# Run with no arguments to print this help. + +set -uo pipefail + +# ----------------------------------------------------------------------------- +# Usage +# ----------------------------------------------------------------------------- + +usage() { + cat <<'EOF' +Usage: tmux-util <subcommand> [args] + +Subcommands: + go <name> Attach to session <name>; create it (in $PWD) if missing. + pick Fzf-driven session switcher. + ls List sessions with attached / idle / window / cwd columns. + find <pattern> Locate panes whose foreground process matches <pattern>. + reap Send SIGHUP to every unattached session's panes and close + the session. Sessions matching $TMUX_UTIL_REAP_SKIP + (default: ^aiv-) are skipped. + rename Pick a session via fzf, prompt for a new name, rename it. + +Run with no arguments to print this help. +EOF +} + +# ----------------------------------------------------------------------------- +# Reap +# ----------------------------------------------------------------------------- + +cmd_reap() { + local skip="${TMUX_UTIL_REAP_SKIP:-^aiv-}" + local sessions + sessions=$(tmux list-sessions -F '#{session_name} #{session_attached}' 2>/dev/null \ + | awk '$2 == 0 {print $1}' \ + | grep -vE "$skip" || true) + + if [ -z "$sessions" ]; then + echo "No unattached sessions to reap." + return 0 + fi + + local s pids + while IFS= read -r s; do + [ -n "$s" ] || continue + echo "Reaping: $s" + pids=$(tmux list-panes -s -t "$s" -F '#{pane_pid}' 2>/dev/null) + if [ -n "$pids" ]; then + echo "$pids" | xargs -r kill -HUP + fi + # Give the session a moment to wind down naturally. + local i + for i in 1 2 3; do + tmux has-session -t "$s" 2>/dev/null || break + sleep 1 + done + if tmux has-session -t "$s" 2>/dev/null; then + echo " still alive, force killing" + tmux kill-session -t "$s" + fi + done <<<"$sessions" +} + +# ----------------------------------------------------------------------------- +# Dispatch +# ----------------------------------------------------------------------------- + +main() { + if [ "$#" -eq 0 ]; then + usage + return 0 + fi + + local sub="$1" + shift + + case "$sub" in + -h|--help|help) + usage + ;; + reap) + cmd_reap "$@" + ;; + go|pick|ls|find|rename) + echo "tmux-util: subcommand '$sub' is not implemented yet" >&2 + return 2 + ;; + *) + echo "tmux-util: unknown subcommand: $sub" >&2 + usage >&2 + return 2 + ;; + esac +} + +main "$@" |
