agent-browser.js
1 #!/usr/bin/env node 2 3 /** 4 * Cross-platform CLI wrapper for agent-browser 5 * 6 * This wrapper enables npx support on Windows where shell scripts don't work. 7 * For global installs, postinstall.js patches the shims to invoke the native 8 * binary directly (zero overhead). 9 */ 10 11 import { spawn, execSync } from 'child_process'; 12 import { existsSync, accessSync, chmodSync, constants } from 'fs'; 13 import { dirname, join } from 'path'; 14 import { fileURLToPath } from 'url'; 15 import { platform, arch } from 'os'; 16 17 const __dirname = dirname(fileURLToPath(import.meta.url)); 18 19 // Detect if the system uses musl libc (e.g. Alpine Linux) 20 function isMusl() { 21 if (platform() !== 'linux') return false; 22 try { 23 const result = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' }); 24 return result.toLowerCase().includes('musl'); 25 } catch { 26 return existsSync('/lib/ld-musl-x86_64.so.1') || existsSync('/lib/ld-musl-aarch64.so.1'); 27 } 28 } 29 30 // Map Node.js platform/arch to binary naming convention 31 function getBinaryName() { 32 const os = platform(); 33 const cpuArch = arch(); 34 35 let osKey; 36 switch (os) { 37 case 'darwin': 38 osKey = 'darwin'; 39 break; 40 case 'linux': 41 osKey = isMusl() ? 'linux-musl' : 'linux'; 42 break; 43 case 'win32': 44 osKey = 'win32'; 45 break; 46 default: 47 return null; 48 } 49 50 let archKey; 51 switch (cpuArch) { 52 case 'x64': 53 case 'x86_64': 54 archKey = 'x64'; 55 break; 56 case 'arm64': 57 case 'aarch64': 58 archKey = 'arm64'; 59 break; 60 default: 61 return null; 62 } 63 64 const ext = os === 'win32' ? '.exe' : ''; 65 return `agent-browser-${osKey}-${archKey}${ext}`; 66 } 67 68 function main() { 69 const binaryName = getBinaryName(); 70 71 if (!binaryName) { 72 console.error(`Error: Unsupported platform: ${platform()}-${arch()}`); 73 process.exit(1); 74 } 75 76 const binaryPath = join(__dirname, binaryName); 77 78 if (!existsSync(binaryPath)) { 79 console.error(`Error: No binary found for ${platform()}-${arch()}`); 80 console.error(`Expected: ${binaryPath}`); 81 console.error(''); 82 console.error('Run "npm run build:native" to build for your platform,'); 83 console.error('or reinstall the package to trigger the postinstall download.'); 84 process.exit(1); 85 } 86 87 // Ensure binary is executable (fixes EACCES on macOS/Linux when postinstall didn't run, 88 // e.g., when using bun which blocks lifecycle scripts by default) 89 if (platform() !== 'win32') { 90 try { 91 accessSync(binaryPath, constants.X_OK); 92 } catch { 93 // Binary exists but isn't executable - fix it 94 try { 95 chmodSync(binaryPath, 0o755); 96 } catch (chmodErr) { 97 console.error(`Error: Cannot make binary executable: ${chmodErr.message}`); 98 console.error('Try running: chmod +x ' + binaryPath); 99 process.exit(1); 100 } 101 } 102 } 103 104 // Spawn the native binary with inherited stdio 105 const child = spawn(binaryPath, process.argv.slice(2), { 106 stdio: 'inherit', 107 windowsHide: false, 108 }); 109 110 child.on('error', (err) => { 111 console.error(`Error executing binary: ${err.message}`); 112 process.exit(1); 113 }); 114 115 child.on('close', (code) => { 116 process.exit(code ?? 0); 117 }); 118 } 119 120 main();