tmux
Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
Install
npx skills add https://github.com/Navinspire-ia/navin/tree/main/navin/skills/tmux
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install navinspire-ia-navin@llmmart
git clone https://github.com/Navinspire-ia/navin.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole navinspire-ia/navin collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
tmux Skill
Use tmux only when you need an interactive TTY. Prefer exec background mode for long-running, non-interactive tasks.
Quickstart (isolated socket, exec tool)
SOCKET_DIR="${NAVIN_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/navin-tmux-sockets}"
mkdir -p "$SOCKET_DIR"
SOCKET="$SOCKET_DIR/navin.sock"
SESSION=navin-python
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200
After starting a session, always print monitor commands:
To monitor:
tmux -S "$SOCKET" attach -t "$SESSION"
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200
Socket convention
- Use
NAVIN_TMUX_SOCKET_DIRenvironment variable. - Default socket path:
"$NAVIN_TMUX_SOCKET_DIR/navin.sock".
Targeting panes and naming
- Target format:
session:window.pane(defaults to:0.0). - Keep names short; avoid spaces.
- Inspect:
tmux -S "$SOCKET" list-sessions,tmux -S "$SOCKET" list-panes -a.
Finding sessions
- List sessions on your socket:
{baseDir}/scripts/find-sessions.sh -S "$SOCKET". - Scan all sockets:
{baseDir}/scripts/find-sessions.sh --all(usesNAVIN_TMUX_SOCKET_DIR).
Sending input safely
- Prefer literal sends:
tmux -S "$SOCKET" send-keys -t target -l -- "$cmd". - Control keys:
tmux -S "$SOCKET" send-keys -t target C-c.
Watching output
- Capture recent history:
tmux -S "$SOCKET" capture-pane -p -J -t target -S -200. - Wait for prompts:
{baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern'. - Attaching is OK; detach with
Ctrl+b d.
Spawning processes
- For python REPLs, set
PYTHON_BASIC_REPL=1(non-basic REPL breaks send-keys flows).
Windows / WSL
- tmux is supported on macOS/Linux. On Windows, use WSL and install tmux inside WSL.
- This skill is gated to
darwin/linuxand requirestmuxon PATH.
Orchestrating Coding Agents (Codex, Claude Code)
tmux excels at running multiple coding agents in parallel:
SOCKET="${TMPDIR:-/tmp}/codex-army.sock"
# Create multiple sessions
for i in 1 2 3 4 5; do
tmux -S "$SOCKET" new-session -d -s "agent-$i"
done
# Launch agents in different workdirs
tmux -S "$SOCKET" send-keys -t agent-1 "cd /tmp/project1 && codex --yolo 'Fix bug X'" Enter
tmux -S "$SOCKET" send-keys -t agent-2 "cd /tmp/project2 && codex --yolo 'Fix bug Y'" Enter
# Poll for completion (check if prompt returned)
for sess in agent-1 agent-2; do
if tmux -S "$SOCKET" capture-pane -p -t "$sess" -S -3 | grep -q "❯"; then
echo "$sess: DONE"
else
echo "$sess: Running..."
fi
done
# Get full output from completed session
tmux -S "$SOCKET" capture-pane -p -t agent-1 -S -500
Tips:
- Use separate git worktrees for parallel fixes (no branch conflicts)
pnpm installfirst before running codex in fresh clones- Check for shell prompt (
❯or$) to detect completion - Codex needs
--yoloor--full-autofor non-interactive fixes
Cleanup
- Kill a session:
tmux -S "$SOCKET" kill-session -t "$SESSION". - Kill all sessions on a socket:
tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t. - Remove everything on the private socket:
tmux -S "$SOCKET" kill-server.
Helper: wait-for-text.sh
{baseDir}/scripts/wait-for-text.sh polls a pane for a regex (or fixed string) with a timeout.
{baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
-t/--targetpane target (required)-p/--patternregex to match (required); add-Ffor fixed string-Ttimeout seconds (integer, default 15)-ipoll interval seconds (default 0.5)-lhistory lines to search (integer, default 1000)
Files (navin)
-
scripts
-
find-sessions.sh 2.9 KB
#!/usr/bin/env bash 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 NAVIN_TMUX_SOCKET_DIR -q, --query case-insensitive substring to filter session names -h, --help show this help USAGE } socket_name="" socket_path="" query="" scan_all=false socket_dir="${NAVIN_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/navin-tmux-sockets}" while [[ $# -gt 0 ]]; do case "$1" in -L|--socket) socket_name="${2-}"; shift 2 ;; -S|--socket-path) socket_path="${2-}"; shift 2 ;; -A|--all) scan_all=true; shift ;; -q|--query) query="${2-}"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage; exit 1 ;; esac done if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then echo "Cannot combine --all with -L or -S" >&2 exit 1 fi if [[ -n "$socket_name" && -n "$socket_path" ]]; then echo "Use either -L or -S, not both" >&2 exit 1 fi if ! command -v tmux >/dev/null 2>&1; then echo "tmux not found in PATH" >&2 exit 1 fi list_sessions() { local label="$1"; shift local tmux_cmd=(tmux "$@") 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 if [[ -n "$query" ]]; then sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)" fi if [[ -z "$sessions" ]]; then echo "No sessions found on $label" return 0 fi echo "Sessions on $label:" printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached") printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created" done } if [[ "$scan_all" == true ]]; then if [[ ! -d "$socket_dir" ]]; then echo "Socket directory not found: $socket_dir" >&2 exit 1 fi shopt -s nullglob sockets=("$socket_dir"/*) shopt -u nullglob if [[ "${#sockets[@]}" -eq 0 ]]; then echo "No sockets found under $socket_dir" >&2 exit 1 fi exit_code=0 for sock in "${sockets[@]}"; do if [[ ! -S "$sock" ]]; then continue fi list_sessions "socket path '$sock'" -S "$sock" || exit_code=$? done exit "$exit_code" fi tmux_cmd=(tmux) socket_label="default socket" if [[ -n "$socket_name" ]]; then tmux_cmd+=(-L "$socket_name") socket_label="socket name '$socket_name'" elif [[ -n "$socket_path" ]]; then tmux_cmd+=(-S "$socket_path") socket_label="socket path '$socket_path'" fi list_sessions "$socket_label" "${tmux_cmd[@]:1}" -
wait-for-text.sh 2.1 KB
#!/usr/bin/env bash set -euo pipefail usage() { cat <<'USAGE' Usage: wait-for-text.sh -t target -p pattern [options] Poll a tmux pane for text and exit when found. Options: -t, --target tmux target (session:window.pane), required -p, --pattern regex pattern to look for, required -F, --fixed treat pattern as a fixed string (grep -F) -T, --timeout seconds to wait (integer, default: 15) -i, --interval poll interval in seconds (default: 0.5) -l, --lines number of history lines to inspect (integer, default: 1000) -h, --help show this help USAGE } target="" pattern="" grep_flag="-E" timeout=15 interval=0.5 lines=1000 while [[ $# -gt 0 ]]; do case "$1" in -t|--target) target="${2-}"; shift 2 ;; -p|--pattern) pattern="${2-}"; shift 2 ;; -F|--fixed) grep_flag="-F"; shift ;; -T|--timeout) timeout="${2-}"; shift 2 ;; -i|--interval) interval="${2-}"; shift 2 ;; -l|--lines) lines="${2-}"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "Unknown option: $1" >&2; usage; exit 1 ;; esac done if [[ -z "$target" || -z "$pattern" ]]; then echo "target and pattern are required" >&2 usage exit 1 fi if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then echo "timeout must be an integer number of seconds" >&2 exit 1 fi if ! [[ "$lines" =~ ^[0-9]+$ ]]; then echo "lines must be an integer" >&2 exit 1 fi if ! command -v tmux >/dev/null 2>&1; then echo "tmux not found in PATH" >&2 exit 1 fi # End time in epoch seconds (integer, good enough for polling) start_epoch=$(date +%s) deadline=$((start_epoch + timeout)) while true; do # -J joins wrapped lines, -S uses negative index to read last N lines pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)" if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then exit 0 fi now=$(date +%s) if (( now >= deadline )); then echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2 echo "Last ${lines} lines from $target:" >&2 printf '%s\n' "$pane_text" >&2 exit 1 fi sleep "$interval" done
-
-
SKILL.md 4 KB
--- name: tmux description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output. metadata: {"navin":{"emoji":"🧵","os":["darwin","linux"],"requires":{"bins":["tmux"]}}} --- # tmux Skill Use tmux only when you need an interactive TTY. Prefer exec background mode for long-running, non-interactive tasks. ## Quickstart (isolated socket, exec tool) ```bash SOCKET_DIR="${NAVIN_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/navin-tmux-sockets}" mkdir -p "$SOCKET_DIR" SOCKET="$SOCKET_DIR/navin.sock" SESSION=navin-python tmux -S "$SOCKET" new -d -s "$SESSION" -n shell tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'PYTHON_BASIC_REPL=1 python3 -q' Enter tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 ``` After starting a session, always print monitor commands: ``` To monitor: tmux -S "$SOCKET" attach -t "$SESSION" tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 ``` ## Socket convention - Use `NAVIN_TMUX_SOCKET_DIR` environment variable. - Default socket path: `"$NAVIN_TMUX_SOCKET_DIR/navin.sock"`. ## Targeting panes and naming - Target format: `session:window.pane` (defaults to `:0.0`). - Keep names short; avoid spaces. - Inspect: `tmux -S "$SOCKET" list-sessions`, `tmux -S "$SOCKET" list-panes -a`. ## Finding sessions - List sessions on your socket: `{baseDir}/scripts/find-sessions.sh -S "$SOCKET"`. - Scan all sockets: `{baseDir}/scripts/find-sessions.sh --all` (uses `NAVIN_TMUX_SOCKET_DIR`). ## Sending input safely - Prefer literal sends: `tmux -S "$SOCKET" send-keys -t target -l -- "$cmd"`. - Control keys: `tmux -S "$SOCKET" send-keys -t target C-c`. ## Watching output - Capture recent history: `tmux -S "$SOCKET" capture-pane -p -J -t target -S -200`. - Wait for prompts: `{baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern'`. - Attaching is OK; detach with `Ctrl+b d`. ## Spawning processes - For python REPLs, set `PYTHON_BASIC_REPL=1` (non-basic REPL breaks send-keys flows). ## Windows / WSL - tmux is supported on macOS/Linux. On Windows, use WSL and install tmux inside WSL. - This skill is gated to `darwin`/`linux` and requires `tmux` on PATH. ## Orchestrating Coding Agents (Codex, Claude Code) tmux excels at running multiple coding agents in parallel: ```bash SOCKET="${TMPDIR:-/tmp}/codex-army.sock" # Create multiple sessions for i in 1 2 3 4 5; do tmux -S "$SOCKET" new-session -d -s "agent-$i" done # Launch agents in different workdirs tmux -S "$SOCKET" send-keys -t agent-1 "cd /tmp/project1 && codex --yolo 'Fix bug X'" Enter tmux -S "$SOCKET" send-keys -t agent-2 "cd /tmp/project2 && codex --yolo 'Fix bug Y'" Enter # Poll for completion (check if prompt returned) for sess in agent-1 agent-2; do if tmux -S "$SOCKET" capture-pane -p -t "$sess" -S -3 | grep -q "❯"; then echo "$sess: DONE" else echo "$sess: Running..." fi done # Get full output from completed session tmux -S "$SOCKET" capture-pane -p -t agent-1 -S -500 ``` **Tips:** - Use separate git worktrees for parallel fixes (no branch conflicts) - `pnpm install` first before running codex in fresh clones - Check for shell prompt (`❯` or `$`) to detect completion - Codex needs `--yolo` or `--full-auto` for non-interactive fixes ## Cleanup - Kill a session: `tmux -S "$SOCKET" kill-session -t "$SESSION"`. - Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`. - Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`. ## Helper: wait-for-text.sh `{baseDir}/scripts/wait-for-text.sh` polls a pane for a regex (or fixed string) with a timeout. ```bash {baseDir}/scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000] ``` - `-t`/`--target` pane target (required) - `-p`/`--pattern` regex to match (required); add `-F` for fixed string - `-T` timeout seconds (integer, default 15) - `-i` poll interval seconds (default 0.5) - `-l` history lines to search (integer, default 1000)
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.