docs: 项目架构设计文档(中文)及使用指南

- architecture.md: 完整架构设计(技术栈、API、CLI、生命周期、测试策略)
- api.md: REST + WebSocket 接口文档
- cli.md: CLI 命令参考
- contributing.md: 开发规范与 JSON 转义要求
- quickstart.md: 快速开始指南
- llm-integration.md: 智能体集成指南
- examples.md: 8 个典型使用场景
This commit is contained in:
2026-08-12 20:14:54 +08:00
parent 4f7854ab6e
commit 0c437a96f1
9 changed files with 1548 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# Dependencies
node_modules/
# Build output
dist/
*.tsbuildinfo
# Runtime state
.visionl/
# Environment
.env
.env.local
# Logs
*.log
npm-debug.log*
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Test artifacts
coverage/
test-results/
+32
View File
@@ -1,2 +1,34 @@
# VisionL # VisionL
面向 AI 智能体的开源可持久化浏览器。
LLM 通过 `VisionL-cli` 工具调用操控网页——打开页面、点击、输入、截图、提取内容。
页面独立持久化,关闭任何窗口都不会杀死后台页面。
## 特性
- **智能体优先**:CLI 子命令专为 LLM 工具调用设计,输出结构化 JSON
- **页面持久化**:页面存活不受客户端断连影响,只有显式 `kill` 才终止
- **全功能自动化**:点击、输入、滚动、截图、JS 执行、网络拦截
- **多页面并行**:同时管理多个页面,ID + 别名双轨引用
- **自动拉起**:CLI 自动检测并启动后台 daemon
## 文档
| 文档 | 说明 |
|------|------|
| [快速开始](docs/usage/quickstart.md) | 安装和基本使用 |
| [LLM 集成](docs/usage/llm-integration.md) | 在智能体中集成 VisionL |
| [使用示例](docs/usage/examples.md) | 典型场景 |
| [架构设计](docs/development/architecture.md) | 整体架构 |
| [API 文档](docs/development/api.md) | REST + WebSocket 接口 |
| [CLI 命令](docs/development/cli.md) | 命令参考 |
| [开发规范](docs/development/contributing.md) | 贡献指南 |
## 技术栈
TypeScript + Node.js + Playwright + Chromium
## License
待定
+345
View File
@@ -0,0 +1,345 @@
# VisionL REST API 接口文档
> 适用版本:v1
## 通用约定
### Base URL
```
http://127.0.0.1:9527
```
端口可通过启动参数 `--port` 修改。
### 响应格式
所有响应统一为 JSON
```json
// 成功
{
"ok": true,
"data": { ... }
}
// 失败
{
"ok": false,
"error": {
"code": "PAGE_NOT_FOUND",
"message": "页面 p_xyz 不存在"
}
}
```
### 错误码
| 错误码 | HTTP 状态码 | 说明 |
|--------|-----------|------|
| `PAGE_NOT_FOUND` | 404 | 指定页面不存在 |
| `DAEMON_UNREACHABLE` | 502 | daemon 不可达(CLI 端产生) |
| `INVALID_URL` | 400 | URL 格式不合法 |
| `ALIAS_EXISTS` | 409 | 别名已被使用 |
| `TIMEOUT` | 408 | 操作超时 |
| `INTERNAL` | 500 | 内部错误 |
### JSON 转义
所有响应通过 `JSON.stringify` 序列化。页面内容(如 `text``html` 字段)中的
引号、反斜杠、控制字符均被正确转义,外层 JSON 始终合法。
---
## 页面管理
### 打开页面
```
POST /pages
```
**请求体:**
```json
{
"url": "https://example.com",
"alias": "demo" // 可选
}
```
**响应:**
```json
{
"ok": true,
"data": {
"id": "p_a1b2c3d4",
"url": "https://example.com",
"alias": "demo",
"title": "Example Domain",
"status": "active"
}
}
```
### 列出所有页面
```
GET /pages
```
**响应:**
```json
{
"ok": true,
"data": [
{
"id": "p_a1b2c3d4",
"url": "https://example.com",
"alias": "demo",
"title": "Example Domain",
"status": "active"
}
]
}
```
### 页面详情
```
GET /pages/:id
```
**响应:** 同上 `data` 中的单个对象。
### 杀死页面
```
DELETE /pages/:id
```
**响应:**
```json
{ "ok": true, "data": null }
```
---
## 页面操作
### 跳转
```
POST /pages/:id/navigate
```
**请求体:**
```json
{ "url": "https://new-url.com" }
```
**响应:**
```json
{
"ok": true,
"data": {
"url": "https://new-url.com",
"title": "New Page"
}
}
```
### 点击元素
```
POST /pages/:id/click
```
**请求体:**
```json
{ "selector": "#login-button" }
```
**响应:**
```json
{ "ok": true, "data": { "success": true } }
```
### 输入文本
```
POST /pages/:id/type
```
**请求体:**
```json
{
"selector": "#username",
"text": "hello world"
}
```
**响应:** 同 click。
### 滚动
```
POST /pages/:id/scroll
```
**请求体(至少提供一个):**
```json
{
"deltaY": 500,
"toBottom": true
}
```
**响应:** 同 click。
### 执行 JavaScript
```
POST /pages/:id/eval
```
**请求体:**
```json
{
"code": "document.title"
}
```
**响应:**
```json
{
"ok": true,
"data": {
"result": "Example Domain"
}
}
```
> 注意:`result` 类型取决于 JS 代码返回值,可以是 string、number、boolean、object 或 null。
### 等待条件
```
POST /pages/:id/wait
```
**请求体(至少提供一个):**
```json
{
"selector": ".loaded",
"ms": 2000
}
```
**响应:** 同 click。
---
## 内容获取
### 截图
```
GET /pages/:id/screenshot
```
**响应:**
```json
{
"ok": true,
"data": {
"base64": "iVBORw0KGgoAAAANS...",
"mime": "image/png"
}
}
```
### 纯文本
```
GET /pages/:id/text
```
**响应:**
```json
{
"ok": true,
"data": {
"text": "页面正文内容..."
}
}
```
### HTML
```
GET /pages/:id/html
```
**响应:**
```json
{
"ok": true,
"data": {
"html": "<!DOCTYPE html>..."
}
}
```
---
## Daemon 管理
### 健康检查
```
GET /health
```
**响应:**
```json
{ "status": "ok" }
```
---
## WebSocket 接口
### 连接
```
ws://127.0.0.1:9527/ws
```
### 事件类型
所有事件 JSON 格式:`{ "type": "...", "data": { ... } }`
| 事件 | 方向 | 说明 |
|------|------|------|
| `page:created` | daemon → 客户端 | 新页面打开 |
| `page:closed` | daemon → 客户端 | 页面被杀死 |
| `page:navigated` | daemon → 客户端 | 页面 URL 发生变化 |
| `page:crashed` | daemon → 客户端 | Playwright 页面崩溃 |
| `page:console` | daemon → 客户端 | 页面控制台输出(调试) |
+348
View File
@@ -0,0 +1,348 @@
# VisionL 架构设计
> 面向 AI 智能体的开源可持久化浏览器 — 架构设计文档
## 1. 概述
VisionL 是一款专为具备本地读写执行能力的 AI 智能体设计的开源浏览器。
LLM 通过 `VisionL-cli` 工具调用来操控网页。页面独立持久化——关闭 GUI 窗口不会杀死页面后台,
每个页面只能通过 CLI 参数或未来 GUI 的右键菜单按钮来关闭。
### 核心原则
- **页面持久化**:页面在客户端断连后依然存活,只有显式 `kill` 才能终止
- **智能体优先**:CLI 子命令为 LLM 工具调用而设计,输出结构化 JSON
- **本地优先**Daemon 仅监听 localhost,不对外开放
- **GUI 就绪**HTTP/WS API 同时服务于 CLI 和未来的 GUI,避免重复实现
---
## 2. 技术栈
| 层级 | 选择 | 理由 |
|------|------|------|
| 浏览器引擎 | Playwright + Chromium | AI 浏览器自动化的事实标准 |
| 语言 | TypeScript + Node.js | Playwright 原生语言,生态最优 |
| 通信协议 | HTTP REST + WebSocket (localhost) | 职责分离,GUI 可复用同一套 API |
| 包管理 | npm workspaces (monorepo) | 共享类型,单仓库管理 |
| CLI 框架 | commander + chalk | 轻量、社区熟知 |
| HTTP 服务 | 原生 `http` + `ws` 库 | 本地 daemon 无需重型框架 |
### JSON 转义处理
所有返回给智能体的 JSON 必须正确处理特殊字符转义。页面内容(文本、HTML)中可能包含
引号、反斜杠、控制字符、Unicode 代理对等,统一通过 `JSON.stringify` 序列化整个响应对象,
禁止手动拼接 JSON 字符串。
---
## 3. 项目结构
```
VisionL/
├── docs/
│ ├── development/
│ │ ├── architecture.md # 本文档
│ │ ├── api.md # REST + WS 接口文档
│ │ ├── cli.md # CLI 命令参考
│ │ └── contributing.md # 开发规范
│ └── usage/
│ ├── quickstart.md # 快速开始
│ ├── llm-integration.md # 智能体集成指南
│ └── examples.md # 典型场景示例
├── packages/
│ ├── core/ # 共享库:类型定义、HTTP 客户端、工具函数
│ │ ├── src/
│ │ │ ├── types/
│ │ │ │ ├── page.ts # Page 结构
│ │ │ │ ├── api.ts # 请求/响应类型
│ │ │ │ └── ws.ts # WebSocket 事件类型
│ │ │ ├── client.ts # Daemon HTTP + WS 客户端(CLI 和 GUI 共用)
│ │ │ ├── escape.ts # JSON 安全转义
│ │ │ └── index.ts
│ │ └── package.json
│ ├── daemon/ # 后台进程:Playwright 管理 + HTTP/WS 服务
│ │ ├── src/
│ │ │ ├── server.ts # HTTP + WS 入口
│ │ │ ├── routes/
│ │ │ │ ├── pages.ts # 页面 CRUD
│ │ │ │ ├── actions.ts # click/type/scroll/eval/navigate
│ │ │ │ ├── content.ts # screenshot/text/html
│ │ │ │ └── health.ts # 健康检查
│ │ │ ├── browser-manager.ts # Playwright 浏览器/页面生命周期
│ │ │ ├── page-registry.ts # 内存页面表(id → Playwright Page
│ │ │ ├── ws-relay.ts # WebSocket 事件广播
│ │ │ └── pidfile.ts # Daemon 进程管理
│ │ └── package.json
│ └── cli/ # CLI 工具:用户 + LLM 交互入口
│ ├── src/
│ │ ├── index.ts # CLI 入口(commander
│ │ ├── commands/
│ │ │ ├── page.ts # visionl page open/close/list/info
│ │ │ ├── action.ts # visionl click/type/scroll/eval/wait/navigate
│ │ │ ├── view.ts # visionl screenshot/text/html
│ │ │ └── daemon.ts # visionl daemon start/stop/status
│ │ ├── format.ts # 输出格式化(JSON/pretty
│ │ └── auto-daemon.ts # 自动拉起 daemon 逻辑
│ └── package.json
├── package.json # monorepo root
├── tsconfig.json
├── .gitignore
├── README.md
└── LICENSE
```
---
## 4. Daemon 设计
### 4.1 REST API
| 方法 | 路径 | 说明 | 请求体 | 响应 |
|------|------|------|--------|------|
| POST | `/pages` | 打开新页面 | `{ url, alias? }` | `{ id, url, alias?, title, status }` |
| GET | `/pages` | 列出所有页面 | — | `[{ id, url, alias?, title, status }]` |
| GET | `/pages/:id` | 页面详情 | — | `{ id, url, alias?, title, status }` |
| DELETE | `/pages/:id` | 杀死页面 | — | `{ ok: true }` |
| POST | `/pages/:id/navigate` | 跳转 URL | `{ url }` | `{ url, title }` |
| POST | `/pages/:id/click` | 点击元素 | `{ selector }` | `{ success }` |
| POST | `/pages/:id/type` | 输入文本 | `{ selector, text }` | `{ success }` |
| POST | `/pages/:id/scroll` | 滚动页面 | `{ deltaY?, toBottom? }` | `{ success }` |
| POST | `/pages/:id/eval` | 执行 JS | `{ code }` | `{ result }` |
| POST | `/pages/:id/wait` | 等待条件 | `{ selector?, timeout?, ms? }` | `{ success }` |
| GET | `/pages/:id/screenshot` | 页面截图 | — | `{ base64, mime }` |
| GET | `/pages/:id/text` | 页面纯文本 | — | `{ text }` |
| GET | `/pages/:id/html` | 页面 HTML | — | `{ html }` |
| GET | `/health` | 健康检查 | — | `{ status: "ok" }` |
### 4.2 WebSocket 事件
客户端通过 `ws://127.0.0.1:{port}/ws` 建立连接,daemon 主动推送:
| 事件 | 数据 | 触发时机 |
|------|------|---------|
| `page:created` | `{ id, url, alias?, title }` | 新页面打开 |
| `page:closed` | `{ id }` | 页面被杀死 |
| `page:navigated` | `{ id, url, title }` | 页面 URL 变化 |
| `page:crashed` | `{ id, error }` | 页面崩溃 |
| `page:console` | `{ id, level, text }` | 控制台输出(调试用) |
### 4.3 页面生命周期
```
visionl page open https://example.com --alias demo
→ POST /pages { url, alias: "demo" }
→ daemon 创建 Playwright Context + Page
→ 注册到 PageRegistry { id: "p_abc123", page, alias: "demo" }
→ 返回 { id: "p_abc123", url, alias: "demo", title, status: "active" }
visionl page screenshot p_abc123
→ GET /pages/p_abc123/screenshot
→ daemon 调用 page.screenshot(),返回 base64
visionl page kill p_abc123
→ DELETE /pages/p_abc123
→ daemon 调用 page.close()、context.close()
→ 从 PageRegistry 移除
```
**规则:**
- 页面在被显式 `kill` 之前永远存活,无超时、无闲置清理
- Daemon 重启不会恢复之前的页面(v1 不包含此功能)
- `visionl daemon stop` 优雅退出 daemon,但不杀死页面(Playwright 浏览器进程独立管理)
---
## 5. CLI 设计
### 5.1 页面管理
```bash
visionl page open <url> [--alias <name>]
visionl page list
visionl page info <id|alias>
visionl page kill <id|alias>
visionl page kill-all
```
### 5.2 页面操作
```bash
visionl click <id|alias> <selector>
visionl type <id|alias> <selector> <text>
visionl scroll <id|alias> [--down <px>] [--bottom]
visionl navigate <id|alias> <url>
visionl eval <id|alias> <js-code>
visionl wait <id|alias> [--selector <sel>] [--ms <n>]
```
### 5.3 内容获取
```bash
visionl screenshot <id|alias> [-o <file.png>]
visionl text <id|alias>
visionl html <id|alias>
```
### 5.4 Daemon 管理
```bash
visionl daemon start [--port <n>]
visionl daemon stop
visionl daemon status
```
### 5.5 REST 直通
```bash
visionl raw POST /pages '{"url":"https://example.com"}'
visionl raw GET /pages
visionl raw GET /pages/p_abc123/text
```
### 5.6 输出格式
默认输出 JSON(供 LLM 解析)。使用 `--pretty` 切换为人类可读格式。
```json
// 成功
{ "ok": true, "data": { ... } }
// 失败
{ "ok": false, "error": { "code": "PAGE_NOT_FOUND", "message": "页面 p_xyz 不存在" } }
```
**JSON 转义规则**:所有响应通过 `JSON.stringify` 序列化完整对象。页面内容字段(`text``html`
作为不透明字符串处理,由 `JSON.stringify` 负责转义,不手动拼接。
### 5.7 自动拉起
CLI 在任何页面/操作命令前执行 daemon 存活检测:
```
1. 向配置端口(默认 9527)发送 GET /health
2. 有响应 → 继续执行
3. 超时/拒绝 → 检查 pidfile~/.visionl/daemon.pid
- pid 存活且端口可达 → 更新端口,继续
- pid 已死或端口不可达 → 清理残留 pidfile,启动 daemon
4. 启动:child_process.spawn('visionl-daemon', ['--port', port])
5. 轮询 GET /health(最多 3 秒,间隔 100ms
6. 就绪 → 继续;超时 → 报错退出
```
**状态文件:**
- `~/.visionl/daemon.pid` — daemon 进程 PID
- `~/.visionl/daemon.port` — daemon 监听端口
---
## 6. 类型定义(core 包)
```typescript
// packages/core/src/types/page.ts
interface PageInfo {
id: string; // 自动生成,如 "p_a1b2c3d4"
url: string;
alias?: string; // 用户指定,可选
title: string;
status: 'active' | 'crashed';
}
// packages/core/src/types/api.ts
interface ApiResponse<T = unknown> {
ok: boolean;
data?: T;
error?: {
code: string;
message: string;
};
}
// 错误码
enum ErrorCode {
PAGE_NOT_FOUND = 'PAGE_NOT_FOUND',
DAEMON_UNREACHABLE = 'DAEMON_UNREACHABLE',
INVALID_URL = 'INVALID_URL',
ALIAS_EXISTS = 'ALIAS_EXISTS',
TIMEOUT = 'TIMEOUT',
INTERNAL = 'INTERNAL',
}
// packages/core/src/types/ws.ts
type WsEvent =
| { type: 'page:created'; data: PageInfo }
| { type: 'page:closed'; data: { id: string } }
| { type: 'page:navigated'; data: { id: string; url: string; title: string } }
| { type: 'page:crashed'; data: { id: string; error: string } }
| { type: 'page:console'; data: { id: string; level: string; text: string } };
```
---
## 7. 关键设计决策
### v1 不包含会话恢复
页面在客户端断连后存活,但 daemon 重启后不会恢复。理由:
- Playwright 浏览器进程状态复杂,难以序列化/恢复
- v1 聚焦 CLI 智能体工作流,daemon 在任务期间保持运行即可
- 会话恢复(跨 daemon 重启保存/恢复页面状态)可在 v2 添加
### 无身份认证
Daemon 仅绑定 `127.0.0.1`,任何本地进程均可连接。这是刻意的设计:智能体与 daemon 运行在同一台机器。v2 可添加基于 token 的认证以支持多用户场景。
### Page ID 与别名解析
- Page ID`p_xxxxxxxx`)始终唯一,自动生成
- 别名由用户指定,必须唯一。重复使用已有别名会报错
- 命令同时接受两种形式:`visionl text myalias``visionl text p_abc123`
- 解析顺序:先尝试别名匹配,再尝试 ID 匹配
### 并发模型
- 每个 daemon 只有一个 Playwright `Browser` 实例
- 每次 `POST /pages` 创建一个新的 `BrowserContext`(隔离 cookie/存储)+ `Page`
- v1 不设并发上限;由操作系统自然限制
- 未来:可配置最大页面数、排队、优先级
---
## 8. 错误处理策略
| 场景 | Daemon 行为 | CLI 行为 |
|------|------------|---------|
| 页面不存在 | 404 + 错误码 | 退出码 1,输出 JSON 错误 |
| Daemon 不可达 | — | 自动拉起或退出码 1 |
| 无效 URL | 400 + 错误码 | 退出码 1,输出 JSON 错误 |
| 选择器未找到 | 操作响应内 404 | 退出码 1,`{ ok: false, error: ... }` |
| 页面崩溃 | `page:crashed` WS 事件,status → crashed | 后续操作返回 500 |
| Daemon 端口被占用 | 启动失败,日志记录 | 退出码 1,建议 `--port` 覆盖 |
---
## 9. 测试策略
| 层级 | 工具 | 范围 |
|------|------|------|
| `core` 类型 | ts 类型检查 | 编译期正确性 |
| `core/client.ts` | vitest + msw | HTTP 客户端行为、错误处理 |
| `daemon/routes` | vitest + playwright-test | 路由逻辑 + 真实 Chromium |
| `daemon/browser-manager` | vitest + playwright-test | 页面生命周期、崩溃恢复 |
| `cli/commands` | vitest + mock 客户端 | 命令解析、输出格式化 |
| `cli/auto-daemon` | vitest + fs mock | Pidfile 逻辑、spawn 行为 |
| 集成测试 | vitest + 真实 daemon | CLI → daemon → Playwright 端到端 |
| JSON 转义 | vitest | 验证特殊字符、Unicode、XSS 向量安全 |
---
## 10. 未来规划(v1 范围外)
- `visionl-gui`:基于 Electron/Tauri 的桌面 GUI,用于查看和管理页面
- 会话持久化:跨 daemon 重启保存/恢复页面状态
- 多用户支持:基于 token 的认证
- 插件系统:自定义页面处理器
- 远程 daemon 支持(突破 localhost 限制)
- 网络拦截和 Mock API
- Cookie/存储管理 CLI
- 页面录制与回放
+286
View File
@@ -0,0 +1,286 @@
# VisionL CLI 命令参考
> 适用版本:v1
## 安装
```bash
# 全局安装(需要先构建)
npm install -g ./packages/cli
# 或开发模式
cd packages/cli && npm link
```
安装后可使用 `visionl` 命令。
## 全局选项
| 选项 | 说明 |
|------|------|
| `--pretty` | 人类可读格式输出(默认 JSON) |
| `--port <n>` | daemon 端口(默认 9527 |
| `--help` | 查看帮助 |
---
## 页面管理
### `visionl page open`
打开新页面。
```bash
visionl page open <url> [--alias <name>]
```
**示例:**
```bash
visionl page open https://www.baidu.com --alias baidu
# {"ok":true,"data":{"id":"p_a1b2c3d4","url":"https://www.baidu.com","alias":"baidu","title":"百度一下,你就知道","status":"active"}}
```
### `visionl page list`
列出所有已打开页面。
```bash
visionl page list
# {"ok":true,"data":[{"id":"p_a1b2c3d4","url":"...","alias":"baidu",...}]}
```
### `visionl page info`
查看页面详情。
```bash
visionl page info <id|alias>
visionl page info baidu
```
### `visionl page kill`
关闭指定页面。
```bash
visionl page kill <id|alias>
```
### `visionl page kill-all`
关闭所有页面。
```bash
visionl page kill-all
```
---
## 页面操作
### `visionl click`
点击指定元素。
```bash
visionl click <id|alias> <selector>
# 示例
visionl click baidu "#su"
```
### `visionl type`
在输入框中输入文本。
```bash
visionl type <id|alias> <selector> <text>
# 示例
visionl type baidu "#kw" "VisionL浏览器"
```
### `visionl scroll`
滚动页面。
```bash
visionl scroll <id|alias> [--down <px>] [--bottom]
# 示例
visionl scroll baidu --down 300
visionl scroll baidu --bottom
```
### `visionl navigate`
页面跳转。
```bash
visionl navigate <id|alias> <url>
```
### `visionl eval`
在页面中执行 JavaScript。
```bash
visionl eval <id|alias> <js-code>
# 示例
visionl eval baidu "document.title"
# {"ok":true,"data":{"result":"百度一下,你就知道"}}
```
### `visionl wait`
等待条件满足。
```bash
visionl wait <id|alias> [--selector <sel>] [--ms <n>]
# 等待选择器出现
visionl wait baidu --selector "#content"
# 等待 2 秒
visionl wait baidu --ms 2000
```
---
## 内容获取
### `visionl screenshot`
获取页面截图。
```bash
visionl screenshot <id|alias> [-o <file.png>]
# 输出 base64(默认)
visionl screenshot baidu
# 写入文件
visionl screenshot baidu -o screenshot.png
```
### `visionl text`
获取页面纯文本。
```bash
visionl text <id|alias>
# {"ok":true,"data":{"text":"百度一下,你就知道\n..."}}
```
### `visionl html`
获取页面 HTML 源码。
```bash
visionl html <id|alias>
# {"ok":true,"data":{"html":"<!DOCTYPE html>..."}}
```
---
## Daemon 管理
### `visionl daemon start`
手动启动 daemon。
```bash
visionl daemon start [--port <n>]
# 默认端口 9527
visionl daemon start
# 指定端口
visionl daemon start --port 8080
```
### `visionl daemon stop`
优雅关闭 daemon(不杀死已打开的页面)。
```bash
visionl daemon stop
```
### `visionl daemon status`
查看 daemon 运行状态。
```bash
visionl daemon status
# {"ok":true,"data":{"running":true,"pid":12345,"port":9527}}
```
---
## REST 直通
直接向 daemon 发送原始 HTTP 请求。
```bash
visionl raw <METHOD> <path> [body]
# 示例
visionl raw POST /pages '{"url":"https://example.com"}'
visionl raw GET /pages
visionl raw GET /pages/p_abc123/text
visionl raw DELETE /pages/p_abc123
```
---
## 自动拉起
CLI 在执行页面命令前会自动检测 daemon 是否存活。如果 daemon 未运行,会自动启动。
自动拉起流程:
1. 向默认端口(9527)发送 `GET /health`
2. 无响应时检查 `~/.visionl/daemon.pid` 中的进程是否存活
3. 都不行则 `spawn` 启动 daemon
4. 轮询健康检查,最多等待 3 秒
5. 超时则报错退出
**状态文件:**
- `~/.visionl/daemon.pid` — daemon PID
- `~/.visionl/daemon.port` — daemon 端口
---
## 输出格式
### JSON(默认)
每行一个 JSON 对象,LLM 直接解析:
```json
{"ok":true,"data":{"id":"p_a1b2c3d4","url":"https://example.com","title":"Example"}}
```
### 人类可读(--pretty
```bash
visionl page open https://example.com --pretty
# ✓ 页面已打开
# ID: p_a1b2c3d4
# URL: https://example.com
# 标题: Example Domain
# 状态: active
```
---
## 退出码
| 退出码 | 含义 |
|--------|------|
| 0 | 成功 |
| 1 | 一般错误(页面不存在、选择器未找到等) |
| 2 | daemon 不可达 |
| 3 | 无效参数 |
+136
View File
@@ -0,0 +1,136 @@
# VisionL 开发规范
## 环境要求
- Node.js >= 18
- npm >= 9
- Git
## 克隆与安装
```bash
git clone ssh://git@git.yeij.top:2222/AskaEth/VisionL.git
cd VisionL
git checkout dev
# 安装所有 workspace 依赖
npm install
# 安装 Playwright 浏览器(Chromium
npx playwright install chromium
```
## 项目结构
Monorepo 使用 npm workspaces 管理:
```
packages/
├── core/ # 共享类型、HTTP 客户端、工具函数
├── daemon/ # 后台守护进程
└── cli/ # 命令行工具
```
## 开发命令
```bash
# 类型检查(全仓)
npm run typecheck
# 运行所有测试
npm test
# 运行单个包的测试
npm test --workspace=packages/core
npm test --workspace=packages/daemon
npm test --workspace=packages/cli
# 构建
npm run build
# 本地开发(cli 链接到全局)
cd packages/cli && npm link
# 然后可以在任何目录使用 visionl 命令
```
## 代码规范
### 命名
- 文件名:kebab-case`browser-manager.ts`
- 变量/函数:camelCase`pageRegistry``getPageInfo`
- 类型/接口:PascalCase`PageInfo``ApiResponse`
- 常量:UPPER_SNAKE_CASE`DEFAULT_PORT`
### TypeScript
- 严格模式 (`strict: true`)
- 禁止 `any`(必须显式类型)
- 导出类型使用 `interface` 而非 `type`(可扩展性更好)
### 注释
- 文件头简述文件职责(中英双语一行)
- 公共 API 必须有 JSDoc
- 不写废话注释(不解释显而易见的代码)
### 日志
Daemon 和 CLI 使用统一的日志层级:
- `DEBUG`:调试信息(带上下文)
- `INFO`:关键状态变更(页面打开/关闭/崩溃)
- `WARN`:可恢复的异常
- `ERROR`:需要关注的错误
生产环境默认 `INFO` 级别,开发模式可设为 `DEBUG`
## 测试规范
- 使用 `vitest` 作为测试框架
- 测试文件放在 `src/__tests__/` 目录下
- 文件命名:`*.test.ts`
- 每个 PR 必须包含相关测试
- 集成测试放在 `packages/daemon/src/__tests__/integration/`
## Git 工作流
- `main`:稳定发布分支
- `dev`:开发分支(日常开发在此)
- 功能分支:`feature/xxx`
- 修复分支:`fix/xxx`
### Commit 规范
遵循 Conventional Commits
```
feat: 添加页面截图功能
fix: 修复 daemon 端口冲突时的错误提示
docs: 更新 API 文档
test: 添加 CLI 自动拉起单元测试
refactor: 重构 page-registry 为 Map 实现
```
## 发布流程
1. 功能开发在 `dev` 分支
2. 测试全过后合并到 `main`
3. 打 tag`v1.0.0`
4. 发布到 npm
---
## JSON 转义规范
所有返回给外部的 JSON 必须通过 `JSON.stringify` 序列化,禁止以下写法:
```typescript
// ❌ 禁止
`{"ok":true,"text":"${pageText}"}`
// ✅ 正确
JSON.stringify({ ok: true, text: pageText })
```
页面内容(文本、HTML)中可能包含任意字符,手动拼接必然导致 JSON 非法。
+143
View File
@@ -0,0 +1,143 @@
# VisionL 使用示例
> 面向智能体和人工用户的典型场景。
## 场景一:搜索引擎查询
```bash
# 1. 打开百度
visionl page open https://www.baidu.com --alias search
# → {"ok":true,"data":{"id":"p_1234","alias":"search",...}}
# 2. 输入搜索词
visionl type search "#kw" "VisionL 浏览器"
# 3. 点击搜索
visionl click search "#su"
# 4. 等待结果加载
visionl wait search --selector "#content_left"
# 5. 获取页面文本
visionl text search
# → {"ok":true,"data":{"text":"搜索结果..."}}
# 6. 用完关闭
visionl page kill search
```
## 场景二:多页面信息收集
```bash
# 同时打开多个信息源
visionl page open https://news.ycombinator.com --alias hn
visionl page open https://www.reddit.com/r/programming --alias reddit
visionl page open https://github.com/trending --alias gh
# 分别提取内容
visionl text hn
visionl text reddit
visionl text gh
# 用完批量关闭
visionl page kill-all
```
## 场景三:表单填写
```bash
# 1. 打开登录页
visionl page open https://example.com/login --alias login
# 2. 填写表单
visionl type login "#email" "user@example.com"
visionl type login "#password" "s3cret"
# 3. 提交
visionl click login "button[type=submit]"
# 4. 截图验证
visionl screenshot login -o logged-in.png
```
## 场景四:页面截图
```bash
# 打开并截图
visionl page open https://www.example.com --alias page
visionl wait page --ms 2000 # 等待渲染
visionl screenshot page -o page.png
# 滚动后截图
visionl scroll page --down 600
visionl screenshot page -o page-scrolled.png
```
## 场景五:JS 数据提取
```bash
# 1. 打开页面
visionl page open https://api.example.com/data --alias data
# 2. 执行 JS 提取 JSON 数据
visionl eval data "JSON.parse(document.body.innerText)"
# → {"ok":true,"data":{"result":{"items":[...]}}}
# 3. 获取页面标题
visionl eval data "document.title"
# → {"ok":true,"data":{"result":"API Data Page"}}
```
## 场景六:REST 直通(高级)
```bash
# 不使用子命令,直接调用 HTTP API
visionl raw POST /pages '{"url":"https://example.com","alias":"test"}'
# → {"ok":true,"data":{"id":"p_5678",...}}
visionl raw GET /pages/p_5678/text
# → {"ok":true,"data":{"text":"..."}}
visionl raw DELETE /pages/p_5678
# → {"ok":true,"data":null}
```
## 场景七:智能体自动化工作流
LLM 通过 VisionL 完成机票比价:
```
1. visionl page open https://flights.example.com --alias flights
2. visionl type flights "#from" "北京"
3. visionl type flights "#to" "上海"
4. visionl type flights "#date" "2026-08-20"
5. visionl click flights "#search"
6. visionl wait flights --selector ".results"
7. visionl text flights
→ 提取票价信息
8. visionl eval flights "document.querySelectorAll('.price').length"
→ 统计结果数量
9. visionl page kill flights
```
## 场景八:长时间运行的任务
```bash
# 打开监控面板
visionl page open https://monitor.example.com --alias monitor --pretty
# ✓ 页面已打开
# ID: p_mon_001
# URL: https://monitor.example.com
# 别名: monitor
# ... 过了一段时间 ...
# 还是同一个页面
visionl page list --pretty
# ✓ 1 个页面
# p_mon_001 monitor https://monitor.example.com active
# 刷新重新截图
visionl navigate monitor https://monitor.example.com
visionl screenshot monitor -o latest.png
```
+138
View File
@@ -0,0 +1,138 @@
# 在 LLM 智能体中集成 VisionL
> 本文档介绍如何让 LLM 通过工具调用使用 VisionL-CLI 操控浏览器。
## 原理
LLM 将 `visionl` 注册为一个系统命令/工具,在需要浏览网页时调用。
所有命令输出结构化的 JSON,LLM 直接解析结果并决定下一步操作。
## 集成方式
### 方式一:Function Calling(推荐)
在 LLM 的 function/tool 定义中注册 VisionL 命令。大多数 LLM 平台(OpenAI、Claude、本地模型)都支持。
**工具定义示例(OpenAI 格式):**
```json
{
"type": "function",
"function": {
"name": "visionl",
"description": "通过 VisionL 浏览器操控网页。子命令: page open|list|info|kill|kill-all, click, type, scroll, navigate, eval, wait, screenshot, text, html, daemon start|stop|status, raw",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "完整 visionl 命令,例如 'page open https://example.com'"
}
},
"required": ["command"]
}
}
}
```
**使用流程:**
1. LLM 决策需要访问网页
2. LLM 生成 `visionl page open <url>` 调用
3. 宿主程序在终端执行该命令,将 JSON 输出返回给 LLM
4. LLM 解析结果,继续决策(截图、点击、提取文本等)
### 方式二:MCP Server
可以封装一个 MCPModel Context ProtocolServer,将 VisionL-CLI 包装为 MCP 工具:
```typescript
// 伪代码示意
server.tool(
"visionl",
"通过 VisionL 浏览器操控网页",
{ command: z.string() },
async ({ command }) => {
const { stdout } = await exec(`visionl ${command}`);
return JSON.parse(stdout);
}
);
```
### 方式三:Agent 框架集成
与 LangChain、AutoGPT、CrewAI 等框架集成,注册为自定义工具。
**LangChain 示例:**
```python
from langchain.tools import Tool
import subprocess, json
def visionl_tool(command: str) -> str:
result = subprocess.run(
["visionl", *command.split()],
capture_output=True, text=True
)
return result.stdout
visionl = Tool(
name="visionl",
description="浏览器操控工具。命令示例:page open <url>, click <id> <sel>, text <id>",
func=visionl_tool,
)
```
## LLM Prompt 建议
在系统 prompt 中添加以下指引:
```
你可以使用 visionl 命令操控浏览器:
1. visionl page open <url> [--alias <name>] — 打开页面
2. visionl page list — 列出所有页面
3. visionl text <id|alias> — 获取页面文本
4. visionl screenshot <id|alias> — 截图(返回 base64)
5. visionl click <id|alias> <selector> — 点击元素
6. visionl type <id|alias> <selector> <text> — 输入文本
7. visionl page kill <id|alias> — 关闭页面
所有命令返回 JSON。解析 ok 字段判断成功/失败。
使用 --alias 给页面起别名方便后续引用。
```
## 多页面管理
LLM 可以同时打开多个页面,通过别名区分:
```
LLM: visionl page open https://docs.python.org --alias py
LLM: visionl page open https://developer.mozilla.org --alias mdn
LLM: visionl text py # 读 Python 文档
LLM: visionl text mdn # 读 MDN 文档
```
## 错误处理
LLM 应检查返回的 `ok` 字段:
```json
// 失败示例
{"ok":false,"error":{"code":"PAGE_NOT_FOUND","message":"页面 py 不存在"}}
```
常见错误及处理:
| 错误码 | 处理建议 |
|--------|---------|
| `PAGE_NOT_FOUND` | 页面可能已被关闭,重新打开 |
| `DAEMON_UNREACHABLE` | 等待几秒重试(自动拉起正在启动) |
| `TIMEOUT` | 页面加载慢,重试或增加等待时间 |
| `ALIAS_EXISTS` | 换一个别名或直接用 page ID |
## 安全注意事项
- VisionL daemon 仅监听 127.0.0.1,外部不可访问
- 执行的 JS 代码在页面沙箱内运行,无法逃逸到宿主机
- LLM 应避免在不可信页面执行敏感操作(自动填写密码等)
+89
View File
@@ -0,0 +1,89 @@
# VisionL 快速开始
## 安装
```bash
# 1. 克隆仓库
git clone ssh://git@git.yeij.top:2222/AskaEth/VisionL.git
cd VisionL
# 2. 安装依赖
npm install
# 3. 安装 Chromium 浏览器
npx playwright install chromium
# 4. 构建
npm run build
# 5. 全局安装 CLI(可选)
cd packages/cli && npm link
```
## 基本使用
### 启动 daemon
```bash
visionl daemon start
# ✓ Daemon 已启动 (端口 9527, PID 12345)
```
### 打开一个页面
```bash
visionl page open https://www.baidu.com --alias baidu
# {"ok":true,"data":{"id":"p_a1b2c3d4","url":"https://www.baidu.com","alias":"baidu","title":"百度一下,你就知道","status":"active"}}
```
### 获取页面文本
```bash
visionl text baidu
# {"ok":true,"data":{"text":"百度一下,你就知道\n..."}}
```
### 搜索
```bash
visionl type baidu "#kw" "VisionL"
visionl click baidu "#su"
```
### 截图
```bash
visionl screenshot baidu -o result.png
```
### 关闭页面
```bash
visionl page kill baidu
# {"ok":true,"data":null}
```
### 停止 daemon
```bash
visionl daemon stop
```
## 无需手动启动 daemon
CLI 会自动检测 daemon 是否运行,未运行则自动启动:
```bash
# 直接使用,CLI 自动拉起 daemon
visionl page open https://example.com
# 关闭所有页面(daemon 继续运行)
visionl page kill-all
```
## 查看所有页面
```bash
visionl page list
# {"ok":true,"data":[{"id":"p_xxx","url":"...","alias":"...","title":"...","status":"active"}]}
```