VF Tech Stack Source of Truth
Last Verified: 2026-03-04 Purpose: Stop wasting time searching for things that are already configured. Read this before claiming anything is "missing" or "not set up."
Core Principle
If you're about to say "this env var isn't configured" or "I need to find this credential" — READ THIS FILE FIRST. This file is the authoritative record of what's deployed and where.
Deployment Architecture
| App | Domain | Host | Root Directory |
|---|---|---|---|
| Website | valuefirstteam.com | Vercel | apps/website |
| Portal | clients.valuefirstteam.com | Vercel | apps/portal |
| MCP Server | Local | Node.js | apps/mcp-server |
Vercel Environment Variables — Website (valuefirstteam.com)
All confirmed present in Vercel as of Mar 25, 2026:
| Variable | Purpose | Status |
|---|---|---|
HUBSPOT_PRIVATE_APP_KEY |
VF Team HubSpot API access | Active |
JWT_SECRET |
Auth token signing | Active |
SANITY_API_TOKEN |
Sanity CMS read/write | Active |
GOOGLE_SERVICE_ACCOUNT_JSON |
Google Calendar + Gmail (base64-encoded) | Active (added Feb 24) |
GEMINI_API_KEY |
Gemini API for AI features | Active |
ELEVENLABS_API_KEY |
ElevenLabs TTS | Active |
ELEVENLABS_CHRIS_VOICE_ID |
Chris's voice clone ID | Active |
MUX_TOKEN_ID |
Mux video hosting | Active |
MUX_TOKEN_SECRET |
Mux video hosting | Active |
Mux Player Usage:
- Mux Player is a web component loaded via CDN:
<script src="https://cdn.jsdelivr.net/npm/@mux/mux-player@latest"></script> - In Astro templates (
.astro): use native HTML attributes directly (playback-id,accent-color,class,poster,preload) - In React components (
.tsx): usedangerouslySetInnerHTMLwith a template string — do NOT use JSX<mux-player>with React props. React 18 does not mapclassNametoclassfor custom elements. - Always set
preload="none"and explicitposter="https://image.mux.com/{PLAYBACK_ID}/thumbnail.webp?time=5"to prevent Error NaN metadata display - Thumbnail API:
https://image.mux.com/{PLAYBACK_ID}/thumbnail.webp?time={SECONDS}(returns 200 with image/webp) |COACH_CHRIS_ENABLED| Feature flag for Coach Chris AI | Active | |KRISP_WEBHOOK_SECRET| Krisp integration webhook | Active | |INNGEST_EVENT_KEY| Inngest event ingestion | Active (added Mar 25) | |INNGEST_SIGNING_KEY| Inngest function signing/verification | Active (added Mar 25) | |SANITY_API_KEY| Sanity API key | Active | |HUBSPOT_PRIVATE_APP_KEY| HubSpot private app key | Active | |PUBLIC_SUPABASE_URL| Supabase project URL (live chat/Q&A) | Active (added Mar 30) | |PUBLIC_SUPABASE_ANON_KEY| Supabase anon/publishable key | Active (added Mar 30) | |SUPABASE_SERVICE_ROLE_KEY| Supabase service role key (server-side) | Active (added Mar 30) |
NEVER say any of these are missing. They are confirmed deployed.
Vercel Environment Variables — Portal (clients.valuefirstteam.com)
| Variable | Purpose | Status |
|---|---|---|
JWT_SECRET |
Auth token signing | Active |
HUBSPOT_VF_TEAM_TOKEN |
VF Team HubSpot access | Active |
HUBSPOT_CLIENT_{SLUG}_TOKEN |
Per-client HubSpot tokens | Active per client |
DEV_BYPASS_AUTH |
Dev mode flag | Active (preview only) |
Local Environment Variables (Development)
| File | Variables | Notes |
|---|---|---|
/mnt/d/.env |
GEMINI_API_KEY |
Windows line endings (\r\n) — use grep | tr -d '\r' | cut -d= -f2 |
apps/website/.env |
SANITY_API_TOKEN, SANITY_DEPLOY_TOKEN |
Deploy token for Sanity Studio deploys |
Root .env |
HUBSPOT_ACCESS_TOKEN |
VF Team HubSpot |
clients/{slug}/.env |
HUBSPOT_ACCESS_TOKEN |
Per-client HubSpot |
Service Accounts & Credentials
Google Service Account
- Key file:
/mnt/d/credentials/google-service-account.json - Impersonates:
chris.carolan@valuefirstteam.com - Method: Domain-wide delegation
- Deployed as: Base64-encoded
GOOGLE_SERVICE_ACCOUNT_JSONon Vercel (both website and portal)
Google Workspace CLI (gws) — Universal Google API Access
- Command:
gws(wrapper at/usr/local/bin/gwsauto-mints token via service account) - Version: 0.9.1
- Covers: Gmail, Drive, Calendar, Docs, Sheets, Slides, Tasks, and all other Google Workspace APIs
- Auth: Transparent — wrapper runs
scripts/google/gws-token.jsto mint a token per invocation - Capability report:
skills/infrastructure/gws-capability-report.md
# Usage — just type gws, no token prefix needed
gws gmail users messages list --params '{"userId": "me", "maxResults": 5}'
gws drive files list --params '{"pageSize": 5, "fields": "files(id,name,mimeType)"}'
gws calendar events list --params '{"calendarId": "primary", "maxResults": 5}'
gws docs documents get --params '{"documentId": "DOC_ID"}'
gws schema <service.resource.method> # Discover any API method's parameters
When to use gws: Ad-hoc queries, exploring Google APIs, any read operation, quick one-liners.
When to use purpose-built scripts: Complex operations with MIME encoding, state management, or sharing logic.
Available Google Scripts (Local)
| Script | Purpose |
|---|---|
scripts/google/send-email.js |
Send email via Gmail |
scripts/google/get-inbox.js |
Read Gmail inbox |
scripts/google/get-calendar.js |
Read Google Calendar |
scripts/google/sync-transcripts.js |
Sync Drive transcripts |
scripts/google/create-draft.js |
Create Gmail draft |
scripts/google/create-doc.js |
Create Google Doc (with sharing) |
scripts/google/create-calendar-event.js |
Create calendar event |
scripts/google/gws-wrapper.sh |
gws CLI wrapper (auto-mints token) |
scripts/google/gws-token.js |
Token minter for gws |
NEVER say "I don't have Gmail/Calendar/Drive access." The service account is ALWAYS available locally. Use gws for ad-hoc queries or purpose-built scripts for complex operations. On Vercel, it's the GOOGLE_SERVICE_ACCOUNT_JSON env var.
Sanity CMS
- Project ID:
0efm0pow - Dataset:
production - API Token: In
apps/website/.envasSANITY_API_TOKEN - Deploy Token: In
apps/website/.envasSANITY_DEPLOY_TOKEN(for Studio deploys only) - CLI Query Tool:
scripts/sanity/query.js(6 presets + custom GROQ, auto-loads token) - NEVER use
curlto query Sanity. Always usenode scripts/sanity/query.js.
Supabase (Live Chat/Q&A + Community)
- Project:
lrdlfxqdoxzoxgpobbui - URL:
https://lrdlfxqdoxzoxgpobbui.supabase.co - REST API:
https://lrdlfxqdoxzoxgpobbui.supabase.co/rest/v1/ - Credentials:
apps/website/.env(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY) - Access method: REST API (PostgREST) with service role key. NEVER use direct PostgreSQL connections from WSL2 — DB host is IPv6-only, WSL2 cannot reach it.
- Migrations: Apply via Supabase Dashboard SQL Editor (not programmatic migration runners)
- Schema: 9 tables (members, spaces, threads, posts, reactions, mentions, notifications, space_follows, thread_follows)
- Required headers:
apikey: {key},Authorization: Bearer {key},Accept: application/json
HubSpot
- VF Team Portal: 40810431
- Local MCP:
mcp__hubspot__hubspot-*(22 tools, supports ALL objects — use when available) - CLI Tool:
node scripts/hubspot/api.js— unified CLI, zero deps, auto-loads token, supports ALL objects (use when MCP didn't start) - Cloud MCP:
mcp__claude_ai_HubSpot__*(does NOT support custom objects — NEVER use for VF Team) - Rule: If local MCP is available, use it. If not, use CLI. Never say HubSpot is unavailable.
- Chris Owner ID: 474813558
- Ryan Owner ID: 85787138
Build & Deploy
| Operation | Command | Duration |
|---|---|---|
| Website build | cd apps/website && npx pnpm build |
~5-6 minutes |
| Portal build | cd apps/portal && npx pnpm build |
~3 minutes |
| Sanity Studio deploy | cd apps/website/studio && npm install && SANITY_AUTH_TOKEN=$SANITY_DEPLOY_TOKEN npx sanity deploy |
~1 minute |
| Git push triggers Vercel deploy | git -C /mnt/d/Projects/value-first-operations push |
~2-3 minutes |
Chris prefers build-and-push to production. No dev server testing.
Content Infrastructure
| System | Location | Notes |
|---|---|---|
| Content Vault | /mnt/d/data/content-vault.db |
34MB SQLite, schema v2, FTS5 search |
| Content Census | /mnt/d/data/content-census-report.md |
1,003 episodes, 114 articles, 1,496 canonical |
| Sanity CMS | sanity.io project 0efm0pow |
Shows, episodes, articles, glossary terms |
| HubSpot Listings | VF Team portal | Intelligence reports, planning docs, ideal week, decision queue |
Anti-Patterns (Things That Waste Time)
| Bad Pattern | Correct Action |
|---|---|
| Searching for GOOGLE_SERVICE_ACCOUNT_JSON | It's on Vercel. Read this file. |
| Searching for SANITY_API_TOKEN location | It's in apps/website/.env. Read this file. |
Running hubspot-list-properties |
Read skills/hubspot/property-index/{object}.json instead. |
Using curl for Sanity queries |
Use node scripts/sanity/query.js. |
| Using cloud HubSpot MCP for VF Team | Use local MCP (mcp__hubspot__hubspot-*) if available, otherwise CLI (node scripts/hubspot/api.js). |
| Saying "I don't have access to Gmail" | Use gws or scripts/google/*.js. Always available. |
| Writing a new script for a Google API query | Use gws <service> <resource> <method> — it covers ALL Google APIs. |
| Claiming env vars aren't configured | Check this file first. Then check Vercel dashboard. |
| Attempting direct PostgreSQL connection to Supabase | Use REST API at /rest/v1/ with service role key. WSL2 is IPv6-blocked. |