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.
Files changed (122) hide show
  1. package/README.md +1256 -240
  2. package/bin/agent-browser-darwin-arm64 +0 -0
  3. package/bin/agent-browser-darwin-x64 +0 -0
  4. package/bin/agent-browser-linux-arm64 +0 -0
  5. package/bin/agent-browser-linux-x64 +0 -0
  6. package/bin/agent-browser-win32-x64.exe +0 -0
  7. package/bin/agent-browser.js +13 -2
  8. package/extensions/tab-group-cdp/content-script.js +425 -0
  9. package/extensions/tab-group-cdp/icons/icon.svg +7 -0
  10. package/extensions/tab-group-cdp/manifest.json +34 -0
  11. package/extensions/tab-group-cdp/page-bridge.js +133 -0
  12. package/extensions/tab-group-cdp/service-worker.js +2249 -0
  13. package/extensions/tab-group-cdp/sidepanel.css +258 -0
  14. package/extensions/tab-group-cdp/sidepanel.html +28 -0
  15. package/extensions/tab-group-cdp/sidepanel.js +1225 -0
  16. package/package.json +17 -69
  17. package/scripts/build-all-platforms.sh +6 -0
  18. package/scripts/check-version-sync.js +14 -2
  19. package/scripts/copy-native.js +8 -50
  20. package/scripts/postinstall.js +149 -165
  21. package/scripts/windows-debug/provision.sh +220 -0
  22. package/scripts/windows-debug/run.sh +92 -0
  23. package/scripts/windows-debug/start.sh +43 -0
  24. package/scripts/windows-debug/stop.sh +28 -0
  25. package/scripts/windows-debug/sync.sh +27 -0
  26. package/skills/agent-browser/SKILL.md +256 -159
  27. package/skills/agent-browser/references/authentication.md +101 -0
  28. package/skills/agent-browser/references/commands.md +34 -2
  29. package/skills/agent-browser/references/snapshot-refs.md +25 -0
  30. package/skills/agentcore/SKILL.md +115 -0
  31. package/skills/dogfood/SKILL.md +4 -2
  32. package/skills/electron/SKILL.md +26 -2
  33. package/skills/slack/SKILL.md +0 -9
  34. package/skills/slack/references/slack-tasks.md +2 -8
  35. package/skills/vercel-sandbox/SKILL.md +280 -0
  36. package/bin/agent-browser-local +0 -0
  37. package/bin/agent-browser-stealth +0 -0
  38. package/bin/agent-browser-stealth.d +0 -1
  39. package/dist/action-policy.d.ts +0 -14
  40. package/dist/action-policy.d.ts.map +0 -1
  41. package/dist/action-policy.js +0 -253
  42. package/dist/action-policy.js.map +0 -1
  43. package/dist/actions.d.ts +0 -21
  44. package/dist/actions.d.ts.map +0 -1
  45. package/dist/actions.js +0 -2139
  46. package/dist/actions.js.map +0 -1
  47. package/dist/auth-cli.d.ts +0 -2
  48. package/dist/auth-cli.d.ts.map +0 -1
  49. package/dist/auth-cli.js +0 -97
  50. package/dist/auth-cli.js.map +0 -1
  51. package/dist/auth-vault.d.ts +0 -36
  52. package/dist/auth-vault.d.ts.map +0 -1
  53. package/dist/auth-vault.js +0 -125
  54. package/dist/auth-vault.js.map +0 -1
  55. package/dist/browser.d.ts +0 -665
  56. package/dist/browser.d.ts.map +0 -1
  57. package/dist/browser.js +0 -3210
  58. package/dist/browser.js.map +0 -1
  59. package/dist/confirmation.d.ts +0 -8
  60. package/dist/confirmation.d.ts.map +0 -1
  61. package/dist/confirmation.js +0 -30
  62. package/dist/confirmation.js.map +0 -1
  63. package/dist/daemon.d.ts +0 -78
  64. package/dist/daemon.d.ts.map +0 -1
  65. package/dist/daemon.js +0 -744
  66. package/dist/daemon.js.map +0 -1
  67. package/dist/diff.d.ts +0 -18
  68. package/dist/diff.d.ts.map +0 -1
  69. package/dist/diff.js +0 -271
  70. package/dist/diff.js.map +0 -1
  71. package/dist/domain-filter.d.ts +0 -28
  72. package/dist/domain-filter.d.ts.map +0 -1
  73. package/dist/domain-filter.js +0 -149
  74. package/dist/domain-filter.js.map +0 -1
  75. package/dist/encryption.d.ts +0 -73
  76. package/dist/encryption.d.ts.map +0 -1
  77. package/dist/encryption.js +0 -171
  78. package/dist/encryption.js.map +0 -1
  79. package/dist/ios-actions.d.ts +0 -11
  80. package/dist/ios-actions.d.ts.map +0 -1
  81. package/dist/ios-actions.js +0 -228
  82. package/dist/ios-actions.js.map +0 -1
  83. package/dist/ios-manager.d.ts +0 -266
  84. package/dist/ios-manager.d.ts.map +0 -1
  85. package/dist/ios-manager.js +0 -1073
  86. package/dist/ios-manager.js.map +0 -1
  87. package/dist/protocol.d.ts +0 -26
  88. package/dist/protocol.d.ts.map +0 -1
  89. package/dist/protocol.js +0 -990
  90. package/dist/protocol.js.map +0 -1
  91. package/dist/snapshot.d.ts +0 -67
  92. package/dist/snapshot.d.ts.map +0 -1
  93. package/dist/snapshot.js +0 -514
  94. package/dist/snapshot.js.map +0 -1
  95. package/dist/state-utils.d.ts +0 -77
  96. package/dist/state-utils.d.ts.map +0 -1
  97. package/dist/state-utils.js +0 -178
  98. package/dist/state-utils.js.map +0 -1
  99. package/dist/stealth.d.ts +0 -41
  100. package/dist/stealth.d.ts.map +0 -1
  101. package/dist/stealth.js +0 -1743
  102. package/dist/stealth.js.map +0 -1
  103. package/dist/stream-server.d.ts +0 -117
  104. package/dist/stream-server.d.ts.map +0 -1
  105. package/dist/stream-server.js +0 -309
  106. package/dist/stream-server.js.map +0 -1
  107. package/dist/types.d.ts +0 -973
  108. package/dist/types.d.ts.map +0 -1
  109. package/dist/types.js +0 -2
  110. package/dist/types.js.map +0 -1
  111. package/scripts/check-creepjs-headless.js +0 -137
  112. package/scripts/check-daemon-pid-recovery.js +0 -148
  113. package/scripts/check-sannysoft-webdriver.js +0 -112
  114. package/scripts/check-stealth-regression.js +0 -199
  115. package/scripts/check-turnstile-testkey.ts +0 -125
  116. package/scripts/clawhub-sync.sh +0 -27
  117. package/scripts/sync-upstream.sh +0 -142
  118. package/scripts/verify-bundled-binaries.js +0 -71
  119. package/scripts/verify-native-version.js +0 -48
  120. package/scripts/verify-packed-host-binary.js +0 -88
  121. package/scripts/verify-registry-host-binary.js +0 -120
  122. package/skills/agent-browser-stealth/SKILL.md +0 -127
@@ -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 { execFileSync, execSync } from 'child_process';
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 platformKey = `${platform()}-${arch()}`;
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(readFileSync(join(projectRoot, 'package.json'), 'utf8'));
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 = getGitHubRepoFromPackage(packageJson);
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
- const installedVersion = readBinaryVersion(binaryPath);
115
- if (installedVersion.includes(version)) {
116
- console.log(`✓ Native binary ready: ${binaryName}`);
115
+ console.log(`✓ Native binary ready: ${binaryName}`);
117
116
 
118
- // On global installs, fix npm's bin entry to use native binary directly
119
- await fixGlobalInstallBin();
117
+ writeInstallMethod();
120
118
 
121
- showPlaywrightReminder();
122
- return;
123
- }
119
+ // On global installs, fix npm's bin entry to use native binary directly
120
+ await fixGlobalInstallBin();
124
121
 
125
- console.log(
126
- `⚠ Binary version mismatch detected: expected ${version}, got "${installedVersion || 'unknown'}"`
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(`⚠ Could not download native binary: ${err.message}`);
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: pnpm run build:native');
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
- showPlaywrightReminder();
157
+ showInstallReminder();
169
158
  }
170
159
 
171
- function readBinaryVersion(path) {
172
- try {
173
- return execFileSync(path, ['--version'], {
174
- encoding: 'utf8',
175
- stdio: ['ignore', 'pipe', 'pipe'],
176
- }).trim();
177
- } catch {
178
- return '';
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 showPlaywrightReminder() {
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
- let optimized = false;
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
- // Replace symlink to point directly to native binary
249
- try {
250
- unlinkSync(symlinkPath);
251
- symlinkSync(binaryPath, symlinkPath);
252
- optimized = true;
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
- if (optimized) {
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
- // Path to native binary relative to npm prefix
279
- const packagePath = packageName.replace(/\//g, '\\');
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
- // npm may create shims after lifecycle scripts, and binary may be absent
284
- // when running with JS fallback; skip rewriting in those cases.
285
- if (!existsSync(absoluteBinaryPath)) {
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
- let optimized = false;
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
- for (const commandName of binCommands) {
292
- // The shims are in the npm prefix directory (not prefix/bin on Windows)
293
- const cmdShim = join(npmBinDir, `${commandName}.cmd`);
294
- const ps1Shim = join(npmBinDir, `${commandName}.ps1`);
295
+ // Only rewrite shims if the native binary actually exists
296
+ if (!existsSync(absoluteBinaryPath)) {
297
+ return;
298
+ }
295
299
 
296
- // Only fix if shims exist (indicates global install)
297
- if (!existsSync(cmdShim)) {
298
- continue;
299
- }
300
+ try {
301
+ const cmdContent = `@ECHO off\r\n"%~dp0${relativeBinaryPath}" %*\r\n`;
302
+ writeFileSync(cmdShim, cmdContent);
300
303
 
301
- try {
302
- // Overwrite .cmd shim
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"