opencli-web-automation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OpenCLI Web Automation

OpenCLI 网页自动化

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.

ara.so 开发的技能工具 — 属于Daily 2026技能合集。
OpenCLI通过复用Chrome已登录的浏览器会话,将任意网站转换为命令行界面(CLI)。它开箱即用地支持19个网站和80+条命令,还允许你通过将TypeScript或YAML文件放入
clis/
文件夹来添加新的适配器。

Installation

安装

bash
undefined
bash
undefined

Install globally via npm

通过npm全局安装

npm install -g @jackwener/opencli
npm install -g @jackwener/opencli

One-time setup: discovers Playwright MCP token and distributes to all tools

一次性配置:发现Playwright MCP令牌并分发给所有工具

opencli setup
opencli setup

Verify everything is working

验证所有功能是否正常

opencli doctor --live
undefined
opencli doctor --live
undefined

Prerequisites

前置要求

  • Node.js >= 18.0.0
  • Chrome browser running and logged into the target site
  • Playwright MCP Bridge extension installed in Chrome
  • Node.js >= 18.0.0
  • Chrome浏览器 已运行且已登录目标网站
  • Playwright MCP Bridge 扩展程序已安装在Chrome中

Install from Source (Development)

从源码安装(开发环境)

bash
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link

bash
git clone git@github.com:jackwener/opencli.git
cd opencli
npm install
npm run build
npm link

Environment Configuration

环境配置

bash
undefined
bash
undefined

Required: set in ~/.zshrc or ~/.bashrc after running opencli setup

必需:运行opencli setup后,在~/.zshrc或~/.bashrc中设置

export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-from-setup>"

MCP client config (Claude/Cursor/Codex `~/.config/*/config.json`):

```json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest", "--extension"],
      "env": {
        "PLAYWRIGHT_MCP_EXTENSION_TOKEN": "$PLAYWRIGHT_MCP_EXTENSION_TOKEN"
      }
    }
  }
}

export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-from-setup>"

MCP客户端配置(Claude/Cursor/Codex的`~/.config/*/config.json`):

```json
{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest", "--extension"],
      "env": {
        "PLAYWRIGHT_MCP_EXTENSION_TOKEN": "$PLAYWRIGHT_MCP_EXTENSION_TOKEN"
      }
    }
  }
}

Key CLI Commands

核心CLI命令

Discovery & Registry

命令发现与注册表

bash
opencli list                        # Show all registered commands
opencli list -f yaml                # Output registry as YAML
opencli list -f json                # Output registry as JSON
bash
opencli list                        # 显示所有已注册的命令
opencli list -f yaml                # 以YAML格式输出注册表
opencli list -f json                # 以JSON格式输出注册表

Running Built-in Commands

运行内置命令

bash
undefined
bash
undefined

Public API commands (no browser login needed)

公共API命令(无需浏览器登录)

opencli hackernews top --limit 10 opencli github search "playwright automation" opencli bbc news
opencli hackernews top --limit 10 opencli github search "playwright automation" opencli bbc news

Browser commands (must be logged into site in Chrome)

浏览器命令(必须已在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"
undefined
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"
undefined

Output Formats

输出格式

All commands support
--format
/
-f
:
bash
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
所有命令均支持
--format
/
-f
参数:
bash
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           # Verbose模式:显示流水线调试步骤

AI Agent Workflow (Creating New Commands)

AI Agent工作流(创建新命令)

bash
undefined
bash
undefined

1. Deep explore a site — discovers APIs, auth, capabilities

1. 深度探索网站 — 发现API、认证方式和功能

opencli explore https://example.com --site mysite
opencli explore https://example.com --site mysite

2. Synthesize YAML adapters from explore artifacts

2. 从探索产物中合成YAML适配器

opencli synthesize mysite
opencli synthesize mysite

3. One-shot: explore → synthesize → register in one command

3. 一站式操作:探索→合成→注册,一步完成

opencli generate https://example.com --goal "hot posts"
opencli generate https://example.com --goal "hot posts"

4. Strategy cascade — auto-probes PUBLIC → COOKIE → HEADER auth

4. 策略级联 — 自动探测PUBLIC→COOKIE→HEADER三种认证方式


Explore artifacts are saved to `.opencli/explore/<site>/`:
- `manifest.json` — site metadata
- `endpoints.json` — discovered API endpoints
- `capabilities.json` — inferred command capabilities
- `auth.json` — authentication strategy

---

探索产物会保存到`.opencli/explore/<site>/`目录下:
- `manifest.json` — 网站元数据
- `endpoints.json` — 发现的API端点
- `capabilities.json` — 推断出的命令功能
- `auth.json` — 认证策略

---

Adding a New Adapter

添加新适配器

Option 1: YAML Declarative Adapter

选项1:YAML声明式适配器

Drop a
.yaml
file into
clis/
— auto-registered on next run:
yaml
undefined
.yaml
文件放入
clis/
目录 — 下次运行时会自动注册:
yaml
undefined

clis/producthunt.yaml

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}}"
undefined
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}}"
undefined

Option 2: TypeScript Adapter

选项2:TypeScript适配器

typescript
// 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;

typescript
// 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;

Common Patterns

常见模式

Pattern: Authenticated API Extraction (Cookie Injection)

模式:已认证API提取(Cookie注入)

typescript
// 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;
}
typescript
// 当网站暴露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;
}

Pattern: Header Token Extraction

模式:Header令牌提取

typescript
// 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;
}
typescript
// 从浏览器存储中提取认证令牌用于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;
}

Pattern: DOM Scraping with Wait

模式:带等待的DOM爬取

typescript
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));
}
typescript
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));
}

Pattern: Pagination

模式:分页处理

typescript
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));
}

typescript
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));
}

Maintenance Commands

维护命令

bash
undefined
bash
undefined

Diagnose token and config across all tools

诊断所有工具的令牌和配置

opencli doctor
opencli doctor

Test live browser connectivity

测试实时浏览器连接

opencli doctor --live
opencli doctor --live

Fix mismatched configs interactively

交互式修复不匹配的配置

opencli doctor --fix
opencli doctor --fix

Fix all configs non-interactively

非交互式修复所有配置

opencli doctor --fix -y

---
opencli doctor --fix -y

---

Testing

测试

bash
npm run build
bash
npm run build

Run all tests

运行所有测试

npx vitest run
npx vitest run

Unit tests only

仅运行单元测试

npx vitest run src/
npx vitest run src/

E2E tests only

仅运行端到端测试

npx vitest run tests/e2e/
npx vitest run tests/e2e/

Headless browser mode for CI

CI环境下使用无头浏览器模式

OPENCLI_HEADLESS=1 npx vitest run tests/e2e/

---
OPENCLI_HEADLESS=1 npx vitest run tests/e2e/

---

Troubleshooting

故障排查

SymptomFix
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 errorsUpgrade to Node.js >= 18
Token not foundRun
opencli setup
or
opencli doctor --fix
Stale login sessionVisit the target site in Chrome and interact with it to prove human presence
症状解决方法
Failed to connect to Playwright MCP Bridge
确保Chrome中已启用该扩展程序;安装后重启Chrome
数据为空 /
Unauthorized
打开Chrome,导航到目标网站,登录或刷新页面
Node API错误升级到Node.js >= 18版本
未找到令牌运行
opencli setup
opencli doctor --fix
登录会话过期在Chrome中访问目标网站并进行交互,以验证人工身份

Debug Verbose Mode

调试Verbose模式

bash
undefined
bash
undefined

See full pipeline execution steps

查看完整的流水线执行步骤

opencli bilibili hot -v
opencli bilibili hot -v

Check what explore discovered

查看探索过程发现的内容

cat .opencli/explore/mysite/endpoints.json cat .opencli/explore/mysite/auth.json

---
cat .opencli/explore/mysite/endpoints.json cat .opencli/explore/mysite/auth.json

---

Project Structure (for Adapter Authors)

项目结构(面向适配器开发者)

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
opencli/
├── clis/               # 将.ts或.yaml适配器放入此处(自动注册)
│   ├── bilibili.ts
│   ├── twitter.ts
│   └── hackernews.yaml
├── src/
│   ├── types.ts        # CLIAdapter、Command接口定义
│   ├── browser.ts      # Playwright MCP桥接器封装
│   ├── loader.ts       # 动态适配器加载器
│   └── output.ts       # 表格/JSON/YAML/MD/CSV格式器
├── tests/
│   └── e2e/            # 按网站划分的端到端测试
└── CLI-EXPLORER.md     # 完整的AI Agent探索工作流文档