From c4bee6aad30162df1cd4fcd631b1db701911a792 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 10 Jul 2026 02:10:31 +0200 Subject: [PATCH] chore(seo-data): install/make/doctor wiring + gitleaks allowlist for token store --- .env.example | 9 +++++++++ .gitignore | 5 +++++ .gitleaks.toml | 3 +++ Makefile | 10 ++++++++-- doctor.sh | 15 +++++++++++++++ install.sh | 10 ++++++++++ lib/seo-data/seo-data.test.sh | 11 +++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 0c77f45..91fbe99 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,12 @@ # Used by: lib/toggle-external.sh enable|disable magic # Get a key at: https://21st.dev/magic (dashboard → API keys) MAGIC_API_KEY=your_21st_dev_magic_api_key_here + +# ── Google SEO data layer (lib/seo-data) — used by /seo FULL ── +# OAuth Desktop client: GCP console → APIs & Services → Credentials → OAuth client (Desktop). +# Scope requested at consent: webmasters.readonly. One-time setup: make seo-connect +GOOGLE_OAUTH_CLIENT_ID= +GOOGLE_OAUTH_CLIENT_SECRET= +# CrUX + PageSpeed API key (GCP console → Credentials → API key, restricted to those APIs). +# Get it: https://developer.chrome.com/docs/crux/api +CRUX_API_KEY= diff --git a/.gitignore b/.gitignore index cd80599..6cab62b 100644 --- a/.gitignore +++ b/.gitignore @@ -113,6 +113,11 @@ install-*.log .env.* !.env.example +# seo-data engine local artifacts (live under ~/.claude, never committed) +.venv-seo-data/ +seo-data/tokens.json +__pycache__/ + # OS .DS_Store Thumbs.db diff --git a/.gitleaks.toml b/.gitleaks.toml index 462c78d..d11491c 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -34,4 +34,7 @@ paths = [ # for stray COPIES of secrets outside this file; flagging the vault # itself on every run is pure noise, not signal. '''(^|/)\.env$''', + # seo-data OAuth token store — legitimate local secret (like ~/.claude/.env), + # 0600, outside git. Allowlisted so `make scan-secrets` doesn't flag the vault. + '''(^|/)\.claude/seo-data/tokens\.json$''', ] diff --git a/Makefile b/Makefile index 03ca995..7d8d1da 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help install plugin link doctor update new-skill profile profile-list profile-current profile-reset onboard test scan-secrets +.PHONY: help install plugin link doctor update new-skill profile profile-list profile-current profile-reset onboard test scan-secrets seo-connect help: ## Show available commands @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " make %-14s %s\n", $$1, $$2}' @@ -22,8 +22,14 @@ onboard: link ## Onboard an existing project (run from the project directory) @echo "Open Claude Code in your project directory and run: /onboard" @echo "Or with hints: /onboard Python FastAPI monorepo" +seo-connect: ## Connect a Google account for /seo FULL (creates venv, OAuth consent) + @python3 -m venv "$$HOME/.claude/.venv-seo-data" + @"$$HOME/.claude/.venv-seo-data/bin/pip" install -q -r lib/seo-data/requirements.txt + @bash -c 'read -r -p "Label for this account (e.g. client-a): " label; \ + "$$HOME/.claude/.venv-seo-data/bin/python3" lib/seo-data/connect.py --label "$$label"' + test: ## Run deterministic tests (lib/tests/*.test.sh + lib/gitflow-test.sh + lib/tests/run-*.sh) - @fail=0; for t in lib/tests/*.test.sh lib/gitflow-test.sh lib/tests/run-*.sh; do \ + @fail=0; for t in lib/tests/*.test.sh lib/seo-data/*.test.sh lib/gitflow-test.sh lib/tests/run-*.sh; do \ echo "== $$t"; \ case "$$(basename "$$t")" in \ run-release-candidate.sh) RC_WORK=$$(mktemp -d) RC_TAG=1 bash "$$t" || fail=1 ;; \ diff --git a/doctor.sh b/doctor.sh index 6cf9b81..647a244 100644 --- a/doctor.sh +++ b/doctor.sh @@ -400,6 +400,21 @@ fi echo "" +# ── seo-data (GSC/CrUX data layer) — non-fatal ── +ENVF="$HOME/.claude/.env" +if grep -qE '^[[:space:]]*(export[[:space:]]+)?CRUX_API_KEY=.' "$ENVF" 2>/dev/null; then + pass "seo-data: CRUX_API_KEY present" +else + warn "seo-data: CRUX_API_KEY absent in ~/.claude/.env — /seo FULL falls back to lab PageSpeed" +fi +STORE="$HOME/.claude/seo-data/tokens.json" +if [ -f "$STORE" ]; then + N=$(python3 "$REPO/lib/seo-data/tokenstore.py" list --file "$STORE" 2>/dev/null | grep -o '"label"' | wc -l) + pass "seo-data: $N Google account(s) connected" +else + warn "seo-data: no Google account connected (run: make seo-connect) — GSC data disabled" +fi + # ──────────────────────────────────────────────────────────── # Summary # ──────────────────────────────────────────────────────────── diff --git a/install.sh b/install.sh index 82b3f05..05db9ab 100755 --- a/install.sh +++ b/install.sh @@ -106,6 +106,16 @@ echo "" echo "── Setting up symlinks..." bash "$REPO/link.sh" +# ── 5b. Optional: connect a Google account for /seo FULL ── +echo "" +if [ -f "$HOME/.claude/seo-data/tokens.json" ]; then + ok "seo-data: a Google account is already connected" +else + info "SEO data layer (GSC + CrUX) is optional. To enable real Search Console" + info "data in /seo FULL: add GOOGLE_OAUTH_* + CRUX_API_KEY to ~/.claude/.env," + info "then run: make seo-connect" +fi + # ── 6. Install plugins ── echo "" echo "── Installing plugins..." diff --git a/lib/seo-data/seo-data.test.sh b/lib/seo-data/seo-data.test.sh index 5a06271..cc05458 100644 --- a/lib/seo-data/seo-data.test.sh +++ b/lib/seo-data/seo-data.test.sh @@ -106,6 +106,17 @@ has "connect.persist wrote prop" "$L3" 'sc-domain:x.com' hasnt "connect.persist redacts" "$L3" 'RT_X' rm -rf "$TMP3" +echo "── wiring locks ──" +tf() { if grep -qF -- "$3" "$2" 2>/dev/null; then ok "$1"; else no "$1" "missing: $3"; fi; } +tf "env.example client id" "$REPO/.env.example" "GOOGLE_OAUTH_CLIENT_ID=" +tf "env.example crux key" "$REPO/.env.example" "CRUX_API_KEY=" +tf "makefile seo-connect" "$REPO/Makefile" "seo-connect:" +tf "makefile discovers test" "$REPO/Makefile" "lib/seo-data/*.test.sh" +tf "install prompts connect" "$REPO/install.sh" "make seo-connect" +tf "doctor checks seo-data" "$REPO/doctor.sh" "seo-data" +tf "gitleaks allowlist store" "$REPO/.gitleaks.toml" "seo-data/tokens" +tf "gitignore venv" "$REPO/.gitignore" ".venv-seo-data" + echo "" echo "seo-data engine: $PASS pass, $FAIL fail" [ "$FAIL" -eq 0 ]