@jackwener/opencli 0.4.5 → 0.5.0

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,16 +72,6 @@ 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)
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.*
84
-
85
75
  ## Quick Start
86
76
 
87
77
  ### Install via npm (recommended)
@@ -184,8 +174,6 @@ Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, ca
184
174
  - **"Failed to connect to Playwright MCP Bridge"**
185
175
  - Ensure the Playwright MCP extension is installed and **enabled** in your running Chrome.
186
176
  - 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
177
  - **Empty data returns or 'Unauthorized' error**
190
178
  - 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
179
  - **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,16 +72,6 @@ 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 自动发现(备选)
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 地址。*
84
-
85
75
  ## 快速开始
86
76
 
87
77
  ### npm 全局安装(推荐)
@@ -184,8 +174,6 @@ opencli cascade https://api.example.com/data
184
174
  - **"Failed to connect to Playwright MCP Bridge"** 报错
185
175
  - 确保你当前的 Chrome 已安装且**开启了** Playwright MCP Bridge 浏览器插件。
186
176
  - 如果是刚装完插件,需要重启 Chrome 浏览器。
187
- - **"CDP command failed" / "被风控拦截"**
188
- - 有些网站(例如 BOSS 直聘)会因为开了 DevTools 或者 CDP 端口拦截验证。OpenCLI 有 cookie 降级机制,通常不需要干预,不用去强行加上 CDP 标识参数即可。
189
177
  - **返回空数据,或者报错 "Unauthorized"**
190
178
  - Chrome 里的登录态可能已经过期(甚至被要求过滑动验证码)。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
191
179
  - **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.4.6
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,13 +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
- export declare function discoverChromeEndpoint(): Promise<string | null>;
6
- 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';
7
6
  type PlaywrightMCPState = 'idle' | 'connecting' | 'connected' | 'closing' | 'closed';
8
7
  type ConnectFailureInput = {
9
8
  kind: ConnectFailureKind;
10
- mode: 'extension' | 'cdp';
11
9
  timeout: number;
12
10
  hasExtensionToken: boolean;
13
11
  tokenFingerprint?: string | null;
@@ -80,7 +78,6 @@ export declare class PlaywrightMCP {
80
78
  private _resetAfterFailedConnect;
81
79
  connect(opts?: {
82
80
  timeout?: number;
83
- forceExtension?: boolean;
84
81
  }): Promise<Page>;
85
82
  close(): Promise<void>;
86
83
  }
package/dist/browser.js CHANGED
@@ -1,76 +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 net from 'node:net';
8
7
  import { fileURLToPath } from 'node:url';
9
8
  import * as fs from 'node:fs';
10
9
  import * as os from 'node:os';
11
10
  import * as path from 'node:path';
12
11
  import { formatSnapshot } from './snapshotFormatter.js';
13
- /**
14
- * Chrome 144+ auto-discovery: read DevToolsActivePort file to get CDP endpoint.
15
- *
16
- * Starting with Chrome 144, users can enable remote debugging from
17
- * chrome://inspect#remote-debugging without any command-line flags.
18
- * Chrome writes the active port and browser GUID to a DevToolsActivePort file
19
- * in the user data directory, which we read to construct the WebSocket endpoint.
20
- *
21
- * Priority: OPENCLI_CDP_ENDPOINT env > DevToolsActivePort auto-discovery > --extension fallback
22
- */
23
- /** Quick TCP port probe to verify Chrome is actually listening */
24
- function isPortReachable(port, host = '127.0.0.1', timeoutMs = 800) {
25
- return new Promise(resolve => {
26
- const sock = net.createConnection({ port, host });
27
- sock.setTimeout(timeoutMs);
28
- sock.on('connect', () => { sock.destroy(); resolve(true); });
29
- sock.on('error', () => resolve(false));
30
- sock.on('timeout', () => { sock.destroy(); resolve(false); });
31
- });
32
- }
33
- export async function discoverChromeEndpoint() {
34
- const candidates = [];
35
- // User-specified Chrome data dir takes highest priority
36
- if (process.env.CHROME_USER_DATA_DIR) {
37
- candidates.push(path.join(process.env.CHROME_USER_DATA_DIR, 'DevToolsActivePort'));
38
- }
39
- // Standard Chrome/Edge user data dirs per platform
40
- if (process.platform === 'win32') {
41
- const localAppData = process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local');
42
- candidates.push(path.join(localAppData, 'Google', 'Chrome', 'User Data', 'DevToolsActivePort'));
43
- candidates.push(path.join(localAppData, 'Microsoft', 'Edge', 'User Data', 'DevToolsActivePort'));
44
- }
45
- else if (process.platform === 'darwin') {
46
- candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort'));
47
- candidates.push(path.join(os.homedir(), 'Library', 'Application Support', 'Microsoft Edge', 'DevToolsActivePort'));
48
- }
49
- else {
50
- candidates.push(path.join(os.homedir(), '.config', 'google-chrome', 'DevToolsActivePort'));
51
- candidates.push(path.join(os.homedir(), '.config', 'chromium', 'DevToolsActivePort'));
52
- candidates.push(path.join(os.homedir(), '.config', 'microsoft-edge', 'DevToolsActivePort'));
53
- }
54
- for (const filePath of candidates) {
55
- try {
56
- const content = fs.readFileSync(filePath, 'utf-8').trim();
57
- const lines = content.split('\n');
58
- if (lines.length >= 2) {
59
- const port = parseInt(lines[0], 10);
60
- const browserPath = lines[1]; // e.g. /devtools/browser/<GUID>
61
- if (port > 0 && browserPath.startsWith('/devtools/browser/')) {
62
- const endpoint = `ws://127.0.0.1:${port}${browserPath}`;
63
- // Verify the port is actually reachable (Chrome may have closed, leaving a stale file)
64
- if (await isPortReachable(port)) {
65
- return endpoint;
66
- }
67
- }
68
- }
69
- }
70
- catch { }
71
- }
72
- return null;
73
- }
74
12
  // Read version from package.json (single source of truth)
75
13
  const __browser_dirname = path.dirname(fileURLToPath(import.meta.url));
76
14
  const PKG_VERSION = (() => { try {
@@ -81,6 +19,7 @@ catch {
81
19
  } })();
82
20
  const CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
83
21
  const STDERR_BUFFER_LIMIT = 16 * 1024;
22
+ const INITIAL_TABS_TIMEOUT_MS = 1500;
84
23
  const TAB_CLEANUP_TIMEOUT_MS = 2000;
85
24
  let _cachedMcpServerPath;
86
25
  export function getTokenFingerprint(token) {
@@ -92,31 +31,24 @@ export function formatBrowserConnectError(input) {
92
31
  const stderr = input.stderr?.trim();
93
32
  const suffix = stderr ? `\n\nMCP stderr:\n${stderr}` : '';
94
33
  const tokenHint = input.tokenFingerprint ? ` Token fingerprint: ${input.tokenFingerprint}.` : '';
95
- if (input.mode === 'extension') {
96
- if (input.kind === 'missing-token') {
97
- return new Error('Failed to connect to Playwright MCP Bridge: PLAYWRIGHT_MCP_EXTENSION_TOKEN is not set.\n\n' +
98
- 'Without this token, Chrome will show a manual approval dialog for every new MCP connection. ' +
99
- 'Copy the token from the Playwright MCP Bridge extension and set it in BOTH your shell environment and MCP client config.' +
100
- suffix);
101
- }
102
- if (input.kind === 'extension-not-installed') {
103
- return new Error('Failed to connect to Playwright MCP Bridge: the browser extension did not attach.\n\n' +
104
- 'Make sure Chrome is running and the "Playwright MCP Bridge" extension is installed and enabled. ' +
105
- 'If Chrome shows an approval dialog, click Allow.' +
106
- suffix);
107
- }
108
- if (input.kind === 'extension-timeout') {
109
- const likelyCause = input.hasExtensionToken
110
- ? `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.`
111
- : 'PLAYWRIGHT_MCP_EXTENSION_TOKEN is not configured, so the extension may be waiting for manual approval.';
112
- return new Error(`Timed out connecting to Playwright MCP Bridge (${input.timeout}s).\n\n` +
113
- `${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.` +
114
- suffix);
115
- }
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);
116
45
  }
117
- if (input.mode === 'cdp' && input.kind === 'cdp-timeout') {
118
- return new Error(`Timed out connecting to browser via CDP (${input.timeout}s).\n\n` +
119
- '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.` +
120
52
  suffix);
121
53
  }
122
54
  if (input.kind === 'mcp-init') {
@@ -130,7 +62,7 @@ export function formatBrowserConnectError(input) {
130
62
  }
131
63
  function inferConnectFailureKind(args) {
132
64
  const haystack = `${args.rawMessage ?? ''}\n${args.stderr}`.toLowerCase();
133
- if (args.mode === 'extension' && !args.hasExtensionToken)
65
+ if (!args.hasExtensionToken)
134
66
  return 'missing-token';
135
67
  if (haystack.includes('extension connection timeout') || haystack.includes('playwright mcp bridge'))
136
68
  return 'extension-not-installed';
@@ -138,11 +70,7 @@ function inferConnectFailureKind(args) {
138
70
  return 'mcp-init';
139
71
  if (args.exited)
140
72
  return 'process-exit';
141
- if (args.mode === 'extension')
142
- return 'extension-timeout';
143
- if (args.mode === 'cdp')
144
- return 'cdp-timeout';
145
- return 'unknown';
73
+ return 'extension-timeout';
146
74
  }
147
75
  // JSON-RPC helpers
148
76
  let _nextId = 1;
@@ -435,25 +363,9 @@ export class PlaywrightMCP {
435
363
  PlaywrightMCP._activeInsts.add(this);
436
364
  this._state = 'connecting';
437
365
  const timeout = opts.timeout ?? CONNECT_TIMEOUT;
438
- // Connection priority:
439
- // 1. OPENCLI_CDP_ENDPOINT env var → explicit CDP endpoint
440
- // 2. OPENCLI_USE_CDP=1 → auto-discover via DevToolsActivePort
441
- // 3. Default → --extension mode (Playwright MCP Bridge)
442
- // Some anti-bot sites (e.g. BOSS Zhipin) detect CDP — use forceExtension to bypass.
443
- const forceExt = opts.forceExtension || process.env.OPENCLI_FORCE_EXTENSION === '1';
444
- let cdpEndpoint = null;
445
- if (!forceExt) {
446
- if (process.env.OPENCLI_CDP_ENDPOINT) {
447
- cdpEndpoint = process.env.OPENCLI_CDP_ENDPOINT;
448
- }
449
- else if (process.env.OPENCLI_USE_CDP === '1') {
450
- cdpEndpoint = await discoverChromeEndpoint();
451
- }
452
- }
453
366
  return new Promise((resolve, reject) => {
454
367
  const isDebug = process.env.DEBUG?.includes('opencli:mcp');
455
368
  const debugLog = (msg) => isDebug && console.error(`[opencli:mcp] ${msg}`);
456
- const mode = cdpEndpoint ? 'cdp' : 'extension';
457
369
  const extensionToken = process.env.PLAYWRIGHT_MCP_EXTENSION_TOKEN;
458
370
  const tokenFingerprint = getTokenFingerprint(extensionToken);
459
371
  let stderrBuffer = '';
@@ -467,7 +379,6 @@ export class PlaywrightMCP {
467
379
  this._resetAfterFailedConnect();
468
380
  reject(formatBrowserConnectError({
469
381
  kind,
470
- mode,
471
382
  timeout,
472
383
  hasExtensionToken: !!extensionToken,
473
384
  tokenFingerprint,
@@ -487,23 +398,13 @@ export class PlaywrightMCP {
487
398
  const timer = setTimeout(() => {
488
399
  debugLog('Connection timed out');
489
400
  settleError(inferConnectFailureKind({
490
- mode,
491
401
  hasExtensionToken: !!extensionToken,
492
402
  stderr: stderrBuffer,
493
403
  }));
494
404
  }, timeout * 1000);
495
- const mcpArgs = [mcpPath];
496
- if (cdpEndpoint) {
497
- mcpArgs.push('--cdp-endpoint', cdpEndpoint);
498
- }
499
- else {
500
- mcpArgs.push('--extension');
501
- }
405
+ const mcpArgs = [mcpPath, '--extension'];
502
406
  if (process.env.OPENCLI_VERBOSE) {
503
- console.error(`[opencli] CDP mode: ${cdpEndpoint ? `auto-discovered ${cdpEndpoint}` : 'fallback to --extension'}`);
504
- if (mode === 'extension') {
505
- console.error(`[opencli] Extension token: ${extensionToken ? `configured (fingerprint ${tokenFingerprint})` : 'missing'}`);
506
- }
407
+ console.error(`[opencli] Extension token: ${extensionToken ? `configured (fingerprint ${tokenFingerprint})` : 'missing'}`);
507
408
  }
508
409
  if (process.env.OPENCLI_BROWSER_EXECUTABLE_PATH) {
509
410
  mcpArgs.push('--executablePath', process.env.OPENCLI_BROWSER_EXECUTABLE_PATH);
@@ -557,7 +458,6 @@ export class PlaywrightMCP {
557
458
  this._rejectPendingRequests(new Error(`Playwright MCP process exited before response${code == null ? '' : ` (code ${code})`}`));
558
459
  if (!settled) {
559
460
  settleError(inferConnectFailureKind({
560
- mode,
561
461
  hasExtensionToken: !!extensionToken,
562
462
  stderr: stderrBuffer,
563
463
  exited: true,
@@ -574,7 +474,6 @@ export class PlaywrightMCP {
574
474
  debugLog('Got initialize response');
575
475
  if (resp.error) {
576
476
  settleError(inferConnectFailureKind({
577
- mode,
578
477
  hasExtensionToken: !!extensionToken,
579
478
  stderr: stderrBuffer,
580
479
  rawMessage: `MCP init failed: ${resp.error.message}`,
@@ -584,9 +483,9 @@ export class PlaywrightMCP {
584
483
  const initializedMsg = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n';
585
484
  debugLog(`SEND: ${initializedMsg.trim()}`);
586
485
  this._proc?.stdin?.write(initializedMsg);
587
- // Get initial tab count for cleanup (with timeout CDP mode may hang on browser_tabs)
486
+ // Use tabs as a readiness probe and for tab cleanup bookkeeping.
588
487
  debugLog('Fetching initial tabs count...');
589
- withTimeout(page.tabs(), 5000, 'Timed out fetching initial tabs').then((tabs) => {
488
+ withTimeout(page.tabs(), INITIAL_TABS_TIMEOUT_MS, 'Timed out fetching initial tabs').then((tabs) => {
590
489
  debugLog(`Tabs response: ${typeof tabs === 'string' ? tabs : JSON.stringify(tabs)}`);
591
490
  this._initialTabIdentities = extractTabIdentities(tabs);
592
491
  settleSuccess(page);
@@ -608,7 +507,7 @@ export class PlaywrightMCP {
608
507
  this._state = 'closing';
609
508
  this._closingPromise = (async () => {
610
509
  try {
611
- // Close tabs opened during this session (site tabs + extension tabs)
510
+ // Extension mode opens bridge/session tabs that we can clean up best-effort.
612
511
  if (this._page && this._proc && !this._proc.killed) {
613
512
  try {
614
513
  const tabs = await withTimeout(this._page.tabs(), TAB_CLEANUP_TIMEOUT_MS, 'Timed out fetching tabs during cleanup');
@@ -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[];
package/dist/doctor.js CHANGED
@@ -3,8 +3,7 @@ import * as os from 'node:os';
3
3
  import * as path from 'node:path';
4
4
  import { createInterface } from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
- import { PlaywrightMCP, discoverChromeEndpoint, getTokenFingerprint } from './browser.js';
7
- import { browserSession } from './runtime.js';
6
+ import { getTokenFingerprint } from './browser.js';
8
7
  const PLAYWRIGHT_SERVER_NAME = 'playwright';
9
8
  const PLAYWRIGHT_TOKEN_ENV = 'PLAYWRIGHT_MCP_EXTENSION_TOKEN';
10
9
  const PLAYWRIGHT_EXTENSION_ID = 'mmlmfjhmonkocbjadbfplnigmagldckm';
@@ -170,46 +169,8 @@ function readConfigStatus(filePath) {
170
169
  };
171
170
  }
172
171
  }
173
- async function extractTokenViaCdp() {
174
- if (!(process.env.OPENCLI_USE_CDP === '1' || process.env.OPENCLI_CDP_ENDPOINT))
175
- return null;
176
- const candidates = [
177
- `chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/options.html`,
178
- `chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/popup.html`,
179
- `chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/connect.html`,
180
- `chrome-extension://${PLAYWRIGHT_EXTENSION_ID}/index.html`,
181
- ];
182
- const result = await browserSession(PlaywrightMCP, async (page) => {
183
- for (const url of candidates) {
184
- try {
185
- await page.goto(url);
186
- await page.wait(1);
187
- const token = await page.evaluate(`() => {
188
- const values = new Set();
189
- const push = (value) => {
190
- if (!value || typeof value !== 'string') return;
191
- for (const match of value.matchAll(/[A-Za-z0-9_-]{24,}/g)) values.add(match[0]);
192
- };
193
- document.querySelectorAll('input, textarea, code, pre, span, div').forEach((el) => {
194
- push(el.value);
195
- push(el.textContent || '');
196
- push(el.getAttribute && el.getAttribute('value'));
197
- });
198
- return Array.from(values);
199
- }`);
200
- const matches = Array.isArray(token) ? token.filter((v) => v.length >= 24) : [];
201
- if (matches.length > 0)
202
- return matches.sort((a, b) => b.length - a.length)[0];
203
- }
204
- catch { }
205
- }
206
- return null;
207
- });
208
- return typeof result === 'string' && result ? result : null;
209
- }
210
172
  export async function runBrowserDoctor(opts = {}) {
211
173
  const envToken = process.env[PLAYWRIGHT_TOKEN_ENV] ?? null;
212
- const remoteDebuggingEndpoint = await discoverChromeEndpoint().catch(() => null);
213
174
  const shellPath = opts.shellRc ?? getDefaultShellRcPath();
214
175
  const shellFiles = [shellPath].map((filePath) => {
215
176
  if (!fileExists(filePath))
@@ -220,27 +181,20 @@ export async function runBrowserDoctor(opts = {}) {
220
181
  });
221
182
  const configPaths = opts.configPaths?.length ? opts.configPaths : getDefaultMcpConfigPaths();
222
183
  const configs = configPaths.map(readConfigStatus);
223
- const cdpToken = !opts.token && !envToken ? await extractTokenViaCdp().catch(() => null) : null;
224
184
  const allTokens = [
225
185
  opts.token ?? null,
226
186
  envToken,
227
187
  ...shellFiles.map(s => s.token),
228
188
  ...configs.map(c => c.token),
229
- cdpToken,
230
189
  ].filter((v) => !!v);
231
190
  const uniqueTokens = [...new Set(allTokens)];
232
- const recommendedToken = opts.token ?? envToken ?? (uniqueTokens.length === 1 ? uniqueTokens[0] : cdpToken) ?? null;
191
+ const recommendedToken = opts.token ?? envToken ?? (uniqueTokens.length === 1 ? uniqueTokens[0] : null) ?? null;
233
192
  const report = {
234
193
  cliVersion: opts.cliVersion,
235
194
  envToken,
236
195
  envFingerprint: getTokenFingerprint(envToken ?? undefined),
237
196
  shellFiles,
238
197
  configs,
239
- remoteDebuggingEnabled: !!remoteDebuggingEndpoint,
240
- remoteDebuggingEndpoint,
241
- cdpEnabled: process.env.OPENCLI_USE_CDP === '1' || !!process.env.OPENCLI_CDP_ENDPOINT,
242
- cdpToken,
243
- cdpFingerprint: getTokenFingerprint(cdpToken ?? undefined),
244
198
  recommendedToken,
245
199
  recommendedFingerprint: getTokenFingerprint(recommendedToken ?? undefined),
246
200
  warnings: [],
@@ -254,17 +208,12 @@ export async function runBrowserDoctor(opts = {}) {
254
208
  report.issues.push('No scanned MCP config currently contains a Playwright extension token.');
255
209
  if (uniqueTokens.length > 1)
256
210
  report.issues.push('Detected inconsistent Playwright MCP tokens across env/config files.');
257
- if (!report.remoteDebuggingEnabled)
258
- report.warnings.push('Chrome remote debugging appears to be disabled or Chrome is not currently exposing a DevTools endpoint.');
259
211
  for (const config of configs) {
260
212
  if (config.parseError)
261
213
  report.warnings.push(`Could not parse ${config.path}: ${config.parseError}`);
262
214
  }
263
215
  if (!recommendedToken) {
264
- if (report.cdpEnabled)
265
- report.warnings.push('CDP is enabled, but no token could be extracted automatically from the extension UI.');
266
- else
267
- report.warnings.push('No token source found. Enable OPENCLI_USE_CDP=1 to allow a best-effort token read from the extension page.');
216
+ report.warnings.push('No token source found.');
268
217
  }
269
218
  return report;
270
219
  }
@@ -277,9 +226,6 @@ export function renderBrowserDoctorReport(report) {
277
226
  const uniqueFingerprints = [...new Set(tokenFingerprints)];
278
227
  const hasMismatch = uniqueFingerprints.length > 1;
279
228
  const lines = [`opencli v${report.cliVersion ?? 'unknown'} doctor`, ''];
280
- lines.push(statusLine(report.remoteDebuggingEnabled ? 'OK' : 'WARN', `Chrome remote debugging: ${report.remoteDebuggingEnabled ? 'enabled' : 'disabled'}`));
281
- if (report.remoteDebuggingEndpoint)
282
- lines.push(` ${report.remoteDebuggingEndpoint}`);
283
229
  const envStatus = !report.envToken ? 'MISSING' : hasMismatch ? 'MISMATCH' : 'OK';
284
230
  lines.push(statusLine(envStatus, `Environment token: ${tokenSummary(report.envToken, report.envFingerprint)}`));
285
231
  for (const shell of report.shellFiles) {
@@ -306,10 +252,6 @@ export function renderBrowserDoctorReport(report) {
306
252
  }
307
253
  if (missingConfigCount > 0)
308
254
  lines.push(` Other scanned config locations not present: ${missingConfigCount}`);
309
- if (report.cdpEnabled) {
310
- const cdpStatus = report.cdpToken ? 'OK' : 'WARN';
311
- lines.push(statusLine(cdpStatus, `CDP token probe: ${tokenSummary(report.cdpToken, report.cdpFingerprint)}`));
312
- }
313
255
  lines.push('');
314
256
  lines.push(statusLine(hasMismatch ? 'MISMATCH' : report.recommendedToken ? 'OK' : 'WARN', `Recommended token fingerprint: ${report.recommendedFingerprint ?? 'unavailable'}`));
315
257
  if (report.issues.length) {
@@ -341,7 +283,7 @@ function writeFileWithMkdir(filePath, content) {
341
283
  export async function applyBrowserDoctorFix(report, opts = {}) {
342
284
  const token = opts.token ?? report.recommendedToken;
343
285
  if (!token)
344
- throw new Error('No Playwright MCP token is available to write. Provide --token or enable CDP token probing first.');
286
+ throw new Error('No Playwright MCP token is available to write. Provide --token first.');
345
287
  const plannedWrites = [];
346
288
  const shellPath = opts.shellRc ?? report.shellFiles[0]?.path ?? getDefaultShellRcPath();
347
289
  plannedWrites.push(shellPath);
@@ -76,17 +76,11 @@ describe('doctor report rendering', () => {
76
76
  envFingerprint: 'fp1',
77
77
  shellFiles: [{ path: '/tmp/.zshrc', exists: true, token: 'abc123', fingerprint: 'fp1' }],
78
78
  configs: [{ path: '/tmp/mcp.json', exists: true, format: 'json', token: 'abc123', fingerprint: 'fp1', writable: true }],
79
- remoteDebuggingEnabled: true,
80
- remoteDebuggingEndpoint: 'ws://127.0.0.1:9222/devtools/browser/test',
81
- cdpEnabled: false,
82
- cdpToken: null,
83
- cdpFingerprint: null,
84
79
  recommendedToken: 'abc123',
85
80
  recommendedFingerprint: 'fp1',
86
81
  warnings: [],
87
82
  issues: [],
88
83
  });
89
- expect(text).toContain('[OK] Chrome remote debugging: enabled');
90
84
  expect(text).toContain('[OK] Environment token: configured (fp1)');
91
85
  expect(text).toContain('[OK] MCP config /tmp/mcp.json: configured (fp1)');
92
86
  });
@@ -96,17 +90,11 @@ describe('doctor report rendering', () => {
96
90
  envFingerprint: 'fp1',
97
91
  shellFiles: [{ path: '/tmp/.zshrc', exists: true, token: 'def456', fingerprint: 'fp2' }],
98
92
  configs: [{ path: '/tmp/mcp.json', exists: true, format: 'json', token: 'abc123', fingerprint: 'fp1', writable: true }],
99
- remoteDebuggingEnabled: false,
100
- remoteDebuggingEndpoint: null,
101
- cdpEnabled: false,
102
- cdpToken: null,
103
- cdpFingerprint: null,
104
93
  recommendedToken: 'abc123',
105
94
  recommendedFingerprint: 'fp1',
106
- warnings: ['Chrome remote debugging appears to be disabled or Chrome is not currently exposing a DevTools endpoint.'],
95
+ warnings: [],
107
96
  issues: ['Detected inconsistent Playwright MCP tokens across env/config files.'],
108
97
  });
109
- expect(text).toContain('[WARN] Chrome remote debugging: disabled');
110
98
  expect(text).toContain('[MISMATCH] Environment token: configured (fp1)');
111
99
  expect(text).toContain('[MISMATCH] Shell file /tmp/.zshrc: configured (fp2)');
112
100
  expect(text).toContain('[MISMATCH] Recommended token fingerprint: fp1');
package/dist/main.js CHANGED
@@ -152,7 +152,7 @@ for (const [, cmd] of registry) {
152
152
  process.env.OPENCLI_VERBOSE = '1';
153
153
  let result;
154
154
  if (cmd.browser) {
155
- result = await browserSession(PlaywrightMCP, async (page) => runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) }), { forceExtension: cmd.forceExtension });
155
+ result = await browserSession(PlaywrightMCP, async (page) => runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) }));
156
156
  }
157
157
  else {
158
158
  result = await executeCommand(cmd, null, kwargs, actionOpts.verbose);