Skip to content

Repository files navigation

auto_demo

deterministic flow replay Β· Playwright video capture Β· AI voice narration Β· vertical and split exports Β· animated SVG heroes Β· re-record on every deploy

Demo recorder: one flow file in, deterministic video out

Quick start | Features | Modes | Widget demos | Watch mode | License

No LLM at record time: the flow file drives the browser, not a model.

❀️ Sponsor this project ❀️

CI License Last commit Contributors

GitHub stars Follow on GitHub


auto_demo hero: the CLI records a browser flow and writes a reproducible video

You check a demo flow file into the repo; auto_demo replays it with Playwright and writes a reproducible video, screenshots, and a manifest. AI may author or repair the flow file; it never drives the recording run. Nine subcommands share one flow schema with fourteen step actions, so the same .demo.json that records your hero also feeds narration, vertical cuts, split-screen reviews, and storyboards.

npm run demo:smoke
# β†’ .work/smoke-demo/recording.webm + manifest.json + screenshots/

Why this exists

screencli is the obvious alternative, but it forces hosted GitHub-login auth on first run unless an Anthropic API key is already configured, and either way its AI runs in the cloud, which fails the bar for unattended repo-local recording. Playwright is deterministic and local, with browser control and video capture built in, so the recording run needs no login and no cloud call.

🎬 Features

  • 🎬 Deterministic recording: run replays a checked-in .demo.json with a visible cursor, click pulses, cinematic zooms, and on-video captions, then retimes the webm to real-time playback.
  • πŸ—£οΈ AI narration mux: narrate lays a voiceover onto an existing recording; real ElevenLabs TTS with a key, a deterministic mock tone without one.
  • πŸ’¬ Widget demos: widget compiles 7 shipped scenarios into business landing pages with a live ElevenLabs chat widget, recorded through the widget's real text-mode selectors.
  • πŸ“± Vertical export: vertical re-frames any recording into a 1080x1920 MP4, center-crop or letterbox.
  • πŸͺž Split-screen review: split renders the flow's step list beside the recording for frame-by-frame walkthroughs.
  • πŸ–ΌοΈ Animated SVG heroes: svg emits a self-contained animated SVG under a 200KB ceiling, embeddable in a README without binary asset hosting.
  • πŸ“ Storyboards and scripts: storyboard writes a markdown summary of a recorded run; from-url drafts a 5-step script from a URL and a plain-English goal.
  • πŸ‘€ Change detection: watch --once hashes DOM snapshots and fires a re-run hook only when the UI actually changed.
  • βš™οΈ CI re-recording: a shipped GitHub Actions template re-records the demo on every push to main.
flowchart LR
    A[Flow file .demo.json] --> B[auto_demo run]
    B --> C[Playwright + Chromium]
    C --> D[video + manifest + screenshots]
Loading

πŸš€ Quick start

  1. Install and build:

    git clone https://github.com/wranngle/auto_demo && cd auto_demo
    npm install
    npx playwright install chromium
    npm run build

    The npm package name is ui-demo-runner (see package.json); it is not published to the registry, so install and run it from this repo's source.

  2. Put ffmpeg + ffprobe on PATH (apt install ffmpeg / brew install ffmpeg). The recorder calls them to (a) re-time every webm to real-time playback (Playwright tags recordings at 25 fps while capturing ~75 fps of real frames, so without this step recordings play back 3-5x slow) and (b) power narrate, vertical, split, and svg. If ffmpeg is missing, run still records and writes manifest.json; only the recording retiming is skipped. The narrate, vertical, split, and svg subcommands require ffmpeg/ffprobe and fail without them.

  3. Record your own app. Create a *.demo.json beside it, then run it:

    Example flow file: console-overview.demo.json
    {
      "name": "console-overview",
      "startUrl": "http://127.0.0.1:5177/console/",
      "viewport": {
        "width": 1280,
        "height": 720
      },
      "steps": [
        {
          "action": "waitForText",
          "text": "Pipeline Console"
        },
        {
          "action": "click",
          "selector": "text=Opportunities",
          "label": "Open opportunity review"
        },
        {
          "action": "screenshot",
          "name": "opportunity-review"
        }
      ]
    }
    node dist/cli.js run path/to/console-overview.demo.json \
      --output output/console-overview \
      --speed 1.25

Relative startUrl and goto.url values resolve against the flow file's directory. With --base-url, relative URLs resolve against that local dev server.

Two more run flags worth knowing:

  • --quality 720p | 1080p | 4k sets the capture viewport and re-encodes the recording at the preset's bitrate target during post-process; the manifest's retime block records what was applied (or why the post-process failed).
  • --captions-lang en,es,pt,fr exports one SRT caption track per language beside the recording, cue-timed to the flow's timing.speed.

Run the smoke demo

npm run demo:smoke

The run writes:

  • .work/smoke-demo/recording.webm: the retimed video
  • .work/smoke-demo/manifest.json: per-step snapshot of the whole run
  • .work/smoke-demo/events.jsonl: one structured line per step
  • .work/smoke-demo/screenshots/opportunity-review.png: the flow's screenshot step

πŸ“¦ What one run emits

recording.webm
the retimed Playwright video
manifest.json
per-run snapshot of every step
events.jsonl
one structured NDJSON line per step
screenshots/*.png
a named PNG per screenshot step
*.srt
optional per-language caption tracks
...from one flow file
rerun it after every UI change

Every artifact from one flow file.

🎯 Heroes shot with auto_demo

Repo What it recorded
gtm_ops 11-step pipeline replay into its README hero
orb_forge the live WebGL orb, captured headless
wranngle_com the live landing page, compressed to an animated webp
...any repo with a URL point a .demo.json at it

Three sibling heroes recorded by this CLI.

Named repos are usage examples, not integrations.

Modes

  • goto: navigate to another URL.
  • caption: show a short on-video caption for a timed beat.
  • waitForText: wait until visible text appears.
  • waitForSelector: wait until a selector is visible.
  • focus: move the cursor to a selector and smoothly zoom toward it.
  • resetZoom: return from a focus or zoom beat to full-frame context.
  • click: click a selector with a visible cursor pulse.
  • fill: fill a field.
  • hover: hover a selector.
  • press: press a keyboard key, optionally scoped to a selector.
  • scroll: scroll by pixel delta.
  • pause: wait for a fixed number of milliseconds.
  • zoom: animate a cinematic punch-in to (x, y) at the given scale over durationMs.
  • screenshot: write a PNG artifact.

πŸ—£οΈ Narrate (AI voice mux)

ui-demo-runner narrate muxes a voiceover track onto an existing recording. The default --voice mock synthesizes a deterministic sine tone per line so tests never hit the network. --voice elevenlabs performs real ElevenLabs TTS: it reads ELEVENLABS_API_KEY, synthesizes each line with the voice from --voice-id, and backs off exponentially on 429/5xx before failing. Without a key it falls back to the deterministic mock tone; the JSON result's voice field always reports what actually ran.

node dist/cli.js narrate \
  --script fixtures/short-script.txt \
  --in output/console-overview/recording.webm \
  --out output/console-overview/narrated.mp4 \
  --voice mock

Script format is one line per cue: start_sec | duration_sec | text. Lines beginning with # and blank lines are ignored. The output MP4 carries the original video track plus a single AAC audio track (ffprobe reports both).

πŸ“± Vertical export (9:16)

ui-demo-runner vertical converts an existing recording into a 9:16 (1080x1920) MP4 for phone-native vertical feeds. The default --fit crop center-crops the source frame; --fit pad letterboxes the source onto a black 9:16 canvas instead. The video track is re-encoded to H.264 with +faststart; the audio track is stripped (mux narration on top separately if needed).

node dist/cli.js vertical \
  --in output/console-overview/recording.webm \
  --out output/console-overview/short.mp4 \
  --aspect 9:16 \
  --fit crop

ffprobe on the output reports width=1080,height=1920 (ratio ~0.5625).

πŸ“ Generate a script from a URL (mock LLM)

For ideation, from-url produces a deterministic 5-step script from a URL + plain-English goal. The default client is a deterministic mock backed by a checked-in fixture, so tests and offline runs are stable.

node dist/cli.js from-url https://example.com/billing \
  --goal "show how to add a credit card" \
  --out out/credit-card.script.json \
  --narration-out out/credit-card.narration.txt

Every step in the emitted steps[] carries selector, action, and narration: the contract that downstream run and narrate consume. --narration-out writes the bridge for the narrate half: a start | duration | text script with one cue per step, slot-timed by reading speed, ready for narrate --script (mock tone or ElevenLabs).

πŸ’¬ ElevenLabs widget demos

ui-demo-runner widget compiles a *.scenario.json into a business landing page with an <elevenlabs-convai> chat widget plus the matching .demo.json flow. One scenario, two modes (live block present = real agent; absent = mock), both driven through the real widget's text-mode selectors.

node scripts/provision-agents.mjs        # idempotent: create/reuse 7 demo agents
node scripts/tune-agents.mjs             # PATCH each agent: markdown reply + client tools + branded text-contents
node scripts/record-live-demos.mjs       # record all 7 β†’ output/live-widget/

Seven shipped scenarios under examples/widget/: six vertical demos (restaurant, dental, salon, ecommerce, medspa, home-services) plus a dedicated SaaS wranngle-scheduling scenario that exercises the real Cal.com book_demo webhook end-to-end (the only scenario whose recording creates a real Cal.com entry). Each live block tunes the widget per business: orb gradient, branding.{mainLabel,startCall} β†’ widget text-contents, linkHosts β†’ markdown-link-allowed-hosts, and clientTools[] declares browser-side tools (name, description, params, canned result). The page registers the canned handlers via the elevenlabs-convai:call event; the real agent's LLM decides to call a tool, it runs in-page with no backend, no side effects, returns the canned result, and the agent speaks it as rich markdown (a bold heading + bulleted detail list + a clickable confirmation link). Live recordings are choreographed for motion: a zoom punch anchored at the widget's bottom-right corner held while the reply streams, then a pull-back.

live.workspaceToolIds: string[] attaches existing ElevenLabs workspace tools by id (e.g. the native Cal.com book_demo webhook). These take real actions when invoked: real Cal.com bookings, real SMS. tune-agents.mjs merges them onto the agent's prompt.tool_ids (the API rejects sending inline tools + tool_ids together). Only the wranngle-scheduling scenario ships with the real Cal.com book_demo attached; re-recording it may create a real booking if the conversation reaches that tool. The six vertical scenarios use client-tool mocks exclusively; recording them is side-effect-free.

πŸ–ΌοΈ Animated SVG export (README hero)

ui-demo-runner svg samples frames from an existing MP4 and emits a single self-contained animated SVG suitable for embedding directly in a README: the same surface as docs/hero.gif from the hero block, but as inline markup that renders without binary asset hosting.

node dist/cli.js svg \
  --fixture examples/fixtures/short-clip.mp4 \
  --out out/demo.svg

The output is a single .svg file with base64-embedded JPEG frames driven by SMIL <animate> elements. The renderer enforces a 200KB ceiling so the file stays cheap to ship alongside the README; tune --frames, --width, or --jpeg-quality if the budget is tight.

πŸͺž Split-screen export (flow + recording side-by-side)

ui-demo-runner split renders a 1920x1080 MP4 with the flow's step list on the left and an existing recording on the right: internal review clip for walking a teammate through what each step actually does, frame by frame.

node dist/cli.js split \
  path/to/flow.demo.json \
  output/console-overview/recording.webm \
  --output output/console-overview/split.mp4

The flow panel renders each step as a captioned card; the recording panel plays the source MP4 timed against the same per-step window. Scratch frames land under .ui-demo-runner-split/ (gitignored) and are cleaned up on completion; pass an explicit --work-dir to keep them for inspection.

πŸ“‹ Storyboard (markdown summary of a recorded run)

ui-demo-runner storyboard walks a recorded run's manifest.json and emits a markdown storyboard.md beside it: one row per step with the screenshot artifact thumbnail and the narration text. Useful for PR descriptions, async review, and dropping a flat summary into a doc without re-running the demo.

node dist/cli.js storyboard output/console-overview
# β†’ output/console-overview/storyboard.md

✨ Polish controls

Flow files can opt into the recording style used for portfolio demos:

{
  "timing": {
    "speed": 1.25,
    "moveMs": 180,
    "clickPauseMs": 160,
    "zoomMs": 360
  },
  "polish": {
    "cursor": {
      "style": "modern",
      "accentColor": "#ff5f00"
    },
    "actionRail": {
      "enabled": true
    },
    "captions": {
      "enabled": true,
      "position": "bottom"
    },
    "zoom": {
      "defaultScale": 1.06,
      "durationMs": 360,
      "resetMs": 260
    }
  }
}

Use the action rail for internal review clips and dense walkthroughs where the viewer needs to see the planned sequence. Turn it off for final public exports if it competes with the product UI.

βš™οΈ CI integration: re-record on every deploy

Drop in the shipped GitHub Actions template so every push to main produces a fresh recording.webm + manifest.json as a workflow artifact:

mkdir -p .github/workflows
cp templates/auto-demo-on-deploy.yml.template \
   .github/workflows/ui-demo-runner-on-deploy.yml
git add .github/workflows/ui-demo-runner-on-deploy.yml

Edit the copied workflow to point at your own flows/<name>.demo.json and commit. Artifacts land under the workflow run as ui-demo-runner-<sha>.

πŸ‘€ Watch mode

Re-record only when the UI actually changes. watch --once hashes the previous and next DOM snapshots, emits CHANGE_DETECTED (or NO_CHANGE), and fires a re-run hook exactly once per detected change. --once is the only mode wired today; wire a polling loop on top via your scheduler or a CI scheduled workflow that re-runs this command against your latest DOM snapshot.

node dist/cli.js watch --once \
  --fixture fixtures/old-dom.html \
  --next fixtures/new-dom.html

πŸ—ΊοΈ Roadmap

Surface Status
Recorder core: run, retiming, quality presets, captions Shipped
narrate: ElevenLabs TTS + deterministic mock Shipped
widget: 7 scenarios, live agent + mock Shipped
vertical, split, svg, storyboard exports Shipped
from-url script generation Shipped
watch --once change detection Shipped
0.2.0 tag Cut when the CLI surface stabilizes

⭐ Star history

GitHub stars

View the interactive star history, drawn live even while star-history's image API is down.

License

MIT

Design bias

This tool is intentionally boring at runtime. The valuable part of a demo recording tool is not an agent hallucinating a flow; it is a flow you can rerun after every UI repair pass and trust enough to publish.

About

Deterministic Playwright CLI for UI demos: flow files produce videos, screenshots, manifests, and widget demos.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages