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 匹配時,步驟才執行(**表示任何路徑)
動作類型參考
| 類型 | 必要欄位 | 備註 |
|---|---|---|
| click | target | |
| type | target, text | 可選 delay(毫秒),按鍵間隔(預設 80) |
| hover | target | |
| scroll | target | 將元素滾動到視圖中 |
| wait | duration (ms) | 為觀看者暫停(預設 1000 毫秒) |
| press | key | 鍵盤按鍵,例如 "Enter"、"Escape" |
| select | target, option | 下拉選單選擇 |
片段 ID 規則
- 小寫字母數字,使用連字號:
open-settings、toggle-dark - 必須在劇本內唯一
- 用作音訊/影片的檔案名稱,因此請保持簡短
疑難排解
- 瀏覽器無回應:執行
${CLAUDE_SKILL_DIR}/ndemo close然後再open。 檢查.ndemo/daemon.log以取得瀏覽器守護行程的輸出。 - 找不到元素:執行
${CLAUDE_SKILL_DIR}/ndemo page-state查看目前頁面上的內容,更新目標選擇器,然後重試。