Docs›For Developers
Developer Guide
Everything you need to run, build, and deploy the CDB app platform.
Prerequisites
- Node.js 22+
npmorpnpm- Vercel CLI:
npm i -g vercel - Access to Vercel project (ask Jerry for token)
- Access to Supabase project (ask Jerry for service key)
Repos
| App | Location |
|-----|----------|
| Field | ~/.openclaw/workspace/cdb-field/ |
| Crew | ~/.openclaw/workspace/cdb-crew/ |
| Mission Control | ~/.openclaw/workspace/mission-control/ |
Local Development
Field
cd ~/.openclaw/workspace/cdb-field
# Install dependencies
npm install
# Set up env vars (copy from Vercel or ask Jerry)
cp .env.example .env.local
# Edit .env.local with real values
# Start dev server
npm run dev
# → http://localhost:3000
Required .env.local variables for Field
SUPABASE_URL=https://evfgrjslfrjwyopyzqzx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<from secrets/supabase-service-key.txt>
ALLOWED_USERS=david-7894,jerry-9274,phil-field
GOOGLE_CLIENT_ID=<from google-oauth-client.json>
GOOGLE_CLIENT_SECRET=<from google-oauth-client.json>
GOOGLE_REFRESH_TOKEN=<from google-tokens.json>
TWILIO_ACCOUNT_SID=<twilio console>
TWILIO_AUTH_TOKEN=<twilio console>
TWILIO_FROM_NUMBER=+14155551234
GOOGLE_CALENDAR_ID_1=<calendar id>
Stack
Field
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS v4 + custom CSS variables
- Database client: Direct Supabase REST API (via
supabase-js) - Auth: Stateless shared token — stored in localStorage, sent as
Authorization: Bearer <token>header - SMS: Twilio Node SDK
- Calendar: Google Calendar API (via googleapis)
- Markdown:
react-markdown - Deploy: Vercel
Mission Control
- Frontend: Vanilla HTML/CSS/JS (single
index.html) - API: Vercel serverless functions (Node ESM)
- Deploy: GitHub Actions → Vercel
API Routes Pattern (Field)
All API routes live in app/api/. They follow this pattern:
// app/api/contacts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export async function GET(req: NextRequest) {
// 1. Auth check
const token = req.headers.get('authorization')?.replace('Bearer ', '');
const allowed = process.env.ALLOWED_USERS?.split(',') ?? [];
if (!token || !allowed.includes(token)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 2. Query Supabase
const { data, error } = await supabase.from('contacts').select('*');
if (error) return NextResponse.json({ error: error.message }, { status: 500 });
return NextResponse.json({ contacts: data });
}
Deploying
Field (Vercel CLI)
cd ~/.openclaw/workspace/cdb-field
vercel --token $(cat ~/.openclaw/workspace/secrets/vercel-token.txt) --prod
Mission Control (Git push)
cd ~/.openclaw/workspace/mission-control
git add -A && git commit -m "feat: description"
git push origin main
# GitHub Actions deploys automatically
Database
Supabase project: evfgrjslfrjwyopyzqzx
Running queries
# Via Supabase Management API
curl -X POST \
"https://api.supabase.com/v1/projects/evfgrjslfrjwyopyzqzx/database/query" \
-H "Authorization: Bearer $(cat ~/.openclaw/workspace/secrets/supabase-mgmt-token.txt)" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT * FROM jobs LIMIT 5;"}'
Key rules
- Never delete transactions — use
status = 'excluded' - Every new contact gets pushed to Google Contacts (via
/api/contacts) - Markup not margin — all estimating UI uses markup percentages
Design System
Field uses CSS custom properties for theming:
--bg: #181614; /* page background */
--surface: #211E1A; /* cards, sidebar */
--surface-2: #2E2A25; /* input backgrounds, hover states */
--mid: #4A4540; /* borders on hover */
--muted: #7A7570; /* secondary text */
--text: #EDE8E0; /* primary text */
--white: #F7F3EC; /* headings */
--accent: #C4622A; /* CDB orange — CTAs, active states */
--accent-hover: #D4723A;
--border: #2E2A25;
Tailwind is available but the codebase prefers inline styles and CSS classes (.card, .btn, .badge-*, .page-header, etc.) defined in globals.css.
Adding a New Page
- Create
app/your-page/page.tsx - Add
'use client';at the top if it uses hooks or browser APIs - Use
page-header+page-bodyclasses for layout consistency - Add to sidebar (
components/Sidebar.tsx) under the appropriate section - Add to More page (
app/more/page.tsx) if it's a secondary page