@hasna/browser 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1410 -307
- package/dist/index.js +387 -150
- package/dist/lib/actions-ref.test.d.ts +2 -0
- package/dist/lib/actions-ref.test.d.ts.map +1 -0
- package/dist/lib/actions.d.ts +12 -0
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/annotate.d.ts +18 -0
- package/dist/lib/annotate.d.ts.map +1 -0
- package/dist/lib/annotate.test.d.ts +2 -0
- package/dist/lib/annotate.test.d.ts.map +1 -0
- package/dist/lib/dialogs.d.ts +15 -0
- package/dist/lib/dialogs.d.ts.map +1 -0
- package/dist/lib/profiles.d.ts +23 -0
- package/dist/lib/profiles.d.ts.map +1 -0
- package/dist/lib/session-v3.test.d.ts +2 -0
- package/dist/lib/session-v3.test.d.ts.map +1 -0
- package/dist/lib/session.d.ts +5 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/snapshot-diff.test.d.ts +2 -0
- package/dist/lib/snapshot-diff.test.d.ts.map +1 -0
- package/dist/lib/snapshot.d.ts +33 -0
- package/dist/lib/snapshot.d.ts.map +1 -0
- package/dist/lib/snapshot.test.d.ts +2 -0
- package/dist/lib/snapshot.test.d.ts.map +1 -0
- package/dist/lib/stealth.d.ts +5 -0
- package/dist/lib/stealth.d.ts.map +1 -0
- package/dist/lib/stealth.test.d.ts +2 -0
- package/dist/lib/stealth.test.d.ts.map +1 -0
- package/dist/lib/tabs.d.ts +18 -0
- package/dist/lib/tabs.d.ts.map +1 -0
- package/dist/mcp/index.js +1452 -359
- package/dist/server/index.js +309 -166
- package/dist/types/index.d.ts +35 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2525,6 +2525,336 @@ var init_selector = __esm(() => {
|
|
|
2525
2525
|
};
|
|
2526
2526
|
});
|
|
2527
2527
|
|
|
2528
|
+
// src/db/network-log.ts
|
|
2529
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2530
|
+
function logRequest(data) {
|
|
2531
|
+
const db = getDatabase();
|
|
2532
|
+
const id = randomUUID2();
|
|
2533
|
+
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
2534
|
+
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
2535
|
+
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);
|
|
2536
|
+
return getNetworkRequest(id);
|
|
2537
|
+
}
|
|
2538
|
+
function getNetworkRequest(id) {
|
|
2539
|
+
const db = getDatabase();
|
|
2540
|
+
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
2541
|
+
}
|
|
2542
|
+
function getNetworkLog(sessionId) {
|
|
2543
|
+
const db = getDatabase();
|
|
2544
|
+
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
2545
|
+
}
|
|
2546
|
+
function clearNetworkLog(sessionId) {
|
|
2547
|
+
const db = getDatabase();
|
|
2548
|
+
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
2549
|
+
}
|
|
2550
|
+
var init_network_log = __esm(() => {
|
|
2551
|
+
init_schema();
|
|
2552
|
+
});
|
|
2553
|
+
|
|
2554
|
+
// src/lib/network.ts
|
|
2555
|
+
function enableNetworkLogging(page, sessionId) {
|
|
2556
|
+
const requestStart = new Map;
|
|
2557
|
+
const onRequest = (req) => {
|
|
2558
|
+
requestStart.set(req.url(), Date.now());
|
|
2559
|
+
};
|
|
2560
|
+
const onResponse = (res) => {
|
|
2561
|
+
const start = requestStart.get(res.url()) ?? Date.now();
|
|
2562
|
+
const duration = Date.now() - start;
|
|
2563
|
+
const req = res.request();
|
|
2564
|
+
try {
|
|
2565
|
+
logRequest({
|
|
2566
|
+
session_id: sessionId,
|
|
2567
|
+
method: req.method(),
|
|
2568
|
+
url: res.url(),
|
|
2569
|
+
status_code: res.status(),
|
|
2570
|
+
request_headers: JSON.stringify(req.headers()),
|
|
2571
|
+
response_headers: JSON.stringify(res.headers()),
|
|
2572
|
+
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
2573
|
+
duration_ms: duration,
|
|
2574
|
+
resource_type: req.resourceType()
|
|
2575
|
+
});
|
|
2576
|
+
} catch {}
|
|
2577
|
+
};
|
|
2578
|
+
page.on("request", onRequest);
|
|
2579
|
+
page.on("response", onResponse);
|
|
2580
|
+
return () => {
|
|
2581
|
+
page.off("request", onRequest);
|
|
2582
|
+
page.off("response", onResponse);
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
async function addInterceptRule(page, rule) {
|
|
2586
|
+
await page.route(rule.pattern, async (route) => {
|
|
2587
|
+
if (rule.action === "block") {
|
|
2588
|
+
await route.abort();
|
|
2589
|
+
} else if (rule.action === "modify" && rule.response) {
|
|
2590
|
+
await route.fulfill({
|
|
2591
|
+
status: rule.response.status,
|
|
2592
|
+
body: rule.response.body,
|
|
2593
|
+
headers: rule.response.headers
|
|
2594
|
+
});
|
|
2595
|
+
} else {
|
|
2596
|
+
await route.continue();
|
|
2597
|
+
}
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
function startHAR(page) {
|
|
2601
|
+
const entries = [];
|
|
2602
|
+
const requestStart = new Map;
|
|
2603
|
+
const onRequest = (req) => {
|
|
2604
|
+
requestStart.set(req.url() + req.method(), {
|
|
2605
|
+
time: Date.now(),
|
|
2606
|
+
method: req.method(),
|
|
2607
|
+
headers: req.headers(),
|
|
2608
|
+
postData: req.postData() ?? undefined
|
|
2609
|
+
});
|
|
2610
|
+
};
|
|
2611
|
+
const onResponse = async (res) => {
|
|
2612
|
+
const key = res.url() + res.request().method();
|
|
2613
|
+
const start = requestStart.get(key);
|
|
2614
|
+
if (!start)
|
|
2615
|
+
return;
|
|
2616
|
+
const duration = Date.now() - start.time;
|
|
2617
|
+
const entry = {
|
|
2618
|
+
startedDateTime: new Date(start.time).toISOString(),
|
|
2619
|
+
time: duration,
|
|
2620
|
+
request: {
|
|
2621
|
+
method: start.method,
|
|
2622
|
+
url: res.url(),
|
|
2623
|
+
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
2624
|
+
postData: start.postData ? { text: start.postData } : undefined
|
|
2625
|
+
},
|
|
2626
|
+
response: {
|
|
2627
|
+
status: res.status(),
|
|
2628
|
+
statusText: res.statusText(),
|
|
2629
|
+
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
2630
|
+
content: {
|
|
2631
|
+
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
2632
|
+
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
2633
|
+
}
|
|
2634
|
+
},
|
|
2635
|
+
timings: { send: 0, wait: duration, receive: 0 }
|
|
2636
|
+
};
|
|
2637
|
+
entries.push(entry);
|
|
2638
|
+
requestStart.delete(key);
|
|
2639
|
+
};
|
|
2640
|
+
page.on("request", onRequest);
|
|
2641
|
+
page.on("response", onResponse);
|
|
2642
|
+
return {
|
|
2643
|
+
entries,
|
|
2644
|
+
stop: () => {
|
|
2645
|
+
page.off("request", onRequest);
|
|
2646
|
+
page.off("response", onResponse);
|
|
2647
|
+
return {
|
|
2648
|
+
log: {
|
|
2649
|
+
version: "1.2",
|
|
2650
|
+
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
2651
|
+
entries
|
|
2652
|
+
}
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
var init_network = __esm(() => {
|
|
2658
|
+
init_network_log();
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
// src/db/console-log.ts
|
|
2662
|
+
var exports_console_log = {};
|
|
2663
|
+
__export(exports_console_log, {
|
|
2664
|
+
logConsoleMessage: () => logConsoleMessage,
|
|
2665
|
+
getConsoleMessage: () => getConsoleMessage,
|
|
2666
|
+
getConsoleLog: () => getConsoleLog,
|
|
2667
|
+
clearConsoleLog: () => clearConsoleLog
|
|
2668
|
+
});
|
|
2669
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
2670
|
+
function logConsoleMessage(data) {
|
|
2671
|
+
const db = getDatabase();
|
|
2672
|
+
const id = randomUUID3();
|
|
2673
|
+
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);
|
|
2674
|
+
return getConsoleMessage(id);
|
|
2675
|
+
}
|
|
2676
|
+
function getConsoleMessage(id) {
|
|
2677
|
+
const db = getDatabase();
|
|
2678
|
+
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
2679
|
+
}
|
|
2680
|
+
function getConsoleLog(sessionId, level) {
|
|
2681
|
+
const db = getDatabase();
|
|
2682
|
+
if (level) {
|
|
2683
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
2684
|
+
}
|
|
2685
|
+
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
2686
|
+
}
|
|
2687
|
+
function clearConsoleLog(sessionId) {
|
|
2688
|
+
const db = getDatabase();
|
|
2689
|
+
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
2690
|
+
}
|
|
2691
|
+
var init_console_log = __esm(() => {
|
|
2692
|
+
init_schema();
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
// src/lib/console.ts
|
|
2696
|
+
function enableConsoleCapture(page, sessionId) {
|
|
2697
|
+
const onConsole = (msg) => {
|
|
2698
|
+
const levelMap = {
|
|
2699
|
+
log: "log",
|
|
2700
|
+
warn: "warn",
|
|
2701
|
+
error: "error",
|
|
2702
|
+
debug: "debug",
|
|
2703
|
+
info: "info",
|
|
2704
|
+
warning: "warn"
|
|
2705
|
+
};
|
|
2706
|
+
const level = levelMap[msg.type()] ?? "log";
|
|
2707
|
+
const location = msg.location();
|
|
2708
|
+
try {
|
|
2709
|
+
logConsoleMessage({
|
|
2710
|
+
session_id: sessionId,
|
|
2711
|
+
level,
|
|
2712
|
+
message: msg.text(),
|
|
2713
|
+
source: location.url || undefined,
|
|
2714
|
+
line_number: location.lineNumber || undefined
|
|
2715
|
+
});
|
|
2716
|
+
} catch {}
|
|
2717
|
+
};
|
|
2718
|
+
page.on("console", onConsole);
|
|
2719
|
+
return () => page.off("console", onConsole);
|
|
2720
|
+
}
|
|
2721
|
+
var init_console = __esm(() => {
|
|
2722
|
+
init_console_log();
|
|
2723
|
+
});
|
|
2724
|
+
|
|
2725
|
+
// src/lib/stealth.ts
|
|
2726
|
+
async function applyStealthPatches(page) {
|
|
2727
|
+
await page.context().addInitScript(STEALTH_SCRIPT);
|
|
2728
|
+
await page.context().setExtraHTTPHeaders({
|
|
2729
|
+
"User-Agent": REALISTIC_USER_AGENT
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", STEALTH_SCRIPT = `
|
|
2733
|
+
// \u2500\u2500 1. Remove navigator.webdriver flag \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2734
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
2735
|
+
get: () => false,
|
|
2736
|
+
configurable: true,
|
|
2737
|
+
});
|
|
2738
|
+
|
|
2739
|
+
// \u2500\u2500 2. Override navigator.plugins to show typical Chrome plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2740
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
2741
|
+
get: () => {
|
|
2742
|
+
const plugins = [
|
|
2743
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
|
|
2744
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
|
|
2745
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
|
|
2746
|
+
];
|
|
2747
|
+
// Mimic PluginArray interface
|
|
2748
|
+
const pluginArray = Object.create(PluginArray.prototype);
|
|
2749
|
+
plugins.forEach((p, i) => {
|
|
2750
|
+
const plugin = Object.create(Plugin.prototype);
|
|
2751
|
+
Object.defineProperties(plugin, {
|
|
2752
|
+
name: { value: p.name, enumerable: true },
|
|
2753
|
+
filename: { value: p.filename, enumerable: true },
|
|
2754
|
+
description: { value: p.description, enumerable: true },
|
|
2755
|
+
length: { value: p.length, enumerable: true },
|
|
2756
|
+
});
|
|
2757
|
+
pluginArray[i] = plugin;
|
|
2758
|
+
});
|
|
2759
|
+
Object.defineProperty(pluginArray, 'length', { value: plugins.length });
|
|
2760
|
+
pluginArray.item = (i) => pluginArray[i] || null;
|
|
2761
|
+
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
|
|
2762
|
+
pluginArray.refresh = () => {};
|
|
2763
|
+
return pluginArray;
|
|
2764
|
+
},
|
|
2765
|
+
configurable: true,
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
// \u2500\u2500 3. Override navigator.languages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2769
|
+
Object.defineProperty(navigator, 'languages', {
|
|
2770
|
+
get: () => ['en-US', 'en'],
|
|
2771
|
+
configurable: true,
|
|
2772
|
+
});
|
|
2773
|
+
|
|
2774
|
+
// \u2500\u2500 4. Override chrome.runtime to appear like real Chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2775
|
+
if (!window.chrome) {
|
|
2776
|
+
window.chrome = {};
|
|
2777
|
+
}
|
|
2778
|
+
if (!window.chrome.runtime) {
|
|
2779
|
+
window.chrome.runtime = {
|
|
2780
|
+
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {} }; },
|
|
2781
|
+
sendMessage: function() {},
|
|
2782
|
+
onMessage: { addListener: function() {}, removeListener: function() {} },
|
|
2783
|
+
id: undefined,
|
|
2784
|
+
};
|
|
2785
|
+
}
|
|
2786
|
+
`;
|
|
2787
|
+
var init_stealth = () => {};
|
|
2788
|
+
|
|
2789
|
+
// src/lib/dialogs.ts
|
|
2790
|
+
function setupDialogHandler(page, sessionId) {
|
|
2791
|
+
const onDialog = (dialog) => {
|
|
2792
|
+
const info = {
|
|
2793
|
+
type: dialog.type(),
|
|
2794
|
+
message: dialog.message(),
|
|
2795
|
+
default_value: dialog.defaultValue(),
|
|
2796
|
+
timestamp: new Date().toISOString()
|
|
2797
|
+
};
|
|
2798
|
+
const autoTimer = setTimeout(() => {
|
|
2799
|
+
try {
|
|
2800
|
+
dialog.dismiss().catch(() => {});
|
|
2801
|
+
} catch {}
|
|
2802
|
+
const list = pendingDialogs.get(sessionId);
|
|
2803
|
+
if (list) {
|
|
2804
|
+
const idx = list.findIndex((p) => p.dialog === dialog);
|
|
2805
|
+
if (idx >= 0)
|
|
2806
|
+
list.splice(idx, 1);
|
|
2807
|
+
if (list.length === 0)
|
|
2808
|
+
pendingDialogs.delete(sessionId);
|
|
2809
|
+
}
|
|
2810
|
+
}, AUTO_DISMISS_MS);
|
|
2811
|
+
const pending = { dialog, info, autoTimer };
|
|
2812
|
+
if (!pendingDialogs.has(sessionId)) {
|
|
2813
|
+
pendingDialogs.set(sessionId, []);
|
|
2814
|
+
}
|
|
2815
|
+
pendingDialogs.get(sessionId).push(pending);
|
|
2816
|
+
};
|
|
2817
|
+
page.on("dialog", onDialog);
|
|
2818
|
+
return () => {
|
|
2819
|
+
page.off("dialog", onDialog);
|
|
2820
|
+
const list = pendingDialogs.get(sessionId);
|
|
2821
|
+
if (list) {
|
|
2822
|
+
for (const p of list)
|
|
2823
|
+
clearTimeout(p.autoTimer);
|
|
2824
|
+
pendingDialogs.delete(sessionId);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
function getDialogs(sessionId) {
|
|
2829
|
+
const list = pendingDialogs.get(sessionId);
|
|
2830
|
+
if (!list)
|
|
2831
|
+
return [];
|
|
2832
|
+
return list.map((p) => p.info);
|
|
2833
|
+
}
|
|
2834
|
+
async function handleDialog(sessionId, action, promptText) {
|
|
2835
|
+
const list = pendingDialogs.get(sessionId);
|
|
2836
|
+
if (!list || list.length === 0) {
|
|
2837
|
+
return { handled: false };
|
|
2838
|
+
}
|
|
2839
|
+
const pending = list.shift();
|
|
2840
|
+
clearTimeout(pending.autoTimer);
|
|
2841
|
+
if (list.length === 0) {
|
|
2842
|
+
pendingDialogs.delete(sessionId);
|
|
2843
|
+
}
|
|
2844
|
+
try {
|
|
2845
|
+
if (action === "accept") {
|
|
2846
|
+
await pending.dialog.accept(promptText);
|
|
2847
|
+
} else {
|
|
2848
|
+
await pending.dialog.dismiss();
|
|
2849
|
+
}
|
|
2850
|
+
} catch {}
|
|
2851
|
+
return { handled: true, dialog: pending.info };
|
|
2852
|
+
}
|
|
2853
|
+
var pendingDialogs, AUTO_DISMISS_MS = 5000;
|
|
2854
|
+
var init_dialogs = __esm(() => {
|
|
2855
|
+
pendingDialogs = new Map;
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2528
2858
|
// src/lib/session.ts
|
|
2529
2859
|
async function createSession2(opts = {}) {
|
|
2530
2860
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
@@ -2550,13 +2880,33 @@ async function createSession2(opts = {}) {
|
|
|
2550
2880
|
engine: resolvedEngine,
|
|
2551
2881
|
projectId: opts.projectId,
|
|
2552
2882
|
agentId: opts.agentId,
|
|
2553
|
-
startUrl: opts.startUrl
|
|
2883
|
+
startUrl: opts.startUrl,
|
|
2884
|
+
name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
|
|
2554
2885
|
});
|
|
2555
|
-
|
|
2886
|
+
if (opts.stealth) {
|
|
2887
|
+
try {
|
|
2888
|
+
await applyStealthPatches(page);
|
|
2889
|
+
} catch {}
|
|
2890
|
+
}
|
|
2891
|
+
const cleanups = [];
|
|
2892
|
+
if (opts.captureNetwork !== false) {
|
|
2893
|
+
try {
|
|
2894
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
2895
|
+
} catch {}
|
|
2896
|
+
}
|
|
2897
|
+
if (opts.captureConsole !== false) {
|
|
2898
|
+
try {
|
|
2899
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
2900
|
+
} catch {}
|
|
2901
|
+
}
|
|
2902
|
+
try {
|
|
2903
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
2904
|
+
} catch {}
|
|
2905
|
+
handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
2556
2906
|
if (opts.startUrl) {
|
|
2557
2907
|
try {
|
|
2558
2908
|
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
2559
|
-
} catch
|
|
2909
|
+
} catch {}
|
|
2560
2910
|
}
|
|
2561
2911
|
return { session, page };
|
|
2562
2912
|
}
|
|
@@ -2564,11 +2914,28 @@ function getSessionPage(sessionId) {
|
|
|
2564
2914
|
const handle = handles.get(sessionId);
|
|
2565
2915
|
if (!handle)
|
|
2566
2916
|
throw new SessionNotFoundError(sessionId);
|
|
2917
|
+
try {
|
|
2918
|
+
handle.page.url();
|
|
2919
|
+
} catch {
|
|
2920
|
+
handles.delete(sessionId);
|
|
2921
|
+
throw new SessionNotFoundError(sessionId);
|
|
2922
|
+
}
|
|
2567
2923
|
return handle.page;
|
|
2568
2924
|
}
|
|
2925
|
+
function setSessionPage(sessionId, page) {
|
|
2926
|
+
const handle = handles.get(sessionId);
|
|
2927
|
+
if (!handle)
|
|
2928
|
+
throw new SessionNotFoundError(sessionId);
|
|
2929
|
+
handle.page = page;
|
|
2930
|
+
}
|
|
2569
2931
|
async function closeSession2(sessionId) {
|
|
2570
2932
|
const handle = handles.get(sessionId);
|
|
2571
2933
|
if (handle) {
|
|
2934
|
+
for (const cleanup of handle.cleanups) {
|
|
2935
|
+
try {
|
|
2936
|
+
cleanup();
|
|
2937
|
+
} catch {}
|
|
2938
|
+
}
|
|
2572
2939
|
try {
|
|
2573
2940
|
await handle.page.context().close();
|
|
2574
2941
|
} catch {}
|
|
@@ -2579,6 +2946,9 @@ async function closeSession2(sessionId) {
|
|
|
2579
2946
|
}
|
|
2580
2947
|
return closeSession(sessionId);
|
|
2581
2948
|
}
|
|
2949
|
+
function getSession2(sessionId) {
|
|
2950
|
+
return getSession(sessionId);
|
|
2951
|
+
}
|
|
2582
2952
|
function listSessions2(filter) {
|
|
2583
2953
|
return listSessions(filter);
|
|
2584
2954
|
}
|
|
@@ -2588,6 +2958,10 @@ function getSessionByName2(name) {
|
|
|
2588
2958
|
function renameSession2(id, name) {
|
|
2589
2959
|
return renameSession(id, name);
|
|
2590
2960
|
}
|
|
2961
|
+
function getTokenBudget(sessionId) {
|
|
2962
|
+
const handle = handles.get(sessionId);
|
|
2963
|
+
return handle ? handle.tokenBudget : null;
|
|
2964
|
+
}
|
|
2591
2965
|
var handles;
|
|
2592
2966
|
var init_session = __esm(() => {
|
|
2593
2967
|
init_types();
|
|
@@ -2596,9 +2970,186 @@ var init_session = __esm(() => {
|
|
|
2596
2970
|
init_playwright();
|
|
2597
2971
|
init_lightpanda();
|
|
2598
2972
|
init_selector();
|
|
2973
|
+
init_network();
|
|
2974
|
+
init_console();
|
|
2975
|
+
init_stealth();
|
|
2976
|
+
init_dialogs();
|
|
2599
2977
|
handles = new Map;
|
|
2600
2978
|
});
|
|
2601
2979
|
|
|
2980
|
+
// src/lib/snapshot.ts
|
|
2981
|
+
function getLastSnapshot(sessionId) {
|
|
2982
|
+
return lastSnapshots.get(sessionId) ?? null;
|
|
2983
|
+
}
|
|
2984
|
+
function setLastSnapshot(sessionId, snapshot) {
|
|
2985
|
+
lastSnapshots.set(sessionId, snapshot);
|
|
2986
|
+
}
|
|
2987
|
+
async function takeSnapshot(page, sessionId) {
|
|
2988
|
+
let ariaTree;
|
|
2989
|
+
try {
|
|
2990
|
+
ariaTree = await page.locator("body").ariaSnapshot();
|
|
2991
|
+
} catch {
|
|
2992
|
+
ariaTree = "";
|
|
2993
|
+
}
|
|
2994
|
+
const refs = {};
|
|
2995
|
+
const refMap = new Map;
|
|
2996
|
+
let refCounter = 0;
|
|
2997
|
+
for (const role of INTERACTIVE_ROLES) {
|
|
2998
|
+
const locators = page.getByRole(role);
|
|
2999
|
+
const count = await locators.count();
|
|
3000
|
+
for (let i = 0;i < count; i++) {
|
|
3001
|
+
const el = locators.nth(i);
|
|
3002
|
+
let name = "";
|
|
3003
|
+
let visible = false;
|
|
3004
|
+
let enabled = true;
|
|
3005
|
+
let value;
|
|
3006
|
+
let checked;
|
|
3007
|
+
try {
|
|
3008
|
+
visible = await el.isVisible();
|
|
3009
|
+
if (!visible)
|
|
3010
|
+
continue;
|
|
3011
|
+
} catch {
|
|
3012
|
+
continue;
|
|
3013
|
+
}
|
|
3014
|
+
try {
|
|
3015
|
+
name = await el.evaluate((e) => {
|
|
3016
|
+
const el2 = e;
|
|
3017
|
+
return el2.getAttribute("aria-label") ?? el2.textContent?.trim().slice(0, 80) ?? el2.getAttribute("title") ?? el2.getAttribute("placeholder") ?? "";
|
|
3018
|
+
});
|
|
3019
|
+
} catch {
|
|
3020
|
+
continue;
|
|
3021
|
+
}
|
|
3022
|
+
if (!name)
|
|
3023
|
+
continue;
|
|
3024
|
+
try {
|
|
3025
|
+
enabled = await el.isEnabled();
|
|
3026
|
+
} catch {}
|
|
3027
|
+
try {
|
|
3028
|
+
if (role === "checkbox" || role === "radio" || role === "switch") {
|
|
3029
|
+
checked = await el.isChecked();
|
|
3030
|
+
}
|
|
3031
|
+
} catch {}
|
|
3032
|
+
try {
|
|
3033
|
+
if (role === "textbox" || role === "searchbox" || role === "spinbutton" || role === "combobox") {
|
|
3034
|
+
value = await el.inputValue();
|
|
3035
|
+
}
|
|
3036
|
+
} catch {}
|
|
3037
|
+
const ref = `@e${refCounter}`;
|
|
3038
|
+
refCounter++;
|
|
3039
|
+
refs[ref] = { role, name, visible, enabled, value, checked };
|
|
3040
|
+
const escapedName = name.replace(/"/g, "\\\"");
|
|
3041
|
+
refMap.set(ref, { role, name, locatorSelector: `role=${role}[name="${escapedName}"]` });
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
let annotatedTree = ariaTree;
|
|
3045
|
+
for (const [ref, info] of Object.entries(refs)) {
|
|
3046
|
+
const escapedName = info.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3047
|
+
const pattern = new RegExp(`(${info.role}\\s+"${escapedName.slice(0, 40)}[^"]*")`, "m");
|
|
3048
|
+
const match = annotatedTree.match(pattern);
|
|
3049
|
+
if (match) {
|
|
3050
|
+
annotatedTree = annotatedTree.replace(match[0], `${match[0]} [${ref}]`);
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
const unmatchedRefs = Object.entries(refs).filter(([ref]) => !annotatedTree.includes(`[${ref}]`));
|
|
3054
|
+
if (unmatchedRefs.length > 0) {
|
|
3055
|
+
annotatedTree += `
|
|
3056
|
+
|
|
3057
|
+
--- Interactive elements ---`;
|
|
3058
|
+
for (const [ref, info] of unmatchedRefs) {
|
|
3059
|
+
const extras = [];
|
|
3060
|
+
if (info.checked !== undefined)
|
|
3061
|
+
extras.push(`checked=${info.checked}`);
|
|
3062
|
+
if (!info.enabled)
|
|
3063
|
+
extras.push("disabled");
|
|
3064
|
+
if (info.value)
|
|
3065
|
+
extras.push(`value="${info.value}"`);
|
|
3066
|
+
const extrasStr = extras.length ? ` (${extras.join(", ")})` : "";
|
|
3067
|
+
annotatedTree += `
|
|
3068
|
+
${info.role} "${info.name}" [${ref}]${extrasStr}`;
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
if (sessionId) {
|
|
3072
|
+
sessionRefMaps.set(sessionId, refMap);
|
|
3073
|
+
}
|
|
3074
|
+
return {
|
|
3075
|
+
tree: annotatedTree,
|
|
3076
|
+
refs,
|
|
3077
|
+
interactive_count: refCounter
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
function getRefLocator(page, sessionId, ref) {
|
|
3081
|
+
const refMap = sessionRefMaps.get(sessionId);
|
|
3082
|
+
if (!refMap)
|
|
3083
|
+
throw new Error(`No snapshot taken for session ${sessionId}. Call browser_snapshot first.`);
|
|
3084
|
+
const entry = refMap.get(ref);
|
|
3085
|
+
if (!entry)
|
|
3086
|
+
throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
|
|
3087
|
+
return page.getByRole(entry.role, { name: entry.name }).first();
|
|
3088
|
+
}
|
|
3089
|
+
function refKey(info) {
|
|
3090
|
+
return `${info.role}::${info.name}`;
|
|
3091
|
+
}
|
|
3092
|
+
function diffSnapshots(before, after) {
|
|
3093
|
+
const beforeMap = new Map;
|
|
3094
|
+
for (const [ref, info] of Object.entries(before.refs)) {
|
|
3095
|
+
beforeMap.set(refKey(info), { ref, info });
|
|
3096
|
+
}
|
|
3097
|
+
const afterMap = new Map;
|
|
3098
|
+
for (const [ref, info] of Object.entries(after.refs)) {
|
|
3099
|
+
afterMap.set(refKey(info), { ref, info });
|
|
3100
|
+
}
|
|
3101
|
+
const added = [];
|
|
3102
|
+
const removed = [];
|
|
3103
|
+
const modified = [];
|
|
3104
|
+
for (const [key, afterEntry] of afterMap) {
|
|
3105
|
+
const beforeEntry = beforeMap.get(key);
|
|
3106
|
+
if (!beforeEntry) {
|
|
3107
|
+
added.push({ ref: afterEntry.ref, info: afterEntry.info });
|
|
3108
|
+
} else {
|
|
3109
|
+
const b = beforeEntry.info;
|
|
3110
|
+
const a = afterEntry.info;
|
|
3111
|
+
if (b.visible !== a.visible || b.enabled !== a.enabled || b.value !== a.value || b.checked !== a.checked || b.description !== a.description) {
|
|
3112
|
+
modified.push({ ref: afterEntry.ref, before: b, after: a });
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
for (const [key, beforeEntry] of beforeMap) {
|
|
3117
|
+
if (!afterMap.has(key)) {
|
|
3118
|
+
removed.push({ ref: beforeEntry.ref, info: beforeEntry.info });
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
const url_changed = before.tree.split(`
|
|
3122
|
+
`)[0] !== after.tree.split(`
|
|
3123
|
+
`)[0];
|
|
3124
|
+
const title_changed = before.tree !== after.tree && (added.length > 0 || removed.length > 0 || modified.length > 0);
|
|
3125
|
+
return { added, removed, modified, url_changed, title_changed };
|
|
3126
|
+
}
|
|
3127
|
+
var lastSnapshots, sessionRefMaps, INTERACTIVE_ROLES;
|
|
3128
|
+
var init_snapshot = __esm(() => {
|
|
3129
|
+
lastSnapshots = new Map;
|
|
3130
|
+
sessionRefMaps = new Map;
|
|
3131
|
+
INTERACTIVE_ROLES = [
|
|
3132
|
+
"button",
|
|
3133
|
+
"link",
|
|
3134
|
+
"textbox",
|
|
3135
|
+
"checkbox",
|
|
3136
|
+
"radio",
|
|
3137
|
+
"combobox",
|
|
3138
|
+
"menuitem",
|
|
3139
|
+
"menuitemcheckbox",
|
|
3140
|
+
"menuitemradio",
|
|
3141
|
+
"option",
|
|
3142
|
+
"searchbox",
|
|
3143
|
+
"slider",
|
|
3144
|
+
"spinbutton",
|
|
3145
|
+
"switch",
|
|
3146
|
+
"tab",
|
|
3147
|
+
"treeitem",
|
|
3148
|
+
"listbox",
|
|
3149
|
+
"menu"
|
|
3150
|
+
];
|
|
3151
|
+
});
|
|
3152
|
+
|
|
2602
3153
|
// src/lib/actions.ts
|
|
2603
3154
|
async function click(page, selector, opts) {
|
|
2604
3155
|
try {
|
|
@@ -2818,9 +3369,67 @@ function stopWatch(watchId) {
|
|
|
2818
3369
|
activeWatches.delete(watchId);
|
|
2819
3370
|
}
|
|
2820
3371
|
}
|
|
3372
|
+
async function clickRef(page, sessionId, ref, opts) {
|
|
3373
|
+
try {
|
|
3374
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
3375
|
+
await locator.click({ timeout: opts?.timeout ?? 1e4 });
|
|
3376
|
+
} catch (err) {
|
|
3377
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
3378
|
+
throw new ElementNotFoundError(ref);
|
|
3379
|
+
if (err instanceof Error && err.message.includes("No snapshot"))
|
|
3380
|
+
throw new BrowserError(err.message, "NO_SNAPSHOT");
|
|
3381
|
+
throw new BrowserError(`clickRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CLICK_REF_FAILED");
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
async function typeRef(page, sessionId, ref, text, opts) {
|
|
3385
|
+
try {
|
|
3386
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
3387
|
+
if (opts?.clear)
|
|
3388
|
+
await locator.fill("", { timeout: opts.timeout ?? 1e4 });
|
|
3389
|
+
await locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
3390
|
+
} catch (err) {
|
|
3391
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
3392
|
+
throw new ElementNotFoundError(ref);
|
|
3393
|
+
throw new BrowserError(`typeRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "TYPE_REF_FAILED");
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
async function selectRef(page, sessionId, ref, value, timeout = 1e4) {
|
|
3397
|
+
try {
|
|
3398
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
3399
|
+
return await locator.selectOption(value, { timeout });
|
|
3400
|
+
} catch (err) {
|
|
3401
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
3402
|
+
throw new ElementNotFoundError(ref);
|
|
3403
|
+
throw new BrowserError(`selectRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "SELECT_REF_FAILED");
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
async function checkRef(page, sessionId, ref, checked, timeout = 1e4) {
|
|
3407
|
+
try {
|
|
3408
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
3409
|
+
if (checked)
|
|
3410
|
+
await locator.check({ timeout });
|
|
3411
|
+
else
|
|
3412
|
+
await locator.uncheck({ timeout });
|
|
3413
|
+
} catch (err) {
|
|
3414
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
3415
|
+
throw new ElementNotFoundError(ref);
|
|
3416
|
+
throw new BrowserError(`checkRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CHECK_REF_FAILED");
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
async function hoverRef(page, sessionId, ref, timeout = 1e4) {
|
|
3420
|
+
try {
|
|
3421
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
3422
|
+
await locator.hover({ timeout });
|
|
3423
|
+
} catch (err) {
|
|
3424
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
3425
|
+
throw new ElementNotFoundError(ref);
|
|
3426
|
+
throw new BrowserError(`hoverRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "HOVER_REF_FAILED");
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
2821
3429
|
var RETRYABLE_ERRORS, activeWatches;
|
|
2822
3430
|
var init_actions = __esm(() => {
|
|
2823
3431
|
init_types();
|
|
3432
|
+
init_snapshot();
|
|
2824
3433
|
RETRYABLE_ERRORS = ["Timeout", "timeout", "navigation", "net::ERR", "Target closed"];
|
|
2825
3434
|
activeWatches = new Map;
|
|
2826
3435
|
});
|
|
@@ -2890,24 +3499,6 @@ async function extractTable(page, selector) {
|
|
|
2890
3499
|
return rows.map((row) => Array.from(row.querySelectorAll("th, td")).map((cell) => cell.textContent?.trim() ?? ""));
|
|
2891
3500
|
}, selector);
|
|
2892
3501
|
}
|
|
2893
|
-
async function getAriaSnapshot(page) {
|
|
2894
|
-
try {
|
|
2895
|
-
return await page.ariaSnapshot?.() ?? page.evaluate(() => {
|
|
2896
|
-
function walk(el, indent = 0) {
|
|
2897
|
-
const role = el.getAttribute("role") ?? el.tagName.toLowerCase();
|
|
2898
|
-
const label = el.getAttribute("aria-label") ?? el.getAttribute("aria-labelledby") ?? el.textContent?.trim().slice(0, 50) ?? "";
|
|
2899
|
-
const line = " ".repeat(indent) + `[${role}] ${label}`;
|
|
2900
|
-
const children = Array.from(el.children).map((c) => walk(c, indent + 1)).join(`
|
|
2901
|
-
`);
|
|
2902
|
-
return children ? `${line}
|
|
2903
|
-
${children}` : line;
|
|
2904
|
-
}
|
|
2905
|
-
return walk(document.body);
|
|
2906
|
-
});
|
|
2907
|
-
} catch {
|
|
2908
|
-
return page.evaluate(() => document.body.innerText?.slice(0, 2000) ?? "");
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
3502
|
async function extract(page, opts = {}) {
|
|
2912
3503
|
const result = {};
|
|
2913
3504
|
const format = opts.format ?? "text";
|
|
@@ -9377,7 +9968,7 @@ __export(exports_gallery, {
|
|
|
9377
9968
|
deleteEntry: () => deleteEntry,
|
|
9378
9969
|
createEntry: () => createEntry
|
|
9379
9970
|
});
|
|
9380
|
-
import { randomUUID as
|
|
9971
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
9381
9972
|
function deserialize(row) {
|
|
9382
9973
|
return {
|
|
9383
9974
|
id: row.id,
|
|
@@ -9401,7 +9992,7 @@ function deserialize(row) {
|
|
|
9401
9992
|
}
|
|
9402
9993
|
function createEntry(data) {
|
|
9403
9994
|
const db = getDatabase();
|
|
9404
|
-
const id =
|
|
9995
|
+
const id = randomUUID4();
|
|
9405
9996
|
db.prepare(`
|
|
9406
9997
|
INSERT INTO gallery_entries
|
|
9407
9998
|
(id, session_id, project_id, url, title, path, thumbnail_path, format,
|
|
@@ -9674,7 +10265,7 @@ var init_screenshot = __esm(() => {
|
|
|
9674
10265
|
});
|
|
9675
10266
|
|
|
9676
10267
|
// src/db/crawl-results.ts
|
|
9677
|
-
import { randomUUID as
|
|
10268
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
9678
10269
|
function deserialize2(row) {
|
|
9679
10270
|
const pages = JSON.parse(row.pages);
|
|
9680
10271
|
return {
|
|
@@ -9690,7 +10281,7 @@ function deserialize2(row) {
|
|
|
9690
10281
|
}
|
|
9691
10282
|
function createCrawlResult(data) {
|
|
9692
10283
|
const db = getDatabase();
|
|
9693
|
-
const id =
|
|
10284
|
+
const id = randomUUID5();
|
|
9694
10285
|
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));
|
|
9695
10286
|
return getCrawlResult(id);
|
|
9696
10287
|
}
|
|
@@ -9783,7 +10374,7 @@ __export(exports_agents, {
|
|
|
9783
10374
|
deleteAgent: () => deleteAgent,
|
|
9784
10375
|
cleanStaleAgents: () => cleanStaleAgents
|
|
9785
10376
|
});
|
|
9786
|
-
import { randomUUID as
|
|
10377
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
9787
10378
|
function registerAgent(name, opts = {}) {
|
|
9788
10379
|
const db = getDatabase();
|
|
9789
10380
|
const existing = db.query("SELECT * FROM agents WHERE name = ?").get(name);
|
|
@@ -9791,7 +10382,7 @@ function registerAgent(name, opts = {}) {
|
|
|
9791
10382
|
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);
|
|
9792
10383
|
return getAgentByName(name);
|
|
9793
10384
|
}
|
|
9794
|
-
const id =
|
|
10385
|
+
const id = randomUUID6();
|
|
9795
10386
|
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);
|
|
9796
10387
|
return getAgent(id);
|
|
9797
10388
|
}
|
|
@@ -9801,7 +10392,7 @@ function heartbeat(agentId) {
|
|
|
9801
10392
|
if (!agent)
|
|
9802
10393
|
throw new AgentNotFoundError(agentId);
|
|
9803
10394
|
db.prepare("UPDATE agents SET last_seen = datetime('now') WHERE id = ?").run(agentId);
|
|
9804
|
-
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(
|
|
10395
|
+
db.prepare("INSERT INTO heartbeats (id, agent_id, session_id) VALUES (?, ?, ?)").run(randomUUID6(), agentId, agent.session_id ?? null);
|
|
9805
10396
|
}
|
|
9806
10397
|
function getAgent(id) {
|
|
9807
10398
|
const db = getDatabase();
|
|
@@ -9878,10 +10469,10 @@ var init_agents2 = __esm(() => {
|
|
|
9878
10469
|
});
|
|
9879
10470
|
|
|
9880
10471
|
// src/db/projects.ts
|
|
9881
|
-
import { randomUUID as
|
|
10472
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
9882
10473
|
function createProject(data) {
|
|
9883
10474
|
const db = getDatabase();
|
|
9884
|
-
const id =
|
|
10475
|
+
const id = randomUUID7();
|
|
9885
10476
|
db.prepare("INSERT INTO projects (id, name, path, description) VALUES (?, ?, ?, ?)").run(id, data.name, data.path, data.description ?? null);
|
|
9886
10477
|
return getProject(id);
|
|
9887
10478
|
}
|
|
@@ -9917,7 +10508,7 @@ __export(exports_recordings, {
|
|
|
9917
10508
|
deleteRecording: () => deleteRecording,
|
|
9918
10509
|
createRecording: () => createRecording
|
|
9919
10510
|
});
|
|
9920
|
-
import { randomUUID as
|
|
10511
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
9921
10512
|
function deserialize3(row) {
|
|
9922
10513
|
return {
|
|
9923
10514
|
...row,
|
|
@@ -9928,7 +10519,7 @@ function deserialize3(row) {
|
|
|
9928
10519
|
}
|
|
9929
10520
|
function createRecording(data) {
|
|
9930
10521
|
const db = getDatabase();
|
|
9931
|
-
const id =
|
|
10522
|
+
const id = randomUUID8();
|
|
9932
10523
|
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 ?? []));
|
|
9933
10524
|
return getRecording(id);
|
|
9934
10525
|
}
|
|
@@ -14031,139 +14622,6 @@ var init_zod = __esm(() => {
|
|
|
14031
14622
|
init_external();
|
|
14032
14623
|
});
|
|
14033
14624
|
|
|
14034
|
-
// src/db/network-log.ts
|
|
14035
|
-
import { randomUUID as randomUUID7 } from "crypto";
|
|
14036
|
-
function logRequest(data) {
|
|
14037
|
-
const db = getDatabase();
|
|
14038
|
-
const id = randomUUID7();
|
|
14039
|
-
db.prepare(`INSERT INTO network_log (id, session_id, method, url, status_code, request_headers,
|
|
14040
|
-
response_headers, request_body, body_size, duration_ms, resource_type)
|
|
14041
|
-
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);
|
|
14042
|
-
return getNetworkRequest(id);
|
|
14043
|
-
}
|
|
14044
|
-
function getNetworkRequest(id) {
|
|
14045
|
-
const db = getDatabase();
|
|
14046
|
-
return db.query("SELECT * FROM network_log WHERE id = ?").get(id) ?? null;
|
|
14047
|
-
}
|
|
14048
|
-
function getNetworkLog(sessionId) {
|
|
14049
|
-
const db = getDatabase();
|
|
14050
|
-
return db.query("SELECT * FROM network_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
14051
|
-
}
|
|
14052
|
-
function clearNetworkLog(sessionId) {
|
|
14053
|
-
const db = getDatabase();
|
|
14054
|
-
db.prepare("DELETE FROM network_log WHERE session_id = ?").run(sessionId);
|
|
14055
|
-
}
|
|
14056
|
-
var init_network_log = __esm(() => {
|
|
14057
|
-
init_schema();
|
|
14058
|
-
});
|
|
14059
|
-
|
|
14060
|
-
// src/lib/network.ts
|
|
14061
|
-
function enableNetworkLogging(page, sessionId) {
|
|
14062
|
-
const requestStart = new Map;
|
|
14063
|
-
const onRequest = (req) => {
|
|
14064
|
-
requestStart.set(req.url(), Date.now());
|
|
14065
|
-
};
|
|
14066
|
-
const onResponse = (res) => {
|
|
14067
|
-
const start = requestStart.get(res.url()) ?? Date.now();
|
|
14068
|
-
const duration = Date.now() - start;
|
|
14069
|
-
const req = res.request();
|
|
14070
|
-
try {
|
|
14071
|
-
logRequest({
|
|
14072
|
-
session_id: sessionId,
|
|
14073
|
-
method: req.method(),
|
|
14074
|
-
url: res.url(),
|
|
14075
|
-
status_code: res.status(),
|
|
14076
|
-
request_headers: JSON.stringify(req.headers()),
|
|
14077
|
-
response_headers: JSON.stringify(res.headers()),
|
|
14078
|
-
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
14079
|
-
duration_ms: duration,
|
|
14080
|
-
resource_type: req.resourceType()
|
|
14081
|
-
});
|
|
14082
|
-
} catch {}
|
|
14083
|
-
};
|
|
14084
|
-
page.on("request", onRequest);
|
|
14085
|
-
page.on("response", onResponse);
|
|
14086
|
-
return () => {
|
|
14087
|
-
page.off("request", onRequest);
|
|
14088
|
-
page.off("response", onResponse);
|
|
14089
|
-
};
|
|
14090
|
-
}
|
|
14091
|
-
async function addInterceptRule(page, rule) {
|
|
14092
|
-
await page.route(rule.pattern, async (route) => {
|
|
14093
|
-
if (rule.action === "block") {
|
|
14094
|
-
await route.abort();
|
|
14095
|
-
} else if (rule.action === "modify" && rule.response) {
|
|
14096
|
-
await route.fulfill({
|
|
14097
|
-
status: rule.response.status,
|
|
14098
|
-
body: rule.response.body,
|
|
14099
|
-
headers: rule.response.headers
|
|
14100
|
-
});
|
|
14101
|
-
} else {
|
|
14102
|
-
await route.continue();
|
|
14103
|
-
}
|
|
14104
|
-
});
|
|
14105
|
-
}
|
|
14106
|
-
function startHAR(page) {
|
|
14107
|
-
const entries = [];
|
|
14108
|
-
const requestStart = new Map;
|
|
14109
|
-
const onRequest = (req) => {
|
|
14110
|
-
requestStart.set(req.url() + req.method(), {
|
|
14111
|
-
time: Date.now(),
|
|
14112
|
-
method: req.method(),
|
|
14113
|
-
headers: req.headers(),
|
|
14114
|
-
postData: req.postData() ?? undefined
|
|
14115
|
-
});
|
|
14116
|
-
};
|
|
14117
|
-
const onResponse = async (res) => {
|
|
14118
|
-
const key = res.url() + res.request().method();
|
|
14119
|
-
const start = requestStart.get(key);
|
|
14120
|
-
if (!start)
|
|
14121
|
-
return;
|
|
14122
|
-
const duration = Date.now() - start.time;
|
|
14123
|
-
const entry = {
|
|
14124
|
-
startedDateTime: new Date(start.time).toISOString(),
|
|
14125
|
-
time: duration,
|
|
14126
|
-
request: {
|
|
14127
|
-
method: start.method,
|
|
14128
|
-
url: res.url(),
|
|
14129
|
-
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
14130
|
-
postData: start.postData ? { text: start.postData } : undefined
|
|
14131
|
-
},
|
|
14132
|
-
response: {
|
|
14133
|
-
status: res.status(),
|
|
14134
|
-
statusText: res.statusText(),
|
|
14135
|
-
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
14136
|
-
content: {
|
|
14137
|
-
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
14138
|
-
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
14139
|
-
}
|
|
14140
|
-
},
|
|
14141
|
-
timings: { send: 0, wait: duration, receive: 0 }
|
|
14142
|
-
};
|
|
14143
|
-
entries.push(entry);
|
|
14144
|
-
requestStart.delete(key);
|
|
14145
|
-
};
|
|
14146
|
-
page.on("request", onRequest);
|
|
14147
|
-
page.on("response", onResponse);
|
|
14148
|
-
return {
|
|
14149
|
-
entries,
|
|
14150
|
-
stop: () => {
|
|
14151
|
-
page.off("request", onRequest);
|
|
14152
|
-
page.off("response", onResponse);
|
|
14153
|
-
return {
|
|
14154
|
-
log: {
|
|
14155
|
-
version: "1.2",
|
|
14156
|
-
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
14157
|
-
entries
|
|
14158
|
-
}
|
|
14159
|
-
};
|
|
14160
|
-
}
|
|
14161
|
-
};
|
|
14162
|
-
}
|
|
14163
|
-
var init_network = __esm(() => {
|
|
14164
|
-
init_network_log();
|
|
14165
|
-
});
|
|
14166
|
-
|
|
14167
14625
|
// src/engines/cdp.ts
|
|
14168
14626
|
class CDPClient {
|
|
14169
14627
|
session;
|
|
@@ -14263,120 +14721,56 @@ class CDPClient {
|
|
|
14263
14721
|
this.on("Network.responseReceived", onResponse);
|
|
14264
14722
|
return () => {
|
|
14265
14723
|
this.off("Network.requestWillBeSent", onRequest);
|
|
14266
|
-
this.off("Network.responseReceived", onResponse);
|
|
14267
|
-
};
|
|
14268
|
-
}
|
|
14269
|
-
async detach() {
|
|
14270
|
-
try {
|
|
14271
|
-
await this.session.detach();
|
|
14272
|
-
} catch {}
|
|
14273
|
-
}
|
|
14274
|
-
}
|
|
14275
|
-
var init_cdp = __esm(() => {
|
|
14276
|
-
init_types();
|
|
14277
|
-
});
|
|
14278
|
-
|
|
14279
|
-
// src/lib/performance.ts
|
|
14280
|
-
async function getPerformanceMetrics(page) {
|
|
14281
|
-
const navTiming = await page.evaluate(() => {
|
|
14282
|
-
const t = performance.timing;
|
|
14283
|
-
const nav = performance.getEntriesByType("navigation")[0];
|
|
14284
|
-
return {
|
|
14285
|
-
ttfb: nav ? nav.responseStart - nav.requestStart : t.responseStart - t.requestStart,
|
|
14286
|
-
domInteractive: nav ? nav.domInteractive : t.domInteractive - t.navigationStart,
|
|
14287
|
-
domComplete: nav ? nav.domComplete : t.domComplete - t.navigationStart,
|
|
14288
|
-
loadEvent: nav ? nav.loadEventEnd : t.loadEventEnd - t.navigationStart
|
|
14289
|
-
};
|
|
14290
|
-
});
|
|
14291
|
-
const paintEntries = await page.evaluate(() => {
|
|
14292
|
-
const entries = performance.getEntriesByType("paint");
|
|
14293
|
-
const fcp = entries.find((e) => e.name === "first-contentful-paint");
|
|
14294
|
-
return { fcp: fcp?.startTime };
|
|
14295
|
-
});
|
|
14296
|
-
let heapMetrics = {};
|
|
14297
|
-
try {
|
|
14298
|
-
const cdp = await CDPClient.fromPage(page);
|
|
14299
|
-
const cdpMetrics = await cdp.getPerformanceMetrics();
|
|
14300
|
-
heapMetrics = {
|
|
14301
|
-
js_heap_size_used: cdpMetrics.js_heap_size_used,
|
|
14302
|
-
js_heap_size_total: cdpMetrics.js_heap_size_total
|
|
14303
|
-
};
|
|
14304
|
-
} catch {}
|
|
14305
|
-
return {
|
|
14306
|
-
fcp: paintEntries.fcp,
|
|
14307
|
-
ttfb: navTiming.ttfb,
|
|
14308
|
-
dom_interactive: navTiming.domInteractive,
|
|
14309
|
-
dom_complete: navTiming.domComplete,
|
|
14310
|
-
load_event: navTiming.loadEvent,
|
|
14311
|
-
...heapMetrics
|
|
14312
|
-
};
|
|
14313
|
-
}
|
|
14314
|
-
var init_performance = __esm(() => {
|
|
14315
|
-
init_cdp();
|
|
14316
|
-
});
|
|
14317
|
-
|
|
14318
|
-
// src/db/console-log.ts
|
|
14319
|
-
var exports_console_log = {};
|
|
14320
|
-
__export(exports_console_log, {
|
|
14321
|
-
logConsoleMessage: () => logConsoleMessage,
|
|
14322
|
-
getConsoleMessage: () => getConsoleMessage,
|
|
14323
|
-
getConsoleLog: () => getConsoleLog,
|
|
14324
|
-
clearConsoleLog: () => clearConsoleLog
|
|
14325
|
-
});
|
|
14326
|
-
import { randomUUID as randomUUID8 } from "crypto";
|
|
14327
|
-
function logConsoleMessage(data) {
|
|
14328
|
-
const db = getDatabase();
|
|
14329
|
-
const id = randomUUID8();
|
|
14330
|
-
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);
|
|
14331
|
-
return getConsoleMessage(id);
|
|
14332
|
-
}
|
|
14333
|
-
function getConsoleMessage(id) {
|
|
14334
|
-
const db = getDatabase();
|
|
14335
|
-
return db.query("SELECT * FROM console_log WHERE id = ?").get(id) ?? null;
|
|
14336
|
-
}
|
|
14337
|
-
function getConsoleLog(sessionId, level) {
|
|
14338
|
-
const db = getDatabase();
|
|
14339
|
-
if (level) {
|
|
14340
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? AND level = ? ORDER BY timestamp ASC").all(sessionId, level);
|
|
14341
|
-
}
|
|
14342
|
-
return db.query("SELECT * FROM console_log WHERE session_id = ? ORDER BY timestamp ASC").all(sessionId);
|
|
14343
|
-
}
|
|
14344
|
-
function clearConsoleLog(sessionId) {
|
|
14345
|
-
const db = getDatabase();
|
|
14346
|
-
db.prepare("DELETE FROM console_log WHERE session_id = ?").run(sessionId);
|
|
14347
|
-
}
|
|
14348
|
-
var init_console_log = __esm(() => {
|
|
14349
|
-
init_schema();
|
|
14350
|
-
});
|
|
14351
|
-
|
|
14352
|
-
// src/lib/console.ts
|
|
14353
|
-
function enableConsoleCapture(page, sessionId) {
|
|
14354
|
-
const onConsole = (msg) => {
|
|
14355
|
-
const levelMap = {
|
|
14356
|
-
log: "log",
|
|
14357
|
-
warn: "warn",
|
|
14358
|
-
error: "error",
|
|
14359
|
-
debug: "debug",
|
|
14360
|
-
info: "info",
|
|
14361
|
-
warning: "warn"
|
|
14724
|
+
this.off("Network.responseReceived", onResponse);
|
|
14362
14725
|
};
|
|
14363
|
-
|
|
14364
|
-
|
|
14726
|
+
}
|
|
14727
|
+
async detach() {
|
|
14365
14728
|
try {
|
|
14366
|
-
|
|
14367
|
-
session_id: sessionId,
|
|
14368
|
-
level,
|
|
14369
|
-
message: msg.text(),
|
|
14370
|
-
source: location.url || undefined,
|
|
14371
|
-
line_number: location.lineNumber || undefined
|
|
14372
|
-
});
|
|
14729
|
+
await this.session.detach();
|
|
14373
14730
|
} catch {}
|
|
14731
|
+
}
|
|
14732
|
+
}
|
|
14733
|
+
var init_cdp = __esm(() => {
|
|
14734
|
+
init_types();
|
|
14735
|
+
});
|
|
14736
|
+
|
|
14737
|
+
// src/lib/performance.ts
|
|
14738
|
+
async function getPerformanceMetrics(page) {
|
|
14739
|
+
const navTiming = await page.evaluate(() => {
|
|
14740
|
+
const t = performance.timing;
|
|
14741
|
+
const nav = performance.getEntriesByType("navigation")[0];
|
|
14742
|
+
return {
|
|
14743
|
+
ttfb: nav ? nav.responseStart - nav.requestStart : t.responseStart - t.requestStart,
|
|
14744
|
+
domInteractive: nav ? nav.domInteractive : t.domInteractive - t.navigationStart,
|
|
14745
|
+
domComplete: nav ? nav.domComplete : t.domComplete - t.navigationStart,
|
|
14746
|
+
loadEvent: nav ? nav.loadEventEnd : t.loadEventEnd - t.navigationStart
|
|
14747
|
+
};
|
|
14748
|
+
});
|
|
14749
|
+
const paintEntries = await page.evaluate(() => {
|
|
14750
|
+
const entries = performance.getEntriesByType("paint");
|
|
14751
|
+
const fcp = entries.find((e) => e.name === "first-contentful-paint");
|
|
14752
|
+
return { fcp: fcp?.startTime };
|
|
14753
|
+
});
|
|
14754
|
+
let heapMetrics = {};
|
|
14755
|
+
try {
|
|
14756
|
+
const cdp = await CDPClient.fromPage(page);
|
|
14757
|
+
const cdpMetrics = await cdp.getPerformanceMetrics();
|
|
14758
|
+
heapMetrics = {
|
|
14759
|
+
js_heap_size_used: cdpMetrics.js_heap_size_used,
|
|
14760
|
+
js_heap_size_total: cdpMetrics.js_heap_size_total
|
|
14761
|
+
};
|
|
14762
|
+
} catch {}
|
|
14763
|
+
return {
|
|
14764
|
+
fcp: paintEntries.fcp,
|
|
14765
|
+
ttfb: navTiming.ttfb,
|
|
14766
|
+
dom_interactive: navTiming.domInteractive,
|
|
14767
|
+
dom_complete: navTiming.domComplete,
|
|
14768
|
+
load_event: navTiming.loadEvent,
|
|
14769
|
+
...heapMetrics
|
|
14374
14770
|
};
|
|
14375
|
-
page.on("console", onConsole);
|
|
14376
|
-
return () => page.off("console", onConsole);
|
|
14377
14771
|
}
|
|
14378
|
-
var
|
|
14379
|
-
|
|
14772
|
+
var init_performance = __esm(() => {
|
|
14773
|
+
init_cdp();
|
|
14380
14774
|
});
|
|
14381
14775
|
|
|
14382
14776
|
// src/lib/storage.ts
|
|
@@ -14673,6 +15067,282 @@ async function persistFile(localPath, opts) {
|
|
|
14673
15067
|
}
|
|
14674
15068
|
var init_files_integration = () => {};
|
|
14675
15069
|
|
|
15070
|
+
// src/lib/tabs.ts
|
|
15071
|
+
async function newTab(page, url) {
|
|
15072
|
+
const context = page.context();
|
|
15073
|
+
const newPage = await context.newPage();
|
|
15074
|
+
if (url) {
|
|
15075
|
+
await newPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
15076
|
+
}
|
|
15077
|
+
const pages = context.pages();
|
|
15078
|
+
const index = pages.indexOf(newPage);
|
|
15079
|
+
return {
|
|
15080
|
+
index,
|
|
15081
|
+
url: newPage.url(),
|
|
15082
|
+
title: await newPage.title(),
|
|
15083
|
+
is_active: true
|
|
15084
|
+
};
|
|
15085
|
+
}
|
|
15086
|
+
async function listTabs(page) {
|
|
15087
|
+
const context = page.context();
|
|
15088
|
+
const pages = context.pages();
|
|
15089
|
+
const activePage = page;
|
|
15090
|
+
const tabs = [];
|
|
15091
|
+
for (let i = 0;i < pages.length; i++) {
|
|
15092
|
+
let url = "";
|
|
15093
|
+
let title = "";
|
|
15094
|
+
try {
|
|
15095
|
+
url = pages[i].url();
|
|
15096
|
+
title = await pages[i].title();
|
|
15097
|
+
} catch {}
|
|
15098
|
+
tabs.push({
|
|
15099
|
+
index: i,
|
|
15100
|
+
url,
|
|
15101
|
+
title,
|
|
15102
|
+
is_active: pages[i] === activePage
|
|
15103
|
+
});
|
|
15104
|
+
}
|
|
15105
|
+
return tabs;
|
|
15106
|
+
}
|
|
15107
|
+
async function switchTab(page, index) {
|
|
15108
|
+
const context = page.context();
|
|
15109
|
+
const pages = context.pages();
|
|
15110
|
+
if (index < 0 || index >= pages.length) {
|
|
15111
|
+
throw new Error(`Tab index ${index} out of range (0-${pages.length - 1})`);
|
|
15112
|
+
}
|
|
15113
|
+
const targetPage = pages[index];
|
|
15114
|
+
await targetPage.bringToFront();
|
|
15115
|
+
return {
|
|
15116
|
+
page: targetPage,
|
|
15117
|
+
tab: {
|
|
15118
|
+
index,
|
|
15119
|
+
url: targetPage.url(),
|
|
15120
|
+
title: await targetPage.title(),
|
|
15121
|
+
is_active: true
|
|
15122
|
+
}
|
|
15123
|
+
};
|
|
15124
|
+
}
|
|
15125
|
+
async function closeTab(page, index) {
|
|
15126
|
+
const context = page.context();
|
|
15127
|
+
const pages = context.pages();
|
|
15128
|
+
if (index < 0 || index >= pages.length) {
|
|
15129
|
+
throw new Error(`Tab index ${index} out of range (0-${pages.length - 1})`);
|
|
15130
|
+
}
|
|
15131
|
+
if (pages.length <= 1) {
|
|
15132
|
+
throw new Error("Cannot close the last tab");
|
|
15133
|
+
}
|
|
15134
|
+
const targetPage = pages[index];
|
|
15135
|
+
const isActivePage = targetPage === page;
|
|
15136
|
+
await targetPage.close();
|
|
15137
|
+
const remainingPages = context.pages();
|
|
15138
|
+
const activeIndex = isActivePage ? Math.min(index, remainingPages.length - 1) : remainingPages.indexOf(page);
|
|
15139
|
+
const activePage = remainingPages[activeIndex >= 0 ? activeIndex : 0];
|
|
15140
|
+
return {
|
|
15141
|
+
closed_index: index,
|
|
15142
|
+
active_tab: {
|
|
15143
|
+
index: activeIndex >= 0 ? activeIndex : 0,
|
|
15144
|
+
url: activePage.url(),
|
|
15145
|
+
title: await activePage.title(),
|
|
15146
|
+
is_active: true
|
|
15147
|
+
}
|
|
15148
|
+
};
|
|
15149
|
+
}
|
|
15150
|
+
|
|
15151
|
+
// src/lib/profiles.ts
|
|
15152
|
+
import { mkdirSync as mkdirSync6, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
15153
|
+
import { join as join6 } from "path";
|
|
15154
|
+
import { homedir as homedir6 } from "os";
|
|
15155
|
+
function getProfilesDir() {
|
|
15156
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
|
|
15157
|
+
const dir = join6(dataDir, "profiles");
|
|
15158
|
+
mkdirSync6(dir, { recursive: true });
|
|
15159
|
+
return dir;
|
|
15160
|
+
}
|
|
15161
|
+
function getProfileDir(name) {
|
|
15162
|
+
return join6(getProfilesDir(), name);
|
|
15163
|
+
}
|
|
15164
|
+
async function saveProfile(page, name) {
|
|
15165
|
+
const dir = getProfileDir(name);
|
|
15166
|
+
mkdirSync6(dir, { recursive: true });
|
|
15167
|
+
const cookies = await page.context().cookies();
|
|
15168
|
+
writeFileSync2(join6(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
|
|
15169
|
+
let localStorage2 = {};
|
|
15170
|
+
try {
|
|
15171
|
+
localStorage2 = await page.evaluate(() => {
|
|
15172
|
+
const result = {};
|
|
15173
|
+
for (let i = 0;i < window.localStorage.length; i++) {
|
|
15174
|
+
const key = window.localStorage.key(i);
|
|
15175
|
+
result[key] = window.localStorage.getItem(key);
|
|
15176
|
+
}
|
|
15177
|
+
return result;
|
|
15178
|
+
});
|
|
15179
|
+
} catch {}
|
|
15180
|
+
writeFileSync2(join6(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
|
|
15181
|
+
const savedAt = new Date().toISOString();
|
|
15182
|
+
const url = page.url();
|
|
15183
|
+
const meta = { saved_at: savedAt, url };
|
|
15184
|
+
writeFileSync2(join6(dir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
15185
|
+
return {
|
|
15186
|
+
name,
|
|
15187
|
+
saved_at: savedAt,
|
|
15188
|
+
url,
|
|
15189
|
+
cookie_count: cookies.length,
|
|
15190
|
+
storage_key_count: Object.keys(localStorage2).length
|
|
15191
|
+
};
|
|
15192
|
+
}
|
|
15193
|
+
function loadProfile(name) {
|
|
15194
|
+
const dir = getProfileDir(name);
|
|
15195
|
+
if (!existsSync3(dir)) {
|
|
15196
|
+
throw new Error(`Profile not found: ${name}`);
|
|
15197
|
+
}
|
|
15198
|
+
const cookiesPath = join6(dir, "cookies.json");
|
|
15199
|
+
const storagePath = join6(dir, "storage.json");
|
|
15200
|
+
const metaPath2 = join6(dir, "meta.json");
|
|
15201
|
+
const cookies = existsSync3(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
|
|
15202
|
+
const localStorage2 = existsSync3(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
|
|
15203
|
+
let savedAt = new Date().toISOString();
|
|
15204
|
+
let url;
|
|
15205
|
+
if (existsSync3(metaPath2)) {
|
|
15206
|
+
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
15207
|
+
savedAt = meta.saved_at ?? savedAt;
|
|
15208
|
+
url = meta.url;
|
|
15209
|
+
}
|
|
15210
|
+
return { cookies, localStorage: localStorage2, saved_at: savedAt, url };
|
|
15211
|
+
}
|
|
15212
|
+
async function applyProfile(page, profileData) {
|
|
15213
|
+
if (profileData.cookies.length > 0) {
|
|
15214
|
+
await page.context().addCookies(profileData.cookies);
|
|
15215
|
+
}
|
|
15216
|
+
const storageKeys = Object.keys(profileData.localStorage);
|
|
15217
|
+
if (storageKeys.length > 0) {
|
|
15218
|
+
try {
|
|
15219
|
+
await page.evaluate((storage) => {
|
|
15220
|
+
for (const [key, value] of Object.entries(storage)) {
|
|
15221
|
+
window.localStorage.setItem(key, value);
|
|
15222
|
+
}
|
|
15223
|
+
}, profileData.localStorage);
|
|
15224
|
+
} catch {}
|
|
15225
|
+
}
|
|
15226
|
+
return {
|
|
15227
|
+
cookies_applied: profileData.cookies.length,
|
|
15228
|
+
storage_keys_applied: storageKeys.length
|
|
15229
|
+
};
|
|
15230
|
+
}
|
|
15231
|
+
function listProfiles() {
|
|
15232
|
+
const dir = getProfilesDir();
|
|
15233
|
+
if (!existsSync3(dir))
|
|
15234
|
+
return [];
|
|
15235
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
15236
|
+
const profiles = [];
|
|
15237
|
+
for (const entry of entries) {
|
|
15238
|
+
if (!entry.isDirectory())
|
|
15239
|
+
continue;
|
|
15240
|
+
const name = entry.name;
|
|
15241
|
+
const profileDir = join6(dir, name);
|
|
15242
|
+
let savedAt = "";
|
|
15243
|
+
let url;
|
|
15244
|
+
let cookieCount = 0;
|
|
15245
|
+
let storageKeyCount = 0;
|
|
15246
|
+
try {
|
|
15247
|
+
const metaPath2 = join6(profileDir, "meta.json");
|
|
15248
|
+
if (existsSync3(metaPath2)) {
|
|
15249
|
+
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
15250
|
+
savedAt = meta.saved_at ?? "";
|
|
15251
|
+
url = meta.url;
|
|
15252
|
+
}
|
|
15253
|
+
const cookiesPath = join6(profileDir, "cookies.json");
|
|
15254
|
+
if (existsSync3(cookiesPath)) {
|
|
15255
|
+
const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
|
|
15256
|
+
cookieCount = Array.isArray(cookies) ? cookies.length : 0;
|
|
15257
|
+
}
|
|
15258
|
+
const storagePath = join6(profileDir, "storage.json");
|
|
15259
|
+
if (existsSync3(storagePath)) {
|
|
15260
|
+
const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
|
|
15261
|
+
storageKeyCount = Object.keys(storage).length;
|
|
15262
|
+
}
|
|
15263
|
+
} catch {}
|
|
15264
|
+
profiles.push({
|
|
15265
|
+
name,
|
|
15266
|
+
saved_at: savedAt,
|
|
15267
|
+
url,
|
|
15268
|
+
cookie_count: cookieCount,
|
|
15269
|
+
storage_key_count: storageKeyCount
|
|
15270
|
+
});
|
|
15271
|
+
}
|
|
15272
|
+
return profiles.sort((a, b) => b.saved_at.localeCompare(a.saved_at));
|
|
15273
|
+
}
|
|
15274
|
+
function deleteProfile(name) {
|
|
15275
|
+
const dir = getProfileDir(name);
|
|
15276
|
+
if (!existsSync3(dir))
|
|
15277
|
+
return false;
|
|
15278
|
+
try {
|
|
15279
|
+
rmSync(dir, { recursive: true, force: true });
|
|
15280
|
+
return true;
|
|
15281
|
+
} catch {
|
|
15282
|
+
return false;
|
|
15283
|
+
}
|
|
15284
|
+
}
|
|
15285
|
+
var init_profiles = () => {};
|
|
15286
|
+
|
|
15287
|
+
// src/lib/annotate.ts
|
|
15288
|
+
var exports_annotate = {};
|
|
15289
|
+
__export(exports_annotate, {
|
|
15290
|
+
annotateScreenshot: () => annotateScreenshot
|
|
15291
|
+
});
|
|
15292
|
+
async function annotateScreenshot(page, sessionId) {
|
|
15293
|
+
const snapshot = await takeSnapshot(page, sessionId);
|
|
15294
|
+
const rawBuffer = await page.screenshot({ type: "png" });
|
|
15295
|
+
const meta = await import_sharp3.default(rawBuffer).metadata();
|
|
15296
|
+
const imgWidth = meta.width ?? 1280;
|
|
15297
|
+
const imgHeight = meta.height ?? 720;
|
|
15298
|
+
const annotations = [];
|
|
15299
|
+
const labelToRef = {};
|
|
15300
|
+
let labelCounter = 1;
|
|
15301
|
+
for (const [ref, info] of Object.entries(snapshot.refs)) {
|
|
15302
|
+
try {
|
|
15303
|
+
const locator = page.getByRole(info.role, { name: info.name }).first();
|
|
15304
|
+
const box = await locator.boundingBox();
|
|
15305
|
+
if (!box)
|
|
15306
|
+
continue;
|
|
15307
|
+
const annotation = {
|
|
15308
|
+
ref,
|
|
15309
|
+
label: labelCounter,
|
|
15310
|
+
x: Math.round(box.x),
|
|
15311
|
+
y: Math.round(box.y),
|
|
15312
|
+
width: Math.round(box.width),
|
|
15313
|
+
height: Math.round(box.height),
|
|
15314
|
+
role: info.role,
|
|
15315
|
+
name: info.name
|
|
15316
|
+
};
|
|
15317
|
+
annotations.push(annotation);
|
|
15318
|
+
labelToRef[labelCounter] = ref;
|
|
15319
|
+
labelCounter++;
|
|
15320
|
+
} catch {}
|
|
15321
|
+
}
|
|
15322
|
+
const circleR = 10;
|
|
15323
|
+
const fontSize = 12;
|
|
15324
|
+
const svgParts = [];
|
|
15325
|
+
for (const ann of annotations) {
|
|
15326
|
+
const cx = Math.min(Math.max(ann.x + circleR, circleR), imgWidth - circleR);
|
|
15327
|
+
const cy = Math.min(Math.max(ann.y - circleR - 2, circleR), imgHeight - circleR);
|
|
15328
|
+
svgParts.push(`
|
|
15329
|
+
<circle cx="${cx}" cy="${cy}" r="${circleR}" fill="#e11d48" stroke="white" stroke-width="1.5"/>
|
|
15330
|
+
<text x="${cx}" y="${cy + 4}" text-anchor="middle" fill="white" font-size="${fontSize}" font-family="Arial,sans-serif" font-weight="bold">${ann.label}</text>
|
|
15331
|
+
`);
|
|
15332
|
+
svgParts.push(`
|
|
15333
|
+
<rect x="${ann.x}" y="${ann.y}" width="${ann.width}" height="${ann.height}" fill="none" stroke="#e11d48" stroke-width="1.5" stroke-opacity="0.6" rx="2"/>
|
|
15334
|
+
`);
|
|
15335
|
+
}
|
|
15336
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${imgWidth}" height="${imgHeight}">${svgParts.join("")}</svg>`;
|
|
15337
|
+
const annotatedBuffer = await import_sharp3.default(rawBuffer).composite([{ input: Buffer.from(svg), top: 0, left: 0 }]).webp({ quality: 85 }).toBuffer();
|
|
15338
|
+
return { buffer: annotatedBuffer, annotations, labelToRef };
|
|
15339
|
+
}
|
|
15340
|
+
var import_sharp3;
|
|
15341
|
+
var init_annotate = __esm(() => {
|
|
15342
|
+
init_snapshot();
|
|
15343
|
+
import_sharp3 = __toESM(require_lib(), 1);
|
|
15344
|
+
});
|
|
15345
|
+
|
|
14676
15346
|
// src/mcp/index.ts
|
|
14677
15347
|
var exports_mcp = {};
|
|
14678
15348
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -14706,8 +15376,11 @@ var init_mcp = __esm(async () => {
|
|
|
14706
15376
|
init_gallery();
|
|
14707
15377
|
init_downloads();
|
|
14708
15378
|
init_gallery_diff();
|
|
15379
|
+
init_snapshot();
|
|
14709
15380
|
init_files_integration();
|
|
14710
15381
|
init_recordings();
|
|
15382
|
+
init_dialogs();
|
|
15383
|
+
init_profiles();
|
|
14711
15384
|
init_types();
|
|
14712
15385
|
networkLogCleanup = new Map;
|
|
14713
15386
|
consoleCaptureCleanup = new Map;
|
|
@@ -14724,8 +15397,9 @@ var init_mcp = __esm(async () => {
|
|
|
14724
15397
|
start_url: exports_external.string().optional(),
|
|
14725
15398
|
headless: exports_external.boolean().optional().default(true),
|
|
14726
15399
|
viewport_width: exports_external.number().optional().default(1280),
|
|
14727
|
-
viewport_height: exports_external.number().optional().default(720)
|
|
14728
|
-
|
|
15400
|
+
viewport_height: exports_external.number().optional().default(720),
|
|
15401
|
+
stealth: exports_external.boolean().optional().default(false)
|
|
15402
|
+
}, async ({ engine, use_case, project_id, agent_id, start_url, headless, viewport_width, viewport_height, stealth }) => {
|
|
14729
15403
|
try {
|
|
14730
15404
|
const { session } = await createSession2({
|
|
14731
15405
|
engine,
|
|
@@ -14734,7 +15408,8 @@ var init_mcp = __esm(async () => {
|
|
|
14734
15408
|
agentId: agent_id,
|
|
14735
15409
|
startUrl: start_url,
|
|
14736
15410
|
headless,
|
|
14737
|
-
viewport: { width: viewport_width, height: viewport_height }
|
|
15411
|
+
viewport: { width: viewport_width, height: viewport_height },
|
|
15412
|
+
stealth
|
|
14738
15413
|
});
|
|
14739
15414
|
return json({ session });
|
|
14740
15415
|
} catch (e) {
|
|
@@ -14761,11 +15436,28 @@ var init_mcp = __esm(async () => {
|
|
|
14761
15436
|
return err(e);
|
|
14762
15437
|
}
|
|
14763
15438
|
});
|
|
14764
|
-
server.tool("browser_navigate", "Navigate to a URL
|
|
15439
|
+
server.tool("browser_navigate", "Navigate to a URL. Returns title + thumbnail + accessibility snapshot preview with refs.", { session_id: exports_external.string(), url: exports_external.string(), timeout: exports_external.number().optional().default(30000), auto_snapshot: exports_external.boolean().optional().default(true), auto_thumbnail: exports_external.boolean().optional().default(true) }, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
|
|
14765
15440
|
try {
|
|
14766
15441
|
const page = getSessionPage(session_id);
|
|
14767
15442
|
await navigate(page, url, timeout);
|
|
14768
|
-
|
|
15443
|
+
const title = await getTitle(page);
|
|
15444
|
+
const current_url = await getUrl(page);
|
|
15445
|
+
const result = { url, title, current_url };
|
|
15446
|
+
if (auto_thumbnail) {
|
|
15447
|
+
try {
|
|
15448
|
+
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
15449
|
+
result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
|
|
15450
|
+
} catch {}
|
|
15451
|
+
}
|
|
15452
|
+
if (auto_snapshot) {
|
|
15453
|
+
try {
|
|
15454
|
+
const snap = await takeSnapshot(page, session_id);
|
|
15455
|
+
result.snapshot_preview = snap.tree.slice(0, 3000);
|
|
15456
|
+
result.interactive_count = snap.interactive_count;
|
|
15457
|
+
result.has_errors = getConsoleLog(session_id, "error").length > 0;
|
|
15458
|
+
} catch {}
|
|
15459
|
+
}
|
|
15460
|
+
return json(result);
|
|
14769
15461
|
} catch (e) {
|
|
14770
15462
|
return err(e);
|
|
14771
15463
|
}
|
|
@@ -14797,29 +15489,47 @@ var init_mcp = __esm(async () => {
|
|
|
14797
15489
|
return err(e);
|
|
14798
15490
|
}
|
|
14799
15491
|
});
|
|
14800
|
-
server.tool("browser_click", "Click an element
|
|
15492
|
+
server.tool("browser_click", "Click an element by ref (from snapshot) or CSS selector. Prefer ref for reliability.", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), button: exports_external.enum(["left", "right", "middle"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, ref, button, timeout }) => {
|
|
14801
15493
|
try {
|
|
14802
15494
|
const page = getSessionPage(session_id);
|
|
15495
|
+
if (ref) {
|
|
15496
|
+
await clickRef(page, session_id, ref, { timeout });
|
|
15497
|
+
return json({ clicked: ref, method: "ref" });
|
|
15498
|
+
}
|
|
15499
|
+
if (!selector)
|
|
15500
|
+
return err(new Error("Either ref or selector is required"));
|
|
14803
15501
|
await click(page, selector, { button, timeout });
|
|
14804
|
-
return json({ clicked: selector });
|
|
15502
|
+
return json({ clicked: selector, method: "selector" });
|
|
14805
15503
|
} catch (e) {
|
|
14806
15504
|
return err(e);
|
|
14807
15505
|
}
|
|
14808
15506
|
});
|
|
14809
|
-
server.tool("browser_type", "Type text into an element", { session_id: exports_external.string(), selector: exports_external.string(), text: exports_external.string(), clear: exports_external.boolean().optional().default(false), delay: exports_external.number().optional() }, async ({ session_id, selector, text, clear, delay }) => {
|
|
15507
|
+
server.tool("browser_type", "Type text into an element by ref or selector. Prefer ref.", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), text: exports_external.string(), clear: exports_external.boolean().optional().default(false), delay: exports_external.number().optional() }, async ({ session_id, selector, ref, text, clear, delay }) => {
|
|
14810
15508
|
try {
|
|
14811
15509
|
const page = getSessionPage(session_id);
|
|
15510
|
+
if (ref) {
|
|
15511
|
+
await typeRef(page, session_id, ref, text, { clear, delay });
|
|
15512
|
+
return json({ typed: text, ref, method: "ref" });
|
|
15513
|
+
}
|
|
15514
|
+
if (!selector)
|
|
15515
|
+
return err(new Error("Either ref or selector is required"));
|
|
14812
15516
|
await type(page, selector, text, { clear, delay });
|
|
14813
|
-
return json({ typed: text, selector });
|
|
15517
|
+
return json({ typed: text, selector, method: "selector" });
|
|
14814
15518
|
} catch (e) {
|
|
14815
15519
|
return err(e);
|
|
14816
15520
|
}
|
|
14817
15521
|
});
|
|
14818
|
-
server.tool("browser_hover", "Hover over an element", { session_id: exports_external.string(), selector: exports_external.string() }, async ({ session_id, selector }) => {
|
|
15522
|
+
server.tool("browser_hover", "Hover over an element by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional() }, async ({ session_id, selector, ref }) => {
|
|
14819
15523
|
try {
|
|
14820
15524
|
const page = getSessionPage(session_id);
|
|
15525
|
+
if (ref) {
|
|
15526
|
+
await hoverRef(page, session_id, ref);
|
|
15527
|
+
return json({ hovered: ref, method: "ref" });
|
|
15528
|
+
}
|
|
15529
|
+
if (!selector)
|
|
15530
|
+
return err(new Error("Either ref or selector is required"));
|
|
14821
15531
|
await hover(page, selector);
|
|
14822
|
-
return json({ hovered: selector });
|
|
15532
|
+
return json({ hovered: selector, method: "selector" });
|
|
14823
15533
|
} catch (e) {
|
|
14824
15534
|
return err(e);
|
|
14825
15535
|
}
|
|
@@ -14833,20 +15543,32 @@ var init_mcp = __esm(async () => {
|
|
|
14833
15543
|
return err(e);
|
|
14834
15544
|
}
|
|
14835
15545
|
});
|
|
14836
|
-
server.tool("browser_select", "Select a dropdown option", { session_id: exports_external.string(), selector: exports_external.string(), value: exports_external.string() }, async ({ session_id, selector, value }) => {
|
|
15546
|
+
server.tool("browser_select", "Select a dropdown option by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), value: exports_external.string() }, async ({ session_id, selector, ref, value }) => {
|
|
14837
15547
|
try {
|
|
14838
15548
|
const page = getSessionPage(session_id);
|
|
15549
|
+
if (ref) {
|
|
15550
|
+
const selected2 = await selectRef(page, session_id, ref, value);
|
|
15551
|
+
return json({ selected: selected2, method: "ref" });
|
|
15552
|
+
}
|
|
15553
|
+
if (!selector)
|
|
15554
|
+
return err(new Error("Either ref or selector is required"));
|
|
14839
15555
|
const selected = await selectOption(page, selector, value);
|
|
14840
|
-
return json({ selected });
|
|
15556
|
+
return json({ selected, method: "selector" });
|
|
14841
15557
|
} catch (e) {
|
|
14842
15558
|
return err(e);
|
|
14843
15559
|
}
|
|
14844
15560
|
});
|
|
14845
|
-
server.tool("browser_check", "Check or uncheck a checkbox", { session_id: exports_external.string(), selector: exports_external.string(), checked: exports_external.boolean() }, async ({ session_id, selector, checked }) => {
|
|
15561
|
+
server.tool("browser_check", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
|
|
14846
15562
|
try {
|
|
14847
15563
|
const page = getSessionPage(session_id);
|
|
15564
|
+
if (ref) {
|
|
15565
|
+
await checkRef(page, session_id, ref, checked);
|
|
15566
|
+
return json({ checked, ref, method: "ref" });
|
|
15567
|
+
}
|
|
15568
|
+
if (!selector)
|
|
15569
|
+
return err(new Error("Either ref or selector is required"));
|
|
14848
15570
|
await checkBox(page, selector, checked);
|
|
14849
|
-
return json({ checked, selector });
|
|
15571
|
+
return json({ checked, selector, method: "selector" });
|
|
14850
15572
|
} catch (e) {
|
|
14851
15573
|
return err(e);
|
|
14852
15574
|
}
|
|
@@ -14927,15 +15649,17 @@ var init_mcp = __esm(async () => {
|
|
|
14927
15649
|
return err(e);
|
|
14928
15650
|
}
|
|
14929
15651
|
});
|
|
14930
|
-
server.tool("browser_snapshot", "Get
|
|
15652
|
+
server.tool("browser_snapshot", "Get a structured accessibility snapshot with element refs (@e0, @e1...). Use refs in browser_click, browser_type, etc.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
14931
15653
|
try {
|
|
14932
15654
|
const page = getSessionPage(session_id);
|
|
14933
|
-
|
|
15655
|
+
const result = await takeSnapshot(page, session_id);
|
|
15656
|
+
setLastSnapshot(session_id, result);
|
|
15657
|
+
return json({ snapshot: result.tree, refs: result.refs, interactive_count: result.interactive_count });
|
|
14934
15658
|
} catch (e) {
|
|
14935
15659
|
return err(e);
|
|
14936
15660
|
}
|
|
14937
15661
|
});
|
|
14938
|
-
server.tool("browser_screenshot", "Take a screenshot
|
|
15662
|
+
server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overlay numbered labels on interactive elements for visual+ref workflows.", {
|
|
14939
15663
|
session_id: exports_external.string(),
|
|
14940
15664
|
selector: exports_external.string().optional(),
|
|
14941
15665
|
full_page: exports_external.boolean().optional().default(false),
|
|
@@ -14943,17 +15667,37 @@ var init_mcp = __esm(async () => {
|
|
|
14943
15667
|
quality: exports_external.number().optional(),
|
|
14944
15668
|
max_width: exports_external.number().optional().default(1280),
|
|
14945
15669
|
compress: exports_external.boolean().optional().default(true),
|
|
14946
|
-
thumbnail: exports_external.boolean().optional().default(true)
|
|
14947
|
-
|
|
15670
|
+
thumbnail: exports_external.boolean().optional().default(true),
|
|
15671
|
+
annotate: exports_external.boolean().optional().default(false)
|
|
15672
|
+
}, async ({ session_id, selector, full_page, format, quality, max_width, compress, thumbnail, annotate }) => {
|
|
14948
15673
|
try {
|
|
14949
15674
|
const page = getSessionPage(session_id);
|
|
15675
|
+
if (annotate && !selector && !full_page) {
|
|
15676
|
+
const { annotateScreenshot: annotateScreenshot2 } = await Promise.resolve().then(() => (init_annotate(), exports_annotate));
|
|
15677
|
+
const annotated = await annotateScreenshot2(page, session_id);
|
|
15678
|
+
const base64 = annotated.buffer.toString("base64");
|
|
15679
|
+
return json({
|
|
15680
|
+
base64: base64.length > 50000 ? undefined : base64,
|
|
15681
|
+
base64_truncated: base64.length > 50000,
|
|
15682
|
+
size_bytes: annotated.buffer.length,
|
|
15683
|
+
annotations: annotated.annotations,
|
|
15684
|
+
label_to_ref: annotated.labelToRef,
|
|
15685
|
+
annotation_count: annotated.annotations.length
|
|
15686
|
+
});
|
|
15687
|
+
}
|
|
14950
15688
|
const result = await takeScreenshot(page, { selector, fullPage: full_page, format, quality, maxWidth: max_width, compress, thumbnail });
|
|
15689
|
+
result.url = page.url();
|
|
14951
15690
|
try {
|
|
14952
15691
|
const buf = Buffer.from(result.base64, "base64");
|
|
14953
15692
|
const filename = result.path.split("/").pop() ?? `screenshot.${format ?? "webp"}`;
|
|
14954
15693
|
const dl = saveToDownloads(buf, filename, { sessionId: session_id, type: "screenshot", sourceUrl: page.url() });
|
|
14955
15694
|
result.download_id = dl.id;
|
|
14956
15695
|
} catch {}
|
|
15696
|
+
if (result.base64.length > 50000) {
|
|
15697
|
+
result.base64_truncated = true;
|
|
15698
|
+
result.full_image_path = result.path;
|
|
15699
|
+
result.base64 = result.thumbnail_base64 ?? "";
|
|
15700
|
+
}
|
|
14957
15701
|
return json(result);
|
|
14958
15702
|
} catch (e) {
|
|
14959
15703
|
return err(e);
|
|
@@ -15248,6 +15992,37 @@ var init_mcp = __esm(async () => {
|
|
|
15248
15992
|
return err(e);
|
|
15249
15993
|
}
|
|
15250
15994
|
});
|
|
15995
|
+
server.tool("browser_scroll_and_screenshot", "Scroll the page and take a screenshot in one call. Saves 3 separate tool calls.", { session_id: exports_external.string(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(500), wait_ms: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount, wait_ms }) => {
|
|
15996
|
+
try {
|
|
15997
|
+
const page = getSessionPage(session_id);
|
|
15998
|
+
await scroll(page, direction, amount);
|
|
15999
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
16000
|
+
const result = await takeScreenshot(page, { maxWidth: 1280, track: true });
|
|
16001
|
+
result.url = page.url();
|
|
16002
|
+
if (result.base64.length > 50000) {
|
|
16003
|
+
result.base64_truncated = true;
|
|
16004
|
+
result.full_image_path = result.path;
|
|
16005
|
+
result.base64 = result.thumbnail_base64 ?? "";
|
|
16006
|
+
}
|
|
16007
|
+
return json({ scrolled: { direction, amount }, screenshot: result });
|
|
16008
|
+
} catch (e) {
|
|
16009
|
+
return err(e);
|
|
16010
|
+
}
|
|
16011
|
+
});
|
|
16012
|
+
server.tool("browser_wait_for_navigation", "Wait for URL change after a click or action. Returns the new URL and title.", { session_id: exports_external.string(), timeout: exports_external.number().optional().default(30000), url_pattern: exports_external.string().optional() }, async ({ session_id, timeout, url_pattern }) => {
|
|
16013
|
+
try {
|
|
16014
|
+
const page = getSessionPage(session_id);
|
|
16015
|
+
const start = Date.now();
|
|
16016
|
+
if (url_pattern) {
|
|
16017
|
+
await page.waitForURL(url_pattern, { timeout });
|
|
16018
|
+
} else {
|
|
16019
|
+
await page.waitForLoadState("domcontentloaded", { timeout });
|
|
16020
|
+
}
|
|
16021
|
+
return json({ url: page.url(), title: await getTitle(page), elapsed_ms: Date.now() - start });
|
|
16022
|
+
} catch (e) {
|
|
16023
|
+
return err(e);
|
|
16024
|
+
}
|
|
16025
|
+
});
|
|
15251
16026
|
server.tool("browser_session_get_by_name", "Get a session by its name", { name: exports_external.string() }, async ({ name }) => {
|
|
15252
16027
|
try {
|
|
15253
16028
|
const session = getSessionByName2(name);
|
|
@@ -15361,6 +16136,40 @@ var init_mcp = __esm(async () => {
|
|
|
15361
16136
|
return err(e);
|
|
15362
16137
|
}
|
|
15363
16138
|
});
|
|
16139
|
+
server.tool("browser_page_check", "One-call page summary: page info + console errors + performance metrics + thumbnail + accessibility snapshot preview. Replaces 4-5 separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16140
|
+
try {
|
|
16141
|
+
const page = getSessionPage(session_id);
|
|
16142
|
+
const info = await getPageInfo(page);
|
|
16143
|
+
const errors2 = getConsoleLog(session_id, "error");
|
|
16144
|
+
info.has_console_errors = errors2.length > 0;
|
|
16145
|
+
let perf = {};
|
|
16146
|
+
try {
|
|
16147
|
+
perf = await getPerformanceMetrics(page);
|
|
16148
|
+
} catch {}
|
|
16149
|
+
let thumbnail_base64 = "";
|
|
16150
|
+
try {
|
|
16151
|
+
const ss = await takeScreenshot(page, { maxWidth: 400, quality: 60, track: false, thumbnail: false });
|
|
16152
|
+
thumbnail_base64 = ss.base64;
|
|
16153
|
+
} catch {}
|
|
16154
|
+
let snapshot_preview = "";
|
|
16155
|
+
let interactive_count = 0;
|
|
16156
|
+
try {
|
|
16157
|
+
const snap = await takeSnapshot(page, session_id);
|
|
16158
|
+
snapshot_preview = snap.tree.slice(0, 2000);
|
|
16159
|
+
interactive_count = snap.interactive_count;
|
|
16160
|
+
} catch {}
|
|
16161
|
+
return json({
|
|
16162
|
+
...info,
|
|
16163
|
+
error_count: errors2.length,
|
|
16164
|
+
performance: perf,
|
|
16165
|
+
thumbnail_base64: thumbnail_base64.length > 50000 ? "" : thumbnail_base64,
|
|
16166
|
+
snapshot_preview,
|
|
16167
|
+
interactive_count
|
|
16168
|
+
});
|
|
16169
|
+
} catch (e) {
|
|
16170
|
+
return err(e);
|
|
16171
|
+
}
|
|
16172
|
+
});
|
|
15364
16173
|
server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
|
|
15365
16174
|
project_id: exports_external.string().optional(),
|
|
15366
16175
|
session_id: exports_external.string().optional(),
|
|
@@ -15509,6 +16318,297 @@ var init_mcp = __esm(async () => {
|
|
|
15509
16318
|
return err(e);
|
|
15510
16319
|
}
|
|
15511
16320
|
});
|
|
16321
|
+
server.tool("browser_snapshot_diff", "Take a new accessibility snapshot and diff it against the last snapshot for this session. Shows added/removed/modified interactive elements.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16322
|
+
try {
|
|
16323
|
+
const page = getSessionPage(session_id);
|
|
16324
|
+
const before = getLastSnapshot(session_id);
|
|
16325
|
+
const after = await takeSnapshot(page, session_id);
|
|
16326
|
+
setLastSnapshot(session_id, after);
|
|
16327
|
+
if (!before) {
|
|
16328
|
+
return json({
|
|
16329
|
+
message: "No previous snapshot \u2014 returning current snapshot only.",
|
|
16330
|
+
snapshot: after.tree,
|
|
16331
|
+
refs: after.refs,
|
|
16332
|
+
interactive_count: after.interactive_count
|
|
16333
|
+
});
|
|
16334
|
+
}
|
|
16335
|
+
const diff = diffSnapshots(before, after);
|
|
16336
|
+
return json({
|
|
16337
|
+
diff,
|
|
16338
|
+
added_count: diff.added.length,
|
|
16339
|
+
removed_count: diff.removed.length,
|
|
16340
|
+
modified_count: diff.modified.length,
|
|
16341
|
+
url_changed: diff.url_changed,
|
|
16342
|
+
title_changed: diff.title_changed,
|
|
16343
|
+
current_interactive_count: after.interactive_count
|
|
16344
|
+
});
|
|
16345
|
+
} catch (e) {
|
|
16346
|
+
return err(e);
|
|
16347
|
+
}
|
|
16348
|
+
});
|
|
16349
|
+
server.tool("browser_session_stats", "Get session info and estimated token usage (based on network log, console log, and gallery entry sizes).", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16350
|
+
try {
|
|
16351
|
+
const session = getSession2(session_id);
|
|
16352
|
+
const networkLog = getNetworkLog(session_id);
|
|
16353
|
+
const consoleLog = getConsoleLog(session_id);
|
|
16354
|
+
const galleryEntries = listEntries({ sessionId: session_id, limit: 1000 });
|
|
16355
|
+
let totalChars = 0;
|
|
16356
|
+
for (const req of networkLog) {
|
|
16357
|
+
totalChars += (req.url?.length ?? 0) + (req.request_headers?.length ?? 0) + (req.response_headers?.length ?? 0) + (req.request_body?.length ?? 0);
|
|
16358
|
+
}
|
|
16359
|
+
for (const msg of consoleLog) {
|
|
16360
|
+
totalChars += (msg.message?.length ?? 0) + (msg.source?.length ?? 0);
|
|
16361
|
+
}
|
|
16362
|
+
for (const entry of galleryEntries) {
|
|
16363
|
+
totalChars += (entry.url?.length ?? 0) + (entry.title?.length ?? 0) + (entry.notes?.length ?? 0) + (entry.tags?.join(",").length ?? 0);
|
|
16364
|
+
}
|
|
16365
|
+
const estimatedTokens = Math.ceil(totalChars / 4);
|
|
16366
|
+
const tokenBudget = getTokenBudget(session_id);
|
|
16367
|
+
return json({
|
|
16368
|
+
session,
|
|
16369
|
+
network_request_count: networkLog.length,
|
|
16370
|
+
console_message_count: consoleLog.length,
|
|
16371
|
+
gallery_entry_count: galleryEntries.length,
|
|
16372
|
+
estimated_tokens_used: estimatedTokens,
|
|
16373
|
+
token_budget: tokenBudget,
|
|
16374
|
+
data_size_chars: totalChars
|
|
16375
|
+
});
|
|
16376
|
+
} catch (e) {
|
|
16377
|
+
return err(e);
|
|
16378
|
+
}
|
|
16379
|
+
});
|
|
16380
|
+
server.tool("browser_tab_new", "Open a new tab in the session's browser context, optionally navigating to a URL", { session_id: exports_external.string(), url: exports_external.string().optional() }, async ({ session_id, url }) => {
|
|
16381
|
+
try {
|
|
16382
|
+
const page = getSessionPage(session_id);
|
|
16383
|
+
const tab = await newTab(page, url);
|
|
16384
|
+
return json(tab);
|
|
16385
|
+
} catch (e) {
|
|
16386
|
+
return err(e);
|
|
16387
|
+
}
|
|
16388
|
+
});
|
|
16389
|
+
server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16390
|
+
try {
|
|
16391
|
+
const page = getSessionPage(session_id);
|
|
16392
|
+
const tabs = await listTabs(page);
|
|
16393
|
+
return json({ tabs, count: tabs.length });
|
|
16394
|
+
} catch (e) {
|
|
16395
|
+
return err(e);
|
|
16396
|
+
}
|
|
16397
|
+
});
|
|
16398
|
+
server.tool("browser_tab_switch", "Switch to a different tab by index. Updates the session's active page.", { session_id: exports_external.string(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
|
|
16399
|
+
try {
|
|
16400
|
+
const page = getSessionPage(session_id);
|
|
16401
|
+
const result = await switchTab(page, tab_id);
|
|
16402
|
+
setSessionPage(session_id, result.page);
|
|
16403
|
+
return json(result.tab);
|
|
16404
|
+
} catch (e) {
|
|
16405
|
+
return err(e);
|
|
16406
|
+
}
|
|
16407
|
+
});
|
|
16408
|
+
server.tool("browser_tab_close", "Close a tab by index. Cannot close the last tab.", { session_id: exports_external.string(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
|
|
16409
|
+
try {
|
|
16410
|
+
const page = getSessionPage(session_id);
|
|
16411
|
+
const context = page.context();
|
|
16412
|
+
const result = await closeTab(page, tab_id);
|
|
16413
|
+
const remainingPages = context.pages();
|
|
16414
|
+
const newActivePage = remainingPages[result.active_tab.index];
|
|
16415
|
+
if (newActivePage) {
|
|
16416
|
+
setSessionPage(session_id, newActivePage);
|
|
16417
|
+
}
|
|
16418
|
+
return json(result);
|
|
16419
|
+
} catch (e) {
|
|
16420
|
+
return err(e);
|
|
16421
|
+
}
|
|
16422
|
+
});
|
|
16423
|
+
server.tool("browser_handle_dialog", "Accept or dismiss a pending dialog (alert, confirm, prompt). Handles the oldest pending dialog.", { session_id: exports_external.string(), action: exports_external.enum(["accept", "dismiss"]), prompt_text: exports_external.string().optional() }, async ({ session_id, action, prompt_text }) => {
|
|
16424
|
+
try {
|
|
16425
|
+
const result = await handleDialog(session_id, action, prompt_text);
|
|
16426
|
+
if (!result.handled)
|
|
16427
|
+
return err(new Error("No pending dialogs for this session"));
|
|
16428
|
+
return json(result);
|
|
16429
|
+
} catch (e) {
|
|
16430
|
+
return err(e);
|
|
16431
|
+
}
|
|
16432
|
+
});
|
|
16433
|
+
server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
16434
|
+
try {
|
|
16435
|
+
const dialogs = getDialogs(session_id);
|
|
16436
|
+
return json({ dialogs, count: dialogs.length });
|
|
16437
|
+
} catch (e) {
|
|
16438
|
+
return err(e);
|
|
16439
|
+
}
|
|
16440
|
+
});
|
|
16441
|
+
server.tool("browser_profile_save", "Save cookies + localStorage from the current session as a named profile", { session_id: exports_external.string(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
16442
|
+
try {
|
|
16443
|
+
const page = getSessionPage(session_id);
|
|
16444
|
+
const info = await saveProfile(page, name);
|
|
16445
|
+
return json(info);
|
|
16446
|
+
} catch (e) {
|
|
16447
|
+
return err(e);
|
|
16448
|
+
}
|
|
16449
|
+
});
|
|
16450
|
+
server.tool("browser_profile_load", "Load a saved profile and apply cookies + localStorage to the current session", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
16451
|
+
try {
|
|
16452
|
+
const profileData = loadProfile(name);
|
|
16453
|
+
if (session_id) {
|
|
16454
|
+
const page = getSessionPage(session_id);
|
|
16455
|
+
const applied = await applyProfile(page, profileData);
|
|
16456
|
+
return json({ ...applied, profile: name });
|
|
16457
|
+
}
|
|
16458
|
+
return json({ profile: name, cookies: profileData.cookies.length, storage_keys: Object.keys(profileData.localStorage).length });
|
|
16459
|
+
} catch (e) {
|
|
16460
|
+
return err(e);
|
|
16461
|
+
}
|
|
16462
|
+
});
|
|
16463
|
+
server.tool("browser_profile_list", "List all saved browser profiles", {}, async () => {
|
|
16464
|
+
try {
|
|
16465
|
+
return json({ profiles: listProfiles() });
|
|
16466
|
+
} catch (e) {
|
|
16467
|
+
return err(e);
|
|
16468
|
+
}
|
|
16469
|
+
});
|
|
16470
|
+
server.tool("browser_profile_delete", "Delete a saved browser profile", { name: exports_external.string() }, async ({ name }) => {
|
|
16471
|
+
try {
|
|
16472
|
+
const deleted = deleteProfile(name);
|
|
16473
|
+
if (!deleted)
|
|
16474
|
+
return err(new Error(`Profile not found: ${name}`));
|
|
16475
|
+
return json({ deleted: name });
|
|
16476
|
+
} catch (e) {
|
|
16477
|
+
return err(e);
|
|
16478
|
+
}
|
|
16479
|
+
});
|
|
16480
|
+
server.tool("browser_help", "Show all available browser tools grouped by category with one-line descriptions", {}, async () => {
|
|
16481
|
+
try {
|
|
16482
|
+
const groups = {
|
|
16483
|
+
Navigation: [
|
|
16484
|
+
{ tool: "browser_navigate", description: "Navigate to a URL" },
|
|
16485
|
+
{ tool: "browser_back", description: "Navigate back in history" },
|
|
16486
|
+
{ tool: "browser_forward", description: "Navigate forward in history" },
|
|
16487
|
+
{ tool: "browser_reload", description: "Reload the current page" },
|
|
16488
|
+
{ tool: "browser_wait_for_navigation", description: "Wait for URL change after action" }
|
|
16489
|
+
],
|
|
16490
|
+
Interaction: [
|
|
16491
|
+
{ tool: "browser_click", description: "Click element by ref or selector" },
|
|
16492
|
+
{ tool: "browser_click_text", description: "Click element by visible text" },
|
|
16493
|
+
{ tool: "browser_type", description: "Type text into an element" },
|
|
16494
|
+
{ tool: "browser_hover", description: "Hover over an element" },
|
|
16495
|
+
{ tool: "browser_scroll", description: "Scroll the page" },
|
|
16496
|
+
{ tool: "browser_select", description: "Select a dropdown option" },
|
|
16497
|
+
{ tool: "browser_check", description: "Check/uncheck a checkbox" },
|
|
16498
|
+
{ tool: "browser_upload", description: "Upload a file to an input" },
|
|
16499
|
+
{ tool: "browser_press_key", description: "Press a keyboard key" },
|
|
16500
|
+
{ tool: "browser_wait", description: "Wait for a selector to appear" },
|
|
16501
|
+
{ tool: "browser_wait_for_text", description: "Wait for text to appear" },
|
|
16502
|
+
{ tool: "browser_fill_form", description: "Fill multiple form fields at once" },
|
|
16503
|
+
{ tool: "browser_handle_dialog", description: "Accept or dismiss a dialog" }
|
|
16504
|
+
],
|
|
16505
|
+
Extraction: [
|
|
16506
|
+
{ tool: "browser_get_text", description: "Get text content from page/selector" },
|
|
16507
|
+
{ tool: "browser_get_html", description: "Get HTML content from page/selector" },
|
|
16508
|
+
{ tool: "browser_get_links", description: "Get all links on the page" },
|
|
16509
|
+
{ tool: "browser_get_page_info", description: "Full page summary in one call" },
|
|
16510
|
+
{ tool: "browser_extract", description: "Extract content in various formats" },
|
|
16511
|
+
{ tool: "browser_find", description: "Find elements by selector" },
|
|
16512
|
+
{ tool: "browser_element_exists", description: "Check if a selector exists" },
|
|
16513
|
+
{ tool: "browser_snapshot", description: "Get accessibility snapshot with refs" },
|
|
16514
|
+
{ tool: "browser_evaluate", description: "Execute JavaScript in page context" }
|
|
16515
|
+
],
|
|
16516
|
+
Capture: [
|
|
16517
|
+
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP)" },
|
|
16518
|
+
{ tool: "browser_pdf", description: "Generate a PDF of the page" },
|
|
16519
|
+
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" }
|
|
16520
|
+
],
|
|
16521
|
+
Storage: [
|
|
16522
|
+
{ tool: "browser_cookies_get", description: "Get cookies" },
|
|
16523
|
+
{ tool: "browser_cookies_set", description: "Set a cookie" },
|
|
16524
|
+
{ tool: "browser_cookies_clear", description: "Clear cookies" },
|
|
16525
|
+
{ tool: "browser_storage_get", description: "Get localStorage/sessionStorage" },
|
|
16526
|
+
{ tool: "browser_storage_set", description: "Set localStorage/sessionStorage" },
|
|
16527
|
+
{ tool: "browser_profile_save", description: "Save cookies + localStorage as profile" },
|
|
16528
|
+
{ tool: "browser_profile_load", description: "Load and apply a saved profile" },
|
|
16529
|
+
{ tool: "browser_profile_list", description: "List saved profiles" },
|
|
16530
|
+
{ tool: "browser_profile_delete", description: "Delete a saved profile" }
|
|
16531
|
+
],
|
|
16532
|
+
Network: [
|
|
16533
|
+
{ tool: "browser_network_log", description: "Get captured network requests" },
|
|
16534
|
+
{ tool: "browser_network_intercept", description: "Add a network interception rule" },
|
|
16535
|
+
{ tool: "browser_har_start", description: "Start HAR capture" },
|
|
16536
|
+
{ tool: "browser_har_stop", description: "Stop HAR capture and get data" }
|
|
16537
|
+
],
|
|
16538
|
+
Performance: [
|
|
16539
|
+
{ tool: "browser_performance", description: "Get performance metrics" }
|
|
16540
|
+
],
|
|
16541
|
+
Console: [
|
|
16542
|
+
{ tool: "browser_console_log", description: "Get console messages" },
|
|
16543
|
+
{ tool: "browser_has_errors", description: "Check for console errors" },
|
|
16544
|
+
{ tool: "browser_clear_errors", description: "Clear console error log" },
|
|
16545
|
+
{ tool: "browser_get_dialogs", description: "Get pending dialogs" }
|
|
16546
|
+
],
|
|
16547
|
+
Recording: [
|
|
16548
|
+
{ tool: "browser_record_start", description: "Start recording actions" },
|
|
16549
|
+
{ tool: "browser_record_step", description: "Add a step to recording" },
|
|
16550
|
+
{ tool: "browser_record_stop", description: "Stop and save recording" },
|
|
16551
|
+
{ tool: "browser_record_replay", description: "Replay a recorded sequence" },
|
|
16552
|
+
{ tool: "browser_recordings_list", description: "List all recordings" }
|
|
16553
|
+
],
|
|
16554
|
+
Crawl: [
|
|
16555
|
+
{ tool: "browser_crawl", description: "Crawl a URL recursively" }
|
|
16556
|
+
],
|
|
16557
|
+
Agent: [
|
|
16558
|
+
{ tool: "browser_register_agent", description: "Register an agent" },
|
|
16559
|
+
{ tool: "browser_heartbeat", description: "Send agent heartbeat" },
|
|
16560
|
+
{ tool: "browser_agent_list", description: "List registered agents" }
|
|
16561
|
+
],
|
|
16562
|
+
Project: [
|
|
16563
|
+
{ tool: "browser_project_create", description: "Create or ensure a project" },
|
|
16564
|
+
{ tool: "browser_project_list", description: "List all projects" }
|
|
16565
|
+
],
|
|
16566
|
+
Gallery: [
|
|
16567
|
+
{ tool: "browser_gallery_list", description: "List screenshot gallery entries" },
|
|
16568
|
+
{ tool: "browser_gallery_get", description: "Get a gallery entry by id" },
|
|
16569
|
+
{ tool: "browser_gallery_tag", description: "Add a tag to gallery entry" },
|
|
16570
|
+
{ tool: "browser_gallery_untag", description: "Remove a tag from gallery entry" },
|
|
16571
|
+
{ tool: "browser_gallery_favorite", description: "Mark/unmark as favorite" },
|
|
16572
|
+
{ tool: "browser_gallery_delete", description: "Delete a gallery entry" },
|
|
16573
|
+
{ tool: "browser_gallery_search", description: "Search gallery entries" },
|
|
16574
|
+
{ tool: "browser_gallery_stats", description: "Get gallery statistics" },
|
|
16575
|
+
{ tool: "browser_gallery_diff", description: "Pixel-diff two screenshots" }
|
|
16576
|
+
],
|
|
16577
|
+
Downloads: [
|
|
16578
|
+
{ tool: "browser_downloads_list", description: "List downloaded files" },
|
|
16579
|
+
{ tool: "browser_downloads_get", description: "Get a download by id" },
|
|
16580
|
+
{ tool: "browser_downloads_delete", description: "Delete a download" },
|
|
16581
|
+
{ tool: "browser_downloads_clean", description: "Clean old downloads" },
|
|
16582
|
+
{ tool: "browser_downloads_export", description: "Copy download to a path" },
|
|
16583
|
+
{ tool: "browser_persist_file", description: "Persist file permanently" }
|
|
16584
|
+
],
|
|
16585
|
+
Session: [
|
|
16586
|
+
{ tool: "browser_session_create", description: "Create a new browser session" },
|
|
16587
|
+
{ tool: "browser_session_list", description: "List all sessions" },
|
|
16588
|
+
{ tool: "browser_session_close", description: "Close a session" },
|
|
16589
|
+
{ tool: "browser_session_get_by_name", description: "Get session by name" },
|
|
16590
|
+
{ tool: "browser_session_rename", description: "Rename a session" },
|
|
16591
|
+
{ tool: "browser_session_stats", description: "Get session stats and token usage" },
|
|
16592
|
+
{ tool: "browser_tab_new", description: "Open a new tab" },
|
|
16593
|
+
{ tool: "browser_tab_list", description: "List all open tabs" },
|
|
16594
|
+
{ tool: "browser_tab_switch", description: "Switch to a tab by index" },
|
|
16595
|
+
{ tool: "browser_tab_close", description: "Close a tab by index" }
|
|
16596
|
+
],
|
|
16597
|
+
Meta: [
|
|
16598
|
+
{ tool: "browser_page_check", description: "One-call page summary with diagnostics" },
|
|
16599
|
+
{ tool: "browser_help", description: "Show this help (all tools)" },
|
|
16600
|
+
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
16601
|
+
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
16602
|
+
{ tool: "browser_watch_get_changes", description: "Get captured DOM changes" },
|
|
16603
|
+
{ tool: "browser_watch_stop", description: "Stop DOM watcher" }
|
|
16604
|
+
]
|
|
16605
|
+
};
|
|
16606
|
+
const totalTools = Object.values(groups).reduce((sum, g) => sum + g.length, 0);
|
|
16607
|
+
return json({ groups, total_tools: totalTools });
|
|
16608
|
+
} catch (e) {
|
|
16609
|
+
return err(e);
|
|
16610
|
+
}
|
|
16611
|
+
});
|
|
15512
16612
|
transport = new StdioServerTransport;
|
|
15513
16613
|
await server.connect(transport);
|
|
15514
16614
|
});
|
|
@@ -15551,8 +16651,8 @@ var init_snapshots = __esm(() => {
|
|
|
15551
16651
|
|
|
15552
16652
|
// src/server/index.ts
|
|
15553
16653
|
var exports_server = {};
|
|
15554
|
-
import { join as
|
|
15555
|
-
import { existsSync as
|
|
16654
|
+
import { join as join7 } from "path";
|
|
16655
|
+
import { existsSync as existsSync4 } from "fs";
|
|
15556
16656
|
function ok(data, status = 200) {
|
|
15557
16657
|
return new Response(JSON.stringify(data), {
|
|
15558
16658
|
status,
|
|
@@ -15802,14 +16902,14 @@ var init_server = __esm(() => {
|
|
|
15802
16902
|
if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
|
|
15803
16903
|
const id = path.split("/")[3];
|
|
15804
16904
|
const entry = getEntry(id);
|
|
15805
|
-
if (!entry?.thumbnail_path || !
|
|
16905
|
+
if (!entry?.thumbnail_path || !existsSync4(entry.thumbnail_path))
|
|
15806
16906
|
return notFound("Thumbnail not found");
|
|
15807
16907
|
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
|
|
15808
16908
|
}
|
|
15809
16909
|
if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
|
|
15810
16910
|
const id = path.split("/")[3];
|
|
15811
16911
|
const entry = getEntry(id);
|
|
15812
|
-
if (!entry?.path || !
|
|
16912
|
+
if (!entry?.path || !existsSync4(entry.path))
|
|
15813
16913
|
return notFound("Image not found");
|
|
15814
16914
|
return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
|
|
15815
16915
|
}
|
|
@@ -15837,7 +16937,7 @@ var init_server = __esm(() => {
|
|
|
15837
16937
|
if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
|
|
15838
16938
|
const id = path.split("/")[3];
|
|
15839
16939
|
const file = getDownload(id);
|
|
15840
|
-
if (!file || !
|
|
16940
|
+
if (!file || !existsSync4(file.path))
|
|
15841
16941
|
return notFound("Download not found");
|
|
15842
16942
|
return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
|
|
15843
16943
|
}
|
|
@@ -15845,13 +16945,13 @@ var init_server = __esm(() => {
|
|
|
15845
16945
|
const id = path.split("/")[3];
|
|
15846
16946
|
return ok({ deleted: deleteDownload(id) });
|
|
15847
16947
|
}
|
|
15848
|
-
const dashboardDist =
|
|
15849
|
-
if (
|
|
15850
|
-
const filePath = path === "/" ?
|
|
15851
|
-
if (
|
|
16948
|
+
const dashboardDist = join7(import.meta.dir, "../../dashboard/dist");
|
|
16949
|
+
if (existsSync4(dashboardDist)) {
|
|
16950
|
+
const filePath = path === "/" ? join7(dashboardDist, "index.html") : join7(dashboardDist, path);
|
|
16951
|
+
if (existsSync4(filePath)) {
|
|
15852
16952
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
15853
16953
|
}
|
|
15854
|
-
return new Response(Bun.file(
|
|
16954
|
+
return new Response(Bun.file(join7(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
15855
16955
|
}
|
|
15856
16956
|
if (path === "/" || path === "") {
|
|
15857
16957
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
@@ -15893,9 +16993,12 @@ init_projects();
|
|
|
15893
16993
|
init_recorder();
|
|
15894
16994
|
init_recordings();
|
|
15895
16995
|
init_lightpanda();
|
|
16996
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
16997
|
+
import { join as join8 } from "path";
|
|
15896
16998
|
import chalk from "chalk";
|
|
16999
|
+
var pkg = JSON.parse(readFileSync3(join8(import.meta.dir, "../../package.json"), "utf8"));
|
|
15897
17000
|
var program2 = new Command;
|
|
15898
|
-
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(
|
|
17001
|
+
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
|
|
15899
17002
|
program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headless", "Run in headless mode (default: true)", true).action(async (url, opts) => {
|
|
15900
17003
|
const { session, page } = await createSession2({ engine: opts.engine, headless: true });
|
|
15901
17004
|
console.log(chalk.gray(`Session: ${session.id} (${session.engine})`));
|
|
@@ -16136,11 +17239,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
|
|
|
16136
17239
|
});
|
|
16137
17240
|
galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
|
|
16138
17241
|
const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
16139
|
-
const { existsSync:
|
|
17242
|
+
const { existsSync: existsSync5 } = await import("fs");
|
|
16140
17243
|
const entries = listEntries2({ limit: 9999 });
|
|
16141
17244
|
let removed = 0;
|
|
16142
17245
|
for (const e of entries) {
|
|
16143
|
-
if (!
|
|
17246
|
+
if (!existsSync5(e.path)) {
|
|
16144
17247
|
deleteEntry2(e.id);
|
|
16145
17248
|
removed++;
|
|
16146
17249
|
}
|