内容
<h1 align="center">vibesurfer (<code>vs</code>)</h1>
<p align="center"><strong>您本地 AI 代理的真正浏览器。</strong></p>
<p align="center">
<a href="https://github.com/frane/vibesurfer/actions/workflows/ci.yml"><img alt="ci" src="https://img.shields.io/github/actions/workflow/status/frane/vibesurfer/ci.yml?branch=main&label=ci&style=flat-square"></a>
<a href="https://github.com/frane/vibesurfer/actions/workflows/engine-tests.yml"><img alt="engine-tests" src="https://img.shields.io/github/actions/workflow/status/frane/vibesurfer/engine-tests.yml?branch=main&label=engine-tests&style=flat-square"></a>
<a href="https://github.com/frane/vibesurfer/releases/latest"><img alt="release" src="https://img.shields.io/github/v/release/frane/vibesurfer?style=flat-square"></a>
<a href="https://github.com/frane/vibesurfer/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/badge/license-Apache_2.0-blue?style=flat-square"></a>
</p>
<p align="center">
<img src="https://github.com/frane/vibesurfer/raw/main/docs/demo-claude.gif" alt="Claude 代码使用 vibesurfer" width="560">
</p>
## 为什么
我希望代理可以通过浏览器测试 Web 应用程序。我尝试过的所有方法(Playwright、Puppeteer 以及其他包装 CDP 的工具)都太过笨重且不稳定。CDP 会丢失会话。Playwright 在长时间运行时会崩溃。Chrome 每次发布都会变得更大。这些都不是实际的问题。CDP 和 Chrome 是为人类盯着 DevTools 设计的。它们从来不是为陷入无限循环的代理设计的。
代理按 token 计费。它会因响应而阻塞。它无法处理事件洪流,而每次读取 4kb 的 DOM 转储会迅速消耗上下文预算。通过 Playwright,Hacker News 首页大约需要 2000 个输入 token 才能让代理执行任何操作。而通过 vibesurfer,大约只需要 50 个 token。
vibesurfer 是一个用 Rust 编写的原生浏览器守护进程。读取操作会返回状态 token 和树差异,而不是完整的 DOM。写入操作会检查 token。如果在读取和写入之间有任何变动,调用操作会失败,代理会重新读取而不是点击过时的页面。在底层,有三个真正的引擎:macOS 上的 WKWebView,Linux 上的 WebKitGTK,Windows 上的 WebView2。在此之上的协议是文本和面向行的。
当您真正需要像素时,可以使用 `vs capture` 进行截图,`vs viewport` 切换移动和桌面布局,`vs layout` 获取边界框。但文本优先。
## 安装
Homebrew(macOS,Linux):
```bash
brew tap frane/tap && brew install vibesurfer
```
curl:
```bash
curl -sSL https://raw.githubusercontent.com/frane/vibesurfer/main/install.sh | sh
```
Cargo:
```bash
cargo install vibesurfer
```
从源码:
```bash
git clone https://github.com/frane/vibesurfer && cd vibesurfer
cargo install --path crates/vs-cli
```
Linux 需要 WebKitGTK 6。Windows 需要 WebView2 运行时(已包含在 Windows 11 中,Windows 10 可从 Microsoft 获取)。
## 将其连接到您的代理
两种集成路径,它们是独立的。您可以安装其中一个或两个:
- **技能**:将 `SKILL.md` 放入代理的技能目录中。代理将其作为上下文读取,并直接通过其 shell 调用 `vs` 二进制文件。在不支持 MCP 的情况下,请为任何运行 Bash 的代理使用此方法。
- **MCP**:将 `vs mcp` 注册为 MCP 服务器。代理通过 JSON-RPC 将 vibesurfer 原语调用为 MCP 工具,而无需 shell。在具有原生 MCP 支持的代理中使用此方法。
自动安装程序在支持的情况下同时执行这两个操作。在将 `vs` 添加到您的 PATH 之后:
```bash
vs skill install
```
它检测到 Claude Desktop、Claude Code、Cursor、Codex CLI、Gemini CLI 和 OpenClaw,然后将 SKILL.md 和 MCP 条目写入每个文件中。仅支持其中一个的代理只会获得相关部分。升级后请重新运行。
### 手动操作
对于 **技能路径**,请将存储库中的 `skills/vibesurfer/SKILL.md` 复制到代理的技能目录中。对于 Claude 家族代理,通常是 `~/.claude/skills/vibesurfer/SKILL.md`。
对于 **MCP 路径**,请将以下内容添加到代理的 MCP 配置文件(`claude_desktop_config.json`、`.cursor/mcp.json` 等)中:
```json
{
"mcpServers": {
"vibesurfer": {
"command": "vs",
"args": ["mcp"]
}
}
}
```
Codex 使用具有相同形状的 TOML,位于 `[mcp_servers.vibesurfer]` 下。JSON 形式也位于 `plugin/.mcp.json`,如果您想从存储库中复制它。
### 每个代理的快捷方式
**Claude Code 市场** 通过一个命令安装两个界面:
```bash
/plugin install frane/vibesurfer
```
解析存储库根目录下的 `.claude-plugin/marketplace.json` 和 `plugin/.claude-plugin/plugin.json`。
**Gemini 扩展** 连接 MCP 服务器和 GEMINI.md 上下文文件:
```bash
gemini extensions install https://github.com/frane/vibesurfer
```
读取存储库根目录下的 `gemini-extension.json`。
## 未被检测为自动化
现代反机器人系统(Cloudflare、hCaptcha、reCAPTCHA、DataDome)首先检查 `event.isTrusted`,然后检查移动定时,最后检查 TLS/HTTP 指纹识别。vibesurfer 的输入调度程序被设计为通过所有三个检查。
在 macOS 上,`vs act click` 和坐标基元(`vs click-at`、`vs hover-at`、`vs move-to`、`vs drag`)通过原生 `NSEvent` mouseDown / mouseUp / mouseMoved 在 `WKWebView` 上路由。每个事件都带有 `isTrusted = true` 在 JS 中,就像真正的光标点击一样。贝塞尔曲线路径在每次点击之前调度,以复制 Fitts 定律到达时间(基于二元图的控制点,可选超调),因此可见运动看起来像人类到达目标,而不是瞬移。
自 v0.1.11 以来,坐标基元在 Linux 和 Windows 上也是原生的:通过 `x11rb` 上的 XTest(WebKitGTK(X11 / Xwayland; 纯 Wayland 降级为 `ENGINE_UNSUPPORTED`),在 Windows 上通过 WebView2 合成控制器上的 `SendMouseInput`。所有三个引擎在光标基元点击时都会发出 `isTrusted = true`。基于 ref 的 `vs act click` 在 macOS 上受信任 - 在 Linux 和 Windows 上,它仍然通过注入的 JS(`isTrusted = false`)调度; 在这些情况下,对于指纹敏感的站点,请使用坐标基元。
遍历器还遵循 ARIA `role="..."`(Radix UI、Headless UI、Reach UI、每个自定义-div-as-button 模式),以及 focusable divs/spans 的 tabindex 启发式。现代 React UI 作为可操作的 ref 出现,无需坐标变通方法。
## 简写
每个基元都有一个 1-3 个字母的别名。长形式存在于文档中; 代理调用应使用短形式以节省 token。
| 长形式 | 短形式 | | 长形式 | 短形式 |
| --- | --- | --- | --- | --- |
| `session-open` | `so` | | `extract` | `x` |
| `session-close` | `sc` | | `mark` | `m` |
| `open` | `o` | | `annotate` | `an` |
| `close` | `c` | | `status` | `st` |
| `view` | `v` | | `log` | `l` |
| `read` | `r` | | `skill` | `sk` |
| `act` | `a` | | `capture` | `cap` |
| `find` | `f` | | `viewport` | `vp` |
| `wait` | `w` | | `layout` | `lay` |
| `auth` | `au` | | `inspect` | `i` |
常见标志:`--session=` / `-S`, `--full` / `-F`, `--since=` / `-s`, `--limit=` / `-n`, `--page=` / `-P`, `--json` / `-j`。检查子命令具有一个或两个字母的别名(`i co` 表示 `inspect console`,`i n` 表示 `network`,`i req` 表示 `request`,`i e` 表示 `eval`,`i s` 表示 `storage`,`i scr` 表示 `scripts`,`i src` 表示 `script`,`i d` 表示 `dom`,`i p` 表示 `performance`)。
两种形式在任何地方都有效。集成测试断言短形式的线路请求与长形式的线路请求是字节相同的。
## 快速入门
```bash
$ vs so # session-open
@0 # 状态令牌(16 个十六进制字符; 0 表示尚未)
s_019e08a7… # 会话 ID
$ vs o https://example.com # 打开 URL
@0 # 打开调用不携带快照
p_019e08a7… # 页面 ID
$ vs v p_019e08a7… # 视图(快照 a11y 树)
@44d01704049d6d31 # 状态令牌
1 文档 "示例域" # 引用 1,文档
0 元素 "" # 无名包装器
2 hd "示例域" # 引用 2,标题
3 p "此域用于…" # 引用 3,段落
5 p "了解更多" # 引用 5,段落
4 lnk "了解更多" click,focus # 引用 4,链接,支持的操作
```
快照是引用的列表。每个引用都是一个在快照之间生存的整数,因此代理可以在十次循环后对引用 4 进行操作,而无需重新读取整个页面。两字母代码(`hd`、`p`、`lnk`、`btn`、`tf` 等)将角色压缩为几个字节,而不是 ARIA 字符串。标签括在引号中; 标签后的令牌列出 `vs act` 操作支持哪些元素。总共约二十个角色代码,列在 [docs/PROTOCOL.md](docs/PROTOCOL.md) 中。
```bash
$ vs a 4 click # act: 点击引用 4
@<新令牌> # 新令牌,页面已变
?nav # 警告: 发生了导航
… 新树 … # act 响应带有增量;
# 在导航时,它重新基准到完整树
```
`vs act` 是唯一改变状态的基元。它接受一个引用和一个操作(`click`、`fill`、`scroll`、`key`、`submit`、`hover`、`focus`),并且需要最新的状态令牌。如果页面在读取和写入之间发生变化(JS 计时器触发,websocket 推送更新,任何操作),调用返回 `! STALE_TOKEN`,代理会重新读取。没有无声过时点击。在同一页面(无导航)成功执行 act 后,响应仅携带增量(`+ref` 表示添加,`-ref` 表示删除,`~ref` 表示属性更改),因此点击增加一个按钮的成本在线路上约为 20 个字节,而不是整个 DOM。
```bash
$ vs st # 状态
session s_019e08a7… pages=1
page p_019e08a7… url=https://www.iana.org/help/example-domains token=…
```
每个基元调用在返回之前都会写入一行到 SQLite 审计日志中。`vs status` 读取该日志。`vs log` 也是如此。重放、调试和治理都简化为针对 `~/.vibesurfer/state.db` 的 SQL 查询。没有单独的事件流可供订阅。
守护进程在第一次调用时自动生成。状态、捕获和下载位于 `~/.vibesurfer/` 下。传输层是 Unix 上的 AF_UNIX 套接字(`~/.vibesurfer/daemon.sock`)和 Windows 上的 Windows 命名管道; 两种情况下,CLI 都处理差异。
29 个线路基元 - 19 个核心基元在 [docs/PRIMITIVES.md](docs/PRIMITIVES.md) 中指定; 后续添加(`vs_inspect`,四个光标基元,提示输入和待处理队列)在捆绑的 [SKILL.md](crates/vs-cli/SKILL.md) 和 CHANGELOG 中记录。完整的线路格式包含每个 sigil 和边缘情况,在 [docs/PROTOCOL.md](docs/PROTOCOL.md) 中。每个平台的每个基元验证矩阵在 [docs/REALITY_CHECK.md](docs/REALITY_CHECK.md) 中。
## 配置
| 路径/变量 | 目的 |
| --- | --- |
| `~/.vibesurfer/state.db` | SQLite,保存会话,审计,标记,身份验证数据块 |
| `~/.vibesurfer/daemon.sock` *(Unix)* | CLI 与之通信的 AF_UNIX 套接字 |
| Windows 命名管道 | 在 Windows 上具有相同的角色; 自动解析 |
| `~/.vibesurfer/captures/` | `vs capture` 的截图 |
| `VS_CAPTURES_DIR` | 重写捕获目录 |
| `VS_HOME` | 重写 vibesurfer 主目录 |
| `VS_DISABLE_INSPECTOR=1` | 跳过检查钩子(仅限测试) |
| `VS_DAEMON_BIN` | 重写用于守护进程自动生成的二进制文件(测试) |
## 从源代码构建
需要 Rust 1.85+。平台特定依赖项:
- **macOS** (15+): 无需额外依赖,与系统 WebKit 链接。
- **Linux**: `libwebkitgtk-6.0-dev`,`libgtk-4-dev`,`libsoup-3.0-dev`。
- **Windows**: WebView2 SDK 在构建时由 `webview2-com` 拉取; 运行时需要 WebView2 运行时。
```bash
git clone https://github.com/frane/vibesurfer && cd vibesurfer
cargo build --release
```
运行测试套件:
```bash
cargo test --workspace --lib --bins # 快速单元测试
cargo test --workspace # 添加集成测试(真实引擎)
```
对于非 Linux 主机上的 Linux 引擎测试,请使用 Docker 容器。WebKitGTK 6 的沙箱需要无特权的用户名空间; CI Linux 作业通过一个 sysctl 在裸机上放松 AppArmor 限制,而 Docker 回退需要 `--privileged` 来执行相同操作:
```bash
docker build -f Dockerfile.linux-test -t vs-test-linux .
docker run --rm --privileged -v "$PWD":/work vs-test-linux
```
参见 [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) 以获取更长的演练。
存储库顶部的演示 gif 是驱动 vibesurfer 的真实交互式 Claude Code 会话。要捕获一个新的,请运行 `docs/demo/record-claude.sh`:
```bash
brew install asciinema agg
docs/demo/record-claude.sh # 写入 docs/demo-claude.gif
```
脚本强制 TTY 保护,隔离演示主目录,并将 Claude 锁定到 Bash,以便代理必须使用真正的 `vs` 二进制文件(无 MCP 回退,无内置文件工具)。每个渲染都是非确定性的,因为模型输出会有所不同。缓存的 gif 已提交,以便克隆者和 CI 不要重新渲染。
## 贡献
欢迎提出问题和拉取请求。对于超出小修复的任何内容,请先提出问题,以便我们讨论方法。代码库在开发过程中使用 [agented](https://github.com/frane/agented) 进行事务性文件编辑; agented 的工作区状态是本地的(`.agented/state.db`),并且不会被提交。
## 致谢
基于:
- [objc2](https://github.com/madsmtm/objc2),macOS WebKit FFI。
- [webkit6](https://github.com/gtk-rs/gtk-rs-core),Linux 引擎绑定。
- [webview2-com](https://github.com/wravery/webview2-rs),Windows COM 层。
- [interprocess](https://github.com/kotauskas/interprocess),跨平台 IPC 传输。
- [tiny_http](https://github.com/tiny-http/tiny-http),集成测试夹具服务器。
协议借鉴自 [agented](https://github.com/frane/agented),一个 AI 代理编辑器。
## 许可证
Apache-2.0。参见 [LICENSE](LICENSE)。