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
18 changes: 15 additions & 3 deletions src/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { emitHook, type HookContext } from './hooks.js';
import { checkDaemonStatus } from './browser/discover.js';
import { log } from './logger.js';
import { isElectronApp } from './electron-apps.js';
import { resolveElectronEndpoint } from './launcher.js';
import { probeCDP, resolveElectronEndpoint } from './launcher.js';

const _loadedModules = new Set<string>();

Expand Down Expand Up @@ -158,8 +158,20 @@ export async function executeCommand(
let cdpEndpoint: string | undefined;

if (electron) {
// Electron apps: auto-detect, prompt restart if needed, launch with CDP
cdpEndpoint = await resolveElectronEndpoint(cmd.site);
// Electron apps: respect manual endpoint override, then try auto-detect
const manualEndpoint = process.env.OPENCLI_CDP_ENDPOINT;
if (manualEndpoint) {
const port = Number(new URL(manualEndpoint).port);
if (!await probeCDP(port)) {
throw new CommandExecutionError(
`CDP not reachable at ${manualEndpoint}`,
'Check that the app is running with --remote-debugging-port and the endpoint is correct.',
);
}
cdpEndpoint = manualEndpoint;
} else {
cdpEndpoint = await resolveElectronEndpoint(cmd.site);
}
} else {
// Browser Bridge: fail-fast when daemon is up but extension is missing.
// 300ms timeout avoids a full 2s wait on cold-start.
Expand Down
14 changes: 13 additions & 1 deletion src/launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function probeCDP(port: number, timeoutMs: number = PROBE_TIMEOUT_MS): Pr
* Uses pgrep on macOS/Linux.
*/
export function detectProcess(processName: string): boolean {
if (process.platform === 'win32') return false; // pgrep not available on Windows
try {
execFileSync('pgrep', ['-x', processName], { encoding: 'utf-8', stdio: 'pipe' });
return true;
Expand All @@ -58,6 +59,7 @@ export function detectProcess(processName: string): boolean {
* Kill a process by name. Sends SIGTERM first, then SIGKILL after grace period.
*/
export function killProcess(processName: string): void {
if (process.platform === 'win32') return; // pkill not available on Windows
try {
execFileSync('pkill', ['-x', processName], { stdio: 'pipe' });
} catch {
Expand Down Expand Up @@ -138,7 +140,17 @@ export async function resolveElectronEndpoint(site: string): Promise<string> {
return endpoint;
}

// Step 2: Running without CDP?
// Step 2: Running without CDP? (process detection requires Unix tools)
if (process.platform !== 'darwin' && process.platform !== 'linux') {
throw new CommandExecutionError(
`${label} is not reachable on CDP port ${port}.`,
`Auto-launch is not yet supported on ${process.platform}.\n` +
`Start ${label} manually with --remote-debugging-port=${port}, then either:\n` +
` • Set OPENCLI_CDP_ENDPOINT=http://127.0.0.1:${port}\n` +
` • Or just re-run the command once ${label} is listening on port ${port}.`,
);
}

const isRunning = detectProcess(processName);
if (isRunning) {
log.debug(`[launcher] ${label} is running but CDP not available`);
Expand Down