Skip to content

OpenCLI 开源项目解读

项目信息

  • 项目名称:OpenCLI

  • 项目描述:OpenCLI 是一个让你通过命令行操作网站、控制 Electron 应用、统一管理 CLI 工具的 AI 驱动型自动化平台。

  • 项目地址:https://github.com/jackwener/OpenCLI

  • 核心功能

    • 70+ 预置网站适配器(bilibili、twitter、hackernews 等)
    • 浏览器自动化(复用登录会话,无需密码)
    • Electron 应用控制(Cursor、ChatGPT 等)
    • CLI 工具统一管理
    • AI Agent 友好的技能系统

1. 项目概览

1.1 项目定位与核心价值

一句话定位:OpenCLI 是一个将网站、浏览器会话、Electron 应用和本地工具转换为确定性命令行接口的 AI 驱动型自动化平台。

核心痛点解决

  1. 网站操作自动化困难:传统爬虫易被反爬虫拦截,需要复用已登录会话。
  2. AI Agent 缺乏可靠的网站操作能力:需要标准化接口、安全的登录状态复用、确定性输出。
  3. CLI 工具碎片化:各种工具分散,缺乏统一发现和调用机制。
  4. Electron 应用自动化:桌面应用缺乏自动化接口。

目标用户

  • 普通用户:通过命令行快速使用网站功能
  • AI Agent 开发者:为 Agent 提供浏览器操作和 CLI 调用能力
  • 网站自动化开发者:编写适配器将网站转为 CLI 命令
  • Claude Code / Cursor 用户:使用内置技能快速操作网站

1.2 目标用户与使用场景

核心场景

  • AI Agent 实时网站操作:Agent 通过 OpenCLI 操作用户已登录的浏览器
  • 确定性网站 CLI 命令:使用预置适配器(70+ 网站)
  • CLI 工具统一管理:作为统一入口发现、安装、调用各种 CLI
  • Electron 应用控制:直接控制 Cursor、ChatGPT 等桌面应用

1.3 核心技术亮点

  • 分层插件化架构:抽象浏览器操作、插件化适配器
  • 安全的会话复用:Daemon + Extension 架构,多层安全防御
  • AI Agent 友好:结构化技能、确定性输出
  • 70+ 预置网站适配器:开箱即用
  • 多种浏览器连接方式:BrowserBridge(扩展)、CDPBridge(直接 CDP)
  • 策略模式适配器:PUBLIC/COOKIE/HEADER/INTERCEPT/UI 多种策略

1.4 技术栈与选型对比

层级技术选型
运行时Node.js >= 21.0.0
CLI 框架Commander.js
浏览器通信Chrome DevTools Protocol (CDP), WebSocket (ws)
数据格式JSON, YAML, CSV, Markdown
测试框架Vitest
文档工具VitePress

关键选型决策

  • 直接使用 CDP 而非 Puppeteer:可连接任意 Chrome 实例、复用登录会话
  • Daemon + Extension 架构:安全地桥接 CLI 和浏览器
  • Skills 系统:为 AI Agent 提供结构化工作流

2. 整体架构设计

2.1 架构概述

OpenCLI 采用分层插件化架构,通过标准化的接口将 CLI、浏览器自动化、网站适配器和 AI 技能组合在一起。架构设计的核心理念是:

  • 抽象浏览器操作:通过 IPage 接口统一不同浏览器控制方式
  • 插件化适配器:网站适配器可以独立开发和注册
  • 安全的会话复用:通过 Daemon + Extension 架构复用浏览器登录状态
  • AI Agent 友好:提供结构化的技能和确定性的输出

2.2 整体架构图 (ASCII)

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                         用户 / AI Agent 层                                   │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐                   │
│  │   人类用户   │    │  Claude Code │    │   其他 Agent │                   │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘                   │
└─────────┼───────────────────┼───────────────────┼───────────────────────────┘
          │                   │                   │
          └───────────────────┴───────────────────┘

┌─────────────────────────────▼───────────────────────────────────────────────┐
│                         CLI 层 (main.ts + cli.ts)                            │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │  Commander.js 命令解析器                                               │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐                 │ │
│  │  │  内置命令    │  │  适配器命令   │  │ 外部 CLI     │                 │ │
│  │  │  (list,     │  │  (bilibili,   │  │  (gh,        │                 │ │
│  │  │   doctor)   │  │   twitter)    │  │   docker)    │                 │ │
│  │  └──────────────┘  └──────────────┘  └──────────────┘                 │ │
│  └───────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────┬───────────────────────────────────────────────┘

              ┌───────────────┴───────────────┐
              │                               │
┌─────────────▼─────────────┐    ┌────────────▼─────────────┐
│    适配器层 (registry.ts) │    │  管道执行层 (pipeline/)  │
│  ┌───────────────────────┐│    ┌─────────────────────────┐│
│  │ 策略枚举:            ││    │ 步骤类型:               ││
│  │  PUBLIC/COOKIE/      ││    │  fetch/extract/         ││
│  │  HEADER/INTERCEPT/UI ││    │  download/transform     ││
│  └───────────────────────┘│    └─────────────────────────┘│
│  ┌───────────────────────┐│    ┌─────────────────────────┐│
│  │ 70+ 网站适配器       ││    │ 执行引擎 (executor.ts) ││
│  └───────────────────────┘│    └─────────────────────────┘│
└─────────────┬─────────────┘    └────────────┬──────────────┘
              │                               │
              └───────────────┬───────────────┘

┌─────────────────────────────▼───────────────────────────────────────────────┐
│                      浏览器控制层 (src/browser/)                            │
│  ┌───────────────────────────────────────────────────────────────────────┐ │
│  │  IPage 接口抽象 (types.ts)                                             │ │
│  ├───────────────────────────────────────────────────────────────────────┤ │
│  │  ┌──────────────────┐    ┌──────────────────┐                         │ │
│  │  │ BrowserBridge    │    │   CDPBridge      │                         │ │
│  │  │ (Extension模式)  │    │  (直接CDP连接)   │                         │ │
│  │  └────────┬─────────┘    └────────┬─────────┘                         │ │
│  └───────────┼─────────────────────────┼───────────────────────────────────┘ │
└──────────────┼─────────────────────────┼───────────────────────────────────────┘
               │                         │
    ┌──────────▼──────────┐    ┌─────────▼─────────┐
    │ DOM 快照与提取      │    │ 网络请求拦截     │
    │ (dom-snapshot.ts)  │    │ (network-cache.ts)│
    └─────────────────────┘    └───────────────────┘
               │                         │
┌──────────────▼─────────────────────────▼───────────────────────────────────┐
│                        通信层 (Daemon + Extension)                         │
│  ┌─────────────────────────┐            ┌─────────────────────────┐       │
│  │  daemon.ts              │            │  Browser Extension      │       │
│  │  (本地 HTTP 服务)       │◄──────────►│  (WebSocket 连接)       │       │
│  └─────────────────────────┘            └─────────────────────────┘       │
└─────────────────────────────────────────────────────────────────────────────┘


                    ┌───────────────────┐
                    │ Chrome/Chromium   │
                    │ (已登录会话)      │
                    └───────────────────┘

2.3 分层职责说明

职责核心文件
CLI 层命令解析、参数处理、输出格式化main.ts, cli.ts, output.ts
适配器层70+ 网站适配器、命令注册、策略管理registry.ts, discovery.ts, clis/
浏览器控制层IPage 抽象、BrowserBridge/CDPBridge、DOM 操作browser/page.ts, bridge.ts, cdp.ts
通信层Daemon HTTP+WS 服务、扩展通信daemon.ts, extension/src/

2.4 完整目录树

shell
opencli/
├── src/                                   # 【核心基建】源代码主目录
   ├── main.ts                            # 【核心基建】CLI 入口,快速路径优化
   ├── cli.ts                             # 【核心基建】CLI 命令注册与 Commander 集成
   ├── daemon.ts                          # 【核心基建】本地守护进程,HTTP+WS 服务
   ├── types.ts                           # 【核心基建】核心类型定义(IPage, etc.)
   ├── registry.ts                        # 【核心基建】命令注册中心(globalThis 单例)
   ├── discovery.ts                       # 【核心基建】适配器/插件发现与加载
   ├── execution.ts                       # 【核心基建】命令执行引擎
   ├── launcher.ts                        # 【核心基建】浏览器会话启动器
   ├── output.ts                          # 【核心基建】输出格式化(table/json/yaml/md/csv)
   ├── errors.ts                          # 【核心基建】错误定义与处理
   ├── constants.ts                       # 【核心基建】常量定义
   ├── utils.ts                           # 【工具集】通用工具函数
   ├── logger.ts                          # 【工具集】日志工具
   ├── version.ts                         # 【工具集】版本信息
   ├── hooks.ts                           # 【工具集】生命周期钩子
   ├── update-check.ts                    # 【工具集】更新检查
   ├── external.ts                        # 【业务模块】外部 CLI 集成
   ├── plugin.ts                          # 【业务模块】插件系统
   ├── plugin-scaffold.ts                 # 【业务模块】插件脚手架生成
   ├── plugin-manifest.ts                 # 【工具集】插件 manifest 处理
   ├── commanderAdapter.ts                # 【工具集】Commander.js 适配器注册
   ├── completion.ts                      # 【工具集】Shell 补全生成
   ├── completion-fast.ts                 # 【工具集】快速补全(从 manifest 读取)
   ├── completion-shared.ts               # 【工具集】补全共享逻辑
   ├── validate.ts                        # 【工具集】适配器验证
   ├── verify.ts                          # 【工具集】适配器验证+测试
   ├── build-manifest.ts                  # 【工具集】构建命令 manifest
   ├── capabilityRouting.ts               # 【工具集】功能路由
   ├── node-network.ts                    # 【工具集】Node.js 网络工具
   ├── runtime.ts                         # 【工具集】运行时检测
   ├── runtime-detect.ts                  # 【工具集】运行时环境检测
   ├── diagnostic.ts                      # 【工具集】诊断工具
   ├── doctor.ts                          # 【工具集】Doctor 命令实现
   ├── electron-apps.ts                   # 【业务模块】Electron 应用控制
   ├── interceptor.ts                     # 【工具集】网络拦截器
   ├── snapshotFormatter.ts               # 【工具集】DOM 快照格式化
   ├── tui.ts                             # 【UI 视图】终端 UI 组件
   ├── analysis.ts                        # 【工具集】网站分析(反爬虫检测等)
   ├── package-paths.ts                   # 【工具集】包路径解析
   ├── external-clis.yaml                 # 【配置】外部 CLI 定义

   ├── browser/                           # 【核心基建】浏览器控制模块
   ├── index.ts                       # 【核心基建】模块导出入口
   ├── bridge.ts                      # 【核心基建】BrowserBridge - 扩展通信
   ├── cdp.ts                         # 【核心基建】CDPBridge - 直接 CDP 连接
   ├── page.ts                        # 【核心基建】Page - IPage 实现
   ├── base-page.ts                   # 【核心基建】BasePage - 基础实现
   ├── dom-snapshot.ts                # 【核心基建】DOM 快照生成
   ├── dom-helpers.ts                 # 【工具集】DOM 帮助函数
   ├── html-tree.ts                   # 【工具集】HTML 树生成
   ├── extract.ts                     # 【工具集】内容提取(文章/Markdown)
   ├── find.ts                        # 【工具集】元素查找
   ├── target-resolver.ts             # 【核心基建】目标解析(CSS+数字引用)
   ├── target-errors.ts               # 【工具集】目标相关错误
   ├── shape.ts                       # 【工具集】数据形状推断
   ├── shape-filter.ts                # 【工具集】形状过滤
   ├── network-key.ts                 # 【工具集】网络请求 Key 生成
   ├── network-cache.ts               # 【核心基建】网络缓存(保存请求供分析)
   ├── analyze.ts                     # 【核心基建】网站分析(反爬虫检测)
   ├── stealth.ts                     # 【核心基建】反检测脚本注入
   ├── daemon-client.ts               # 【工具集】Daemon 客户端
   ├── verify-fixture.ts              # 【工具集】验证夹具
   ├── errors.ts                      # 【工具集】错误定义
   ├── compound.ts                    # 【工具集】复合操作
   ├── tabs.ts                        # 【工具集】标签页管理
   ├── utils.ts                       # 【工具集】浏览器工具函数
   ├── article-extract.ts             # 【工具集】文章提取(Readability)
   └── __fixtures__/                  # 【配置】测试夹具

   ├── pipeline/                          # 【核心基建】管道执行引擎
   ├── index.ts                       # 【核心基建】模块导出
   ├── executor.ts                    # 【核心基建】管道执行器
   ├── registry.ts                    # 【核心基建】管道步骤注册
   └── steps/                         # 【核心基建】内置管道步骤
       ├── browser.ts                 # 浏览器操作步骤
       ├── fetch.ts                   # 网络请求步骤
       └── download.test.ts           # 下载步骤测试

   ├── download/                          # 【业务模块】下载功能
   ├── index.ts                       # 【核心基建】下载模块入口
   ├── media-download.ts              # 【业务模块】媒体下载
   ├── article-download.ts            # 【业务模块】文章下载
   └── progress.ts                    # 【工具集】下载进度显示

   └── commands/                          # 【业务模块】内置命令实现
       └── daemon.ts                      # daemon 子命令

├── clis/                                  # 【业务模块】网站适配器目录(70+ 网站)
   ├── _shared/                          # 【工具集】适配器共享代码
   ├── _shared/desktop-commands.js       # 【工具集】桌面应用命令
   ├── bilibili/                         # 【业务模块】Bilibili 适配器
   ├── hot.js                        # 热门
   ├── search.js                     # 搜索
   ├── video.js                      # 视频
   ├── download.js                   # 下载
   └── ...
   ├── twitter/                          # 【业务模块】Twitter/X 适配器
   ├── hackernews/                       # 【业务模块】HackerNews 适配器
   ├── reddit/                           # 【业务模块】Reddit 适配器
   ├── 1688/                             # 【业务模块】1688 适配器
   └── ... (70+ 网站适配器)

├── extension/                            # 【核心基建】浏览器扩展(Browser Bridge)
   ├── manifest.json                     # 【配置】扩展 manifest
   ├── src/background.ts                 # 【核心基建】Background script
   ├── src/cdp.ts                        # 【核心基建】CDP 封装
   ├── src/identity.ts                   # 【工具集】身份验证
   ├── src/protocol.ts                   # 【工具集】协议定义
   ├── popup.html                        # 【UI 视图】Popup 页面
   ├── popup.js                          # 【UI 视图】Popup 脚本
   └── package.json                      # 【配置】扩展依赖

├── skills/                               # 【工具集】AI Agent 技能
   ├── opencli-adapter-author/           # 【工具集】适配器编写技能
   ├── SKILL.md                      # 技能主文件
   └── references/                   # 参考文档
   ├── opencli-autofix/                  # 【工具集】自动修复技能
   ├── opencli-browser/                  # 【工具集】浏览器操作参考
   ├── opencli-usage/                    # 【工具集】使用指南
   └── smart-search/                     # 【工具集】智能搜索

├── docs/                                 # 【配置】项目文档
   ├── adapters/                         # 适配器文档
   └── guide/                            # 使用指南

├── scripts/                              # 【工具集】构建和部署脚本
   ├── clean-dist.cjs                    # 清理 dist
   ├── copy-yaml.cjs                     # 复制 yaml 文件
   ├── fetch-adapters.js                 # 获取适配器
   └── postinstall.js                    # 安装后脚本

├── autoresearch/                         #  自动化研究相关

├── tests/                                # 【质量保证】测试目录

├── .github/                              # 【配置】GitHub Actions CI/CD

├── package.json                          # 【配置】项目配置
├── tsconfig.json                         # 【配置】TypeScript 配置
├── cli-manifest.json                     # 【配置】命令 manifest(构建生成)
├── README.md                             # 【配置】英文 README
├── README.zh-CN.md                       # 【配置】中文 README
├── LICENSE                               # 【配置】许可证
└── PRIVACY.md                            # 【配置】隐私政策

3. 模块依赖与调用关系

3.1 全局入口与核心路由

逻辑说明

OpenCLI 的入口流程设计为快速路径优先,避免为简单命令付出完整启动成本:

  1. 快速路径 (--version, completion, --get-completions):

    • 直接处理,不进行适配器发现
    • 读取缓存的 manifest 文件获取补全信息
  2. 完整路径(其他命令):

    • 并行执行:用户适配器兼容性检查 + 内置适配器发现
    • 加载用户适配器(~/.opencli/clis/
    • 发现插件
    • 注册更新检查钩子
    • 解析命令并执行

调用拓扑 (plainText)

text
main.ts (入口)

  ├─── 快速路径检查
  │     ├───> --version → 直接输出版本号 → 退出
  │     ├───> completion <shell> → 打印补全脚本 → 退出
  │     └───> --get-completions → 读取 manifest → 输出补全 → 退出

  └─── 完整路径 (普通命令)

        ├─── 并行启动
        │     ├───> ensureUserCliCompatShims()
        │     ├───> ensureUserAdapters()
        │     └───> discoverClis(BUILTIN_CLIS)

        ├───> discoverClis(USER_CLIS)
        ├───> discoverPlugins()

        ├───> registerUpdateNoticeOnExit()
        ├───> checkForUpdateBackground()

        └───> cli.ts → runCli()

              └───> Commander.js 解析命令

                    ├───> 内置命令处理
                    │     ├─── list
                    │     ├─── doctor
                    │     ├─── browser <subcmd>
                    │     ├─── plugin <subcmd>
                    │     └─── ...

                    ├───> 适配器命令处理
                    │     └───> execution.ts → executeCommand()
                    │           │
                    │           ├───> (如有必要) launcher.ts → 启动浏览器会话
                    │           ├───> 执行适配器 func() 或 pipeline
                    │           └───> output.ts → 格式化输出

                    └───> 外部 CLI 处理
                          └───> external.ts → 透传到外部工具

3.2 核心业务实体与关联

实体定义

实体定义核心属性
CliCommand一个 CLI 命令(网站适配器或内置命令)site, name, strategy, args, func/pipeline
IPage浏览器页面的抽象接口goto, evaluate, click, typeText, snapshot, 等
Strategy适配器认证/执行策略枚举PUBLIC, COOKIE, HEADER, INTERCEPT, UI
Registry全局命令注册中心Map<site/name, CliCommand>

实体引用拓扑 (ASCII)

text
[CliCommand] (策略: Strategy)

  ├─── 1:1 ──> [IPage] (浏览器页面抽象)
  │            │
  │            ├───> BrowserBridge 实现
  │            └───> CDPBridge 实现

  ├─── 1:N ──> [Arg] (命令参数定义)

  └─── 注册到

        [Registry] (全局单例,使用 globalThis)

4. 核心模块详解

4.1 浏览器控制模块 (src/browser/)

模块名:浏览器控制模块

设计说明:采用桥接模式 + 策略模式设计:

  • IPage 接口:定义统一页面操作抽象
  • BrowserBridge:通过浏览器扩展 + Daemon 通信
  • CDPBridge:直接连接 Chrome DevTools Protocol(用于 Electron)
  • Page 类:统一实现,依赖底层桥接

关键技术点

  1. 目标解析 (Target Resolver)

    • 支持 CSS 选择器和数字引用(来自 snapshot)
    • 提供稳定的元素重识别机制(应对 DOM 变化)
    • 匹配级别:exact → stable → reidentified
  2. Stealth 脚本

    • 注入各种反检测脚本
    • 隐藏自动化特征
  3. 网络缓存

    • 保存网络请求用于后续分析
    • 支持按 key 检索完整响应

内部结构图 (ASCII)

text
┌─────────────────────────────────────────────────────────────────────────┐
│                          IPage 接口 (types.ts)                            │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  goto(url)         evaluate(js)        click(ref)                   ││
│  │  typeText(ref, text)  pressKey(key)    snapshot(opts)               ││
│  │  wait(opts)          tabs()             networkRequests()           ││
│  └─────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────┬─────────────────────────────────────────────┘

          ┌───────────────────┴───────────────────┐
          │                                       │
┌─────────▼──────────┐                   ┌────────▼─────────┐
│  BrowserBridge     │                   │   CDPBridge      │
│  (Extension +     │                    │  (直接 CDP)      │
│   Daemon)         │                    │                  │
│  ┌─────────────┐ │                     │ ┌─────────────┐ │
│  │ bridge.ts   │ │                     │ │ cdp.ts      │ │
│  │ 连接扩展    │ │                      │ │ 连接 Chrome │ │
│  └─────────────┘ │                     │ └─────────────┘ │
└─────────┬─────────┘                    └────────┬─────────┘
          │                                       │
          └───────────────┬───────────────────────┘

                  ┌───────▼───────┐
                  │   Page 类     │
                  │  (page.ts)   │
                  └───────┬───────┘

         ┌────────────────┼────────────────┐
         │                │                │
    ┌────▼────┐    ┌────▼────┐    ┌──────▼──────┐
    │ DOM快照  │    │ 元素查找 │    │ 网络拦截   │
    │ (dom-   │    │ (find.ts)│    │ (network-  │
    │ snapshot│    │          │    │  cache.ts) │
    │ .ts)    │    └─────────┘    └────────────┘
    └─────────┘

    ┌────▼────┐
    │ 目标解析 │
    │ (target-│
    │ resolver│
    │ .ts)    │
    └─────────┘

4.2 适配器注册与发现 (src/registry.ts + discovery.ts)

模块名:适配器注册与发现模块

设计说明:采用注册中心模式 + 懒加载设计:

  • 使用 globalThis 确保单例(应对 npm link)
  • 支持多种来源:内置、用户目录、插件
  • 策略自动归一化(strategy → browser + navigateBefore)

策略归一化规则

StrategybrowsernavigateBefore说明
PUBLICfalseundefined不需要浏览器
COOKIEtrue"https://domain"需要浏览器,预导航
HEADERtrue"https://domain"需要自定义 header
INTERCEPTtruetrue需要拦截网络
UItruetrue需要 UI 自动化

内部结构图

text
┌─────────────────────────────────────────────────────────────────────────┐
│                        Registry (registry.ts)                           │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  globalThis.__opencli_registry__: Map<string, CliCommand>           ││
│  └─────────────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  cli(opts) → 注册命令                                               ││
│  │  registerCommand(cmd) → 归一化后注册                                ││
│  │  getRegistry() → 获取全局 Map                                       ││
│  │  fullName(cmd) → "site/name"                                        ││
│  └─────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────┬─────────────────────────────────────────────┘

┌─────────────────────────────▼─────────────────────────────────────────────┐
│                        Discovery (discovery.ts)                          │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  discoverClis(dir) → 扫描目录下的适配器                              ││
│  │  - 支持 .js/.ts/.yaml 格式                                           ││
│  │  - 递归扫描子目录                                                    ││
│  ├─────────────────────────────────────────────────────────────────────┤│
│  │  discoverPlugins() → 扫描已安装插件                                  ││
│  │  ensureUserAdapters() → 确保用户适配器目录存在                        ││
│  └─────────────────────────────────────────────────────────────────────┘│
└───────────────────────────────────────────────────────────────────────────┘

         ┌────────────────────┼────────────────────┐
         │                    │                    │
    ┌────▼────┐        ┌─────▼─────┐       ┌──────▼──────┐
    │ 内置    │        │ 用户目录  │       │ 插件        │
    │ clis/   │        │ ~/.opencli│       │ 动态加载    │
    │         │        │ /clis/    │       │             │
    └─────────┘        └───────────┘       └─────────────┘

4.3 守护进程 (src/daemon.ts)

设计说明

守护进程是 OpenCLI 安全架构的核心,采用多层防御设计:

  • Origin 检查:只接受 chrome-extension:// 请求
  • 自定义 Header:要求 X-OpenCLI(浏览器无法跨域发送)
  • 无 CORS:防止网页直接调用
  • Body 大小限制:防止 OOM 攻击

内部结构图

text
┌─────────────────────────────────────────────────────────────────────────┐
│                        Daemon (daemon.ts)                               │
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  HTTP Server (Node.js http.createServer)                            ││
│  │  监听: 127.0.0.1:19825 (默认)                                       ││
│  └─────────────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  HTTP 端点                                                          ││
│  │  GET  /ping    ──┐                                                  ││
│  │  GET  /status    │  无 X-OpenCLI 要求 (健康检查)                   ││
│  │  GET  /logs      │                                                  ││
│  │  DELETE /logs   ──┘                                                  ││
│  │  POST /command  ──┐                                                 ││
│  │  POST /shutdown   │  要求 X-OpenCLI header                          ││
│  │  OPTIONS *       ──┘  (preflight 返回 204, 无 CORS)                ││
│  └─────────────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  WebSocket Server (ws library)                                      ││
│  │  路径: /ext                                                         ││
│  │  verifyClient: 检查 Origin 必须是 chrome-extension://               ││
│  └─────────────────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────────────────┐│
│  │  内部状态                                                           ││
│  │  extensionWs: WebSocket | null (扩展连接)                           ││
│  │  pending: Map<id, {resolve, reject, timer}> (等待中的命令)          ││
│  │  logBuffer: LogEntry[] (日志环形缓冲区)                             ││
│  └─────────────────────────────────────────────────────────────────────┘│
└───────────────────────────────────────────────────────────────────────────┘


              ┌───────────────────────────────┐
              │  命令流转:                    │
              │  CLI → HTTP → Daemon → WS →  │
              │  Extension → 执行 → WS →     │
              │  Daemon → HTTP → CLI         │
              └───────────────────────────────┘

关键端点

  • GET /ping:健康检查(无 X-OpenCLI 要求)
  • GET /status:Daemon 状态
  • POST /command:执行浏览器命令
  • POST /shutdown:关闭 Daemon

心跳与重连机制

  • Extension 每 15s 发送 ping
  • 错过 2 个 pong 则认为连接丢失
  • 关闭连接,清理 pending 命令

5. 关键数据流程

5.1 浏览器命令执行流程

场景说明:用户执行 opencli browser open https://example.com

流转时序图 (Mermaid)

mermaid
sequenceDiagram
    participant User as 用户/Agent
    participant CLI as main.ts
    participant Daemon as daemon.ts
    participant Ext as Browser Extension
    participant Chrome as Chrome/Chromium

    User->>CLI: opencli browser open <url>
    activate CLI

    CLI->>CLI: 检查 daemon 是否运行
    alt Daemon 未运行
        CLI->>CLI: 自动启动 daemon (子进程)
    end

    CLI->>Daemon: GET /ping (检查健康)
    activate Daemon
    Daemon-->>CLI: {ok: true}
    deactivate Daemon

    CLI->>Daemon: POST /command {id, cmd: "open", url}
    activate Daemon

    alt Extension 未连接
        Daemon-->>CLI: 503 {error: "Extension not connected"}
        CLI->>User: 显示错误提示
        deactivate CLI
        stop
    end

    Daemon->>Ext: WebSocket 发送 {id, cmd: "open", url}
    activate Ext

    Ext->>Chrome: CDP 打开 URL
    activate Chrome
    Chrome-->>Ext: 页面加载完成
    deactivate Chrome

    Ext-->>Daemon: WebSocket 返回 {id, ok: true, page: targetId}
    deactivate Ext

    Daemon-->>CLI: HTTP 200 {id, ok: true, page: targetId}
    deactivate Daemon

    CLI->>User: 输出 JSON 结果
    deactivate CLI

5.2 网站适配器执行流程

场景说明:用户执行 opencli bilibili hot --limit 5,这是典型的适配器使用流程。

流转时序图 (Mermaid)

mermaid
sequenceDiagram
    participant User as 用户
    participant CLI as cli.ts
    participant Exec as execution.ts
    participant Launcher as launcher.ts
    participant Adapter as Bilibili Adapter
    participant Page as IPage
    participant Output as output.ts

    User->>CLI: opencli bilibili hot --limit 5
    activate CLI

    CLI->>CLI: 从 Registry 查找 bilibili/hot
    CLI->>Exec: executeCommand(cmd, args)
    activate Exec

    Exec->>Exec: normalizeCommand(cmd)
    note over Exec: 根据 Strategy 确定 browser=true, navigateBefore

    alt 需要浏览器
        Exec->>Launcher: getBrowserSession()
        activate Launcher
        Launcher->>Launcher: 检查/启动 daemon+extension
        Launcher-->>Exec: 返回 IPage 实例
        deactivate Launcher

        alt 需要预导航
            Exec->>Page: page.goto(navigateBeforeUrl)
            activate Page
            Page->>Page: 等待页面加载
            deactivate Page
        end
    end

    Exec->>Adapter: 调用 cmd.func(page, args)
    activate Adapter

    alt 使用 Pipeline
        Adapter->>Exec: pipeline 执行
    else 使用 func
        Adapter->>Page: 各种页面操作
        Adapter->>Page: page.evaluate(js)
        Adapter->>Page: page.extract(selector)
        Page-->>Adapter: 返回数据
    end

    Adapter-->>Exec: 返回结果数据
    deactivate Adapter

    Exec->>Output: formatOutput(data, format)
    activate Output
    Output->>Output: 格式化 (table/json/yaml/md/csv)
    Output-->>User: 输出结果
    deactivate Output

    deactivate Exec
    deactivate CLI

6. 接口与契约规范

6.1 核心内部模块契约 (TypeScript)

IPage - 浏览器页面抽象

IPage - 浏览器页面抽象

typescript
/**
 * Page interface: type-safe abstraction over browser page.
 * All pipeline steps and CLI adapters should use this interface.
 */
export interface IPage {
  goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void>;
  evaluate(js: string): Promise<any>;
  evaluateWithArgs?(js: string, args: Record<string, unknown>): Promise<any>;
  getCookies(opts?: { domain?: string; url?: string }): Promise<BrowserCookie[]>;
  snapshot(opts?: SnapshotOptions): Promise<any>;
  click(ref: string, opts?: { nth?: number; firstOnMulti?: boolean }): Promise<{
    matches_n: number;
    match_level: 'exact' | 'stable' | 'reidentified';
  }>;
  typeText(ref: string, text: string, opts?: { nth?: number; firstOnMulti?: boolean }): Promise<{
    matches_n: number;
    match_level: 'exact' | 'stable' | 'reidentified';
  }>;
  pressKey(key: string): Promise<void>;
  scrollTo(ref: string, opts?: { nth?: number; firstOnMulti?: boolean }): Promise<any>;
  getFormState(): Promise<any>;
  wait(options: number | WaitOptions): Promise<void>;
  tabs(): Promise<any>;
  closeTab?(target?: number | string): Promise<void>;
  newTab?(url?: string): Promise<string | undefined>;
  selectTab(target: number | string): Promise<void>;
  networkRequests(includeStatic?: boolean): Promise<any>;
  consoleMessages(level?: string): Promise<any>;
  scroll(direction?: string, amount?: number): Promise<void>;
  autoScroll(options?: { times?: number; delayMs?: number }): Promise<void>;
  installInterceptor(pattern: string): Promise<void>;
  getInterceptedRequests(): Promise<any[]>;
  waitForCapture(timeout?: number): Promise<void>;
  screenshot(options?: ScreenshotOptions): Promise<string>;
  startNetworkCapture?(pattern?: string): Promise<boolean>;
  readNetworkCapture?(): Promise<unknown[]>;
  setFileInput?(files: string[], selector?: string): Promise<void>;
  insertText?(text: string): Promise<void>;
  closeWindow?(): Promise<void>;
  getCurrentUrl?(): Promise<string | null>;
  getActivePage?(): string | undefined;
  setActivePage?(page?: string): void;
  cdp?(method: string, params?: Record<string, unknown>): Promise<unknown>;
  frames?(): Promise<Array<{ index: number; frameId: string; url: string; name: string }>>;
  evaluateInFrame?(js: string, frameIndex: number): Promise<unknown>;
  nativeClick?(x: number, y: number): Promise<void>;
  nativeType?(text: string): Promise<void>;
  nativeKeyPress?(key: string, modifiers?: string[]): Promise<void>;
}

CliCommand - 命令定义

typescript
/**
 * Core command definition: describes a CLI adapter or built-in command.
 */
export enum Strategy {
  PUBLIC = 'public',
  LOCAL = 'local',
  COOKIE = 'cookie',
  HEADER = 'header',
  INTERCEPT = 'intercept',
  UI = 'ui',
}

export interface Arg {
  name: string;
  type?: string;
  default?: unknown;
  required?: boolean;
  valueRequired?: boolean;
  positional?: boolean;
  help?: string;
  choices?: string[];
}

export interface CliCommand {
  site: string;
  name: string;
  aliases?: string[];
  description: string;
  domain?: string;
  strategy?: Strategy;
  browser?: boolean;
  args: Arg[];
  columns?: string[];
  func?: (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
  pipeline?: Record<string, unknown>[];
  timeoutSeconds?: number;
  source?: string;  // 'yaml', 'ts', or plugin name
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
  requiredEnv?: RequiredEnv[];
  validateArgs?: (kwargs: CommandArgs) => void;
  deprecated?: boolean | string;
  replacedBy?: string;
  navigateBefore?: boolean | string;
  defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'csv';
}

6.2 Daemon HTTP API (OpenSpec 格式)

关键端点

  • POST /command:执行浏览器命令(需要 X-OpenCLI header)
  • GET /status:获取 Daemon 状态
  • GET /ping:健康检查
yaml
openapi: 3.0.0
info:
  title: OpenCLI Daemon API
  version: 1.0.0
servers:
  - url: http://127.0.0.1:19825
paths:
  /ping:
    get:
      summary: 健康检查(无 X-OpenCLI 要求)
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
  /status:
    get:
      summary: Daemon 状态
      parameters:
        - name: X-OpenCLI
          in: header
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  pid:
                    type: integer
                  uptime:
                    type: number
                  daemonVersion:
                    type: string
                  extensionConnected:
                    type: boolean
                  extensionVersion:
                    type: string
                  pending:
                    type: integer
                  memoryMB:
                    type: number
                  port:
                    type: integer
  /logs:
    get:
      summary: 获取日志
      parameters:
        - name: X-OpenCLI
          in: header
          required: true
          schema:
            type: string
        - name: level
          in: query
          schema:
            type: string
      responses:
        '200':
          description: OK
    delete:
      summary: 清空日志
      parameters:
        - name: X-OpenCLI
          in: header
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
  /command:
    post:
      summary: 执行浏览器命令
      parameters:
        - name: X-OpenCLI
          in: header
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id:
                  type: string
                cmd:
                  type: string
                url:
                  type: string
                timeout:
                  type: number
      responses:
        '200':
          description: OK
        '400':
          description: Bad Request
        '403':
          description: Forbidden
        '408':
          description: Request Timeout
        '503':
          description: Extension Not Connected
  /shutdown:
    post:
      summary: 关闭 daemon
      parameters:
        - name: X-OpenCLI
          in: header
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK

7. 快速开始

7.1 环境配置

  • Node.js >= 21.0.0
  • Chrome/Chromium 浏览器
  • OpenCLI Browser Bridge 扩展

7.2 安装与运行

bash
npm install -g @jackwener/opencli
opencli doctor  # 检查安装
opencli list    # 查看所有命令

7.3 典型用例

bash
# 浏览网站
opencli browser open https://example.com

# 使用预置适配器
opencli bilibili hot --limit 5
opencli hackernews top
opencli reddit hot

# 管理外部 CLI
opencli register mycli
opencli gh pr list