Communityコーディング&開発github.com

splitbrain/ndemo

Webアプリケーションのナレーション付き画面録画デモ動画を作成します。

ndemo とは?

ndemoは、Webアプリケーションのナレーション付き画面録画デモ動画を作成するためのAIエージェントスキルです。ユーザーはYAMLベースのプレイブックを記述することで、アプリケーションの操作手順を定義し、自動的にブラウザでの操作、TTSナレーションの生成、そして最終的なMP4動画のレンダリングを実行できます。このスキルは、Playwrightを利用してブラウザを制御し、OpenAIやElevenLabsのTTSサービスと連携して高品質なデモ動画を生成します。主なユースケースとして、ソフトウェアの機能紹介、チュートリアル動画の作成、QAテストの記録などが挙げられます。セットアップは`npm install && npm run build`で完了し、`ndemo doctor`コマンドで依存関係を確認できます。

対応✓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に質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

ndemo は何をしますか?

Webアプリケーションのナレーション付き画面録画デモ動画を作成します。

セットアップ

初回使用時は、コマンドを実行する前に必ずツールキットをビルドしてください。 ${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>1つのセグメントを再生(最初に巻き戻す)
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --from <id>セグメントから最後まで再生
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --to <id>このセグメントで停止
${CLAUDE_SKILL_DIR}/ndemo play <playbook> --audioTTSナレーション音声付きで再生
${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/サブディレクトリにコピーします。 セットアップステップは、各実行前にそれらをコピーして戻すため、デモは常にクリーンな状態から開始されます。

ディレクトリと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 — ナレーションの再生中にアクションが実行されます

まず、すべてのセグメントをナレーションと意図(intent)で記述します。アクションは空の配列のままにします。 プレイブックのパスを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" }              # 表示テキストコンテンツ

少なくとも1つのターゲットフィールドが必要です。複数のフィールドを組み合わせてマッチを絞り込むことができます。

ヒント: Webアプリケーションのソースコードはプロジェクトリポジトリで入手可能です。 アクセシビリティツリーだけでは不十分な場合は、data-testid属性、クラス名、IDのためにコンポーネントのソースを確認してください。

done条件の記述方法:

done:
  visible: ".settings-panel"        # 要素が表示される
  hidden: ".loading-spinner"        # 要素が非表示になる
  networkIdle: true                 # 保留中のリクエストがない
  stable: 500                       # 500ms間DOMが変更されない
  url: "**/settings"                # URLが変更された
  text:                             # 要素にテキストが含まれる
    selector: ".results"
    has: "Q3 Revenue"
  attribute:                        # 要素に属性がある
    selector: html
    name: data-theme
    value: dark
  timeout: 10000                    # デフォルトタイムアウトをオーバーライド(ミリ秒)

ページを変更するすべてのアクションには、必ずdone条件が必要です。 ない場合、ページの準備ができる前に次のアクションが実行される可能性があります。

表示上の変更の後には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時)。 シェルコマンドまたはブラウザアクションを使用できます。

シェルコマンドは、プロジェクトの作業ディレクトリ(ndemoが呼び出されたディレクトリ)に対して相対的に実行されます。 runコマンド内のすべてのパスは、プロジェクトルートからの相対パスである必要があります。

シェルコマンド — ファイル操作には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: "<セレクター>" — セレクターが表示されている要素と一致する場合のみステップを実行
  • hidden: "<セレクター>" — セレクターが表示されている要素と一致しない場合のみステップを実行
  • url: "<パターン>" — 現在のURLが一致する場合のみステップを実行(** = 任意のパス)

アクションタイプリファレンス

タイプ必須フィールド備考
clicktarget
typetarget, textオプションのdelay(キーストローク間のミリ秒、デフォルト 80)
hovertarget
scrolltarget要素をビューにスクロール
waitduration(ミリ秒)視聴者用の一時停止(デフォルト 1000ms)
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 を実行して、現在のページのアクセシビリティツリーを確認し、意図した要素が存在するか、異なるセレクターが必要かを確認してください。
  • アクションがタイムアウトする: done 条件のタイムアウトを増やすか、条件が正しいことを確認してください。

関連スキル