agex 0.2.4 → 0.2.7
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/assets/scripts/ab-close-disabled.sh +5 -0
- package/assets/scripts/ab-close.sh +130 -0
- package/assets/scripts/ab-fx.sh +3 -0
- package/assets/scripts/ab-move.sh +19 -0
- package/assets/scripts/ab-not-found.sh +33 -0
- package/assets/scripts/ab-open.sh +17 -0
- package/assets/scripts/ab-proof.sh +126 -0
- package/assets/scripts/ab-record-disabled.sh +3 -0
- package/assets/scripts/ab-record-start-disabled.sh +2 -0
- package/assets/scripts/ab-record-start.sh +18 -0
- package/assets/scripts/ab-record-stop-disabled.sh +2 -0
- package/assets/scripts/ab-record-stop.sh +5 -0
- package/assets/scripts/ab-record.sh +43 -0
- package/assets/scripts/ab-screenshot.sh +9 -0
- package/assets/scripts/agent-browser-wrapper.sh +6 -0
- package/assets/scripts/fx-annotation.sh +13 -0
- package/assets/scripts/fx-arrow.sh +12 -0
- package/assets/scripts/fx-circle.sh +13 -0
- package/assets/scripts/fx-clear.sh +3 -0
- package/assets/scripts/fx-highlight.sh +32 -0
- package/assets/scripts/fx-spotlight.sh +13 -0
- package/assets/scripts/fx-title.sh +11 -0
- package/assets/scripts/fx-wait.sh +3 -0
- package/dist/{chunk-CY6NF2GO.js → chunk-FCCNIKUD.js} +24 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +11 -6
- package/dist/cli.d.ts +0 -6
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Merges video segments with crossfade, removes duplicate frames, and closes browser
|
|
3
|
+
# Usage: ab-close [segment_numbers...]
|
|
4
|
+
# Examples:
|
|
5
|
+
# ab-close # Merge all segments
|
|
6
|
+
# ab-close 1 3 5 # Merge only segments 1, 3, 5
|
|
7
|
+
|
|
8
|
+
# Remove duplicate/similar frames and create shortened video
|
|
9
|
+
deduplicate_video() {
|
|
10
|
+
local input="$1"
|
|
11
|
+
local output="$2"
|
|
12
|
+
local fps="$3"
|
|
13
|
+
|
|
14
|
+
echo "Removing duplicate frames (output fps: $fps)..."
|
|
15
|
+
# Suppress ffmpeg/ffprobe progress output (extremely verbose, confuses agent)
|
|
16
|
+
if ffmpeg -y -i "$input" -vf "mpdecimate,setpts=N/FRAME_RATE/TB" -r "$fps" -c:v libx264 "$output" 2>/dev/null; then
|
|
17
|
+
local orig_frames=$(ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=noprint_wrappers=1:nokey=1 "$input" 2>/dev/null)
|
|
18
|
+
local new_frames=$(ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=noprint_wrappers=1:nokey=1 "$output" 2>/dev/null)
|
|
19
|
+
echo "Deduplicated: $orig_frames -> $new_frames frames"
|
|
20
|
+
rm -f "$input"
|
|
21
|
+
return 0
|
|
22
|
+
else
|
|
23
|
+
echo "Warning: deduplication failed, keeping original"
|
|
24
|
+
mv "$input" "$output"
|
|
25
|
+
return 1
|
|
26
|
+
fi
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Wait to capture final frame before stopping recording
|
|
30
|
+
agent-browser wait 500 {{SESSION_ARG}} 2>&1 || true
|
|
31
|
+
|
|
32
|
+
# Stop any active recording first (ignore errors if not recording)
|
|
33
|
+
agent-browser record stop {{SESSION_ARG}} 2>&1 || true
|
|
34
|
+
|
|
35
|
+
# If segment numbers provided, use only those; otherwise use all
|
|
36
|
+
if [ $# -gt 0 ]; then
|
|
37
|
+
SEGMENTS=()
|
|
38
|
+
for num in "$@"; do
|
|
39
|
+
SEGMENT_FILE="{{SEGMENTS_DIR}}/segment-$(printf '%03d' $num).webm"
|
|
40
|
+
if [ -f "$SEGMENT_FILE" ]; then
|
|
41
|
+
SEGMENTS+=("$SEGMENT_FILE")
|
|
42
|
+
else
|
|
43
|
+
echo "Warning: segment $num not found ($SEGMENT_FILE)"
|
|
44
|
+
fi
|
|
45
|
+
done
|
|
46
|
+
echo "Using specified segments: $@"
|
|
47
|
+
else
|
|
48
|
+
SEGMENTS=($(ls -1 "{{SEGMENTS_DIR}}"/segment-*.webm 2>/dev/null | sort))
|
|
49
|
+
fi
|
|
50
|
+
SEGMENT_COUNT=${#SEGMENTS[@]}
|
|
51
|
+
|
|
52
|
+
if [ "$SEGMENT_COUNT" -eq "0" ]; then
|
|
53
|
+
echo "No video segments to merge"
|
|
54
|
+
elif [ "$SEGMENT_COUNT" -eq "1" ]; then
|
|
55
|
+
echo "Single segment, processing..."
|
|
56
|
+
cp "${SEGMENTS[0]}" "{{RAW_VIDEO_PATH}}"
|
|
57
|
+
deduplicate_video "{{RAW_VIDEO_PATH}}" "{{FINAL_VIDEO_PATH}}" {{DEDUP_FPS}}
|
|
58
|
+
echo "Video saved: {{FINAL_VIDEO_PATH}}"
|
|
59
|
+
rm -rf "{{SEGMENTS_DIR}}"
|
|
60
|
+
else
|
|
61
|
+
echo "Merging $SEGMENT_COUNT video segments with crossfade..."
|
|
62
|
+
|
|
63
|
+
# Build ffmpeg filter for crossfade between all segments
|
|
64
|
+
# Crossfade duration in seconds
|
|
65
|
+
XFADE_DURATION=0.3
|
|
66
|
+
|
|
67
|
+
# Build input args and filter
|
|
68
|
+
INPUTS=""
|
|
69
|
+
FILTER=""
|
|
70
|
+
|
|
71
|
+
for i in "${!SEGMENTS[@]}"; do
|
|
72
|
+
INPUTS="$INPUTS -i \"${SEGMENTS[$i]}\""
|
|
73
|
+
done
|
|
74
|
+
|
|
75
|
+
# For 2 segments: simple crossfade
|
|
76
|
+
# For N segments: chain crossfades
|
|
77
|
+
if [ "$SEGMENT_COUNT" -eq "2" ]; then
|
|
78
|
+
FILTER="[0:v][1:v]xfade=transition=fade:duration=$XFADE_DURATION:offset=0[outv];[0:a][1:a]acrossfade=d=$XFADE_DURATION[outa]"
|
|
79
|
+
OUTPUT_MAP="-map [outv] -map [outa]"
|
|
80
|
+
else
|
|
81
|
+
# Build chain: xfade 0+1 -> tmp1, xfade tmp1+2 -> tmp2, etc.
|
|
82
|
+
PREV="0:v"
|
|
83
|
+
PREV_A="0:a"
|
|
84
|
+
for ((i=1; i<SEGMENT_COUNT; i++)); do
|
|
85
|
+
if [ $i -eq $((SEGMENT_COUNT-1)) ]; then
|
|
86
|
+
# Last crossfade outputs to final
|
|
87
|
+
FILTER="$FILTER[$PREV][$i:v]xfade=transition=fade:duration=$XFADE_DURATION:offset=0[outv];"
|
|
88
|
+
FILTER="$FILTER[$PREV_A][$i:a]acrossfade=d=$XFADE_DURATION[outa]"
|
|
89
|
+
else
|
|
90
|
+
FILTER="$FILTER[$PREV][$i:v]xfade=transition=fade:duration=$XFADE_DURATION:offset=0[tmp$i];"
|
|
91
|
+
FILTER="$FILTER[$PREV_A][$i:a]acrossfade=d=$XFADE_DURATION[tmpa$i];"
|
|
92
|
+
PREV="tmp$i"
|
|
93
|
+
PREV_A="tmpa$i"
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
OUTPUT_MAP="-map [outv] -map [outa]"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Run ffmpeg with crossfade
|
|
100
|
+
FFMPEG_CMD="ffmpeg $INPUTS -filter_complex \"$FILTER\" $OUTPUT_MAP -y \"{{RAW_VIDEO_PATH}}\""
|
|
101
|
+
|
|
102
|
+
# Suppress ffmpeg progress output (extremely verbose, confuses agent)
|
|
103
|
+
if eval "$FFMPEG_CMD" 2>/dev/null; then
|
|
104
|
+
echo "Video merged with crossfade"
|
|
105
|
+
deduplicate_video "{{RAW_VIDEO_PATH}}" "{{FINAL_VIDEO_PATH}}" {{DEDUP_FPS}}
|
|
106
|
+
echo "Video saved: {{FINAL_VIDEO_PATH}}"
|
|
107
|
+
rm -rf "{{SEGMENTS_DIR}}"
|
|
108
|
+
else
|
|
109
|
+
# Fallback to simple concat if crossfade fails
|
|
110
|
+
echo "Crossfade failed, trying simple concat..."
|
|
111
|
+
CONCAT_LIST="{{SEGMENTS_DIR}}/concat.txt"
|
|
112
|
+
for f in "${SEGMENTS[@]}"; do
|
|
113
|
+
echo "file '$f'"
|
|
114
|
+
done > "$CONCAT_LIST"
|
|
115
|
+
|
|
116
|
+
# Suppress ffmpeg progress output
|
|
117
|
+
if ffmpeg -f concat -safe 0 -i "$CONCAT_LIST" -c copy "{{RAW_VIDEO_PATH}}" -y 2>/dev/null; then
|
|
118
|
+
echo "Video merged (simple concat)"
|
|
119
|
+
deduplicate_video "{{RAW_VIDEO_PATH}}" "{{FINAL_VIDEO_PATH}}" {{DEDUP_FPS}}
|
|
120
|
+
echo "Video saved: {{FINAL_VIDEO_PATH}}"
|
|
121
|
+
rm -rf "{{SEGMENTS_DIR}}"
|
|
122
|
+
else
|
|
123
|
+
echo "Warning: ffmpeg merge failed, keeping segments in {{SEGMENTS_DIR}}"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
agent-browser close {{SESSION_ARG}}
|
|
129
|
+
rm -f "{{OUTPUT_DIR}}/.session"
|
|
130
|
+
echo "Browser closed"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Moves the visual cursor to element center
|
|
3
|
+
# Supports @ref (e.g., @e5) or CSS selector
|
|
4
|
+
SELECTOR="$1"
|
|
5
|
+
if [ -z "$SELECTOR" ]; then
|
|
6
|
+
echo "Usage: ab-move <selector|@ref>"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# Use agent-browser hover to move to element (supports @ref via getLocator!)
|
|
11
|
+
HOVER_RESULT=$(agent-browser hover "$SELECTOR" 2>&1)
|
|
12
|
+
if [[ "$HOVER_RESULT" == *"not found"* ]] || [[ "$HOVER_RESULT" == *"Error"* ]]; then
|
|
13
|
+
echo "element not found: $SELECTOR"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Sync our visual cursor to the mouse position (hover moved mouse to element center)
|
|
18
|
+
agent-browser eval "window.__agexCursor && window.__agexCursor.sync()" >/dev/null 2>&1
|
|
19
|
+
echo "moved to element"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Shows a visual "NOT FOUND" indicator and captures screenshot
|
|
3
|
+
# Usage: ab-not-found "search term" <screenshot.png> [color]
|
|
4
|
+
# Examples:
|
|
5
|
+
# ab-not-found "Login button" 001-no-login.png
|
|
6
|
+
# ab-not-found "word 'test'" 002-no-test.png "#ff0000"
|
|
7
|
+
|
|
8
|
+
SEARCH_TERM="$1"
|
|
9
|
+
SCREENSHOT_NAME="$2"
|
|
10
|
+
COLOR="${3:-#ff4444}"
|
|
11
|
+
|
|
12
|
+
if [ -z "$SEARCH_TERM" ] || [ -z "$SCREENSHOT_NAME" ]; then
|
|
13
|
+
echo "Usage: ab-not-found \"search term\" <screenshot.png> [color]"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
SCREENSHOT_PATH="{{OUTPUT_DIR}}/$SCREENSHOT_NAME"
|
|
18
|
+
|
|
19
|
+
# Escape search term for JS
|
|
20
|
+
ESCAPED_TERM=$(printf '%s' "$SEARCH_TERM" | sed "s/'/\\\\'/g")
|
|
21
|
+
|
|
22
|
+
# Create "NOT FOUND" overlay with search term
|
|
23
|
+
agent-browser eval "window.__agexEffects.proof.notFound('${ESCAPED_TERM}','$COLOR')" 2>&1 || true
|
|
24
|
+
|
|
25
|
+
# Wait for overlay to render
|
|
26
|
+
agent-browser wait 200
|
|
27
|
+
|
|
28
|
+
# Take screenshot
|
|
29
|
+
agent-browser screenshot "$SCREENSHOT_PATH"
|
|
30
|
+
echo "Screenshot saved: $SCREENSHOT_PATH"
|
|
31
|
+
|
|
32
|
+
# Clean up overlay after screenshot
|
|
33
|
+
agent-browser eval "window.__agexEffects.proof.clearNotFound()" 2>&1 || true
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Opens URL with agex setup (viewport, effects)
|
|
3
|
+
URL="$1"
|
|
4
|
+
if [ -z "$URL" ]; then
|
|
5
|
+
echo "Usage: ab-open <url>"
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
# Write session ID to output directory for other scripts to use
|
|
9
|
+
echo "{{SESSION_ID}}" > "{{OUTPUT_DIR}}/.session"
|
|
10
|
+
agent-browser open "$URL" {{SESSION_ARG}} {{HEADED_FLAG}}
|
|
11
|
+
agent-browser set viewport {{VIEWPORT_WIDTH}} {{VIEWPORT_HEIGHT}} {{SESSION_ARG}}
|
|
12
|
+
{{VIDEO_MKDIR}}
|
|
13
|
+
{{VIDEO_INIT_COUNTER}}
|
|
14
|
+
# stdout suppressed to avoid echoing JS source back to agent
|
|
15
|
+
agent-browser eval --stdin {{SESSION_ARG}} < "{{INIT_SCRIPT_PATH}}" >/dev/null
|
|
16
|
+
echo "Browser ready at $URL (session: {{SESSION_ID}})"
|
|
17
|
+
{{VIDEO_READY_MSG}}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Highlights an element and captures a screenshot that includes the full element
|
|
3
|
+
# Usage: ab-proof <selector|@ref> "label" <screenshot.png> [color]
|
|
4
|
+
# Examples:
|
|
5
|
+
# ab-proof @e5 "Table of Contents" 001-toc.png
|
|
6
|
+
# ab-proof "#main-content" "Content verified" 002-content.png "#00ff00"
|
|
7
|
+
|
|
8
|
+
SELECTOR="$1"
|
|
9
|
+
LABEL="$2"
|
|
10
|
+
SCREENSHOT_NAME="$3"
|
|
11
|
+
COLOR="${4:-#00ff00}"
|
|
12
|
+
|
|
13
|
+
if [ -z "$SELECTOR" ] || [ -z "$LABEL" ] || [ -z "$SCREENSHOT_NAME" ]; then
|
|
14
|
+
echo "Usage: ab-proof <selector|@ref> \"label\" <screenshot.png> [color]"
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
SCREENSHOT_PATH="{{OUTPUT_DIR}}/$SCREENSHOT_NAME"
|
|
19
|
+
PADDING=20
|
|
20
|
+
|
|
21
|
+
# Get viewport dimensions
|
|
22
|
+
VIEWPORT_WIDTH={{VIEWPORT_WIDTH}}
|
|
23
|
+
VIEWPORT_HEIGHT={{VIEWPORT_HEIGHT}}
|
|
24
|
+
|
|
25
|
+
# Escape label for JS
|
|
26
|
+
ESCAPED_LABEL=$(printf '%s' "$LABEL" | sed "s/'/\\\\'/g")
|
|
27
|
+
|
|
28
|
+
# Get element bounding box
|
|
29
|
+
if [[ "$SELECTOR" == @* ]]; then
|
|
30
|
+
BOX=$(agent-browser get box "$SELECTOR" 2>&1) || true
|
|
31
|
+
else
|
|
32
|
+
ESCAPED_SELECTOR=$(printf '%s' "$SELECTOR" | sed "s/'/\\\\'/g")
|
|
33
|
+
BOX=$(agent-browser eval "JSON.stringify(window.__agexEffects.proof.getBoundingBox('${ESCAPED_SELECTOR}'))" 2>&1) || true
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
if [ -z "$BOX" ] || [[ "$BOX" == "null" ]] || [[ "$BOX" == *"error"* ]]; then
|
|
37
|
+
echo "Element not found: $SELECTOR"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# Parse bounding box (support negative values)
|
|
42
|
+
EL_X=$(echo "$BOX" | grep -o '"x":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
43
|
+
EL_Y=$(echo "$BOX" | grep -o '"y":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
44
|
+
EL_WIDTH=$(echo "$BOX" | grep -o '"width":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
45
|
+
EL_HEIGHT=$(echo "$BOX" | grep -o '"height":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
46
|
+
|
|
47
|
+
# Account for label height (approx 50px below element)
|
|
48
|
+
LABEL_HEIGHT=50
|
|
49
|
+
TOTAL_HEIGHT=$((EL_HEIGHT + LABEL_HEIGHT + PADDING * 2))
|
|
50
|
+
TOTAL_WIDTH=$((EL_WIDTH + PADDING * 2))
|
|
51
|
+
|
|
52
|
+
# Scroll element into view (centered)
|
|
53
|
+
if [[ "$SELECTOR" == @* ]]; then
|
|
54
|
+
agent-browser eval "window.__agexEffects.proof.scrollToCoords($EL_Y,$VIEWPORT_HEIGHT,$EL_HEIGHT)" 2>&1 || true
|
|
55
|
+
else
|
|
56
|
+
agent-browser eval "window.__agexEffects.proof.scrollToSelector('${ESCAPED_SELECTOR}')" 2>&1 || true
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# Wait for scroll
|
|
60
|
+
agent-browser wait 200
|
|
61
|
+
|
|
62
|
+
# Re-get bounding box after scroll (position changed)
|
|
63
|
+
if [[ "$SELECTOR" == @* ]]; then
|
|
64
|
+
NEW_BOX=$(agent-browser get box "$SELECTOR" 2>&1) || true
|
|
65
|
+
if [ -n "$NEW_BOX" ] && [[ "$NEW_BOX" != "null" ]] && [[ "$NEW_BOX" != *"error"* ]]; then
|
|
66
|
+
NEW_X=$(echo "$NEW_BOX" | grep -o '"x":[0-9.-]*' | cut -d: -f2)
|
|
67
|
+
NEW_Y=$(echo "$NEW_BOX" | grep -o '"y":[0-9.-]*' | cut -d: -f2)
|
|
68
|
+
if [ -n "$NEW_X" ] && [ -n "$NEW_Y" ]; then
|
|
69
|
+
BOX="$NEW_BOX"
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
else
|
|
73
|
+
BOX=$(agent-browser eval "JSON.stringify(window.__agexEffects.proof.getBoundingBox('${ESCAPED_SELECTOR}'))" 2>&1) || true
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# Parse updated bounding box
|
|
77
|
+
EL_X=$(echo "$BOX" | grep -o '"x":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
78
|
+
EL_Y=$(echo "$BOX" | grep -o '"y":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
79
|
+
EL_WIDTH=$(echo "$BOX" | grep -o '"width":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
80
|
+
EL_HEIGHT=$(echo "$BOX" | grep -o '"height":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
81
|
+
|
|
82
|
+
# Apply highlight border + label
|
|
83
|
+
agent-browser eval "window.__agexEffects.proof.highlightProof($BOX,'${ESCAPED_LABEL}','$COLOR')" 2>&1 || true
|
|
84
|
+
|
|
85
|
+
# Wait for highlight to render
|
|
86
|
+
agent-browser wait 100
|
|
87
|
+
|
|
88
|
+
# Decide screenshot strategy based on element size vs viewport
|
|
89
|
+
if [ "$TOTAL_HEIGHT" -gt "$VIEWPORT_HEIGHT" ] || [ "$TOTAL_WIDTH" -gt "$VIEWPORT_WIDTH" ]; then
|
|
90
|
+
# Element larger than viewport - use full page screenshot + crop
|
|
91
|
+
TEMP_FULL="{{OUTPUT_DIR}}/.temp-full-$$.png"
|
|
92
|
+
|
|
93
|
+
agent-browser screenshot "$TEMP_FULL" --full
|
|
94
|
+
|
|
95
|
+
# Get scroll position to calculate absolute coords
|
|
96
|
+
SCROLL_POS=$(agent-browser eval "JSON.stringify(window.__agexEffects.proof.getScrollPosition())" 2>&1) || true
|
|
97
|
+
SCROLL_X=$(echo "$SCROLL_POS" | grep -o '"x":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
98
|
+
SCROLL_Y=$(echo "$SCROLL_POS" | grep -o '"y":[0-9.-]*' | cut -d: -f2 | cut -d. -f1)
|
|
99
|
+
|
|
100
|
+
# Calculate crop region (element position + padding + label)
|
|
101
|
+
CROP_X=$((SCROLL_X + EL_X - PADDING))
|
|
102
|
+
CROP_Y=$((SCROLL_Y + EL_Y - PADDING))
|
|
103
|
+
CROP_WIDTH=$((EL_WIDTH + PADDING * 2))
|
|
104
|
+
CROP_HEIGHT=$((EL_HEIGHT + PADDING * 2 + LABEL_HEIGHT))
|
|
105
|
+
|
|
106
|
+
# Ensure non-negative
|
|
107
|
+
[ "$CROP_X" -lt 0 ] && CROP_X=0
|
|
108
|
+
[ "$CROP_Y" -lt 0 ] && CROP_Y=0
|
|
109
|
+
|
|
110
|
+
# Try to crop with ImageMagick
|
|
111
|
+
if command -v convert &> /dev/null; then
|
|
112
|
+
convert "$TEMP_FULL" -crop "${CROP_WIDTH}x${CROP_HEIGHT}+${CROP_X}+${CROP_Y}" +repage "$SCREENSHOT_PATH"
|
|
113
|
+
rm -f "$TEMP_FULL"
|
|
114
|
+
echo "Screenshot saved (cropped): $SCREENSHOT_PATH"
|
|
115
|
+
else
|
|
116
|
+
mv "$TEMP_FULL" "$SCREENSHOT_PATH"
|
|
117
|
+
echo "Screenshot saved (full page, ImageMagick not available for crop): $SCREENSHOT_PATH"
|
|
118
|
+
fi
|
|
119
|
+
else
|
|
120
|
+
# Element fits in viewport - take regular screenshot
|
|
121
|
+
agent-browser screenshot "$SCREENSHOT_PATH"
|
|
122
|
+
echo "Screenshot saved: $SCREENSHOT_PATH"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# Clean up highlight after screenshot
|
|
126
|
+
agent-browser eval "window.__agexEffects.proof.clearProof()" 2>&1 || true
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Starts video recording
|
|
3
|
+
COUNTER=$(cat "{{SEGMENT_COUNTER_PATH}}" 2>/dev/null || echo "0")
|
|
4
|
+
COUNTER=$((COUNTER + 1))
|
|
5
|
+
echo "$COUNTER" > "{{SEGMENT_COUNTER_PATH}}"
|
|
6
|
+
SEGMENT_NAME=$(printf "segment-%03d.webm" "$COUNTER")
|
|
7
|
+
SEGMENT_PATH="{{SEGMENTS_DIR}}/$SEGMENT_NAME"
|
|
8
|
+
|
|
9
|
+
agent-browser record start "$SEGMENT_PATH" {{SESSION_ARG}}
|
|
10
|
+
|
|
11
|
+
# RE-INJECT init script after recording starts (recording causes page context change)
|
|
12
|
+
# stdout suppressed to avoid echoing JS source back to agent
|
|
13
|
+
agent-browser eval --stdin {{SESSION_ARG}} < "{{INIT_SCRIPT_PATH}}" >/dev/null
|
|
14
|
+
|
|
15
|
+
# Wait to capture initial frame
|
|
16
|
+
agent-browser wait 200 {{SESSION_ARG}}
|
|
17
|
+
|
|
18
|
+
echo "Recording started: $SEGMENT_NAME"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Records a video segment with title overlay
|
|
3
|
+
# Usage: ab-record "Title Text" "command1 && command2 && ..."
|
|
4
|
+
|
|
5
|
+
TITLE="$1"
|
|
6
|
+
COMMANDS="$2"
|
|
7
|
+
|
|
8
|
+
if [ -z "$TITLE" ] || [ -z "$COMMANDS" ]; then
|
|
9
|
+
echo "Usage: ab-record \"Title Text\" \"command1 && command2 && ...\""
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
COUNTER=$(cat "{{SEGMENT_COUNTER_PATH}}" 2>/dev/null || echo "0")
|
|
14
|
+
COUNTER=$((COUNTER + 1))
|
|
15
|
+
echo "$COUNTER" > "{{SEGMENT_COUNTER_PATH}}"
|
|
16
|
+
SEGMENT_NAME=$(printf "segment-%03d.webm" "$COUNTER")
|
|
17
|
+
SEGMENT_PATH="{{SEGMENTS_DIR}}/$SEGMENT_NAME"
|
|
18
|
+
|
|
19
|
+
agent-browser record start "$SEGMENT_PATH" {{SESSION_ARG}}
|
|
20
|
+
echo "Recording segment $SEGMENT_NAME: $TITLE"
|
|
21
|
+
|
|
22
|
+
# RE-INJECT init script after recording starts (recording causes page context change)
|
|
23
|
+
# stdout suppressed to avoid echoing JS source back to agent
|
|
24
|
+
agent-browser eval --stdin {{SESSION_ARG}} < "{{INIT_SCRIPT_PATH}}" >/dev/null
|
|
25
|
+
|
|
26
|
+
# Inject title overlay
|
|
27
|
+
ESCAPED_TITLE=$(printf '%s' "$TITLE" | sed "s/'/\\\\'/g")
|
|
28
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.banner.show('${ESCAPED_TITLE}')"
|
|
29
|
+
|
|
30
|
+
# Wait for effects to be ready
|
|
31
|
+
agent-browser wait 200 {{SESSION_ARG}}
|
|
32
|
+
|
|
33
|
+
# Execute the commands
|
|
34
|
+
eval "$COMMANDS"
|
|
35
|
+
EXIT_CODE=$?
|
|
36
|
+
|
|
37
|
+
# Wait before stopping to capture final frame
|
|
38
|
+
agent-browser wait 200 {{SESSION_ARG}}
|
|
39
|
+
|
|
40
|
+
agent-browser record stop {{SESSION_ARG}}
|
|
41
|
+
echo "Recording stopped: $SEGMENT_NAME"
|
|
42
|
+
|
|
43
|
+
exit $EXIT_CODE
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
SELECTOR="$1"
|
|
3
|
+
TEXT="$2"
|
|
4
|
+
POSITION="${3:-bottom}"
|
|
5
|
+
COLOR="${4:-#333}"
|
|
6
|
+
if [ -z "$SELECTOR" ] || [ -z "$TEXT" ]; then
|
|
7
|
+
echo "Usage: fx-annotation <css-selector> <text> [position:top|bottom|left|right] [color]"
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
{{FX_INJECT_HELPER}}
|
|
11
|
+
ESCAPED_SELECTOR=$(printf '%s' "$SELECTOR" | sed "s/'/\\\\'/g")
|
|
12
|
+
ESCAPED_TEXT=$(printf '%s' "$TEXT" | sed "s/'/\\\\'/g")
|
|
13
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.annotation.add('${ESCAPED_SELECTOR}',{text:'${ESCAPED_TEXT}',position:'$POSITION',color:'$COLOR'})"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
FROM="$1"
|
|
3
|
+
TO="$2"
|
|
4
|
+
COLOR="${3:-#ff4444}"
|
|
5
|
+
if [ -z "$FROM" ] || [ -z "$TO" ]; then
|
|
6
|
+
echo "Usage: fx-arrow <from-css-selector> <to-css-selector> [color]"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
{{FX_INJECT_HELPER}}
|
|
10
|
+
ESCAPED_FROM=$(printf '%s' "$FROM" | sed "s/'/\\\\'/g")
|
|
11
|
+
ESCAPED_TO=$(printf '%s' "$TO" | sed "s/'/\\\\'/g")
|
|
12
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.drawing.arrowBetween('${ESCAPED_FROM}','${ESCAPED_TO}','$COLOR')"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
SELECTOR="$1"
|
|
3
|
+
LABEL="$2"
|
|
4
|
+
COLOR="${3:-#ff4444}"
|
|
5
|
+
if [ -z "$SELECTOR" ] || [ -z "$LABEL" ]; then
|
|
6
|
+
echo "Usage: fx-circle <css-selector> \"label\" [color]"
|
|
7
|
+
echo " Example: fx-circle \"#logo\" \"Logo element\" '#ff0000'"
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
{{FX_INJECT_HELPER}}
|
|
11
|
+
ESCAPED_SELECTOR=$(printf '%s' "$SELECTOR" | sed "s/'/\\\\'/g")
|
|
12
|
+
ESCAPED_LABEL=$(printf '%s' "$LABEL" | sed "s/'/\\\\'/g")
|
|
13
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.drawing.circle('${ESCAPED_SELECTOR}','$COLOR','${ESCAPED_LABEL}')"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
SELECTOR="$1"
|
|
3
|
+
LABEL="$2"
|
|
4
|
+
COLOR="${3:-#2e7d32}"
|
|
5
|
+
if [ -z "$SELECTOR" ] || [ -z "$LABEL" ]; then
|
|
6
|
+
echo "Usage: fx-highlight <selector> \"label\" [color]"
|
|
7
|
+
echo " Examples:"
|
|
8
|
+
echo " fx-highlight @e5 \"Logo found\" '#00ff00'"
|
|
9
|
+
echo " fx-highlight \"img.logo\" \"Logo found\" '#00ff00'"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Escape label for JS (handle quotes and backslashes)
|
|
14
|
+
ESCAPED_LABEL=$(printf '%s' "$LABEL" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\\'/g")
|
|
15
|
+
|
|
16
|
+
if [[ "$SELECTOR" == @* ]]; then
|
|
17
|
+
# @ref format - use agent-browser get box to get element position
|
|
18
|
+
BOX=$(agent-browser get box "$SELECTOR" {{SESSION_ARG}} 2>&1) || true
|
|
19
|
+
if [ -z "$BOX" ] || [[ "$BOX" == *"error"* ]]; then
|
|
20
|
+
echo "Element not found: $SELECTOR"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.proof.highlightBox($BOX,'${ESCAPED_LABEL}','$COLOR')" 2>&1 || true
|
|
25
|
+
else
|
|
26
|
+
# CSS selector - use effects.js
|
|
27
|
+
ESCAPED_SELECTOR=$(printf '%s' "$SELECTOR" | sed "s/\\\\/\\\\\\\\/g; s/'/\\\\'/g")
|
|
28
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.proof.highlightSelector('${ESCAPED_SELECTOR}','${ESCAPED_LABEL}','$COLOR')" 2>&1 || true
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
agent-browser wait 100 {{SESSION_ARG}}
|
|
32
|
+
echo "Highlighted $SELECTOR with label"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
SELECTOR="$1"
|
|
3
|
+
LABEL="$2"
|
|
4
|
+
OPACITY="${3:-0.7}"
|
|
5
|
+
if [ -z "$SELECTOR" ] || [ -z "$LABEL" ]; then
|
|
6
|
+
echo "Usage: fx-spotlight <css-selector> \"label\" [opacity]"
|
|
7
|
+
echo " Example: fx-spotlight \"#main-content\" \"Main content area\" 0.7"
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
{{FX_INJECT_HELPER}}
|
|
11
|
+
ESCAPED_SELECTOR=$(printf '%s' "$SELECTOR" | sed "s/'/\\\\'/g")
|
|
12
|
+
ESCAPED_LABEL=$(printf '%s' "$LABEL" | sed "s/'/\\\\'/g")
|
|
13
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.spotlight.show('${ESCAPED_SELECTOR}',{opacity:$OPACITY,label:'${ESCAPED_LABEL}'})"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
TITLE="$1"
|
|
3
|
+
SUBTITLE="$2"
|
|
4
|
+
if [ -z "$TITLE" ]; then
|
|
5
|
+
echo "Usage: fx-title <title> [subtitle]"
|
|
6
|
+
exit 1
|
|
7
|
+
fi
|
|
8
|
+
{{FX_INJECT_HELPER}}
|
|
9
|
+
ESCAPED_TITLE=$(printf '%s' "$TITLE" | sed "s/'/\\\\'/g")
|
|
10
|
+
ESCAPED_SUBTITLE=$(printf '%s' "$SUBTITLE" | sed "s/'/\\\\'/g")
|
|
11
|
+
agent-browser eval {{SESSION_ARG}} "window.__agexEffects.banner.show('${ESCAPED_TITLE}','${ESCAPED_SUBTITLE}')"
|
|
@@ -5348,6 +5348,18 @@ import * as path2 from "path";
|
|
|
5348
5348
|
import { fileURLToPath } from "url";
|
|
5349
5349
|
function getAssetsDir() {
|
|
5350
5350
|
const __dirname2 = path2.dirname(fileURLToPath(import.meta.url));
|
|
5351
|
+
let dir = __dirname2;
|
|
5352
|
+
for (let i = 0; i < 5; i++) {
|
|
5353
|
+
const candidate = path2.join(dir, "assets");
|
|
5354
|
+
if (fs.existsSync(path2.join(candidate, "cursor.js"))) {
|
|
5355
|
+
return candidate;
|
|
5356
|
+
}
|
|
5357
|
+
const nestedCandidate = path2.join(dir, "assets", "assets");
|
|
5358
|
+
if (fs.existsSync(path2.join(nestedCandidate, "cursor.js"))) {
|
|
5359
|
+
return nestedCandidate;
|
|
5360
|
+
}
|
|
5361
|
+
dir = path2.dirname(dir);
|
|
5362
|
+
}
|
|
5351
5363
|
return path2.resolve(__dirname2, "..", "assets");
|
|
5352
5364
|
}
|
|
5353
5365
|
function getCursorScriptPath() {
|
|
@@ -5409,6 +5421,18 @@ function renderScript(template, vars) {
|
|
|
5409
5421
|
// ../browse/dist/scripts/index.js
|
|
5410
5422
|
var __dirname = path3.dirname(fileURLToPath2(import.meta.url));
|
|
5411
5423
|
function getScriptsDir() {
|
|
5424
|
+
let dir = __dirname;
|
|
5425
|
+
for (let i = 0; i < 5; i++) {
|
|
5426
|
+
const candidate = path3.join(dir, "scripts");
|
|
5427
|
+
if (fs2.existsSync(path3.join(candidate, "ab-fx.sh"))) {
|
|
5428
|
+
return candidate;
|
|
5429
|
+
}
|
|
5430
|
+
const assetsCandidate = path3.join(dir, "assets", "scripts");
|
|
5431
|
+
if (fs2.existsSync(path3.join(assetsCandidate, "ab-fx.sh"))) {
|
|
5432
|
+
return assetsCandidate;
|
|
5433
|
+
}
|
|
5434
|
+
dir = path3.dirname(dir);
|
|
5435
|
+
}
|
|
5412
5436
|
return path3.resolve(__dirname, "..", "..", "scripts");
|
|
5413
5437
|
}
|
|
5414
5438
|
function loadRawScript(name) {
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
"agex": "dist/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
|
-
"dist",
|
|
11
|
+
"dist/cli.js",
|
|
12
|
+
"dist/cli.js.map",
|
|
13
|
+
"dist/index.js",
|
|
14
|
+
"dist/index.js.map",
|
|
15
|
+
"dist/index.d.ts",
|
|
16
|
+
"dist/chunk-*.js",
|
|
12
17
|
"assets"
|
|
13
18
|
],
|
|
14
19
|
"dependencies": {
|
|
@@ -21,15 +26,15 @@
|
|
|
21
26
|
"typescript": "^5.9.3",
|
|
22
27
|
"vitest": "^2.1.9",
|
|
23
28
|
"agex-agent": "0.1.0",
|
|
24
|
-
"agex-core": "0.1.0",
|
|
25
29
|
"agex-browse": "0.1.0",
|
|
26
|
-
"agex-
|
|
30
|
+
"agex-core": "0.1.0",
|
|
27
31
|
"agex-review": "0.1.0",
|
|
28
|
-
"agex-prove": "0.1.0"
|
|
32
|
+
"agex-prove": "0.1.0",
|
|
33
|
+
"agex-prove-pr": "0.1.0"
|
|
29
34
|
},
|
|
30
35
|
"scripts": {
|
|
31
36
|
"build": "tsc -p tsconfig.json",
|
|
32
|
-
"bundle": "tsup && cp -r ../browse/assets ./assets",
|
|
37
|
+
"bundle": "rm -rf dist assets && tsup && mkdir -p ./assets/assets ./assets/scripts && cp -r ../browse/assets/ ./assets/assets/ && cp -r ../browse/scripts/ ./assets/scripts/",
|
|
33
38
|
"test": "vitest run"
|
|
34
39
|
}
|
|
35
40
|
}
|