@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +328 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/journal.d.ts +40 -0
  11. package/dist/journal.d.ts.map +1 -0
  12. package/dist/journal.js +104 -0
  13. package/dist/journal.js.map +1 -0
  14. package/dist/manifest.d.ts +16 -0
  15. package/dist/manifest.d.ts.map +1 -0
  16. package/dist/manifest.js +17 -0
  17. package/dist/manifest.js.map +1 -0
  18. package/package.json +41 -0
  19. package/probes/capture_email/impl/mailpit.sh +77 -0
  20. package/probes/capture_email/install.sh +83 -0
  21. package/probes/capture_email/test.sh +43 -0
  22. package/probes/http/impl/curl.sh +64 -0
  23. package/probes/http/install.sh +19 -0
  24. package/probes/http/test.sh +39 -0
  25. package/probes/runtime_logs/detect.sh +38 -0
  26. package/probes/runtime_logs/impl/adb-logcat.sh +46 -0
  27. package/probes/runtime_logs/impl/docker.sh +51 -0
  28. package/probes/runtime_logs/impl/simctl.sh +42 -0
  29. package/probes/runtime_logs/impl/tail.sh +55 -0
  30. package/probes/runtime_logs/install.sh +36 -0
  31. package/probes/runtime_logs/test.sh +44 -0
  32. package/probes/visual/detect.sh +78 -0
  33. package/probes/visual/impl/adb.sh +28 -0
  34. package/probes/visual/impl/generic.sh +7 -0
  35. package/probes/visual/impl/playwright.sh +70 -0
  36. package/probes/visual/impl/simctl.sh +29 -0
  37. package/probes/visual/impl/window.sh +59 -0
  38. package/probes/visual/install.sh +87 -0
  39. package/probes/visual/test.sh +69 -0
  40. package/shared/_lib.sh +88 -0
  41. 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
+ '