tanuki-telemetry 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/install.sh +65 -33
- package/package.json +1 -1
- package/skills/commands/compare-image.md +414 -0
- package/skills/commands/coordinate.md +283 -0
- package/skills/commands/live-browser.md +45 -0
- package/skills/commands/marathon.md +111 -0
- package/skills/commands/record.md +55 -0
- package/skills/commands/revive.md +144 -0
- package/skills/commands/speak.md +49 -0
- package/skills/scripts/record-browser.sh +100 -0
- package/skills/scripts/revive-watcher.sh +149 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Record agent-browser session as MP4 video
|
|
3
|
+
# Usage: record-browser.sh start [output.mp4] [interval_ms]
|
|
4
|
+
# record-browser.sh stop
|
|
5
|
+
# record-browser.sh status
|
|
6
|
+
|
|
7
|
+
PIDFILE="/tmp/browser-recorder.pid"
|
|
8
|
+
FRAMEDIR="/tmp/browser-recording-frames"
|
|
9
|
+
DEFAULT_OUTPUT="/tmp/browser-recording-$(date +%Y%m%d-%H%M%S).mp4"
|
|
10
|
+
DEFAULT_INTERVAL=1000 # 1 screenshot per second
|
|
11
|
+
|
|
12
|
+
case "${1:-status}" in
|
|
13
|
+
start)
|
|
14
|
+
OUTPUT="${2:-$DEFAULT_OUTPUT}"
|
|
15
|
+
INTERVAL="${3:-$DEFAULT_INTERVAL}"
|
|
16
|
+
|
|
17
|
+
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
|
18
|
+
echo "Recording already in progress (PID $(cat "$PIDFILE"))"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
rm -rf "$FRAMEDIR"
|
|
23
|
+
mkdir -p "$FRAMEDIR"
|
|
24
|
+
|
|
25
|
+
# Background process: take screenshots at interval
|
|
26
|
+
(
|
|
27
|
+
FRAME=0
|
|
28
|
+
while true; do
|
|
29
|
+
PADDED=$(printf "%06d" $FRAME)
|
|
30
|
+
agent-browser screenshot "$FRAMEDIR/frame-${PADDED}.png" 2>/dev/null
|
|
31
|
+
if [ $? -ne 0 ]; then
|
|
32
|
+
sleep 1
|
|
33
|
+
continue
|
|
34
|
+
fi
|
|
35
|
+
FRAME=$((FRAME + 1))
|
|
36
|
+
sleep "$(echo "scale=3; $INTERVAL/1000" | bc)"
|
|
37
|
+
done
|
|
38
|
+
) &
|
|
39
|
+
|
|
40
|
+
echo $! > "$PIDFILE"
|
|
41
|
+
echo "$OUTPUT" > /tmp/browser-recording-output
|
|
42
|
+
echo "Recording started (PID $!) → $OUTPUT"
|
|
43
|
+
echo "Taking screenshots every ${INTERVAL}ms"
|
|
44
|
+
echo "Run: record-browser.sh stop — to finish and create video"
|
|
45
|
+
;;
|
|
46
|
+
|
|
47
|
+
stop)
|
|
48
|
+
if [ ! -f "$PIDFILE" ]; then
|
|
49
|
+
echo "No recording in progress"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
PID=$(cat "$PIDFILE")
|
|
54
|
+
kill "$PID" 2>/dev/null
|
|
55
|
+
wait "$PID" 2>/dev/null
|
|
56
|
+
rm -f "$PIDFILE"
|
|
57
|
+
|
|
58
|
+
OUTPUT=$(cat /tmp/browser-recording-output 2>/dev/null || echo "$DEFAULT_OUTPUT")
|
|
59
|
+
FRAME_COUNT=$(ls "$FRAMEDIR"/frame-*.png 2>/dev/null | wc -l | tr -d ' ')
|
|
60
|
+
|
|
61
|
+
if [ "$FRAME_COUNT" -eq 0 ]; then
|
|
62
|
+
echo "No frames captured — nothing to encode"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
echo "Encoding $FRAME_COUNT frames → $OUTPUT"
|
|
67
|
+
|
|
68
|
+
# Use ffmpeg to create video from frames
|
|
69
|
+
ffmpeg -y -framerate 1 -i "$FRAMEDIR/frame-%06d.png" \
|
|
70
|
+
-c:v libx264 -pix_fmt yuv420p -preset fast \
|
|
71
|
+
-vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" \
|
|
72
|
+
"$OUTPUT" 2>/dev/null
|
|
73
|
+
|
|
74
|
+
if [ $? -eq 0 ]; then
|
|
75
|
+
SIZE=$(du -h "$OUTPUT" | cut -f1)
|
|
76
|
+
echo "✓ Recording saved: $OUTPUT ($SIZE, $FRAME_COUNT frames)"
|
|
77
|
+
rm -rf "$FRAMEDIR"
|
|
78
|
+
else
|
|
79
|
+
echo "✗ ffmpeg encoding failed"
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
;;
|
|
83
|
+
|
|
84
|
+
status)
|
|
85
|
+
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
|
86
|
+
FRAME_COUNT=$(ls "$FRAMEDIR"/frame-*.png 2>/dev/null | wc -l | tr -d ' ')
|
|
87
|
+
OUTPUT=$(cat /tmp/browser-recording-output 2>/dev/null)
|
|
88
|
+
echo "Recording in progress (PID $(cat "$PIDFILE"))"
|
|
89
|
+
echo " Frames captured: $FRAME_COUNT"
|
|
90
|
+
echo " Output: $OUTPUT"
|
|
91
|
+
else
|
|
92
|
+
echo "Not recording"
|
|
93
|
+
rm -f "$PIDFILE" 2>/dev/null
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
|
|
97
|
+
*)
|
|
98
|
+
echo "Usage: record-browser.sh [start|stop|status] [output.mp4] [interval_ms]"
|
|
99
|
+
;;
|
|
100
|
+
esac
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Revive Watcher — monitors a cmux workspace and auto-sends a resume command after /clear
|
|
3
|
+
#
|
|
4
|
+
# Usage: revive-watcher.sh [options]
|
|
5
|
+
# --workspace <id> Workspace to monitor (default: auto-detect current)
|
|
6
|
+
# --surface <id> Surface to monitor (default: auto-detect Claude surface)
|
|
7
|
+
# --command <cmd> Command to send on resume (default: /coordinate)
|
|
8
|
+
# --interval <secs> Poll interval in seconds (default: 10)
|
|
9
|
+
# --once Send the resume command once, then exit
|
|
10
|
+
# --quiet Suppress output except errors
|
|
11
|
+
#
|
|
12
|
+
# The watcher detects when Claude Code is idle (fresh prompt after /clear) and
|
|
13
|
+
# sends the configured resume command to restart the session.
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# Defaults
|
|
18
|
+
WORKSPACE=""
|
|
19
|
+
SURFACE=""
|
|
20
|
+
RESUME_CMD="/coordinate"
|
|
21
|
+
INTERVAL=10
|
|
22
|
+
ONCE=false
|
|
23
|
+
QUIET=false
|
|
24
|
+
PID_FILE="${HOME}/.claude/revive-watcher.pid"
|
|
25
|
+
|
|
26
|
+
log() {
|
|
27
|
+
if [ "$QUIET" = false ]; then
|
|
28
|
+
echo "$(date '+%H:%M:%S') [revive] $*"
|
|
29
|
+
fi
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
err() {
|
|
33
|
+
echo "$(date '+%H:%M:%S') [revive] ERROR: $*" >&2
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Parse args
|
|
37
|
+
while [[ $# -gt 0 ]]; do
|
|
38
|
+
case "$1" in
|
|
39
|
+
--workspace) WORKSPACE="$2"; shift 2 ;;
|
|
40
|
+
--surface) SURFACE="$2"; shift 2 ;;
|
|
41
|
+
--command) RESUME_CMD="$2"; shift 2 ;;
|
|
42
|
+
--interval) INTERVAL="$2"; shift 2 ;;
|
|
43
|
+
--once) ONCE=true; shift ;;
|
|
44
|
+
--quiet) QUIET=true; shift ;;
|
|
45
|
+
--help|-h)
|
|
46
|
+
head -14 "$0" | tail -12
|
|
47
|
+
exit 0
|
|
48
|
+
;;
|
|
49
|
+
*) err "Unknown option: $1"; exit 1 ;;
|
|
50
|
+
esac
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
# Auto-detect workspace if not specified
|
|
54
|
+
if [ -z "$WORKSPACE" ]; then
|
|
55
|
+
# Try to find the current workspace from cmux
|
|
56
|
+
CURRENT=$(cmux list-workspaces 2>/dev/null | grep -o 'workspace:[0-9]*' | head -1 || true)
|
|
57
|
+
if [ -z "$CURRENT" ]; then
|
|
58
|
+
err "Could not auto-detect workspace. Use --workspace <id>"
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
WORKSPACE="$CURRENT"
|
|
62
|
+
log "Auto-detected workspace: $WORKSPACE"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Auto-detect Claude surface if not specified
|
|
66
|
+
if [ -z "$SURFACE" ]; then
|
|
67
|
+
# Read all surfaces and find the one running Claude
|
|
68
|
+
SURFACES=$(cmux list-workspaces 2>/dev/null || true)
|
|
69
|
+
# Try common Claude surface patterns
|
|
70
|
+
for s in "surface:5" "surface:3" "surface:1"; do
|
|
71
|
+
SCREEN=$(cmux read-screen --workspace "$WORKSPACE" --surface "$s" --lines 5 2>/dev/null || true)
|
|
72
|
+
if echo "$SCREEN" | grep -qi "claude\|bypass\|permission\|❯"; then
|
|
73
|
+
SURFACE="$s"
|
|
74
|
+
break
|
|
75
|
+
fi
|
|
76
|
+
done
|
|
77
|
+
if [ -z "$SURFACE" ]; then
|
|
78
|
+
# Default to surface:5 (most common for Claude in cmux)
|
|
79
|
+
SURFACE="surface:5"
|
|
80
|
+
log "Could not auto-detect surface, defaulting to $SURFACE"
|
|
81
|
+
else
|
|
82
|
+
log "Auto-detected Claude surface: $SURFACE"
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# Write PID file for management
|
|
87
|
+
echo $$ > "$PID_FILE"
|
|
88
|
+
trap 'rm -f "$PID_FILE"; log "Watcher stopped."; exit 0' EXIT INT TERM
|
|
89
|
+
|
|
90
|
+
log "Watcher started"
|
|
91
|
+
log " Workspace: $WORKSPACE"
|
|
92
|
+
log " Surface: $SURFACE"
|
|
93
|
+
log " Command: $RESUME_CMD"
|
|
94
|
+
log " Interval: ${INTERVAL}s"
|
|
95
|
+
log " PID: $$"
|
|
96
|
+
|
|
97
|
+
# Track state to avoid spamming
|
|
98
|
+
LAST_SEND_TIME=0
|
|
99
|
+
COOLDOWN=60 # seconds between sends
|
|
100
|
+
|
|
101
|
+
is_idle() {
|
|
102
|
+
local screen
|
|
103
|
+
screen=$(cmux read-screen --workspace "$WORKSPACE" --surface "$SURFACE" --lines 5 2>/dev/null || true)
|
|
104
|
+
|
|
105
|
+
if [ -z "$screen" ]; then
|
|
106
|
+
return 1 # Can't read screen — not idle
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
# Idle = has the prompt marker but NO "esc to interrupt" (which means Claude is working)
|
|
110
|
+
if echo "$screen" | grep -q "esc to interrupt"; then
|
|
111
|
+
return 1 # Claude is actively working
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Check for idle prompt indicators:
|
|
115
|
+
# - Empty prompt (❯ with nothing after)
|
|
116
|
+
# - "bypass permissions" text (settings line visible = idle)
|
|
117
|
+
# - Just the prompt line with no active output
|
|
118
|
+
if echo "$screen" | grep -qE "(^❯\s*$|bypass permissions|What can I help)"; then
|
|
119
|
+
return 0 # Idle
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
return 1 # Not clearly idle
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
while true; do
|
|
126
|
+
sleep "$INTERVAL"
|
|
127
|
+
|
|
128
|
+
if is_idle; then
|
|
129
|
+
NOW=$(date +%s)
|
|
130
|
+
ELAPSED=$((NOW - LAST_SEND_TIME))
|
|
131
|
+
|
|
132
|
+
if [ "$ELAPSED" -ge "$COOLDOWN" ]; then
|
|
133
|
+
log "Workspace idle — sending resume command: $RESUME_CMD"
|
|
134
|
+
cmux send --workspace "$WORKSPACE" --surface "$SURFACE" "$RESUME_CMD" 2>/dev/null || {
|
|
135
|
+
err "Failed to send to $WORKSPACE $SURFACE"
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
sleep 1
|
|
139
|
+
cmux send-key --workspace "$WORKSPACE" --surface "$SURFACE" Enter 2>/dev/null || true
|
|
140
|
+
LAST_SEND_TIME=$NOW
|
|
141
|
+
log "Resume command sent. Cooling down ${COOLDOWN}s."
|
|
142
|
+
|
|
143
|
+
if [ "$ONCE" = true ]; then
|
|
144
|
+
log "One-shot mode — exiting."
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
done
|