Skip to content
Open
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
8 changes: 4 additions & 4 deletions extension/manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"manifest_version": 3,
"name": "OpenCLI",
"name": "AutoCLI",
"version": "1.2.6",
"description": "Bridge between opencli CLI and your browser — execute commands, read cookies, manage tabs.",
"description": "Bridge between AutoCLI and your browser — execute commands, read cookies, manage tabs.",
"permissions": [
"debugger",
"tabs",
Expand All @@ -21,11 +21,11 @@
"128": "icons/icon-128.png"
},
"action": {
"default_title": "OpenCLI",
"default_title": "AutoCLI",
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png"
}
},
"homepage_url": "https://github.com/jackwener/opencli"
"homepage_url": "https://github.com/nashsu/AutoCLI"
}
2 changes: 1 addition & 1 deletion extension/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "opencli-extension",
"name": "autocli-extension",
"version": "1.2.6",
"private": true,
"type": "module",
Expand Down
49 changes: 35 additions & 14 deletions extension/src/background.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Based on OpenCLI (https://github.com/jackwener/opencli) by jackwener
// Licensed under Apache-2.0. Modified for AutoCLI.
/**
* OpenCLI — Service Worker (background script).
* AutoCLI — Service Worker (background script).
*
* Connects to the opencli daemon via WebSocket, receives commands,
* Connects to the AutoCLI daemon via WebSocket, receives commands,
* dispatches them to Chrome APIs (debugger/tabs/cookies), returns results.
*/

import type { Command, Result } from './protocol';
import { DAEMON_WS_URL, WS_RECONNECT_BASE_DELAY, WS_RECONNECT_MAX_DELAY } from './protocol';
import { DAEMON_WS_URL, DAEMON_HTTP_URL, WS_RECONNECT_BASE_DELAY, WS_RECONNECT_MAX_DELAY } from './protocol';
import * as executor from './cdp';

let ws: WebSocket | null = null;
Expand Down Expand Up @@ -36,9 +36,23 @@ console.error = (...args: unknown[]) => { _origError(...args); forwardLog('error

// ─── WebSocket connection ────────────────────────────────────────────

function connect(): void {
/**
* Probe the daemon via its /ping HTTP endpoint before attempting a WebSocket
* connection. fetch() failures are silently catchable; new WebSocket() is not
* — Chrome logs ERR_CONNECTION_REFUSED to the extension error page before any
* JS handler can intercept it. By gating on the probe, we avoid noisy errors
* when the daemon is simply not running yet.
*/
async function connect(): Promise<void> {
if (ws?.readyState === WebSocket.OPEN || ws?.readyState === WebSocket.CONNECTING) return;

try {
const res = await fetch(`${DAEMON_HTTP_URL}/ping`, { signal: AbortSignal.timeout(1000) });
if (!res.ok) return; // unexpected response — not our daemon
} catch {
return; // daemon not running — skip WebSocket to avoid console noise
}

try {
ws = new WebSocket(DAEMON_WS_URL);
} catch {
Expand All @@ -47,7 +61,7 @@ function connect(): void {
}

ws.onopen = () => {
console.log('[opencli] Connected to daemon');
console.log('[autocli] Connected to daemon');
reconnectAttempts = 0; // Reset on successful connection
if (reconnectTimer) {
clearTimeout(reconnectTimer);
Expand All @@ -61,12 +75,12 @@ function connect(): void {
const result = await handleCommand(command);
ws?.send(JSON.stringify(result));
} catch (err) {
console.error('[opencli] Message handling error:', err);
console.error('[autocli] Message handling error:', err);
}
};

ws.onclose = () => {
console.log('[opencli] Disconnected from daemon');
console.log('[autocli] Disconnected from daemon');
ws = null;
scheduleReconnect();
};
Expand All @@ -76,10 +90,17 @@ function connect(): void {
};
}

/**
* After MAX_EAGER_ATTEMPTS (reaching 60s backoff), stop scheduling reconnects.
* The keepalive alarm (~24s) will still call connect() periodically, but at a
* much lower frequency — reducing console noise when the daemon is not running.
*/
const MAX_EAGER_ATTEMPTS = 6; // 2s, 4s, 8s, 16s, 32s, 60s — then stop

function scheduleReconnect(): void {
if (reconnectTimer) return;
reconnectAttempts++;
// Exponential backoff: 2s, 4s, 8s, 16s, ..., capped at 60s
if (reconnectAttempts > MAX_EAGER_ATTEMPTS) return; // let keepalive alarm handle it
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
Expand All @@ -88,7 +109,7 @@ function scheduleReconnect(): void {
}

// ─── Automation window isolation ─────────────────────────────────────
// All opencli operations happen in a dedicated Chrome window so the
// All AutoCLI operations happen in a dedicated Chrome window so the
// user's active browsing session is never touched.
// The window auto-closes after 120s of idle (no commands).

Expand Down Expand Up @@ -180,7 +201,7 @@ function initialize(): void {
chrome.alarms.create('keepalive', { periodInMinutes: 0.4 }); // ~24 seconds
executor.registerListeners();
connect();
console.log('[opencli] OpenCLI extension initialized');
console.log('[autocli] AutoCLI extension initialized');
}

chrome.runtime.onInstalled.addListener(() => {
Expand Down Expand Up @@ -251,10 +272,10 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
const tab = await chrome.tabs.get(tabId);
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`);
console.warn(`[autocli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
} catch {
// Tab was closed — fall through to auto-resolve
console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
console.warn(`[autocli] Tab ${tabId} no longer exists, re-resolving`);
}
}

Expand All @@ -275,7 +296,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
try {
const updated = await chrome.tabs.get(reuseTab.id);
if (isDebuggableUrl(updated.url)) return reuseTab.id;
console.warn(`[opencli] data: URI was intercepted (${updated.url}), creating fresh tab`);
console.warn(`[autocli] data: URI was intercepted (${updated.url}), creating fresh tab`);
} catch {
// Tab was closed during navigation
}
Expand Down Expand Up @@ -370,7 +391,7 @@ async function handleNavigate(cmd: Command, workspace: string): Promise<Result>
setTimeout(() => {
chrome.tabs.onUpdated.removeListener(listener);
timedOut = true;
console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
console.warn(`[autocli] Navigate to ${targetUrl} timed out after 15s`);
resolve();
}, 15000);
});
Expand Down
2 changes: 1 addition & 1 deletion extension/src/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Based on OpenCLI (https://github.com/jackwener/opencli) by jackwener
// Licensed under Apache-2.0. Modified for AutoCLI.
/**
* opencli browser protocol — shared types between daemon, extension, and CLI.
* AutoCLI browser protocol — shared types between daemon, extension, and CLI.
*
* 5 actions: exec, navigate, tabs, cookies, screenshot.
* Everything else is just JS code sent via 'exec'.
Expand Down