Communitygithub.com

demo-video

Crea un video corto de demostración de producto de warden (o la app de ejemplo) en un simulador real, con una terminal escribiendo al lado del teléfono. Úsalo cuando se solicite un video demo, un clip para documentación o X, una regrabación tras un cambio de UI, o para añadir/editar una definición `*.demo.ts`.

¿Qué es demo-video?

Este skill automatiza la creación de videos de demostración de producto de alta calidad, combinando una grabación de un simulador iOS real con una terminal que escribe comandos reales y sus salidas. Está diseñado para desarrolladores y agentes de IA que necesitan producir clips promocionales cortos (10–25 segundos) para documentación, redes sociales o revisiones de funciones. El skill soporta plataformas como warden, Xcode y herramientas de línea de comandos (ffmpeg, uv), y su flujo de trabajo cubre desde la definición de clips en archivos TypeScript hasta la grabación en un simulador arrendado, la revisión de una hoja de contactos (contact sheet) y el renderizado final. Las reglas de honestidad son estrictas: cada comando mostrado se ejecuta realmente contra la app real, y no se permiten composiciones falsas ni simulaciones. Además, incluye variantes para terminal-heavy (e2e, dev dashboard) y exportación a X con especificaciones técnicas precisas.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace demo-video?

bun run demo <feature>/<clip> graba una toma en un simulador iOS arrendado, luego renderiza un video 1920x1080: título a la izquierda, una terminal que escribe cada comando real de warden e imprime su salida cuando el paso termina, el teléfono a la derecha (redondeado, anillo de 3px). Kit: scripts/demo/ (CLI demo.ts, record.ts, render.py, timeline.ts, demo.types.ts). Definiciones: apps/example/demos/<feature>/<clip>.demo.ts (name debe ser igual a <feature>/<clip>); assets: apps/docs/public/demos/<feature>/<clip>.mp4|jpg. Una carpeta por característica (biometrics, network, location, clock), minúsculas, guiones. Interno: sin changeset.

1. Cuándo hacer uno

  • Una característica cuyo efecto es visible en pantalla y ocurre en segundos (biometría, alertas, gestos). No para logs, números de rendimiento o cualquier cosa que una tabla muestre mejor.
  • Después de que la UI de la app o el comportamiento de un comando cambió: regrabar, nunca editar el mp4.
  • Una historia por clip, 10–25 s, 3–6 pasos con subtítulos.

Requiere ffmpeg, ffprobe, uv (brew install ffmpeg uv), Xcode, y las fuentes en node_modules/@expo-google-fonts (bun install).

2. Escribir una definición

Exporta demo: DemoDefinition (scripts/demo/demo.types.ts). Copia apps/example/demos/biometrics/faceid.demo.ts.

CampoSignificado
title / subtitle / terminalTitle / footertexto estático; title es la afirmación, subtitle una línea
phoneCaptionlínea debajo del teléfono, por defecto simulador real · no es una animación; "" lo oculta
claim{ biometrics?, profile? } -> warden claim ios --json --label demo-<feature>-<clip> --ttl 30m [--biometrics]
appbundleId, kind (simulator por defecto), project (cwd relativo al repositorio), deepLink abierto con --settle, appearance (light/dark, establecido via simctl ui en el udid arrendado, restaurado después)
setupwarden argv ejecutados fuera de cámara después de abrir la app, antes del deep link; ["ui","network","set","online","--relaunch"] da el lanzamiento del dylib de iOS que los pasos de red/reloj necesitan. Una entrada es un argv o { argv, optional: true }: un paso opcional que sale con código distinto de cero se registra (setup opcional warden … -> <code>) en lugar de fallar la toma (un permiso que puede no aparecer)
readyselectores warden ui wait que deben pasar después del deep link y antes de que el video comience (ej. id=bio-screen); un paso nunca presiona una pantalla que no se ha montado
leadInMs / tailHoldMstiempo de inactividad antes del paso 1 / última retención de fotograma, rellenado por el renderizador (la cola de la toma es 1 s)
steps[]argv (después de warden; el grabador añade --udid), output (ok dibuja un check vectorial, plain es silenciado, lines imprime varias líneas silenciadas), pauseAfterMs, shown?, hidden?, quote?
outrolínea # comment escrita después de la última salida

output: { kind: "lines", text: [...] } imprime una línea silenciada por entrada (mantén alrededor de 6; cada una después de la primera cuesta 0.4 de un paso de entrada en el diseño) para código o texto mostrado desde un archivo. Combínalo con quote: { file } (ruta relativa a app.project): después del paso, el grabador verifica que cada línea impresa, recortada, aparece en ese archivo y lanza línea citada no está en <file>: <line> en caso contrario, así una línea citada nunca se inventa. Las líneas aún pasan por maskSecrets.

Los tiempos nunca se escriben a mano: el grabador mide el inicio y fin de cada paso contra el reloj del video y la línea de salida aparece al FINAL del paso.

3. El bucle

bun run demo <feature>/<clip> --record-only      # claim, barra de estado 9:41, lanzar, grabar, liberar por id
bun run demo <feature>/<clip> --render-only --sheet   # construir mp4 + jpg, escribir <work>/sheet.jpg
bun run demo <feature>/<clip>          # ambos

Directorio de trabajo: $TMPDIR/warden-demo/<feature>/<clip> (--out dir): take.mp4, steps.json, plan.json, sheet.jpg; en una toma fallida failure-snapshot.txt (error + ui snapshot -i). Nada de eso se commitea.

  1. Grabar.
  2. Leer sheet.jpg (mosaico 4x4, 1 por segundo, pts quemados). Verificar contra el log: cada línea de salida aparece cuando el teléfono reacciona visiblemente; ningún fotograma muestra una alerta no relacionada, teclado, caja roja o barra de estado incorrecta; el último fotograma muestra el estado final.
  3. Para un análisis más detallado: ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.png y Read.
  4. Arreglar en la definición (pauseAfterMs, eliminar/ocultar un paso, shown) y --render-only; regrabar solo cuando la toma en sí fue incorrecta. Correcciones por lotes: una grabación por ronda.
  5. Committear el mp4 + jpg en apps/docs/public/demos/<feature>/.

4. Reglas de honestidad

  • Cada comando impreso se ejecutó, contra la app real. Sin shown que difiera en significado de argv (solo recorta banderas como --timeout).
  • El texto de salida es lo que el teléfono hizo, no lo que el comando imprimió. Si una pulsación no tiene efecto visible (se perdió durante la animación de entrada de una hoja, nada cambió), no le pongas subtítulo: hidden: true, o arregla el timing con un pauseAfterMs anterior más largo.
  • Las alertas de permisos de primera ejecución solo aparecen en una instalación fresca; el claim reutiliza una compilación caliente, así que no subtitules una alerta a menos que la toma la muestre.
  • Sin bypass del lado de la app ni prompt simulado: la afirmación en el título debe ser verdadera en la toma.
  • Secretos: maskSecrets oculta clave=valor, tokens estilo ghp_/sk- y hex/base64 largos en líneas impresas. Aún así verifica el teléfono por tokens, correos, nombres de dispositivo.
  • Simulador real, UI iOS real. Nunca compongas fotogramas falsos.

5. Trampas (codificadas en el kit)

  • simctl video es VFR y descarta la cola inactiva: el renderizador normaliza fps y rellena el último fotograma (tpad) a la duración del plan.
  • El tiempo del video coincide con el log de comandos dentro de ~0.5 s; el grabador inicia el reloj justo después de video start.
  • Martian Mono no tiene glifo de check: render.py lo dibuja como vectores.
  • El fotograma es la paleta oscura del Device Hub de docs (Mona Sans + Martian Mono); establece app.appearance: "dark" para que el teléfono coincida. El grabador restaura la apariencia del simulador después.
  • Desbordamiento de terminal: computeTerminalLayout reduce paso y fuente a 17 px, luego desplaza las entradas más antiguas.
  • Límite de video del dispositivo 3 min; mantén las tomas bajo 40 s de todas formas.
  • Barra de estado: xcrun simctl status_bar <udid> override está permitido en un udid arrendado; el grabador lo limpia después.

6. Exportación a X

1920x1080, H.264 High, yuv420p, 30 fps, +faststart, sin audio, <= 2:20, archivo pequeño (el kit apunta a <= 2 MB via una escalera CRF 24..36). Verificar:

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 y embebido en docs

Salida: apps/docs/public/demos/<feature>/<clip>.mp4 + <clip>.jpg (poster = último fotograma), servido en /demos/<feature>/<clip>.mp4 (Vite public/). Embebe con el componente MDX DemoClip (apps/docs/src/components/demo-clip.tsx): reproducción automática silenciada y en bucle, reposa en el poster bajo movimiento reducido. src no tiene extensión; label dice lo que muestra el clip (coincide con el subtítulo). Un <video> sin formato falla apps/docs/src/lib/source.test.ts.

<DemoClip src="/demos/<feature>/<clip>" label="warden ui … haciendo … en un simulador arrendado" />

Añade una oración de subtítulo nombrando los comandos. El reel por lotes apps/docs/public/demos/e2e/batch.* (embebido con <DemoReel>) es un pipeline diferente (bun run demo:compose).

8. El reel warden dev (dev/hero)

Un tercer pipeline: el panel de desarrollo REAL de warden (izquierda, en una tarjeta de terminal) junto al simulador que warden dev reclama por sí mismo (derecha). Nada se escribe o falsifica: dev-take.ts lanza warden dev ios --label demo-dev-<clip> --headless --no-shutdown en un PTY (Bun.spawn({ terminal })) reflejado en @xterm/headless (lecturas de fotogramas para las esperas) y escribe cada chunk del PTY en tui.cast; el origen del reloj es el lanzamiento. Sondea warden ls --json en busca de la concesión de dispositivo etiquetado, luego establece oscuro + 9:41, desinstala la app e inicia ui screen video (el teléfono está negro hasta entonces). El proceso hijo de dev obtiene su propio WARDEN_SESSION_ID: warden dev adopta una concesión de dispositivo retenida por la sesión del llamante, y cada agente compañero comparte una, así que sin él la toma podría manejar el simulador de otra persona.

bun run demo:dev dev/hero --record-only        # claim, desinstalar app, 9:41 + oscuro, grabar, liberar por 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                      # ambos
  • Kit: dev-demo.ts (CLI), dev-take.ts (grabador, runBeats), dev-render.ts (agg + compose, DEV_LAYOUT), dev-frame.py (tarjeta, anillo, máscara), dev-timeline.ts (texto cast, segmentos de rampa, subtítulos). Tipos: DevDemoDefinition / DevBeat en demo.types.ts. Definición: apps/example/demos/dev/<clip>.demo.ts; directorio de trabajo $TMPDIR/warden-demo/dev/<clip> (take.mp4, tui.cast, dev.json, sheet.jpg, failure-frame.txt).
  • Un beat = waitScreen (regex sobre el fotograma TUI) + waitPhone (selectores ui wait) -> acción (keys en el TUI, y/o una edit real de un archivo del repositorio) -> selectores afterPhone (el aterrizaje de hot reload) -> pauseAfterMs. dev.json contiene el inicio / listo / fin de cada beat en el reloj del video; los subtítulos van desde la acción de un beat hasta la del siguiente.
  • La edit es una edición real de fuente (welcome.tsx string del cuerpo), revertida byte por byte en finally; verifica que git diff apps/example/src esté vacío después de una toma.
  • uninstallFirst elimina la app del simulador arrendado para que warden dev instale la compilación de desarrollo del caché en cámara. La PRIMERA toma en una huella es una compilación local (minutos, la fila dev build compila): graba una vez para calentar el caché, luego regraba. warden app ensure --kind dev no calienta el caché que warden dev lee.
  • Rampas de velocidad: DevBeat.ramp: N acelera la espera de ese beat en N (manteniendo 1.2 s de cabeza y 1 s de cola a velocidad real) y el reel dibuja una etiqueta × N speed. Una toma caliente que es lenta en la espera del cliente dev la acelera. Las teclas y el hot reload son siempre en tiempo real.
  • Honestidad: los hechos en el subtítulo de <DemoReel> provienen de las filas TUI de la toma (dev build 3.5s, Metro 3.9s, dev client 161ms) y dev.json (edit -> ui wait ve el nuevo texto: 0.76 s). Revisarlos después de cada regrabación; cambian.
  • Límites: el panel no tiene fila de claim separada (está dentro de preparando la compilación dev de ios); --headless para que no se abra ninguna ventana de Device Hub; PTY 100x26 (100 columnas es el mínimo del HUD). Un beat con stopVideoFirst detiene el video antes de su acción (q finaliza la concesión y warden ui luego se niega). Después de q, la toma verifica que warden ls no tenga ninguna concesión de la sesión (falla en caso contrario), y el cast se corta donde el panel sale de la pantalla alternativa para que el reel termine en su último fotograma. La pantalla de carga blanca del launcher dev y el banner azul Refrescando… son la app real; una recarga de Metro también puede venir de otro agente editando apps/example/src.
  • Rampas: ramp solo aplica cuando la espera, menos su cabeza de 1.2 s y cola de 1 s, es al menos 6 s (RAMP_MIN_S). El hero acelera su espera de cliente dev 3x con una etiqueta.
  • devEnv añade entorno al hijo warden dev; el hero establece EXPO_NO_DEPENDENCY_VALIDATION=1 (@expo/cli checkDependenciesOnStart) para eliminar la molestia "actualización disponible" de Expo del panel Metro. Úsalo solo para ruido, nunca comportamiento.
  • Embebe con <DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" /> (hero de aterrizaje, sims/dev.mdx, getting-started/quickstart.mdx). Poster = el fotograma de acción del posterBeat + 1 s (HUD encendido, todas las filas hechas, app ejecutándose).

9. Clips de terminal pesada (VID-6)

Todos comandos reales; cada reel muestra solo lo que se ejecutó. Un clip a la vez, liberar por etiqueta después de cada uno.

ClipPipelineComando
e2e/parallelbatch-take.ts: warden e2e <suite> --count N --record --headless en <work>/rec, estilizando cada simulador arrendado oscuro + 9:41 mientras arranca, luego 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; el encabezado muestra el recuento de specs; el contador lee N/total shards done cuando las specs difieren de los trabajos)
dev/worktreesLanesDemoDefinition: un recordDev por carril concurrentemente, una tira de shell (warden ls) debajobun run demo:lanes dev/worktrees (lanes.ts, lanes-frame.py, lanes-demo.ts; los carriles comparten un reloj via DevTake.startedAtS)
golden/cloneDevDemoDefinition.script: un script de shell se ejecuta en el PTY en lugar de warden devbun run demo:dev golden/clone
debug/failurescript + rawVideobun run demo:dev debug/failure --sheet
  • Modo script: sin desinstalación, sin verificación de liberación; la toma termina con release --mine --label L --shutdown y sims delete <udid> --yes. Un beat con waitBoot sondea simctl io screenshot hasta que el PNG supera los 100 KB (HOME_SCREEN_MIN_BYTES): un simulador headless recién clonado muestra el logo de Apple 10+ s después de que claim retorna. Acelera esa espera (3x, con etiqueta).
  • rawVideo: graba con simctl io recordVideo (SIGINT para detener) porque el helper ui screen video de warden entra en conflicto con warden e2e en un dispositivo.
  • --record solo graba dispositivos presentes al inicio; los que se unen tarde de forma elástica no tienen video. La puerta de capacidad del host establece cuántos caben, así que pide no más simuladores de los que se iniciaron al inicio (máx 2 aquí).
  • Spec desechable para debug/failure se escribe en apps/example/e2e/debug-demo.e2e.ts y se elimina mediante un trap de shell; verifica git status después (una toma matada puede dejarlo).
  • Embebe con <DemoReel src="/demos/<feature>/<clip>" command="…" facts="…" label="…" />; los hechos provienen de la toma, reléelos después de cada regrabación.
  • demo:batch estiliza simuladores con un sondeo (styleWhenBooted: warden ls --json para la etiqueta, simctl list para Booted, luego ui appearance dark + status_bar override 9:41) porque warden e2e no tiene hook por dispositivo; libera --mine --label demo-<feature>-<clip> --shutdown en finally. Usa specs que pasen en un simulador limpio (perf, settings, todos no son fiables); la bandera --height 1080 de compose da el diseño 16:9 (terminal

Skills relacionados