diff --git a/.claude/skills/silverback-cloudflare-site/SKILL.md b/.claude/skills/silverback-cloudflare-site/SKILL.md index 769053d..0db1b99 100644 --- a/.claude/skills/silverback-cloudflare-site/SKILL.md +++ b/.claude/skills/silverback-cloudflare-site/SKILL.md @@ -122,11 +122,14 @@ This is the **brochure-site migration playbook**. Use it for things like Chick B 5. **Always rebuild and commit before pushing** — `node sites/build.mjs` must succeed 6. **One commit per logical change** so blame is meaningful 7. **Chatbot guardrails are non-negotiable** — every chatbot must refuse off-topic questions and always surface the site's own contact info, never a third-party (like CARB's) unless the user literally asks for it by name +8. **One URL → one Worker** — never attach the same hostname to two workers. Keep `sites/WORKER_URL_MAP.md` updated. Do not recreate deleted legacy workers (e.g. `workers/cleantruckcheckstockton` — use generated `cleantruckcheck-stockton` only) + ## When in doubt - The user is `belichickgillismusk` on GitHub - The Cloudflare account is "Silverbackai Agency" - Read `sites/README.md` in the repo for the project-specific quick reference +- Read `sites/WORKER_URL_MAP.md` for the canonical hostname ↔ worker inventory - Read `CLAUDE.md` at the repo root for the architecture overview - If the user says "the [city] site is broken", they mean the file `sites/sites.json` entry with that `city` — go there first, fix the field, rebuild, push diff --git a/.github/workflows/deploy-stockton-worker.yml b/.github/workflows/deploy-stockton-worker.yml deleted file mode 100644 index 9828215..0000000 --- a/.github/workflows/deploy-stockton-worker.yml +++ /dev/null @@ -1,24 +0,0 @@ -# .github/workflows/deploy-stockton-worker.yml -name: Deploy Stockton Worker to Cloudflare - -on: - push: - branches: [main, master] - paths: - - 'workers/cleantruckcheckstockton/**' - workflow_dispatch: - -jobs: - deploy: - runs-on: ubuntu-latest - name: Deploy cleantruckcheckstockton - steps: - - uses: actions/checkout@v4 - - - name: Deploy to Cloudflare Workers - uses: cloudflare/wrangler-action@v3 - with: - apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} - accountId: bafa242dd95d3fdce72540d20accd0a2 - workingDirectory: workers/cleantruckcheckstockton - command: deploy diff --git a/AGENTS.md b/AGENTS.md index b62b8ef..d126b2d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,8 @@ Guidance for AI agents working in this repository. - **Root `package.json`** exists but only holds repo automation scripts (`setup:hermes`, `check`) and has **no dependencies** — `npm install` at the root is a no-op. Real dependencies live per-app. - **Two Node apps have real npm dependencies** (need `npm install`): `workers/silverback-ai-studio/` (React + Express + Vite) and `the-unit/api/` (`gumption-api`, a Cloud Run Express/TypeScript proxy with `npm run typecheck` + `npm run test:boot`). - **CARB / law brochure sites** — edit `sites/sites.json` and `sites/templates/*.html`, then run `node sites/build.mjs`. Never hand-edit `sites/dist/` (regenerated output, but committed so workers can be previewed without rebuilding). -- **Legacy inline workers** — `workers/silverbackai`, `security-silverbackai`, `portfolio-showcase`, `norcalcarbmobile`, etc. each have their own `wrangler.toml` and need no `npm install` (run via `npx wrangler@4`). +- **Hand-written Workers** — `workers/silverbackai`, `security-silverbackai`, `portfolio-showcase`, `norcalcarbmobile`, etc. each have their own `wrangler.toml` and need no `npm install` (run via `npx wrangler@4`). +- **One URL → one Worker** — never bind the same hostname to two workers. Canonical map: `sites/WORKER_URL_MAP.md`. Stockton is `cleantruckcheck-stockton` only (not the deleted legacy `cleantruckcheckstockton`). ### After pulling changes @@ -74,3 +75,5 @@ Only `high`/`critical` severities are broadcast. ### Deploy Push to `main` triggers path-scoped GitHub Actions (`.github/workflows/`). Manual: `cd sites/dist/` or `cd workers/` then `npx wrangler deploy` (requires `CLOUDFLARE_API_TOKEN`). + +Brochure sites deploy **only** via `deploy-sites.yml`. There is no separate Stockton workflow — the old `deploy-stockton-worker.yml` targeted a deleted path and risked resurrecting a second worker for `cleantruckcheckstockton.com`. diff --git a/CLAUDE.md b/CLAUDE.md index 7152338..9b1ff4e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,32 +6,52 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Multi-property web platform deployed on **Cloudflare Workers**, plus a standalone React security app. All workers share Cloudflare account ID `bafa242dd95d3fdce72540d20accd0a2`. +## Hard rule: one URL → one worker + +Never attach the same hostname to two Workers. Canonical inventory: **`sites/WORKER_URL_MAP.md`**. + +Known past conflict: legacy worker `cleantruckcheckstockton` and generated `cleantruckcheck-stockton` both claimed `cleantruckcheckstockton.com`. The legacy folder is gone; only the generated site is valid. Do not recreate `workers/cleantruckcheckstockton/`. + ## Architecture ``` +sites/ # Brochure / CARB / law sites (single source of truth) +├── sites.json # Edit phones, colors, prices, domains HERE +├── templates/ # carb.html, law.html, … +├── build.mjs # → sites/dist//{worker.js, wrangler.toml, …} +└── WORKER_URL_MAP.md # Canonical URL ↔ worker inventory + workers/ -├── silverbackai/ # Marketing homepage (worker.js → HTML) -├── silverbackai-toolkit/ # AI tools catalog (worker.js → HTML) -├── security-silverbackai/ # Security dashboard (worker.js → HTML + JSON API) -├── cleantruckcheckstockton/ # CARB emissions testing service (worker.js → HTML) -├── portfolio-showcase/ # 3-tile mosaic (Rent Ruby / Convicts for a Clean CA / Undefeated) — also bundles + serves rent-roll.html at /rent-ruby +├── silverbackai/ # Marketing homepage → silverbackai.agency +├── silverbackai-toolkit/ # AI tools catalog → toolkit.silverbackai.agency +├── security-silverbackai/ # Security dashboard → security.silverbackai.agency +├── portfolio-showcase/ # 3-tile mosaic → showcase.silverbackai.agency +│ # also serves rent-roll.html at /rent-ruby +├── norcalcarbmobile/ # NorCal CARB Mobile (preview on workers.dev; +│ # custom domain routes commented until cutover) └── silverback-ai-studio/ # Full React + Express security app (NOT a CF Worker) ``` -**Cloudflare Workers** (`silverbackai`, `silverbackai-toolkit`, `security-silverbackai`, `cleantruckcheckstockton`): Single `worker.js` files that return complete HTML inline. Each has a `wrangler.toml` for config and a GitHub Actions workflow for auto-deploy on push to `main`. +**Generated Cloudflare Workers** (from `sites/`): one worker per `sites.json` entry — e.g. `cleantruckcheck-stockton`, `chigbulaws`, `mobilecarbsmoketest`. Deployed by `.github/workflows/deploy-sites.yml`. + +**Hand-written Cloudflare Workers** (`silverbackai`, `silverbackai-toolkit`, `security-silverbackai`, `portfolio-showcase`, `norcalcarbmobile`): Single `worker.js` + `wrangler.toml`, each with its own path-scoped GitHub Actions deploy workflow. **silverback-ai-studio**: React 19 + Express + WebSocket app deployed to Google Cloud Run via AI Studio. Uses Firebase Firestore for event storage, Google Gemini API for AI analysis, and Google Sign-In for auth. Monitors 3875 Ruby St, Oakland. ## Deployment -All Cloudflare Worker deploys use GitHub Actions with `cloudflare/wrangler-action@v3`. Workflows are in `.github/workflows/` and trigger on pushes to `main`/`master` scoped by path (e.g., `workers/silverbackai/**`). Requires `CLOUDFLARE_API_TOKEN` secret. +All Cloudflare Worker deploys use GitHub Actions with `cloudflare/wrangler-action@v3`. Workflows are in `.github/workflows/` and trigger on pushes to `main`/`master` scoped by path (e.g., `workers/silverbackai/**` or `sites/**`). Requires `CLOUDFLARE_API_TOKEN` secret. + +Custom domain routing for agency workers is in each `wrangler.toml` via `routes`. Brochure-site custom domains are usually attached once in the Cloudflare dashboard (generated `[[routes]]` stay commented until DNS is ready). -Custom domain routing is configured in each worker's `wrangler.toml` via `routes` (e.g., `security.silverbackai.agency/*`). DNS records must be configured in Cloudflare dashboard separately. +To manually deploy: -To manually deploy a worker: ```bash -cd workers/ -npx wrangler deploy +# Brochure site +cd sites/dist/ && npx wrangler deploy + +# Agency worker +cd workers/ && npx wrangler deploy ``` ## Silverback AI Studio Commands @@ -46,9 +66,10 @@ npm run lint # TypeScript type check (tsc --noEmit) ## Key Patterns -- Workers return full HTML as template literals inside `fetch()` handler — all CSS is inlined -- Dark theme with purple accents (`#8b5cf6`) is the brand standard across all sites -- The `portfolio-showcase` worker bundles its local `rent-roll.html` via wrangler `[[rules]] type = "Text"` and serves it at `/rent-ruby` — use this pattern when a worker needs to inline a large static HTML asset instead of pasting it into a template literal +- Brochure content: edit `sites/sites.json` only, then `node sites/build.mjs` — never hand-edit `sites/dist/` or a live worker in the dashboard +- Hand-written workers return full HTML as template literals inside `fetch()` — CSS inlined +- Dark theme with purple accents (`#8b5cf6`) is the brand standard across agency sites +- The `portfolio-showcase` worker bundles its local `rent-roll.html` via wrangler `[[rules]] type = "Text"` and serves it at `/rent-ruby` - The security-silverbackai worker has `/api/health` and `/api/status` JSON endpoints - Firebase admin is hardcoded to `bryan@norcalcarbmobile.com` in studio app - Firestore rules enforce auth and role-based access (admin vs viewer) diff --git a/ONBOARDING.md b/ONBOARDING.md index ef7ac39..163e028 100644 --- a/ONBOARDING.md +++ b/ONBOARDING.md @@ -68,7 +68,7 @@ npm run lint # tsc --noEmit GitHub Actions, all using `cloudflare/wrangler-action@v3` + `CLOUDFLARE_API_TOKEN`: - **`sites/` registry** → `.github/workflows/deploy-sites.yml`. Builds from `sites.json` and deploys **every** site via a matrix on push to `main`/`master`/`claude/**` (path `sites/**`). Add a site to `sites.json` and it deploys automatically — no workflow edits. Also runnable manually (Actions → "Deploy Silverback Sites" → pick a site or ALL). -- **Older `workers/*`** → one workflow each: `deploy-silverbackai.yml`, `deploy-silverbackai-toolkit.yml`, `deploy-security-silverbackai.yml`, `deploy-portfolio-showcase.yml`, `deploy-stockton-worker.yml`, `deploy-norcalcarbmobile.yml`. Path-scoped to that worker's dir. +- **Hand-written `workers/*`** → one workflow each: `deploy-silverbackai.yml`, `deploy-silverbackai-toolkit.yml`, `deploy-security-silverbackai.yml`, `deploy-portfolio-showcase.yml`, `deploy-norcalcarbmobile.yml`. Path-scoped to that worker's dir. (No separate Stockton workflow — Stockton is `sites/dist/cleantruckcheck-stockton` via `deploy-sites.yml`. See `sites/WORKER_URL_MAP.md`.) - **`silverback-ai-studio`** → Google Cloud Run, out-of-band (not in this repo). - New CARB/brochure-site custom domains need a **one-time** manual DNS + worker-route step in the Cloudflare dashboard. diff --git a/sites/README.md b/sites/README.md index 94c61ad..da3b9b2 100644 --- a/sites/README.md +++ b/sites/README.md @@ -79,17 +79,24 @@ handler — the seam is one function in the generated `worker.js`. ## Sites currently in `sites.json` +Full URL ↔ worker inventory (including agency workers): **`WORKER_URL_MAP.md`**. + | ID | City | Domain | Theme | |---|---|---|---| +| `chigbulaws` | Elk Grove | chigbulaws.com | law (navy/gold) | | `mobilecarbsmoketest` | San Diego | mobilecarbsmoketest.com | san-diego-amber | | `cleantruckcheck-roseville` | Roseville | cleantruckcheckroseville.com | sf-giants (orange/black) | | `cleantruckcheck-fairfield` | Fairfield | cleantruckcheckfairfield.com | default-amber | +| `cleantruckcheck-lodi` | Lodi | cleantruckchecklodi.com | FSU | +| `carbteststockton` | Stockton | carbteststockton.com | Stockton Heat | | `cleantruckcheck-stockton` | Stockton | cleantruckcheckstockton.com | default-amber | | `cleantruckcheck-hayward` | Hayward | cleantruckcheckhayward.com | default-amber | | `carb-clean-truck-check` | Statewide | carb-clean-truck-check.com | default-amber | | `mobilecarbtest` | Statewide | mobilecarbtest.com | default-amber | | `mobilesmoketest` | Statewide | mobilesmoketest.com | default-amber | +**Stockton note:** only worker `cleantruckcheck-stockton` owns `cleantruckcheckstockton.com`. The old `workers/cleantruckcheckstockton` worker name is retired — do not recreate it. + Several entries have `phone: "TBD"` — fill those in when you have a moment. ## Adding a new site diff --git a/sites/WORKER_URL_MAP.md b/sites/WORKER_URL_MAP.md new file mode 100644 index 0000000..6a9ac0c --- /dev/null +++ b/sites/WORKER_URL_MAP.md @@ -0,0 +1,88 @@ +# Worker ↔ URL map (one URL = one worker) + +**Hard rule:** A hostname (custom domain or `*.workers.dev` name) may be bound to **exactly one** Cloudflare Worker. Never attach the same route to two workers. Never keep a deleted legacy worker live on Cloudflare if a generated replacement owns its domain. + +Account: Silverbackai Agency (`bafa242dd95d3fdce72540d20accd0a2`) + +Source of truth in git: + +| Kind | Path | +|------|------| +| Brochure / CARB / law sites | `sites/sites.json` → `sites/dist//` via `node sites/build.mjs` | +| Hand-written agency workers | `workers//wrangler.toml` | + +## Agency / product workers (`workers/`) + +| Worker name | Source | Custom domain(s) | workers.dev | +|-------------|--------|------------------|-------------| +| `silverbackai` | `workers/silverbackai/` | `silverbackai.agency`, `www.silverbackai.agency` | `https://silverbackai.silverbackai.workers.dev/` | +| `silverbackai-toolkit` | `workers/silverbackai-toolkit/` | `toolkit.silverbackai.agency` | `https://silverbackai-toolkit.silverbackai.workers.dev/` | +| `security-silverbackai` | `workers/security-silverbackai/` | `security.silverbackai.agency/*` | `https://security-silverbackai.silverbackai.workers.dev/` | +| `portfolio-showcase` | `workers/portfolio-showcase/` | `showcase.silverbackai.agency` (+ `/rent-ruby` for rent-roll) | `https://portfolio-showcase.silverbackai.workers.dev/` | +| `norcalcarbmobile` | `workers/norcalcarbmobile/` | *(routes commented — attach only after preview OK)* `norcalcarbmobile.com`, `www.norcalcarbmobile.com` | `https://norcalcarbmobile.silverbackai.workers.dev/` | + +`workers/silverback-ai-studio/` is **not** a Cloudflare Worker (Cloud Run). + +## Generated brochure sites (`sites/dist/`) + +Worker **name** = `sites.json` `id`. Custom domain = `sites.json` `domain`. Deploy via `.github/workflows/deploy-sites.yml` only. + +| Worker name | Custom domain | workers.dev | +|-------------|---------------|-------------| +| `chigbulaws` | `chigbulaws.com` | `https://chigbulaws.silverbackai.workers.dev/` | +| `mobilecarbsmoketest` | `mobilecarbsmoketest.com` | `https://mobilecarbsmoketest.silverbackai.workers.dev/` | +| `cleantruckcheck-roseville` | `cleantruckcheckroseville.com` | `https://cleantruckcheck-roseville.silverbackai.workers.dev/` | +| `cleantruckcheck-fairfield` | `cleantruckcheckfairfield.com` | `https://cleantruckcheck-fairfield.silverbackai.workers.dev/` | +| `cleantruckcheck-lodi` | `cleantruckchecklodi.com` | `https://cleantruckcheck-lodi.silverbackai.workers.dev/` | +| `carbteststockton` | `carbteststockton.com` | `https://carbteststockton.silverbackai.workers.dev/` | +| `cleantruckcheck-stockton` | `cleantruckcheckstockton.com` | `https://cleantruckcheck-stockton.silverbackai.workers.dev/` | +| `cleantruckcheck-hayward` | `cleantruckcheckhayward.com` | `https://cleantruckcheck-hayward.silverbackai.workers.dev/` | +| `carb-clean-truck-check` | `carb-clean-truck-check.com` | `https://carb-clean-truck-check.silverbackai.workers.dev/` | +| `mobilecarbtest` | `mobilecarbtest.com` | `https://mobilecarbtest.silverbackai.workers.dev/` | +| `mobilesmoketest` | `mobilesmoketest.com` | `https://mobilesmoketest.silverbackai.workers.dev/` | + +Custom `[[routes]]` in generated `wrangler.toml` files stay **commented** until DNS is ready; attach the domain once in the dashboard (or uncomment + deploy), then leave it alone. + +## Known dual-worker conflicts (resolved in git — clean up dashboard if leftovers remain) + +### 1. Stockton — **the** one-URL / two-workers bug + +| | Legacy (wrong) | Canonical (correct) | +|--|----------------|---------------------| +| Worker name | `cleantruckcheckstockton` (no hyphen) | `cleantruckcheck-stockton` (hyphen) | +| Source | `workers/cleantruckcheckstockton/` (**deleted**) | `sites/dist/cleantruckcheck-stockton/` | +| Domain | `cleantruckcheckstockton.com` | `cleantruckcheckstockton.com` | + +What landed in git: + +- Sites rebuild (`#9` / `9ed8539`) removed the legacy worker folder and generated the hyphenated replacement. +- Stale workflow `.github/workflows/deploy-stockton-worker.yml` still targeted the deleted path — **removed** so it cannot resurrect the orphan worker name. + +**Dashboard one-time cleanup (if still present):** + +1. Cloudflare → Workers → if `cleantruckcheckstockton` exists, remove its custom domain / routes, then **delete** that worker. +2. Ensure `cleantruckcheckstockton.com` (and `www` if used) is attached **only** to `cleantruckcheck-stockton`. + +### 2. dmc-properties (already fixed in repo — PR #44) + +- Orphan `workers/dmc-properties/` (only `rent-roll.html`, no `wrangler.toml`) caused Cloudflare Git-integration build failures. +- Rent-roll is served by **`portfolio-showcase`** at `showcase.silverbackai.agency/rent-ruby`. +- If a Cloudflare project named `dmc-properties` still exists in the dashboard, delete that project so it stops building on every push. + +## Do not recreate + +- `workers/cleantruckcheckstockton/` +- `workers/dmc-properties/` +- A second deploy workflow for any site that already deploys via `deploy-sites.yml` + +## How to verify + +```bash +# Repo inventory +rg -n '^name\s*=' workers/*/wrangler.toml sites/dist/*/wrangler.toml +jq -r '.sites[] | "\(.id)\t\(.domain)"' sites/sites.json + +# Live smoke (GitHub Actions → 🩺 Site Health Check, or locally with CF egress) +curl -fI "https://.silverbackai.workers.dev/" +curl -fI "https:///" +```