@kenkaiiii/ggcoder-eyes 0.1.0
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/LICENSE +21 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +328 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/journal.d.ts +40 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +104 -0
- package/dist/journal.js.map +1 -0
- package/dist/manifest.d.ts +16 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +17 -0
- package/dist/manifest.js.map +1 -0
- package/package.json +41 -0
- package/probes/capture_email/impl/mailpit.sh +77 -0
- package/probes/capture_email/install.sh +83 -0
- package/probes/capture_email/test.sh +43 -0
- package/probes/http/impl/curl.sh +64 -0
- package/probes/http/install.sh +19 -0
- package/probes/http/test.sh +39 -0
- package/probes/runtime_logs/detect.sh +38 -0
- package/probes/runtime_logs/impl/adb-logcat.sh +46 -0
- package/probes/runtime_logs/impl/docker.sh +51 -0
- package/probes/runtime_logs/impl/simctl.sh +42 -0
- package/probes/runtime_logs/impl/tail.sh +55 -0
- package/probes/runtime_logs/install.sh +36 -0
- package/probes/runtime_logs/test.sh +44 -0
- package/probes/visual/detect.sh +78 -0
- package/probes/visual/impl/adb.sh +28 -0
- package/probes/visual/impl/generic.sh +7 -0
- package/probes/visual/impl/playwright.sh +70 -0
- package/probes/visual/impl/simctl.sh +29 -0
- package/probes/visual/impl/window.sh +59 -0
- package/probes/visual/install.sh +87 -0
- package/probes/visual/test.sh +69 -0
- package/shared/_lib.sh +88 -0
- package/shared/_redact.sh +29 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Emits JSON to stdout describing which visual impls make sense for this project.
|
|
3
|
+
# { "candidates": ["playwright","adb","simctl","window","godot","unity","generic"], "primary": "<first>" }
|
|
4
|
+
# Multi-stack projects (e.g. React Native) return multiple candidates — install all that apply.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
ROOT="${EYES_PROJECT_ROOT:-$PWD}"
|
|
8
|
+
CANDIDATES=()
|
|
9
|
+
|
|
10
|
+
add() {
|
|
11
|
+
case " ${CANDIDATES[*]:-} " in *" $1 "*) return;; esac
|
|
12
|
+
CANDIDATES+=("$1")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# --- Web ---
|
|
16
|
+
if [ -f "$ROOT/package.json" ]; then
|
|
17
|
+
if grep -qE '"(next|vite|@sveltejs/kit|astro|remix|nuxt|gatsby|@angular/core|vue|svelte|solid-js|qwik|@builder\.io/qwik|react-dom)"' "$ROOT/package.json" 2>/dev/null; then
|
|
18
|
+
add playwright
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
if [ -f "$ROOT/index.html" ] || [ -f "$ROOT/public/index.html" ] || [ -f "$ROOT/dist/index.html" ]; then
|
|
22
|
+
add playwright
|
|
23
|
+
fi
|
|
24
|
+
# Rails / Django / Laravel / Phoenix / etc. — any project with server-rendered HTML and a dev server
|
|
25
|
+
if [ -f "$ROOT/Gemfile" ] && grep -q 'rails' "$ROOT/Gemfile" 2>/dev/null; then add playwright; fi
|
|
26
|
+
if [ -f "$ROOT/manage.py" ] || { [ -f "$ROOT/pyproject.toml" ] && grep -qE 'django|flask|fastapi|starlette' "$ROOT/pyproject.toml" 2>/dev/null; }; then add playwright; fi
|
|
27
|
+
if [ -f "$ROOT/artisan" ]; then add playwright; fi
|
|
28
|
+
if [ -f "$ROOT/mix.exs" ] && grep -q 'phoenix' "$ROOT/mix.exs" 2>/dev/null; then add playwright; fi
|
|
29
|
+
|
|
30
|
+
# --- Mobile ---
|
|
31
|
+
# Android
|
|
32
|
+
if [ -d "$ROOT/android" ] \
|
|
33
|
+
|| [ -f "$ROOT/AndroidManifest.xml" ] \
|
|
34
|
+
|| find "$ROOT" -maxdepth 3 -name "AndroidManifest.xml" 2>/dev/null | head -1 | grep -q . \
|
|
35
|
+
|| { find "$ROOT" -maxdepth 3 -name "build.gradle*" 2>/dev/null | xargs grep -l 'com.android' 2>/dev/null | head -1 | grep -q .; }; then
|
|
36
|
+
add adb
|
|
37
|
+
fi
|
|
38
|
+
# iOS
|
|
39
|
+
if [ -d "$ROOT/ios" ] \
|
|
40
|
+
|| find "$ROOT" -maxdepth 2 -name "*.xcodeproj" 2>/dev/null | head -1 | grep -q . \
|
|
41
|
+
|| find "$ROOT" -maxdepth 2 -name "*.xcworkspace" 2>/dev/null | head -1 | grep -q . \
|
|
42
|
+
|| [ -f "$ROOT/Podfile" ]; then
|
|
43
|
+
add simctl
|
|
44
|
+
fi
|
|
45
|
+
# Flutter — has both ios/ and android/ dirs under project root; already covered.
|
|
46
|
+
|
|
47
|
+
# --- Desktop ---
|
|
48
|
+
if [ -f "$ROOT/src-tauri/tauri.conf.json" ] \
|
|
49
|
+
|| { [ -f "$ROOT/package.json" ] && grep -qE '"(electron|@electron/|nwjs|neutralinojs)"' "$ROOT/package.json" 2>/dev/null; }; then
|
|
50
|
+
add window
|
|
51
|
+
fi
|
|
52
|
+
# GTK / Qt / wxWidgets
|
|
53
|
+
if find "$ROOT" -maxdepth 2 -name "*.pro" -o -name "CMakeLists.txt" 2>/dev/null | xargs grep -l 'Qt\|gtk\|wx' 2>/dev/null | head -1 | grep -q .; then
|
|
54
|
+
add window
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# --- Games ---
|
|
58
|
+
[ -f "$ROOT/project.godot" ] && add godot
|
|
59
|
+
if [ -d "$ROOT/Assets" ] && [ -d "$ROOT/ProjectSettings" ]; then add unity; fi
|
|
60
|
+
# Unreal
|
|
61
|
+
if find "$ROOT" -maxdepth 2 -name "*.uproject" 2>/dev/null | head -1 | grep -q .; then add unreal; fi
|
|
62
|
+
# LÖVE / Pygame / Bevy / custom — fall through to window capture
|
|
63
|
+
if [ -f "$ROOT/main.lua" ] || { [ -f "$ROOT/Cargo.toml" ] && grep -q 'bevy' "$ROOT/Cargo.toml" 2>/dev/null; }; then
|
|
64
|
+
add window
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# --- Fallback ---
|
|
68
|
+
if [ ${#CANDIDATES[@]} -eq 0 ]; then add generic; fi
|
|
69
|
+
|
|
70
|
+
# --- Emit JSON ---
|
|
71
|
+
printf '{"candidates":['
|
|
72
|
+
first=1
|
|
73
|
+
for c in "${CANDIDATES[@]}"; do
|
|
74
|
+
[ $first -eq 0 ] && printf ','
|
|
75
|
+
printf '"%s"' "$c"
|
|
76
|
+
first=0
|
|
77
|
+
done
|
|
78
|
+
printf '],"primary":"%s"}\n' "${CANDIDATES[0]}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# impl: adb
|
|
3
|
+
# Android screenshot — works for native Android, React Native, Flutter, Capacitor,
|
|
4
|
+
# any project that lands on an Android device/emulator.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# visual-android.sh [device-serial]
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
11
|
+
|
|
12
|
+
eyes_require adb
|
|
13
|
+
|
|
14
|
+
DEVICE="${1:-}"
|
|
15
|
+
ARGS=()
|
|
16
|
+
[ -n "$DEVICE" ] && ARGS+=(-s "$DEVICE")
|
|
17
|
+
|
|
18
|
+
# Fail clearly if no device
|
|
19
|
+
if ! adb "${ARGS[@]}" get-state >/dev/null 2>&1; then
|
|
20
|
+
eyes_die "no android device/emulator available. run 'adb devices' to list; start an emulator or connect a device."
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
OUT="$(eyes_out_dir)/screenshot-android-$(eyes_timestamp).png"
|
|
24
|
+
# exec-out pipes binary PNG cleanly (no CRLF translation on Windows)
|
|
25
|
+
eyes_timeout 15 adb "${ARGS[@]}" exec-out screencap -p > "$OUT" \
|
|
26
|
+
|| eyes_die "adb screencap failed"
|
|
27
|
+
[ -s "$OUT" ] || eyes_die "screenshot empty"
|
|
28
|
+
echo "$OUT"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# impl: generic
|
|
3
|
+
# No-op for projects with no user-visible UI (libraries, pure backends, CLIs).
|
|
4
|
+
# Exits non-zero to make it loud if an agent installs this and then tries to use it.
|
|
5
|
+
echo "visual: this project has no visible UI to capture (library / backend / CLI)." >&2
|
|
6
|
+
echo "Consider: http (API probe), cli_io (CLI probe), runtime_logs, test, or build." >&2
|
|
7
|
+
exit 2
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# impl: playwright
|
|
3
|
+
# Screenshot any URL. Works for any web framework (Next/Vite/Rails/Django/etc.) —
|
|
4
|
+
# it only needs an HTTP server.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# visual.sh <url> [viewport WxH] [--selector <css>] [--wait-for-selector <css>] [--full-page|--viewport-only]
|
|
8
|
+
#
|
|
9
|
+
# Prints the absolute PNG path to stdout.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
13
|
+
|
|
14
|
+
URL="${1:-}"
|
|
15
|
+
[ -z "$URL" ] && eyes_die "usage: visual.sh <url> [WxH] [--selector css] [--wait-for-selector css] [--full-page|--viewport-only]"
|
|
16
|
+
shift
|
|
17
|
+
|
|
18
|
+
VIEWPORT="1280,800"
|
|
19
|
+
FULLPAGE="--full-page"
|
|
20
|
+
SELECTOR=""
|
|
21
|
+
WAIT_SELECTOR=""
|
|
22
|
+
|
|
23
|
+
# Accept either WxH or W,H as second positional arg
|
|
24
|
+
if [ "$#" -gt 0 ] && [[ "$1" =~ ^[0-9]+[x,][0-9]+$ ]]; then
|
|
25
|
+
VIEWPORT="${1/x/,}"
|
|
26
|
+
shift
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
while [ "$#" -gt 0 ]; do
|
|
30
|
+
case "$1" in
|
|
31
|
+
--selector) SELECTOR="$2"; shift 2;;
|
|
32
|
+
--wait-for-selector) WAIT_SELECTOR="$2"; shift 2;;
|
|
33
|
+
--full-page) FULLPAGE="--full-page"; shift;;
|
|
34
|
+
--viewport-only) FULLPAGE=""; shift;;
|
|
35
|
+
*) eyes_die "unknown arg: $1";;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
eyes_require npx
|
|
40
|
+
|
|
41
|
+
OUT="$(eyes_out_dir)/screenshot-$(eyes_timestamp).png"
|
|
42
|
+
ARGS=(playwright screenshot --browser=chromium --viewport-size="$VIEWPORT" --wait-for-timeout=1500)
|
|
43
|
+
[ -n "$FULLPAGE" ] && ARGS+=("$FULLPAGE")
|
|
44
|
+
[ -n "$WAIT_SELECTOR" ] && ARGS+=(--wait-for-selector "$WAIT_SELECTOR")
|
|
45
|
+
|
|
46
|
+
# --selector limits to one element
|
|
47
|
+
if [ -n "$SELECTOR" ]; then
|
|
48
|
+
# Playwright CLI doesn't take a CSS selector for element-only screenshots,
|
|
49
|
+
# so when --selector is used we fall back to a tiny node script.
|
|
50
|
+
eyes_timeout 45 npx --yes -p playwright@latest node -e '
|
|
51
|
+
const { chromium } = require("playwright");
|
|
52
|
+
const [,, url, viewport, selector, out] = process.argv;
|
|
53
|
+
const [w, h] = viewport.split(",").map(Number);
|
|
54
|
+
(async () => {
|
|
55
|
+
const b = await chromium.launch();
|
|
56
|
+
const ctx = await b.newContext({ viewport: { width: w, height: h } });
|
|
57
|
+
const p = await ctx.newPage();
|
|
58
|
+
await p.goto(url, { waitUntil: "networkidle", timeout: 20000 });
|
|
59
|
+
const el = await p.waitForSelector(selector, { timeout: 15000 });
|
|
60
|
+
await el.screenshot({ path: out });
|
|
61
|
+
await b.close();
|
|
62
|
+
})().catch(e => { console.error(e.message); process.exit(1); });
|
|
63
|
+
' "$URL" "$VIEWPORT" "$SELECTOR" "$OUT" >&2 || eyes_die "playwright element screenshot failed"
|
|
64
|
+
else
|
|
65
|
+
eyes_timeout 45 npx --yes -p playwright@latest playwright "${ARGS[@]}" "$URL" "$OUT" >&2 \
|
|
66
|
+
|| eyes_die "playwright screenshot failed (is the server running at $URL?)"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
[ -s "$OUT" ] || eyes_die "screenshot empty: $OUT"
|
|
70
|
+
echo "$OUT"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# impl: simctl
|
|
3
|
+
# iOS Simulator screenshot — works for native iOS, React Native, Flutter,
|
|
4
|
+
# Capacitor, any project that lands on an iOS simulator.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# visual-ios.sh [device-udid]
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
11
|
+
|
|
12
|
+
eyes_require xcrun
|
|
13
|
+
|
|
14
|
+
DEVICE="${1:-booted}"
|
|
15
|
+
|
|
16
|
+
# Verify the target exists and is booted
|
|
17
|
+
if [ "$DEVICE" = "booted" ]; then
|
|
18
|
+
xcrun simctl list devices booted 2>/dev/null | grep -q Booted \
|
|
19
|
+
|| eyes_die "no iOS simulator booted. boot one: 'xcrun simctl boot <udid>' or open Simulator.app"
|
|
20
|
+
else
|
|
21
|
+
xcrun simctl list devices 2>/dev/null | grep -q "$DEVICE" \
|
|
22
|
+
|| eyes_die "simulator not found: $DEVICE"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
OUT="$(eyes_out_dir)/screenshot-ios-$(eyes_timestamp).png"
|
|
26
|
+
eyes_timeout 15 xcrun simctl io "$DEVICE" screenshot "$OUT" >&2 \
|
|
27
|
+
|| eyes_die "simctl screenshot failed"
|
|
28
|
+
[ -s "$OUT" ] || eyes_die "screenshot empty"
|
|
29
|
+
echo "$OUT"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# impl: window
|
|
3
|
+
# Desktop window/screen capture — Tauri, Electron, native Qt/GTK, games running
|
|
4
|
+
# in a window. macOS uses screencapture; Linux dispatches to grim (Wayland),
|
|
5
|
+
# scrot (X11), or ImageMagick's import.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# visual-window.sh [--app <bundle-or-name>] (macOS only: captures the named app's frontmost window)
|
|
9
|
+
# visual-window.sh (captures the frontmost window / full screen as fallback)
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
12
|
+
source "$SCRIPT_DIR/_lib.sh"
|
|
13
|
+
|
|
14
|
+
APP=""
|
|
15
|
+
while [ "$#" -gt 0 ]; do
|
|
16
|
+
case "$1" in
|
|
17
|
+
--app) APP="$2"; shift 2;;
|
|
18
|
+
*) eyes_die "unknown arg: $1";;
|
|
19
|
+
esac
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
OUT="$(eyes_out_dir)/screenshot-window-$(eyes_timestamp).png"
|
|
23
|
+
|
|
24
|
+
case "$(eyes_os)" in
|
|
25
|
+
darwin)
|
|
26
|
+
eyes_require screencapture
|
|
27
|
+
if [ -n "$APP" ]; then
|
|
28
|
+
# Get windowid for the named app's frontmost window via AppleScript.
|
|
29
|
+
WID="$(osascript -e "tell application \"System Events\" to tell (first process whose name is \"$APP\" or bundle identifier is \"$APP\") to id of front window" 2>/dev/null || true)"
|
|
30
|
+
if [ -n "$WID" ]; then
|
|
31
|
+
screencapture -o -x -l "$WID" "$OUT" >/dev/null 2>&1 || eyes_die "screencapture of window $WID failed"
|
|
32
|
+
else
|
|
33
|
+
eyes_die "could not find frontmost window of app: $APP"
|
|
34
|
+
fi
|
|
35
|
+
else
|
|
36
|
+
# Frontmost window of whichever app is active.
|
|
37
|
+
screencapture -o -x -w "$OUT" >/dev/null 2>&1 \
|
|
38
|
+
|| screencapture -o -x "$OUT" >/dev/null 2>&1 \
|
|
39
|
+
|| eyes_die "screencapture failed"
|
|
40
|
+
fi
|
|
41
|
+
;;
|
|
42
|
+
linux)
|
|
43
|
+
if [ -n "${WAYLAND_DISPLAY:-}" ] && command -v grim >/dev/null 2>&1; then
|
|
44
|
+
grim "$OUT" || eyes_die "grim failed"
|
|
45
|
+
elif command -v scrot >/dev/null 2>&1; then
|
|
46
|
+
scrot -u "$OUT" 2>/dev/null || scrot "$OUT" || eyes_die "scrot failed"
|
|
47
|
+
elif command -v import >/dev/null 2>&1; then
|
|
48
|
+
import -window root "$OUT" || eyes_die "import failed"
|
|
49
|
+
else
|
|
50
|
+
eyes_die "no screen-capture tool found (tried: grim, scrot, import)"
|
|
51
|
+
fi
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
eyes_die "window capture not implemented for $(eyes_os)"
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
|
|
58
|
+
[ -s "$OUT" ] || eyes_die "screenshot empty: $OUT"
|
|
59
|
+
echo "$OUT"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install.sh <impl> [--as <name>]
|
|
3
|
+
# Installs deps for <impl>, copies impl/<impl>.sh into .gg/eyes/<name>.sh (default "visual").
|
|
4
|
+
# Multi-impl projects install once per impl with distinct --as names,
|
|
5
|
+
# e.g. visual-ios / visual-android.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$HERE/../../shared/_lib.sh"
|
|
10
|
+
|
|
11
|
+
IMPL="${1:-}"
|
|
12
|
+
[ -z "$IMPL" ] && eyes_die "usage: install.sh <impl> [--as <name>]"
|
|
13
|
+
shift
|
|
14
|
+
|
|
15
|
+
NAME="visual"
|
|
16
|
+
while [ "$#" -gt 0 ]; do
|
|
17
|
+
case "$1" in
|
|
18
|
+
--as) NAME="$2"; shift 2;;
|
|
19
|
+
*) eyes_die "unknown arg: $1";;
|
|
20
|
+
esac
|
|
21
|
+
done
|
|
22
|
+
|
|
23
|
+
IMPL_FILE="$HERE/impl/$IMPL.sh"
|
|
24
|
+
if [ ! -f "$IMPL_FILE" ]; then
|
|
25
|
+
echo "no impl '$IMPL'. available:" >&2
|
|
26
|
+
ls "$HERE/impl/" | sed 's/\.sh$//' | sed 's/^/ - /' >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Per-impl dep install
|
|
31
|
+
case "$IMPL" in
|
|
32
|
+
playwright)
|
|
33
|
+
eyes_require npx
|
|
34
|
+
eyes_require node
|
|
35
|
+
# Browser install is the big/slow part. 5min budget.
|
|
36
|
+
echo "installing chromium for playwright..." >&2
|
|
37
|
+
eyes_timeout 300 npx --yes -p playwright@latest playwright install chromium --with-deps 2>&1 \
|
|
38
|
+
| tail -20 >&2 \
|
|
39
|
+
|| eyes_die "playwright install failed"
|
|
40
|
+
;;
|
|
41
|
+
adb)
|
|
42
|
+
eyes_require adb
|
|
43
|
+
adb devices | tail -n +2 | grep -q device \
|
|
44
|
+
|| echo "warning: no android device/emulator currently connected (adb devices is empty). probe will fail until one is." >&2
|
|
45
|
+
;;
|
|
46
|
+
simctl)
|
|
47
|
+
[ "$(eyes_os)" = "darwin" ] || eyes_die "simctl only works on macOS (iOS simulator requires Xcode)"
|
|
48
|
+
eyes_require xcrun
|
|
49
|
+
xcrun simctl list devices booted 2>/dev/null | grep -q Booted \
|
|
50
|
+
|| echo "warning: no iOS simulator currently booted. probe will fail until one is." >&2
|
|
51
|
+
;;
|
|
52
|
+
window)
|
|
53
|
+
case "$(eyes_os)" in
|
|
54
|
+
darwin) eyes_require screencapture;;
|
|
55
|
+
linux)
|
|
56
|
+
if ! command -v grim >/dev/null 2>&1 \
|
|
57
|
+
&& ! command -v scrot >/dev/null 2>&1 \
|
|
58
|
+
&& ! command -v import >/dev/null 2>&1; then
|
|
59
|
+
eyes_die "need one of: grim (wayland), scrot (x11), or import (imagemagick)"
|
|
60
|
+
fi
|
|
61
|
+
;;
|
|
62
|
+
*) eyes_die "window capture not supported on $(eyes_os)";;
|
|
63
|
+
esac
|
|
64
|
+
;;
|
|
65
|
+
godot)
|
|
66
|
+
command -v godot >/dev/null 2>&1 \
|
|
67
|
+
|| echo "warning: godot not on PATH. install Godot and ensure 'godot' is callable." >&2
|
|
68
|
+
;;
|
|
69
|
+
unity|unreal)
|
|
70
|
+
echo "note: $IMPL headless capture requires an editor CLI. probe is best-effort; see script comments." >&2
|
|
71
|
+
;;
|
|
72
|
+
generic)
|
|
73
|
+
: ;;
|
|
74
|
+
*)
|
|
75
|
+
eyes_die "unknown impl: $IMPL"
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
79
|
+
DEST="$(eyes_root)/$NAME.sh"
|
|
80
|
+
mkdir -p "$(dirname "$DEST")"
|
|
81
|
+
cp "$IMPL_FILE" "$DEST"
|
|
82
|
+
chmod +x "$DEST"
|
|
83
|
+
cp "$HERE/../../shared/_lib.sh" "$(eyes_root)/_lib.sh"
|
|
84
|
+
cp "$HERE/../../shared/_redact.sh" "$(eyes_root)/_redact.sh"
|
|
85
|
+
chmod +x "$(eyes_root)/_redact.sh"
|
|
86
|
+
|
|
87
|
+
echo "installed: $DEST (impl=$IMPL)" >&2
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test.sh [name]
|
|
3
|
+
# End-to-end self-test: invoke the installed probe against a real target, verify
|
|
4
|
+
# the artifact is a non-empty PNG. For impls with no always-available target
|
|
5
|
+
# (adb, simctl), degrade to "can the tool talk to a device?" — still real, just
|
|
6
|
+
# less coverage.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
source "$HERE/../../shared/_lib.sh"
|
|
10
|
+
|
|
11
|
+
NAME="${1:-visual}"
|
|
12
|
+
PROBE="$(eyes_root)/$NAME.sh"
|
|
13
|
+
[ -x "$PROBE" ] || eyes_die "probe not installed: $PROBE"
|
|
14
|
+
|
|
15
|
+
# Infer impl from the first-line comment or script contents
|
|
16
|
+
IMPL_HINT="$(grep -m1 '^# impl:' "$PROBE" | awk '{print $3}' || true)"
|
|
17
|
+
[ -z "$IMPL_HINT" ] && IMPL_HINT="$(basename "$PROBE" .sh)"
|
|
18
|
+
|
|
19
|
+
case "$IMPL_HINT" in
|
|
20
|
+
playwright|visual)
|
|
21
|
+
# Spin up a temp static server on a free port, screenshot it.
|
|
22
|
+
PORT="$(eyes_free_port)"
|
|
23
|
+
TMPDIR="$(mktemp -d)"
|
|
24
|
+
trap 'rm -rf "$TMPDIR"; [ -n "${SRV_PID:-}" ] && kill "$SRV_PID" 2>/dev/null || true' EXIT
|
|
25
|
+
cat > "$TMPDIR/index.html" <<'HTML'
|
|
26
|
+
<!doctype html><html><head><title>eyes-test</title></head>
|
|
27
|
+
<body style="background:#222;color:#0f0;font:48px monospace;padding:2em">
|
|
28
|
+
EYES PROBE OK
|
|
29
|
+
</body></html>
|
|
30
|
+
HTML
|
|
31
|
+
( cd "$TMPDIR" && python3 -m http.server "$PORT" >/dev/null 2>&1 ) & SRV_PID=$!
|
|
32
|
+
# Wait for server
|
|
33
|
+
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
34
|
+
curl -sf "http://127.0.0.1:$PORT/" >/dev/null 2>&1 && break
|
|
35
|
+
sleep 0.3
|
|
36
|
+
done
|
|
37
|
+
OUT="$("$PROBE" "http://127.0.0.1:$PORT/")"
|
|
38
|
+
[ -f "$OUT" ] && [ -s "$OUT" ] || eyes_die "probe produced no artifact: $OUT"
|
|
39
|
+
echo "ok: $OUT"
|
|
40
|
+
;;
|
|
41
|
+
adb)
|
|
42
|
+
adb devices | tail -n +2 | grep -q device || eyes_die "no android device connected; skipping e2e — install succeeded but cannot verify"
|
|
43
|
+
OUT="$("$PROBE")"
|
|
44
|
+
[ -s "$OUT" ] || eyes_die "adb probe produced empty artifact"
|
|
45
|
+
echo "ok: $OUT"
|
|
46
|
+
;;
|
|
47
|
+
simctl)
|
|
48
|
+
xcrun simctl list devices booted 2>/dev/null | grep -q Booted || eyes_die "no iOS simulator booted; cannot verify"
|
|
49
|
+
OUT="$("$PROBE")"
|
|
50
|
+
[ -s "$OUT" ] || eyes_die "simctl probe produced empty artifact"
|
|
51
|
+
echo "ok: $OUT"
|
|
52
|
+
;;
|
|
53
|
+
window|godot|unity|unreal|generic)
|
|
54
|
+
# Best-effort: just run the probe; 'generic' exits non-zero by design.
|
|
55
|
+
set +e
|
|
56
|
+
OUT="$("$PROBE" 2>&1)"; rc=$?
|
|
57
|
+
set -e
|
|
58
|
+
if [ "$IMPL_HINT" = "generic" ]; then
|
|
59
|
+
[ "$rc" -ne 0 ] || eyes_die "generic probe should exit non-zero"
|
|
60
|
+
echo "ok (generic: no-op)"
|
|
61
|
+
else
|
|
62
|
+
[ "$rc" -eq 0 ] || eyes_die "probe failed: $OUT"
|
|
63
|
+
echo "ok: $OUT"
|
|
64
|
+
fi
|
|
65
|
+
;;
|
|
66
|
+
*)
|
|
67
|
+
eyes_die "unknown impl hint: $IMPL_HINT"
|
|
68
|
+
;;
|
|
69
|
+
esac
|
package/shared/_lib.sh
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# _lib.sh — shared helpers for all probes.
|
|
3
|
+
# Probes source this AFTER being copied into .gg/eyes/ as .gg/eyes/_lib.sh.
|
|
4
|
+
# Usage at top of probe:
|
|
5
|
+
# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
# source "$SCRIPT_DIR/_lib.sh"
|
|
7
|
+
|
|
8
|
+
eyes_project_root() {
|
|
9
|
+
if [ -n "${EYES_PROJECT_ROOT:-}" ]; then echo "$EYES_PROJECT_ROOT"; return; fi
|
|
10
|
+
local d="$PWD"
|
|
11
|
+
while [ "$d" != "/" ]; do
|
|
12
|
+
if [ -d "$d/.gg" ]; then echo "$d"; return; fi
|
|
13
|
+
d="$(dirname "$d")"
|
|
14
|
+
done
|
|
15
|
+
echo "$PWD"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
eyes_root() { echo "$(eyes_project_root)/.gg/eyes"; }
|
|
19
|
+
eyes_out_dir() { local d; d="$(eyes_root)/out"; mkdir -p "$d"; echo "$d"; }
|
|
20
|
+
eyes_state_dir() { local d; d="$(eyes_root)/state"; mkdir -p "$d"; echo "$d"; }
|
|
21
|
+
eyes_bin_dir() { local d; d="$(eyes_root)/bin"; mkdir -p "$d"; echo "$d"; }
|
|
22
|
+
|
|
23
|
+
eyes_timestamp() { date -u +"%Y%m%dT%H%M%SZ"; }
|
|
24
|
+
|
|
25
|
+
eyes_die() {
|
|
26
|
+
printf 'eyes: %s\n' "$*" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
eyes_require() {
|
|
31
|
+
command -v "$1" >/dev/null 2>&1 || eyes_die "missing required command: $1"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
eyes_os() {
|
|
35
|
+
case "$(uname -s)" in
|
|
36
|
+
Darwin) echo "darwin";;
|
|
37
|
+
Linux) echo "linux";;
|
|
38
|
+
MINGW*|MSYS*|CYGWIN*) echo "windows";;
|
|
39
|
+
*) echo "unknown";;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
eyes_arch() {
|
|
44
|
+
case "$(uname -m)" in
|
|
45
|
+
x86_64|amd64) echo "amd64";;
|
|
46
|
+
arm64|aarch64) echo "arm64";;
|
|
47
|
+
*) echo "unknown";;
|
|
48
|
+
esac
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
eyes_free_port() {
|
|
52
|
+
python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()' 2>/dev/null \
|
|
53
|
+
|| node -e 'const s=require("net").createServer(); s.listen(0,()=>{console.log(s.address().port); s.close();})' 2>/dev/null \
|
|
54
|
+
|| eyes_die "need python3 or node to pick a free port"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
eyes_state_get() {
|
|
58
|
+
local f; f="$(eyes_state_dir)/$1"
|
|
59
|
+
if [ -f "$f" ]; then cat "$f"; else echo ""; fi
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
eyes_state_set() {
|
|
63
|
+
local f; f="$(eyes_state_dir)/$1"
|
|
64
|
+
printf '%s' "$2" > "$f"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
eyes_redact() {
|
|
68
|
+
# Pipe through: cmd | eyes_redact > out
|
|
69
|
+
local r; r="$(eyes_root)/_redact.sh"
|
|
70
|
+
if [ -x "$r" ]; then "$r"; else cat; fi
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# With-timeout wrapper that works on macOS (no `timeout` by default) and Linux.
|
|
74
|
+
eyes_timeout() {
|
|
75
|
+
local secs="$1"; shift
|
|
76
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
77
|
+
timeout "$secs" "$@"
|
|
78
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
79
|
+
gtimeout "$secs" "$@"
|
|
80
|
+
else
|
|
81
|
+
# Fallback: run in background, kill after secs
|
|
82
|
+
( "$@" ) & local pid=$!
|
|
83
|
+
( sleep "$secs" && kill -TERM "$pid" 2>/dev/null ) & local killer=$!
|
|
84
|
+
wait "$pid" 2>/dev/null; local rc=$?
|
|
85
|
+
kill -TERM "$killer" 2>/dev/null || true
|
|
86
|
+
return $rc
|
|
87
|
+
fi
|
|
88
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# _redact.sh — stdin → stdout, strip obvious secrets from text artifacts.
|
|
3
|
+
# Caveat: does nothing for binary artifacts (screenshots, PDFs, audio). Image
|
|
4
|
+
# redaction of auth'd UIs is a gap the probe author must handle at capture time
|
|
5
|
+
# (e.g. Playwright --mask selectors).
|
|
6
|
+
set -eu
|
|
7
|
+
|
|
8
|
+
exec perl -pe '
|
|
9
|
+
# JWTs
|
|
10
|
+
s/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/[REDACTED_JWT]/g;
|
|
11
|
+
|
|
12
|
+
# Provider-specific API keys
|
|
13
|
+
s/\b(sk-ant-api\d+-[A-Za-z0-9_-]+)/[REDACTED_ANTHROPIC]/g;
|
|
14
|
+
s/\b(sk-proj-[A-Za-z0-9_-]+)/[REDACTED_OPENAI]/g;
|
|
15
|
+
s/\b(sk-[A-Za-z0-9]{32,})/[REDACTED_OPENAI]/g;
|
|
16
|
+
s/\b(gh[pousr]_[A-Za-z0-9]{30,})/[REDACTED_GITHUB]/g;
|
|
17
|
+
s/\b(AKIA[0-9A-Z]{16})/[REDACTED_AWS]/g;
|
|
18
|
+
s/\b(xox[abpors]-[A-Za-z0-9-]{10,})/[REDACTED_SLACK]/g;
|
|
19
|
+
|
|
20
|
+
# Bearer / Basic tokens in headers
|
|
21
|
+
s/(?i)(authorization\s*:\s*bearer\s+)[A-Za-z0-9_.\-]+/$1[REDACTED]/g;
|
|
22
|
+
s/(?i)(authorization\s*:\s*basic\s+)[A-Za-z0-9+\/=]+/$1[REDACTED]/g;
|
|
23
|
+
|
|
24
|
+
# Env-style assignments for anything whose name looks secret-y
|
|
25
|
+
s/(\w*(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|APIKEY|PRIVATE)\w*)\s*[=:]\s*[^\s;,&"\x27]+/$1=[REDACTED]/gi;
|
|
26
|
+
|
|
27
|
+
# Cookie: whole value
|
|
28
|
+
s/(?i)(cookie\s*:\s*)[^\r\n]+/$1[REDACTED_COOKIE]/g;
|
|
29
|
+
'
|