opensteer 0.6.3 → 0.6.4

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 (33) hide show
  1. package/bin/opensteer.mjs +94 -8
  2. package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
  3. package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
  4. package/dist/{chunk-SCNX4NN3.js → chunk-54KNQTOL.js} +141 -2
  5. package/dist/{chunk-FTKWQ6X3.js → chunk-6B6LOYU3.js} +1 -1
  6. package/dist/{chunk-3OMXCBPD.js → chunk-G6V2DJRN.js} +442 -591
  7. package/dist/chunk-K5CL76MG.js +81 -0
  8. package/dist/{chunk-KE35RQOJ.js → chunk-KPPOTU3D.js} +53 -144
  9. package/dist/cli/auth.cjs +53 -6
  10. package/dist/cli/auth.d.cts +1 -1
  11. package/dist/cli/auth.d.ts +1 -1
  12. package/dist/cli/auth.js +2 -2
  13. package/dist/cli/local-profile.cjs +197 -0
  14. package/dist/cli/local-profile.d.cts +18 -0
  15. package/dist/cli/local-profile.d.ts +18 -0
  16. package/dist/cli/local-profile.js +97 -0
  17. package/dist/cli/profile.cjs +2844 -2412
  18. package/dist/cli/profile.d.cts +2 -2
  19. package/dist/cli/profile.d.ts +2 -2
  20. package/dist/cli/profile.js +469 -7
  21. package/dist/cli/server.cjs +649 -204
  22. package/dist/cli/server.js +69 -16
  23. package/dist/index.cjs +578 -185
  24. package/dist/index.d.cts +7 -5
  25. package/dist/index.d.ts +7 -5
  26. package/dist/index.js +4 -3
  27. package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
  28. package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
  29. package/package.json +2 -2
  30. package/skills/opensteer/SKILL.md +34 -14
  31. package/skills/opensteer/references/cli-reference.md +1 -1
  32. package/skills/opensteer/references/examples.md +5 -3
  33. package/skills/opensteer/references/sdk-reference.md +16 -14
@@ -3,7 +3,6 @@ import {
3
3
  cloudNotLaunchedError,
4
4
  cloudUnsupportedMethodError,
5
5
  createCloudRuntimeState,
6
- createKeychainStore,
7
6
  extractErrorMessage,
8
7
  normalizeError,
9
8
  normalizeNamespace,
@@ -13,506 +12,16 @@ import {
13
12
  resolveNamespace,
14
13
  resolveNamespaceDir,
15
14
  selectCloudCredential
16
- } from "./chunk-KE35RQOJ.js";
15
+ } from "./chunk-KPPOTU3D.js";
16
+ import {
17
+ detectChromePaths,
18
+ expandHome,
19
+ listLocalChromeProfiles
20
+ } from "./chunk-K5CL76MG.js";
17
21
  import {
18
22
  flattenExtractionDataToFieldPlan
19
23
  } from "./chunk-3H5RRIMZ.js";
20
24
 
21
- // src/browser/chrome.ts
22
- import { homedir, platform } from "os";
23
- import { join } from "path";
24
- function expandHome(p) {
25
- if (p.startsWith("~/") || p === "~") {
26
- return join(homedir(), p.slice(1));
27
- }
28
- return p;
29
- }
30
-
31
- // src/browser/chromium-profile.ts
32
- import { promisify } from "util";
33
- import { execFile } from "child_process";
34
- import { createDecipheriv, createHash, pbkdf2Sync } from "crypto";
35
- import {
36
- cp,
37
- copyFile,
38
- mkdtemp,
39
- readdir,
40
- readFile,
41
- rm
42
- } from "fs/promises";
43
- import { existsSync, statSync } from "fs";
44
- import { basename, dirname, join as join2 } from "path";
45
- import { tmpdir } from "os";
46
- import { chromium } from "playwright";
47
- var execFileAsync = promisify(execFile);
48
- var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
49
- var AES_BLOCK_BYTES = 16;
50
- var MAC_KEY_ITERATIONS = 1003;
51
- var LINUX_KEY_ITERATIONS = 1;
52
- var KEY_LENGTH = 16;
53
- var KEY_SALT = "saltysalt";
54
- var DEFAULT_CHROMIUM_BRAND = {
55
- macService: "Chrome Safe Storage",
56
- macAccount: "Chrome",
57
- linuxApplications: ["chrome", "google-chrome"]
58
- };
59
- var CHROMIUM_BRANDS = [
60
- {
61
- match: ["bravesoftware", "brave-browser"],
62
- brand: {
63
- macService: "Brave Safe Storage",
64
- macAccount: "Brave",
65
- linuxApplications: ["brave-browser", "brave"]
66
- }
67
- },
68
- {
69
- match: ["microsoft", "edge"],
70
- brand: {
71
- macService: "Microsoft Edge Safe Storage",
72
- macAccount: "Microsoft Edge",
73
- linuxApplications: ["microsoft-edge"],
74
- playwrightChannel: "msedge"
75
- }
76
- },
77
- {
78
- match: ["google", "chrome beta"],
79
- brand: {
80
- macService: "Chrome Beta Safe Storage",
81
- macAccount: "Chrome Beta",
82
- linuxApplications: ["chrome-beta"],
83
- playwrightChannel: "chrome-beta"
84
- }
85
- },
86
- {
87
- match: ["google", "chrome"],
88
- brand: {
89
- macService: "Chrome Safe Storage",
90
- macAccount: "Chrome",
91
- linuxApplications: ["chrome", "google-chrome"],
92
- playwrightChannel: "chrome"
93
- }
94
- },
95
- {
96
- match: ["chromium"],
97
- brand: {
98
- macService: "Chromium Safe Storage",
99
- macAccount: "Chromium",
100
- linuxApplications: ["chromium"]
101
- }
102
- }
103
- ];
104
- function directoryExists(filePath) {
105
- try {
106
- return statSync(filePath).isDirectory();
107
- } catch {
108
- return false;
109
- }
110
- }
111
- function fileExists(filePath) {
112
- try {
113
- return statSync(filePath).isFile();
114
- } catch {
115
- return false;
116
- }
117
- }
118
- function resolveCookieDbPath(profileDir) {
119
- const candidates = [join2(profileDir, "Network", "Cookies"), join2(profileDir, "Cookies")];
120
- for (const candidate of candidates) {
121
- if (fileExists(candidate)) {
122
- return candidate;
123
- }
124
- }
125
- return null;
126
- }
127
- async function selectProfileDirFromUserDataDir(userDataDir) {
128
- const entries = await readdir(userDataDir, {
129
- withFileTypes: true
130
- }).catch(() => []);
131
- const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => join2(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
132
- return candidates;
133
- }
134
- async function resolveChromiumProfileLocation(inputPath) {
135
- const expandedPath = expandHome(inputPath.trim());
136
- if (!expandedPath) {
137
- throw new Error("Profile path cannot be empty.");
138
- }
139
- if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
140
- const directParent = dirname(expandedPath);
141
- const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
142
- const userDataDir = dirname(profileDir);
143
- return {
144
- userDataDir,
145
- profileDir,
146
- profileDirectory: basename(profileDir),
147
- cookieDbPath: expandedPath,
148
- localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
149
- };
150
- }
151
- if (fileExists(expandedPath)) {
152
- throw new Error(
153
- `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
154
- );
155
- }
156
- if (!directoryExists(expandedPath)) {
157
- throw new Error(
158
- `Could not find a Chromium profile at "${inputPath}".`
159
- );
160
- }
161
- const directCookieDb = resolveCookieDbPath(expandedPath);
162
- if (directCookieDb) {
163
- const userDataDir = dirname(expandedPath);
164
- return {
165
- userDataDir,
166
- profileDir: expandedPath,
167
- profileDirectory: basename(expandedPath),
168
- cookieDbPath: directCookieDb,
169
- localStatePath: fileExists(join2(userDataDir, "Local State")) ? join2(userDataDir, "Local State") : null
170
- };
171
- }
172
- const localStatePath = join2(expandedPath, "Local State");
173
- if (!fileExists(localStatePath)) {
174
- throw new Error(
175
- `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
176
- );
177
- }
178
- const profileDirs = await selectProfileDirFromUserDataDir(expandedPath);
179
- if (profileDirs.length === 0) {
180
- throw new Error(
181
- `No Chromium profile with a Cookies database was found under "${inputPath}".`
182
- );
183
- }
184
- if (profileDirs.length > 1) {
185
- const candidates = profileDirs.map((entry) => basename(entry)).join(", ");
186
- throw new Error(
187
- `"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
188
- );
189
- }
190
- const selectedProfileDir = profileDirs[0];
191
- const cookieDbPath = resolveCookieDbPath(selectedProfileDir);
192
- if (!cookieDbPath) {
193
- throw new Error(
194
- `No Chromium Cookies database was found for "${inputPath}".`
195
- );
196
- }
197
- return {
198
- userDataDir: expandedPath,
199
- profileDir: selectedProfileDir,
200
- profileDirectory: basename(selectedProfileDir),
201
- cookieDbPath,
202
- localStatePath
203
- };
204
- }
205
- function resolvePersistentChromiumLaunchProfile(inputPath) {
206
- const expandedPath = expandHome(inputPath.trim());
207
- if (!expandedPath) {
208
- return {
209
- userDataDir: inputPath
210
- };
211
- }
212
- if (fileExists(expandedPath) && basename(expandedPath) === "Cookies") {
213
- const directParent = dirname(expandedPath);
214
- const profileDir = basename(directParent) === "Network" ? dirname(directParent) : directParent;
215
- return {
216
- userDataDir: dirname(profileDir),
217
- profileDirectory: basename(profileDir)
218
- };
219
- }
220
- if (directoryExists(expandedPath) && resolveCookieDbPath(expandedPath) && fileExists(join2(dirname(expandedPath), "Local State"))) {
221
- return {
222
- userDataDir: dirname(expandedPath),
223
- profileDirectory: basename(expandedPath)
224
- };
225
- }
226
- return {
227
- userDataDir: expandedPath
228
- };
229
- }
230
- function detectChromiumBrand(location) {
231
- const normalizedPath = location.userDataDir.toLowerCase();
232
- for (const candidate of CHROMIUM_BRANDS) {
233
- if (candidate.match.every((fragment) => normalizedPath.includes(fragment))) {
234
- return candidate.brand;
235
- }
236
- }
237
- return DEFAULT_CHROMIUM_BRAND;
238
- }
239
- async function createSqliteSnapshot(dbPath) {
240
- const snapshotDir = await mkdtemp(join2(tmpdir(), "opensteer-cookie-db-"));
241
- const snapshotPath = join2(snapshotDir, "Cookies");
242
- await copyFile(dbPath, snapshotPath);
243
- for (const suffix of ["-wal", "-shm", "-journal"]) {
244
- const source = `${dbPath}${suffix}`;
245
- if (!existsSync(source)) {
246
- continue;
247
- }
248
- await copyFile(source, `${snapshotPath}${suffix}`);
249
- }
250
- return {
251
- snapshotPath,
252
- cleanup: async () => {
253
- await rm(snapshotDir, { recursive: true, force: true });
254
- }
255
- };
256
- }
257
- async function querySqliteJson(dbPath, query) {
258
- const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
259
- encoding: "utf8",
260
- maxBuffer: 64 * 1024 * 1024
261
- });
262
- const stdout = result.stdout;
263
- const trimmed = stdout.trim();
264
- if (!trimmed) {
265
- return [];
266
- }
267
- return JSON.parse(trimmed);
268
- }
269
- function convertChromiumTimestampToUnixSeconds(value) {
270
- if (!value || value === "0") {
271
- return -1;
272
- }
273
- const micros = BigInt(value);
274
- if (micros <= CHROMIUM_EPOCH_MICROS) {
275
- return -1;
276
- }
277
- return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
278
- }
279
- function mapChromiumSameSite(value) {
280
- if (value === 2) {
281
- return "Strict";
282
- }
283
- if (value === 0) {
284
- return "None";
285
- }
286
- return "Lax";
287
- }
288
- function stripChromiumPadding(buffer) {
289
- const paddingLength = buffer[buffer.length - 1];
290
- if (paddingLength <= 0 || paddingLength > AES_BLOCK_BYTES) {
291
- return buffer;
292
- }
293
- return buffer.subarray(0, buffer.length - paddingLength);
294
- }
295
- function stripDomainHashPrefix(buffer, hostKey) {
296
- if (buffer.length < 32) {
297
- return buffer;
298
- }
299
- const domainHash = createHash("sha256").update(hostKey, "utf8").digest();
300
- if (buffer.subarray(0, 32).equals(domainHash)) {
301
- return buffer.subarray(32);
302
- }
303
- return buffer;
304
- }
305
- function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
306
- const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
307
- const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
308
- const decipher = createDecipheriv("aes-128-cbc", key, iv);
309
- const plaintext = Buffer.concat([
310
- decipher.update(ciphertext),
311
- decipher.final()
312
- ]);
313
- return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
314
- "utf8"
315
- );
316
- }
317
- function decryptChromiumAes256GcmValue(encryptedValue, key) {
318
- const nonce = encryptedValue.subarray(3, 15);
319
- const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
320
- const authTag = encryptedValue.subarray(encryptedValue.length - 16);
321
- const decipher = createDecipheriv("aes-256-gcm", key, nonce);
322
- decipher.setAuthTag(authTag);
323
- return Buffer.concat([
324
- decipher.update(ciphertext),
325
- decipher.final()
326
- ]).toString("utf8");
327
- }
328
- async function dpapiUnprotect(buffer) {
329
- const script = [
330
- `$inputBytes = [Convert]::FromBase64String('${buffer.toString("base64")}')`,
331
- "$plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(",
332
- " $inputBytes,",
333
- " $null,",
334
- " [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
335
- ")",
336
- "[Convert]::ToBase64String($plainBytes)"
337
- ].join("\n");
338
- const { stdout } = await execFileAsync(
339
- "powershell.exe",
340
- ["-NoProfile", "-NonInteractive", "-Command", script],
341
- {
342
- encoding: "utf8",
343
- maxBuffer: 8 * 1024 * 1024
344
- }
345
- );
346
- return Buffer.from(stdout.trim(), "base64");
347
- }
348
- async function buildChromiumDecryptor(location) {
349
- if (process.platform === "darwin") {
350
- const brand = detectChromiumBrand(location);
351
- const keychainStore = createKeychainStore();
352
- const password = keychainStore?.get(brand.macService, brand.macAccount) ?? null;
353
- if (!password) {
354
- throw new Error(
355
- `Unable to read ${brand.macService} from macOS Keychain.`
356
- );
357
- }
358
- const key = pbkdf2Sync(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
359
- return async (row) => decryptChromiumAes128CbcValue(
360
- Buffer.from(row.encrypted_value || "", "hex"),
361
- key,
362
- row.host_key
363
- );
364
- }
365
- if (process.platform === "linux") {
366
- const brand = detectChromiumBrand(location);
367
- const keychainStore = createKeychainStore();
368
- const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
369
- const key = pbkdf2Sync(
370
- password || "peanuts",
371
- KEY_SALT,
372
- LINUX_KEY_ITERATIONS,
373
- KEY_LENGTH,
374
- "sha1"
375
- );
376
- return async (row) => decryptChromiumAes128CbcValue(
377
- Buffer.from(row.encrypted_value || "", "hex"),
378
- key,
379
- row.host_key
380
- );
381
- }
382
- if (process.platform === "win32") {
383
- if (!location.localStatePath) {
384
- throw new Error(
385
- `Unable to locate Chromium Local State for profile: ${location.profileDir}`
386
- );
387
- }
388
- const localState = JSON.parse(
389
- await readFile(location.localStatePath, "utf8")
390
- );
391
- const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
392
- if (!encryptedKeyBase64) {
393
- throw new Error(
394
- `Local State did not include os_crypt.encrypted_key for ${location.userDataDir}`
395
- );
396
- }
397
- const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
398
- const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
399
- return async (row) => {
400
- const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
401
- if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
402
- const decrypted = await dpapiUnprotect(encryptedValue);
403
- return decrypted.toString("utf8");
404
- }
405
- return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
406
- };
407
- }
408
- throw new Error(
409
- `Local Chromium cookie sync is not supported on ${process.platform}.`
410
- );
411
- }
412
- function buildPlaywrightCookie(row, value) {
413
- if (!row.name.trim()) {
414
- return null;
415
- }
416
- if (!row.host_key.trim()) {
417
- return null;
418
- }
419
- const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
420
- if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
421
- return null;
422
- }
423
- return {
424
- name: row.name,
425
- value,
426
- domain: row.host_key,
427
- path: row.path || "/",
428
- expires,
429
- httpOnly: row.is_httponly === 1,
430
- secure: row.is_secure === 1,
431
- sameSite: mapChromiumSameSite(row.samesite)
432
- };
433
- }
434
- async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
435
- const location = await resolveChromiumProfileLocation(inputPath);
436
- try {
437
- return await loadCookiesFromSqlite(location);
438
- } catch (error) {
439
- if (!isMissingSqliteBinary(error)) {
440
- throw error;
441
- }
442
- }
443
- return await loadCookiesFromBrowserSnapshot(location, options);
444
- }
445
- async function loadCookiesFromSqlite(location) {
446
- const snapshot = await createSqliteSnapshot(location.cookieDbPath);
447
- try {
448
- const rows = await querySqliteJson(
449
- snapshot.snapshotPath,
450
- [
451
- "SELECT",
452
- " host_key,",
453
- " name,",
454
- " value,",
455
- " hex(encrypted_value) AS encrypted_value,",
456
- " path,",
457
- " CAST(expires_utc AS TEXT) AS expires_utc,",
458
- " is_secure,",
459
- " is_httponly,",
460
- " has_expires,",
461
- " samesite",
462
- "FROM cookies"
463
- ].join(" ")
464
- );
465
- const decryptValue = await buildChromiumDecryptor(location);
466
- const cookies = [];
467
- for (const row of rows) {
468
- let value = row.value || "";
469
- if (!value && row.encrypted_value) {
470
- value = await decryptValue(row);
471
- }
472
- const cookie = buildPlaywrightCookie(row, value);
473
- if (cookie) {
474
- cookies.push(cookie);
475
- }
476
- }
477
- return cookies;
478
- } finally {
479
- await snapshot.cleanup();
480
- }
481
- }
482
- async function loadCookiesFromBrowserSnapshot(location, options) {
483
- const snapshotRootDir = await mkdtemp(join2(tmpdir(), "opensteer-profile-"));
484
- const snapshotProfileDir = join2(
485
- snapshotRootDir,
486
- basename(location.profileDir)
487
- );
488
- let context = null;
489
- try {
490
- await cp(location.profileDir, snapshotProfileDir, {
491
- recursive: true
492
- });
493
- if (location.localStatePath) {
494
- await copyFile(location.localStatePath, join2(snapshotRootDir, "Local State"));
495
- }
496
- const brand = detectChromiumBrand(location);
497
- const args = [`--profile-directory=${basename(snapshotProfileDir)}`];
498
- context = await chromium.launchPersistentContext(snapshotRootDir, {
499
- channel: brand.playwrightChannel,
500
- headless: options.headless ?? true,
501
- timeout: options.timeout ?? 12e4,
502
- args
503
- });
504
- return await context.cookies();
505
- } finally {
506
- await context?.close().catch(() => void 0);
507
- await rm(snapshotRootDir, { recursive: true, force: true });
508
- }
509
- }
510
- function isMissingSqliteBinary(error) {
511
- return Boolean(
512
- error && typeof error === "object" && "code" in error && error.code === "ENOENT"
513
- );
514
- }
515
-
516
25
  // src/navigation.ts
517
26
  var DEFAULT_TIMEOUT = 3e4;
518
27
  var DEFAULT_SETTLE_MS = 750;
@@ -5294,7 +4803,7 @@ async function closeTab(context, activePage, index) {
5294
4803
  }
5295
4804
 
5296
4805
  // src/actions/cookies.ts
5297
- import { readFile as readFile2, writeFile } from "fs/promises";
4806
+ import { readFile, writeFile } from "fs/promises";
5298
4807
  async function getCookies(context, url) {
5299
4808
  return context.cookies(url ? [url] : void 0);
5300
4809
  }
@@ -5309,7 +4818,7 @@ async function exportCookies(context, filePath, url) {
5309
4818
  await writeFile(filePath, JSON.stringify(cookies, null, 2), "utf-8");
5310
4819
  }
5311
4820
  async function importCookies(context, filePath) {
5312
- const raw = await readFile2(filePath, "utf-8");
4821
+ const raw = await readFile(filePath, "utf-8");
5313
4822
  const cookies = JSON.parse(raw);
5314
4823
  await context.addCookies(cookies);
5315
4824
  }
@@ -8002,11 +7511,17 @@ function sleep4(ms) {
8002
7511
  }
8003
7512
 
8004
7513
  // src/opensteer.ts
8005
- import { createHash as createHash2, randomUUID } from "crypto";
7514
+ import { createHash, randomUUID } from "crypto";
8006
7515
 
8007
7516
  // src/browser/pool.ts
7517
+ import { spawn } from "child_process";
7518
+ import { existsSync } from "fs";
7519
+ import { copyFile, cp, mkdir, mkdtemp, rm } from "fs/promises";
7520
+ import { createServer } from "net";
7521
+ import { tmpdir } from "os";
7522
+ import { join } from "path";
8008
7523
  import {
8009
- chromium as chromium2
7524
+ chromium
8010
7525
  } from "playwright";
8011
7526
 
8012
7527
  // src/browser/cdp-proxy.ts
@@ -8349,132 +7864,184 @@ function errorMessage(error) {
8349
7864
  // src/browser/pool.ts
8350
7865
  var BrowserPool = class {
8351
7866
  browser = null;
8352
- persistentContext = null;
8353
7867
  cdpProxy = null;
7868
+ launchedProcess = null;
7869
+ tempUserDataDir = null;
8354
7870
  defaults;
8355
7871
  constructor(defaults = {}) {
8356
7872
  this.defaults = defaults;
8357
7873
  }
8358
7874
  async launch(options = {}) {
8359
- if (this.browser || this.cdpProxy) {
7875
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
8360
7876
  await this.close();
8361
7877
  }
8362
- const connectUrl = options.connectUrl ?? this.defaults.connectUrl;
8363
- const channel = options.channel ?? this.defaults.channel;
8364
- const profileDir = options.profileDir ?? this.defaults.profileDir;
8365
- if (connectUrl) {
8366
- return this.connectToRunning(connectUrl, options.timeout);
7878
+ const mode = options.mode ?? this.defaults.mode ?? "chromium";
7879
+ const cdpUrl = options.cdpUrl ?? this.defaults.cdpUrl;
7880
+ const userDataDir = options.userDataDir ?? this.defaults.userDataDir;
7881
+ const profileDirectory = options.profileDirectory ?? this.defaults.profileDirectory;
7882
+ const executablePath = options.executablePath ?? this.defaults.executablePath;
7883
+ if (cdpUrl) {
7884
+ if (mode === "real") {
7885
+ throw new Error(
7886
+ 'cdpUrl cannot be combined with mode "real". Use one browser launch path at a time.'
7887
+ );
7888
+ }
7889
+ if (userDataDir || profileDirectory) {
7890
+ throw new Error(
7891
+ "userDataDir/profileDirectory cannot be combined with cdpUrl."
7892
+ );
7893
+ }
7894
+ if (options.context && Object.keys(options.context).length > 0) {
7895
+ throw new Error(
7896
+ "context launch options are not supported when attaching over CDP."
7897
+ );
7898
+ }
7899
+ return this.connectToRunning(cdpUrl, options.timeout);
8367
7900
  }
8368
- if (profileDir) {
8369
- return this.launchPersistentProfile(options, channel, profileDir);
7901
+ if (mode !== "real" && (userDataDir || profileDirectory)) {
7902
+ throw new Error(
7903
+ 'userDataDir/profileDirectory require mode "real".'
7904
+ );
8370
7905
  }
8371
- if (channel) {
8372
- return this.launchWithChannel(options, channel);
7906
+ if (mode === "real") {
7907
+ if (options.context && Object.keys(options.context).length > 0) {
7908
+ throw new Error(
7909
+ "context launch options are not supported for real-browser mode."
7910
+ );
7911
+ }
7912
+ return this.launchOwnedRealBrowser({
7913
+ ...options,
7914
+ executablePath,
7915
+ userDataDir,
7916
+ profileDirectory
7917
+ });
8373
7918
  }
8374
7919
  return this.launchSandbox(options);
8375
7920
  }
8376
7921
  async close() {
8377
7922
  const browser = this.browser;
8378
- const persistentContext = this.persistentContext;
7923
+ const cdpProxy = this.cdpProxy;
7924
+ const launchedProcess = this.launchedProcess;
7925
+ const tempUserDataDir = this.tempUserDataDir;
8379
7926
  this.browser = null;
8380
- this.persistentContext = null;
7927
+ this.cdpProxy = null;
7928
+ this.launchedProcess = null;
7929
+ this.tempUserDataDir = null;
8381
7930
  try {
8382
- if (persistentContext) {
8383
- await persistentContext.close();
8384
- } else if (browser) {
8385
- await browser.close();
7931
+ if (browser) {
7932
+ await browser.close().catch(() => void 0);
8386
7933
  }
8387
7934
  } finally {
8388
- this.cdpProxy?.close();
8389
- this.cdpProxy = null;
7935
+ cdpProxy?.close();
7936
+ await killProcessTree(launchedProcess);
7937
+ if (tempUserDataDir) {
7938
+ await rm(tempUserDataDir, {
7939
+ recursive: true,
7940
+ force: true
7941
+ }).catch(() => void 0);
7942
+ }
8390
7943
  }
8391
7944
  }
8392
- async connectToRunning(connectUrl, timeout) {
8393
- this.cdpProxy?.close();
8394
- this.cdpProxy = null;
7945
+ async connectToRunning(cdpUrl, timeout) {
8395
7946
  let browser = null;
7947
+ let cdpProxy = null;
8396
7948
  try {
8397
- const { browserWsUrl, targets } = await discoverTargets(connectUrl);
7949
+ const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
8398
7950
  if (targets.length === 0) {
8399
7951
  throw new Error(
8400
7952
  "No page targets found. Is the browser running with an open window?"
8401
7953
  );
8402
7954
  }
8403
- const target = targets[0];
8404
- this.cdpProxy = new CDPProxy(browserWsUrl, target.id);
8405
- const proxyWsUrl = await this.cdpProxy.start();
8406
- browser = await chromium2.connectOverCDP(proxyWsUrl, {
7955
+ cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
7956
+ const proxyWsUrl = await cdpProxy.start();
7957
+ browser = await chromium.connectOverCDP(proxyWsUrl, {
8407
7958
  timeout: timeout ?? 3e4
8408
7959
  });
8409
7960
  this.browser = browser;
8410
- this.persistentContext = null;
8411
- const contexts = browser.contexts();
8412
- if (contexts.length === 0) {
8413
- throw new Error(
8414
- "Connection succeeded but no browser contexts found. Is the browser running with an open window?"
8415
- );
8416
- }
8417
- const context = contexts[0];
8418
- const pages = context.pages();
8419
- const page = pages.length > 0 ? pages[0] : await context.newPage();
7961
+ this.cdpProxy = cdpProxy;
7962
+ const { context, page } = await pickBrowserContextAndPage(browser);
8420
7963
  return { browser, context, page, isExternal: true };
8421
7964
  } catch (error) {
8422
7965
  if (browser) {
8423
7966
  await browser.close().catch(() => void 0);
8424
7967
  }
7968
+ cdpProxy?.close();
8425
7969
  this.browser = null;
8426
- this.persistentContext = null;
8427
- this.cdpProxy?.close();
8428
7970
  this.cdpProxy = null;
8429
7971
  throw error;
8430
7972
  }
8431
7973
  }
8432
- async launchPersistentProfile(options, channel, profileDir) {
8433
- const args = [];
8434
- const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
8435
- if (launchProfile.profileDirectory) {
8436
- args.push(`--profile-directory=${launchProfile.profileDirectory}`);
7974
+ async launchOwnedRealBrowser(options) {
7975
+ const chromePaths = detectChromePaths();
7976
+ const executablePath = options.executablePath ?? chromePaths.executable ?? void 0;
7977
+ if (!executablePath) {
7978
+ throw new Error(
7979
+ "Chrome was not found. Set browser.executablePath or install Chrome in a supported location."
7980
+ );
8437
7981
  }
8438
- const context = await chromium2.launchPersistentContext(
8439
- launchProfile.userDataDir,
8440
- {
8441
- channel,
8442
- headless: options.headless ?? this.defaults.headless,
8443
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
8444
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
8445
- timeout: options.timeout,
8446
- ...options.context || {},
8447
- args
8448
- }
7982
+ const sourceUserDataDir = expandHome(
7983
+ options.userDataDir ?? chromePaths.defaultUserDataDir
8449
7984
  );
8450
- const browser = context.browser();
8451
- if (!browser) {
8452
- await context.close().catch(() => void 0);
8453
- throw new Error("Persistent browser launch did not expose a browser instance.");
8454
- }
8455
- this.browser = browser;
8456
- this.persistentContext = context;
8457
- const pages = context.pages();
8458
- const page = pages.length > 0 ? pages[0] : await context.newPage();
8459
- return { browser, context, page, isExternal: false };
8460
- }
8461
- async launchWithChannel(options, channel) {
8462
- const browser = await chromium2.launch({
8463
- channel,
8464
- headless: options.headless ?? this.defaults.headless,
8465
- executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
8466
- slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
8467
- timeout: options.timeout
7985
+ const profileDirectory = options.profileDirectory ?? "Default";
7986
+ const tempUserDataDir = await cloneProfileToTempDir(
7987
+ sourceUserDataDir,
7988
+ profileDirectory
7989
+ );
7990
+ const debugPort = await reserveDebugPort();
7991
+ const headless = resolveLaunchHeadless(
7992
+ "real",
7993
+ options.headless,
7994
+ this.defaults.headless
7995
+ );
7996
+ const launchArgs = buildRealBrowserLaunchArgs({
7997
+ userDataDir: tempUserDataDir,
7998
+ profileDirectory,
7999
+ debugPort,
8000
+ headless
8468
8001
  });
8469
- this.browser = browser;
8470
- this.persistentContext = null;
8471
- const context = await browser.newContext(options.context || {});
8472
- const page = await context.newPage();
8473
- return { browser, context, page, isExternal: false };
8002
+ const processHandle = spawn(executablePath, launchArgs, {
8003
+ detached: process.platform !== "win32",
8004
+ stdio: "ignore"
8005
+ });
8006
+ processHandle.unref();
8007
+ let browser = null;
8008
+ try {
8009
+ const wsUrl = await resolveCdpWebSocketUrl(
8010
+ `http://127.0.0.1:${debugPort}`,
8011
+ options.timeout ?? 3e4
8012
+ );
8013
+ browser = await chromium.connectOverCDP(wsUrl, {
8014
+ timeout: options.timeout ?? 3e4
8015
+ });
8016
+ const { context, page } = await createOwnedBrowserContextAndPage(
8017
+ browser,
8018
+ {
8019
+ headless,
8020
+ initialUrl: options.initialUrl,
8021
+ timeoutMs: options.timeout ?? 3e4
8022
+ }
8023
+ );
8024
+ this.browser = browser;
8025
+ this.launchedProcess = processHandle;
8026
+ this.tempUserDataDir = tempUserDataDir;
8027
+ return { browser, context, page, isExternal: false };
8028
+ } catch (error) {
8029
+ await browser?.close().catch(() => void 0);
8030
+ await killProcessTree(processHandle);
8031
+ await rm(tempUserDataDir, {
8032
+ recursive: true,
8033
+ force: true
8034
+ }).catch(() => void 0);
8035
+ throw error;
8036
+ }
8474
8037
  }
8475
8038
  async launchSandbox(options) {
8476
- const browser = await chromium2.launch({
8477
- headless: options.headless ?? this.defaults.headless,
8039
+ const browser = await chromium.launch({
8040
+ headless: resolveLaunchHeadless(
8041
+ "chromium",
8042
+ options.headless,
8043
+ this.defaults.headless
8044
+ ),
8478
8045
  executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
8479
8046
  slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
8480
8047
  timeout: options.timeout
@@ -8482,10 +8049,268 @@ var BrowserPool = class {
8482
8049
  const context = await browser.newContext(options.context || {});
8483
8050
  const page = await context.newPage();
8484
8051
  this.browser = browser;
8485
- this.persistentContext = null;
8486
8052
  return { browser, context, page, isExternal: false };
8487
8053
  }
8488
8054
  };
8055
+ async function pickBrowserContextAndPage(browser) {
8056
+ const context = getPrimaryBrowserContext(browser);
8057
+ const pages = context.pages();
8058
+ const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
8059
+ return { context, page };
8060
+ }
8061
+ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
8062
+ if (requestedHeadless !== void 0) {
8063
+ return requestedHeadless;
8064
+ }
8065
+ if (defaultHeadless !== void 0) {
8066
+ return defaultHeadless;
8067
+ }
8068
+ return mode === "real";
8069
+ }
8070
+ async function createOwnedBrowserContextAndPage(browser, options) {
8071
+ const context = getPrimaryBrowserContext(browser);
8072
+ const page = await createOwnedBrowserPage(browser, context, options);
8073
+ return { context, page };
8074
+ }
8075
+ async function createOwnedBrowserPage(browser, context, options) {
8076
+ const targetUrl = options.initialUrl ?? "about:blank";
8077
+ const existingPages = new Set(context.pages());
8078
+ const browserSession = await browser.newBrowserCDPSession();
8079
+ try {
8080
+ const { targetId } = await browserSession.send("Target.createTarget", {
8081
+ url: targetUrl,
8082
+ newWindow: !options.headless
8083
+ });
8084
+ await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
8085
+ const page = await waitForOwnedBrowserPage(context, {
8086
+ existingPages,
8087
+ targetUrl,
8088
+ timeoutMs: options.timeoutMs
8089
+ });
8090
+ if (targetUrl !== "about:blank") {
8091
+ await page.waitForLoadState("domcontentloaded", {
8092
+ timeout: options.timeoutMs
8093
+ });
8094
+ }
8095
+ await closeDisposableStartupTargets(browserSession, targetId);
8096
+ return page;
8097
+ } finally {
8098
+ await browserSession.detach().catch(() => void 0);
8099
+ }
8100
+ }
8101
+ async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
8102
+ const response = await browserSession.send("Target.getTargets").catch(() => null);
8103
+ if (!response) {
8104
+ return;
8105
+ }
8106
+ for (const targetInfo of response.targetInfos) {
8107
+ if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
8108
+ continue;
8109
+ }
8110
+ await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
8111
+ }
8112
+ }
8113
+ async function waitForOwnedBrowserPage(context, options) {
8114
+ const deadline = Date.now() + options.timeoutMs;
8115
+ let fallbackPage = null;
8116
+ while (Date.now() < deadline) {
8117
+ for (const candidate of context.pages()) {
8118
+ if (options.existingPages.has(candidate)) {
8119
+ continue;
8120
+ }
8121
+ const url = candidate.url();
8122
+ if (!isInspectablePageUrl2(url)) {
8123
+ continue;
8124
+ }
8125
+ fallbackPage ??= candidate;
8126
+ if (options.targetUrl === "about:blank") {
8127
+ return candidate;
8128
+ }
8129
+ if (pageLooselyMatchesUrl(url, options.targetUrl)) {
8130
+ return candidate;
8131
+ }
8132
+ }
8133
+ await sleep5(100);
8134
+ }
8135
+ if (fallbackPage) {
8136
+ return fallbackPage;
8137
+ }
8138
+ throw new Error(
8139
+ `Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
8140
+ );
8141
+ }
8142
+ function getPrimaryBrowserContext(browser) {
8143
+ const contexts = browser.contexts();
8144
+ if (contexts.length === 0) {
8145
+ throw new Error(
8146
+ "Connection succeeded but no browser contexts were exposed."
8147
+ );
8148
+ }
8149
+ return contexts[0];
8150
+ }
8151
+ function isInspectablePageUrl2(url) {
8152
+ return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
8153
+ }
8154
+ function isDisposableStartupPageUrl(url) {
8155
+ return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
8156
+ }
8157
+ function pageLooselyMatchesUrl(currentUrl, initialUrl) {
8158
+ try {
8159
+ const current = new URL(currentUrl);
8160
+ const requested = new URL(initialUrl);
8161
+ if (current.href === requested.href) {
8162
+ return true;
8163
+ }
8164
+ return current.hostname === requested.hostname && current.pathname === requested.pathname;
8165
+ } catch {
8166
+ return currentUrl === initialUrl;
8167
+ }
8168
+ }
8169
+ function normalizeDiscoveryUrl(cdpUrl) {
8170
+ let parsed;
8171
+ try {
8172
+ parsed = new URL(cdpUrl);
8173
+ } catch {
8174
+ throw new Error(
8175
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
8176
+ );
8177
+ }
8178
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
8179
+ return parsed;
8180
+ }
8181
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
8182
+ throw new Error(
8183
+ `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
8184
+ );
8185
+ }
8186
+ const normalized = new URL(parsed.toString());
8187
+ normalized.pathname = "/json/version";
8188
+ normalized.search = "";
8189
+ normalized.hash = "";
8190
+ return normalized;
8191
+ }
8192
+ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
8193
+ if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
8194
+ return cdpUrl;
8195
+ }
8196
+ const versionUrl = normalizeDiscoveryUrl(cdpUrl);
8197
+ const deadline = Date.now() + timeoutMs;
8198
+ let lastError = "CDP discovery did not respond.";
8199
+ while (Date.now() < deadline) {
8200
+ const remaining = Math.max(deadline - Date.now(), 1e3);
8201
+ try {
8202
+ const response = await fetch(versionUrl, {
8203
+ signal: AbortSignal.timeout(Math.min(remaining, 5e3))
8204
+ });
8205
+ if (!response.ok) {
8206
+ lastError = `${response.status} ${response.statusText}`;
8207
+ } else {
8208
+ const payload = await response.json();
8209
+ const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
8210
+ if (wsUrl && wsUrl.trim()) {
8211
+ return wsUrl;
8212
+ }
8213
+ lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
8214
+ }
8215
+ } catch (error) {
8216
+ lastError = error instanceof Error ? error.message : "Unknown error";
8217
+ }
8218
+ await sleep5(100);
8219
+ }
8220
+ throw new Error(
8221
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
8222
+ );
8223
+ }
8224
+ async function reserveDebugPort() {
8225
+ return await new Promise((resolve, reject) => {
8226
+ const server = createServer();
8227
+ server.unref();
8228
+ server.on("error", reject);
8229
+ server.listen(0, "127.0.0.1", () => {
8230
+ const address = server.address();
8231
+ if (!address || typeof address === "string") {
8232
+ server.close();
8233
+ reject(new Error("Failed to reserve a local debug port."));
8234
+ return;
8235
+ }
8236
+ server.close((error) => {
8237
+ if (error) {
8238
+ reject(error);
8239
+ return;
8240
+ }
8241
+ resolve(address.port);
8242
+ });
8243
+ });
8244
+ });
8245
+ }
8246
+ async function cloneProfileToTempDir(userDataDir, profileDirectory) {
8247
+ const resolvedUserDataDir = expandHome(userDataDir);
8248
+ const tempUserDataDir = await mkdtemp(
8249
+ join(tmpdir(), "opensteer-real-browser-")
8250
+ );
8251
+ const sourceProfileDir = join(resolvedUserDataDir, profileDirectory);
8252
+ const targetProfileDir = join(tempUserDataDir, profileDirectory);
8253
+ if (existsSync(sourceProfileDir)) {
8254
+ await cp(sourceProfileDir, targetProfileDir, {
8255
+ recursive: true
8256
+ });
8257
+ } else {
8258
+ await mkdir(targetProfileDir, {
8259
+ recursive: true
8260
+ });
8261
+ }
8262
+ const localStatePath = join(resolvedUserDataDir, "Local State");
8263
+ if (existsSync(localStatePath)) {
8264
+ await copyFile(localStatePath, join(tempUserDataDir, "Local State"));
8265
+ }
8266
+ return tempUserDataDir;
8267
+ }
8268
+ function buildRealBrowserLaunchArgs(options) {
8269
+ const args = [
8270
+ `--user-data-dir=${options.userDataDir}`,
8271
+ `--profile-directory=${options.profileDirectory}`,
8272
+ `--remote-debugging-port=${options.debugPort}`,
8273
+ "--no-first-run",
8274
+ "--no-default-browser-check",
8275
+ "--disable-background-networking",
8276
+ "--disable-sync",
8277
+ "--disable-popup-blocking"
8278
+ ];
8279
+ if (options.headless) {
8280
+ args.push("--headless=new");
8281
+ }
8282
+ return args;
8283
+ }
8284
+ async function killProcessTree(processHandle) {
8285
+ if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
8286
+ return;
8287
+ }
8288
+ if (process.platform === "win32") {
8289
+ await new Promise((resolve) => {
8290
+ const killer = spawn(
8291
+ "taskkill",
8292
+ ["/pid", String(processHandle.pid), "/t", "/f"],
8293
+ {
8294
+ stdio: "ignore"
8295
+ }
8296
+ );
8297
+ killer.on("error", () => resolve());
8298
+ killer.on("exit", () => resolve());
8299
+ });
8300
+ return;
8301
+ }
8302
+ try {
8303
+ process.kill(-processHandle.pid, "SIGKILL");
8304
+ } catch {
8305
+ try {
8306
+ processHandle.kill("SIGKILL");
8307
+ } catch {
8308
+ }
8309
+ }
8310
+ }
8311
+ async function sleep5(ms) {
8312
+ await new Promise((resolve) => setTimeout(resolve, ms));
8313
+ }
8489
8314
 
8490
8315
  // src/action-wait.ts
8491
8316
  var ROBUST_PROFILE = {
@@ -8680,7 +8505,7 @@ var AdaptiveNetworkTracker = class {
8680
8505
  this.idleSince = 0;
8681
8506
  }
8682
8507
  const remaining = Math.max(1, options.deadline - now);
8683
- await sleep5(Math.min(NETWORK_POLL_MS, remaining));
8508
+ await sleep6(Math.min(NETWORK_POLL_MS, remaining));
8684
8509
  }
8685
8510
  }
8686
8511
  handleRequestStarted = (request) => {
@@ -8725,7 +8550,7 @@ var AdaptiveNetworkTracker = class {
8725
8550
  return false;
8726
8551
  }
8727
8552
  };
8728
- async function sleep5(ms) {
8553
+ async function sleep6(ms) {
8729
8554
  await new Promise((resolve) => {
8730
8555
  setTimeout(resolve, ms);
8731
8556
  });
@@ -10416,9 +10241,11 @@ var Opensteer = class _Opensteer {
10416
10241
  }
10417
10242
  const session = await this.pool.launch({
10418
10243
  ...options,
10419
- connectUrl: options.connectUrl ?? this.config.browser?.connectUrl,
10420
- channel: options.channel ?? this.config.browser?.channel,
10421
- profileDir: options.profileDir ?? this.config.browser?.profileDir
10244
+ mode: options.mode ?? this.config.browser?.mode,
10245
+ cdpUrl: options.cdpUrl ?? this.config.browser?.cdpUrl,
10246
+ userDataDir: options.userDataDir ?? this.config.browser?.userDataDir,
10247
+ profileDirectory: options.profileDirectory ?? this.config.browser?.profileDirectory,
10248
+ executablePath: options.executablePath ?? this.config.browser?.executablePath
10422
10249
  });
10423
10250
  this.browser = session.browser;
10424
10251
  this.contextRef = session.context;
@@ -10449,6 +10276,32 @@ var Opensteer = class _Opensteer {
10449
10276
  instance.snapshotCache = null;
10450
10277
  return instance;
10451
10278
  }
10279
+ static listLocalProfiles(userDataDir) {
10280
+ return listLocalChromeProfiles(userDataDir);
10281
+ }
10282
+ static fromSystemChrome(browser = {}, config = {}) {
10283
+ const chromePaths = detectChromePaths();
10284
+ const executablePath = browser.executablePath ?? config.browser?.executablePath ?? chromePaths.executable ?? void 0;
10285
+ if (!executablePath) {
10286
+ throw new Error(
10287
+ "Chrome was not found. Pass executablePath explicitly or install Chrome in a supported location."
10288
+ );
10289
+ }
10290
+ const userDataDir = browser.userDataDir ?? config.browser?.userDataDir ?? chromePaths.defaultUserDataDir;
10291
+ const autoDetectedProfiles = listLocalChromeProfiles(userDataDir);
10292
+ const profileDirectory = browser.profileDirectory ?? config.browser?.profileDirectory ?? autoDetectedProfiles[0]?.directory ?? "Default";
10293
+ return new _Opensteer({
10294
+ ...config,
10295
+ browser: {
10296
+ ...config.browser || {},
10297
+ mode: "real",
10298
+ headless: browser.headless ?? config.browser?.headless ?? true,
10299
+ executablePath,
10300
+ userDataDir,
10301
+ profileDirectory
10302
+ }
10303
+ });
10304
+ }
10452
10305
  async close() {
10453
10306
  this.snapshotCache = null;
10454
10307
  if (this.cloud) {
@@ -12481,7 +12334,7 @@ var Opensteer = class _Opensteer {
12481
12334
  }
12482
12335
  resolveStorageKey(description) {
12483
12336
  if (!description) return null;
12484
- return createHash2("sha256").update(description).digest("hex").slice(0, 16);
12337
+ return createHash("sha256").update(description).digest("hex").slice(0, 16);
12485
12338
  }
12486
12339
  normalizePath(path3) {
12487
12340
  return sanitizeElementPath(path3);
@@ -12553,7 +12406,7 @@ function normalizeExtractSource(source) {
12553
12406
  }
12554
12407
  function computeSchemaHash(schema) {
12555
12408
  const stable = stableStringify(schema);
12556
- return createHash2("sha256").update(stable).digest("hex");
12409
+ return createHash("sha256").update(stable).digest("hex");
12557
12410
  }
12558
12411
  function buildPathMap(fields) {
12559
12412
  const out = {};
@@ -12813,8 +12666,6 @@ function buildLocalRunId(namespace) {
12813
12666
  }
12814
12667
 
12815
12668
  export {
12816
- expandHome,
12817
- loadCookiesFromLocalProfileDir,
12818
12669
  waitForVisualStability,
12819
12670
  createEmptyRegistry,
12820
12671
  LocalSelectorStorage,