@jackwener/opencli 1.2.3 → 1.2.5
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/.github/workflows/build-extension.yml +0 -15
- package/README.md +8 -4
- package/README.zh-CN.md +8 -4
- package/docs/guide/browser-bridge.md +3 -3
- package/docs/zh/guide/browser-bridge.md +3 -3
- package/extension/dist/background.js +13 -7
- package/extension/manifest.json +1 -1
- package/extension/package.json +1 -1
- package/extension/src/background.ts +15 -7
- package/extension/src/cdp.ts +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
65
|
-
2.
|
|
66
|
-
3.
|
|
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
|
|
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
|
|
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
|
|
66
|
-
2.
|
|
67
|
-
3.
|
|
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
|
|
|
@@ -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
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
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
|
|
12
|
-
2.
|
|
13
|
-
3.
|
|
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
|
|
9
|
+
if (!url) return true;
|
|
10
10
|
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
|
|
11
11
|
}
|
|
12
12
|
async function ensureAttached(tabId) {
|
|
@@ -228,7 +228,7 @@ async function getAutomationWindow(workspace) {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
const win = await chrome.windows.create({
|
|
231
|
-
url: "
|
|
231
|
+
url: "data:text/html,<html></html>",
|
|
232
232
|
focused: false,
|
|
233
233
|
width: 1280,
|
|
234
234
|
height: 900,
|
|
@@ -242,6 +242,7 @@ async function getAutomationWindow(workspace) {
|
|
|
242
242
|
automationSessions.set(workspace, session);
|
|
243
243
|
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
|
|
244
244
|
resetWindowIdleTimer(workspace);
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
245
246
|
return session.windowId;
|
|
246
247
|
}
|
|
247
248
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
@@ -302,13 +303,14 @@ async function handleCommand(cmd) {
|
|
|
302
303
|
}
|
|
303
304
|
}
|
|
304
305
|
function isDebuggableUrl(url) {
|
|
305
|
-
if (!url) return
|
|
306
|
+
if (!url) return true;
|
|
306
307
|
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
|
|
307
308
|
}
|
|
308
309
|
async function resolveTabId(tabId, workspace) {
|
|
309
310
|
if (tabId !== void 0) {
|
|
310
311
|
try {
|
|
311
312
|
const tab = await chrome.tabs.get(tabId);
|
|
313
|
+
console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
|
|
312
314
|
if (isDebuggableUrl(tab.url)) return tabId;
|
|
313
315
|
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
|
|
314
316
|
} catch {
|
|
@@ -318,10 +320,14 @@ async function resolveTabId(tabId, workspace) {
|
|
|
318
320
|
const windowId = await getAutomationWindow(workspace);
|
|
319
321
|
const tabs = await chrome.tabs.query({ windowId });
|
|
320
322
|
const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
|
|
321
|
-
if (debuggableTab?.id)
|
|
323
|
+
if (debuggableTab?.id) {
|
|
324
|
+
console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
|
|
325
|
+
return debuggableTab.id;
|
|
326
|
+
}
|
|
327
|
+
console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map((t) => `${t.id}=${t.url}`).join(", ")}`);
|
|
322
328
|
const reuseTab = tabs.find((t) => t.id);
|
|
323
329
|
if (reuseTab?.id) {
|
|
324
|
-
await chrome.tabs.update(reuseTab.id, { url: "
|
|
330
|
+
await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
|
|
325
331
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
326
332
|
try {
|
|
327
333
|
const updated = await chrome.tabs.get(reuseTab.id);
|
|
@@ -335,7 +341,7 @@ async function resolveTabId(tabId, workspace) {
|
|
|
335
341
|
} catch {
|
|
336
342
|
}
|
|
337
343
|
}
|
|
338
|
-
const newTab = await chrome.tabs.create({ windowId, url: "
|
|
344
|
+
const newTab = await chrome.tabs.create({ windowId, url: "data:text/html,<html></html>", active: true });
|
|
339
345
|
if (!newTab.id) throw new Error("Failed to create tab in automation window");
|
|
340
346
|
return newTab.id;
|
|
341
347
|
}
|
|
@@ -423,7 +429,7 @@ async function handleTabs(cmd, workspace) {
|
|
|
423
429
|
}
|
|
424
430
|
case "new": {
|
|
425
431
|
const windowId = await getAutomationWindow(workspace);
|
|
426
|
-
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "
|
|
432
|
+
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? "data:text/html,<html></html>", active: true });
|
|
427
433
|
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
|
|
428
434
|
}
|
|
429
435
|
case "close": {
|
package/extension/manifest.json
CHANGED
package/extension/package.json
CHANGED
|
@@ -135,9 +135,10 @@ async function getAutomationWindow(workspace: string): Promise<number> {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
// Create a new window with
|
|
138
|
+
// Create a new window with a data: URI that New Tab Override extensions cannot intercept.
|
|
139
|
+
// Using about:blank would be hijacked by extensions like "New Tab Override".
|
|
139
140
|
const win = await chrome.windows.create({
|
|
140
|
-
url: '
|
|
141
|
+
url: 'data:text/html,<html></html>',
|
|
141
142
|
focused: false,
|
|
142
143
|
width: 1280,
|
|
143
144
|
height: 900,
|
|
@@ -151,6 +152,8 @@ async function getAutomationWindow(workspace: string): Promise<number> {
|
|
|
151
152
|
automationSessions.set(workspace, session);
|
|
152
153
|
console.log(`[opencli] Created automation window ${session.windowId} (${workspace})`);
|
|
153
154
|
resetWindowIdleTimer(workspace);
|
|
155
|
+
// Brief delay to let Chrome load the initial data: URI tab
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
154
157
|
return session.windowId;
|
|
155
158
|
}
|
|
156
159
|
|
|
@@ -228,7 +231,7 @@ async function handleCommand(cmd: Command): Promise<Result> {
|
|
|
228
231
|
|
|
229
232
|
/** Check if a URL can be attached via CDP (not chrome:// or chrome-extension://) */
|
|
230
233
|
function isDebuggableUrl(url?: string): boolean {
|
|
231
|
-
if (!url) return
|
|
234
|
+
if (!url) return true; // empty/undefined = tab still loading, allow it
|
|
232
235
|
return !url.startsWith('chrome://') && !url.startsWith('chrome-extension://');
|
|
233
236
|
}
|
|
234
237
|
|
|
@@ -244,6 +247,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
244
247
|
if (tabId !== undefined) {
|
|
245
248
|
try {
|
|
246
249
|
const tab = await chrome.tabs.get(tabId);
|
|
250
|
+
console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
|
|
247
251
|
if (isDebuggableUrl(tab.url)) return tabId;
|
|
248
252
|
// Tab exists but URL is not debuggable — fall through to auto-resolve
|
|
249
253
|
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
|
|
@@ -259,7 +263,11 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
259
263
|
// Prefer an existing debuggable tab (about:blank, http://, https://, etc.)
|
|
260
264
|
const tabs = await chrome.tabs.query({ windowId });
|
|
261
265
|
const debuggableTab = tabs.find(t => t.id && isDebuggableUrl(t.url));
|
|
262
|
-
if (debuggableTab?.id)
|
|
266
|
+
if (debuggableTab?.id) {
|
|
267
|
+
console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
|
|
268
|
+
return debuggableTab.id;
|
|
269
|
+
}
|
|
270
|
+
console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map(t => `${t.id}=${t.url}`).join(', ')}`);
|
|
263
271
|
|
|
264
272
|
// No debuggable tab found — this typically happens when a "New Tab Override"
|
|
265
273
|
// extension replaces about:blank with a chrome-extension:// page.
|
|
@@ -267,7 +275,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
267
275
|
// accumulating orphan tabs if chrome.tabs.create is also intercepted).
|
|
268
276
|
const reuseTab = tabs.find(t => t.id);
|
|
269
277
|
if (reuseTab?.id) {
|
|
270
|
-
await chrome.tabs.update(reuseTab.id, { url: '
|
|
278
|
+
await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
|
|
271
279
|
// Wait for the navigation to take effect
|
|
272
280
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
273
281
|
// Verify the URL is actually debuggable (New Tab Override may have intercepted)
|
|
@@ -288,7 +296,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
288
296
|
}
|
|
289
297
|
|
|
290
298
|
// Window has no debuggable tabs — create one
|
|
291
|
-
const newTab = await chrome.tabs.create({ windowId, url: '
|
|
299
|
+
const newTab = await chrome.tabs.create({ windowId, url: 'data:text/html,<html></html>', active: true });
|
|
292
300
|
if (!newTab.id) throw new Error('Failed to create tab in automation window');
|
|
293
301
|
return newTab.id;
|
|
294
302
|
}
|
|
@@ -397,7 +405,7 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
|
|
|
397
405
|
}
|
|
398
406
|
case 'new': {
|
|
399
407
|
const windowId = await getAutomationWindow(workspace);
|
|
400
|
-
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? '
|
|
408
|
+
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? 'data:text/html,<html></html>', active: true });
|
|
401
409
|
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
|
|
402
410
|
}
|
|
403
411
|
case 'close': {
|
package/extension/src/cdp.ts
CHANGED
|
@@ -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
|
|
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
|
|