@jackwener/opencli 1.2.4 → 1.2.6

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.
@@ -45,26 +45,12 @@ jobs:
45
45
  cd extension-package
46
46
  zip -r ../opencli-extension.zip .
47
47
 
48
- - name: Create Extension CRX
49
- run: |
50
- npm install -g crx3
51
- if [ -n "${{ secrets.CRX_PRIVATE_KEY }}" ]; then
52
- echo "Found CRX_PRIVATE_KEY, signing extension..."
53
- echo "${{ secrets.CRX_PRIVATE_KEY }}" > crx-key.pem
54
- crx3 pack extension-package -o opencli-extension.crx -p crx-key.pem
55
- rm crx-key.pem
56
- else
57
- echo "No CRX_PRIVATE_KEY configured. Generating CRX with a temporary random key..."
58
- crx3 pack extension-package -o opencli-extension.crx
59
- fi
60
-
61
48
  - name: Upload Artifacts (Action Run)
62
49
  uses: actions/upload-artifact@v4
63
50
  with:
64
51
  name: opencli-extension-build
65
52
  path: |
66
53
  opencli-extension.zip
67
- opencli-extension.crx
68
54
  retention-days: 7
69
55
 
70
56
  - name: Attach to GitHub Release
@@ -73,7 +59,6 @@ jobs:
73
59
  with:
74
60
  files: |
75
61
  opencli-extension.zip
76
- opencli-extension.crx
77
62
  draft: false
78
63
  prerelease: false
79
64
  env:
package/README.md CHANGED
@@ -61,11 +61,15 @@ OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome
61
61
  You can install the extension via either method:
62
62
 
63
63
  **Method 1: Download Pre-built Release (Recommended)**
64
- 1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip` or `opencli-extension.crx`.
65
- 2. Open `chrome://extensions` and enable **Developer mode** (top-right toggle).
66
- 3. Drag and drop the `.crx` file or the unzipped folder into the extensions page.
64
+ 1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip`.
65
+ 2. Unzip the file and open `chrome://extensions`, enable **Developer mode** (top-right toggle).
66
+ 3. Click **Load unpacked** and select the unzipped folder.
67
67
 
68
- **Method 2: Load Unpacked Source (For Developers)**
68
+ **Method 2: Load from npm Package**
69
+ 1. After installing opencli via npm, open `chrome://extensions` and enable **Developer mode**.
70
+ 2. Click **Load unpacked** and select `node_modules/@jackwener/opencli/extension` directory.
71
+
72
+ **Method 3: Load Source (For Developers)**
69
73
  1. Open `chrome://extensions` and enable **Developer mode**.
70
74
  2. Click **Load unpacked** and select the `extension/` directory from this repository.
71
75
 
@@ -324,6 +328,8 @@ npx vitest run tests/e2e/ # E2E tests
324
328
 
325
329
  - **"Extension not connected"**
326
330
  - Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
331
+ - **"attach failed: Cannot access a chrome-extension:// URL"**
332
+ - Another Chrome extension (e.g. youmind, New Tab Override, or AI assistant extensions) may be interfering. Try **disabling other extensions** temporarily, then retry.
327
333
  - **Empty data returns or 'Unauthorized' error**
328
334
  - 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.
329
335
  - **Node API errors**
package/README.zh-CN.md CHANGED
@@ -62,11 +62,15 @@ OpenCLI 通过轻量化的 **Browser Bridge** Chrome 扩展 + 微型 daemon 与
62
62
  你可以选择以下任一方式安装扩展:
63
63
 
64
64
  **方式一:下载构建好的安装包(推荐)**
65
- 1. 到 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip` 或 `opencli-extension.crx`。
66
- 2. 打开 Chrome 的 `chrome://extensions`,启用右上角的 **开发者模式**。
67
- 3. `.crx` 拖入浏览器窗口,或将解压后的文件夹拖入即可完成安装。
65
+ 1. 到 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip`。
66
+ 2. 解压后打开 Chrome 的 `chrome://extensions`,启用右上角的 **开发者模式**。
67
+ 3. 点击 **加载已解压的扩展程序**,选择解压后的文件夹。
68
68
 
69
- **方式二:加载源码(针对开发者)**
69
+ **方式二:从 npm 包加载**
70
+ 1. 通过 npm 安装 opencli 后,打开 `chrome://extensions`,启用 **开发者模式**。
71
+ 2. 点击 **加载已解压的扩展程序**,选择 `node_modules/@jackwener/opencli/extension` 目录。
72
+
73
+ **方式三:加载源码(针对开发者)**
70
74
  1. 同样在 `chrome://extensions` 开启 **开发者模式**。
71
75
  2. 点击 **加载已解压的扩展程序**,选择本仓库代码树中的 `extension/` 文件夹。
72
76
 
@@ -307,6 +311,8 @@ opencli cascade https://api.example.com/data
307
311
 
308
312
  - **"Extension not connected" 报错**
309
313
  - 确保你当前的 Chrome 已安装且**开启了** opencli Browser Bridge 扩展(在 `chrome://extensions` 中检查)。
314
+ - **"attach failed: Cannot access a chrome-extension:// URL" 报错**
315
+ - 其他 Chrome 扩展(如 youmind、New Tab Override 或 AI 助手类扩展)可能产生冲突。请尝试**暂时禁用其他扩展**后重试。
310
316
  - **返回空数据,或者报错 "Unauthorized"**
311
317
  - Chrome 里的登录态可能已经过期。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
312
318
  - **Node API 错误 (如 parseArgs, fs 等)**
@@ -8,9 +8,9 @@ OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome
8
8
 
9
9
  ### Method 1: Download Pre-built Release (Recommended)
10
10
 
11
- 1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip` or `opencli-extension.crx`.
12
- 2. Open `chrome://extensions` and enable **Developer mode** (top-right toggle).
13
- 3. Drag and drop the `.crx` file or the unzipped folder into the extensions page.
11
+ 1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip`.
12
+ 2. Unzip the file and open `chrome://extensions`, enable **Developer mode** (top-right toggle).
13
+ 3. Click **Load unpacked** and select the unzipped folder.
14
14
 
15
15
  ### Method 2: Load Unpacked Source (For Developers)
16
16
 
@@ -8,9 +8,9 @@ OpenCLI 通过轻量级 **Browser Bridge** Chrome 扩展 + 微守护进程连接
8
8
 
9
9
  ### 方法 1:下载预构建版本(推荐)
10
10
 
11
- 1. 前往 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip` 或 `opencli-extension.crx`。
12
- 2. 打开 `chrome://extensions`,启用**开发者模式**。
13
- 3. 拖放 `.crx` 文件或解压后的文件夹到扩展页面。
11
+ 1. 前往 GitHub [Releases 页面](https://github.com/jackwener/opencli/releases) 下载最新的 `opencli-extension.zip`。
12
+ 2. 解压后打开 `chrome://extensions`,启用**开发者模式**。
13
+ 3. 点击**加载已解压的扩展程序**,选择解压后的文件夹。
14
14
 
15
15
  ### 方法 2:加载源码(开发者)
16
16
 
@@ -6,7 +6,7 @@ const WS_RECONNECT_MAX_DELAY = 6e4;
6
6
 
7
7
  const attached = /* @__PURE__ */ new Set();
8
8
  function isDebuggableUrl$1(url) {
9
- if (!url) return false;
9
+ if (!url) return true;
10
10
  return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
11
11
  }
12
12
  async function ensureAttached(tabId) {
@@ -36,6 +36,7 @@ async function ensureAttached(tabId) {
36
36
  await chrome.debugger.attach({ tabId }, "1.3");
37
37
  } catch (e) {
38
38
  const msg = e instanceof Error ? e.message : String(e);
39
+ const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : "";
39
40
  if (msg.includes("Another debugger is already attached")) {
40
41
  try {
41
42
  await chrome.debugger.detach({ tabId });
@@ -44,10 +45,10 @@ async function ensureAttached(tabId) {
44
45
  try {
45
46
  await chrome.debugger.attach({ tabId }, "1.3");
46
47
  } catch {
47
- throw new Error(`attach failed: ${msg}`);
48
+ throw new Error(`attach failed: ${msg}${hint}`);
48
49
  }
49
50
  } else {
50
- throw new Error(`attach failed: ${msg}`);
51
+ throw new Error(`attach failed: ${msg}${hint}`);
51
52
  }
52
53
  }
53
54
  attached.add(tabId);
@@ -242,6 +243,7 @@ async function getAutomationWindow(workspace) {
242
243
  automationSessions.set(workspace, session);
243
244
  console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
244
245
  resetWindowIdleTimer(workspace);
246
+ await new Promise((resolve) => setTimeout(resolve, 200));
245
247
  return session.windowId;
246
248
  }
247
249
  chrome.windows.onRemoved.addListener((windowId) => {
@@ -302,14 +304,13 @@ async function handleCommand(cmd) {
302
304
  }
303
305
  }
304
306
  function isDebuggableUrl(url) {
305
- if (!url) return false;
307
+ if (!url) return true;
306
308
  return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
307
309
  }
308
310
  async function resolveTabId(tabId, workspace) {
309
311
  if (tabId !== void 0) {
310
312
  try {
311
313
  const tab = await chrome.tabs.get(tabId);
312
- console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
313
314
  if (isDebuggableUrl(tab.url)) return tabId;
314
315
  console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
315
316
  } catch {
@@ -319,11 +320,7 @@ async function resolveTabId(tabId, workspace) {
319
320
  const windowId = await getAutomationWindow(workspace);
320
321
  const tabs = await chrome.tabs.query({ windowId });
321
322
  const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
322
- if (debuggableTab?.id) {
323
- console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
324
- return debuggableTab.id;
325
- }
326
- console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map((t) => `${t.id}=${t.url}`).join(", ")}`);
323
+ if (debuggableTab?.id) return debuggableTab.id;
327
324
  const reuseTab = tabs.find((t) => t.id);
328
325
  if (reuseTab?.id) {
329
326
  await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
@@ -331,12 +328,7 @@ async function resolveTabId(tabId, workspace) {
331
328
  try {
332
329
  const updated = await chrome.tabs.get(reuseTab.id);
333
330
  if (isDebuggableUrl(updated.url)) return reuseTab.id;
334
- console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`);
335
- await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
336
- await new Promise((resolve) => setTimeout(resolve, 300));
337
- const updated2 = await chrome.tabs.get(reuseTab.id);
338
- if (isDebuggableUrl(updated2.url)) return reuseTab.id;
339
- console.warn(`[opencli] data: URI also intercepted, creating fresh tab`);
331
+ console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
340
332
  } catch {
341
333
  }
342
334
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "OpenCLI",
4
- "version": "1.2.4",
4
+ "version": "1.2.6",
5
5
  "description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencli-extension",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -88,7 +88,7 @@ function scheduleReconnect(): void {
88
88
  // ─── Automation window isolation ─────────────────────────────────────
89
89
  // All opencli operations happen in a dedicated Chrome window so the
90
90
  // user's active browsing session is never touched.
91
- // The window auto-closes after 30s of idle (no commands).
91
+ // The window auto-closes after 120s of idle (no commands).
92
92
 
93
93
  type AutomationSession = {
94
94
  windowId: number;
@@ -152,6 +152,8 @@ async function getAutomationWindow(workspace: string): Promise<number> {
152
152
  automationSessions.set(workspace, session);
153
153
  console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
154
154
  resetWindowIdleTimer(workspace);
155
+ // Brief delay to let Chrome load the initial data: URI tab
156
+ await new Promise(resolve => setTimeout(resolve, 200));
155
157
  return session.windowId;
156
158
  }
157
159
 
@@ -229,7 +231,7 @@ async function handleCommand(cmd: Command): Promise<Result> {
229
231
 
230
232
  /** Check if a URL can be attached via CDP (not chrome:// or chrome-extension://) */
231
233
  function isDebuggableUrl(url?: string): boolean {
232
- if (!url) return false;
234
+ if (!url) return true; // empty/undefined = tab still loading, allow it
233
235
  return !url.startsWith('chrome://') && !url.startsWith('chrome-extension://');
234
236
  }
235
237
 
@@ -245,7 +247,6 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
245
247
  if (tabId !== undefined) {
246
248
  try {
247
249
  const tab = await chrome.tabs.get(tabId);
248
- console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
249
250
  if (isDebuggableUrl(tab.url)) return tabId;
250
251
  // Tab exists but URL is not debuggable — fall through to auto-resolve
251
252
  console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
@@ -258,42 +259,27 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
258
259
  // Get (or create) the automation window
259
260
  const windowId = await getAutomationWindow(workspace);
260
261
 
261
- // Prefer an existing debuggable tab (about:blank, http://, https://, etc.)
262
+ // Prefer an existing debuggable tab
262
263
  const tabs = await chrome.tabs.query({ windowId });
263
264
  const debuggableTab = tabs.find(t => t.id && isDebuggableUrl(t.url));
264
- if (debuggableTab?.id) {
265
- console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
266
- return debuggableTab.id;
267
- }
268
- console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map(t => `${t.id}=${t.url}`).join(', ')}`);
265
+ if (debuggableTab?.id) return debuggableTab.id;
269
266
 
270
- // No debuggable tab found this typically happens when a "New Tab Override"
271
- // extension replaces about:blank with a chrome-extension:// page.
272
- // Reuse the first existing tab by navigating it to about:blank (avoids
273
- // accumulating orphan tabs if chrome.tabs.create is also intercepted).
267
+ // No debuggable tab — another extension may have hijacked the tab URL.
268
+ // Try to reuse by navigating to a data: URI (not interceptable by New Tab Override).
274
269
  const reuseTab = tabs.find(t => t.id);
275
270
  if (reuseTab?.id) {
276
271
  await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
277
- // Wait for the navigation to take effect
278
272
  await new Promise(resolve => setTimeout(resolve, 300));
279
- // Verify the URL is actually debuggable (New Tab Override may have intercepted)
280
273
  try {
281
274
  const updated = await chrome.tabs.get(reuseTab.id);
282
275
  if (isDebuggableUrl(updated.url)) return reuseTab.id;
283
- // New Tab Override intercepted about:blank try data: URI instead
284
- console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`);
285
- await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
286
- await new Promise(resolve => setTimeout(resolve, 300));
287
- const updated2 = await chrome.tabs.get(reuseTab.id);
288
- if (isDebuggableUrl(updated2.url)) return reuseTab.id;
289
- // data: URI also intercepted — create a brand new tab
290
- console.warn(`[opencli] data: URI also intercepted, creating fresh tab`);
276
+ console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
291
277
  } catch {
292
278
  // Tab was closed during navigation
293
279
  }
294
280
  }
295
281
 
296
- // Window has no debuggable tabs — create one
282
+ // Fallback: create a new tab
297
283
  const newTab = await chrome.tabs.create({ windowId, url: 'data:text/html,<html></html>', active: true });
298
284
  if (!newTab.id) throw new Error('Failed to create tab in automation window');
299
285
  return newTab.id;
@@ -10,7 +10,7 @@ const attached = new Set<number>();
10
10
 
11
11
  /** Check if a URL can be attached via CDP */
12
12
  function isDebuggableUrl(url?: string): boolean {
13
- if (!url) return false;
13
+ if (!url) return true; // empty/undefined = tab still loading, allow it
14
14
  return !url.startsWith('chrome://') && !url.startsWith('chrome-extension://');
15
15
  }
16
16
 
@@ -47,15 +47,18 @@ async function ensureAttached(tabId: number): Promise<void> {
47
47
  await chrome.debugger.attach({ tabId }, '1.3');
48
48
  } catch (e: unknown) {
49
49
  const msg = e instanceof Error ? e.message : String(e);
50
+ const hint = msg.includes('chrome-extension://')
51
+ ? '. Tip: another Chrome extension may be interfering — try disabling other extensions'
52
+ : '';
50
53
  if (msg.includes('Another debugger is already attached')) {
51
54
  try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
52
55
  try {
53
56
  await chrome.debugger.attach({ tabId }, '1.3');
54
57
  } catch {
55
- throw new Error(`attach failed: ${msg}`);
58
+ throw new Error(`attach failed: ${msg}${hint}`);
56
59
  }
57
60
  } else {
58
- throw new Error(`attach failed: ${msg}`);
61
+ throw new Error(`attach failed: ${msg}${hint}`);
59
62
  }
60
63
  }
61
64
  attached.add(tabId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },