@hasna/browser 0.4.10 → 0.4.12
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/cli/index.js +7 -1
- package/dist/engines/bun-webview.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +917 -737
- package/dist/mcp/http.d.ts.map +1 -1
- package/dist/mcp/index.js +46 -8
- package/dist/sdk.d.ts +119 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.test.d.ts +2 -0
- package/dist/sdk.test.d.ts.map +1 -0
- package/dist/server/index.js +7 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9888,6 +9888,94 @@ var init_schema = __esm(() => {
|
|
|
9888
9888
|
init_dist();
|
|
9889
9889
|
});
|
|
9890
9890
|
|
|
9891
|
+
// src/lib/dialogs.ts
|
|
9892
|
+
var exports_dialogs = {};
|
|
9893
|
+
__export(exports_dialogs, {
|
|
9894
|
+
setupDialogHandler: () => setupDialogHandler,
|
|
9895
|
+
handleDialog: () => handleDialog,
|
|
9896
|
+
getDialogs: () => getDialogs,
|
|
9897
|
+
clearDialogs: () => clearDialogs
|
|
9898
|
+
});
|
|
9899
|
+
function setupDialogHandler(page, sessionId) {
|
|
9900
|
+
const onDialog = (dialog) => {
|
|
9901
|
+
const info = {
|
|
9902
|
+
type: dialog.type(),
|
|
9903
|
+
message: dialog.message(),
|
|
9904
|
+
default_value: dialog.defaultValue(),
|
|
9905
|
+
timestamp: new Date().toISOString()
|
|
9906
|
+
};
|
|
9907
|
+
const autoTimer = setTimeout(() => {
|
|
9908
|
+
try {
|
|
9909
|
+
dialog.dismiss().catch(() => {});
|
|
9910
|
+
} catch {}
|
|
9911
|
+
const list = pendingDialogs.get(sessionId);
|
|
9912
|
+
if (list) {
|
|
9913
|
+
const idx = list.findIndex((p) => p.dialog === dialog);
|
|
9914
|
+
if (idx >= 0)
|
|
9915
|
+
list.splice(idx, 1);
|
|
9916
|
+
if (list.length === 0)
|
|
9917
|
+
pendingDialogs.delete(sessionId);
|
|
9918
|
+
}
|
|
9919
|
+
}, AUTO_DISMISS_MS);
|
|
9920
|
+
const pending = { dialog, info, autoTimer };
|
|
9921
|
+
if (!pendingDialogs.has(sessionId)) {
|
|
9922
|
+
pendingDialogs.set(sessionId, []);
|
|
9923
|
+
}
|
|
9924
|
+
pendingDialogs.get(sessionId).push(pending);
|
|
9925
|
+
};
|
|
9926
|
+
page.on("dialog", onDialog);
|
|
9927
|
+
return () => {
|
|
9928
|
+
page.off("dialog", onDialog);
|
|
9929
|
+
const list = pendingDialogs.get(sessionId);
|
|
9930
|
+
if (list) {
|
|
9931
|
+
for (const p of list)
|
|
9932
|
+
clearTimeout(p.autoTimer);
|
|
9933
|
+
pendingDialogs.delete(sessionId);
|
|
9934
|
+
}
|
|
9935
|
+
};
|
|
9936
|
+
}
|
|
9937
|
+
function getDialogs(sessionId) {
|
|
9938
|
+
const list = pendingDialogs.get(sessionId);
|
|
9939
|
+
if (!list)
|
|
9940
|
+
return [];
|
|
9941
|
+
return list.map((p) => p.info);
|
|
9942
|
+
}
|
|
9943
|
+
async function handleDialog(sessionId, action, promptText) {
|
|
9944
|
+
const list = pendingDialogs.get(sessionId);
|
|
9945
|
+
if (!list || list.length === 0) {
|
|
9946
|
+
return { handled: false };
|
|
9947
|
+
}
|
|
9948
|
+
const pending = list.shift();
|
|
9949
|
+
clearTimeout(pending.autoTimer);
|
|
9950
|
+
if (list.length === 0) {
|
|
9951
|
+
pendingDialogs.delete(sessionId);
|
|
9952
|
+
}
|
|
9953
|
+
try {
|
|
9954
|
+
if (action === "accept") {
|
|
9955
|
+
await pending.dialog.accept(promptText);
|
|
9956
|
+
} else {
|
|
9957
|
+
await pending.dialog.dismiss();
|
|
9958
|
+
}
|
|
9959
|
+
} catch {}
|
|
9960
|
+
return { handled: true, dialog: pending.info };
|
|
9961
|
+
}
|
|
9962
|
+
function clearDialogs(sessionId) {
|
|
9963
|
+
const list = pendingDialogs.get(sessionId);
|
|
9964
|
+
if (list) {
|
|
9965
|
+
for (const p of list) {
|
|
9966
|
+
clearTimeout(p.autoTimer);
|
|
9967
|
+
try {
|
|
9968
|
+
p.dialog.dismiss().catch(() => {});
|
|
9969
|
+
} catch {}
|
|
9970
|
+
}
|
|
9971
|
+
pendingDialogs.delete(sessionId);
|
|
9972
|
+
}
|
|
9973
|
+
}
|
|
9974
|
+
var pendingDialogs, AUTO_DISMISS_MS = 5000;
|
|
9975
|
+
var init_dialogs = __esm(() => {
|
|
9976
|
+
pendingDialogs = new Map;
|
|
9977
|
+
});
|
|
9978
|
+
|
|
9891
9979
|
// src/engines/cdp.ts
|
|
9892
9980
|
var exports_cdp = {};
|
|
9893
9981
|
__export(exports_cdp, {
|
|
@@ -9895,9 +9983,9 @@ __export(exports_cdp, {
|
|
|
9895
9983
|
CDPClient: () => CDPClient
|
|
9896
9984
|
});
|
|
9897
9985
|
async function connectToExistingBrowser(cdpUrl) {
|
|
9898
|
-
const { chromium:
|
|
9986
|
+
const { chromium: chromium3 } = await import("playwright");
|
|
9899
9987
|
try {
|
|
9900
|
-
return await
|
|
9988
|
+
return await chromium3.connectOverCDP(cdpUrl);
|
|
9901
9989
|
} catch (err) {
|
|
9902
9990
|
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);
|
|
9903
9991
|
}
|
|
@@ -10014,94 +10102,6 @@ var init_cdp = __esm(() => {
|
|
|
10014
10102
|
init_types();
|
|
10015
10103
|
});
|
|
10016
10104
|
|
|
10017
|
-
// src/lib/dialogs.ts
|
|
10018
|
-
var exports_dialogs = {};
|
|
10019
|
-
__export(exports_dialogs, {
|
|
10020
|
-
setupDialogHandler: () => setupDialogHandler,
|
|
10021
|
-
handleDialog: () => handleDialog,
|
|
10022
|
-
getDialogs: () => getDialogs,
|
|
10023
|
-
clearDialogs: () => clearDialogs
|
|
10024
|
-
});
|
|
10025
|
-
function setupDialogHandler(page, sessionId) {
|
|
10026
|
-
const onDialog = (dialog) => {
|
|
10027
|
-
const info = {
|
|
10028
|
-
type: dialog.type(),
|
|
10029
|
-
message: dialog.message(),
|
|
10030
|
-
default_value: dialog.defaultValue(),
|
|
10031
|
-
timestamp: new Date().toISOString()
|
|
10032
|
-
};
|
|
10033
|
-
const autoTimer = setTimeout(() => {
|
|
10034
|
-
try {
|
|
10035
|
-
dialog.dismiss().catch(() => {});
|
|
10036
|
-
} catch {}
|
|
10037
|
-
const list = pendingDialogs.get(sessionId);
|
|
10038
|
-
if (list) {
|
|
10039
|
-
const idx = list.findIndex((p) => p.dialog === dialog);
|
|
10040
|
-
if (idx >= 0)
|
|
10041
|
-
list.splice(idx, 1);
|
|
10042
|
-
if (list.length === 0)
|
|
10043
|
-
pendingDialogs.delete(sessionId);
|
|
10044
|
-
}
|
|
10045
|
-
}, AUTO_DISMISS_MS);
|
|
10046
|
-
const pending = { dialog, info, autoTimer };
|
|
10047
|
-
if (!pendingDialogs.has(sessionId)) {
|
|
10048
|
-
pendingDialogs.set(sessionId, []);
|
|
10049
|
-
}
|
|
10050
|
-
pendingDialogs.get(sessionId).push(pending);
|
|
10051
|
-
};
|
|
10052
|
-
page.on("dialog", onDialog);
|
|
10053
|
-
return () => {
|
|
10054
|
-
page.off("dialog", onDialog);
|
|
10055
|
-
const list = pendingDialogs.get(sessionId);
|
|
10056
|
-
if (list) {
|
|
10057
|
-
for (const p of list)
|
|
10058
|
-
clearTimeout(p.autoTimer);
|
|
10059
|
-
pendingDialogs.delete(sessionId);
|
|
10060
|
-
}
|
|
10061
|
-
};
|
|
10062
|
-
}
|
|
10063
|
-
function getDialogs(sessionId) {
|
|
10064
|
-
const list = pendingDialogs.get(sessionId);
|
|
10065
|
-
if (!list)
|
|
10066
|
-
return [];
|
|
10067
|
-
return list.map((p) => p.info);
|
|
10068
|
-
}
|
|
10069
|
-
async function handleDialog(sessionId, action, promptText) {
|
|
10070
|
-
const list = pendingDialogs.get(sessionId);
|
|
10071
|
-
if (!list || list.length === 0) {
|
|
10072
|
-
return { handled: false };
|
|
10073
|
-
}
|
|
10074
|
-
const pending = list.shift();
|
|
10075
|
-
clearTimeout(pending.autoTimer);
|
|
10076
|
-
if (list.length === 0) {
|
|
10077
|
-
pendingDialogs.delete(sessionId);
|
|
10078
|
-
}
|
|
10079
|
-
try {
|
|
10080
|
-
if (action === "accept") {
|
|
10081
|
-
await pending.dialog.accept(promptText);
|
|
10082
|
-
} else {
|
|
10083
|
-
await pending.dialog.dismiss();
|
|
10084
|
-
}
|
|
10085
|
-
} catch {}
|
|
10086
|
-
return { handled: true, dialog: pending.info };
|
|
10087
|
-
}
|
|
10088
|
-
function clearDialogs(sessionId) {
|
|
10089
|
-
const list = pendingDialogs.get(sessionId);
|
|
10090
|
-
if (list) {
|
|
10091
|
-
for (const p of list) {
|
|
10092
|
-
clearTimeout(p.autoTimer);
|
|
10093
|
-
try {
|
|
10094
|
-
p.dialog.dismiss().catch(() => {});
|
|
10095
|
-
} catch {}
|
|
10096
|
-
}
|
|
10097
|
-
pendingDialogs.delete(sessionId);
|
|
10098
|
-
}
|
|
10099
|
-
}
|
|
10100
|
-
var pendingDialogs, AUTO_DISMISS_MS = 5000;
|
|
10101
|
-
var init_dialogs = __esm(() => {
|
|
10102
|
-
pendingDialogs = new Map;
|
|
10103
|
-
});
|
|
10104
|
-
|
|
10105
10105
|
// src/lib/storage-state.ts
|
|
10106
10106
|
var exports_storage_state = {};
|
|
10107
10107
|
__export(exports_storage_state, {
|
|
@@ -17311,201 +17311,65 @@ var require_lib3 = __commonJS((exports, module) => {
|
|
|
17311
17311
|
});
|
|
17312
17312
|
|
|
17313
17313
|
// src/index.ts
|
|
17314
|
-
init_schema();
|
|
17315
17314
|
init_types();
|
|
17316
17315
|
|
|
17317
|
-
// src/
|
|
17316
|
+
// src/lib/session.ts
|
|
17317
|
+
init_types();
|
|
17318
|
+
init_types();
|
|
17319
|
+
|
|
17320
|
+
// src/db/sessions.ts
|
|
17318
17321
|
init_schema();
|
|
17319
17322
|
init_types();
|
|
17320
17323
|
import { randomUUID } from "crypto";
|
|
17321
|
-
function
|
|
17324
|
+
function createSession(data) {
|
|
17322
17325
|
const db = getDatabase();
|
|
17323
17326
|
const id = randomUUID();
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17327
|
+
if (data.agentId) {
|
|
17328
|
+
const exists = db.query("SELECT id FROM agents WHERE id = ?").get(data.agentId);
|
|
17329
|
+
if (!exists) {
|
|
17330
|
+
db.prepare("INSERT INTO agents (id, name) VALUES (?, ?)").run(data.agentId, data.agentId);
|
|
17331
|
+
}
|
|
17332
|
+
}
|
|
17333
|
+
let name = data.name ?? null;
|
|
17334
|
+
if (name) {
|
|
17335
|
+
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
17336
|
+
if (existing) {
|
|
17337
|
+
name = `${name}-${id.slice(0, 6)}`;
|
|
17338
|
+
}
|
|
17339
|
+
}
|
|
17340
|
+
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
17341
|
+
return getSession(id);
|
|
17333
17342
|
}
|
|
17334
|
-
function
|
|
17343
|
+
function getSessionByName(name) {
|
|
17335
17344
|
const db = getDatabase();
|
|
17336
|
-
|
|
17337
|
-
if (!row)
|
|
17338
|
-
throw new ProjectNotFoundError(id);
|
|
17339
|
-
return row;
|
|
17345
|
+
return db.query("SELECT * FROM sessions WHERE name = ?").get(name) ?? null;
|
|
17340
17346
|
}
|
|
17341
|
-
function
|
|
17347
|
+
function renameSession(id, name) {
|
|
17342
17348
|
const db = getDatabase();
|
|
17343
|
-
|
|
17349
|
+
db.prepare("UPDATE sessions SET name = ? WHERE id = ?").run(name, id);
|
|
17350
|
+
return getSession(id);
|
|
17344
17351
|
}
|
|
17345
|
-
function
|
|
17352
|
+
function getSession(id) {
|
|
17346
17353
|
const db = getDatabase();
|
|
17347
|
-
|
|
17354
|
+
const row = db.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
17355
|
+
if (!row)
|
|
17356
|
+
throw new SessionNotFoundError(id);
|
|
17357
|
+
return row;
|
|
17348
17358
|
}
|
|
17349
|
-
function
|
|
17359
|
+
function listSessions(filter) {
|
|
17350
17360
|
const db = getDatabase();
|
|
17351
|
-
const
|
|
17361
|
+
const conditions = [];
|
|
17352
17362
|
const values = [];
|
|
17353
|
-
if (
|
|
17354
|
-
|
|
17355
|
-
values.push(
|
|
17356
|
-
}
|
|
17357
|
-
if (data.path !== undefined) {
|
|
17358
|
-
fields.push("path = ?");
|
|
17359
|
-
values.push(data.path);
|
|
17363
|
+
if (filter?.status) {
|
|
17364
|
+
conditions.push("status = ?");
|
|
17365
|
+
values.push(filter.status);
|
|
17360
17366
|
}
|
|
17361
|
-
if (
|
|
17362
|
-
|
|
17363
|
-
values.push(
|
|
17367
|
+
if (filter?.projectId) {
|
|
17368
|
+
conditions.push("project_id = ?");
|
|
17369
|
+
values.push(filter.projectId);
|
|
17364
17370
|
}
|
|
17365
|
-
|
|
17366
|
-
|
|
17367
|
-
values.push(id);
|
|
17368
|
-
db.prepare(`UPDATE projects SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
17369
|
-
return getProject(id);
|
|
17370
|
-
}
|
|
17371
|
-
function deleteProject(id) {
|
|
17372
|
-
const db = getDatabase();
|
|
17373
|
-
db.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
17374
|
-
}
|
|
17375
|
-
// src/db/agents.ts
|
|
17376
|
-
init_schema();
|
|
17377
|
-
init_types();
|
|
17378
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
17379
|
-
function registerAgent(name, opts = {}) {
|
|
17380
|
-
const db = getDatabase();
|
|
17381
|
-
const existing = db.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
17382
|
-
if (existing) {
|
|
17383
|
-
db.prepare("UPDATE agents SET last_seen = datetime('now'), session_id = ?, project_id = ?, working_dir = ? WHERE name = ?").run(opts.sessionId ?? existing.session_id ?? null, opts.projectId ?? existing.project_id ?? null, opts.workingDir ?? existing.working_dir ?? null, name);
|
|
17384
|
-
return getAgentByName(name);
|
|
17385
|
-
}
|
|
17386
|
-
const id = randomUUID2();
|
|
17387
|
-
db.prepare("INSERT INTO agents (id, name, description, session_id, project_id, working_dir) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, opts.description ?? null, opts.sessionId ?? null, opts.projectId ?? null, opts.workingDir ?? null);
|
|
17388
|
-
return getAgent(id);
|
|
17389
|
-
}
|
|
17390
|
-
function heartbeat(agentId) {
|
|
17391
|
-
const db = getDatabase();
|
|
17392
|
-
const agent = db.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
17393
|
-
if (!agent)
|
|
17394
|
-
throw new AgentNotFoundError(agentId);
|
|
17395
|
-
db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
|
|
17396
|
-
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(randomUUID2(), agentId, agent.session_id ?? null);
|
|
17397
|
-
}
|
|
17398
|
-
function getAgent(id) {
|
|
17399
|
-
const db = getDatabase();
|
|
17400
|
-
const row = db.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
17401
|
-
if (!row)
|
|
17402
|
-
throw new AgentNotFoundError(id);
|
|
17403
|
-
return row;
|
|
17404
|
-
}
|
|
17405
|
-
function getAgentByName(name) {
|
|
17406
|
-
const db = getDatabase();
|
|
17407
|
-
return db.query("SELECT * FROM agents WHERE name = ?").get(name) ?? null;
|
|
17408
|
-
}
|
|
17409
|
-
function listAgents(projectId) {
|
|
17410
|
-
const db = getDatabase();
|
|
17411
|
-
if (projectId) {
|
|
17412
|
-
return db.query("SELECT * FROM agents WHERE project_id = ? ORDER BY last_seen DESC").all(projectId);
|
|
17413
|
-
}
|
|
17414
|
-
return db.query("SELECT * FROM agents ORDER BY last_seen DESC").all();
|
|
17415
|
-
}
|
|
17416
|
-
function updateAgent(id, data) {
|
|
17417
|
-
const db = getDatabase();
|
|
17418
|
-
const fields = [];
|
|
17419
|
-
const values = [];
|
|
17420
|
-
if (data.name !== undefined) {
|
|
17421
|
-
fields.push("name = ?");
|
|
17422
|
-
values.push(data.name ?? null);
|
|
17423
|
-
}
|
|
17424
|
-
if (data.description !== undefined) {
|
|
17425
|
-
fields.push("description = ?");
|
|
17426
|
-
values.push(data.description ?? null);
|
|
17427
|
-
}
|
|
17428
|
-
if (data.session_id !== undefined) {
|
|
17429
|
-
fields.push("session_id = ?");
|
|
17430
|
-
values.push(data.session_id ?? null);
|
|
17431
|
-
}
|
|
17432
|
-
if (data.project_id !== undefined) {
|
|
17433
|
-
fields.push("project_id = ?");
|
|
17434
|
-
values.push(data.project_id ?? null);
|
|
17435
|
-
}
|
|
17436
|
-
if (data.working_dir !== undefined) {
|
|
17437
|
-
fields.push("working_dir = ?");
|
|
17438
|
-
values.push(data.working_dir ?? null);
|
|
17439
|
-
}
|
|
17440
|
-
if (fields.length === 0)
|
|
17441
|
-
return getAgent(id);
|
|
17442
|
-
values.push(id);
|
|
17443
|
-
db.prepare(`UPDATE agents SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
17444
|
-
return getAgent(id);
|
|
17445
|
-
}
|
|
17446
|
-
function deleteAgent(id) {
|
|
17447
|
-
const db = getDatabase();
|
|
17448
|
-
db.prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
17449
|
-
}
|
|
17450
|
-
function cleanStaleAgents(thresholdMs) {
|
|
17451
|
-
const db = getDatabase();
|
|
17452
|
-
const cutoff = new Date(Date.now() - thresholdMs).toISOString().replace("T", " ").split(".")[0];
|
|
17453
|
-
const result = db.prepare("DELETE FROM agents WHERE last_seen < ?").run(cutoff);
|
|
17454
|
-
return result.changes;
|
|
17455
|
-
}
|
|
17456
|
-
// src/db/sessions.ts
|
|
17457
|
-
init_schema();
|
|
17458
|
-
init_types();
|
|
17459
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
17460
|
-
function createSession(data) {
|
|
17461
|
-
const db = getDatabase();
|
|
17462
|
-
const id = randomUUID3();
|
|
17463
|
-
if (data.agentId) {
|
|
17464
|
-
const exists = db.query("SELECT id FROM agents WHERE id = ?").get(data.agentId);
|
|
17465
|
-
if (!exists) {
|
|
17466
|
-
db.prepare("INSERT INTO agents (id, name) VALUES (?, ?)").run(data.agentId, data.agentId);
|
|
17467
|
-
}
|
|
17468
|
-
}
|
|
17469
|
-
let name = data.name ?? null;
|
|
17470
|
-
if (name) {
|
|
17471
|
-
const existing = db.query("SELECT id FROM sessions WHERE name = ?").get(name);
|
|
17472
|
-
if (existing) {
|
|
17473
|
-
name = `${name}-${id.slice(0, 6)}`;
|
|
17474
|
-
}
|
|
17475
|
-
}
|
|
17476
|
-
db.prepare("INSERT INTO sessions (id, engine, project_id, agent_id, start_url, name) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.engine, data.projectId ?? null, data.agentId ?? null, data.startUrl ?? null, name);
|
|
17477
|
-
return getSession(id);
|
|
17478
|
-
}
|
|
17479
|
-
function getSessionByName(name) {
|
|
17480
|
-
const db = getDatabase();
|
|
17481
|
-
return db.query("SELECT * FROM sessions WHERE name = ?").get(name) ?? null;
|
|
17482
|
-
}
|
|
17483
|
-
function renameSession(id, name) {
|
|
17484
|
-
const db = getDatabase();
|
|
17485
|
-
db.prepare("UPDATE sessions SET name = ? WHERE id = ?").run(name, id);
|
|
17486
|
-
return getSession(id);
|
|
17487
|
-
}
|
|
17488
|
-
function getSession(id) {
|
|
17489
|
-
const db = getDatabase();
|
|
17490
|
-
const row = db.query("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
17491
|
-
if (!row)
|
|
17492
|
-
throw new SessionNotFoundError(id);
|
|
17493
|
-
return row;
|
|
17494
|
-
}
|
|
17495
|
-
function listSessions(filter) {
|
|
17496
|
-
const db = getDatabase();
|
|
17497
|
-
const conditions = [];
|
|
17498
|
-
const values = [];
|
|
17499
|
-
if (filter?.status) {
|
|
17500
|
-
conditions.push("status = ?");
|
|
17501
|
-
values.push(filter.status);
|
|
17502
|
-
}
|
|
17503
|
-
if (filter?.projectId) {
|
|
17504
|
-
conditions.push("project_id = ?");
|
|
17505
|
-
values.push(filter.projectId);
|
|
17506
|
-
}
|
|
17507
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
17508
|
-
return db.query(`SELECT * FROM sessions ${where} ORDER BY created_at DESC`).all(...values);
|
|
17371
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
17372
|
+
return db.query(`SELECT * FROM sessions ${where} ORDER BY created_at DESC`).all(...values);
|
|
17509
17373
|
}
|
|
17510
17374
|
function updateSessionStatus(id, status) {
|
|
17511
17375
|
const db = getDatabase();
|
|
@@ -17536,229 +17400,38 @@ function deleteSession(id) {
|
|
|
17536
17400
|
const db = getDatabase();
|
|
17537
17401
|
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
17538
17402
|
}
|
|
17539
|
-
|
|
17540
|
-
|
|
17541
|
-
|
|
17542
|
-
|
|
17543
|
-
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17548
|
-
|
|
17549
|
-
|
|
17550
|
-
|
|
17551
|
-
}
|
|
17552
|
-
function listSnapshots(sessionId) {
|
|
17553
|
-
const db = getDatabase();
|
|
17554
|
-
return db.query("SELECT * FROM snapshots WHERE session_id = ? ORDER BY timestamp DESC").all(sessionId);
|
|
17555
|
-
}
|
|
17556
|
-
function deleteSnapshot(id) {
|
|
17557
|
-
const db = getDatabase();
|
|
17558
|
-
db.prepare("DELETE FROM snapshots WHERE id = ?").run(id);
|
|
17559
|
-
}
|
|
17560
|
-
function deleteSnapshotsBySession(sessionId) {
|
|
17561
|
-
const db = getDatabase();
|
|
17562
|
-
db.prepare("DELETE FROM snapshots WHERE session_id = ?").run(sessionId);
|
|
17563
|
-
}
|
|
17564
|
-
// src/db/network-log.ts
|
|
17565
|
-
init_schema();
|
|
17566
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
17567
|
-
function logRequest(data) {
|
|
17568
|
-
const db = getDatabase();
|
|
17569
|
-
const id = randomUUID5();
|
|
17570
|
-
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
17571
|
-
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
17572
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
|
|
17573
|
-
return getNetworkRequest(id);
|
|
17574
|
-
}
|
|
17575
|
-
function getNetworkRequest(id) {
|
|
17576
|
-
const db = getDatabase();
|
|
17577
|
-
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
17578
|
-
}
|
|
17579
|
-
function getNetworkLog(sessionId) {
|
|
17580
|
-
const db = getDatabase();
|
|
17581
|
-
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
17582
|
-
}
|
|
17583
|
-
function clearNetworkLog(sessionId) {
|
|
17584
|
-
const db = getDatabase();
|
|
17585
|
-
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
17586
|
-
}
|
|
17587
|
-
function deleteNetworkRequest(id) {
|
|
17588
|
-
const db = getDatabase();
|
|
17589
|
-
db.prepare("DELETE FROM network_log WHERE id = ?").run(id);
|
|
17590
|
-
}
|
|
17591
|
-
// src/db/console-log.ts
|
|
17592
|
-
init_schema();
|
|
17593
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
17594
|
-
function logConsoleMessage(data) {
|
|
17595
|
-
const db = getDatabase();
|
|
17596
|
-
const id = randomUUID6();
|
|
17597
|
-
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);
|
|
17598
|
-
return getConsoleMessage(id);
|
|
17599
|
-
}
|
|
17600
|
-
function getConsoleMessage(id) {
|
|
17601
|
-
const db = getDatabase();
|
|
17602
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
17603
|
-
}
|
|
17604
|
-
function getConsoleLog(sessionId, level) {
|
|
17605
|
-
const db = getDatabase();
|
|
17606
|
-
if (level) {
|
|
17607
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
17403
|
+
|
|
17404
|
+
// src/engines/playwright.ts
|
|
17405
|
+
init_types();
|
|
17406
|
+
import { chromium } from "playwright";
|
|
17407
|
+
var DEFAULT_VIEWPORT = { width: 1280, height: 720 };
|
|
17408
|
+
async function launchPlaywright(options) {
|
|
17409
|
+
try {
|
|
17410
|
+
return await chromium.launch({
|
|
17411
|
+
headless: options?.headless ?? true,
|
|
17412
|
+
executablePath: options?.executablePath
|
|
17413
|
+
});
|
|
17414
|
+
} catch (err) {
|
|
17415
|
+
throw new BrowserError(`Failed to launch Playwright browser: ${err instanceof Error ? err.message : String(err)}`, "PLAYWRIGHT_LAUNCH_FAILED", true);
|
|
17608
17416
|
}
|
|
17609
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
17610
17417
|
}
|
|
17611
|
-
function
|
|
17612
|
-
const
|
|
17613
|
-
|
|
17418
|
+
async function getPage(browser, options) {
|
|
17419
|
+
const context = await browser.newContext({
|
|
17420
|
+
viewport: options?.viewport ?? DEFAULT_VIEWPORT,
|
|
17421
|
+
userAgent: options?.userAgent,
|
|
17422
|
+
locale: options?.locale
|
|
17423
|
+
});
|
|
17424
|
+
return context.newPage();
|
|
17614
17425
|
}
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
function deserialize(row) {
|
|
17620
|
-
return {
|
|
17621
|
-
...row,
|
|
17622
|
-
project_id: row.project_id ?? undefined,
|
|
17623
|
-
start_url: row.start_url ?? undefined,
|
|
17624
|
-
steps: JSON.parse(row.steps)
|
|
17625
|
-
};
|
|
17426
|
+
async function closeBrowser(browser) {
|
|
17427
|
+
try {
|
|
17428
|
+
await browser.close();
|
|
17429
|
+
} catch {}
|
|
17626
17430
|
}
|
|
17627
|
-
function
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
return getRecording(id);
|
|
17632
|
-
}
|
|
17633
|
-
function getRecording(id) {
|
|
17634
|
-
const db = getDatabase();
|
|
17635
|
-
const row = db.query("SELECT * FROM recordings WHERE id = ?").get(id);
|
|
17636
|
-
if (!row)
|
|
17637
|
-
throw new RecordingNotFoundError(id);
|
|
17638
|
-
return deserialize(row);
|
|
17639
|
-
}
|
|
17640
|
-
function listRecordings(projectId) {
|
|
17641
|
-
const db = getDatabase();
|
|
17642
|
-
const rows = projectId ? db.query("SELECT * FROM recordings WHERE project_id = ? ORDER BY created_at DESC").all(projectId) : db.query("SELECT * FROM recordings ORDER BY created_at DESC").all();
|
|
17643
|
-
return rows.map(deserialize);
|
|
17644
|
-
}
|
|
17645
|
-
function updateRecording(id, data) {
|
|
17646
|
-
const db = getDatabase();
|
|
17647
|
-
const fields = [];
|
|
17648
|
-
const values = [];
|
|
17649
|
-
if (data.name !== undefined) {
|
|
17650
|
-
fields.push("name = ?");
|
|
17651
|
-
values.push(data.name);
|
|
17652
|
-
}
|
|
17653
|
-
if (data.steps !== undefined) {
|
|
17654
|
-
fields.push("steps = ?");
|
|
17655
|
-
values.push(JSON.stringify(data.steps));
|
|
17656
|
-
}
|
|
17657
|
-
if (data.start_url !== undefined) {
|
|
17658
|
-
fields.push("start_url = ?");
|
|
17659
|
-
values.push(data.start_url ?? null);
|
|
17660
|
-
}
|
|
17661
|
-
if (fields.length === 0)
|
|
17662
|
-
return getRecording(id);
|
|
17663
|
-
values.push(id);
|
|
17664
|
-
db.prepare(`UPDATE recordings SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
17665
|
-
return getRecording(id);
|
|
17666
|
-
}
|
|
17667
|
-
function deleteRecording(id) {
|
|
17668
|
-
const db = getDatabase();
|
|
17669
|
-
db.prepare("DELETE FROM recordings WHERE id = ?").run(id);
|
|
17670
|
-
}
|
|
17671
|
-
// src/db/crawl-results.ts
|
|
17672
|
-
init_schema();
|
|
17673
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
17674
|
-
function deserialize2(row) {
|
|
17675
|
-
const pages = JSON.parse(row.pages);
|
|
17676
|
-
return {
|
|
17677
|
-
id: row.id,
|
|
17678
|
-
project_id: row.project_id ?? undefined,
|
|
17679
|
-
start_url: row.start_url,
|
|
17680
|
-
depth: row.depth,
|
|
17681
|
-
pages,
|
|
17682
|
-
total_links: pages.reduce((acc, p) => acc + p.links.length, 0),
|
|
17683
|
-
errors: JSON.parse(row.errors),
|
|
17684
|
-
created_at: row.created_at
|
|
17685
|
-
};
|
|
17686
|
-
}
|
|
17687
|
-
function createCrawlResult(data) {
|
|
17688
|
-
const db = getDatabase();
|
|
17689
|
-
const id = randomUUID8();
|
|
17690
|
-
db.prepare("INSERT INTO crawl_results (id, project_id, start_url, depth, pages, links, errors) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, data.project_id ?? null, data.start_url, data.depth, JSON.stringify(data.pages), JSON.stringify(data.pages.flatMap((p) => p.links)), JSON.stringify(data.errors));
|
|
17691
|
-
return getCrawlResult(id);
|
|
17692
|
-
}
|
|
17693
|
-
function getCrawlResult(id) {
|
|
17694
|
-
const db = getDatabase();
|
|
17695
|
-
const row = db.query("SELECT * FROM crawl_results WHERE id = ?").get(id);
|
|
17696
|
-
return row ? deserialize2(row) : null;
|
|
17697
|
-
}
|
|
17698
|
-
function listCrawlResults(projectId) {
|
|
17699
|
-
const db = getDatabase();
|
|
17700
|
-
const rows = projectId ? db.query("SELECT * FROM crawl_results WHERE project_id = ? ORDER BY created_at DESC").all(projectId) : db.query("SELECT * FROM crawl_results ORDER BY created_at DESC").all();
|
|
17701
|
-
return rows.map(deserialize2);
|
|
17702
|
-
}
|
|
17703
|
-
function deleteCrawlResult(id) {
|
|
17704
|
-
const db = getDatabase();
|
|
17705
|
-
db.prepare("DELETE FROM crawl_results WHERE id = ?").run(id);
|
|
17706
|
-
}
|
|
17707
|
-
// src/db/heartbeats.ts
|
|
17708
|
-
init_schema();
|
|
17709
|
-
import { randomUUID as randomUUID9 } from "crypto";
|
|
17710
|
-
function recordHeartbeat(agentId, sessionId) {
|
|
17711
|
-
const db = getDatabase();
|
|
17712
|
-
const id = randomUUID9();
|
|
17713
|
-
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(id, agentId, sessionId ?? null);
|
|
17714
|
-
db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
|
|
17715
|
-
return getLastHeartbeat(agentId);
|
|
17716
|
-
}
|
|
17717
|
-
function getLastHeartbeat(agentId) {
|
|
17718
|
-
const db = getDatabase();
|
|
17719
|
-
return db.query("SELECT * FROM heartbeats WHERE agent_id = ? ORDER BY timestamp DESC LIMIT 1").get(agentId) ?? null;
|
|
17720
|
-
}
|
|
17721
|
-
function listHeartbeats(agentId, limit = 50) {
|
|
17722
|
-
const db = getDatabase();
|
|
17723
|
-
return db.query("SELECT * FROM heartbeats WHERE agent_id = ? ORDER BY timestamp DESC LIMIT ?").all(agentId, limit);
|
|
17724
|
-
}
|
|
17725
|
-
function cleanOldHeartbeats(olderThanMs) {
|
|
17726
|
-
const db = getDatabase();
|
|
17727
|
-
const cutoff = new Date(Date.now() - olderThanMs).toISOString().replace("T", " ").split(".")[0];
|
|
17728
|
-
const result = db.prepare("DELETE FROM heartbeats WHERE timestamp < ?").run(cutoff);
|
|
17729
|
-
return result.changes;
|
|
17730
|
-
}
|
|
17731
|
-
// src/engines/playwright.ts
|
|
17732
|
-
init_types();
|
|
17733
|
-
import { chromium } from "playwright";
|
|
17734
|
-
var DEFAULT_VIEWPORT = { width: 1280, height: 720 };
|
|
17735
|
-
async function launchPlaywright(options) {
|
|
17736
|
-
try {
|
|
17737
|
-
return await chromium.launch({
|
|
17738
|
-
headless: options?.headless ?? true,
|
|
17739
|
-
executablePath: options?.executablePath
|
|
17740
|
-
});
|
|
17741
|
-
} catch (err) {
|
|
17742
|
-
throw new BrowserError(`Failed to launch Playwright browser: ${err instanceof Error ? err.message : String(err)}`, "PLAYWRIGHT_LAUNCH_FAILED", true);
|
|
17743
|
-
}
|
|
17744
|
-
}
|
|
17745
|
-
async function getPage(browser, options) {
|
|
17746
|
-
const context = await browser.newContext({
|
|
17747
|
-
viewport: options?.viewport ?? DEFAULT_VIEWPORT,
|
|
17748
|
-
userAgent: options?.userAgent,
|
|
17749
|
-
locale: options?.locale
|
|
17750
|
-
});
|
|
17751
|
-
return context.newPage();
|
|
17752
|
-
}
|
|
17753
|
-
async function closeBrowser(browser) {
|
|
17754
|
-
try {
|
|
17755
|
-
await browser.close();
|
|
17756
|
-
} catch {}
|
|
17757
|
-
}
|
|
17758
|
-
async function closePage(page) {
|
|
17759
|
-
try {
|
|
17760
|
-
await page.context().close();
|
|
17761
|
-
} catch {}
|
|
17431
|
+
async function closePage(page) {
|
|
17432
|
+
try {
|
|
17433
|
+
await page.context().close();
|
|
17434
|
+
} catch {}
|
|
17762
17435
|
}
|
|
17763
17436
|
|
|
17764
17437
|
class BrowserPool {
|
|
@@ -17813,9 +17486,6 @@ class BrowserPool {
|
|
|
17813
17486
|
}
|
|
17814
17487
|
}
|
|
17815
17488
|
|
|
17816
|
-
// src/index.ts
|
|
17817
|
-
init_cdp();
|
|
17818
|
-
|
|
17819
17489
|
// src/engines/lightpanda.ts
|
|
17820
17490
|
init_types();
|
|
17821
17491
|
import { execSync, spawn } from "child_process";
|
|
@@ -17959,15 +17629,19 @@ class LightpandaPage {
|
|
|
17959
17629
|
await this.page.context().close();
|
|
17960
17630
|
}
|
|
17961
17631
|
}
|
|
17962
|
-
// src/engines/selector.ts
|
|
17963
|
-
init_types();
|
|
17964
17632
|
|
|
17965
17633
|
// src/engines/bun-webview.ts
|
|
17966
17634
|
init_schema();
|
|
17967
17635
|
import { join as join7 } from "path";
|
|
17968
17636
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
17969
17637
|
function isBunWebViewAvailable() {
|
|
17970
|
-
|
|
17638
|
+
if (typeof globalThis.Bun === "undefined" || typeof globalThis.Bun.WebView === "undefined") {
|
|
17639
|
+
return false;
|
|
17640
|
+
}
|
|
17641
|
+
if (process.platform === "linux" && process.env["BROWSER_ENABLE_BUN_WEBVIEW"] !== "1") {
|
|
17642
|
+
return false;
|
|
17643
|
+
}
|
|
17644
|
+
return true;
|
|
17971
17645
|
}
|
|
17972
17646
|
function getProfileDir(profileName) {
|
|
17973
17647
|
const base = getDataDir2();
|
|
@@ -18361,6 +18035,9 @@ class BunWebViewSession {
|
|
|
18361
18035
|
}
|
|
18362
18036
|
}
|
|
18363
18037
|
|
|
18038
|
+
// src/engines/selector.ts
|
|
18039
|
+
init_types();
|
|
18040
|
+
|
|
18364
18041
|
// src/engines/tui.ts
|
|
18365
18042
|
init_types();
|
|
18366
18043
|
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
@@ -18814,9 +18491,6 @@ function inferUseCase(label) {
|
|
|
18814
18491
|
};
|
|
18815
18492
|
return map[label.toLowerCase()] ?? "spa_navigate" /* SPA_NAVIGATE */;
|
|
18816
18493
|
}
|
|
18817
|
-
// src/lib/session.ts
|
|
18818
|
-
init_types();
|
|
18819
|
-
init_types();
|
|
18820
18494
|
|
|
18821
18495
|
// src/lib/tui-recording.ts
|
|
18822
18496
|
var recordingIntervals = new Map;
|
|
@@ -18828,6 +18502,34 @@ function stopTuiRecording(sessionId) {
|
|
|
18828
18502
|
recordingIntervals.delete(sessionId);
|
|
18829
18503
|
}
|
|
18830
18504
|
|
|
18505
|
+
// src/db/network-log.ts
|
|
18506
|
+
init_schema();
|
|
18507
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
18508
|
+
function logRequest(data) {
|
|
18509
|
+
const db = getDatabase();
|
|
18510
|
+
const id = randomUUID2();
|
|
18511
|
+
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
18512
|
+
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
18513
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, data.session_id, data.method, data.url, data.status_code ?? null, data.request_headers ?? null, data.response_headers ?? null, data.request_body ?? null, data.body_size ?? null, data.duration_ms ?? null, data.resource_type ?? null);
|
|
18514
|
+
return getNetworkRequest(id);
|
|
18515
|
+
}
|
|
18516
|
+
function getNetworkRequest(id) {
|
|
18517
|
+
const db = getDatabase();
|
|
18518
|
+
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
18519
|
+
}
|
|
18520
|
+
function getNetworkLog(sessionId) {
|
|
18521
|
+
const db = getDatabase();
|
|
18522
|
+
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
18523
|
+
}
|
|
18524
|
+
function clearNetworkLog(sessionId) {
|
|
18525
|
+
const db = getDatabase();
|
|
18526
|
+
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
18527
|
+
}
|
|
18528
|
+
function deleteNetworkRequest(id) {
|
|
18529
|
+
const db = getDatabase();
|
|
18530
|
+
db.prepare("DELETE FROM network_log WHERE id = ?").run(id);
|
|
18531
|
+
}
|
|
18532
|
+
|
|
18831
18533
|
// src/lib/network.ts
|
|
18832
18534
|
function enableNetworkLogging(page, sessionId) {
|
|
18833
18535
|
const requestStart = new Map;
|
|
@@ -18950,6 +18652,31 @@ function startHAR(page) {
|
|
|
18950
18652
|
};
|
|
18951
18653
|
}
|
|
18952
18654
|
|
|
18655
|
+
// src/db/console-log.ts
|
|
18656
|
+
init_schema();
|
|
18657
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
18658
|
+
function logConsoleMessage(data) {
|
|
18659
|
+
const db = getDatabase();
|
|
18660
|
+
const id = randomUUID3();
|
|
18661
|
+
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);
|
|
18662
|
+
return getConsoleMessage(id);
|
|
18663
|
+
}
|
|
18664
|
+
function getConsoleMessage(id) {
|
|
18665
|
+
const db = getDatabase();
|
|
18666
|
+
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
18667
|
+
}
|
|
18668
|
+
function getConsoleLog(sessionId, level) {
|
|
18669
|
+
const db = getDatabase();
|
|
18670
|
+
if (level) {
|
|
18671
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
18672
|
+
}
|
|
18673
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
18674
|
+
}
|
|
18675
|
+
function clearConsoleLog(sessionId) {
|
|
18676
|
+
const db = getDatabase();
|
|
18677
|
+
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
18678
|
+
}
|
|
18679
|
+
|
|
18953
18680
|
// src/lib/console.ts
|
|
18954
18681
|
function enableConsoleCapture(page, sessionId) {
|
|
18955
18682
|
const onConsole = (msg) => {
|
|
@@ -19451,7 +19178,7 @@ function countActiveSessions2() {
|
|
|
19451
19178
|
return countActiveSessions();
|
|
19452
19179
|
}
|
|
19453
19180
|
|
|
19454
|
-
// src/
|
|
19181
|
+
// src/sdk.ts
|
|
19455
19182
|
init_actions();
|
|
19456
19183
|
|
|
19457
19184
|
// src/lib/extractor.ts
|
|
@@ -19618,6 +19345,698 @@ async function getPageInfo(page) {
|
|
|
19618
19345
|
viewport
|
|
19619
19346
|
};
|
|
19620
19347
|
}
|
|
19348
|
+
|
|
19349
|
+
// src/lib/screenshot.ts
|
|
19350
|
+
init_types();
|
|
19351
|
+
var import_sharp = __toESM(require_lib3(), 1);
|
|
19352
|
+
import { join as join10 } from "path";
|
|
19353
|
+
import { mkdirSync as mkdirSync7 } from "fs";
|
|
19354
|
+
|
|
19355
|
+
// src/db/gallery.ts
|
|
19356
|
+
init_schema();
|
|
19357
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
19358
|
+
import { join as join9, resolve, relative as relative2, isAbsolute } from "path";
|
|
19359
|
+
function validateDataPath(filePath) {
|
|
19360
|
+
if (filePath.includes("..")) {
|
|
19361
|
+
throw new Error(`File path must not contain '..': ${filePath}`);
|
|
19362
|
+
}
|
|
19363
|
+
const dataDir = resolve(getDataDir2());
|
|
19364
|
+
const resolved = resolve(isAbsolute(filePath) ? filePath : join9(dataDir, filePath));
|
|
19365
|
+
const rel = relative2(dataDir, resolved);
|
|
19366
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
19367
|
+
throw new Error(`File path must be within data directory: ${filePath}`);
|
|
19368
|
+
}
|
|
19369
|
+
return filePath;
|
|
19370
|
+
}
|
|
19371
|
+
function deserialize(row) {
|
|
19372
|
+
return {
|
|
19373
|
+
id: row.id,
|
|
19374
|
+
session_id: row.session_id ?? undefined,
|
|
19375
|
+
project_id: row.project_id ?? undefined,
|
|
19376
|
+
url: row.url ?? undefined,
|
|
19377
|
+
title: row.title ?? undefined,
|
|
19378
|
+
path: row.path,
|
|
19379
|
+
thumbnail_path: row.thumbnail_path ?? undefined,
|
|
19380
|
+
format: row.format ?? undefined,
|
|
19381
|
+
width: row.width ?? undefined,
|
|
19382
|
+
height: row.height ?? undefined,
|
|
19383
|
+
original_size_bytes: row.original_size_bytes ?? undefined,
|
|
19384
|
+
compressed_size_bytes: row.compressed_size_bytes ?? undefined,
|
|
19385
|
+
compression_ratio: row.compression_ratio ?? undefined,
|
|
19386
|
+
tags: (() => {
|
|
19387
|
+
try {
|
|
19388
|
+
return JSON.parse(row.tags);
|
|
19389
|
+
} catch {
|
|
19390
|
+
return [];
|
|
19391
|
+
}
|
|
19392
|
+
})(),
|
|
19393
|
+
notes: row.notes ?? undefined,
|
|
19394
|
+
is_favorite: row.is_favorite === 1,
|
|
19395
|
+
created_at: row.created_at
|
|
19396
|
+
};
|
|
19397
|
+
}
|
|
19398
|
+
function createEntry(data) {
|
|
19399
|
+
const db = getDatabase();
|
|
19400
|
+
const id = randomUUID4();
|
|
19401
|
+
validateDataPath(data.path);
|
|
19402
|
+
if (data.thumbnail_path)
|
|
19403
|
+
validateDataPath(data.thumbnail_path);
|
|
19404
|
+
db.prepare(`
|
|
19405
|
+
INSERT INTO gallery_entries
|
|
19406
|
+
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
19407
|
+
width, height, original_size_bytes, compressed_size_bytes, compression_ratio,
|
|
19408
|
+
tags, notes, is_favorite)
|
|
19409
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
19410
|
+
`).run(id, data.session_id ?? null, data.project_id ?? null, data.url ?? null, data.title ?? null, data.path, data.thumbnail_path ?? null, data.format ?? null, data.width ?? null, data.height ?? null, data.original_size_bytes ?? null, data.compressed_size_bytes ?? null, data.compression_ratio ?? null, JSON.stringify(data.tags ?? []), data.notes ?? null, data.is_favorite ? 1 : 0);
|
|
19411
|
+
return getEntry(id);
|
|
19412
|
+
}
|
|
19413
|
+
function getEntry(id) {
|
|
19414
|
+
const db = getDatabase();
|
|
19415
|
+
const row = db.query("SELECT * FROM gallery_entries WHERE id = ?").get(id);
|
|
19416
|
+
return row ? deserialize(row) : null;
|
|
19417
|
+
}
|
|
19418
|
+
|
|
19419
|
+
// src/lib/screenshot.ts
|
|
19420
|
+
init_schema();
|
|
19421
|
+
function getScreenshotDir(projectId) {
|
|
19422
|
+
const base = join10(getDataDir2(), "screenshots");
|
|
19423
|
+
const date = new Date().toISOString().split("T")[0];
|
|
19424
|
+
const dir = projectId ? join10(base, projectId, date) : join10(base, date);
|
|
19425
|
+
mkdirSync7(dir, { recursive: true });
|
|
19426
|
+
return dir;
|
|
19427
|
+
}
|
|
19428
|
+
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
19429
|
+
let pipeline = import_sharp.default(raw).resize({ width: maxWidth, withoutEnlargement: true });
|
|
19430
|
+
switch (format) {
|
|
19431
|
+
case "webp":
|
|
19432
|
+
return pipeline.webp({ quality, effort: 4 }).toBuffer();
|
|
19433
|
+
case "jpeg":
|
|
19434
|
+
return pipeline.jpeg({ quality, mozjpeg: true }).toBuffer();
|
|
19435
|
+
case "png":
|
|
19436
|
+
return pipeline.png({ compressionLevel: 9 }).toBuffer();
|
|
19437
|
+
}
|
|
19438
|
+
}
|
|
19439
|
+
async function generateThumbnail(raw, dir, stem) {
|
|
19440
|
+
const thumbPath = join10(dir, `${stem}.thumb.webp`);
|
|
19441
|
+
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
19442
|
+
await Bun.write(thumbPath, thumbBuffer);
|
|
19443
|
+
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
19444
|
+
}
|
|
19445
|
+
async function takeScreenshot(page, opts) {
|
|
19446
|
+
try {
|
|
19447
|
+
const dir = getScreenshotDir(opts?.projectId);
|
|
19448
|
+
const timestamp = Date.now();
|
|
19449
|
+
const format = opts?.format ?? "webp";
|
|
19450
|
+
const compress = opts?.compress ?? true;
|
|
19451
|
+
const maxWidth = opts?.maxWidth ?? 1200;
|
|
19452
|
+
const quality = opts?.quality ?? (format === "webp" ? 80 : format === "jpeg" ? 70 : undefined);
|
|
19453
|
+
const stem = String(timestamp);
|
|
19454
|
+
const rawOpts = {
|
|
19455
|
+
fullPage: opts?.fullPage ?? false,
|
|
19456
|
+
type: "png"
|
|
19457
|
+
};
|
|
19458
|
+
let rawBuffer;
|
|
19459
|
+
const isBunView = typeof page.getNativeView === "function";
|
|
19460
|
+
if (opts?.selector) {
|
|
19461
|
+
if (isBunView) {
|
|
19462
|
+
const uint8 = await page.screenshot();
|
|
19463
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
19464
|
+
} else {
|
|
19465
|
+
const el = await page.$(opts.selector);
|
|
19466
|
+
if (!el)
|
|
19467
|
+
throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
|
|
19468
|
+
rawBuffer = await el.screenshot(rawOpts);
|
|
19469
|
+
}
|
|
19470
|
+
} else if (isBunView) {
|
|
19471
|
+
const uint8 = await page.screenshot();
|
|
19472
|
+
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
19473
|
+
} else {
|
|
19474
|
+
rawBuffer = await page.screenshot(rawOpts);
|
|
19475
|
+
}
|
|
19476
|
+
const originalSizeBytes = rawBuffer.length;
|
|
19477
|
+
const MAX_SIZE_BYTES = 500 * 1024;
|
|
19478
|
+
let finalBuffer;
|
|
19479
|
+
let compressed = true;
|
|
19480
|
+
let fallback = false;
|
|
19481
|
+
try {
|
|
19482
|
+
if (compress && format !== "png") {
|
|
19483
|
+
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 70, maxWidth);
|
|
19484
|
+
if (finalBuffer.length > MAX_SIZE_BYTES) {
|
|
19485
|
+
const reducedFormat = format === "webp" ? "webp" : "jpeg";
|
|
19486
|
+
for (const q of [60, 50, 40, 30]) {
|
|
19487
|
+
const attempt = await compressBuffer(rawBuffer, reducedFormat, q, maxWidth);
|
|
19488
|
+
if (attempt.length <= MAX_SIZE_BYTES) {
|
|
19489
|
+
finalBuffer = attempt;
|
|
19490
|
+
break;
|
|
19491
|
+
}
|
|
19492
|
+
}
|
|
19493
|
+
}
|
|
19494
|
+
} else if (compress && format === "png") {
|
|
19495
|
+
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
19496
|
+
} else {
|
|
19497
|
+
finalBuffer = rawBuffer;
|
|
19498
|
+
compressed = false;
|
|
19499
|
+
}
|
|
19500
|
+
} catch (sharpErr) {
|
|
19501
|
+
fallback = true;
|
|
19502
|
+
compressed = false;
|
|
19503
|
+
finalBuffer = rawBuffer;
|
|
19504
|
+
}
|
|
19505
|
+
const compressedSizeBytes = finalBuffer.length;
|
|
19506
|
+
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
19507
|
+
const ext = format;
|
|
19508
|
+
const screenshotPath = opts?.path ?? join10(dir, `${stem}.${ext}`);
|
|
19509
|
+
await Bun.write(screenshotPath, finalBuffer);
|
|
19510
|
+
let thumbnailPath;
|
|
19511
|
+
let thumbnailBase64;
|
|
19512
|
+
if (opts?.thumbnail !== false) {
|
|
19513
|
+
const thumb = await generateThumbnail(rawBuffer, dir, stem);
|
|
19514
|
+
thumbnailPath = thumb.path;
|
|
19515
|
+
thumbnailBase64 = thumb.base64;
|
|
19516
|
+
}
|
|
19517
|
+
const meta = await import_sharp.default(finalBuffer).metadata();
|
|
19518
|
+
const width = meta.width ?? (page.viewportSize()?.width ?? 1280);
|
|
19519
|
+
const height = meta.height ?? (page.viewportSize()?.height ?? 720);
|
|
19520
|
+
const result = {
|
|
19521
|
+
path: screenshotPath,
|
|
19522
|
+
base64: finalBuffer.toString("base64"),
|
|
19523
|
+
width,
|
|
19524
|
+
height,
|
|
19525
|
+
size_bytes: compressedSizeBytes,
|
|
19526
|
+
original_size_bytes: originalSizeBytes,
|
|
19527
|
+
compressed_size_bytes: compressedSizeBytes,
|
|
19528
|
+
compression_ratio: compressionRatio,
|
|
19529
|
+
thumbnail_path: thumbnailPath,
|
|
19530
|
+
thumbnail_base64: thumbnailBase64,
|
|
19531
|
+
...fallback ? { fallback: true, compressed: false } : {}
|
|
19532
|
+
};
|
|
19533
|
+
if (opts?.track !== false) {
|
|
19534
|
+
try {
|
|
19535
|
+
const url = await page.url().valueOf();
|
|
19536
|
+
let title;
|
|
19537
|
+
try {
|
|
19538
|
+
title = await page.title();
|
|
19539
|
+
} catch {}
|
|
19540
|
+
const entry = createEntry({
|
|
19541
|
+
session_id: opts?.sessionId,
|
|
19542
|
+
project_id: opts?.projectId,
|
|
19543
|
+
url,
|
|
19544
|
+
title,
|
|
19545
|
+
path: screenshotPath,
|
|
19546
|
+
thumbnail_path: thumbnailPath,
|
|
19547
|
+
format: ext,
|
|
19548
|
+
width,
|
|
19549
|
+
height,
|
|
19550
|
+
original_size_bytes: originalSizeBytes,
|
|
19551
|
+
compressed_size_bytes: compressedSizeBytes,
|
|
19552
|
+
compression_ratio: compressionRatio,
|
|
19553
|
+
tags: [],
|
|
19554
|
+
is_favorite: false
|
|
19555
|
+
});
|
|
19556
|
+
result.gallery_id = entry.id;
|
|
19557
|
+
} catch {}
|
|
19558
|
+
}
|
|
19559
|
+
return result;
|
|
19560
|
+
} catch (err) {
|
|
19561
|
+
if (err instanceof BrowserError)
|
|
19562
|
+
throw err;
|
|
19563
|
+
throw new BrowserError(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`, "SCREENSHOT_FAILED");
|
|
19564
|
+
}
|
|
19565
|
+
}
|
|
19566
|
+
async function generatePDF(page, opts) {
|
|
19567
|
+
try {
|
|
19568
|
+
const base = join10(getDataDir2(), "pdfs");
|
|
19569
|
+
const date = new Date().toISOString().split("T")[0];
|
|
19570
|
+
const dir = opts?.projectId ? join10(base, opts.projectId, date) : join10(base, date);
|
|
19571
|
+
mkdirSync7(dir, { recursive: true });
|
|
19572
|
+
const timestamp = Date.now();
|
|
19573
|
+
const pdfPath = opts?.path ?? join10(dir, `${timestamp}.pdf`);
|
|
19574
|
+
const buffer = await page.pdf({
|
|
19575
|
+
path: pdfPath,
|
|
19576
|
+
format: opts?.format ?? "A4",
|
|
19577
|
+
landscape: opts?.landscape ?? false,
|
|
19578
|
+
margin: opts?.margin,
|
|
19579
|
+
printBackground: opts?.printBackground ?? true
|
|
19580
|
+
});
|
|
19581
|
+
return {
|
|
19582
|
+
path: pdfPath,
|
|
19583
|
+
base64: Buffer.from(buffer).toString("base64"),
|
|
19584
|
+
size_bytes: buffer.length
|
|
19585
|
+
};
|
|
19586
|
+
} catch (err) {
|
|
19587
|
+
throw new BrowserError(`PDF generation failed: ${err instanceof Error ? err.message : String(err)}`, "PDF_FAILED");
|
|
19588
|
+
}
|
|
19589
|
+
}
|
|
19590
|
+
|
|
19591
|
+
// src/sdk.ts
|
|
19592
|
+
function buildDependencies(input) {
|
|
19593
|
+
return {
|
|
19594
|
+
createSession: input?.createSession ?? createSession2,
|
|
19595
|
+
closeSession: input?.closeSession ?? closeSession2,
|
|
19596
|
+
getSessionPage: input?.getSessionPage ?? getSessionPage,
|
|
19597
|
+
getPageInfo: input?.getPageInfo ?? getPageInfo,
|
|
19598
|
+
navigate: input?.navigate ?? navigate,
|
|
19599
|
+
click: input?.click ?? click,
|
|
19600
|
+
typeText: input?.typeText ?? type,
|
|
19601
|
+
fill: input?.fill ?? fill,
|
|
19602
|
+
pressKey: input?.pressKey ?? pressKey,
|
|
19603
|
+
waitForSelector: input?.waitForSelector ?? waitForSelector,
|
|
19604
|
+
takeScreenshot: input?.takeScreenshot ?? takeScreenshot
|
|
19605
|
+
};
|
|
19606
|
+
}
|
|
19607
|
+
|
|
19608
|
+
class BrowserSDK {
|
|
19609
|
+
deps;
|
|
19610
|
+
sessions = new Map;
|
|
19611
|
+
constructor(options = {}) {
|
|
19612
|
+
this.deps = buildDependencies(options.dependencies);
|
|
19613
|
+
}
|
|
19614
|
+
async open(options = {}) {
|
|
19615
|
+
const { session, page } = await this.deps.createSession(options);
|
|
19616
|
+
const handle = { id: session.id, session, page };
|
|
19617
|
+
this.sessions.set(handle.id, handle);
|
|
19618
|
+
return handle;
|
|
19619
|
+
}
|
|
19620
|
+
async createSession(options = {}) {
|
|
19621
|
+
return this.open(options);
|
|
19622
|
+
}
|
|
19623
|
+
getPage(ref) {
|
|
19624
|
+
if (typeof ref !== "string")
|
|
19625
|
+
return ref.page;
|
|
19626
|
+
return this.sessions.get(ref)?.page ?? this.deps.getSessionPage(ref);
|
|
19627
|
+
}
|
|
19628
|
+
async pageInfo(ref) {
|
|
19629
|
+
return this.deps.getPageInfo(this.getPage(ref));
|
|
19630
|
+
}
|
|
19631
|
+
async navigate(ref, url, opts) {
|
|
19632
|
+
await this.deps.navigate(this.getPage(ref), url, opts?.timeout);
|
|
19633
|
+
return this.pageInfo(ref);
|
|
19634
|
+
}
|
|
19635
|
+
async click(ref, selector, opts) {
|
|
19636
|
+
return this.deps.click(this.getPage(ref), selector, {
|
|
19637
|
+
timeout: opts?.timeout,
|
|
19638
|
+
selfHeal: opts?.selfHeal
|
|
19639
|
+
});
|
|
19640
|
+
}
|
|
19641
|
+
async fill(ref, selector, value, opts) {
|
|
19642
|
+
return this.deps.fill(this.getPage(ref), selector, value, opts?.timeout, opts?.selfHeal);
|
|
19643
|
+
}
|
|
19644
|
+
async type(ref, selector, text, opts) {
|
|
19645
|
+
return this.deps.typeText(this.getPage(ref), selector, text, {
|
|
19646
|
+
timeout: opts?.timeout,
|
|
19647
|
+
clear: opts?.clear,
|
|
19648
|
+
selfHeal: opts?.selfHeal
|
|
19649
|
+
});
|
|
19650
|
+
}
|
|
19651
|
+
async pressKey(ref, key) {
|
|
19652
|
+
return this.deps.pressKey(this.getPage(ref), key);
|
|
19653
|
+
}
|
|
19654
|
+
async waitForSelector(ref, selector, opts) {
|
|
19655
|
+
return this.deps.waitForSelector(this.getPage(ref), selector, opts);
|
|
19656
|
+
}
|
|
19657
|
+
async screenshot(ref, options) {
|
|
19658
|
+
const sessionId = typeof ref === "string" ? ref : ref.id;
|
|
19659
|
+
return this.deps.takeScreenshot(this.getPage(ref), {
|
|
19660
|
+
...options,
|
|
19661
|
+
sessionId
|
|
19662
|
+
});
|
|
19663
|
+
}
|
|
19664
|
+
async close(ref) {
|
|
19665
|
+
const sessionId = typeof ref === "string" ? ref : ref.id;
|
|
19666
|
+
this.sessions.delete(sessionId);
|
|
19667
|
+
return this.deps.closeSession(sessionId);
|
|
19668
|
+
}
|
|
19669
|
+
async run(options) {
|
|
19670
|
+
const ownsSession = !options.session;
|
|
19671
|
+
const handle = options.session ? this.resolveHandle(options.session) : await this.open(options.sessionOptions);
|
|
19672
|
+
const stepResults = [];
|
|
19673
|
+
try {
|
|
19674
|
+
for (const step of options.steps) {
|
|
19675
|
+
stepResults.push(await this.runStep(handle, step));
|
|
19676
|
+
}
|
|
19677
|
+
return {
|
|
19678
|
+
session: handle.session,
|
|
19679
|
+
pageInfo: await this.pageInfo(handle),
|
|
19680
|
+
steps: stepResults
|
|
19681
|
+
};
|
|
19682
|
+
} finally {
|
|
19683
|
+
const shouldClose = options.autoClose ?? ownsSession;
|
|
19684
|
+
if (shouldClose) {
|
|
19685
|
+
await this.close(handle);
|
|
19686
|
+
}
|
|
19687
|
+
}
|
|
19688
|
+
}
|
|
19689
|
+
resolveHandle(ref) {
|
|
19690
|
+
if (typeof ref !== "string")
|
|
19691
|
+
return ref;
|
|
19692
|
+
const cached = this.sessions.get(ref);
|
|
19693
|
+
if (cached)
|
|
19694
|
+
return cached;
|
|
19695
|
+
return {
|
|
19696
|
+
id: ref,
|
|
19697
|
+
session: {
|
|
19698
|
+
id: ref,
|
|
19699
|
+
engine: "playwright",
|
|
19700
|
+
status: "active",
|
|
19701
|
+
created_at: new Date(0).toISOString()
|
|
19702
|
+
},
|
|
19703
|
+
page: this.deps.getSessionPage(ref)
|
|
19704
|
+
};
|
|
19705
|
+
}
|
|
19706
|
+
async runStep(handle, step) {
|
|
19707
|
+
switch (step.type) {
|
|
19708
|
+
case "navigate":
|
|
19709
|
+
await this.navigate(handle, step.url, { timeout: step.timeout });
|
|
19710
|
+
return { type: step.type, ok: true };
|
|
19711
|
+
case "click":
|
|
19712
|
+
await this.click(handle, step.selector, { timeout: step.timeout, selfHeal: step.selfHeal });
|
|
19713
|
+
return { type: step.type, ok: true };
|
|
19714
|
+
case "fill":
|
|
19715
|
+
await this.fill(handle, step.selector, step.value, { timeout: step.timeout, selfHeal: step.selfHeal });
|
|
19716
|
+
return { type: step.type, ok: true };
|
|
19717
|
+
case "type":
|
|
19718
|
+
await this.type(handle, step.selector, step.text, {
|
|
19719
|
+
timeout: step.timeout,
|
|
19720
|
+
clear: step.clear,
|
|
19721
|
+
selfHeal: step.selfHeal
|
|
19722
|
+
});
|
|
19723
|
+
return { type: step.type, ok: true };
|
|
19724
|
+
case "press":
|
|
19725
|
+
await this.pressKey(handle, step.key);
|
|
19726
|
+
return { type: step.type, ok: true };
|
|
19727
|
+
case "wait":
|
|
19728
|
+
await this.waitForSelector(handle, step.selector, {
|
|
19729
|
+
timeout: step.timeout,
|
|
19730
|
+
state: step.state
|
|
19731
|
+
});
|
|
19732
|
+
return { type: step.type, ok: true };
|
|
19733
|
+
case "screenshot":
|
|
19734
|
+
return {
|
|
19735
|
+
type: step.type,
|
|
19736
|
+
ok: true,
|
|
19737
|
+
screenshot: await this.screenshot(handle, step.options)
|
|
19738
|
+
};
|
|
19739
|
+
case "pageInfo":
|
|
19740
|
+
return {
|
|
19741
|
+
type: step.type,
|
|
19742
|
+
ok: true,
|
|
19743
|
+
info: await this.pageInfo(handle)
|
|
19744
|
+
};
|
|
19745
|
+
}
|
|
19746
|
+
}
|
|
19747
|
+
}
|
|
19748
|
+
function createBrowserSDK(options) {
|
|
19749
|
+
return new BrowserSDK(options);
|
|
19750
|
+
}
|
|
19751
|
+
|
|
19752
|
+
// src/index.ts
|
|
19753
|
+
init_schema();
|
|
19754
|
+
|
|
19755
|
+
// src/db/projects.ts
|
|
19756
|
+
init_schema();
|
|
19757
|
+
init_types();
|
|
19758
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
19759
|
+
function createProject(data) {
|
|
19760
|
+
const db = getDatabase();
|
|
19761
|
+
const id = randomUUID5();
|
|
19762
|
+
db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
|
|
19763
|
+
return getProject(id);
|
|
19764
|
+
}
|
|
19765
|
+
function ensureProject(name, path, description) {
|
|
19766
|
+
const db = getDatabase();
|
|
19767
|
+
const existing = db.query("SELECT * FROM projects WHERE name = ?").get(name);
|
|
19768
|
+
if (existing)
|
|
19769
|
+
return existing;
|
|
19770
|
+
return createProject({ name, path, description });
|
|
19771
|
+
}
|
|
19772
|
+
function getProject(id) {
|
|
19773
|
+
const db = getDatabase();
|
|
19774
|
+
const row = db.query("SELECT * FROM projects WHERE id = ?").get(id);
|
|
19775
|
+
if (!row)
|
|
19776
|
+
throw new ProjectNotFoundError(id);
|
|
19777
|
+
return row;
|
|
19778
|
+
}
|
|
19779
|
+
function getProjectByName(name) {
|
|
19780
|
+
const db = getDatabase();
|
|
19781
|
+
return db.query("SELECT * FROM projects WHERE name = ?").get(name) ?? null;
|
|
19782
|
+
}
|
|
19783
|
+
function listProjects() {
|
|
19784
|
+
const db = getDatabase();
|
|
19785
|
+
return db.query("SELECT * FROM projects ORDER BY created_at DESC").all();
|
|
19786
|
+
}
|
|
19787
|
+
function updateProject(id, data) {
|
|
19788
|
+
const db = getDatabase();
|
|
19789
|
+
const fields = [];
|
|
19790
|
+
const values = [];
|
|
19791
|
+
if (data.name !== undefined) {
|
|
19792
|
+
fields.push("name = ?");
|
|
19793
|
+
values.push(data.name);
|
|
19794
|
+
}
|
|
19795
|
+
if (data.path !== undefined) {
|
|
19796
|
+
fields.push("path = ?");
|
|
19797
|
+
values.push(data.path);
|
|
19798
|
+
}
|
|
19799
|
+
if (data.description !== undefined) {
|
|
19800
|
+
fields.push("description = ?");
|
|
19801
|
+
values.push(data.description ?? null);
|
|
19802
|
+
}
|
|
19803
|
+
if (fields.length === 0)
|
|
19804
|
+
return getProject(id);
|
|
19805
|
+
values.push(id);
|
|
19806
|
+
db.prepare(`UPDATE projects SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
19807
|
+
return getProject(id);
|
|
19808
|
+
}
|
|
19809
|
+
function deleteProject(id) {
|
|
19810
|
+
const db = getDatabase();
|
|
19811
|
+
db.prepare("DELETE FROM projects WHERE id = ?").run(id);
|
|
19812
|
+
}
|
|
19813
|
+
// src/db/agents.ts
|
|
19814
|
+
init_schema();
|
|
19815
|
+
init_types();
|
|
19816
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
19817
|
+
function registerAgent(name, opts = {}) {
|
|
19818
|
+
const db = getDatabase();
|
|
19819
|
+
const existing = db.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
19820
|
+
if (existing) {
|
|
19821
|
+
db.prepare("UPDATE agents SET last_seen = datetime('now'), session_id = ?, project_id = ?, working_dir = ? WHERE name = ?").run(opts.sessionId ?? existing.session_id ?? null, opts.projectId ?? existing.project_id ?? null, opts.workingDir ?? existing.working_dir ?? null, name);
|
|
19822
|
+
return getAgentByName(name);
|
|
19823
|
+
}
|
|
19824
|
+
const id = randomUUID6();
|
|
19825
|
+
db.prepare("INSERT INTO agents (id, name, description, session_id, project_id, working_dir) VALUES (?, ?, ?, ?, ?, ?)").run(id, name, opts.description ?? null, opts.sessionId ?? null, opts.projectId ?? null, opts.workingDir ?? null);
|
|
19826
|
+
return getAgent(id);
|
|
19827
|
+
}
|
|
19828
|
+
function heartbeat(agentId) {
|
|
19829
|
+
const db = getDatabase();
|
|
19830
|
+
const agent = db.query("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
19831
|
+
if (!agent)
|
|
19832
|
+
throw new AgentNotFoundError(agentId);
|
|
19833
|
+
db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
|
|
19834
|
+
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(randomUUID6(), agentId, agent.session_id ?? null);
|
|
19835
|
+
}
|
|
19836
|
+
function getAgent(id) {
|
|
19837
|
+
const db = getDatabase();
|
|
19838
|
+
const row = db.query("SELECT * FROM agents WHERE id = ?").get(id);
|
|
19839
|
+
if (!row)
|
|
19840
|
+
throw new AgentNotFoundError(id);
|
|
19841
|
+
return row;
|
|
19842
|
+
}
|
|
19843
|
+
function getAgentByName(name) {
|
|
19844
|
+
const db = getDatabase();
|
|
19845
|
+
return db.query("SELECT * FROM agents WHERE name = ?").get(name) ?? null;
|
|
19846
|
+
}
|
|
19847
|
+
function listAgents(projectId) {
|
|
19848
|
+
const db = getDatabase();
|
|
19849
|
+
if (projectId) {
|
|
19850
|
+
return db.query("SELECT * FROM agents WHERE project_id = ? ORDER BY last_seen DESC").all(projectId);
|
|
19851
|
+
}
|
|
19852
|
+
return db.query("SELECT * FROM agents ORDER BY last_seen DESC").all();
|
|
19853
|
+
}
|
|
19854
|
+
function updateAgent(id, data) {
|
|
19855
|
+
const db = getDatabase();
|
|
19856
|
+
const fields = [];
|
|
19857
|
+
const values = [];
|
|
19858
|
+
if (data.name !== undefined) {
|
|
19859
|
+
fields.push("name = ?");
|
|
19860
|
+
values.push(data.name ?? null);
|
|
19861
|
+
}
|
|
19862
|
+
if (data.description !== undefined) {
|
|
19863
|
+
fields.push("description = ?");
|
|
19864
|
+
values.push(data.description ?? null);
|
|
19865
|
+
}
|
|
19866
|
+
if (data.session_id !== undefined) {
|
|
19867
|
+
fields.push("session_id = ?");
|
|
19868
|
+
values.push(data.session_id ?? null);
|
|
19869
|
+
}
|
|
19870
|
+
if (data.project_id !== undefined) {
|
|
19871
|
+
fields.push("project_id = ?");
|
|
19872
|
+
values.push(data.project_id ?? null);
|
|
19873
|
+
}
|
|
19874
|
+
if (data.working_dir !== undefined) {
|
|
19875
|
+
fields.push("working_dir = ?");
|
|
19876
|
+
values.push(data.working_dir ?? null);
|
|
19877
|
+
}
|
|
19878
|
+
if (fields.length === 0)
|
|
19879
|
+
return getAgent(id);
|
|
19880
|
+
values.push(id);
|
|
19881
|
+
db.prepare(`UPDATE agents SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
19882
|
+
return getAgent(id);
|
|
19883
|
+
}
|
|
19884
|
+
function deleteAgent(id) {
|
|
19885
|
+
const db = getDatabase();
|
|
19886
|
+
db.prepare("DELETE FROM agents WHERE id = ?").run(id);
|
|
19887
|
+
}
|
|
19888
|
+
function cleanStaleAgents(thresholdMs) {
|
|
19889
|
+
const db = getDatabase();
|
|
19890
|
+
const cutoff = new Date(Date.now() - thresholdMs).toISOString().replace("T", " ").split(".")[0];
|
|
19891
|
+
const result = db.prepare("DELETE FROM agents WHERE last_seen < ?").run(cutoff);
|
|
19892
|
+
return result.changes;
|
|
19893
|
+
}
|
|
19894
|
+
// src/db/snapshots.ts
|
|
19895
|
+
init_schema();
|
|
19896
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
19897
|
+
function createSnapshot(data) {
|
|
19898
|
+
const db = getDatabase();
|
|
19899
|
+
const id = randomUUID7();
|
|
19900
|
+
db.prepare("INSERT INTO snapshots (id, session_id, url, title, html, screenshot_path) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.url, data.title ?? null, data.html ?? null, data.screenshot_path ?? null);
|
|
19901
|
+
return getSnapshot(id);
|
|
19902
|
+
}
|
|
19903
|
+
function getSnapshot(id) {
|
|
19904
|
+
const db = getDatabase();
|
|
19905
|
+
return db.query("SELECT * FROM snapshots WHERE id = ?").get(id) ?? null;
|
|
19906
|
+
}
|
|
19907
|
+
function listSnapshots(sessionId) {
|
|
19908
|
+
const db = getDatabase();
|
|
19909
|
+
return db.query("SELECT * FROM snapshots WHERE session_id = ? ORDER BY timestamp DESC").all(sessionId);
|
|
19910
|
+
}
|
|
19911
|
+
function deleteSnapshot(id) {
|
|
19912
|
+
const db = getDatabase();
|
|
19913
|
+
db.prepare("DELETE FROM snapshots WHERE id = ?").run(id);
|
|
19914
|
+
}
|
|
19915
|
+
function deleteSnapshotsBySession(sessionId) {
|
|
19916
|
+
const db = getDatabase();
|
|
19917
|
+
db.prepare("DELETE FROM snapshots WHERE session_id = ?").run(sessionId);
|
|
19918
|
+
}
|
|
19919
|
+
// src/db/recordings.ts
|
|
19920
|
+
init_schema();
|
|
19921
|
+
init_types();
|
|
19922
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
19923
|
+
function deserialize2(row) {
|
|
19924
|
+
return {
|
|
19925
|
+
...row,
|
|
19926
|
+
project_id: row.project_id ?? undefined,
|
|
19927
|
+
start_url: row.start_url ?? undefined,
|
|
19928
|
+
steps: JSON.parse(row.steps)
|
|
19929
|
+
};
|
|
19930
|
+
}
|
|
19931
|
+
function createRecording(data) {
|
|
19932
|
+
const db = getDatabase();
|
|
19933
|
+
const id = randomUUID8();
|
|
19934
|
+
db.prepare("INSERT INTO recordings (id, name, project_id, start_url, steps) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.project_id ?? null, data.start_url ?? null, JSON.stringify(data.steps ?? []));
|
|
19935
|
+
return getRecording(id);
|
|
19936
|
+
}
|
|
19937
|
+
function getRecording(id) {
|
|
19938
|
+
const db = getDatabase();
|
|
19939
|
+
const row = db.query("SELECT * FROM recordings WHERE id = ?").get(id);
|
|
19940
|
+
if (!row)
|
|
19941
|
+
throw new RecordingNotFoundError(id);
|
|
19942
|
+
return deserialize2(row);
|
|
19943
|
+
}
|
|
19944
|
+
function listRecordings(projectId) {
|
|
19945
|
+
const db = getDatabase();
|
|
19946
|
+
const rows = projectId ? db.query("SELECT * FROM recordings WHERE project_id = ? ORDER BY created_at DESC").all(projectId) : db.query("SELECT * FROM recordings ORDER BY created_at DESC").all();
|
|
19947
|
+
return rows.map(deserialize2);
|
|
19948
|
+
}
|
|
19949
|
+
function updateRecording(id, data) {
|
|
19950
|
+
const db = getDatabase();
|
|
19951
|
+
const fields = [];
|
|
19952
|
+
const values = [];
|
|
19953
|
+
if (data.name !== undefined) {
|
|
19954
|
+
fields.push("name = ?");
|
|
19955
|
+
values.push(data.name);
|
|
19956
|
+
}
|
|
19957
|
+
if (data.steps !== undefined) {
|
|
19958
|
+
fields.push("steps = ?");
|
|
19959
|
+
values.push(JSON.stringify(data.steps));
|
|
19960
|
+
}
|
|
19961
|
+
if (data.start_url !== undefined) {
|
|
19962
|
+
fields.push("start_url = ?");
|
|
19963
|
+
values.push(data.start_url ?? null);
|
|
19964
|
+
}
|
|
19965
|
+
if (fields.length === 0)
|
|
19966
|
+
return getRecording(id);
|
|
19967
|
+
values.push(id);
|
|
19968
|
+
db.prepare(`UPDATE recordings SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
|
19969
|
+
return getRecording(id);
|
|
19970
|
+
}
|
|
19971
|
+
function deleteRecording(id) {
|
|
19972
|
+
const db = getDatabase();
|
|
19973
|
+
db.prepare("DELETE FROM recordings WHERE id = ?").run(id);
|
|
19974
|
+
}
|
|
19975
|
+
// src/db/crawl-results.ts
|
|
19976
|
+
init_schema();
|
|
19977
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
19978
|
+
function deserialize3(row) {
|
|
19979
|
+
const pages = JSON.parse(row.pages);
|
|
19980
|
+
return {
|
|
19981
|
+
id: row.id,
|
|
19982
|
+
project_id: row.project_id ?? undefined,
|
|
19983
|
+
start_url: row.start_url,
|
|
19984
|
+
depth: row.depth,
|
|
19985
|
+
pages,
|
|
19986
|
+
total_links: pages.reduce((acc, p) => acc + p.links.length, 0),
|
|
19987
|
+
errors: JSON.parse(row.errors),
|
|
19988
|
+
created_at: row.created_at
|
|
19989
|
+
};
|
|
19990
|
+
}
|
|
19991
|
+
function createCrawlResult(data) {
|
|
19992
|
+
const db = getDatabase();
|
|
19993
|
+
const id = randomUUID9();
|
|
19994
|
+
db.prepare("INSERT INTO crawl_results (id, project_id, start_url, depth, pages, links, errors) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, data.project_id ?? null, data.start_url, data.depth, JSON.stringify(data.pages), JSON.stringify(data.pages.flatMap((p) => p.links)), JSON.stringify(data.errors));
|
|
19995
|
+
return getCrawlResult(id);
|
|
19996
|
+
}
|
|
19997
|
+
function getCrawlResult(id) {
|
|
19998
|
+
const db = getDatabase();
|
|
19999
|
+
const row = db.query("SELECT * FROM crawl_results WHERE id = ?").get(id);
|
|
20000
|
+
return row ? deserialize3(row) : null;
|
|
20001
|
+
}
|
|
20002
|
+
function listCrawlResults(projectId) {
|
|
20003
|
+
const db = getDatabase();
|
|
20004
|
+
const rows = projectId ? db.query("SELECT * FROM crawl_results WHERE project_id = ? ORDER BY created_at DESC").all(projectId) : db.query("SELECT * FROM crawl_results ORDER BY created_at DESC").all();
|
|
20005
|
+
return rows.map(deserialize3);
|
|
20006
|
+
}
|
|
20007
|
+
function deleteCrawlResult(id) {
|
|
20008
|
+
const db = getDatabase();
|
|
20009
|
+
db.prepare("DELETE FROM crawl_results WHERE id = ?").run(id);
|
|
20010
|
+
}
|
|
20011
|
+
// src/db/heartbeats.ts
|
|
20012
|
+
init_schema();
|
|
20013
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
20014
|
+
function recordHeartbeat(agentId, sessionId) {
|
|
20015
|
+
const db = getDatabase();
|
|
20016
|
+
const id = randomUUID10();
|
|
20017
|
+
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(id, agentId, sessionId ?? null);
|
|
20018
|
+
db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
|
|
20019
|
+
return getLastHeartbeat(agentId);
|
|
20020
|
+
}
|
|
20021
|
+
function getLastHeartbeat(agentId) {
|
|
20022
|
+
const db = getDatabase();
|
|
20023
|
+
return db.query("SELECT * FROM heartbeats WHERE agent_id = ? ORDER BY timestamp DESC LIMIT 1").get(agentId) ?? null;
|
|
20024
|
+
}
|
|
20025
|
+
function listHeartbeats(agentId, limit = 50) {
|
|
20026
|
+
const db = getDatabase();
|
|
20027
|
+
return db.query("SELECT * FROM heartbeats WHERE agent_id = ? ORDER BY timestamp DESC LIMIT ?").all(agentId, limit);
|
|
20028
|
+
}
|
|
20029
|
+
function cleanOldHeartbeats(olderThanMs) {
|
|
20030
|
+
const db = getDatabase();
|
|
20031
|
+
const cutoff = new Date(Date.now() - olderThanMs).toISOString().replace("T", " ").split(".")[0];
|
|
20032
|
+
const result = db.prepare("DELETE FROM heartbeats WHERE timestamp < ?").run(cutoff);
|
|
20033
|
+
return result.changes;
|
|
20034
|
+
}
|
|
20035
|
+
|
|
20036
|
+
// src/index.ts
|
|
20037
|
+
init_cdp();
|
|
20038
|
+
init_actions();
|
|
20039
|
+
|
|
19621
20040
|
// src/lib/performance.ts
|
|
19622
20041
|
init_cdp();
|
|
19623
20042
|
async function getPerformanceMetrics(page) {
|
|
@@ -19706,247 +20125,6 @@ async function startCoverage(page) {
|
|
|
19706
20125
|
}
|
|
19707
20126
|
};
|
|
19708
20127
|
}
|
|
19709
|
-
// src/lib/screenshot.ts
|
|
19710
|
-
init_types();
|
|
19711
|
-
var import_sharp = __toESM(require_lib3(), 1);
|
|
19712
|
-
import { join as join10 } from "path";
|
|
19713
|
-
import { mkdirSync as mkdirSync7 } from "fs";
|
|
19714
|
-
|
|
19715
|
-
// src/db/gallery.ts
|
|
19716
|
-
init_schema();
|
|
19717
|
-
import { randomUUID as randomUUID10 } from "crypto";
|
|
19718
|
-
import { join as join9, resolve, relative as relative2, isAbsolute } from "path";
|
|
19719
|
-
function validateDataPath(filePath) {
|
|
19720
|
-
if (filePath.includes("..")) {
|
|
19721
|
-
throw new Error(`File path must not contain '..': ${filePath}`);
|
|
19722
|
-
}
|
|
19723
|
-
const dataDir = resolve(getDataDir2());
|
|
19724
|
-
const resolved = resolve(isAbsolute(filePath) ? filePath : join9(dataDir, filePath));
|
|
19725
|
-
const rel = relative2(dataDir, resolved);
|
|
19726
|
-
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
19727
|
-
throw new Error(`File path must be within data directory: ${filePath}`);
|
|
19728
|
-
}
|
|
19729
|
-
return filePath;
|
|
19730
|
-
}
|
|
19731
|
-
function deserialize3(row) {
|
|
19732
|
-
return {
|
|
19733
|
-
id: row.id,
|
|
19734
|
-
session_id: row.session_id ?? undefined,
|
|
19735
|
-
project_id: row.project_id ?? undefined,
|
|
19736
|
-
url: row.url ?? undefined,
|
|
19737
|
-
title: row.title ?? undefined,
|
|
19738
|
-
path: row.path,
|
|
19739
|
-
thumbnail_path: row.thumbnail_path ?? undefined,
|
|
19740
|
-
format: row.format ?? undefined,
|
|
19741
|
-
width: row.width ?? undefined,
|
|
19742
|
-
height: row.height ?? undefined,
|
|
19743
|
-
original_size_bytes: row.original_size_bytes ?? undefined,
|
|
19744
|
-
compressed_size_bytes: row.compressed_size_bytes ?? undefined,
|
|
19745
|
-
compression_ratio: row.compression_ratio ?? undefined,
|
|
19746
|
-
tags: (() => {
|
|
19747
|
-
try {
|
|
19748
|
-
return JSON.parse(row.tags);
|
|
19749
|
-
} catch {
|
|
19750
|
-
return [];
|
|
19751
|
-
}
|
|
19752
|
-
})(),
|
|
19753
|
-
notes: row.notes ?? undefined,
|
|
19754
|
-
is_favorite: row.is_favorite === 1,
|
|
19755
|
-
created_at: row.created_at
|
|
19756
|
-
};
|
|
19757
|
-
}
|
|
19758
|
-
function createEntry(data) {
|
|
19759
|
-
const db = getDatabase();
|
|
19760
|
-
const id = randomUUID10();
|
|
19761
|
-
validateDataPath(data.path);
|
|
19762
|
-
if (data.thumbnail_path)
|
|
19763
|
-
validateDataPath(data.thumbnail_path);
|
|
19764
|
-
db.prepare(`
|
|
19765
|
-
INSERT INTO gallery_entries
|
|
19766
|
-
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
19767
|
-
width, height, original_size_bytes, compressed_size_bytes, compression_ratio,
|
|
19768
|
-
tags, notes, is_favorite)
|
|
19769
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
19770
|
-
`).run(id, data.session_id ?? null, data.project_id ?? null, data.url ?? null, data.title ?? null, data.path, data.thumbnail_path ?? null, data.format ?? null, data.width ?? null, data.height ?? null, data.original_size_bytes ?? null, data.compressed_size_bytes ?? null, data.compression_ratio ?? null, JSON.stringify(data.tags ?? []), data.notes ?? null, data.is_favorite ? 1 : 0);
|
|
19771
|
-
return getEntry(id);
|
|
19772
|
-
}
|
|
19773
|
-
function getEntry(id) {
|
|
19774
|
-
const db = getDatabase();
|
|
19775
|
-
const row = db.query("SELECT * FROM gallery_entries WHERE id = ?").get(id);
|
|
19776
|
-
return row ? deserialize3(row) : null;
|
|
19777
|
-
}
|
|
19778
|
-
|
|
19779
|
-
// src/lib/screenshot.ts
|
|
19780
|
-
init_schema();
|
|
19781
|
-
function getScreenshotDir(projectId) {
|
|
19782
|
-
const base = join10(getDataDir2(), "screenshots");
|
|
19783
|
-
const date = new Date().toISOString().split("T")[0];
|
|
19784
|
-
const dir = projectId ? join10(base, projectId, date) : join10(base, date);
|
|
19785
|
-
mkdirSync7(dir, { recursive: true });
|
|
19786
|
-
return dir;
|
|
19787
|
-
}
|
|
19788
|
-
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
19789
|
-
let pipeline = import_sharp.default(raw).resize({ width: maxWidth, withoutEnlargement: true });
|
|
19790
|
-
switch (format) {
|
|
19791
|
-
case "webp":
|
|
19792
|
-
return pipeline.webp({ quality, effort: 4 }).toBuffer();
|
|
19793
|
-
case "jpeg":
|
|
19794
|
-
return pipeline.jpeg({ quality, mozjpeg: true }).toBuffer();
|
|
19795
|
-
case "png":
|
|
19796
|
-
return pipeline.png({ compressionLevel: 9 }).toBuffer();
|
|
19797
|
-
}
|
|
19798
|
-
}
|
|
19799
|
-
async function generateThumbnail(raw, dir, stem) {
|
|
19800
|
-
const thumbPath = join10(dir, `${stem}.thumb.webp`);
|
|
19801
|
-
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
19802
|
-
await Bun.write(thumbPath, thumbBuffer);
|
|
19803
|
-
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
19804
|
-
}
|
|
19805
|
-
async function takeScreenshot(page, opts) {
|
|
19806
|
-
try {
|
|
19807
|
-
const dir = getScreenshotDir(opts?.projectId);
|
|
19808
|
-
const timestamp = Date.now();
|
|
19809
|
-
const format = opts?.format ?? "webp";
|
|
19810
|
-
const compress = opts?.compress ?? true;
|
|
19811
|
-
const maxWidth = opts?.maxWidth ?? 1200;
|
|
19812
|
-
const quality = opts?.quality ?? (format === "webp" ? 80 : format === "jpeg" ? 70 : undefined);
|
|
19813
|
-
const stem = String(timestamp);
|
|
19814
|
-
const rawOpts = {
|
|
19815
|
-
fullPage: opts?.fullPage ?? false,
|
|
19816
|
-
type: "png"
|
|
19817
|
-
};
|
|
19818
|
-
let rawBuffer;
|
|
19819
|
-
const isBunView = typeof page.getNativeView === "function";
|
|
19820
|
-
if (opts?.selector) {
|
|
19821
|
-
if (isBunView) {
|
|
19822
|
-
const uint8 = await page.screenshot();
|
|
19823
|
-
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
19824
|
-
} else {
|
|
19825
|
-
const el = await page.$(opts.selector);
|
|
19826
|
-
if (!el)
|
|
19827
|
-
throw new BrowserError(`Element not found: ${opts.selector}`, "ELEMENT_NOT_FOUND");
|
|
19828
|
-
rawBuffer = await el.screenshot(rawOpts);
|
|
19829
|
-
}
|
|
19830
|
-
} else if (isBunView) {
|
|
19831
|
-
const uint8 = await page.screenshot();
|
|
19832
|
-
rawBuffer = Buffer.from(uint8 instanceof Uint8Array ? uint8 : await uint8);
|
|
19833
|
-
} else {
|
|
19834
|
-
rawBuffer = await page.screenshot(rawOpts);
|
|
19835
|
-
}
|
|
19836
|
-
const originalSizeBytes = rawBuffer.length;
|
|
19837
|
-
const MAX_SIZE_BYTES = 500 * 1024;
|
|
19838
|
-
let finalBuffer;
|
|
19839
|
-
let compressed = true;
|
|
19840
|
-
let fallback = false;
|
|
19841
|
-
try {
|
|
19842
|
-
if (compress && format !== "png") {
|
|
19843
|
-
finalBuffer = await compressBuffer(rawBuffer, format, quality ?? 70, maxWidth);
|
|
19844
|
-
if (finalBuffer.length > MAX_SIZE_BYTES) {
|
|
19845
|
-
const reducedFormat = format === "webp" ? "webp" : "jpeg";
|
|
19846
|
-
for (const q of [60, 50, 40, 30]) {
|
|
19847
|
-
const attempt = await compressBuffer(rawBuffer, reducedFormat, q, maxWidth);
|
|
19848
|
-
if (attempt.length <= MAX_SIZE_BYTES) {
|
|
19849
|
-
finalBuffer = attempt;
|
|
19850
|
-
break;
|
|
19851
|
-
}
|
|
19852
|
-
}
|
|
19853
|
-
}
|
|
19854
|
-
} else if (compress && format === "png") {
|
|
19855
|
-
finalBuffer = await compressBuffer(rawBuffer, "png", quality ?? 9, maxWidth);
|
|
19856
|
-
} else {
|
|
19857
|
-
finalBuffer = rawBuffer;
|
|
19858
|
-
compressed = false;
|
|
19859
|
-
}
|
|
19860
|
-
} catch (sharpErr) {
|
|
19861
|
-
fallback = true;
|
|
19862
|
-
compressed = false;
|
|
19863
|
-
finalBuffer = rawBuffer;
|
|
19864
|
-
}
|
|
19865
|
-
const compressedSizeBytes = finalBuffer.length;
|
|
19866
|
-
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
19867
|
-
const ext = format;
|
|
19868
|
-
const screenshotPath = opts?.path ?? join10(dir, `${stem}.${ext}`);
|
|
19869
|
-
await Bun.write(screenshotPath, finalBuffer);
|
|
19870
|
-
let thumbnailPath;
|
|
19871
|
-
let thumbnailBase64;
|
|
19872
|
-
if (opts?.thumbnail !== false) {
|
|
19873
|
-
const thumb = await generateThumbnail(rawBuffer, dir, stem);
|
|
19874
|
-
thumbnailPath = thumb.path;
|
|
19875
|
-
thumbnailBase64 = thumb.base64;
|
|
19876
|
-
}
|
|
19877
|
-
const meta = await import_sharp.default(finalBuffer).metadata();
|
|
19878
|
-
const width = meta.width ?? (page.viewportSize()?.width ?? 1280);
|
|
19879
|
-
const height = meta.height ?? (page.viewportSize()?.height ?? 720);
|
|
19880
|
-
const result = {
|
|
19881
|
-
path: screenshotPath,
|
|
19882
|
-
base64: finalBuffer.toString("base64"),
|
|
19883
|
-
width,
|
|
19884
|
-
height,
|
|
19885
|
-
size_bytes: compressedSizeBytes,
|
|
19886
|
-
original_size_bytes: originalSizeBytes,
|
|
19887
|
-
compressed_size_bytes: compressedSizeBytes,
|
|
19888
|
-
compression_ratio: compressionRatio,
|
|
19889
|
-
thumbnail_path: thumbnailPath,
|
|
19890
|
-
thumbnail_base64: thumbnailBase64,
|
|
19891
|
-
...fallback ? { fallback: true, compressed: false } : {}
|
|
19892
|
-
};
|
|
19893
|
-
if (opts?.track !== false) {
|
|
19894
|
-
try {
|
|
19895
|
-
const url = await page.url().valueOf();
|
|
19896
|
-
let title;
|
|
19897
|
-
try {
|
|
19898
|
-
title = await page.title();
|
|
19899
|
-
} catch {}
|
|
19900
|
-
const entry = createEntry({
|
|
19901
|
-
session_id: opts?.sessionId,
|
|
19902
|
-
project_id: opts?.projectId,
|
|
19903
|
-
url,
|
|
19904
|
-
title,
|
|
19905
|
-
path: screenshotPath,
|
|
19906
|
-
thumbnail_path: thumbnailPath,
|
|
19907
|
-
format: ext,
|
|
19908
|
-
width,
|
|
19909
|
-
height,
|
|
19910
|
-
original_size_bytes: originalSizeBytes,
|
|
19911
|
-
compressed_size_bytes: compressedSizeBytes,
|
|
19912
|
-
compression_ratio: compressionRatio,
|
|
19913
|
-
tags: [],
|
|
19914
|
-
is_favorite: false
|
|
19915
|
-
});
|
|
19916
|
-
result.gallery_id = entry.id;
|
|
19917
|
-
} catch {}
|
|
19918
|
-
}
|
|
19919
|
-
return result;
|
|
19920
|
-
} catch (err) {
|
|
19921
|
-
if (err instanceof BrowserError)
|
|
19922
|
-
throw err;
|
|
19923
|
-
throw new BrowserError(`Screenshot failed: ${err instanceof Error ? err.message : String(err)}`, "SCREENSHOT_FAILED");
|
|
19924
|
-
}
|
|
19925
|
-
}
|
|
19926
|
-
async function generatePDF(page, opts) {
|
|
19927
|
-
try {
|
|
19928
|
-
const base = join10(getDataDir2(), "pdfs");
|
|
19929
|
-
const date = new Date().toISOString().split("T")[0];
|
|
19930
|
-
const dir = opts?.projectId ? join10(base, opts.projectId, date) : join10(base, date);
|
|
19931
|
-
mkdirSync7(dir, { recursive: true });
|
|
19932
|
-
const timestamp = Date.now();
|
|
19933
|
-
const pdfPath = opts?.path ?? join10(dir, `${timestamp}.pdf`);
|
|
19934
|
-
const buffer = await page.pdf({
|
|
19935
|
-
path: pdfPath,
|
|
19936
|
-
format: opts?.format ?? "A4",
|
|
19937
|
-
landscape: opts?.landscape ?? false,
|
|
19938
|
-
margin: opts?.margin,
|
|
19939
|
-
printBackground: opts?.printBackground ?? true
|
|
19940
|
-
});
|
|
19941
|
-
return {
|
|
19942
|
-
path: pdfPath,
|
|
19943
|
-
base64: Buffer.from(buffer).toString("base64"),
|
|
19944
|
-
size_bytes: buffer.length
|
|
19945
|
-
};
|
|
19946
|
-
} catch (err) {
|
|
19947
|
-
throw new BrowserError(`PDF generation failed: ${err instanceof Error ? err.message : String(err)}`, "PDF_FAILED");
|
|
19948
|
-
}
|
|
19949
|
-
}
|
|
19950
20128
|
// src/lib/storage.ts
|
|
19951
20129
|
async function getCookies(page, filter) {
|
|
19952
20130
|
const cookies = await page.context().cookies();
|
|
@@ -20404,6 +20582,7 @@ export {
|
|
|
20404
20582
|
createRecording,
|
|
20405
20583
|
createProject,
|
|
20406
20584
|
createCrawlResult,
|
|
20585
|
+
createBrowserSDK,
|
|
20407
20586
|
crawl,
|
|
20408
20587
|
countActiveSessions2 as countActiveSessions,
|
|
20409
20588
|
connectToExistingBrowser,
|
|
@@ -20438,6 +20617,7 @@ export {
|
|
|
20438
20617
|
EngineNotAvailableError,
|
|
20439
20618
|
ElementNotFoundError,
|
|
20440
20619
|
CDPClient,
|
|
20620
|
+
BrowserSDK,
|
|
20441
20621
|
BrowserPool,
|
|
20442
20622
|
BrowserError,
|
|
20443
20623
|
AgentNotFoundError
|