diff --git a/bin/agent-browser b/bin/agent-browser index e9239fe0..31ac9d86 100755 --- a/bin/agent-browser +++ b/bin/agent-browser @@ -1,26 +1,80 @@ -#!/bin/sh -# agent-browser CLI wrapper -# Detects OS/arch and runs the appropriate native binary - -SCRIPT="$0" -while [ -L "$SCRIPT" ]; do - SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" - SCRIPT="$(readlink "$SCRIPT")" - case "$SCRIPT" in /*) ;; *) SCRIPT="$SCRIPT_DIR/$SCRIPT" ;; esac -done -SCRIPT_DIR="$(cd "$(dirname "$SCRIPT")" && pwd)" - -OS=$(uname -s | tr '[:upper:]' '[:lower:]') -ARCH=$(uname -m) -case "$OS" in darwin) OS="darwin" ;; linux) OS="linux" ;; mingw*|msys*|cygwin*) OS="win32" ;; esac -case "$ARCH" in x86_64|amd64) ARCH="x64" ;; aarch64|arm64) ARCH="arm64" ;; esac - -BINARY="$SCRIPT_DIR/agent-browser-${OS}-${ARCH}" - -if [ -f "$BINARY" ] && [ -x "$BINARY" ]; then - exec "$BINARY" "$@" -fi - -echo "Error: No binary found for ${OS}-${ARCH}" >&2 -echo "Run 'npm run build:native' to build for your platform" >&2 -exit 1 +#!/usr/bin/env node + +async function main() { + const [{ accessSync, constants, existsSync, realpathSync }, { spawn }, { dirname, join }] = + await Promise.all([import('fs'), import('child_process'), import('path')]); + + const scriptArg = process.argv[1]; + if (!scriptArg) { + console.error('Error: Missing script path for agent-browser wrapper'); + process.exit(1); + } + + let scriptPath; + try { + scriptPath = realpathSync(scriptArg); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Error: Failed to resolve script path: ${message}`); + process.exit(1); + } + + const scriptDir = dirname(scriptPath); + const platform = process.platform; + const arch = process.arch; + + const os = + platform === 'win32' ? 'win32' : platform === 'darwin' ? 'darwin' : platform === 'linux' ? 'linux' : null; + + if (!os) { + console.error(`Error: Unsupported platform ${platform}`); + process.exit(1); + } + + const mappedArch = arch === 'x64' ? 'x64' : arch === 'arm64' ? 'arm64' : null; + + if (!mappedArch) { + console.error(`Error: Unsupported architecture ${arch}`); + process.exit(1); + } + + const ext = platform === 'win32' ? '.exe' : ''; + const binaryName = `agent-browser-${os}-${mappedArch}${ext}`; + const binaryPath = join(scriptDir, binaryName); + + try { + if (!existsSync(binaryPath)) { + throw new Error(`No binary found for ${os}-${mappedArch}`); + } + if (platform !== 'win32') { + accessSync(binaryPath, constants.X_OK); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error(`Error: ${message}`); + console.error("Run 'npm run build:native' to build for your platform"); + process.exit(1); + } + + const child = spawn(binaryPath, process.argv.slice(2), { stdio: 'inherit' }); + + child.on('error', (err) => { + const message = err instanceof Error ? err.message : String(err); + console.error(`Error: Failed to launch ${binaryName}: ${message}`); + process.exit(1); + }); + + child.on('close', (code, signal) => { + if (typeof code === 'number') { + process.exit(code); + } + console.error(`Error: ${binaryName} exited with signal ${signal ?? 'unknown'}`); + process.exit(1); + }); +} + +main().catch((err) => { + const message = err instanceof Error ? err.message : String(err); + console.error(`Error: Unexpected failure in CLI wrapper: ${message}`); + process.exit(1); +}); diff --git a/bin/agent-browser.cmd b/bin/agent-browser.cmd index 58750e28..5ffdd64a 100644 --- a/bin/agent-browser.cmd +++ b/bin/agent-browser.cmd @@ -1,5 +1,5 @@ @echo off setlocal set "SCRIPT_DIR=%~dp0" -node "%SCRIPT_DIR%..\dist\index.js" %* +node "%SCRIPT_DIR%agent-browser" %* exit /b %errorlevel% diff --git a/cli/src/connection.rs b/cli/src/connection.rs index 14300612..56ce35c5 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -256,16 +256,26 @@ pub fn ensure_daemon( cmd.env("AGENT_BROWSER_EXTENSIONS", extensions.join(",")); } - // CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS + // CREATE_NEW_PROCESS_GROUP (keep console association for headed mode) const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200; - const DETACHED_PROCESS: u32 = 0x00000008; - - cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn() - .map_err(|e| format!("Failed to start daemon: {}", e))?; + + if headed { + cmd.creation_flags(CREATE_NEW_PROCESS_GROUP) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| format!("Failed to start daemon: {}", e))?; + } else { + // DETACHED_PROCESS avoids flashing a console window in headless mode + const DETACHED_PROCESS: u32 = 0x00000008; + cmd.creation_flags(CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| format!("Failed to start daemon: {}", e))?; + } } for _ in 0..50 {