@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,77 @@
1
+ #!/usr/bin/env bash
2
+ # impl: mailpit
3
+ # Talk to the running Mailpit HTTP API. Works for any app that can be pointed
4
+ # at a local SMTP server — language-agnostic.
5
+ #
6
+ # Usage:
7
+ # mail.sh count — number of captured messages
8
+ # mail.sh list [--limit N] — JSON summary of recent messages
9
+ # mail.sh latest — subject + from/to + redacted body of most recent
10
+ # mail.sh read <id> — full (redacted) body of message by ID
11
+ # mail.sh clear — delete all captured messages
12
+ set -euo pipefail
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ source "$SCRIPT_DIR/_lib.sh"
15
+
16
+ PORT="$(eyes_state_get mailpit.http_port)"
17
+ [ -n "$PORT" ] || eyes_die "mailpit not installed; run capture_email install"
18
+ BASE="http://127.0.0.1:$PORT"
19
+ curl -sf "$BASE/api/v1/info" >/dev/null 2>&1 || eyes_die "mailpit not responding at $BASE"
20
+
21
+ CMD="${1:-}"
22
+ shift || true
23
+
24
+ case "$CMD" in
25
+ count)
26
+ curl -sf "$BASE/api/v1/messages?limit=1" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("total",0))'
27
+ ;;
28
+ list)
29
+ LIMIT=20
30
+ [ "${1:-}" = "--limit" ] && LIMIT="$2"
31
+ curl -sf "$BASE/api/v1/messages?limit=$LIMIT" | python3 -c '
32
+ import sys, json
33
+ d = json.load(sys.stdin)
34
+ for m in d.get("messages", []):
35
+ print(json.dumps({"id": m["ID"], "from": m["From"]["Address"], "to": [t["Address"] for t in m.get("To",[])], "subject": m["Subject"], "created": m["Created"]}))
36
+ ' | "$SCRIPT_DIR/_redact.sh"
37
+ ;;
38
+ latest)
39
+ ID="$(curl -sf "$BASE/api/v1/messages?limit=1" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d["messages"][0]["ID"]) if d.get("messages") else None')"
40
+ [ -n "$ID" ] && [ "$ID" != "None" ] || eyes_die "no messages"
41
+ curl -sf "$BASE/api/v1/message/$ID" | python3 -c '
42
+ import sys, json
43
+ m = json.load(sys.stdin)
44
+ print("id:", m["ID"])
45
+ print("from:", m["From"]["Address"])
46
+ print("to:", ", ".join(t["Address"] for t in m.get("To",[])))
47
+ print("subject:", m["Subject"])
48
+ print("---")
49
+ print(m.get("Text") or m.get("HTML") or "")
50
+ ' | "$SCRIPT_DIR/_redact.sh"
51
+ ;;
52
+ read)
53
+ ID="${1:-}"
54
+ [ -n "$ID" ] || eyes_die "usage: mail.sh read <id>"
55
+ curl -sf "$BASE/api/v1/message/$ID" | python3 -c '
56
+ import sys, json
57
+ m = json.load(sys.stdin)
58
+ print("id:", m["ID"])
59
+ print("from:", m["From"]["Address"])
60
+ print("subject:", m["Subject"])
61
+ print("---")
62
+ print(m.get("Text") or m.get("HTML") or "")
63
+ ' | "$SCRIPT_DIR/_redact.sh"
64
+ ;;
65
+ clear)
66
+ curl -sf -X DELETE "$BASE/api/v1/messages" >/dev/null
67
+ echo "cleared"
68
+ ;;
69
+ ""|help|-h|--help)
70
+ cat <<USAGE
71
+ mail.sh <count|list|latest|read <id>|clear>
72
+ USAGE
73
+ ;;
74
+ *)
75
+ eyes_die "unknown subcommand: $CMD"
76
+ ;;
77
+ esac
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # capture_email/install.sh [impl]
3
+ # Installs Mailpit — a local SMTP sink + HTTP UI that any app can point at
4
+ # (Node/Python/Ruby/Go/Java/PHP/.NET — anything speaking SMTP works).
5
+ #
6
+ # After install you must configure your app's SMTP settings:
7
+ # host=127.0.0.1 port=<smtp_port> user="" pass="" tls=off
8
+ # Ports are written to .gg/eyes/state/mailpit.smtp_port and .gg/eyes/state/mailpit.http_port.
9
+ set -euo pipefail
10
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11
+ source "$HERE/../../shared/_lib.sh"
12
+
13
+ IMPL="${1:-mailpit}"
14
+ [ "$IMPL" = "mailpit" ] || eyes_die "only 'mailpit' impl is provided"
15
+
16
+ # Resolve or install binary into .gg/eyes/bin/mailpit
17
+ BIN="$(eyes_bin_dir)/mailpit"
18
+ if [ ! -x "$BIN" ]; then
19
+ if command -v mailpit >/dev/null 2>&1; then
20
+ ln -sf "$(command -v mailpit)" "$BIN"
21
+ elif [ "$(eyes_os)" = "darwin" ] && command -v brew >/dev/null 2>&1; then
22
+ brew install mailpit >&2 || eyes_die "brew install mailpit failed"
23
+ ln -sf "$(command -v mailpit)" "$BIN"
24
+ else
25
+ # Download a release binary from github
26
+ OS="$(eyes_os)"; ARCH="$(eyes_arch)"
27
+ case "$OS-$ARCH" in
28
+ darwin-amd64|darwin-arm64|linux-amd64|linux-arm64) : ;;
29
+ *) eyes_die "no prebuilt mailpit for $OS-$ARCH; install manually and re-run";;
30
+ esac
31
+ URL="https://github.com/axllent/mailpit/releases/latest/download/mailpit-${OS}-${ARCH}.tar.gz"
32
+ TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
33
+ echo "downloading mailpit ($OS-$ARCH)..." >&2
34
+ eyes_timeout 60 curl -sSL -o "$TMP/mp.tgz" "$URL" || eyes_die "download failed: $URL"
35
+ tar -xzf "$TMP/mp.tgz" -C "$TMP" || eyes_die "extract failed"
36
+ cp "$TMP/mailpit" "$BIN"
37
+ chmod +x "$BIN"
38
+ fi
39
+ fi
40
+
41
+ # Allocate ports if not already allocated, then (re)start
42
+ SMTP_PORT="$(eyes_state_get mailpit.smtp_port)"
43
+ HTTP_PORT="$(eyes_state_get mailpit.http_port)"
44
+ [ -n "$SMTP_PORT" ] || SMTP_PORT="$(eyes_free_port)"
45
+ [ -n "$HTTP_PORT" ] || HTTP_PORT="$(eyes_free_port)"
46
+ eyes_state_set mailpit.smtp_port "$SMTP_PORT"
47
+ eyes_state_set mailpit.http_port "$HTTP_PORT"
48
+
49
+ # Stop any prior instance
50
+ OLD_PID="$(eyes_state_get mailpit.pid)"
51
+ if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
52
+ kill -TERM "$OLD_PID" 2>/dev/null || true
53
+ sleep 0.3
54
+ fi
55
+
56
+ LOG="$(eyes_out_dir)/mailpit.log"
57
+ nohup "$BIN" --smtp "127.0.0.1:$SMTP_PORT" --listen "127.0.0.1:$HTTP_PORT" --quiet >"$LOG" 2>&1 &
58
+ NEW_PID=$!
59
+ eyes_state_set mailpit.pid "$NEW_PID"
60
+
61
+ # Wait for HTTP port
62
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
63
+ curl -sf "http://127.0.0.1:$HTTP_PORT/api/v1/info" >/dev/null 2>&1 && break
64
+ sleep 0.3
65
+ done
66
+ curl -sf "http://127.0.0.1:$HTTP_PORT/api/v1/info" >/dev/null 2>&1 \
67
+ || eyes_die "mailpit did not come up; see $LOG"
68
+
69
+ # Install the probe script
70
+ DEST="$(eyes_root)/mail.sh"
71
+ cp "$HERE/impl/mailpit.sh" "$DEST"
72
+ chmod +x "$DEST"
73
+ cp "$HERE/../../shared/_lib.sh" "$(eyes_root)/_lib.sh"
74
+ cp "$HERE/../../shared/_redact.sh" "$(eyes_root)/_redact.sh"
75
+ chmod +x "$(eyes_root)/_redact.sh"
76
+
77
+ cat >&2 <<EOF
78
+ mailpit running:
79
+ SMTP: 127.0.0.1:$SMTP_PORT (point your app here)
80
+ HTTP: http://127.0.0.1:$HTTP_PORT
81
+ PID: $NEW_PID
82
+ probe: $DEST (subcommands: list, count, latest, read <id>, clear)
83
+ EOF
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ # test.sh — send a real message via SMTP to the running mailpit, then verify
3
+ # the probe can list/read it.
4
+ set -euo pipefail
5
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$HERE/../../shared/_lib.sh"
7
+
8
+ PROBE="$(eyes_root)/mail.sh"
9
+ [ -x "$PROBE" ] || eyes_die "probe not installed"
10
+
11
+ SMTP_PORT="$(eyes_state_get mailpit.smtp_port)"
12
+ HTTP_PORT="$(eyes_state_get mailpit.http_port)"
13
+ [ -n "$SMTP_PORT" ] && [ -n "$HTTP_PORT" ] || eyes_die "mailpit state missing; re-run install"
14
+
15
+ curl -sf "http://127.0.0.1:$HTTP_PORT/api/v1/info" >/dev/null \
16
+ || eyes_die "mailpit not responding; run install.sh"
17
+
18
+ # Clear any prior messages
19
+ "$PROBE" clear >/dev/null
20
+
21
+ # Send via python smtplib (universal, no extra deps)
22
+ eyes_require python3
23
+ python3 - <<PY
24
+ import smtplib
25
+ from email.message import EmailMessage
26
+ m = EmailMessage()
27
+ m["Subject"] = "eyes-test"
28
+ m["From"] = "probe@eyes.test"
29
+ m["To"] = "user@eyes.test"
30
+ m.set_content("hello from the eyes probe self-test. reset link: https://example.com/reset?token=sk-proj-secretxxxxxxxxxxxxx")
31
+ with smtplib.SMTP("127.0.0.1", $SMTP_PORT, timeout=5) as s:
32
+ s.send_message(m)
33
+ PY
34
+
35
+ # Verify probe sees it
36
+ COUNT="$("$PROBE" count)"
37
+ [ "$COUNT" -ge 1 ] || eyes_die "expected >=1 message, got $COUNT"
38
+
39
+ LATEST="$("$PROBE" latest)"
40
+ echo "$LATEST" | grep -q 'eyes-test' || eyes_die "latest did not include our subject: $LATEST"
41
+ echo "$LATEST" | grep -q 'REDACTED_OPENAI' || eyes_die "redactor did not scrub the fake token in the email body"
42
+
43
+ echo "ok"
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # impl: curl
3
+ # Universal HTTP probe. Works for any backend/API regardless of language —
4
+ # Express, FastAPI, Rails, Spring, Gin, Actix, Phoenix, Laravel, etc.
5
+ #
6
+ # Usage:
7
+ # http.sh <url> [method] [body-or-@file] [-H "Header: value" ...]
8
+ #
9
+ # Prints JSON to stdout: {"status":200,"size":1234,"headers":"<path>","body":"<path>","time_ms":45}
10
+ # Body is redacted before writing.
11
+ set -euo pipefail
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ source "$SCRIPT_DIR/_lib.sh"
14
+
15
+ URL="${1:-}"
16
+ [ -z "$URL" ] && eyes_die "usage: http.sh <url> [method] [body-or-@file] [-H ...]"
17
+ METHOD="${2:-GET}"
18
+ BODY="${3:-}"
19
+
20
+ EXTRA_HEADERS=()
21
+ if [ "$#" -gt 3 ]; then
22
+ shift 3
23
+ while [ "$#" -gt 0 ]; do
24
+ case "$1" in
25
+ -H) EXTRA_HEADERS+=(-H "$2"); shift 2;;
26
+ *) eyes_die "unknown arg: $1";;
27
+ esac
28
+ done
29
+ fi
30
+
31
+ eyes_require curl
32
+
33
+ TS="$(eyes_timestamp)"
34
+ HEADERS="$(eyes_out_dir)/http-$TS.headers"
35
+ BODY_OUT="$(eyes_out_dir)/http-$TS.body"
36
+
37
+ # Build curl args
38
+ ARGS=(-sS -D "$HEADERS" -o - -X "$METHOD" --max-time 20)
39
+ ARGS+=("${EXTRA_HEADERS[@]}")
40
+ if [ -n "$BODY" ]; then
41
+ case "$BODY" in
42
+ @*) ARGS+=(--data-binary "$BODY");;
43
+ *) ARGS+=(-H "Content-Type: application/json" --data-binary "$BODY");;
44
+ esac
45
+ fi
46
+
47
+ START_MS=$(python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null || date +%s%3N 2>/dev/null || echo 0)
48
+
49
+ # Pipe body through redactor, then measure size after redaction.
50
+ if ! curl "${ARGS[@]}" "$URL" | "$(eyes_root)/_redact.sh" > "$BODY_OUT"; then
51
+ eyes_die "curl failed to reach $URL"
52
+ fi
53
+ END_MS=$(python3 -c 'import time; print(int(time.time()*1000))' 2>/dev/null || date +%s%3N 2>/dev/null || echo 0)
54
+
55
+ STATUS="$(grep -m1 -oE 'HTTP/[0-9.]+ [0-9]+' "$HEADERS" | awk '{print $2}' || echo 0)"
56
+ SIZE=$(wc -c < "$BODY_OUT" | awk '{print $1}')
57
+ TIME_MS=$(( END_MS - START_MS ))
58
+
59
+ # Also redact headers file in place (authorization, cookie)
60
+ tmp="$(mktemp)"
61
+ "$(eyes_root)/_redact.sh" < "$HEADERS" > "$tmp" && mv "$tmp" "$HEADERS"
62
+
63
+ printf '{"status":%s,"size":%s,"time_ms":%s,"headers":"%s","body":"%s"}\n' \
64
+ "${STATUS:-0}" "$SIZE" "$TIME_MS" "$HEADERS" "$BODY_OUT"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # http/install.sh [impl]
3
+ # Only one impl (curl). Copies it into .gg/eyes/http.sh.
4
+ set -euo pipefail
5
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "$HERE/../../shared/_lib.sh"
7
+
8
+ IMPL="${1:-curl}"
9
+ [ "$IMPL" = "curl" ] || eyes_die "only 'curl' impl exists for http"
10
+ eyes_require curl
11
+
12
+ DEST="$(eyes_root)/http.sh"
13
+ cp "$HERE/impl/curl.sh" "$DEST"
14
+ chmod +x "$DEST"
15
+ cp "$HERE/../../shared/_lib.sh" "$(eyes_root)/_lib.sh"
16
+ cp "$HERE/../../shared/_redact.sh" "$(eyes_root)/_redact.sh"
17
+ chmod +x "$(eyes_root)/_redact.sh"
18
+
19
+ echo "installed: $DEST (impl=curl)" >&2
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ # http/test.sh
3
+ # Spin up a minimal local server, hit it with the probe, verify JSON output
4
+ # describes the response.
5
+ set -euo pipefail
6
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$HERE/../../shared/_lib.sh"
8
+
9
+ PROBE="$(eyes_root)/http.sh"
10
+ [ -x "$PROBE" ] || eyes_die "probe not installed: $PROBE"
11
+
12
+ eyes_require python3
13
+ PORT="$(eyes_free_port)"
14
+ python3 -c "
15
+ import http.server, socketserver, json, threading
16
+ class H(http.server.BaseHTTPRequestHandler):
17
+ def do_GET(self):
18
+ self.send_response(200); self.send_header('content-type','application/json'); self.end_headers()
19
+ self.wfile.write(b'{\"ok\":true,\"token\":\"sk-proj-test-secret-xyz-1234567890\"}')
20
+ def log_message(self, *a): pass
21
+ s = socketserver.TCPServer(('127.0.0.1', $PORT), H); threading.Thread(target=s.serve_forever, daemon=True).start()
22
+ import time; time.sleep(30)
23
+ " &
24
+ SRV_PID=$!
25
+ trap 'kill "$SRV_PID" 2>/dev/null || true' EXIT
26
+
27
+ for _ in 1 2 3 4 5 6 7 8 9 10; do
28
+ curl -sf "http://127.0.0.1:$PORT/" >/dev/null 2>&1 && break
29
+ sleep 0.3
30
+ done
31
+
32
+ OUT_JSON="$("$PROBE" "http://127.0.0.1:$PORT/health")"
33
+ echo "$OUT_JSON" | grep -q '"status":200' || eyes_die "expected status 200, got: $OUT_JSON"
34
+
35
+ # Validate redactor ran on body
36
+ BODY_PATH="$(echo "$OUT_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin)["body"])')"
37
+ grep -q 'REDACTED_OPENAI' "$BODY_PATH" || eyes_die "redactor did not scrub the fake sk- token in body: $BODY_PATH"
38
+
39
+ echo "ok: $OUT_JSON"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # Emits: {"candidates":["tail","adb-logcat","simctl","docker"], "primary":"..."}
3
+ # tail works anywhere (you give it a log path); per-platform impls layer on top.
4
+ set -euo pipefail
5
+ ROOT="${EYES_PROJECT_ROOT:-$PWD}"
6
+ CANDIDATES=()
7
+ add() { case " ${CANDIDATES[*]:-} " in *" $1 "*) return;; esac; CANDIDATES+=("$1"); }
8
+
9
+ # Android
10
+ if [ -d "$ROOT/android" ] \
11
+ || [ -f "$ROOT/AndroidManifest.xml" ] \
12
+ || find "$ROOT" -maxdepth 3 -name "AndroidManifest.xml" 2>/dev/null | head -1 | grep -q .; then
13
+ add adb-logcat
14
+ fi
15
+ # iOS
16
+ if [ -d "$ROOT/ios" ] \
17
+ || find "$ROOT" -maxdepth 2 -name "*.xcodeproj" 2>/dev/null | head -1 | grep -q . \
18
+ || [ -f "$ROOT/Podfile" ]; then
19
+ add simctl
20
+ fi
21
+ # Docker / compose
22
+ if [ -f "$ROOT/docker-compose.yml" ] \
23
+ || [ -f "$ROOT/docker-compose.yaml" ] \
24
+ || [ -f "$ROOT/compose.yml" ] \
25
+ || [ -f "$ROOT/Dockerfile" ]; then
26
+ add docker
27
+ fi
28
+ # Everything can use tail; include it as the catch-all last so it's not primary.
29
+ add tail
30
+
31
+ printf '{"candidates":['
32
+ first=1
33
+ for c in "${CANDIDATES[@]}"; do
34
+ [ $first -eq 0 ] && printf ','
35
+ printf '"%s"' "$c"
36
+ first=0
37
+ done
38
+ printf '],"primary":"%s"}\n' "${CANDIDATES[0]}"
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # impl: adb-logcat
3
+ # Android runtime logs. Works for native, RN, Flutter, Capacitor.
4
+ #
5
+ # Usage:
6
+ # logs-android.sh [--device <serial>] [--tag <tag>] [--lines N] [--grep <pattern>] [--level V|D|I|W|E|F]
7
+ set -euo pipefail
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/_lib.sh"
10
+
11
+ eyes_require adb
12
+
13
+ DEVICE=""
14
+ TAG=""
15
+ LINES=200
16
+ PATTERN=""
17
+ LEVEL="V"
18
+
19
+ while [ "$#" -gt 0 ]; do
20
+ case "$1" in
21
+ --device) DEVICE="$2"; shift 2;;
22
+ --tag) TAG="$2"; shift 2;;
23
+ --lines) LINES="$2"; shift 2;;
24
+ --grep) PATTERN="$2"; shift 2;;
25
+ --level) LEVEL="$2"; shift 2;;
26
+ *) eyes_die "unknown arg: $1";;
27
+ esac
28
+ done
29
+
30
+ DEV_ARGS=()
31
+ [ -n "$DEVICE" ] && DEV_ARGS+=(-s "$DEVICE")
32
+
33
+ adb "${DEV_ARGS[@]}" get-state >/dev/null 2>&1 || eyes_die "no android device/emulator connected"
34
+
35
+ FILTER=""
36
+ if [ -n "$TAG" ]; then
37
+ FILTER="$TAG:$LEVEL *:S"
38
+ fi
39
+
40
+ # -d = dump and exit, -t N = last N lines
41
+ OUT="$(eyes_timeout 10 adb "${DEV_ARGS[@]}" logcat -d -t "$LINES" $FILTER 2>/dev/null || true)"
42
+ if [ -n "$PATTERN" ]; then
43
+ echo "$OUT" | grep -E "$PATTERN" | eyes_redact || true
44
+ else
45
+ echo "$OUT" | eyes_redact
46
+ fi
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # impl: docker
3
+ # Tail a docker container's logs. Works for any language (the container is just a
4
+ # PID from docker's PoV).
5
+ #
6
+ # Usage:
7
+ # logs-docker.sh <container-name-or-id> [--lines N] [--since <duration>] [--grep <pattern>] [--follow --timeout S]
8
+ set -euo pipefail
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ source "$SCRIPT_DIR/_lib.sh"
11
+
12
+ eyes_require docker
13
+
14
+ CONTAINER="${1:-}"
15
+ [ -z "$CONTAINER" ] && eyes_die "usage: logs-docker.sh <container> [--lines N] [--since 5m] [--grep p] [--follow --timeout S]"
16
+ shift
17
+
18
+ LINES=200
19
+ SINCE=""
20
+ PATTERN=""
21
+ FOLLOW=0
22
+ FTIMEOUT=5
23
+
24
+ while [ "$#" -gt 0 ]; do
25
+ case "$1" in
26
+ --lines) LINES="$2"; shift 2;;
27
+ --since) SINCE="--since $2"; shift 2;;
28
+ --grep) PATTERN="$2"; shift 2;;
29
+ --follow) FOLLOW=1; shift;;
30
+ --timeout) FTIMEOUT="$2"; shift 2;;
31
+ *) eyes_die "unknown arg: $1";;
32
+ esac
33
+ done
34
+
35
+ docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER" \
36
+ || docker ps --format '{{.ID}}' | grep -q "^$CONTAINER" \
37
+ || eyes_die "container not running: $CONTAINER"
38
+
39
+ if [ "$FOLLOW" -eq 1 ]; then
40
+ if [ -n "$PATTERN" ]; then
41
+ eyes_timeout "$FTIMEOUT" docker logs --tail "$LINES" -f $SINCE "$CONTAINER" 2>&1 | grep --line-buffered -E "$PATTERN" | eyes_redact
42
+ else
43
+ eyes_timeout "$FTIMEOUT" docker logs --tail "$LINES" -f $SINCE "$CONTAINER" 2>&1 | eyes_redact
44
+ fi
45
+ else
46
+ if [ -n "$PATTERN" ]; then
47
+ docker logs --tail "$LINES" $SINCE "$CONTAINER" 2>&1 | grep -E "$PATTERN" | eyes_redact || true
48
+ else
49
+ docker logs --tail "$LINES" $SINCE "$CONTAINER" 2>&1 | eyes_redact
50
+ fi
51
+ fi
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # impl: simctl
3
+ # iOS simulator log stream (last N lines).
4
+ #
5
+ # Usage:
6
+ # logs-ios.sh [--device <udid>] [--lines N] [--grep <pattern>] [--bundle <id>]
7
+ set -euo pipefail
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ source "$SCRIPT_DIR/_lib.sh"
10
+
11
+ eyes_require xcrun
12
+
13
+ DEVICE="booted"
14
+ LINES=200
15
+ PATTERN=""
16
+ BUNDLE=""
17
+
18
+ while [ "$#" -gt 0 ]; do
19
+ case "$1" in
20
+ --device) DEVICE="$2"; shift 2;;
21
+ --lines) LINES="$2"; shift 2;;
22
+ --grep) PATTERN="$2"; shift 2;;
23
+ --bundle) BUNDLE="$2"; shift 2;;
24
+ *) eyes_die "unknown arg: $1";;
25
+ esac
26
+ done
27
+
28
+ if [ "$DEVICE" = "booted" ]; then
29
+ xcrun simctl list devices booted 2>/dev/null | grep -q Booted \
30
+ || eyes_die "no iOS simulator booted"
31
+ fi
32
+
33
+ PREDICATE=""
34
+ [ -n "$BUNDLE" ] && PREDICATE="--predicate 'subsystem == \"$BUNDLE\"'"
35
+
36
+ # `log show` gives historical logs; simpler and bounded than `log stream`.
37
+ OUT="$(eyes_timeout 15 xcrun simctl spawn "$DEVICE" log show --last 3m --style compact $PREDICATE 2>/dev/null | tail -n "$LINES" || true)"
38
+ if [ -n "$PATTERN" ]; then
39
+ echo "$OUT" | grep -E "$PATTERN" | eyes_redact || true
40
+ else
41
+ echo "$OUT" | eyes_redact
42
+ fi
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # impl: tail
3
+ # Generic log probe. By convention, up.sh redirects every started process's
4
+ # stdout+stderr into .gg/eyes/out/<service>.log. This probe reads from there OR
5
+ # any arbitrary file path the caller gives it.
6
+ #
7
+ # Usage:
8
+ # logs.sh [--file <path>] [--service <name>] [--lines N] [--follow --timeout S] [--grep <pattern>]
9
+ #
10
+ # Prints the (redacted) tail to stdout.
11
+ set -euo pipefail
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ source "$SCRIPT_DIR/_lib.sh"
14
+
15
+ FILE=""
16
+ SERVICE=""
17
+ LINES=100
18
+ FOLLOW=0
19
+ FTIMEOUT=5
20
+ PATTERN=""
21
+
22
+ while [ "$#" -gt 0 ]; do
23
+ case "$1" in
24
+ --file) FILE="$2"; shift 2;;
25
+ --service) SERVICE="$2"; shift 2;;
26
+ --lines) LINES="$2"; shift 2;;
27
+ --follow) FOLLOW=1; shift;;
28
+ --timeout) FTIMEOUT="$2"; shift 2;;
29
+ --grep) PATTERN="$2"; shift 2;;
30
+ *) eyes_die "unknown arg: $1";;
31
+ esac
32
+ done
33
+
34
+ if [ -z "$FILE" ]; then
35
+ if [ -n "$SERVICE" ]; then
36
+ FILE="$(eyes_out_dir)/$SERVICE.log"
37
+ else
38
+ eyes_die "provide --file <path> or --service <name>"
39
+ fi
40
+ fi
41
+ [ -f "$FILE" ] || eyes_die "log file not found: $FILE (did you start the service via .gg/eyes/up.sh?)"
42
+
43
+ if [ "$FOLLOW" -eq 1 ]; then
44
+ if [ -n "$PATTERN" ]; then
45
+ eyes_timeout "$FTIMEOUT" tail -n "$LINES" -f "$FILE" | grep --line-buffered -E "$PATTERN" | eyes_redact
46
+ else
47
+ eyes_timeout "$FTIMEOUT" tail -n "$LINES" -f "$FILE" | eyes_redact
48
+ fi
49
+ else
50
+ if [ -n "$PATTERN" ]; then
51
+ tail -n "$LINES" "$FILE" | grep -E "$PATTERN" | eyes_redact || true
52
+ else
53
+ tail -n "$LINES" "$FILE" | eyes_redact
54
+ fi
55
+ fi
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # install.sh <impl> [--as <name>]
3
+ set -euo pipefail
4
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ source "$HERE/../../shared/_lib.sh"
6
+
7
+ IMPL="${1:-}"
8
+ [ -z "$IMPL" ] && eyes_die "usage: install.sh <impl> [--as <name>]"
9
+ shift
10
+ NAME="logs"
11
+ while [ "$#" -gt 0 ]; do
12
+ case "$1" in
13
+ --as) NAME="$2"; shift 2;;
14
+ *) eyes_die "unknown arg: $1";;
15
+ esac
16
+ done
17
+
18
+ IMPL_FILE="$HERE/impl/$IMPL.sh"
19
+ [ -f "$IMPL_FILE" ] || eyes_die "no impl: $IMPL"
20
+
21
+ case "$IMPL" in
22
+ adb-logcat) eyes_require adb;;
23
+ simctl) [ "$(eyes_os)" = "darwin" ] || eyes_die "simctl only on macOS"; eyes_require xcrun;;
24
+ docker) eyes_require docker;;
25
+ tail) : ;;
26
+ *) eyes_die "unknown impl: $IMPL";;
27
+ esac
28
+
29
+ DEST="$(eyes_root)/$NAME.sh"
30
+ cp "$IMPL_FILE" "$DEST"
31
+ chmod +x "$DEST"
32
+ cp "$HERE/../../shared/_lib.sh" "$(eyes_root)/_lib.sh"
33
+ cp "$HERE/../../shared/_redact.sh" "$(eyes_root)/_redact.sh"
34
+ chmod +x "$(eyes_root)/_redact.sh"
35
+
36
+ echo "installed: $DEST (impl=$IMPL)" >&2
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env bash
2
+ # test.sh [name]
3
+ # Tail-based e2e: write a known line to a temp file, tail --since-start, confirm we see it.
4
+ # For adb-logcat / simctl / docker we just verify the tool responds (no target to guarantee).
5
+ set -euo pipefail
6
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
+ source "$HERE/../../shared/_lib.sh"
8
+
9
+ NAME="${1:-logs}"
10
+ PROBE="$(eyes_root)/$NAME.sh"
11
+ [ -x "$PROBE" ] || eyes_die "probe not installed: $PROBE"
12
+
13
+ IMPL_HINT="$(grep -m1 '^# impl:' "$PROBE" | awk '{print $3}' || true)"
14
+
15
+ case "$IMPL_HINT" in
16
+ tail)
17
+ TMP="$(mktemp)"
18
+ trap 'rm -f "$TMP"' EXIT
19
+ printf 'line1\nline2 sk-proj-should-redact-xxxxxxxxx\nline3\n' > "$TMP"
20
+ OUT="$("$PROBE" --file "$TMP" --lines 3)"
21
+ echo "$OUT" | grep -q 'line3' || eyes_die "tail probe did not return tail of file"
22
+ echo "$OUT" | grep -q 'REDACTED_OPENAI' || eyes_die "tail probe did not redact"
23
+ echo "ok"
24
+ ;;
25
+ adb-logcat)
26
+ adb get-state >/dev/null 2>&1 || eyes_die "no android device; cannot verify"
27
+ "$PROBE" --lines 5 >/dev/null
28
+ echo "ok"
29
+ ;;
30
+ simctl)
31
+ xcrun simctl list devices booted 2>/dev/null | grep -q Booted || eyes_die "no iOS simulator booted"
32
+ "$PROBE" --lines 5 >/dev/null
33
+ echo "ok"
34
+ ;;
35
+ docker)
36
+ docker ps >/dev/null 2>&1 || eyes_die "docker not running; cannot verify"
37
+ # Find any running container to tail
38
+ C="$(docker ps --format '{{.Names}}' | head -1 || true)"
39
+ [ -n "$C" ] || { echo "ok (no containers to tail, but docker works)"; exit 0; }
40
+ "$PROBE" "$C" --lines 3 >/dev/null
41
+ echo "ok"
42
+ ;;
43
+ *) eyes_die "unknown impl hint: $IMPL_HINT";;
44
+ esac