docs: add tmux skill guidance
This commit is contained in:
@@ -21,6 +21,7 @@
|
|||||||
- Agent: add soft block-stream chunking (800–1200 chars default) with paragraph/newline preference.
|
- Agent: add soft block-stream chunking (800–1200 chars default) with paragraph/newline preference.
|
||||||
- Agent tools: scope the Discord tool to Discord surface runs.
|
- Agent tools: scope the Discord tool to Discord surface runs.
|
||||||
- Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style.
|
- Agent tools: format verbose tool summaries without brackets, with unique emojis and `tool: detail` style.
|
||||||
|
- Gateway: split server helpers + tests to improve isolation; add unit coverage for hooks/session utils/ws log.
|
||||||
- macOS Connections: move to sidebar + detail layout with structured sections and header actions.
|
- macOS Connections: move to sidebar + detail layout with structured sections and header actions.
|
||||||
- macOS onboarding: increase window height so the permissions page fits without scrolling.
|
- macOS onboarding: increase window height so the permissions page fits without scrolling.
|
||||||
- Thinking: default to low for reasoning-capable models when no /think or config default is set.
|
- Thinking: default to low for reasoning-capable models when no /think or config default is set.
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
- Skills: add Sheets/Docs examples to gog skill (#128) — thanks @mbelinky.
|
- Skills: add Sheets/Docs examples to gog skill (#128) — thanks @mbelinky.
|
||||||
- Skills: clarify bear-notes token + callback usage (#120) — thanks @tylerwince.
|
- Skills: clarify bear-notes token + callback usage (#120) — thanks @tylerwince.
|
||||||
- Skills: document Discord `sendMessage` media attachments and `to` format clarification.
|
- Skills: document Discord `sendMessage` media attachments and `to` format clarification.
|
||||||
|
- Skills: add tmux skill + interactive coding guidance in coding-agent.
|
||||||
- Gateway: document port configuration + multi-instance isolation.
|
- Gateway: document port configuration + multi-instance isolation.
|
||||||
- Onboarding/Config: add protocol notes for wizard + schema RPC.
|
- Onboarding/Config: add protocol notes for wizard + schema RPC.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ metadata: {"clawdis":{"emoji":"🧩","requires":{"anyBins":["claude","codex","op
|
|||||||
|
|
||||||
# Coding Agent (background-first)
|
# Coding Agent (background-first)
|
||||||
|
|
||||||
Use **bash background mode** for coding agents. Full programmatic control, no tmux needed.
|
Use **bash background mode** for non-interactive coding work. For interactive coding sessions, use the **tmux** skill (always, except very simple one-shot prompts).
|
||||||
|
|
||||||
## The Pattern: workdir + background
|
## The Pattern: workdir + background
|
||||||
|
|
||||||
@@ -138,9 +138,9 @@ bash workdir:~/project background:true command:"pi --provider openai --model gpt
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## tmux (optional)
|
## tmux (interactive sessions)
|
||||||
|
|
||||||
Only use tmux if you need persistence/interaction; otherwise prefer background mode.
|
Use the tmux skill for interactive coding sessions (always, except very simple one-shot prompts). Prefer bash background mode for non-interactive runs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
86
skills/tmux/SKILL.md
Normal file
86
skills/tmux/SKILL.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
name: tmux
|
||||||
|
description: Remote-control tmux sessions for interactive CLIs by sending keystrokes and scraping pane output.
|
||||||
|
metadata: {"clawdis":{"emoji":"🧵","os":["darwin","linux"],"requires":{"bins":["tmux"]}}}
|
||||||
|
---
|
||||||
|
|
||||||
|
# tmux Skill (Clawdis)
|
||||||
|
|
||||||
|
Use tmux only when you need an interactive TTY. Prefer bash background mode for long-running, non-interactive tasks.
|
||||||
|
|
||||||
|
## Quickstart (isolated socket, bash tool)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SOCKET_DIR="${CLAWDIS_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/clawdis-tmux-sockets}"
|
||||||
|
mkdir -p "$SOCKET_DIR"
|
||||||
|
SOCKET="$SOCKET_DIR/clawdis.sock"
|
||||||
|
SESSION=clawdis-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 `CLAWDIS_TMUX_SOCKET_DIR` (default `${TMPDIR:-/tmp}/clawdis-tmux-sockets`).
|
||||||
|
- Default socket path: `"$CLAWDIS_TMUX_SOCKET_DIR/clawdis.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 `CLAWDIS_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.
|
||||||
|
|
||||||
|
## 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)
|
||||||
112
skills/tmux/scripts/find-sessions.sh
Executable file
112
skills/tmux/scripts/find-sessions.sh
Executable file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/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 CLAWDIS_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="${CLAWDIS_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/clawdis-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}"
|
||||||
83
skills/tmux/scripts/wait-for-text.sh
Executable file
83
skills/tmux/scripts/wait-for-text.sh
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user