Call Analyst — Build Docs
Everything a new collaborator needs to understand the build, run it, and start advising. Written so you can ramp without me in the room. Nothing secret is printed here; this page itself sits behind the Daxos password gate.
Live product: callanalyst.app · Repo: cus-commits/call-analyst (private) · Owner: Mark / Daxos Capital
1. What it is
Call Analyst is an AI meeting notetaker aimed at investors and dealmakers. A notetaker bot joins your Zoom/Meet/Teams call, transcribes it live, and an AI analyzes the conversation in real time and afterward. The killer surface beyond "notes" is the Projects Hub (a.k.a. Data Room): every call about a given deal/company accumulates into a persistent memory you can ask questions across.
It ships as two products from one codebase:
Consumer callanalyst.app
Internal analyst.daxos.us
2. Tech stack (deliberately boring)
- Frontend: one ~20,000-line vanilla-JS single-file PWA —
call-analyst-pwa/index.html— plustour.jsand a service workersw.js. No framework. No bundler. No build step. The HTML file is served as-is. - Backend: Netlify serverless — Node functions (
netlify/functions/*.mjs) for Stripe / credits / calendar / email, and Deno edge functions (netlify/edge-functions/*.mjs) for Recall, Projects, and storage. - Data & services: Supabase (Postgres + Auth), Stripe (billing), Recall.ai (the notetaker bot), Fireflies/Otter/Fathom (transcript sources), Google Calendar (auto-join), Cloudflare R2 (file storage), Resend (email). Anthropic + OpenAI are the AI.
- Hosting: Netlify. DNS on Cloudflare.
3. The one big idea: the build flavor flag
At the top of index.html (~line 5206) the app reads the hostname and sets flags:
window.IS_CONSUMER // true on consumer-analyst / consumer-testing hosts
window.IS_DAXOS // true on the internal daxos-analyst host
window.BUILD_FLAVOR = { // ~line 5223 — the single source of truth
tours, demoCall, accountChrome, // consumer-only capabilities
passwordGate, modelQuickSelector,// internal-only capabilities
projectsHub, aiRouting: 'proxy'|'direct', ...
}
Every behavioral difference between the two products reads a capability flag, never a bare hostname check. Consumer says "Ask AI"; internal says "Ask Claude". Consumer routes the AI through the /api/ask proxy (server keys, billing, credits); internal calls Anthropic/OpenAI directly with Mark's keys. When you add a feature, you gate it on a flag.
4. Repo & branches
cus-commits/call-analyst — one private repo. Single-file PWA + netlify/ functions.
| Branch | Build | Notes |
|---|---|---|
main | Consumer | Auto-deploys to the consumer sites (incl. prod). This is the launch product. |
internal | Internal (Daxos) | Diverges from main in ~11 files: strips Stripe checkout + bot-branding, swaps the live-transcript engine to Fireflies. CLI-deployed only. |
Gitignored: .backups/, .netlify/, node_modules/, .env*, *.bak. No secrets and no customer/founder content ever land in git.
5. Deploy topology
Deploy command for every site (memorize the dir flag):
netlify deploy --dir=call-analyst-pwa --site=<site-id> # add --prod to publish
# NEVER --dir=. → that publishes the repo root and breaks the site.
Consumer sites (IS_CONSUMER=true)
| Site | URL | How it deploys |
|---|---|---|
| consumer-analyst PROD | callanalyst.app | Repo-linked, auto-publishes from main |
| consumer-testing | consumer-testing.netlify.app | Repo-linked, auto-publishes from main |
| callanalyst-launch-preview | launch.callanalyst.app | CLI promote / throwaway preview |
Internal sites (IS_CONSUMER=false)
| daxos-analyst | analyst.daxos.us | repo=None — CLI deploy only |
| call-analyst | call-analyst.netlify.app | builds stopped; CLI deploys of internal |
| call-analyst-testing | call-analyst-testing.netlify.app | CLI deploy of internal |
main auto-publishes to both consumer-testing and prod (callanalyst.app) at once. There is no safe staging on main. To test without touching prod, push a feature branch and use the Netlify deploy-preview URL, or drag-deploy to a throwaway preview site by id. Never push experimental work straight to main.6. The app — index.html (~20.5k lines)
All markup, CSS, and JS inline. You navigate by these anchors (approximate lines — search the nearby function name to be exact):
| Module | ~Line | What lives there |
|---|---|---|
| Flavor system | 5206–5274 | IS_CONSUMER, BUILD_FLAVOR, brand + "AI"/"Claude" strings |
| Auth (Supabase) | 12633–12930 | magic-link + Google OAuth, JWT, consumer-only |
| Home / calls UI | 9512 (renderContent) | #cvHome shell, calls list, tabs: Transcript / Ask AI / Auto-Analysis |
| Live transcript sync | 8099–8634 | checkMeetings() + pollTx() Fireflies poll; Recall streaming into the same line model |
| Ask AI composer | 11387 (sendChat) | builds messages → /api/ask, streaming |
| Model selector + registry | 8648 (MODEL_INFO) | fable-5 / opus / sonnet / haiku / gpt-4o; internal quick-selector |
| Projects Hub / Data Room | 13098+ (capDr*) | per-project chat, renderers, aap2 = ask-across-projects (15393) |
| Settings | 12056 (renderSettings) | Connections/keys, BYOK, storage meter |
| Paywall / billing | 4955 (showPaywall) | Founding-50 banner, tiers, /api/checkout |
| AI credits | 6637 | near-limit nudge, credits config UI |
Key globals: store (localStorage wrapper, ca_* keys) · getKeys()/getEnabledKeys() (notetaker API keys) · getSessions() (the canonical call+transcript records every renderer and poller consumes).
tour.js — interactive product tour, consumer-only (hard if(!BUILD_FLAVOR.tours)return). Two flows (call / hub) × three styles. Also defines caStartDemoCall(), which seeds a real sample session (Northbeam Robotics seed pitch, 25 turns) through the actual machinery, so transcript, analyses, and Ask AI genuinely work on the demo.
7. Serverless functions
netlify.toml: publish dir call-analyst-pwa; Node functions in netlify/functions, Deno edge in netlify/edge-functions (each self-registers its route via config.path). Scheduled jobs: calendar-poll + credit-autorefill every 2 min, cost-alarm hourly.
Node functions — netlify/functions/
| File | Does |
|---|---|
ask.mjs | /api/ask — the AI proxy: auth, paywall, per-tier credit limits, model registry, BYOK, cost tracking |
checkout.mjs | creates Stripe Checkout (trial→sub), auto-applies Founding/TEAMDAXOS coupon |
stripe-webhook.mjs | syncs subscription status + credit-pack purchases into Supabase |
stripe-coupon-status.mjs | real Founding-50 redemption count for the paywall counter |
subscription-change/preview.mjs | prorated in-place plan changes (+ preview) |
portal.mjs | opens the Stripe Customer Portal |
credits-config / credits-checkout / credit-autorefill | AI-credit wallet: state, one-time pack purchase, scheduled off-session refill |
cost-alarm.mjs | hourly fleet-spend alarm, emails Mark if 24h cost exceeds a ceiling |
recall-webhook.mjs | /api/webhook/recall — Svix-verified bot webhook; stores transcript chunks + bot status |
profile-byok / profile-org | saves AES-GCM-encrypted BYOK keys / org name (brands the bot) |
welcome-email.mjs | one-time "you're in" email (idempotent) |
calendar-*.mjs | Google Calendar OAuth start/callback, status, events, update, exclude, poll (auto-join), poll-now (Test) |
projects-file-text.mjs | extracts text from uploaded PDFs/docx (pdf-parse/mammoth), caches it |
lib/* | shared.mjs (JWT+Supabase helpers), credits.mjs, bot-branding.mjs, atrack.mjs (analytics), _r2.mjs (R2 presigner) |
Edge functions — netlify/edge-functions/
| File | Does |
|---|---|
recall-bot-create.mjs | /api/recall/bot — dispatch a notetaker bot (join now or join_at), tier-gated, branded |
recall-bot-leave / recall-calls / recall-chunks | bot leave; list a user's notetaker calls; full finalized transcript |
projects.mjs / projects-id.mjs | /api/projects list+create / get+patch+soft-delete a project |
projects-ask.mjs | /api/projects/:id/ask — AI proxy with a system prompt built from the project's transcripts+files+notes |
projects-upload-url / project-from-transcript | signed PUT for direct upload; create a Data Room from a finished call |
storage-usage.mjs | storage used vs tier caps for the UI meter |
profile-bot-image.mjs | Pro custom bot camera image |
notetaker-proxy.mjs | SSRF-safe proxy to Otter/Fathom (works around their missing CORS) |
lib/* | shared.mjs, r2.mjs, bot-branding.mjs (default bot JPEG + display-name) |
8. How data flows
A live call (via the Recall notetaker bot)
- User pastes a meeting link or has calendar auto-join on → app POSTs
/api/recall/bot. recall-bot-createtier-gates, brands the bot (org name + logo), tells Recall to join with streaming transcript + webhook, inserts arecall_botsrow.- Recall joins the meeting. As people speak it POSTs
transcript.dataevents to/api/webhook/recall. recall-webhookSvix-verifies, dedupes, updates bot status, insertsrecall_transcript_chunks(bumping bot-hours).- The app polls
/api/recall/chunksand renders the growing transcript into the live call card viagetSessions()+renderContent().
Ask AI
- User types in the composer →
sendChat()builds{system, messages, model}. - Consumer POSTs
/api/ask: authenticate (Supabase JWT) → enforce paywall/tier. - Look up the model → pick provider (Anthropic vs OpenAI) → pick key (server env key, or the user's BYOK).
- Check/deduct AI credits (monthly allotment, then the purchased wallet).
- Call the provider, stream the answer back, log tokens + cost. (Internal builds skip the proxy and stream directly.)
9. Integrations
| Service | Role |
|---|---|
| Supabase | Auth (magic-link + password), Postgres (profiles = tier/usage/credits, plus events + RPCs for quota/usage/monthly-rollover), and a project-files storage bucket. Server writes use the service key. |
| Stripe | Subscriptions Lite/Standard/Pro ($29/$59/$149), each with monthly/annual + BYOK price variants. Founding-50 auto-coupon (20% off forever) and a secret TEAMDAXOS (33%). Checkout + webhook + customer portal + proration. |
| Recall.ai | The notetaker bot — joins Zoom/Meet/Teams, streams live transcript to our webhook, branded name+logo, auto-leave handling. ~$0.65/hr COGS. Region us-east-1. |
| Fireflies / Otter / Fathom | Alternative transcript sources. Fireflies is the internal build's live-call engine (polls active_meetings); Otter/Fathom are consumer import sources via the SSRF-safe proxy. |
| Google Calendar | OAuth connect → a scheduled poller lists events ~30 min ahead and auto-dispatches a bot with join_at. |
| Cloudflare R2 | S3-compatible Data-Room file storage (zero egress), behind a STORAGE_PROVIDER flag. Dependency-free SigV4 presigning. |
| Resend | Transactional email (welcome, cost alarms). |
10. Env & secrets
Secrets live only as Netlify environment variables on each site — never in the repo, never in this doc. The key names (so you know what exists):
SUPABASE_URL / ANON_KEY / SERVICE_KEY STRIPE_SECRET_KEY / WEBHOOK_SECRET STRIPE_PRICE_* (lite/standard/pro × mo/yr × byok) FOUNDING_COUPON_ID / TEAMDAXOS_COUPON_ID RECALL_API_KEY / WEBHOOK_SECRET / REGION_BASE ANTHROPIC_KEY OPENAI_KEY GOOGLE_OAUTH_CLIENT_ID / SECRET / CALENDAR_STATE_SECRET R2_ACCOUNT_ID / ACCESS_KEY_ID / SECRET_ACCESS_KEY / BUCKET STORAGE_PROVIDER BYOK_ENC_KEY ANALYTICS_KEY RESEND_API_KEY
process.env.X || 'sk-...' — push protection will reject it; the fix is to remove the literal. (2) Netlify env writes via netlify env:set --site silently fail; set vars through the Netlify API, and to update an existing key DELETE then POST (PUT 400s). Mark holds the actual values.11. Get it running locally
# clone (Mark will add you to the private repo)
git clone https://github.com/cus-commits/call-analyst.git
cd call-analyst
# the app is a static file — open it directly or serve it
npx serve call-analyst-pwa # or: python3 -m http.server -d call-analyst-pwa
# for the functions + a prod-like env, use the Netlify CLI
npm i -g netlify-cli
netlify link # link to a preview site, NOT prod
netlify dev # runs functions + edge locally
On localhost IS_CONSUMER is false by default (it's hostname-based), so you'll see the internal flavor unless you force the flag. To preview the consumer build, deploy a feature branch and open its Netlify deploy-preview URL — that's the realistic path.
Shipping a change safely:
- Branch off
main(git checkout -b feat/whatever). - Edit, commit, push the branch → open the Netlify deploy-preview URL to test.
- Get Mark's sign-off. Only then merge to
main(which publishes to prod). - Backend changes that the internal build shares get cherry-picked/merged into
internaltoo.
12. Where you can help / advise
Highest-leverage places for a second brain:
🏗 Build the roadmap features
🔭 Architecture review
🔌 Reliability of the call pipeline
💳 Billing & abuse
📈 Growth instrumentation
/analytics.html. Help define the funnel + the few metrics that actually predict retention.🧠 AI quality
main/prod), gate consumer-vs-internal behavior on a BUILD_FLAVOR flag, keep secrets out of the repo, and keep founder/customer content out of git. Simplicity first — match the existing style, don't refactor what isn't broken.13. Roadmap — the 7 features
Ranked, approved, mockups built. See the visual examples →
| # | Feature | Why it matters |
|---|---|---|
| 1 | Auto pre-call "brief me" dossier build first | One tap before a meeting pulls your whole history with that person/company from the Projects Hub. Unfair advantage; runs on data we own. |
| 2 | Live in-call coaching panel most viral | Talk-ratio nudges, next-best question, instant answers from your own deal history — live and consensual. |
| 3 | One-tap agentic follow-through biggest moat | On call-end, drafts the follow-up email + CRM update + task. Solves the field's #1 unsolved problem: action items that never get done. |
| 4 | Proactive cross-deal push | The Hub stops waiting to be asked — tells you what changed across your pipeline (deals gone quiet, champion left, a number moved). |
| 5 | Bot-free / consent-first capture | The category's biggest wound is the uninvited bot. An optional on-device mode turns that wound into our wedge. |
| 6 | No-login recap share pages | Public recap with a "powered by — get your own" footer. Cheapest growth loop in the category. |
| 7 | Explainable deal scoring | A score per deal with the why exposed. Competitors score but hide the reasoning, so nobody trusts it. |
Full spec + constraints: memory file project_callanalyst_7features_build.md. Build target: consumer testing/preview only until approved, never prod, never the internal build.
14. Gotchas & glossary
main= prod. Pushingmainpublishes to callanalyst.app. Use feature branches + deploy-previews to test.- Always
--dir=call-analyst-pwaon deploy.--dir=.breaks the site. - Gate consumer/internal on
BUILD_FLAVOR, never a raw hostname check. - Netlify env writes via CLI
--sitesilently fail — use the API; DELETE+POST to update a key. - No secret literals in code; no founder content in git.
- callanalyst.app ≠ DRA. "Data Room Analyst" (dra.daxos.us) is a separate product in a separate repo. The "Projects Hub" inside Call Analyst is a different thing despite the similar "data room" language. Don't cross them.
Glossary: PWA installable web app · Projects Hub / Data Room per-deal persistent memory inside Call Analyst · aap2 ask-across-projects · BYOK bring-your-own-key · notetaker bot the Recall.ai participant · Founding 50 first-50-users 20%-off coupon · flavor consumer vs internal build.