Claude Skill

tmux

Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download navinspire-ia-navin-navin_skills_tmux-e9c73a3.zip · 4 KB
Part of navinspire-ia/navin — 182 skills

Install

skills CLI npx skills add https://github.com/Navinspire-ia/navin/tree/main/navin/skills/tmux
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install navinspire-ia-navin@llmmart
Git 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_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:

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.

{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)
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.

No comments yet.

Reviews (0)

No reviews yet.

Related