Launch the canned proposal trace →
Click once. The Generate page auto-loads the HVAC sample brief and replays the 11-step pipeline (intake → extract → enrichment → pricing → compliance → scope → PDF render → audit). No backend, no signup, no operator interaction. Lands on a ready-to-review proposal in about 60 seconds.
Voice-agent GTM runtime. An inbound voice agent enriches the lead from CRM context, structured LLM extraction generates a branded PDF proposal, every step writes audit logs, and operators review the result in the ops-console: one repo, one runnable thing, end-to-end against synthetic fixtures (DEMO_MODE) or a live backend.
apps/ops-console/: operator UI: React (loaded via UMD + babel-standalone, no build step) for the main/console/;/evaluation/is a compatibility redirect into/console/?route=evals, and/eval-runs/remains the static harness output surface. Same code runs static (DEMO_MODE) or against the live backend. Includes the Agents route and the Evals regression lab: live ElevenLabs ConvAI playgrounds for the Sales Coach + Sarah Intake agents wired to app context.lib/: intake, CRM enrichment, post-call processing, LLM extraction, branded PDF generation, audit log surface, evaluation hooks.lib/html-report-generator.ts: explicit context → Mustache template → HTML report boundary; PDFs render from this HTML artifact.server.ts: Express/api/*surface (live mode, full Express backend).functions/api/: Cloudflare Pages Functions that port a selected subset of the/api/*surface so the Pages deploy can answer those routes server-side. These functions read from D1 first and fall back to the bundled fixtures when D1 is empty or unbound. Unported routes fall through to the fixture/static shims, and/api/generatestays Express-only on a separate host.cli.ts: presales pipeline CLI.templates/: branded PDF templates rendered withtokens/.tokens/: machine-readable extracts of the brand system (tokens.css,tokens.json,tokens.tailwind.js); seeDESIGN.mdfor the long-form spec.
The n8n workflow library is the single source of truth at wranngle/n8n, not duplicated here.
The deployed Pages site (Cloudflare Pages project gtm-ops) serves on two hosts: app.wranngle.com is the canonical custom domain, and gtm-ops.pages.dev is the preview host. / and /index.html redirect to /console/. Open /console/ to drive the operator UI, /console/?route=evals for the native eval dashboard, and /eval-runs/ for the harness output surface. DEMO_MODE only activates on *.pages.dev hosts (including gtm-ops.pages.dev), on file://, or on a local static server. In that mode the page intercepts /api/* and serves the bundled fixtures, so the Generate page replays a canned 11-step pipeline trace without a live backend.
See ARCHITECTURE.md for the product layers (intake → enrichment → voice → post-call → presales → ops-console) and how this repo connects to its satellites:
wranngle/voice_ai_agent_evals: eval harness wired to the live ElevenLabs agentwranngle/n8n: full sanitized n8n workflow library
The app-to-harness boundary is encoded in eval-harness.manifest.json
and documented in docs/eval-harness-contract.md.
gtm_ops owns app fixtures and Playwright/Vitest semantics; the harness consumes
the manifest and normalizes results.
Live mode (full Express backend):
bun install
bun run start # Express server on :3000Static / DEMO_MODE (no backend, fixture-driven UI):
cd apps/ops-console
python3 -m http.server 4173 # then open http://localhost:4173/console/In DEMO_MODE, every /api/* call falls through to JSON in apps/ops-console/fixtures/. The same UI runs in both modes. A small "demo data" pill appears in the topbar when the backend returns no historic runs.
ElevenLabs Sales Coach + Sarah Intake mount as live ConvAI widgets from the coach launcher, Agents route, and Evals regression lab when unpkg.com/@elevenlabs/convai-widget-embed is reachable. If the embed script can't load (corporate network, strict CSP), the widget container shows a fallback message + deep link to the agent on elevenlabs.io. Append ?admin=1 to the /console/ URL to reveal admin-only agents.
bun run typecheck # tsc --noEmit
bun run test:run # vitest unit tests (~10s)
bun run test:console # Playwright UI suite, 100+ tests
bun run test:e2e # Playwright PDF/report suite
bun run eval:harness # optional: run this repo through voice_ai_agent_evalsCI runs static, unit, and console-e2e jobs on every PR (see .github/workflows/test.yml).
DESIGN.md is the canonical brand system. Token extracts in tokens/ (tokens.css, tokens.json, tokens.tailwind.js) are the machine-readable surface: vendor those into consumer repos rather than copy the long-form spec.
See LICENSE.
apps/ops-console/ deploys to Cloudflare Pages, and a selected subset of
/api/* routes is served by Pages Functions under functions/api/* (D1-backed
where bindings are configured, falling back to the bundled fixtures otherwise).
Unported routes fall through to the fixture/static shims, and /api/generate
stays Express-only on a separate host. The DEMO_MODE shim in each HTML page
also intercepts /api/* client-side, so the site stays interactive even if no
backend is wired.
npx wrangler login # browser OAuth
wrangler pages project create gtm-ops --production-branch mainOr connect the GitHub repo in the Cloudflare dashboard:
- Build command: (none, static)
- Build output directory:
apps/ops-console - Root directory:
/
bun run deploy # production
bun run deploy:preview # preview branch
bun run pages:dev # local CF Pages emulatorwrangler.toml: Pages config (pages_build_output_dir = "apps/ops-console")apps/ops-console/_headers: security + cache headers (X-Frame-Options, immutable tokens, fixture caching)apps/ops-console/_redirects:/api/*→/fixtures/*.jsonfallback for non-shim consumers
The full Express runtime (bun run start) is a node-friendlier alternative to
the Pages Functions deploy when you need things Pages Functions can't easily
do: long-running streams, native binary deps, big-memory PDF rendering.
Options: Fly.io, Render, Railway. Pages Functions remain the canonical
deploy target.