Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## Unreleased

### ⚠ BREAKING CHANGES

* **browser session model** — replace the browser-facing `--workspace` model with explicit `--session <name>` on `opencli browser *`. Browser commands now require a session name, `browser bind`/`unbind` use `--session`, and bind no longer accepts `--domain`, `--path-prefix`, or `--allow-navigate-bound`. Browser primitives keep their session tab by design; the browser namespace no longer exposes `--keep-tab`. Adapter `--keep-tab` and `browserSession.reuse: 'site'` are unchanged.

### Internal

* **extension 1.0.11** — switch Browser Bridge lease routing from user-facing workspaces to explicit browser sessions.

## [1.7.16](https://github.com/jackwener/opencli/compare/v1.7.15...v1.7.16) (2026-05-11)

Extension bumped to 1.0.10 (rename adapter-owned tab group `OpenCLI Automation` → `OpenCLI Adapter`). Performance and stability sweep across browser-backed adapters; new external CLI integrations (tg-cli, discord-cli, wx-cli).
Expand Down
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,15 @@ The agent handles all the `opencli browser` commands internally — you just des

Available browser commands include `open`, `state`, `click`, `type`, `fill`, `select`, `keys`, `wait`, `get`, `find`, `extract`, `frames`, `screenshot`, `scroll`, `back`, `eval`, `network`, `tab list`, `tab new`, `tab select`, `tab close`, `init`, `verify`, and `close`.

`opencli browser open <url>` and `opencli browser tab new [url]` both return a target ID. Use `opencli browser tab list` to inspect the target IDs of tabs that already exist, then pass `--tab <targetId>` to route a command to a specific tab. `tab new` creates a new tab without changing the default browser target; only `tab select <targetId>` promotes that tab to the default target for later untargeted `opencli browser ...` commands.
`opencli browser` commands require `--session <name>`. `opencli browser --session work open <url>` and `opencli browser --session work tab new [url]` both return a target ID. Use `opencli browser --session work tab list` to inspect target IDs, then pass `--tab <targetId>` to route a command to a specific tab. `tab new` creates a new tab without changing the default browser target; only `tab select <targetId>` promotes that tab to the default target for later untargeted commands in the same session.

## Core Concepts

### `browser`: AI Agent browser control

`opencli browser` commands are the low-level primitives that AI Agents use to operate websites. You don't run these manually — instead, install the `opencli-adapter-author` skill into your AI agent, describe what you want in natural language, and the agent handles the browser operations.

For example, tell your agent: *"Help me check my Xiaohongshu notifications"* — the agent will use `opencli browser open`, `state`, `click`, etc. under the hood.
For example, tell your agent: *"Help me check my Xiaohongshu notifications"* — the agent will use `opencli browser --session <name> open`, `state`, `click`, etc. under the hood.

### Built-in adapters: stable commands

Expand All @@ -174,7 +174,7 @@ When the site you need is not yet covered, use the `opencli-adapter-author` skil
2. Discover the right endpoint — network inspection, initial state, bundle search, token trace, or interceptor fallback.
3. Decide the auth strategy — `PUBLIC` / `COOKIE` / `INTERCEPT` / `UI` / `LOCAL`.
4. Decode response fields and design output columns.
5. `opencli browser analyze <url>` for one-shot recon, then `opencli browser init <site>/<name>` → write adapter → `opencli browser verify <site>/<name>`.
5. `opencli browser --session recon analyze <url>` for one-shot recon, then `opencli browser --session recon init <site>/<name>` → write adapter → `opencli browser --session recon verify <site>/<name>`.
6. Persist site knowledge to `~/.opencli/sites/<site>/` so the next adapter for the same site is faster.

### CLI Hub and desktop adapters
Expand All @@ -199,7 +199,7 @@ OpenCLI is not only for websites. It can also:
| `OPENCLI_DAEMON_PORT` | `19825` | HTTP port for the daemon-extension bridge |
| `OPENCLI_PROFILE` | — | Browser Bridge profile alias/contextId to use when multiple Chrome profiles are connected |
| `OPENCLI_WINDOW` | command default | Set to `foreground` or `background` to override Browser Bridge window placement. Browser-backed commands also accept `--window <foreground\|background>`. |
| `OPENCLI_KEEP_TAB` | command default | Set to `true` or `false` to keep or release the browser tab lease after a browser-backed command. Browser-backed commands also accept `--keep-tab <true\|false>`. |
| `OPENCLI_KEEP_TAB` | command default | Set to `true` or `false` to keep or release the browser tab lease after a browser-backed adapter command. Browser-backed adapter commands also accept `--keep-tab <true\|false>`. |
| `OPENCLI_BROWSER_REUSE` | adapter default | Set to `none` or `site` to override adapter browser tab reuse. The `--reuse <none\|site>` flag sets this. |
| `OPENCLI_BROWSER_CONNECT_TIMEOUT` | `30` | Seconds to wait for browser connection |
| `OPENCLI_BROWSER_COMMAND_TIMEOUT` | `60` | Seconds to wait for a single browser command |
Expand All @@ -208,7 +208,7 @@ OpenCLI is not only for websites. It can also:
| `OPENCLI_VERBOSE` | `false` | Enable verbose logging (`-v` flag also works) |
| `DEBUG_SNAPSHOT` | — | Set to `1` for DOM snapshot debug output |

`opencli browser *` uses a foreground browser window and keeps its tab lease by default. Browser-backed adapters use a background automation window and release one-shot tab leases by default. Some interactive adapters default to `--reuse site`, which also keeps the site tab lease; pass `--reuse none` for a one-shot tab.
`opencli browser *` requires an explicit `--session <name>`, uses a foreground browser window by default, and keeps that session's tab lease until `browser --session <name> close` or idle cleanup. Browser-backed adapters use a background adapter window and release one-shot tab leases by default. Some interactive adapters default to `--reuse site`, which keeps the site tab lease for continuity; pass `--reuse none` for a one-shot tab.

## Update

Expand Down Expand Up @@ -409,10 +409,10 @@ See [Plugins Guide](./docs/guide/plugins.md) for creating your own plugin.
Before writing any adapter code, read the [`opencli-adapter-author` skill](./skills/opencli-adapter-author/SKILL.md). It takes you end-to-end:

- Recon the site and pick a pattern (SPA / SSR / JSONP / Token / Streaming).
- Discover the right endpoint via `opencli browser network`, `eval`, or the interceptor fallback.
- Discover the right endpoint via `opencli browser --session <name> network`, `eval`, or the interceptor fallback.
- Decide auth strategy (`PUBLIC` / `COOKIE` / `INTERCEPT` / `UI` / `LOCAL`).
- Run `opencli browser analyze <url>` for one-shot recon, decode response fields, design columns, scaffold with `opencli browser init`.
- Verify with `opencli browser verify <site>/<name>` before shipping.
- Run `opencli browser --session recon analyze <url>` for one-shot recon, decode response fields, design columns, scaffold with `opencli browser --session recon init`.
- Verify with `opencli browser --session recon verify <site>/<name>` before shipping.

For long-lived personal commands that should live in your own Git repo, use a local plugin instead; see [Extending OpenCLI](./docs/guide/extending-opencli.md). Quick private adapters can still live at `~/.opencli/clis/<site>/<name>.js`. Site knowledge (endpoints, field maps, fixtures) accumulates in `~/.opencli/sites/<site>/` so the next adapter for the same site starts from context instead of zero.

Expand Down
16 changes: 8 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,15 @@ Agent 在内部自动处理所有 `opencli browser` 命令——你只需用自

`browser` 可用命令包括:`open`、`state`、`click`、`type`、`fill`、`select`、`keys`、`wait`、`get`、`find`、`extract`、`frames`、`screenshot`、`scroll`、`back`、`eval`、`network`、`tab list`、`tab new`、`tab select`、`tab close`、`init`、`verify`、`close`。

`opencli browser open <url>` 和 `opencli browser tab new [url]` 都会返回 target ID。`opencli browser tab list` 用来查看当前已存在 tab 的 target ID,再通过 `--tab <targetId>` 把命令明确路由到某个 tab。`tab new` 只会新建 tab,不会改变默认浏览器目标;只有显式执行 `tab select <targetId>`,才会把该 tab 设为后续未指定 target 的 `opencli browser ...` 命令的默认目标
`opencli browser` 命令必须显式传 `--session <name>`。`opencli browser --session work open <url>` 和 `opencli browser --session work tab new [url]` 都会返回 target ID。`opencli browser --session work tab list` 用来查看当前已存在 tab 的 target ID,再通过 `--tab <targetId>` 把命令明确路由到某个 tab。`tab new` 只会新建 tab,不会改变默认浏览器目标;只有显式执行 `tab select <targetId>`,才会把该 tab 设为同一 session 后续未指定 target 的默认目标

## 核心概念

### `browser`:AI Agent 的浏览器控制层

`opencli browser` 命令是 AI Agent 操作网站的底层原语。你不需要手动运行这些命令——把 `opencli-adapter-author` skill 安装到你的 AI Agent 中,用自然语言描述你想做的事,Agent 会自动处理浏览器操作。

比如你告诉 Agent:*"帮我看看小红书的通知"*——Agent 会在底层调用 `opencli browser open`、`state`、`click` 等命令。
比如你告诉 Agent:*"帮我看看小红书的通知"*——Agent 会在底层调用 `opencli browser --session <name> open`、`state`、`click` 等命令。

### 内置适配器:稳定命令

Expand All @@ -158,7 +158,7 @@ Agent 在内部自动处理所有 `opencli browser` 命令——你只需用自
2. 发现目标 endpoint——network 精读、initial state、bundle 搜索、token 溯源,或 interceptor 兜底
3. 定认证策略——`PUBLIC` / `COOKIE` / `INTERCEPT` / `UI` / `LOCAL`
4. 字段解码 + 设计输出列
5. `opencli browser analyze <url>` 一步侦察,再 `opencli browser init <site>/<name>` → 写适配器 → `opencli browser verify <site>/<name>`
5. `opencli browser --session recon analyze <url>` 一步侦察,再 `opencli browser --session recon init <site>/<name>` → 写适配器 → `opencli browser --session recon verify <site>/<name>`
6. 把站点知识沉到 `~/.opencli/sites/<site>/`,下次写同站点的其他命令直接吃缓存

### CLI 枢纽与桌面端适配器
Expand All @@ -182,15 +182,15 @@ OpenCLI 不只是网站 CLI,还可以:
|------|--------|------|
| `OPENCLI_DAEMON_PORT` | `19825` | daemon-extension 通信端口 |
| `OPENCLI_WINDOW` | 命令默认值 | 设为 `foreground` 或 `background` 来覆盖 Browser Bridge 窗口位置。浏览器型命令也支持 `--window <foreground\|background>` |
| `OPENCLI_KEEP_TAB` | 命令默认值 | 设为 `true` 或 `false` 来控制浏览器型命令结束后是否保留 tab lease。浏览器型命令也支持 `--keep-tab <true\|false>` |
| `OPENCLI_KEEP_TAB` | 命令默认值 | 设为 `true` 或 `false` 来控制浏览器型 adapter 命令结束后是否保留 tab lease。浏览器型 adapter 命令也支持 `--keep-tab <true\|false>` |
| `OPENCLI_BROWSER_CONNECT_TIMEOUT` | `30` | 浏览器连接超时(秒) |
| `OPENCLI_BROWSER_COMMAND_TIMEOUT` | `60` | 单个浏览器命令超时(秒) |
| `OPENCLI_CDP_ENDPOINT` | — | Chrome DevTools Protocol 端点,用于远程浏览器或 Electron 应用 |
| `OPENCLI_CDP_TARGET` | — | 按 URL 子串过滤 CDP target(如 `detail.1688.com`) |
| `OPENCLI_VERBOSE` | `false` | 启用详细日志(`-v` 也可以) |
| `DEBUG_SNAPSHOT` | — | 设为 `1` 输出 DOM 快照调试信息 |

`opencli browser *` 默认使用前台窗口并保留 tab lease,直到你手动执行 `opencli browser close` 或等空闲超时。浏览器型 adapter 默认使用后台 automation 窗口并在命令结束后释放 tab lease;如果需要调试最终页面,可以传 `--window foreground --keep-tab true`。
`opencli browser *` 必须显式传 `--session <name>`,默认使用前台窗口,并保留该 session 的 tab lease,直到你手动执行 `opencli browser --session <name> close` 或等空闲超时。浏览器型 adapter 默认使用后台 adapter 窗口并在命令结束后释放一次性 tab lease;如果需要调试最终页面,可以传 `--window foreground --keep-tab true`。

## 更新

Expand Down Expand Up @@ -508,10 +508,10 @@ opencli plugin uninstall my-tool # 卸载
在动代码前,先读 [`opencli-adapter-author` skill](./skills/opencli-adapter-author/SKILL.md)。它把整个流程串起来:

- 侦察站点,选定 pattern(SPA / SSR / JSONP / Token / Streaming)
- 用 `opencli browser network`、`eval`、interceptor 等找到目标 endpoint
- 用 `opencli browser --session <name> network`、`eval`、interceptor 等找到目标 endpoint
- 定认证策略(`PUBLIC` / `COOKIE` / `INTERCEPT` / `UI` / `LOCAL`)
- 先用 `opencli browser analyze <url>` 一步侦察,再字段解码、设计 columns、`opencli browser init` 生成骨架
- 交付前用 `opencli browser verify <site>/<name>` 验证
- 先用 `opencli browser --session recon analyze <url>` 一步侦察,再字段解码、设计 columns、`opencli browser --session recon init` 生成骨架
- 交付前用 `opencli browser --session recon verify <site>/<name>` 验证

在仓库外写的私有适配器放到 `~/.opencli/clis/<site>/<name>.js`;每个站点的 endpoint、字段映射、抓包样本会累积在 `~/.opencli/sites/<site>/`,下次写同站点的其他命令可以直接复用。

Expand Down
2 changes: 1 addition & 1 deletion cli-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15536,7 +15536,7 @@
"aliases": [
"select"
],
"description": "Open one NotebookLM notebook in the automation workspace by id or URL",
"description": "Open one NotebookLM notebook in the adapter session by id or URL",
"access": "read",
"domain": "notebooklm.google.com",
"strategy": "cookie",
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/current.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm current', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm current', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const current = await readCurrentNotebooklm(page);
if (!current) {
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm get', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm get', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const rpcRow = await getNotebooklmDetailViaRpc(page).catch(() => null);
if (rpcRow)
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/history.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm history', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm history', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const rows = await listNotebooklmHistoryViaRpc(page);
return rows;
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/note-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm note-list', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm note-list', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const rows = await listNotebooklmNotesFromPage(page);
if (rows.length > 0)
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/notes-get.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm notes-get', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm notes-get', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const query = typeof kwargs.note === 'string' ? kwargs.note : String(kwargs.note ?? '');
const visible = await readNotebooklmVisibleNoteFromPage(page);
Expand Down
4 changes: 2 additions & 2 deletions clis/notebooklm/open.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cli({
name: 'open',
access: 'read',
aliases: ['select'],
description: 'Open one NotebookLM notebook in the automation workspace by id or URL',
description: 'Open one NotebookLM notebook in the adapter session by id or URL',
domain: NOTEBOOKLM_DOMAIN,
strategy: Strategy.COOKIE,
browser: true,
Expand All @@ -28,7 +28,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new CliError('NOTEBOOKLM_OPEN_FAILED', `NotebookLM notebook "${notebookId}" did not open in the automation workspace`, 'Run `opencli notebooklm list -f json` first and pass a valid notebook id.');
throw new CliError('NOTEBOOKLM_OPEN_FAILED', `NotebookLM notebook "${notebookId}" did not open in the adapter session`, 'Run `opencli notebooklm list -f json` first and pass a valid notebook id.');
}
if (state.notebookId !== notebookId) {
console.warn(`[notebooklm open] expected notebook "${notebookId}" but page reports "${state.notebookId}"; continuing`);
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/open.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('notebooklm open', () => {
source: 'current-page',
});
});
it('opens a notebook by id in the automation workspace', async () => {
it('opens a notebook by id in the adapter session', async () => {
const page = {
goto: vi.fn(async () => { }),
wait: vi.fn(async () => { }),
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/source-fulltext.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm source-fulltext', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm source-fulltext', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
const rows = rpcRows.length > 0 ? rpcRows : await listNotebooklmSourcesFromPage(page);
Expand Down
2 changes: 1 addition & 1 deletion clis/notebooklm/source-get.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ cli({
await requireNotebooklmSession(page);
const state = await getNotebooklmPageState(page);
if (state.kind !== 'notebook') {
throw new EmptyResultError('opencli notebooklm source-get', 'No NotebookLM notebook is open in the automation workspace. Run `opencli notebooklm open <notebook>` first.');
throw new EmptyResultError('opencli notebooklm source-get', 'No NotebookLM notebook is open in the adapter session. Run `opencli notebooklm open <notebook>` first.');
}
const rpcRows = await listNotebooklmSourcesViaRpc(page).catch(() => []);
const rows = rpcRows.length > 0 ? rpcRows : await listNotebooklmSourcesFromPage(page);
Expand Down
Loading
Loading