Community编程与开发github.com

splitbrain/ndemo

创建带旁白的网页应用屏幕录制演示视频。

ndemo 是什么?

ndemo 是一款 AI 驱动的技能工具,专门用于创建带旁白的网页应用屏幕录制演示视频。它通过 Playbook(剧本)机制,让 AI 助手(如 Claude)能够自动规划并执行一系列浏览器操作步骤,同步生成语音旁白(TTS),最终合成 MP4 视频文件。该工具支持复杂的设置步骤(如登录、文件恢复)、条件执行、以及通过无障碍树精确定位页面元素。它兼容所有 CLI 环境,尤其适合与 Claude Code 配合使用,帮助开发者快速制作高质量的软件功能演示。

兼容平台✓Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/splitbrain/ndemo/tree/main

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

ndemo 是做什么的?

创建带旁白的网页应用屏幕录制演示视频。

设置

首次使用时,必须先构建工具包再运行任何命令。 检查 ${CLAUDE_SKILL_DIR}/dist/cli.js 是否存在。如果不存在,请运行:

cd ${CLAUDE_SKILL_DIR} && npm install && npm run build

然后根据需要安装 Playwright 浏览器:

cd ${CLAUDE_SKILL_DIR} && npx playwright install chromium

验证设置:

${CLAUDE_SKILL_DIR}/ndemo doctor

命令

所有命令均通过 ${CLAUDE_SKILL_DIR}/ndemo 运行:

命令作用
${CLAUDE_SKILL_DIR}/ndemo open <playbook>启动浏览器守护进程,导航到应用
${CLAUDE_SKILL_DIR}/ndemo close关闭浏览器守护进程
${CLAUDE_SKILL_DIR}/ndemo reset导航回应用 URL(全新状态)
${CLAUDE_SKILL_DIR}/ndemo page-state打印当前页面的无障碍树
${CLAUDE_SKILL_DIR}/ndemo page-state --screenshot同上 + 保存截图至 .ndemo/screenshot.png
${CLAUDE_SKILL_DIR}/ndemo play <playbook>播放所有片段
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --segment <id>播放单个片段(先回退)
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --from <id>从指定片段播放至结尾
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --to <id>在该片段后停止
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --audio播放时附带 TTS 旁白音频
${CLAUDE_SKILL_DIR}/ndemo render <playbook>完整流水线:TTS → 回放 → 合并 → mp4
${CLAUDE_SKILL_DIR}/ndemo render <playbook> --output <path>渲染到指定的输出文件
${CLAUDE_SKILL_DIR}/ndemo doctor检查依赖关系

工作流程

第 1 步 — 创建 Playbook

每个 Playbook 位于用户项目的 demo/ 目录下的独立子目录中。子目录名称与 playbook 名称一致。

demo/
  my-feature/
    my-feature.yaml    ← playbook
    fixtures/          ← 设置时要恢复的文件
    audio/             ← 生成的 TTS 文件(自动)
    video-raw/         ← 原始录制(自动)
    demo.mp4           ← 最终输出(自动)

在编写 playbook 之前,请思考应用需要处于什么状态才能使演示可靠且可重复:

  • 用户是否需要登录?→ 添加条件登录步骤
  • 演示是否会修改需要恢复的文件/数据?→ 将原始文件复制到 playbook 的 fixtures/ 目录,以便设置时恢复
  • 演示是否依赖于特定内容的存在?→ 在 fixtures 中创建或通过设置 Shell 命令创建

将演示期间会被修改的任何文件 复制到 playbook 目录的 fixtures/ 子目录中。设置步骤会在每次运行前将它们复制回去,确保演示始终从干净状态开始。

创建目录和 YAML 文件。完整的 playbook 模式支持以下顶级部分:

# demo/edit-page/edit-page.yaml
app:
  url: http://localhost:8080/wiki
  viewport:                          # 可选,默认 1920x1080
    width: 1920
    height: 1080
  scale: 2                           # 设备缩放因子(默认 2)
  zoom: 1.25                         # CSS 缩放级别(默认 1.25)
  colorScheme: light                 # "light" 或 "dark"(默认 "light")
  setup:
    # 恢复演示期间修改的文件
    - run: cp demo/edit-page/fixtures/page.txt data/pages/page.txt
    # 清理之前运行产生的工件
    - run: rm -f data/cache/*.tmp
    # 如果需要则登录(条件 — 如果已登录则跳过)
    - type: click
      target: { role: link, name: "Login" }
      if:
        hidden: ".user-info"
    - type: type
      target: { role: textbox, name: "Username" }
      text: admin
      if:
        visible: ".login-form"
    - type: type
      target: { role: textbox, name: "Password" }
      text: password
      if:
        visible: ".login-form"
    - type: click
      target: { role: button, name: "Sign in" }
      if:
        visible: ".login-form"
      done:
        visible: ".user-info"

titleCard:                               # 可选的标题卡片(作为第一帧显示)
  title: "Editing a Wiki Page"           # 突出显示
  subtitle: "A quick tour of the editor" # 可选的副标题
  duration: 3000                         # 保持显示的毫秒数(默认 3000)

tts:                                   # 可选的 TTS 配置
  provider: openai                     # "openai"(默认)或 "elevenlabs"
  voice: alloy                         # TTS 语音名称(默认 "alloy")
  speed: 1.0                           # 语速倍数(默认 1.0)

recording:                             # 可选的录制设置
  outputDir: "."                       # 相对于 playbook 的输出目录(默认 ".")
  fps: 30                              # 视频帧率(默认 30)

segments:
  - id: intro
    narration: "Welcome to our wiki. Let's edit a page."
    intent: "show the wiki start page"
    timing: after                      # "after"(默认)或 "parallel"
    actions:
      - type: wait
        duration: 2000

  - id: open-editor
    narration: "Click the edit button to open the editor."
    intent: "click the edit button"
    actions: []

片段时序 控制操作相对于旁白的执行时机:

  • after(默认)— 先播放旁白,然后执行操作
  • parallel — 旁白播放的同时执行操作

首先为所有片段编写旁白和意图。将操作留作空数组。将 Playbook 路径传递给 ndemo 命令时使用绝对路径。

第 2 步 — 打开浏览器

${CLAUDE_SKILL_DIR}/ndemo open /absolute/path/to/demo/my-demo/my-demo.yaml

第 3 步 — 编写每个片段

对于操作为空的每个片段:

a) 读取当前页面状态:

${CLAUDE_SKILL_DIR}/ndemo page-state

b) 查看无障碍树输出。找到片段意图中引用的元素。使用树中的信息将操作写入 playbook YAML。

如何编写目标 — 使用 page-state 输出的信息:

如果 page-state 显示 [button "Settings"]:

target: { role: button, name: "Settings" }

如果 page-state 显示 [searchbox "Search reports" value=""]:

target: { role: searchbox, name: "Search reports" }

其他目标字段 — 使用最能标识元素的字段:

target: { selector: "#my-element" }        # CSS 选择器
target: { testId: "submit-btn" }           # data-testid 属性
target: { label: "Email" }                 # aria-label
target: { placeholder: "Search..." }       # 占位符文本
target: { text: "Click me" }              # 可见文本内容

至少需要一个目标字段。可以组合多个字段以缩小匹配范围。

提示: 网页应用的源代码在项目仓库中可用。当无障碍树不够用时,请查看组件源码以获取 data-testid 属性、类名或 ID。

如何编写完成条件:

done:
  visible: ".settings-panel"        # 元素出现
  hidden: ".loading-spinner"        # 元素消失
  networkIdle: true                 # 没有待处理的请求
  stable: 500                       # DOM 500 毫秒内未改变
  url: "**/settings"                # URL 已更改
  text:                             # 元素包含文本
    selector: ".results"
    has: "Q3 Revenue"
  attribute:                        # 元素具有属性
    selector: html
    name: data-theme
    value: dark
  timeout: 10000                    # 覆盖默认超时时间(毫秒)

每个更改页面的操作都必须有完成条件。 如果没有,下一个操作可能会在页面准备好之前执行。

在可见更改后添加 wait 操作,以便观众能够看到发生了什么:

- type: wait
  duration: 2000    # 2 秒

c) 测试片段:

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml --segment <id>

d) 如果失败,运行 ${CLAUDE_SKILL_DIR}/ndemo page-state 查看当前屏幕内容,调整操作,然后重试。

e) 片段工作正常后,在编写下一个片段之前再次读取 page-state — 页面已经发生了变化。

第 4 步 — 审查

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml

在浏览器中观看完整序列。询问用户是否看起来正确。

要附带 TTS 旁白进行审查(需要 OPENAI_API_KEY 和 ffplay):

${CLAUDE_SKILL_DIR}/ndemo play /absolute/path/to/demo/my-demo/my-demo.yaml --audio

这会为每个片段生成 TTS 音频(按内容哈希缓存),将音频与浏览器操作一起播放,并调整时序使操作与旁白时长匹配。如果旁白文本发生更改,音频会自动重新生成,旧文件会被删除。

第 5 步 — 迭代

用户可能会要求更改。常见模式:

用户说该怎么做
"点了错误的按钮"运行 page-state,找到正确的元素,更新目标
"太快" / "太慢"调整等待时长,使用 --audio 重放以检查时序
"将旁白改为..."编辑旁文字段
"添加一个展示 X 的步骤"插入新片段,编写其操作
"删除那个步骤"从 YAML 中删除该片段
"从片段 X 开始重播"${CLAUDE_SKILL_DIR}/ndemo play --from <id>
"重新开始"${CLAUDE_SKILL_DIR}/ndemo reset 然后重播

每次更改后,重新播放受影响的片段以验证。

第 6 步 — 渲染

当用户批准后:

${CLAUDE_SKILL_DIR}/ndemo render /absolute/path/to/demo/my-demo/my-demo.yaml

这将生成带有 TTS 旁白的最终 mp4 文件。

设置步骤参考

设置步骤在演示开始前运行(在 open、reset、play 和 render 时)。它们可以是 Shell 命令或浏览器操作。

Shell 命令相对于项目工作目录执行(即调用 ndemo 的目录)。run 命令中的所有路径都应相对于项目根目录。

Shell 命令 — 对文件操作使用 run:

setup:
  - run: cp demo/my-feature/fixtures/original.txt data/page.txt
  - run: rm -f data/cache/*.tmp
  - run: ./scripts/reset-db.sh

浏览器操作 — 与片段操作语法相同:

setup:
  - type: click
    target: { role: button, name: "Login" }
  - type: type
    target: { role: textbox, name: "Username" }
    text: admin

条件步骤 — 添加 if 以在不满足条件时跳过:

setup:
  # 仅在未登录时登录
  - type: click
    target: { role: link, name: "Sign in" }
    if:
      hidden: ".user-menu"      # 如果用户菜单可见则跳过
  # 仅在特定页面运行
  - type: click
    target: { role: button, name: "Reset" }
    if:
      url: "**/admin/settings"  # 如果不在该页面则跳过

条件字段:

  • visible: "<selector>" — 仅当选择器匹配可见元素时运行步骤
  • hidden: "<selector>" — 仅当选择器不匹配任何可见元素时运行步骤
  • url: "<pattern>" — 仅当当前 URL 匹配时运行步骤(** 匹配任何路径)

操作类型参考

类型必填字段注释
clicktarget
typetarget, text可选的 delay(毫秒)按键间隔(默认 80)
hovertarget
scrolltarget将元素滚动到视图中
waitduration (毫秒)让观众暂停(默认 1000 毫秒)
presskey键盘按键,例如 "Enter"、"Escape"
selecttarget, option下拉选择

片段 ID 规则

  • 小写字母数字加连字符:open-settings、toggle-dark
  • 在 playbook 内必须唯一
  • 用作音频/视频的文件名,请保持简短

故障排除

  • 浏览器无响应:先运行 ${CLAUDE_SKILL_DIR}/ndemo close,再运行 open。检查 .ndemo/daemon.log 以查看浏览器守护进程输出。
  • 找不到元素:运行 ${CLAUDE_SKILL_DIR}/ndemo page-state 查看当前页面内容,确保元素存在于无障碍树中。可能需要等待页面加载完成。
  • TTS 失败:确保 OPENAI_API_KEY 环境变量已设置。运行 ${CLAUDE_SKILL_DIR}/ndemo doctor 检查所有依赖项。
  • 渲染失败:检查磁盘空间和文件权限。渲染会消耗大量临时空间。

相关技能