@hasna/browser 0.0.9 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28,6 +28,79 @@ var __export = (target, all) => {
28
28
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
29
  var __require = import.meta.require;
30
30
 
31
+ // src/types/index.ts
32
+ var UseCase, BrowserError, SessionNotFoundError, EngineNotAvailableError, NavigationError, ElementNotFoundError, RecordingNotFoundError, AgentNotFoundError, ProjectNotFoundError;
33
+ var init_types = __esm(() => {
34
+ ((UseCase2) => {
35
+ UseCase2["SCRAPE"] = "scrape";
36
+ UseCase2["EXTRACT_LINKS"] = "extract_links";
37
+ UseCase2["STATUS_CHECK"] = "status_check";
38
+ UseCase2["FORM_FILL"] = "form_fill";
39
+ UseCase2["SPA_NAVIGATE"] = "spa_navigate";
40
+ UseCase2["SCREENSHOT"] = "screenshot";
41
+ UseCase2["AUTH_FLOW"] = "auth_flow";
42
+ UseCase2["MULTI_TAB"] = "multi_tab";
43
+ UseCase2["NETWORK_MONITOR"] = "network_monitor";
44
+ UseCase2["HAR_CAPTURE"] = "har_capture";
45
+ UseCase2["PERF_PROFILE"] = "perf_profile";
46
+ UseCase2["SCRIPT_INJECT"] = "script_inject";
47
+ UseCase2["COVERAGE"] = "coverage";
48
+ UseCase2["RECORD_REPLAY"] = "record_replay";
49
+ })(UseCase ||= {});
50
+ BrowserError = class BrowserError extends Error {
51
+ code;
52
+ retryable;
53
+ constructor(message, code = "BROWSER_ERROR", retryable = false) {
54
+ super(message);
55
+ this.code = code;
56
+ this.retryable = retryable;
57
+ this.name = "BrowserError";
58
+ }
59
+ };
60
+ SessionNotFoundError = class SessionNotFoundError extends BrowserError {
61
+ constructor(id) {
62
+ super(`Session not found: ${id}`, "SESSION_NOT_FOUND", false);
63
+ this.name = "SessionNotFoundError";
64
+ }
65
+ };
66
+ EngineNotAvailableError = class EngineNotAvailableError extends BrowserError {
67
+ constructor(engine, reason) {
68
+ super(`Engine '${engine}' is not available${reason ? `: ${reason}` : ""}`, "ENGINE_NOT_AVAILABLE", false);
69
+ this.name = "EngineNotAvailableError";
70
+ }
71
+ };
72
+ NavigationError = class NavigationError extends BrowserError {
73
+ constructor(url, reason) {
74
+ super(`Navigation to '${url}' failed${reason ? `: ${reason}` : ""}`, "NAVIGATION_ERROR", true);
75
+ this.name = "NavigationError";
76
+ }
77
+ };
78
+ ElementNotFoundError = class ElementNotFoundError extends BrowserError {
79
+ constructor(selector) {
80
+ super(`Element not found: ${selector}`, "ELEMENT_NOT_FOUND", false);
81
+ this.name = "ElementNotFoundError";
82
+ }
83
+ };
84
+ RecordingNotFoundError = class RecordingNotFoundError extends BrowserError {
85
+ constructor(id) {
86
+ super(`Recording not found: ${id}`, "RECORDING_NOT_FOUND", false);
87
+ this.name = "RecordingNotFoundError";
88
+ }
89
+ };
90
+ AgentNotFoundError = class AgentNotFoundError extends BrowserError {
91
+ constructor(id) {
92
+ super(`Agent not found: ${id}`, "AGENT_NOT_FOUND", false);
93
+ this.name = "AgentNotFoundError";
94
+ }
95
+ };
96
+ ProjectNotFoundError = class ProjectNotFoundError extends BrowserError {
97
+ constructor(id) {
98
+ super(`Project not found: ${id}`, "PROJECT_NOT_FOUND", false);
99
+ this.name = "ProjectNotFoundError";
100
+ }
101
+ };
102
+ });
103
+
31
104
  // src/db/schema.ts
32
105
  import { Database } from "bun:sqlite";
33
106
  import { join } from "path";
@@ -243,6 +316,71 @@ function runMigrations(db) {
243
316
  );
244
317
  CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
245
318
  `
319
+ },
320
+ {
321
+ version: 6,
322
+ sql: `
323
+ CREATE TABLE IF NOT EXISTS auth_flows (
324
+ id TEXT PRIMARY KEY,
325
+ name TEXT NOT NULL UNIQUE,
326
+ domain TEXT NOT NULL,
327
+ recording_id TEXT REFERENCES recordings(id),
328
+ storage_state_path TEXT,
329
+ created_at TEXT DEFAULT (datetime('now')),
330
+ last_used TEXT
331
+ );
332
+
333
+ CREATE INDEX IF NOT EXISTS idx_auth_flows_domain ON auth_flows(domain);
334
+ CREATE INDEX IF NOT EXISTS idx_auth_flows_name ON auth_flows(name);
335
+ `
336
+ },
337
+ {
338
+ version: 7,
339
+ sql: `
340
+ CREATE TABLE IF NOT EXISTS workflows (
341
+ id TEXT PRIMARY KEY,
342
+ name TEXT NOT NULL UNIQUE,
343
+ description TEXT,
344
+ steps TEXT NOT NULL DEFAULT '[]',
345
+ start_url TEXT,
346
+ last_run TEXT,
347
+ last_heal TEXT,
348
+ heal_count INTEGER DEFAULT 0,
349
+ run_count INTEGER DEFAULT 0,
350
+ created_at TEXT DEFAULT (datetime('now')),
351
+ updated_at TEXT DEFAULT (datetime('now'))
352
+ );
353
+ `
354
+ },
355
+ {
356
+ version: 8,
357
+ sql: `
358
+ CREATE TABLE IF NOT EXISTS datasets (
359
+ id TEXT PRIMARY KEY,
360
+ name TEXT NOT NULL UNIQUE,
361
+ source_url TEXT,
362
+ source_type TEXT NOT NULL DEFAULT 'page',
363
+ data TEXT NOT NULL DEFAULT '[]',
364
+ schema TEXT,
365
+ row_count INTEGER DEFAULT 0,
366
+ last_refresh TEXT,
367
+ created_at TEXT DEFAULT (datetime('now')),
368
+ updated_at TEXT DEFAULT (datetime('now'))
369
+ );
370
+
371
+ CREATE TABLE IF NOT EXISTS api_endpoints (
372
+ id TEXT PRIMARY KEY,
373
+ session_id TEXT,
374
+ url TEXT NOT NULL,
375
+ method TEXT DEFAULT 'GET',
376
+ response_schema TEXT,
377
+ sample_response TEXT,
378
+ status_code INTEGER,
379
+ content_type TEXT,
380
+ discovered_at TEXT DEFAULT (datetime('now'))
381
+ );
382
+ CREATE INDEX IF NOT EXISTS idx_api_endpoints_session ON api_endpoints(session_id);
383
+ `
246
384
  }
247
385
  ];
248
386
  for (const m of migrations) {
@@ -291,6 +429,188 @@ var init_console_log = __esm(() => {
291
429
  init_schema();
292
430
  });
293
431
 
432
+ // src/engines/cdp.ts
433
+ var exports_cdp = {};
434
+ __export(exports_cdp, {
435
+ connectToExistingBrowser: () => connectToExistingBrowser,
436
+ CDPClient: () => CDPClient
437
+ });
438
+ async function connectToExistingBrowser(cdpUrl) {
439
+ const { chromium: chromium2 } = await import("playwright");
440
+ try {
441
+ return await chromium2.connectOverCDP(cdpUrl);
442
+ } catch (err) {
443
+ throw new BrowserError(`Failed to connect to browser at ${cdpUrl}: ${err instanceof Error ? err.message : String(err)}. Start Chrome with: google-chrome --remote-debugging-port=9222`, "CDP_CONNECT_FAILED", true);
444
+ }
445
+ }
446
+
447
+ class CDPClient {
448
+ session;
449
+ networkEnabled = false;
450
+ performanceEnabled = false;
451
+ constructor(session) {
452
+ this.session = session;
453
+ }
454
+ static async fromPage(page) {
455
+ try {
456
+ const session = await page.context().newCDPSession(page);
457
+ return new CDPClient(session);
458
+ } catch (err) {
459
+ throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
460
+ }
461
+ }
462
+ async send(method, params) {
463
+ try {
464
+ return await this.session.send(method, params);
465
+ } catch (err) {
466
+ throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
467
+ }
468
+ }
469
+ on(event, handler) {
470
+ this.session.on(event, handler);
471
+ }
472
+ off(event, handler) {
473
+ this.session.off(event, handler);
474
+ }
475
+ async enableNetwork() {
476
+ if (!this.networkEnabled) {
477
+ await this.send("Network.enable");
478
+ this.networkEnabled = true;
479
+ }
480
+ }
481
+ async enablePerformance() {
482
+ if (!this.performanceEnabled) {
483
+ await this.send("Performance.enable");
484
+ this.performanceEnabled = true;
485
+ }
486
+ }
487
+ async getPerformanceMetrics() {
488
+ await this.enablePerformance();
489
+ const result = await this.send("Performance.getMetrics");
490
+ const m = {};
491
+ for (const metric of result.metrics) {
492
+ m[metric.name] = metric.value;
493
+ }
494
+ return {
495
+ js_heap_size_used: m["JSHeapUsedSize"],
496
+ js_heap_size_total: m["JSHeapTotalSize"],
497
+ dom_interactive: m["DOMInteractive"],
498
+ dom_complete: m["DOMComplete"],
499
+ load_event: m["LoadEventEnd"]
500
+ };
501
+ }
502
+ async startJSCoverage() {
503
+ await this.send("Profiler.enable");
504
+ await this.send("Debugger.enable");
505
+ await this.send("Profiler.startPreciseCoverage", {
506
+ callCount: false,
507
+ detailed: true
508
+ });
509
+ }
510
+ async stopJSCoverage() {
511
+ const result = await this.send("Profiler.takePreciseCoverage");
512
+ await this.send("Profiler.stopPreciseCoverage");
513
+ return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
514
+ url: r.url,
515
+ text: "",
516
+ ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
517
+ }));
518
+ }
519
+ async getCoverage() {
520
+ await this.startJSCoverage();
521
+ const js = await this.stopJSCoverage();
522
+ const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
523
+ return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
524
+ }
525
+ async captureHAREntries(page, handler) {
526
+ await this.enableNetwork();
527
+ const requestTimings = new Map;
528
+ const onRequest = (params) => {
529
+ requestTimings.set(params.requestId, params.timestamp);
530
+ };
531
+ const onResponse = (params) => {
532
+ const start = requestTimings.get(params.requestId);
533
+ const duration = start != null ? (params.timestamp - start) * 1000 : 0;
534
+ handler({
535
+ method: "GET",
536
+ url: params.response.url,
537
+ status: params.response.status,
538
+ duration
539
+ });
540
+ };
541
+ this.on("Network.requestWillBeSent", onRequest);
542
+ this.on("Network.responseReceived", onResponse);
543
+ return () => {
544
+ this.off("Network.requestWillBeSent", onRequest);
545
+ this.off("Network.responseReceived", onResponse);
546
+ };
547
+ }
548
+ async detach() {
549
+ try {
550
+ await this.session.detach();
551
+ } catch {}
552
+ }
553
+ }
554
+ var init_cdp = __esm(() => {
555
+ init_types();
556
+ });
557
+
558
+ // src/lib/storage-state.ts
559
+ var exports_storage_state = {};
560
+ __export(exports_storage_state, {
561
+ saveStateFromPage: () => saveStateFromPage,
562
+ saveState: () => saveState,
563
+ loadStatePath: () => loadStatePath,
564
+ listStates: () => listStates,
565
+ deleteState: () => deleteState
566
+ });
567
+ import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
568
+ import { join as join3 } from "path";
569
+ import { homedir as homedir3 } from "os";
570
+ function ensureDir() {
571
+ mkdirSync3(STATES_DIR, { recursive: true });
572
+ }
573
+ function statePath(name) {
574
+ return join3(STATES_DIR, `${name}.json`);
575
+ }
576
+ async function saveState(context, name) {
577
+ ensureDir();
578
+ const path = statePath(name);
579
+ const state = await context.storageState({ path });
580
+ return path;
581
+ }
582
+ async function saveStateFromPage(page, name) {
583
+ return saveState(page.context(), name);
584
+ }
585
+ function loadStatePath(name) {
586
+ const path = statePath(name);
587
+ return existsSync(path) ? path : null;
588
+ }
589
+ function listStates() {
590
+ ensureDir();
591
+ return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
592
+ const path = join3(STATES_DIR, f);
593
+ const stat = Bun.file(path);
594
+ return {
595
+ name: f.replace(".json", ""),
596
+ path,
597
+ modified: new Date(stat.lastModified).toISOString()
598
+ };
599
+ }).sort((a, b) => b.modified.localeCompare(a.modified));
600
+ }
601
+ function deleteState(name) {
602
+ const path = statePath(name);
603
+ if (existsSync(path)) {
604
+ unlinkSync(path);
605
+ return true;
606
+ }
607
+ return false;
608
+ }
609
+ var STATES_DIR;
610
+ var init_storage_state = __esm(() => {
611
+ STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
612
+ });
613
+
294
614
  // node_modules/sharp/lib/is.js
295
615
  var require_is = __commonJS((exports, module) => {
296
616
  /*!
@@ -6686,90 +7006,13 @@ var require_lib = __commonJS((exports, module) => {
6686
7006
  module.exports = Sharp;
6687
7007
  });
6688
7008
 
6689
- // src/types/index.ts
6690
- var UseCase;
6691
- ((UseCase2) => {
6692
- UseCase2["SCRAPE"] = "scrape";
6693
- UseCase2["EXTRACT_LINKS"] = "extract_links";
6694
- UseCase2["STATUS_CHECK"] = "status_check";
6695
- UseCase2["FORM_FILL"] = "form_fill";
6696
- UseCase2["SPA_NAVIGATE"] = "spa_navigate";
6697
- UseCase2["SCREENSHOT"] = "screenshot";
6698
- UseCase2["AUTH_FLOW"] = "auth_flow";
6699
- UseCase2["MULTI_TAB"] = "multi_tab";
6700
- UseCase2["NETWORK_MONITOR"] = "network_monitor";
6701
- UseCase2["HAR_CAPTURE"] = "har_capture";
6702
- UseCase2["PERF_PROFILE"] = "perf_profile";
6703
- UseCase2["SCRIPT_INJECT"] = "script_inject";
6704
- UseCase2["COVERAGE"] = "coverage";
6705
- UseCase2["RECORD_REPLAY"] = "record_replay";
6706
- })(UseCase ||= {});
6707
-
6708
- class BrowserError extends Error {
6709
- code;
6710
- retryable;
6711
- constructor(message, code = "BROWSER_ERROR", retryable = false) {
6712
- super(message);
6713
- this.code = code;
6714
- this.retryable = retryable;
6715
- this.name = "BrowserError";
6716
- }
6717
- }
6718
-
6719
- class SessionNotFoundError extends BrowserError {
6720
- constructor(id) {
6721
- super(`Session not found: ${id}`, "SESSION_NOT_FOUND", false);
6722
- this.name = "SessionNotFoundError";
6723
- }
6724
- }
6725
-
6726
- class EngineNotAvailableError extends BrowserError {
6727
- constructor(engine, reason) {
6728
- super(`Engine '${engine}' is not available${reason ? `: ${reason}` : ""}`, "ENGINE_NOT_AVAILABLE", false);
6729
- this.name = "EngineNotAvailableError";
6730
- }
6731
- }
6732
-
6733
- class NavigationError extends BrowserError {
6734
- constructor(url, reason) {
6735
- super(`Navigation to '${url}' failed${reason ? `: ${reason}` : ""}`, "NAVIGATION_ERROR", true);
6736
- this.name = "NavigationError";
6737
- }
6738
- }
6739
-
6740
- class ElementNotFoundError extends BrowserError {
6741
- constructor(selector) {
6742
- super(`Element not found: ${selector}`, "ELEMENT_NOT_FOUND", false);
6743
- this.name = "ElementNotFoundError";
6744
- }
6745
- }
6746
-
6747
- class RecordingNotFoundError extends BrowserError {
6748
- constructor(id) {
6749
- super(`Recording not found: ${id}`, "RECORDING_NOT_FOUND", false);
6750
- this.name = "RecordingNotFoundError";
6751
- }
6752
- }
6753
-
6754
- class AgentNotFoundError extends BrowserError {
6755
- constructor(id) {
6756
- super(`Agent not found: ${id}`, "AGENT_NOT_FOUND", false);
6757
- this.name = "AgentNotFoundError";
6758
- }
6759
- }
6760
-
6761
- class ProjectNotFoundError extends BrowserError {
6762
- constructor(id) {
6763
- super(`Project not found: ${id}`, "PROJECT_NOT_FOUND", false);
6764
- this.name = "ProjectNotFoundError";
6765
- }
6766
- }
6767
-
6768
7009
  // src/index.ts
6769
7010
  init_schema();
7011
+ init_types();
6770
7012
 
6771
7013
  // src/db/projects.ts
6772
7014
  init_schema();
7015
+ init_types();
6773
7016
  import { randomUUID } from "crypto";
6774
7017
  function createProject(data) {
6775
7018
  const db = getDatabase();
@@ -6827,6 +7070,7 @@ function deleteProject(id) {
6827
7070
  }
6828
7071
  // src/db/agents.ts
6829
7072
  init_schema();
7073
+ init_types();
6830
7074
  import { randomUUID as randomUUID2 } from "crypto";
6831
7075
  function registerAgent(name, opts = {}) {
6832
7076
  const db = getDatabase();
@@ -6907,6 +7151,7 @@ function cleanStaleAgents(thresholdMs) {
6907
7151
  }
6908
7152
  // src/db/sessions.ts
6909
7153
  init_schema();
7154
+ init_types();
6910
7155
  import { randomUUID as randomUUID3 } from "crypto";
6911
7156
  function createSession(data) {
6912
7157
  const db = getDatabase();
@@ -7039,6 +7284,7 @@ init_console_log();
7039
7284
 
7040
7285
  // src/db/recordings.ts
7041
7286
  init_schema();
7287
+ init_types();
7042
7288
  import { randomUUID as randomUUID7 } from "crypto";
7043
7289
  function deserialize(row) {
7044
7290
  return {
@@ -7153,6 +7399,7 @@ function cleanOldHeartbeats(olderThanMs) {
7153
7399
  return result.changes;
7154
7400
  }
7155
7401
  // src/engines/playwright.ts
7402
+ init_types();
7156
7403
  import { chromium } from "playwright";
7157
7404
  var DEFAULT_VIEWPORT = { width: 1280, height: 720 };
7158
7405
  async function launchPlaywright(options) {
@@ -7230,115 +7477,12 @@ class BrowserPool {
7230
7477
  return this.pool.filter((e) => !e.inUse).length;
7231
7478
  }
7232
7479
  }
7233
- // src/engines/cdp.ts
7234
- class CDPClient {
7235
- session;
7236
- networkEnabled = false;
7237
- performanceEnabled = false;
7238
- constructor(session) {
7239
- this.session = session;
7240
- }
7241
- static async fromPage(page) {
7242
- try {
7243
- const session = await page.context().newCDPSession(page);
7244
- return new CDPClient(session);
7245
- } catch (err) {
7246
- throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
7247
- }
7248
- }
7249
- async send(method, params) {
7250
- try {
7251
- return await this.session.send(method, params);
7252
- } catch (err) {
7253
- throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
7254
- }
7255
- }
7256
- on(event, handler) {
7257
- this.session.on(event, handler);
7258
- }
7259
- off(event, handler) {
7260
- this.session.off(event, handler);
7261
- }
7262
- async enableNetwork() {
7263
- if (!this.networkEnabled) {
7264
- await this.send("Network.enable");
7265
- this.networkEnabled = true;
7266
- }
7267
- }
7268
- async enablePerformance() {
7269
- if (!this.performanceEnabled) {
7270
- await this.send("Performance.enable");
7271
- this.performanceEnabled = true;
7272
- }
7273
- }
7274
- async getPerformanceMetrics() {
7275
- await this.enablePerformance();
7276
- const result = await this.send("Performance.getMetrics");
7277
- const m = {};
7278
- for (const metric of result.metrics) {
7279
- m[metric.name] = metric.value;
7280
- }
7281
- return {
7282
- js_heap_size_used: m["JSHeapUsedSize"],
7283
- js_heap_size_total: m["JSHeapTotalSize"],
7284
- dom_interactive: m["DOMInteractive"],
7285
- dom_complete: m["DOMComplete"],
7286
- load_event: m["LoadEventEnd"]
7287
- };
7288
- }
7289
- async startJSCoverage() {
7290
- await this.send("Profiler.enable");
7291
- await this.send("Debugger.enable");
7292
- await this.send("Profiler.startPreciseCoverage", {
7293
- callCount: false,
7294
- detailed: true
7295
- });
7296
- }
7297
- async stopJSCoverage() {
7298
- const result = await this.send("Profiler.takePreciseCoverage");
7299
- await this.send("Profiler.stopPreciseCoverage");
7300
- return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
7301
- url: r.url,
7302
- text: "",
7303
- ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
7304
- }));
7305
- }
7306
- async getCoverage() {
7307
- await this.startJSCoverage();
7308
- const js = await this.stopJSCoverage();
7309
- const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
7310
- return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
7311
- }
7312
- async captureHAREntries(page, handler) {
7313
- await this.enableNetwork();
7314
- const requestTimings = new Map;
7315
- const onRequest = (params) => {
7316
- requestTimings.set(params.requestId, params.timestamp);
7317
- };
7318
- const onResponse = (params) => {
7319
- const start = requestTimings.get(params.requestId);
7320
- const duration = start != null ? (params.timestamp - start) * 1000 : 0;
7321
- handler({
7322
- method: "GET",
7323
- url: params.response.url,
7324
- status: params.response.status,
7325
- duration
7326
- });
7327
- };
7328
- this.on("Network.requestWillBeSent", onRequest);
7329
- this.on("Network.responseReceived", onResponse);
7330
- return () => {
7331
- this.off("Network.requestWillBeSent", onRequest);
7332
- this.off("Network.responseReceived", onResponse);
7333
- };
7334
- }
7335
- async detach() {
7336
- try {
7337
- await this.session.detach();
7338
- } catch {}
7339
- }
7340
- }
7480
+
7481
+ // src/index.ts
7482
+ init_cdp();
7483
+
7341
7484
  // src/engines/lightpanda.ts
7485
+ init_types();
7342
7486
  import { execSync, spawn } from "child_process";
7343
7487
  import { chromium as chromium2 } from "playwright";
7344
7488
  var DEFAULT_LIGHTPANDA_PORT = 9222;
@@ -7480,6 +7624,9 @@ class LightpandaPage {
7480
7624
  await this.page.context().close();
7481
7625
  }
7482
7626
  }
7627
+ // src/engines/selector.ts
7628
+ init_types();
7629
+
7483
7630
  // src/engines/bun-webview.ts
7484
7631
  import { join as join2 } from "path";
7485
7632
  import { mkdirSync as mkdirSync2 } from "fs";
@@ -7953,6 +8100,10 @@ function inferUseCase(label) {
7953
8100
  };
7954
8101
  return map[label.toLowerCase()] ?? "spa_navigate" /* SPA_NAVIGATE */;
7955
8102
  }
8103
+ // src/lib/session.ts
8104
+ init_types();
8105
+ init_types();
8106
+
7956
8107
  // src/lib/network.ts
7957
8108
  function enableNetworkLogging(page, sessionId) {
7958
8109
  const requestStart = new Map;
@@ -8229,6 +8380,37 @@ function createBunProxy(view) {
8229
8380
  return view;
8230
8381
  }
8231
8382
  async function createSession2(opts = {}) {
8383
+ if (opts.cdpUrl) {
8384
+ const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
8385
+ const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
8386
+ const contexts = cdpBrowser.contexts();
8387
+ const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
8388
+ const pages = context.pages();
8389
+ const page2 = pages.length > 0 ? pages[0] : await context.newPage();
8390
+ const session2 = createSession({
8391
+ engine: "cdp",
8392
+ projectId: opts.projectId,
8393
+ agentId: opts.agentId,
8394
+ startUrl: page2.url(),
8395
+ name: opts.name ?? "attached"
8396
+ });
8397
+ const cleanups2 = [];
8398
+ if (opts.captureNetwork !== false) {
8399
+ try {
8400
+ cleanups2.push(enableNetworkLogging(page2, session2.id));
8401
+ } catch {}
8402
+ }
8403
+ if (opts.captureConsole !== false) {
8404
+ try {
8405
+ cleanups2.push(enableConsoleCapture(page2, session2.id));
8406
+ } catch {}
8407
+ }
8408
+ try {
8409
+ cleanups2.push(setupDialogHandler(page2, session2.id));
8410
+ } catch {}
8411
+ handles.set(session2.id, { browser: cdpBrowser, bunView: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
8412
+ return { session: session2, page: page2 };
8413
+ }
8232
8414
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
8233
8415
  const resolvedEngine = engine === "auto" ? "playwright" : engine;
8234
8416
  let browser = null;
@@ -8254,7 +8436,22 @@ async function createSession2(opts = {}) {
8254
8436
  page = await context.newPage();
8255
8437
  } else {
8256
8438
  browser = await pool.acquire(opts.headless ?? true);
8257
- page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8439
+ if (opts.storageState) {
8440
+ const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
8441
+ const statePath2 = loadStatePath2(opts.storageState);
8442
+ if (statePath2) {
8443
+ const context = await browser.newContext({
8444
+ viewport: opts.viewport ?? { width: 1280, height: 720 },
8445
+ userAgent: opts.userAgent,
8446
+ storageState: statePath2
8447
+ });
8448
+ page = await context.newPage();
8449
+ } else {
8450
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8451
+ }
8452
+ } else {
8453
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8454
+ }
8258
8455
  }
8259
8456
  const sessionName = opts.name ?? (opts.startUrl ? (() => {
8260
8457
  try {
@@ -8452,6 +8649,9 @@ function isAutoGallery(sessionId) {
8452
8649
  function countActiveSessions2() {
8453
8650
  return countActiveSessions();
8454
8651
  }
8652
+ // src/lib/actions.ts
8653
+ init_types();
8654
+
8455
8655
  // src/lib/snapshot.ts
8456
8656
  var lastSnapshots = new Map;
8457
8657
  var sessionRefMaps = new Map;
@@ -8465,6 +8665,66 @@ function getRefLocator(page, sessionId, ref) {
8465
8665
  return page.getByRole(entry.role, { name: entry.name }).first();
8466
8666
  }
8467
8667
 
8668
+ // src/lib/self-heal.ts
8669
+ async function healSelector(page, selector, sessionId) {
8670
+ const attempts = [];
8671
+ attempts.push(`selector: ${selector}`);
8672
+ try {
8673
+ const loc = page.locator(selector).first();
8674
+ if (await loc.count() > 0) {
8675
+ return { found: true, locator: loc, method: "original", healed: false, attempts };
8676
+ }
8677
+ } catch {}
8678
+ if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[") && !selector.includes(">") && !selector.includes(" ")) {
8679
+ attempts.push(`text: "${selector}"`);
8680
+ try {
8681
+ const loc = page.getByText(selector, { exact: false }).first();
8682
+ if (await loc.count() > 0) {
8683
+ return { found: true, locator: loc, method: "text", healed: true, attempts };
8684
+ }
8685
+ } catch {}
8686
+ }
8687
+ const roleMap = {
8688
+ button: ["button", "submit", "reset"],
8689
+ link: ["a"],
8690
+ input: ["input", "textarea"],
8691
+ heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
8692
+ };
8693
+ const nameHint = selector.replace(/^[#.]/, "").replace(/[-_]/g, " ").toLowerCase();
8694
+ for (const [role, tags] of Object.entries(roleMap)) {
8695
+ attempts.push(`role: ${role} name~="${nameHint}"`);
8696
+ try {
8697
+ const loc = page.getByRole(role, { name: new RegExp(nameHint.split(" ")[0], "i") }).first();
8698
+ if (await loc.count() > 0) {
8699
+ return { found: true, locator: loc, method: "role", healed: true, attempts };
8700
+ }
8701
+ } catch {}
8702
+ }
8703
+ if (selector.startsWith("#")) {
8704
+ const idPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
8705
+ const partialSel = `[id*="${idPart}"]`;
8706
+ attempts.push(`partial_id: ${partialSel}`);
8707
+ try {
8708
+ const loc = page.locator(partialSel).first();
8709
+ if (await loc.count() > 0) {
8710
+ return { found: true, locator: loc, method: "partial_id", healed: true, attempts };
8711
+ }
8712
+ } catch {}
8713
+ }
8714
+ if (selector.startsWith(".")) {
8715
+ const classPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
8716
+ const partialSel = `[class*="${classPart}"]`;
8717
+ attempts.push(`partial_class: ${partialSel}`);
8718
+ try {
8719
+ const loc = page.locator(partialSel).first();
8720
+ if (await loc.count() > 0) {
8721
+ return { found: true, locator: loc, method: "partial_class", healed: true, attempts };
8722
+ }
8723
+ } catch {}
8724
+ }
8725
+ return { found: false, locator: null, method: "none", healed: false, attempts };
8726
+ }
8727
+
8468
8728
  // src/lib/actions.ts
8469
8729
  async function click(page, selector, opts) {
8470
8730
  try {
@@ -8474,11 +8734,22 @@ async function click(page, selector, opts) {
8474
8734
  delay: opts?.delay,
8475
8735
  timeout: opts?.timeout ?? 1e4
8476
8736
  });
8477
- } catch (err) {
8478
- if (err instanceof Error && err.message.includes("not found")) {
8737
+ return {};
8738
+ } catch (originalError) {
8739
+ if (opts?.selfHeal !== false) {
8740
+ const result = await healSelector(page, selector);
8741
+ if (result.found && result.locator) {
8742
+ await result.locator.click({
8743
+ button: opts?.button ?? "left",
8744
+ timeout: opts?.timeout ?? 1e4
8745
+ });
8746
+ return { healed: true, method: result.method, attempts: result.attempts };
8747
+ }
8748
+ }
8749
+ if (originalError instanceof Error && originalError.message.includes("not found")) {
8479
8750
  throw new ElementNotFoundError(selector);
8480
8751
  }
8481
- throw new BrowserError(`Click failed on '${selector}': ${err instanceof Error ? err.message : String(err)}`, "CLICK_FAILED");
8752
+ throw new BrowserError(`Click failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "CLICK_FAILED");
8482
8753
  }
8483
8754
  }
8484
8755
  async function type(page, selector, text, opts) {
@@ -8487,17 +8758,35 @@ async function type(page, selector, text, opts) {
8487
8758
  await page.fill(selector, "", { timeout: opts?.timeout ?? 1e4 });
8488
8759
  }
8489
8760
  await page.type(selector, text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
8490
- } catch (err) {
8491
- if (err instanceof Error && err.message.includes("not found")) {
8761
+ return {};
8762
+ } catch (originalError) {
8763
+ if (opts?.selfHeal !== false) {
8764
+ const result = await healSelector(page, selector);
8765
+ if (result.found && result.locator) {
8766
+ if (opts?.clear)
8767
+ await result.locator.fill("", { timeout: opts?.timeout ?? 1e4 });
8768
+ await result.locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
8769
+ return { healed: true, method: result.method, attempts: result.attempts };
8770
+ }
8771
+ }
8772
+ if (originalError instanceof Error && originalError.message.includes("not found")) {
8492
8773
  throw new ElementNotFoundError(selector);
8493
8774
  }
8494
- throw new BrowserError(`Type failed on '${selector}': ${err instanceof Error ? err.message : String(err)}`, "TYPE_FAILED");
8775
+ throw new BrowserError(`Type failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "TYPE_FAILED");
8495
8776
  }
8496
8777
  }
8497
- async function fill(page, selector, value, timeout = 1e4) {
8778
+ async function fill(page, selector, value, timeout = 1e4, selfHeal = true) {
8498
8779
  try {
8499
8780
  await page.fill(selector, value, { timeout });
8500
- } catch (err) {
8781
+ return {};
8782
+ } catch (originalError) {
8783
+ if (selfHeal) {
8784
+ const result = await healSelector(page, selector);
8785
+ if (result.found && result.locator) {
8786
+ await result.locator.fill(value, { timeout });
8787
+ return { healed: true, method: result.method, attempts: result.attempts };
8788
+ }
8789
+ }
8501
8790
  throw new ElementNotFoundError(selector);
8502
8791
  }
8503
8792
  }
@@ -8623,12 +8912,39 @@ async function clickText(page, text, opts) {
8623
8912
  }
8624
8913
  }, { retries: opts?.retries ?? 1 });
8625
8914
  }
8626
- async function fillForm(page, fields, submitSelector) {
8915
+ async function fillForm(page, fields, submitSelector, selfHeal = true) {
8627
8916
  let filled = 0;
8628
8917
  const errors = [];
8918
+ const healedFields = [];
8629
8919
  for (const [selector, value] of Object.entries(fields)) {
8630
8920
  try {
8631
- const el = await page.$(selector);
8921
+ let el = await page.$(selector);
8922
+ if (!el && selfHeal) {
8923
+ const result = await healSelector(page, selector);
8924
+ if (result.found && result.locator) {
8925
+ const handle = await result.locator.elementHandle();
8926
+ if (handle) {
8927
+ el = handle;
8928
+ healedFields.push(selector);
8929
+ const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
8930
+ const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
8931
+ if (tagName2 === "select") {
8932
+ await result.locator.selectOption(String(value));
8933
+ } else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
8934
+ if (Boolean(value))
8935
+ await result.locator.check();
8936
+ else
8937
+ await result.locator.uncheck();
8938
+ } else {
8939
+ await result.locator.fill(String(value));
8940
+ }
8941
+ filled++;
8942
+ continue;
8943
+ }
8944
+ }
8945
+ errors.push(`${selector}: element not found`);
8946
+ continue;
8947
+ }
8632
8948
  if (!el) {
8633
8949
  errors.push(`${selector}: element not found`);
8634
8950
  continue;
@@ -8655,11 +8971,21 @@ async function fillForm(page, fields, submitSelector) {
8655
8971
  if (submitSelector) {
8656
8972
  try {
8657
8973
  await page.click(submitSelector);
8658
- } catch (err) {
8659
- errors.push(`submit(${submitSelector}): ${err instanceof Error ? err.message : String(err)}`);
8974
+ } catch (submitErr) {
8975
+ if (selfHeal) {
8976
+ const result = await healSelector(page, submitSelector);
8977
+ if (result.found && result.locator) {
8978
+ await result.locator.click();
8979
+ healedFields.push(submitSelector);
8980
+ } else {
8981
+ errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
8982
+ }
8983
+ } else {
8984
+ errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
8985
+ }
8660
8986
  }
8661
8987
  }
8662
- return { filled, errors, fields_attempted: Object.keys(fields).length };
8988
+ return { filled, errors, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
8663
8989
  }
8664
8990
  async function waitForText(page, text, opts) {
8665
8991
  const timeout = opts?.timeout ?? 1e4;
@@ -8939,6 +9265,7 @@ async function getPageInfo(page) {
8939
9265
  };
8940
9266
  }
8941
9267
  // src/lib/performance.ts
9268
+ init_cdp();
8942
9269
  async function getPerformanceMetrics(page) {
8943
9270
  const navTiming = await page.evaluate(() => {
8944
9271
  const t = performance.timing;
@@ -9026,10 +9353,11 @@ async function startCoverage(page) {
9026
9353
  };
9027
9354
  }
9028
9355
  // src/lib/screenshot.ts
9356
+ init_types();
9029
9357
  var import_sharp = __toESM(require_lib(), 1);
9030
- import { join as join3 } from "path";
9031
- import { mkdirSync as mkdirSync3 } from "fs";
9032
- import { homedir as homedir3 } from "os";
9358
+ import { join as join4 } from "path";
9359
+ import { mkdirSync as mkdirSync4 } from "fs";
9360
+ import { homedir as homedir4 } from "os";
9033
9361
 
9034
9362
  // src/db/gallery.ts
9035
9363
  init_schema();
@@ -9075,13 +9403,13 @@ function getEntry(id) {
9075
9403
 
9076
9404
  // src/lib/screenshot.ts
9077
9405
  function getDataDir2() {
9078
- return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
9406
+ return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
9079
9407
  }
9080
9408
  function getScreenshotDir(projectId) {
9081
- const base = join3(getDataDir2(), "screenshots");
9409
+ const base = join4(getDataDir2(), "screenshots");
9082
9410
  const date = new Date().toISOString().split("T")[0];
9083
- const dir = projectId ? join3(base, projectId, date) : join3(base, date);
9084
- mkdirSync3(dir, { recursive: true });
9411
+ const dir = projectId ? join4(base, projectId, date) : join4(base, date);
9412
+ mkdirSync4(dir, { recursive: true });
9085
9413
  return dir;
9086
9414
  }
9087
9415
  async function compressBuffer(raw, format, quality, maxWidth) {
@@ -9096,7 +9424,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
9096
9424
  }
9097
9425
  }
9098
9426
  async function generateThumbnail(raw, dir, stem) {
9099
- const thumbPath = join3(dir, `${stem}.thumb.webp`);
9427
+ const thumbPath = join4(dir, `${stem}.thumb.webp`);
9100
9428
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
9101
9429
  await Bun.write(thumbPath, thumbBuffer);
9102
9430
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -9153,7 +9481,7 @@ async function takeScreenshot(page, opts) {
9153
9481
  const compressedSizeBytes = finalBuffer.length;
9154
9482
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
9155
9483
  const ext = format;
9156
- const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
9484
+ const screenshotPath = opts?.path ?? join4(dir, `${stem}.${ext}`);
9157
9485
  await Bun.write(screenshotPath, finalBuffer);
9158
9486
  let thumbnailPath;
9159
9487
  let thumbnailBase64;
@@ -9213,12 +9541,12 @@ async function takeScreenshot(page, opts) {
9213
9541
  }
9214
9542
  async function generatePDF(page, opts) {
9215
9543
  try {
9216
- const base = join3(getDataDir2(), "pdfs");
9544
+ const base = join4(getDataDir2(), "pdfs");
9217
9545
  const date = new Date().toISOString().split("T")[0];
9218
- const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
9219
- mkdirSync3(dir, { recursive: true });
9546
+ const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
9547
+ mkdirSync4(dir, { recursive: true });
9220
9548
  const timestamp = Date.now();
9221
- const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
9549
+ const pdfPath = opts?.path ?? join4(dir, `${timestamp}.pdf`);
9222
9550
  const buffer = await page.pdf({
9223
9551
  path: pdfPath,
9224
9552
  format: opts?.format ?? "A4",
@@ -9308,6 +9636,7 @@ async function getIndexedDB(page, dbName, storeName) {
9308
9636
  }), [dbName, storeName]);
9309
9637
  }
9310
9638
  // src/lib/recorder.ts
9639
+ init_types();
9311
9640
  var activeRecordings = new Map;
9312
9641
  function startRecording(sessionId, name, startUrl) {
9313
9642
  const steps = [];
@@ -9466,6 +9795,7 @@ function exportRecording(recordingId, format = "json") {
9466
9795
  `);
9467
9796
  }
9468
9797
  // src/lib/crawler.ts
9798
+ init_types();
9469
9799
  async function crawl(startUrl, opts = {}) {
9470
9800
  const maxDepth = opts.maxDepth ?? 2;
9471
9801
  const maxPages = opts.maxPages ?? 50;
@@ -9678,6 +10008,7 @@ export {
9678
10008
  createCrawlResult,
9679
10009
  crawl,
9680
10010
  countActiveSessions2 as countActiveSessions,
10011
+ connectToExistingBrowser,
9681
10012
  connectLightpanda,
9682
10013
  closeSession2 as closeSession,
9683
10014
  closePage,