# @mooncat/browser

> 浏览器自动化工具包：可独立启动的本地服务（browserd）+ JS/TS client。
> 双路由（WebPlater 扩展 / CDP），控制**宿主 Chrome**（用户看得见的本地 Chrome，不是无头集群）。

`@mooncat/browser` 是从 [mooncat](https://github.com/) 总发行体中拆出的浏览器能力，
**不依赖** mooncat 总发行体、`MOONCAT_DIST`、workspace 结构或 PM2。
它只管 browser 自己。

## 它是什么

```
你的代码 (BrowserClient)
   │  HTTP JSON-RPC (:17322)
   ▼
browserd (常驻服务，持有会话)
   │  双路由自动选择
   ├── WebPlater 扩展路（无 CDP 端口，对高危平台更安全）
   └── CDP 路（Playwright connectOverCDP 直连宿主 Chrome）
```

- **browserd** 是真正持有 Chrome 的常驻进程。open 一次，跨多步复用 handle，满意前不 close。
- **handle 是纯数据**：拿到 `pageHandle` 就直接用，所有页面动作走 `operate({ pageHandle, action, params })`。
  绝不判断路由（extension/CDP 是 browserd 内部的事）。
- **CDP 路用 `playwright-core`**：通过 `connectOverCDP` 连接你机器上的 Chrome，
  **从不下载自带浏览器**（install 不会拉几百 MB 的 chromium）。

## 安装

### 全局服务

```powershell
npm i -g @mooncat/browser
mooncat-browser start --port 17322 --profile D:\browser-profile
```

### 项目内 client

```powershell
npm i @mooncat/browser
```

## 快速开始

```ts
import { BrowserClient } from "@mooncat/browser";

const browser = new BrowserClient({ baseUrl: "http://127.0.0.1:17322" });

// 1. 探测服务是否就绪
const h = await browser.health();
if (!h?.browserOpen) await browser.open({ headless: false });

// 2. 打开页面（newTab 返回 TabInfo，operate 传 tab.pageHandle）
const tab = await browser.newTab({ url: "https://example.com" });

// 3. 页面动作（40+ 个 action：click/fill/goto/snapshot/evaluate/...）
await browser.operate({
  pageHandle: tab.pageHandle,
  action: "click",
  params: { selector: "button" },
});

// 4. 满意后再 close（保留登录态）
await browser.close();
```

## CLI

```powershell
mooncat-browser start [--port 17322] [--profile D:\p] [--chrome path] [--health-port 17440]
mooncat-browser health [--port 17322]
mooncat-browser stop  [--port 17322]
mooncat-browser open --url <url> [--port 17322]
mooncat-browser install-extension
```

| 命令 | 说明 |
| --- | --- |
| `start` | 启动 browser 服务（wrapper + browserd 常驻；browserd 崩溃自动重启） |
| `health` | 探测 browserd 是否就绪 + 是否已 open |
| `stop` | graceful 停止（关闭 Chrome + 退出 wrapper，不重启；保留登录态） |
| `open` | 连接已运行的服务，打开/复用浏览器并导航到 URL |
| `install-extension` | 打印 WebPlater 扩展路径 + Chrome "Load unpacked" 指引 |

## 配置

优先级（高 → 低）：**CLI 参数 > 环境变量 > 配置文件 > 默认值**。

### 环境变量

| 变量 | 含义 | 默认 |
| --- | --- | --- |
| `MOONCAT_BROWSERD_PORT` | browserd RPC 端口（client 连这里） | `17322` |
| `MOONCAT_BROWSER_PORT` | wrapper health 端口 | `17440` |
| `MOONCAT_BROWSER_PROFILE` | Chrome user-data-dir | `<appData>/mooncat-browser/profile` |
| `MOONCAT_BROWSER_CHROME` | Chrome 可执行文件路径 | 自动探测 |
| `MOONCAT_BROWSER_CONFIG` | 配置文件路径 | `<appData>/mooncat-browser/config.json` |

### 配置文件（JSON）

- Windows: `%LOCALAPPDATA%\mooncat-browser\config.json`
- macOS: `~/Library/Application Support/mooncat-browser/config.json`
- Linux: `$XDG_DATA_HOME/mooncat-browser/config.json`（默认 `~/.local/share/...`）

```json
{
  "rpcPort": 17322,
  "healthPort": 17440,
  "profile": "D:\\browser-profile",
  "chromePath": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
}
```

### 端口语义

- `--port` / `MOONCAT_BROWSERD_PORT`（默认 **17322**）= **RPC 端口**，browserd 监听这里，
  也是 `BrowserClient` 连接的端口。`mooncat-browser start --port 17322` 后，
  client 用 `baseUrl: "http://127.0.0.1:17322"`。
- `--health-port` / `MOONCAT_BROWSER_PORT`（默认 **17440**）= **wrapper health 端口**，
  供 `mooncat-browser health`（探测 wrapper）/ `stop`（POST /shutdown）/ 外部监控用。

## 公开 API（BrowserClient）

```
health()              探测 browserd（返回 /health 或 null）
open(options?)        打开浏览器（双路由自动选择 extension/cdp）
newTab(options?)      新建/复用标签页（同 host 默认复用）
listTabs()            列出所有标签页
operate(params)       在页面上执行动作（40+ 个 action）
getCookies(filter?)   获取 cookies
setCookies(cookies)   设置 cookies
clearCookies(filter?) 清除 cookies
close()               关闭浏览器（graceful）
reuseTab(options)     按 url 复用 tab（高危平台避免重开）
listDownloads(limit?)  列出最近下载（仅 extension 模式）
getDownload(id)       查单个下载（仅 extension 模式）
waitForDownload(opts) 轮询等下载完成（仅 extension 模式）
waitFor(opts)         统一等待 DOM/Tab/Frame/组合状态（状态机等待默认不刷新）
```

类型契约见 [`src/protocol.ts`](./src/protocol.ts)，JSON Schema 草稿见 [`schemas/`](./schemas/)。

测试分层、action 有效性验证、iframe/frameId 与 `evaluate` 使用边界见 [`docs/TESTING.md`](./docs/TESTING.md)。

## operate 的 action 清单

`operate({ pageHandle, action, params })` 支持 40+ 个 action，覆盖导航 / 交互 / 读取 / 等待 / 存储 / 截图。
完整清单见 `browser-op/backend/browserd.cjs` 的 `rpcOperate`。常用：

- 导航：`goto` `goBack` `goForward` `reload` `status` `waitForLoadState` `waitForURL`
- 交互：`click` `fill` `type` `press` `hover` `focus` `check` `uncheck` `selectOption` `dblclick` `dragTo` `clickAt` `clickByText`
- 读取：`innerHTML` `innerText` `textContent` `getAttribute` `inputValue` `boundingBox` `count` `snapshot`
- 等待：`waitForSelector` `waitForFunction` `waitForTimeout`
- 存储：`getLocalStorage` `setLocalStorage` `removeLocalStorage` `clearLocalStorage`
- 截图：`screenshot`
- 进阶：`evaluate` `operateSequence` `locateVisibleText` `setInputFiles` `setDialogHandler`

selector 语法：CSS / `aria-ref=eN`（来自 snapshot 的 ref）/ `.cls>>nth(n)` / `.cls>>last`。

## 路由（extension / cdp）

`open({ routeMode })` 自动探测：
- WebPlater 扩展已装且活 → `mode: "extension"`（不暴露 CDP 端口，对淘宝/京东等高危平台更安全）
- 未装 / 被禁用 / 不响应 → `mode: "cdp"`（带调试端口，用 `playwright-core` 连）

装扩展后需**重启浏览器**（`close()` + `open()`）才会切到 extension 路。
要安装扩展：`mooncat-browser install-extension`（Load unpacked 指向包内 `browser-op/webplater/dist/chrome-mv3`）。

## 目录结构

```
mooncat-browser/
  package.json            @mooncat/browser
  tsconfig.json
  src/                    TypeScript 源码（编译到 dist/）
    cli.ts                mooncat-browser 命令
    server.ts             服务包装层（spawn browserd + health 代理 + graceful shutdown）
    client.ts             BrowserClient（HTTP JSON-RPC client）
    protocol.ts           公共类型契约
    config.ts             配置解析（CLI > env > file > default）
  browser-op/             双路由核心（CJS，原样来自 mooncat libs/browser）
    backend/browserd.cjs  常驻 daemon（HTTP RPC :17322）
    index.cjs             BrowserOp 双路由总入口
    cdp/ extension/ web/  各路由实现
    webplater/            WebPlater Chrome 扩展（wxt 构建）
  schemas/                browser.* JSON Schema（API 草稿）
  scripts/clean.mjs
  tests/                  单元/集成测试（vitest）
```

## 开发

```powershell
npm install              # 装根依赖（含 playwright-core，不下载浏览器）
npm run build:ts         # 编译 src/ -> dist/
npm run build:extension  # 构建 WebPlater 扩展（需 pnpm + 网络）
npm run build            # = build:ts + build:extension
npm test                 # vitest（不依赖真实 Chrome）
npm run typecheck
```

> `build:extension` 需要 `pnpm`。若只改 TS 层（client/server/cli/config），`npm run build:ts` 即可。
> 扩展构建产物在 `browser-op/webplater/dist/chrome-mv3`，随包发布以便 `install-extension` 开箱即用。

## 设计原则

1. **handle 是纯数据**——绝不 `if (ph.mode === 'cdp')`，路由不泄漏。
2. **会话长生命周期**——`open()` 一次跨多步复用，满意前不 `close()`（启动 Chrome 慢，反复开关丢登录态）。
3. **步进式 co-work**——一步一确认，中间产物落盘。
4. **出错就 STOP**——报告具体错误，等指示；绝不强杀 Chrome（丢登录态/留僵尸端口）。

## License

MIT
