@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,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
|