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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ npx vitest run tests/e2e/ # E2E tests

- **"Extension not connected"**
- Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
- **"attach failed: Cannot access a chrome-extension:// URL"**
- Another Chrome extension (e.g. youmind, New Tab Override, or AI assistant extensions) may be interfering. Try **disabling other extensions** temporarily, then retry.
- **Empty data returns or 'Unauthorized' error**
- 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.
- **Node API errors**
Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ opencli cascade https://api.example.com/data

- **"Extension not connected" 报错**
- 确保你当前的 Chrome 已安装且**开启了** opencli Browser Bridge 扩展(在 `chrome://extensions` 中检查)。
- **"attach failed: Cannot access a chrome-extension:// URL" 报错**
- 其他 Chrome 扩展(如 youmind、New Tab Override 或 AI 助手类扩展)可能产生冲突。请尝试**暂时禁用其他扩展**后重试。
- **返回空数据,或者报错 "Unauthorized"**
- Chrome 里的登录态可能已经过期。请打开当前 Chrome 页面,在新标签页重新手工登录或刷新该页面。
- **Node API 错误 (如 parseArgs, fs 等)**
Expand Down
19 changes: 5 additions & 14 deletions extension/dist/background.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async function ensureAttached(tabId) {
await chrome.debugger.attach({ tabId }, "1.3");
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
const hint = msg.includes("chrome-extension://") ? ". Tip: another Chrome extension may be interfering — try disabling other extensions" : "";
if (msg.includes("Another debugger is already attached")) {
try {
await chrome.debugger.detach({ tabId });
Expand All @@ -44,10 +45,10 @@ async function ensureAttached(tabId) {
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch {
throw new Error(`attach failed: ${msg}`);
throw new Error(`attach failed: ${msg}${hint}`);
}
} else {
throw new Error(`attach failed: ${msg}`);
throw new Error(`attach failed: ${msg}${hint}`);
}
}
attached.add(tabId);
Expand Down Expand Up @@ -310,7 +311,6 @@ async function resolveTabId(tabId, workspace) {
if (tabId !== void 0) {
try {
const tab = await chrome.tabs.get(tabId);
console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
if (isDebuggableUrl(tab.url)) return tabId;
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
} catch {
Expand All @@ -320,24 +320,15 @@ async function resolveTabId(tabId, workspace) {
const windowId = await getAutomationWindow(workspace);
const tabs = await chrome.tabs.query({ windowId });
const debuggableTab = tabs.find((t) => t.id && isDebuggableUrl(t.url));
if (debuggableTab?.id) {
console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
return debuggableTab.id;
}
console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map((t) => `${t.id}=${t.url}`).join(", ")}`);
if (debuggableTab?.id) return debuggableTab.id;
const reuseTab = tabs.find((t) => t.id);
if (reuseTab?.id) {
await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
await new Promise((resolve) => setTimeout(resolve, 300));
try {
const updated = await chrome.tabs.get(reuseTab.id);
if (isDebuggableUrl(updated.url)) return reuseTab.id;
console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`);
await chrome.tabs.update(reuseTab.id, { url: "data:text/html,<html></html>" });
await new Promise((resolve) => setTimeout(resolve, 300));
const updated2 = await chrome.tabs.get(reuseTab.id);
if (isDebuggableUrl(updated2.url)) return reuseTab.id;
console.warn(`[opencli] data: URI also intercepted, creating fresh tab`);
console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
} catch {
}
}
Expand Down
2 changes: 1 addition & 1 deletion extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "OpenCLI",
"version": "1.2.5",
"version": "1.2.6",
"description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.",
"permissions": [
"debugger",
Expand Down
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "opencli-extension",
"version": "1.2.5",
"version": "1.2.6",
"private": true,
"type": "module",
"scripts": {
Expand Down
30 changes: 7 additions & 23 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function scheduleReconnect(): void {
// ─── Automation window isolation ─────────────────────────────────────
// All opencli operations happen in a dedicated Chrome window so the
// user's active browsing session is never touched.
// The window auto-closes after 30s of idle (no commands).
// The window auto-closes after 120s of idle (no commands).

type AutomationSession = {
windowId: number;
Expand Down Expand Up @@ -247,7 +247,6 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
if (tabId !== undefined) {
try {
const tab = await chrome.tabs.get(tabId);
console.log(`[opencli] resolveTabId: explicit tabId=${tabId}, url=${tab.url}`);
if (isDebuggableUrl(tab.url)) return tabId;
// Tab exists but URL is not debuggable — fall through to auto-resolve
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
Expand All @@ -260,42 +259,27 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
// Get (or create) the automation window
const windowId = await getAutomationWindow(workspace);

// Prefer an existing debuggable tab (about:blank, http://, https://, etc.)
// Prefer an existing debuggable tab
const tabs = await chrome.tabs.query({ windowId });
const debuggableTab = tabs.find(t => t.id && isDebuggableUrl(t.url));
if (debuggableTab?.id) {
console.log(`[opencli] resolveTabId: found debuggable tab ${debuggableTab.id} (${debuggableTab.url})`);
return debuggableTab.id;
}
console.warn(`[opencli] resolveTabId: no debuggable tabs found, tabs: ${tabs.map(t => `${t.id}=${t.url}`).join(', ')}`);
if (debuggableTab?.id) return debuggableTab.id;

// No debuggable tab found — this typically happens when a "New Tab Override"
// extension replaces about:blank with a chrome-extension:// page.
// Reuse the first existing tab by navigating it to about:blank (avoids
// accumulating orphan tabs if chrome.tabs.create is also intercepted).
// No debuggable tab — another extension may have hijacked the tab URL.
// Try to reuse by navigating to a data: URI (not interceptable by New Tab Override).
const reuseTab = tabs.find(t => t.id);
if (reuseTab?.id) {
await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
// Wait for the navigation to take effect
await new Promise(resolve => setTimeout(resolve, 300));
// Verify the URL is actually debuggable (New Tab Override may have intercepted)
try {
const updated = await chrome.tabs.get(reuseTab.id);
if (isDebuggableUrl(updated.url)) return reuseTab.id;
// New Tab Override intercepted about:blank — try data: URI instead
console.warn(`[opencli] about:blank was intercepted (${updated.url}), trying data: URI`);
await chrome.tabs.update(reuseTab.id, { url: 'data:text/html,<html></html>' });
await new Promise(resolve => setTimeout(resolve, 300));
const updated2 = await chrome.tabs.get(reuseTab.id);
if (isDebuggableUrl(updated2.url)) return reuseTab.id;
// data: URI also intercepted — create a brand new tab
console.warn(`[opencli] data: URI also intercepted, creating fresh tab`);
console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
} catch {
// Tab was closed during navigation
}
}

// Window has no debuggable tabs — create one
// Fallback: create a new tab
const newTab = await chrome.tabs.create({ windowId, url: 'data:text/html,<html></html>', active: true });
if (!newTab.id) throw new Error('Failed to create tab in automation window');
return newTab.id;
Expand Down
7 changes: 5 additions & 2 deletions extension/src/cdp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@ async function ensureAttached(tabId: number): Promise<void> {
await chrome.debugger.attach({ tabId }, '1.3');
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
const hint = msg.includes('chrome-extension://')
? '. Tip: another Chrome extension may be interfering — try disabling other extensions'
: '';
if (msg.includes('Another debugger is already attached')) {
try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
try {
await chrome.debugger.attach({ tabId }, '1.3');
} catch {
throw new Error(`attach failed: ${msg}`);
throw new Error(`attach failed: ${msg}${hint}`);
}
} else {
throw new Error(`attach failed: ${msg}`);
throw new Error(`attach failed: ${msg}${hint}`);
}
}
attached.add(tabId);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jackwener/opencli",
"version": "1.2.5",
"version": "1.2.6",
"publishConfig": {
"access": "public"
},
Expand Down
Loading