diff --git a/lib/seo-data/fetch.sh b/lib/seo-data/fetch.sh new file mode 100644 index 0000000..c8a09b0 --- /dev/null +++ b/lib/seo-data/fetch.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Stable entrypoint for the seo-data engine. JSON on stdout; exit 0 on ok/degrade, +# exit 2 on bad usage. Never prints secrets. +set -uo pipefail +HERE="$(cd "$(dirname "$0")" && pwd)" +ENV_FILE="${SEO_DATA_ENV_FILE:-${HOME}/.claude/.env}" # canonical; tests override to /dev/null +STORE="${SEO_DATA_STORE:-${HOME}/.claude/seo-data/tokens.json}" +VENV_PY="${HOME}/.claude/.venv-seo-data/bin/python3" + +# Library stderr must never leak a secret into agent context — suppress it +# globally unless explicitly debugging (SEO_DATA_DEBUG=1 restores it). +[ -n "${SEO_DATA_DEBUG:-}" ] || exec 2>/dev/null + +# Load secrets quietly (sourced, never echoed). +if [ -f "$ENV_FILE" ]; then + set -a; # shellcheck source=/dev/null + . "$ENV_FILE"; set +a +fi +# Prefer the isolated venv (has google-auth); fall back to system python3 for +# stdlib-only paths (accounts / mock / degrade). +PY="python3"; [ -x "$VENV_PY" ] && PY="$VENV_PY" + +cmd="${1:-}"; shift || true +case "$cmd" in + accounts) exec "$PY" "$HERE/tokenstore.py" list --file "$STORE" ;; + crux|queries|inspect) + exec "$PY" "$HERE/google_seo.py" "$cmd" --store "$STORE" "$@" ;; + *) echo '{"status":"error","reason":"usage: fetch.sh {accounts|crux|queries|inspect} [flags]"}' + exit 2 ;; +esac diff --git a/lib/seo-data/seo-data.test.sh b/lib/seo-data/seo-data.test.sh index f89aff1..8e9517f 100644 --- a/lib/seo-data/seo-data.test.sh +++ b/lib/seo-data/seo-data.test.sh @@ -63,6 +63,22 @@ has "gsc degrades w/o creds" "$DEG" '"status": "degraded"' has "gsc degrade reason" "$DEG" 'no_credentials' rm -rf "$TMP2" +echo "── fetch.sh ──" +FETCH="$SD/fetch.sh" +# SEO_DATA_ENV_FILE=/dev/null: tests must NEVER source the real ~/.claude/.env — +# on a machine with a live CRUX_API_KEY the degrade tests would hit the network. +NOENV=/dev/null +ACC="$(SEO_DATA_ENV_FILE=$NOENV SEO_DATA_STORE=/nonexistent/tokens.json bash "$FETCH" accounts)" +has "accounts empty is ok json" "$ACC" '"accounts": []' +CR="$(SEO_DATA_ENV_FILE=$NOENV SEO_DATA_MOCK_DIR="$MOCK" bash "$FETCH" crux --url https://ex.com)" +has "fetch crux ok" "$CR" '"status": "ok"' +SEO_DATA_ENV_FILE=$NOENV bash "$FETCH" bogus-subcmd >/dev/null 2>&1; RC=$? +[ "$RC" = "2" ] && ok "bad subcmd exit 2" || no "bad subcmd exit 2" "got $RC" +DG="$(SEO_DATA_ENV_FILE=$NOENV env -u SEO_DATA_MOCK_DIR -u CRUX_API_KEY bash "$FETCH" crux --url https://ex.com)"; RC=$? +has "degrade json" "$DG" '"status": "degraded"' +[ "$RC" = "0" ] && ok "degrade exit 0" || no "degrade exit 0" "got $RC" +hasnt "no secret echoed" "$DG" 'RT_' + echo "" echo "seo-data engine: $PASS pass, $FAIL fail" [ "$FAIL" -eq 0 ]