show-n-tell(デモ動画作成スキル) は何をしますか?
あなたは、任意のウェブサイト、ロゴ、そして少しの意図を基に、5分程度のナレーション付きブランドデモ動画を生成する完全なパイプラインを統括します。scripts/ 内のスクリプトは単なるパイプであり、あなたが指揮者です。
このフォルダ内の参照ドキュメント:docs/SCHEMAS.md(YAML + アクション文法)、docs/GOTCHAS.md(既知の問題と回避策)、docs/CONTEXT.md(「良い出力とは」)、examples/halyard-spme/(正規の完全な例)。必要に応じてこれらを読んでください。事前に読み込む必要はありません。
このスキルを使うタイミング
- ユーザーがウェブサイト(ライブ、ローカル開発、静的サイトを問わず)のナレーション付きデモ動画を希望している。
- ユーザーが少なくとも軽い意図のガイダンス(「X、Y、Z を示して」/「ダッシュボードのフローをデモして」)を持っている。
- ユーザーがロゴまたはワードマークにアクセスできること。
ユーザーが意図メモもロゴも提供しない場合は、処理を進めることを拒否してください。 デフォルトのブランディングを使った一般的なデモは質の低いものになります。黙って質の低い動画を生成するのではなく、ガイダンスを求めてください。
環境検出(開始前に読むこと)
何かを始める前に、Claude がどの環境で動作しているかを特定してください:
- Cowork:Claude のツールリストに
mcp__cowork__list_artifactsとmcp__computer-use__screenshotが存在します。 - Claude Code CLI:これらのツールは存在しません。
これは、複数のフェーズで環境によって動作が異なるため重要です。簡易リファレンス:
| フェーズ | Claude Code CLI | Cowork |
|---|---|---|
| 作業ディレクトリ(フェーズ5) | ~/demo-videos/<スラッグ>/ | request_cowork_directory がパスを出力 |
| サイト探索 公開(フェーズ2b) | Playwright MCP ツール | Chrome MCP ツール または explore_page.py ヘッドレス |
| 認証取得 OAuth(フェーズ2a) | Bash 経由で capture_auth.py を実行 | ユーザーがローカルで capture_auth.py を実行 |
| 長時間コマンド(フェーズ7~9) | 同期的に実行 | nohup + ポーリング(45秒のbashタイムアウト) |
| ハンドオフ(フェーズ11) | mp4 パスを報告 | mp4 パス + computer:// リンク |
フェーズ1 — インタビュー(情報収集)
一度にすべてを尋ねるのではなく、1~2つの質問をしてください。必須項目:
- ターゲットURL — 到達可能であることを確認。
- デモの意図メモ — どのようなストーリーを伝えるか? どの機能が重要なのか?
- オーディエンスとトーン — 社内向け/社外向け/投資家向け;説明型/セールス型/技術型/カジュアル型。
- 目標時間 — デフォルトは5分。
- ロゴ — URL またはパス。テキストのみしか利用できない場合は、その制限を伝えた上で受け入れる。
- ブランドカラー — プライマリ(インク)+ アクセント。指定がない場合は、PIL を使ってロゴから推測する。
- 認証 — サイトにログインが必要か?
- いいえ → そのまま進む。
- はい、フォームベース(サイトに自分で制御できるメール/パスワードフィールドがある)→
demo_config.yamlでsession.pre_sessionを使用。認証情報の環境変数名(実際の値は不可)とログインURL/セレクタを収集する。ユーザーには、実際の認証情報を自身で<作業ディレクトリ>/.envに(チャットでは決して)入れるよう伝える。examples/login-flow/を参照。 - はい、OAuth / SSO / マジックリンク / パスキー(Google, Microsoft, Okta など)→
session.storage_stateを使用。ユーザーはコマンドを実行しません。あなたがフェーズ2aでhelpers/capture_auth.pyをユーザーの代わりに起動し、開いたブラウザウィンドウでユーザーが対話的にログインするだけです。取得したauth.jsonは、フェーズ2bの探索とフェーズ8の録画の両方で使用されます。examples/oauth-storage-state/を参照。
フェーズ2a — 認証取得(OAuthのみ)
フェーズ1の質問7で「認証なし」または「フォームベース」と決まった場合は、このフェーズを完全にスキップしてください。OAuth / SSO / マジックリンク / パスキーの場合は、サイト探索の前に、認証済みセッションを今すぐ取得してください — フェーズ2bで認証済みページを表示するために必要です。
mkdir -p ~/demo-videos/<demo-slug>/
uv run helpers/capture_auth.py <login_or_start_url> \
--out ~/demo-videos/<demo-slug>/auth.json \
--viewport <recording.viewport from demo_config.yaml; デフォルト 1440x900>
Bash ツールで timeout: 600000(最大10分)を指定してこれを実行してください。このスクリプトは、ヘッダ付きの Chromium ウィンドウを開きます。ユーザーに平易な言葉で伝えてください:
「ログインするためのブラウザウィンドウを開きます。ログインを完了し(Google、Microsoft など、2FAも対応)、録画を開始したいページに移動したら、ブラウザウィンドウを閉じてください。セッションを保存し、自動的に処理を続行します。」
スクリプトは auth.json をモード0600で原子的に書き込みます。ユーザーが10分以上かかった場合(稀ですが、別室での電話ベースの2FAなどで発生し得ます)、Bash のタイムアウトが発生します — 同じコマンドを再実行してください。
--viewport は demo_config.yaml の recording.viewport と一致させる必要があります。一部のサイトはビューポートサイズをフィンガープリントします;不一致は、録画時または探索時に取得したセッションを無効にする可能性があります。
Playwright の Chromium がまだインストールされていない場合(スキルの新規クローン)、スクリプトは対処可能なエラーを表示します。uv run --with playwright playwright install chromium を一度実行してください。
Claude Code CLI で実行する場合: 上記の説明通り、Bash 経由で capture_auth.py を実行します。
Cowork で実行する場合: Claude は computer-use と Chrome ツールを使用してこれを直接処理します — ユーザーはブラウザが開いたときにログインするだけで済みます。手順:
-
作業ディレクトリのパスを確認します。 フェーズ5がまだ実行されていない場合は、今すぐ
mcp__cowork__request_cowork_directoryを呼び出して出力パスを取得し、<作業ディレクトリ>を<出力パス>/<デモスラッグ>/に設定します。 -
ユーザーのデスクトップで Chrome を開きます:
mcp__computer-use__open_application → "Google Chrome" (または "Chrome") -
ログイン URL に移動します。 Chrome MCP が接続されている場合は、そちらを優先します:
- Chrome 拡張機能が利用可能:
mcp__Claude_in_Chrome__navigateで<login_or_start_url>へ - Chrome 拡張機能が接続されていない:
mcp__computer-use__*ツールを使用してアドレスバーをクリックし、URL を入力します
- Chrome 拡張機能が利用可能:
-
ユーザーにログインするよう伝えます — 平易な言葉で:
「Chrome がログインページで開いています。ログインしてください(2FA、SSO なども処理)、その後、録画を開始するページに移動してください。完了したら教えてください。」
-
待機して監視します。
mcp__computer-use__screenshotで約15秒ごとにポーリングします。URL がログイン/認証ページではなくなり、ダッシュボードやアプリのページが表示されたら続行します。 -
JavaScript でセッション状態を抽出します。
mcp__Claude_in_Chrome__javascript_toolで次のスニペットを実行します:(() => { const cookies = document.cookie.split(';').map(c => { const [name, ...rest] = c.trim().split('='); return { name: name.trim(), value: rest.join('='), domain: location.hostname, path: '/', expires: -1, httpOnly: false, secure: location.protocol === 'https:', sameSite: 'Lax' }; }).filter(c => c.name); const ls = {}; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); ls[k] = localStorage.getItem(k); } const ss = {}; for (let i = 0; i < sessionStorage.length; i++) { const k = sessionStorage.key(i); ss[k] = sessionStorage.getItem(k); } return JSON.stringify({ cookies, origins: [{ origin: location.origin, localStorage: Object.entries(ls).map(([n,v]) => ({name:n,value:v})), sessionStorage: Object.entries(ss).map(([n,v]) => ({name:n,value:v})) }] }, null, 2); })() -
結果を
<作業ディレクトリ>/auth.jsonに書き込みます。 Write ツールを使用します。これは有効な Playwrightstorage_state形式です —helpers/explore_page.pyとrecord_demo.pyはどちらも、browser.new_context(storage_state=...)経由でこれを消費します。 -
HttpOnly Cookie の注意点。 JavaScript は HttpOnly Cookie を読み取ることができません — これらは意図的に
document.cookieから不可視です。サイトのセッショントークンが HttpOnly のみの場合(古い Rails/Django セッションCookie でよく見られます)、抽出された状態にはそれが欠落します。ユーザーに伝えてください:「注意:HttpOnly とマークされたセッションCookie は JavaScript からアクセスできないため、すべての認証トークンを取得できなかった可能性があります。録画は取得されたものを使用しようとします — フェーズ8でログイン画面に遭遇した場合、ローカルで
capture_auth.pyを使用する代替手段があります。」 その後続行します — 多くの最新サイト(Google、Microsoft、Okta)は、Playwright が動作するのに十分な状態を非 HttpOnly Cookie または localStorage に保持しています。
フェーズ2b — サイト探索
フェーズ2a が実行された場合(auth.json が存在する場合)、helpers/explore_page.py を使用して認証済みページを表示します — Playwright MCP は storage_state を読み込めず、ログイン画面しか見えません。認証なしのデモの場合、Playwright MCP で問題なく、より対話的ですので、以前と同様に使用してください。
認証済みページの場合:
mkdir -p ~/demo-videos/<demo-slug>/_explore/
uv run helpers/explore_page.py https://target.example.com/dashboard \
--storage-state ~/demo-videos/<demo-slug>/auth.json \
--out-dir ~/demo-videos/<demo-slug>/_explore/
これにより、ページごとに3つのファイルが _explore/ に書き込まれます:<slug>.png(スクリーンショット)、<slug>.dom.html(セレクタ選択用のDOM)、<slug>.meta.json({url_requested, final_url, title, status})。Read ツールで PNG を読み取ってページを確認し、DOM からセレクタを grep します。meta.json の final_url がログインページを指している場合、セッションが期限切れです — フェーズ2aを再実行してください。
公開ページの場合(任意のデモのランディングページ、マーケティングページ;認証なしデモのすべて)— 以前と同様に Playwright MCP ツール(mcp__plugin_playwright_playwright__*)を使用します:
browser_navigateでターゲット URL に移動し、次にユーザーの意図が言及している2~5ページに移動します。browser_snapshotまたはbrowser_take_screenshotで各ページのレイアウトを確認します。browser_evaluateを使用して、安定したセレクタのDOMを読み取ります(可能な限り、nth-of-typeよりもセマンティックなものを優先)。
Claude Code CLI で実行する場合: 上記の説明通り、mcp__plugin_playwright_playwright__* ツールを使用します。
Cowork で実行する場合: 最初に Chromium が利用可能であることを確認します:
uv run --with playwright playwright install chromium
次に、利用可能な最良の方法を選択します:
mcp__Claude_in_Chrome__navigateが利用可能な場合(Chrome 拡張機能が接続されている)、mcp__Claude_in_Chrome__navigate+mcp__Claude_in_Chrome__read_pageを使用して対話的にサイトを探索します。- それ以外の場合は、ヘッドレス探索にフォールバックします:
(公開ページの場合mkdir -p <working_dir>/_explore/ uv run helpers/explore_page.py <url> --out-dir <working_dir>/_explore/--storage-stateは必要ありません — この引数はオプションになりました。)
重要な各ページについてメモします:
- デモに値する瞬間(機能までのスクロール、詳細へのクリック、ホバーツールチップ)。
- 必要なサイト適応:スティッキーヘッダーのトリック? PDF ディープリンク? モーダルブロッカー? 認証壁?
- 脆弱に見え、
wait_for_selector防御が必要かもしれないセレクタ。
網羅的にクロールしないでください。ページあたり2~5アクションで十分です。
フェーズ3 — ストーリーボード草案 storyboard.yaml
15~30ビートを生成します。各ビート = 1つのカメラアクション + 1~3文のナレーション。アクション文法については docs/SCHEMAS.md を参照(フェーズAサブセット:goto, goto_and_scroll, scroll_into_view, scroll_y, hover, click)。
1.2倍速での5分間の動画の場合の目標:
- 合計(速度変更後):約5:00
- 合計生実行時間:約6:00
- ビートあたりの平均:約12秒、うち約9~10秒がナレーション
ナレーション草案作成時の厳格ルール:
- すべての事実主張は、このビートのフレームで画面上に表示されている必要があります。 架空の数字(「エージェントが199件の変更にフラグを付けました」)は禁止。スクリーンショットで確認できない場合は、述べてはいけません。
- 頭文字を一字ずつ読む場合は、ハイフンで区切って記述します:
S-P-M-E、K-Y-B、A-P-I。TTSプロンプトはこの規則を強制します。 - 長さは視覚的な内容に一致させます: スクロールビートは8~18秒、クリックビートは6~12秒。
- トーンは
branding.yamlのvoice.toneフィールドに一致させます。
ビートをメンタルでセグメント(概要 / 機能A / 機能B / クロージング)にグループ化しますが、YAML ではフラットなリストを記述します — セクションマーカーは使用しません。
フェーズ4 — 平易な英語でのレビュー
ユーザーは YAML を直接編集しません。 あなたはドラフトを番号付きリストとして提示し、自然言語でのフィードバックを受け付けます。次の形式を正確に使用します:
以下が私が作成したデモフローです。推定実行時間:5:08(1.2倍速後)。
変更したい点があれば教えてください。
概要(60秒)
1. ホームページに着陸(10秒)— 「Acme Cloudへようこそ。小規模チーム向けのサーバーレスプラットフォームです...」
2. 機能までスクロール(15秒)— 「3つのコア機能がプラットフォームを支えています...」
3. 出力までスクロール(8秒)— 「これら10のポリシー分野...」
機能1 — ダッシュボード(45秒)
4. ダッシュボードに移動(8秒)— 「ダッシュボードをクリックすると...」
5. ライブアクティビティフィードにホバー(12秒)— 「すべてのプロジェクトにライブアクティビティフィードが表示されます...」
...
クロージング(20秒)
27. ホームページの統計に戻る(12秒)— 「チームがAcmeを選ぶ3つの理由...」
28. アウトロ(8秒)
次に: 「追加、削除、並べ替えはありますか? ナレーションのトーン調整は? スキップしたいものはありますか?」
自然言語のフィードバックを YAML 編集に変換します:
- 「ビート7をスキップ」→ YAML から削除
- 「ビート3を長く」→ 画面上の詳細を追加してナレーションを拡張(大幅に長くなる場合は2ビートに分割)
- 「どこかで価格に言及して」→ 適切なビートを特定し、文を追加(そのビートのフレームに価格が実際に表示されていることを確認)
- 「トーンが堅すぎる」→ ナレーションをよりカジュアルな表現に書き換え
- 「チームページを代わりに表示」→ URL を交換し、ナレーションを調整
更新されたフローを再度提示します。ユーザーが明示的に承認するまで繰り返します。
TTS前の実行時間変動チェック: すべてのビートの len(beat.narration) / 16(140語/分での文字/秒)を合計します。estimated * (1 / speed_multiplier) が目標から30秒以上ずれている場合は、それを提示します:「これは現在、速度変更後約6:20ですが、目標は5分です。トリミングしましょうか?」
フェーズ5 — 作業ディレクトリのセットアップ
mkdir -p ~/demo-videos/<demo-slug>/_assets
cp <logo_path> ~/demo-videos/<demo-slug>/_assets/<logo>.png
# 下書きから storyboard.yaml、branding.yaml、demo_config.yaml を書き込み
Claude Code CLI で実行する場合: 上記のように ~/demo-videos/<demo-slug>/ を使用します。
Cowork で実行する場合: mcp__cowork__request_cowork_directory を呼び出してユーザーの出力パスを取得します。作業ディレクトリを <出力パス>/<demo-slug>/ に設定します。このパスをフェーズ6~11の以降のすべてのファイル書き込みに使用し、セッション中は一貫して保持します。_assets/ サブディレクトリは、~/demo-videos/ ではなく、このパスの下に作成します。
ユーザーがロゴファイルをディスクに持っておらず、URL しか持っていない場合は、curl でダウンロードし _assets/ に配置します。
フェーズ6 — storyboard.yaml の検証(TTS前最終チェック)
docs/SCHEMAS.md の文法に照らして storyboard.yaml を検証します。
- 各ビートに
action、url(goto系アクションの場合)、narration、duration_seconds、selectors(該当する場合)があることを確認。 duration_secondsがナレーションの長さと一致していることを確認:duration_seconds≈len(narration) / 16、ただし範囲は 6~18秒。- ビート 0(
goto)の URL がフェーズ1のターゲット URL と一致していることを確認。 - 同じ URL への連続した
gotoがないことを確認(不要なページ再読み込みを避けるため)。
フェーズ7 — オーディオ生成
# すべてのビートを JSON にダンプ
uv run helpers/dump_narrations.py storyboard.yaml
# OpenAI TTS を呼び出して各ビートの .mp3 を生成
uv run helpers/generate_audio.py storyboard.yaml
これらは同期的に実行されます。完了まで待ちます。エラーが発生した場合(レート制限、無効な音声など)、メッセージを読み、適切に対応します(再試行、代替音声など)。
フェーズ8 — 録画
認証の場合: auth.json が存在することを確認します(フェーズ2a でまだ行われていない場合、自動検出)。
uv run helpers/record_demo.py demo_config.yaml
これにより、record_demo.py が実行され、ストーリーボードに従って録画が行われます:
- 各
goto/clickビートを実行し、その操作を画面に表示します。 - ブラウザの「Pause」動作を使用して、指定された
duration_secondsの間、ページを静止状態に保ちます。 - 指定された
selectorsでスクロール、ホバー、クリックを実行します。 - 各ビートを連続したビデオセグメントに連結します。
Claude Code CLI で実行する場合: uv run helpers/record_demo.py demo_config.yaml を同期的に実行します。縦長の動画表示が表示され、録画が進むにつれてフレームが更新されます。
Cowork で実行する場合: nohup uv run helpers/record_demo.py demo_config.yaml > record.log 2>&1 & を使用します。45秒の Bash タイムアウトに達する可能性があるため、nohup でバックグラウンド実行します。15~30秒ごとに record.log をポーリングして進行状況を確認し、エラーがないか監視します。完了したら結果を確認します。
フェーズ9 — 動画編集
uv run helpers/mux.py demo_config.yaml # 音声 + ビデオをマージし、速度を調整
uv run helpers/overlay_logo.py demo_config.yaml # ブランドオーバーレイを適用
# 将来のフェーズB:openai_helper でイントロ/アウトロ/字幕を生成
これらのスクリプトが完了するのを待ちます(エラーがないか確認しながら)。
フェーズ10 — 品質チェック
結果の mp4 を確認します。ファイルサイズが異常に小さい(< 1MB)場合は、録画が失敗している可能性があります。ユーザーに動画の長さ(ffprobe を使用)と簡単な品質評価を尋ねます。
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 output/demo.mp4
ls -lh output/demo.mp4
ユーザーが満足している場合:完了。満足していない場合:変更点をメモし、影響を受けるビートを修正し、フェーズ7(該当する場合)またはフェーズ6(小規模な編集)から再実行します。
フェーズ11 — ハンドオフ
Claude Code CLI で実行する場合: mp4 へのパスを報告します。要件1.2(例:Slack、メールで送信)のために、ユーザーによるアップロードを促しますが、ユーザーが実行します。
Cowork で実行する場合: mp4 パスと、computer:// リンクを報告します。ファイルを開いて品質を確認するために:
mcp__computer-use__open_file → /path/to/demo.mp4
スキルの制限事項
- 「ドラッグ」インタラクションなし。 Playwright では
drag-and-dropが不安定です。代わりにclick+ 状態変更を使用します。 - WebGL / iframe が多いサイト。 録画は Canvas の内部を表示できません。これらの場合、
explore_page.pyのスクリーンショットに頼ってください。 - ライブデータ。 動画のナレーションに動的データを含めると、時間の経過とともに古くなります。可能な限り「このダッシュボードには...が表示されます」のような一般的な表現を使用してください。
- 音声の長さの制限。 各 TTS 呼び出しは約60秒の音声に制限されています。ビートのナレーションが長すぎる場合は分割してください。
- ブラウザの違い。 Chromium での録画は、Safari や Firefox で一部の CSS が異なるレンダリングになる場合があります。主に Chromium ユーザーを対象とするか、クロスブラウザのメモを提供します。
おまけ:環境間の高速切り替え表
| タスク | Claude Code CLI | Cowork |
|---|---|---|
| 認証なしのサイトを探索 | Playwright MCP(browser_navigate, browser_snapshot) | Chrome MCP(navigate + read_page)または explore_page.py ヘッドレス |
| 認証ありのサイトを探索 | explore_page.py(--storage-state 付き) | explore_page.py(--storage-state 付き) |
| 作業ディレクトリ | ~/demo-videos// | request_cowork_directory から取得した <出力パス>// |
| OAuth 認証を取得 | capture_auth.py(Bash) | ユーザー支援 + Chrome MCP(JavaScript 抽出) |
| 長時間コマンド | 同期的に実行(タイムアウトは Bash が処理) | nohup + ポーリング(45秒の Bash タイムアウトを回避) |
| 最終結果を報告 | mp4 へのパス | mp4 パス + computer:// リンク |
付録:設定リファレンス
demo_config.yaml
demo:
slug: my-demo
title: "Acme Cloud の紹介"
target_url: https://example.com
length_target_seconds: 300
speed_multiplier: 1.2
recording:
viewport:
width: 1440
height: 900
record_timeout: 180 # ビートあたりの最大秒数
session:
# オプション:認証に pre_session または storage_state のいずれか、またはなし
# pre_session(フォームベースログイン用):
# login_url: https://example.com/login
# username_env: DEMO_USERNAME
# password_env: DEMO_PASSWORD
# username_selector: '#email'
# password_selector: '#password'
# submit_selector: 'button[type="submit"]'
# storage_state(OAuth / SSO 用):path: auth.json
branding:
logo_path: _assets/logo.png
primary_color: "#1a1a2e" # インクカラー
accent_color: "#e94560" # アクセントカラー
watermark: false
voice:
model: tts-1
voice: nova # alloy, echo, fable, onyx, nova, shimmer
tone: professional # カジュアル、技術的、プロフェッショナル