diff --git a/src/browser/daemon-lifecycle.ts b/src/browser/daemon-lifecycle.ts index 6123199b7..d6e40d827 100644 --- a/src/browser/daemon-lifecycle.ts +++ b/src/browser/daemon-lifecycle.ts @@ -4,6 +4,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { DEFAULT_DAEMON_PORT } from '../constants.js'; import { BrowserConnectError } from '../errors.js'; +import { findPackageRoot, hasLocalExtensionSource } from '../package-paths.js'; import { PKG_VERSION } from '../version.js'; import { waitForBridgeReady } from './bridge-readiness.js'; import { fetchDaemonStatus, getDaemonHealth, requestDaemonShutdown, type DaemonHealth, type DaemonStatus } from './daemon-transport.js'; @@ -181,12 +182,17 @@ function browserConnectErrorFromHealth(health: DaemonHealth, contextId?: string) ); } if (health.state === 'no-extension') { + const packageRoot = findPackageRoot(fileURLToPath(import.meta.url)); + const installSteps = hasLocalExtensionSource(packageRoot) + ? ' 1. Open chrome://extensions → Developer Mode → Load unpacked\n' + + ` 2. Select this checkout's extension/ folder (${path.join(packageRoot, 'extension')})` + : ' 1. Download: https://github.com/jackwener/opencli/releases\n' + + ' 2. Open chrome://extensions → Developer Mode → Load unpacked'; return new BrowserConnectError( 'Browser Bridge extension not connected', 'Make sure Chrome/Chromium is open and the OpenCLI extension is enabled.\n' + 'If not installed:\n' + - ' 1. Download: https://github.com/jackwener/opencli/releases\n' + - ' 2. Open chrome://extensions → Developer Mode → Load unpacked', + installSteps, 'extension-not-connected', ); } diff --git a/src/doctor.ts b/src/doctor.ts index 53c6198a0..093087563 100644 --- a/src/doctor.ts +++ b/src/doctor.ts @@ -4,6 +4,8 @@ * Simplified for the daemon-based architecture. */ +import { fileURLToPath } from 'node:url'; +import * as path from 'node:path'; import { DEFAULT_DAEMON_PORT } from './constants.js'; import { BrowserBridge } from './browser/index.js'; import { setDaemonCommandTimeoutSeconds } from './browser/daemon-client.js'; @@ -15,6 +17,7 @@ import type { BrowserProfileStatus } from './browser/daemon-transport.js'; import { aliasForContextId, loadProfileConfig } from './browser/profile.js'; import { formatDaemonVersion, isDaemonStale, staleDaemonIssue } from './browser/daemon-version.js'; import { findShadowedUserAdapters, formatAdapterShadowIssue, type AdapterShadow } from './adapter-shadow.js'; +import { findPackageRoot, hasLocalExtensionSource } from './package-paths.js'; const DOCTOR_LIVE_TIMEOUT_SECONDS = 8; const DOCTOR_SESSION = '__doctor__'; @@ -151,13 +154,18 @@ export async function runBrowserDoctor(opts: DoctorOptions = {}): Promise boolean = fs.existsSync, +): boolean { + return fileExists(path.join(packageRoot, 'extension', 'manifest.json')); +}