opencli-web-automation by aradotso/trending-skills
npx skills add https://github.com/aradotso/trending-skills --skill opencli-web-automationSkill by ara.so — Daily 2026 Skills collection.
OpenCLI 通过复用 Chrome 已登录的浏览器会话,将任何网站转化为命令行界面。它开箱即用支持 19 个网站和 80 多个命令,并允许您通过将 TypeScript 或 YAML 文件放入 clis/ 文件夹来添加新的适配器。
# 通过 npm 全局安装
npm install -g @jackwener/opencli
# 一次性设置:发现 Playwright MCP 令牌并分发到所有工具
opencli setup
# 验证一切是否正常工作
opencli doctor --live
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link
# 必需:运行 opencli setup 后在 ~/.zshrc 或 ~/.bashrc 中设置
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-from-setup>"
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
MCP 客户端配置(Claude/Cursor/Codex ~/.config/*/config.json):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--extension"],
"env": {
"PLAYWRIGHT_MCP_EXTENSION_TOKEN": "$PLAYWRIGHT_MCP_EXTENSION_TOKEN"
}
}
}
}
opencli list # 显示所有已注册的命令
opencli list -f yaml # 以 YAML 格式输出注册表
opencli list -f json # 以 JSON 格式输出注册表
# 公共 API 命令(无需浏览器登录)
opencli hackernews top --limit 10
opencli github search "playwright automation"
opencli bbc news
# 浏览器命令(必须在 Chrome 中登录网站)
opencli bilibili hot --limit 5
opencli twitter trending
opencli zhihu hot -f json
opencli reddit frontpage --limit 20
opencli xiaohongshu search "TypeScript"
opencli youtube search "browser automation"
opencli linkedin search "senior engineer"
所有命令都支持 --format / -f:
opencli bilibili hot -f table # 丰富的终端表格(默认)
opencli bilibili hot -f json # JSON(可管道传输到 jq)
opencli bilibili hot -f yaml # YAML
opencli bilibili hot -f md # Markdown
opencli bilibili hot -f csv # CSV 导出
opencli bilibili hot -v # 详细模式:显示管道调试步骤
# 1. 深度探索网站 — 发现 API、认证、能力
opencli explore https://example.com --site mysite
# 2. 根据探索产物合成 YAML 适配器
opencli synthesize mysite
# 3. 一键完成:探索 → 合成 → 注册
opencli generate https://example.com --goal "hot posts"
# 4. 策略级联 — 自动探测 PUBLIC → COOKIE → HEADER 认证
opencli cascade https://api.example.com/data
探索产物保存到 .opencli/explore/<site>/:
manifest.json — 网站元数据endpoints.json — 发现的 API 端点capabilities.json — 推断的命令能力auth.json — 认证策略将 .yaml 文件放入 clis/ — 下次运行时自动注册:
# clis/producthunt.yaml
site: producthunt
commands:
- name: trending
description: Get trending products on Product Hunt
args:
- name: limit
type: number
default: 10
pipeline:
- type: navigate
url: https://www.producthunt.com
- type: waitFor
selector: "[data-test='post-item']"
- type: extract
selector: "[data-test='post-item']"
fields:
name:
selector: "h3"
type: text
tagline:
selector: "p"
type: text
votes:
selector: "[data-test='vote-button']"
type: text
url:
selector: "a"
attr: href
- type: limit
count: "{{limit}}"
// clis/producthunt.ts
import type { CLIAdapter } from "../src/types";
const adapter: CLIAdapter = {
site: "producthunt",
commands: [
{
name: "trending",
description: "Get trending products on Product Hunt",
options: [
{
flags: "--limit <n>",
description: "Number of results",
defaultValue: "10",
},
],
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://www.producthunt.com");
await page.waitForSelector("[data-test='post-item']");
const products = await page.evaluate(() => {
return Array.from(
document.querySelectorAll("[data-test='post-item']")
).map((el) => ({
name: el.querySelector("h3")?.textContent?.trim() ?? "",
tagline: el.querySelector("p")?.textContent?.trim() ?? "",
votes:
el
.querySelector("[data-test='vote-button']")
?.textContent?.trim() ?? "",
url:
(el.querySelector("a") as HTMLAnchorElement)?.href ?? "",
}));
});
return products.slice(0, Number(options.limit));
},
},
],
};
export default adapter;
// 当网站暴露 JSON API 但需要登录 cookie 时
async run(options, browser) {
const page = await browser.currentPage();
// 首先导航以确保 cookie 处于活动状态
await page.goto("https://api.example.com");
const data = await page.evaluate(async () => {
const res = await fetch("/api/v1/feed?limit=20", {
credentials: "include", // 复用浏览器 cookie
});
return res.json();
});
return data.items;
}
// 从浏览器存储中提取认证令牌用于 API 调用
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://example.com");
const token = await page.evaluate(() => {
return localStorage.getItem("auth_token") ||
sessionStorage.getItem("token");
});
const data = await page.evaluate(async (tok) => {
const res = await fetch("/api/data", {
headers: { Authorization: `Bearer ${tok}` },
});
return res.json();
}, token);
return data;
}
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://news.ycombinator.com");
// 等待动态内容加载
await page.waitForSelector(".athing", { timeout: 10000 });
return page.evaluate((limit) => {
return Array.from(document.querySelectorAll(".athing"))
.slice(0, limit)
.map((row) => ({
title: row.querySelector(".titleline a")?.textContent?.trim(),
url: (row.querySelector(".titleline a") as HTMLAnchorElement)?.href,
score:
row.nextElementSibling
?.querySelector(".score")
?.textContent?.trim() ?? "0",
}));
}, Number(options.limit));
}
async run(options, browser) {
const page = await browser.currentPage();
const results = [];
let pageNum = 1;
while (results.length < Number(options.limit)) {
await page.goto(`https://example.com/posts?page=${pageNum}`);
await page.waitForSelector(".post-item");
const items = await page.evaluate(() =>
Array.from(document.querySelectorAll(".post-item")).map((el) => ({
title: el.querySelector("h2")?.textContent?.trim(),
url: (el.querySelector("a") as HTMLAnchorElement)?.href,
}))
);
if (items.length === 0) break;
results.push(...items);
pageNum++;
}
return results.slice(0, Number(options.limit));
}
# 诊断所有工具的令牌和配置
opencli doctor
# 测试实时浏览器连接性
opencli doctor --live
# 交互式修复不匹配的配置
opencli doctor --fix
# 非交互式修复所有配置
opencli doctor --fix -y
npm run build
# 运行所有测试
npx vitest run
# 仅单元测试
npx vitest run src/
# 仅 E2E 测试
npx vitest run tests/e2e/
# CI 的无头浏览器模式
OPENCLI_HEADLESS=1 npx vitest run tests/e2e/
| 症状 | 修复方法 |
|---|---|
Failed to connect to Playwright MCP Bridge | 确保扩展在 Chrome 中已启用;安装后重启 Chrome |
空数据 / Unauthorized | 打开 Chrome,导航到该网站,登录或刷新页面 |
| Node API 错误 | 升级到 Node.js >= 18 |
| 找不到令牌 | 运行 opencli setup 或 opencli doctor --fix |
| 登录会话过期 | 在 Chrome 中访问目标网站并与之交互以证明是真人操作 |
# 查看完整的管道执行步骤
opencli bilibili hot -v
# 检查探索发现了什么
cat .opencli/explore/mysite/endpoints.json
cat .opencli/explore/mysite/auth.json
opencli/
├── clis/ # 将 .ts 或 .yaml 适配器放在这里(自动注册)
│ ├── bilibili.ts
│ ├── twitter.ts
│ └── hackernews.yaml
├── src/
│ ├── types.ts # CLIAdapter、Command 接口
│ ├── browser.ts # Playwright MCP 桥接包装器
│ ├── loader.ts # 动态适配器加载器
│ └── output.ts # table/json/yaml/md/csv 格式化器
├── tests/
│ └── e2e/ # 每个网站的 E2E 测试
└── CLI-EXPLORER.md # 完整的 AI 代理探索工作流
每周安装量
348
仓库
GitHub Stars
10
首次出现
8 天前
安全审计
安装于
gemini-cli347
github-copilot347
codex347
amp347
cline347
kimi-cli347
Skill by ara.so — Daily 2026 Skills collection.
OpenCLI turns any website into a command-line interface by reusing Chrome's logged-in browser session. It supports 19 sites and 80+ commands out of the box, and lets you add new adapters via TypeScript or YAML dropped into the clis/ folder.
# Install globally via npm
npm install -g @jackwener/opencli
# One-time setup: discovers Playwright MCP token and distributes to all tools
opencli setup
# Verify everything is working
opencli doctor --live
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link
# Required: set in ~/.zshrc or ~/.bashrc after running opencli setup
export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-from-setup>"
MCP client config (Claude/Cursor/Codex ~/.config/*/config.json):
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest", "--extension"],
"env": {
"PLAYWRIGHT_MCP_EXTENSION_TOKEN": "$PLAYWRIGHT_MCP_EXTENSION_TOKEN"
}
}
}
}
opencli list # Show all registered commands
opencli list -f yaml # Output registry as YAML
opencli list -f json # Output registry as JSON
# Public API commands (no browser login needed)
opencli hackernews top --limit 10
opencli github search "playwright automation"
opencli bbc news
# Browser commands (must be logged into site in Chrome)
opencli bilibili hot --limit 5
opencli twitter trending
opencli zhihu hot -f json
opencli reddit frontpage --limit 20
opencli xiaohongshu search "TypeScript"
opencli youtube search "browser automation"
opencli linkedin search "senior engineer"
All commands support --format / -f:
opencli bilibili hot -f table # Rich terminal table (default)
opencli bilibili hot -f json # JSON (pipe to jq)
opencli bilibili hot -f yaml # YAML
opencli bilibili hot -f md # Markdown
opencli bilibili hot -f csv # CSV export
opencli bilibili hot -v # Verbose: show pipeline debug steps
# 1. Deep explore a site — discovers APIs, auth, capabilities
opencli explore https://example.com --site mysite
# 2. Synthesize YAML adapters from explore artifacts
opencli synthesize mysite
# 3. One-shot: explore → synthesize → register in one command
opencli generate https://example.com --goal "hot posts"
# 4. Strategy cascade — auto-probes PUBLIC → COOKIE → HEADER auth
opencli cascade https://api.example.com/data
Explore artifacts are saved to .opencli/explore/<site>/:
manifest.json — site metadataendpoints.json — discovered API endpointscapabilities.json — inferred command capabilitiesauth.json — authentication strategyDrop a .yaml file into clis/ — auto-registered on next run:
# clis/producthunt.yaml
site: producthunt
commands:
- name: trending
description: Get trending products on Product Hunt
args:
- name: limit
type: number
default: 10
pipeline:
- type: navigate
url: https://www.producthunt.com
- type: waitFor
selector: "[data-test='post-item']"
- type: extract
selector: "[data-test='post-item']"
fields:
name:
selector: "h3"
type: text
tagline:
selector: "p"
type: text
votes:
selector: "[data-test='vote-button']"
type: text
url:
selector: "a"
attr: href
- type: limit
count: "{{limit}}"
// clis/producthunt.ts
import type { CLIAdapter } from "../src/types";
const adapter: CLIAdapter = {
site: "producthunt",
commands: [
{
name: "trending",
description: "Get trending products on Product Hunt",
options: [
{
flags: "--limit <n>",
description: "Number of results",
defaultValue: "10",
},
],
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://www.producthunt.com");
await page.waitForSelector("[data-test='post-item']");
const products = await page.evaluate(() => {
return Array.from(
document.querySelectorAll("[data-test='post-item']")
).map((el) => ({
name: el.querySelector("h3")?.textContent?.trim() ?? "",
tagline: el.querySelector("p")?.textContent?.trim() ?? "",
votes:
el
.querySelector("[data-test='vote-button']")
?.textContent?.trim() ?? "",
url:
(el.querySelector("a") as HTMLAnchorElement)?.href ?? "",
}));
});
return products.slice(0, Number(options.limit));
},
},
],
};
export default adapter;
// When a site exposes a JSON API but requires login cookies
async run(options, browser) {
const page = await browser.currentPage();
// Navigate first to ensure cookies are active
await page.goto("https://api.example.com");
const data = await page.evaluate(async () => {
const res = await fetch("/api/v1/feed?limit=20", {
credentials: "include", // reuse browser cookies
});
return res.json();
});
return data.items;
}
// Extract auth tokens from browser storage for API calls
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://example.com");
const token = await page.evaluate(() => {
return localStorage.getItem("auth_token") ||
sessionStorage.getItem("token");
});
const data = await page.evaluate(async (tok) => {
const res = await fetch("/api/data", {
headers: { Authorization: `Bearer ${tok}` },
});
return res.json();
}, token);
return data;
}
async run(options, browser) {
const page = await browser.currentPage();
await page.goto("https://news.ycombinator.com");
// Wait for dynamic content to load
await page.waitForSelector(".athing", { timeout: 10000 });
return page.evaluate((limit) => {
return Array.from(document.querySelectorAll(".athing"))
.slice(0, limit)
.map((row) => ({
title: row.querySelector(".titleline a")?.textContent?.trim(),
url: (row.querySelector(".titleline a") as HTMLAnchorElement)?.href,
score:
row.nextElementSibling
?.querySelector(".score")
?.textContent?.trim() ?? "0",
}));
}, Number(options.limit));
}
async run(options, browser) {
const page = await browser.currentPage();
const results = [];
let pageNum = 1;
while (results.length < Number(options.limit)) {
await page.goto(`https://example.com/posts?page=${pageNum}`);
await page.waitForSelector(".post-item");
const items = await page.evaluate(() =>
Array.from(document.querySelectorAll(".post-item")).map((el) => ({
title: el.querySelector("h2")?.textContent?.trim(),
url: (el.querySelector("a") as HTMLAnchorElement)?.href,
}))
);
if (items.length === 0) break;
results.push(...items);
pageNum++;
}
return results.slice(0, Number(options.limit));
}
# Diagnose token and config across all tools
opencli doctor
# Test live browser connectivity
opencli doctor --live
# Fix mismatched configs interactively
opencli doctor --fix
# Fix all configs non-interactively
opencli doctor --fix -y
npm run build
# Run all tests
npx vitest run
# Unit tests only
npx vitest run src/
# E2E tests only
npx vitest run tests/e2e/
# Headless browser mode for CI
OPENCLI_HEADLESS=1 npx vitest run tests/e2e/
| Symptom | Fix |
|---|---|
Failed to connect to Playwright MCP Bridge | Ensure extension is enabled in Chrome; restart Chrome after install |
Empty data / Unauthorized | Open Chrome, navigate to the site, log in or refresh the page |
| Node API errors | Upgrade to Node.js >= 18 |
| Token not found | Run opencli setup or opencli doctor --fix |
| Stale login session | Visit the target site in Chrome and interact with it to prove human presence |
# See full pipeline execution steps
opencli bilibili hot -v
# Check what explore discovered
cat .opencli/explore/mysite/endpoints.json
cat .opencli/explore/mysite/auth.json
opencli/
├── clis/ # Drop .ts or .yaml adapters here (auto-registered)
│ ├── bilibili.ts
│ ├── twitter.ts
│ └── hackernews.yaml
├── src/
│ ├── types.ts # CLIAdapter, Command interfaces
│ ├── browser.ts # Playwright MCP bridge wrapper
│ ├── loader.ts # Dynamic adapter loader
│ └── output.ts # table/json/yaml/md/csv formatters
├── tests/
│ └── e2e/ # E2E tests per site
└── CLI-EXPLORER.md # Full AI agent exploration workflow
Weekly Installs
348
Repository
GitHub Stars
10
First Seen
8 days ago
Security Audits
Gen Agent Trust HubWarnSocketPassSnykFail
Installed on
gemini-cli347
github-copilot347
codex347
amp347
cline347
kimi-cli347
agent-browser 浏览器自动化工具 - Vercel Labs 命令行网页操作与测试
140,500 周安装
AI技能使用规范:强制调用与优先级指南 | Claude技能工作流优化
276 周安装
个人财务管家 - Python财务分析工具,自动处理交易数据生成可视化报告
627 周安装
Expo移动应用开发指南:React Native应用开发、RevenueCat付费墙、AdMob广告集成
276 周安装
TypeScript/JavaScript日志模式审查与优化 - 结构化日志与错误处理最佳实践
629 周安装
Motion 动画库:JavaScript/React/Vue 高性能动画解决方案,支持120fps GPU加速
623 周安装
Python文档处理指南:PDF、Excel、Word、PowerPoint自动化操作与最佳实践
636 周安装