CommunityProgramación y desarrollogithub.com

splitbrain/ndemo

Crea videos de demostración narrados de aplicaciones web mediante grabación de pantalla.

¿Qué es ndemo?

ndemo es una herramienta potente para crear videos de demostración narrados de aplicaciones web, ideal para desarrolladores que necesitan documentar visualmente sus funcionalidades. Permite definir un "playbook" (guión) en YAML que describe cada paso de la demostración, incluyendo narración por TTS (Text-to-Speech) desde OpenAI o ElevenLabs, acciones en el navegador (clics, escritura, esperas) y condiciones de finalización para garantizar la sincronización. Su flujo de trabajo es estructurado: primero se crea el playbook en la carpeta `demo/` del proyecto, luego se abre un navegador controlado por Playwright, se autoria cada segmento (leyendo el árbol de accesibilidad de la página para identificar elementos), se revisa y se itera, y finalmente se renderiza el video MP4 con la narración integrada. Soporta condiciones condicionales en la configuración y en los segmentos, lo que permite que las demostraciones sean repetibles y robustas incluso cuando el estado de la aplicación cambia. Es compatible con Cursor, Codex y otros asistentes que ejecutan scripts locales, y se integra como un skill de Claude Code para facilitar la autoría asistida por IA.

Compatible con✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/splitbrain/ndemo/tree/main

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

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

Documentación

¿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:

ComandoQué hace
${CLAUDE_SKILL_DIR}/ndemo open <playbook>Inicia el demonio del navegador y navega a la aplicación
${CLAUDE_SKILL_DIR}/ndemo closeCierra el demonio del navegador
${CLAUDE_SKILL_DIR}/ndemo resetVuelve a la URL de la aplicación (estado fresco)
${CLAUDE_SKILL_DIR}/ndemo page-stateMuestra el árbol de accesibilidad de la página actual
${CLAUDE_SKILL_DIR}/ndemo page-state --screenshotLo 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> --audioReproduce 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 doctorVerifica 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 acciones
  • parallel — 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 diceQué 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 visibles
  • hidden: "<selector>" — el paso solo se ejecuta si el selector no coincide con elementos visibles
  • url: "<patrón>" — el paso solo se ejecuta si la URL actual coincide (** = cualquier ruta)

Referencia de tipos de acciones

TipoCampos requeridosNotas
clicktarget
typetarget, textdelay opcional en ms entre pulsaciones de teclas (por defecto 80)
hovertarget
scrolltargetDesplaza el elemento a la vista
waitduration (ms)Pausa para el espectador (por defecto 1000ms)
presskeyTecla del teclado, ej. "Enter", "Escape"
selecttarget, optionSelecció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 close y luego open de nuevo. Revisa .ndemo/daemon.log para la salida del demonio del navegador.
  • Elemento no encontrado: Ejecuta ${CLAUDE_SKILL_DIR}/ndemo page-state para 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 --audio o render sin 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 reset para restaurar el estado inicial antes de reproducir/renderizar.

Skills relacionados