Opencli Web Automation

作者 reason-machines2384a003145a無授權條款83 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 個月前更新

Turn any website into a CLI using browser session reuse and AI-powered command discovery

AI 產生的概覽

透過重用已登入的 Chrome 工作階段把網站變成 CLI 指令,並支援 AI 輔助探索與 YAML 或 TypeScript 轉接器。

功能
OpenCLI 把網站包裝成命令列指令,藉由 Playwright MCP Bridge 重用 Chrome 中已登入的瀏覽器工作階段。它內建多個網站的指令,並允許作者把 YAML 或 TypeScript 轉接器檔案放進 clis/ 資料夾來新增指令。它也提供 explore、synthesize、generate 與 cascade 工作流程,用來探索端點、驗證策略與能力,並支援 table、JSON、YAML、Markdown 與 CSV 輸出格式。
適用情境
當你希望不必重新登入就能從命令列擷取或自動化某個網站時使用它,或需要為某個網站建立新的 CLI 轉接器時使用。它也適合透過 doctor 指令排查瀏覽器工作階段與權杖問題。
執行需求
需要 Node.js 18 或更新版本、正在執行且已登入目標網站的 Chrome 瀏覽器,以及 Playwright MCP Bridge Chrome 擴充功能。需要設定 PLAYWRIGHT_MCP_EXTENSION_TOKEN 環境變數,並全域安裝 npm 套件 @jackwener/opencli。它不附帶指令碼,僅為說明文件。

OpenCLI Web Automation

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.


Installation

bash
# Install globally via npmnpm install -g @jackwener/opencli
# One-time setup: discovers Playwright MCP token and distributes to all toolsopencli setup
# Verify everything is workingopencli doctor --live

Prerequisites

  • Node.js >= 18.0.0
  • Chrome browser running and logged into the target site
  • Playwright MCP Bridge extension installed in Chrome

Install from Source (Development)

bash
git clone [email protected]:jackwener/opencli.gitcd openclinpm installnpm run buildnpm link

Environment Configuration

bash
# Required: set in ~/.zshrc or ~/.bashrc after running opencli setupexport 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"      }    }  }}

Key CLI Commands

Discovery & Registry

bash
opencli list                        # Show all registered commandsopencli list -f yaml                # Output registry as YAMLopencli list -f json                # Output registry as JSON

Running Built-in Commands

bash
# Public API commands (no browser login needed)opencli hackernews top --limit 10opencli github search "playwright automation"opencli bbc news
# Browser commands (must be logged into site in Chrome)opencli bilibili hot --limit 5opencli twitter trendingopencli zhihu hot -f jsonopencli reddit frontpage --limit 20opencli xiaohongshu search "TypeScript"opencli youtube search "browser automation"opencli linkedin search "senior engineer"

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      # YAMLopencli bilibili hot -f md        # Markdownopencli bilibili hot -f csv       # CSV exportopencli bilibili hot -v           # Verbose: show pipeline debug steps

AI Agent Workflow (Creating New Commands)

bash
# 1. Deep explore a site — discovers APIs, auth, capabilitiesopencli explore https://example.com --site mysite
# 2. Synthesize YAML adapters from explore artifactsopencli synthesize mysite
# 3. One-shot: explore → synthesize → register in one commandopencli generate https://example.com --goal "hot posts"
# 4. Strategy cascade — auto-probes PUBLIC → COOKIE → HEADER authopencli cascade https://api.example.com/data

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

Adding a New Adapter

Option 1: YAML Declarative Adapter

Drop a .yaml file into clis/ — auto-registered on next run:

yaml
# clis/producthunt.yamlsite: producthuntcommands:  - 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}}"

Option 2: TypeScript Adapter

typescript
// clis/producthunt.tsimport 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)

typescript
// When a site exposes a JSON API but requires login cookiesasync 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;}

Pattern: Header Token Extraction

typescript
// Extract auth tokens from browser storage for API callsasync 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

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

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

Maintenance Commands

bash
# Diagnose token and config across all toolsopencli doctor
# Test live browser connectivityopencli doctor --live
# Fix mismatched configs interactivelyopencli doctor --fix
# Fix all configs non-interactivelyopencli doctor --fix -y

Testing

bash
npm run build
# Run all testsnpx vitest run
# Unit tests onlynpx vitest run src/
# E2E tests onlynpx vitest run tests/e2e/
# Headless browser mode for CIOPENCLI_HEADLESS=1 npx vitest run tests/e2e/

Troubleshooting

SymptomFix
Failed to connect to Playwright MCP BridgeEnsure extension is enabled in Chrome; restart Chrome after install
Empty data / UnauthorizedOpen 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

Debug Verbose Mode

bash
# See full pipeline execution stepsopencli bilibili hot -v
# Check what explore discoveredcat .opencli/explore/mysite/endpoints.jsoncat .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

來源與署名

來源:reason-machines/trending-skills位於skills/opencli-web-automation提交2384a00

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架