# Project export: Compass

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2026
- Tagline: Compass helps parents of autistic kids navigate diagnosis, specialists, and benefits. This is with an AI comfort companion for hard moments alongside a real human-in-the-loop feedback.
- Devpost: https://devpost.com/software/compass-8bghuk
- GitHub: https://github.com/recalculator/Compass
- Demo: https://compass-test-sooty.vercel.app/
- Video: https://www.youtube.com/embed/9JgbtQaAXxc?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Ayaan (30 commits), Claude Sonnet 4.6 (19 commits), dide1 (6 commits)

## Devpost submission (written by the team)

### Inspiration

We didn't start with a piece of technology we wanted to use. We started with a moment we kept hearing about, over and over, when we talked to parents of autistic kids. The moment right after the diagnosis appointment, standing in a parking lot, holding a folder full of paper, and realizing nobody had told them what to actually do next. Not what's wrong. They already knew that. What to do. Which specialist. Which form. Which benefit they probably qualify for but will never hear about unless they happen to ask the right person the right question. Who to call at nine at night when something's hard and they just need another human being to talk it through with, and the only humans available are exhausted too. That parking lot moment is what Compass is for. Not the diagnosis. The everything after.

### What it does

Compass is a navigator, not a database. A parent uploads an IEP, an evaluation, a therapy note. And Claude reads it the way an exhausted parent at midnight can't, and turns it into a timeline and a short, clear list of what to do next. Need a speech therapist who actually takes your insurance? A specialist search agent goes and looks, live, right now, instead of handing back a directory that's been stale since 2022. Wondering if your state has a waiver program you've never heard of? Same thing, a real search, not a guess. And then there's the part of the app we care about most, because it's the part that shows up at the worst moments: a single mic button. No form to fill out while you're upset. Press it, and an AI comfort companion is immediately there, talking with you, present with you, while you figure out what's going on with your kid. It is never pretending to be a doctor. It never promises something it can't know. It is just there, which is sometimes the entire thing a person needs at 9pm. From there, Compass also opens a request for a real human to join, right now, today that means a member of the general public through Terac's annotator pool, and we say that plainly rather than dress it up as something it isn't. The path forward, and the thing we actually want to build next, is a version of that same human layer screened and verified for medical professionals, so the person who picks up is exactly who a parent needs in that moment. What makes the AI companion trustworthy isn't just that we wrote it a calm system prompt. It's that real people, through Terac, listen to what it produced and tell us, honestly, whether it was actually good. And we used that feedback to make it better, and then proved it got better, blind, with a second round of real people judging the before and after without knowing which was which.

### How we built it

We built Compass as a Next.js app on Supabase, with Claude doing the reading, summarizing, and reasoning throughout. Extracting structure out of messy uploaded documents, generating next steps, moderating a community feed, writing plain English explanations of dense IEP language. For the parts of the app that needed to know things about the world, not just reason over text, we used Browserbase and Stagehand to run real browser agents against Psychology Today and findhelp.org, so the specialist and benefits search results are genuinely current, not something we faked to look alive. The live connect feature, the mic button, is where the architecture got genuinely complicated, in a good way. A press of the mic spins up a private Daily.co video room and starts a Vapi voice agent talking to the parent, live, in the browser. When the conversation ends, Vapi hands us a summary, and that's when we reach for Terac. We create and launch a real opportunity asking a general population annotator to read that summary and tell us, honestly, how clear and helpful it actually was, and to rewrite it if it wasn't good enough. That correction doesn't just sit in a database. We built a four script pipeline that turns real corrections into an improved version of the summarizing prompt, runs both versions against held out test scenarios, and then asks a second, separate group of Terac annotators to blindly judge which one is better, with no idea which prompt produced which answer. That blind judgment is what let us say, honestly, that human feedback measurably made the system better, not because we believed it would, but because we watched real people prefer it, 8 times out of 8. A teammate built the Poke integration in parallel on its own branch, a parent can text Compass related questions over iMessage without ever opening the app, and we merged our work together once both pieces were solid.

### Challenges we ran into

Almost nothing about this went in a straight line, and we think that's worth saying honestly instead of pretending it did. We started building toward connecting parents with real, credentialed specialists through Terac, before realizing, partway through a working integration, that the hackathon's actual rules require general population annotators only, no clinical panels. That meant throwing out targeting logic we'd already built and rethinking what the feature was actually for. It also meant being honest in our own product about who a parent is actually being connected to right now, a general member of the public, not a verified professional, and treating that as a clearly labeled current limitation rather than something to gloss over. We hit a real, documented outage on Terac's launch endpoint mid build. We proved it wasn't our bug by replaying a byte for byte successful payload and watching it fail identically, and had to keep building around a dependency we didn't control. We accidentally left two test opportunities running and burned real credits before we caught it, which taught us fast to put a hard cost cap in code, not just in our heads. We learned the hard way that a JWT with dots in it will break a URL templating system on the other end, and that the fix that came out of that bug, never handing a sensitive token to a third party at all, routing through our own redirect instead, ended up being more secure than what we'd originally designed.

### Accomplishments we're proud of

We're proud that the human in the loop story isn't a slide, it's real data. Real annotators, real corrections, a real blind comparison, an actual 8 out of 8 preference result we can show, not just claim. We're proud of the mic button. It sounds small. It isn't. Replacing a form with a single press, and trusting an AI conversation to gather what a form would have demanded up front, is a genuine bet on meeting someone where they are instead of making them perform organization while they're scared. We're also proud that we held the line on honesty about who a parent is actually talking to right now. It would have been easy to quietly imply the person on the other end of that call was a credentialed professional. We didn't, and we built the screened, verified version of that into our roadmap instead of our marketing. And we're proud that when we hit real, unglamorous problems, an outage, a billing scare, a confusing third party bug, we slowed down, diagnosed them properly instead of guessing, and fixed the actual cause instead of papering over the symptom.

### What we learned

We learned that "use the human in the loop" sounds like a feature checkbox until you actually build it, and then it becomes a real design question. Who are these humans, what are you asking them to judge, and how do you know their feedback actually made anything better rather than just feeling like it did. The blind comparison wasn't a nice to have. It was the only honest way to answer that question. We learned that reading a sponsor's actual rules document, closely, more than once, costs you an hour and saves you a weekend. We learned that the gap between "general population reviewer" and "verified medical professional" isn't a wording choice, it's a real safety boundary, and that the right move when you haven't built the second one yet is to say so clearly, not to describe the first one as if it were the second. And we learned, again, that the parents we built this for don't need another app that's impressive. They need one thing to actually work when they're tired and scared and it's late. That's a much higher bar than it sounds like, and we tried to hold ourselves to it.

### What's next

The most important next step is the one we already named honestly in this README. Taking the general population annotator pool we used for this hackathon and building the real version on top of it, a screened, verified pathway where the person who joins a parent's call is confirmed to be a medical professional, with real credential checks, not just an open task on a marketplace. That's not a someday idea, it's the direct next milestone for live connect. We'd also want to run the before and after evaluation at real scale, not 8 scenarios, enough comparisons to trust the number, not just be encouraged by it. And we'd want to close the loop further, letting the live connect companion get better continuously, from every real call, not just the ones we happened to run during a weekend. Mostly, though, what's next is the same thing that was first. Finding the next parent standing in a parking lot with a folder full of paper, and making sure Compass is the thing that tells them what to do next, and eventually, who to safely talk to.

## README (from the GitHub repository)

# Compass

Compass is a navigator app for parents of autistic and special-needs
children. Getting a diagnosis is the easy part — the hard part is
everything after: figuring out what an IEP actually means, finding a
therapist who takes your insurance, learning what your state will
actually pay for, and not feeling alone while you do it. Compass puts all
of that in one place and uses AI to do the research a parent would
otherwise spend hours doing themselves.

This README covers what's actually built and working today, including
what was built specifically for the Terac challenge.

## Hackathon Tracks

Compass was built for, and submits to, three tracks:

- **Terac** — the live-connect annotation + blind A/B evaluation flow
  described in detail below (real human annotators rating and correcting
  AI summaries, then a second blind comparison measuring whether that
  feedback actually improved the model).
- **Browserbase** — the Directory (specialist search) and Benefit Finder
  features both run live Browserbase/Stagehand agents against real sites
  (Psychology Today, findhelp.org) rather than static or mocked data.
- **Poke** — the onboarding/Settings phone-number handoff that connects a
  parent to a pre-built Poke recipe for texting Compass-related questions.

## Key Features

### Roadmap
Upload an IEP, evaluation, or therapy note (PDF or image) and Claude
extracts diagnoses, current services, goals, recommendations, and
important dates directly from the document. Those extracted items build a
timeline, and a separate Claude call generates 3–6 concrete "next steps"
(with urgency: now / soon / upcoming) based on the child's profile and
extraction history. This is the entry point for a parent who just got a
new document and doesn't know what to do with it.

### Compass Coach
Upload a document and get a plain-English, section-by-section breakdown:
what each part means, which parts are worth raising concerns about,
and a list of questions to bring to the next IEP meeting/doctor appointment. Stateless —
nothing is persisted, it's a one-time analysis tool.

### Directory (Specialist Search)
Search for ABA, speech, OT, PT, psychology, or developmental-pediatrics
providers by ZIP code. Live results come from a Browserbase/Stagehand
agent that searches Psychology Today and extracts real listings (name,
phone, address, description, profile link), cached for 7 days per
ZIP+specialty. Results can be saved for later, and each one can get an
on-demand Claude-generated summary.

### Benefit Finder
Same Browserbase/Stagehand approach, pointed at findhelp.org, searching
for Medicaid waivers, SSI, Regional Center / state DD-agency programs,
and other disability-specific assistance — targeted using the child's
diagnosis, age bracket, and current services so results lean toward
specific, relevant programs rather than generic disability listings.
Results are cached for 7 days and can be saved.

### Village
A public community feed — parents post under topics (newly diagnosed,
IEP help, school, behavior, therapies, general) and reply in threaded
comments. Every post and comment passes through a Claude moderation
check before publishing. Deliberately feed-only: no private messaging
between parents.

### Poke (text assistant)
During onboarding (or later in Settings), a parent can save their phone
number and connect to a pre-built Poke recipe, letting them text
Compass-related questions via iMessage/SMS without opening the app. The
phone number is the only thing Compass itself stores and manages — the
text-handling logic lives in Poke's hosted recipe.

### Live-Connect (mic-first AI intake)
This is the feature built specifically around the Terac challenge — see
the next section for the full breakdown of how it works and what it
measures.

## The Terac Challenge: How Compass Uses It

The core idea: instead of routing a parent to a real human specialist (a
licensed clinician) for an emotionally difficult moment, Compass routes
them to an **AI comfort companion** first, and uses **Terac's
general-population annotator marketplace** to evaluate and improve the
AI's output with real human judgment — without needing licensed
clinicians in the loop at all.

### The flow

1. A parent presses a single mic button on `/connect`. No form, no
   waiting room.
2. A private Daily.co video room is created immediately for that
   request (in case anyone wants to join later).
3. A Vapi voice agent talks to the parent in-browser — a "comfort
   companion," not a clinician — and listens to whatever they want to
   talk through.
4. When the call ends, Vapi generates a summary and posts it to
   Compass's webhook.
5. Compass creates and launches a **Terac opportunity**: a task asking a
   general-population annotator to read the AI-generated summary and
   rate it.
6. Compass polls Terac for a claimed submission. Once someone claims the
   task, it's auto-approved and the request status flips to
   `scheduled`. The parent gets a link to join the Daily call room if
   they want to talk live to whoever picked up the task; the annotator
   gets the same link from their task page.

### What the annotator actually does

The Terac participant lands on a public Compass page
(`/annotate/{requestId}` — no login required, since they have no Compass
account) and:

- Reads the AI-generated summary of the parent's call.
- Rates its **clarity** on a 1–5 scale.
- Optionally writes a **corrected/improved version** of the summary.
- Optionally leaves free-text notes.

That submission is stored in an `annotations` table, tied back to the
Terac submission ID. This is the human-data-collection half of the
challenge: real people, not the model itself, judging and correcting AI
output.

### The before/after evaluation methodology

The annotation data feeds a second pipeline designed to measure whether
human feedback actually improves the AI's summaries — via a **blind A/B
comparison**, also run through Terac, so the evaluation itself is human
judgment, not a self-graded metric.

1. `scripts/build-improved-prompt.mjs` — pulls real corrected summaries
   out of the `annotations` table and uses them as few-shot examples to
   build a `v2_improved` prompt (falls back to hand-written guidelines
   from common failure patterns if no corrections exist yet).
2. `scripts/run-eval.mjs` — runs 8 fixed, held-out test scenarios
   (realistic intake-call transcripts) through both `v1_baseline` and
   `v2_improved` prompts via Claude, producing paired summaries.
3. `scripts/collect-blind-eval.mjs` — randomly assigns each pair's two
   summaries to slots A/B (the v1/v2 label is hidden), writes them to a
   `comparison_pairs` table, and launches a **second, separate Terac
   opportunity** — one task per pair, at `/compare/{pairId}` — asking
   general-population annotators which summary is clearer, with no idea
   which one came from which prompt version.
4. `scripts/report-results.mjs` — tallies `comparison_results` against
   the hidden labels and prints how often `v2_improved` was preferred
   over `v1_baseline`.

**Current status:** the annotation pipeline (steps 1–6 of the live-connect
flow above) is live and wired end-to-end through real calls, and the
four-script before/after pipeline has been run for real. `v2_improved` was
built from 3 real Terac-collected annotation corrections (not synthetic
guidelines), and an 8-scenario blind A/B comparison — also judged by real
Terac annotators, with the v1/v2 label hidden from them — came back
**8/8 (100%) in favor of `v2_improved`**. That improved prompt has since
been deployed to the live Vapi assistant (`scripts/update-vapi-assistant.mjs`
refuses to deploy anything not built from `source: "annotations"`), so
production calls now use the human-feedback-improved summarizer, not just
the eval script.

### Why general-population annotators, not clinicians

Per the Terac challenge's design, annotators are general members of the
public, not screened for any clinical credential. Compass's 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 164 recognized source files, 1194 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 195)

```
.agents/skills/app-sizzle/references/liquid-glass.md
.agents/skills/app-sizzle/references/template-a-cinematic.md
.agents/skills/app-sizzle/SKILL.md
.agents/skills/app-store-screens/references/default-layout.md
.agents/skills/app-store-screens/references/layout-archetypes.md
.agents/skills/app-store-screens/references/render-pipeline.md
.agents/skills/app-store-screens/SKILL.md
.agents/skills/baseball-trend/SKILL.md
.agents/skills/build-a-brand/references/brand-directions.md
.agents/skills/build-a-brand/references/brand-guidelines.md
.agents/skills/build-a-brand/references/brand-identity.md
.agents/skills/build-a-brand/references/brand-md-template.md
.agents/skills/build-a-brand/SKILL.md
.agents/skills/content-director/formats/dance.md
.agents/skills/content-director/formats/duet.md
.agents/skills/content-director/formats/pov.md
.agents/skills/content-director/formats/talking.md
.agents/skills/content-director/formats/teleprompter.html
.agents/skills/content-director/formats/teleprompter.md
.agents/skills/content-director/README.md
.agents/skills/content-director/SKILL.md
.agents/skills/explainer/SKILL.md
.agents/skills/fix-my-look/SKILL.md
.agents/skills/founder-product-video/references/ops-notes.md
.agents/skills/founder-product-video/SKILL.md
.agents/skills/kiss-cam/SKILL.md
.agents/skills/language-swap/references/language-coverage.md
.agents/skills/language-swap/SKILL.md
.agents/skills/persona-builder/references/aesthetic-prompts.md
.agents/skills/persona-builder/references/persona-md-template.md
.agents/skills/persona-builder/references/templates/mood-board-no-header.template.html
.agents/skills/persona-builder/references/templates/mood-board.template.html
.agents/skills/persona-builder/references/templates/pdf-about.template.html
.agents/skills/persona-builder/references/templates/pdf-content-categories.template.html
.agents/skills/persona-builder/references/templates/pdf-cover.template.html
.agents/skills/persona-builder/references/templates/pdf-do-dont.template.html
.agents/skills/persona-builder/references/templates/pdf-hooks-dm.template.html
.agents/skills/persona-builder/references/templates/pdf-moodboard.template.html
.agents/skills/persona-builder/references/templates/pdf-next-steps.template.html
.agents/skills/persona-builder/references/templates/pdf-shared-head.template.html
.agents/skills/persona-builder/references/templates/pdf-voice-mode.template.html
.agents/skills/persona-builder/SKILL.md
.agents/skills/podcast/SKILL.md
.agents/skills/ugc-ads/SKILL.md
.agents/skills/viral-hook/SKILL.md
.claude/skills/app-sizzle
.claude/skills/app-store-screens
.claude/skills/baseball-trend
.claude/skills/build-a-brand
.claude/skills/content-director
.claude/skills/explainer
.claude/skills/fix-my-look
.claude/skills/founder-product-video
.claude/skills/kiss-cam
.claude/skills/language-swap
.claude/skills/persona-builder
.claude/skills/podcast
.claude/skills/ugc-ads
.claude/skills/viral-hook
.env.example
.env.local.bak
.eslintrc.json
.gitignore
.mcp.json
next.config.mjs
package.json
postcss.config.js
README.md
scripts/build-improved-prompt.mjs
scripts/collect-blind-eval.mjs
scripts/create-vapi-assistant.mjs
scripts/diagnose-connect-request.mjs
scripts/report-results.mjs
scripts/run-eval.mjs
scripts/test-browserbase.ts
scripts/test-scenarios.json
scripts/update-vapi-assistant.mjs
skills-lock.json
src/app/(app)/benefits/page.tsx
src/app/(app)/connect/[id]/call/page.tsx
src/app/(app)/connect/page.tsx
src/app/(app)/dashboard/page.tsx
src/app/(app)/dashboard/settings/page.tsx
src/app/(app)/directory/page.tsx
src/app/(app)/iep-coach/IepCoachClient.tsx
src/app/(app)/iep-coach/page.tsx
src/app/(app)/layout.tsx
src/app/(app)/roadmap/ClearAllButton.tsx
src/app/(app)/roadmap/DocumentsList.tsx
src/app/(app)/roadmap/NextSteps.tsx
src/app/(app)/roadmap/page.tsx
src/app/(app)/roadmap/SummaryReport.tsx
src/app/(app)/roadmap/Timeline.tsx
src/app/(app)/roadmap/UploadForm.tsx
src/app/(app)/specialists/page.tsx
src/app/(app)/village/[id]/page.tsx
src/app/(app)/village/page.tsx
src/app/annotate/[requestId]/page.tsx
src/app/api/annotate/[callNotesId]/route.ts
src/app/api/auth/signout/route.ts
src/app/api/benefits/generate/route.ts
src/app/api/benefits/save/route.ts
src/app/api/chat/route.ts
src/app/api/community/comments/route.ts
src/app/api/community/posts/route.ts
src/app/api/compare/[pairId]/route.ts
src/app/api/connect/request/[id]/status/route.ts
src/app/api/connect/request/route.ts
src/app/api/documents/[id]/route.ts
src/app/api/documents/clear/route.ts
src/app/api/documents/upload/route.ts
src/app/api/iep/analyze/route.ts
src/app/api/milestones/generate/route.ts
src/app/api/milestones/update/route.ts
src/app/api/roadmap/next-steps/route.ts
src/app/api/roadmap/summary-pdf/route.ts
src/app/api/saved/benefits/route.ts
src/app/api/saved/specialists/route.ts
src/app/api/search/benefit-summary/route.ts
src/app/api/search/benefits/route.ts
[75 more files omitted for size]
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.65.0, @browserbasehq/stagehand@^3.6.0, @daily-co/daily-js@^0.91.0, @supabase/ssr@^0.12.0, @supabase/supabase-js@^2.45.4, @types/node@^20.16.10, @types/pdfkit@^0.13.4, @types/react@^18.3.10, @types/react-dom@^18.3.0, @vapi-ai/web@^2.5.2, autoprefixer@^10.4.20, clsx@^2.1.1, date-fns@^4.1.0, eslint@^8.57.1, eslint-config-next@14.2.35, lucide-react@^0.451.0, next@^14.2.35, pdfkit@^0.15.1, postcss@^8.4.47, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^3.4.13, typescript@^5.6.2, zod@^3.23.8

### Recent commits (newest first)

- Update IEP Coach section to Compass Coach
- Deploy real human-corrected prompt to production, polish UI, update README
- Merge remote-tracking branch 'origin/main' into feature/terac-live-connect-v2
- Polish UI across dashboard/roadmap/specialists/benefits/village/connect, fix doc upload bug, pin sidebar, remove digest emails
- feat: full frontend renovation with Pika-generated visual assets
- Rewrite README to reflect current architecture and Terac challenge integration
- Remove Recommended-for box from Specialists, fix Benefit Finder hallucinating fake results
- Dial back Benefit Finder strictness — bias, don't filter
- Make Benefit Finder results diagnosis/age-specific instead of generic
- Remove DMs from Village — feed-only, like Twitter
- Show all registered users by default in the message search
- Add user search to start a DM from the Messages tab
- Make Village avatar clickable to start a DM too
- Make Village author names clickable to start a DM
- Persist benefits search results in localStorage, not sessionStorage
- Retry Stagehand/Browserbase calls on transient 500s
- Cache benefits search results in sessionStorage
- Fix Browserbase search failing on Vercel with pino-pretty transport error
- Add Connect to the sidebar nav
- Merge origin/main into feature/terac-live-connect-v2

## Key source files (fetched from GitHub, selected and truncated for size)

### .agents/skills/viral-hook/SKILL.md

```markdown
---
name: viral-hook
description: >-
  Prepend a 4s viral hook + optional designed title to a user's video, then hard-cut into
  the real clip. An attention-grabbing event erupts into the user's OWN scene; the title is
  rendered in-scene by Seedance. One MCP call does the whole render. Requires Pika MCP.
  Triggers: "add a viral hook", "disruption hook on my video", "hook + title on my clip",
  "viral hook with typography", "scroll-stopper intro".
argument-hint: "[video path or URL] [optional title line]"
required-capabilities:
  - mcp__plugin_pika_pika__upload_asset
  - mcp__plugin_pika_pika__analyze_media
  - mcp__plugin_pika_pika__generate_viral_hook
  - mcp__plugin_pika_pika__task_status
---

# Viral Hook

Prepends an extreme, no-dialogue ~4s hook to a user's video — an attention-grabbing event
that erupts into the user's own scene, with an optional designed title burned into the
lower third — then hard-cuts into the untouched clip.

`mcp__plugin_pika_pika__generate_viral_hook` does the whole render in one call and returns BOTH
the hook and the stitched final. This skill's job is the creative judgment the tool can't
make: read the scene, pick a hook action from the menu, and write a title.

## Hook rules (govern what you WRITE as `hook_action`)

1. **Certain to grab attention, fast.** Extreme and unmissable inside ~2s — over plausibility
   or relevance to the video's topic.
2. **Erupts into the user's OWN scene.** Same person, location, lighting — the tool anchors
   on the best visual anchor frame: 0s when usable, otherwise the first detectable face frame.
   Describe the event entering THAT space, never a different setting.
3. **Use the scene's real geometry.** Enter through a doorway with depth, a wall behind the
   subject, headroom above.
4. **No dialogue.** Voice-free (only ambient / impact SFX) — don't write spoken lines into
   `hook_action`.

## Hook super-category menu

Four super-categories. **Rotate** super-category per run on the same clip/session — without
rotation, regenerates collapse to vehicle / explosion / creature. Archetypes are starting
points, not a closed list; invent freely within the category.

### A. Destructive intrusion — external force violently enters and damages the scene

| Archetype | What happens | Scene affordance |
|---|---|---|
| Incoming vehicle | car / truck / bus smashes through a wall or doorway and barrels at camera | background depth or a back wall |
| Flash flood / wave | a wall bursts and water surges down toward camera | corridor / back wall |
| Structural collapse | ceiling / wall / shelf caves in, debris rains down | headroom / walls |
| Explosion / blast | fireball erupts behind them, shockwave + debris rush forward | a background surface |
| Creature attack | animal / monster lunges from off-frame or bursts in | open off-frame edge |
| Absurd intruder | dinosaur, elephant, UFO, giant hand enters behind them | background space |
| Natural disaster | tornado / meteor / lightning / sinkhole | 
[truncated — 6089 more characters]
```

### .agents/skills/podcast/SKILL.md

```markdown
---
name: podcast
description: >-
  Two-host podcast video for any URL or free-form topic — 1 minute, 4 acts × ~15s,
  native multi-shot dialogue, optional voice cloning for Host A. Use when the user
  asks to "make a podcast", "podcast about [thing]", "podcast review of [url]",
  "two-host explainer", "interview-style clip", "two people talking on camera",
  "I/me and X talk about Y", or "interview with [persona] about [topic]". Native
  audio is the deliverable; captions are skipped by default because podcast dialogue
  mistranscribes domain terms.
argument-hint: <url-or-topic> [bg_img=] [host_a_img=] [host_b_img=] [voice_a=] [voice_b=] [use_avatar] [aspect_ratio=16:9]
---

# /pika:podcast

4 acts × 15s each = 60s. Host A always LEFT, Host B always RIGHT. Accepts a URL **or** a free-form topic / brief.

## Parameters

| Param | Default | Notes |
|---|---|---|
| `input` | required | URL to review **or** free-form topic / brief (e.g. "I and Elon Musk talk about Mars") |
| `bg_img` | auto-generated | Podcast studio background |
| `host_a_img` | auto-generated | Host A portrait — see Real-person handling below |
| `host_b_img` | auto-generated | Host B portrait — see Real-person handling below |
| `voice_a` | `876341503281471517` | Kling preset or cloned voice ID for Host A |
| `voice_b` | `829837252279803904` | Kling preset or cloned voice ID for Host B |
| `use_avatar` | off | Clone user's identity voice as Host A via `clone_voice` |
| `aspect_ratio` | `16:9` | Output aspect ratio |

## Defaults — fire fast, no mid-flow confirmation

- **Use the param-table defaults silently for voices.** `voice_a` defaults to the Kling preset `876341503281471517` and `voice_b` to `829837252279803904`. Do **not** ask "which voice?" or "should I clone yours?" before firing — only honor explicit overrides (`voice_a=`, `voice_b=`, `use_avatar`).
- **Auto-generate any missing host portraits silently** (Step 1's archetype prompts). Do **not** ask "should I generate a host image?" — just generate.
- **No "type yes to proceed" gates.** Submit → render the 4 acts → return URL. Account credit balance + provider failover are the canonical guardrails. The `--yes` flag is accepted as a no-op for backward compatibility.
- **Topic-mode personas (Step 3)** — when the user names a real public figure, follow Step 4 (Real-person handling) silently: archetype portrait by default, no auto-generated photographic likeness, no question to the user about likeness rights.

## Local images on Claude Desktop

Claude Desktop can't pass inline-pasted images to MCP tools yet (Anthropic-side limitation). If the user pastes a photo inline, or mentions a local file they want as `host_a_img` / `host_b_img`, pause Step 1 and kindly send them this — something like:

> Heads up — pasted images don't reach MCP tools on Claude Desktop yet (Anthropic limitation). Two easy options for your photo:
>
> - **Paste a URL** if it's already hosted (Imgur, S3, your site) — fastest
> - **Attach the image file** 
[truncated — 9613 more characters]
```

### package.json

```
{
  "name": "compass",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.65.0",
    "@browserbasehq/stagehand": "^3.6.0",
    "@daily-co/daily-js": "^0.91.0",
    "@supabase/ssr": "^0.12.0",
    "@supabase/supabase-js": "^2.45.4",
    "@vapi-ai/web": "^2.5.2",
    "clsx": "^2.1.1",
    "date-fns": "^4.1.0",
    "lucide-react": "^0.451.0",
    "next": "^14.2.35",
    "pdfkit": "^0.15.1",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "zod": "^3.23.8"
  },
  "devDependencies": {
    "@types/node": "^20.16.10",
    "@types/pdfkit": "^0.13.4",
    "@types/react": "^18.3.10",
    "@types/react-dom": "^18.3.0",
    "autoprefixer": "^10.4.20",
    "eslint": "^8.57.1",
    "eslint-config-next": "14.2.35",
    "postcss": "^8.4.47",
    "tailwindcss": "^3.4.13",
    "typescript": "^5.6.2"
  }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from 'next';
import './globals.css';

export const metadata: Metadata = {
  title: 'Compass — Your child\'s journey, navigated',
  description:
    'Compass is a personal navigator for parents of autistic and special needs children. It knows your child, knows your journey, and tells you what to do next.',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

```

### src/app/page.tsx

```typescript
import Link from 'next/link';
import Image from 'next/image';
import { HandCoins, Mic } from 'lucide-react';
import { Logo } from '@/components/ui/Logo';

const FEATURES = [
  {
    image: '/assets/feature-roadmap.png',
    title: 'The Roadmap',
    description:
      'Upload IEPs, evaluations, and therapy notes. Compass reads them, builds a visual timeline of your child\'s journey, and tells you what comes next.',
  },
  {
    image: '/assets/feature-directory.png',
    title: 'The Directory',
    description:
      'Find ABA, speech, OT, feeding, and developmental pediatric specialists near you, pulled live from real provider listings.',
  },
  {
    icon: HandCoins,
    title: 'Benefit Finder',
    description:
      'Discover Medicaid waivers, SSI, and state assistance programs matched to your child\'s diagnosis and age — not generic disability lists.',
  },
  {
    image: '/assets/feature-village.png',
    title: 'The Village',
    description:
      'A community of parents who get it. Ask questions, share what worked, and never feel like you\'re doing this alone.',
  },
  {
    image: '/assets/feature-iep-coach.png',
    title: 'IEP Coach',
    description:
      'Upload your child\'s IEP and get a plain-English walkthrough, flags on anything to question, and questions to bring to your next meeting.',
  },
  {
    icon: Mic,
    title: 'Connect',
    description:
      'Press one button to talk it out with an AI companion any time you need to — no forms, no waiting room.',
  },
];

export default function LandingPage() {
  return (
    <div className="min-h-screen bg-[#fdf8f4]">
      {/* Hero */}
      <div className="relative min-h-[75vh] overflow-hidden">
        <Image
          src="/assets/hero-bg.png"
          alt=""
          fill
          className="object-cover object-center"
          priority
        />
        <div className="absolute inset-0 bg-[#fdf8f4]/60" />

        <div className="relative z-10">
          <header className="mx-auto flex max-w-6xl items-center justify-between px-6 py-6">
            <Logo />
            <nav className="flex items-center gap-3">
              <Link href="/login" className="btn-ghost">
                Log in
              </Link>
              <Link href="/signup" className="btn-primary">
                Get started
              </Link>
            </nav>
          </header>

          <section className="mx-auto max-w-4xl px-6 pt-20 pb-32 text-center">
            <h1 className="text-5xl font-bold italic tracking-tight text-sage-900 sm:text-6xl">
              Your child&apos;s journey,<br />navigated.
            </h1>
            <p className="mx-auto mt-6 max-w-2xl text-lg leading-relaxed text-sage-700">
              Compass is a personal navigator for parents of autistic and special needs
              children. It knows your child, knows your journey, and tells you what to do
              next — so you can spend less time figuring out the system and more time with
              your kid.
            </p>
            <div className="mt-10 flex items-center justify-center gap-4">
              <Link href="/signup" className="btn-primary px-8 py-3 text-base">
                Start your roadmap
              </Link>
              <Link href="/login" className="btn-secondary px-8 py-3 text-base">
                I already have an account
              </Link>
            </div>
          </section>
        </div>
      </div>

      <main>
        {/* Feature cards */}
        <section className="mx-auto max-w-6xl px-6 py-20">
          <h2 className="mb-10 text-center text-2xl font-bold text-sage-900">
            Everything you need, in one place
          </h2>
          <div className="grid gap-6 sm:grid-cols-2">
            {FEATURES.map((feature) => (
              <div
                key={feature.title}
                className="card flex gap-5 transition-transform hover:-translate-y-0.5 hover:shadow-md"
              >
                <div className="shrink-0">
                  {feature.image ? (
                    <Image
                      src={feature.image}
                      alt={feature.title}
                      width={80}
                      height={80}
                      className="rounded-xl object-cover"
                    />
                  ) : (
                    <div className="flex h-20 w-20 items-center justify-center rounded-xl bg-sky-100 text-sky-700">
                      {feature.icon && <feature.icon className="h-8 w-8" />}
                    </div>
                  )}
                </div>
                <div>
                  <h3 className="text-lg font-semibold text-sage-900">{feature.title}</h3>
                  <p className="mt-2 text-sm leading-relaxed text-sage-600">
                    {feature.description}
                  </p>
                </div>
              </div>
            ))}
          </div>
        </section>

        {/* Quote */}
        <section className="mx-auto max-w-3xl px-6 pb-24">
          <blockquote className="rounded-xl2 border-l-4 border-[#e6b893] bg-sage-50 p-8 text-lg italic text-sage-700 shadow-soft">
            &ldquo;You don&apos;t have to figure this out alone. Compass is here to help you
            understand where your child has been — and where to go next.&rdquo;
          </blockquote>
        </section>
      </main>

      <footer className="border-t border-sage-100 bg-[#fdf8f4] px-6 py-8">
        <div className="mx-auto flex max-w-6xl items-center justify-between">
          <Logo />
          <p className="text-sm text-sage-400">
            © {new Date().getFullYear()} Compass. Built for the parents doing the hardest job there is.
          </p>
        </div>
      </footer>
    </div>
  );
}

```

### src/app/(app)/layout.tsx

```typescript
import { redirect } from 'next/navigation';
import { createClient } from '@/lib/supabase/server';
import { getCurrentChild } from '@/lib/child/getCurrentChild';
import { Sidebar } from '@/components/nav/Sidebar';
import { FloatingChat } from '@/components/chat/FloatingChat';

export default async function AppLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const supabase = createClient();
  const { data: { user } } = await supabase.auth.getUser();

  if (!user) {
    redirect('/login');
  }

  const profile = await getCurrentChild(supabase, user.id);

  if (!profile) {
    redirect('/onboarding');
  }

  return (
    <div className="flex min-h-screen bg-sage-50">
      <Sidebar childName={profile.child_name} />
      <main className="min-w-0 flex-1">{children}</main>
      <FloatingChat />
    </div>
  );
}

```

### src/lib/supabase/server.ts

```typescript
import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { cookies } from 'next/headers';

export function createClient() {
  const cookieStore = cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options });
          } catch {
            // called from a Server Component — middleware refreshes sessions instead
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: '', ...options });
          } catch {
            // called from a Server Component — middleware refreshes sessions instead
          }
        },
      },
    }
  );
}

export function createServiceRoleClient() {
  const cookieStore = cookies();
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.SUPABASE_SERVICE_ROLE_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set() {},
        remove() {},
      },
    }
  );
}

```

### src/app/login/page.tsx

```typescript
'use client';

import { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { createClient } from '@/lib/supabase/client';
import { Logo } from '@/components/ui/Logo';

export default function LoginPage() {
  const router = useRouter();
  const supabase = createClient();
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError(null);
    setLoading(true);

    const { error: signInError } = await supabase.auth.signInWithPassword({
      email,
      password,
    });

    setLoading(false);

    if (signInError) {
      setError(signInError.message);
      return;
    }

    router.push('/dashboard');
    router.refresh();
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-sage-50 px-6">
      <div className="w-full max-w-sm">
        <div className="mb-8 flex justify-center">
          <Logo />
        </div>
        <div className="card">
          <h1 className="text-xl font-semibold text-sage-900">Welcome back</h1>
          <p className="mt-1 text-sm text-sage-600">Log in to your Compass account.</p>

          <form onSubmit={handleSubmit} className="mt-6 space-y-4">
            <div>
              <label className="label-text" htmlFor="email">Email</label>
              <input
                id="email"
                type="email"
                required
                className="input-field"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@example.com"
              />
            </div>
            <div>
              <label className="label-text" htmlFor="password">Password</label>
              <input
                id="password"
                type="password"
                required
                className="input-field"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder="••••••••"
              />
            </div>

            {error && (
              <p className="rounded-lg bg-clay-50 px-3 py-2 text-sm text-clay-500">
                {error}
              </p>
            )}

            <button type="submit" disabled={loading} className="btn-primary w-full">
              {loading ? 'Logging in…' : 'Log in'}
            </button>
          </form>
        </div>

        <p className="mt-6 text-center text-sm text-sage-600">
          Don&apos;t have an account?{' '}
          <Link href="/signup" className="font-semibold text-sky-600 hover:underline">
            Sign up
          </Link>
        </p>
      </div>
    </div>
  );
}

```

### src/app/signup/page.tsx

```typescript
'use client';

import { useState } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { createClient } from '@/lib/supabase/client';
import { Logo } from '@/components/ui/Logo';

export default function SignupPage() {
  const router = useRouter();
  const supabase = createClient();
  const [fullName, setFullName] = useState('');
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [confirmSent, setConfirmSent] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError(null);
    setLoading(true);

    const { data, error: signUpError } = await supabase.auth.signUp({
      email,
      password,
      options: {
        data: { full_name: fullName },
        emailRedirectTo: `${window.location.origin}/auth/callback?next=/onboarding`,
      },
    });

    setLoading(false);

    if (signUpError) {
      setError(signUpError.message);
      return;
    }

    if (data.session) {
      router.push('/onboarding');
      router.refresh();
    } else {
      setConfirmSent(true);
    }
  }

  if (confirmSent) {
    return (
      <div className="flex min-h-screen items-center justify-center bg-sage-50 px-6">
        <div className="card max-w-sm text-center">
          <h1 className="text-xl font-semibold text-sage-900">Check your inbox</h1>
          <p className="mt-2 text-sm text-sage-600">
            We sent a confirmation link to <strong>{email}</strong>. Click it to finish
            setting up your account.
          </p>
        </div>
      </div>
    );
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-sage-50 px-6">
      <div className="w-full max-w-sm">
        <div className="mb-8 flex justify-center">
          <Logo />
        </div>
        <div className="card">
          <h1 className="text-xl font-semibold text-sage-900">Create your account</h1>
          <p className="mt-1 text-sm text-sage-600">
            Let&apos;s start building your child&apos;s roadmap.
          </p>

          <form onSubmit={handleSubmit} className="mt-6 space-y-4">
            <div>
              <label className="label-text" htmlFor="fullName">Your name</label>
              <input
                id="fullName"
                type="text"
                required
                className="input-field"
                value={fullName}
                onChange={(e) => setFullName(e.target.value)}
                placeholder="Jordan Rivera"
              />
            </div>
            <div>
              <label className="label-text" htmlFor="email">Email</label>
              <input
                id="email"
                type="email"
                required
                className="input-field"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@example.com"
              />
            </div>
            <div>
              <label className="label-text" htmlFor="password">Password</label>
              <input
                id="password"
                type="password"
                required
                minLength={8}
                className="input-field"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
                placeholder="At least 8 characters"
              />
            </div>

            {error && (
              <p className="rounded-lg bg-clay-50 px-3 py-2 text-sm text-clay-500">
                {error}
              </p>
            )}

            <button type="submit" disabled={loading} className="btn-primary w-full">
              {loading ? 'Creating account…' : 'Create account'}
            </button>
          </form>
        </div>

        <p className="mt-6 text-center text-sm text-sage-600">
          Already have an account?{' '}
          <Link href="/login" className="font-semibold text-sky-600 hover:underline">
            Log in
          </Link>
        </p>
      </div>
    </div>
  );
}

```

### src/app/onboarding/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import Image from 'next/image';
import { createClient } from '@/lib/supabase/client';
import { Logo } from '@/components/ui/Logo';
import clsx from 'clsx';

const DIAGNOSIS_OPTIONS = [
  'Autism Spectrum Disorder',
  'Speech/Language Delay',
  'Developmental Delay',
  'ADHD',
  'Sensory Processing Disorder',
  'Down Syndrome',
  'Not yet diagnosed',
  'Other',
];

const SERVICE_OPTIONS = [
  'ABA Therapy',
  'Speech Therapy',
  'Occupational Therapy',
  'Physical Therapy',
  'Feeding Therapy',
  'Early Intervention',
  'Special Education / IEP',
  'None yet',
];

const STEPS = ['Your child', 'Diagnosis', 'Current services', 'Location', 'Phone', 'Text alerts'] as const;

function toggleInArray(arr: string[], value: string) {
  return arr.includes(value) ? arr.filter((v) => v !== value) : [...arr, value];
}

export default function OnboardingPage() {
  const router = useRouter();
  const supabase = createClient();

  const [step, setStep] = useState(0);
  const [submitting, setSubmitting] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const [childName, setChildName] = useState('');
  const [birthDate, setBirthDate] = useState('');
  const [diagnosis, setDiagnosis] = useState<string[]>([]);
  const [services, setServices] = useState<string[]>([]);
  const [zip, setZip] = useState('');
  const [city, setCity] = useState('');
  const [state, setStateVal] = useState('');
  const [parentPhone, setParentPhone] = useState('');

  const canAdvance =
    (step === 0 && childName.trim().length > 0) ||
    (step === 1 && diagnosis.length > 0) ||
    (step === 2 && services.length > 0) ||
    step === 3 ||
    step === 4 ||
    step === 5;

  async function handleFinish() {
    setSubmitting(true);
    setError(null);

    const { data: userData } = await supabase.auth.getUser();
    const user = userData.user;
    if (!user) {
      setError('You need to be logged in to continue.');
      setSubmitting(false);
      return;
    }

    if (parentPhone.trim()) {
      await supabase
        .from('users')
        .update({ phone_number: parentPhone.trim() })
        .eq('id', user.id);
    }

    const { error: insertError } = await supabase
      .from('child_profiles')
      .insert({
        user_id: user.id,
        child_name: childName.trim(),
        birth_date: birthDate || null,
        diagnosis,
        current_services: services,
        location_zip: zip || null,
        location_city: city || null,
        location_state: state || null,
      });

    setSubmitting(false);

    if (insertError) {
      setError(insertError.message);
      return;
    }

    router.push('/roadmap');
    router.refresh();
  }

  return (
    <div className="flex min-h-screen items-center justify-center bg-[#fdf8f4] px-6 py-12">
      <div className="w-full max-w-lg">
        <div className="mb-6 overflow-hidden rounded-xl2">
          <Image
            src="/assets/onboarding-header.png"
            alt="A parent and child walking together"
            width={672}
            height={378}
            className="w-full object-cover"
            priority
          />
        </div>
        <div className="mb-8 flex justify-center">
          <Logo />
        </div>

        <div className="mb-6 flex items-center gap-2">
          {STEPS.map((label, i) => (
            <div key={label} className="flex flex-1 flex-col items-center gap-1.5">
              <div
                className={clsx(
                  'h-1.5 w-full rounded-full',
                  i <= step ? 'bg-sage-600' : 'bg-sage-200'
                )}
              />
              <span
                className={clsx(
                  'text-xs',
                  i === step ? 'font-semibold text-sage-800' : 'text-sage-400'
                )}
              >
                {label}
              </span>
            </div>
          ))}
        </div>

        <div className="card">
          {step === 0 && (
            <div className="space-y-4">
              <h2 className="text-lg font-semibold text-sage-900">
                Tell us about your child
              </h2>
              <p className="text-sm text-sage-600">
                This helps Compass personalize everything — from the roadmap to next
                steps.
              </p>
              <div>
                <label className="label-text" htmlFor="childName">Child&apos;s name</label>
                <input
                  id="childName"
                  className="input-field"
                  value={childName}
                  onChange={(e) => setChildName(e.target.value)}
                  placeholder="e.g. Mia"
                />
              </div>
              <div>
                <label className="label-text" htmlFor="birthDate">Date of birth</label>
                <input
                  id="birthDate"
                  type="date"
                  className="input-field"
                  value={birthDate}
                  onChange={(e) => setBirthDate(e.target.value)}
                />
              </div>
            </div>
          )}

          {step === 1 && (
            <div className="space-y-4">
              <h2 className="text-lg font-semibold text-sage-900">
                What&apos;s {childName || 'your child'}&apos;s diagnosis?
              </h2>
              <p className="text-sm text-sage-600">Select all that apply.</p>
              <div className="flex flex-wrap gap-2">
                {DIAGNOSIS_OPTIONS.map((opt) => (
                  <button
                    type="button"
                    key={opt}
                    onClick={() => setDiagnosis(toggleInArray(diagnosis, opt))}
                    className={clsx(
                      'rounded-full border px-4 py-2 text-sm transition',
                      diagnosis.includes(opt)
                        ? 'border-sage-600 bg-sage-600 text-whi
[truncated — 5769 more characters]
```

[147 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]