agent-browser-stealth 0.17.0-fork.2 → 0.24.0-fork.2
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.
- package/README.md +1256 -240
- package/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/bin/agent-browser-win32-x64.exe +0 -0
- package/bin/agent-browser.js +13 -2
- package/extensions/tab-group-cdp/content-script.js +425 -0
- package/extensions/tab-group-cdp/icons/icon.svg +7 -0
- package/extensions/tab-group-cdp/manifest.json +34 -0
- package/extensions/tab-group-cdp/page-bridge.js +133 -0
- package/extensions/tab-group-cdp/service-worker.js +2249 -0
- package/extensions/tab-group-cdp/sidepanel.css +258 -0
- package/extensions/tab-group-cdp/sidepanel.html +28 -0
- package/extensions/tab-group-cdp/sidepanel.js +1225 -0
- package/package.json +17 -69
- package/scripts/build-all-platforms.sh +6 -0
- package/scripts/check-version-sync.js +14 -2
- package/scripts/copy-native.js +8 -50
- package/scripts/postinstall.js +149 -165
- package/scripts/windows-debug/provision.sh +220 -0
- package/scripts/windows-debug/run.sh +92 -0
- package/scripts/windows-debug/start.sh +43 -0
- package/scripts/windows-debug/stop.sh +28 -0
- package/scripts/windows-debug/sync.sh +27 -0
- package/skills/agent-browser/SKILL.md +256 -159
- package/skills/agent-browser/references/authentication.md +101 -0
- package/skills/agent-browser/references/commands.md +34 -2
- package/skills/agent-browser/references/snapshot-refs.md +25 -0
- package/skills/agentcore/SKILL.md +115 -0
- package/skills/dogfood/SKILL.md +4 -2
- package/skills/electron/SKILL.md +26 -2
- package/skills/slack/SKILL.md +0 -9
- package/skills/slack/references/slack-tasks.md +2 -8
- package/skills/vercel-sandbox/SKILL.md +280 -0
- package/bin/agent-browser-local +0 -0
- package/bin/agent-browser-stealth +0 -0
- package/bin/agent-browser-stealth.d +0 -1
- package/dist/action-policy.d.ts +0 -14
- package/dist/action-policy.d.ts.map +0 -1
- package/dist/action-policy.js +0 -253
- package/dist/action-policy.js.map +0 -1
- package/dist/actions.d.ts +0 -21
- package/dist/actions.d.ts.map +0 -1
- package/dist/actions.js +0 -2139
- package/dist/actions.js.map +0 -1
- package/dist/auth-cli.d.ts +0 -2
- package/dist/auth-cli.d.ts.map +0 -1
- package/dist/auth-cli.js +0 -97
- package/dist/auth-cli.js.map +0 -1
- package/dist/auth-vault.d.ts +0 -36
- package/dist/auth-vault.d.ts.map +0 -1
- package/dist/auth-vault.js +0 -125
- package/dist/auth-vault.js.map +0 -1
- package/dist/browser.d.ts +0 -665
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -3210
- package/dist/browser.js.map +0 -1
- package/dist/confirmation.d.ts +0 -8
- package/dist/confirmation.d.ts.map +0 -1
- package/dist/confirmation.js +0 -30
- package/dist/confirmation.js.map +0 -1
- package/dist/daemon.d.ts +0 -78
- package/dist/daemon.d.ts.map +0 -1
- package/dist/daemon.js +0 -744
- package/dist/daemon.js.map +0 -1
- package/dist/diff.d.ts +0 -18
- package/dist/diff.d.ts.map +0 -1
- package/dist/diff.js +0 -271
- package/dist/diff.js.map +0 -1
- package/dist/domain-filter.d.ts +0 -28
- package/dist/domain-filter.d.ts.map +0 -1
- package/dist/domain-filter.js +0 -149
- package/dist/domain-filter.js.map +0 -1
- package/dist/encryption.d.ts +0 -73
- package/dist/encryption.d.ts.map +0 -1
- package/dist/encryption.js +0 -171
- package/dist/encryption.js.map +0 -1
- package/dist/ios-actions.d.ts +0 -11
- package/dist/ios-actions.d.ts.map +0 -1
- package/dist/ios-actions.js +0 -228
- package/dist/ios-actions.js.map +0 -1
- package/dist/ios-manager.d.ts +0 -266
- package/dist/ios-manager.d.ts.map +0 -1
- package/dist/ios-manager.js +0 -1073
- package/dist/ios-manager.js.map +0 -1
- package/dist/protocol.d.ts +0 -26
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -990
- package/dist/protocol.js.map +0 -1
- package/dist/snapshot.d.ts +0 -67
- package/dist/snapshot.d.ts.map +0 -1
- package/dist/snapshot.js +0 -514
- package/dist/snapshot.js.map +0 -1
- package/dist/state-utils.d.ts +0 -77
- package/dist/state-utils.d.ts.map +0 -1
- package/dist/state-utils.js +0 -178
- package/dist/state-utils.js.map +0 -1
- package/dist/stealth.d.ts +0 -41
- package/dist/stealth.d.ts.map +0 -1
- package/dist/stealth.js +0 -1743
- package/dist/stealth.js.map +0 -1
- package/dist/stream-server.d.ts +0 -117
- package/dist/stream-server.d.ts.map +0 -1
- package/dist/stream-server.js +0 -309
- package/dist/stream-server.js.map +0 -1
- package/dist/types.d.ts +0 -973
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/scripts/check-creepjs-headless.js +0 -137
- package/scripts/check-daemon-pid-recovery.js +0 -148
- package/scripts/check-sannysoft-webdriver.js +0 -112
- package/scripts/check-stealth-regression.js +0 -199
- package/scripts/check-turnstile-testkey.ts +0 -125
- package/scripts/clawhub-sync.sh +0 -27
- package/scripts/sync-upstream.sh +0 -142
- package/scripts/verify-bundled-binaries.js +0 -71
- package/scripts/verify-native-version.js +0 -48
- package/scripts/verify-packed-host-binary.js +0 -88
- package/scripts/verify-registry-host-binary.js +0 -120
- package/skills/agent-browser-stealth/SKILL.md +0 -127
package/scripts/postinstall.js
CHANGED
|
@@ -2,80 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Postinstall script for agent-browser
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Downloads the platform-specific native binary if not present.
|
|
7
7
|
* On global installs, patches npm's bin entry to use the native binary directly:
|
|
8
8
|
* - Windows: Overwrites .cmd/.ps1 shims
|
|
9
9
|
* - Mac/Linux: Replaces symlink to point to native binary
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
existsSync,
|
|
14
|
-
mkdirSync,
|
|
15
|
-
chmodSync,
|
|
16
|
-
createWriteStream,
|
|
17
|
-
unlinkSync,
|
|
18
|
-
writeFileSync,
|
|
19
|
-
symlinkSync,
|
|
20
|
-
lstatSync,
|
|
21
|
-
readFileSync,
|
|
22
|
-
} from 'fs';
|
|
12
|
+
import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync, writeFileSync, symlinkSync, lstatSync } from 'fs';
|
|
23
13
|
import { dirname, join } from 'path';
|
|
24
14
|
import { fileURLToPath } from 'url';
|
|
25
15
|
import { platform, arch } from 'os';
|
|
26
16
|
import { get } from 'https';
|
|
27
|
-
import {
|
|
17
|
+
import { execSync } from 'child_process';
|
|
28
18
|
|
|
29
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
20
|
const projectRoot = join(__dirname, '..');
|
|
31
21
|
const binDir = join(projectRoot, 'bin');
|
|
32
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
|
+
|
|
33
34
|
// Platform detection
|
|
34
|
-
const
|
|
35
|
+
const osKey = platform() === 'linux' && isMusl() ? 'linux-musl' : platform();
|
|
36
|
+
const platformKey = `${osKey}-${arch()}`;
|
|
35
37
|
const ext = platform() === 'win32' ? '.exe' : '';
|
|
36
38
|
const binaryName = `agent-browser-${platformKey}${ext}`;
|
|
37
39
|
const binaryPath = join(binDir, binaryName);
|
|
38
40
|
|
|
39
41
|
// Package info
|
|
40
|
-
const packageJson = JSON.parse(
|
|
42
|
+
const packageJson = JSON.parse(
|
|
43
|
+
(await import('fs')).readFileSync(join(projectRoot, 'package.json'), 'utf8')
|
|
44
|
+
);
|
|
41
45
|
const version = packageJson.version;
|
|
42
|
-
const packageName = packageJson.name;
|
|
43
|
-
const binCommands = getBinCommands(packageJson);
|
|
44
46
|
|
|
45
47
|
// GitHub release URL
|
|
46
|
-
const GITHUB_REPO =
|
|
48
|
+
const GITHUB_REPO = 'vercel-labs/agent-browser';
|
|
47
49
|
const DOWNLOAD_URL = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${binaryName}`;
|
|
48
50
|
|
|
49
|
-
function getGitHubRepoFromPackage(pkg) {
|
|
50
|
-
const repo = pkg?.repository;
|
|
51
|
-
const repoUrl = typeof repo === 'string' ? repo : repo?.url;
|
|
52
|
-
|
|
53
|
-
if (typeof repoUrl === 'string') {
|
|
54
|
-
const match = repoUrl.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
55
|
-
if (match?.[1]) {
|
|
56
|
-
return match[1];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Fallback for legacy package metadata
|
|
61
|
-
return 'vercel-labs/agent-browser';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function getBinCommands(pkg) {
|
|
65
|
-
const bin = pkg?.bin;
|
|
66
|
-
if (typeof bin === 'string') {
|
|
67
|
-
return [pkg.name.replace(/^@[^/]+\//, '')];
|
|
68
|
-
}
|
|
69
|
-
if (bin && typeof bin === 'object') {
|
|
70
|
-
return Object.keys(bin);
|
|
71
|
-
}
|
|
72
|
-
return ['agent-browser'];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
51
|
async function downloadFile(url, dest) {
|
|
76
52
|
return new Promise((resolve, reject) => {
|
|
77
53
|
const file = createWriteStream(dest);
|
|
78
|
-
|
|
54
|
+
|
|
79
55
|
const request = (url) => {
|
|
80
56
|
get(url, (response) => {
|
|
81
57
|
// Handle redirects
|
|
@@ -83,12 +59,12 @@ async function downloadFile(url, dest) {
|
|
|
83
59
|
request(response.headers.location);
|
|
84
60
|
return;
|
|
85
61
|
}
|
|
86
|
-
|
|
62
|
+
|
|
87
63
|
if (response.statusCode !== 200) {
|
|
88
64
|
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
89
65
|
return;
|
|
90
66
|
}
|
|
91
|
-
|
|
67
|
+
|
|
92
68
|
response.pipe(file);
|
|
93
69
|
file.on('finish', () => {
|
|
94
70
|
file.close();
|
|
@@ -99,11 +75,36 @@ async function downloadFile(url, dest) {
|
|
|
99
75
|
reject(err);
|
|
100
76
|
});
|
|
101
77
|
};
|
|
102
|
-
|
|
78
|
+
|
|
103
79
|
request(url);
|
|
104
80
|
});
|
|
105
81
|
}
|
|
106
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Detect which package manager ran this postinstall and write a marker file
|
|
85
|
+
* next to the binary so `agent-browser upgrade` can use the correct one
|
|
86
|
+
* without fragile path heuristics or slow subprocess probing.
|
|
87
|
+
*
|
|
88
|
+
* npm_config_user_agent is set by npm/pnpm/yarn/bun during lifecycle scripts,
|
|
89
|
+
* e.g. "pnpm/8.10.0 node/v20.10.0 linux x64"
|
|
90
|
+
*/
|
|
91
|
+
function writeInstallMethod() {
|
|
92
|
+
const ua = process.env.npm_config_user_agent || '';
|
|
93
|
+
let method = '';
|
|
94
|
+
if (ua.startsWith('pnpm/')) method = 'pnpm';
|
|
95
|
+
else if (ua.startsWith('yarn/')) method = 'yarn';
|
|
96
|
+
else if (ua.startsWith('bun/')) method = 'bun';
|
|
97
|
+
else if (ua.startsWith('npm/')) method = 'npm';
|
|
98
|
+
|
|
99
|
+
if (method) {
|
|
100
|
+
try {
|
|
101
|
+
writeFileSync(join(binDir, '.install-method'), method);
|
|
102
|
+
} catch {
|
|
103
|
+
// Non-critical — upgrade will fall back to heuristics
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
107
108
|
async function main() {
|
|
108
109
|
// Check if binary already exists
|
|
109
110
|
if (existsSync(binaryPath)) {
|
|
@@ -111,21 +112,15 @@ async function main() {
|
|
|
111
112
|
if (platform() !== 'win32') {
|
|
112
113
|
chmodSync(binaryPath, 0o755);
|
|
113
114
|
}
|
|
114
|
-
|
|
115
|
-
if (installedVersion.includes(version)) {
|
|
116
|
-
console.log(`✓ Native binary ready: ${binaryName}`);
|
|
115
|
+
console.log(`✓ Native binary ready: ${binaryName}`);
|
|
117
116
|
|
|
118
|
-
|
|
119
|
-
await fixGlobalInstallBin();
|
|
117
|
+
writeInstallMethod();
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
119
|
+
// On global installs, fix npm's bin entry to use native binary directly
|
|
120
|
+
await fixGlobalInstallBin();
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
128
|
-
console.log(` Re-downloading ${binaryName} from release assets...`);
|
|
122
|
+
showInstallReminder();
|
|
123
|
+
return;
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
// Ensure bin directory exists
|
|
@@ -144,72 +139,86 @@ async function main() {
|
|
|
144
139
|
chmodSync(binaryPath, 0o755);
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
const downloadedVersion = readBinaryVersion(binaryPath);
|
|
148
|
-
if (!downloadedVersion.includes(version)) {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`downloaded binary version mismatch: expected ${version}, got "${downloadedVersion || 'unknown'}"`
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
142
|
console.log(`✓ Downloaded native binary: ${binaryName}`);
|
|
155
143
|
} catch (err) {
|
|
156
|
-
console.log(
|
|
157
|
-
console.log(` The CLI will use Node.js fallback (slightly slower startup)`);
|
|
144
|
+
console.log(`Could not download native binary: ${err.message}`);
|
|
158
145
|
console.log('');
|
|
159
146
|
console.log('To build the native binary locally:');
|
|
160
147
|
console.log(' 1. Install Rust: https://rustup.rs');
|
|
161
|
-
console.log(' 2. Run:
|
|
148
|
+
console.log(' 2. Run: npm run build:native');
|
|
162
149
|
}
|
|
163
150
|
|
|
151
|
+
writeInstallMethod();
|
|
152
|
+
|
|
164
153
|
// On global installs, fix npm's bin entry to use native binary directly
|
|
165
154
|
// This avoids the /bin/sh error on Windows and provides zero-overhead execution
|
|
166
155
|
await fixGlobalInstallBin();
|
|
167
156
|
|
|
168
|
-
|
|
157
|
+
showInstallReminder();
|
|
169
158
|
}
|
|
170
159
|
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
160
|
+
function findSystemChrome() {
|
|
161
|
+
const os = platform();
|
|
162
|
+
if (os === 'darwin') {
|
|
163
|
+
const candidates = [
|
|
164
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
165
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
166
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
167
|
+
];
|
|
168
|
+
return candidates.find(p => existsSync(p)) || null;
|
|
169
|
+
}
|
|
170
|
+
if (os === 'linux') {
|
|
171
|
+
const names = ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium'];
|
|
172
|
+
for (const name of names) {
|
|
173
|
+
try {
|
|
174
|
+
const result = execSync(`which ${name} 2>/dev/null`, { encoding: 'utf8' }).trim();
|
|
175
|
+
if (result) return result;
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
179
|
}
|
|
180
|
+
if (os === 'win32') {
|
|
181
|
+
const candidates = [
|
|
182
|
+
`${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
183
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
184
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
185
|
+
];
|
|
186
|
+
return candidates.find(p => p && existsSync(p)) || null;
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
180
189
|
}
|
|
181
190
|
|
|
182
|
-
function
|
|
191
|
+
function showInstallReminder() {
|
|
192
|
+
const systemChrome = findSystemChrome();
|
|
193
|
+
if (systemChrome) {
|
|
194
|
+
console.log('');
|
|
195
|
+
console.log(` ✓ System Chrome found: ${systemChrome}`);
|
|
196
|
+
console.log(' agent-browser will use it automatically.');
|
|
197
|
+
console.log('');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log('');
|
|
202
|
+
console.log(' ⚠ No Chrome installation detected.');
|
|
203
|
+
console.log(' If you plan to use a local browser, run:');
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(' agent-browser install');
|
|
206
|
+
if (platform() === 'linux') {
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(' On Linux, include system dependencies with:');
|
|
209
|
+
console.log('');
|
|
210
|
+
console.log(' agent-browser install --with-deps');
|
|
211
|
+
}
|
|
212
|
+
console.log('');
|
|
213
|
+
console.log(' You can skip this if you use --cdp, --provider, --engine, or --executable-path.');
|
|
183
214
|
console.log('');
|
|
184
|
-
console.log('╔═══════════════════════════════════════════════════════════════════════════╗');
|
|
185
|
-
console.log('║ To download browser binaries, run: ║');
|
|
186
|
-
console.log('║ ║');
|
|
187
|
-
console.log('║ npx playwright install chromium ║');
|
|
188
|
-
console.log('║ ║');
|
|
189
|
-
console.log('║ On Linux, include system dependencies with: ║');
|
|
190
|
-
console.log('║ ║');
|
|
191
|
-
console.log('║ npx playwright install --with-deps chromium ║');
|
|
192
|
-
console.log('║ ║');
|
|
193
|
-
console.log('╚═══════════════════════════════════════════════════════════════════════════╝');
|
|
194
215
|
}
|
|
195
216
|
|
|
196
217
|
/**
|
|
197
218
|
* Fix npm's bin entry on global installs to use the native binary directly.
|
|
198
219
|
* This provides zero-overhead CLI execution for global installs.
|
|
199
220
|
*/
|
|
200
|
-
function isPnpmGlobalInstall() {
|
|
201
|
-
const ua = process.env.npm_config_user_agent || '';
|
|
202
|
-
return ua.includes('pnpm/');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
221
|
async function fixGlobalInstallBin() {
|
|
206
|
-
// pnpm already manages global shims in its own bin dir.
|
|
207
|
-
// Rewriting links via `npm prefix -g` can create stale links in unrelated paths
|
|
208
|
-
// (e.g. /opt/homebrew/bin), which then shadow pnpm's up-to-date shims.
|
|
209
|
-
if (isPnpmGlobalInstall()) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
222
|
if (platform() === 'win32') {
|
|
214
223
|
await fixWindowsShims();
|
|
215
224
|
} else {
|
|
@@ -231,34 +240,27 @@ async function fixUnixSymlink() {
|
|
|
231
240
|
return; // npm not available
|
|
232
241
|
}
|
|
233
242
|
|
|
234
|
-
|
|
235
|
-
for (const commandName of binCommands) {
|
|
236
|
-
const symlinkPath = join(npmBinDir, commandName);
|
|
237
|
-
|
|
238
|
-
// Check if symlink exists (indicates global install)
|
|
239
|
-
try {
|
|
240
|
-
const stat = lstatSync(symlinkPath);
|
|
241
|
-
if (!stat.isSymbolicLink()) {
|
|
242
|
-
continue; // Not a symlink, don't touch it
|
|
243
|
-
}
|
|
244
|
-
} catch {
|
|
245
|
-
continue; // Symlink doesn't exist, not a global install
|
|
246
|
-
}
|
|
243
|
+
const symlinkPath = join(npmBinDir, 'agent-browser');
|
|
247
244
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} catch (err) {
|
|
254
|
-
// Permission error or other issue - not critical, JS wrapper still works
|
|
255
|
-
console.log(`⚠ Could not optimize symlink (${commandName}): ${err.message}`);
|
|
256
|
-
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
245
|
+
// Check if symlink exists (indicates global install)
|
|
246
|
+
try {
|
|
247
|
+
const stat = lstatSync(symlinkPath);
|
|
248
|
+
if (!stat.isSymbolicLink()) {
|
|
249
|
+
return; // Not a symlink, don't touch it
|
|
257
250
|
}
|
|
251
|
+
} catch {
|
|
252
|
+
return; // Symlink doesn't exist, not a global install
|
|
258
253
|
}
|
|
259
254
|
|
|
260
|
-
|
|
255
|
+
// Replace symlink to point directly to native binary
|
|
256
|
+
try {
|
|
257
|
+
unlinkSync(symlinkPath);
|
|
258
|
+
symlinkSync(binaryPath, symlinkPath);
|
|
261
259
|
console.log('✓ Optimized: symlink points to native binary (zero overhead)');
|
|
260
|
+
} catch (err) {
|
|
261
|
+
// Permission error or other issue - not critical, JS wrapper still works
|
|
262
|
+
console.log(`⚠ Could not optimize symlink: ${err.message}`);
|
|
263
|
+
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
|
|
@@ -275,55 +277,37 @@ async function fixWindowsShims() {
|
|
|
275
277
|
return;
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
|
|
279
|
-
const
|
|
280
|
-
const relativeBinaryPath = `node_modules\\${packagePath}\\bin\\${binaryName}`;
|
|
281
|
-
const absoluteBinaryPath = join(npmBinDir, relativeBinaryPath);
|
|
280
|
+
const cmdShim = join(npmBinDir, 'agent-browser.cmd');
|
|
281
|
+
const ps1Shim = join(npmBinDir, 'agent-browser.ps1');
|
|
282
282
|
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
|
|
283
|
+
// Shims may not exist yet during postinstall (npm creates them after
|
|
284
|
+
// lifecycle scripts). If missing, fall back: the JS wrapper at
|
|
285
|
+
// bin/agent-browser.js handles Windows correctly via child_process.spawn.
|
|
286
|
+
if (!existsSync(cmdShim)) {
|
|
286
287
|
return;
|
|
287
288
|
}
|
|
288
289
|
|
|
289
|
-
|
|
290
|
+
// Detect architecture so ARM64 Windows is handled correctly
|
|
291
|
+
const cpuArch = arch() === 'arm64' ? 'arm64' : 'x64';
|
|
292
|
+
const relativeBinaryPath = `node_modules\\agent-browser\\bin\\agent-browser-win32-${cpuArch}.exe`;
|
|
293
|
+
const absoluteBinaryPath = join(npmBinDir, relativeBinaryPath);
|
|
290
294
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
// Only rewrite shims if the native binary actually exists
|
|
296
|
+
if (!existsSync(absoluteBinaryPath)) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
295
299
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
+
try {
|
|
301
|
+
const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
|
|
302
|
+
writeFileSync(cmdShim, cmdContent);
|
|
300
303
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
|
|
304
|
-
writeFileSync(cmdShim, cmdContent);
|
|
305
|
-
|
|
306
|
-
// Overwrite .ps1 shim
|
|
307
|
-
const ps1Content = `#!/usr/bin/env pwsh
|
|
308
|
-
$basedir = Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
309
|
-
$exe = ""
|
|
310
|
-
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
311
|
-
$exe = ".exe"
|
|
312
|
-
}
|
|
313
|
-
& "$basedir/${relativeBinaryPath.replace(/\\/g, '/')}" $args
|
|
314
|
-
exit $LASTEXITCODE
|
|
315
|
-
`;
|
|
316
|
-
writeFileSync(ps1Shim, ps1Content);
|
|
317
|
-
optimized = true;
|
|
318
|
-
} catch (err) {
|
|
319
|
-
// Permission error or other issue - not critical, JS wrapper still works
|
|
320
|
-
console.log(`⚠ Could not optimize shims (${commandName}): ${err.message}`);
|
|
321
|
-
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
322
|
-
}
|
|
323
|
-
}
|
|
304
|
+
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`;
|
|
305
|
+
writeFileSync(ps1Shim, ps1Content);
|
|
324
306
|
|
|
325
|
-
if (optimized) {
|
|
326
307
|
console.log('✓ Optimized: shims point to native binary (zero overhead)');
|
|
308
|
+
} catch (err) {
|
|
309
|
+
console.log(`⚠ Could not optimize shims: ${err.message}`);
|
|
310
|
+
console.log(' CLI will work via Node.js wrapper (slightly slower startup)');
|
|
327
311
|
}
|
|
328
312
|
}
|
|
329
313
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
5
|
+
INSTANCE_FILE="$SCRIPT_DIR/.instance"
|
|
6
|
+
NAME_PREFIX="agent-browser-debug"
|
|
7
|
+
INSTANCE_TYPE="${INSTANCE_TYPE:-t3.xlarge}"
|
|
8
|
+
|
|
9
|
+
if [[ -f "$INSTANCE_FILE" ]]; then
|
|
10
|
+
echo "Error: Instance already provisioned. See $INSTANCE_FILE"
|
|
11
|
+
echo "Run ./scripts/windows-debug/start.sh to start it, or delete .instance to re-provision."
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
REGION=$(aws configure get region 2>/dev/null || echo "")
|
|
16
|
+
if [[ -z "$REGION" ]]; then
|
|
17
|
+
echo "Error: No AWS region configured. Run: aws configure set region us-east-1"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "Provisioning Windows debug instance in $REGION..."
|
|
22
|
+
|
|
23
|
+
# --- IAM Role for SSM ---
|
|
24
|
+
ROLE_NAME="${IAM_ROLE_NAME:-$NAME_PREFIX-ssm-role}"
|
|
25
|
+
PROFILE_NAME="${INSTANCE_PROFILE_NAME:-$NAME_PREFIX-instance-profile}"
|
|
26
|
+
|
|
27
|
+
if aws iam get-instance-profile --instance-profile-name "$PROFILE_NAME" &>/dev/null; then
|
|
28
|
+
echo "Instance profile $PROFILE_NAME already exists, reusing."
|
|
29
|
+
else
|
|
30
|
+
echo "Instance profile $PROFILE_NAME not found. Creating IAM resources..."
|
|
31
|
+
|
|
32
|
+
if ! aws iam get-role --role-name "$ROLE_NAME" &>/dev/null; then
|
|
33
|
+
echo "Creating IAM role: $ROLE_NAME"
|
|
34
|
+
if ! aws iam create-role \
|
|
35
|
+
--role-name "$ROLE_NAME" \
|
|
36
|
+
--assume-role-policy-document '{
|
|
37
|
+
"Version": "2012-10-17",
|
|
38
|
+
"Statement": [{
|
|
39
|
+
"Effect": "Allow",
|
|
40
|
+
"Principal": {"Service": "ec2.amazonaws.com"},
|
|
41
|
+
"Action": "sts:AssumeRole"
|
|
42
|
+
}]
|
|
43
|
+
}' \
|
|
44
|
+
--no-cli-pager; then
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo "Error: Failed to create IAM role (see error above)."
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Ask an IAM admin to create the following, then re-run with:"
|
|
50
|
+
echo " INSTANCE_PROFILE_NAME=<name> ./scripts/windows-debug/provision.sh"
|
|
51
|
+
echo ""
|
|
52
|
+
echo "What the admin needs to create:"
|
|
53
|
+
echo " 1. IAM Role: $ROLE_NAME"
|
|
54
|
+
echo " - Trusted entity: EC2 (ec2.amazonaws.com)"
|
|
55
|
+
echo " - Attached policy: AmazonSSMManagedInstanceCore"
|
|
56
|
+
echo " 2. Instance Profile: $PROFILE_NAME"
|
|
57
|
+
echo " - With the above role added to it"
|
|
58
|
+
echo ""
|
|
59
|
+
echo "Or run these commands with an account that has iam:CreateRole permission:"
|
|
60
|
+
echo ""
|
|
61
|
+
echo " aws iam create-role --role-name $ROLE_NAME \\"
|
|
62
|
+
echo " --assume-role-policy-document '{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ec2.amazonaws.com\"},\"Action\":\"sts:AssumeRole\"}]}'"
|
|
63
|
+
echo ""
|
|
64
|
+
echo " aws iam attach-role-policy --role-name $ROLE_NAME \\"
|
|
65
|
+
echo " --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
|
66
|
+
echo ""
|
|
67
|
+
echo " aws iam create-instance-profile --instance-profile-name $PROFILE_NAME"
|
|
68
|
+
echo ""
|
|
69
|
+
echo " aws iam add-role-to-instance-profile \\"
|
|
70
|
+
echo " --instance-profile-name $PROFILE_NAME --role-name $ROLE_NAME"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
aws iam attach-role-policy \
|
|
75
|
+
--role-name "$ROLE_NAME" \
|
|
76
|
+
--policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
|
|
77
|
+
else
|
|
78
|
+
echo "IAM role $ROLE_NAME already exists."
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
echo "Creating instance profile: $PROFILE_NAME"
|
|
82
|
+
aws iam create-instance-profile --instance-profile-name "$PROFILE_NAME" --no-cli-pager
|
|
83
|
+
aws iam add-role-to-instance-profile \
|
|
84
|
+
--instance-profile-name "$PROFILE_NAME" \
|
|
85
|
+
--role-name "$ROLE_NAME"
|
|
86
|
+
echo "Waiting for instance profile propagation..."
|
|
87
|
+
sleep 10
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# --- Security Group (no inbound rules) ---
|
|
91
|
+
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query "Vpcs[0].VpcId" --output text)
|
|
92
|
+
if [[ "$VPC_ID" == "None" || -z "$VPC_ID" ]]; then
|
|
93
|
+
echo "Error: No default VPC found. Create one with: aws ec2 create-default-vpc"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
SG_NAME="$NAME_PREFIX-sg"
|
|
98
|
+
SG_ID=$(aws ec2 describe-security-groups \
|
|
99
|
+
--filters "Name=group-name,Values=$SG_NAME" "Name=vpc-id,Values=$VPC_ID" \
|
|
100
|
+
--query "SecurityGroups[0].GroupId" --output text 2>/dev/null || echo "None")
|
|
101
|
+
|
|
102
|
+
if [[ "$SG_ID" == "None" || -z "$SG_ID" ]]; then
|
|
103
|
+
echo "Creating security group: $SG_NAME"
|
|
104
|
+
SG_ID=$(aws ec2 create-security-group \
|
|
105
|
+
--group-name "$SG_NAME" \
|
|
106
|
+
--description "agent-browser Windows debug instance (SSM only, no inbound)" \
|
|
107
|
+
--vpc-id "$VPC_ID" \
|
|
108
|
+
--query "GroupId" --output text)
|
|
109
|
+
|
|
110
|
+
# Revoke default egress isn't needed; SSM requires outbound HTTPS.
|
|
111
|
+
# No inbound rules -- SSM uses outbound connections only.
|
|
112
|
+
else
|
|
113
|
+
echo "Security group $SG_NAME ($SG_ID) already exists, reusing."
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# --- AMI (latest Windows Server 2022) ---
|
|
117
|
+
AMI_ID=$(aws ssm get-parameter \
|
|
118
|
+
--name "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" \
|
|
119
|
+
--query "Parameter.Value" --output text)
|
|
120
|
+
echo "Using AMI: $AMI_ID (Windows Server 2022)"
|
|
121
|
+
|
|
122
|
+
# --- UserData bootstrap script ---
|
|
123
|
+
USERDATA_FILE=$(mktemp)
|
|
124
|
+
trap "rm -f $USERDATA_FILE" EXIT
|
|
125
|
+
|
|
126
|
+
cat > "$USERDATA_FILE" <<'PWSH'
|
|
127
|
+
<powershell>
|
|
128
|
+
$ErrorActionPreference = "Continue"
|
|
129
|
+
$logFile = "C:\bootstrap.log"
|
|
130
|
+
|
|
131
|
+
function Log($msg) {
|
|
132
|
+
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
|
133
|
+
"$ts $msg" | Tee-Object -FilePath $logFile -Append
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Log "--- Bootstrap starting ---"
|
|
137
|
+
|
|
138
|
+
# Install Git
|
|
139
|
+
Log "Installing Git..."
|
|
140
|
+
$gitInstaller = "$env:TEMP\git-installer.exe"
|
|
141
|
+
Invoke-WebRequest -Uri "https://github.com/git-for-windows/git/releases/download/v2.47.1.windows.2/Git-2.47.1.2-64-bit.exe" -OutFile $gitInstaller
|
|
142
|
+
Start-Process -FilePath $gitInstaller -ArgumentList "/VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS /COMPONENTS=`"icons,ext\reg\shellhere,assoc,assoc_sh`"" -Wait
|
|
143
|
+
$env:PATH = "C:\Program Files\Git\cmd;$env:PATH"
|
|
144
|
+
[Environment]::SetEnvironmentVariable("PATH", "C:\Program Files\Git\cmd;$([Environment]::GetEnvironmentVariable('PATH', 'Machine'))", "Machine")
|
|
145
|
+
Log "Git installed: $(git --version)"
|
|
146
|
+
|
|
147
|
+
# Install Rust
|
|
148
|
+
Log "Installing Rust..."
|
|
149
|
+
$rustupInit = "$env:TEMP\rustup-init.exe"
|
|
150
|
+
Invoke-WebRequest -Uri "https://win.rustup.rs/x86_64" -OutFile $rustupInit
|
|
151
|
+
Start-Process -FilePath $rustupInit -ArgumentList "-y --default-toolchain stable" -Wait
|
|
152
|
+
$env:PATH = "$env:USERPROFILE\.cargo\bin;$env:PATH"
|
|
153
|
+
[Environment]::SetEnvironmentVariable("PATH", "$env:USERPROFILE\.cargo\bin;$([Environment]::GetEnvironmentVariable('PATH', 'Machine'))", "Machine")
|
|
154
|
+
Log "Rust installed: $(rustc --version)"
|
|
155
|
+
|
|
156
|
+
# Install MSVC build tools (required for Rust on Windows)
|
|
157
|
+
Log "Installing Visual Studio Build Tools..."
|
|
158
|
+
$vsInstaller = "$env:TEMP\vs_buildtools.exe"
|
|
159
|
+
Invoke-WebRequest -Uri "https://aka.ms/vs/17/release/vs_buildtools.exe" -OutFile $vsInstaller
|
|
160
|
+
Start-Process -FilePath $vsInstaller -ArgumentList "--quiet --wait --norestart --nocache --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" -Wait
|
|
161
|
+
Log "Build tools installed."
|
|
162
|
+
|
|
163
|
+
# Clone repo
|
|
164
|
+
Log "Cloning agent-browser..."
|
|
165
|
+
git clone https://github.com/vercel-labs/agent-browser.git C:\agent-browser
|
|
166
|
+
Set-Location C:\agent-browser
|
|
167
|
+
Log "Repo cloned."
|
|
168
|
+
|
|
169
|
+
# Build CLI
|
|
170
|
+
Log "Building agent-browser CLI..."
|
|
171
|
+
cargo build --release --manifest-path cli\Cargo.toml
|
|
172
|
+
Log "Build complete."
|
|
173
|
+
|
|
174
|
+
# Install Chrome
|
|
175
|
+
Log "Installing Chrome via agent-browser..."
|
|
176
|
+
.\cli\target\release\agent-browser.exe install
|
|
177
|
+
Log "Chrome installed."
|
|
178
|
+
|
|
179
|
+
Log "--- Bootstrap complete ---"
|
|
180
|
+
</powershell>
|
|
181
|
+
PWSH
|
|
182
|
+
|
|
183
|
+
# --- Launch instance ---
|
|
184
|
+
echo "Launching $INSTANCE_TYPE instance..."
|
|
185
|
+
INSTANCE_ID=$(aws ec2 run-instances \
|
|
186
|
+
--image-id "$AMI_ID" \
|
|
187
|
+
--instance-type "$INSTANCE_TYPE" \
|
|
188
|
+
--iam-instance-profile "Name=$PROFILE_NAME" \
|
|
189
|
+
--security-group-ids "$SG_ID" \
|
|
190
|
+
--user-data "file://$USERDATA_FILE" \
|
|
191
|
+
--block-device-mappings '[{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":80,"VolumeType":"gp3"}}]' \
|
|
192
|
+
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$NAME_PREFIX}]" \
|
|
193
|
+
--metadata-options "HttpTokens=required" \
|
|
194
|
+
--query "Instances[0].InstanceId" --output text)
|
|
195
|
+
|
|
196
|
+
echo "Instance launched: $INSTANCE_ID"
|
|
197
|
+
|
|
198
|
+
# Save instance config
|
|
199
|
+
cat > "$INSTANCE_FILE" <<EOF
|
|
200
|
+
INSTANCE_ID=$INSTANCE_ID
|
|
201
|
+
REGION=$REGION
|
|
202
|
+
EOF
|
|
203
|
+
|
|
204
|
+
echo "Waiting for instance to enter running state..."
|
|
205
|
+
aws ec2 wait instance-running --instance-ids "$INSTANCE_ID"
|
|
206
|
+
echo "Instance is running."
|
|
207
|
+
|
|
208
|
+
echo ""
|
|
209
|
+
echo "Instance $INSTANCE_ID is booting and bootstrapping (Rust, Git, Chrome)."
|
|
210
|
+
echo "Bootstrap takes ~15-20 minutes on first boot."
|
|
211
|
+
echo ""
|
|
212
|
+
echo "Check bootstrap progress:"
|
|
213
|
+
echo " ./scripts/windows-debug/run.sh \"Get-Content C:\\bootstrap.log\""
|
|
214
|
+
echo ""
|
|
215
|
+
echo "Once ready, sync your branch and start debugging:"
|
|
216
|
+
echo " ./scripts/windows-debug/sync.sh"
|
|
217
|
+
echo " ./scripts/windows-debug/run.sh \"cd C:\\agent-browser && cargo test\""
|
|
218
|
+
echo ""
|
|
219
|
+
echo "Stop when done to save costs:"
|
|
220
|
+
echo " ./scripts/windows-debug/stop.sh"
|