Video-Editing

Specs/2026-05-03-demo-factory-design.md

Demo Factory — Design Spec v1.0

1. Vision & Scope

What it is

Demo Factory is a Claude Code plugin that produces flashy, 30–60 second product demo videos from a running local dev server and project documentation. It is Phase 1 of a larger ArijitOS Demo Studio — a capability of Arijit's personal OS for building, demonstrating, and distributing products.

It is simultaneously: - A standalone tool — invoke it, get an MP4 demo video - A feeder — produce the script + screenshot bundle + audio formatted for Heygen (avatar) and 11Labs (voiceover) ingestion - A platform foundation — every artifact it produces is separable and re-usable by future extensions (LinkedIn carousels, YouTube Shorts, AI b-roll, UGC ads)

Phase 1 target products

Crawler Factory, Algolia Central, PRISM, NoCo II.

What it is NOT (Phase 1)

  • Not a full video editor
  • Not a UGC ad creator
  • Not a LinkedIn carousel generator
  • Not an AI b-roll generator
  • All of the above are captured as Future Extensions (see Section 10)

2. Distribution Shape

A Claude Code plugin — not a single skill, not a standalone app.

demo-factory/
├── plugin.json              # Plugin manifest
├── skills/
│   ├── demo-factory/        # Main conversational activator + pipeline conductor
│   ├── capture/             # Capture sub-skill (LLM-3: YAML generation only)
│   ├── synopsis/            # Doc synthesis sub-skill (LLM-1)
│   ├── script-writer/       # Multi-track script generation (LLM-2)
│   └── brand-extractor/     # Brand autodetect + proposal (LLM-4)
├── commands/
│   └── demo/                # /demo <product> <url> slash command
├── scripts/                 # Deterministic TypeScript/Node — NO LLM
│   ├── workspace.ts         # Run directory setup + state machine
│   ├── capture.ts           # Playwright runner (replay + record modes)
│   ├── brand-detect.ts      # CSS/favicon/meta scraper
│   ├── render.ts            # Remotion CLI wrapper (per-format)
│   ├── upload.ts            # Cloud archive uploader (StorageProvider)
│   ├── validate.ts          # JSON Schema validation for all artifacts
│   └── preflight.ts         # Dependency check + auto-install (runs on first invoke)
├── engine/                  # Remotion compositions
│   ├── package.json
│   └── src/
│       ├── DemoComposition.tsx   # Root, aspect-aware
│       ├── layers/
│       │   ├── ScreenLayer.tsx
│       │   ├── TextOverlayLayer.tsx
│       │   ├── VOPlayer.tsx
│       │   └── AvatarLayer.tsx
│       └── themes/
│           └── BrandContext.tsx
├── adapters/
│   ├── voice/elevenlabs.ts  # VoiceProvider implementation
│   ├── avatar/heygen.ts     # AvatarProvider implementation
│   └── storage/gdrive.ts    # StorageProvider implementation (Phase 1)
├── cli/
│   └── demo-factory.ts      # Standalone binary (no LLM, orchestrates scripts)
└── types/
    ├── script.ts            # Multi-track script schema (TypeScript + JSON Schema)
    ├── brand.ts             # Brand profile schema
    ├── capture.ts           # Capture manifest schema
    └── run.ts               # Run state schema

Installation

# From Claude Code plugin marketplace
/plugin install arijitchowdhury/demo-factory

# Or from GitHub (local install)
/plugin install /path/to/demo-factory

# Installs the Remotion official skill as a sub-dependency
npx skills add remotion-dev/skills

Multi-user distribution

Teammates install the plugin in their own Claude Code installation — same slash command, same local path install. Each installation is fully standalone. No API bridge, no server, no shared runtime. This is the only supported "Cowork" surface: each user runs their own copy of the plugin.

See Section 15 for full prerequisites and preflight check.

Invocation — three surfaces, same engine

# Slash command (Claude Code)
/demo crawler-factory http://localhost:3000

# Conversational skill (any Claude Code session)
"Build a demo for Crawler Factory, dev server is on :3000"

# Standalone CLI (outside Claude Code)
demo-factory build --product crawler-factory --url http://localhost:3000
demo-factory build --product crawler-factory --url http://localhost:3000 --format=all
demo-factory build --product crawler-factory --url http://localhost:3000 --yolo

3. The LLM Discipline Boundary

Core rule: Deterministic scripts do the work. LLM is the brain for synthesis only. No LLM calling LLM.

The 4 LLM synthesis points (and nothing else)

ID Skill Input Output Invoked when
LLM-1 synopsis/ Raw docs text (vault + repo + pasted) 2–3 para synopsis G2 gate, once per run
LLM-2 script-writer/ Synopsis + screenshot manifest + brand profile script.json (validated) G3 gate, once per run
LLM-3 capture/ Recorded Playwright interaction log capture.yaml After first live-record session only
LLM-4 brand-extractor/ Raw CSS/favicon/meta JSON from brand-detect.ts Proposed brand.yaml First run for a new product only

What is always deterministic

  • Playwright capture replay
  • Playwright live-record execution
  • CSS/favicon scraping (brand-detect.ts)
  • Schema validation (validate.ts)
  • Remotion render (render.ts)
  • 11Labs API call and audio download
  • Heygen export bundle generation
  • Cloud upload
  • Workspace creation and state transitions
  • Cost estimates (calculated from script length and provider rate tables, no LLM)
  • Brand drift detection (CSS diff comparison, no LLM)

4. Pipeline — The 4-Gate Flow

Every demo run is a state machine: init → captured → synopsized → scripted → rendered → done.

Each transition writes a state file (run.json) to the run workspace. The pipeline is resumable — if you rerun for a product with a run in state scripted, it skips to G3. Each gate is bypassable via --yolo once trusted.

Step -1 — Scope & Angle Selection

This step runs before any workspace is created. It is the answer to: "What story are we telling?"

A 30–60s demo cannot cover a whole product. DemoFactory must know the demo angle — the specific user story, feature flow, or value proposition this run is demonstrating — before it touches a single file, runs Playwright, or reads any documentation. The angle drives everything: which docs to read, which flow to record, which beats the script generates.

For a known product with previous runs

DemoFactory loads Products/<slug>/runs-index.json and presents angles already used:

Algolia Central has 3 previous demo angles:
  1. Search-as-you-type UX (2026-04-10) — 42s, 9:16
  2. Analytics dashboard tour (2026-04-18) — 58s, 9:16
  3. Configuration onboarding (2026-04-25) — 35s, 9:16

Use an existing angle, describe a new one, or type "readiness" to check demo readiness.

If user picks an existing angle: load its capture.yaml and previous synopsis.md as starting context. The pipeline still runs all gates — but with a head start.

For a new product or new angle

DemoFactory asks two questions (only two — not an intake form):

1. What's the demo angle? Describe the specific flow or feature in 1–2 sentences.
   (e.g., "The search-as-you-type UX — from empty search box to result click in 8 seconds.")

2. Who's watching? Pick one: [technical] [business] [mixed]

From the user's answer, DemoFactory derives: - angle_slug — kebab-case identifier stored in run.json (e.g., search-as-you-type) - audience — drives tone in LLM-2 script generation - focus_keywords — 3–5 terms extracted from the angle description, used to filter docs in Step 2

If the user describes an angle, DemoFactory echoes back: "Got it — demoing [angle summary] for a [audience] audience. Starting capture." No further confirmation needed.

Why this matters for large products

Without an angle, the synopsis step drowns in irrelevant docs, the capture records the wrong flow, and the script tries to tell too many stories at once. The angle is the constraint that makes a 60s demo possible for a product like Algolia Central.


Step 0 — Invoke

User invokes via slash command, conversational skill, or CLI. Engine: 1. Creates run workspace: Runs/YYYY-MM-DD-<slug>-<seq>/ in vault, tagged with angle_slug 2. Creates binary workspace: ~/AI-Development/Video_Editing/binaries/<slug>/<run-id>/ 3. Loads merged brand profile (base + product override) 4. Verifies dev server is reachable (deterministic health check) 5. Writes run.json with status init, including angle, audience, focus_keywords

Step 1 — Capture

scripts/capture.ts is invoked:

  • If Products/<slug>/capture.yaml exists: Replay mode — Playwright executes the script, screenshots taken per scene, output is a capture-manifest.json (scene ID, file path, timestamp, annotations).
  • If missing: Record mode — Playwright opens a browser window, user drives the app, every nav/click/wait is logged to interaction-log.json. On exit, user confirms, then LLM-3 (capture/ skill) converts the log into capture.yaml and persists it to the product workspace.

Output: binaries/<slug>/<run-id>/captures/ (PNG screenshots) + capture-manifest.json in vault run folder.

Gate 1 — Capture approval

Skill presents: "Captured N scenes. Here's the manifest with thumbnail paths." User can: - Approve as-is - Mark scenes as cut - Trigger re-capture of specific scene IDs - Request a full re-record

Step 2 — Synopsis

synopsis/ skill (LLM-1) must produce a tight, angle-scoped synopsis — not a product overview. It receives focus_keywords from run.json and uses them to filter which docs it reads.

Doc selection strategy (critical for large products)

Phase A — Orient (always, cheap):

Before reading any file content, build a file index: 1. List Projects/<slug>/ directory tree in vault (names + paths only, no content) 2. List repo root files if repo_path is configured 3. Score each file against focus_keywords — files whose name/path contains a keyword score higher 4. Sort by score descending, take top N that fit the token budget

Phase B — Read in priority order (bounded by token budgets):

Source Token budget What to read
Vault angle-specific 4,000 Highest-scoring files from Phase A — typically the relevant Spec or PRD section
Vault overview 2,000 README.md or top-level overview doc in vault. Always read even if off-angle — provides product framing
Repo 1,500 README.md, CLAUDE.md only. Never read source files. git log --oneline -10 for recent changes
Pasted 500 Only if Phases A–B produced < 500 tokens of useful content

Never read source code. .ts, .tsx, .py, .go etc. are explicitly excluded. Only documentation files (.md, .txt, config-as-docs .yaml with comments).

Phase C — Angle-filtered synthesis:

LLM-1 prompt explicitly states the angle and audience. It is instructed to synthesize only information relevant to the demo angle — not a general product description. Files that were read but contain no angle-relevant content should be explicitly noted as "read but not used" in the provenance report.

What the synopsis contains: - 1 paragraph: the specific problem this demo angle addresses - 1 paragraph: what the user sees in the demo flow (step by step) - 1 paragraph: the "wow moment" — the one thing that lands for this audience

Synthesizes from angle-filtered reading. Provenance is surfaced — skill reports exactly which files were read, which were used, and their last-modified dates.

Output: synopsis.md in vault run folder.

Gate 2 — Synopsis approval

Skill presents the synopsis with provenance. User can: - Approve as-is - Edit inline and approve - Paste an entirely different synopsis

Step 3 — Script

script-writer/ skill (LLM-2) generates a multi-track script object from the approved synopsis + capture manifest + merged brand profile.

The script is a structured JSON object. Every beat has:

interface Beat {
  id: string;
  role: "hook" | "problem" | "demo" | "cta"; // narrative position — enables per-role styling + selective re-generation
  start_ms: number;
  duration_ms: number;
  screen_ref: string;          // references a scene in capture-manifest
  vo?: string;                 // voiceover line → 11Labs
  text_overlay?: string;       // on-screen kinetic text → Remotion TextOverlayLayer
  avatar_line?: string;        // avatar talking line → Heygen
}

interface Script {
  product: string;
  run_id: string;
  angle_slug: string;          // from run.json — ties script to the specific demo angle
  total_duration_ms: number;   // enforced ≤ 60000
  beats: Beat[];
  structure: "hook-problem-demo-cta"; // validated by LLM-2
}

validate.ts runs JSON Schema validation before presenting to user. If validation fails, LLM-2 is re-invoked with the validation errors (not the user's attention).

Output: script.json in vault run folder.

Gate 3 — Script approval

Skill presents the script as a beat table: beat × (screen thumbnail + VO + text overlay + avatar line + duration). Also shows: - Total duration vs 60s budget - Cost estimate: "11Labs ~$0.04 (auto). Heygen ~$0.30 (export mode — no API cost). Proceed?" - Track selection: user chooses which tracks render for this run: - --tracks=text — silent kinetic-text reel (default for first run) - --tracks=vo,text — narrated explainer - --tracks=vo,text,avatar — full stack

User can: - Approve as-is - Edit any beat inline - Change track selection - Reject and loop back to G2 with notes

Step 4 — Render

Executed in parallel per requested output format:

  1. VoiceProvider (11Labs adapter, auto mode by default): - Takes script.vo_track (array of VO lines with timing) - Calls 11Labs API with voice_id from brand profile - Downloads MP3 to binaries/<slug>/<run-id>/audio/vo.mp3 - Returns timing manifest (character → ms offset)

  2. AvatarProvider (Heygen adapter, export mode by default): - Takes script.avatar_track + avatar_id from brand profile + screenshot refs - Generates export bundle: binaries/<slug>/<run-id>/heygen-bundle/ with manifest.json + assets - If auto mode: calls Heygen API, polls for completion, downloads avatar MP4

  3. Remotion render (scripts/render.ts): - For each requested format (default: 9:16 only):

    • Assembles <DemoComposition> with: active tracks, screenshot paths, VO MP3, brand profile, avatar MP4 (if available)
    • Fires npx remotion render
    • Output: binaries/<slug>/<run-id>/renders/<format>/demo.mp4

Gate 4 — Render approval

Skill prints: "Render complete. Path: binaries/.../renders/9:16/demo.mp4. Duration: 42s."

User can: - Approve → triggers Step 5 (archive) - Reject → loops back to G3 with notes (or G2 if synopsis was the problem) - Request re-render with different tracks or format

Step 5 — Archive

On approval: 1. Final MP4(s) uploaded to cloud archive via StorageProvider (Phase 1: Google Drive) 2. Cloud URL(s) returned and written to run record 3. Runs/INDEX.md in vault updated: run-id, product, status, formats, cloud URLs, duration, date 4. run.json state set to done 5. Console: "Demo archived. Cloud URL: [url]. Local: [path]."


5. Artifact Map

Every artifact is a first-class file, addressable, re-usable, re-renderable.

Vault (text, synced):
  Projects/Video-Editing/
    Products/<slug>/
      brand.yaml              # merged brand override (D9)
      capture.yaml            # Playwright replay script (D3) — also accepted from <app-repo>/demo/
      config.yaml             # product config (vault path, repo path, voice_id, avatar_id) — see DemoConfig schema below
    Runs/
      INDEX.md                # master index of all runs
      <run-id>/
        run.json              # pipeline state + metadata
        capture-manifest.json # scene list + paths
        synopsis.md           # approved synopsis
        script.json           # approved multi-track script
        render-manifest.json  # output paths + cloud URLs

App-side (in each product's own repo — see DEMO-READY.md contract):
  <app-repo>/demo/
    config.yaml             # DemoConfig — scenarios, api_mocks, doc paths (see schema below)
    capture.yaml            # Playwright replay script for this product
    fixtures/               # API response fixtures for mock interception
      *.json                # JSON responses
      *.txt                 # SSE streams (text/event-stream)
    brand.yaml              # optional brand override (auto-detected on first run)

Local disk (binaries, not synced):
  ~/AI-Development/Demo_Factory/binaries/<slug>/<run-id>/
    captures/                 # raw PNG screenshots
    audio/
      vo.mp3                  # 11Labs VO audio
    heygen-bundle/            # Heygen export assets (or avatar.mp4 if auto)
    renders/
      9-16/demo.mp4
      16-9/demo.mp4           # only if --format includes it
      1-1/demo.mp4

Cloud archive (finals):
  Google Drive: /Demo-Factory-Archive/<slug>/<run-id>/
    demo-9-16.mp4
    demo-16-9.mp4             # if rendered

DemoConfig schema (TypeScript source: types/demo-config.ts)

interface ApiMock {
  endpoint: string;       // substring match against full request URL
  method: string;         // GET | POST | PUT | DELETE
  fixture: string;        // path relative to <app-repo>/demo/
  content_type?: string;  // explicit override; default: .txt→text/event-stream, .json→application/json
}

interface DemoScenario {
  id: string;             // kebab-case slug
  title: string;          // one-sentence description
  angle: string;          // narrative spine (1–2 sentences)
  audience: string;       // technical | business | mixed
  capture: string;        // filename of capture.yaml relative to demo/
  api_mocks?: ApiMock[];  // Playwright route interceptions for this scenario
}

interface DemoConfig {
  product: string;        // slug, kebab-case
  url: string;            // dev server URL
  docs?: {
    vault_path?: string;  // relative to vault root ArijitOS-Brain/Projects/
    repo_path?: string;   // path to app repo (. = current repo)
  };
  scenarios: DemoScenario[];
}

CLI flag: --repo <path> on build and readiness commands loads <path>/demo/config.yaml and uses <path>/demo/<scenario.capture> as the capture script. First scenario is used unless --scenario <id> is specified.


6. Output Formats

Format Resolution Default Flag
Vertical 1080×1920 (9:16) ✅ Yes --format=9:16
Landscape 1920×1080 (16:9) No --format=16:9
Square 1080×1080 (1:1) No --format=1:1
All All three No --format=all

Remotion compositions are aspect-aware from day one — same component tree renders all three. Screen captures are taken at 2× resolution for clean downscaling.


7. Brand System

Two-layer model

  1. Base profile~/.demo-factory/brand/arijitos-base.yaml: - Default display + body fonts - Default avatar_id (Heygen) - Default voice_id (11Labs) - House color system - Global tone: "technical, builder, founder"

  2. Per-product overrideProducts/<slug>/brand.yaml: - Only fields that differ from base - Typically: primary/accent colors, tagline, product-specific logo, product-specific tone

First-run brand setup for a new product

  1. scripts/brand-detect.ts scrapes running app: CSS custom properties, favicon, meta theme-color, font-family declarations
  2. Outputs raw brand-data.json
  3. LLM-4 (brand-extractor/ skill) interprets and proposes brand.yaml
  4. Skill shows the proposed profile (colors, fonts, logo, tone) side by side with the base
  5. User confirms/edits/approves
  6. Persisted to Products/<slug>/brand.yaml

Subsequent runs: Load from saved override. Skill runs brand-detect.ts and compares to saved profile — if CSS has drifted, surfaces a warning (not a block): "Brand drift detected in primary color (#0066ff → #0055ee). Update profile?"

Brand schema

interface BrandProfile {
  name: string;
  colors: { primary: string; accent: string; bg: string; text: string; };
  fonts: { display: string; body: string; mono?: string; };
  logo: { path: string; dark_variant?: string; };
  tone: string[];
  tagline?: string;
  voice_id: string;          // 11Labs voice ID
  avatar_id: string;         // Heygen avatar ID
}

8. Provider Interfaces

VoiceProvider

interface VoiceProvider {
  estimate(voTrack: VOLine[]): CostEstimate;
  generate(voTrack: VOLine[], voiceId: string, mode: "auto" | "export"): Promise<VoiceOutput>;
}

interface VoiceOutput {
  mode: "auto" | "export";
  audioPath?: string;         // auto mode: path to MP3
  exportBundle?: string;      // export mode: path to bundle folder
  timingManifest: TimingEntry[];
}

AvatarProvider

interface AvatarProvider {
  estimate(avatarTrack: AvatarLine[]): CostEstimate;
  generate(avatarTrack: AvatarLine[], avatarId: string, mode: "auto" | "export"): Promise<AvatarOutput>;
}

interface AvatarOutput {
  mode: "auto" | "export";
  videoPath?: string;         // auto mode: path to avatar MP4
  exportBundle?: string;      // export mode: path to Heygen bundle
}

StorageProvider

interface StorageProvider {
  upload(localPath: string, destination: string): Promise<UploadResult>;
}

interface UploadResult {
  url: string;                // public or shareable URL
  provider: string;
}

Phase 1 adapters: - voice/elevenlabs.ts — auto mode default - avatar/heygen.ts — export mode default - storage/gdrive.ts — Google Drive (existing CloudStorage mount)


9. Reuse Map — Research → Implementation

Researched Skill What we take
Remotion official skill (remotion-dev/skills) Install as dependency. 28 modular rules (animations, compositions, captions, lottie) feed directly into our engine/ compositions.
GSAP official skills (SplitText, ScrollTrigger) Kinetic typography patterns for TextOverlayLayer — staggered reveals, scramble effects, spring physics on text.
wshuyi/remotion-video-skill TTS-to-timing sync pattern for VOPlayer — MiniMax/Edge TTS approach inspires our 11Labs adapter timing manifest.
digitalsamba/claude-code-video-toolkit Primary reference architecture — their Playwright + Remotion + FFmpeg combined pipeline is our design target. Brand profiles, transitions, reusable components.
Yusuke710/manim-skill Gate loop architecture — Plan→Code→Render→Iterate maps exactly to our G1→G2→G3→G4. Autonomous feedback-driven iteration within each gate is the pattern.
browser-use/video-use Subtitle burn-in styling patterns for TextOverlayLayer. Filler word removal approach inspires VO track cleanup in script-writer.
talknerdytome-labs/wiggle-claude-skill Logo animation patterns (fade, bounce, pulse, wiggle) for the brand intro scene in DemoComposition.
YouTube Clipper Skill Semantic scene annotation approach — chapter/segment semantic labeling inspires how we annotate captured scenes in capture-manifest.json.
Playwright MCP --save-video Informs our record-mode capture — we use the same Playwright browser but capture screenshots per scene rather than raw screen recording.
Algorithmic-art (p5.js) skill Background animation / particle effects patterns for brand intro/outro scene transitions if needed.

10. Future Extensions (from requirements)

ID Extension Notes
F1 UGC Ad Creation Different tone/pacing/actor framing. Same capture + script engine, different composition.
F2 Nano Banana Image Gen From script beats → Gemini 3 presentation-grade images. Feeds slides and LinkedIn.
F3 Visual Storyboard Script beats → storyboard image sequence. Input to AI video generators.
F4 AI B-roll (Veo/Sora/Kling) Hosted video gen for transitions/atmospheric shots composited into Remotion timeline.
F5 LinkedIn Carousel Script + screens → multi-slide PNG/PDF carousel.
F6 Multi-Channel Distribution Pack --format=all produces 9:16, 16:9, 1:1 + caption copy + hashtags.
F7 Full Video Editing Suite Clip cutting, multi-source compositing, color grading. Demo Factory is one capability inside.
F8 Demo Library Runs/INDEX.md foundation already in place. Searchable in Obsidian.
F9 Avatar Library Multiple avatars per product / persona, managed profiles.
F10 Voice Library Multiple 11Labs voice profiles. A/B test voices across renders.
F11 Re-cut / Update Pipeline Re-run against evolved product. Diff old vs new run.
F12 Analytics Loop Engagement metrics → optimize future scripts.
F13 Cloud Archive Expansion S3/R2/YouTube Unlisted as StorageProvider alternatives.

11. Open Sub-Decisions

ID Decision Candidates Status
SD-1 Cloud archive target Google Drive (Phase 1 default), S3/R2, YouTube Unlisted Deferred; Phase 1 = Google Drive
SD-2 CLI packaging ts-node runner vs compiled binary via esbuild or pkg Deferred to implementation plan
SD-3 Remotion render environment Local Node.js vs Docker vs remote rendering (Remotion Cloud) Phase 1 = local; deferred if render times too slow

12. Non-Goals (explicitly out of scope for Phase 1)

  • Real-time video generation (AI models generating frames from scratch)
  • Multi-user / team workflow
  • Video hosting / publishing (we archive to cloud; distribution is manual)
  • Audio mixing / background music (future extension)
  • Animated b-roll (Future Extension F4)

13. Constraints

  • No LLM calling LLM. LLM invoked only at the 4 synthesis points defined in Section 3.
  • No secrets in repo. API keys via env or ~/.demo-factory/credentials. Never committed.
  • Type-safe artifacts. All artifact schemas have TypeScript types + JSON Schema definitions. Validated by validate.ts before every gate.
  • Bypassable gates. --yolo flag skips all gates for trusted pipelines. Individual gate skip: --skip-g1, etc.
  • Resumable runs. Every step writes state. Re-invoking for a run-id resumes from last completed gate.
  • Render is idempotent. Same script.json + assets → same MP4. Re-render does not require re-capturing or re-scripting.
  • Brand profile is injectable. No hardcoded colors, fonts, or logos anywhere in engine or scripts.
  • Preflight before every first run. scripts/preflight.ts must pass (or have a fresh marker) before any pipeline gate executes. Hard fails on missing Node ≥ 18 or ffmpeg. See Section 15.

14. Visual Design — Light Modern Theme

The default rendered video frame style is Light Modern: clean, developer-friendly, and neutral enough that the product UI (shown in the browser window) is always the hero — not the frame around it.

Design Language

Property Value
Background White (#FFFFFF); outer card #F8F9FA
Browser chrome Simulated browser bar — traffic lights, URL bar, single tab
Text primary Near-black #1A1A2E (headings)
Text secondary Medium gray #4A4A6A (body, captions)
Accent Brand primary color — CTA badges, progress bar, kinetic text highlights
Typography Display + body: Inter (fallback); mono: JetBrains Mono
Shadow 0 4px 24px rgba(0,0,0,0.08)
Radius Cards: 12px; UI elements: 8px

Layout per format

Format Layout
9:16 vertical Browser chrome panel top 70%; bottom 30% = kinetic text + brand bar
16:9 landscape Browser chrome left 70%; right panel = kinetic text + VO captions
1:1 square Browser chrome full-bleed; top text overlay band; bottom brand bar

Implementation (engine/src/themes/BrandContext.tsx)

BrandContext.tsx exports a useTheme() hook. Light Modern is the base theme — injected into context when no product override specifies a theme. Brand primary and accent from BrandProfile are overlaid on top at render time.

const LIGHT_MODERN_THEME = {
  bg: '#FFFFFF',
  cardBg: '#F8F9FA',
  textPrimary: '#1A1A2E',
  textSecondary: '#4A4A6A',
  border: 'rgba(0,0,0,0.08)',
  radius: { card: '12px', element: '8px' },
  shadow: '0 4px 24px rgba(0,0,0,0.08)',
  font: { display: 'Inter', body: 'Inter', mono: 'JetBrains Mono' },
} as const;

Alternative themes (Phase 2)

dark-cinematic and bold-gradient token sets exist as stubs in engine/src/themes/ and are selectable via --theme flag. Phase 1 ships Light Modern only.


15. Prerequisites & Preflight Check

System dependencies (user must install — not auto-installable)

Dependency Required for Min version Install
Node.js Everything 18 LTS brew install node / nvm use 18
ffmpeg Remotion video render Any recent brew install ffmpeg / apt install ffmpeg / choco install ffmpeg
Git Repo doc source (Section 4 Step 2) Any Usually pre-installed

ffmpeg is the critical one — Remotion shells out to it for every render. The plugin will not render without it.

npm packages (auto-installed by plugin on first run)

scripts/preflight.ts runs npm install inside the plugin directory on first invocation. No manual action needed.

Key packages installed:

Package Purpose
@remotion/cli, remotion Video composition and render
@playwright/test Capture (record + replay mode)
playwright (chromium binary) Browser for capture — npx playwright install chromium runs automatically
typescript, ts-node Script execution
ajv JSON Schema validation (validate.ts)
zod Runtime type validation in scripts
axios 11Labs + Heygen API calls

External service credentials

Stored in ~/.demo-factory/credentials (never in repo). Plugin reads from env first, falls back to credentials file.

Credential Required When needed Default Phase 1 behaviour
ELEVEN_LABS_API_KEY Yes Step 4, VO generation Preflight warns if missing; VO track silently skipped if not set and --tracks includes vo
HEYGEN_API_KEY No (Phase 1) Step 4, avatar auto mode only Phase 1 default is export mode — no API key needed
Google Drive No (Phase 1) Step 5, archive Uses existing macOS CloudStorage mount at ~/Library/CloudStorage/ — no API key needed

Preflight check — scripts/preflight.ts

Runs automatically on every first invocation for a new install. Also runnable manually: demo-factory preflight.

Checks in order:

  1. Node versionnode --version ≥ 18. Hard fail if below.
  2. ffmpegwhich ffmpeg. Hard fail with platform-specific install instructions if missing.
  3. Gitwhich git. Soft warn if missing (repo doc source unavailable; vault + pasted still work).
  4. npm packages — checks node_modules exists and key packages present. Runs npm install if not.
  5. Playwright browser — checks Chromium binary exists. Runs npx playwright install chromium if missing.
  6. 11Labs key — checks ELEVEN_LABS_API_KEY in env or credentials file. Soft warn if missing (VO track disabled until set).
  7. Dev server — at run time (not preflight), checks http://<url> is reachable. Hard fail if not.

On success, writes ~/.demo-factory/.preflight-passed marker. Subsequent runs skip steps 1–5 unless the marker is older than 7 days.

Failure behaviour

Hard fails (preflight exits, run blocked): - Node < 18 - ffmpeg not found

Soft warns (preflight continues, feature degraded): - Git not found → repo doc source disabled - 11Labs key missing → VO track disabled; plugin shows "Set ELEVEN_LABS_API_KEY to enable voiceover" - Heygen key missing → ignored in Phase 1 (export mode needs no key)

Platform-aware install instructions are printed for hard fails:

✗ ffmpeg not found.

  macOS:   brew install ffmpeg
  Ubuntu:  sudo apt install ffmpeg
  Windows: choco install ffmpeg
           — or download from https://ffmpeg.org/download.html

Re-run after installing.

16. Demo Readiness Check

A distinct mode — not a pipeline run. Triggered when the user says: - "Am I demo ready?" - "Is [product] demo ready?" - "Check [product] demo readiness" - /demo <slug> --check

It answers: can we ship a demo video for this product right now, and if not, what's missing?

What it checks

All checks are deterministic (no LLM). Each check is independent.

Check Pass condition Fail consequence
Dev server GET http://<url> returns 200 within 5s Cannot capture — need running server
Capture script Products/<slug>/capture.yaml exists Must record first; no replay available
Brand profile Products/<slug>/brand.yaml exists Will use base profile; first render may need brand review
Vault docs ≥ 1 .md file exists in Projects/<slug>/ Synopsis step will need user to paste docs
Demo config <repo>/demo/config.yaml valid + all fixtures present api_mocks will be skipped; dynamic scenes may not render
Previous runs Any entry in Runs/INDEX.md for this slug No resume available; cold start only
Binary workspace binaries/<slug>/ exists with ≥ 1 run No cached captures; first run will be slower
11Labs key ELEVEN_LABS_API_KEY present in env or credentials VO track unavailable

Note: Demo config check is only run when --repo <path> is provided to the readiness command.

Output format

Demo readiness check — algolia-central
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✅  Dev server          http://localhost:3000 responds (200 OK)
✅  Capture script      Products/algolia-central/capture.yaml  (3 flows recorded)
✅  Brand profile       Products/algolia-central/brand.yaml  (primary: #003DFF)
✅  Vault docs          7 docs found in Projects/algolia-central/
                         Specs/2026-04-10-search-ux.md (high relevance)
                         PRD/2026-03-15-algolia-central-prd.md (high relevance)
                         + 5 more
⚠️  Previous runs       2 runs found (last: 2026-04-25, status: done)
✅  11Labs key          Present

Overall: READY — run /demo algolia-central or describe an angle to start.

Recommended angle (based on your most recent run): search-as-you-type UX
Previous runs:
  2026-04-25  search-as-you-type  42s  9:16  ✅ archived
  2026-04-18  analytics-dashboard  58s  9:16  ✅ archived

Or if not ready:

Demo readiness check — algolia-central
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

❌  Dev server          Cannot reach http://localhost:3000 (connection refused)
                         → Start the dev server first
✅  Capture script      Products/algolia-central/capture.yaml  (3 flows recorded)
⚠️  Brand profile       Not found — will auto-detect on first run
✅  Vault docs          7 docs found
❌  11Labs key          Not set
                         → Set ELEVEN_LABS_API_KEY to enable voiceover track

Overall: NOT READY — 2 blockers.
Fix: start the dev server, then run /demo algolia-central.

What it does NOT do

  • Does not start the pipeline
  • Does not read docs or run LLM synthesis
  • Does not write any state files
  • Does not create a run workspace

It is a pure read-only audit. Safe to run at any time.


17. Token Discipline & Anthropic API Best Practices

Core rule: each LLM call is atomic and scoped

No synthesis call receives conversation history. Each of the 4 LLM points is a fresh, single-turn API request with exactly the context it needs. The pipeline state machine (run.json) maintains continuity between steps — the LLM does not.

Model selection

Match model capability to task complexity. Never default everything to the most powerful model.

Point Skill Model Rationale
LLM-1 synopsis/ claude-sonnet-4-6 Balanced synthesis; Opus overkill for 2–3 para summary
LLM-2 script-writer/ claude-opus-4-7 Most complex: multi-track, timing constraints, narrative arc
LLM-3 capture/ claude-haiku-4-5 Structured extraction only: interaction log → YAML
LLM-4 brand-extractor/ claude-haiku-4-5 Structured extraction only: CSS JSON → brand.yaml

Prompt caching

Use cache_control: { type: "ephemeral" } on stable blocks at each synthesis point. Cache hits reduce cost ~90% and latency ~85% on repeat runs.

Synthesis point What gets cached
LLM-1 System prompt; vault doc text (if unchanged since last run — compare mtime)
LLM-2 System prompt; approved synopsis; brand profile
LLM-3 System prompt
LLM-4 System prompt; base brand profile

Cache hit target: ≥ 80% of input tokens on repeat runs of the same product.

Input token budgets

Hard limits enforced by validate.ts before the API call is made. Never trust the LLM to stay within limits — truncate upstream.

Point Max input tokens Truncation strategy
LLM-1 12,000 Vault gets 8,000; repo gets 3,000; pasted gets 1,000. Each source truncates to its budget before assembly. Section headers always preserved.
LLM-2 8,000 Synopsis ≤ 1,000; capture manifest ≤ 4,000; brand profile ≤ 500; JSON Schema ≤ 2,500.
LLM-3 4,000 Interaction log truncated. Preflight warns at record time if session exceeds ~90s (≈ 3,500 tokens).
LLM-4 2,000 brand-detect.ts outputs only what fits; truncates at scraper level, not LLM level.

Output constraints

All 4 synthesis points request structured JSON. The JSON Schema for the expected output is passed in the prompt. validate.ts runs schema validation on every response.

Point Max output tokens Output format
LLM-1 600 Plain markdown (2–3 paragraphs)
LLM-2 3,000 script.json — validated against types/script.ts JSON Schema
LLM-3 1,500 capture.yaml — validated against types/capture.ts JSON Schema
LLM-4 400 brand.yaml — validated against types/brand.ts JSON Schema

Retry logic: if validation fails, re-invoke with the validation errors appended. Maximum 2 retries. On third failure, hard fail with the raw LLM output and error surfaced to user — no silent loops.

No conversation accumulation

Skills must never append LLM synthesis outputs to the ongoing conversation context and then pass that context to the next synthesis call. The only data passed between pipeline steps is structured artifacts on disk (run.json, synopsis.md, script.json). This is enforced by the architecture: each skill reads from disk, not from conversation state.

Streaming

Synthesis calls (messages.create) are not streamed — they resolve fully before validate.ts runs. Gate presentations (the user-facing output at each gate checkpoint) are streamed from the skill's own narration, separate from the synthesis call.

Extended thinking (LLM-2 only, optional)

LLM-2 (script-writer) may use extended thinking for complex timing-constrained scripts. Budget: 2,000 thinking tokens. Gated behind --thinking flag. Not on by default — adds ~3s latency and cost.

Skill construction

All skills in this plugin (demo-factory/, capture/, synopsis/, script-writer/, brand-extractor/) are created and maintained using the skill-creator:skill-creator superpowers skill. No skill file is hand-written. This ensures consistent structure, description quality, and trigger accuracy across all skills.


18. Testing Strategy

Governed by TestingSOPs. All three layers required before any module is considered done. TDD (test-first) is mandatory.

Test layers per module

Module Unit Integration Contract Layer 4 (E2E)
validate.ts ✅ schema pass/fail, error messages
workspace.ts ✅ slugify, state machine transitions, run ID format ✅ run.json schema via zod
preflight.ts ✅ version checks, env key detection
readiness.ts ✅ file presence checks, server mock
brand-detect.ts ✅ CSS var extraction logic ✅ brand-data.json schema
capture.ts ✅ parseCaptureYaml, buildSceneId ⚠️ skip-real-browser (Playwright) ✅ CaptureManifest schema
engine/BrandContext ✅ ThemeProvider merge, useTheme defaults, token values ✅ ThemeTokens shape
engine/ScreenLayer ✅ opacity/scale interpolation math ✅ props contract
engine/TextOverlayLayer ✅ roleStyles map, translateY math ✅ BeatRole → style mapping
engine/DemoComposition ✅ msToFrames, beat → frame mapping ✅ DemoCompositionProps schema
engine/VOPlayer ✅ renders Audio element
engine/AvatarLayer ✅ opacity fade, position styles
adapters/elevenlabs ✅ estimateVoCost, buildTtsPayload ⚠️ skip-real-api (11Labs) ✅ VOLine, VoiceOutput schemas
adapters/heygen ✅ estimateAvatarCost, buildExportBundle ⚠️ skip-real-api (Heygen) ✅ AvatarLine, manifest shape
adapters/gdrive ✅ buildDestinationPath, resolveCloudStoragePath ⚠️ skip-real-mount (GDrive)
scripts/render.ts ✅ ASPECT_DIMENSIONS, error paths (mocked Remotion) ⚠️ skip-real-render (ffmpeg) ✅ RenderManifest schema ⚠️ skip-real-render
scripts/upload.ts ✅ archiveRun flow (mocked gdrive), run.json mutation ✅ UploadResult schema
cli/demo-factory.ts ✅ parseArgs, flag parsing

Legend: - ✅ Required and implemented (or must be implemented) - ⚠️ skip-real-X — marked with vitest skip or custom marker; run only with real credentials/infrastructure

Validation Risk Surface

Test layer What it proves What it does NOT prove
tsc --noEmit Type safety, correct interfaces, no hallucinated properties Live API behavior, runtime data shape
vitest unit Pure function behavior, state machine transitions, interpolation math Network calls, browser rendering, real render output
vitest integration (adapter layer) API response shape in real network conditions Full pipeline timing, MP4 output quality
Browser / E2E (Layer 4) Full pipeline end-to-end, MP4 produced, correct duration

Remaining risk after all non-skip tests pass: The actual MP4 output quality (frame timing, audio sync, browser screenshot fidelity) is only provable via Layer 4 with real ffmpeg + Playwright. This is the single remaining unverified risk surface after all unit/integration tests pass.

Remotion engine test approach

Remotion hooks (useCurrentFrame, useVideoConfig, spring, interpolate) are mocked via engine/src/__mocks__/remotion.ts. The mock: - useCurrentFrame() returns a configurable frame number (default: 0) - useVideoConfig() returns { fps: 30, width: 1080, height: 1920, durationInFrames: 1800 } - spring() returns a linear 0→1 value based on frame - interpolate() delegates to the actual Remotion interpolate (it's pure math, no runtime dependency) - AbsoluteFill, Img, Audio, Video, Composition render as plain <div> or <span> elements

This allows testing component logic (theme consumption, conditional rendering, prop contracts) without a real Remotion render environment.

Pre-commit gate (all four required)

npx tsc --noEmit           # 0 type errors
npx vitest run             # all non-skip tests pass
npx prettier --check .     # 0 formatting issues
npx eslint .               # 0 lint errors

Layer 4 — deferred to CI

Full pipeline E2E test (capture → render → MP4 file exists and has correct duration) is gated behind DEMO_FACTORY_E2E=true and skipped in local unit runs. Requires: ffmpeg installed, Playwright chromium installed, dev server running. Run only in CI against a controlled test product.

Testing SOPs cross-reference

See TestingSOPs for: - Three-layer test structure (unit / integration / contract) - Test naming convention: test_{what}_{condition}_{expected_result} - Mock strategy (adapter injection, not monkey-patching) - Definition of Done (all six gates before a module is done)