Community程式設計與開發github.com

splitbrain/ndemo

為網頁應用程式建立含旁白的螢幕錄製示範影片。

ndemo 是什麼?

ndemo 是一個強大的 AI 驅動技能,專為建立網頁應用程式的專業示範影片而設計。它能自動化螢幕錄製、文字轉語音 (TTS) 旁白生成,以及最終 MP4 影片的合成。該技能支援 Claude Code、Coder 等 AI 編碼工具,並可與 Playwright 瀏覽器自動化工具整合。使用者只需定義 YAML 格式的劇本 (playbook),包含應用程式 URL、UI 互動步驟、旁白腳本和錄製設定。ndemo 會根據劇本自動執行動作、錄製畫面、生成旁白音訊,最後合成為完整的示範影片。其獨特之處在於支援基於可訪問性樹 (accessibility tree) 的元素定位、條件式設定步驟、以及分段播放與渲染功能,讓 AI 代理能夠動態調整示範內容,確保每次錄製的一致性和可靠性。

相容平台✓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 — 建立劇本

每個劇本都位於使用者專案中 demo/ 目錄下的自己的子目錄中。目錄名稱與劇本名稱相符。

demo/
  my-feature/
    my-feature.yaml    ← 劇本
    fixtures/          ← 設定期間要還原的檔案
    audio/             ← 產生的 TTS 檔案(自動)
    video-raw/         ← 原始錄影(自動)
    demo.mp4           ← 最終輸出(自動)

在編寫劇本之前,請考慮應用程式需要處於什麼狀態,才能使示範可靠且可重複地運作:

  • 使用者是否需要登入? → 加入條件式登入步驟
  • 示範是否會修改需要還原的檔案/資料? → 將原始檔複製到劇本的 fixtures/ 目錄中,以便設定可以還原它們
  • 示範是否依賴於特定內容的存在? → 在 fixtures 中建立該內容,或透過設定 shell 命令建立

將示範期間會修改的任何檔案複製到劇本目錄的 fixtures/ 子目錄中。設定步驟會在每次執行前將它們複製回來,以使示範始終從乾淨的狀態開始。

建立目錄和 YAML 檔案。完整的劇本結構支援下列頂層區段:

# 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: "."                       # 相對於劇本的輸出目錄(預設 ".")
  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 — 旁白播放的同時執行動作

首先編寫所有片段的旁白和意圖。將動作保留為空陣列。將劇本路徑傳遞給 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) 查看無障礙樹輸出。找到片段意圖中引用的元素。使用樹中的資訊,將動作寫入劇本 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,找到正確元素,更新目標
"太快了" / "太慢了"調整 wait 持續時間,使用 --audio 重播以檢查時間
"把旁白改成..."編輯 narration 欄位
"加入一個展示 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 (ms)為觀看者暫停(預設 1000 毫秒)
presskey鍵盤按鍵,例如 "Enter"、"Escape"
selecttarget, option下拉選單選擇

片段 ID 規則

  • 小寫字母數字,使用連字號:open-settings、toggle-dark
  • 必須在劇本內唯一
  • 用作音訊/影片的檔案名稱,因此請保持簡短

疑難排解

  • 瀏覽器無回應:執行 ${CLAUDE_SKILL_DIR}/ndemo close 然後再 open。 檢查 .ndemo/daemon.log 以取得瀏覽器守護行程的輸出。
  • 找不到元素:執行 ${CLAUDE_SKILL_DIR}/ndemo page-state 查看目前頁面上的內容,更新目標選擇器,然後重試。

相關技能