¿Qué hace ndemo?
Crea videos de demostración narrados de aplicaciones web mediante grabación de pantalla.
Configuración
En el primer uso, DEBES construir el kit de herramientas antes de ejecutar cualquier comando.
Verifica si existe ${CLAUDE_SKILL_DIR}/dist/cli.js. Si no existe, ejecuta:
cd ${CLAUDE_SKILL_DIR} && npm install && npm run build
Luego instala el navegador Playwright si es necesario:
cd ${CLAUDE_SKILL_DIR} && npx playwright install chromium
Verifica la configuración:
${CLAUDE_SKILL_DIR}/ndemo doctor
Comandos
Todos los comandos se ejecutan mediante ${CLAUDE_SKILL_DIR}/ndemo:
| Comando | Qué hace |
|---|---|
${CLAUDE_SKILL_DIR}/ndemo open <playbook> | Inicia el demonio del navegador y navega a la aplicación |
${CLAUDE_SKILL_DIR}/ndemo close | Cierra el demonio del navegador |
${CLAUDE_SKILL_DIR}/ndemo reset | Vuelve a la URL de la aplicación (estado fresco) |
${CLAUDE_SKILL_DIR}/ndemo page-state | Muestra el árbol de accesibilidad de la página actual |
${CLAUDE_SKILL_DIR}/ndemo page-state --screenshot | Lo mismo + guarda una captura en .ndemo/screenshot.png |
${CLAUDE_SKILL_DIR}/ndemo play <playbook> | Reproduce todos los segmentos |
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --segment <id> | Reproduce un solo segmento (rebobina primero) |
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --from <id> | Reproduce desde el segmento hasta el final |
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --to <id> | Detiene después de este segmento |
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --audio | Reproduce con narración TTS |
${CLAUDE_SKILL_DIR}/ndemo render <playbook> | Pipeline completo: TTS → reproducción → combinación → mp4 |
${CLAUDE_SKILL_DIR}/ndemo render <playbook> --output <ruta> | Renderiza a un archivo de salida específico |
${CLAUDE_SKILL_DIR}/ndemo doctor | Verifica las dependencias |
Flujo de trabajo
Paso 1 — Crear el playbook
Cada playbook reside en su propio directorio dentro de demo/ en el
proyecto del usuario. El nombre del directorio coincide con el nombre del playbook.
demo/
mi-funcionalidad/
mi-funcionalidad.yaml ← playbook
fixtures/ ← archivos a restaurar durante la configuración
audio/ ← archivos TTS generados (automático)
video-raw/ ← grabación cruda (automático)
demo.mp4 ← salida final (automático)
Antes de escribir el playbook, piensa en el estado que necesita tener la aplicación para que la demostración funcione de manera confiable y repetible:
- ¿El usuario necesita estar autenticado? → agrega pasos de inicio de sesión condicionales
- ¿La demostración modifica archivos/datos que deben restaurarse? → copia
los originales en el directorio
fixtures/del playbook para que la configuración pueda restaurarlos - ¿La demostración depende de contenido específico existente? → créalo en fixtures o mediante comandos shell de configuración
Copia los archivos que se modificarán durante la demostración en el
subdirectorio fixtures/ del directorio del playbook. Los pasos de configuración
los copiarán de vuelta antes de cada ejecución para que la demostración siempre
comience desde un estado limpio.
Crea el directorio y el archivo YAML. El esquema completo del playbook admite estas secciones de nivel superior:
# demo/editar-pagina/editar-pagina.yaml
app:
url: http://localhost:8080/wiki
viewport: # opcional, por defecto 1920x1080
width: 1920
height: 1080
scale: 2 # factor de escala del dispositivo (por defecto 2)
zoom: 1.25 # nivel de zoom CSS (por defecto 1.25)
colorScheme: light # "light" o "dark" (por defecto "light")
setup:
# Restaurar archivos modificados durante la demostración
- run: cp demo/editar-pagina/fixtures/pagina.txt data/paginas/pagina.txt
# Limpiar artefactos de ejecuciones anteriores
- run: rm -f data/cache/*.tmp
# Iniciar sesión si es necesario (condicional — se omite si ya está autenticado)
- type: click
target: { role: link, name: "Iniciar sesión" }
if:
hidden: ".user-info"
- type: type
target: { role: textbox, name: "Usuario" }
text: admin
if:
visible: ".login-form"
- type: type
target: { role: textbox, name: "Contraseña" }
text: password
if:
visible: ".login-form"
- type: click
target: { role: button, name: "Iniciar sesión" }
if:
visible: ".login-form"
done:
visible: ".user-info"
titleCard: # tarjeta de título opcional (se muestra como primer fotograma)
title: "Editando una Página Wiki" # se muestra prominentemente
subtitle: "Un recorrido rápido por el editor" # subtítulo opcional
duration: 3000 # milisegundos de duración (por defecto 3000)
tts: # configuración TTS opcional
provider: openai # "openai" (por defecto) o "elevenlabs"
voice: alloy # nombre de la voz TTS (por defecto "alloy")
speed: 1.0 # multiplicador de velocidad del habla (por defecto 1.0)
recording: # configuración de grabación opcional
outputDir: "." # directorio de salida relativo al playbook (por defecto ".")
fps: 30 # fotogramas por segundo del video (por defecto 30)
segments:
- id: introduccion
narration: "Bienvenido a nuestro wiki. Vamos a editar una página."
intent: "mostrar la página de inicio del wiki"
timing: after # "after" (por defecto) o "parallel"
actions:
- type: wait
duration: 2000
- id: abrir-editor
narration: "Haz clic en el botón de editar para abrir el editor."
intent: "hacer clic en el botón de editar"
actions: []
Sincronización de segmentos controla cuándo se ejecutan las acciones en relación con la narración:
after(por defecto) — la narración se reproduce primero, luego se ejecutan las accionesparallel— las acciones se ejecutan mientras se reproduce la narración
Escribe todos los segmentos con narración e intento primero. Deja las acciones como arreglos vacíos. Usa rutas absolutas al pasar rutas de playbook a los comandos ndemo.
Paso 2 — Abrir el navegador
${CLAUDE_SKILL_DIR}/ndemo open /ruta/absoluta/demo/mi-demo/mi-demo.yaml
Paso 3 — Redactar cada segmento
Para cada segmento con acciones vacías:
a) Lee el estado actual de la página:
${CLAUDE_SKILL_DIR}/ndemo page-state
b) Observa la salida del árbol de accesibilidad. Encuentra los elementos mencionados en el intento del segmento. Escribe acciones en el playbook YAML usando elementos del árbol.
Cómo escribir objetivos — usa la información de la salida page-state:
Si page-state muestra [button "Configuración"]:
target: { role: button, name: "Configuración" }
Si page-state muestra [searchbox "Buscar informes" value=""]:
target: { role: searchbox, name: "Buscar informes" }
Otros campos de objetivo — usa el que mejor identifique el elemento:
target: { selector: "#mi-elemento" } # selector CSS
target: { testId: "btn-enviar" } # atributo data-testid
target: { label: "Correo electrónico" } # aria-label
target: { placeholder: "Buscar..." } # texto placeholder
target: { text: "Haz clic aquí" } # contenido de texto visible
Se requiere al menos un campo de objetivo. Se pueden combinar múltiples campos para reducir la coincidencia.
Consejo: El código fuente de la aplicación web está disponible en el repositorio del proyecto.
Examina el código fuente del componente en busca de atributos data-testid,
nombres de clase o IDs cuando el árbol de accesibilidad no sea suficiente.
Cómo escribir condiciones de finalización:
done:
visible: ".panel-configuracion" # aparece el elemento
hidden: ".spinner-carga" # desaparece el elemento
networkIdle: true # sin solicitudes pendientes
stable: 500 # DOM sin cambios durante 500ms
url: "**/configuracion" # URL cambiada
text: # elemento contiene texto
selector: ".resultados"
has: "Ingresos Q3"
attribute: # elemento tiene atributo
selector: html
name: data-theme
value: dark
timeout: 10000 # anular el tiempo de espera predeterminado (ms)
TODA acción que cambie la página DEBE tener una condición de finalización. Sin una, la siguiente acción podría ejecutarse antes de que la página esté lista.
Agrega acciones wait después de cambios visibles para que el espectador pueda ver
lo que sucedió:
- type: wait
duration: 2000 # 2 segundos
c) Prueba el segmento:
${CLAUDE_SKILL_DIR}/ndemo play /ruta/absoluta/demo/mi-demo/mi-demo.yaml --segment <id>
d) Si falla, ejecuta ${CLAUDE_SKILL_DIR}/ndemo page-state para ver
lo que hay ahora en pantalla, ajusta las acciones y vuelve a intentarlo.
e) Después de que el segmento funcione, vuelve a leer page-state antes de redactar el siguiente segmento — la página ha cambiado.
Paso 4 — Revisión
${CLAUDE_SKILL_DIR}/ndemo play /ruta/absoluta/demo/mi-demo/mi-demo.yaml
Observa la secuencia completa en el navegador. Pregunta al usuario si se ve correcta.
Para revisar con narración TTS (requiere OPENAI_API_KEY y ffplay):
${CLAUDE_SKILL_DIR}/ndemo play /ruta/absoluta/demo/mi-demo/mi-demo.yaml --audio
Esto genera audio TTS para cada segmento (almacenado en caché por hash de contenido), reproduce el audio junto con las acciones del navegador y ajusta la sincronización para que las acciones coincidan con la duración de la narración. Si el texto de la narración cambia, el audio se regenera automáticamente y el archivo antiguo se elimina.
Paso 5 — Iteración
El usuario puede solicitar cambios. Patrones comunes:
| El usuario dice | Qué hacer |
|---|---|
| "Botón incorrecto" | Ejecuta page-state, encuentra el elemento correcto, actualiza el objetivo |
| "Demasiado rápido" / "demasiado lento" | Ajusta las duraciones de espera, reproduce con --audio para verificar la sincronización |
| "Cambiar la narración a ..." | Edita el campo narration |
| "Agregar un paso que muestre X" | Inserta un nuevo segmento, redacta sus acciones |
| "Eliminar ese paso" | Elimina el segmento del YAML |
| "Reproducir desde el segmento X" | ${CLAUDE_SKILL_DIR}/ndemo play --from <id> |
| "Empezar de nuevo" | ${CLAUDE_SKILL_DIR}/ndemo reset y luego reproduce de nuevo |
Después de cada cambio, reproduce los segmentos afectados para verificar.
Paso 6 — Renderizado
Cuando el usuario aprueba:
${CLAUDE_SKILL_DIR}/ndemo render /ruta/absoluta/demo/mi-demo/mi-demo.yaml
Esto produce el mp4 final con narración TTS.
Referencia de pasos de configuración
Los pasos de configuración se ejecutan antes de que comience la demostración (en open, reset, play
y render). Pueden ser comandos shell o acciones del navegador.
Los comandos shell se ejecutan relativos al directorio de trabajo del proyecto
(el directorio desde el que se llamó a ndemo). Todas las rutas en los comandos run
deben ser relativas a la raíz del proyecto.
Comandos shell — usa run para operaciones de archivos:
setup:
- run: cp demo/mi-funcionalidad/fixtures/original.txt data/pagina.txt
- run: rm -f data/cache/*.tmp
- run: ./scripts/reset-db.sh
Acciones del navegador — misma sintaxis que las acciones de segmento:
setup:
- type: click
target: { role: button, name: "Iniciar sesión" }
- type: type
target: { role: textbox, name: "Usuario" }
text: admin
Pasos condicionales — agrega if para omitir cuando no se cumpla la condición:
setup:
# Solo iniciar sesión si no está autenticado
- type: click
target: { role: link, name: "Iniciar sesión" }
if:
hidden: ".user-menu" # omitir si el menú de usuario es visible
# Solo ejecutar en una página específica
- type: click
target: { role: button, name: "Restablecer" }
if:
url: "**/admin/configuracion" # omitir si no está en esta página
Campos de condición:
visible: "<selector>"— el paso solo se ejecuta si el selector coincide con elementos visibleshidden: "<selector>"— el paso solo se ejecuta si el selector no coincide con elementos visiblesurl: "<patrón>"— el paso solo se ejecuta si la URL actual coincide (**= cualquier ruta)
Referencia de tipos de acciones
| Tipo | Campos requeridos | Notas |
|---|---|---|
| click | target | |
| type | target, text | delay opcional en ms entre pulsaciones de teclas (por defecto 80) |
| hover | target | |
| scroll | target | Desplaza el elemento a la vista |
| wait | duration (ms) | Pausa para el espectador (por defecto 1000ms) |
| press | key | Tecla del teclado, ej. "Enter", "Escape" |
| select | target, option | Selección de menú desplegable |
Reglas de ID de segmento
- Minúsculas alfanuméricas con guiones:
abrir-configuracion,alternar-oscuro - Debe ser único dentro del playbook
- Se usa como nombre de archivo para audio/video, así que mantenlos cortos
Solución de problemas
- El navegador no responde:
${CLAUDE_SKILL_DIR}/ndemo closey luegoopende nuevo. Revisa.ndemo/daemon.logpara la salida del demonio del navegador. - Elemento no encontrado: Ejecuta
${CLAUDE_SKILL_DIR}/ndemo page-statepara ver qué elementos están disponibles. Asegúrate de que el elemento no esté dentro de un iframe o sombra DOM. - Error de TTS: Verifica que OPENAI_API_KEY (o la clave API de ElevenLabs) esté configurada.
Si usas
--audioorendersin clave, se omitirá el audio. - El video de salida no se reproduce: Asegúrate de que ffmpeg esté instalado
y que la ruta de salida tenga extensión
.mp4. - El estado de la página es incorrecto: Usa
${CLAUDE_SKILL_DIR}/ndemo resetpara restaurar el estado inicial antes de reproducir/renderizar.