@hasna/browser 0.0.4 → 0.0.5

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
@@ -16,8 +16,249 @@ var __toESM = (mod, isNodeMode, target) => {
16
16
  return to;
17
17
  };
18
18
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
19
29
  var __require = import.meta.require;
20
30
 
31
+ // src/db/schema.ts
32
+ import { Database } from "bun:sqlite";
33
+ import { join } from "path";
34
+ import { mkdirSync } from "fs";
35
+ import { homedir } from "os";
36
+ function getDataDir() {
37
+ return process.env["BROWSER_DATA_DIR"] ?? join(homedir(), ".browser");
38
+ }
39
+ function getDatabase(path) {
40
+ const resolvedPath = path ?? process.env["BROWSER_DB_PATH"] ?? join(getDataDir(), "browser.db");
41
+ if (_db && _dbPath === resolvedPath)
42
+ return _db;
43
+ if (_db) {
44
+ try {
45
+ _db.close();
46
+ } catch {}
47
+ _db = null;
48
+ }
49
+ mkdirSync(join(resolvedPath, ".."), { recursive: true });
50
+ _db = new Database(resolvedPath);
51
+ _dbPath = resolvedPath;
52
+ _db.exec("PRAGMA journal_mode=WAL;");
53
+ _db.exec("PRAGMA foreign_keys=ON;");
54
+ runMigrations(_db);
55
+ return _db;
56
+ }
57
+ function resetDatabase() {
58
+ if (_db) {
59
+ try {
60
+ _db.close();
61
+ } catch {}
62
+ }
63
+ _db = null;
64
+ _dbPath = null;
65
+ }
66
+ function runMigrations(db) {
67
+ db.exec(`
68
+ CREATE TABLE IF NOT EXISTS schema_migrations (
69
+ version INTEGER PRIMARY KEY,
70
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
71
+ );
72
+ `);
73
+ const applied = new Set(db.query("SELECT version FROM schema_migrations").all().map((r) => r.version));
74
+ const migrations = [
75
+ {
76
+ version: 1,
77
+ sql: `
78
+ CREATE TABLE IF NOT EXISTS projects (
79
+ id TEXT PRIMARY KEY,
80
+ name TEXT NOT NULL UNIQUE,
81
+ path TEXT NOT NULL,
82
+ description TEXT,
83
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
84
+ );
85
+
86
+ CREATE TABLE IF NOT EXISTS agents (
87
+ id TEXT PRIMARY KEY,
88
+ name TEXT NOT NULL,
89
+ description TEXT,
90
+ session_id TEXT,
91
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
92
+ working_dir TEXT,
93
+ last_seen TEXT NOT NULL DEFAULT (datetime('now')),
94
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
95
+ );
96
+
97
+ CREATE TABLE IF NOT EXISTS heartbeats (
98
+ id TEXT PRIMARY KEY,
99
+ agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
100
+ session_id TEXT,
101
+ timestamp TEXT NOT NULL DEFAULT (datetime('now'))
102
+ );
103
+
104
+ CREATE TABLE IF NOT EXISTS sessions (
105
+ id TEXT PRIMARY KEY,
106
+ engine TEXT NOT NULL,
107
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
108
+ agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
109
+ start_url TEXT,
110
+ status TEXT NOT NULL DEFAULT 'active',
111
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
112
+ closed_at TEXT
113
+ );
114
+
115
+ CREATE TABLE IF NOT EXISTS snapshots (
116
+ id TEXT PRIMARY KEY,
117
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
118
+ url TEXT NOT NULL,
119
+ title TEXT,
120
+ html TEXT,
121
+ screenshot_path TEXT,
122
+ timestamp TEXT NOT NULL DEFAULT (datetime('now'))
123
+ );
124
+
125
+ CREATE TABLE IF NOT EXISTS network_log (
126
+ id TEXT PRIMARY KEY,
127
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
128
+ method TEXT NOT NULL,
129
+ url TEXT NOT NULL,
130
+ status_code INTEGER,
131
+ request_headers TEXT,
132
+ response_headers TEXT,
133
+ request_body TEXT,
134
+ body_size INTEGER,
135
+ duration_ms INTEGER,
136
+ resource_type TEXT,
137
+ timestamp TEXT NOT NULL DEFAULT (datetime('now'))
138
+ );
139
+
140
+ CREATE TABLE IF NOT EXISTS console_log (
141
+ id TEXT PRIMARY KEY,
142
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
143
+ level TEXT NOT NULL DEFAULT 'log',
144
+ message TEXT NOT NULL,
145
+ source TEXT,
146
+ line_number INTEGER,
147
+ timestamp TEXT NOT NULL DEFAULT (datetime('now'))
148
+ );
149
+
150
+ CREATE TABLE IF NOT EXISTS recordings (
151
+ id TEXT PRIMARY KEY,
152
+ name TEXT NOT NULL,
153
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
154
+ start_url TEXT,
155
+ steps TEXT NOT NULL DEFAULT '[]',
156
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
157
+ );
158
+
159
+ CREATE TABLE IF NOT EXISTS crawl_results (
160
+ id TEXT PRIMARY KEY,
161
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
162
+ start_url TEXT NOT NULL,
163
+ depth INTEGER NOT NULL DEFAULT 1,
164
+ pages TEXT NOT NULL DEFAULT '[]',
165
+ links TEXT NOT NULL DEFAULT '[]',
166
+ errors TEXT NOT NULL DEFAULT '[]',
167
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
168
+ );
169
+
170
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
171
+ CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
172
+ CREATE INDEX IF NOT EXISTS idx_snapshots_session ON snapshots(session_id);
173
+ CREATE INDEX IF NOT EXISTS idx_network_log_session ON network_log(session_id);
174
+ CREATE INDEX IF NOT EXISTS idx_console_log_session ON console_log(session_id);
175
+ CREATE INDEX IF NOT EXISTS idx_agents_project ON agents(project_id);
176
+ CREATE INDEX IF NOT EXISTS idx_heartbeats_agent ON heartbeats(agent_id);
177
+ CREATE INDEX IF NOT EXISTS idx_recordings_project ON recordings(project_id);
178
+ CREATE INDEX IF NOT EXISTS idx_crawl_results_project ON crawl_results(project_id);
179
+ `
180
+ },
181
+ {
182
+ version: 2,
183
+ sql: `
184
+ -- Gallery entries
185
+ CREATE TABLE IF NOT EXISTS gallery_entries (
186
+ id TEXT PRIMARY KEY,
187
+ session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
188
+ project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
189
+ url TEXT,
190
+ title TEXT,
191
+ path TEXT NOT NULL,
192
+ thumbnail_path TEXT,
193
+ format TEXT,
194
+ width INTEGER,
195
+ height INTEGER,
196
+ original_size_bytes INTEGER,
197
+ compressed_size_bytes INTEGER,
198
+ compression_ratio REAL,
199
+ tags TEXT NOT NULL DEFAULT '[]',
200
+ notes TEXT,
201
+ is_favorite INTEGER NOT NULL DEFAULT 0,
202
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
203
+ );
204
+
205
+ -- Session name column (migration 2 adds it)
206
+ ALTER TABLE sessions ADD COLUMN name TEXT;
207
+
208
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name) WHERE name IS NOT NULL;
209
+ CREATE INDEX IF NOT EXISTS idx_gallery_session ON gallery_entries(session_id);
210
+ CREATE INDEX IF NOT EXISTS idx_gallery_project ON gallery_entries(project_id);
211
+ CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
212
+ CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
213
+ `
214
+ }
215
+ ];
216
+ for (const m of migrations) {
217
+ if (!applied.has(m.version)) {
218
+ db.transaction(() => {
219
+ db.exec(m.sql);
220
+ db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(m.version);
221
+ })();
222
+ }
223
+ }
224
+ }
225
+ var _db = null, _dbPath = null;
226
+ var init_schema = () => {};
227
+
228
+ // src/db/console-log.ts
229
+ var exports_console_log = {};
230
+ __export(exports_console_log, {
231
+ logConsoleMessage: () => logConsoleMessage,
232
+ getConsoleMessage: () => getConsoleMessage,
233
+ getConsoleLog: () => getConsoleLog,
234
+ clearConsoleLog: () => clearConsoleLog
235
+ });
236
+ import { randomUUID as randomUUID6 } from "crypto";
237
+ function logConsoleMessage(data) {
238
+ const db = getDatabase();
239
+ const id = randomUUID6();
240
+ db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
241
+ return getConsoleMessage(id);
242
+ }
243
+ function getConsoleMessage(id) {
244
+ const db = getDatabase();
245
+ return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
246
+ }
247
+ function getConsoleLog(sessionId, level) {
248
+ const db = getDatabase();
249
+ if (level) {
250
+ return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
251
+ }
252
+ return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
253
+ }
254
+ function clearConsoleLog(sessionId) {
255
+ const db = getDatabase();
256
+ db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
257
+ }
258
+ var init_console_log = __esm(() => {
259
+ init_schema();
260
+ });
261
+
21
262
  // node_modules/sharp/lib/is.js
22
263
  var require_is = __commonJS((exports, module) => {
23
264
  /*!
@@ -3680,7 +3921,7 @@ var require_color = __commonJS((exports, module) => {
3680
3921
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3681
3922
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
3682
3923
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
3683
- var __export = (target, all) => {
3924
+ var __export2 = (target, all) => {
3684
3925
  for (var name in all)
3685
3926
  __defProp2(target, name, { get: all[name], enumerable: true });
3686
3927
  };
@@ -3694,7 +3935,7 @@ var require_color = __commonJS((exports, module) => {
3694
3935
  };
3695
3936
  var __toCommonJS = (mod) => __copyProps(__defProp2({}, "__esModule", { value: true }), mod);
3696
3937
  var index_exports = {};
3697
- __export(index_exports, {
3938
+ __export2(index_exports, {
3698
3939
  default: () => index_default
3699
3940
  });
3700
3941
  module.exports = __toCommonJS(index_exports);
@@ -6491,209 +6732,18 @@ class ProjectNotFoundError extends BrowserError {
6491
6732
  this.name = "ProjectNotFoundError";
6492
6733
  }
6493
6734
  }
6494
- // src/db/schema.ts
6495
- import { Database } from "bun:sqlite";
6496
- import { join } from "path";
6497
- import { mkdirSync } from "fs";
6498
- import { homedir } from "os";
6499
- function getDataDir() {
6500
- return process.env["BROWSER_DATA_DIR"] ?? join(homedir(), ".browser");
6501
- }
6502
- var _db = null;
6503
- var _dbPath = null;
6504
- function getDatabase(path) {
6505
- const resolvedPath = path ?? process.env["BROWSER_DB_PATH"] ?? join(getDataDir(), "browser.db");
6506
- if (_db && _dbPath === resolvedPath)
6507
- return _db;
6508
- if (_db) {
6509
- try {
6510
- _db.close();
6511
- } catch {}
6512
- _db = null;
6513
- }
6514
- mkdirSync(join(resolvedPath, ".."), { recursive: true });
6515
- _db = new Database(resolvedPath);
6516
- _dbPath = resolvedPath;
6517
- _db.exec("PRAGMA journal_mode=WAL;");
6518
- _db.exec("PRAGMA foreign_keys=ON;");
6519
- runMigrations(_db);
6520
- return _db;
6521
- }
6522
- function resetDatabase() {
6523
- if (_db) {
6524
- try {
6525
- _db.close();
6526
- } catch {}
6527
- }
6528
- _db = null;
6529
- _dbPath = null;
6530
- }
6531
- function runMigrations(db) {
6532
- db.exec(`
6533
- CREATE TABLE IF NOT EXISTS schema_migrations (
6534
- version INTEGER PRIMARY KEY,
6535
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
6536
- );
6537
- `);
6538
- const applied = new Set(db.query("SELECT version FROM schema_migrations").all().map((r) => r.version));
6539
- const migrations = [
6540
- {
6541
- version: 1,
6542
- sql: `
6543
- CREATE TABLE IF NOT EXISTS projects (
6544
- id TEXT PRIMARY KEY,
6545
- name TEXT NOT NULL UNIQUE,
6546
- path TEXT NOT NULL,
6547
- description TEXT,
6548
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
6549
- );
6550
-
6551
- CREATE TABLE IF NOT EXISTS agents (
6552
- id TEXT PRIMARY KEY,
6553
- name TEXT NOT NULL,
6554
- description TEXT,
6555
- session_id TEXT,
6556
- project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
6557
- working_dir TEXT,
6558
- last_seen TEXT NOT NULL DEFAULT (datetime('now')),
6559
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
6560
- );
6561
-
6562
- CREATE TABLE IF NOT EXISTS heartbeats (
6563
- id TEXT PRIMARY KEY,
6564
- agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
6565
- session_id TEXT,
6566
- timestamp TEXT NOT NULL DEFAULT (datetime('now'))
6567
- );
6568
-
6569
- CREATE TABLE IF NOT EXISTS sessions (
6570
- id TEXT PRIMARY KEY,
6571
- engine TEXT NOT NULL,
6572
- project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
6573
- agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
6574
- start_url TEXT,
6575
- status TEXT NOT NULL DEFAULT 'active',
6576
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
6577
- closed_at TEXT
6578
- );
6579
-
6580
- CREATE TABLE IF NOT EXISTS snapshots (
6581
- id TEXT PRIMARY KEY,
6582
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
6583
- url TEXT NOT NULL,
6584
- title TEXT,
6585
- html TEXT,
6586
- screenshot_path TEXT,
6587
- timestamp TEXT NOT NULL DEFAULT (datetime('now'))
6588
- );
6589
-
6590
- CREATE TABLE IF NOT EXISTS network_log (
6591
- id TEXT PRIMARY KEY,
6592
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
6593
- method TEXT NOT NULL,
6594
- url TEXT NOT NULL,
6595
- status_code INTEGER,
6596
- request_headers TEXT,
6597
- response_headers TEXT,
6598
- request_body TEXT,
6599
- body_size INTEGER,
6600
- duration_ms INTEGER,
6601
- resource_type TEXT,
6602
- timestamp TEXT NOT NULL DEFAULT (datetime('now'))
6603
- );
6604
-
6605
- CREATE TABLE IF NOT EXISTS console_log (
6606
- id TEXT PRIMARY KEY,
6607
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
6608
- level TEXT NOT NULL DEFAULT 'log',
6609
- message TEXT NOT NULL,
6610
- source TEXT,
6611
- line_number INTEGER,
6612
- timestamp TEXT NOT NULL DEFAULT (datetime('now'))
6613
- );
6614
-
6615
- CREATE TABLE IF NOT EXISTS recordings (
6616
- id TEXT PRIMARY KEY,
6617
- name TEXT NOT NULL,
6618
- project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
6619
- start_url TEXT,
6620
- steps TEXT NOT NULL DEFAULT '[]',
6621
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
6622
- );
6623
-
6624
- CREATE TABLE IF NOT EXISTS crawl_results (
6625
- id TEXT PRIMARY KEY,
6626
- project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
6627
- start_url TEXT NOT NULL,
6628
- depth INTEGER NOT NULL DEFAULT 1,
6629
- pages TEXT NOT NULL DEFAULT '[]',
6630
- links TEXT NOT NULL DEFAULT '[]',
6631
- errors TEXT NOT NULL DEFAULT '[]',
6632
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
6633
- );
6634
-
6635
- CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);
6636
- CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
6637
- CREATE INDEX IF NOT EXISTS idx_snapshots_session ON snapshots(session_id);
6638
- CREATE INDEX IF NOT EXISTS idx_network_log_session ON network_log(session_id);
6639
- CREATE INDEX IF NOT EXISTS idx_console_log_session ON console_log(session_id);
6640
- CREATE INDEX IF NOT EXISTS idx_agents_project ON agents(project_id);
6641
- CREATE INDEX IF NOT EXISTS idx_heartbeats_agent ON heartbeats(agent_id);
6642
- CREATE INDEX IF NOT EXISTS idx_recordings_project ON recordings(project_id);
6643
- CREATE INDEX IF NOT EXISTS idx_crawl_results_project ON crawl_results(project_id);
6644
- `
6645
- },
6646
- {
6647
- version: 2,
6648
- sql: `
6649
- -- Gallery entries
6650
- CREATE TABLE IF NOT EXISTS gallery_entries (
6651
- id TEXT PRIMARY KEY,
6652
- session_id TEXT REFERENCES sessions(id) ON DELETE SET NULL,
6653
- project_id TEXT REFERENCES projects(id) ON DELETE SET NULL,
6654
- url TEXT,
6655
- title TEXT,
6656
- path TEXT NOT NULL,
6657
- thumbnail_path TEXT,
6658
- format TEXT,
6659
- width INTEGER,
6660
- height INTEGER,
6661
- original_size_bytes INTEGER,
6662
- compressed_size_bytes INTEGER,
6663
- compression_ratio REAL,
6664
- tags TEXT NOT NULL DEFAULT '[]',
6665
- notes TEXT,
6666
- is_favorite INTEGER NOT NULL DEFAULT 0,
6667
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
6668
- );
6669
-
6670
- -- Session name column (migration 2 adds it)
6671
- ALTER TABLE sessions ADD COLUMN name TEXT;
6672
-
6673
- CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_name ON sessions(name) WHERE name IS NOT NULL;
6674
- CREATE INDEX IF NOT EXISTS idx_gallery_session ON gallery_entries(session_id);
6675
- CREATE INDEX IF NOT EXISTS idx_gallery_project ON gallery_entries(project_id);
6676
- CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
6677
- CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
6678
- `
6679
- }
6680
- ];
6681
- for (const m of migrations) {
6682
- if (!applied.has(m.version)) {
6683
- db.transaction(() => {
6684
- db.exec(m.sql);
6685
- db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(m.version);
6686
- })();
6687
- }
6688
- }
6689
- }
6690
- // src/db/projects.ts
6691
- import { randomUUID } from "crypto";
6692
- function createProject(data) {
6693
- const db = getDatabase();
6694
- const id = randomUUID();
6695
- db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
6696
- return getProject(id);
6735
+
6736
+ // src/index.ts
6737
+ init_schema();
6738
+
6739
+ // src/db/projects.ts
6740
+ init_schema();
6741
+ import { randomUUID } from "crypto";
6742
+ function createProject(data) {
6743
+ const db = getDatabase();
6744
+ const id = randomUUID();
6745
+ db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
6746
+ return getProject(id);
6697
6747
  }
6698
6748
  function ensureProject(name, path, description) {
6699
6749
  const db = getDatabase();
@@ -6744,6 +6794,7 @@ function deleteProject(id) {
6744
6794
  db.prepare("DELETE FROM projects WHERE id = ?").run(id);
6745
6795
  }
6746
6796
  // src/db/agents.ts
6797
+ init_schema();
6747
6798
  import { randomUUID as randomUUID2 } from "crypto";
6748
6799
  function registerAgent(name, opts = {}) {
6749
6800
  const db = getDatabase();
@@ -6823,6 +6874,7 @@ function cleanStaleAgents(thresholdMs) {
6823
6874
  return result.changes;
6824
6875
  }
6825
6876
  // src/db/sessions.ts
6877
+ init_schema();
6826
6878
  import { randomUUID as randomUUID3 } from "crypto";
6827
6879
  function createSession(data) {
6828
6880
  const db = getDatabase();
@@ -6882,6 +6934,7 @@ function deleteSession(id) {
6882
6934
  db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
6883
6935
  }
6884
6936
  // src/db/snapshots.ts
6937
+ init_schema();
6885
6938
  import { randomUUID as randomUUID4 } from "crypto";
6886
6939
  function createSnapshot(data) {
6887
6940
  const db = getDatabase();
@@ -6906,6 +6959,7 @@ function deleteSnapshotsBySession(sessionId) {
6906
6959
  db.prepare("DELETE FROM snapshots WHERE session_id = ?").run(sessionId);
6907
6960
  }
6908
6961
  // src/db/network-log.ts
6962
+ init_schema();
6909
6963
  import { randomUUID as randomUUID5 } from "crypto";
6910
6964
  function logRequest(data) {
6911
6965
  const db = getDatabase();
@@ -6931,30 +6985,12 @@ function deleteNetworkRequest(id) {
6931
6985
  const db = getDatabase();
6932
6986
  db.prepare("DELETE FROM network_log WHERE id = ?").run(id);
6933
6987
  }
6934
- // src/db/console-log.ts
6935
- import { randomUUID as randomUUID6 } from "crypto";
6936
- function logConsoleMessage(data) {
6937
- const db = getDatabase();
6938
- const id = randomUUID6();
6939
- db.prepare("INSERT INTO console_log (id, session_id, level, message, source, line_number) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.level, data.message, data.source ?? null, data.line_number ?? null);
6940
- return getConsoleMessage(id);
6941
- }
6942
- function getConsoleMessage(id) {
6943
- const db = getDatabase();
6944
- return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
6945
- }
6946
- function getConsoleLog(sessionId, level) {
6947
- const db = getDatabase();
6948
- if (level) {
6949
- return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
6950
- }
6951
- return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
6952
- }
6953
- function clearConsoleLog(sessionId) {
6954
- const db = getDatabase();
6955
- db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
6956
- }
6988
+
6989
+ // src/index.ts
6990
+ init_console_log();
6991
+
6957
6992
  // src/db/recordings.ts
6993
+ init_schema();
6958
6994
  import { randomUUID as randomUUID7 } from "crypto";
6959
6995
  function deserialize(row) {
6960
6996
  return {
@@ -7009,6 +7045,7 @@ function deleteRecording(id) {
7009
7045
  db.prepare("DELETE FROM recordings WHERE id = ?").run(id);
7010
7046
  }
7011
7047
  // src/db/crawl-results.ts
7048
+ init_schema();
7012
7049
  import { randomUUID as randomUUID8 } from "crypto";
7013
7050
  function deserialize2(row) {
7014
7051
  const pages = JSON.parse(row.pages);
@@ -7044,6 +7081,7 @@ function deleteCrawlResult(id) {
7044
7081
  db.prepare("DELETE FROM crawl_results WHERE id = ?").run(id);
7045
7082
  }
7046
7083
  // src/db/heartbeats.ts
7084
+ init_schema();
7047
7085
  import { randomUUID as randomUUID9 } from "crypto";
7048
7086
  function recordHeartbeat(agentId, sessionId) {
7049
7087
  const db = getDatabase();
@@ -7394,14 +7432,413 @@ class LightpandaPage {
7394
7432
  await this.page.context().close();
7395
7433
  }
7396
7434
  }
7435
+ // src/engines/bun-webview.ts
7436
+ import { join as join2 } from "path";
7437
+ import { mkdirSync as mkdirSync2 } from "fs";
7438
+ import { homedir as homedir2 } from "os";
7439
+ function isBunWebViewAvailable() {
7440
+ return typeof globalThis.Bun !== "undefined" && typeof globalThis.Bun.WebView !== "undefined";
7441
+ }
7442
+ function getProfileDir(profileName) {
7443
+ const base = process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
7444
+ const dir = join2(base, "profiles", profileName);
7445
+ mkdirSync2(dir, { recursive: true });
7446
+ return dir;
7447
+ }
7448
+
7449
+ class BunWebViewSession {
7450
+ view;
7451
+ _sessionId;
7452
+ _eventListeners = new Map;
7453
+ constructor(opts = {}) {
7454
+ if (!isBunWebViewAvailable()) {
7455
+ throw new Error("Bun.WebView is not available. Install Bun canary: bun upgrade --canary");
7456
+ }
7457
+ const BunWebView = globalThis.Bun.WebView;
7458
+ const constructorOpts = {
7459
+ width: opts.width ?? 1280,
7460
+ height: opts.height ?? 720
7461
+ };
7462
+ if (opts.profile) {
7463
+ constructorOpts.dataStore = { directory: getProfileDir(opts.profile) };
7464
+ } else {
7465
+ constructorOpts.dataStore = "ephemeral";
7466
+ }
7467
+ if (opts.onConsole) {
7468
+ constructorOpts.console = opts.onConsole;
7469
+ }
7470
+ this.view = new BunWebView(constructorOpts);
7471
+ this.view.onNavigated = (url) => {
7472
+ this._emit("navigated", url);
7473
+ };
7474
+ this.view.onNavigationFailed = (error) => {
7475
+ this._emit("navigationfailed", error);
7476
+ };
7477
+ }
7478
+ async goto(url, opts) {
7479
+ await this.view.navigate(url);
7480
+ await new Promise((r) => setTimeout(r, 200));
7481
+ }
7482
+ async goBack() {
7483
+ await this.view.goBack();
7484
+ }
7485
+ async goForward() {
7486
+ await this.view.goForward();
7487
+ }
7488
+ async reload() {
7489
+ await this.view.reload();
7490
+ }
7491
+ async evaluate(fnOrExpr, ...args) {
7492
+ let expr;
7493
+ if (typeof fnOrExpr === "function") {
7494
+ const serializedArgs = args.map((a) => JSON.stringify(a)).join(", ");
7495
+ expr = `(${fnOrExpr.toString()})(${serializedArgs})`;
7496
+ } else {
7497
+ expr = fnOrExpr;
7498
+ }
7499
+ return this.view.evaluate(expr);
7500
+ }
7501
+ async screenshot(opts) {
7502
+ const uint8 = await this.view.screenshot();
7503
+ return Buffer.from(uint8);
7504
+ }
7505
+ async click(selector, opts) {
7506
+ await this.view.click(selector, opts ? { button: opts.button } : undefined);
7507
+ }
7508
+ async type(selector, text, opts) {
7509
+ try {
7510
+ await this.view.click(selector);
7511
+ } catch {}
7512
+ await this.view.type(text);
7513
+ }
7514
+ async fill(selector, value) {
7515
+ await this.view.evaluate(`
7516
+ (() => {
7517
+ const el = document.querySelector(${JSON.stringify(selector)});
7518
+ if (el) { el.value = ''; el.dispatchEvent(new Event('input')); }
7519
+ })()
7520
+ `);
7521
+ await this.type(selector, value);
7522
+ }
7523
+ async press(key, opts) {
7524
+ await this.view.press(key, opts);
7525
+ }
7526
+ async scroll(direction, amount) {
7527
+ const dx = direction === "left" ? -amount : direction === "right" ? amount : 0;
7528
+ const dy = direction === "up" ? -amount : direction === "down" ? amount : 0;
7529
+ await this.view.scroll(dx, dy);
7530
+ }
7531
+ async scrollIntoView(selector) {
7532
+ await this.view.scrollTo(selector);
7533
+ }
7534
+ async hover(selector) {
7535
+ try {
7536
+ await this.view.scrollTo(selector);
7537
+ } catch {}
7538
+ }
7539
+ async resize(width, height) {
7540
+ await this.view.resize(width, height);
7541
+ }
7542
+ async $(selector) {
7543
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
7544
+ if (!exists)
7545
+ return null;
7546
+ return {
7547
+ textContent: async () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`)
7548
+ };
7549
+ }
7550
+ async $$(selector) {
7551
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
7552
+ return Array.from({ length: count }, (_, i) => ({
7553
+ textContent: async () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${i}]?.textContent ?? null`)
7554
+ }));
7555
+ }
7556
+ async inputValue(selector) {
7557
+ return this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.value ?? ''`);
7558
+ }
7559
+ async isChecked(selector) {
7560
+ return this.view.evaluate(`!!(document.querySelector(${JSON.stringify(selector)})?.checked)`);
7561
+ }
7562
+ async isVisible(selector) {
7563
+ return this.view.evaluate(`
7564
+ (() => {
7565
+ const el = document.querySelector(${JSON.stringify(selector)});
7566
+ if (!el) return false;
7567
+ const style = window.getComputedStyle(el);
7568
+ return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0;
7569
+ })()
7570
+ `);
7571
+ }
7572
+ async isEnabled(selector) {
7573
+ return this.view.evaluate(`!(document.querySelector(${JSON.stringify(selector)})?.disabled)`);
7574
+ }
7575
+ async selectOption(selector, value) {
7576
+ await this.view.evaluate(`
7577
+ (() => {
7578
+ const el = document.querySelector(${JSON.stringify(selector)});
7579
+ if (el) {
7580
+ el.value = ${JSON.stringify(value)};
7581
+ el.dispatchEvent(new Event('change'));
7582
+ }
7583
+ })()
7584
+ `);
7585
+ return [value];
7586
+ }
7587
+ async check(selector) {
7588
+ await this.view.evaluate(`
7589
+ (() => {
7590
+ const el = document.querySelector(${JSON.stringify(selector)});
7591
+ if (el && !el.checked) { el.checked = true; el.dispatchEvent(new Event('change')); }
7592
+ })()
7593
+ `);
7594
+ }
7595
+ async uncheck(selector) {
7596
+ await this.view.evaluate(`
7597
+ (() => {
7598
+ const el = document.querySelector(${JSON.stringify(selector)});
7599
+ if (el && el.checked) { el.checked = false; el.dispatchEvent(new Event('change')); }
7600
+ })()
7601
+ `);
7602
+ }
7603
+ async setInputFiles(selector, files) {
7604
+ throw new Error("File upload not supported in Bun.WebView engine. Use engine: 'playwright' instead.");
7605
+ }
7606
+ getByRole(role, opts) {
7607
+ const name = opts?.name?.toString() ?? "";
7608
+ const selector = name ? `[role="${role}"][aria-label*="${name}"], ${role}[aria-label*="${name}"]` : `[role="${role}"], ${role}`;
7609
+ return {
7610
+ click: (clickOpts) => this.click(selector, clickOpts),
7611
+ fill: (value) => this.fill(selector, value),
7612
+ check: () => this.check(selector),
7613
+ uncheck: () => this.uncheck(selector),
7614
+ isVisible: () => this.isVisible(selector),
7615
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
7616
+ inputValue: () => this.inputValue(selector),
7617
+ first: () => ({
7618
+ click: (clickOpts) => this.click(selector, clickOpts),
7619
+ fill: (value) => this.fill(selector, value),
7620
+ textContent: () => this.view.evaluate(`document.querySelector(${JSON.stringify(selector)})?.textContent ?? null`),
7621
+ isVisible: () => this.isVisible(selector),
7622
+ hover: () => this.hover(selector),
7623
+ boundingBox: async () => null,
7624
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
7625
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
7626
+ waitFor: (opts2) => {
7627
+ return new Promise((resolve, reject) => {
7628
+ const timeout = opts2?.timeout ?? 1e4;
7629
+ const start = Date.now();
7630
+ const check = async () => {
7631
+ const visible = await this.isVisible(selector);
7632
+ if (visible)
7633
+ return resolve();
7634
+ if (Date.now() - start > timeout)
7635
+ return reject(new Error(`Timeout waiting for ${selector}`));
7636
+ setTimeout(check, 100);
7637
+ };
7638
+ check();
7639
+ });
7640
+ }
7641
+ }),
7642
+ count: async () => {
7643
+ const count = await this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)}).length`);
7644
+ return count;
7645
+ },
7646
+ nth: (n) => ({
7647
+ click: (clickOpts) => this.click(selector, clickOpts),
7648
+ textContent: () => this.view.evaluate(`document.querySelectorAll(${JSON.stringify(selector)})[${n}]?.textContent ?? null`),
7649
+ isVisible: () => this.isVisible(selector)
7650
+ })
7651
+ };
7652
+ }
7653
+ getByText(text, opts) {
7654
+ const selector = opts?.exact ? `*:is(button, a, span, div, p, h1, h2, h3, h4, label)` : "*";
7655
+ return {
7656
+ first: () => ({
7657
+ click: async (clickOpts) => {
7658
+ await this.view.evaluate(`
7659
+ (() => {
7660
+ const text = ${JSON.stringify(text)};
7661
+ const all = document.querySelectorAll('*');
7662
+ for (const el of all) {
7663
+ if (el.children.length === 0 && el.textContent?.trim() === text) {
7664
+ el.click(); return;
7665
+ }
7666
+ }
7667
+ for (const el of all) {
7668
+ if (el.textContent?.includes(text)) { el.click(); return; }
7669
+ }
7670
+ })()
7671
+ `);
7672
+ },
7673
+ waitFor: (waitOpts) => {
7674
+ const timeout = waitOpts?.timeout ?? 1e4;
7675
+ return new Promise((resolve, reject) => {
7676
+ const start = Date.now();
7677
+ const check = async () => {
7678
+ const found = await this.view.evaluate(`document.body?.textContent?.includes(${JSON.stringify(text)})`);
7679
+ if (found)
7680
+ return resolve();
7681
+ if (Date.now() - start > timeout)
7682
+ return reject(new Error(`Timeout: text "${text}" not found`));
7683
+ setTimeout(check, 100);
7684
+ };
7685
+ check();
7686
+ });
7687
+ }
7688
+ })
7689
+ };
7690
+ }
7691
+ locator(selector) {
7692
+ return {
7693
+ click: (opts) => this.click(selector, opts),
7694
+ fill: (value) => this.fill(selector, value),
7695
+ scrollIntoViewIfNeeded: () => this.scrollIntoView(selector),
7696
+ first: () => this.getByRole("*").first(),
7697
+ evaluate: (fn) => this.view.evaluate(`(${fn.toString()})(document.querySelector(${JSON.stringify(selector)}))`),
7698
+ waitFor: (opts) => {
7699
+ const timeout = opts?.timeout ?? 1e4;
7700
+ return new Promise((resolve, reject) => {
7701
+ const start = Date.now();
7702
+ const check = async () => {
7703
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
7704
+ if (exists)
7705
+ return resolve();
7706
+ if (Date.now() - start > timeout)
7707
+ return reject(new Error(`Timeout: ${selector}`));
7708
+ setTimeout(check, 100);
7709
+ };
7710
+ check();
7711
+ });
7712
+ }
7713
+ };
7714
+ }
7715
+ url() {
7716
+ return this.view.url;
7717
+ }
7718
+ async title() {
7719
+ return this.view.title || await this.evaluate("document.title");
7720
+ }
7721
+ viewportSize() {
7722
+ return { width: 1280, height: 720 };
7723
+ }
7724
+ async waitForLoadState(state, opts) {
7725
+ await new Promise((r) => setTimeout(r, 200));
7726
+ }
7727
+ async waitForURL(pattern, opts) {
7728
+ const timeout = opts?.timeout ?? 30000;
7729
+ const start = Date.now();
7730
+ while (Date.now() - start < timeout) {
7731
+ const url = this.view.url;
7732
+ const matches = pattern instanceof RegExp ? pattern.test(url) : url.includes(pattern);
7733
+ if (matches)
7734
+ return;
7735
+ await new Promise((r) => setTimeout(r, 100));
7736
+ }
7737
+ throw new Error(`Timeout waiting for URL to match ${pattern}`);
7738
+ }
7739
+ async waitForSelector(selector, opts) {
7740
+ const timeout = opts?.timeout ?? 1e4;
7741
+ const start = Date.now();
7742
+ while (Date.now() - start < timeout) {
7743
+ const exists = await this.view.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
7744
+ if (exists)
7745
+ return;
7746
+ await new Promise((r) => setTimeout(r, 100));
7747
+ }
7748
+ throw new Error(`Timeout waiting for ${selector}`);
7749
+ }
7750
+ async setContent(html) {
7751
+ await this.view.navigate(`data:text/html,${encodeURIComponent(html)}`);
7752
+ await new Promise((r) => setTimeout(r, 100));
7753
+ }
7754
+ async content() {
7755
+ return this.view.evaluate("document.documentElement.outerHTML");
7756
+ }
7757
+ async addInitScript(script) {
7758
+ const expr = typeof script === "function" ? `(${script.toString()})()` : script;
7759
+ await this.view.evaluate(expr);
7760
+ }
7761
+ keyboard = {
7762
+ press: (key) => this.view.press(key)
7763
+ };
7764
+ context() {
7765
+ return {
7766
+ close: async () => {
7767
+ await this.close();
7768
+ },
7769
+ newPage: async () => {
7770
+ throw new Error("Multi-tab not supported in Bun.WebView. Use engine: 'playwright'");
7771
+ },
7772
+ cookies: async () => [],
7773
+ addCookies: async (_) => {},
7774
+ clearCookies: async () => {},
7775
+ newCDPSession: async () => {
7776
+ throw new Error("CDP session via context not available in Bun.WebView. Use view.cdp() when shipped.");
7777
+ },
7778
+ route: async (_pattern, _handler) => {
7779
+ throw new Error("Network interception not supported in Bun.WebView. Use engine: 'cdp' or 'playwright'.");
7780
+ },
7781
+ unrouteAll: async () => {},
7782
+ pages: () => [],
7783
+ addInitScript: async (script) => {
7784
+ await this.addInitScript(script);
7785
+ }
7786
+ };
7787
+ }
7788
+ on(event, handler) {
7789
+ if (!this._eventListeners.has(event))
7790
+ this._eventListeners.set(event, []);
7791
+ this._eventListeners.get(event).push(handler);
7792
+ return this;
7793
+ }
7794
+ off(event, handler) {
7795
+ const listeners = this._eventListeners.get(event) ?? [];
7796
+ this._eventListeners.set(event, listeners.filter((l) => l !== handler));
7797
+ return this;
7798
+ }
7799
+ _emit(event, ...args) {
7800
+ for (const handler of this._eventListeners.get(event) ?? []) {
7801
+ try {
7802
+ handler(...args);
7803
+ } catch {}
7804
+ }
7805
+ }
7806
+ async pdf(_opts) {
7807
+ throw new Error("PDF generation not supported in Bun.WebView. Use engine: 'playwright'.");
7808
+ }
7809
+ coverage = {
7810
+ startJSCoverage: async () => {},
7811
+ stopJSCoverage: async () => [],
7812
+ startCSSCoverage: async () => {},
7813
+ stopCSSCoverage: async () => []
7814
+ };
7815
+ setSessionId(id) {
7816
+ this._sessionId = id;
7817
+ }
7818
+ getSessionId() {
7819
+ return this._sessionId;
7820
+ }
7821
+ getNativeView() {
7822
+ return this.view;
7823
+ }
7824
+ async close() {
7825
+ try {
7826
+ await this.view.close();
7827
+ } catch {}
7828
+ }
7829
+ [Symbol.asyncDispose]() {
7830
+ return this.close();
7831
+ }
7832
+ }
7833
+
7397
7834
  // src/engines/selector.ts
7398
7835
  var ENGINE_MAP = {
7399
- ["scrape" /* SCRAPE */]: "lightpanda",
7400
- ["extract_links" /* EXTRACT_LINKS */]: "lightpanda",
7401
- ["status_check" /* STATUS_CHECK */]: "lightpanda",
7836
+ ["scrape" /* SCRAPE */]: "bun",
7837
+ ["extract_links" /* EXTRACT_LINKS */]: "bun",
7838
+ ["status_check" /* STATUS_CHECK */]: "bun",
7839
+ ["screenshot" /* SCREENSHOT */]: "bun",
7840
+ ["spa_navigate" /* SPA_NAVIGATE */]: "bun",
7402
7841
  ["form_fill" /* FORM_FILL */]: "playwright",
7403
- ["spa_navigate" /* SPA_NAVIGATE */]: "playwright",
7404
- ["screenshot" /* SCREENSHOT */]: "playwright",
7405
7842
  ["auth_flow" /* AUTH_FLOW */]: "playwright",
7406
7843
  ["multi_tab" /* MULTI_TAB */]: "playwright",
7407
7844
  ["record_replay" /* RECORD_REPLAY */]: "playwright",
@@ -7415,6 +7852,14 @@ function selectEngine(useCase, explicit) {
7415
7852
  if (explicit && explicit !== "auto")
7416
7853
  return explicit;
7417
7854
  const preferred = ENGINE_MAP[useCase];
7855
+ if (preferred === "bun") {
7856
+ if (isBunWebViewAvailable())
7857
+ return "bun";
7858
+ if (useCase === "scrape" /* SCRAPE */ || useCase === "extract_links" /* EXTRACT_LINKS */ || useCase === "status_check" /* STATUS_CHECK */) {
7859
+ return isLightpandaAvailable() ? "lightpanda" : "playwright";
7860
+ }
7861
+ return "playwright";
7862
+ }
7418
7863
  if (preferred === "lightpanda" && !isLightpandaAvailable()) {
7419
7864
  return "playwright";
7420
7865
  }
@@ -7423,6 +7868,8 @@ function selectEngine(useCase, explicit) {
7423
7868
  function isEngineAvailable(engine) {
7424
7869
  if (engine === "auto")
7425
7870
  return true;
7871
+ if (engine === "bun")
7872
+ return isBunWebViewAvailable();
7426
7873
  if (engine === "playwright")
7427
7874
  return true;
7428
7875
  if (engine === "cdp")
@@ -7566,6 +8013,7 @@ function startHAR(page) {
7566
8013
  }
7567
8014
 
7568
8015
  // src/lib/console.ts
8016
+ init_console_log();
7569
8017
  function enableConsoleCapture(page, sessionId) {
7570
8018
  const onConsole = (msg) => {
7571
8019
  const levelMap = {
@@ -7715,12 +8163,30 @@ function setupDialogHandler(page, sessionId) {
7715
8163
 
7716
8164
  // src/lib/session.ts
7717
8165
  var handles = new Map;
8166
+ function createBunProxy(view) {
8167
+ return view;
8168
+ }
7718
8169
  async function createSession2(opts = {}) {
7719
8170
  const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
7720
8171
  const resolvedEngine = engine === "auto" ? "playwright" : engine;
7721
- let browser;
8172
+ let browser = null;
8173
+ let bunView = null;
7722
8174
  let page;
7723
- if (resolvedEngine === "lightpanda") {
8175
+ if (resolvedEngine === "bun") {
8176
+ if (!isBunWebViewAvailable()) {
8177
+ console.warn("[browser] Bun.WebView requested but not available \u2014 falling back to playwright. Run: bun upgrade --canary");
8178
+ browser = await launchPlaywright({ headless: opts.headless ?? true, viewport: opts.viewport, userAgent: opts.userAgent });
8179
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
8180
+ } else {
8181
+ bunView = new BunWebViewSession({
8182
+ width: opts.viewport?.width ?? 1280,
8183
+ height: opts.viewport?.height ?? 720,
8184
+ profile: opts.name ?? undefined
8185
+ });
8186
+ if (opts.stealth) {}
8187
+ page = createBunProxy(bunView);
8188
+ }
8189
+ } else if (resolvedEngine === "lightpanda") {
7724
8190
  browser = await connectLightpanda();
7725
8191
  const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
7726
8192
  page = await context.newPage();
@@ -7730,12 +8196,9 @@ async function createSession2(opts = {}) {
7730
8196
  viewport: opts.viewport,
7731
8197
  userAgent: opts.userAgent
7732
8198
  });
7733
- page = await getPage(browser, {
7734
- viewport: opts.viewport,
7735
- userAgent: opts.userAgent
7736
- });
8199
+ page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
7737
8200
  }
7738
- let sessionName = opts.name ?? (opts.startUrl ? (() => {
8201
+ const sessionName = opts.name ?? (opts.startUrl ? (() => {
7739
8202
  try {
7740
8203
  return new URL(opts.startUrl).hostname;
7741
8204
  } catch {
@@ -7743,35 +8206,57 @@ async function createSession2(opts = {}) {
7743
8206
  }
7744
8207
  })() : undefined);
7745
8208
  const session = createSession({
7746
- engine: resolvedEngine,
8209
+ engine: bunView ? "bun" : browser ? resolvedEngine : resolvedEngine,
7747
8210
  projectId: opts.projectId,
7748
8211
  agentId: opts.agentId,
7749
8212
  startUrl: opts.startUrl,
7750
8213
  name: sessionName
7751
8214
  });
7752
- if (opts.stealth) {
8215
+ if (opts.stealth && !bunView) {
7753
8216
  try {
7754
8217
  await applyStealthPatches(page);
7755
8218
  } catch {}
7756
8219
  }
7757
8220
  const cleanups = [];
7758
- if (opts.captureNetwork !== false) {
7759
- try {
7760
- cleanups.push(enableNetworkLogging(page, session.id));
7761
- } catch {}
7762
- }
7763
- if (opts.captureConsole !== false) {
8221
+ if (!bunView) {
8222
+ if (opts.captureNetwork !== false) {
8223
+ try {
8224
+ cleanups.push(enableNetworkLogging(page, session.id));
8225
+ } catch {}
8226
+ }
8227
+ if (opts.captureConsole !== false) {
8228
+ try {
8229
+ cleanups.push(enableConsoleCapture(page, session.id));
8230
+ } catch {}
8231
+ }
7764
8232
  try {
7765
- cleanups.push(enableConsoleCapture(page, session.id));
8233
+ cleanups.push(setupDialogHandler(page, session.id));
7766
8234
  } catch {}
8235
+ } else {
8236
+ if (opts.captureConsole !== false) {
8237
+ try {
8238
+ const { logConsoleMessage: logConsoleMessage2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
8239
+ await bunView.addInitScript(`
8240
+ (() => {
8241
+ const orig = { log: console.log, warn: console.warn, error: console.error, debug: console.debug, info: console.info };
8242
+ ['log','warn','error','debug','info'].forEach(level => {
8243
+ console[level] = (...args) => {
8244
+ orig[level](...args);
8245
+ };
8246
+ });
8247
+ })()
8248
+ `);
8249
+ } catch {}
8250
+ }
7767
8251
  }
7768
- try {
7769
- cleanups.push(setupDialogHandler(page, session.id));
7770
- } catch {}
7771
- handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
8252
+ handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
7772
8253
  if (opts.startUrl) {
7773
8254
  try {
7774
- await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
8255
+ if (bunView) {
8256
+ await bunView.goto(opts.startUrl);
8257
+ } else {
8258
+ await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
8259
+ }
7775
8260
  } catch {}
7776
8261
  }
7777
8262
  return { session, page };
@@ -7781,17 +8266,29 @@ function getSessionPage(sessionId) {
7781
8266
  if (!handle)
7782
8267
  throw new SessionNotFoundError(sessionId);
7783
8268
  try {
7784
- handle.page.url();
8269
+ if (handle.bunView) {
8270
+ handle.bunView.url();
8271
+ } else {
8272
+ handle.page.url();
8273
+ }
7785
8274
  } catch {
7786
8275
  handles.delete(sessionId);
7787
8276
  throw new SessionNotFoundError(sessionId);
7788
8277
  }
7789
8278
  return handle.page;
7790
8279
  }
8280
+ function getSessionBunView(sessionId) {
8281
+ return handles.get(sessionId)?.bunView ?? null;
8282
+ }
8283
+ function isBunSession(sessionId) {
8284
+ return handles.get(sessionId)?.engine === "bun";
8285
+ }
7791
8286
  function getSessionBrowser(sessionId) {
7792
8287
  const handle = handles.get(sessionId);
7793
8288
  if (!handle)
7794
8289
  throw new SessionNotFoundError(sessionId);
8290
+ if (!handle.browser)
8291
+ throw new BrowserError("This session uses Bun.WebView (no Playwright browser)", "NO_PLAYWRIGHT_BROWSER");
7795
8292
  return handle.browser;
7796
8293
  }
7797
8294
  function getSessionEngine(sessionId) {
@@ -7817,12 +8314,19 @@ async function closeSession2(sessionId) {
7817
8314
  cleanup();
7818
8315
  } catch {}
7819
8316
  }
7820
- try {
7821
- await handle.page.context().close();
7822
- } catch {}
7823
- try {
7824
- await closeBrowser(handle.browser);
7825
- } catch {}
8317
+ if (handle.bunView) {
8318
+ try {
8319
+ await handle.bunView.close();
8320
+ } catch {}
8321
+ } else {
8322
+ try {
8323
+ await handle.page.context().close();
8324
+ } catch {}
8325
+ try {
8326
+ if (handle.browser)
8327
+ await closeBrowser(handle.browser);
8328
+ } catch {}
8329
+ }
7826
8330
  handles.delete(sessionId);
7827
8331
  }
7828
8332
  return closeSession(sessionId);
@@ -8204,9 +8708,19 @@ async function getLinks(page, baseUrl) {
8204
8708
  }, baseUrl ?? page.url());
8205
8709
  }
8206
8710
  async function getTitle(page) {
8711
+ if (typeof page.getNativeView === "function") {
8712
+ const nativeView = page.getNativeView();
8713
+ const t = nativeView?.title;
8714
+ return typeof t === "string" && t ? t : "";
8715
+ }
8207
8716
  return page.title();
8208
8717
  }
8209
8718
  async function getUrl(page) {
8719
+ if (typeof page.getNativeView === "function") {
8720
+ const nativeView = page.getNativeView();
8721
+ const u = nativeView?.url;
8722
+ return typeof u === "string" ? u : "";
8723
+ }
8210
8724
  return page.url();
8211
8725
  }
8212
8726
  async function getMetaTags(page) {
@@ -8413,11 +8927,12 @@ async function startCoverage(page) {
8413
8927
  }
8414
8928
  // src/lib/screenshot.ts
8415
8929
  var import_sharp = __toESM(require_lib(), 1);
8416
- import { join as join2 } from "path";
8417
- import { mkdirSync as mkdirSync2 } from "fs";
8418
- import { homedir as homedir2 } from "os";
8930
+ import { join as join3 } from "path";
8931
+ import { mkdirSync as mkdirSync3 } from "fs";
8932
+ import { homedir as homedir3 } from "os";
8419
8933
 
8420
8934
  // src/db/gallery.ts
8935
+ init_schema();
8421
8936
  import { randomUUID as randomUUID10 } from "crypto";
8422
8937
  function deserialize3(row) {
8423
8938
  return {
@@ -8460,13 +8975,13 @@ function getEntry(id) {
8460
8975
 
8461
8976
  // src/lib/screenshot.ts
8462
8977
  function getDataDir2() {
8463
- return process.env["BROWSER_DATA_DIR"] ?? join2(homedir2(), ".browser");
8978
+ return process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser");
8464
8979
  }
8465
8980
  function getScreenshotDir(projectId) {
8466
- const base = join2(getDataDir2(), "screenshots");
8981
+ const base = join3(getDataDir2(), "screenshots");
8467
8982
  const date = new Date().toISOString().split("T")[0];
8468
- const dir = projectId ? join2(base, projectId, date) : join2(base, date);
8469
- mkdirSync2(dir, { recursive: true });
8983
+ const dir = projectId ? join3(base, projectId, date) : join3(base, date);
8984
+ mkdirSync3(dir, { recursive: true });
8470
8985
  return dir;
8471
8986
  }
8472
8987
  async function compressBuffer(raw, format, quality, maxWidth) {
@@ -8481,7 +8996,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
8481
8996
  }
8482
8997
  }
8483
8998
  async function generateThumbnail(raw, dir, stem) {
8484
- const thumbPath = join2(dir, `${stem}.thumb.webp`);
8999
+ const thumbPath = join3(dir, `${stem}.thumb.webp`);
8485
9000
  const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
8486
9001
  await Bun.write(thumbPath, thumbBuffer);
8487
9002
  return { path: thumbPath, base64: thumbBuffer.toString("base64") };
@@ -8500,11 +9015,20 @@ async function takeScreenshot(page, opts) {
8500
9015
  type: "png"
8501
9016
  };
8502
9017
  let rawBuffer;
9018
+ const isBunView = typeof page.getNativeView === "function";
8503
9019
  if (opts?.selector) {
8504
- const el = await page.$(opts.selector);
8505
- if (!el)
8506
- throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
8507
- rawBuffer = await el.screenshot(rawOpts);
9020
+ if (isBunView) {
9021
+ const uint8 = await page.screenshot();
9022
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
9023
+ } else {
9024
+ const el = await page.$(opts.selector);
9025
+ if (!el)
9026
+ throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
9027
+ rawBuffer = await el.screenshot(rawOpts);
9028
+ }
9029
+ } else if (isBunView) {
9030
+ const uint8 = await page.screenshot();
9031
+ rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
8508
9032
  } else {
8509
9033
  rawBuffer = await page.screenshot(rawOpts);
8510
9034
  }
@@ -8529,7 +9053,7 @@ async function takeScreenshot(page, opts) {
8529
9053
  const compressedSizeBytes = finalBuffer.length;
8530
9054
  const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
8531
9055
  const ext = format;
8532
- const screenshotPath = opts?.path ?? join2(dir, `${stem}.${ext}`);
9056
+ const screenshotPath = opts?.path ?? join3(dir, `${stem}.${ext}`);
8533
9057
  await Bun.write(screenshotPath, finalBuffer);
8534
9058
  let thumbnailPath;
8535
9059
  let thumbnailBase64;
@@ -8589,12 +9113,12 @@ async function takeScreenshot(page, opts) {
8589
9113
  }
8590
9114
  async function generatePDF(page, opts) {
8591
9115
  try {
8592
- const base = join2(getDataDir2(), "pdfs");
9116
+ const base = join3(getDataDir2(), "pdfs");
8593
9117
  const date = new Date().toISOString().split("T")[0];
8594
- const dir = opts?.projectId ? join2(base, opts.projectId, date) : join2(base, date);
8595
- mkdirSync2(dir, { recursive: true });
9118
+ const dir = opts?.projectId ? join3(base, opts.projectId, date) : join3(base, date);
9119
+ mkdirSync3(dir, { recursive: true });
8596
9120
  const timestamp = Date.now();
8597
- const pdfPath = opts?.path ?? join2(dir, `${timestamp}.pdf`);
9121
+ const pdfPath = opts?.path ?? join3(dir, `${timestamp}.pdf`);
8598
9122
  const buffer = await page.pdf({
8599
9123
  path: pdfPath,
8600
9124
  format: opts?.format ?? "A4",
@@ -8966,6 +9490,7 @@ export {
8966
9490
  launchLightpanda,
8967
9491
  isLightpandaAvailable,
8968
9492
  isEngineAvailable,
9493
+ isBunSession,
8969
9494
  isAgentStale,
8970
9495
  inferUseCase,
8971
9496
  hoverRef,
@@ -8985,6 +9510,7 @@ export {
8985
9510
  getSessionPage,
8986
9511
  getSessionEngine,
8987
9512
  getSessionByName2 as getSessionByName,
9513
+ getSessionBunView,
8988
9514
  getSessionBrowser,
8989
9515
  getSession,
8990
9516
  getRecording,