show-n-tell 是做什么的?
你负责编排一个完整的管道,将任何网站 + 一个 logo + 一点意图,转化为一个约 5 分钟、带解说和品牌元素的演示视频。scripts/ 中的脚本只是简单的管道工具;你是指挥员。
本文件夹中的参考文档:docs/SCHEMAS.md(YAML + 动作语法),docs/GOTCHAS.md(已知问题及解决方案),docs/CONTEXT.md(“什么样的输出是好的”),examples/halyard-spme/(规范的完整示例)。根据需要阅读这些文档——不要预加载它们。
何时使用此技能
- 用户希望制作一个关于某个网站(线上、本地开发或静态站点)的演示视频。
- 用户至少提供了一些轻量的意图指导(“展示 X、Y、Z” / “演示仪表盘流程”)。
- 用户可以访问 logo 或文字商标。
如果用户既没有提供意图说明,也没有提供 logo,则拒绝继续。 使用默认品牌元素的通用演示视频质量很差。请向用户寻求指导,而不是默默生成一个低质量的视频。
环境检测(开始前阅读)
在开始任何操作之前,确定 Claude 运行在哪个环境中:
- Cowork:
mcp__cowork__list_artifacts和mcp__computer-use__screenshot在 Claude 的工具列表中可用。 - Claude Code CLI:这些工具不可用。
这一点很重要,因为多个阶段在不同环境下的行为不同。快速参考:
| 阶段 | Claude Code CLI | Cowork |
|---|---|---|
| 工作目录(阶段 5) | ~/demo-videos/<slug>/ | 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 — 访谈(收集输入)
一次只问一两个问题,不要一次问完所有。必备项:
- 目标 URL — 确认其可访问。
- 演示意图说明 — 要讲述什么故事?哪些功能重要?
- 受众和语调 — 内部/外部/投资者;解释性/销售性/技术性/随意性。
- 目标时长 — 默认 5 分钟。
- Logo — URL 或路径。如果只有纯文本可用,请明确指出限制但接受。
- 品牌颜色 — 主色调(墨水色)+ 强调色。如果未指定,使用 PIL 从 logo 推断。
- 需要认证? — 网站是否需要登录?
- 不需要 → 继续。
- 需要,基于表单(网站有自己的邮箱/密码字段,你可以控制)→ 在
demo_config.yaml中使用session.pre_session。收集凭据的环境变量名(不是实际值)和登录 URL/选择器。告知用户在运行前将实际凭据放在<working_dir>/.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; default 1440x900>
通过 Bash 工具运行此命令,设置 timeout: 600000(10 分钟——最大值)。脚本会打开一个有头 Chromium 窗口。用通俗的话告诉用户:
“我正在为你打开一个浏览器窗口用于登录。完成登录(Google、Microsoft 等,处理双重认证),导航到你希望录制开始的页面,然后关闭浏览器窗口。我将保存会话并自动继续。”
脚本会将 auth.json 以 0600 权限原子地写入。如果用户耗时超过 10 分钟(罕见,但在另一个房间使用基于手机的双重认证时可能发生),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获取输出路径,并将<working_dir>设置为<outputs_path>/<demo-slug>/。 -
在用户桌面上打开 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 已在登录页面打开。请登录(处理任何双重认证、SSO 等),然后导航到录制应开始的页面。完成后请告诉我。”
-
等待并观察。 大约每 15 秒用
mcp__computer-use__screenshot轮询一次。一旦 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); })() -
使用写入工具将结果写入
<working_dir>/auth.json。 这是有效的 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)在非 HttpOnly cookie 或 localStorage 中保留了足够的状态,以便 Playwright 正常工作。
阶段 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/
这会将每个页面的三个文件写入 _explore/:<slug>.png(截图),<slug>.dom.html(用于选择器选取的 DOM),<slug>.meta.json({url_requested, final_url, title, status})。使用读取工具读取 PNG 以查看页面;在 DOM 中搜索选择器。如果 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–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. 滚动到输出(8 秒)—“这十个政策领域…”
功能 1 — 仪表盘(45 秒)
4. 导航到仪表盘(8 秒)—“点击进入仪表盘…”
5. 悬停活动动态(12 秒)—“每个项目都显示一个实时活动动态…”
...
结束语(20 秒)
27. 返回主页统计数据(12 秒)—“团队选择 Acme 的三个原因…”
28. 片尾(8 秒)
然后:“有要添加、删除或重新排序的内容吗?解说语调需要调整吗?有要跳过的内容吗?”
将自然语言反馈转换为 YAML 编辑:
- “跳过节拍 7” → 从 YAML 中移除
- “让节拍 3 更长” → 用更多的屏幕细节扩展解说(如果长很多,则拆分为两个节拍)
- “在某个地方提到定价” → 确定正确的节拍并添加一句话(确认定价实际上在该节拍画面中可见)
- “语调太正式” → 用更随意的措辞重写解说
- “显示团队页面而不是关于页面” → 交换 URL + 调整解说
重新呈现更新后的流程。反复迭代,直到用户明确批准。
TTS 前的运行时漂移检查: 对所有节拍求和 len(beat.narration) / 16(140 wpm 时的字符数/秒)。如果 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 获取用户的输出路径。将工作目录设置为 <outputs_path>/<demo-slug>/。在阶段 6–11 中,所有后续文件写入都使用此路径——立即存储它并在整个会话中保持一致。在此路径下创建 _assets/ 子目录,而不是在 ~/demo-videos/ 下。
如果用户磁盘上没有 logo 文件,只有一个 URL,使用 curl 下载并放入 _assets/ 中。
如果用户没有提供品牌颜色,请安装 Pillow 并从 logo 中提取主色:
uv run python3 -c "
from PIL import Image
import colorsys
img = Image.open('~/demo-videos/<slug>/_assets/logo.png').resize((1, 1))
r, g, b = img.getpixel((0, 0))[:3]
# Convert to HSL to find a complement
h, l, s = colorsys.rgb_to_hls(r/255, g/255, b/255)
accent_h = (h + 0.5) % 1.0
ar, ag, ab = colorsys.hls_to_rgb(accent_h, l, s)
print(f'Ink: #{r:02x}{g:02x}{b:02x}')
print(f'Accent: #{int(ar*255):02x}{int(ag*255):02x}{int(ab*255):02x}')
"
现在编写三个配置文件。
branding.yaml
# 使用此精确结构
brand:
logo_path: ./_assets/<logo>.png
colors:
ink: '#1a1a2e'
accent: '#e94560'
voice:
tone: explanatory # 或 sales / technical / casual
speaking_rate: 1.0 # OpenAI TTS 速度(默认 1.0)
watermark:
enabled: true # 将 logo 叠加在右下角
scale: 0.08 # 相对于帧宽度的 logo 大小
opacity: 0.85
demo_config.yaml
demo:
slug: <demo-slug>
title: "Acme Cloud — Serverless Platform for Small Teams"
target_url: https://target.example.com
speed_multiplier: 1.2 # Phase 9 的默认值
target_duration_s: 300 # 加速后 5 分钟
recording:
viewport: { width: 1440, height: 900 }
headless: true # 录制期间隐藏浏览器窗口(仅在 Cowork 中相关)
wait_for_selector_timeout: 8000
session:
type: none # none | form | oauth
storage_state: null # 如果 type=oauth 则设置
pre_session: {} # 如果 type=form 则设置
storyboard.yaml
作为该阶段的输出,编写您从阶段 3–4 完善的完整故事板列表。
阶段 6 — 生成音频
对于 storyboard.yaml 中的每个节拍,调取 OpenAI TTS 端点。
mkdir -p ~/demo-videos/<demo-slug>/_audio/
# 对于每个节拍 i(从 0 开始):
curl -s https://api.openai.com/v1/audio/speech \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "tts-1",
"input": "<beat.narration>",
"voice": "echo",
"speed": 1.0
}' \
--output ~/demo-videos/<demo-slug>/_audio/beat_$(printf '%03d' $i).mp3
sleep 0.5 # 速率限制:大约 2 个请求/秒
TTS 提示注入: 所有解说文本的编写方式确保 TTS 输出清晰、表现力强。规则:
- 对于首字母缩略词,使用连字符:
S-P-M-E、A-P-I、K-Y-B(输出读作“S-P-M-E”而不是“SPME”)。 - 在自然停顿处使用逗号。
- 避免技术上正确但口语尴尬的句子。如果“前端开发说明书”听起来别扭,请重新措辞为“用于前端开发的说明书”。
- 在 narration 字段中不要包含 SSML 或地道的发音指南——只需在 narration 字符串本身中用连字符分隔首字母缩略词即可。
- 高度技术性的概念(“异步事务”、“零知识证明”)应该有一个简短的词典式解释,除非已知观众熟悉该术语。
如果在 Claude Code CLI 中运行: 按上述描述运行,同步进行。
如果在 Cowork 中运行: 逐节拍运行 curl 命令。对于超过 15 个节拍的故事板,使用 nohup 模式以避免 45 秒的 bash 超时:
cat > ~/demo-videos/<slug>/_audio/generate_audio.sh << 'EOFSCRIPT'
... # 包含上述循环的脚本
EOFSCRIPT
chmod +x ~/demo-videos/<slug>/_audio/generate_audio.sh
nohup bash ~/demo-videos/<slug>/_audio/generate_audio.sh &
echo "PID: $!"
轮询 ~/demo-videos//audio/beat*.mp3 的存在以检查进度。所有文件可见后,通过报告总文件数和预期节拍数(并与 storyboard.yaml 中的节拍列表一致)来验证完整性。如果文件数量不匹配,识别缺失的节拍并重新运行其 curl 命令。
阶段 7 — 创建屏幕录制
对于 storyboard.yaml 中的每个节拍,执行指定的操作并捕获/编码帧。
demo_config.yaml 的 session 配置如何处理:
session.type: none→ 在运行录制工具时省略所有--storage-state和--pre-session标志。session.type: oauth→ 包含--storage-state ~/demo-videos/<slug>/auth.json。session.type: form→ 在运行录制工具之前,通过demo_config.yaml的pre_session设置提供凭据映射。工具在启动上下文中查找.env文件。
mkdir -p ~/demo-videos/<slug>/_frames/
uv run helpers/record_demo.py \
--config ~/demo-videos/<slug>/demo_config.yaml \
--audio-dir ~/demo-videos/<slug>/_audio/ \
--frames-dir ~/demo-videos/<slug>/_frames/ \
--storage-state ~/demo-videos/<slug>/auth.json # OAuth 场景;自定义认证场景中设置为 --pre-session
record_demo.py 处理:
- 节拍动作执行(goto、click、scroll_into_view、scroll_y、hover)。
- 每个动作后的帧捕获。
- 播放每个节拍的音频文件并捕获同步的帧序列。
- 为速度调整(Phase 9 的
speed_multiplier)添加时间戳。
如果在 Claude Code CLI 中运行: 按上述描述运行 record_demo.py。命令通常运行 5–10 分钟——确保 Bash 工具超时设置足够高(例如,timeout: 600000)。
如果在 Cowork 中运行: 使用 nohup 模式:
nohup uv run helpers/record_demo.py --config ... &
echo "PID: $!"
每 30 秒轮询 ~/demo-videos/<slug>/_frames/ 中 .png 文件的存在以检查进度。完成后,验证帧计数是否与节拍数量匹配。
阶段 8 — 合成音频 + 帧
uv run helpers/mux_audio_video.py \
--frames-dir ~/demo-videos/<slug>/_frames/ \
--audio-dir ~/demo-videos/<slug>/_audio/ \
--output ~/demo-videos/<slug>/_raw.mp4 \
--framerate 30
mux_audio_video.py 从 _frames/ 和 _audio/ 构建最终 mp4。它处理:
- 将帧序列编码为视频流(H.264)。
- 从每个节拍的音频文件中读取并交错音频流。
- 计算总时长以用于后续的速度调整。
此步骤后输出 _raw.mp4。
如果帧/音频文件丢失: mux_audio_video.py 会打印警告并跳过缺失的节拍。查看警告,识别缺失的节拍,并酌情重新运行阶段 6 或 7 来填补空缺。
阶段 9 — 速度调整
uv run helpers/adjust_speed.py \
--input ~/demo-videos/<slug>/_raw.mp4 \
--output ~/demo-videos/<slug>/_sped.mp4 \
--speed 1.2
--speed 值来自 demo_config.yaml 的 speed_multiplier。adjust_speed.py 使用 FFmpeg 的 setpts 和 atempo 过滤器来均匀加速视频。音频音高通过 atempo 过滤器保持不变。
阶段 10 — 叠加品牌元素
uv run helpers/add_brand_overlay.py \
--input ~/demo-videos/<slug>/_sped.mp4 \
--output ~/demo-videos/<slug>/_branded.mp4 \
--branding ~/demo-videos/<slug>/branding.yaml
add_brand_overlay.py 读取 branding.yaml 并应用:
- 页面角落的纯色/半透明 logo 水印。
- 品牌颜色用于片头/片尾卡片(B 阶段)。
- 如果 B 阶段尚未实现,则仅应用水印,并在
<slug>/_branded.mp4输出——无需错误。
阶段 11 — 交付
向用户报告 mp4 文件路径。明确说明新文件。
如果在 Claude Code CLI 中运行:
Done! Your branded demo video is at:
~/demo-videos/<slug>/_branded.mp4 (14 MB)
如果在 Cowork 中运行:
Done! Your branded demo video is at:
<outputs_path>/<slug>/_branded.mp4
You can open it via computer://<outputs_path>/<slug>/_branded.mp4
如果发生错误,请提供具体的故障排除步骤,而不仅仅是“出了点问题”。
已知陷阱(GOTCHAS)
在启动完整管道之前,请参阅 docs/GOTCHAS.md 以获取全面的列表。重点关注的问题:
- 帧同步漂移: 如果音频和帧不同步,检查
record_demo.py是否以正确的 fps 捕获帧,并且mux_audio_video.py的视频流时间戳没有偏移。 - logo 路径丢失: 如果
branding.yaml引用了一个不存在的 logo 文件,fix 脚本会警告并跳过覆盖层。始终在阶段 5 中确认文件是否存在。 - OpenAI 速率限制: 阶段 6 中的
sleep 0.5可能不足以满足 tier-1 账户(1000 RPM)。调整或无限制。 - Playwright 浏览器安装: 如果 Phase 2a 或 Phase 7 中出现“未找到浏览器”错误,运行
uv run --with playwright playwright install chromium。 - OAuth 令牌过期: 如果 Phase 8 中遇到登录墙,重新运行 Phase 2a。会话可能已过期。
- 故事板节拍计数: 确保节拍计数在 Phases 4 到 7 之间保持一致。不匹配会导致 Phase 8 中出现“缺失节拍”警告。