Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/skills/silverback-cloudflare-site/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
24 changes: 0 additions & 24 deletions .github/workflows/deploy-stockton-worker.yml

This file was deleted.

5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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/<id>` or `cd workers/<name>` 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`.
49 changes: 35 additions & 14 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<id>/{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/<worker-name>
npx wrangler deploy
# Brochure site
cd sites/dist/<site-id> && npx wrangler deploy

# Agency worker
cd workers/<worker-name> && npx wrangler deploy
```

## Silverback AI Studio Commands
Expand All @@ -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)
2 changes: 1 addition & 1 deletion ONBOARDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
7 changes: 7 additions & 0 deletions sites/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
88 changes: 88 additions & 0 deletions sites/WORKER_URL_MAP.md
Original file line number Diff line number Diff line change
@@ -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/<id>/` via `node sites/build.mjs` |
| Hand-written agency workers | `workers/<name>/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://<worker-name>.silverbackai.workers.dev/"
curl -fI "https://<custom-domain>/"
```