Communitygithub.com

delacournz/demo-video

Erstelle ein kurzes Produkt-Demo-Video von warden (oder der Beispiel-App) auf einem echten Simulator mit einem Terminal, das die Befehle neben dem Telefon eintippt.

Was ist demo-video?

Dieses Skill ermöglicht es, vollautomatisch kurze Produkt-Demo-Videos zu erstellen, die einen iOS-Simulator in Aktion zeigen, wobei ein eingeblendetes Terminal die tatsächlich ausgeführten `warden`-Befehle eintippt und deren Konsolenausgabe anzeigt. Es ist ideal für Entwickler und Produktteams, die schnell UI-Änderungen dokumentieren, Feature-Clips für Marketing oder Docs erstellen oder nach UI-Updates alte Videos neu aufnehmen möchten. Das Skill unterstützt macOS (mit Xcode, ffmpeg und UV) und wird über die Kommandozeile `bun run demo <feature>/<clip>` gesteuert. Der Workflow umfasst das Aufnehmen auf einem geliehenen iOS-Simulator, die automatische Überprüfung des Kontaktblatts, das Rendern des finalen 1920×1080-Videos mit 30 fps sowie die Einhaltung von „Honesty Rules“, die sicherstellen, dass jeder gezeigte CLI-Befehl und jede Reaktion auf dem Bildschirm echt ist. Es gibt auch spezielle Pipelines für das `warden dev`-Dashboard und terminallastige Clips (z. B. E2E-Tests oder Dev-Worktrees).

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/delacournz/warden/tree/HEAD/.claude/skills/demo-video

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Was macht demo-video?

bun run demo <feature>/<clip> zeichnet auf einem gemieteten iOS-Simulator auf und rendert dann ein 1920×1080-Video: Titel links, ein Terminal, das jeden echten warden-Befehl eintippt und seine Ausgabe am Ende des Schritts anzeigt, das Telefon rechts (abgerundet, 3px Ring). Kit: scripts/demo/ (demo.ts CLI, record.ts, render.py, timeline.ts, demo.types.ts). Definitionen: apps/example/demos/<feature>/<clip>.demo.ts (name muss gleich <feature>/<clip> sein); Assets: apps/docs/public/demos/<feature>/<clip>.mp4|jpg. Ein Ordner pro Feature (biometrics, network, location, clock), Kleinbuchstaben, Bindestriche. Intern: kein Changeset.

1. Wann erstellt man eines

  • Ein Feature, dessen Effekt auf dem Bildschirm sichtbar ist und in Sekunden eintritt (Biometrie, Alarme, Gesten). Nicht für Logs, Performance-Zahlen oder alles, was eine Tabelle besser zeigt.
  • Nach einer Änderung der App-UI oder des Verhaltens eines Befehls: Neu aufnehmen, niemals das mp4 bearbeiten.
  • Eine Geschichte pro Clip, 10–25 s, 3–6 Schritte mit Bildunterschriften.

Benötigt ffmpeg, ffprobe, uv (brew install ffmpeg uv), Xcode und die Schriftarten in node_modules/@expo-google-fonts (bun install).

2. Definition schreiben

Exportiere demo: DemoDefinition (scripts/demo/demo.types.ts). Kopiere apps/example/demos/biometrics/faceid.demo.ts.

FeldBedeutung
title / subtitle / terminalTitle / footerStatischer Text; title ist die Behauptung, subtitle eine Zeile
phoneCaptionZeile unter dem Telefon, Standard echter Simulator · keine Animation; "" blendet sie aus
claim{ biometrics?, profile? } -> warden claim ios --json --label demo-<feature>-<clip> --ttl 30m [--biometrics]
appbundleId, kind (Standard simulator), project (repo-relatives cwd), deepLink geöffnet mit --settle, appearance (light/dark, gesetzt über simctl ui auf der gemieteten udid, danach wiederhergestellt)
setupwarden-argv, die nach dem Öffnen der App und vor dem Deep Link außerhalb des Bildschirms ausgeführt werden; ["ui","network","set","online","--relaunch"] gibt dem iOS-dylib das Launch-Netzwerk / Uhrenschritte, die benötigt werden. Ein Eintrag ist ein argv oder { argv, optional: true }: Ein optionaler Schritt, der mit Nicht-Null-Exitcode beendet wird, wird protokolliert (optionales setup warden … -> <code>) anstatt den Take fehlschlagen zu lassen (eine Berechtigungsabfrage, die möglicherweise nicht erscheint)
readywarden ui wait-Selektoren, die nach dem Deep Link und vor dem Start des Videos bestehen müssen (z. B. id=bio-screen); ein Schritt drückt niemals auf einen Bildschirm, der nicht gemountet ist
leadInMs / tailHoldMsLeerlauf vor Schritt 1 / letztes Frame halten, vom Renderer aufgefüllt (der Take-Schwanz ist 1 s)
steps[]argv (nach warden; der Recorder hängt --udid an), output (ok zeichnet einen Vektor-Haken, plain ist stumm, lines gibt mehrere stumme Zeilen aus), pauseAfterMs, shown?, hidden?, quote?
outro# comment-Zeile, die nach der letzten Ausgabe eingetippt wird

output: { kind: "lines", text: [...] } gibt eine stumme Zeile pro Eintrag aus (maximal etwa 6; jeder weitere kostet 0,4 eines Eintragsabstands im Layout) für Code oder Text, der aus einer Datei angezeigt wird. Kombiniere es mit quote: { file } (Pfad relativ zu app.project): Nach dem Schritt prüft der Recorder, ob jede gedruckte Zeile (getrimmt) in dieser Datei vorkommt, und wirft zitierte Zeile nicht in <file>: <line>, andernfalls wird nie eine Zeile erfunden. Zeilen durchlaufen dennoch maskSecrets.

Zeiten werden niemals von Hand geschrieben: Der Recorder misst Start und Ende jedes Schritts anhand der Video-Clock und die Ausgabezeile erscheint am ENDE des Schritts.

3. Der Kreislauf

bun run demo <feature>/<clip> --record-only      # Claim, 9:41 Statusleiste, Start, Aufnahme, Freigabe nach ID
bun run demo <feature>/<clip> --render-only --sheet   # mp4 + jpg erstellen, <work>/sheet.jpg schreiben
bun run demo <feature>/<clip>          # beides

Arbeitsverzeichnis: $TMPDIR/warden-demo/<feature>/<clip> (--out dir): take.mp4, steps.json, plan.json, sheet.jpg; bei einem fehlgeschlagenen Take: failure-snapshot.txt (Fehler + ui snapshot -i). Nichts davon wird committet.

  1. Aufnehmen.
  2. sheet.jpg lesen (4×4 Kacheln, 1 pro Sekunde, pts eingebrannt). Gegen das Log prüfen: Jede Ausgabezeile erscheint, wenn das Telefon sichtbar reagiert; kein Frame zeigt einen unzusammenhängenden Alarm, eine Tastatur, eine rote Box oder eine falsche Statusleiste; das letzte Frame zeigt den Endzustand.
  3. Für eine genauere Ansicht: ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.png und lesen.
  4. In der Definition korrigieren (pauseAfterMs, Schritt löschen/ausblenden, shown) und --render-only; nur neu aufnehmen, wenn der Take selbst falsch war. Stapelkorrekturen: eine Aufnahme pro Runde.
  5. Das mp4 + jpg in apps/docs/public/demos/<feature>/ committen.

4. Ehrlichkeitsregeln

  • Jeder gedruckte Befehl wurde ausgeführt, gegen die echte App. Kein shown, das sich in der Bedeutung von argv unterscheidet (es kürzt nur Flags wie --timeout).
  • Der Ausgabetext ist das, was das Telefon getan hat, nicht das, was der Befehl gedruckt hat. Wenn ein Tastendruck keine sichtbare Wirkung hat (während einer Blatteingabe-Animation fallen gelassen, nichts geändert), beschrifte ihn nicht: hidden: true, oder korrigiere das Timing mit einem längeren vorherigen pauseAfterMs.
  • Berechtigungsalarme beim ersten Start erscheinen nur bei einer Neuinstallation; der Claim verwendet einen warmen Build, also beschrifte keinen Alarm, es sei denn, der Take zeigt ihn.
  • Keine app-seitige Umgehung oder vorgetäuschte Eingabeaufforderung: Die Behauptung im Titel muss im Take wahr sein.
  • Geheimnisse: maskSecrets verbirgt key=value, ghp_/sk--ähnliche Token und lange Hex/Base64 in gedruckten Zeilen. Überprüfe dennoch das Telefon auf Token, E-Mails, Gerätenamen.
  • Echter Simulator, echte iOS-Oberfläche. Niemals gefälschte Frames zusammensetzen.

5. Fallstricke (im Kit kodiert)

  • simctl video ist VFR und lässt den Leerlauf-Schwanz weg: Der Renderer normalisiert die fps und füllt das letzte Frame (tpad) auf die Planlänge auf.
  • Die Videozeit stimmt mit dem Befehlslog auf ~0,5 s genau überein; der Recorder startet die Uhr direkt nach video start.
  • Martian Mono hat kein Häkchen-Glyph: render.py zeichnet es als Vektoren.
  • Der Rahmen ist die dunkle Palette des Device Hub der Docs (Mona Sans + Martian Mono); setze app.appearance: "dark", damit das Telefon dazu passt. Der Recorder stellt das Erscheinungsbild des Simulators danach wieder her.
  • Terminalüberlauf: computeTerminalLayout verkleinert Zeichenabstand und Schrift auf 17 px und scrollt dann die ältesten Einträge weg.
  • Geräte-Videolimit 3 min; halte Takes dennoch unter 40 s.
  • Statusleiste: xcrun simctl status_bar <udid> override ist auf einer gemieteten udid erlaubt; der Recorder löscht es danach.

6. X-Export

1920×1080, H.264 High, yuv420p, 30 fps, +faststart, kein Audio, <= 2:20, kleine Datei (Kit zielt auf <= 2 MB über eine CRF-Leiter 24..36). Prüfen:

ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<feature>/<clip>.mp4

7. Assets und Docs-Einbettung

Ausgabe: apps/docs/public/demos/<feature>/<clip>.mp4 + <clip>.jpg (Poster = letztes Frame), bereitgestellt unter /demos/<feature>/<clip>.mp4 (Vite public/). Einbettung mit der DemoClip MDX-Komponente (apps/docs/src/components/demo-clip.tsx): Autoplay stumm und Schleife, ruht auf dem Poster bei reduzierter Bewegung. src hat keine Erweiterung; label sagt, was der Clip zeigt (entspricht der Bildunterschrift). Ein rohes <video> besteht apps/docs/src/lib/source.test.ts nicht.

<DemoClip src="/demos/<feature>/<clip>" label="warden ui … führt … auf einem gemieteten Simulator aus" />

Füge einen Bildunterschrifts-Satz hinzu, der die Befehle nennt. Die Batch-Spule apps/docs/public/demos/e2e/batch.* (eingebettet mit <DemoReel>) ist eine andere Pipeline (bun run demo:compose).

8. Die warden dev-Spule (dev/hero)

Eine dritte Pipeline: Wardens ECHTES Dev-Dashboard (links, in einer Terminalkarte) neben dem Simulator, den warden dev selbst claimt (rechts). Nichts wird getippt oder gefälscht: dev-take.ts erzeugt warden dev ios --label demo-dev-<clip> --headless --no-shutdown in einem PTY (Bun.spawn({ terminal })) gespiegelt in @xterm/headless (Frame-Liest für die Warteschritte) und schreibt jeden PTY-Chunk in tui.cast; der Uhrursprung ist der Spawn. Es pollt warden ls --json nach dem markierten Geräte-Lease, setzt dann dunkel + 9:41, deinstalliert die App und startet ui screen video (das Telefon ist bis dahin schwarz). Das Dev-Kind erhält seine eigene WARDEN_SESSION_ID: warden dev übernimmt einen Geräte-Lease, der von der Sitzung des Aufrufers gehalten wird, und jeder Teammitglied-Agent teilt sich einen, also könnte der Take sonst den Simulator eines anderen steuern.

bun run demo:dev dev/hero --record-only        # Claim, App deinstallieren, 9:41 + dunkel, aufnehmen, Freigabe nach ID
bun run demo:dev dev/hero --render-only --sheet # agg + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg}, <work>/sheet.jpg
bun run demo:dev dev/hero                      # beides
  • Kit: dev-demo.ts (CLI), dev-take.ts (Recorder, runBeats), dev-render.ts (agg + compose, DEV_LAYOUT), dev-frame.py (Karte, Ring, Maske), dev-timeline.ts (Cast-Text, Rampensegmente, Bildunterschriften). Typen: DevDemoDefinition / DevBeat in demo.types.ts. Definition: apps/example/demos/dev/<clip>.demo.ts; Arbeitsverzeichnis $TMPDIR/warden-demo/dev/<clip> (take.mp4, tui.cast, dev.json, sheet.jpg, failure-frame.txt).
  • Ein Beat = waitScreen (Regex über den TUI-Frame) + waitPhone (ui wait-Selektoren) -> Aktion (keys in die TUI und/oder eine echte edit einer Repo-Datei) -> afterPhone-Selektoren (das Hot-Reload-Landing) -> pauseAfterMs. dev.json hält Start / Bereit / Ende jedes Beats auf der Video-Clock; Bildunterschriften laufen von der Aktion eines Beats bis zur nächsten.
  • Die Bearbeitung ist eine echte Quellcode-Bearbeitung (welcome.tsx-Textkörper), die Byte für Byte in finally rückgängig gemacht wird; überprüfe git diff apps/example/src nach einem Take: Es muss leer sein.
  • uninstallFirst entfernt die App vom gemieteten Simulator, damit warden dev den Dev-Build aus dem Cache auf der Kamera installiert. Der ERSTE Take auf einem Fingerabdruck ist ein lokaler Build (Minuten, dev build-Reihe kompiliert): Einmal aufnehmen, um den Cache zu wärmen, dann neu aufnehmen. warden app ensure --kind dev wärmt nicht den Cache, den warden dev liest.
  • Geschwindigkeitsrampen: DevBeat.ramp: N beschleunigt das Warten dieses Beats um N (wobei 1,2 s Kopf und 1 s Schwanz in Echtgeschwindigkeit bleiben) und die Spule zeichnet ein × N speed-Tag. Ein warmer Take, der im Dev-Client-Warten langsam ist, rampelt ihn. Tastenanschläge und der Hot Reload sind immer in Echtzeit.
  • Ehrlichkeit: Fakten in der <DemoReel> Bildunterschrift stammen aus den TUI-Zeilen des Takes (dev build 3.5s, Metro 3.9s, dev client 161ms) und dev.json (Bearbeitung -> ui wait sieht den neuen Text: 0.76 s). Lies sie nach jeder Neuaufnahme erneut; sie ändern sich.
  • Grenzen: Das Dashboard hat keine separate Claim-Zeile (es ist innerhalb von preparing the ios dev build); --headless damit kein Device Hub-Fenster geöffnet wird; 100×26 PTY (100 Spalten ist das Minimum des HUD). Ein Beat mit stopVideoFirst stoppt das Video vor seiner Aktion (q beendet den Lease und warden ui weigert sich dann). Nach q überprüft der Take, dass warden ls keinen Lease der Sitzung hält (schlägt andernfalls fehl), und der Cast wird dort abgeschnitten, wo das Dashboard den Alternativbildschirm verlässt, sodass die Spule auf ihrem letzten Frame endet. Der weiße Ladebildschirm des Dev-Launchers und das blaue Refreshing…-Banner sind die echte App; ein Metro-Neuladen kann auch von einem anderen Agenten stammen, der apps/example/src bearbeitet.
  • Rampen: ramp gilt nur, wenn das Warten abzüglich 1,2 s Kopf und 1 s Schwanz mindestens 6 s (RAMP_MIN_S) beträgt. Der Hero rampelt sein Dev-Client-Warten 3x mit einem Tag.
  • devEnv fügt Umgebungsvariablen zum warden dev-Kind hinzu; der Hero setzt EXPO_NO_DEPENDENCY_VALIDATION=1 (@expo/cli checkDependenciesOnStart), um Expo's "Update verfügbar"-Nervigkeit aus dem Metro-Bereich zu entfernen. Verwende es nur für Rauschen, niemals für Verhalten.
  • Einbettung mit <DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" /> (Landing-Hero, sims/dev.mdx, getting-started/quickstart.mdx). Poster = das Aktionsframe von posterBeat + 1 s (HUD eingeschaltet, alle Zeilen erledigt, App läuft).

9. Terminallastige Clips (VID-6)

Alle echten Befehle; jede Spule zeigt nur, was ausgeführt wurde. Ein Clip nach dem anderen, Freigabe nach Label nach jedem.

ClipPipelineBefehl
e2e/parallelbatch-take.ts: warden e2e <suite> --count N --record --headless in <work>/rec, Styling jedes gemieteten Simulators dunkel + 9:41 beim Booten, dann compose.ts --height 1080bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2 (--record-only / --compose-only; Header zeigt Spec-Anzahl; Zähler liest N/total erledigte Shards, wenn sich Specs von Jobs unterscheiden)
dev/worktreesLanesDemoDefinition: ein recordDev pro Lane gleichzeitig, ein Shell-Streifen (warden ls) darunterbun run demo:lanes dev/worktrees (lanes.ts, lanes-frame.py, lanes-demo.ts; Lanes teilen eine Clock über DevTake.startedAtS)
golden/cloneDevDemoDefinition.script: Ein Shell-Skript läuft im PTY anstelle von warden devbun run demo:dev golden/clone
debug/failurescript + rawVideobun run demo:dev debug/failure --sheet
  • script-Modus: Keine Deinstallation, keine Freigabeprüfung; der Take endet mit release --mine --label L --shutdown und sims delete <udid> --yes. Ein Beat mit waitBoot pollt simctl io screenshot, bis das PNG 100 KB überschreitet (HOME_SCREEN_MIN_BYTES): Ein frisch geklonter Headless-Simulator zeigt das Apple-Logo 10+ s nach der Rückkehr von claim. Rample dieses Warten (3x, mit Tag).
  • rawVideo: Zeichnet mit simctl io recordVideo auf (SIGINT zum Stoppen), weil der ui screen video-Helfer von warden mit warden e2e auf einem Gerät in Konflikt gerät.
  • --record zeichnet nur Geräte auf, die zu Beginn vorhanden sind; elastische späte Beitreter haben kein Video. Das Host-Kapazitäts-Gate bestimmt, wie viele passen, also fordere nicht mehr Sims an, als zu Beginn gebootet sind (hier max 2).
  • Wegwerf-Spec für debug/failure wird in apps/example/e2e/debug-demo.e2e.ts geschrieben und durch eine Shell-Falle entfernt; überprüfe git status danach (ein abgebrochener Take kann sie hinterlassen).
  • Einbettung mit <DemoReel src="/demos/<feature>/<clip>" command="…" facts="…" label="…" />; Fakten stammen aus dem Take, lies sie nach jeder Neuaufnahme erneut.
  • demo:batch stylt Sims mit einem Poller (styleWhenBooted: warden ls --json für das Label, simctl list für Booted, dann ui appearance dark + status_bar override 9:41), weil warden e2e keinen Per-Geräte-Hook hat; gibt --mine --label demo-<feature>-<clip> --shutdown in finally frei. Verwende Specs, die auf einem sauberen Simulator bestehen (perf, settings, todos bestehen nicht zuverlässig); das compose --height 1080-Flag gibt das 16:9-Layout (Terminal

Verwandte Skills