关于 bunwv
bunwv 是用于 Agent 驱动浏览器自动化的命令行技能,通过持久化 WebView 会话保留页面状态。命令提供结构化结果与错误,并支持截图和事件捕获。
主要功能
- 01持久化 WebView 会话
- 02浏览器操作与页面等待
- 03DOM 求值和元素检查
- 04表单交互与提交
- 05截图和图像转换
- 06控制台与 CDP 事件捕获
适用场景
- 01自动执行多步骤网站任务
- 02测试表单提交和页面导航
- 03检查页面内容与浏览器错误
- 04在 UI 检查中捕获截图
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ naticha/ bunwv --skill bunwv安装后在 Claude Code 中输入/bunwv确认已加载。
详细介绍
bunwv
使用持久化 WebView 会话进行无头浏览器自动化。守护进程会让单个 WebView 实例持续运行,因此页面状态(DOM、模态框、表单、SPA 路由、滚动位置)会在各条命令之间保持不变。
面向 Agent 的使用模式
bunwv 专为通过离散工具调用驱动它的 AI Agent 设计。以下是一些可依赖的约定:
- 成功的动词命令不会向 stdout 输出任何内容,并以 0 退出。
click、type、navigate、press、scroll、scroll-to、clear、submit、resize、back/forward/reload、close、exists、wait-for、wait-for-gone、cdp-subscribe、cdp-unsubscribe都遵循此规则。读取类命令(status、evaluate、events、console、cdp、cdp-subscriptions、screenshot、sessions)会输出结果。 - 错误会以 JSON 格式写入 stderr,并使用稳定的退出码。 根据退出码分支,而不是根据 stderr 文本:
0正常,1通用错误,2用法错误,3超时,4未找到元素,5守护进程无法访问,6批处理部分失败(仅用于batch --keep-going)。
- 执行命令期间,
console.error/console.warn会自动显示。 如果页面在某条命令运行时记录了错误,bunwv 会将{"console":[…]}输出到 stderr。无需再次调用即可看到失败信息。 - 使用游标拉取事件。
events --since <seq>会返回游标之后的新条目以及一个新游标。请在不同轮次之间保留游标,并在执行操作后重新获取。如果缓冲区已淘汰较早的条目,响应会包含"truncated":true,"oldest":<seq>。 - 二进制输出使用文件路径。 默认情况下,
bunwv screenshot会将字节写入/tmp/bunwv-screenshot-<session>.jpg(JPEG @ q80),并将路径输出到 stdout。使用该路径调用 Read 工具查看图像。 - 使用
--json获取统一封装格式。 任何命令加上--json都会以单行 JSON 返回{ok, data?, error?, exitCode}。如果你更倾向于使用统一格式而不是简洁输出,请使用此选项。 - 标志语法灵活。
--flag value、--flag=value和重复标志(例如--mod Shift --mod Control)都有效。标志可以放在命令之前或之后:bunwv --json status和bunwv status --json等效。 BUNWV_SESSION环境变量 — 设置一次后,--session就是可选的。
命令
使用 bunwv <command> 运行所有命令(通过 bun install -g @naticha/bunwv 全局安装)。
bunwv start [--width N] [--height N] [--data-store PATH] [--idle-timeout ms]
[--backend webkit|chrome] [--chrome-path PATH] [--chrome-argv '[json]']
[--chrome-url <ws-url>] [--url <initial-url>]
bunwv navigate <url>
bunwv click --selector <css> | --text <text> | --at <x,y>
[--text-match exact|contains|regex] # default: contains (trimmed)
[--button left|right|middle] [--count 1|2|3]
[--mod Shift] [--mod Control] [--mod Alt] [--mod Meta]
[--timeout ms]
bunwv exists <selector> # silent; exit 0 if present, 4 if not
bunwv type <text>
bunwv press <key> [--mod Shift] [--mod Control] ...
bunwv clear <selector>
bunwv submit [--form <selector>] [--button <text>]
bunwv scroll <dx> <dy>
bunwv scroll-to <selector> [--block start|center|end|nearest] [--timeout ms]
bunwv screenshot [--format png|jpeg|webp|avif|heic] [--quality 0-100]
[--max-width N] [--max-height N]
[--placeholder | --metadata]
[--encoding blob|buffer|base64|shmem] [--out <path>|-]
bunwv image <input> [--out <path>|-] [--format ...] [--quality N]
[--resize WxH | --max-width N | --max-height N]
[--rotate 90|180|270] [--flip] [--flop] [--metadata] [--placeholder]
bunwv evaluate <expression>
bunwv console [--clear] [--since <seq>] # terse "<seq> [<level>] <message>", cursor-based
bunwv events [--since <seq>]
bunwv cdp <method> [--params '{}']
bunwv cdp-subscribe <CDP.event> [<CDP.event> ...]
bunwv cdp-unsubscribe <CDP.event> [<CDP.event> ...]
bunwv cdp-subscriptions
bunwv wait-for <selector> | --url <substring> | --title <substring>
[--timeout ms]
bunwv wait-for-gone <selector> | --url <substring> | --title <substring>
[--timeout ms]
bunwv batch [--file <path>] [--keep-going] # stdin NDJSON of JSON arrays
bunwv status [--json]
bunwv resize <width> <height>
bunwv back / forward / reload
bunwv sessions
bunwv close [--all]
bunwv help
所有命令都接受 --json(可选封装格式)、--session <name>(或 BUNWV_SESSION 环境变量),以及上文所述的灵活标志语法。
默认视口为 1920x1080,以便清晰查看截图。
会话管理
会话是命名且彼此隔离的。每个会话都在独立的 Unix 套接字上运行自己的守护进程。默认会话名为 default;可通过 --session <name> 或 BUNWV_SESSION 环境变量指定其他会话。
bunwv start # starts "default" session
bunwv start --session cmais # separate "cmais" session
BUNWV_SESSION=cmais bunwv navigate http://localhost:3000
bunwv sessions # list all running sessions
bunwv close --session cmais # stop a specific session
bunwv close --all # stop every running session
自动关闭:守护进程在闲置 30 分钟后退出。可通过 --idle-timeout 覆盖此设置:
bunwv start --idle-timeout 3600000 # 1 hour
bunwv start --idle-timeout 0 # never auto-shutdown
复用检测:如果会话已存在,运行 bunwv start 会输出当前 URL 并以 0 退出。
最佳实践:在对话开始时运行 bunwv sessions,检查是否存在孤立的守护进程。使用 bunwv close --all 关闭不需要的会话。
核心交互循环
先观察,再操作,然后再次观察。典型的单轮循环如下:
- 启动守护进程:
bunwv start(如果已在运行则不执行任何操作) - 导航:
bunwv navigate http://localhost:3000 - 截图:
bunwv screenshot— 将/tmp/bunwv-screenshot-<session>.png输出到 stdout - 使用 Read 工具读取截图
- 操作:
bunwv click --selector "button.submit"(或--text) - 在下一次截图前运行
bunwv wait-for --url "/next"(或wait-for "<selector>") - 再次截图以验证
- 任务完成后运行
bunwv close
对于多步骤流程,优先使用 bunwv batch(见下文)——它会在一个进程中运行整个序列,并返回可供检查的 NDJSON 记录。
点击元素
click 是多态的——恰好使用 --selector、--text 或 --at 中的一个:
bunwv click --selector "button.submit"
bunwv click --text "Sign In" # default: trimmed contains match
bunwv click --text "Sign In" --text-match exact
bunwv click --text "^Sign.+In$" --text-match regex
bunwv click --at 100,200
修饰键、按钮和点击次数彼此独立:
bunwv click --selector "#ctx" --button right # context menu
bunwv click --selector ".item" --count 2 # double-click
bunwv click --selector "a" --mod Shift # shift+click
bunwv click --selector "a" --mod Meta --mod Shift # cmd+shift+click
bunwv click --selector "button" --timeout 60000 # longer actionability wait
--text 默认使用去除首尾空白的子字符串匹配(区分大小写)。使用 --text-match exact 进行严格相等匹配,或使用 --text-match regex 匹配正则表达式模式。--selector 和 --text 都会在等待元素可操作后生成原生的 isTrusted: true 事件;--at 则会跳过等待。
清空和编辑输入字段
不要使用 Cmd+A / Backspace 清空 React 输入框——这不会更新 React 状态。请使用 clear:
bunwv clear "input[name='email']"
bunwv click --selector "input[name='email']"
bunwv type "[email protected]"
编辑现有输入值时,务必先 clear,然后 click,再 type。
等待元素、URL 或标题
执行会触发页面变化的操作后,使用 wait-for:
bunwv click --text "Save Changes"
bunwv wait-for-gone "[role='dialog']" # wait for modal to close
bunwv screenshot
bunwv click --text "Edit"
bunwv wait-for "[role='dialog']" # wait for modal to appear
bunwv wait-for --url "/dashboard" # wait until URL contains substring
bunwv wait-for --title "Home" # wait until <title> contains substring
--url 会轮询 location.href,--title 会轮询 document.title。必须且只能指定 <selector>、--url、--title 中的一个。默认超时时间为 10s。
检查是否存在
使用 exists 进行低成本探测(不输出内容;存在时退出码为 0,不存在时为 4):
bunwv exists "[data-loaded]"
if [ $? -eq 0 ]; then ... fi
优先使用 exists,而不是 evaluate "!!document.querySelector(...)"——所需令牌更少,行为也更清晰。
提取页面数据
evaluate 会将结果以 JSON 字面量形式输出——字符串保留引号,数字不带引号,对象则以结构化 JSON 形式返回:
bunwv evaluate "document.title" # "Example"
bunwv evaluate "document.querySelectorAll('.error').length" # 3
bunwv evaluate "[...document.querySelectorAll('h2')].map(h => h.textContent)"
语句(const、let、if 等)会自动包装在 IIFE 中。
提交表单
使用 submit,而不是点击提交按钮——它会调用 form.requestSubmit(),React 表单可以接受此方式(JS .click() 生成的 isTrusted:false 会被许多 React 处理程序忽略):
bunwv submit # first form on page
bunwv submit --button "Save Changes" # submit via a specific button
bunwv submit --form "form.edit-quote" # target a specific form
提交后,等待 DOM 发生相应变化:
bunwv submit --button "Save Changes"
bunwv wait-for-gone "[role='dialog']"
bunwv screenshot
填写表单
先点击输入框,然后输入。使用 Tab 在字段间移动:
bunwv click --selector "input[name='email']"
bunwv type "[email protected]"
bunwv press Tab
bunwv type "password123"
bunwv submit --button "Sign In"
凭据放在 .env 中(Bun 会自动加载)。Shell 会在 CLI 参数中展开 $VAR:
bunwv type "$TEST_EMAIL"
持久化身份验证
使用 --data-store,在守护进程重启后保留 cookie 和 localStorage:
bunwv start --data-store ./bunwv-session
登录一次,后续会话便会保持已验证状态。
使用控制台捕获进行调试
页面控制台输出会自动捕获。动词执行期间触发的 console.error/console.warn 条目会与动词响应一起输出到 stderr。要获取完整缓冲区:
bunwv console # terse: "<seq> [<level>] <message>", one per line
bunwv console --clear # print then clear
bunwv console --since 42 # only entries with seq > 42 (matches events cursor model)
bunwv --json console # {messages:[…], cursor, truncated?, oldest?}
简洁输出会对消息中的 \n 和 \r 进行转义,使每条记录都保持在一行内。缓冲区为空时不输出内容(退出码为 0)。将你看到的最大 seq(每行的第一个字段)用作 --since 的值,以推进读取位置。需要原始消息文本(例如多行堆栈跟踪)或截断标记时,使用 --json。
导航和 CDP 事件
导航事件和已订阅的 CDP 事件会进入环形缓冲区。使用游标获取它们:
bunwv events # full buffer, prints {events, cursor}
bunwv events --since 42 # only events with seq > 42
订阅 CDP 事件(仅限 Chrome 后端;需先启用对应域)。每次调用可指定多个类型:
bunwv cdp Network.enable
bunwv cdp-subscribe Network.responseReceived Network.requestWillBeSent
bunwv navigate https://example.com
bunwv events --since 0 # inspect events
bunwv cdp-unsubscribe Network.responseReceived Network.requestWillBeSent
bunwv cdp-subscriptions # list active subscriptions, one per line
如果缓冲区移除了较旧的条目,events 会返回 "truncated":true,"oldest":<seq>。
截图选项
默认情况下,截图会以 JPEG 格式(质量为 80)写入会话专属文件,并输出文件路径。默认使用 JPEG,是因为它通常比相同视口尺寸的 PNG 小 5–15×,从而大幅降低截图循环的成本。
bunwv screenshot # /tmp/bunwv-screenshot-<session>.jpg (JPEG @ q80)
bunwv screenshot --format png # opt back into PNG for pixel-exact comparisons
bunwv screenshot --format webp --quality 70 # smaller again at the cost of decode speed
bunwv screenshot --max-width 1024 # cap longest side; aspect preserved; never upscales
bunwv screenshot --max-width 800 --max-height 600 # bound both axes
bunwv screenshot --out shot.jpg # write to a specific path
bunwv screenshot --out - # bytes to stdout
bunwv screenshot --encoding base64 # base64 string to stdout
无需完整像素时的低成本预览:
bunwv screenshot --metadata # {"width":1920,"height":1080,"format":"png"} — size-check before Reading
bunwv screenshot --placeholder # data:image/png;base64,... (tiny blur-up preview, no file)
--placeholder 和 --metadata 会在 stdout 输出结构化结果,而不是写入文件;二者不能同时使用。它们不能与 --encoding shmem 组合使用。
**平台说明:**在 Apple Silicon 上运行的 macOS/Windows 才支持编码 --format avif 和 --format heic。其他平台会返回结构化错误。为确保代码可移植,请使用 jpeg/webp/png。
--encoding shmem(Kitty 终端)会输出 {name, size},并将 POSIX shm 段留给调用方取消链接。它会绕过 Bun.Image 流程,因此与之组合使用时会拒绝 --max-width/--max-height/--placeholder/--metadata。
转换任意图像:bunwv image
对于截图循环之外的本地图像文件(上传文件、下载的资源、测试夹具),bunwv image 会直接在 CLI 进程中运行 Bun.Image——无需守护进程,也无需会话。
bunwv image input.png # input.jpg (jpeg @ q80, same dir)
bunwv image input.png --out small.webp --max-width 512 # webp, capped at 512 wide, aspect preserved
bunwv image input.png --resize 200x200 # explicit resize (may distort if aspect differs)
bunwv image input.png --rotate 90 # rotate 90/180/270
bunwv image input.png --flip # vertical flip; --flop is horizontal
bunwv image input.png --metadata # {"width","height","format"} JSON on stdout
bunwv image input.png --placeholder # data: URL on stdout
如果未指定 --format,输出格式会根据 --out 的扩展名推断;否则默认为 JPEG。如果省略 --out,结果会以新扩展名写入输入文件旁边。
Chrome 后端与 CDP
macOS 默认使用 WebKit;Linux/Windows 会自动使用 Chrome。可在任意位置覆盖:
bunwv start --backend chrome
bunwv start --chrome-path /path/to/chromium
bunwv start --chrome-argv '["--headless=new"]'
bunwv start --chrome-url ws://127.0.0.1:9222/devtools/browser/<id> # attach to an existing Chrome
原始 CDP 调用(仅限 Chrome):
bunwv cdp "Page.getLayoutMetrics"
bunwv cdp "Runtime.evaluate" --params '{"expression": "1+1"}'
bunwv cdp "Emulation.setDeviceMetricsOverride" --params '{"width":375,"height":812,"deviceScaleFactor":2,"mobile":true}'
WebKit 后端不支持 CDP。
调试崩溃的后端
将后端进程的标准输入输出重定向到守护进程的标准输入输出(仅供人工调试;Agent 无需使用这些功能):
bunwv start --backend chrome --chrome-stderr inherit
bunwv start --webkit-stderr inherit
批处理模式
bunwv batch 会在单个进程中执行多条命令——每个动词只需一次 socket 往返,无需为每条命令单独启动 Bun。每一行标准输入都是一个 JSON 参数数组;每个响应都是标准输出中的一个 NDJSON 信封。batch 上的标志(例如 --session)会应用于每一行,除非该行指定了自己的标志。
$ cat <<'EOF' | bunwv batch --session cmais --keep-going
["navigate","http://localhost:3000/login"]
["click","--selector","input[name='email']"]
["type","[email protected]"]
["press","Tab"]
["type","hunter2"]
["submit","--button","Sign In"]
["wait-for","--url","/dashboard"]
["screenshot"]
EOF
{"argv":[...],"ok":true,"exitCode":0}
{"argv":[...],"ok":true,"exitCode":0}
...
如果某一行失败,--keep-going 仍会执行完整列表;只要有任何行失败,进程就会以 6(batch-partial)退出;如果全部成功,则以 0 退出;如果关闭了 --keep-going,则以失败行对应的退出码退出。--file <path> 会从文件读取,而不是从标准输入读取。
stdout 字段包含命令的简短输出(例如,evaluate 的输出为 "\"Example Domain\"");stdoutBytes 是二进制输出(例如 screenshot --out -)的 base64 编码。
错误恢复
如果命令失败或超时:
- 截图查看页面状态
- 运行
bunwv console查看捕获到的错误 - 运行
bunwv events --since 0查看导航/CDP 事件 - 运行
bunwv evaluate检查 DOM - 如果无法访问守护进程(退出码 5),运行
bunwv start——数据存储会保留身份验证信息
Socket 权限
每个会话的 Unix socket(/tmp/bunwv-<session>.sock)和 PID 文件都设置为 chmod 0600——只有启动守护进程的用户才能与其通信。在共享机器(容器、构建机)上,这可以防止其他本地用户控制你的浏览器会话。
已知限制
- macOS:默认使用 WebKit(无依赖项)。Linux/Windows:自动检测 Chrome。使用
--backend覆盖。 click --selector/--text会自动等待元素可操作(可见、稳定且未被遮挡);WebView 默认超时为 30s,可通过--timeout覆盖。- **
--text默认使用去除首尾空白后的子字符串匹配(包含匹配)。**使用--text-match exact|regex更改匹配方式。 --at会跳过可操作性等待——需要知道精确坐标。如果 CSS/文本不起作用,请使用evaluate+getBoundingClientRect()。- React 输入框需要使用
clear——Cmd+A/Backspace 不会更新 React 的内部状态。 - **CDP 仅支持 Chrome。**WebKit 不支持
bunwv cdp和bunwv cdp-subscribe。 - 事件缓冲区:最多保存 1000 条记录或 10 MB,以先达到者为准。较早的记录会被静默丢弃;如果有记录遗漏,
events --since会报告truncated。