@ulpi/browse 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ const __import_meta_url = require("url").pathToFileURL(__filename).href;
3
+ "use strict";
2
4
  var __create = Object.create;
3
5
  var __defProp = Object.defineProperty;
4
6
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
8
  var __getProtoOf = Object.getPrototypeOf;
7
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
10
  var __esm = (fn, res) => function __init() {
15
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
12
  };
17
- var __commonJS = (cb, mod) => function __require2() {
13
+ var __commonJS = (cb, mod) => function __require() {
18
14
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
19
15
  };
20
16
  var __export = (target, all) => {
@@ -38,6 +34,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
38
34
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
39
35
  mod
40
36
  ));
37
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
41
38
 
42
39
  // src/constants.ts
43
40
  var BROWSE_TIMEOUT, DEFAULTS;
@@ -68,9 +65,6 @@ var chrome_discover_exports = {};
68
65
  __export(chrome_discover_exports, {
69
66
  discoverChrome: () => discoverChrome
70
67
  });
71
- import * as os from "os";
72
- import * as fs2 from "fs";
73
- import * as path2 from "path";
74
68
  async function fetchWsUrl(port) {
75
69
  try {
76
70
  const res = await fetch(`http://127.0.0.1:${port}/json/version`, {
@@ -108,10 +102,13 @@ async function discoverChrome() {
108
102
  }
109
103
  return null;
110
104
  }
111
- var PROFILE_PATHS, PROBE_PORTS;
105
+ var os, fs2, path2, PROFILE_PATHS, PROBE_PORTS;
112
106
  var init_chrome_discover = __esm({
113
107
  "src/chrome-discover.ts"() {
114
108
  "use strict";
109
+ os = __toESM(require("os"), 1);
110
+ fs2 = __toESM(require("fs"), 1);
111
+ path2 = __toESM(require("path"), 1);
115
112
  PROFILE_PATHS = [
116
113
  "Google/Chrome",
117
114
  "Arc/User Data",
@@ -124,10 +121,10 @@ var init_chrome_discover = __esm({
124
121
 
125
122
  // package.json
126
123
  var require_package = __commonJS({
127
- "package.json"(exports, module) {
128
- module.exports = {
124
+ "package.json"(exports2, module2) {
125
+ module2.exports = {
129
126
  name: "@ulpi/browse",
130
- version: "1.0.1",
127
+ version: "1.0.3",
131
128
  repository: {
132
129
  type: "git",
133
130
  url: "https://github.com/ulpi-io/browse"
@@ -141,7 +138,7 @@ var require_package = __commonJS({
141
138
  "playwright-core": "^1.58.2"
142
139
  },
143
140
  bin: {
144
- browse: "dist/browse.mjs"
141
+ browse: "dist/browse.cjs"
145
142
  },
146
143
  description: "Fast headless browser CLI \u2014 persistent Chromium daemon via Playwright.",
147
144
  engines: {
@@ -168,7 +165,8 @@ var require_package = __commonJS({
168
165
  access: "public"
169
166
  },
170
167
  scripts: {
171
- build: "esbuild src/cli.ts --bundle --format=esm --platform=node --target=node18 --outfile=dist/browse.mjs --external:playwright --external:playwright-core --external:better-sqlite3 --external:electron --external:chromium-bidi --banner:js='#!/usr/bin/env node'",
168
+ build: `esbuild src/cli.ts --bundle --format=cjs --platform=node --target=node18 --outfile=dist/browse.cjs --external:playwright --external:playwright-core --external:better-sqlite3 --external:electron --external:chromium-bidi --banner:js='#!/usr/bin/env node
169
+ const __import_meta_url = require("url").pathToFileURL(__filename).href;' --define:import.meta.url=__import_meta_url`,
172
170
  "build:all": "bash scripts/build-all.sh",
173
171
  dev: "tsx src/cli.ts",
174
172
  server: "tsx src/server.ts",
@@ -181,6 +179,7 @@ var require_package = __commonJS({
181
179
  devDependencies: {
182
180
  "@types/better-sqlite3": "^7.0.0",
183
181
  "@types/node": "^25.5.0",
182
+ "@vitest/coverage-v8": "^4.1.0",
184
183
  esbuild: "^0.25.0",
185
184
  tsx: "^4.0.0",
186
185
  typescript: "^5.9.3",
@@ -198,9 +197,6 @@ var install_skill_exports = {};
198
197
  __export(install_skill_exports, {
199
198
  installSkill: () => installSkill
200
199
  });
201
- import * as fs3 from "fs";
202
- import * as path3 from "path";
203
- import { fileURLToPath } from "url";
204
200
  function installSkill(targetDir) {
205
201
  const dir = targetDir || process.cwd();
206
202
  const hasGit = fs3.existsSync(path3.join(dir, ".git"));
@@ -212,7 +208,7 @@ function installSkill(targetDir) {
212
208
  }
213
209
  const skillDir = path3.join(dir, ".claude", "skills", "browse");
214
210
  fs3.mkdirSync(skillDir, { recursive: true });
215
- const skillSource = path3.resolve(path3.dirname(fileURLToPath(import.meta.url)), "..", "skill", "SKILL.md");
211
+ const skillSource = path3.resolve(path3.dirname((0, import_url.fileURLToPath)(__import_meta_url)), "..", "skill", "SKILL.md");
216
212
  const skillDest = path3.join(skillDir, "SKILL.md");
217
213
  if (!fs3.existsSync(skillSource)) {
218
214
  console.error(`SKILL.md not found at ${skillSource}`);
@@ -249,10 +245,13 @@ function installSkill(targetDir) {
249
245
  }
250
246
  console.log("\nDone. Claude Code will now use browse for web tasks automatically.");
251
247
  }
252
- var PERMISSIONS;
248
+ var fs3, path3, import_url, PERMISSIONS;
253
249
  var init_install_skill = __esm({
254
250
  "src/install-skill.ts"() {
255
251
  "use strict";
252
+ fs3 = __toESM(require("fs"), 1);
253
+ path3 = __toESM(require("path"), 1);
254
+ import_url = require("url");
256
255
  PERMISSIONS = [
257
256
  "Bash(browse:*)",
258
257
  "Bash(browse goto:*)",
@@ -318,35 +317,29 @@ var rebrowser_playwright_exports = {};
318
317
  __export(rebrowser_playwright_exports, {
319
318
  default: () => rebrowser_playwright_default
320
319
  });
321
- import * as playwright_core_star from "playwright-core";
322
- import playwright from "playwright-core";
323
- var rebrowser_playwright_default;
320
+ var import_playwright_core, rebrowser_playwright_default;
324
321
  var init_rebrowser_playwright = __esm({
325
322
  "node_modules/rebrowser-playwright/index.mjs"() {
326
- __reExport(rebrowser_playwright_exports, playwright_core_star);
327
- rebrowser_playwright_default = playwright;
323
+ __reExport(rebrowser_playwright_exports, require("playwright-core"));
324
+ import_playwright_core = __toESM(require("playwright-core"), 1);
325
+ rebrowser_playwright_default = import_playwright_core.default;
328
326
  }
329
327
  });
330
328
 
331
329
  // src/runtime.ts
332
- import { homedir as homedir2 } from "os";
333
- import { existsSync as existsSync3 } from "fs";
334
- import { execSync, spawn } from "child_process";
335
- import { join as join4 } from "path";
336
- import * as net from "net";
337
330
  function findLightpanda() {
338
331
  try {
339
- const result = execSync("which lightpanda", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
332
+ const result = (0, import_child_process.execSync)("which lightpanda", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
340
333
  if (result) return result;
341
334
  } catch {
342
335
  }
343
- const home = homedir2();
336
+ const home = (0, import_os.homedir)();
344
337
  const candidates = [
345
- join4(home, ".lightpanda", "lightpanda"),
346
- join4(home, ".local", "bin", "lightpanda")
338
+ (0, import_path.join)(home, ".lightpanda", "lightpanda"),
339
+ (0, import_path.join)(home, ".local", "bin", "lightpanda")
347
340
  ];
348
341
  for (const candidate of candidates) {
349
- if (existsSync3(candidate)) return candidate;
342
+ if ((0, import_fs.existsSync)(candidate)) return candidate;
350
343
  }
351
344
  return null;
352
345
  }
@@ -360,10 +353,15 @@ async function getRuntime(name) {
360
353
  }
361
354
  return loader();
362
355
  }
363
- var registry, AVAILABLE_RUNTIMES;
356
+ var import_os, import_fs, import_child_process, import_path, net, registry, AVAILABLE_RUNTIMES;
364
357
  var init_runtime = __esm({
365
358
  "src/runtime.ts"() {
366
359
  "use strict";
360
+ import_os = require("os");
361
+ import_fs = require("fs");
362
+ import_child_process = require("child_process");
363
+ import_path = require("path");
364
+ net = __toESM(require("net"), 1);
367
365
  registry = {
368
366
  playwright: async () => {
369
367
  const pw = await import("playwright");
@@ -395,7 +393,7 @@ var init_runtime = __esm({
395
393
  });
396
394
  srv.on("error", reject);
397
395
  });
398
- const child = spawn(
396
+ const child = (0, import_child_process.spawn)(
399
397
  binaryPath,
400
398
  ["serve", "--host", "127.0.0.1", "--port", String(port), "--timeout", "604800"],
401
399
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -452,7 +450,31 @@ stderr: ${stderrData.slice(0, 2e3)}` : "")
452
450
  });
453
451
 
454
452
  // src/buffers.ts
455
- var SessionBuffers;
453
+ var buffers_exports = {};
454
+ __export(buffers_exports, {
455
+ SessionBuffers: () => SessionBuffers,
456
+ addConsoleEntry: () => addConsoleEntry,
457
+ addNetworkEntry: () => addNetworkEntry,
458
+ consoleBuffer: () => consoleBuffer,
459
+ consoleTotalAdded: () => consoleTotalAdded,
460
+ networkBuffer: () => networkBuffer,
461
+ networkTotalAdded: () => networkTotalAdded
462
+ });
463
+ function addConsoleEntry(entry) {
464
+ if (consoleBuffer.length >= DEFAULTS.BUFFER_HIGH_WATER_MARK) {
465
+ consoleBuffer.shift();
466
+ }
467
+ consoleBuffer.push(entry);
468
+ consoleTotalAdded++;
469
+ }
470
+ function addNetworkEntry(entry) {
471
+ if (networkBuffer.length >= DEFAULTS.BUFFER_HIGH_WATER_MARK) {
472
+ networkBuffer.shift();
473
+ }
474
+ networkBuffer.push(entry);
475
+ networkTotalAdded++;
476
+ }
477
+ var SessionBuffers, consoleBuffer, networkBuffer, consoleTotalAdded, networkTotalAdded;
456
478
  var init_buffers = __esm({
457
479
  "src/buffers.ts"() {
458
480
  "use strict";
@@ -480,25 +502,51 @@ var init_buffers = __esm({
480
502
  this.networkTotalAdded++;
481
503
  }
482
504
  };
505
+ consoleBuffer = [];
506
+ networkBuffer = [];
507
+ consoleTotalAdded = 0;
508
+ networkTotalAdded = 0;
509
+ }
510
+ });
511
+
512
+ // src/sanitize.ts
513
+ function sanitizeName(name) {
514
+ const sanitized = name.replace(/[\/\\]/g, "_").replace(/\.\./g, "_");
515
+ if (!sanitized || /^[._]+$/.test(sanitized)) {
516
+ throw new Error(`Invalid name: "${name}"`);
517
+ }
518
+ return sanitized;
519
+ }
520
+ var init_sanitize = __esm({
521
+ "src/sanitize.ts"() {
522
+ "use strict";
483
523
  }
484
524
  });
485
525
 
486
526
  // src/browser-manager.ts
487
- import { chromium, devices as playwrightDevices } from "playwright";
527
+ var browser_manager_exports = {};
528
+ __export(browser_manager_exports, {
529
+ BrowserManager: () => BrowserManager,
530
+ deleteProfile: () => deleteProfile,
531
+ getProfileDir: () => getProfileDir,
532
+ listDevices: () => listDevices,
533
+ listProfiles: () => listProfiles,
534
+ resolveDevice: () => resolveDevice
535
+ });
488
536
  function resolveDevice(name) {
489
537
  const alias = DEVICE_ALIASES[name.toLowerCase()];
490
538
  const aliasTarget = alias || name;
491
539
  if (CUSTOM_DEVICES[aliasTarget]) {
492
540
  return CUSTOM_DEVICES[aliasTarget];
493
541
  }
494
- if (playwrightDevices[aliasTarget]) {
495
- return playwrightDevices[aliasTarget];
542
+ if (import_playwright.devices[aliasTarget]) {
543
+ return import_playwright.devices[aliasTarget];
496
544
  }
497
545
  const lower = name.toLowerCase();
498
546
  for (const [key, desc] of Object.entries(CUSTOM_DEVICES)) {
499
547
  if (key.toLowerCase() === lower) return desc;
500
548
  }
501
- for (const [key, desc] of Object.entries(playwrightDevices)) {
549
+ for (const [key, desc] of Object.entries(import_playwright.devices)) {
502
550
  if (key.toLowerCase() === lower) {
503
551
  return desc;
504
552
  }
@@ -508,15 +556,56 @@ function resolveDevice(name) {
508
556
  function listDevices() {
509
557
  const all = /* @__PURE__ */ new Set([
510
558
  ...Object.keys(CUSTOM_DEVICES),
511
- ...Object.keys(playwrightDevices)
559
+ ...Object.keys(import_playwright.devices)
512
560
  ]);
513
561
  return [...all].sort();
514
562
  }
515
- var DEVICE_ALIASES, CUSTOM_DEVICES, BrowserManager;
563
+ function getProfileDir(localDir, name) {
564
+ const sanitized = sanitizeName(name);
565
+ if (!sanitized) throw new Error("Invalid profile name");
566
+ return path4.join(localDir, "profiles", sanitized);
567
+ }
568
+ function listProfiles(localDir) {
569
+ const profilesDir = path4.join(localDir, "profiles");
570
+ if (!fs4.existsSync(profilesDir)) return [];
571
+ return fs4.readdirSync(profilesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
572
+ const dir = path4.join(profilesDir, d.name);
573
+ const stat = fs4.statSync(dir);
574
+ let totalSize = 0;
575
+ try {
576
+ const files = fs4.readdirSync(dir, { recursive: true, withFileTypes: true });
577
+ for (const f of files) {
578
+ if (f.isFile()) {
579
+ try {
580
+ totalSize += fs4.statSync(path4.join(f.parentPath || f.path || dir, f.name)).size;
581
+ } catch {
582
+ }
583
+ }
584
+ }
585
+ } catch {
586
+ }
587
+ const sizeMB = (totalSize / 1024 / 1024).toFixed(1);
588
+ return {
589
+ name: d.name,
590
+ size: `${sizeMB}MB`,
591
+ lastUsed: stat.mtime.toISOString().split("T")[0]
592
+ };
593
+ });
594
+ }
595
+ function deleteProfile(localDir, name) {
596
+ const dir = getProfileDir(localDir, name);
597
+ if (!fs4.existsSync(dir)) throw new Error(`Profile "${name}" not found`);
598
+ fs4.rmSync(dir, { recursive: true, force: true });
599
+ }
600
+ var path4, fs4, import_playwright, DEVICE_ALIASES, CUSTOM_DEVICES, BrowserManager;
516
601
  var init_browser_manager = __esm({
517
602
  "src/browser-manager.ts"() {
518
603
  "use strict";
604
+ path4 = __toESM(require("path"), 1);
605
+ fs4 = __toESM(require("fs"), 1);
606
+ import_playwright = require("playwright");
519
607
  init_buffers();
608
+ init_sanitize();
520
609
  DEVICE_ALIASES = {
521
610
  "iphone": "iPhone 15",
522
611
  "iphone-12": "iPhone 12",
@@ -626,6 +715,8 @@ var init_browser_manager = __esm({
626
715
  domainFilter = null;
627
716
  // Whether this instance owns (and should close) the Browser process
628
717
  ownsBrowser = false;
718
+ // Whether this instance uses a persistent browser context (profile mode)
719
+ isPersistent = false;
629
720
  constructor(buffers) {
630
721
  this.buffers = buffers || new SessionBuffers();
631
722
  }
@@ -643,7 +734,7 @@ var init_browser_manager = __esm({
643
734
  * This instance owns the browser and will close it on close().
644
735
  */
645
736
  async launch(onCrash) {
646
- this.browser = await chromium.launch({ headless: true });
737
+ this.browser = await import_playwright.chromium.launch({ headless: true });
647
738
  this.ownsBrowser = true;
648
739
  this.browser.on("disconnected", () => {
649
740
  if (onCrash) onCrash();
@@ -668,7 +759,66 @@ var init_browser_manager = __esm({
668
759
  });
669
760
  await this.newTab();
670
761
  }
762
+ /**
763
+ * Launch with a persistent browser profile directory.
764
+ * Data (cookies, localStorage, cache) persists across restarts.
765
+ * The context IS the browser — closing it closes everything.
766
+ */
767
+ async launchPersistent(profileDir, onCrash) {
768
+ let context;
769
+ try {
770
+ context = await import_playwright.chromium.launchPersistentContext(profileDir, {
771
+ headless: process.env.BROWSE_HEADED !== "1",
772
+ viewport: { width: 1920, height: 1080 },
773
+ ...this.customUserAgent ? { userAgent: this.customUserAgent } : {}
774
+ });
775
+ } catch (err) {
776
+ if (err.message?.includes("Failed to launch") || err.message?.includes("Target closed")) {
777
+ const fs16 = await import("fs");
778
+ console.error(`[browse] Profile directory corrupted, recreating: ${profileDir}`);
779
+ fs16.rmSync(profileDir, { recursive: true, force: true });
780
+ context = await import_playwright.chromium.launchPersistentContext(profileDir, {
781
+ headless: process.env.BROWSE_HEADED !== "1",
782
+ viewport: { width: 1920, height: 1080 }
783
+ });
784
+ } else {
785
+ throw err;
786
+ }
787
+ }
788
+ this.context = context;
789
+ this.browser = context.browser();
790
+ this.isPersistent = true;
791
+ this.ownsBrowser = true;
792
+ if (this.browser) {
793
+ this.browser.on("disconnected", () => {
794
+ if (onCrash) onCrash();
795
+ });
796
+ }
797
+ const pages = context.pages();
798
+ if (pages.length > 0) {
799
+ for (const page of pages) {
800
+ const tabId = this.nextTabId++;
801
+ this.wirePageEvents(page);
802
+ this.pages.set(tabId, page);
803
+ this.activeTabId = tabId;
804
+ }
805
+ } else {
806
+ await this.newTab();
807
+ }
808
+ }
671
809
  async close() {
810
+ if (this.isPersistent) {
811
+ this.pages.clear();
812
+ this.tabSnapshots.clear();
813
+ this.refMap.clear();
814
+ if (this.context) {
815
+ await this.context.close().catch(() => {
816
+ });
817
+ this.context = null;
818
+ this.browser = null;
819
+ }
820
+ return;
821
+ }
672
822
  for (const [, page] of this.pages) {
673
823
  await page.close().catch(() => {
674
824
  });
@@ -690,6 +840,9 @@ var init_browser_manager = __esm({
690
840
  isHealthy() {
691
841
  return this.browser !== null && this.browser.isConnected();
692
842
  }
843
+ getIsPersistent() {
844
+ return this.isPersistent;
845
+ }
693
846
  // ─── Tab Management ────────────────────────────────────────
694
847
  async newTab(url) {
695
848
  if (!this.context) throw new Error("Browser not launched");
@@ -950,6 +1103,11 @@ var init_browser_manager = __esm({
950
1103
  * Cannot preserve: localStorage/sessionStorage (bound to old context).
951
1104
  */
952
1105
  async recreateContext(contextOptions) {
1106
+ if (this.isPersistent) {
1107
+ throw new Error(
1108
+ "Cannot change device/viewport/user-agent in profile mode \u2014 profiles use a fixed browser context. Use --session instead."
1109
+ );
1110
+ }
953
1111
  if (!this.browser) return;
954
1112
  if (this.videoRecording && !contextOptions.recordVideo) {
955
1113
  contextOptions = {
@@ -1149,8 +1307,8 @@ var init_browser_manager = __esm({
1149
1307
  // ─── Video Recording ──────────────────────────────────────
1150
1308
  async startVideoRecording(dir) {
1151
1309
  if (this.videoRecording) throw new Error("Video recording already active");
1152
- const fs15 = await import("fs");
1153
- fs15.mkdirSync(dir, { recursive: true });
1310
+ const fs16 = await import("fs");
1311
+ fs16.mkdirSync(dir, { recursive: true });
1154
1312
  this.videoRecording = { dir, startedAt: Date.now() };
1155
1313
  const viewport = this.currentDevice?.viewport || { width: 1920, height: 1080 };
1156
1314
  await this.recreateContext({
@@ -1425,24 +1583,7 @@ var init_domain_filter = __esm({
1425
1583
  }
1426
1584
  });
1427
1585
 
1428
- // src/sanitize.ts
1429
- function sanitizeName(name) {
1430
- const sanitized = name.replace(/[\/\\]/g, "_").replace(/\.\./g, "_");
1431
- if (!sanitized || /^[._]+$/.test(sanitized)) {
1432
- throw new Error(`Invalid name: "${name}"`);
1433
- }
1434
- return sanitized;
1435
- }
1436
- var init_sanitize = __esm({
1437
- "src/sanitize.ts"() {
1438
- "use strict";
1439
- }
1440
- });
1441
-
1442
1586
  // src/encryption.ts
1443
- import * as crypto from "crypto";
1444
- import * as fs4 from "fs";
1445
- import * as path4 from "path";
1446
1587
  function resolveEncryptionKey(localDir) {
1447
1588
  const envKey = process.env.BROWSE_ENCRYPTION_KEY;
1448
1589
  if (envKey) {
@@ -1451,13 +1592,13 @@ function resolveEncryptionKey(localDir) {
1451
1592
  }
1452
1593
  return Buffer.from(envKey, "hex");
1453
1594
  }
1454
- const keyPath = path4.join(localDir, ".encryption-key");
1455
- if (fs4.existsSync(keyPath)) {
1456
- const hex = fs4.readFileSync(keyPath, "utf-8").trim();
1595
+ const keyPath = path5.join(localDir, ".encryption-key");
1596
+ if (fs5.existsSync(keyPath)) {
1597
+ const hex = fs5.readFileSync(keyPath, "utf-8").trim();
1457
1598
  return Buffer.from(hex, "hex");
1458
1599
  }
1459
1600
  const key = crypto.randomBytes(32);
1460
- fs4.writeFileSync(keyPath, key.toString("hex") + "\n", { mode: 384 });
1601
+ fs5.writeFileSync(keyPath, key.toString("hex") + "\n", { mode: 384 });
1461
1602
  return key;
1462
1603
  }
1463
1604
  function encrypt(plaintext, key) {
@@ -1483,9 +1624,13 @@ function decrypt(ciphertext, iv, authTag, key) {
1483
1624
  ]);
1484
1625
  return decrypted.toString("utf-8");
1485
1626
  }
1627
+ var crypto, fs5, path5;
1486
1628
  var init_encryption = __esm({
1487
1629
  "src/encryption.ts"() {
1488
1630
  "use strict";
1631
+ crypto = __toESM(require("crypto"), 1);
1632
+ fs5 = __toESM(require("fs"), 1);
1633
+ path5 = __toESM(require("path"), 1);
1489
1634
  }
1490
1635
  });
1491
1636
 
@@ -1497,8 +1642,6 @@ __export(session_persist_exports, {
1497
1642
  loadSessionState: () => loadSessionState,
1498
1643
  saveSessionState: () => saveSessionState
1499
1644
  });
1500
- import * as fs5 from "fs";
1501
- import * as path5 from "path";
1502
1645
  async function saveSessionState(sessionDir, context, encryptionKey) {
1503
1646
  try {
1504
1647
  const state = await context.storageState();
@@ -1510,20 +1653,20 @@ async function saveSessionState(sessionDir, context, encryptionKey) {
1510
1653
  } else {
1511
1654
  content = json;
1512
1655
  }
1513
- fs5.mkdirSync(sessionDir, { recursive: true });
1514
- fs5.writeFileSync(path5.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
1656
+ fs6.mkdirSync(sessionDir, { recursive: true });
1657
+ fs6.writeFileSync(path6.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
1515
1658
  } catch (err) {
1516
1659
  console.log(`[session-persist] Warning: failed to save state: ${err.message}`);
1517
1660
  }
1518
1661
  }
1519
1662
  async function loadSessionState(sessionDir, context, encryptionKey) {
1520
- const statePath = path5.join(sessionDir, STATE_FILENAME);
1521
- if (!fs5.existsSync(statePath)) {
1663
+ const statePath = path6.join(sessionDir, STATE_FILENAME);
1664
+ if (!fs6.existsSync(statePath)) {
1522
1665
  return false;
1523
1666
  }
1524
1667
  let stateData;
1525
1668
  try {
1526
- const raw = fs5.readFileSync(statePath, "utf-8");
1669
+ const raw = fs6.readFileSync(statePath, "utf-8");
1527
1670
  const parsed = JSON.parse(raw);
1528
1671
  if (parsed.encrypted) {
1529
1672
  if (!encryptionKey) {
@@ -1579,24 +1722,24 @@ async function loadSessionState(sessionDir, context, encryptionKey) {
1579
1722
  }
1580
1723
  }
1581
1724
  function hasPersistedState(sessionDir) {
1582
- return fs5.existsSync(path5.join(sessionDir, STATE_FILENAME));
1725
+ return fs6.existsSync(path6.join(sessionDir, STATE_FILENAME));
1583
1726
  }
1584
1727
  function cleanOldStates(localDir, maxAgeDays) {
1585
1728
  const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
1586
1729
  const now = Date.now();
1587
1730
  let deleted = 0;
1588
- const statesDir = path5.join(localDir, "states");
1589
- if (fs5.existsSync(statesDir)) {
1731
+ const statesDir = path6.join(localDir, "states");
1732
+ if (fs6.existsSync(statesDir)) {
1590
1733
  try {
1591
- const entries = fs5.readdirSync(statesDir);
1734
+ const entries = fs6.readdirSync(statesDir);
1592
1735
  for (const entry of entries) {
1593
1736
  if (!entry.endsWith(".json")) continue;
1594
- const filePath = path5.join(statesDir, entry);
1737
+ const filePath = path6.join(statesDir, entry);
1595
1738
  try {
1596
- const stat = fs5.statSync(filePath);
1739
+ const stat = fs6.statSync(filePath);
1597
1740
  if (!stat.isFile()) continue;
1598
1741
  if (now - stat.mtimeMs > maxAgeMs) {
1599
- fs5.unlinkSync(filePath);
1742
+ fs6.unlinkSync(filePath);
1600
1743
  deleted++;
1601
1744
  }
1602
1745
  } catch (_) {
@@ -1605,23 +1748,23 @@ function cleanOldStates(localDir, maxAgeDays) {
1605
1748
  } catch (_) {
1606
1749
  }
1607
1750
  }
1608
- const sessionsDir = path5.join(localDir, "sessions");
1609
- if (fs5.existsSync(sessionsDir)) {
1751
+ const sessionsDir = path6.join(localDir, "sessions");
1752
+ if (fs6.existsSync(sessionsDir)) {
1610
1753
  try {
1611
- const sessionDirs = fs5.readdirSync(sessionsDir);
1754
+ const sessionDirs = fs6.readdirSync(sessionsDir);
1612
1755
  for (const dir of sessionDirs) {
1613
- const dirPath = path5.join(sessionsDir, dir);
1756
+ const dirPath = path6.join(sessionsDir, dir);
1614
1757
  try {
1615
- const dirStat = fs5.statSync(dirPath);
1758
+ const dirStat = fs6.statSync(dirPath);
1616
1759
  if (!dirStat.isDirectory()) continue;
1617
1760
  } catch (_) {
1618
1761
  continue;
1619
1762
  }
1620
- const statePath = path5.join(dirPath, STATE_FILENAME);
1763
+ const statePath = path6.join(dirPath, STATE_FILENAME);
1621
1764
  try {
1622
- const stat = fs5.statSync(statePath);
1765
+ const stat = fs6.statSync(statePath);
1623
1766
  if (now - stat.mtimeMs > maxAgeMs) {
1624
- fs5.unlinkSync(statePath);
1767
+ fs6.unlinkSync(statePath);
1625
1768
  deleted++;
1626
1769
  }
1627
1770
  } catch (_) {
@@ -1632,19 +1775,19 @@ function cleanOldStates(localDir, maxAgeDays) {
1632
1775
  }
1633
1776
  return { deleted };
1634
1777
  }
1635
- var STATE_FILENAME;
1778
+ var fs6, path6, STATE_FILENAME;
1636
1779
  var init_session_persist = __esm({
1637
1780
  "src/session-persist.ts"() {
1638
1781
  "use strict";
1782
+ fs6 = __toESM(require("fs"), 1);
1783
+ path6 = __toESM(require("path"), 1);
1639
1784
  init_encryption();
1640
1785
  STATE_FILENAME = "state.json";
1641
1786
  }
1642
1787
  });
1643
1788
 
1644
1789
  // src/session-manager.ts
1645
- import * as fs6 from "fs";
1646
- import * as path6 from "path";
1647
- var SessionManager;
1790
+ var fs7, path7, SessionManager;
1648
1791
  var init_session_manager = __esm({
1649
1792
  "src/session-manager.ts"() {
1650
1793
  "use strict";
@@ -1654,6 +1797,8 @@ var init_session_manager = __esm({
1654
1797
  init_sanitize();
1655
1798
  init_session_persist();
1656
1799
  init_encryption();
1800
+ fs7 = __toESM(require("fs"), 1);
1801
+ path7 = __toESM(require("path"), 1);
1657
1802
  SessionManager = class {
1658
1803
  sessions = /* @__PURE__ */ new Map();
1659
1804
  browser;
@@ -1706,8 +1851,8 @@ var init_session_manager = __esm({
1706
1851
  }
1707
1852
  return session;
1708
1853
  }
1709
- const outputDir = path6.join(this.localDir, "sessions", sanitizeName(sessionId));
1710
- fs6.mkdirSync(outputDir, { recursive: true });
1854
+ const outputDir = path7.join(this.localDir, "sessions", sanitizeName(sessionId));
1855
+ fs7.mkdirSync(outputDir, { recursive: true });
1711
1856
  const buffers = new SessionBuffers();
1712
1857
  const manager = new BrowserManager(buffers);
1713
1858
  await manager.launchWithBrowser(this.browser);
@@ -1843,7 +1988,6 @@ var read_exports = {};
1843
1988
  __export(read_exports, {
1844
1989
  handleReadCommand: () => handleReadCommand
1845
1990
  });
1846
- import * as fs7 from "fs";
1847
1991
  async function handleReadCommand(command, args, bm, buffers) {
1848
1992
  const page = bm.getPage();
1849
1993
  const evalCtx = await bm.getFrameContext() || page;
@@ -1938,8 +2082,8 @@ async function handleReadCommand(command, args, bm, buffers) {
1938
2082
  case "eval": {
1939
2083
  const filePath = args[0];
1940
2084
  if (!filePath) throw new Error("Usage: browse eval <js-file>");
1941
- if (!fs7.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
1942
- const code = fs7.readFileSync(filePath, "utf-8");
2085
+ if (!fs8.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
2086
+ const code = fs8.readFileSync(filePath, "utf-8");
1943
2087
  const result = await evalCtx.evaluate(code);
1944
2088
  return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? "");
1945
2089
  }
@@ -2177,11 +2321,13 @@ async function handleReadCommand(command, args, bm, buffers) {
2177
2321
  throw new Error(`Unknown read command: ${command}`);
2178
2322
  }
2179
2323
  }
2324
+ var fs8;
2180
2325
  var init_read = __esm({
2181
2326
  "src/commands/read.ts"() {
2182
2327
  "use strict";
2183
2328
  init_browser_manager();
2184
2329
  init_constants();
2330
+ fs8 = __toESM(require("fs"), 1);
2185
2331
  }
2186
2332
  });
2187
2333
 
@@ -2190,7 +2336,6 @@ var write_exports = {};
2190
2336
  __export(write_exports, {
2191
2337
  handleWriteCommand: () => handleWriteCommand
2192
2338
  });
2193
- import * as fs8 from "fs";
2194
2339
  async function rebuildRoutes(context, bm, domainFilter) {
2195
2340
  await context.unrouteAll();
2196
2341
  for (const r of bm.getUserRoutes()) {
@@ -2394,14 +2539,14 @@ async function handleWriteCommand(command, args, bm, domainFilter) {
2394
2539
  const file = args[1];
2395
2540
  if (!file) throw new Error("Usage: browse cookie export <file>");
2396
2541
  const cookies = await page.context().cookies();
2397
- fs8.writeFileSync(file, JSON.stringify(cookies, null, 2));
2542
+ fs9.writeFileSync(file, JSON.stringify(cookies, null, 2));
2398
2543
  return `Exported ${cookies.length} cookie(s) to ${file}`;
2399
2544
  }
2400
2545
  if (cookieStr === "import") {
2401
2546
  const file = args[1];
2402
2547
  if (!file) throw new Error("Usage: browse cookie import <file>");
2403
- if (!fs8.existsSync(file)) throw new Error(`File not found: ${file}`);
2404
- const cookies = JSON.parse(fs8.readFileSync(file, "utf-8"));
2548
+ if (!fs9.existsSync(file)) throw new Error(`File not found: ${file}`);
2549
+ const cookies = JSON.parse(fs9.readFileSync(file, "utf-8"));
2405
2550
  if (!Array.isArray(cookies)) throw new Error("Cookie file must contain a JSON array of cookie objects");
2406
2551
  await page.context().addCookies(cookies);
2407
2552
  return `Imported ${cookies.length} cookie(s) from ${file}`;
@@ -2468,7 +2613,7 @@ Note: Cookies and tab URLs preserved. localStorage/sessionStorage were reset (Pl
2468
2613
  const [selector, ...filePaths] = args;
2469
2614
  if (!selector || filePaths.length === 0) throw new Error("Usage: browse upload <selector> <file1> [file2] ...");
2470
2615
  for (const fp of filePaths) {
2471
- if (!fs8.existsSync(fp)) throw new Error(`File not found: ${fp}`);
2616
+ if (!fs9.existsSync(fp)) throw new Error(`File not found: ${fp}`);
2472
2617
  }
2473
2618
  const resolved = bm.resolveRef(selector);
2474
2619
  if ("locator" in resolved) {
@@ -2804,11 +2949,13 @@ Note: Cookies and tab URLs preserved. localStorage/sessionStorage were reset (Pl
2804
2949
  throw new Error(`Unknown write command: ${command}`);
2805
2950
  }
2806
2951
  }
2952
+ var fs9;
2807
2953
  var init_write = __esm({
2808
2954
  "src/commands/write.ts"() {
2809
2955
  "use strict";
2810
2956
  init_browser_manager();
2811
2957
  init_constants();
2958
+ fs9 = __toESM(require("fs"), 1);
2812
2959
  }
2813
2960
  });
2814
2961
 
@@ -3535,11 +3682,11 @@ var init_lib = __esm({
3535
3682
  }
3536
3683
  }
3537
3684
  },
3538
- addToPath: function addToPath(path12, added, removed, oldPosInc, options) {
3539
- var last = path12.lastComponent;
3685
+ addToPath: function addToPath(path13, added, removed, oldPosInc, options) {
3686
+ var last = path13.lastComponent;
3540
3687
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
3541
3688
  return {
3542
- oldPos: path12.oldPos + oldPosInc,
3689
+ oldPos: path13.oldPos + oldPosInc,
3543
3690
  lastComponent: {
3544
3691
  count: last.count + 1,
3545
3692
  added,
@@ -3549,7 +3696,7 @@ var init_lib = __esm({
3549
3696
  };
3550
3697
  } else {
3551
3698
  return {
3552
- oldPos: path12.oldPos + oldPosInc,
3699
+ oldPos: path13.oldPos + oldPosInc,
3553
3700
  lastComponent: {
3554
3701
  count: 1,
3555
3702
  added,
@@ -3607,7 +3754,7 @@ var init_lib = __esm({
3607
3754
  tokenize: function tokenize(value) {
3608
3755
  return Array.from(value);
3609
3756
  },
3610
- join: function join8(chars) {
3757
+ join: function join9(chars) {
3611
3758
  return chars.join("");
3612
3759
  },
3613
3760
  postProcess: function postProcess(changeObjects) {
@@ -3772,23 +3919,23 @@ var policy_exports = {};
3772
3919
  __export(policy_exports, {
3773
3920
  PolicyChecker: () => PolicyChecker
3774
3921
  });
3775
- import * as fs9 from "fs";
3776
- import * as path7 from "path";
3777
3922
  function findFileUpward(filename) {
3778
3923
  let dir = process.cwd();
3779
3924
  for (let i = 0; i < 20; i++) {
3780
- const candidate = path7.join(dir, filename);
3781
- if (fs9.existsSync(candidate)) return candidate;
3782
- const parent = path7.dirname(dir);
3925
+ const candidate = path8.join(dir, filename);
3926
+ if (fs10.existsSync(candidate)) return candidate;
3927
+ const parent = path8.dirname(dir);
3783
3928
  if (parent === dir) break;
3784
3929
  dir = parent;
3785
3930
  }
3786
3931
  return null;
3787
3932
  }
3788
- var PolicyChecker;
3933
+ var fs10, path8, PolicyChecker;
3789
3934
  var init_policy = __esm({
3790
3935
  "src/policy.ts"() {
3791
3936
  "use strict";
3937
+ fs10 = __toESM(require("fs"), 1);
3938
+ path8 = __toESM(require("path"), 1);
3792
3939
  PolicyChecker = class {
3793
3940
  filePath = null;
3794
3941
  lastMtime = 0;
@@ -3807,10 +3954,10 @@ var init_policy = __esm({
3807
3954
  reload() {
3808
3955
  if (!this.filePath) return;
3809
3956
  try {
3810
- const stat = fs9.statSync(this.filePath);
3957
+ const stat = fs10.statSync(this.filePath);
3811
3958
  if (stat.mtimeMs === this.lastMtime) return;
3812
3959
  this.lastMtime = stat.mtimeMs;
3813
- const raw = fs9.readFileSync(this.filePath, "utf-8");
3960
+ const raw = fs10.readFileSync(this.filePath, "utf-8");
3814
3961
  this.policy = JSON.parse(raw);
3815
3962
  } catch {
3816
3963
  }
@@ -3841,7 +3988,6 @@ __export(png_compare_exports, {
3841
3988
  encodePNG: () => encodePNG,
3842
3989
  generateDiffImage: () => generateDiffImage
3843
3990
  });
3844
- import * as zlib from "zlib";
3845
3991
  function decodePNG(buf) {
3846
3992
  for (let i = 0; i < 8; i++) {
3847
3993
  if (buf[i] !== PNG_MAGIC[i]) throw new Error("Not a valid PNG file");
@@ -4016,10 +4162,11 @@ function compareScreenshots(baselineBuf, currentBuf, thresholdPct = 0.1, colorTh
4016
4162
  }
4017
4163
  return result;
4018
4164
  }
4019
- var PNG_MAGIC;
4165
+ var zlib, PNG_MAGIC;
4020
4166
  var init_png_compare = __esm({
4021
4167
  "src/png-compare.ts"() {
4022
4168
  "use strict";
4169
+ zlib = __toESM(require("zlib"), 1);
4023
4170
  PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10];
4024
4171
  }
4025
4172
  });
@@ -4029,8 +4176,6 @@ var auth_vault_exports = {};
4029
4176
  __export(auth_vault_exports, {
4030
4177
  AuthVault: () => AuthVault
4031
4178
  });
4032
- import * as fs10 from "fs";
4033
- import * as path8 from "path";
4034
4179
  async function autoDetectSelector(page, field) {
4035
4180
  if (field === "username") {
4036
4181
  const candidates2 = [
@@ -4077,10 +4222,12 @@ async function autoDetectSelector(page, field) {
4077
4222
  }
4078
4223
  throw new Error("Could not auto-detect submit button.");
4079
4224
  }
4080
- var AuthVault;
4225
+ var fs11, path9, AuthVault;
4081
4226
  var init_auth_vault = __esm({
4082
4227
  "src/auth-vault.ts"() {
4083
4228
  "use strict";
4229
+ fs11 = __toESM(require("fs"), 1);
4230
+ path9 = __toESM(require("path"), 1);
4084
4231
  init_constants();
4085
4232
  init_encryption();
4086
4233
  init_sanitize();
@@ -4088,11 +4235,11 @@ var init_auth_vault = __esm({
4088
4235
  authDir;
4089
4236
  encryptionKey;
4090
4237
  constructor(localDir) {
4091
- this.authDir = path8.join(localDir, "auth");
4238
+ this.authDir = path9.join(localDir, "auth");
4092
4239
  this.encryptionKey = resolveEncryptionKey(localDir);
4093
4240
  }
4094
4241
  save(name, url, username, password, selectors) {
4095
- fs10.mkdirSync(this.authDir, { recursive: true });
4242
+ fs11.mkdirSync(this.authDir, { recursive: true });
4096
4243
  const { ciphertext, iv, authTag } = encrypt(password, this.encryptionKey);
4097
4244
  const now = (/* @__PURE__ */ new Date()).toISOString();
4098
4245
  const credential = {
@@ -4109,15 +4256,15 @@ var init_auth_vault = __esm({
4109
4256
  createdAt: now,
4110
4257
  updatedAt: now
4111
4258
  };
4112
- const filePath = path8.join(this.authDir, `${sanitizeName(name)}.json`);
4113
- fs10.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
4259
+ const filePath = path9.join(this.authDir, `${sanitizeName(name)}.json`);
4260
+ fs11.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
4114
4261
  }
4115
4262
  load(name) {
4116
- const filePath = path8.join(this.authDir, `${sanitizeName(name)}.json`);
4117
- if (!fs10.existsSync(filePath)) {
4263
+ const filePath = path9.join(this.authDir, `${sanitizeName(name)}.json`);
4264
+ if (!fs11.existsSync(filePath)) {
4118
4265
  throw new Error(`Credential "${name}" not found. Run "browse auth list" to see saved credentials.`);
4119
4266
  }
4120
- return JSON.parse(fs10.readFileSync(filePath, "utf-8"));
4267
+ return JSON.parse(fs11.readFileSync(filePath, "utf-8"));
4121
4268
  }
4122
4269
  async login(name, bm) {
4123
4270
  const cred = this.load(name);
@@ -4138,11 +4285,11 @@ var init_auth_vault = __esm({
4138
4285
  return `Logged in as ${cred.username} at ${page.url()}`;
4139
4286
  }
4140
4287
  list() {
4141
- if (!fs10.existsSync(this.authDir)) return [];
4142
- const files = fs10.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
4288
+ if (!fs11.existsSync(this.authDir)) return [];
4289
+ const files = fs11.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
4143
4290
  return files.map((f) => {
4144
4291
  try {
4145
- const data = JSON.parse(fs10.readFileSync(path8.join(this.authDir, f), "utf-8"));
4292
+ const data = JSON.parse(fs11.readFileSync(path9.join(this.authDir, f), "utf-8"));
4146
4293
  return {
4147
4294
  name: data.name,
4148
4295
  url: data.url,
@@ -4156,11 +4303,11 @@ var init_auth_vault = __esm({
4156
4303
  }).filter(Boolean);
4157
4304
  }
4158
4305
  delete(name) {
4159
- const filePath = path8.join(this.authDir, `${sanitizeName(name)}.json`);
4160
- if (!fs10.existsSync(filePath)) {
4306
+ const filePath = path9.join(this.authDir, `${sanitizeName(name)}.json`);
4307
+ if (!fs11.existsSync(filePath)) {
4161
4308
  throw new Error(`Credential "${name}" not found.`);
4162
4309
  }
4163
- fs10.unlinkSync(filePath);
4310
+ fs11.unlinkSync(filePath);
4164
4311
  }
4165
4312
  };
4166
4313
  }
@@ -4174,18 +4321,12 @@ __export(cookie_import_exports, {
4174
4321
  importCookies: () => importCookies,
4175
4322
  listDomains: () => listDomains
4176
4323
  });
4177
- import Database from "better-sqlite3";
4178
- import { spawn as spawn2 } from "child_process";
4179
- import * as crypto2 from "crypto";
4180
- import * as fs11 from "fs";
4181
- import * as path9 from "path";
4182
- import * as os2 from "os";
4183
4324
  function findInstalledBrowsers() {
4184
- const appSupport = path9.join(os2.homedir(), "Library", "Application Support");
4325
+ const appSupport = path10.join(os2.homedir(), "Library", "Application Support");
4185
4326
  return BROWSER_REGISTRY.filter((b) => {
4186
- const dbPath = path9.join(appSupport, b.dataDir, "Default", "Cookies");
4327
+ const dbPath = path10.join(appSupport, b.dataDir, "Default", "Cookies");
4187
4328
  try {
4188
- return fs11.existsSync(dbPath);
4329
+ return fs12.existsSync(dbPath);
4189
4330
  } catch {
4190
4331
  return false;
4191
4332
  }
@@ -4268,9 +4409,9 @@ function validateProfile(profile) {
4268
4409
  }
4269
4410
  function getCookieDbPath(browser2, profile) {
4270
4411
  validateProfile(profile);
4271
- const appSupport = path9.join(os2.homedir(), "Library", "Application Support");
4272
- const dbPath = path9.join(appSupport, browser2.dataDir, profile, "Cookies");
4273
- if (!fs11.existsSync(dbPath)) {
4412
+ const appSupport = path10.join(os2.homedir(), "Library", "Application Support");
4413
+ const dbPath = path10.join(appSupport, browser2.dataDir, profile, "Cookies");
4414
+ if (!fs12.existsSync(dbPath)) {
4274
4415
  throw new CookieImportError(
4275
4416
  `${browser2.name} is not installed (no cookie database at ${dbPath})`,
4276
4417
  "not_installed"
@@ -4280,7 +4421,7 @@ function getCookieDbPath(browser2, profile) {
4280
4421
  }
4281
4422
  function openDb(dbPath, browserName) {
4282
4423
  try {
4283
- return new Database(dbPath, { readonly: true });
4424
+ return new import_better_sqlite3.default(dbPath, { readonly: true });
4284
4425
  } catch (err) {
4285
4426
  if (err.message?.includes("SQLITE_BUSY") || err.message?.includes("database is locked")) {
4286
4427
  return openDbFromCopy(dbPath, browserName);
@@ -4297,32 +4438,32 @@ function openDb(dbPath, browserName) {
4297
4438
  function openDbFromCopy(dbPath, browserName) {
4298
4439
  const tmpPath = `/tmp/browse-cookies-${browserName.toLowerCase()}-${crypto2.randomUUID()}.db`;
4299
4440
  try {
4300
- fs11.copyFileSync(dbPath, tmpPath);
4441
+ fs12.copyFileSync(dbPath, tmpPath);
4301
4442
  const walPath = dbPath + "-wal";
4302
4443
  const shmPath = dbPath + "-shm";
4303
- if (fs11.existsSync(walPath)) fs11.copyFileSync(walPath, tmpPath + "-wal");
4304
- if (fs11.existsSync(shmPath)) fs11.copyFileSync(shmPath, tmpPath + "-shm");
4305
- const db = new Database(tmpPath, { readonly: true });
4444
+ if (fs12.existsSync(walPath)) fs12.copyFileSync(walPath, tmpPath + "-wal");
4445
+ if (fs12.existsSync(shmPath)) fs12.copyFileSync(shmPath, tmpPath + "-shm");
4446
+ const db = new import_better_sqlite3.default(tmpPath, { readonly: true });
4306
4447
  const origClose = db.close.bind(db);
4307
4448
  db.close = (() => {
4308
4449
  origClose();
4309
4450
  try {
4310
- fs11.unlinkSync(tmpPath);
4451
+ fs12.unlinkSync(tmpPath);
4311
4452
  } catch {
4312
4453
  }
4313
4454
  try {
4314
- fs11.unlinkSync(tmpPath + "-wal");
4455
+ fs12.unlinkSync(tmpPath + "-wal");
4315
4456
  } catch {
4316
4457
  }
4317
4458
  try {
4318
- fs11.unlinkSync(tmpPath + "-shm");
4459
+ fs12.unlinkSync(tmpPath + "-shm");
4319
4460
  } catch {
4320
4461
  }
4321
4462
  });
4322
4463
  return db;
4323
4464
  } catch {
4324
4465
  try {
4325
- fs11.unlinkSync(tmpPath);
4466
+ fs12.unlinkSync(tmpPath);
4326
4467
  } catch {
4327
4468
  }
4328
4469
  throw new CookieImportError(
@@ -4341,7 +4482,7 @@ async function getDerivedKey(browser2) {
4341
4482
  return derived;
4342
4483
  }
4343
4484
  async function getKeychainPassword(service) {
4344
- const proc = spawn2("security", ["find-generic-password", "-s", service, "-w"], {
4485
+ const proc = (0, import_child_process2.spawn)("security", ["find-generic-password", "-s", service, "-w"], {
4345
4486
  stdio: ["ignore", "pipe", "pipe"]
4346
4487
  });
4347
4488
  let stdout = "";
@@ -4448,10 +4589,16 @@ function mapSameSite(value) {
4448
4589
  return "Lax";
4449
4590
  }
4450
4591
  }
4451
- var CookieImportError, BROWSER_REGISTRY, keyCache, CHROMIUM_EPOCH_OFFSET;
4592
+ var import_better_sqlite3, import_child_process2, crypto2, fs12, path10, os2, CookieImportError, BROWSER_REGISTRY, keyCache, CHROMIUM_EPOCH_OFFSET;
4452
4593
  var init_cookie_import = __esm({
4453
4594
  "src/cookie-import.ts"() {
4454
4595
  "use strict";
4596
+ import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
4597
+ import_child_process2 = require("child_process");
4598
+ crypto2 = __toESM(require("crypto"), 1);
4599
+ fs12 = __toESM(require("fs"), 1);
4600
+ path10 = __toESM(require("path"), 1);
4601
+ os2 = __toESM(require("os"), 1);
4455
4602
  CookieImportError = class extends Error {
4456
4603
  constructor(message, code, action) {
4457
4604
  super(message);
@@ -4620,7 +4767,6 @@ var init_record_export = __esm({
4620
4767
  });
4621
4768
 
4622
4769
  // src/commands/meta.ts
4623
- import * as fs12 from "fs";
4624
4770
  async function handleMetaCommand(command, args, bm, shutdown2, sessionManager2, currentSession) {
4625
4771
  switch (command) {
4626
4772
  // ─── Tabs ──────────────────────────────────────────
@@ -4702,7 +4848,7 @@ async function handleMetaCommand(command, args, bm, shutdown2, sessionManager2,
4702
4848
  const lines = entries.map(
4703
4849
  (e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
4704
4850
  ).join("\n") + "\n";
4705
- fs12.appendFileSync(consolePath, lines);
4851
+ fs13.appendFileSync(consolePath, lines);
4706
4852
  buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
4707
4853
  }
4708
4854
  const newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
@@ -4712,7 +4858,7 @@ async function handleMetaCommand(command, args, bm, shutdown2, sessionManager2,
4712
4858
  const lines = entries.map(
4713
4859
  (e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
4714
4860
  ).join("\n") + "\n";
4715
- fs12.appendFileSync(networkPath, lines);
4861
+ fs13.appendFileSync(networkPath, lines);
4716
4862
  buffers.lastNetworkFlushed = buffers.networkTotalAdded;
4717
4863
  }
4718
4864
  }
@@ -4729,22 +4875,22 @@ async function handleMetaCommand(command, args, bm, shutdown2, sessionManager2,
4729
4875
  const statesDir = `${LOCAL_DIR}/states`;
4730
4876
  const statePath = `${statesDir}/${name}.json`;
4731
4877
  if (subcommand === "list") {
4732
- if (!fs12.existsSync(statesDir)) return "(no saved states)";
4733
- const files = fs12.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
4878
+ if (!fs13.existsSync(statesDir)) return "(no saved states)";
4879
+ const files = fs13.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
4734
4880
  if (files.length === 0) return "(no saved states)";
4735
4881
  const lines = [];
4736
4882
  for (const file of files) {
4737
4883
  const fp = `${statesDir}/${file}`;
4738
- const stat = fs12.statSync(fp);
4884
+ const stat = fs13.statSync(fp);
4739
4885
  lines.push(` ${file.replace(".json", "")} ${stat.size}B ${new Date(stat.mtimeMs).toISOString()}`);
4740
4886
  }
4741
4887
  return lines.join("\n");
4742
4888
  }
4743
4889
  if (subcommand === "show") {
4744
- if (!fs12.existsSync(statePath)) {
4890
+ if (!fs13.existsSync(statePath)) {
4745
4891
  throw new Error(`State file not found: ${statePath}`);
4746
4892
  }
4747
- const data = JSON.parse(fs12.readFileSync(statePath, "utf-8"));
4893
+ const data = JSON.parse(fs13.readFileSync(statePath, "utf-8"));
4748
4894
  const cookieCount = data.cookies?.length || 0;
4749
4895
  const originCount = data.origins?.length || 0;
4750
4896
  const storageItems = (data.origins || []).reduce((sum, o) => sum + (o.localStorage?.length || 0), 0);
@@ -4769,15 +4915,15 @@ async function handleMetaCommand(command, args, bm, shutdown2, sessionManager2,
4769
4915
  const context = bm.getContext();
4770
4916
  if (!context) throw new Error("No browser context");
4771
4917
  const state = await context.storageState();
4772
- fs12.mkdirSync(statesDir, { recursive: true });
4773
- fs12.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
4918
+ fs13.mkdirSync(statesDir, { recursive: true });
4919
+ fs13.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
4774
4920
  return `State saved: ${statePath}`;
4775
4921
  }
4776
4922
  if (subcommand === "load") {
4777
- if (!fs12.existsSync(statePath)) {
4923
+ if (!fs13.existsSync(statePath)) {
4778
4924
  throw new Error(`State file not found: ${statePath}. Run "browse state save ${name}" first.`);
4779
4925
  }
4780
- const stateData = JSON.parse(fs12.readFileSync(statePath, "utf-8"));
4926
+ const stateData = JSON.parse(fs13.readFileSync(statePath, "utf-8"));
4781
4927
  const context = bm.getContext();
4782
4928
  if (!context) throw new Error("No browser context");
4783
4929
  const warnings = [];
@@ -4936,9 +5082,9 @@ ${legend.join("\n")}`;
4936
5082
  try {
4937
5083
  for (const vp of viewports) {
4938
5084
  await page.setViewportSize({ width: vp.width, height: vp.height });
4939
- const path12 = `${prefix}-${vp.name}.png`;
4940
- await page.screenshot({ path: path12, fullPage: true });
4941
- results.push(`${vp.name} (${vp.width}x${vp.height}): ${path12}`);
5085
+ const path13 = `${prefix}-${vp.name}.png`;
5086
+ await page.screenshot({ path: path13, fullPage: true });
5087
+ results.push(`${vp.name} (${vp.width}x${vp.height}): ${path13}`);
4942
5088
  }
4943
5089
  } finally {
4944
5090
  if (originalViewport) {
@@ -5082,13 +5228,13 @@ ${legend.join("\n")}`;
5082
5228
  const diffArgs = args.filter((a) => a !== "--full");
5083
5229
  const baseline = diffArgs[0];
5084
5230
  if (!baseline) throw new Error("Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1] [--full]");
5085
- if (!fs12.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5231
+ if (!fs13.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
5086
5232
  let thresholdPct = 0.1;
5087
5233
  const threshIdx = diffArgs.indexOf("--threshold");
5088
5234
  if (threshIdx !== -1 && diffArgs[threshIdx + 1]) {
5089
5235
  thresholdPct = parseFloat(diffArgs[threshIdx + 1]);
5090
5236
  }
5091
- const baselineBuffer = fs12.readFileSync(baseline);
5237
+ const baselineBuffer = fs13.readFileSync(baseline);
5092
5238
  let currentBuffer;
5093
5239
  let currentPath;
5094
5240
  for (let i = 1; i < diffArgs.length; i++) {
@@ -5102,8 +5248,8 @@ ${legend.join("\n")}`;
5102
5248
  }
5103
5249
  }
5104
5250
  if (currentPath) {
5105
- if (!fs12.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5106
- currentBuffer = fs12.readFileSync(currentPath);
5251
+ if (!fs13.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
5252
+ currentBuffer = fs13.readFileSync(currentPath);
5107
5253
  } else {
5108
5254
  const page = bm.getPage();
5109
5255
  currentBuffer = await page.screenshot({ fullPage: isFullPageDiff });
@@ -5113,7 +5259,7 @@ ${legend.join("\n")}`;
5113
5259
  const extIdx = baseline.lastIndexOf(".");
5114
5260
  const diffPath = extIdx > 0 ? baseline.slice(0, extIdx) + "-diff" + baseline.slice(extIdx) : baseline + "-diff.png";
5115
5261
  if (!result.passed && result.diffImage) {
5116
- fs12.writeFileSync(diffPath, result.diffImage);
5262
+ fs13.writeFileSync(diffPath, result.diffImage);
5117
5263
  }
5118
5264
  return [
5119
5265
  `Pixels: ${result.totalPixels}`,
@@ -5249,7 +5395,7 @@ ${legend.join("\n")}`;
5249
5395
  const { formatAsHar: formatAsHar2 } = await Promise.resolve().then(() => (init_har(), har_exports));
5250
5396
  const har = formatAsHar2(sessionBuffers.networkBuffer, recording.startTime);
5251
5397
  const harPath = args[1] || (currentSession ? `${currentSession.outputDir}/recording.har` : `${LOCAL_DIR}/browse-recording.har`);
5252
- fs12.writeFileSync(harPath, JSON.stringify(har, null, 2));
5398
+ fs13.writeFileSync(harPath, JSON.stringify(har, null, 2));
5253
5399
  const entryCount = har.log.entries.length;
5254
5400
  return `HAR saved: ${harPath} (${entryCount} entries)`;
5255
5401
  }
@@ -5469,18 +5615,57 @@ Manual: npm install -g @ulpi/browse`;
5469
5615
  }
5470
5616
  const filePath = args[2];
5471
5617
  if (filePath) {
5472
- fs12.writeFileSync(filePath, output);
5618
+ fs13.writeFileSync(filePath, output);
5473
5619
  return `Exported ${steps.length} steps as ${format}: ${filePath}`;
5474
5620
  }
5475
5621
  return output;
5476
5622
  }
5477
5623
  throw new Error("Usage: browse record start | stop | status | export browse|replay [path]");
5478
5624
  }
5625
+ // ─── Profile Management ────────────────────────────────
5626
+ case "profile": {
5627
+ const subcommand = args[0];
5628
+ if (!subcommand) throw new Error("Usage: browse profile list | delete <name> | clean [--older-than <days>]");
5629
+ if (subcommand === "list") {
5630
+ const { listProfiles: listProfiles2 } = await Promise.resolve().then(() => (init_browser_manager(), browser_manager_exports));
5631
+ const profiles = listProfiles2(LOCAL_DIR);
5632
+ if (profiles.length === 0) return "No profiles found";
5633
+ return profiles.map((p) => `${p.name} ${p.size} last used: ${p.lastUsed}`).join("\n");
5634
+ }
5635
+ if (subcommand === "delete") {
5636
+ const name = args[1];
5637
+ if (!name) throw new Error("Usage: browse profile delete <name>");
5638
+ const { deleteProfile: deleteProfile2 } = await Promise.resolve().then(() => (init_browser_manager(), browser_manager_exports));
5639
+ deleteProfile2(LOCAL_DIR, name);
5640
+ return `Profile "${name}" deleted`;
5641
+ }
5642
+ if (subcommand === "clean") {
5643
+ const { listProfiles: listProfiles2, deleteProfile: deleteProfile2 } = await Promise.resolve().then(() => (init_browser_manager(), browser_manager_exports));
5644
+ let maxDays = 7;
5645
+ const olderIdx = args.indexOf("--older-than");
5646
+ if (olderIdx !== -1 && args[olderIdx + 1]) {
5647
+ maxDays = parseInt(args[olderIdx + 1], 10);
5648
+ if (isNaN(maxDays)) throw new Error("Usage: browse profile clean --older-than <days>");
5649
+ }
5650
+ const profiles = listProfiles2(LOCAL_DIR);
5651
+ const cutoff = /* @__PURE__ */ new Date();
5652
+ cutoff.setDate(cutoff.getDate() - maxDays);
5653
+ let cleaned = 0;
5654
+ for (const p of profiles) {
5655
+ if (new Date(p.lastUsed) < cutoff) {
5656
+ deleteProfile2(LOCAL_DIR, p.name);
5657
+ cleaned++;
5658
+ }
5659
+ }
5660
+ return cleaned > 0 ? `Cleaned ${cleaned} profile(s) older than ${maxDays} days` : "No profiles to clean";
5661
+ }
5662
+ throw new Error("Usage: browse profile list | delete <name> | clean [--older-than <days>]");
5663
+ }
5479
5664
  default:
5480
5665
  throw new Error(`Unknown meta command: ${command}`);
5481
5666
  }
5482
5667
  }
5483
- var LOCAL_DIR;
5668
+ var fs13, LOCAL_DIR;
5484
5669
  var init_meta = __esm({
5485
5670
  "src/commands/meta.ts"() {
5486
5671
  "use strict";
@@ -5488,18 +5673,13 @@ var init_meta = __esm({
5488
5673
  init_constants();
5489
5674
  init_sanitize();
5490
5675
  init_lib();
5676
+ fs13 = __toESM(require("fs"), 1);
5491
5677
  LOCAL_DIR = process.env.BROWSE_LOCAL_DIR || "/tmp";
5492
5678
  }
5493
5679
  });
5494
5680
 
5495
5681
  // src/server.ts
5496
5682
  var server_exports = {};
5497
- import * as fs13 from "fs";
5498
- import * as path10 from "path";
5499
- import * as crypto3 from "crypto";
5500
- import * as http from "http";
5501
- import { fileURLToPath as fileURLToPath2 } from "url";
5502
- import * as net2 from "net";
5503
5683
  function nodeServe(opts) {
5504
5684
  const server = http.createServer(async (nodeReq, nodeRes) => {
5505
5685
  try {
@@ -5531,9 +5711,13 @@ function validateAuth(req) {
5531
5711
  const header = req.headers.get("authorization");
5532
5712
  return header === `Bearer ${AUTH_TOKEN}`;
5533
5713
  }
5534
- function flushAllBuffers(sessionManager2, final = false) {
5535
- for (const session of sessionManager2.getAllSessions()) {
5536
- flushSessionBuffers(session, final);
5714
+ function flushAllBuffers(sm, final = false) {
5715
+ if (sm) {
5716
+ for (const session of sm.getAllSessions()) {
5717
+ flushSessionBuffers(session, final);
5718
+ }
5719
+ } else if (profileSession) {
5720
+ flushSessionBuffers(profileSession, final);
5537
5721
  }
5538
5722
  }
5539
5723
  function flushSessionBuffers(session, final) {
@@ -5547,7 +5731,7 @@ function flushSessionBuffers(session, final) {
5547
5731
  const lines = newEntries.map(
5548
5732
  (e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
5549
5733
  ).join("\n") + "\n";
5550
- fs13.appendFileSync(consolePath, lines);
5734
+ fs14.appendFileSync(consolePath, lines);
5551
5735
  buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
5552
5736
  }
5553
5737
  let newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
@@ -5572,7 +5756,7 @@ function flushSessionBuffers(session, final) {
5572
5756
  const lines = prefix.map(
5573
5757
  (e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
5574
5758
  ).join("\n") + "\n";
5575
- fs13.appendFileSync(networkPath, lines);
5759
+ fs14.appendFileSync(networkPath, lines);
5576
5760
  buffers.lastNetworkFlushed += prefixLen;
5577
5761
  }
5578
5762
  }
@@ -5686,7 +5870,7 @@ async function handleCommand(body, session, opts) {
5686
5870
  } else if (WRITE_COMMANDS.has(command)) {
5687
5871
  result = await handleWriteCommand(command, args, session.manager, session.domainFilter);
5688
5872
  } else if (META_COMMANDS.has(command)) {
5689
- result = await handleMetaCommand(command, args, session.manager, shutdown, sessionManager, session);
5873
+ result = await handleMetaCommand(command, args, session.manager, shutdown, sessionManager ?? void 0, session);
5690
5874
  } else {
5691
5875
  const error = `Unknown command: ${command}`;
5692
5876
  const hint = `Available commands: ${[...READ_COMMANDS, ...WRITE_COMMANDS, ...META_COMMANDS].sort().join(", ")}`;
@@ -5745,18 +5929,23 @@ async function shutdown() {
5745
5929
  clearInterval(flushInterval);
5746
5930
  clearInterval(sessionCleanupInterval);
5747
5931
  flushAllBuffers(sessionManager, true);
5748
- await sessionManager.closeAll();
5749
- if (browser && !isRemoteBrowser) {
5750
- browser.removeAllListeners("disconnected");
5751
- await browser.close().catch(() => {
5932
+ if (profileSession) {
5933
+ await profileSession.manager.close().catch(() => {
5752
5934
  });
5935
+ } else if (sessionManager) {
5936
+ await sessionManager.closeAll();
5937
+ if (browser && !isRemoteBrowser) {
5938
+ browser.removeAllListeners("disconnected");
5939
+ await browser.close().catch(() => {
5940
+ });
5941
+ }
5753
5942
  }
5754
5943
  await activeRuntime?.close?.().catch(() => {
5755
5944
  });
5756
5945
  try {
5757
- const currentState = JSON.parse(fs13.readFileSync(STATE_FILE, "utf-8"));
5946
+ const currentState = JSON.parse(fs14.readFileSync(STATE_FILE, "utf-8"));
5758
5947
  if (currentState.pid === process.pid || currentState.token === AUTH_TOKEN) {
5759
- fs13.unlinkSync(STATE_FILE);
5948
+ fs14.unlinkSync(STATE_FILE);
5760
5949
  }
5761
5950
  } catch {
5762
5951
  }
@@ -5768,38 +5957,65 @@ async function start() {
5768
5957
  const runtime = await getRuntime(runtimeName);
5769
5958
  activeRuntime = runtime;
5770
5959
  console.log(`[browse] Runtime: ${runtime.name}`);
5771
- const cdpUrl = process.env.BROWSE_CDP_URL;
5772
- if (cdpUrl) {
5773
- browser = await runtime.chromium.connectOverCDP(cdpUrl);
5774
- isRemoteBrowser = true;
5775
- console.log(`[browse] Connected to remote Chrome via CDP: ${cdpUrl}`);
5776
- } else if (runtime.browser) {
5777
- browser = runtime.browser;
5778
- browser.on("disconnected", () => {
5960
+ const profileName = process.env.BROWSE_PROFILE;
5961
+ if (profileName) {
5962
+ const { BrowserManager: BrowserManager2, getProfileDir: getProfileDir2 } = await Promise.resolve().then(() => (init_browser_manager(), browser_manager_exports));
5963
+ const { SessionBuffers: SessionBuffers2 } = await Promise.resolve().then(() => (init_buffers(), buffers_exports));
5964
+ const profileDir = getProfileDir2(LOCAL_DIR2, profileName);
5965
+ fs14.mkdirSync(profileDir, { recursive: true });
5966
+ const bm = new BrowserManager2();
5967
+ await bm.launchPersistent(profileDir, () => {
5779
5968
  if (isShuttingDown) return;
5780
- console.error("[browse] Browser disconnected. Shutting down.");
5969
+ console.error("[browse] Chromium disconnected (profile mode). Shutting down.");
5781
5970
  shutdown();
5782
5971
  });
5972
+ const outputDir = path11.join(LOCAL_DIR2, "sessions", profileName);
5973
+ fs14.mkdirSync(outputDir, { recursive: true });
5974
+ profileSession = {
5975
+ id: profileName,
5976
+ manager: bm,
5977
+ buffers: new SessionBuffers2(),
5978
+ domainFilter: null,
5979
+ recording: null,
5980
+ outputDir,
5981
+ lastActivity: Date.now(),
5982
+ createdAt: Date.now()
5983
+ };
5984
+ console.log(`[browse] Profile mode: "${profileName}" (${profileDir})`);
5783
5985
  } else {
5784
- const launchOptions = { headless: process.env.BROWSE_HEADED !== "1" };
5785
- if (DEBUG_PORT > 0) {
5786
- launchOptions.args = [`--remote-debugging-port=${DEBUG_PORT}`];
5787
- }
5788
- const proxyServer = process.env.BROWSE_PROXY;
5789
- if (proxyServer) {
5790
- launchOptions.proxy = { server: proxyServer };
5791
- if (process.env.BROWSE_PROXY_BYPASS) {
5792
- launchOptions.proxy.bypass = process.env.BROWSE_PROXY_BYPASS;
5986
+ const cdpUrl = process.env.BROWSE_CDP_URL;
5987
+ if (cdpUrl) {
5988
+ browser = await runtime.chromium.connectOverCDP(cdpUrl);
5989
+ isRemoteBrowser = true;
5990
+ console.log(`[browse] Connected to remote Chrome via CDP: ${cdpUrl}`);
5991
+ } else if (runtime.browser) {
5992
+ browser = runtime.browser;
5993
+ browser.on("disconnected", () => {
5994
+ if (isShuttingDown) return;
5995
+ console.error("[browse] Browser disconnected. Shutting down.");
5996
+ shutdown();
5997
+ });
5998
+ } else {
5999
+ const launchOptions = { headless: process.env.BROWSE_HEADED !== "1" };
6000
+ if (DEBUG_PORT > 0) {
6001
+ launchOptions.args = [`--remote-debugging-port=${DEBUG_PORT}`];
5793
6002
  }
6003
+ const proxyServer = process.env.BROWSE_PROXY;
6004
+ if (proxyServer) {
6005
+ launchOptions.proxy = { server: proxyServer };
6006
+ if (process.env.BROWSE_PROXY_BYPASS) {
6007
+ launchOptions.proxy.bypass = process.env.BROWSE_PROXY_BYPASS;
6008
+ }
6009
+ }
6010
+ browser = await runtime.chromium.launch(launchOptions);
6011
+ browser.on("disconnected", () => {
6012
+ if (isShuttingDown) return;
6013
+ console.error("[browse] Chromium disconnected. Shutting down.");
6014
+ shutdown();
6015
+ });
5794
6016
  }
5795
- browser = await runtime.chromium.launch(launchOptions);
5796
- browser.on("disconnected", () => {
5797
- if (isShuttingDown) return;
5798
- console.error("[browse] Chromium disconnected. Shutting down.");
5799
- shutdown();
5800
- });
6017
+ sessionManager = new SessionManager(browser, LOCAL_DIR2);
5801
6018
  }
5802
- sessionManager = new SessionManager(browser, LOCAL_DIR2);
5803
6019
  const startTime = Date.now();
5804
6020
  const server = nodeServe({
5805
6021
  port,
@@ -5807,11 +6023,20 @@ async function start() {
5807
6023
  fetch: async (req) => {
5808
6024
  const url = new URL(req.url);
5809
6025
  if (url.pathname === "/health") {
5810
- const healthy = !isShuttingDown && browser.isConnected();
6026
+ let healthy;
6027
+ let sessionCount;
6028
+ if (profileSession) {
6029
+ healthy = !isShuttingDown && !!profileSession.manager.getContext();
6030
+ sessionCount = 1;
6031
+ } else {
6032
+ healthy = !isShuttingDown && !!browser && browser.isConnected();
6033
+ sessionCount = sessionManager ? sessionManager.getSessionCount() : 0;
6034
+ }
5811
6035
  return new Response(JSON.stringify({
5812
6036
  status: healthy ? "healthy" : "unhealthy",
5813
6037
  uptime: Math.floor((Date.now() - startTime) / 1e3),
5814
- sessions: sessionManager.getSessionCount()
6038
+ sessions: sessionCount,
6039
+ ...profileName ? { profile: profileName } : {}
5815
6040
  }), {
5816
6041
  status: 200,
5817
6042
  headers: { "Content-Type": "application/json" }
@@ -5825,15 +6050,21 @@ async function start() {
5825
6050
  }
5826
6051
  if (url.pathname === "/command" && req.method === "POST") {
5827
6052
  const body = await req.json();
5828
- const sessionId = req.headers.get("x-browse-session") || "default";
5829
- const allowedDomains = req.headers.get("x-browse-allowed-domains") || void 0;
5830
- const session = await sessionManager.getOrCreate(sessionId, allowedDomains);
6053
+ let session;
6054
+ if (profileSession) {
6055
+ session = profileSession;
6056
+ session.lastActivity = Date.now();
6057
+ } else {
6058
+ const sessionId = req.headers.get("x-browse-session") || "default";
6059
+ const allowedDomains = req.headers.get("x-browse-allowed-domains") || void 0;
6060
+ session = await sessionManager.getOrCreate(sessionId, allowedDomains);
6061
+ }
5831
6062
  const stateFilePath = req.headers.get("x-browse-state");
5832
6063
  if (stateFilePath) {
5833
6064
  const context = session.manager.getContext();
5834
6065
  if (context) {
5835
6066
  try {
5836
- const stateData = JSON.parse(fs13.readFileSync(stateFilePath, "utf-8"));
6067
+ const stateData = JSON.parse(fs14.readFileSync(stateFilePath, "utf-8"));
5837
6068
  if (stateData.cookies?.length) {
5838
6069
  await context.addCookies(stateData.cookies);
5839
6070
  }
@@ -5860,17 +6091,20 @@ async function start() {
5860
6091
  port,
5861
6092
  token: AUTH_TOKEN,
5862
6093
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
5863
- serverPath: path10.resolve(path10.dirname(fileURLToPath2(import.meta.url)), "server.ts")
6094
+ serverPath: path11.resolve(path11.dirname((0, import_url2.fileURLToPath)(__import_meta_url)), "server.ts")
5864
6095
  };
6096
+ if (profileName) {
6097
+ state.profile = profileName;
6098
+ }
5865
6099
  if (DEBUG_PORT > 0) {
5866
6100
  state.debugPort = DEBUG_PORT;
5867
6101
  }
5868
- fs13.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
6102
+ fs14.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
5869
6103
  console.log(`[browse] Server running on http://127.0.0.1:${port} (PID: ${process.pid})`);
5870
6104
  console.log(`[browse] State file: ${STATE_FILE}`);
5871
6105
  console.log(`[browse] Idle timeout: ${IDLE_TIMEOUT_MS / 1e3}s`);
5872
6106
  }
5873
- var AUTH_TOKEN, DEBUG_PORT, BROWSE_PORT, BROWSE_INSTANCE, INSTANCE_SUFFIX, LOCAL_DIR2, STATE_FILE, IDLE_TIMEOUT_MS, sessionManager, browser, activeRuntime, isShuttingDown, isRemoteBrowser, policyChecker, READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS, RECORDING_SKIP, PAGE_CONTENT_COMMANDS, BOUNDARY_NONCE, flushInterval, sessionCleanupInterval;
6107
+ var fs14, path11, crypto3, http, import_url2, net2, AUTH_TOKEN, DEBUG_PORT, BROWSE_PORT, BROWSE_INSTANCE, INSTANCE_SUFFIX, LOCAL_DIR2, STATE_FILE, IDLE_TIMEOUT_MS, sessionManager, browser, profileSession, activeRuntime, isShuttingDown, isRemoteBrowser, policyChecker, READ_COMMANDS, WRITE_COMMANDS, META_COMMANDS, RECORDING_SKIP, PAGE_CONTENT_COMMANDS, BOUNDARY_NONCE, flushInterval, sessionCleanupInterval;
5874
6108
  var init_server = __esm({
5875
6109
  "src/server.ts"() {
5876
6110
  "use strict";
@@ -5881,6 +6115,12 @@ var init_server = __esm({
5881
6115
  init_meta();
5882
6116
  init_policy();
5883
6117
  init_constants();
6118
+ fs14 = __toESM(require("fs"), 1);
6119
+ path11 = __toESM(require("path"), 1);
6120
+ crypto3 = __toESM(require("crypto"), 1);
6121
+ http = __toESM(require("http"), 1);
6122
+ import_url2 = require("url");
6123
+ net2 = __toESM(require("net"), 1);
5884
6124
  AUTH_TOKEN = crypto3.randomUUID();
5885
6125
  DEBUG_PORT = parseInt(process.env.BROWSE_DEBUG_PORT || "0", 10);
5886
6126
  BROWSE_PORT = parseInt(process.env.BROWSE_PORT || "0", 10);
@@ -5889,6 +6129,9 @@ var init_server = __esm({
5889
6129
  LOCAL_DIR2 = process.env.BROWSE_LOCAL_DIR || "/tmp";
5890
6130
  STATE_FILE = process.env.BROWSE_STATE_FILE || `${LOCAL_DIR2}/browse-server${INSTANCE_SUFFIX}.json`;
5891
6131
  IDLE_TIMEOUT_MS = parseInt(process.env.BROWSE_IDLE_TIMEOUT || String(DEFAULTS.IDLE_TIMEOUT_MS), 10);
6132
+ sessionManager = null;
6133
+ browser = null;
6134
+ profileSession = null;
5892
6135
  isShuttingDown = false;
5893
6136
  isRemoteBrowser = false;
5894
6137
  policyChecker = new PolicyChecker();
@@ -5986,7 +6229,8 @@ var init_server = __esm({
5986
6229
  "record",
5987
6230
  "cookie-import",
5988
6231
  "doctor",
5989
- "upgrade"
6232
+ "upgrade",
6233
+ "profile"
5990
6234
  ]);
5991
6235
  RECORDING_SKIP = /* @__PURE__ */ new Set([
5992
6236
  "record",
@@ -6017,10 +6261,19 @@ var init_server = __esm({
6017
6261
  process.on("SIGTERM", shutdown);
6018
6262
  process.on("SIGINT", shutdown);
6019
6263
  flushInterval = setInterval(() => {
6020
- if (sessionManager) flushAllBuffers(sessionManager);
6264
+ if (sessionManager || profileSession) flushAllBuffers(sessionManager);
6021
6265
  }, DEFAULTS.BUFFER_FLUSH_INTERVAL_MS);
6022
6266
  sessionCleanupInterval = setInterval(async () => {
6023
- if (!sessionManager || isShuttingDown) return;
6267
+ if (isShuttingDown) return;
6268
+ if (profileSession) {
6269
+ const idleMs = Date.now() - profileSession.lastActivity;
6270
+ if (idleMs > IDLE_TIMEOUT_MS) {
6271
+ console.log(`[browse] Profile session idle for ${IDLE_TIMEOUT_MS / 1e3}s \u2014 shutting down`);
6272
+ shutdown();
6273
+ }
6274
+ return;
6275
+ }
6276
+ if (!sessionManager) return;
6024
6277
  const closed = await sessionManager.closeIdleSessions(IDLE_TIMEOUT_MS, (session) => flushSessionBuffers(session, true));
6025
6278
  for (const id of closed) {
6026
6279
  console.log(`[browse] Session "${id}" idle for ${IDLE_TIMEOUT_MS / 1e3}s \u2014 closed`);
@@ -6038,15 +6291,22 @@ var init_server = __esm({
6038
6291
  });
6039
6292
 
6040
6293
  // src/cli.ts
6294
+ var cli_exports = {};
6295
+ __export(cli_exports, {
6296
+ SAFE_TO_RETRY: () => SAFE_TO_RETRY,
6297
+ main: () => main,
6298
+ resolveServerScript: () => resolveServerScript
6299
+ });
6300
+ module.exports = __toCommonJS(cli_exports);
6301
+ var fs15 = __toESM(require("fs"), 1);
6302
+ var path12 = __toESM(require("path"), 1);
6303
+ var import_child_process3 = require("child_process");
6304
+ var import_url3 = require("url");
6041
6305
  init_constants();
6042
- import * as fs14 from "fs";
6043
- import * as path11 from "path";
6044
- import { spawn as spawn3 } from "child_process";
6045
- import { fileURLToPath as fileURLToPath3 } from "url";
6046
6306
 
6047
6307
  // src/config.ts
6048
- import * as fs from "fs";
6049
- import * as path from "path";
6308
+ var fs = __toESM(require("fs"), 1);
6309
+ var path = __toESM(require("path"), 1);
6050
6310
  function loadConfig() {
6051
6311
  let dir = process.cwd();
6052
6312
  for (let i = 0; i < 20; i++) {
@@ -6083,7 +6343,8 @@ var cliFlags = {
6083
6343
  headed: false,
6084
6344
  stateFile: "",
6085
6345
  maxOutput: 0,
6086
- cdpUrl: ""
6346
+ cdpUrl: "",
6347
+ profile: ""
6087
6348
  };
6088
6349
  var stateFileApplied = false;
6089
6350
  var BROWSE_PORT2 = parseInt(process.env.BROWSE_PORT || "0", 10);
@@ -6092,50 +6353,50 @@ var INSTANCE_SUFFIX2 = BROWSE_PORT2 ? `-${BROWSE_PORT2}` : BROWSE_INSTANCE2 ? `-
6092
6353
  function resolveLocalDir() {
6093
6354
  if (process.env.BROWSE_LOCAL_DIR) {
6094
6355
  try {
6095
- fs14.mkdirSync(process.env.BROWSE_LOCAL_DIR, { recursive: true });
6356
+ fs15.mkdirSync(process.env.BROWSE_LOCAL_DIR, { recursive: true });
6096
6357
  } catch {
6097
6358
  }
6098
6359
  return process.env.BROWSE_LOCAL_DIR;
6099
6360
  }
6100
6361
  let dir = process.cwd();
6101
6362
  for (let i = 0; i < 20; i++) {
6102
- if (fs14.existsSync(path11.join(dir, ".git")) || fs14.existsSync(path11.join(dir, ".claude"))) {
6103
- const browseDir = path11.join(dir, ".browse");
6363
+ if (fs15.existsSync(path12.join(dir, ".git")) || fs15.existsSync(path12.join(dir, ".claude"))) {
6364
+ const browseDir = path12.join(dir, ".browse");
6104
6365
  try {
6105
- fs14.mkdirSync(browseDir, { recursive: true });
6106
- const gi = path11.join(browseDir, ".gitignore");
6107
- if (!fs14.existsSync(gi)) {
6108
- fs14.writeFileSync(gi, "*\n");
6366
+ fs15.mkdirSync(browseDir, { recursive: true });
6367
+ const gi = path12.join(browseDir, ".gitignore");
6368
+ if (!fs15.existsSync(gi)) {
6369
+ fs15.writeFileSync(gi, "*\n");
6109
6370
  }
6110
6371
  } catch {
6111
6372
  }
6112
6373
  return browseDir;
6113
6374
  }
6114
- const parent = path11.dirname(dir);
6375
+ const parent = path12.dirname(dir);
6115
6376
  if (parent === dir) break;
6116
6377
  dir = parent;
6117
6378
  }
6118
6379
  return "/tmp";
6119
6380
  }
6120
6381
  var LOCAL_DIR3 = resolveLocalDir();
6121
- var STATE_FILE2 = process.env.BROWSE_STATE_FILE || path11.join(LOCAL_DIR3, `browse-server${INSTANCE_SUFFIX2}.json`);
6382
+ var STATE_FILE2 = process.env.BROWSE_STATE_FILE || path12.join(LOCAL_DIR3, `browse-server${INSTANCE_SUFFIX2}.json`);
6122
6383
  var MAX_START_WAIT = 8e3;
6123
6384
  var LOCK_FILE = STATE_FILE2 + ".lock";
6124
6385
  var LOCK_STALE_MS = DEFAULTS.LOCK_STALE_THRESHOLD_MS;
6125
- var __filename_cli = fileURLToPath3(import.meta.url);
6126
- var __dirname_cli = path11.dirname(__filename_cli);
6386
+ var __filename_cli = (0, import_url3.fileURLToPath)(__import_meta_url);
6387
+ var __dirname_cli = path12.dirname(__filename_cli);
6127
6388
  function resolveServerScript(env = process.env, metaDir = __dirname_cli) {
6128
6389
  if (env.BROWSE_SERVER_SCRIPT) {
6129
6390
  return env.BROWSE_SERVER_SCRIPT;
6130
6391
  }
6131
6392
  if (metaDir.startsWith("/")) {
6132
- const direct = path11.resolve(metaDir, "server.ts");
6133
- if (fs14.existsSync(direct)) {
6393
+ const direct = path12.resolve(metaDir, "server.ts");
6394
+ if (fs15.existsSync(direct)) {
6134
6395
  return direct;
6135
6396
  }
6136
6397
  }
6137
- const selfPath = fileURLToPath3(import.meta.url);
6138
- if (fs14.existsSync(selfPath)) {
6398
+ const selfPath = (0, import_url3.fileURLToPath)(__import_meta_url);
6399
+ if (fs15.existsSync(selfPath)) {
6139
6400
  return "__self__";
6140
6401
  }
6141
6402
  throw new Error(
@@ -6145,7 +6406,7 @@ function resolveServerScript(env = process.env, metaDir = __dirname_cli) {
6145
6406
  var SERVER_SCRIPT = resolveServerScript();
6146
6407
  function readState() {
6147
6408
  try {
6148
- const data = fs14.readFileSync(STATE_FILE2, "utf-8");
6409
+ const data = fs15.readFileSync(STATE_FILE2, "utf-8");
6149
6410
  return JSON.parse(data);
6150
6411
  } catch {
6151
6412
  return null;
@@ -6161,7 +6422,7 @@ function isProcessAlive(pid) {
6161
6422
  }
6162
6423
  async function listInstances() {
6163
6424
  try {
6164
- const files = fs14.readdirSync(LOCAL_DIR3).filter(
6425
+ const files = fs15.readdirSync(LOCAL_DIR3).filter(
6165
6426
  (f) => f.startsWith("browse-server") && f.endsWith(".json") && !f.endsWith(".lock")
6166
6427
  );
6167
6428
  if (files.length === 0) {
@@ -6171,7 +6432,7 @@ async function listInstances() {
6171
6432
  let found = false;
6172
6433
  for (const file of files) {
6173
6434
  try {
6174
- const data = JSON.parse(fs14.readFileSync(path11.join(LOCAL_DIR3, file), "utf-8"));
6435
+ const data = JSON.parse(fs15.readFileSync(path12.join(LOCAL_DIR3, file), "utf-8"));
6175
6436
  if (!data.pid || !data.port) continue;
6176
6437
  const alive = isProcessAlive(data.pid);
6177
6438
  let status = "dead";
@@ -6194,7 +6455,7 @@ async function listInstances() {
6194
6455
  found = true;
6195
6456
  if (!alive) {
6196
6457
  try {
6197
- fs14.unlinkSync(path11.join(LOCAL_DIR3, file));
6458
+ fs15.unlinkSync(path12.join(LOCAL_DIR3, file));
6198
6459
  } catch {
6199
6460
  }
6200
6461
  }
@@ -6208,7 +6469,7 @@ async function listInstances() {
6208
6469
  }
6209
6470
  function isBrowseProcess(pid) {
6210
6471
  try {
6211
- const { execSync: execSync2 } = __require("child_process");
6472
+ const { execSync: execSync2 } = require("child_process");
6212
6473
  const cmd = execSync2(`ps -p ${pid} -o command=`, { encoding: "utf-8" }).trim();
6213
6474
  return cmd.includes("browse") || cmd.includes("__BROWSE_SERVER_MODE");
6214
6475
  } catch {
@@ -6217,15 +6478,15 @@ function isBrowseProcess(pid) {
6217
6478
  }
6218
6479
  function acquireLock() {
6219
6480
  try {
6220
- fs14.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
6481
+ fs15.writeFileSync(LOCK_FILE, String(process.pid), { flag: "wx" });
6221
6482
  return true;
6222
6483
  } catch (err) {
6223
6484
  if (err.code === "EEXIST") {
6224
6485
  try {
6225
- const stat = fs14.statSync(LOCK_FILE);
6486
+ const stat = fs15.statSync(LOCK_FILE);
6226
6487
  if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
6227
6488
  try {
6228
- fs14.unlinkSync(LOCK_FILE);
6489
+ fs15.unlinkSync(LOCK_FILE);
6229
6490
  } catch {
6230
6491
  }
6231
6492
  return acquireLock();
@@ -6239,7 +6500,7 @@ function acquireLock() {
6239
6500
  }
6240
6501
  function releaseLock() {
6241
6502
  try {
6242
- fs14.unlinkSync(LOCK_FILE);
6503
+ fs15.unlinkSync(LOCK_FILE);
6243
6504
  } catch {
6244
6505
  }
6245
6506
  }
@@ -6256,7 +6517,7 @@ async function startServer() {
6256
6517
  }
6257
6518
  await sleep(100);
6258
6519
  }
6259
- if (!fs14.existsSync(LOCK_FILE) || fs14.readFileSync(LOCK_FILE, "utf-8").trim() !== String(process.pid)) {
6520
+ if (!fs15.existsSync(LOCK_FILE) || fs15.readFileSync(LOCK_FILE, "utf-8").trim() !== String(process.pid)) {
6260
6521
  const state = readState();
6261
6522
  if (state && isProcessAlive(state.pid)) return state;
6262
6523
  throw new Error("Server failed to start (another process is starting it)");
@@ -6266,14 +6527,14 @@ async function startServer() {
6266
6527
  try {
6267
6528
  const oldState = readState();
6268
6529
  if (oldState && !isProcessAlive(oldState.pid)) {
6269
- fs14.unlinkSync(STATE_FILE2);
6530
+ fs15.unlinkSync(STATE_FILE2);
6270
6531
  }
6271
6532
  } catch {
6272
6533
  }
6273
- const selfPath = fileURLToPath3(import.meta.url);
6534
+ const selfPath = (0, import_url3.fileURLToPath)(__import_meta_url);
6274
6535
  const spawnCmd = SERVER_SCRIPT === "__self__" ? [process.execPath, selfPath] : [process.execPath, "--import", "tsx", SERVER_SCRIPT];
6275
- const spawnEnv = { ...process.env, __BROWSE_SERVER_MODE: "1", BROWSE_LOCAL_DIR: LOCAL_DIR3, BROWSE_INSTANCE: BROWSE_INSTANCE2, ...cliFlags.headed ? { BROWSE_HEADED: "1" } : {}, ...cliFlags.cdpUrl ? { BROWSE_CDP_URL: cliFlags.cdpUrl } : {} };
6276
- const proc = spawn3(spawnCmd[0], spawnCmd.slice(1), {
6536
+ const spawnEnv = { ...process.env, __BROWSE_SERVER_MODE: "1", BROWSE_LOCAL_DIR: LOCAL_DIR3, BROWSE_INSTANCE: BROWSE_INSTANCE2, ...cliFlags.headed ? { BROWSE_HEADED: "1" } : {}, ...cliFlags.cdpUrl ? { BROWSE_CDP_URL: cliFlags.cdpUrl } : {}, ...cliFlags.profile ? { BROWSE_PROFILE: cliFlags.profile } : {} };
6537
+ const proc = (0, import_child_process3.spawn)(spawnCmd[0], spawnCmd.slice(1), {
6277
6538
  stdio: ["ignore", "ignore", "pipe"],
6278
6539
  env: spawnEnv,
6279
6540
  detached: true
@@ -6339,7 +6600,7 @@ async function ensureServer() {
6339
6600
  }
6340
6601
  if (state) {
6341
6602
  try {
6342
- fs14.unlinkSync(STATE_FILE2);
6603
+ fs15.unlinkSync(STATE_FILE2);
6343
6604
  } catch {
6344
6605
  }
6345
6606
  }
@@ -6349,21 +6610,21 @@ async function ensureServer() {
6349
6610
  }
6350
6611
  function cleanOrphanedServers() {
6351
6612
  try {
6352
- const files = fs14.readdirSync(LOCAL_DIR3);
6613
+ const files = fs15.readdirSync(LOCAL_DIR3);
6353
6614
  for (const file of files) {
6354
6615
  if (!file.startsWith("browse-server") || !file.endsWith(".json") || file.endsWith(".lock")) continue;
6355
- const filePath = path11.join(LOCAL_DIR3, file);
6616
+ const filePath = path12.join(LOCAL_DIR3, file);
6356
6617
  if (filePath === STATE_FILE2) continue;
6357
6618
  try {
6358
- const data = JSON.parse(fs14.readFileSync(filePath, "utf-8"));
6619
+ const data = JSON.parse(fs15.readFileSync(filePath, "utf-8"));
6359
6620
  if (!data.pid) {
6360
- fs14.unlinkSync(filePath);
6621
+ fs15.unlinkSync(filePath);
6361
6622
  continue;
6362
6623
  }
6363
6624
  const suffixMatch = file.match(/browse-server-(\d+)\.json$/);
6364
6625
  if (suffixMatch && data.port === parseInt(suffixMatch[1], 10) && isProcessAlive(data.pid)) continue;
6365
6626
  if (!isProcessAlive(data.pid)) {
6366
- fs14.unlinkSync(filePath);
6627
+ fs15.unlinkSync(filePath);
6367
6628
  continue;
6368
6629
  }
6369
6630
  if (isBrowseProcess(data.pid)) {
@@ -6374,7 +6635,7 @@ function cleanOrphanedServers() {
6374
6635
  }
6375
6636
  } catch {
6376
6637
  try {
6377
- fs14.unlinkSync(filePath);
6638
+ fs15.unlinkSync(filePath);
6378
6639
  } catch {
6379
6640
  }
6380
6641
  }
@@ -6475,7 +6736,7 @@ async function sendCommand(state, command, args, retries = 0, sessionId) {
6475
6736
  await sleep(300);
6476
6737
  }
6477
6738
  try {
6478
- fs14.unlinkSync(STATE_FILE2);
6739
+ fs15.unlinkSync(STATE_FILE2);
6479
6740
  } catch {
6480
6741
  }
6481
6742
  if (command === "restart") {
@@ -6523,7 +6784,7 @@ async function main() {
6523
6784
  function findCommandIndex(a) {
6524
6785
  for (let i = 0; i < a.length; i++) {
6525
6786
  if (!a[i].startsWith("-")) return i;
6526
- if (a[i] === "--session" || a[i] === "--allowed-domains" || a[i] === "--cdp" || a[i] === "--state") i++;
6787
+ if (a[i] === "--session" || a[i] === "--allowed-domains" || a[i] === "--cdp" || a[i] === "--state" || a[i] === "--profile") i++;
6527
6788
  }
6528
6789
  return a.length;
6529
6790
  }
@@ -6538,6 +6799,21 @@ async function main() {
6538
6799
  args.splice(sessionIdx, 2);
6539
6800
  }
6540
6801
  sessionId = sessionId || process.env.BROWSE_SESSION || config.session || void 0;
6802
+ let profileName;
6803
+ const profileIdx = args.indexOf("--profile");
6804
+ if (profileIdx !== -1 && profileIdx < findCommandIndex(args)) {
6805
+ profileName = args[profileIdx + 1];
6806
+ if (!profileName || profileName.startsWith("-")) {
6807
+ console.error("Usage: browse --profile <name> <command> [args...]");
6808
+ process.exit(1);
6809
+ }
6810
+ args.splice(profileIdx, 2);
6811
+ }
6812
+ profileName = profileName || process.env.BROWSE_PROFILE || void 0;
6813
+ if (sessionId && profileName) {
6814
+ console.error("Cannot use --profile and --session together. Profiles use their own Chromium; sessions share one.");
6815
+ process.exit(1);
6816
+ }
6541
6817
  let jsonMode = false;
6542
6818
  const jsonIdx = args.indexOf("--json");
6543
6819
  if (jsonIdx !== -1 && jsonIdx < findCommandIndex(args)) {
@@ -6636,6 +6912,7 @@ async function main() {
6636
6912
  cliFlags.stateFile = stateFile;
6637
6913
  cliFlags.maxOutput = maxOutput;
6638
6914
  cliFlags.cdpUrl = cdpUrl;
6915
+ cliFlags.profile = profileName || "";
6639
6916
  if (args[0] === "version" || args[0] === "--version" || args[0] === "-V") {
6640
6917
  const pkg = await Promise.resolve().then(() => __toESM(require_package(), 1));
6641
6918
  console.log(pkg.version);
@@ -6704,6 +6981,7 @@ Setup: install-skill [path]
6704
6981
 
6705
6982
  Options:
6706
6983
  --session <id> Named session (isolates tabs, refs, cookies)
6984
+ --profile <name> Persistent browser profile (own Chromium, full state persistence)
6707
6985
  --json Wrap output as {success, data, command}
6708
6986
  --content-boundaries Wrap page content in nonce-delimited markers
6709
6987
  --allowed-domains <d,d> Block navigation/resources outside allowlist
@@ -6743,14 +7021,15 @@ Refs: After 'snapshot', use @e1, @e2... as selectors:
6743
7021
  }
6744
7022
  if (process.env.__BROWSE_SERVER_MODE === "1") {
6745
7023
  Promise.resolve().then(() => init_server());
6746
- } else if (process.argv[1] && fs14.realpathSync(process.argv[1]) === fs14.realpathSync(__filename_cli)) {
7024
+ } else if (process.argv[1] && fs15.realpathSync(process.argv[1]) === fs15.realpathSync(__filename_cli)) {
6747
7025
  main().catch((err) => {
6748
7026
  console.error(`[browse] ${err.message}`);
6749
7027
  process.exit(1);
6750
7028
  });
6751
7029
  }
6752
- export {
7030
+ // Annotate the CommonJS export names for ESM import in node:
7031
+ 0 && (module.exports = {
6753
7032
  SAFE_TO_RETRY,
6754
7033
  main,
6755
7034
  resolveServerScript
6756
- };
7035
+ });