简介
让任何网站成为你的 CLI,零配置,AI 友好。 OpenCLI 是一个将网站转换为 CLI 命令的工具,专为 AI Agent 设计。搭配OpenClaw食用风味更佳。
核心特性
- ✅ 零配置 - 无需 Playwright/Puppeteer,复用已登录的 Chrome
- ✅ AI 友好 - 输出结构化数据,适合 AI 解析
- ✅ 反检测 - 内置 stealth.js,避免被网站检测
- ✅ 扩展系统 - 支持自定义插件
适用场景
| 场景 | 说明 |
|---|---|
| 数据抓取 | 从网站提取结构化数据 |
| 自动化操作 | 自动填写表单、提交数据 |
| AI Agent | 为 AI 提供浏览器操作能力 |
| 网站监控 | 定期检查网站状态 |
架构
架构示意
┌─────────────────────────────────────────────────────────────────┐
│ Chrome 浏览器 │
├──────────────────────────────┬──────────────────────────────────┤
│ 用户浏览器窗口 │ Automation Window │
│ (正常使用) │ (OpenCLI 管理) │
│ │ │
│ ┌─────────────────────┐ │ ┌─────────────────────────┐ │
│ │ Tab: 掘金页面 │ │ │ Tab: data:text/html │ │
│ │ Tab: GitHub │ │ │ Tab: Google搜索 │ │
│ │ Tab: Google │ │ │ Tab: ... │ │
│ └─────────────────────┘ │ └─────────────────────────┘ │
│ OpenClaw Browser 操作这里 | │
│ 用户可见,正常交互 │ focused: false (不抢焦点) │
│ │ 30秒无操作自动关闭 │
└──────────────────────────────┴──────────────────────────────────┘
│
│ CDP WebSocket
▼
┌─────────────────────────┐
│ OpenCLI Daemon │
│ (Node.js, :19825) │
└─────────────────────────┘
│
│
▼
┌─────────────────────────┐
│ OpenCLI CLI │
│ (命令行工具) │
└─────────────────────────┘
核心组件
| 组件 | 说明 |
|---|---|
| CLI | 命令行工具,用户交互入口 |
| Daemon | HTTP + WebSocket 服务,默认端口 19825 |
| Browser Bridge | Chrome 扩展,连接浏览器和 Daemon |
| Automation Window | 独立的 Chrome 窗口,OpenCLI 操作区域 |
关键特性
Automation Window
// OpenCLI 创建独立窗口
chrome.windows.create({
url: 'data:text/html,<html></html>',
focused: false, // 不抢焦点
width: 1280,
height: 900,
type: 'normal'
});
- 隔离性:不干扰用户的正常浏览
- 安全性:用户数据不会被意外修改
- 自动清理:30 秒无操作自动关闭
安装
1. 安装 CLI
npm install -g @jackwener/opencli
2. 安装 Browser Bridge 扩展
- 下载 OpenCLI Browser Bridge
- 打开
chrome://extensions/ - 启用「开发者模式」
- 点击「加载已解压的扩展程序」
- 选择扩展目录
3. 验证安装
opencli doctor
期望输出:
[OK] Daemon: running on port 19825
[OK] Extension: connected (v1.5.5)
[OK] Connectivity: connected in 0.2s
基本命令
查看帮助
opencli --help
opencli <site> --help
列出所有命令
opencli list
opencli <site> list
内置命令示例
# Hacker News
opencli hackernews top --limit 10
# Bilibili
opencli bilibili hot --limit 5
# GitHub
opencli gh repo list --limit 10
# 天气
opencli weather beijing
通用选项
| 选项 | 说明 |
|---|---|
--verbose | 详细输出 |
--json | JSON 格式输出 |
--help | 显示帮助 |
插件开发
插件基本结构
~/.opencli/plugins/<plugin-name>/
├── <plugin-name>.ts # 插件源码
├── <plugin-name>.js # 编译后的代码
├── package.json # 依赖
└── opencli-plugin.json # 插件元数据
最小示例
// ~/.opencli/plugins/my-plugin/my-plugin.ts
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'my-site',
name: 'hello',
description: '打招呼',
strategy: Strategy.PUBLIC,
browser: true,
args: [
{ name: 'name', type: 'string', default: 'World', help: '名字' }
],
columns: ['message'],
func: async (page, kwargs) => {
const name = kwargs.name as string;
return [{ message: `Hello, ${name}!` }];
},
});
参数类型
args: [
{ name: 'text', type: 'string', default: '', help: '文本' },
{ name: 'count', type: 'int', default: 10, help: '数量' },
{ name: 'enabled', type: 'boolean', default: false, help: '是否启用' },
{ name: 'url', type: 'string', required: true, help: 'URL' },
]
浏览器操作
func: async (page, kwargs) => {
// 导航
await page.goto('https://example.com');
// 等待
await page.wait(2); // 2 秒
// 执行 JS
const title = await page.evaluate('document.title');
// 截图
await page.screenshot({ path: '/tmp/screenshot.png' });
// 获取 Cookie
const cookies = await page.getCookies({ domain: 'example.com' });
// Tab 操作
const tabs = await page.tabs();
await page.selectTab(0);
await page.newTab();
await page.closeTab();
return [{ success: true }];
}
编译插件
# 方法1:使用 opencli 命令
opencli plugin update <plugin-name>
# 方法2:使用 esbuild
esbuild ~/.opencli/plugins/<name>/<name>.ts \
--outfile=~/.opencli/plugins/<name>/<name>.js \
--platform=node --format=esm --bundle
插件发现
OpenCLI 自动扫描以下文件:
*.yaml- YAML 格式命令定义*.ts- TypeScript 插件(需要编译)*.js- JavaScript 插件
API 参考
IPage 接口
export interface IPage {
// 导航
goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void>;
getCurrentUrl(): Promise<string | null>;
// 执行代码
evaluate(js: string): Promise<any>;
// 等待
wait(options: number | WaitOptions): Promise<void>;
// Tab 管理
tabs(): Promise<any[]>;
selectTab(index: number): Promise<void>;
newTab(): Promise<void>;
closeTab(index?: number): Promise<void>;
closeWindow(): Promise<void>;
// 截图
screenshot(options?: ScreenshotOptions): Promise<string>;
// Cookie
getCookies(options?: { domain?: string; url?: string }): Promise<BrowserCookie[]>;
// 快照
snapshot(options?: SnapshotOptions): Promise<any>;
// 交互
click(ref: string): Promise<void>;
typeText(ref: string, text: string): Promise<void>;
pressKey(key: string): Promise<void>;
scrollTo(ref: string): Promise<any>;
// 滚动
scroll(direction?: string, amount?: number): Promise<void>;
autoScroll(options?: { times?: number; delayMs?: number }): Promise<void>;
// 表单
getFormState(): Promise<any>;
setFileInput(files: string[], selector?: string): Promise<void>;
// 网络
networkRequests(includeStatic?: boolean): Promise<any[]>;
installInterceptor(pattern: string): Promise<void>;
getInterceptedRequests(): Promise<any[]>;
waitForCapture(timeout?: number): Promise<void>;
// 控制台
consoleMessages(level?: string): Promise<any>;
}
WaitOptions
interface WaitOptions {
time?: number; // 等待时间(秒)
selector?: string; // 等待元素出现
text?: string; // 等待文本出现
timeout?: number; // 超时时间(秒)
}
ScreenshotOptions
interface ScreenshotOptions {
format?: 'png' | 'jpeg'; // 图片格式
quality?: number; // JPEG 质量 (0-100)
fullPage?: boolean; // 全页面截图
path?: string; // 保存路径
}
page.wait() 时间单位
⚠️ 重要:page.wait() 参数单位是 秒,不是毫秒!
// 正确 ✅
await page.wait(2); // 等待 2 秒
await page.wait(3); // 等待 3 秒
// 错误 ❌
await page.wait(2000); // 等待 2000 秒(33 分钟)
方法说明
滚动相关
// 向下滚动 500px(默认)
await page.scroll('down', 500);
// 向上滚动
await page.scroll('up', 300);
// 自动滚动(用于长页面截图)
await page.autoScroll({ times: 3, delayMs: 2000 }); // 滚动3次,每次间隔2秒
文件上传
// 设置文件输入框的文件
await page.setFileInput(
['/path/to/file1.pdf', '/path/to/file2.pdf'],
'input[type="file"]' // 可选选择器
);
网络拦截
// 安装请求拦截器
await page.installInterceptor('/api/**');
// 导航到页面
await page.goto('https://example.com');
// 获取拦截的请求
const requests = await page.getInterceptedRequests();
// 等待请求捕获完成
await page.waitForCapture(5000); // 等待5秒
控制台消息
// 获取所有控制台消息
const messages = await page.consoleMessages();
// 获取特定级别的消息
const errors = await page.consoleMessages('error');
const warnings = await page.consoleMessages('warning');
窗口管理
// 关闭当前窗口
await page.closeWindow();
调试方法
1. 使用 --verbose
opencli <site> <command> --verbose
2. 使用 DEBUG 环境变量
DEBUG=* opencli <site> <command>
3. 检查连接状态
opencli doctor
4. 创建调试命令
cli({
site: 'my-site',
name: 'debug-example',
func: async (page) => {
const results = [];
// Step 1
await page.goto('https://example.com');
const url = await page.getCurrentUrl();
results.push({ step: 'navigate', url });
// Step 2
const title = await page.evaluate('document.title');
results.push({ step: 'title', title });
// Step 3
await page.wait(2);
results.push({ step: 'wait', done: true });
return results;
}
});
5. 分步执行
问题:一次性 evaluate 太多操作可能失败
解决:分成多个步骤,用 page.wait() 等待
// ❌ 错误:所有操作在一次 evaluate 中
await page.evaluate(`
(async () => {
await delay(1000);
btn.click();
await delay(1000);
// ...
})()
`);
// ✅ 正确:分步执行
await page.evaluate(`btn.click()`);
await page.wait(1); // 外部等待
const result = await page.evaluate(`checkResult()`);
常见问题
Q: Extension not connected
原因:Browser Bridge 扩展未安装或未启用
解决:
- 确认扩展已安装
- 确认扩展 badge 显示 ON
- 重启 Chrome
Q: Tab 操作不生效
原因:OpenCLI 在独立的 Automation Window 操作
解决:
page.tabs()返回 Automation Window 的标签页- 使用 Browser 工具操作用户窗口的标签页
Q: 截图保存在哪里?
解决:通过 path 参数指定
await page.screenshot({
path: '/path/to/screenshot.png'
});
Q: page.wait() 等待时间不对
原因:单位是秒,不是毫秒
解决:
await page.wait(2); // 2 秒
Q: Cookie 获取失败
解决:指定 domain 或 url
await page.getCookies({ domain: 'example.com' });
await page.getCookies({ url: 'https://example.com' });
Q: 插件更新后代码没变
原因:.js 文件缓存
解决:
rm -f ~/.opencli/plugins/<name>/<name>.js
opencli plugin update <name>
与 Playwright 对比
| 特性 | OpenCLI | Playwright |
|---|---|---|
| 依赖 | 仅 ws 包 | 完整浏览器二进制 |
| 连接 | 需要运行中的 Chrome | 自动启动浏览器 |
| 扩展 | 需要 Browser Bridge | 无需扩展 |
| 窗口 | Automation Window | 可配置 |
| 反检测 | 内置 stealth.js | 需要插件 |
| AI 友好 | ✅ 设计目标 | ⚠️ 需要适配 |
| Tab 管理 | Chrome Extension API | 原生支持 |
相关链接
文档版本: 2026-04-03