agentshot:截图与演示视频 是做什么的?
该工具位于 /path/to/agentshot。按如下方式运行:
node /path/to/agentshot/bin/agentshot.mjs <command> ...
下方agentshot指代该命令。每个命令都输出一行 JSON("ok": true|false)。长时间渲染的任务还会在 stderr 上输出进度。运行 agentshot help 可查看命令列表。
工作流程
- 首先阅读产品的规则:
agentshot style <product>会输出style.json(默认外观)和guidelines.md(用户偏好)。请遵循这些规则。如果产品是新建的,运行agentshot style <product> --init。 - 捕获(选择其一):
- Web 应用或 Expo 应用 → 编写包含
"url"的场景 JSON 文件,运行agentshot capture scenario.json。 - Mac 上的 iOS 应用 → 编写包含
"simulator"的场景(在 iOS 模拟器中的真实原生应用,参见 “iOS 模拟器”);快速静态截图 →agentshot sim snap --name <capture> --clean-status-bar。 - 桌面程序 → 编写包含
"window"的场景:你通过真实的鼠标/键盘驱动它,同时记录(参见 “桌面应用”)。仅需静态截图 →agentshot snap --window "<title>" --name <capture>。用户自行操作 →agentshot record start/stop。 - 用户已有文件(iPhone 截图/屏幕录制) →
agentshot import <file> --name <capture>。
- Web 应用或 Expo 应用 → 编写包含
- 检查:
agentshot inspect <capture>会列出截帧、元素框(屏幕坐标点)、点击时间、时长,以及对于视频,还会列出moves(指针移动)、changes(页面切换:大的视觉跳变)、quiet(无任何变化:可裁剪或加速),以及场景的timeline。根据这些数据规划裁剪和缩放,然后通过agentshot frames <capture> --from a --to b确认(一张带时间戳的联系表;请用读取工具查看其输出的 PNG)。在应用样式前,先用读取工具查看截帧的 PNG。 - 编写配方 JSON 文件并运行
agentshot render recipe.json --note "<what changed>"。 - 自行检查结果:读取 PNG;对于 MP4,使用
agentshot frames <product>/<asset>(整个视频)和agentshot frames <product>/<asset> --from 3 --to 4 --every 0.033 --crop x,y,w,h(逐帧查看裁剪或点击附近区域)。修复并重新渲染(会添加新版本)。当用户希望自行手动调整或微调配方时,在后台运行agentshot preview recipe.json:它会打开一个包含场景和滑块控件的本地页面,并在编辑配方(或产品的style.json)时实时更新。它会输出一行包含url的 JSON,并持续提供服务。 - 报告输出路径(
results[].output)。--copy-to <dir>还会将其复制到用户的项目中。 - 学习:当用户纠正或表扬某些内容时,在产品的
guidelines.md中追加带日期的注释(如果是默认外观,同时更新style.json的默认值)。下次使用时先读取它。
将场景和配方文件保存在用户的项目中(例如 docs/shots/),以便在 UI 更改后可以重新运行。
场景(捕获 Web 应用)
下面 JSON 示例中的注释仅为解释说明;实际文件必须是纯 JSON。
{
"name": "ellie-today-drag", // 捕获名称,唯一;以产品名称为前缀
"url": "http://localhost:8081", // 或者文件路径
"device": "iphone", // iphone | iphone-max | ipad | desktop | laptop
"safeArea": true, // Expo/RN Web:Chrome 会报告 iPhone 的安全区域 inset,应用布局方式
// 如同在设备上一样("pad" = 为忽略 inset 的应用强制添加内边距)
"css": "[class*='navigationMenuRoot'] { display: none }", // 隐藏仅限 Web 的界面元素(Expo Router 的标签页在 Web 上会渲染为顶部菜单)
"waitFor": "today-list", // 在开始前等待此元素出现
"track": { "row": "text=Plan a birthday" }, // 额外的命名元素框记录
"steps": [
{ "shot": "main" }, // 全分辨率静态截图(用于截图)
{ "wait": 0.6 },
{ "tap": "add-button" }, // 点击/轻触目标(会先移动到那里)
{ "type": "Buy milk", "into": "title-input" },
{ "press": "Enter" },
{ "drag": "task-birthday", "to": "slot-11am", "duration": 1.4 },
{ "scroll": 400, "duration": 0.8 },
{ "move": [200, 300], "duration": 0.6 },
{ "goto": "/settings" },
{ "waitFor": "settings-title" },
{ "shot": "after" }
]
}
目标:仅名称匹配 data-shot="..." 或 data-testid="..."(React Native 的 testID 在 Web 上会变成 data-testid,因此请为要指向的元素添加 testID),或任何 Playwright 选择器(text=Save、role=button[name="Save"]、#id、.class),或 CSS px 单位下的 [x, y]。所有具有 data-shot/data-testid 的元素及其边框都会被记录,同时 track 条目也会被记录。
时间控制:默认情况下("timing": "frame")页面时钟会被暂停,并以每帧(60 fps)的方式精确推进,每帧都渲染全分辨率截图,因此应用动画在任何分辨率下都很流畅;捕获时间会比场景实际时间长几倍。仅当应用在受控时钟下出错(某个步骤超时)时使用 "timing": "real"。agentshot capture 会报告点击时间(clicks),以便规划 cuts/zoom,以及控制台错误/警告(console)。开发者覆盖层(Expo LogBox toast、Next.js 指示器)会被隐藏;设置 "devOverlays": true 可保留它们。建议使用一个连续的 cuts 片段,而不是多个硬切割:片段之间的跳跃看起来会很生硬。
在无 testID 的应用中查找目标:使用 Playwright 打开页面,列出按钮和输入框(role、aria-label、文本),然后使用 text=...、role=button[name=/^My daughter/] 或 [aria-label='Your prayer'],并通过 track 命名你希望突出显示或放大查看的元素框。如果输入框被固定页脚遮挡,仍然可以正常输入(输入框会获得焦点)。
Expo:首先启动 Web 构建(npx expo start --web,通常地址为 http://localhost:8081)。仅限原生平台的模块可能在 Web 上无法工作;如果某个界面崩溃,请向用户索要 iPhone 截图/录制并 import。
桌面应用(包含 "window" 的场景)
agentshot windows 会列出窗口(标题、进程、尺寸)。通过标题匹配窗口,然后按进程名匹配,如果窗口最小化则恢复并前置。坐标是窗口像素:请先查看静态截图(agentshot snap --window "X" --name x-probe,读取 PNG),并在 track 中命名你使用的元素框。
{
"name": "notes-tour",
"window": "Notes", // 标题部分或进程名
"track": { "nav-recent": [20, 200, 190, 40], "nav-search": [20, 96, 190, 40], "content": [232, 40, 1030, 650], "profile": [16, 718, 150, 62] },
"cursor": [1000, 330], // 指针起始位置
"steps": [
{ "shot": "home" }, // 静态截图 = 此时记录的帧
{ "wait": 1 },
{ "click": "nav-recent", "duration": 0.9 }, // 滑移到那里(带缓动,轻微弧形),立即点击
{ "waitStill": 0.6, "region": "content" }, // 等待屏幕变化,然后等待稳定
{ "shot": "recent" },
{ "wheel": 4, "at": [760, 460] }, // 向下滚动 4 格(负值 = 向上)
{ "click": "nav-search" }, { "press": "ctrl+a" },
{ "type": "Weekly plan", "cps": 12 }, // 任意文本,包括非拉丁字符
{ "press": "Enter" }, // 按键:enter esc tab space backspace delete arrows home end f1..f12, ctrl/alt/shift/win+x
{ "drag": [100, 300], "to": [400, 300], "duration": 1.2 },
{ "click": [640, 400], "button": "right" }, { "click": [640, 400], "double": true },
{ "note": "beat 2" } // 时间线中的标记
]
}
-
结果中包含
timeline(每个步骤在视频中的开始/结束时间,at= 点击时刻)和shots(从视频中截取的静态帧)。配方使用track名称作为目标(如对"profile"进行"blur"等)。 -
waitStill首先等待(最长changeTimeout,3 秒)直到区域与之前不同(change,平均像素差值,默认 6;"change": false则仅等待稳定),然后等待指定秒数内无变化(threshold0.6)。使用不包含动画部分(播放器、动态背景)的region。 -
到达后立即点击:光标在控件上停留约 0.5 秒会使 Windows 工具提示出现在视频中。
-
对于保留旧文本的输入框:在
type之前先press ctrl+a。 -
驱动程序在录制前启动(需要几秒钟)并移动真实光标:在此期间不要使用机器。桌面捕获为 1x 缩放,因此放大镜效果会显得模糊;建议使用
lift。 -
可在录制的任意时间点获取静态帧:
agentshot snap <capture> --at 12.4 --shot name。 -
微小变化(如弹出窗口关闭)可能不足以使整个窗口的
change阈值触发:请指定变化周围的region。 -
快捷键:
mod在 Windows 上是 Ctrl,在 macOS 上是 Cmd("press": "mod+a"),因此一个场景可以在两个系统上运行。
手动录制(用户执行操作;光标位置由渲染器记录和绘制):
agentshot record --name app-filter --window "Figma" --duration 12 # 录制 12 秒,然后停止
agentshot record start --name app-filter --window "Figma" # 或开始 ...
agentshot record stop # ... 并稍后停止
macOS
命令和场景与 Windows 相同;录制器 recorder/mac.swift 会在首次使用时编译(需要 Xcode Command Line Tools 和 macOS 14+)。首先运行 agentshot doctor:运行代理的应用(Terminal、iTerm、Claude、VS Code)需要在系统设置 > 隐私与安全性中启用屏幕录制和辅助功能权限(agentshot doctor --request 会打开权限提示;授权后重新启动该应用)。没有屏幕录制权限时,窗口标题仅显示应用名称,录制会失败;没有辅助功能权限时,驱动程序无法点击或输入。
- 坐标(
track、步骤、配方目标)是点;静态帧和视频使用显示器的像素缩放比例(Retina 上为 2,inspect会显示scale),因此lift和zoom效果保持清晰。 - ScreenCaptureKit 录制的是窗口本身:窗口可以部分被遮挡,但不能最小化。
node scripts/selftest-mac.mjs会在 TextEdit(以及已启动的模拟器)上检查所有功能,并打印失败的模块。同步检查:agentshot record ... --show-cursor会烧录真实光标;将其与记录的指针位置进行对比(像其他录制一样使用agentshot frames和inspect)。
iOS 模拟器 (macOS)
使用原生应用而非其 Web 版本:真实的字体、原生模块、iOS 动画。启动一个模拟器(open -a Simulator),在其中运行应用(npx expo run:ios 或开发构建),然后:
agentshot sim devices # 已启动的模拟器
agentshot sim snap --name app-home --clean-status-bar # 无损静态截图,显示 9:41 / 满电量
agentshot sim record start --name app-flow # 其他人操作 ...
agentshot sim record stop
场景使用 idb 驱动(brew install facebook/fb/idb-companion,pipx install fb-idb);坐标是设备点(读取 sim snap 的图片后将其像素值除以 scale):
{
"name": "app-onboarding",
"simulator": "booted", // 或 UDID 或设备名称的一部分
"statusBar": "clean", // 捕获期间显示 9:41,捕获后清除
"appearance": "light", // 或 "dark"
"openUrl": "exp://127.0.0.1:8081/--/today", // 录制前打开的深度链接(或 "launch": "<bundle id>")
"track": { "add": [330, 760, 48, 48], "title": [24, 120, 340, 44] },
"steps": [
{ "shot": "today" },
{ "tap": "add" },
{ "waitStill": 0.5 },
{ "type": "Buy milk" }, // 输入到当前焦点所在的输入框
{ "press": "enter" }, // enter esc backspace tab space arrows home lock
{ "swipe": [200, 600], "to": [200, 250], "duration": 0.4 },
{ "openUrl": "exp://127.0.0.1:8081/--/settings" },
{ "waitStill": 0.6 },
{ "shot": "settings" }
]
}
静态帧是模拟器的无损截图;轻触和滑动操作会变成触摸指针和点击,因此配方中使用 "pointer": "touch" 和 "device": "iphone-black",与浏览器捕获类似。如果没有 idb,则只有 wait/waitStill/agentshot/openUrl/launch 步骤可以工作。
配方(样式与渲染)
{
"name": "today-drag", // 库中的资产名称
"product": "ellie", // 库文件夹;应用 style.json 的默认值
"capture": "ellie-today-drag",
"shot": "main", // 设置为 PNG 静态截图的截帧名称;省略则生成 MP4 视频
"size": "16:9", // 16:9 16:10 4:3 3:2 1:1 4:5 9:16, 或宽/高
"background": "lavender", // lavender peach sage sky rose mint neutral midnight ink white,
// 任意 CSS 颜色/渐变,或 public/ 目录下的图片(backgrounds/x.jpg)
"backgroundBlur": 0.02, // 对于照片背景,为短边占比
"device": "iphone-black", // iphone-black iphone-natural iphone-white browser window none
"url": "app.ellie.so/today", // 浏览器地址栏文本;"title":窗口标题(用于 "window")
"padding": 0.08, "tilt": 12, // 设备外边距;3D 倾斜角度(度)
"position": [0.69, 0.5], // 设备在画布上的中心位置,为标题等留出空间
"accent": "#6d5ce8", // 步骤编号、轮廓、标签、点击涟漪的颜色
"font": "Gilda Display", "labelFont": "Inter", // 标题 / 放大标签字体:Inter Manrope Poppins Nunito Fraunces Lora
// "Playfair Display" "DM Serif Display" "Gilda Display" "Cormorant Garamond" Caveat
"lift": { "target": "task-row", "label": "Drag it onto your day", "start": 3.9, "pad": 10, "dim": 0.1 },
"loupe": { "target": "label-chip", "anchor": [0.2, 0.5], "zoom": 2.3, "size": 0.12, "offset": [-0.15, 0.12] },
"annotations": [
{ "t