Skill: brennerspear/tmux-controller (version 1.0.0)

--- name: tmux slug: tmux-controller version: 1.0.0 description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output. metadata: { "openclaw": { "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. ## Default Server — No Custom Sockets **Always use the default tmux server.** Do NOT use `-S` custom sockets. The user needs to `tmux attach` easily without knowing obscure socket paths. ## Session Naming **Convention:** `oc-${project}-${feature}` (e.g. `oc-knowhere-date-range-picker`, `oc-deck-auth-flow`) - `oc-` prefix = OpenClaw-managed, avoids collision with user sessions - Easy to find: `tmux ls | grep oc-` ## Quickstart ```bash SESSION=oc-myproject-feature tmux new-session -d -s "$SESSION" -c ~/projects/myproject tmux send-keys -t "$SESSION" 'claude --dangerously-skip-permissions' Enter tmux capture-pane -p -J -t "$SESSION" -S -200 ``` After starting a session, tell the user: ``` To monitor: tmux attach -t $SESSION ``` ## Targeting panes and naming - Target format: `session:window.pane` (defaults to `:0.0`). - Keep names short; avoid spaces. - Inspect: `tmux list-sessions`, `tmux list-panes -a`. ## Sending input safely - Prefer literal sends: `tmux send-keys -t target -l -- "$cmd"`. - Control keys: `tmux send-keys -t target C-c`. - For interactive TUI apps like Claude Code/Codex, **do not** append `Enter` in the same `send-keys`. These apps may treat a fast text+Enter sequence as paste/multi-line input and not submit. Send text and `Enter` as separate commands with a small delay: ```bash tmux send-keys -t target -l -- "$cmd" && sleep 0.1 && tmux send-keys -t target Enter ``` ## Watching output - Capture recent history: `tmux capture-pane -p -J -t target -S -200`. - 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). ## Orchestrating Coding Agents (Codex, Claude Code) tmux excels at running multiple coding agents in parallel: ```bash # Create sessions in different worktrees tmux new-session -d -s oc-project-fix1 -c ~/projects/project-fix1 tmux new-session -d -s oc-project-fix2 -c ~/projects/project-fix2 # Launch agents tmux send-keys -t oc-project-fix1 'claude --dangerously-skip-permissions' Enter tmux send-keys -t oc-project-fix2 'codex --full-auto' Enter # Send a prompt (text + Enter separated by delay) tmux send-keys -t oc-project-fix1 -l -- "Fix the date picker styling." && sleep 0.1 && tmux send-keys -t oc-project-fix1 Enter # Poll for completion (check if shell prompt returned) for sess in oc-project-fix1 oc-project-fix2; do if tmux capture-pane -p -t "$sess" -S -3 | grep -q "❯"; then echo "$sess: DONE" else echo "$sess: Running..." fi done # Get full output tmux capture-pane -p -t oc-project-fix1 -S -500 ``` **Tips:** - Use separate git worktrees for parallel fixes (no branch conflicts) - `bun install` / `pnpm install` first before running agents 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 kill-session -t "$SESSION"`. - Kill all OpenClaw sessions: `tmux ls -F '#{session_name}' | grep '^oc-' | xargs -n1 tmux kill-session -t`.

path: scripts/wait-for-text.sh
content: #!/usr/bin/env bash
# wait-for-text.sh — Poll a tmux pane for a regex match.
# Uses default tmux server (no custom sockets).
#
# Two timeout modes:
#   -T <seconds>        Hard timeout — fail after N seconds regardless
#   --stale <seconds>   Stale timeout — only timeout if pane output hasn't
#                       changed for N seconds (default: 300 = 5 min of no output)
#
# Usage:
#   wait-for-text.sh -t oc-project-feature -p '❯'                    # stale timeout (default 5 min)
#   wait-for-text.sh -t oc-project-feature -p '❯' --stale 600        # stale timeout 10 min
#   wait-for-text.sh -t oc-project-feature -p '❯' -T 60              # hard timeout 60s

set -euo pipefail

target=""
pattern=""
grep_flag="-E"
hard_timeout=0
stale_timeout=300
interval=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)   hard_timeout="${2-}"; shift 2 ;;
    --stale)        stale_timeout="${2-}"; shift 2 ;;
    -i|--interval)  interval="${2-}"; shift 2 ;;
    -l|--lines)     lines="${2-}"; shift 2 ;;
    -h|--help)
      echo "Usage: wait-for-text.sh -t <session:win.pane> -p <pattern> [--stale secs | -T secs] [-i secs]"
      echo ""
      echo "  --stale 
  Timeout only if output unchanged for 
 seconds (default: 300)"
      echo "  -T 
       Hard timeout after 
 seconds regardless"
      exit 0
      ;;
    *) echo "Unknown option: $1" >&2; exit 1 ;;
  esac
done

[[ -z "$target" || -z "$pattern" ]] && { echo "target (-t) and pattern (-p) required" >&2; exit 1; }

start_epoch=$(date +%s)
last_output=""
last_change_epoch=$start_epoch

while true; do
  pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)"

  # Check for pattern match
  if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then
    exit 0
  fi

  now=$(date +%s)

  # Track output changes
  if [[ "$pane_text" != "$last_output" ]]; then
    last_output="$pane_text"
    last_change_epoch=$now
  fi

  # Hard timeout (if set)
  if (( hard_timeout > 0 )) && (( now - start_epoch >= hard_timeout )); then
    echo "Hard timeout after ${hard_timeout}s waiting for: $pattern" >&2
    exit 1
  fi

  # Stale timeout — only fire if output hasn't changed
  if (( stale_timeout > 0 )) && (( now - last_change_epoch >= stale_timeout )); then
    echo "Stale timeout: no output change for ${stale_timeout}s waiting for: $pattern" >&2
    exit 1
  fi

  sleep "$interval"
done


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)