chore: rename extension to OpenCLI

This commit is contained in:
jackwener committed 2026-03-20 11:03:02 +08:00
1 parent 3229294f08
commit f5f7a9500e
3 files changed
+342 -434

No files matched your search

+338 -430
View File
@@ -1,484 +1,392 @@
//#region src/protocol.ts
/** Default daemon port */
var DAEMON_PORT = 19825;
var DAEMON_HOST = "localhost";
var DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
`${DAEMON_HOST}${DAEMON_PORT}`;
/** Base reconnect delay for extension WebSocket (ms) */
var WS_RECONNECT_BASE_DELAY = 2e3;
/** Max reconnect delay (ms) */
var WS_RECONNECT_MAX_DELAY = 6e4;
//#endregion
//#region src/cdp.ts
/**
* CDP execution via chrome.debugger API.
*
* chrome.debugger only needs the "debugger" permission — no host_permissions.
* It can attach to any http/https tab. Avoid chrome:// and chrome-extension://
* tabs (resolveTabId in background.ts filters them).
*/
var attached = /* @__PURE__ */ new Set();
const DAEMON_PORT = 19825;
const DAEMON_HOST = "localhost";
const DAEMON_WS_URL = `ws://${DAEMON_HOST}:${DAEMON_PORT}/ext`;
const WS_RECONNECT_BASE_DELAY = 2e3;
const WS_RECONNECT_MAX_DELAY = 6e4;
const attached = /* @__PURE__ */ new Set();
async function ensureAttached(tabId) {
if (attached.has(tabId)) return;
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes("Another debugger is already attached")) {
try {
await chrome.debugger.detach({ tabId });
} catch {}
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch {
throw new Error(`attach failed: ${msg}`);
}
} else throw new Error(`attach failed: ${msg}`);
}
attached.add(tabId);
try {
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
} catch {}
if (attached.has(tabId)) return;
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes("Another debugger is already attached")) {
try {
await chrome.debugger.detach({ tabId });
} catch {
}
try {
await chrome.debugger.attach({ tabId }, "1.3");
} catch {
throw new Error(`attach failed: ${msg}`);
}
} else {
throw new Error(`attach failed: ${msg}`);
}
}
attached.add(tabId);
try {
await chrome.debugger.sendCommand({ tabId }, "Runtime.enable");
} catch {
}
}
async function evaluate(tabId, expression) {
await ensureAttached(tabId);
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
expression,
returnByValue: true,
awaitPromise: true
});
if (result.exceptionDetails) {
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
throw new Error(errMsg);
}
return result.result?.value;
await ensureAttached(tabId);
const result = await chrome.debugger.sendCommand({ tabId }, "Runtime.evaluate", {
expression,
returnByValue: true,
awaitPromise: true
});
if (result.exceptionDetails) {
const errMsg = result.exceptionDetails.exception?.description || result.exceptionDetails.text || "Eval error";
throw new Error(errMsg);
}
return result.result?.value;
}
var evaluateAsync = evaluate;
/**
* Capture a screenshot via CDP Page.captureScreenshot.
* Returns base64-encoded image data.
*/
const evaluateAsync = evaluate;
async function screenshot(tabId, options = {}) {
await ensureAttached(tabId);
const format = options.format ?? "png";
if (options.fullPage) {
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
const size = metrics.cssContentSize || metrics.contentSize;
if (size) await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
mobile: false,
width: Math.ceil(size.width),
height: Math.ceil(size.height),
deviceScaleFactor: 1
});
}
try {
const params = { format };
if (format === "jpeg" && options.quality !== void 0) params.quality = Math.max(0, Math.min(100, options.quality));
return (await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params)).data;
} finally {
if (options.fullPage) await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {});
}
await ensureAttached(tabId);
const format = options.format ?? "png";
if (options.fullPage) {
const metrics = await chrome.debugger.sendCommand({ tabId }, "Page.getLayoutMetrics");
const size = metrics.cssContentSize || metrics.contentSize;
if (size) {
await chrome.debugger.sendCommand({ tabId }, "Emulation.setDeviceMetricsOverride", {
mobile: false,
width: Math.ceil(size.width),
height: Math.ceil(size.height),
deviceScaleFactor: 1
});
}
}
try {
const params = { format };
if (format === "jpeg" && options.quality !== void 0) {
params.quality = Math.max(0, Math.min(100, options.quality));
}
const result = await chrome.debugger.sendCommand({ tabId }, "Page.captureScreenshot", params);
return result.data;
} finally {
if (options.fullPage) {
await chrome.debugger.sendCommand({ tabId }, "Emulation.clearDeviceMetricsOverride").catch(() => {
});
}
}
}
function detach(tabId) {
if (!attached.has(tabId)) return;
attached.delete(tabId);
try {
chrome.debugger.detach({ tabId });
} catch {}
if (!attached.has(tabId)) return;
attached.delete(tabId);
try {
chrome.debugger.detach({ tabId });
} catch {
}
}
function registerListeners() {
chrome.tabs.onRemoved.addListener((tabId) => {
attached.delete(tabId);
});
chrome.debugger.onDetach.addListener((source) => {
if (source.tabId) attached.delete(source.tabId);
});
chrome.tabs.onRemoved.addListener((tabId) => {
attached.delete(tabId);
});
chrome.debugger.onDetach.addListener((source) => {
if (source.tabId) attached.delete(source.tabId);
});
}
//#endregion
//#region src/background.ts
var ws = null;
var reconnectTimer = null;
var reconnectAttempts = 0;
var _origLog = console.log.bind(console);
var _origWarn = console.warn.bind(console);
var _origError = console.error.bind(console);
let ws = null;
let reconnectTimer = null;
let reconnectAttempts = 0;
const _origLog = console.log.bind(console);
const _origWarn = console.warn.bind(console);
const _origError = console.error.bind(console);
function forwardLog(level, args) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
try {
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
ws.send(JSON.stringify({
type: "log",
level,
msg,
ts: Date.now()
}));
} catch {}
if (!ws || ws.readyState !== WebSocket.OPEN) return;
try {
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
ws.send(JSON.stringify({ type: "log", level, msg, ts: Date.now() }));
} catch {
}
}
console.log = (...args) => {
_origLog(...args);
forwardLog("info", args);
_origLog(...args);
forwardLog("info", args);
};
console.warn = (...args) => {
_origWarn(...args);
forwardLog("warn", args);
_origWarn(...args);
forwardLog("warn", args);
};
console.error = (...args) => {
_origError(...args);
forwardLog("error", args);
_origError(...args);
forwardLog("error", args);
};
function connect() {
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
try {
ws = new WebSocket(DAEMON_WS_URL);
} catch {
scheduleReconnect();
return;
}
ws.onopen = () => {
console.log("[opencli] Connected to daemon");
reconnectAttempts = 0;
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
};
ws.onmessage = async (event) => {
try {
const result = await handleCommand(JSON.parse(event.data));
ws?.send(JSON.stringify(result));
} catch (err) {
console.error("[opencli] Message handling error:", err);
}
};
ws.onclose = () => {
console.log("[opencli] Disconnected from daemon");
ws = null;
scheduleReconnect();
};
ws.onerror = () => {
ws?.close();
};
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;
try {
ws = new WebSocket(DAEMON_WS_URL);
} catch {
scheduleReconnect();
return;
}
ws.onopen = () => {
console.log("[opencli] Connected to daemon");
reconnectAttempts = 0;
if (reconnectTimer) {
clearTimeout(reconnectTimer);
reconnectTimer = null;
}
};
ws.onmessage = async (event) => {
try {
const command = JSON.parse(event.data);
const result = await handleCommand(command);
ws?.send(JSON.stringify(result));
} catch (err) {
console.error("[opencli] Message handling error:", err);
}
};
ws.onclose = () => {
console.log("[opencli] Disconnected from daemon");
ws = null;
scheduleReconnect();
};
ws.onerror = () => {
ws?.close();
};
}
function scheduleReconnect() {
if (reconnectTimer) return;
reconnectAttempts++;
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, delay);
if (reconnectTimer) return;
reconnectAttempts++;
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, delay);
}
var automationWindowId = null;
var windowIdleTimer = null;
var WINDOW_IDLE_TIMEOUT = 3e4;
let automationWindowId = null;
let windowIdleTimer = null;
const WINDOW_IDLE_TIMEOUT = 3e4;
function resetWindowIdleTimer() {
if (windowIdleTimer) clearTimeout(windowIdleTimer);
windowIdleTimer = setTimeout(async () => {
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
} catch {}
automationWindowId = null;
}
windowIdleTimer = null;
}, WINDOW_IDLE_TIMEOUT);
if (windowIdleTimer) clearTimeout(windowIdleTimer);
windowIdleTimer = setTimeout(async () => {
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
console.log(`[opencli] Automation window ${automationWindowId} closed (idle timeout)`);
} catch {
}
automationWindowId = null;
}
windowIdleTimer = null;
}, WINDOW_IDLE_TIMEOUT);
}
/** Get or create the dedicated automation window. */
async function getAutomationWindow() {
if (automationWindowId !== null) try {
await chrome.windows.get(automationWindowId);
return automationWindowId;
} catch {
automationWindowId = null;
}
automationWindowId = (await chrome.windows.create({
url: "about:blank",
focused: false,
width: 1280,
height: 900,
type: "normal"
})).id;
console.log(`[opencli] Created automation window ${automationWindowId}`);
return automationWindowId;
if (automationWindowId !== null) {
try {
await chrome.windows.get(automationWindowId);
return automationWindowId;
} catch {
automationWindowId = null;
}
}
const win = await chrome.windows.create({
url: "about:blank",
focused: false,
width: 1280,
height: 900,
type: "normal"
});
automationWindowId = win.id;
console.log(`[opencli] Created automation window ${automationWindowId}`);
return automationWindowId;
}
chrome.windows.onRemoved.addListener((windowId) => {
if (windowId === automationWindowId) {
console.log("[opencli] Automation window closed");
automationWindowId = null;
if (windowIdleTimer) {
clearTimeout(windowIdleTimer);
windowIdleTimer = null;
}
}
if (windowId === automationWindowId) {
console.log("[opencli] Automation window closed");
automationWindowId = null;
if (windowIdleTimer) {
clearTimeout(windowIdleTimer);
windowIdleTimer = null;
}
}
});
var initialized = false;
let initialized = false;
function initialize() {
if (initialized) return;
initialized = true;
chrome.alarms.create("keepalive", { periodInMinutes: .4 });
registerListeners();
connect();
console.log("[opencli] Browser Bridge extension initialized");
if (initialized) return;
initialized = true;
chrome.alarms.create("keepalive", { periodInMinutes: 0.4 });
registerListeners();
connect();
console.log("[opencli] OpenCLI extension initialized");
}
chrome.runtime.onInstalled.addListener(() => {
initialize();
initialize();
});
chrome.runtime.onStartup.addListener(() => {
initialize();
initialize();
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === "keepalive") connect();
if (alarm.name === "keepalive") connect();
});
async function handleCommand(cmd) {
resetWindowIdleTimer();
try {
switch (cmd.action) {
case "exec": return await handleExec(cmd);
case "navigate": return await handleNavigate(cmd);
case "tabs": return await handleTabs(cmd);
case "cookies": return await handleCookies(cmd);
case "screenshot": return await handleScreenshot(cmd);
case "close-window": return await handleCloseWindow(cmd);
default: return {
id: cmd.id,
ok: false,
error: `Unknown action: ${cmd.action}`
};
}
} catch (err) {
return {
id: cmd.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
};
}
resetWindowIdleTimer();
try {
switch (cmd.action) {
case "exec":
return await handleExec(cmd);
case "navigate":
return await handleNavigate(cmd);
case "tabs":
return await handleTabs(cmd);
case "cookies":
return await handleCookies(cmd);
case "screenshot":
return await handleScreenshot(cmd);
case "close-window":
return await handleCloseWindow(cmd);
default:
return { id: cmd.id, ok: false, error: `Unknown action: ${cmd.action}` };
}
} catch (err) {
return {
id: cmd.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
};
}
}
/** Check if a URL is a debuggable web page (not chrome:// or extension page) */
function isWebUrl(url) {
if (!url) return false;
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
if (!url) return false;
return !url.startsWith("chrome://") && !url.startsWith("chrome-extension://");
}
/**
* Resolve target tab in the automation window.
* If explicit tabId is given, use that directly.
* Otherwise, find or create a tab in the dedicated automation window.
*/
async function resolveTabId(tabId) {
if (tabId !== void 0) return tabId;
const windowId = await getAutomationWindow();
const tabs = await chrome.tabs.query({ windowId });
const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
if (webTab?.id) return webTab.id;
if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
const newTab = await chrome.tabs.create({
windowId,
url: "about:blank",
active: true
});
if (!newTab.id) throw new Error("Failed to create tab in automation window");
return newTab.id;
if (tabId !== void 0) return tabId;
const windowId = await getAutomationWindow();
const tabs = await chrome.tabs.query({ windowId });
const webTab = tabs.find((t) => t.id && isWebUrl(t.url));
if (webTab?.id) return webTab.id;
if (tabs.length > 0 && tabs[0]?.id) return tabs[0].id;
const newTab = await chrome.tabs.create({ windowId, url: "about:blank", active: true });
if (!newTab.id) throw new Error("Failed to create tab in automation window");
return newTab.id;
}
async function handleExec(cmd) {
if (!cmd.code) return {
id: cmd.id,
ok: false,
error: "Missing code"
};
const tabId = await resolveTabId(cmd.tabId);
try {
const data = await evaluateAsync(tabId, cmd.code);
return {
id: cmd.id,
ok: true,
data
};
} catch (err) {
return {
id: cmd.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
};
}
if (!cmd.code) return { id: cmd.id, ok: false, error: "Missing code" };
const tabId = await resolveTabId(cmd.tabId);
try {
const data = await evaluateAsync(tabId, cmd.code);
return { id: cmd.id, ok: true, data };
} catch (err) {
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function handleNavigate(cmd) {
if (!cmd.url) return {
id: cmd.id,
ok: false,
error: "Missing url"
};
const tabId = await resolveTabId(cmd.tabId);
await chrome.tabs.update(tabId, { url: cmd.url });
await new Promise((resolve) => {
chrome.tabs.get(tabId).then((tab) => {
if (tab.status === "complete") {
resolve();
return;
}
const listener = (id, info) => {
if (id === tabId && info.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}, 15e3);
});
});
const tab = await chrome.tabs.get(tabId);
return {
id: cmd.id,
ok: true,
data: {
title: tab.title,
url: tab.url,
tabId
}
};
if (!cmd.url) return { id: cmd.id, ok: false, error: "Missing url" };
const tabId = await resolveTabId(cmd.tabId);
await chrome.tabs.update(tabId, { url: cmd.url });
await new Promise((resolve) => {
chrome.tabs.get(tabId).then((tab2) => {
if (tab2.status === "complete") {
resolve();
return;
}
const listener = (id, info) => {
if (id === tabId && info.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}
};
chrome.tabs.onUpdated.addListener(listener);
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(listener);
resolve();
}, 15e3);
});
});
const tab = await chrome.tabs.get(tabId);
return { id: cmd.id, ok: true, data: { title: tab.title, url: tab.url, tabId } };
}
async function handleTabs(cmd) {
switch (cmd.op) {
case "list": {
const data = (await chrome.tabs.query({})).filter((t) => isWebUrl(t.url)).map((t, i) => ({
index: i,
tabId: t.id,
url: t.url,
title: t.title,
active: t.active
}));
return {
id: cmd.id,
ok: true,
data
};
}
case "new": {
const tab = await chrome.tabs.create({
url: cmd.url,
active: true
});
return {
id: cmd.id,
ok: true,
data: {
tabId: tab.id,
url: tab.url
}
};
}
case "close": {
if (cmd.index !== void 0) {
const target = (await chrome.tabs.query({}))[cmd.index];
if (!target?.id) return {
id: cmd.id,
ok: false,
error: `Tab index ${cmd.index} not found`
};
await chrome.tabs.remove(target.id);
detach(target.id);
return {
id: cmd.id,
ok: true,
data: { closed: target.id }
};
}
const tabId = await resolveTabId(cmd.tabId);
await chrome.tabs.remove(tabId);
detach(tabId);
return {
id: cmd.id,
ok: true,
data: { closed: tabId }
};
}
case "select": {
if (cmd.index === void 0 && cmd.tabId === void 0) return {
id: cmd.id,
ok: false,
error: "Missing index or tabId"
};
if (cmd.tabId !== void 0) {
await chrome.tabs.update(cmd.tabId, { active: true });
return {
id: cmd.id,
ok: true,
data: { selected: cmd.tabId }
};
}
const target = (await chrome.tabs.query({}))[cmd.index];
if (!target?.id) return {
id: cmd.id,
ok: false,
error: `Tab index ${cmd.index} not found`
};
await chrome.tabs.update(target.id, { active: true });
return {
id: cmd.id,
ok: true,
data: { selected: target.id }
};
}
default: return {
id: cmd.id,
ok: false,
error: `Unknown tabs op: ${cmd.op}`
};
}
switch (cmd.op) {
case "list": {
const tabs = await chrome.tabs.query({});
const data = tabs.filter((t) => isWebUrl(t.url)).map((t, i) => ({
index: i,
tabId: t.id,
url: t.url,
title: t.title,
active: t.active
}));
return { id: cmd.id, ok: true, data };
}
case "new": {
const tab = await chrome.tabs.create({ url: cmd.url, active: true });
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
}
case "close": {
if (cmd.index !== void 0) {
const tabs = await chrome.tabs.query({});
const target = tabs[cmd.index];
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
await chrome.tabs.remove(target.id);
detach(target.id);
return { id: cmd.id, ok: true, data: { closed: target.id } };
}
const tabId = await resolveTabId(cmd.tabId);
await chrome.tabs.remove(tabId);
detach(tabId);
return { id: cmd.id, ok: true, data: { closed: tabId } };
}
case "select": {
if (cmd.index === void 0 && cmd.tabId === void 0)
return { id: cmd.id, ok: false, error: "Missing index or tabId" };
if (cmd.tabId !== void 0) {
await chrome.tabs.update(cmd.tabId, { active: true });
return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
}
const tabs = await chrome.tabs.query({});
const target = tabs[cmd.index];
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
await chrome.tabs.update(target.id, { active: true });
return { id: cmd.id, ok: true, data: { selected: target.id } };
}
default:
return { id: cmd.id, ok: false, error: `Unknown tabs op: ${cmd.op}` };
}
}
async function handleCookies(cmd) {
const details = {};
if (cmd.domain) details.domain = cmd.domain;
if (cmd.url) details.url = cmd.url;
const data = (await chrome.cookies.getAll(details)).map((c) => ({
name: c.name,
value: c.value,
domain: c.domain,
path: c.path,
secure: c.secure,
httpOnly: c.httpOnly,
expirationDate: c.expirationDate
}));
return {
id: cmd.id,
ok: true,
data
};
const details = {};
if (cmd.domain) details.domain = cmd.domain;
if (cmd.url) details.url = cmd.url;
const cookies = await chrome.cookies.getAll(details);
const data = cookies.map((c) => ({
name: c.name,
value: c.value,
domain: c.domain,
path: c.path,
secure: c.secure,
httpOnly: c.httpOnly,
expirationDate: c.expirationDate
}));
return { id: cmd.id, ok: true, data };
}
async function handleScreenshot(cmd) {
const tabId = await resolveTabId(cmd.tabId);
try {
const data = await screenshot(tabId, {
format: cmd.format,
quality: cmd.quality,
fullPage: cmd.fullPage
});
return {
id: cmd.id,
ok: true,
data
};
} catch (err) {
return {
id: cmd.id,
ok: false,
error: err instanceof Error ? err.message : String(err)
};
}
const tabId = await resolveTabId(cmd.tabId);
try {
const data = await screenshot(tabId, {
format: cmd.format,
quality: cmd.quality,
fullPage: cmd.fullPage
});
return { id: cmd.id, ok: true, data };
} catch (err) {
return { id: cmd.id, ok: false, error: err instanceof Error ? err.message : String(err) };
}
}
async function handleCloseWindow(cmd) {
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
} catch {}
automationWindowId = null;
}
return {
id: cmd.id,
ok: true,
data: { closed: true }
};
if (automationWindowId !== null) {
try {
await chrome.windows.remove(automationWindowId);
} catch {
}
automationWindowId = null;
}
return { id: cmd.id, ok: true, data: { closed: true } };
}
//#endregion
+2 -2
View File
@@ -1,6 +1,6 @@
{
"manifest_version": 3,
"name": "opencli Browser Bridge",
"name": "OpenCLI",
"version": "0.2.0",
"description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.",
"permissions": [
@@ -21,7 +21,7 @@
"128": "icons/icon-128.png"
},
"action": {
"default_title": "opencli Browser Bridge",
"default_title": "OpenCLI",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png"
+2 -2
View File
@@ -1,5 +1,5 @@
/**
* opencli Browser Bridge — Service Worker (background script).
* OpenCLI — Service Worker (background script).
*
* Connects to the opencli daemon via WebSocket, receives commands,
* dispatches them to Chrome APIs (debugger/tabs/cookies), returns results.
@@ -155,7 +155,7 @@ function initialize(): void {
chrome.alarms.create('keepalive', { periodInMinutes: 0.4 }); // ~24 seconds
executor.registerListeners();
connect();
console.log('[opencli] Browser Bridge extension initialized');
console.log('[opencli] OpenCLI extension initialized');
}
chrome.runtime.onInstalled.addListener(() => {