# Project export: Demoless

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: Never lose a prospect again. 24/7 live product demos, led by an autonomous agent who actually understands your leads.
- Devpost: https://devpost.com/software/demoless
- GitHub: https://github.com/FisherXZ/demoless
- Demo: https://demoless.vercel.app/
- Video: https://www.youtube.com/embed/gcZsby-3Scg?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 6 GitHub contributor(s) — fischerxz (105 commits), Claude Sonnet 4.6 (59 commits), Zhen Liu (39 commits), Daniel Lin (11 commits), Chen Lexin (8 commits), Cursor (3 commits)

## Devpost submission (written by the team)

### Overview

An AI product specialist that runs live, personalized software demos on your website, 24/7.

### Inspiration

For most B2B software companies, the live demo is where deals are won, and it is also the biggest bottleneck in the funnel. A visitor lands on the site with real intent, clicks "Book a demo," fills out a form, and then waits days for a calendar slot. By the time a human rep finally jumps on a generic call, the moment of interest is long gone. Demos do not scale: reps are expensive, they sleep, they are not available the instant a prospect is curious, and they cannot be in fifty time zones at once. The result is a leaky funnel where the vast majority of interested visitors never see the product actually work. We wanted to remove that bottleneck entirely. What if every visitor could get a real, live demo the second they wanted one, with a specialist who already knows the product cold, drives the actual app on screen, adapts to who is asking, and remembers them next time? That is Demoless: it sits on a company's site and demos their product for them, around the clock, so sales teams wake up to warm, qualified pipeline with full conversation context instead of cold form fills.

### What it does

Demoless is an AI-led product demo platform. A visitor enters a few details, joins a live call, and talks to an AI product specialist named Messi. From the visitor's point of view: They talk, Messi talks back. Messi listens through the microphone (speech to text), thinks, and answers out loud with natural, low-latency voice. The visitor can interrupt mid-sentence and Messi stops and adjusts, the way a real rep would. Messi drives the real product live. Instead of describing features, Messi opens an actual cloud browser session that the visitor watches in real time, navigating the live product, clicking through real flows, and narrating only the value, not the clicks. It grounds every answer in the product. Before stating a fact, Messi retrieves the most relevant product documentation through semantic search, so answers are grounded in the company's real docs rather than guessed. It personalizes to who is asking. The visitor picks their role on the way in, which Messi classifies into a technical or non-technical persona and matches its depth and language to: precise and technical for engineers and builders, plain and outcome-focused for product, ops, and marketing roles. It keeps refining that read as the conversation unfolds. It remembers the buyer. Useful context (pain points, interests, objections, next steps) is saved during the call, and a returning visitor is greeted with a callback to what they cared about last time. It speaks multiple languages. The voice loop supports English, Spanish, and Mandarin, switchable on the fly, and can auto-detect the visitor's language from their first words. It hands off to the team. Every session is saved and analyzed into a post-demo packet for an operator dashboard, so the sales team gets structured, evidence-backed insights instead of a raw transcript. After each demo the agent also reflects on how it went and captures lessons it can apply to future demos. How it works (end to end) A single streaming "brain" owns voice, browser control, and memory at once. As Claude generates a reply, the system speaks it sentence by sentence so the visitor hears the first words before the full answer is finished, and it executes browser and memory actions in the same loop.

### How we built it

Frontend: Next.js (App Router) with TypeScript and Tailwind CSS for the buyer-facing demo room and the operator dashboard. Next.js API routes serve the supporting data (agent name, operator info, the live notes feed, and auth), while the live demo itself streams over a WebSocket to the voice backend. Shared types: TypeScript across the whole stack. The frontend and backend share the same types for the voice protocol messages, buyer memory, and session records, which kept the real-time contract between them reliable. Voice backend: A Node WebSocket gateway manages each live conversation: turn-taking, barge-in (interruption handling), session lifecycle, and streaming audio to the browser. Speech: Deepgram for speech to text (nova-3) and text to speech (Aura-2), with a provider-agnostic TTS layer that also supports OpenAI and ElevenLabs voices. Mandarin output routes to OpenAI because it has the voice coverage Aura lacks. Brain and tool use: Anthropic's Claude is the orchestrator, running a streaming native tool-use loop with six tools (navigate, click, look, remember, search the knowledge base, and report sales phase). We run Claude Haiku 4.5 for cheaper token usage at the volume a 24/7 agent demands, and the same model handles post-session analysis and cross-session learnings. The model is configurable. Live browser: Browserbase powers each cloud browser session, using Sessions, an embeddable live view the visitor watches, replay links, persistent pre-authenticated contexts (to demo signed-in products), and a Playwright integration so Messi drives the real product. Stealth and proxy support is available for sites that need it. Memory and storage: Redis and Redis Stack hold two separate layers: per-buyer memory (profile and notes, ranked by importance and recency) and a product knowledge base (a larger document corpus retrieved by semantic search). Redis also backs session and dashboard state. Retrieval: OpenAI embeddings power vector search over the product knowledge base so Messi answers from real docs. Tooling: The project was built and debugged with Claude Code.

### Challenges we ran into

Making the agent feel like a sharp rep, not a tour guide. Getting Messi to show rather than narrate, lead with the buyer's biggest pain, and stop selling past a yes took heavy prompt engineering and persona tuning. Solved. Two memories that should not be confused. "Who is this buyer" and "what does the product do" are different problems with different storage and retrieval. We built them as two distinct Redis-backed layers, one ranked by importance and recency, the other by semantic vector search. Solved. Latency in a real-time voice loop. Speech, model output, browser actions, and audio playback all compete for the same seconds. We pipelined text to speech per sentence, cached the model prompt prefix, and tuned endpointing and barge-in so the conversation feels live. Solved.

### Accomplishments we're proud of

A single streaming brain that listens, thinks, talks, drives a real browser, and remembers, all in one loop. Persona-aware demos: the visitor's selected role drives a technical or non-technical persona that shifts the agent's depth and language, refined further through the conversation. A complete memory and storage system covering agent memory, grounded knowledge retrieval, and post-demo insights. Real-time voice with interruption handling and multilingual support (English, Spanish, Mandarin). Demonstrably smarter responses through prompt engineering, agent memory, and disciplined tool use, plus the foundation for the agent to learn from each completed demo.

### What we learned

We learned how to build an autonomous, agentic system from scratch and make it genuinely useful in real time: coordinating voice, live browser control, retrieval, and memory inside one streaming loop without it falling apart under latency. We learned how much of an agent's quality lives in prompt design, persona shaping, and grounded tool use rather than the model alone, and we designed the storage and knowledge layers with future scaling in mind as the user base grows.

### What's next

CRM integration with tools such as HubSpot, Salesforce, and Clay, so qualified pipeline and full conversation context flow straight into the sales team's workflow. Deeper self-improvement for the agent, moving from per-session reflection toward continuous learning loops and reinforcement from outcomes. FAQ and conversion logs that capture recurring questions and the answers that led to buying intent, so the agent responds faster and sells better over time. Additional tool access so the agent can demo more complex workflows.

## README (from the GitHub repository)

# Demoless

**Tagline:** Live product demos, run by an AI rep that can listen, talk, browse, remember, and follow up.

Demoless is an AI-led product demo app. A visitor enters a few details, joins a live call, and talks to an AI product specialist named Messi. Messi listens through the microphone, answers with voice, drives a real browser session that the visitor can watch, remembers useful buyer context, and saves the session for the post-demo dashboard.

The reference demo in this repo is for Browserbase, but the project is structured so the target product, prompt, knowledge base, and browser destination can be changed.

## What This Project Does

Demoless combines a web app, a voice gateway, a browser automation layer, and memory/session storage into one live demo experience.

1. A visitor starts on the landing page.
2. The pre-call form collects name and work email so the demo can be attached to a real buyer record.
3. The demo room opens and automatically starts the voice session.
4. Messi greets the visitor, asks what they want to figure out, and listens for spoken or typed input.
5. The server transcribes speech with Deepgram, sends the conversation to the AI orchestrator, and streams spoken replies back to the browser.
6. When useful, the orchestrator drives a real Browserbase cloud browser session and sends the live view URL to the demo room.
7. Memory and session events are stored so the dashboard can show live activity, past sessions, notes, and recap data.

## Core Functionality

**Live demo room**

The main experience lives in `components/DemoRoom.tsx`. It shows the watched browser, voice status, captions, chat input, language toggle, and call controls. It uses `lib/voice/useVoiceAgent.ts` to connect to the voice WebSocket server.

**Voice agent**

The browser captures microphone audio with `public/worklets/pcm-capture.js`, sends raw PCM to the voice gateway, and plays streamed TTS audio back through the client. The shared message contract is in `lib/voice/messages.ts`.

**Voice gateway**

`server/index.ts` starts the WebSocket server. Each connection creates a `VoiceSession` in `server/session.ts`. A session owns turn-taking, speech-to-text, text-to-speech, barge-in, language switching, buyer identity, browser startup, and session snapshots.

**AI brain and orchestration**

The orchestrator in `server/orchestrator/` decides what to say and what actions to take. It builds prompts from product config, buyer notes, cross-session learnings, and the current page. The lower-level model/tool loop lives in `server/brain/`.

**Browser automation**

The server starts a Browserbase cloud browser through `lib/browser/session`. The live view is embedded in the demo room, so the visitor watches the product being navigated in real time.

**Memory and learnings**

The memory layer in `lib/memory/`, `lib/knowledge/`, `lib/learnings/`, and `lib/sessions/` stores buyer context, product knowledge, session events, replay metadata, and distilled learnings from past demos. Redis is used for this layer.

**Dashboard**

The dashboard components read saved sessions and buyer context so teams can review what happened after the call.

## Tech Stack

**TypeScript**

Used across the app, server, tests, and shared contracts. The frontend and backend share types for voice messages, demo state, sessions, and memory.

**Next.js 15 and React**

Power the web app in `app/` and `components/`. Next.js serves the landing page, pre-call flow, demo room, dashboard, and API routes such as `/api/agent-name`.

**Tailwind CSS**

Provides the design system and utility styling. Theme tokens live in `tailwind.config.ts`.

**Node.js**

Runs the voice gateway, model orchestration, Browserbase session management, memory scripts, smoke tests, and build tooling.

**WebSocket (`ws`)**

Connects the browser to the local voice server for low-latency audio, captions, state updates, live browser URLs, and typed chat messages.

**Deepgram**

Provides speech-to-text and the default text-to-speech voice. The active voice can also determine the agent display name unless `AGENT_NAME` overrides it.

**Anthropic**

Used by the orchestrator for the AI demo brain and tool-use flow.

**Browserbase and Playwright**

Create and control the real cloud browser session that the visitor watches during the demo.

**Redis / Redis Stack**

Stores memory, product knowledge search data, sessions, dashboard state, and learnings. Redis Stack is recommended because product knowledge search uses RediSearch.

**OpenAI**

Used for embeddings in the product-knowledge system and for Mandarin TTS fallback when configured.

**Vitest**

Runs unit tests for the server, orchestration, memory, and session behavior.

## Setup

These steps are written for someone who just wants to run the project locally.

### 1. Install the basics

Install these first:

- Node.js 20 or newer: https://nodejs.org/
- Git: https://git-scm.com/
- A code editor such as VS Code: https://code.visualstudio.com/

Then open a terminal in the project folder.

On Windows PowerShell, this repo is currently at:

```powershell
cd C:\Users\zhenl\Downloads\demoless
```

### 2. Install project packages

```bash
npm install
```

This downloads the libraries listed in `package.json`.

### 3. Create your local settings file

Copy the example environment file:

```powershell
Copy-Item .env.example .env.local
```

On macOS or Linux:

```bash
cp .env.example .env.local
```

Open `.env.local` in your editor and fill in the keys you have.

For the full live voice demo, you usually need:

- `DEEPGRAM_API_KEY`
- `ANTHROPIC_API_KEY`
- `BROWSERBASE_API_KEY`
- `BROWSERBASE_PROJECT_ID`
- `REDIS_URL`
- `OPENAI_API_KEY` if you want product-knowledge embeddings or Mandarin TTS fallback

To force the agent name to Messi, add or keep:

```bash
AGENT_NAME=Messi
```

### 4. Run the web app only

Use this if you just want to see the screens without the voice gateway:

```bash
npm run dev
```

Then open:

```text
http://localhost:3000
```

### 5. Run the full voice demo

Use this when your `.env.local` has the voice, AI, browser, and memory keys:

```bash
npm run dev:all
```

This starts:

- Web app: `http://localhost:3000`
- Voice gateway: `ws://localhost:3001`

Open `http://localhost:3000`, start a demo, allow microphone access, and join the call.

### 6. Optional: Run Redis locally

If you do not already have Redis, you can run Redis Stack with Docker:

```bash
docker run -p 6379:6379 redis/redis-stack:latest
```

Then set this in `.env.local`:

```bash
REDIS_URL=redis://localhost:6379
```

### 7. Useful commands

```bash
npm run dev          # Start the Next.js web app only
npm run dev:voice    # Start the voice gateway only
npm run dev:all      # Start both web app and voice gateway
npm run build        # Create a production build
npm run start        # Run the production web server
npm test             # Run the test suite
npm run smoke        # Run the voice/server smoke test
```

## Project Map

| Path | Purpose |
| --- | --- |
| `app/` | Next.js routes and API endpoints |
| `components/` | Landing page, pre-call form, demo room, dashboard, providers |
| `lib/voice/` | Browser-side voice hook, audio playback, shared message types |
| `server/` | Voice gateway, session lifecycle, orchestration, STT/TTS, model loop |
| `lib/browser/` | Browserbase session helpers |
| `lib/memory/` | Buyer memory and Redis-backed storage |
| `lib/knowledge/` | Product knowledge search and embeddings |
| `lib/learnings/` | Cross-session learning extraction |
| `lib/sessions/` | Session recording, replay metadata, analysis storage |
| `public/worklets/` | Browser AudioWorklet for microphone capture |
| `docs/` | Architecture notes, dogfooding plans, and implementation history |
| `scripts/` | Utility scripts for memory, knowledge, sessions, and learnings |

## Common Problems

**The page opens, but voice does not work.**

Make sure you ran `npm run dev:all`, allowed microphone access, and filled in

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 286 recognized source files, 3192 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- OpenAI (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Cursor — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 346)

```
.cursor/skills/SKILL.md
.dockerignore
.env.example
.gitignore
app/api/agent-name/route.ts
app/api/auth/[...nextauth]/route.ts
app/api/notes/stream/route.ts
app/api/operator/route.ts
app/dashboard/layout.tsx
app/dashboard/page.tsx
app/dashboard/people/[id]/page.tsx
app/dashboard/people/page.tsx
app/dashboard/sessions/[id]/page.tsx
app/dashboard/sessions/page.tsx
app/globals.css
app/harness/page.tsx
app/layout.tsx
app/page.tsx
app/sandbox/page.tsx
auth.ts
BRAINSTORM.txt
CLAUDE.md
components/dashboard/AskBar.tsx
components/dashboard/LiveNotes.tsx
components/dashboard/PacketPanel.tsx
components/dashboard/RecapPanel.tsx
components/dashboard/SessionList.tsx
components/dashboard/SignalGroup.tsx
components/DemoHandoff.tsx
components/DemoRoom.tsx
components/harness/Harness.tsx
components/harness/TracePane.tsx
components/Landing.tsx
components/PreCallForm.tsx
components/Providers.tsx
DESIGN.md
Dockerfile
docs/dogfooding/dogfooding-log.md
docs/dogfooding/INDEX.md
docs/dogfooding/persona-research-brief.md
docs/dogfooding/personas.md
docs/dogfooding/personas/11-qa-regression-smoke.md
docs/dogfooding/personas/12-sre-statuspage-monitor.md
docs/dogfooding/personas/13-ml-eval-leaderboard.md
docs/dogfooding/personas/14-oss-maintainer-triage.md
docs/dogfooding/personas/15-data-engineer-etl-scrape.md
docs/dogfooding/personas/16-indie-hacker-launch-monitor.md
docs/dogfooding/personas/17-security-cve-monitor.md
docs/dogfooding/personas/18-seo-rank-monitor.md
docs/dogfooding/personas/19-founder-vendor-diligence.md
docs/dogfooding/personas/20-analyst-report-pulls.md
docs/dogfooding/personas/21-appstore-review-monitor.md
docs/dogfooding/personas/22-auto-repair-parts-sourcing.md
docs/dogfooding/personas/23-salon-spa-competitor-pricing.md
docs/dogfooding/personas/24-restaurant-food-cost-monitor.md
docs/dogfooding/personas/25-real-estate-comps-cma.md
docs/dogfooding/personas/26-recruiter-candidate-sourcing.md
docs/dogfooding/personas/27-ecommerce-seller-buybox-monitor.md
docs/dogfooding/personas/28-paralegal-court-records.md
docs/dogfooding/personas/29-grants-opportunity-monitor.md
docs/dogfooding/personas/30-academic-paper-monitor.md
docs/memory/learnings.md
docs/plans/2026-06-20-execution-roadmap-demoless-v2.md
docs/plans/2026-06-20-execution-roadmap-demoless.md
docs/plans/2026-06-20-orchestrator-implementation-plan.md
docs/SPONSOR_PREP.md
docs/superpowers/2026-06-21-browser-agent-workflows-executive-report.md
docs/superpowers/plans/2026-06-20-discovery-first-live-agent.md
docs/superpowers/plans/2026-06-20-post-session-recap.md
docs/superpowers/plans/2026-06-20-single-brain-convergence.md
docs/superpowers/specs/2026-06-20-post-session-recap-design.md
docs/superpowers/specs/2026-06-20-single-brain-convergence-design.md
docs/superpowers/specs/2026-06-21-perception-a11y-vision-design.md
docs/VOICE_AGENT.md
lib/actions.ts
lib/browser/__fixtures__/aria-snapshot.fixture.yaml
lib/browser/__fixtures__/aria-snapshot.real.yaml
lib/browser/session.test.ts
lib/browser/session.ts
lib/clayConfig.ts
lib/dashboard/data.test.ts
lib/dashboard/data.ts
lib/dashboard/recapFormat.test.ts
lib/dashboard/recapFormat.ts
lib/dashboard/source.test.ts
lib/dashboard/source.ts
lib/dashboard/useOperator.ts
lib/data.test.ts
lib/data.ts
lib/demoConfig.test.ts
lib/demoConfig.ts
lib/demoRoom.copy.test.ts
lib/design/tailwind-colors.test.ts
lib/harness/mockServer.test.ts
lib/harness/mockServer.ts
lib/harness/useHarness.ts
lib/integrations/connectors.test.ts
lib/integrations/connectors.ts
lib/integrations/index.ts
lib/integrations/run.test.ts
lib/integrations/run.ts
lib/integrations/store.test.ts
lib/integrations/store.ts
lib/integrations/types.ts
lib/knowledge/answer.test.ts
lib/knowledge/answer.ts
lib/knowledge/chunk.test.ts
lib/knowledge/chunk.ts
lib/knowledge/curation.test.ts
lib/knowledge/curation.ts
lib/knowledge/embed.ts
lib/knowledge/index.ts
lib/knowledge/keys.test.ts
lib/knowledge/keys.ts
lib/knowledge/source.ts
lib/knowledge/store.ts
lib/knowledge/types.ts
lib/learnings/index.ts
lib/learnings/keys.test.ts
lib/learnings/keys.ts
[226 more files omitted for size]
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.105.0, @browserbasehq/sdk@^2.14.1, @deepgram/sdk@^5.4.0, @types/node@^20, @types/react@^18, @types/react-dom@^18, @types/ws@^8.18.1, @vitest/coverage-v8@^3.2.6, autoprefixer@^10.4.20, concurrently@^10.0.3, dotenv@^17.4.2, ioredis@^5.4.1, next@^15.5.19, next-auth@^5.0.0-beta.31, playwright-core@^1.61.0, postcss@^8.4.49, react@^18.3.1, react-dom@^18.3.1, tailwindcss@^3.4.17, tsx@^4.22.4, typescript@^5, vitest@^3.2.6, ws@^8.21.0, zod@^4.4.3

### Recent commits (newest first)

- Merge pull request #31 from FisherXZ/codex/perception-a11y-vision
- feat(perception): accessibility-tree elements + screenshot vision fallback
- docs: spec for a11y-tree + vision-fallback perception upgrade
- chore: ignore .worktrees/ worktree dir
- feat: clay multi-product demo, browser tool expansion, integrations layer, handoff screen
- feat(voice): gate buyer prior-session memory behind DEMO_MEMORY (default off)
- Update landing page hero and remove em dashes from marketing copy.
- feat(voice): warmer session opening that learns who the visitor is and why
- fix(voice): stop gateway crashing when a client disconnects mid-startup
- Improve live demo voice agent UX with playbooks and STT filtering.
- feat(voice): gateway that strips stage directions/filler before TTS
- feat(personas): restore audience persona adaptation by visitor role
- Merge branch 'main' of https://github.com/FisherXZ/demoless
- fixed static voice agent output issue
- fix(deps): bump next to ^15.5.19 to clear CRITICAL CVE blocking Railway build
- perf(voice): cache model prompt prefix; add Railway/Docker deploy for the voice backend
- fix(voice): remove hardcoded tool fillers so the agent stops narrating clicks
- feat(demo): gate cross-session learnings behind DEMO_LEARNINGS (default off)
- feat(demo): add common-plays + confusions playbook to Messi system prompt
- feat(demo): tune agent to run demos like a seasoned sales rep

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

### PRODUCT.md

```markdown
# Product

## Register

product

## Users

**Primary: B2B sales teams** — reps, sales engineers, and founders running a
pipeline. Their context is post-call or between-calls: they need to know fast
whether a buyer is worth chasing and what to say next. They live in dashboards
and CRMs all day, so the bar is *instrument-grade* — dense, scannable, trustworthy
at a glance, never a marketing page.

**Secondary: the prospect being demoed to** — the buyer who lands in the live
demo room. They experience Demoless as an AI rep that walks them through a product
(currently **Browserbase**) over chat/voice with a live browser on screen. Their
job: understand the product without booking a human call. Demoless watches this
conversation and turns it into the intelligence the primary user consumes.

The job to be done: **turn an unattended product demo into a scored, evidenced
read on the buyer** — so a sales team knows who's qualified, what they objected
to, and how to follow up, without a human having sat the call.

## Product Purpose

Demoless is an AI agent that demos a product to a prospect (chat/voice + a live
browser) and, while doing it, extracts buyer intelligence. The agent runs a
HOOK → DISCOVERY → WALKTHROUGH → CLOSE arc, remembers returning buyers across
sessions, and emits structured signals (interest, objection, role, decision-maker)
that roll up into a dashboard: KPIs, session replays, per-buyer scorecards, and
follow-up recommendations.

Today the demoed product is **Browserbase** (the agent's knowledge base sells
Browserbase to the prospect). The architecture keeps the demoed product behind a
swappable KB seam, but Browserbase is the committed target — not a placeholder.

Success looks like: a sales team opens the dashboard after a buyer self-served a
demo and trusts the read — score, evidence, and next step — enough to act on it
without re-watching the call.

## Brand Personality

**Serious, fast, precise.** The one line that governs everything (from DESIGN.md):
*"Serious, fast software that turns a conversation into intelligence."* The feel
is **warm technical editorial** — Stripe's warmth × Linear's precision. Confident
and earned, never decorative or hype-y. Color shows up as *data and state*, not
ornament; numbers are monospaced so the product reads like an instrument.

Voice: direct, evidence-first, no marketing fluff. We show the signal and its
timestamp rather than asserting the adjective.

## Anti-references

The de-slop mandate is the canonical list (see DESIGN.md). The product must never
read "a bit AI." Explicitly avoid:

- Default violet-indigo hero fills, indigo-washed panels, gradient tiles.
- Decorative gradients, blobs, glow-rings, pulse halos.
- Template tropes: 3-column icon-square feature grids, fake-logo clouds,
  centered-everything heroes, "3×" brag testimonial cards as decoration.
- Soft-everything: pillowy radii, ambient drop-shadows on flat cards.
- Generic SaaS dashboard chrome: rainbow charts, big-gradient KPI hero 
[truncated — 1480 more characters]
```

### DESIGN.md

```markdown
# Demoless — Design System

Source of truth for the visual system. Formalized from the existing prototype
(`app/globals.css`, `tailwind.config.ts`) and extended to cover the dashboard
surfaces. When code and this file disagree, this file wins — update the tokens
to match.

Date: 2026-06-20 · Set via /design-consultation.

---

## The one thing to remember

**"Serious, fast software that turns a conversation into intelligence."**

Every decision serves that. The product watches a buyer, learns, and hands a
sales team a scored, evidenced read on them. The design should feel *precise and
earned*, never decorative. If an element doesn't carry information or hierarchy,
it comes out.

## Tone: warm technical editorial

Reference north-star: **Stripe's warmth × Linear's precision.** Warm neutrals do
most of the work; color appears as *data and state*, not decoration; type is
confident and tightly tracked; numbers are monospaced so the dashboard reads like
an instrument, not a marketing page.

### What we are deliberately moving away from (de-slop mandate)

The earlier prototype read "a bit AI." These are banned going forward:

- ❌ **Default violet-indigo as a hero fill.** The accent is pulled bluer and used
  sparingly (see Color). No large indigo-washed panels as a default.
- ❌ **Decorative gradients, blobs, glow-rings.** No `radial-gradient` blobs behind
  mockups, no `dlRing` pulse halos, no `from-coal to-brand` gradient tiles. Flat,
  intentional surfaces only. (Functional gradients — e.g. a subtle scrim over a
  video for caption legibility — are fine.)
- ❌ **Template tropes.** No 3-column icon-square feature grids, no fake-logo cloud,
  no centered-everything hero, no "3×" testimonial brag card as decoration.
- ❌ **Soft-everything.** Tighten radii and shadows (see Radii/Elevation). Crisp
  beats pillowy.

What we keep (the good bones): warm stone-tinted neutrals, warm near-black ink on
warm paper, the dark "night" surfaces, Hanken + JetBrains Mono.

### Register split: light marketing, dark instrument app (2026-06-20)

The restraint mandate above was executed into *blandness* on the dashboard (flat
white, starved color, placeholder charts). Correction: the **app surfaces
(dashboard + demo room) are a dark "command center"**, the **marketing landing
stays light**. Dark earns the techy/trustworthy/wow read through *depth, mono data
texture, real chart craft, and disciplined-but-vivid color* — not through the
banned gradients/blobs/glow. See the **Dark instrument layer** under Color and the
dashboard component specs below. Light-surface specs in this file now apply to the
marketing register only.

---

## Typography

| Role | Font | Treatment |
|------|------|-----------|
| Display / headings | **Hanken Grotesk** (`--font-hanken`) | `font-extrabold`, tight tracking (`-0.02em` display, up to `-0.035em` on hero) |
| Body / UI | **Hanken Grotesk** | regular/medium/semibold, normal tracking |
| Micro-labels | **JetBrains Mono** (`--font-jetbrains`) | upp
[truncated — 8531 more characters]
```

### Dockerfile

```
# Voice/WebSocket backend (server/index.ts) for Railway.
# The Next.js frontend deploys separately on Vercel — this image is backend only.
FROM node:20-slim

WORKDIR /app

# Install all deps (incl. devDeps): `npm run server` runs via tsx, a devDependency.
COPY package*.json ./
RUN npm ci

# App source. playwright-core needs no local browser — execution is remote on Browserbase.
COPY . .

# Railway injects PORT; server/index.ts binds it (falls back to 3001).
CMD ["npm", "run", "server"]

```

### package.json

```
{
  "name": "demoless-web",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "dev:voice": "tsx watch server/index.ts",
    "dev:all": "concurrently -n web,voice -c cyan,magenta \"npm:dev\" \"npm:dev:voice\"",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "server": "node --env-file-if-exists=.env.local --env-file-if-exists=.env --import tsx server/index.ts",
    "smoke": "tsx server/smoke.ts",
    "test": "vitest run",
    "test:watch": "vitest",
    "memory:smoke": "node --env-file-if-exists=.env.local --import tsx scripts/memory-smoke.ts",
    "knowledge:seed": "node --env-file-if-exists=.env.local --import tsx scripts/knowledge-seed.ts",
    "knowledge:curate": "node --env-file-if-exists=.env.local --import tsx scripts/knowledge-curate.ts",
    "knowledge:reindex": "node --env-file-if-exists=.env.local --import tsx scripts/knowledge-reindex.ts",
    "knowledge:export": "node --env-file-if-exists=.env.local --import tsx scripts/knowledge-export.ts",
    "knowledge:smoke": "node --env-file-if-exists=.env.local --import tsx scripts/knowledge-smoke.ts",
    "learnings:smoke": "node --env-file-if-exists=.env.local --import tsx scripts/learnings-smoke.ts",
    "sessions:smoke": "node --env-file-if-exists=.env.local --import tsx scripts/sessions-smoke.ts"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.105.0",
    "@browserbasehq/sdk": "^2.14.1",
    "@deepgram/sdk": "^5.4.0",
    "ioredis": "^5.4.1",
    "next": "^15.5.19",
    "next-auth": "^5.0.0-beta.31",
    "playwright-core": "^1.61.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "ws": "^8.21.0",
    "zod": "^4.4.3"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "@types/ws": "^8.18.1",
    "@vitest/coverage-v8": "^3.2.6",
    "autoprefixer": "^10.4.20",
    "concurrently": "^10.0.3",
    "dotenv": "^17.4.2",
    "postcss": "^8.4.49",
    "tailwindcss": "^3.4.17",
    "tsx": "^4.22.4",
    "typescript": "^5",
    "vitest": "^3.2.6"
  }
}

```

### app/page.tsx

```typescript
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useDemoState } from "@/lib/useDemoState";
import Landing from "@/components/Landing";
import PreCallForm from "@/components/PreCallForm";
import DemoRoom from "@/components/DemoRoom";
import DemoHandoff from "@/components/DemoHandoff";

export default function Page() {
  const vals = useDemoState();
  const router = useRouter();

  // The buyer flow (landing → form → room) stays on screen-state; "End call"
  // (goDashboard) routes into the real operator app.
  useEffect(() => {
    if (vals.screen === "dashboard") router.replace("/dashboard");
  }, [vals.screen, router]);

  return (
    <div className="min-h-screen bg-paper">
      {vals.screen === "landing" && <Landing vals={vals} />}
      {vals.screen === "form" && <PreCallForm vals={vals} />}
      {vals.screen === "room" && <DemoRoom vals={vals} />}
      {vals.screen === "handoff" && <DemoHandoff vals={vals} />}
    </div>
  );
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Hanken_Grotesk, JetBrains_Mono, Newsreader } from "next/font/google";
import "./globals.css";
import Providers from "@/components/Providers";

const hanken = Hanken_Grotesk({
  subsets: ["latin"],
  weight: ["400", "500", "600", "700", "800"],
  variable: "--font-hanken",
  display: "swap",
});

const jetbrains = JetBrains_Mono({
  subsets: ["latin"],
  weight: ["400", "500", "600"],
  variable: "--font-jetbrains",
  display: "swap",
});

// Editorial display serif for hero + dashboard page titles.
const newsreader = Newsreader({
  subsets: ["latin"],
  weight: ["400", "500", "600"],
  style: ["normal", "italic"],
  variable: "--font-serif",
  display: "swap",
});

export const metadata: Metadata = {
  title: "Demoless",
  description: "The AI AE that sells your SaaS while your team sleeps.",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={`${hanken.variable} ${jetbrains.variable} ${newsreader.variable}`}>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

```

### server/index.ts

```typescript
import dotenv from "dotenv";
// Prefer .env.local (gitignored, matches Next.js), fall back to .env.
dotenv.config({ path: ".env.local" });
dotenv.config();

import { createServer } from "http";
import { WebSocketServer } from "ws";
import { VoiceSession } from "./session";

/**
 * Voice WebSocket gateway (P2).
 *
 * Keeps Deepgram/Anthropic keys server-side. The browser connects to
 * NEXT_PUBLIC_VOICE_WS_URL; each connection gets one {@link VoiceSession}.
 */
// Prefer VOICE_SERVER_PORT locally (avoids the text-harness PORT clash); fall
// back to the PORT injected by container hosts (Railway), then the dev default.
const port = Number(process.env.VOICE_SERVER_PORT ?? process.env.PORT ?? 3001);
const deepgramKey = process.env.DEEPGRAM_API_KEY ?? "";

if (!deepgramKey) {
  console.warn(
    "[voice] DEEPGRAM_API_KEY is not set - STT/TTS will fail. Copy .env.example to .env.local and add your keys."
  );
}
if (!process.env.ANTHROPIC_API_KEY) {
  console.warn(
    "[voice] ANTHROPIC_API_KEY is not set - the stub orchestrator will fail to answer."
  );
}

// Share one HTTP server with the WS upgrade so PaaS health checks have a
// route to hit (Railway probes GET /health over HTTP on the same port).
const httpServer = createServer((req, res) => {
  if (req.method === "GET" && (req.url === "/health" || req.url === "/")) {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("ok");
    return;
  }
  res.writeHead(404);
  res.end();
});

// Backstop: a single session's stray async error must never take down the
// whole gateway (and with it every other live demo). Log loudly, stay up.
process.on("unhandledRejection", (reason) => {
  console.error("[voice] unhandledRejection (kept alive):", reason);
});
process.on("uncaughtException", (err) => {
  console.error("[voice] uncaughtException (kept alive):", err);
});

const wss = new WebSocketServer({ server: httpServer });

wss.on("connection", (ws) => {
  console.log("[voice] client connected");
  new VoiceSession(ws, deepgramKey);
});

wss.on("error", (err) => {
  console.error("[voice] server error:", err);
});

httpServer.listen(port, "0.0.0.0", () => {
  console.log(`[voice] gateway listening on :${port} (ws + GET /health)`);
});

const shutdown = () => {
  console.log("[voice] shutting down");
  wss.close();
  httpServer.close(() => process.exit(0));
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);

```

### app/harness/page.tsx

```typescript
import Harness from "@/components/harness/Harness";

export const metadata = {
  title: "Demoless · Chat Harness",
};

export default function HarnessPage() {
  return <Harness />;
}

```

### lib/integrations/index.ts

```typescript
// Public surface of the outbound integration layer.
export type {
  ConnectorId,
  ActionField,
  DraftAction,
  IntegrationAction,
  ConnectorStatus,
} from "./types";
export {
  buildActions,
  buildHubspotActions,
  buildClayActions,
  buildLinearActions,
} from "./connectors";
export { recordActions, listActions, connectorStatuses } from "./store";
export { dispatchIntegrations } from "./run";

```

### lib/learnings/index.ts

```typescript
// Public surface of the cross-session demo-learnings layer.
export {
  writeLearnings,
  getLearnings,
  rankLearnings,
  buildLearningsContext,
  MAX_LEARNINGS,
  TOP_K,
  MIN_CONFIDENCE,
} from "./store";
export {
  reflectOnSession,
  reflectAndStore,
  parseLearnings,
  type ChatFn,
  type ReflectTurn,
} from "./reflect";
export { learningsKey, companySlug, NS } from "./keys";
export type { Learning, LearningInput } from "./types";

```

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