@jackwener/opencli 0.4.6 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,9 +41,9 @@ A CLI tool that turns **any website** into a command-line interface. **57 comman
41
41
 
42
42
  > **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands. If you get empty data or errors, check your login status first.
43
43
 
44
- OpenCLI needs a way to communicate with your browser. We highly recommend configuring **both** of the following methods for maximum reliability.
44
+ OpenCLI connects to your browser through the Playwright MCP Bridge extension.
45
45
 
46
- ### Connection Method A: Playwright MCP Bridge Extension (Primary)
46
+ ### Playwright MCP Bridge Extension Setup
47
47
 
48
48
  1. Install **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** extension in Chrome.
49
49
  2. Obtain your token by clicking the extension icon in the browser toolbar or from the extension settings page.
@@ -72,15 +72,11 @@ And, so that `opencli` commands can use it directly in the terminal, export it i
72
72
  export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<your-token-here>"
73
73
  ```
74
74
 
75
- ### Connection Method B: Chrome 144+ Auto-Discovery (Fallback)
75
+ After configuring, run `opencli doctor` to verify your token is correctly set up across all locations:
76
76
 
77
- No extensions needed. Just enable Chrome's built-in remote debugging:
78
-
79
- 1. Open `chrome://inspect#remote-debugging` in Chrome
80
- 2. Check **"Allow remote debugging for this browser instance"**
81
- 3. Set `OPENCLI_USE_CDP=1` before running opencli
82
-
83
- *You can also manually specify an endpoint via `OPENCLI_CDP_ENDPOINT` env var.*
77
+ ```bash
78
+ opencli doctor
79
+ ```
84
80
 
85
81
  ## Quick Start
86
82
 
@@ -184,8 +180,6 @@ Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, ca
184
180
  - **"Failed to connect to Playwright MCP Bridge"**
185
181
  - Ensure the Playwright MCP extension is installed and **enabled** in your running Chrome.
186
182
  - Restart the Chrome browser if you just installed the extension.
187
- - **"CDP command failed" or "boss search blocked"**
188
- - Some sites (like BOSS Zhipin) actively block Chrome DevTools Protocol connections. OpenCLI falls back to cookie extraction, but ensure you didn't force `--chrome-mode` unnecessarily.
189
183
  - **Empty data returns or 'Unauthorized' error**
190
184
  - Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page to prove you are human.
191
185
  - **Node API errors**
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
10
10
  [![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
11
11
 
12
- OpenCLI 通过 Chrome 浏览器 + [Playwright MCP Bridge](https://github.com/nichochar/playwright-mcp) 扩展,将任何网站变成命令行工具。57个内置命令。不存密码、不泄 token,直接复用浏览器已登录状态。
12
+ OpenCLI 将任何网站变成命令行工具。**57 个命令**覆盖 **17 个站点** B站、知乎、小红书、Twitter、Reddit、雪球、GitHub、V2EX、Hacker News、BBC、微博、BOSS直聘、Yahoo Finance、路透社、什么值得买、携程、YouTube — 复用浏览器登录态,AI 驱动探索。
13
13
 
14
14
  ---
15
15
 
@@ -41,9 +41,9 @@ OpenCLI 通过 Chrome 浏览器 + [Playwright MCP Bridge](https://github.com/nic
41
41
 
42
42
  > **⚠️ 重要**:大多数命令复用你的 Chrome 登录状态。运行命令前,你必须已在 Chrome 中打开目标网站并完成登录。如果获取到空数据或报错,请先检查你的浏览器登录状态。
43
43
 
44
- 为了让 OpenCLI 能够联通你的浏览器,你需要配置连接方式。**强烈建议以下两种方式都配置上**,互为后备:
44
+ OpenCLI 通过 Playwright MCP Bridge 扩展与你的浏览器通信。
45
45
 
46
- ### 连接方式 A:Playwright MCP Bridge 扩展(首选)
46
+ ### Playwright MCP Bridge 扩展配置
47
47
 
48
48
  1. 安装 **[Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm)** 扩展
49
49
  2. 在浏览器插件栏点击该插件,或者在插件设置页获取你的 Extension Token。
@@ -72,15 +72,11 @@ OpenCLI 通过 Chrome 浏览器 + [Playwright MCP Bridge](https://github.com/nic
72
72
  export PLAYWRIGHT_MCP_EXTENSION_TOKEN="<你的-token>"
73
73
  ```
74
74
 
75
- ### 连接方式 B:Chrome 144+ CDP 自动发现(备选)
75
+ 配置完成后,运行 `opencli doctor` 检测你的 Token 是否在所有位置都正确配置:
76
76
 
77
- 无需安装任何扩展。只需开启 Chrome 内置的远程调试:
78
-
79
- 1. 在 Chrome 中打开 `chrome://inspect#remote-debugging`
80
- 2. 勾选 **"允许对此浏览器实例进行远程调试" (Allow remote debugging for this browser instance)**
81
- 3. 运行时设置环境变量 `OPENCLI_USE_CDP=1`
82
-
83
- *也可通过 `OPENCLI_CDP_ENDPOINT` 环境变量手动指定 CDP endpoint 地址。*
77
+ ```bash
78
+ opencli doctor
79
+ ```
84
80
 
85
81
  ## 快速开始
86
82
 
@@ -184,8 +180,6 @@ opencli cascade https://api.example.com/data
184
180
  - **"Failed to connect to Playwright MCP Bridge"** 报错
185
181
  - 确保你当前的 Chrome 已安装且**开启了** Playwright MCP Bridge 浏览器插件。
186
182
  - 如果是刚装完插件,需要重启 Chrome 浏览器。
187
- - **"CDP command failed" / "被风控拦截"**
188
- - 有些网站(例如 BOSS 直聘)会因为开了 DevTools 或者 CDP 端口拦截验证。OpenCLI 有 cookie 降级机制,通常不需要干预,不用去强行加上 CDP 标识参数即可。
189
183
  - **返回空数据,或者报错 "Unauthorized"**
190
184
  - Chrome 里的登录态可能已经过期(甚至被要求过滑动验证码)。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
191
185
  - **Node API 错误 (如 parseArgs, fs 等)**
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: opencli
3
3
  description: "OpenCLI — Make any website your CLI. Zero risk, AI-powered, reuse Chrome login."
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  author: jackwener
6
6
  tags: [cli, browser, web, mcp, playwright, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, AI, agent]
7
7
  ---
@@ -34,8 +34,7 @@ npm update -g @jackwener/opencli
34
34
 
35
35
  Browser commands require:
36
36
  1. Chrome browser running **(logged into target sites)**
37
- 2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension (default connection mode)
38
- 3. **Alternative**: Chrome 144+ CDP auto-discovery — set `OPENCLI_USE_CDP=1` (no extension needed)
37
+ 2. [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) extension installed and configured
39
38
 
40
39
  > **Note**: You must be logged into the target website in Chrome before running commands. Tabs opened during command execution are auto-closed afterwards.
41
40
 
@@ -339,9 +338,6 @@ ${{ index + 1 }}
339
338
  | `OPENCLI_BROWSER_CONNECT_TIMEOUT` | 30 | Browser connection timeout (sec) |
340
339
  | `OPENCLI_BROWSER_COMMAND_TIMEOUT` | 45 | Command execution timeout (sec) |
341
340
  | `OPENCLI_BROWSER_EXPLORE_TIMEOUT` | 120 | Explore timeout (sec) |
342
- | `OPENCLI_CDP_ENDPOINT` | — | Manual CDP WebSocket endpoint (overrides auto-discovery) |
343
- | `OPENCLI_USE_CDP` | — | Set to `1` to use Chrome 144+ CDP auto-discovery instead of extension |
344
- | `OPENCLI_FORCE_EXTENSION` | — | Set to `1` to skip CDP and force extension mode |
345
341
  | `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | — | Auto-approve extension connection |
346
342
 
347
343
  ## Troubleshooting
@@ -349,6 +345,6 @@ ${{ index + 1 }}
349
345
  | Issue | Solution |
350
346
  |-------|----------|
351
347
  | `npx not found` | Install Node.js: `brew install node` |
352
- | `Timed out connecting to browser` | 1) Chrome must be open 2) Enable remote debugging at `chrome://inspect#remote-debugging` or install MCP Bridge extension |
348
+ | `Timed out connecting to browser` | 1) Chrome must be open 2) Install MCP Bridge extension and configure token |
353
349
  | `Target page context` error | Add `navigate:` step before `evaluate:` in YAML |
354
350
  | Empty table data | Check if evaluate returns JSON string (MCP parsing) or data path is wrong |
package/dist/browser.d.ts CHANGED
@@ -1,21 +1,11 @@
1
1
  /**
2
- * Browser interaction via Chrome DevTools Protocol.
3
- * Connects to an existing Chrome browser through CDP auto-discovery or extension bridge.
2
+ * Browser interaction via Playwright MCP Bridge extension.
3
+ * Connects to an existing Chrome browser through the extension.
4
4
  */
5
- /**
6
- * Verify the CDP HTTP JSON API is functional.
7
- * Chrome's chrome://inspect#remote-debugging mode writes DevToolsActivePort
8
- * but doesn't expose the full CDP HTTP API (/json/version), which means
9
- * Playwright's connectOverCDP won't work properly (init succeeds but
10
- * all tool calls hang silently).
11
- */
12
- export declare function isCdpApiAvailable(port: number, host?: string, timeoutMs?: number): Promise<boolean>;
13
- export declare function discoverChromeEndpoint(): Promise<string | null>;
14
- type ConnectFailureKind = 'missing-token' | 'extension-timeout' | 'extension-not-installed' | 'cdp-timeout' | 'mcp-init' | 'process-exit' | 'unknown';
5
+ type ConnectFailureKind = 'missing-token' | 'extension-timeout' | 'extension-not-installed' | 'mcp-init' | 'process-exit' | 'unknown';
15
6
  type PlaywrightMCPState = 'idle' | 'connecting' | 'connected' | 'closing' | 'closed';
16
7
  type ConnectFailureInput = {
17
8
  kind: ConnectFailureKind;
18
- mode: 'extension' | 'cdp';
19
9
  timeout: number;
20
10
  hasExtensionToken: boolean;
21
11
  tokenFingerprint?: string | null;
@@ -81,7 +71,6 @@ export declare class PlaywrightMCP {
81
71
  private _initialTabIdentities;
82
72
  private _closingPromise;
83
73
  private _state;
84
- private _mode;
85
74
  private _page;
86
75
  get state(): PlaywrightMCPState;
87
76
  private _sendRequest;
@@ -89,7 +78,6 @@ export declare class PlaywrightMCP {
89
78
  private _resetAfterFailedConnect;
90
79
  connect(opts?: {
91
80
  timeout?: number;
92
- forceExtension?: boolean;
93
81
  }): Promise<Page>;
94
82
  close(): Promise<void>;
95
83
  }
@@ -109,6 +97,5 @@ export declare const __test__: {
109
97
  diffTabIndexes: typeof diffTabIndexes;
110
98
  appendLimited: typeof appendLimited;
111
99
  withTimeout: typeof withTimeout;
112
- isCdpApiAvailable: typeof isCdpApiAvailable;
113
100
  };
114
101
  export {};
package/dist/browser.js CHANGED
@@ -1,109 +1,14 @@
1
1
  /**
2
- * Browser interaction via Chrome DevTools Protocol.
3
- * Connects to an existing Chrome browser through CDP auto-discovery or extension bridge.
2
+ * Browser interaction via Playwright MCP Bridge extension.
3
+ * Connects to an existing Chrome browser through the extension.
4
4
  */
5
5
  import { spawn, execSync } from 'node:child_process';
6
6
  import { createHash } from 'node:crypto';
7
- import * as http from 'node:http';
8
- import * as net from 'node:net';
9
7
  import { fileURLToPath } from 'node:url';
10
8
  import * as fs from 'node:fs';
11
9
  import * as os from 'node:os';
12
10
  import * as path from 'node:path';
13
11
  import { formatSnapshot } from './snapshotFormatter.js';
14
- /**
15
- * Chrome 144+ auto-discovery: read DevToolsActivePort file to get CDP endpoint.
16
- *
17
- * Starting with Chrome 144, users can enable remote debugging from
18
- * chrome://inspect#remote-debugging without any command-line flags.
19
- * Chrome writes the active port and browser GUID to a DevToolsActivePort file
20
- * in the user data directory, which we read to construct the WebSocket endpoint.
21
- *
22
- * Priority: OPENCLI_CDP_ENDPOINT env > DevToolsActivePort auto-discovery > --extension fallback
23
- */
24
- /** Quick TCP port probe to verify Chrome is actually listening */
25
- function isPortReachable(port, host = '127.0.0.1', timeoutMs = 800) {
26
- return new Promise(resolve => {
27
- const sock = net.createConnection({ port, host });
28
- sock.setTimeout(timeoutMs);
29
- sock.on('connect', () => { sock.destroy(); resolve(true); });
30
- sock.on('error', () => resolve(false));
31
- sock.on('timeout', () => { sock.destroy(); resolve(false); });
32
- });
33
- }
34
- /**
35
- * Verify the CDP HTTP JSON API is functional.
36
- * Chrome's chrome://inspect#remote-debugging mode writes DevToolsActivePort
37
- * but doesn't expose the full CDP HTTP API (/json/version), which means
38
- * Playwright's connectOverCDP won't work properly (init succeeds but
39
- * all tool calls hang silently).
40
- */
41
- export function isCdpApiAvailable(port, host = '127.0.0.1', timeoutMs = 2000) {
42
- return new Promise(resolve => {
43
- const req = http.get(`http://${host}:${port}/json/version`, { timeout: timeoutMs }, (res) => {
44
- let body = '';
45
- res.on('data', (chunk) => { body += chunk.toString(); });
46
- res.on('end', () => {
47
- try {
48
- const data = JSON.parse(body);
49
- // A valid CDP endpoint returns { Browser, ... } with a webSocketDebuggerUrl
50
- resolve(!!data && typeof data === 'object' && !!data.Browser);
51
- }
52
- catch {
53
- resolve(false);
54
- }
55
- });
56
- });
57
- req.on('error', () => resolve(false));
58
- req.on('timeout', () => { req.destroy(); resolve(false); });
59
- });
60
- }
61
- export async function discoverChromeEndpoint() {
62
- const candidates = [];
63
- // User-specified Chrome data dir takes highest priority
64
- if (process.env.CHROME_USER_DATA_DIR) {
65
- candidates.push(path.join(process.env.CHROME_USER_DATA_DIR, 'DevToolsActivePort'));
66
- }
67
- // Standard Chrome/Edge user data dirs per platform
68
- if (process.platform === 'win32') {
69
- const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
70
- candidates.push(path.join(localAppData, 'Google', 'Chrome', 'User Data', 'DevToolsActivePort'));
71
- candidates.push(path.join(localAppData, 'Microsoft', 'Edge', 'User Data', 'DevToolsActivePort'));
72
- }
73
- else if (process.platform === 'darwin') {
74
- candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort'));
75
- candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'Microsoft Edge', 'DevToolsActivePort'));
76
- }
77
- else {
78
- candidates.push(path.join(os.homedir(), '.config', 'google-chrome', 'DevToolsActivePort'));
79
- candidates.push(path.join(os.homedir(), '.config', 'chromium', 'DevToolsActivePort'));
80
- candidates.push(path.join(os.homedir(), '.config', 'microsoft-edge', 'DevToolsActivePort'));
81
- }
82
- for (const filePath of candidates) {
83
- try {
84
- const content = fs.readFileSync(filePath, 'utf-8').trim();
85
- const lines = content.split('\n');
86
- if (lines.length >= 2) {
87
- const port = parseInt(lines[0], 10);
88
- const browserPath = lines[1]; // e.g. /devtools/browser/<GUID>
89
- if (port > 0 && browserPath.startsWith('/devtools/browser/')) {
90
- const endpoint = `ws://127.0.0.1:${port}${browserPath}`;
91
- // Verify the port is actually reachable (Chrome may have closed, leaving a stale file)
92
- if (await isPortReachable(port)) {
93
- // Verify CDP HTTP API is functional — chrome://inspect#remote-debugging
94
- // writes DevToolsActivePort but doesn't expose the full CDP API,
95
- // causing Playwright connectOverCDP to hang on all tool calls.
96
- if (await isCdpApiAvailable(port)) {
97
- return endpoint;
98
- }
99
- }
100
- }
101
- }
102
- }
103
- catch { }
104
- }
105
- return null;
106
- }
107
12
  // Read version from package.json (single source of truth)
108
13
  const __browser_dirname = path.dirname(fileURLToPath(import.meta.url));
109
14
  const PKG_VERSION = (() => { try {
@@ -115,7 +20,6 @@ catch {
115
20
  const CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
116
21
  const STDERR_BUFFER_LIMIT = 16 * 1024;
117
22
  const INITIAL_TABS_TIMEOUT_MS = 1500;
118
- const CDP_READINESS_PROBE_TIMEOUT_MS = 5000;
119
23
  const TAB_CLEANUP_TIMEOUT_MS = 2000;
120
24
  let _cachedMcpServerPath;
121
25
  export function getTokenFingerprint(token) {
@@ -127,31 +31,24 @@ export function formatBrowserConnectError(input) {
127
31
  const stderr = input.stderr?.trim();
128
32
  const suffix = stderr ? `\n\nMCP stderr:\n${stderr}` : '';
129
33
  const tokenHint = input.tokenFingerprint ? ` Token fingerprint: ${input.tokenFingerprint}.` : '';
130
- if (input.mode === 'extension') {
131
- if (input.kind === 'missing-token') {
132
- return new Error('Failed to connect to Playwright MCP Bridge: PLAYWRIGHT_MCP_EXTENSION_TOKEN is not set.\n\n' +
133
- 'Without this token, Chrome will show a manual approval dialog for every new MCP connection. ' +
134
- 'Copy the token from the Playwright MCP Bridge extension and set it in BOTH your shell environment and MCP client config.' +
135
- suffix);
136
- }
137
- if (input.kind === 'extension-not-installed') {
138
- return new Error('Failed to connect to Playwright MCP Bridge: the browser extension did not attach.\n\n' +
139
- 'Make sure Chrome is running and the "Playwright MCP Bridge" extension is installed and enabled. ' +
140
- 'If Chrome shows an approval dialog, click Allow.' +
141
- suffix);
142
- }
143
- if (input.kind === 'extension-timeout') {
144
- const likelyCause = input.hasExtensionToken
145
- ? `The most likely cause is that PLAYWRIGHT_MCP_EXTENSION_TOKEN does not match the token currently shown by the browser extension.${tokenHint} Re-copy the token from the extension and update BOTH your shell environment and MCP client config.`
146
- : 'PLAYWRIGHT_MCP_EXTENSION_TOKEN is not configured, so the extension may be waiting for manual approval.';
147
- return new Error(`Timed out connecting to Playwright MCP Bridge (${input.timeout}s).\n\n` +
148
- `${likelyCause} If a browser prompt is visible, click Allow. You can also switch to Chrome remote debugging mode with OPENCLI_USE_CDP=1 as a fallback.` +
149
- suffix);
150
- }
34
+ if (input.kind === 'missing-token') {
35
+ return new Error('Failed to connect to Playwright MCP Bridge: PLAYWRIGHT_MCP_EXTENSION_TOKEN is not set.\n\n' +
36
+ 'Without this token, Chrome will show a manual approval dialog for every new MCP connection. ' +
37
+ 'Copy the token from the Playwright MCP Bridge extension and set it in BOTH your shell environment and MCP client config.' +
38
+ suffix);
39
+ }
40
+ if (input.kind === 'extension-not-installed') {
41
+ return new Error('Failed to connect to Playwright MCP Bridge: the browser extension did not attach.\n\n' +
42
+ 'Make sure Chrome is running and the "Playwright MCP Bridge" extension is installed and enabled. ' +
43
+ 'If Chrome shows an approval dialog, click Allow.' +
44
+ suffix);
151
45
  }
152
- if (input.mode === 'cdp' && input.kind === 'cdp-timeout') {
153
- return new Error(`Timed out connecting to browser via CDP (${input.timeout}s).\n\n` +
154
- 'Make sure Chrome is running and remote debugging is enabled at chrome://inspect#remote-debugging, or set OPENCLI_CDP_ENDPOINT explicitly.' +
46
+ if (input.kind === 'extension-timeout') {
47
+ const likelyCause = input.hasExtensionToken
48
+ ? `The most likely cause is that PLAYWRIGHT_MCP_EXTENSION_TOKEN does not match the token currently shown by the browser extension.${tokenHint} Re-copy the token from the extension and update BOTH your shell environment and MCP client config.`
49
+ : 'PLAYWRIGHT_MCP_EXTENSION_TOKEN is not configured, so the extension may be waiting for manual approval.';
50
+ return new Error(`Timed out connecting to Playwright MCP Bridge (${input.timeout}s).\n\n` +
51
+ `${likelyCause} If a browser prompt is visible, click Allow.` +
155
52
  suffix);
156
53
  }
157
54
  if (input.kind === 'mcp-init') {
@@ -165,7 +62,7 @@ export function formatBrowserConnectError(input) {
165
62
  }
166
63
  function inferConnectFailureKind(args) {
167
64
  const haystack = `${args.rawMessage ?? ''}\n${args.stderr}`.toLowerCase();
168
- if (args.mode === 'extension' && !args.hasExtensionToken)
65
+ if (!args.hasExtensionToken)
169
66
  return 'missing-token';
170
67
  if (haystack.includes('extension connection timeout') || haystack.includes('playwright mcp bridge'))
171
68
  return 'extension-not-installed';
@@ -173,11 +70,7 @@ function inferConnectFailureKind(args) {
173
70
  return 'mcp-init';
174
71
  if (args.exited)
175
72
  return 'process-exit';
176
- if (args.mode === 'extension')
177
- return 'extension-timeout';
178
- if (args.mode === 'cdp')
179
- return 'cdp-timeout';
180
- return 'unknown';
73
+ return 'extension-timeout';
181
74
  }
182
75
  // JSON-RPC helpers
183
76
  let _nextId = 1;
@@ -413,7 +306,6 @@ export class PlaywrightMCP {
413
306
  _initialTabIdentities = [];
414
307
  _closingPromise = null;
415
308
  _state = 'idle';
416
- _mode = null;
417
309
  _page = null;
418
310
  get state() {
419
311
  return this._state;
@@ -445,7 +337,6 @@ export class PlaywrightMCP {
445
337
  this._page = null;
446
338
  this._proc = null;
447
339
  this._buffer = '';
448
- this._mode = null;
449
340
  this._initialTabIdentities = [];
450
341
  this._rejectPendingRequests(new Error('Playwright MCP connect failed'));
451
342
  PlaywrightMCP._activeInsts.delete(this);
@@ -472,26 +363,9 @@ export class PlaywrightMCP {
472
363
  PlaywrightMCP._activeInsts.add(this);
473
364
  this._state = 'connecting';
474
365
  const timeout = opts.timeout ?? CONNECT_TIMEOUT;
475
- // Connection priority:
476
- // 1. OPENCLI_CDP_ENDPOINT env var → explicit CDP endpoint
477
- // 2. OPENCLI_USE_CDP=1 → auto-discover via DevToolsActivePort
478
- // 3. Default → --extension mode (Playwright MCP Bridge)
479
- // Some anti-bot sites (e.g. BOSS Zhipin) detect CDP — use forceExtension to bypass.
480
- const forceExt = opts.forceExtension || process.env.OPENCLI_FORCE_EXTENSION === '1';
481
- let cdpEndpoint = null;
482
- if (!forceExt) {
483
- if (process.env.OPENCLI_CDP_ENDPOINT) {
484
- cdpEndpoint = process.env.OPENCLI_CDP_ENDPOINT;
485
- }
486
- else if (process.env.OPENCLI_USE_CDP === '1') {
487
- cdpEndpoint = await discoverChromeEndpoint();
488
- }
489
- }
490
366
  return new Promise((resolve, reject) => {
491
367
  const isDebug = process.env.DEBUG?.includes('opencli:mcp');
492
368
  const debugLog = (msg) => isDebug && console.error(`[opencli:mcp] ${msg}`);
493
- const mode = cdpEndpoint ? 'cdp' : 'extension';
494
- this._mode = mode;
495
369
  const extensionToken = process.env.PLAYWRIGHT_MCP_EXTENSION_TOKEN;
496
370
  const tokenFingerprint = getTokenFingerprint(extensionToken);
497
371
  let stderrBuffer = '';
@@ -505,7 +379,6 @@ export class PlaywrightMCP {
505
379
  this._resetAfterFailedConnect();
506
380
  reject(formatBrowserConnectError({
507
381
  kind,
508
- mode,
509
382
  timeout,
510
383
  hasExtensionToken: !!extensionToken,
511
384
  tokenFingerprint,
@@ -525,23 +398,13 @@ export class PlaywrightMCP {
525
398
  const timer = setTimeout(() => {
526
399
  debugLog('Connection timed out');
527
400
  settleError(inferConnectFailureKind({
528
- mode,
529
401
  hasExtensionToken: !!extensionToken,
530
402
  stderr: stderrBuffer,
531
403
  }));
532
404
  }, timeout * 1000);
533
- const mcpArgs = [mcpPath];
534
- if (cdpEndpoint) {
535
- mcpArgs.push('--cdp-endpoint', cdpEndpoint);
536
- }
537
- else {
538
- mcpArgs.push('--extension');
539
- }
405
+ const mcpArgs = [mcpPath, '--extension'];
540
406
  if (process.env.OPENCLI_VERBOSE) {
541
- console.error(`[opencli] CDP mode: ${cdpEndpoint ? `auto-discovered ${cdpEndpoint}` : 'fallback to --extension'}`);
542
- if (mode === 'extension') {
543
- console.error(`[opencli] Extension token: ${extensionToken ? `configured (fingerprint ${tokenFingerprint})` : 'missing'}`);
544
- }
407
+ console.error(`[opencli] Extension token: ${extensionToken ? `configured (fingerprint ${tokenFingerprint})` : 'missing'}`);
545
408
  }
546
409
  if (process.env.OPENCLI_BROWSER_EXECUTABLE_PATH) {
547
410
  mcpArgs.push('--executablePath', process.env.OPENCLI_BROWSER_EXECUTABLE_PATH);
@@ -595,7 +458,6 @@ export class PlaywrightMCP {
595
458
  this._rejectPendingRequests(new Error(`Playwright MCP process exited before response${code == null ? '' : ` (code ${code})`}`));
596
459
  if (!settled) {
597
460
  settleError(inferConnectFailureKind({
598
- mode,
599
461
  hasExtensionToken: !!extensionToken,
600
462
  stderr: stderrBuffer,
601
463
  exited: true,
@@ -612,7 +474,6 @@ export class PlaywrightMCP {
612
474
  debugLog('Got initialize response');
613
475
  if (resp.error) {
614
476
  settleError(inferConnectFailureKind({
615
- mode,
616
477
  hasExtensionToken: !!extensionToken,
617
478
  stderr: stderrBuffer,
618
479
  rawMessage: `MCP init failed: ${resp.error.message}`,
@@ -622,28 +483,7 @@ export class PlaywrightMCP {
622
483
  const initializedMsg = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n';
623
484
  debugLog(`SEND: ${initializedMsg.trim()}`);
624
485
  this._proc?.stdin?.write(initializedMsg);
625
- if (mode === 'cdp') {
626
- // CDP readiness probe: verify tool calls actually work.
627
- // Some CDP endpoints (e.g. chrome://inspect mode) accept WebSocket
628
- // connections and respond to MCP init but silently drop tool calls.
629
- debugLog('CDP readiness probe (tabs)...');
630
- withTimeout(page.tabs(), CDP_READINESS_PROBE_TIMEOUT_MS, 'CDP readiness probe timed out')
631
- .then(() => {
632
- debugLog('CDP readiness probe succeeded');
633
- settleSuccess(page);
634
- })
635
- .catch((err) => {
636
- debugLog(`CDP readiness probe failed: ${err.message}`);
637
- settleError('cdp-timeout', {
638
- rawMessage: 'CDP endpoint connected but tool calls are unresponsive. ' +
639
- 'This usually means Chrome was opened with chrome://inspect#remote-debugging ' +
640
- 'which is not fully compatible. Launch Chrome with --remote-debugging-port=9222 instead, ' +
641
- 'or use the Playwright MCP Bridge extension (default mode).',
642
- });
643
- });
644
- return;
645
- }
646
- // Extension mode uses tabs as a readiness probe and for tab cleanup bookkeeping.
486
+ // Use tabs as a readiness probe and for tab cleanup bookkeeping.
647
487
  debugLog('Fetching initial tabs count...');
648
488
  withTimeout(page.tabs(), INITIAL_TABS_TIMEOUT_MS, 'Timed out fetching initial tabs').then((tabs) => {
649
489
  debugLog(`Tabs response: ${typeof tabs === 'string' ? tabs : JSON.stringify(tabs)}`);
@@ -668,7 +508,7 @@ export class PlaywrightMCP {
668
508
  this._closingPromise = (async () => {
669
509
  try {
670
510
  // Extension mode opens bridge/session tabs that we can clean up best-effort.
671
- if (this._mode === 'extension' && this._page && this._proc && !this._proc.killed) {
511
+ if (this._page && this._proc && !this._proc.killed) {
672
512
  try {
673
513
  const tabs = await withTimeout(this._page.tabs(), TAB_CLEANUP_TIMEOUT_MS, 'Timed out fetching tabs during cleanup');
674
514
  const tabEntries = extractTabEntries(tabs);
@@ -707,7 +547,6 @@ export class PlaywrightMCP {
707
547
  this._rejectPendingRequests(new Error('Playwright MCP session closed'));
708
548
  this._page = null;
709
549
  this._proc = null;
710
- this._mode = null;
711
550
  this._state = 'closed';
712
551
  PlaywrightMCP._activeInsts.delete(this);
713
552
  }
@@ -733,13 +572,23 @@ function extractTabEntries(raw) {
733
572
  .map(line => line.trim())
734
573
  .filter(Boolean)
735
574
  .map(line => {
736
- const match = line.match(/Tab\s+(\d+)\s*(.*)$/);
737
- if (!match)
738
- return null;
739
- return {
740
- index: parseInt(match[1], 10),
741
- identity: match[2].trim() || `tab-${match[1]}`,
742
- };
575
+ // Match actual Playwright MCP format: "- 0: (current) [title](url)" or "- 1: [title](url)"
576
+ const mcpMatch = line.match(/^-\s+(\d+):\s*(.*)$/);
577
+ if (mcpMatch) {
578
+ return {
579
+ index: parseInt(mcpMatch[1], 10),
580
+ identity: mcpMatch[2].trim() || `tab-${mcpMatch[1]}`,
581
+ };
582
+ }
583
+ // Legacy format: "Tab 0 ..."
584
+ const legacyMatch = line.match(/Tab\s+(\d+)\s*(.*)$/);
585
+ if (legacyMatch) {
586
+ return {
587
+ index: parseInt(legacyMatch[1], 10),
588
+ identity: legacyMatch[2].trim() || `tab-${legacyMatch[1]}`,
589
+ };
590
+ }
591
+ return null;
743
592
  })
744
593
  .filter((entry) => entry !== null);
745
594
  }
@@ -790,7 +639,6 @@ export const __test__ = {
790
639
  diffTabIndexes,
791
640
  appendLimited,
792
641
  withTimeout,
793
- isCdpApiAvailable,
794
642
  };
795
643
  function findMcpServerPath() {
796
644
  if (_cachedMcpServerPath !== undefined)
@@ -15,6 +15,13 @@ describe('browser helpers', () => {
15
15
  { index: 1, identity: 'Chrome Extension' },
16
16
  ]);
17
17
  });
18
+ it('extracts tab entries from MCP markdown format', () => {
19
+ const entries = __test__.extractTabEntries('- 0: (current) [Playwright MCP extension](chrome-extension://abc/connect.html)\n- 1: [知乎 - 首页](https://www.zhihu.com/)');
20
+ expect(entries).toEqual([
21
+ { index: 0, identity: '(current) [Playwright MCP extension](chrome-extension://abc/connect.html)' },
22
+ { index: 1, identity: '[知乎 - 首页](https://www.zhihu.com/)' },
23
+ ]);
24
+ });
18
25
  it('closes only tabs that were opened during the session', () => {
19
26
  const tabsToClose = __test__.diffTabIndexes(['https://example.com', 'Chrome Extension'], [
20
27
  { index: 0, identity: 'https://example.com' },
@@ -53,10 +60,4 @@ describe('PlaywrightMCP state', () => {
53
60
  mcp._state = 'closing';
54
61
  await expect(mcp.connect()).rejects.toThrow('Playwright MCP is closing');
55
62
  });
56
- it('tracks backend mode for lifecycle policy', async () => {
57
- const mcp = new PlaywrightMCP();
58
- expect(mcp._mode).toBeNull();
59
- await mcp.close();
60
- expect(mcp._mode).toBeNull();
61
- });
62
63
  });
@@ -67,7 +67,6 @@ cli({
67
67
  description: 'BOSS直聘搜索职位',
68
68
  domain: 'www.zhipin.com',
69
69
  strategy: Strategy.COOKIE,
70
- forceExtension: true, // BOSS Zhipin detects CDP mode — must use extension bridge
71
70
  browser: true,
72
71
  args: [
73
72
  { name: 'query', required: true, help: 'Search keyword (e.g. AI agent, 前端)' },
@@ -9,7 +9,6 @@ cli({
9
9
  domain: 'www.v2ex.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  browser: true,
12
- forceExtension: true,
13
12
  args: [],
14
13
  columns: ['status', 'message'],
15
14
  func: async (page) => {
@@ -9,7 +9,6 @@ cli({
9
9
  domain: 'www.v2ex.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  browser: true,
12
- forceExtension: true,
13
12
  args: [],
14
13
  columns: ['username', 'balance', 'unread_notifications', 'daily_reward_ready'],
15
14
  func: async (page) => {
@@ -9,7 +9,6 @@ cli({
9
9
  domain: 'www.v2ex.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  browser: true,
12
- forceExtension: true,
13
12
  args: [
14
13
  { name: 'limit', type: 'int', default: 20, help: 'Number of notifications' }
15
14
  ],
package/dist/doctor.d.ts CHANGED
@@ -28,11 +28,6 @@ export type DoctorReport = {
28
28
  envFingerprint: string | null;
29
29
  shellFiles: ShellFileStatus[];
30
30
  configs: McpConfigStatus[];
31
- remoteDebuggingEnabled: boolean;
32
- remoteDebuggingEndpoint: string | null;
33
- cdpEnabled: boolean;
34
- cdpToken: string | null;
35
- cdpFingerprint: string | null;
36
31
  recommendedToken: string | null;
37
32
  recommendedFingerprint: string | null;
38
33
  warnings: string[];