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

The public SaaS. Sign-up, Stripe billing, AI credits, product tours, "Ask AI" branding. This is what we're launching and where all new feature work goes.

Internal analyst.daxos.us

Daxos's own private build. Password-gated, no billing, uses Mark's own API keys, branded "Claude", live calls come from Fireflies. Off-limits for the consumer roadmap.
The split is the single most important thing to internalize. One file serves both. A runtime flag decides which product you're looking at. Get this wrong and a consumer change leaks into the internal build (or vice-versa). See §3.

2. Tech stack (deliberately boring)

Why no framework? It's one person shipping fast. A single file means no module graph to reason about, instant deploys, and an AI can hold the whole thing in context. The trade-off is a large file — you navigate by line anchors and search, which this doc gives you.

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.

This flag is layered on top of a branch split (next section). The flag decides behavior at runtime; the branch decides which code ships to which host. Both matter.

4. Repo & branches

cus-commits/call-analyst — one private repo. Single-file PWA + netlify/ functions.

BranchBuildNotes
mainConsumerAuto-deploys to the consumer sites (incl. prod). This is the launch product.
internalInternal (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)

SiteURLHow it deploys
consumer-analyst PRODcallanalyst.appRepo-linked, auto-publishes from main
consumer-testingconsumer-testing.netlify.appRepo-linked, auto-publishes from main
callanalyst-launch-previewlaunch.callanalyst.appCLI promote / throwaway preview

Internal sites (IS_CONSUMER=false)

daxos-analystanalyst.daxos.usrepo=None — CLI deploy only
call-analystcall-analyst.netlify.appbuilds stopped; CLI deploys of internal
call-analyst-testingcall-analyst-testing.netlify.appCLI deploy of internal
⚠ The #1 gotcha: 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~LineWhat lives there
Flavor system5206–5274IS_CONSUMER, BUILD_FLAVOR, brand + "AI"/"Claude" strings
Auth (Supabase)12633–12930magic-link + Google OAuth, JWT, consumer-only
Home / calls UI9512 (renderContent)#cvHome shell, calls list, tabs: Transcript / Ask AI / Auto-Analysis
Live transcript sync8099–8634checkMeetings() + pollTx() Fireflies poll; Recall streaming into the same line model
Ask AI composer11387 (sendChat)builds messages → /api/ask, streaming
Model selector + registry8648 (MODEL_INFO)fable-5 / opus / sonnet / haiku / gpt-4o; internal quick-selector
Projects Hub / Data Room13098+ (capDr*)per-project chat, renderers, aap2 = ask-across-projects (15393)
Settings12056 (renderSettings)Connections/keys, BYOK, storage meter
Paywall / billing4955 (showPaywall)Founding-50 banner, tiers, /api/checkout
AI credits6637near-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/

FileDoes
ask.mjs/api/ask — the AI proxy: auth, paywall, per-tier credit limits, model registry, BYOK, cost tracking
checkout.mjscreates Stripe Checkout (trial→sub), auto-applies Founding/TEAMDAXOS coupon
stripe-webhook.mjssyncs subscription status + credit-pack purchases into Supabase
stripe-coupon-status.mjsreal Founding-50 redemption count for the paywall counter
subscription-change/preview.mjsprorated in-place plan changes (+ preview)
portal.mjsopens the Stripe Customer Portal
credits-config / credits-checkout / credit-autorefillAI-credit wallet: state, one-time pack purchase, scheduled off-session refill
cost-alarm.mjshourly 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-orgsaves AES-GCM-encrypted BYOK keys / org name (brands the bot)
welcome-email.mjsone-time "you're in" email (idempotent)
calendar-*.mjsGoogle Calendar OAuth start/callback, status, events, update, exclude, poll (auto-join), poll-now (Test)
projects-file-text.mjsextracts 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/

FileDoes
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-chunksbot 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-transcriptsigned PUT for direct upload; create a Data Room from a finished call
storage-usage.mjsstorage used vs tier caps for the UI meter
profile-bot-image.mjsPro custom bot camera image
notetaker-proxy.mjsSSRF-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)

  1. User pastes a meeting link or has calendar auto-join on → app POSTs /api/recall/bot.
  2. recall-bot-create tier-gates, brands the bot (org name + logo), tells Recall to join with streaming transcript + webhook, inserts a recall_bots row.
  3. Recall joins the meeting. As people speak it POSTs transcript.data events to /api/webhook/recall.
  4. recall-webhook Svix-verifies, dedupes, updates bot status, inserts recall_transcript_chunks (bumping bot-hours).
  5. The app polls /api/recall/chunks and renders the growing transcript into the live call card via getSessions() + renderContent().

Ask AI

  1. User types in the composer → sendChat() builds {system, messages, model}.
  2. Consumer POSTs /api/ask: authenticate (Supabase JWT) → enforce paywall/tier.
  3. Look up the model → pick provider (Anthropic vs OpenAI) → pick key (server env key, or the user's BYOK).
  4. Check/deduct AI credits (monthly allotment, then the purchased wallet).
  5. Call the provider, stream the answer back, log tokens + cost. (Internal builds skip the proxy and stream directly.)

9. Integrations

ServiceRole
SupabaseAuth (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.
StripeSubscriptions 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.aiThe 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 / FathomAlternative 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 CalendarOAuth connect → a scheduled poller lists events ~30 min ahead and auto-dispatches a bot with join_at.
Cloudflare R2S3-compatible Data-Room file storage (zero egress), behind a STORAGE_PROVIDER flag. Dependency-free SigV4 presigning.
ResendTransactional 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

Two secret-hygiene rules that have bitten before: (1) never hardcode a fallback like 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:

  1. Branch off main (git checkout -b feat/whatever).
  2. Edit, commit, push the branch → open the Netlify deploy-preview URL to test.
  3. Get Mark's sign-off. Only then merge to main (which publishes to prod).
  4. Backend changes that the internal build shares get cherry-picked/merged into internal too.

12. Where you can help / advise

Highest-leverage places for a second brain:

🏗 Build the roadmap features

The 7 features in §13. #1 (pre-call brief) and #2 (live coaching) are next. Both run mostly on data we already have (Projects Hub + the live pipeline). Pure feature work, no infra risk.

🔭 Architecture review

It's a 20k-line single file by design. Tell us where that's about to hurt, what to extract first, and what NOT to over-engineer. Pragmatic calls > purity.

🔌 Reliability of the call pipeline

Recall webhook → transcript → UI is the spine. Edge cases (bot drops, rejoins, dedupe, late chunks) are where real users get burned. Hardening here is gold.

💳 Billing & abuse

Credits, BYOK, coupons, auto-refill caps. Find the holes (free-tier abuse, credit gaming, proration edge cases) before users do.

📈 Growth instrumentation

We have first-party analytics at /analytics.html. Help define the funnel + the few metrics that actually predict retention.

🧠 AI quality

Prompt design for the analyses + the cross-deal "Ask Across Projects". Where does the model hallucinate or go shallow? Citation discipline matters.
Ground rules for any code you touch: build on a feature branch (never straight to 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 →

#FeatureWhy it matters
1Auto pre-call "brief me" dossier build firstOne tap before a meeting pulls your whole history with that person/company from the Projects Hub. Unfair advantage; runs on data we own.
2Live in-call coaching panel most viralTalk-ratio nudges, next-best question, instant answers from your own deal history — live and consensual.
3One-tap agentic follow-through biggest moatOn call-end, drafts the follow-up email + CRM update + task. Solves the field's #1 unsolved problem: action items that never get done.
4Proactive cross-deal pushThe Hub stops waiting to be asked — tells you what changed across your pipeline (deals gone quiet, champion left, a number moved).
5Bot-free / consent-first captureThe category's biggest wound is the uninvited bot. An optional on-device mode turns that wound into our wedge.
6No-login recap share pagesPublic recap with a "powered by — get your own" footer. Cheapest growth loop in the category.
7Explainable deal scoringA 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

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.

Built for a collaborator joining the Call Analyst build · Daxos Capital · generated June 2026 from the live repo + infra. Behind the Daxos password gate. Questions → Mark.