agent-browser-priv 0.27.3-priv.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +1564 -0
  3. package/bin/agent-browser.js +125 -0
  4. package/package.json +52 -0
  5. package/scripts/build-all-platforms.sh +76 -0
  6. package/scripts/check-version-sync.js +51 -0
  7. package/scripts/copy-native.js +36 -0
  8. package/scripts/postinstall.js +327 -0
  9. package/scripts/sync-version.js +81 -0
  10. package/scripts/windows-debug/provision.sh +220 -0
  11. package/scripts/windows-debug/run.sh +92 -0
  12. package/scripts/windows-debug/start.sh +43 -0
  13. package/scripts/windows-debug/stop.sh +28 -0
  14. package/scripts/windows-debug/sync.sh +27 -0
  15. package/skill-data/agentcore/SKILL.md +115 -0
  16. package/skill-data/core/SKILL.md +488 -0
  17. package/skill-data/core/references/authentication.md +303 -0
  18. package/skill-data/core/references/commands.md +403 -0
  19. package/skill-data/core/references/profiling.md +120 -0
  20. package/skill-data/core/references/proxy-support.md +194 -0
  21. package/skill-data/core/references/session-management.md +193 -0
  22. package/skill-data/core/references/snapshot-refs.md +219 -0
  23. package/skill-data/core/references/trust-boundaries.md +89 -0
  24. package/skill-data/core/references/video-recording.md +175 -0
  25. package/skill-data/core/templates/authenticated-session.sh +105 -0
  26. package/skill-data/core/templates/capture-workflow.sh +69 -0
  27. package/skill-data/core/templates/form-automation.sh +62 -0
  28. package/skill-data/dogfood/SKILL.md +220 -0
  29. package/skill-data/dogfood/references/issue-taxonomy.md +109 -0
  30. package/skill-data/dogfood/templates/dogfood-report-template.md +53 -0
  31. package/skill-data/electron/SKILL.md +236 -0
  32. package/skill-data/slack/SKILL.md +285 -0
  33. package/skill-data/slack/references/slack-tasks.md +348 -0
  34. package/skill-data/slack/templates/slack-report-template.md +163 -0
  35. package/skill-data/vercel-sandbox/SKILL.md +280 -0
  36. package/skills/agent-browser/SKILL.md +55 -0
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cross-platform CLI wrapper for agent-browser-priv
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
+ if (isMusl()) {
42
+ console.error('Error: agent-browser-priv does not publish musl Linux binaries.');
43
+ console.error('Use a glibc-based Linux environment or build locally with npm run build:native.');
44
+ process.exit(1);
45
+ }
46
+ osKey = 'linux';
47
+ break;
48
+ case 'win32':
49
+ osKey = 'win32';
50
+ break;
51
+ default:
52
+ return null;
53
+ }
54
+
55
+ let archKey;
56
+ switch (cpuArch) {
57
+ case 'x64':
58
+ case 'x86_64':
59
+ archKey = 'x64';
60
+ break;
61
+ case 'arm64':
62
+ case 'aarch64':
63
+ archKey = 'arm64';
64
+ break;
65
+ default:
66
+ return null;
67
+ }
68
+
69
+ const ext = os === 'win32' ? '.exe' : '';
70
+ return `agent-browser-priv-${osKey}-${archKey}${ext}`;
71
+ }
72
+
73
+ function main() {
74
+ const binaryName = getBinaryName();
75
+
76
+ if (!binaryName) {
77
+ console.error(`Error: Unsupported platform: ${platform()}-${arch()}`);
78
+ process.exit(1);
79
+ }
80
+
81
+ const binaryPath = join(__dirname, binaryName);
82
+
83
+ if (!existsSync(binaryPath)) {
84
+ console.error(`Error: No binary found for ${platform()}-${arch()}`);
85
+ console.error(`Expected: ${binaryPath}`);
86
+ console.error('');
87
+ console.error('Run "npm run build:native" to build for your platform,');
88
+ console.error('or reinstall the package to trigger the postinstall download.');
89
+ process.exit(1);
90
+ }
91
+
92
+ // Ensure binary is executable (fixes EACCES on macOS/Linux when postinstall didn't run,
93
+ // e.g., when using bun which blocks lifecycle scripts by default)
94
+ if (platform() !== 'win32') {
95
+ try {
96
+ accessSync(binaryPath, constants.X_OK);
97
+ } catch {
98
+ // Binary exists but isn't executable - fix it
99
+ try {
100
+ chmodSync(binaryPath, 0o755);
101
+ } catch (chmodErr) {
102
+ console.error(`Error: Cannot make binary executable: ${chmodErr.message}`);
103
+ console.error('Try running: chmod +x ' + binaryPath);
104
+ process.exit(1);
105
+ }
106
+ }
107
+ }
108
+
109
+ // Spawn the native binary with inherited stdio
110
+ const child = spawn(binaryPath, process.argv.slice(2), {
111
+ stdio: 'inherit',
112
+ windowsHide: false,
113
+ });
114
+
115
+ child.on('error', (err) => {
116
+ console.error(`Error executing binary: ${err.message}`);
117
+ process.exit(1);
118
+ });
119
+
120
+ child.on('close', (code) => {
121
+ process.exit(code ?? 0);
122
+ });
123
+ }
124
+
125
+ main();
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "agent-browser-priv",
3
+ "version": "0.27.3-priv.3",
4
+ "description": "Browser automation CLI for AI agents",
5
+ "type": "module",
6
+ "packageManager": "pnpm@11.1.3",
7
+ "engines": {
8
+ "node": ">=24.0.0",
9
+ "pnpm": ">=11.0.0"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "scripts",
14
+ "skill-data",
15
+ "skills"
16
+ ],
17
+ "bin": {
18
+ "agent-browser-priv": "bin/agent-browser.js"
19
+ },
20
+ "scripts": {
21
+ "version:sync": "node scripts/sync-version.js",
22
+ "version": "pnpm run version:sync && git add cli/Cargo.toml",
23
+ "build:native": "pnpm run version:sync && cargo build --release --manifest-path cli/Cargo.toml && node scripts/copy-native.js",
24
+ "build:linux": "pnpm run version:sync && docker compose -f docker/docker-compose.yml run --rm build-linux",
25
+ "build:macos": "pnpm run version:sync && cargo build --release --manifest-path cli/Cargo.toml --target aarch64-apple-darwin && cp cli/target/aarch64-apple-darwin/release/agent-browser bin/agent-browser-priv-darwin-arm64",
26
+ "build:windows": "pnpm run version:sync && docker compose -f docker/docker-compose.yml run --rm build-windows",
27
+ "build:all-platforms": "pnpm run version:sync && (pnpm run build:linux & pnpm run build:windows & wait) && pnpm run build:macos",
28
+ "build:docker": "docker build --platform linux/amd64 -t agent-browser-builder -f docker/Dockerfile.build .",
29
+ "release": "pnpm run version:sync && pnpm run build:all-platforms",
30
+ "postinstall": "node scripts/postinstall.js",
31
+ "build:dashboard": "cd packages/dashboard && pnpm build"
32
+ },
33
+ "keywords": [
34
+ "browser",
35
+ "automation",
36
+ "headless",
37
+ "chrome",
38
+ "cdp",
39
+ "cli",
40
+ "agent"
41
+ ],
42
+ "license": "Apache-2.0",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/liuwen/agent-browser-priv.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/liuwen/agent-browser-priv/issues"
49
+ },
50
+ "homepage": "https://agent-browser.dev",
51
+ "devDependencies": {}
52
+ }
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Build agent-browser for all platforms using Docker
5
+ # Usage: ./scripts/build-all-platforms.sh
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
9
+ OUTPUT_DIR="$PROJECT_ROOT/bin"
10
+
11
+ # Colors
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ NC='\033[0m' # No Color
16
+
17
+ echo -e "${YELLOW}Building agent-browser for all platforms...${NC}"
18
+ echo ""
19
+
20
+ # Ensure output directory exists
21
+ mkdir -p "$OUTPUT_DIR"
22
+
23
+ # Build the Docker image if needed
24
+ echo -e "${YELLOW}Building Docker cross-compilation image...${NC}"
25
+ docker build --platform linux/amd64 -t agent-browser-builder -f "$PROJECT_ROOT/docker/Dockerfile.build" "$PROJECT_ROOT"
26
+
27
+ # Function to build for a target
28
+ build_target() {
29
+ local rust_target=$1
30
+ local build_target=$2
31
+ local output_name=$3
32
+
33
+ echo -e "${YELLOW}Building for ${build_target}...${NC}"
34
+
35
+ rm -f "$OUTPUT_DIR/$output_name"
36
+
37
+ docker run --rm \
38
+ --platform linux/amd64 \
39
+ -v "$PROJECT_ROOT/cli:/build" \
40
+ -v "$OUTPUT_DIR:/output" \
41
+ agent-browser-builder \
42
+ -c "set -euo pipefail
43
+ cargo zigbuild --release --target ${build_target}
44
+ source_path=/build/target/${rust_target}/release/agent-browser
45
+ if [ -f \"\$source_path.exe\" ]; then
46
+ source_path=\"\$source_path.exe\"
47
+ fi
48
+ cp \"\$source_path\" /output/${output_name}
49
+ chmod +x /output/${output_name} 2>/dev/null || true"
50
+
51
+ if [ -f "$OUTPUT_DIR/$output_name" ]; then
52
+ echo -e "${GREEN}✓ Built ${output_name}${NC}"
53
+ else
54
+ echo -e "${RED}✗ Failed to build ${output_name}${NC}"
55
+ return 1
56
+ fi
57
+ }
58
+
59
+ # Build for each platform
60
+ # Linux x64
61
+ build_target "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-gnu.2.28" "agent-browser-priv-linux-x64"
62
+
63
+ # Linux ARM64
64
+ build_target "aarch64-unknown-linux-gnu" "aarch64-unknown-linux-gnu.2.28" "agent-browser-priv-linux-arm64"
65
+
66
+ # Windows x64
67
+ build_target "x86_64-pc-windows-gnu" "x86_64-pc-windows-gnu" "agent-browser-priv-win32-x64.exe"
68
+
69
+ # macOS ARM64 (via zig for cross-compilation)
70
+ build_target "aarch64-apple-darwin" "aarch64-apple-darwin" "agent-browser-priv-darwin-arm64"
71
+
72
+ echo ""
73
+ echo -e "${GREEN}Build complete!${NC}"
74
+ echo ""
75
+ echo "Binaries are in: $OUTPUT_DIR"
76
+ ls -la "$OUTPUT_DIR"/agent-browser-priv-*
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Verifies that package.json and cli/Cargo.toml have the same version.
5
+ * Used in CI to catch version drift.
6
+ */
7
+
8
+ import { readFileSync } from 'fs';
9
+ import { dirname, join } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const rootDir = join(__dirname, '..');
14
+
15
+ // Read package.json version
16
+ const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8'));
17
+ const packageVersion = packageJson.version;
18
+
19
+ // Read Cargo.toml version
20
+ const cargoToml = readFileSync(join(rootDir, 'cli/Cargo.toml'), 'utf-8');
21
+ const cargoVersionMatch = cargoToml.match(/^version\s*=\s*"([^"]*)"/m);
22
+
23
+ if (!cargoVersionMatch) {
24
+ console.error('Could not find version in cli/Cargo.toml');
25
+ process.exit(1);
26
+ }
27
+
28
+ const cargoVersion = cargoVersionMatch[1];
29
+
30
+ // Read dashboard package.json version
31
+ const dashboardPkg = JSON.parse(readFileSync(join(rootDir, 'packages/dashboard/package.json'), 'utf-8'));
32
+ const dashboardVersion = dashboardPkg.version;
33
+
34
+ const mismatches = [];
35
+ if (packageVersion !== cargoVersion) {
36
+ mismatches.push(` cli/Cargo.toml: ${cargoVersion}`);
37
+ }
38
+ if (packageVersion !== dashboardVersion) {
39
+ mismatches.push(` packages/dashboard: ${dashboardVersion}`);
40
+ }
41
+
42
+ if (mismatches.length > 0) {
43
+ console.error('Version mismatch detected!');
44
+ console.error(` package.json: ${packageVersion}`);
45
+ for (const m of mismatches) console.error(m);
46
+ console.error('');
47
+ console.error("Run 'pnpm run version:sync' to fix this.");
48
+ process.exit(1);
49
+ }
50
+
51
+ console.log(`Versions are in sync: ${packageVersion}`);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Copies the compiled Rust binary to bin/ with platform-specific naming
5
+ */
6
+
7
+ import { copyFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { dirname, join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { platform, arch } from 'os';
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const projectRoot = join(__dirname, '..');
14
+
15
+ const sourceExt = platform() === 'win32' ? '.exe' : '';
16
+ const sourcePath = join(projectRoot, `cli/target/release/agent-browser${sourceExt}`);
17
+ const binDir = join(projectRoot, 'bin');
18
+
19
+ // Determine platform suffix
20
+ const platformKey = `${platform()}-${arch()}`;
21
+ const ext = platform() === 'win32' ? '.exe' : '';
22
+ const targetName = `agent-browser-priv-${platformKey}${ext}`;
23
+ const targetPath = join(binDir, targetName);
24
+
25
+ if (!existsSync(sourcePath)) {
26
+ console.error(`Error: Native binary not found at ${sourcePath}`);
27
+ console.error('Run "cargo build --release --manifest-path cli/Cargo.toml" first');
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!existsSync(binDir)) {
32
+ mkdirSync(binDir, { recursive: true });
33
+ }
34
+
35
+ copyFileSync(sourcePath, targetPath);
36
+ console.log(`✓ Copied native binary to ${targetPath}`);
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script for agent-browser-priv
5
+ *
6
+ * Downloads the platform-specific native binary if not present.
7
+ * On global installs, patches npm's bin entry to use the native binary directly:
8
+ * - Windows: Overwrites .cmd/.ps1 shims
9
+ * - Mac/Linux: Replaces symlink to point to native binary
10
+ */
11
+
12
+ import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync, writeFileSync, symlinkSync, lstatSync } from 'fs';
13
+ import { dirname, join } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import { platform, arch } from 'os';
16
+ import { get } from 'https';
17
+ import { execSync } from 'child_process';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const projectRoot = join(__dirname, '..');
21
+ const binDir = join(projectRoot, 'bin');
22
+
23
+ // Detect if the system uses musl libc (e.g. Alpine Linux)
24
+ function isMusl() {
25
+ if (platform() !== 'linux') return false;
26
+ try {
27
+ const result = execSync('ldd --version 2>&1 || true', { encoding: 'utf8' });
28
+ return result.toLowerCase().includes('musl');
29
+ } catch {
30
+ return existsSync('/lib/ld-musl-x86_64.so.1') || existsSync('/lib/ld-musl-aarch64.so.1');
31
+ }
32
+ }
33
+
34
+ if (platform() === 'linux' && isMusl()) {
35
+ console.log('agent-browser-priv does not publish musl Linux binaries.');
36
+ console.log('Use a glibc-based Linux environment or build locally with npm run build:native.');
37
+ process.exit(0);
38
+ }
39
+
40
+ // Platform detection
41
+ const osKey = platform();
42
+ // Windows ARM64 falls back to x64 binary (no native ARM64 build available).
43
+ // x64 binaries run via Windows' built-in emulation on ARM64.
44
+ const effectiveArch = platform() === 'win32' && arch() === 'arm64' ? 'x64' : arch();
45
+ const platformKey = `${osKey}-${effectiveArch}`;
46
+ const ext = platform() === 'win32' ? '.exe' : '';
47
+ const binaryName = `agent-browser-priv-${platformKey}${ext}`;
48
+ const binaryPath = join(binDir, binaryName);
49
+
50
+ // Package info
51
+ const packageJson = JSON.parse(
52
+ (await import('fs')).readFileSync(join(projectRoot, 'package.json'), 'utf8')
53
+ );
54
+ const version = packageJson.version;
55
+
56
+ // GitHub release URL
57
+ const GITHUB_REPO = 'liuwen/agent-browser-priv';
58
+ const DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${binaryName}`;
59
+
60
+ async function downloadFile(url, dest) {
61
+ return new Promise((resolve, reject) => {
62
+ const file = createWriteStream(dest);
63
+
64
+ const request = (url) => {
65
+ get(url, (response) => {
66
+ // Handle redirects
67
+ if (response.statusCode === 301 || response.statusCode === 302) {
68
+ request(response.headers.location);
69
+ return;
70
+ }
71
+
72
+ if (response.statusCode !== 200) {
73
+ reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
74
+ return;
75
+ }
76
+
77
+ response.pipe(file);
78
+ file.on('finish', () => {
79
+ file.close();
80
+ resolve();
81
+ });
82
+ }).on('error', (err) => {
83
+ unlinkSync(dest);
84
+ reject(err);
85
+ });
86
+ };
87
+
88
+ request(url);
89
+ });
90
+ }
91
+
92
+ /**
93
+ * Detect which package manager ran this postinstall and write a marker file
94
+ * next to the binary so `agent-browser-priv upgrade` can use the correct one
95
+ * without fragile path heuristics or slow subprocess probing.
96
+ *
97
+ * npm_config_user_agent is set by npm/pnpm/yarn/bun during lifecycle scripts,
98
+ * e.g. "pnpm/8.10.0 node/v20.10.0 linux x64"
99
+ */
100
+ function writeInstallMethod() {
101
+ const ua = process.env.npm_config_user_agent || '';
102
+ let method = '';
103
+ if (ua.startsWith('pnpm/')) method = 'pnpm';
104
+ else if (ua.startsWith('yarn/')) method = 'yarn';
105
+ else if (ua.startsWith('bun/')) method = 'bun';
106
+ else if (ua.startsWith('npm/')) method = 'npm';
107
+
108
+ if (method) {
109
+ try {
110
+ writeFileSync(join(binDir, '.install-method'), method);
111
+ } catch {
112
+ // Non-critical — upgrade will fall back to heuristics
113
+ }
114
+ }
115
+ }
116
+
117
+ async function main() {
118
+ // Check if binary already exists
119
+ if (existsSync(binaryPath)) {
120
+ // Ensure binary is executable (npm doesn't preserve execute bit)
121
+ if (platform() !== 'win32') {
122
+ chmodSync(binaryPath, 0o755);
123
+ }
124
+ console.log(`✓ Native binary ready: ${binaryName}`);
125
+
126
+ writeInstallMethod();
127
+
128
+ // On global installs, fix npm's bin entry to use native binary directly
129
+ await fixGlobalInstallBin();
130
+
131
+ showInstallReminder();
132
+ return;
133
+ }
134
+
135
+ // Ensure bin directory exists
136
+ if (!existsSync(binDir)) {
137
+ mkdirSync(binDir, { recursive: true });
138
+ }
139
+
140
+ console.log(`Downloading native binary for ${platformKey}...`);
141
+ if (platform() === 'win32' && arch() === 'arm64') {
142
+ console.log(` Note: Using x64 binary on ARM64 Windows (runs via emulation)`);
143
+ }
144
+ console.log(`URL: ${DOWNLOAD_URL}`);
145
+
146
+ try {
147
+ await downloadFile(DOWNLOAD_URL, binaryPath);
148
+
149
+ // Make executable on Unix
150
+ if (platform() !== 'win32') {
151
+ chmodSync(binaryPath, 0o755);
152
+ }
153
+
154
+ console.log(`✓ Downloaded native binary: ${binaryName}`);
155
+ } catch (err) {
156
+ console.log(`Could not download native binary: ${err.message}`);
157
+ console.log('');
158
+ console.log('To build the native binary locally:');
159
+ console.log(' 1. Install Rust: https://rustup.rs');
160
+ console.log(' 2. Run: npm run build:native');
161
+ }
162
+
163
+ writeInstallMethod();
164
+
165
+ // On global installs, fix npm's bin entry to use native binary directly
166
+ // This avoids the /bin/sh error on Windows and provides zero-overhead execution
167
+ await fixGlobalInstallBin();
168
+
169
+ showInstallReminder();
170
+ }
171
+
172
+ function findSystemChrome() {
173
+ const os = platform();
174
+ if (os === 'darwin') {
175
+ const candidates = [
176
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
177
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
178
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
179
+ ];
180
+ return candidates.find(p => existsSync(p)) || null;
181
+ }
182
+ if (os === 'linux') {
183
+ const names = ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium'];
184
+ for (const name of names) {
185
+ try {
186
+ const result = execSync(`which ${name} 2>/dev/null`, { encoding: 'utf8' }).trim();
187
+ if (result) return result;
188
+ } catch {}
189
+ }
190
+ return null;
191
+ }
192
+ if (os === 'win32') {
193
+ const candidates = [
194
+ `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
195
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
196
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
197
+ ];
198
+ return candidates.find(p => p && existsSync(p)) || null;
199
+ }
200
+ return null;
201
+ }
202
+
203
+ function showInstallReminder() {
204
+ const systemChrome = findSystemChrome();
205
+ if (systemChrome) {
206
+ console.log('');
207
+ console.log(` ✓ System Chrome found: ${systemChrome}`);
208
+ console.log(' agent-browser-priv will use it automatically.');
209
+ console.log('');
210
+ return;
211
+ }
212
+
213
+ console.log('');
214
+ console.log(' ⚠ No Chrome installation detected.');
215
+ console.log(' If you plan to use a local browser, run:');
216
+ console.log('');
217
+ console.log(' agent-browser-priv install');
218
+ if (platform() === 'linux') {
219
+ console.log('');
220
+ console.log(' On Linux, include system dependencies with:');
221
+ console.log('');
222
+ console.log(' agent-browser-priv install --with-deps');
223
+ }
224
+ console.log('');
225
+ console.log(' You can skip this if you use --cdp, --provider, --engine, or --executable-path.');
226
+ console.log('');
227
+ }
228
+
229
+ /**
230
+ * Fix npm's bin entry on global installs to use the native binary directly.
231
+ * This provides zero-overhead CLI execution for global installs.
232
+ */
233
+ async function fixGlobalInstallBin() {
234
+ if (platform() === 'win32') {
235
+ await fixWindowsShims();
236
+ } else {
237
+ await fixUnixSymlink();
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Fix npm symlink on Mac/Linux global installs.
243
+ * Replace the symlink to the JS wrapper with a symlink to the native binary.
244
+ */
245
+ async function fixUnixSymlink() {
246
+ // Get npm's global bin directory (npm prefix -g + /bin)
247
+ let npmBinDir;
248
+ try {
249
+ const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
250
+ npmBinDir = join(prefix, 'bin');
251
+ } catch {
252
+ return; // npm not available
253
+ }
254
+
255
+ const symlinkPath = join(npmBinDir, 'agent-browser-priv');
256
+
257
+ // Check if symlink exists (indicates global install)
258
+ try {
259
+ const stat = lstatSync(symlinkPath);
260
+ if (!stat.isSymbolicLink()) {
261
+ return; // Not a symlink, don't touch it
262
+ }
263
+ } catch {
264
+ return; // Symlink doesn't exist, not a global install
265
+ }
266
+
267
+ // Replace symlink to point directly to native binary
268
+ try {
269
+ unlinkSync(symlinkPath);
270
+ symlinkSync(binaryPath, symlinkPath);
271
+ console.log('✓ Optimized: symlink points to native binary (zero overhead)');
272
+ } catch (err) {
273
+ // Permission error or other issue - not critical, JS wrapper still works
274
+ console.log(`⚠ Could not optimize symlink: ${err.message}`);
275
+ console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Fix npm-generated shims on Windows global installs.
281
+ * npm generates shims that try to run /bin/sh, which doesn't exist on Windows.
282
+ * We overwrite them to invoke the native .exe directly.
283
+ */
284
+ async function fixWindowsShims() {
285
+ let npmBinDir;
286
+ try {
287
+ npmBinDir = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
288
+ } catch {
289
+ return;
290
+ }
291
+
292
+ const cmdShim = join(npmBinDir, 'agent-browser-priv.cmd');
293
+ const ps1Shim = join(npmBinDir, 'agent-browser-priv.ps1');
294
+
295
+ // Shims may not exist yet during postinstall (npm creates them after
296
+ // lifecycle scripts). If missing, fall back: the JS wrapper at
297
+ // bin/agent-browser.js handles Windows correctly via child_process.spawn.
298
+ if (!existsSync(cmdShim)) {
299
+ return;
300
+ }
301
+
302
+ // Detect architecture so ARM64 Windows is handled correctly
303
+ // (falls back to x64 binary — see platform detection above)
304
+ const cpuArch = effectiveArch;
305
+ const relativeBinaryPath = `node_modules\\agent-browser-priv\\bin\\agent-browser-priv-win32-${cpuArch}.exe`;
306
+ const absoluteBinaryPath = join(npmBinDir, relativeBinaryPath);
307
+
308
+ // Only rewrite shims if the native binary actually exists
309
+ if (!existsSync(absoluteBinaryPath)) {
310
+ return;
311
+ }
312
+
313
+ try {
314
+ const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
315
+ writeFileSync(cmdShim, cmdContent);
316
+
317
+ const ps1Content = `#!/usr/bin/env pwsh\r\n$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent\r\n& "$basedir\\${relativeBinaryPath}" $args\r\nexit $LASTEXITCODE\r\n`;
318
+ writeFileSync(ps1Shim, ps1Content);
319
+
320
+ console.log('✓ Optimized: shims point to native binary (zero overhead)');
321
+ } catch (err) {
322
+ console.log(`⚠ Could not optimize shims: ${err.message}`);
323
+ console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
324
+ }
325
+ }
326
+
327
+ main().catch(console.error);