Firecrawl + 4-channel outreach (email/form/GBP/voice) + ElevenLabs + autoresearch flywheel. Auth via GWS CLI. Dynamic CF Workers via wrangler.
GWS CLI — Full Setup
All Google API authentication uses gcloud Application Default Credentials (ADC) and service accounts. No manual OAuth tokens.
#!/usr/bin/env bash
# scripts/setup-gws.sh — run once to provision all GCP resources
set -euo pipefail
PROJECT_ID="${GCP_PROJECT_ID:?Set GCP_PROJECT_ID}"
SA_NAME="map-leads-sa"
SA_EMAIL="${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
KEY_FILE="credentials/map-leads-sa.json"
echo "==> Authenticating..."
gcloud auth login --quiet
gcloud config set project "$PROJECT_ID"
echo "==> Enabling APIs..."
gcloud services enable \
places.googleapis.com \
mybusinessbusinessinformation.googleapis.com \
mybusinessaccountmanagement.googleapis.com \
businessprofileperformance.googleapis.com
echo "==> Creating service account..."
gcloud iam service-accounts create "$SA_NAME" \
--display-name="Map Leads SA" --quiet 2>/dev/null || echo "SA exists"
echo "==> Granting roles..."
for ROLE in \
"roles/businessprofileperformance.viewer" \
"roles/iam.serviceAccountTokenCreator"; do
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${SA_EMAIL}" --role="$ROLE" --quiet
done
echo "==> Generating SA key..."
mkdir -p credentials
gcloud iam service-accounts keys create "$KEY_FILE" \
--iam-account="$SA_EMAIL" --quiet
echo "Key written → $KEY_FILE"
echo "==> Setting ADC..."
export GOOGLE_APPLICATION_CREDENTIALS="$(pwd)/$KEY_FILE"
echo "GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/$KEY_FILE" >> .env
echo "==> Creating Places API key..."
gcloud services api-keys create \
--api-target=service=places.googleapis.com \
--display-name="map-leads-places" 2>/dev/null || true
echo "Done. Add $KEY_FILE to .gitignore and CF Workers secrets."
Wrangler Secrets (GCP key for CF Workers)
cat credentials/map-leads-sa.json | \
wrangler secret put GOOGLE_SA_JSON --name map-leads-analyzer
# (repeat for each worker that calls GBP API)
Workers (one per agent)
worker-discovery — cron → Places API
worker-scraper — Firecrawl reviews + site
worker-analyzer — Claude / Gemma (via Tailscale)
worker-scorer — dynamic weights from KV
worker-outreach — 4 channels
worker-flywheel — EXP-N loops
Auth via GWS CLI
GOOGLE_APPLICATION_CREDENTIALS — SA JSON path (local)
GOOGLE_SA_JSON — wrangler secret (CF Workers)
gcloud auth adc — local dev
gcloud services enable — API provisioning
No manual OAuth tokens or refresh flows
CF Infrastructure
CF Queues — worker-to-worker messaging
Durable Objects — LeadStateMachine/lead
CF KV — weights, prompts, run log
CF R2 — ElevenLabs audio, JSONL exports
CF Pages — mapleads.organizedai.vip
Local Infra (Tailscale)
claws-mac-mini — 100.82.244.127
NoClaw/Gemma — :11434 (free inference)
Hermes — :7700 (orchestration + memory)
ExoClaw — CF Worker → Tailscale bridge
OpenClaw — :18789 (agent routing)
#!/usr/bin/env bash
# scripts/deploy-workers.sh
set -euo pipefail
ACCOUNT_ID="691fe25d377abac03627d6a88d3eeac9"
WORKERS=(
"map-leads-discovery:apps/worker-discovery"
"map-leads-scraper:apps/worker-scraper"
"map-leads-analyzer:apps/worker-analyzer"
"map-leads-scorer:apps/worker-scorer"
"map-leads-outreach:apps/worker-outreach"
"map-leads-flywheel:apps/worker-flywheel"
)
for entry in "${WORKERS[@]}"; do
NAME="${entry%%:*}"
DIR="${entry##*:}"
echo "==> Deploying $NAME from $DIR..."
CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID" \
wrangler deploy --name "$NAME" \
--config "$DIR/wrangler.toml" \
--commit-dirty=true
done
echo "==> Deploying dashboard..."
CLOUDFLARE_ACCOUNT_ID="$ACCOUNT_ID" \
wrangler pages deploy apps/dashboard/dist \
--project-name map-leads \
--commit-dirty=true
echo "All deployed."
PHASE 0
Bootstrap + GWS CLI + CF Workers Scaffold
Organized Codebase, pnpm monorepo, wrangler.toml (6 workers + queues), GWS CLI setup, Durable Object schema.
- Run
scripts/setup-gws.sh — provisions GCP SA, enables APIs, writes credentials - Push SA JSON as wrangler secret:
wrangler secret put GOOGLE_SA_JSON --name map-leads-outreach - CF KV namespace:
MAP_LEADS_KV, CF Queues: discovery → scrape → analyze → score → outreach → outcome → flywheel - Durable Object:
LeadStateMachine
gcloud CLIwrangler CLIOrganized Codebase
PHASE 1
worker-discovery — Google Places
CF Worker on cron. Places API key via gcloud. Pushes to scrape-queue. Creates Durable Object per lead.
- Places key:
gcloud services api-keys create --api-target=service=places.googleapis.com - Filter: rating ≥ 2.5, review_count ≥ 10
- DO state:
discovered → push to SCRAPE_QUEUE
Google Placesgcloud API key
PHASE 2 UPDATED
worker-scraper — Firecrawl (replaces SerpAPI)
Reviews + website crawl. Contact form discovery, staff names, services. KV cache 7d TTL.
- Firecrawl key from
FC_API_KEY wrangler secret - Scrape Maps reviews page + crawl business site
- Build ReviewCorpus JSONL + SiteContext
- State → scraped. Push to analyze-queue
Firecrawlfc-a48926b25c7848bcad8c1354430359f7
PHASE 3
worker-analyzer — Claude / Gemma via Tailscale
Pain point extraction. Routes to Gemma (claws-mac-mini via ExoClaw) for bulk, Claude for final output.
- Gemma via ExoClaw →
http://100.82.244.127:11434/api/generate for classification - Claude Sonnet for final pain point JSON + evidence quotes
- Zod validation. KV category tracking
Claude SonnetGemma 27BExoClaw
PHASE 4
worker-scorer — Dynamic Weights
Weights loaded from KV (flywheel updates). Hot/warm → outreach. All → flywheel baseline.
- Load
map-leads:weights:current from KV - Hot >0.7 → outreach-queue + all channels. Warm 0.4–0.7 → email only
- Push baseline event to outcome-queue
Dynamic weights (KV)
PHASE 5 EXPANDED
worker-outreach — 4 Channels + GWS CLI Auth
Email (Resend) + Form (Firecrawl) + GBP Messages (gcloud SA) + Voice (ElevenLabs + VAPI). All run in parallel.
- Email: Claude generates. Resend API. A/B subjects. Webhook → outcome-queue
- Form: Firecrawl fills + submits contact form. CAPTCHA → skip + log
- GBP: Auth via
GOOGLE_SA_JSON wrangler secret (gcloud SA key). google-auth-library JWT client. POST to mybusiness messages endpoint - Voice: Claude script → ElevenLabs TTS → upload to CF R2 → VAPI outbound call
ResendFirecrawlGBP + gcloud SAElevenLabs + VAPI
PHASE 6 NEW
worker-flywheel — Autoresearch EXP-N
Overnight EXP-N loops. Aggregate outcomes → test weight variations → update KV weights + prompts. JSONL to CF R2.
- Triggered by 3am cron + flywheel-queue
- Run EXP-N loops (mirrors gtm-autoresearch pattern)
- Update
map-leads:weights:current when score improves >5% - Export JSONL to R2:
map-leads-experiments/EXP-{n}.jsonl - Slack webhook on improvement >5%
Autoresearch EXP-NCF KV + R2
PHASE 7
Dashboard + Wrangler Deploy
CF Pages Kanban dashboard. Deploy all 6 workers + dashboard via scripts/deploy-workers.sh.
- Run
scripts/deploy-workers.sh — deploys all 6 workers wrangler pages deploy apps/dashboard/dist --project-name map-leads --commit-dirty=true- Custom domain:
mapleads.organizedai.vip - CRON:
0 2 * * * discovery, 0 3 * * * flywheel
CF Pageswrangler CLI