@hasna/browser 0.0.7 → 0.1.0
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 +2095 -609
- package/dist/db/sessions.d.ts +15 -0
- package/dist/db/sessions.d.ts.map +1 -1
- package/dist/db/timeline.d.ts +11 -0
- package/dist/db/timeline.d.ts.map +1 -0
- package/dist/engines/cdp.d.ts +2 -1
- package/dist/engines/cdp.d.ts.map +1 -1
- package/dist/engines/playwright.d.ts +1 -1
- package/dist/engines/playwright.d.ts.map +1 -1
- package/dist/index.js +614 -226
- package/dist/lib/actions.d.ts +22 -4
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/auth-flow.d.ts +43 -0
- package/dist/lib/auth-flow.d.ts.map +1 -0
- package/dist/lib/sanitize.d.ts +21 -0
- package/dist/lib/sanitize.d.ts.map +1 -0
- package/dist/lib/self-heal.d.ts +18 -0
- package/dist/lib/self-heal.d.ts.map +1 -0
- package/dist/lib/session.d.ts +7 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/storage-state.d.ts +15 -0
- package/dist/lib/storage-state.d.ts.map +1 -0
- package/dist/lib/vision-fallback.d.ts +29 -0
- package/dist/lib/vision-fallback.d.ts.map +1 -0
- package/dist/mcp/index.js +2480 -1041
- package/dist/server/index.js +472 -172
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2309,6 +2309,55 @@ function runMigrations(db) {
|
|
|
2309
2309
|
CREATE INDEX IF NOT EXISTS idx_gallery_favorite ON gallery_entries(is_favorite);
|
|
2310
2310
|
CREATE INDEX IF NOT EXISTS idx_gallery_created ON gallery_entries(created_at);
|
|
2311
2311
|
`
|
|
2312
|
+
},
|
|
2313
|
+
{
|
|
2314
|
+
version: 3,
|
|
2315
|
+
sql: `
|
|
2316
|
+
-- Session lock/claim for multi-agent ownership
|
|
2317
|
+
ALTER TABLE sessions ADD COLUMN locked_by TEXT;
|
|
2318
|
+
ALTER TABLE sessions ADD COLUMN locked_at TEXT;
|
|
2319
|
+
`
|
|
2320
|
+
},
|
|
2321
|
+
{
|
|
2322
|
+
version: 4,
|
|
2323
|
+
sql: `
|
|
2324
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
2325
|
+
id TEXT PRIMARY KEY,
|
|
2326
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
2327
|
+
event_type TEXT NOT NULL,
|
|
2328
|
+
details TEXT DEFAULT '{}',
|
|
2329
|
+
timestamp TEXT DEFAULT (datetime('now'))
|
|
2330
|
+
);
|
|
2331
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id, timestamp);
|
|
2332
|
+
`
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
version: 5,
|
|
2336
|
+
sql: `
|
|
2337
|
+
CREATE TABLE IF NOT EXISTS session_tags (
|
|
2338
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
2339
|
+
tag TEXT NOT NULL,
|
|
2340
|
+
PRIMARY KEY (session_id, tag)
|
|
2341
|
+
);
|
|
2342
|
+
CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
|
|
2343
|
+
`
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
version: 6,
|
|
2347
|
+
sql: `
|
|
2348
|
+
CREATE TABLE IF NOT EXISTS auth_flows (
|
|
2349
|
+
id TEXT PRIMARY KEY,
|
|
2350
|
+
name TEXT NOT NULL UNIQUE,
|
|
2351
|
+
domain TEXT NOT NULL,
|
|
2352
|
+
recording_id TEXT REFERENCES recordings(id),
|
|
2353
|
+
storage_state_path TEXT,
|
|
2354
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
2355
|
+
last_used TEXT
|
|
2356
|
+
);
|
|
2357
|
+
|
|
2358
|
+
CREATE INDEX IF NOT EXISTS idx_auth_flows_domain ON auth_flows(domain);
|
|
2359
|
+
CREATE INDEX IF NOT EXISTS idx_auth_flows_name ON auth_flows(name);
|
|
2360
|
+
`
|
|
2312
2361
|
}
|
|
2313
2362
|
];
|
|
2314
2363
|
for (const m of migrations) {
|
|
@@ -2324,6 +2373,28 @@ var _db = null, _dbPath = null;
|
|
|
2324
2373
|
var init_schema = () => {};
|
|
2325
2374
|
|
|
2326
2375
|
// src/db/sessions.ts
|
|
2376
|
+
var exports_sessions = {};
|
|
2377
|
+
__export(exports_sessions, {
|
|
2378
|
+
updateSessionStatus: () => updateSessionStatus,
|
|
2379
|
+
unlockSession: () => unlockSession,
|
|
2380
|
+
transferSession: () => transferSession,
|
|
2381
|
+
renameSession: () => renameSession,
|
|
2382
|
+
removeSessionTag: () => removeSessionTag,
|
|
2383
|
+
lockSession: () => lockSession,
|
|
2384
|
+
listSessionsByTag: () => listSessionsByTag,
|
|
2385
|
+
listSessions: () => listSessions,
|
|
2386
|
+
isSessionLocked: () => isSessionLocked,
|
|
2387
|
+
getSessionTags: () => getSessionTags,
|
|
2388
|
+
getSessionByName: () => getSessionByName,
|
|
2389
|
+
getSession: () => getSession,
|
|
2390
|
+
getDefaultActiveSession: () => getDefaultActiveSession,
|
|
2391
|
+
getActiveSessionForAgent: () => getActiveSessionForAgent,
|
|
2392
|
+
deleteSession: () => deleteSession,
|
|
2393
|
+
createSession: () => createSession,
|
|
2394
|
+
countActiveSessions: () => countActiveSessions,
|
|
2395
|
+
closeSession: () => closeSession,
|
|
2396
|
+
addSessionTag: () => addSessionTag
|
|
2397
|
+
});
|
|
2327
2398
|
import { randomUUID } from "crypto";
|
|
2328
2399
|
function createSession(data) {
|
|
2329
2400
|
const db = getDatabase();
|
|
@@ -2376,8 +2447,81 @@ function updateSessionStatus(id, status) {
|
|
|
2376
2447
|
return getSession(id);
|
|
2377
2448
|
}
|
|
2378
2449
|
function closeSession(id) {
|
|
2450
|
+
const db = getDatabase();
|
|
2451
|
+
db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
|
|
2379
2452
|
return updateSessionStatus(id, "closed");
|
|
2380
2453
|
}
|
|
2454
|
+
function lockSession(id, agentId) {
|
|
2455
|
+
const db = getDatabase();
|
|
2456
|
+
const session = getSession(id);
|
|
2457
|
+
if (session.status !== "active")
|
|
2458
|
+
throw new SessionNotFoundError(id);
|
|
2459
|
+
const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
|
|
2460
|
+
if (row?.locked_by && row.locked_by !== agentId) {
|
|
2461
|
+
throw new Error(`Session locked by agent ${row.locked_by}`);
|
|
2462
|
+
}
|
|
2463
|
+
db.prepare("UPDATE sessions SET locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(agentId, id);
|
|
2464
|
+
return getSession(id);
|
|
2465
|
+
}
|
|
2466
|
+
function unlockSession(id, agentId) {
|
|
2467
|
+
const db = getDatabase();
|
|
2468
|
+
if (agentId) {
|
|
2469
|
+
const row = db.query("SELECT locked_by FROM sessions WHERE id = ?").get(id);
|
|
2470
|
+
if (row?.locked_by && row.locked_by !== agentId) {
|
|
2471
|
+
throw new Error(`Session locked by agent ${row.locked_by}, not ${agentId}`);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
db.prepare("UPDATE sessions SET locked_by = NULL, locked_at = NULL WHERE id = ?").run(id);
|
|
2475
|
+
return getSession(id);
|
|
2476
|
+
}
|
|
2477
|
+
function isSessionLocked(id) {
|
|
2478
|
+
const db = getDatabase();
|
|
2479
|
+
const row = db.query("SELECT locked_by, locked_at FROM sessions WHERE id = ?").get(id);
|
|
2480
|
+
if (!row)
|
|
2481
|
+
throw new SessionNotFoundError(id);
|
|
2482
|
+
return { locked: !!row.locked_by, locked_by: row.locked_by ?? undefined, locked_at: row.locked_at ?? undefined };
|
|
2483
|
+
}
|
|
2484
|
+
function transferSession(id, toAgentId) {
|
|
2485
|
+
const db = getDatabase();
|
|
2486
|
+
db.prepare("UPDATE sessions SET agent_id = ?, locked_by = ?, locked_at = datetime('now') WHERE id = ?").run(toAgentId, toAgentId, id);
|
|
2487
|
+
return getSession(id);
|
|
2488
|
+
}
|
|
2489
|
+
function getActiveSessionForAgent(agentId) {
|
|
2490
|
+
const db = getDatabase();
|
|
2491
|
+
return db.query("SELECT * FROM sessions WHERE agent_id = ? AND status = 'active' ORDER BY created_at DESC LIMIT 1").get(agentId) ?? null;
|
|
2492
|
+
}
|
|
2493
|
+
function getDefaultActiveSession() {
|
|
2494
|
+
const db = getDatabase();
|
|
2495
|
+
const rows = db.query("SELECT * FROM sessions WHERE status = 'active' ORDER BY created_at DESC LIMIT 2").all();
|
|
2496
|
+
return rows.length === 1 ? rows[0] : null;
|
|
2497
|
+
}
|
|
2498
|
+
function countActiveSessions() {
|
|
2499
|
+
const db = getDatabase();
|
|
2500
|
+
const row = db.query("SELECT COUNT(*) as count FROM sessions WHERE status = 'active'").get();
|
|
2501
|
+
return row?.count ?? 0;
|
|
2502
|
+
}
|
|
2503
|
+
function deleteSession(id) {
|
|
2504
|
+
const db = getDatabase();
|
|
2505
|
+
db.prepare("DELETE FROM sessions WHERE id = ?").run(id);
|
|
2506
|
+
}
|
|
2507
|
+
function addSessionTag(id, tag) {
|
|
2508
|
+
const db = getDatabase();
|
|
2509
|
+
db.prepare("INSERT OR IGNORE INTO session_tags (session_id, tag) VALUES (?, ?)").run(id, tag);
|
|
2510
|
+
return getSessionTags(id);
|
|
2511
|
+
}
|
|
2512
|
+
function removeSessionTag(id, tag) {
|
|
2513
|
+
const db = getDatabase();
|
|
2514
|
+
db.prepare("DELETE FROM session_tags WHERE session_id = ? AND tag = ?").run(id, tag);
|
|
2515
|
+
return getSessionTags(id);
|
|
2516
|
+
}
|
|
2517
|
+
function getSessionTags(id) {
|
|
2518
|
+
const db = getDatabase();
|
|
2519
|
+
return db.query("SELECT tag FROM session_tags WHERE session_id = ? ORDER BY tag").all(id).map((r) => r.tag);
|
|
2520
|
+
}
|
|
2521
|
+
function listSessionsByTag(tag) {
|
|
2522
|
+
const db = getDatabase();
|
|
2523
|
+
return db.query("SELECT s.* FROM sessions s JOIN session_tags t ON s.id = t.session_id WHERE t.tag = ? ORDER BY s.created_at DESC").all(tag);
|
|
2524
|
+
}
|
|
2381
2525
|
var init_sessions = __esm(() => {
|
|
2382
2526
|
init_schema();
|
|
2383
2527
|
init_types();
|
|
@@ -2403,10 +2547,52 @@ async function getPage(browser, options) {
|
|
|
2403
2547
|
});
|
|
2404
2548
|
return context.newPage();
|
|
2405
2549
|
}
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2550
|
+
|
|
2551
|
+
class BrowserPool {
|
|
2552
|
+
pool = [];
|
|
2553
|
+
maxSize;
|
|
2554
|
+
options;
|
|
2555
|
+
constructor(maxSize = 3, options) {
|
|
2556
|
+
this.maxSize = maxSize;
|
|
2557
|
+
this.options = options;
|
|
2558
|
+
}
|
|
2559
|
+
async acquire(headless = true) {
|
|
2560
|
+
const available = this.pool.find((e) => !e.inUse);
|
|
2561
|
+
if (available) {
|
|
2562
|
+
available.inUse = true;
|
|
2563
|
+
return available.browser;
|
|
2564
|
+
}
|
|
2565
|
+
if (this.pool.length < this.maxSize) {
|
|
2566
|
+
const browser = await launchPlaywright({ ...this.options, headless });
|
|
2567
|
+
this.pool.push({ browser, inUse: true, createdAt: Date.now() });
|
|
2568
|
+
return browser;
|
|
2569
|
+
}
|
|
2570
|
+
return new Promise((resolve) => {
|
|
2571
|
+
const interval = setInterval(() => {
|
|
2572
|
+
const free = this.pool.find((e) => !e.inUse);
|
|
2573
|
+
if (free) {
|
|
2574
|
+
clearInterval(interval);
|
|
2575
|
+
free.inUse = true;
|
|
2576
|
+
resolve(free.browser);
|
|
2577
|
+
}
|
|
2578
|
+
}, 100);
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
release(browser) {
|
|
2582
|
+
const entry = this.pool.find((e) => e.browser === browser);
|
|
2583
|
+
if (entry)
|
|
2584
|
+
entry.inUse = false;
|
|
2585
|
+
}
|
|
2586
|
+
async destroyAll() {
|
|
2587
|
+
await Promise.all(this.pool.map((e) => e.browser.close().catch(() => {})));
|
|
2588
|
+
this.pool = [];
|
|
2589
|
+
}
|
|
2590
|
+
get size() {
|
|
2591
|
+
return this.pool.length;
|
|
2592
|
+
}
|
|
2593
|
+
get available() {
|
|
2594
|
+
return this.pool.filter((e) => !e.inUse).length;
|
|
2595
|
+
}
|
|
2410
2596
|
}
|
|
2411
2597
|
var DEFAULT_VIEWPORT;
|
|
2412
2598
|
var init_playwright = __esm(() => {
|
|
@@ -3287,6 +3473,188 @@ var init_dialogs = __esm(() => {
|
|
|
3287
3473
|
pendingDialogs = new Map;
|
|
3288
3474
|
});
|
|
3289
3475
|
|
|
3476
|
+
// src/engines/cdp.ts
|
|
3477
|
+
var exports_cdp = {};
|
|
3478
|
+
__export(exports_cdp, {
|
|
3479
|
+
connectToExistingBrowser: () => connectToExistingBrowser,
|
|
3480
|
+
CDPClient: () => CDPClient
|
|
3481
|
+
});
|
|
3482
|
+
async function connectToExistingBrowser(cdpUrl) {
|
|
3483
|
+
const { chromium: chromium3 } = await import("playwright");
|
|
3484
|
+
try {
|
|
3485
|
+
return await chromium3.connectOverCDP(cdpUrl);
|
|
3486
|
+
} catch (err) {
|
|
3487
|
+
throw new BrowserError(`Failed to connect to browser at ${cdpUrl}: ${err instanceof Error ? err.message : String(err)}. Start Chrome with: google-chrome --remote-debugging-port=9222`, "CDP_CONNECT_FAILED", true);
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
class CDPClient {
|
|
3492
|
+
session;
|
|
3493
|
+
networkEnabled = false;
|
|
3494
|
+
performanceEnabled = false;
|
|
3495
|
+
constructor(session) {
|
|
3496
|
+
this.session = session;
|
|
3497
|
+
}
|
|
3498
|
+
static async fromPage(page) {
|
|
3499
|
+
try {
|
|
3500
|
+
const session = await page.context().newCDPSession(page);
|
|
3501
|
+
return new CDPClient(session);
|
|
3502
|
+
} catch (err) {
|
|
3503
|
+
throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
async send(method, params) {
|
|
3507
|
+
try {
|
|
3508
|
+
return await this.session.send(method, params);
|
|
3509
|
+
} catch (err) {
|
|
3510
|
+
throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
on(event, handler) {
|
|
3514
|
+
this.session.on(event, handler);
|
|
3515
|
+
}
|
|
3516
|
+
off(event, handler) {
|
|
3517
|
+
this.session.off(event, handler);
|
|
3518
|
+
}
|
|
3519
|
+
async enableNetwork() {
|
|
3520
|
+
if (!this.networkEnabled) {
|
|
3521
|
+
await this.send("Network.enable");
|
|
3522
|
+
this.networkEnabled = true;
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
async enablePerformance() {
|
|
3526
|
+
if (!this.performanceEnabled) {
|
|
3527
|
+
await this.send("Performance.enable");
|
|
3528
|
+
this.performanceEnabled = true;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
async getPerformanceMetrics() {
|
|
3532
|
+
await this.enablePerformance();
|
|
3533
|
+
const result = await this.send("Performance.getMetrics");
|
|
3534
|
+
const m = {};
|
|
3535
|
+
for (const metric of result.metrics) {
|
|
3536
|
+
m[metric.name] = metric.value;
|
|
3537
|
+
}
|
|
3538
|
+
return {
|
|
3539
|
+
js_heap_size_used: m["JSHeapUsedSize"],
|
|
3540
|
+
js_heap_size_total: m["JSHeapTotalSize"],
|
|
3541
|
+
dom_interactive: m["DOMInteractive"],
|
|
3542
|
+
dom_complete: m["DOMComplete"],
|
|
3543
|
+
load_event: m["LoadEventEnd"]
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
async startJSCoverage() {
|
|
3547
|
+
await this.send("Profiler.enable");
|
|
3548
|
+
await this.send("Debugger.enable");
|
|
3549
|
+
await this.send("Profiler.startPreciseCoverage", {
|
|
3550
|
+
callCount: false,
|
|
3551
|
+
detailed: true
|
|
3552
|
+
});
|
|
3553
|
+
}
|
|
3554
|
+
async stopJSCoverage() {
|
|
3555
|
+
const result = await this.send("Profiler.takePreciseCoverage");
|
|
3556
|
+
await this.send("Profiler.stopPreciseCoverage");
|
|
3557
|
+
return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
|
|
3558
|
+
url: r.url,
|
|
3559
|
+
text: "",
|
|
3560
|
+
ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
|
|
3561
|
+
}));
|
|
3562
|
+
}
|
|
3563
|
+
async getCoverage() {
|
|
3564
|
+
await this.startJSCoverage();
|
|
3565
|
+
const js = await this.stopJSCoverage();
|
|
3566
|
+
const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
|
|
3567
|
+
return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
|
|
3568
|
+
}
|
|
3569
|
+
async captureHAREntries(page, handler) {
|
|
3570
|
+
await this.enableNetwork();
|
|
3571
|
+
const requestTimings = new Map;
|
|
3572
|
+
const onRequest = (params) => {
|
|
3573
|
+
requestTimings.set(params.requestId, params.timestamp);
|
|
3574
|
+
};
|
|
3575
|
+
const onResponse = (params) => {
|
|
3576
|
+
const start = requestTimings.get(params.requestId);
|
|
3577
|
+
const duration = start != null ? (params.timestamp - start) * 1000 : 0;
|
|
3578
|
+
handler({
|
|
3579
|
+
method: "GET",
|
|
3580
|
+
url: params.response.url,
|
|
3581
|
+
status: params.response.status,
|
|
3582
|
+
duration
|
|
3583
|
+
});
|
|
3584
|
+
};
|
|
3585
|
+
this.on("Network.requestWillBeSent", onRequest);
|
|
3586
|
+
this.on("Network.responseReceived", onResponse);
|
|
3587
|
+
return () => {
|
|
3588
|
+
this.off("Network.requestWillBeSent", onRequest);
|
|
3589
|
+
this.off("Network.responseReceived", onResponse);
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
3592
|
+
async detach() {
|
|
3593
|
+
try {
|
|
3594
|
+
await this.session.detach();
|
|
3595
|
+
} catch {}
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
var init_cdp = __esm(() => {
|
|
3599
|
+
init_types();
|
|
3600
|
+
});
|
|
3601
|
+
|
|
3602
|
+
// src/lib/storage-state.ts
|
|
3603
|
+
var exports_storage_state = {};
|
|
3604
|
+
__export(exports_storage_state, {
|
|
3605
|
+
saveStateFromPage: () => saveStateFromPage,
|
|
3606
|
+
saveState: () => saveState,
|
|
3607
|
+
loadStatePath: () => loadStatePath,
|
|
3608
|
+
listStates: () => listStates,
|
|
3609
|
+
deleteState: () => deleteState
|
|
3610
|
+
});
|
|
3611
|
+
import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
|
|
3612
|
+
import { join as join3 } from "path";
|
|
3613
|
+
import { homedir as homedir3 } from "os";
|
|
3614
|
+
function ensureDir() {
|
|
3615
|
+
mkdirSync3(STATES_DIR, { recursive: true });
|
|
3616
|
+
}
|
|
3617
|
+
function statePath(name) {
|
|
3618
|
+
return join3(STATES_DIR, `${name}.json`);
|
|
3619
|
+
}
|
|
3620
|
+
async function saveState(context, name) {
|
|
3621
|
+
ensureDir();
|
|
3622
|
+
const path = statePath(name);
|
|
3623
|
+
const state = await context.storageState({ path });
|
|
3624
|
+
return path;
|
|
3625
|
+
}
|
|
3626
|
+
async function saveStateFromPage(page, name) {
|
|
3627
|
+
return saveState(page.context(), name);
|
|
3628
|
+
}
|
|
3629
|
+
function loadStatePath(name) {
|
|
3630
|
+
const path = statePath(name);
|
|
3631
|
+
return existsSync(path) ? path : null;
|
|
3632
|
+
}
|
|
3633
|
+
function listStates() {
|
|
3634
|
+
ensureDir();
|
|
3635
|
+
return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3636
|
+
const path = join3(STATES_DIR, f);
|
|
3637
|
+
const stat = Bun.file(path);
|
|
3638
|
+
return {
|
|
3639
|
+
name: f.replace(".json", ""),
|
|
3640
|
+
path,
|
|
3641
|
+
modified: new Date(stat.lastModified).toISOString()
|
|
3642
|
+
};
|
|
3643
|
+
}).sort((a, b) => b.modified.localeCompare(a.modified));
|
|
3644
|
+
}
|
|
3645
|
+
function deleteState(name) {
|
|
3646
|
+
const path = statePath(name);
|
|
3647
|
+
if (existsSync(path)) {
|
|
3648
|
+
unlinkSync(path);
|
|
3649
|
+
return true;
|
|
3650
|
+
}
|
|
3651
|
+
return false;
|
|
3652
|
+
}
|
|
3653
|
+
var STATES_DIR;
|
|
3654
|
+
var init_storage_state = __esm(() => {
|
|
3655
|
+
STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
|
|
3656
|
+
});
|
|
3657
|
+
|
|
3290
3658
|
// src/lib/session.ts
|
|
3291
3659
|
var exports_session = {};
|
|
3292
3660
|
__export(exports_session, {
|
|
@@ -3294,6 +3662,7 @@ __export(exports_session, {
|
|
|
3294
3662
|
renameSession: () => renameSession2,
|
|
3295
3663
|
listSessions: () => listSessions2,
|
|
3296
3664
|
isBunSession: () => isBunSession,
|
|
3665
|
+
isAutoGallery: () => isAutoGallery,
|
|
3297
3666
|
hasActiveHandle: () => hasActiveHandle,
|
|
3298
3667
|
getTokenBudget: () => getTokenBudget,
|
|
3299
3668
|
getSessionPage: () => getSessionPage,
|
|
@@ -3302,15 +3671,50 @@ __export(exports_session, {
|
|
|
3302
3671
|
getSessionBunView: () => getSessionBunView,
|
|
3303
3672
|
getSessionBrowser: () => getSessionBrowser,
|
|
3304
3673
|
getSession: () => getSession2,
|
|
3674
|
+
getDefaultSession: () => getDefaultSession,
|
|
3305
3675
|
getActiveSessions: () => getActiveSessions,
|
|
3676
|
+
getActiveSessionForAgent: () => getActiveSessionForAgent2,
|
|
3306
3677
|
createSession: () => createSession2,
|
|
3678
|
+
countActiveSessions: () => countActiveSessions2,
|
|
3307
3679
|
closeSession: () => closeSession2,
|
|
3308
|
-
closeAllSessions: () => closeAllSessions
|
|
3680
|
+
closeAllSessions: () => closeAllSessions,
|
|
3681
|
+
browserPool: () => pool
|
|
3309
3682
|
});
|
|
3310
3683
|
function createBunProxy(view) {
|
|
3311
3684
|
return view;
|
|
3312
3685
|
}
|
|
3313
3686
|
async function createSession2(opts = {}) {
|
|
3687
|
+
if (opts.cdpUrl) {
|
|
3688
|
+
const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
|
|
3689
|
+
const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
|
|
3690
|
+
const contexts = cdpBrowser.contexts();
|
|
3691
|
+
const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
|
|
3692
|
+
const pages = context.pages();
|
|
3693
|
+
const page2 = pages.length > 0 ? pages[0] : await context.newPage();
|
|
3694
|
+
const session2 = createSession({
|
|
3695
|
+
engine: "cdp",
|
|
3696
|
+
projectId: opts.projectId,
|
|
3697
|
+
agentId: opts.agentId,
|
|
3698
|
+
startUrl: page2.url(),
|
|
3699
|
+
name: opts.name ?? "attached"
|
|
3700
|
+
});
|
|
3701
|
+
const cleanups2 = [];
|
|
3702
|
+
if (opts.captureNetwork !== false) {
|
|
3703
|
+
try {
|
|
3704
|
+
cleanups2.push(enableNetworkLogging(page2, session2.id));
|
|
3705
|
+
} catch {}
|
|
3706
|
+
}
|
|
3707
|
+
if (opts.captureConsole !== false) {
|
|
3708
|
+
try {
|
|
3709
|
+
cleanups2.push(enableConsoleCapture(page2, session2.id));
|
|
3710
|
+
} catch {}
|
|
3711
|
+
}
|
|
3712
|
+
try {
|
|
3713
|
+
cleanups2.push(setupDialogHandler(page2, session2.id));
|
|
3714
|
+
} catch {}
|
|
3715
|
+
handles.set(session2.id, { browser: cdpBrowser, bunView: null, page: page2, engine: "cdp", cleanups: cleanups2, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
3716
|
+
return { session: session2, page: page2 };
|
|
3717
|
+
}
|
|
3314
3718
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
3315
3719
|
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
3316
3720
|
let browser = null;
|
|
@@ -3335,12 +3739,23 @@ async function createSession2(opts = {}) {
|
|
|
3335
3739
|
const context = await browser.newContext({ viewport: opts.viewport ?? { width: 1280, height: 720 } });
|
|
3336
3740
|
page = await context.newPage();
|
|
3337
3741
|
} else {
|
|
3338
|
-
browser = await
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3742
|
+
browser = await pool.acquire(opts.headless ?? true);
|
|
3743
|
+
if (opts.storageState) {
|
|
3744
|
+
const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
3745
|
+
const statePath2 = loadStatePath2(opts.storageState);
|
|
3746
|
+
if (statePath2) {
|
|
3747
|
+
const context = await browser.newContext({
|
|
3748
|
+
viewport: opts.viewport ?? { width: 1280, height: 720 },
|
|
3749
|
+
userAgent: opts.userAgent,
|
|
3750
|
+
storageState: statePath2
|
|
3751
|
+
});
|
|
3752
|
+
page = await context.newPage();
|
|
3753
|
+
} else {
|
|
3754
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
3755
|
+
}
|
|
3756
|
+
} else {
|
|
3757
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
3758
|
+
}
|
|
3344
3759
|
}
|
|
3345
3760
|
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
3346
3761
|
try {
|
|
@@ -3393,7 +3808,7 @@ async function createSession2(opts = {}) {
|
|
|
3393
3808
|
} catch {}
|
|
3394
3809
|
}
|
|
3395
3810
|
}
|
|
3396
|
-
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
3811
|
+
handles.set(session.id, { browser, bunView, page, engine: bunView ? "bun" : resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 }, lastActivity: Date.now(), autoGallery: opts.autoGallery ?? false });
|
|
3397
3812
|
if (opts.startUrl) {
|
|
3398
3813
|
try {
|
|
3399
3814
|
if (bunView) {
|
|
@@ -3419,6 +3834,7 @@ function getSessionPage(sessionId) {
|
|
|
3419
3834
|
handles.delete(sessionId);
|
|
3420
3835
|
throw new SessionNotFoundError(sessionId);
|
|
3421
3836
|
}
|
|
3837
|
+
handle.lastActivity = Date.now();
|
|
3422
3838
|
return handle.page;
|
|
3423
3839
|
}
|
|
3424
3840
|
function getSessionBunView(sessionId) {
|
|
@@ -3466,10 +3882,8 @@ async function closeSession2(sessionId) {
|
|
|
3466
3882
|
try {
|
|
3467
3883
|
await handle.page.context().close();
|
|
3468
3884
|
} catch {}
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
await closeBrowser(handle.browser);
|
|
3472
|
-
} catch {}
|
|
3885
|
+
if (handle.browser)
|
|
3886
|
+
pool.release(handle.browser);
|
|
3473
3887
|
}
|
|
3474
3888
|
handles.delete(sessionId);
|
|
3475
3889
|
}
|
|
@@ -3488,6 +3902,7 @@ async function closeAllSessions() {
|
|
|
3488
3902
|
for (const [id] of handles) {
|
|
3489
3903
|
await closeSession2(id).catch(() => {});
|
|
3490
3904
|
}
|
|
3905
|
+
await pool.destroyAll();
|
|
3491
3906
|
}
|
|
3492
3907
|
function getSessionByName2(name) {
|
|
3493
3908
|
return getSessionByName(name);
|
|
@@ -3499,7 +3914,49 @@ function getTokenBudget(sessionId) {
|
|
|
3499
3914
|
const handle = handles.get(sessionId);
|
|
3500
3915
|
return handle ? handle.tokenBudget : null;
|
|
3501
3916
|
}
|
|
3502
|
-
|
|
3917
|
+
function getActiveSessionForAgent2(agentId) {
|
|
3918
|
+
const session = getActiveSessionForAgent(agentId);
|
|
3919
|
+
if (!session)
|
|
3920
|
+
return null;
|
|
3921
|
+
const handle = handles.get(session.id);
|
|
3922
|
+
if (!handle)
|
|
3923
|
+
return null;
|
|
3924
|
+
try {
|
|
3925
|
+
if (handle.bunView)
|
|
3926
|
+
handle.bunView.url();
|
|
3927
|
+
else
|
|
3928
|
+
handle.page.url();
|
|
3929
|
+
} catch {
|
|
3930
|
+
handles.delete(session.id);
|
|
3931
|
+
return null;
|
|
3932
|
+
}
|
|
3933
|
+
return { session, page: handle.page };
|
|
3934
|
+
}
|
|
3935
|
+
function getDefaultSession() {
|
|
3936
|
+
const session = getDefaultActiveSession();
|
|
3937
|
+
if (!session)
|
|
3938
|
+
return null;
|
|
3939
|
+
const handle = handles.get(session.id);
|
|
3940
|
+
if (!handle)
|
|
3941
|
+
return null;
|
|
3942
|
+
try {
|
|
3943
|
+
if (handle.bunView)
|
|
3944
|
+
handle.bunView.url();
|
|
3945
|
+
else
|
|
3946
|
+
handle.page.url();
|
|
3947
|
+
} catch {
|
|
3948
|
+
handles.delete(session.id);
|
|
3949
|
+
return null;
|
|
3950
|
+
}
|
|
3951
|
+
return { session, page: handle.page };
|
|
3952
|
+
}
|
|
3953
|
+
function isAutoGallery(sessionId) {
|
|
3954
|
+
return handles.get(sessionId)?.autoGallery ?? false;
|
|
3955
|
+
}
|
|
3956
|
+
function countActiveSessions2() {
|
|
3957
|
+
return countActiveSessions();
|
|
3958
|
+
}
|
|
3959
|
+
var handles, pool, SESSION_TTL_MS, ttlInterval;
|
|
3503
3960
|
var init_session = __esm(() => {
|
|
3504
3961
|
init_types();
|
|
3505
3962
|
init_types();
|
|
@@ -3513,6 +3970,20 @@ var init_session = __esm(() => {
|
|
|
3513
3970
|
init_stealth();
|
|
3514
3971
|
init_dialogs();
|
|
3515
3972
|
handles = new Map;
|
|
3973
|
+
pool = new BrowserPool(5);
|
|
3974
|
+
SESSION_TTL_MS = parseInt(process.env["SESSION_TTL_MINUTES"] ?? "10", 10) * 60000;
|
|
3975
|
+
ttlInterval = setInterval(async () => {
|
|
3976
|
+
const now = Date.now();
|
|
3977
|
+
for (const [id, handle] of handles) {
|
|
3978
|
+
if (now - handle.lastActivity > SESSION_TTL_MS) {
|
|
3979
|
+
try {
|
|
3980
|
+
await closeSession2(id);
|
|
3981
|
+
} catch {}
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
}, 60000);
|
|
3985
|
+
if (ttlInterval.unref)
|
|
3986
|
+
ttlInterval.unref();
|
|
3516
3987
|
});
|
|
3517
3988
|
|
|
3518
3989
|
// src/lib/snapshot.ts
|
|
@@ -3789,6 +4260,66 @@ var init_snapshot = __esm(() => {
|
|
|
3789
4260
|
];
|
|
3790
4261
|
});
|
|
3791
4262
|
|
|
4263
|
+
// src/lib/self-heal.ts
|
|
4264
|
+
async function healSelector(page, selector, sessionId) {
|
|
4265
|
+
const attempts = [];
|
|
4266
|
+
attempts.push(`selector: ${selector}`);
|
|
4267
|
+
try {
|
|
4268
|
+
const loc = page.locator(selector).first();
|
|
4269
|
+
if (await loc.count() > 0) {
|
|
4270
|
+
return { found: true, locator: loc, method: "original", healed: false, attempts };
|
|
4271
|
+
}
|
|
4272
|
+
} catch {}
|
|
4273
|
+
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[") && !selector.includes(">") && !selector.includes(" ")) {
|
|
4274
|
+
attempts.push(`text: "${selector}"`);
|
|
4275
|
+
try {
|
|
4276
|
+
const loc = page.getByText(selector, { exact: false }).first();
|
|
4277
|
+
if (await loc.count() > 0) {
|
|
4278
|
+
return { found: true, locator: loc, method: "text", healed: true, attempts };
|
|
4279
|
+
}
|
|
4280
|
+
} catch {}
|
|
4281
|
+
}
|
|
4282
|
+
const roleMap = {
|
|
4283
|
+
button: ["button", "submit", "reset"],
|
|
4284
|
+
link: ["a"],
|
|
4285
|
+
input: ["input", "textarea"],
|
|
4286
|
+
heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
|
|
4287
|
+
};
|
|
4288
|
+
const nameHint = selector.replace(/^[#.]/, "").replace(/[-_]/g, " ").toLowerCase();
|
|
4289
|
+
for (const [role, tags] of Object.entries(roleMap)) {
|
|
4290
|
+
attempts.push(`role: ${role} name~="${nameHint}"`);
|
|
4291
|
+
try {
|
|
4292
|
+
const loc = page.getByRole(role, { name: new RegExp(nameHint.split(" ")[0], "i") }).first();
|
|
4293
|
+
if (await loc.count() > 0) {
|
|
4294
|
+
return { found: true, locator: loc, method: "role", healed: true, attempts };
|
|
4295
|
+
}
|
|
4296
|
+
} catch {}
|
|
4297
|
+
}
|
|
4298
|
+
if (selector.startsWith("#")) {
|
|
4299
|
+
const idPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
|
|
4300
|
+
const partialSel = `[id*="${idPart}"]`;
|
|
4301
|
+
attempts.push(`partial_id: ${partialSel}`);
|
|
4302
|
+
try {
|
|
4303
|
+
const loc = page.locator(partialSel).first();
|
|
4304
|
+
if (await loc.count() > 0) {
|
|
4305
|
+
return { found: true, locator: loc, method: "partial_id", healed: true, attempts };
|
|
4306
|
+
}
|
|
4307
|
+
} catch {}
|
|
4308
|
+
}
|
|
4309
|
+
if (selector.startsWith(".")) {
|
|
4310
|
+
const classPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
|
|
4311
|
+
const partialSel = `[class*="${classPart}"]`;
|
|
4312
|
+
attempts.push(`partial_class: ${partialSel}`);
|
|
4313
|
+
try {
|
|
4314
|
+
const loc = page.locator(partialSel).first();
|
|
4315
|
+
if (await loc.count() > 0) {
|
|
4316
|
+
return { found: true, locator: loc, method: "partial_class", healed: true, attempts };
|
|
4317
|
+
}
|
|
4318
|
+
} catch {}
|
|
4319
|
+
}
|
|
4320
|
+
return { found: false, locator: null, method: "none", healed: false, attempts };
|
|
4321
|
+
}
|
|
4322
|
+
|
|
3792
4323
|
// src/lib/actions.ts
|
|
3793
4324
|
var exports_actions = {};
|
|
3794
4325
|
__export(exports_actions, {
|
|
@@ -3830,11 +4361,22 @@ async function click(page, selector, opts) {
|
|
|
3830
4361
|
delay: opts?.delay,
|
|
3831
4362
|
timeout: opts?.timeout ?? 1e4
|
|
3832
4363
|
});
|
|
3833
|
-
|
|
3834
|
-
|
|
4364
|
+
return {};
|
|
4365
|
+
} catch (originalError) {
|
|
4366
|
+
if (opts?.selfHeal !== false) {
|
|
4367
|
+
const result = await healSelector(page, selector);
|
|
4368
|
+
if (result.found && result.locator) {
|
|
4369
|
+
await result.locator.click({
|
|
4370
|
+
button: opts?.button ?? "left",
|
|
4371
|
+
timeout: opts?.timeout ?? 1e4
|
|
4372
|
+
});
|
|
4373
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
if (originalError instanceof Error && originalError.message.includes("not found")) {
|
|
3835
4377
|
throw new ElementNotFoundError(selector);
|
|
3836
4378
|
}
|
|
3837
|
-
throw new BrowserError(`Click failed on '${selector}': ${
|
|
4379
|
+
throw new BrowserError(`Click failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "CLICK_FAILED");
|
|
3838
4380
|
}
|
|
3839
4381
|
}
|
|
3840
4382
|
async function type(page, selector, text, opts) {
|
|
@@ -3843,17 +4385,35 @@ async function type(page, selector, text, opts) {
|
|
|
3843
4385
|
await page.fill(selector, "", { timeout: opts?.timeout ?? 1e4 });
|
|
3844
4386
|
}
|
|
3845
4387
|
await page.type(selector, text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
3846
|
-
|
|
3847
|
-
|
|
4388
|
+
return {};
|
|
4389
|
+
} catch (originalError) {
|
|
4390
|
+
if (opts?.selfHeal !== false) {
|
|
4391
|
+
const result = await healSelector(page, selector);
|
|
4392
|
+
if (result.found && result.locator) {
|
|
4393
|
+
if (opts?.clear)
|
|
4394
|
+
await result.locator.fill("", { timeout: opts?.timeout ?? 1e4 });
|
|
4395
|
+
await result.locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
4396
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
if (originalError instanceof Error && originalError.message.includes("not found")) {
|
|
3848
4400
|
throw new ElementNotFoundError(selector);
|
|
3849
4401
|
}
|
|
3850
|
-
throw new BrowserError(`Type failed on '${selector}': ${
|
|
4402
|
+
throw new BrowserError(`Type failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "TYPE_FAILED");
|
|
3851
4403
|
}
|
|
3852
4404
|
}
|
|
3853
|
-
async function fill(page, selector, value, timeout = 1e4) {
|
|
4405
|
+
async function fill(page, selector, value, timeout = 1e4, selfHeal = true) {
|
|
3854
4406
|
try {
|
|
3855
4407
|
await page.fill(selector, value, { timeout });
|
|
3856
|
-
|
|
4408
|
+
return {};
|
|
4409
|
+
} catch (originalError) {
|
|
4410
|
+
if (selfHeal) {
|
|
4411
|
+
const result = await healSelector(page, selector);
|
|
4412
|
+
if (result.found && result.locator) {
|
|
4413
|
+
await result.locator.fill(value, { timeout });
|
|
4414
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
3857
4417
|
throw new ElementNotFoundError(selector);
|
|
3858
4418
|
}
|
|
3859
4419
|
}
|
|
@@ -3978,12 +4538,39 @@ async function clickText(page, text, opts) {
|
|
|
3978
4538
|
}
|
|
3979
4539
|
}, { retries: opts?.retries ?? 1 });
|
|
3980
4540
|
}
|
|
3981
|
-
async function fillForm(page, fields, submitSelector) {
|
|
4541
|
+
async function fillForm(page, fields, submitSelector, selfHeal = true) {
|
|
3982
4542
|
let filled = 0;
|
|
3983
4543
|
const errors = [];
|
|
4544
|
+
const healedFields = [];
|
|
3984
4545
|
for (const [selector, value] of Object.entries(fields)) {
|
|
3985
4546
|
try {
|
|
3986
|
-
|
|
4547
|
+
let el = await page.$(selector);
|
|
4548
|
+
if (!el && selfHeal) {
|
|
4549
|
+
const result = await healSelector(page, selector);
|
|
4550
|
+
if (result.found && result.locator) {
|
|
4551
|
+
const handle = await result.locator.elementHandle();
|
|
4552
|
+
if (handle) {
|
|
4553
|
+
el = handle;
|
|
4554
|
+
healedFields.push(selector);
|
|
4555
|
+
const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
|
|
4556
|
+
const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
|
|
4557
|
+
if (tagName2 === "select") {
|
|
4558
|
+
await result.locator.selectOption(String(value));
|
|
4559
|
+
} else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
|
|
4560
|
+
if (Boolean(value))
|
|
4561
|
+
await result.locator.check();
|
|
4562
|
+
else
|
|
4563
|
+
await result.locator.uncheck();
|
|
4564
|
+
} else {
|
|
4565
|
+
await result.locator.fill(String(value));
|
|
4566
|
+
}
|
|
4567
|
+
filled++;
|
|
4568
|
+
continue;
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
errors.push(`${selector}: element not found`);
|
|
4572
|
+
continue;
|
|
4573
|
+
}
|
|
3987
4574
|
if (!el) {
|
|
3988
4575
|
errors.push(`${selector}: element not found`);
|
|
3989
4576
|
continue;
|
|
@@ -4010,11 +4597,21 @@ async function fillForm(page, fields, submitSelector) {
|
|
|
4010
4597
|
if (submitSelector) {
|
|
4011
4598
|
try {
|
|
4012
4599
|
await page.click(submitSelector);
|
|
4013
|
-
} catch (
|
|
4014
|
-
|
|
4600
|
+
} catch (submitErr) {
|
|
4601
|
+
if (selfHeal) {
|
|
4602
|
+
const result = await healSelector(page, submitSelector);
|
|
4603
|
+
if (result.found && result.locator) {
|
|
4604
|
+
await result.locator.click();
|
|
4605
|
+
healedFields.push(submitSelector);
|
|
4606
|
+
} else {
|
|
4607
|
+
errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
4608
|
+
}
|
|
4609
|
+
} else {
|
|
4610
|
+
errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
4611
|
+
}
|
|
4015
4612
|
}
|
|
4016
4613
|
}
|
|
4017
|
-
return { filled, errors, fields_attempted: Object.keys(fields).length };
|
|
4614
|
+
return { filled, errors, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
|
|
4018
4615
|
}
|
|
4019
4616
|
async function waitForText(page, text, opts) {
|
|
4020
4617
|
const timeout = opts?.timeout ?? 1e4;
|
|
@@ -10870,17 +11467,17 @@ var init_gallery = __esm(() => {
|
|
|
10870
11467
|
});
|
|
10871
11468
|
|
|
10872
11469
|
// src/lib/screenshot.ts
|
|
10873
|
-
import { join as
|
|
10874
|
-
import { mkdirSync as
|
|
10875
|
-
import { homedir as
|
|
11470
|
+
import { join as join4 } from "path";
|
|
11471
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
11472
|
+
import { homedir as homedir4 } from "os";
|
|
10876
11473
|
function getDataDir2() {
|
|
10877
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
11474
|
+
return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
|
|
10878
11475
|
}
|
|
10879
11476
|
function getScreenshotDir(projectId) {
|
|
10880
|
-
const base =
|
|
11477
|
+
const base = join4(getDataDir2(), "screenshots");
|
|
10881
11478
|
const date = new Date().toISOString().split("T")[0];
|
|
10882
|
-
const dir = projectId ?
|
|
10883
|
-
|
|
11479
|
+
const dir = projectId ? join4(base, projectId, date) : join4(base, date);
|
|
11480
|
+
mkdirSync4(dir, { recursive: true });
|
|
10884
11481
|
return dir;
|
|
10885
11482
|
}
|
|
10886
11483
|
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
@@ -10895,7 +11492,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
|
|
|
10895
11492
|
}
|
|
10896
11493
|
}
|
|
10897
11494
|
async function generateThumbnail(raw, dir, stem) {
|
|
10898
|
-
const thumbPath =
|
|
11495
|
+
const thumbPath = join4(dir, `${stem}.thumb.webp`);
|
|
10899
11496
|
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
10900
11497
|
await Bun.write(thumbPath, thumbBuffer);
|
|
10901
11498
|
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
@@ -10952,7 +11549,7 @@ async function takeScreenshot(page, opts) {
|
|
|
10952
11549
|
const compressedSizeBytes = finalBuffer.length;
|
|
10953
11550
|
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
10954
11551
|
const ext = format;
|
|
10955
|
-
const screenshotPath = opts?.path ??
|
|
11552
|
+
const screenshotPath = opts?.path ?? join4(dir, `${stem}.${ext}`);
|
|
10956
11553
|
await Bun.write(screenshotPath, finalBuffer);
|
|
10957
11554
|
let thumbnailPath;
|
|
10958
11555
|
let thumbnailBase64;
|
|
@@ -11012,12 +11609,12 @@ async function takeScreenshot(page, opts) {
|
|
|
11012
11609
|
}
|
|
11013
11610
|
async function generatePDF(page, opts) {
|
|
11014
11611
|
try {
|
|
11015
|
-
const base =
|
|
11612
|
+
const base = join4(getDataDir2(), "pdfs");
|
|
11016
11613
|
const date = new Date().toISOString().split("T")[0];
|
|
11017
|
-
const dir = opts?.projectId ?
|
|
11018
|
-
|
|
11614
|
+
const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
|
|
11615
|
+
mkdirSync4(dir, { recursive: true });
|
|
11019
11616
|
const timestamp = Date.now();
|
|
11020
|
-
const pdfPath = opts?.path ??
|
|
11617
|
+
const pdfPath = opts?.path ?? join4(dir, `${timestamp}.pdf`);
|
|
11021
11618
|
const buffer = await page.pdf({
|
|
11022
11619
|
path: pdfPath,
|
|
11023
11620
|
format: opts?.format ?? "A4",
|
|
@@ -11344,6 +11941,17 @@ var init_recordings = __esm(() => {
|
|
|
11344
11941
|
});
|
|
11345
11942
|
|
|
11346
11943
|
// src/lib/recorder.ts
|
|
11944
|
+
var exports_recorder = {};
|
|
11945
|
+
__export(exports_recorder, {
|
|
11946
|
+
stopRecording: () => stopRecording,
|
|
11947
|
+
startRecording: () => startRecording,
|
|
11948
|
+
replayRecording: () => replayRecording,
|
|
11949
|
+
recordStep: () => recordStep,
|
|
11950
|
+
listRecordings: () => listRecordings,
|
|
11951
|
+
getRecording: () => getRecording,
|
|
11952
|
+
exportRecording: () => exportRecording,
|
|
11953
|
+
attachPageListeners: () => attachPageListeners
|
|
11954
|
+
});
|
|
11347
11955
|
function startRecording(sessionId, name, startUrl) {
|
|
11348
11956
|
const steps = [];
|
|
11349
11957
|
const recording = createRecording({ name, start_url: startUrl, steps });
|
|
@@ -11354,6 +11962,23 @@ function startRecording(sessionId, name, startUrl) {
|
|
|
11354
11962
|
});
|
|
11355
11963
|
return recording;
|
|
11356
11964
|
}
|
|
11965
|
+
function attachPageListeners(page, recordingId) {
|
|
11966
|
+
const active = activeRecordings.get(recordingId);
|
|
11967
|
+
if (!active)
|
|
11968
|
+
throw new BrowserError(`No active recording: ${recordingId}`, "RECORDING_NOT_ACTIVE");
|
|
11969
|
+
const onFrameNav = () => {
|
|
11970
|
+
active.steps.push({
|
|
11971
|
+
type: "navigate",
|
|
11972
|
+
url: page.url(),
|
|
11973
|
+
timestamp: Date.now()
|
|
11974
|
+
});
|
|
11975
|
+
};
|
|
11976
|
+
page.on("framenavigated", onFrameNav);
|
|
11977
|
+
const cleanup = () => {
|
|
11978
|
+
page.off("framenavigated", onFrameNav);
|
|
11979
|
+
};
|
|
11980
|
+
active.cleanup = cleanup;
|
|
11981
|
+
}
|
|
11357
11982
|
function recordStep(recordingId, step) {
|
|
11358
11983
|
const active = activeRecordings.get(recordingId);
|
|
11359
11984
|
if (!active)
|
|
@@ -11425,6 +12050,64 @@ async function replayRecording(recordingId, page) {
|
|
|
11425
12050
|
duration_ms: Date.now() - startTime
|
|
11426
12051
|
};
|
|
11427
12052
|
}
|
|
12053
|
+
function exportRecording(recordingId, format = "json") {
|
|
12054
|
+
const recording = getRecording(recordingId);
|
|
12055
|
+
if (format === "json") {
|
|
12056
|
+
return JSON.stringify(recording, null, 2);
|
|
12057
|
+
}
|
|
12058
|
+
if (format === "playwright") {
|
|
12059
|
+
const lines2 = [
|
|
12060
|
+
`import { test, expect } from '@playwright/test';`,
|
|
12061
|
+
``,
|
|
12062
|
+
`test('${recording.name}', async ({ page }) => {`
|
|
12063
|
+
];
|
|
12064
|
+
for (const step of recording.steps) {
|
|
12065
|
+
switch (step.type) {
|
|
12066
|
+
case "navigate":
|
|
12067
|
+
lines2.push(` await page.goto('${step.url}');`);
|
|
12068
|
+
break;
|
|
12069
|
+
case "click":
|
|
12070
|
+
lines2.push(` await page.click('${step.selector}');`);
|
|
12071
|
+
break;
|
|
12072
|
+
case "type":
|
|
12073
|
+
lines2.push(` await page.type('${step.selector}', '${step.value}');`);
|
|
12074
|
+
break;
|
|
12075
|
+
case "scroll":
|
|
12076
|
+
lines2.push(` await page.evaluate(() => window.scrollBy(0, 300));`);
|
|
12077
|
+
break;
|
|
12078
|
+
case "evaluate":
|
|
12079
|
+
lines2.push(` await page.evaluate(${step.value});`);
|
|
12080
|
+
break;
|
|
12081
|
+
}
|
|
12082
|
+
}
|
|
12083
|
+
lines2.push(`});`);
|
|
12084
|
+
return lines2.join(`
|
|
12085
|
+
`);
|
|
12086
|
+
}
|
|
12087
|
+
const lines = [
|
|
12088
|
+
`const puppeteer = require('puppeteer');`,
|
|
12089
|
+
``,
|
|
12090
|
+
`(async () => {`,
|
|
12091
|
+
` const browser = await puppeteer.launch();`,
|
|
12092
|
+
` const page = await browser.newPage();`
|
|
12093
|
+
];
|
|
12094
|
+
for (const step of recording.steps) {
|
|
12095
|
+
switch (step.type) {
|
|
12096
|
+
case "navigate":
|
|
12097
|
+
lines.push(` await page.goto('${step.url}');`);
|
|
12098
|
+
break;
|
|
12099
|
+
case "click":
|
|
12100
|
+
lines.push(` await page.click('${step.selector}');`);
|
|
12101
|
+
break;
|
|
12102
|
+
case "type":
|
|
12103
|
+
lines.push(` await page.type('${step.selector}', '${step.value}');`);
|
|
12104
|
+
break;
|
|
12105
|
+
}
|
|
12106
|
+
}
|
|
12107
|
+
lines.push(` await browser.close();`, `})();`);
|
|
12108
|
+
return lines.join(`
|
|
12109
|
+
`);
|
|
12110
|
+
}
|
|
11428
12111
|
var activeRecordings;
|
|
11429
12112
|
var init_recorder = __esm(() => {
|
|
11430
12113
|
init_recordings();
|
|
@@ -15399,118 +16082,6 @@ var init_zod = __esm(() => {
|
|
|
15399
16082
|
init_external();
|
|
15400
16083
|
});
|
|
15401
16084
|
|
|
15402
|
-
// src/engines/cdp.ts
|
|
15403
|
-
class CDPClient {
|
|
15404
|
-
session;
|
|
15405
|
-
networkEnabled = false;
|
|
15406
|
-
performanceEnabled = false;
|
|
15407
|
-
constructor(session) {
|
|
15408
|
-
this.session = session;
|
|
15409
|
-
}
|
|
15410
|
-
static async fromPage(page) {
|
|
15411
|
-
try {
|
|
15412
|
-
const session = await page.context().newCDPSession(page);
|
|
15413
|
-
return new CDPClient(session);
|
|
15414
|
-
} catch (err) {
|
|
15415
|
-
throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
|
|
15416
|
-
}
|
|
15417
|
-
}
|
|
15418
|
-
async send(method, params) {
|
|
15419
|
-
try {
|
|
15420
|
-
return await this.session.send(method, params);
|
|
15421
|
-
} catch (err) {
|
|
15422
|
-
throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
|
|
15423
|
-
}
|
|
15424
|
-
}
|
|
15425
|
-
on(event, handler) {
|
|
15426
|
-
this.session.on(event, handler);
|
|
15427
|
-
}
|
|
15428
|
-
off(event, handler) {
|
|
15429
|
-
this.session.off(event, handler);
|
|
15430
|
-
}
|
|
15431
|
-
async enableNetwork() {
|
|
15432
|
-
if (!this.networkEnabled) {
|
|
15433
|
-
await this.send("Network.enable");
|
|
15434
|
-
this.networkEnabled = true;
|
|
15435
|
-
}
|
|
15436
|
-
}
|
|
15437
|
-
async enablePerformance() {
|
|
15438
|
-
if (!this.performanceEnabled) {
|
|
15439
|
-
await this.send("Performance.enable");
|
|
15440
|
-
this.performanceEnabled = true;
|
|
15441
|
-
}
|
|
15442
|
-
}
|
|
15443
|
-
async getPerformanceMetrics() {
|
|
15444
|
-
await this.enablePerformance();
|
|
15445
|
-
const result = await this.send("Performance.getMetrics");
|
|
15446
|
-
const m = {};
|
|
15447
|
-
for (const metric of result.metrics) {
|
|
15448
|
-
m[metric.name] = metric.value;
|
|
15449
|
-
}
|
|
15450
|
-
return {
|
|
15451
|
-
js_heap_size_used: m["JSHeapUsedSize"],
|
|
15452
|
-
js_heap_size_total: m["JSHeapTotalSize"],
|
|
15453
|
-
dom_interactive: m["DOMInteractive"],
|
|
15454
|
-
dom_complete: m["DOMComplete"],
|
|
15455
|
-
load_event: m["LoadEventEnd"]
|
|
15456
|
-
};
|
|
15457
|
-
}
|
|
15458
|
-
async startJSCoverage() {
|
|
15459
|
-
await this.send("Profiler.enable");
|
|
15460
|
-
await this.send("Debugger.enable");
|
|
15461
|
-
await this.send("Profiler.startPreciseCoverage", {
|
|
15462
|
-
callCount: false,
|
|
15463
|
-
detailed: true
|
|
15464
|
-
});
|
|
15465
|
-
}
|
|
15466
|
-
async stopJSCoverage() {
|
|
15467
|
-
const result = await this.send("Profiler.takePreciseCoverage");
|
|
15468
|
-
await this.send("Profiler.stopPreciseCoverage");
|
|
15469
|
-
return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
|
|
15470
|
-
url: r.url,
|
|
15471
|
-
text: "",
|
|
15472
|
-
ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
|
|
15473
|
-
}));
|
|
15474
|
-
}
|
|
15475
|
-
async getCoverage() {
|
|
15476
|
-
await this.startJSCoverage();
|
|
15477
|
-
const js = await this.stopJSCoverage();
|
|
15478
|
-
const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
|
|
15479
|
-
return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
|
|
15480
|
-
}
|
|
15481
|
-
async captureHAREntries(page, handler) {
|
|
15482
|
-
await this.enableNetwork();
|
|
15483
|
-
const requestTimings = new Map;
|
|
15484
|
-
const onRequest = (params) => {
|
|
15485
|
-
requestTimings.set(params.requestId, params.timestamp);
|
|
15486
|
-
};
|
|
15487
|
-
const onResponse = (params) => {
|
|
15488
|
-
const start = requestTimings.get(params.requestId);
|
|
15489
|
-
const duration = start != null ? (params.timestamp - start) * 1000 : 0;
|
|
15490
|
-
handler({
|
|
15491
|
-
method: "GET",
|
|
15492
|
-
url: params.response.url,
|
|
15493
|
-
status: params.response.status,
|
|
15494
|
-
duration
|
|
15495
|
-
});
|
|
15496
|
-
};
|
|
15497
|
-
this.on("Network.requestWillBeSent", onRequest);
|
|
15498
|
-
this.on("Network.responseReceived", onResponse);
|
|
15499
|
-
return () => {
|
|
15500
|
-
this.off("Network.requestWillBeSent", onRequest);
|
|
15501
|
-
this.off("Network.responseReceived", onResponse);
|
|
15502
|
-
};
|
|
15503
|
-
}
|
|
15504
|
-
async detach() {
|
|
15505
|
-
try {
|
|
15506
|
-
await this.session.detach();
|
|
15507
|
-
} catch {}
|
|
15508
|
-
}
|
|
15509
|
-
}
|
|
15510
|
-
var init_cdp = __esm(() => {
|
|
15511
|
-
init_types();
|
|
15512
|
-
});
|
|
15513
|
-
|
|
15514
16085
|
// src/lib/performance.ts
|
|
15515
16086
|
async function getPerformanceMetrics(page) {
|
|
15516
16087
|
const navTiming = await page.evaluate(() => {
|
|
@@ -15616,16 +16187,16 @@ __export(exports_downloads, {
|
|
|
15616
16187
|
cleanStaleDownloads: () => cleanStaleDownloads
|
|
15617
16188
|
});
|
|
15618
16189
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
15619
|
-
import { join as
|
|
15620
|
-
import { mkdirSync as
|
|
15621
|
-
import { homedir as
|
|
16190
|
+
import { join as join5, basename, extname } from "path";
|
|
16191
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync, readFileSync } from "fs";
|
|
16192
|
+
import { homedir as homedir5 } from "os";
|
|
15622
16193
|
function getDataDir3() {
|
|
15623
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
16194
|
+
return process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
|
|
15624
16195
|
}
|
|
15625
16196
|
function getDownloadsDir(sessionId) {
|
|
15626
|
-
const base =
|
|
15627
|
-
const dir = sessionId ?
|
|
15628
|
-
|
|
16197
|
+
const base = join5(getDataDir3(), "downloads");
|
|
16198
|
+
const dir = sessionId ? join5(base, sessionId) : base;
|
|
16199
|
+
mkdirSync5(dir, { recursive: true });
|
|
15629
16200
|
return dir;
|
|
15630
16201
|
}
|
|
15631
16202
|
function ensureDownloadsDir() {
|
|
@@ -15640,7 +16211,7 @@ function saveToDownloads(buffer, filename, opts) {
|
|
|
15640
16211
|
const ext = extname(filename) || "";
|
|
15641
16212
|
const stem = basename(filename, ext);
|
|
15642
16213
|
const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
|
|
15643
|
-
const filePath =
|
|
16214
|
+
const filePath = join5(dir, uniqueName);
|
|
15644
16215
|
writeFileSync(filePath, buffer);
|
|
15645
16216
|
const meta = {
|
|
15646
16217
|
id,
|
|
@@ -15669,20 +16240,20 @@ function listDownloads(sessionId) {
|
|
|
15669
16240
|
const dir = getDownloadsDir(sessionId);
|
|
15670
16241
|
const results = [];
|
|
15671
16242
|
function scanDir(d) {
|
|
15672
|
-
if (!
|
|
16243
|
+
if (!existsSync2(d))
|
|
15673
16244
|
return;
|
|
15674
|
-
const entries =
|
|
16245
|
+
const entries = readdirSync2(d);
|
|
15675
16246
|
for (const entry of entries) {
|
|
15676
16247
|
if (entry.endsWith(".meta.json"))
|
|
15677
16248
|
continue;
|
|
15678
|
-
const full =
|
|
16249
|
+
const full = join5(d, entry);
|
|
15679
16250
|
const stat = statSync(full);
|
|
15680
16251
|
if (stat.isDirectory()) {
|
|
15681
16252
|
scanDir(full);
|
|
15682
16253
|
continue;
|
|
15683
16254
|
}
|
|
15684
16255
|
const mpath = metaPath(full);
|
|
15685
|
-
if (!
|
|
16256
|
+
if (!existsSync2(mpath))
|
|
15686
16257
|
continue;
|
|
15687
16258
|
try {
|
|
15688
16259
|
const meta = JSON.parse(readFileSync(mpath, "utf8"));
|
|
@@ -15712,9 +16283,9 @@ function deleteDownload(id, sessionId) {
|
|
|
15712
16283
|
if (!file)
|
|
15713
16284
|
return false;
|
|
15714
16285
|
try {
|
|
15715
|
-
|
|
15716
|
-
if (
|
|
15717
|
-
|
|
16286
|
+
unlinkSync2(file.path);
|
|
16287
|
+
if (existsSync2(file.meta_path))
|
|
16288
|
+
unlinkSync2(file.meta_path);
|
|
15718
16289
|
return true;
|
|
15719
16290
|
} catch {
|
|
15720
16291
|
return false;
|
|
@@ -15764,9 +16335,9 @@ var exports_gallery_diff = {};
|
|
|
15764
16335
|
__export(exports_gallery_diff, {
|
|
15765
16336
|
diffImages: () => diffImages
|
|
15766
16337
|
});
|
|
15767
|
-
import { join as
|
|
15768
|
-
import { mkdirSync as
|
|
15769
|
-
import { homedir as
|
|
16338
|
+
import { join as join6 } from "path";
|
|
16339
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
16340
|
+
import { homedir as homedir6 } from "os";
|
|
15770
16341
|
async function diffImages(path1, path2) {
|
|
15771
16342
|
const img1 = import_sharp2.default(path1);
|
|
15772
16343
|
const img2 = import_sharp2.default(path2);
|
|
@@ -15797,10 +16368,10 @@ async function diffImages(path1, path2) {
|
|
|
15797
16368
|
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
15798
16369
|
}
|
|
15799
16370
|
}
|
|
15800
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
15801
|
-
const diffDir =
|
|
15802
|
-
|
|
15803
|
-
const diffPath =
|
|
16371
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
|
|
16372
|
+
const diffDir = join6(dataDir, "diffs");
|
|
16373
|
+
mkdirSync6(diffDir, { recursive: true });
|
|
16374
|
+
const diffPath = join6(diffDir, `diff-${Date.now()}.webp`);
|
|
15804
16375
|
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
15805
16376
|
await Bun.write(diffPath, diffImageBuffer);
|
|
15806
16377
|
return {
|
|
@@ -15817,9 +16388,9 @@ var init_gallery_diff = __esm(() => {
|
|
|
15817
16388
|
});
|
|
15818
16389
|
|
|
15819
16390
|
// src/lib/files-integration.ts
|
|
15820
|
-
import { join as
|
|
15821
|
-
import { mkdirSync as
|
|
15822
|
-
import { homedir as
|
|
16391
|
+
import { join as join7 } from "path";
|
|
16392
|
+
import { mkdirSync as mkdirSync7, copyFileSync as copyFileSync2 } from "fs";
|
|
16393
|
+
import { homedir as homedir7 } from "os";
|
|
15823
16394
|
async function persistFile(localPath, opts) {
|
|
15824
16395
|
try {
|
|
15825
16396
|
const mod = await import("@hasna/files");
|
|
@@ -15828,12 +16399,12 @@ async function persistFile(localPath, opts) {
|
|
|
15828
16399
|
return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
|
|
15829
16400
|
}
|
|
15830
16401
|
} catch {}
|
|
15831
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
16402
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join7(homedir7(), ".browser");
|
|
15832
16403
|
const date = new Date().toISOString().split("T")[0];
|
|
15833
|
-
const dir =
|
|
15834
|
-
|
|
16404
|
+
const dir = join7(dataDir, "persistent", date);
|
|
16405
|
+
mkdirSync7(dir, { recursive: true });
|
|
15835
16406
|
const filename = localPath.split("/").pop() ?? "file";
|
|
15836
|
-
const targetPath =
|
|
16407
|
+
const targetPath = join7(dir, filename);
|
|
15837
16408
|
copyFileSync2(localPath, targetPath);
|
|
15838
16409
|
return {
|
|
15839
16410
|
id: `local-${Date.now()}`,
|
|
@@ -15844,6 +16415,20 @@ async function persistFile(localPath, opts) {
|
|
|
15844
16415
|
}
|
|
15845
16416
|
var init_files_integration = () => {};
|
|
15846
16417
|
|
|
16418
|
+
// src/db/timeline.ts
|
|
16419
|
+
function logEvent(sessionId, eventType, details = {}) {
|
|
16420
|
+
const db = getDatabase();
|
|
16421
|
+
const id = crypto.randomUUID();
|
|
16422
|
+
db.prepare("INSERT INTO session_events (id, session_id, event_type, details) VALUES (?, ?, ?, ?)").run(id, sessionId, eventType, JSON.stringify(details));
|
|
16423
|
+
}
|
|
16424
|
+
function getTimeline(sessionId, limit = 100) {
|
|
16425
|
+
const db = getDatabase();
|
|
16426
|
+
return db.query("SELECT * FROM session_events WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?").all(sessionId, limit);
|
|
16427
|
+
}
|
|
16428
|
+
var init_timeline = __esm(() => {
|
|
16429
|
+
init_schema();
|
|
16430
|
+
});
|
|
16431
|
+
|
|
15847
16432
|
// src/lib/tabs.ts
|
|
15848
16433
|
async function newTab(page, url) {
|
|
15849
16434
|
const context = page.context();
|
|
@@ -15934,23 +16519,23 @@ __export(exports_profiles, {
|
|
|
15934
16519
|
deleteProfile: () => deleteProfile,
|
|
15935
16520
|
applyProfile: () => applyProfile
|
|
15936
16521
|
});
|
|
15937
|
-
import { mkdirSync as
|
|
15938
|
-
import { join as
|
|
15939
|
-
import { homedir as
|
|
16522
|
+
import { mkdirSync as mkdirSync8, existsSync as existsSync4, readdirSync as readdirSync3, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16523
|
+
import { join as join8 } from "path";
|
|
16524
|
+
import { homedir as homedir8 } from "os";
|
|
15940
16525
|
function getProfilesDir() {
|
|
15941
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
15942
|
-
const dir =
|
|
15943
|
-
|
|
16526
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser");
|
|
16527
|
+
const dir = join8(dataDir, "profiles");
|
|
16528
|
+
mkdirSync8(dir, { recursive: true });
|
|
15944
16529
|
return dir;
|
|
15945
16530
|
}
|
|
15946
16531
|
function getProfileDir2(name) {
|
|
15947
|
-
return
|
|
16532
|
+
return join8(getProfilesDir(), name);
|
|
15948
16533
|
}
|
|
15949
16534
|
async function saveProfile(page, name) {
|
|
15950
16535
|
const dir = getProfileDir2(name);
|
|
15951
|
-
|
|
16536
|
+
mkdirSync8(dir, { recursive: true });
|
|
15952
16537
|
const cookies = await page.context().cookies();
|
|
15953
|
-
writeFileSync2(
|
|
16538
|
+
writeFileSync2(join8(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
|
|
15954
16539
|
let localStorage2 = {};
|
|
15955
16540
|
try {
|
|
15956
16541
|
localStorage2 = await page.evaluate(() => {
|
|
@@ -15962,11 +16547,11 @@ async function saveProfile(page, name) {
|
|
|
15962
16547
|
return result;
|
|
15963
16548
|
});
|
|
15964
16549
|
} catch {}
|
|
15965
|
-
writeFileSync2(
|
|
16550
|
+
writeFileSync2(join8(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
|
|
15966
16551
|
const savedAt = new Date().toISOString();
|
|
15967
16552
|
const url = page.url();
|
|
15968
16553
|
const meta = { saved_at: savedAt, url };
|
|
15969
|
-
writeFileSync2(
|
|
16554
|
+
writeFileSync2(join8(dir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
15970
16555
|
return {
|
|
15971
16556
|
name,
|
|
15972
16557
|
saved_at: savedAt,
|
|
@@ -15977,17 +16562,17 @@ async function saveProfile(page, name) {
|
|
|
15977
16562
|
}
|
|
15978
16563
|
function loadProfile(name) {
|
|
15979
16564
|
const dir = getProfileDir2(name);
|
|
15980
|
-
if (!
|
|
16565
|
+
if (!existsSync4(dir)) {
|
|
15981
16566
|
throw new Error(`Profile not found: ${name}`);
|
|
15982
16567
|
}
|
|
15983
|
-
const cookiesPath =
|
|
15984
|
-
const storagePath =
|
|
15985
|
-
const metaPath2 =
|
|
15986
|
-
const cookies =
|
|
15987
|
-
const localStorage2 =
|
|
16568
|
+
const cookiesPath = join8(dir, "cookies.json");
|
|
16569
|
+
const storagePath = join8(dir, "storage.json");
|
|
16570
|
+
const metaPath2 = join8(dir, "meta.json");
|
|
16571
|
+
const cookies = existsSync4(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
|
|
16572
|
+
const localStorage2 = existsSync4(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
|
|
15988
16573
|
let savedAt = new Date().toISOString();
|
|
15989
16574
|
let url;
|
|
15990
|
-
if (
|
|
16575
|
+
if (existsSync4(metaPath2)) {
|
|
15991
16576
|
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
15992
16577
|
savedAt = meta.saved_at ?? savedAt;
|
|
15993
16578
|
url = meta.url;
|
|
@@ -16015,33 +16600,33 @@ async function applyProfile(page, profileData) {
|
|
|
16015
16600
|
}
|
|
16016
16601
|
function listProfiles() {
|
|
16017
16602
|
const dir = getProfilesDir();
|
|
16018
|
-
if (!
|
|
16603
|
+
if (!existsSync4(dir))
|
|
16019
16604
|
return [];
|
|
16020
|
-
const entries =
|
|
16605
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
16021
16606
|
const profiles = [];
|
|
16022
16607
|
for (const entry of entries) {
|
|
16023
16608
|
if (!entry.isDirectory())
|
|
16024
16609
|
continue;
|
|
16025
16610
|
const name = entry.name;
|
|
16026
|
-
const profileDir =
|
|
16611
|
+
const profileDir = join8(dir, name);
|
|
16027
16612
|
let savedAt = "";
|
|
16028
16613
|
let url;
|
|
16029
16614
|
let cookieCount = 0;
|
|
16030
16615
|
let storageKeyCount = 0;
|
|
16031
16616
|
try {
|
|
16032
|
-
const metaPath2 =
|
|
16033
|
-
if (
|
|
16617
|
+
const metaPath2 = join8(profileDir, "meta.json");
|
|
16618
|
+
if (existsSync4(metaPath2)) {
|
|
16034
16619
|
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
16035
16620
|
savedAt = meta.saved_at ?? "";
|
|
16036
16621
|
url = meta.url;
|
|
16037
16622
|
}
|
|
16038
|
-
const cookiesPath =
|
|
16039
|
-
if (
|
|
16623
|
+
const cookiesPath = join8(profileDir, "cookies.json");
|
|
16624
|
+
if (existsSync4(cookiesPath)) {
|
|
16040
16625
|
const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
|
|
16041
16626
|
cookieCount = Array.isArray(cookies) ? cookies.length : 0;
|
|
16042
16627
|
}
|
|
16043
|
-
const storagePath =
|
|
16044
|
-
if (
|
|
16628
|
+
const storagePath = join8(profileDir, "storage.json");
|
|
16629
|
+
if (existsSync4(storagePath)) {
|
|
16045
16630
|
const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
|
|
16046
16631
|
storageKeyCount = Object.keys(storage).length;
|
|
16047
16632
|
}
|
|
@@ -16058,7 +16643,7 @@ function listProfiles() {
|
|
|
16058
16643
|
}
|
|
16059
16644
|
function deleteProfile(name) {
|
|
16060
16645
|
const dir = getProfileDir2(name);
|
|
16061
|
-
if (!
|
|
16646
|
+
if (!existsSync4(dir))
|
|
16062
16647
|
return false;
|
|
16063
16648
|
try {
|
|
16064
16649
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -16069,6 +16654,97 @@ function deleteProfile(name) {
|
|
|
16069
16654
|
}
|
|
16070
16655
|
var init_profiles = () => {};
|
|
16071
16656
|
|
|
16657
|
+
// src/lib/sanitize.ts
|
|
16658
|
+
var exports_sanitize = {};
|
|
16659
|
+
__export(exports_sanitize, {
|
|
16660
|
+
sanitizeText: () => sanitizeText,
|
|
16661
|
+
sanitizeHTML: () => sanitizeHTML
|
|
16662
|
+
});
|
|
16663
|
+
function sanitizeText(text) {
|
|
16664
|
+
let stripped = 0;
|
|
16665
|
+
const warnings = [];
|
|
16666
|
+
let clean = text;
|
|
16667
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
16668
|
+
pattern.lastIndex = 0;
|
|
16669
|
+
const matches = clean.match(pattern);
|
|
16670
|
+
if (matches) {
|
|
16671
|
+
stripped += matches.length;
|
|
16672
|
+
warnings.push(`Stripped ${matches.length}x: ${pattern.source}`);
|
|
16673
|
+
pattern.lastIndex = 0;
|
|
16674
|
+
clean = clean.replace(pattern, "[STRIPPED]");
|
|
16675
|
+
}
|
|
16676
|
+
}
|
|
16677
|
+
return { text: clean, stripped, warnings };
|
|
16678
|
+
}
|
|
16679
|
+
function sanitizeHTML(html) {
|
|
16680
|
+
let stripped = 0;
|
|
16681
|
+
const warnings = [];
|
|
16682
|
+
let clean = html;
|
|
16683
|
+
const commentMatches = clean.match(/<!--[\s\S]*?-->/g);
|
|
16684
|
+
if (commentMatches) {
|
|
16685
|
+
for (const comment of commentMatches) {
|
|
16686
|
+
if (comment.replace(/<!--\s*-->/g, "").trim().length > 20) {
|
|
16687
|
+
stripped++;
|
|
16688
|
+
warnings.push(`Stripped HTML comment (${comment.length} chars)`);
|
|
16689
|
+
}
|
|
16690
|
+
}
|
|
16691
|
+
clean = clean.replace(/<!--[\s\S]*?-->/g, "");
|
|
16692
|
+
}
|
|
16693
|
+
const hiddenPatterns = [
|
|
16694
|
+
/style\s*=\s*"[^"]*display\s*:\s*none[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16695
|
+
/style\s*=\s*"[^"]*visibility\s*:\s*hidden[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16696
|
+
/style\s*=\s*"[^"]*opacity\s*:\s*0[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16697
|
+
/style\s*=\s*"[^"]*font-size\s*:\s*0[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16698
|
+
/style\s*=\s*"[^"]*position\s*:\s*absolute[^"]*left\s*:\s*-\d{4,}[^"]*"[^>]*>[\s\S]*?<\//gi
|
|
16699
|
+
];
|
|
16700
|
+
for (const pattern of hiddenPatterns) {
|
|
16701
|
+
pattern.lastIndex = 0;
|
|
16702
|
+
const matches = clean.match(pattern);
|
|
16703
|
+
if (matches) {
|
|
16704
|
+
stripped += matches.length;
|
|
16705
|
+
warnings.push(`Stripped ${matches.length} hidden elements`);
|
|
16706
|
+
pattern.lastIndex = 0;
|
|
16707
|
+
clean = clean.replace(pattern, "");
|
|
16708
|
+
}
|
|
16709
|
+
}
|
|
16710
|
+
const ariaHiddenPattern = /aria-hidden\s*=\s*"true"[^>]*>[\s\S]*?<\//gi;
|
|
16711
|
+
const ariaHidden = clean.match(ariaHiddenPattern);
|
|
16712
|
+
if (ariaHidden) {
|
|
16713
|
+
stripped += ariaHidden.length;
|
|
16714
|
+
warnings.push(`Stripped ${ariaHidden.length} aria-hidden elements`);
|
|
16715
|
+
ariaHiddenPattern.lastIndex = 0;
|
|
16716
|
+
clean = clean.replace(ariaHiddenPattern, "");
|
|
16717
|
+
}
|
|
16718
|
+
const textResult = sanitizeText(clean);
|
|
16719
|
+
return {
|
|
16720
|
+
text: textResult.text,
|
|
16721
|
+
stripped: stripped + textResult.stripped,
|
|
16722
|
+
warnings: [...warnings, ...textResult.warnings]
|
|
16723
|
+
};
|
|
16724
|
+
}
|
|
16725
|
+
var INJECTION_PATTERNS;
|
|
16726
|
+
var init_sanitize = __esm(() => {
|
|
16727
|
+
INJECTION_PATTERNS = [
|
|
16728
|
+
/ignore\s+(all\s+)?previous\s+instructions/gi,
|
|
16729
|
+
/ignore\s+(all\s+)?prior\s+instructions/gi,
|
|
16730
|
+
/disregard\s+(all\s+)?previous/gi,
|
|
16731
|
+
/forget\s+(all\s+)?previous/gi,
|
|
16732
|
+
/you\s+are\s+now\s+/gi,
|
|
16733
|
+
/new\s+instructions?\s*:/gi,
|
|
16734
|
+
/system\s+prompt\s*:/gi,
|
|
16735
|
+
/\[INST\]/gi,
|
|
16736
|
+
/\[\/INST\]/gi,
|
|
16737
|
+
/<\|im_start\|>/gi,
|
|
16738
|
+
/<\|im_end\|>/gi,
|
|
16739
|
+
/<<SYS>>/gi,
|
|
16740
|
+
/<<\/SYS>>/gi,
|
|
16741
|
+
/IMPORTANT:\s*ignore/gi,
|
|
16742
|
+
/CRITICAL:\s*override/gi,
|
|
16743
|
+
/assistant:\s/gi,
|
|
16744
|
+
/human:\s/gi
|
|
16745
|
+
];
|
|
16746
|
+
});
|
|
16747
|
+
|
|
16072
16748
|
// src/lib/annotate.ts
|
|
16073
16749
|
var exports_annotate = {};
|
|
16074
16750
|
__export(exports_annotate, {
|
|
@@ -16129,26 +16805,213 @@ var init_annotate = __esm(() => {
|
|
|
16129
16805
|
import_sharp3 = __toESM(require_lib(), 1);
|
|
16130
16806
|
});
|
|
16131
16807
|
|
|
16808
|
+
// src/lib/auth-flow.ts
|
|
16809
|
+
var exports_auth_flow = {};
|
|
16810
|
+
__export(exports_auth_flow, {
|
|
16811
|
+
tryReplayAuth: () => tryReplayAuth,
|
|
16812
|
+
touchAuthFlow: () => touchAuthFlow,
|
|
16813
|
+
saveAuthFlow: () => saveAuthFlow,
|
|
16814
|
+
listAuthFlows: () => listAuthFlows,
|
|
16815
|
+
isAuthRedirect: () => isAuthRedirect,
|
|
16816
|
+
isAuthPage: () => isAuthPage,
|
|
16817
|
+
getAuthFlowByName: () => getAuthFlowByName,
|
|
16818
|
+
getAuthFlowByDomain: () => getAuthFlowByDomain,
|
|
16819
|
+
getAuthFlow: () => getAuthFlow,
|
|
16820
|
+
deleteAuthFlow: () => deleteAuthFlow
|
|
16821
|
+
});
|
|
16822
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
16823
|
+
function saveAuthFlow(data) {
|
|
16824
|
+
const db = getDatabase();
|
|
16825
|
+
const id = randomUUID10();
|
|
16826
|
+
db.prepare("INSERT OR REPLACE INTO auth_flows (id, name, domain, recording_id, storage_state_path) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.domain, data.recordingId ?? null, data.storageStatePath ?? null);
|
|
16827
|
+
return getAuthFlow(id);
|
|
16828
|
+
}
|
|
16829
|
+
function getAuthFlow(id) {
|
|
16830
|
+
const db = getDatabase();
|
|
16831
|
+
return db.query("SELECT * FROM auth_flows WHERE id = ?").get(id) ?? null;
|
|
16832
|
+
}
|
|
16833
|
+
function getAuthFlowByName(name) {
|
|
16834
|
+
const db = getDatabase();
|
|
16835
|
+
return db.query("SELECT * FROM auth_flows WHERE name = ?").get(name) ?? null;
|
|
16836
|
+
}
|
|
16837
|
+
function getAuthFlowByDomain(domain) {
|
|
16838
|
+
const db = getDatabase();
|
|
16839
|
+
return db.query("SELECT * FROM auth_flows WHERE domain = ? ORDER BY last_used DESC LIMIT 1").get(domain) ?? null;
|
|
16840
|
+
}
|
|
16841
|
+
function listAuthFlows() {
|
|
16842
|
+
const db = getDatabase();
|
|
16843
|
+
return db.query("SELECT * FROM auth_flows ORDER BY last_used DESC, created_at DESC").all();
|
|
16844
|
+
}
|
|
16845
|
+
function deleteAuthFlow(name) {
|
|
16846
|
+
const db = getDatabase();
|
|
16847
|
+
const result = db.prepare("DELETE FROM auth_flows WHERE name = ?").run(name);
|
|
16848
|
+
return result.changes > 0;
|
|
16849
|
+
}
|
|
16850
|
+
function touchAuthFlow(id) {
|
|
16851
|
+
const db = getDatabase();
|
|
16852
|
+
db.prepare("UPDATE auth_flows SET last_used = datetime('now') WHERE id = ?").run(id);
|
|
16853
|
+
}
|
|
16854
|
+
function isAuthPage(url) {
|
|
16855
|
+
return AUTH_URL_PATTERNS.some((pattern) => pattern.test(url));
|
|
16856
|
+
}
|
|
16857
|
+
function isAuthRedirect(fromUrl, toUrl) {
|
|
16858
|
+
if (fromUrl === toUrl)
|
|
16859
|
+
return false;
|
|
16860
|
+
return isAuthPage(toUrl) && !isAuthPage(fromUrl);
|
|
16861
|
+
}
|
|
16862
|
+
async function tryReplayAuth(page, domain) {
|
|
16863
|
+
const flow = getAuthFlowByDomain(domain);
|
|
16864
|
+
if (!flow)
|
|
16865
|
+
return { replayed: false };
|
|
16866
|
+
if (flow.storage_state_path) {
|
|
16867
|
+
try {
|
|
16868
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3 } = await import("fs");
|
|
16869
|
+
if (existsSync5(flow.storage_state_path)) {
|
|
16870
|
+
const state = JSON.parse(readFileSync3(flow.storage_state_path, "utf8"));
|
|
16871
|
+
if (state.cookies?.length) {
|
|
16872
|
+
await page.context().addCookies(state.cookies);
|
|
16873
|
+
await page.reload();
|
|
16874
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
16875
|
+
if (!isAuthPage(page.url())) {
|
|
16876
|
+
touchAuthFlow(flow.id);
|
|
16877
|
+
return { replayed: true, flow, method: "storage_state" };
|
|
16878
|
+
}
|
|
16879
|
+
}
|
|
16880
|
+
}
|
|
16881
|
+
} catch {}
|
|
16882
|
+
}
|
|
16883
|
+
if (flow.recording_id) {
|
|
16884
|
+
try {
|
|
16885
|
+
const { replayRecording: replayRecording2 } = await Promise.resolve().then(() => (init_recorder(), exports_recorder));
|
|
16886
|
+
const result = await replayRecording2(flow.recording_id, page);
|
|
16887
|
+
if (result.success) {
|
|
16888
|
+
try {
|
|
16889
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
16890
|
+
const path = await saveStateFromPage2(page, flow.name);
|
|
16891
|
+
const db = getDatabase();
|
|
16892
|
+
db.prepare("UPDATE auth_flows SET storage_state_path = ?, last_used = datetime('now') WHERE id = ?").run(path, flow.id);
|
|
16893
|
+
} catch {}
|
|
16894
|
+
touchAuthFlow(flow.id);
|
|
16895
|
+
return { replayed: true, flow, method: "recording_replay" };
|
|
16896
|
+
}
|
|
16897
|
+
} catch {}
|
|
16898
|
+
}
|
|
16899
|
+
return { replayed: false, flow };
|
|
16900
|
+
}
|
|
16901
|
+
var AUTH_URL_PATTERNS;
|
|
16902
|
+
var init_auth_flow = __esm(() => {
|
|
16903
|
+
init_schema();
|
|
16904
|
+
AUTH_URL_PATTERNS = [
|
|
16905
|
+
/\/login/i,
|
|
16906
|
+
/\/signin/i,
|
|
16907
|
+
/\/sign-in/i,
|
|
16908
|
+
/\/auth/i,
|
|
16909
|
+
/\/sso/i,
|
|
16910
|
+
/\/oauth/i,
|
|
16911
|
+
/\/cas\/login/i,
|
|
16912
|
+
/accounts\.google\.com/i,
|
|
16913
|
+
/login\.microsoftonline\.com/i,
|
|
16914
|
+
/github\.com\/login/i,
|
|
16915
|
+
/auth0\.com/i
|
|
16916
|
+
];
|
|
16917
|
+
});
|
|
16918
|
+
|
|
16919
|
+
// src/lib/vision-fallback.ts
|
|
16920
|
+
var exports_vision_fallback = {};
|
|
16921
|
+
__export(exports_vision_fallback, {
|
|
16922
|
+
findElementByVision: () => findElementByVision,
|
|
16923
|
+
clickByVision: () => clickByVision
|
|
16924
|
+
});
|
|
16925
|
+
async function findElementByVision(page, description, opts) {
|
|
16926
|
+
const model = opts?.model ?? process.env["BROWSER_VISION_MODEL"] ?? DEFAULT_MODEL;
|
|
16927
|
+
const screenshot = await page.screenshot({ type: "jpeg", quality: 80 });
|
|
16928
|
+
const base64 = screenshot.toString("base64");
|
|
16929
|
+
const viewport = page.viewportSize() ?? { width: 1280, height: 720 };
|
|
16930
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
16931
|
+
if (!apiKey) {
|
|
16932
|
+
return { found: false, x: 0, y: 0, confidence: "none", description, model, error: "ANTHROPIC_API_KEY not set" };
|
|
16933
|
+
}
|
|
16934
|
+
try {
|
|
16935
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
16936
|
+
method: "POST",
|
|
16937
|
+
headers: {
|
|
16938
|
+
"content-type": "application/json",
|
|
16939
|
+
"x-api-key": apiKey,
|
|
16940
|
+
"anthropic-version": "2023-06-01"
|
|
16941
|
+
},
|
|
16942
|
+
body: JSON.stringify({
|
|
16943
|
+
model,
|
|
16944
|
+
max_tokens: 256,
|
|
16945
|
+
messages: [{
|
|
16946
|
+
role: "user",
|
|
16947
|
+
content: [
|
|
16948
|
+
{
|
|
16949
|
+
type: "image",
|
|
16950
|
+
source: { type: "base64", media_type: "image/jpeg", data: base64 }
|
|
16951
|
+
},
|
|
16952
|
+
{
|
|
16953
|
+
type: "text",
|
|
16954
|
+
text: `Find the element matching this description: "${description}"
|
|
16955
|
+
|
|
16956
|
+
The screenshot is ${viewport.width}x${viewport.height} pixels.
|
|
16957
|
+
|
|
16958
|
+
Reply with ONLY a JSON object (no markdown, no explanation):
|
|
16959
|
+
{"found": true, "x": <pixel_x>, "y": <pixel_y>, "confidence": "high|medium|low", "description": "<what you found>"}
|
|
16960
|
+
|
|
16961
|
+
If you cannot find the element:
|
|
16962
|
+
{"found": false, "x": 0, "y": 0, "confidence": "none", "description": "not found"}`
|
|
16963
|
+
}
|
|
16964
|
+
]
|
|
16965
|
+
}]
|
|
16966
|
+
})
|
|
16967
|
+
});
|
|
16968
|
+
const data = await response.json();
|
|
16969
|
+
const text = data.content?.[0]?.text ?? "";
|
|
16970
|
+
const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
|
|
16971
|
+
const result = JSON.parse(jsonStr);
|
|
16972
|
+
result.model = model;
|
|
16973
|
+
return result;
|
|
16974
|
+
} catch (err) {
|
|
16975
|
+
return {
|
|
16976
|
+
found: false,
|
|
16977
|
+
x: 0,
|
|
16978
|
+
y: 0,
|
|
16979
|
+
confidence: "none",
|
|
16980
|
+
description,
|
|
16981
|
+
model,
|
|
16982
|
+
error: err instanceof Error ? err.message : String(err)
|
|
16983
|
+
};
|
|
16984
|
+
}
|
|
16985
|
+
}
|
|
16986
|
+
async function clickByVision(page, description, opts) {
|
|
16987
|
+
const result = await findElementByVision(page, description, opts);
|
|
16988
|
+
if (result.found && result.x > 0 && result.y > 0) {
|
|
16989
|
+
await page.mouse.click(result.x, result.y);
|
|
16990
|
+
}
|
|
16991
|
+
return result;
|
|
16992
|
+
}
|
|
16993
|
+
var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
|
|
16994
|
+
|
|
16132
16995
|
// src/lib/auth.ts
|
|
16133
16996
|
var exports_auth = {};
|
|
16134
16997
|
__export(exports_auth, {
|
|
16135
16998
|
loginWithCredentials: () => loginWithCredentials,
|
|
16136
16999
|
getCredentials: () => getCredentials
|
|
16137
17000
|
});
|
|
16138
|
-
import { existsSync as
|
|
16139
|
-
import { join as
|
|
16140
|
-
import { homedir as
|
|
17001
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
17002
|
+
import { join as join9 } from "path";
|
|
17003
|
+
import { homedir as homedir9 } from "os";
|
|
16141
17004
|
async function getCredentials(service) {
|
|
16142
17005
|
try {
|
|
16143
|
-
const { getSecret } = await import(`${
|
|
17006
|
+
const { getSecret } = await import(`${homedir9()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
|
|
16144
17007
|
const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
|
|
16145
17008
|
const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
|
|
16146
17009
|
if (email?.value && password?.value) {
|
|
16147
17010
|
return { email: email.value, password: password.value };
|
|
16148
17011
|
}
|
|
16149
17012
|
} catch {}
|
|
16150
|
-
const secretsPath =
|
|
16151
|
-
if (
|
|
17013
|
+
const secretsPath = join9(homedir9(), ".secrets");
|
|
17014
|
+
if (existsSync5(secretsPath)) {
|
|
16152
17015
|
const content = readFileSync3(secretsPath, "utf8");
|
|
16153
17016
|
const lines = content.split(`
|
|
16154
17017
|
`);
|
|
@@ -16235,6 +17098,7 @@ __export(exports_dist, {
|
|
|
16235
17098
|
shortUuid: () => shortUuid,
|
|
16236
17099
|
setFocus: () => setFocus,
|
|
16237
17100
|
setActiveProfile: () => setActiveProfile,
|
|
17101
|
+
setActiveModel: () => setActiveModel,
|
|
16238
17102
|
searchMemories: () => searchMemories,
|
|
16239
17103
|
runCleanup: () => runCleanup,
|
|
16240
17104
|
resolveProjectId: () => resolveProjectId,
|
|
@@ -16285,6 +17149,8 @@ __export(exports_dist, {
|
|
|
16285
17149
|
getAutoMemoryStats: () => getAutoMemoryStats,
|
|
16286
17150
|
getAgent: () => getAgent2,
|
|
16287
17151
|
getActiveProfile: () => getActiveProfile,
|
|
17152
|
+
getActiveModel: () => getActiveModel,
|
|
17153
|
+
gatherTrainingData: () => gatherTrainingData,
|
|
16288
17154
|
focusFilterSQL: () => focusFilterSQL,
|
|
16289
17155
|
findPath: () => findPath,
|
|
16290
17156
|
enforceQuotas: () => enforceQuotas,
|
|
@@ -16301,6 +17167,7 @@ __export(exports_dist, {
|
|
|
16301
17167
|
containsSecrets: () => containsSecrets,
|
|
16302
17168
|
configureAutoMemory: () => configureAutoMemory,
|
|
16303
17169
|
closeDatabase: () => closeDatabase,
|
|
17170
|
+
clearActiveModel: () => clearActiveModel,
|
|
16304
17171
|
cleanExpiredMemories: () => cleanExpiredMemories,
|
|
16305
17172
|
cleanExpiredLocks: () => cleanExpiredLocks,
|
|
16306
17173
|
checkMemoryWriteLock: () => checkMemoryWriteLock,
|
|
@@ -16321,25 +17188,29 @@ __export(exports_dist, {
|
|
|
16321
17188
|
InvalidScopeError: () => InvalidScopeError,
|
|
16322
17189
|
EntityNotFoundError: () => EntityNotFoundError,
|
|
16323
17190
|
DuplicateMemoryError: () => DuplicateMemoryError,
|
|
17191
|
+
DEFAULT_MODEL: () => DEFAULT_MODEL2,
|
|
16324
17192
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
16325
17193
|
});
|
|
16326
17194
|
import { Database as Database2 } from "bun:sqlite";
|
|
16327
|
-
import { existsSync as
|
|
16328
|
-
import { dirname, join as
|
|
16329
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as
|
|
16330
|
-
import { homedir as
|
|
17195
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync9 } from "fs";
|
|
17196
|
+
import { dirname, join as join10, resolve } from "path";
|
|
17197
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
17198
|
+
import { homedir as homedir10 } from "os";
|
|
16331
17199
|
import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve2 } from "path";
|
|
16332
17200
|
import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
16333
17201
|
import { homedir as homedir22 } from "os";
|
|
16334
17202
|
import { join as join32 } from "path";
|
|
17203
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync42, readFileSync as readFileSync32, writeFileSync as writeFileSync32 } from "fs";
|
|
17204
|
+
import { homedir as homedir32 } from "os";
|
|
17205
|
+
import { join as join42 } from "path";
|
|
16335
17206
|
function isInMemoryDb(path) {
|
|
16336
17207
|
return path === ":memory:" || path.startsWith("file::memory:");
|
|
16337
17208
|
}
|
|
16338
17209
|
function findNearestMementosDb(startDir) {
|
|
16339
17210
|
let dir = resolve(startDir);
|
|
16340
17211
|
while (true) {
|
|
16341
|
-
const candidate =
|
|
16342
|
-
if (
|
|
17212
|
+
const candidate = join10(dir, ".mementos", "mementos.db");
|
|
17213
|
+
if (existsSync6(candidate))
|
|
16343
17214
|
return candidate;
|
|
16344
17215
|
const parent = dirname(dir);
|
|
16345
17216
|
if (parent === dir)
|
|
@@ -16351,7 +17222,7 @@ function findNearestMementosDb(startDir) {
|
|
|
16351
17222
|
function findGitRoot(startDir) {
|
|
16352
17223
|
let dir = resolve(startDir);
|
|
16353
17224
|
while (true) {
|
|
16354
|
-
if (
|
|
17225
|
+
if (existsSync6(join10(dir, ".git")))
|
|
16355
17226
|
return dir;
|
|
16356
17227
|
const parent = dirname(dir);
|
|
16357
17228
|
if (parent === dir)
|
|
@@ -16371,25 +17242,25 @@ function getDbPath() {
|
|
|
16371
17242
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
16372
17243
|
const gitRoot = findGitRoot(cwd);
|
|
16373
17244
|
if (gitRoot) {
|
|
16374
|
-
return
|
|
17245
|
+
return join10(gitRoot, ".mementos", "mementos.db");
|
|
16375
17246
|
}
|
|
16376
17247
|
}
|
|
16377
17248
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
16378
|
-
return
|
|
17249
|
+
return join10(home, ".mementos", "mementos.db");
|
|
16379
17250
|
}
|
|
16380
|
-
function
|
|
17251
|
+
function ensureDir2(filePath) {
|
|
16381
17252
|
if (isInMemoryDb(filePath))
|
|
16382
17253
|
return;
|
|
16383
17254
|
const dir = dirname(resolve(filePath));
|
|
16384
|
-
if (!
|
|
16385
|
-
|
|
17255
|
+
if (!existsSync6(dir)) {
|
|
17256
|
+
mkdirSync9(dir, { recursive: true });
|
|
16386
17257
|
}
|
|
16387
17258
|
}
|
|
16388
17259
|
function getDatabase2(dbPath) {
|
|
16389
17260
|
if (_db2)
|
|
16390
17261
|
return _db2;
|
|
16391
17262
|
const path = dbPath || getDbPath();
|
|
16392
|
-
|
|
17263
|
+
ensureDir2(path);
|
|
16393
17264
|
_db2 = new Database2(path, { create: true });
|
|
16394
17265
|
_db2.run("PRAGMA journal_mode = WAL");
|
|
16395
17266
|
_db2.run("PRAGMA busy_timeout = 5000");
|
|
@@ -18232,7 +19103,7 @@ function isValidCategory(value) {
|
|
|
18232
19103
|
return VALID_CATEGORIES.includes(value);
|
|
18233
19104
|
}
|
|
18234
19105
|
function loadConfig() {
|
|
18235
|
-
const configPath = join22(
|
|
19106
|
+
const configPath = join22(homedir10(), ".mementos", "config.json");
|
|
18236
19107
|
let fileConfig = {};
|
|
18237
19108
|
if (existsSync22(configPath)) {
|
|
18238
19109
|
try {
|
|
@@ -18259,10 +19130,10 @@ function loadConfig() {
|
|
|
18259
19130
|
return merged;
|
|
18260
19131
|
}
|
|
18261
19132
|
function profilesDir() {
|
|
18262
|
-
return join22(
|
|
19133
|
+
return join22(homedir10(), ".mementos", "profiles");
|
|
18263
19134
|
}
|
|
18264
19135
|
function globalConfigPath() {
|
|
18265
|
-
return join22(
|
|
19136
|
+
return join22(homedir10(), ".mementos", "config.json");
|
|
18266
19137
|
}
|
|
18267
19138
|
function readGlobalConfig() {
|
|
18268
19139
|
const p = globalConfigPath();
|
|
@@ -18276,7 +19147,7 @@ function readGlobalConfig() {
|
|
|
18276
19147
|
}
|
|
18277
19148
|
function writeGlobalConfig(data) {
|
|
18278
19149
|
const p = globalConfigPath();
|
|
18279
|
-
|
|
19150
|
+
ensureDir22(dirname2(p));
|
|
18280
19151
|
writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
|
|
18281
19152
|
}
|
|
18282
19153
|
function getActiveProfile() {
|
|
@@ -18299,18 +19170,18 @@ function listProfiles2() {
|
|
|
18299
19170
|
const dir = profilesDir();
|
|
18300
19171
|
if (!existsSync22(dir))
|
|
18301
19172
|
return [];
|
|
18302
|
-
return
|
|
19173
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
|
|
18303
19174
|
}
|
|
18304
19175
|
function deleteProfile2(name) {
|
|
18305
19176
|
const dbPath = join22(profilesDir(), `${name}.db`);
|
|
18306
19177
|
if (!existsSync22(dbPath))
|
|
18307
19178
|
return false;
|
|
18308
|
-
|
|
19179
|
+
unlinkSync3(dbPath);
|
|
18309
19180
|
if (getActiveProfile() === name)
|
|
18310
19181
|
setActiveProfile(null);
|
|
18311
19182
|
return true;
|
|
18312
19183
|
}
|
|
18313
|
-
function
|
|
19184
|
+
function ensureDir22(dir) {
|
|
18314
19185
|
if (!existsSync22(dir)) {
|
|
18315
19186
|
mkdirSync22(dir, { recursive: true });
|
|
18316
19187
|
}
|
|
@@ -19365,6 +20236,85 @@ function jaccardSimilarity(a, b) {
|
|
|
19365
20236
|
const union = new Set([...a, ...b]).size;
|
|
19366
20237
|
return intersection / union;
|
|
19367
20238
|
}
|
|
20239
|
+
function memoryToRecallExample(memory) {
|
|
20240
|
+
return {
|
|
20241
|
+
messages: [
|
|
20242
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
20243
|
+
{
|
|
20244
|
+
role: "user",
|
|
20245
|
+
content: `What do you remember about "${memory.key}"?`
|
|
20246
|
+
},
|
|
20247
|
+
{
|
|
20248
|
+
role: "assistant",
|
|
20249
|
+
content: memory.summary ? `${memory.value}
|
|
20250
|
+
|
|
20251
|
+
Summary: ${memory.summary}` : memory.value
|
|
20252
|
+
}
|
|
20253
|
+
]
|
|
20254
|
+
};
|
|
20255
|
+
}
|
|
20256
|
+
function memoryToSaveExample(memory) {
|
|
20257
|
+
const tags = memory.tags ?? [];
|
|
20258
|
+
return {
|
|
20259
|
+
messages: [
|
|
20260
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
20261
|
+
{
|
|
20262
|
+
role: "user",
|
|
20263
|
+
content: `Remember this for me: ${memory.key} = ${memory.value}${tags.length ? ` (tags: ${tags.join(", ")})` : ""}`
|
|
20264
|
+
},
|
|
20265
|
+
{
|
|
20266
|
+
role: "assistant",
|
|
20267
|
+
content: `Saved to memory: "${memory.key}" with ${memory.category} category, importance ${memory.importance}/10, scope: ${memory.scope}.`
|
|
20268
|
+
}
|
|
20269
|
+
]
|
|
20270
|
+
};
|
|
20271
|
+
}
|
|
20272
|
+
function memoryToSearchExample(memories, category) {
|
|
20273
|
+
const matched = memories.filter((m) => m.category === category && m.status === "active").slice(0, 5);
|
|
20274
|
+
return {
|
|
20275
|
+
messages: [
|
|
20276
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
20277
|
+
{ role: "user", content: `What ${category} memories do you have?` },
|
|
20278
|
+
{
|
|
20279
|
+
role: "assistant",
|
|
20280
|
+
content: matched.length > 0 ? `Here are my ${category} memories:
|
|
20281
|
+
${matched.map((m) => `- ${m.key}: ${m.value.slice(0, 120)}${m.value.length > 120 ? "..." : ""}`).join(`
|
|
20282
|
+
`)}` : `I don't have any ${category} memories stored yet.`
|
|
20283
|
+
}
|
|
20284
|
+
]
|
|
20285
|
+
};
|
|
20286
|
+
}
|
|
20287
|
+
function readConfig() {
|
|
20288
|
+
if (!existsSync42(CONFIG_PATH))
|
|
20289
|
+
return {};
|
|
20290
|
+
try {
|
|
20291
|
+
const raw = readFileSync32(CONFIG_PATH, "utf-8");
|
|
20292
|
+
return JSON.parse(raw);
|
|
20293
|
+
} catch {
|
|
20294
|
+
return {};
|
|
20295
|
+
}
|
|
20296
|
+
}
|
|
20297
|
+
function writeConfig(config) {
|
|
20298
|
+
if (!existsSync42(CONFIG_DIR)) {
|
|
20299
|
+
mkdirSync42(CONFIG_DIR, { recursive: true });
|
|
20300
|
+
}
|
|
20301
|
+
writeFileSync32(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
20302
|
+
`, "utf-8");
|
|
20303
|
+
}
|
|
20304
|
+
function getActiveModel() {
|
|
20305
|
+
const config = readConfig();
|
|
20306
|
+
return config.activeModel ?? DEFAULT_MODEL2;
|
|
20307
|
+
}
|
|
20308
|
+
function setActiveModel(modelId) {
|
|
20309
|
+
const config = readConfig();
|
|
20310
|
+
config.activeModel = modelId;
|
|
20311
|
+
writeConfig(config);
|
|
20312
|
+
}
|
|
20313
|
+
function clearActiveModel() {
|
|
20314
|
+
const config = readConfig();
|
|
20315
|
+
delete config.activeModel;
|
|
20316
|
+
writeConfig(config);
|
|
20317
|
+
}
|
|
19368
20318
|
var __defProp2, __export2 = (target, all) => {
|
|
19369
20319
|
for (var name in all)
|
|
19370
20320
|
__defProp2(target, name, {
|
|
@@ -19407,7 +20357,27 @@ Return JSON with this exact shape:
|
|
|
19407
20357
|
"relations": [
|
|
19408
20358
|
{ "from": string, "to": string, "type": "uses"|"knows"|"depends_on"|"created_by"|"related_to"|"contradicts"|"part_of"|"implements"|"happened_before"|"happened_after"|"caused_by"|"resulted_in"|"supersedes"|"version_of" }
|
|
19409
20359
|
]
|
|
19410
|
-
}`, ANTHROPIC_MODELS, AnthropicProvider, OpenAICompatProvider, OPENAI_MODELS, OpenAIProvider, CEREBRAS_MODELS, CerebrasProvider, GROK_MODELS, GrokProvider, providerRegistry, MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue, DEDUP_SIMILARITY_THRESHOLD = 0.85, DEFAULT_CONFIG2, _stats
|
|
20360
|
+
}`, ANTHROPIC_MODELS, AnthropicProvider, OpenAICompatProvider, OPENAI_MODELS, OpenAIProvider, CEREBRAS_MODELS, CerebrasProvider, GROK_MODELS, GrokProvider, providerRegistry, MAX_QUEUE_SIZE = 100, CONCURRENCY = 3, autoMemoryQueue, DEDUP_SIMILARITY_THRESHOLD = 0.85, DEFAULT_CONFIG2, _stats, SYSTEM_PROMPT = "You are an AI assistant with persistent memory that recalls and saves information across sessions.", gatherTrainingData = async (options = {}) => {
|
|
20361
|
+
const allMemories = listMemories({ status: "active" });
|
|
20362
|
+
const filtered = options.since ? allMemories.filter((m) => new Date(m.created_at) >= options.since) : allMemories;
|
|
20363
|
+
const sorted = filtered.slice().sort((a, b) => b.importance - a.importance);
|
|
20364
|
+
const fetchSet = options.limit ? sorted.slice(0, options.limit * 3) : sorted;
|
|
20365
|
+
const examples = [];
|
|
20366
|
+
for (const memory of fetchSet) {
|
|
20367
|
+
examples.push(memoryToRecallExample(memory));
|
|
20368
|
+
examples.push(memoryToSaveExample(memory));
|
|
20369
|
+
}
|
|
20370
|
+
const categories = [...new Set(fetchSet.map((m) => m.category))];
|
|
20371
|
+
for (const category of categories) {
|
|
20372
|
+
examples.push(memoryToSearchExample(fetchSet, category));
|
|
20373
|
+
}
|
|
20374
|
+
const finalExamples = options.limit ? examples.slice(0, options.limit) : examples;
|
|
20375
|
+
return {
|
|
20376
|
+
source: "mementos",
|
|
20377
|
+
examples: finalExamples,
|
|
20378
|
+
count: finalExamples.length
|
|
20379
|
+
};
|
|
20380
|
+
}, DEFAULT_MODEL2 = "gpt-4o-mini", CONFIG_DIR, CONFIG_PATH;
|
|
19411
20381
|
var init_dist = __esm(() => {
|
|
19412
20382
|
__defProp2 = Object.defineProperty;
|
|
19413
20383
|
exports_database = {};
|
|
@@ -20687,6 +21657,8 @@ Return only a number 0-10.`);
|
|
|
20687
21657
|
keepLonger: true
|
|
20688
21658
|
};
|
|
20689
21659
|
_stats = { checked: 0, skipped: 0, updated: 0 };
|
|
21660
|
+
CONFIG_DIR = join42(homedir32(), ".mementos");
|
|
21661
|
+
CONFIG_PATH = join42(CONFIG_DIR, "config.json");
|
|
20690
21662
|
});
|
|
20691
21663
|
|
|
20692
21664
|
// src/lib/page-memory.ts
|
|
@@ -20841,30 +21813,30 @@ __export(exports_dist2, {
|
|
|
20841
21813
|
acquireLock: () => acquireLock2
|
|
20842
21814
|
});
|
|
20843
21815
|
import { Database as Database3 } from "bun:sqlite";
|
|
20844
|
-
import { mkdirSync as
|
|
20845
|
-
import { join as
|
|
20846
|
-
import { homedir as
|
|
20847
|
-
import { randomUUID as
|
|
21816
|
+
import { mkdirSync as mkdirSync10 } from "fs";
|
|
21817
|
+
import { join as join11, dirname as dirname3 } from "path";
|
|
21818
|
+
import { homedir as homedir11 } from "os";
|
|
21819
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
20848
21820
|
import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
|
|
20849
21821
|
import { join as join33 } from "path";
|
|
20850
|
-
import { homedir as
|
|
21822
|
+
import { homedir as homedir33 } from "os";
|
|
20851
21823
|
import { readFileSync as readFileSync5 } from "fs";
|
|
20852
21824
|
import { join as join23 } from "path";
|
|
20853
21825
|
import { homedir as homedir23 } from "os";
|
|
20854
21826
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
20855
21827
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync33 } from "fs";
|
|
20856
|
-
import { join as
|
|
21828
|
+
import { join as join43, dirname as dirname22 } from "path";
|
|
20857
21829
|
import { homedir as homedir42 } from "os";
|
|
20858
21830
|
function getDbPath2() {
|
|
20859
21831
|
if (process.env.CONVERSATIONS_DB_PATH)
|
|
20860
21832
|
return process.env.CONVERSATIONS_DB_PATH;
|
|
20861
|
-
return
|
|
21833
|
+
return join11(homedir11(), ".conversations", "messages.db");
|
|
20862
21834
|
}
|
|
20863
21835
|
function getDb() {
|
|
20864
21836
|
if (db)
|
|
20865
21837
|
return db;
|
|
20866
21838
|
const dbPath = getDbPath2();
|
|
20867
|
-
|
|
21839
|
+
mkdirSync10(dirname3(dbPath), { recursive: true });
|
|
20868
21840
|
db = new Database3(dbPath, { create: true });
|
|
20869
21841
|
db.exec("PRAGMA journal_mode = WAL");
|
|
20870
21842
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -21192,7 +22164,7 @@ function parseMessage(row) {
|
|
|
21192
22164
|
function getAttachmentsDir() {
|
|
21193
22165
|
if (process.env.CONVERSATIONS_ATTACHMENTS_DIR)
|
|
21194
22166
|
return process.env.CONVERSATIONS_ATTACHMENTS_DIR;
|
|
21195
|
-
return join33(
|
|
22167
|
+
return join33(homedir33(), ".conversations", "attachments");
|
|
21196
22168
|
}
|
|
21197
22169
|
function guessMimeType(name) {
|
|
21198
22170
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
@@ -21224,7 +22196,7 @@ function guessMimeType(name) {
|
|
|
21224
22196
|
function sendMessage(opts) {
|
|
21225
22197
|
const db2 = getDb();
|
|
21226
22198
|
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
21227
|
-
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${
|
|
22199
|
+
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID11().slice(0, 8)}`);
|
|
21228
22200
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
21229
22201
|
const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
|
|
21230
22202
|
const blocking = opts.blocking ? 1 : 0;
|
|
@@ -24863,7 +25835,7 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
24863
25835
|
"zinc-eagle",
|
|
24864
25836
|
"zone-fox"
|
|
24865
25837
|
];
|
|
24866
|
-
AGENT_ID_FILE =
|
|
25838
|
+
AGENT_ID_FILE = join43(homedir42(), ".conversations", "agent-id");
|
|
24867
25839
|
init_db();
|
|
24868
25840
|
init_db();
|
|
24869
25841
|
CONFLICT_THRESHOLD_SECONDS = 30 * 60;
|
|
@@ -25205,7 +26177,7 @@ __export(exports_dist3, {
|
|
|
25205
26177
|
deleteTemplate: () => deleteTemplate,
|
|
25206
26178
|
deleteTaskList: () => deleteTaskList,
|
|
25207
26179
|
deleteTask: () => deleteTask,
|
|
25208
|
-
deleteSession: () =>
|
|
26180
|
+
deleteSession: () => deleteSession2,
|
|
25209
26181
|
deleteProject: () => deleteProject2,
|
|
25210
26182
|
deletePlan: () => deletePlan,
|
|
25211
26183
|
deleteOrg: () => deleteOrg,
|
|
@@ -25266,17 +26238,17 @@ __export(exports_dist3, {
|
|
|
25266
26238
|
AgentNotFoundError: () => AgentNotFoundError2
|
|
25267
26239
|
});
|
|
25268
26240
|
import { Database as Database4 } from "bun:sqlite";
|
|
25269
|
-
import { existsSync as
|
|
25270
|
-
import { dirname as dirname5, join as
|
|
26241
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync11 } from "fs";
|
|
26242
|
+
import { dirname as dirname5, join as join12, resolve as resolve3 } from "path";
|
|
25271
26243
|
import { existsSync as existsSync33 } from "fs";
|
|
25272
26244
|
import { join as join34 } from "path";
|
|
25273
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as
|
|
26245
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
25274
26246
|
import { join as join24 } from "path";
|
|
25275
|
-
import { existsSync as
|
|
25276
|
-
import { join as
|
|
26247
|
+
import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
|
|
26248
|
+
import { join as join44 } from "path";
|
|
25277
26249
|
import { existsSync as existsSync52 } from "fs";
|
|
25278
26250
|
import { join as join52 } from "path";
|
|
25279
|
-
import { readFileSync as
|
|
26251
|
+
import { readFileSync as readFileSync33, statSync as statSync22 } from "fs";
|
|
25280
26252
|
import { relative, resolve as resolve22, join as join62 } from "path";
|
|
25281
26253
|
import { execSync as execSync2 } from "child_process";
|
|
25282
26254
|
|
|
@@ -25500,8 +26472,8 @@ function isInMemoryDb2(path) {
|
|
|
25500
26472
|
function findNearestTodosDb(startDir) {
|
|
25501
26473
|
let dir = resolve3(startDir);
|
|
25502
26474
|
while (true) {
|
|
25503
|
-
const candidate =
|
|
25504
|
-
if (
|
|
26475
|
+
const candidate = join12(dir, ".todos", "todos.db");
|
|
26476
|
+
if (existsSync7(candidate))
|
|
25505
26477
|
return candidate;
|
|
25506
26478
|
const parent = dirname5(dir);
|
|
25507
26479
|
if (parent === dir)
|
|
@@ -25513,7 +26485,7 @@ function findNearestTodosDb(startDir) {
|
|
|
25513
26485
|
function findGitRoot2(startDir) {
|
|
25514
26486
|
let dir = resolve3(startDir);
|
|
25515
26487
|
while (true) {
|
|
25516
|
-
if (
|
|
26488
|
+
if (existsSync7(join12(dir, ".git")))
|
|
25517
26489
|
return dir;
|
|
25518
26490
|
const parent = dirname5(dir);
|
|
25519
26491
|
if (parent === dir)
|
|
@@ -25533,18 +26505,18 @@ function getDbPath3() {
|
|
|
25533
26505
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
25534
26506
|
const gitRoot = findGitRoot2(cwd);
|
|
25535
26507
|
if (gitRoot) {
|
|
25536
|
-
return
|
|
26508
|
+
return join12(gitRoot, ".todos", "todos.db");
|
|
25537
26509
|
}
|
|
25538
26510
|
}
|
|
25539
26511
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
25540
|
-
return
|
|
26512
|
+
return join12(home, ".todos", "todos.db");
|
|
25541
26513
|
}
|
|
25542
26514
|
function ensureDir3(filePath) {
|
|
25543
26515
|
if (isInMemoryDb2(filePath))
|
|
25544
26516
|
return;
|
|
25545
26517
|
const dir = dirname5(resolve3(filePath));
|
|
25546
|
-
if (!
|
|
25547
|
-
|
|
26518
|
+
if (!existsSync7(dir)) {
|
|
26519
|
+
mkdirSync11(dir, { recursive: true });
|
|
25548
26520
|
}
|
|
25549
26521
|
}
|
|
25550
26522
|
function getDatabase3(dbPath) {
|
|
@@ -26003,14 +26975,14 @@ function ensureProject2(name, path, db2) {
|
|
|
26003
26975
|
}
|
|
26004
26976
|
return createProject3({ name, path }, d);
|
|
26005
26977
|
}
|
|
26006
|
-
function
|
|
26978
|
+
function ensureDir23(dir) {
|
|
26007
26979
|
if (!existsSync23(dir))
|
|
26008
26980
|
mkdirSync24(dir, { recursive: true });
|
|
26009
26981
|
}
|
|
26010
26982
|
function listJsonFiles(dir) {
|
|
26011
26983
|
if (!existsSync23(dir))
|
|
26012
26984
|
return [];
|
|
26013
|
-
return
|
|
26985
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".json"));
|
|
26014
26986
|
}
|
|
26015
26987
|
function readJsonFile(path) {
|
|
26016
26988
|
try {
|
|
@@ -27634,11 +28606,11 @@ function autoReleaseStaleAgents(db2) {
|
|
|
27634
28606
|
const result = d.run("UPDATE agents SET session_id = NULL WHERE status = 'active' AND session_id IS NOT NULL AND last_seen_at < ?", [cutoff]);
|
|
27635
28607
|
return result.changes;
|
|
27636
28608
|
}
|
|
27637
|
-
function getAvailableNamesFromPool(
|
|
28609
|
+
function getAvailableNamesFromPool(pool2, db2) {
|
|
27638
28610
|
autoReleaseStaleAgents(db2);
|
|
27639
28611
|
const cutoff = new Date(Date.now() - getActiveWindowMs()).toISOString();
|
|
27640
28612
|
const activeNames = new Set(db2.query("SELECT name FROM agents WHERE status = 'active' AND last_seen_at > ?").all(cutoff).map((r) => r.name.toLowerCase()));
|
|
27641
|
-
return
|
|
28613
|
+
return pool2.filter((name) => !activeNames.has(name.toLowerCase()));
|
|
27642
28614
|
}
|
|
27643
28615
|
function shortUuid2() {
|
|
27644
28616
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -27715,9 +28687,9 @@ function registerAgent5(input, db2) {
|
|
|
27715
28687
|
function isAgentConflict2(result) {
|
|
27716
28688
|
return result.conflict === true;
|
|
27717
28689
|
}
|
|
27718
|
-
function buildConflictError(existing, lastSeenMs,
|
|
28690
|
+
function buildConflictError(existing, lastSeenMs, pool2, d) {
|
|
27719
28691
|
const minutesAgo = Math.round((Date.now() - lastSeenMs) / 60000);
|
|
27720
|
-
const suggestions =
|
|
28692
|
+
const suggestions = pool2 ? getAvailableNamesFromPool(pool2, d) : [];
|
|
27721
28693
|
return {
|
|
27722
28694
|
conflict: true,
|
|
27723
28695
|
existing_id: existing.id,
|
|
@@ -28003,7 +28975,7 @@ function updateSessionActivity(id, db2) {
|
|
|
28003
28975
|
const d = db2 || getDatabase3();
|
|
28004
28976
|
d.run("UPDATE sessions SET last_activity = ? WHERE id = ?", [now2(), id]);
|
|
28005
28977
|
}
|
|
28006
|
-
function
|
|
28978
|
+
function deleteSession2(id, db2) {
|
|
28007
28979
|
const d = db2 || getDatabase3();
|
|
28008
28980
|
const result = d.run("DELETE FROM sessions WHERE id = ?", [id]);
|
|
28009
28981
|
return result.changes > 0;
|
|
@@ -28822,13 +29794,13 @@ function searchTasks(options, projectId, taskListId, db2) {
|
|
|
28822
29794
|
return rows.map(rowToTask3);
|
|
28823
29795
|
}
|
|
28824
29796
|
function getTaskListDir(taskListId) {
|
|
28825
|
-
return
|
|
29797
|
+
return join44(HOME, ".claude", "tasks", taskListId);
|
|
28826
29798
|
}
|
|
28827
29799
|
function readClaudeTask(dir, filename) {
|
|
28828
|
-
return readJsonFile(
|
|
29800
|
+
return readJsonFile(join44(dir, filename));
|
|
28829
29801
|
}
|
|
28830
29802
|
function writeClaudeTask(dir, task) {
|
|
28831
|
-
writeJsonFile(
|
|
29803
|
+
writeJsonFile(join44(dir, `${task.id}.json`), task);
|
|
28832
29804
|
}
|
|
28833
29805
|
function toClaudeStatus(status) {
|
|
28834
29806
|
if (status === "pending" || status === "in_progress" || status === "completed") {
|
|
@@ -28840,14 +29812,14 @@ function toSqliteStatus(status) {
|
|
|
28840
29812
|
return status;
|
|
28841
29813
|
}
|
|
28842
29814
|
function readPrefixCounter(dir) {
|
|
28843
|
-
const path =
|
|
28844
|
-
if (!
|
|
29815
|
+
const path = join44(dir, ".prefix-counter");
|
|
29816
|
+
if (!existsSync43(path))
|
|
28845
29817
|
return 0;
|
|
28846
29818
|
const val = parseInt(readFileSync24(path, "utf-8").trim(), 10);
|
|
28847
29819
|
return isNaN(val) ? 0 : val;
|
|
28848
29820
|
}
|
|
28849
29821
|
function writePrefixCounter(dir, value) {
|
|
28850
|
-
writeFileSync23(
|
|
29822
|
+
writeFileSync23(join44(dir, ".prefix-counter"), String(value));
|
|
28851
29823
|
}
|
|
28852
29824
|
function formatPrefixedSubject(title, prefix, counter) {
|
|
28853
29825
|
const padded = String(counter).padStart(5, "0");
|
|
@@ -28874,8 +29846,8 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
28874
29846
|
}
|
|
28875
29847
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
28876
29848
|
const dir = getTaskListDir(taskListId);
|
|
28877
|
-
if (!
|
|
28878
|
-
|
|
29849
|
+
if (!existsSync43(dir))
|
|
29850
|
+
ensureDir23(dir);
|
|
28879
29851
|
const filter = {};
|
|
28880
29852
|
if (projectId)
|
|
28881
29853
|
filter["project_id"] = projectId;
|
|
@@ -28883,7 +29855,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
28883
29855
|
const existingByTodosId = new Map;
|
|
28884
29856
|
const files = listJsonFiles(dir);
|
|
28885
29857
|
for (const f of files) {
|
|
28886
|
-
const path =
|
|
29858
|
+
const path = join44(dir, f);
|
|
28887
29859
|
const ct = readClaudeTask(dir, f);
|
|
28888
29860
|
if (ct?.metadata?.["todos_id"]) {
|
|
28889
29861
|
existingByTodosId.set(ct.metadata["todos_id"], { task: ct, mtimeMs: getFileMtimeMs(path) });
|
|
@@ -28970,7 +29942,7 @@ function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
28970
29942
|
}
|
|
28971
29943
|
function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
28972
29944
|
const dir = getTaskListDir(taskListId);
|
|
28973
|
-
if (!
|
|
29945
|
+
if (!existsSync43(dir)) {
|
|
28974
29946
|
return { pushed: 0, pulled: 0, errors: [`Task list directory not found: ${dir}`] };
|
|
28975
29947
|
}
|
|
28976
29948
|
const files = readdirSync22(dir).filter((f) => f.endsWith(".json"));
|
|
@@ -28990,7 +29962,7 @@ function pullFromClaudeTaskList(taskListId, projectId, options = {}) {
|
|
|
28990
29962
|
}
|
|
28991
29963
|
for (const f of files) {
|
|
28992
29964
|
try {
|
|
28993
|
-
const filePath =
|
|
29965
|
+
const filePath = join44(dir, f);
|
|
28994
29966
|
const ct = readClaudeTask(dir, f);
|
|
28995
29967
|
if (!ct)
|
|
28996
29968
|
continue;
|
|
@@ -29093,7 +30065,7 @@ function metadataKey(agent) {
|
|
|
29093
30065
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
29094
30066
|
const dir = getTaskListDir2(agent, taskListId);
|
|
29095
30067
|
if (!existsSync52(dir))
|
|
29096
|
-
|
|
30068
|
+
ensureDir23(dir);
|
|
29097
30069
|
const filter = {};
|
|
29098
30070
|
if (projectId)
|
|
29099
30071
|
filter["project_id"] = projectId;
|
|
@@ -29401,7 +30373,7 @@ function extractTodos(options, db2) {
|
|
|
29401
30373
|
for (const file of files) {
|
|
29402
30374
|
const fullPath = statSync22(basePath).isFile() ? basePath : join62(basePath, file);
|
|
29403
30375
|
try {
|
|
29404
|
-
const source =
|
|
30376
|
+
const source = readFileSync33(fullPath, "utf-8");
|
|
29405
30377
|
const relPath = statSync22(basePath).isFile() ? relative(resolve22(basePath, ".."), fullPath) : file;
|
|
29406
30378
|
const comments = extractFromSource(source, relPath, tags);
|
|
29407
30379
|
allComments.push(...comments);
|
|
@@ -30621,13 +31593,13 @@ __export(exports_dist4, {
|
|
|
30621
31593
|
CATEGORIES: () => CATEGORIES,
|
|
30622
31594
|
AGENT_TARGETS: () => AGENT_TARGETS
|
|
30623
31595
|
});
|
|
30624
|
-
import { existsSync as
|
|
30625
|
-
import { join as
|
|
30626
|
-
import { homedir as
|
|
31596
|
+
import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync7, accessSync, constants } from "fs";
|
|
31597
|
+
import { join as join13, dirname as dirname6 } from "path";
|
|
31598
|
+
import { homedir as homedir12 } from "os";
|
|
30627
31599
|
import { fileURLToPath } from "url";
|
|
30628
31600
|
import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
|
|
30629
31601
|
import { join as join25 } from "path";
|
|
30630
|
-
import { existsSync as existsSync34, readFileSync as
|
|
31602
|
+
import { existsSync as existsSync34, readFileSync as readFileSync34, writeFileSync as writeFileSync24, mkdirSync as mkdirSync25 } from "fs";
|
|
30631
31603
|
import { join as join35, dirname as dirname23 } from "path";
|
|
30632
31604
|
import { homedir as homedir24 } from "os";
|
|
30633
31605
|
function getSkillsByCategory(category) {
|
|
@@ -30734,35 +31706,35 @@ function normalizeSkillName(name) {
|
|
|
30734
31706
|
function findSkillsDir() {
|
|
30735
31707
|
let dir = __dirname2;
|
|
30736
31708
|
for (let i = 0;i < 5; i++) {
|
|
30737
|
-
const candidate =
|
|
30738
|
-
if (
|
|
31709
|
+
const candidate = join13(dir, "skills");
|
|
31710
|
+
if (existsSync8(candidate)) {
|
|
30739
31711
|
return candidate;
|
|
30740
31712
|
}
|
|
30741
31713
|
dir = dirname6(dir);
|
|
30742
31714
|
}
|
|
30743
|
-
return
|
|
31715
|
+
return join13(__dirname2, "..", "skills");
|
|
30744
31716
|
}
|
|
30745
31717
|
function getSkillPath(name) {
|
|
30746
31718
|
const skillName = normalizeSkillName(name);
|
|
30747
|
-
return
|
|
31719
|
+
return join13(SKILLS_DIR, skillName);
|
|
30748
31720
|
}
|
|
30749
31721
|
function skillExists(name) {
|
|
30750
|
-
return
|
|
31722
|
+
return existsSync8(getSkillPath(name));
|
|
30751
31723
|
}
|
|
30752
31724
|
function installSkill(name, options = {}) {
|
|
30753
31725
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
30754
31726
|
const skillName = normalizeSkillName(name);
|
|
30755
31727
|
const sourcePath = getSkillPath(name);
|
|
30756
|
-
const destDir =
|
|
30757
|
-
const destPath =
|
|
30758
|
-
if (!
|
|
31728
|
+
const destDir = join13(targetDir, ".skills");
|
|
31729
|
+
const destPath = join13(destDir, skillName);
|
|
31730
|
+
if (!existsSync8(sourcePath)) {
|
|
30759
31731
|
return {
|
|
30760
31732
|
skill: name,
|
|
30761
31733
|
success: false,
|
|
30762
31734
|
error: `Skill '${name}' not found`
|
|
30763
31735
|
};
|
|
30764
31736
|
}
|
|
30765
|
-
if (
|
|
31737
|
+
if (existsSync8(destPath) && !overwrite) {
|
|
30766
31738
|
return {
|
|
30767
31739
|
skill: name,
|
|
30768
31740
|
success: false,
|
|
@@ -30771,10 +31743,10 @@ function installSkill(name, options = {}) {
|
|
|
30771
31743
|
};
|
|
30772
31744
|
}
|
|
30773
31745
|
try {
|
|
30774
|
-
if (!
|
|
30775
|
-
|
|
31746
|
+
if (!existsSync8(destDir)) {
|
|
31747
|
+
mkdirSync12(destDir, { recursive: true });
|
|
30776
31748
|
}
|
|
30777
|
-
if (
|
|
31749
|
+
if (existsSync8(destPath) && overwrite) {
|
|
30778
31750
|
rmSync2(destPath, { recursive: true, force: true });
|
|
30779
31751
|
}
|
|
30780
31752
|
cpSync(sourcePath, destPath, {
|
|
@@ -30813,10 +31785,10 @@ function installSkills(names, options = {}) {
|
|
|
30813
31785
|
return names.map((name) => installSkill(name, options));
|
|
30814
31786
|
}
|
|
30815
31787
|
function updateSkillsIndex(skillsDir) {
|
|
30816
|
-
const indexPath =
|
|
31788
|
+
const indexPath = join13(skillsDir, "index.ts");
|
|
30817
31789
|
const meta = loadMeta(skillsDir);
|
|
30818
31790
|
const disabledSet = new Set(meta.disabled || []);
|
|
30819
|
-
const skills =
|
|
31791
|
+
const skills = readdirSync6(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
30820
31792
|
const exports = skills.map((s) => {
|
|
30821
31793
|
const name = s.replace("skill-", "").replace(/-/g, "_");
|
|
30822
31794
|
return `export * as ${name} from './${s}/src/index.js';`;
|
|
@@ -30832,11 +31804,11 @@ ${exports}
|
|
|
30832
31804
|
writeFileSync6(indexPath, content);
|
|
30833
31805
|
}
|
|
30834
31806
|
function getMetaPath(skillsDir) {
|
|
30835
|
-
return
|
|
31807
|
+
return join13(skillsDir, ".meta.json");
|
|
30836
31808
|
}
|
|
30837
31809
|
function loadMeta(skillsDir) {
|
|
30838
31810
|
const metaPath2 = getMetaPath(skillsDir);
|
|
30839
|
-
if (
|
|
31811
|
+
if (existsSync8(metaPath2)) {
|
|
30840
31812
|
try {
|
|
30841
31813
|
return JSON.parse(readFileSync7(metaPath2, "utf-8"));
|
|
30842
31814
|
} catch {}
|
|
@@ -30851,8 +31823,8 @@ function recordInstall(skillsDir, name) {
|
|
|
30851
31823
|
const skillName = normalizeSkillName(name);
|
|
30852
31824
|
let version = "unknown";
|
|
30853
31825
|
try {
|
|
30854
|
-
const pkgPath =
|
|
30855
|
-
if (
|
|
31826
|
+
const pkgPath = join13(skillsDir, skillName, "package.json");
|
|
31827
|
+
if (existsSync8(pkgPath)) {
|
|
30856
31828
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
30857
31829
|
version = pkg.version || "unknown";
|
|
30858
31830
|
}
|
|
@@ -30866,12 +31838,12 @@ function recordRemove(skillsDir, name) {
|
|
|
30866
31838
|
saveMeta(skillsDir, meta);
|
|
30867
31839
|
}
|
|
30868
31840
|
function getInstallMeta(targetDir = process.cwd()) {
|
|
30869
|
-
return loadMeta(
|
|
31841
|
+
return loadMeta(join13(targetDir, ".skills"));
|
|
30870
31842
|
}
|
|
30871
31843
|
function disableSkill(name, targetDir = process.cwd()) {
|
|
30872
|
-
const skillsDir =
|
|
31844
|
+
const skillsDir = join13(targetDir, ".skills");
|
|
30873
31845
|
const skillName = normalizeSkillName(name);
|
|
30874
|
-
if (!
|
|
31846
|
+
if (!existsSync8(join13(skillsDir, skillName)))
|
|
30875
31847
|
return false;
|
|
30876
31848
|
const meta = loadMeta(skillsDir);
|
|
30877
31849
|
const disabled = new Set(meta.disabled || []);
|
|
@@ -30884,7 +31856,7 @@ function disableSkill(name, targetDir = process.cwd()) {
|
|
|
30884
31856
|
return true;
|
|
30885
31857
|
}
|
|
30886
31858
|
function enableSkill(name, targetDir = process.cwd()) {
|
|
30887
|
-
const skillsDir =
|
|
31859
|
+
const skillsDir = join13(targetDir, ".skills");
|
|
30888
31860
|
const meta = loadMeta(skillsDir);
|
|
30889
31861
|
const disabled = new Set(meta.disabled || []);
|
|
30890
31862
|
if (!disabled.has(name))
|
|
@@ -30896,24 +31868,24 @@ function enableSkill(name, targetDir = process.cwd()) {
|
|
|
30896
31868
|
return true;
|
|
30897
31869
|
}
|
|
30898
31870
|
function getDisabledSkills(targetDir = process.cwd()) {
|
|
30899
|
-
const meta = loadMeta(
|
|
31871
|
+
const meta = loadMeta(join13(targetDir, ".skills"));
|
|
30900
31872
|
return meta.disabled || [];
|
|
30901
31873
|
}
|
|
30902
31874
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
30903
|
-
const skillsDir =
|
|
30904
|
-
if (!
|
|
31875
|
+
const skillsDir = join13(targetDir, ".skills");
|
|
31876
|
+
if (!existsSync8(skillsDir)) {
|
|
30905
31877
|
return [];
|
|
30906
31878
|
}
|
|
30907
|
-
return
|
|
30908
|
-
const fullPath =
|
|
31879
|
+
return readdirSync6(skillsDir).filter((f) => {
|
|
31880
|
+
const fullPath = join13(skillsDir, f);
|
|
30909
31881
|
return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
|
|
30910
31882
|
}).map((f) => f.replace("skill-", ""));
|
|
30911
31883
|
}
|
|
30912
31884
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
30913
31885
|
const skillName = normalizeSkillName(name);
|
|
30914
|
-
const skillsDir =
|
|
30915
|
-
const skillPath =
|
|
30916
|
-
if (!
|
|
31886
|
+
const skillsDir = join13(targetDir, ".skills");
|
|
31887
|
+
const skillPath = join13(skillsDir, skillName);
|
|
31888
|
+
if (!existsSync8(skillPath)) {
|
|
30917
31889
|
return false;
|
|
30918
31890
|
}
|
|
30919
31891
|
rmSync2(skillPath, { recursive: true, force: true });
|
|
@@ -30924,24 +31896,24 @@ function removeSkill(name, targetDir = process.cwd()) {
|
|
|
30924
31896
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
30925
31897
|
const agentDir = `.${agent}`;
|
|
30926
31898
|
if (scope === "project") {
|
|
30927
|
-
return
|
|
31899
|
+
return join13(projectDir || process.cwd(), agentDir, "skills");
|
|
30928
31900
|
}
|
|
30929
|
-
return
|
|
31901
|
+
return join13(homedir12(), agentDir, "skills");
|
|
30930
31902
|
}
|
|
30931
31903
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
30932
31904
|
const skillName = normalizeSkillName(name);
|
|
30933
|
-
return
|
|
31905
|
+
return join13(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
30934
31906
|
}
|
|
30935
31907
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
30936
31908
|
const { agent, scope = "global", projectDir } = options;
|
|
30937
31909
|
const skillName = normalizeSkillName(name);
|
|
30938
31910
|
const sourcePath = getSkillPath(name);
|
|
30939
|
-
if (!
|
|
31911
|
+
if (!existsSync8(sourcePath)) {
|
|
30940
31912
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
30941
31913
|
}
|
|
30942
31914
|
let skillMdContent = null;
|
|
30943
|
-
const skillMdPath =
|
|
30944
|
-
if (
|
|
31915
|
+
const skillMdPath = join13(sourcePath, "SKILL.md");
|
|
31916
|
+
if (existsSync8(skillMdPath)) {
|
|
30945
31917
|
skillMdContent = readFileSync7(skillMdPath, "utf-8");
|
|
30946
31918
|
} else if (generateSkillMd) {
|
|
30947
31919
|
skillMdContent = generateSkillMd(name);
|
|
@@ -30951,8 +31923,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
30951
31923
|
}
|
|
30952
31924
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
30953
31925
|
if (scope === "global") {
|
|
30954
|
-
const agentBaseDir2 =
|
|
30955
|
-
if (!
|
|
31926
|
+
const agentBaseDir2 = join13(homedir12(), `.${agent}`);
|
|
31927
|
+
if (!existsSync8(agentBaseDir2)) {
|
|
30956
31928
|
const agentLabels = {
|
|
30957
31929
|
claude: "Claude Code",
|
|
30958
31930
|
codex: "Codex CLI",
|
|
@@ -30975,8 +31947,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
30975
31947
|
}
|
|
30976
31948
|
}
|
|
30977
31949
|
try {
|
|
30978
|
-
|
|
30979
|
-
writeFileSync6(
|
|
31950
|
+
mkdirSync12(destDir, { recursive: true });
|
|
31951
|
+
writeFileSync6(join13(destDir, "SKILL.md"), skillMdContent);
|
|
30980
31952
|
return { skill: name, success: true, path: destDir };
|
|
30981
31953
|
} catch (error) {
|
|
30982
31954
|
return {
|
|
@@ -30989,7 +31961,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
30989
31961
|
function removeSkillForAgent(name, options) {
|
|
30990
31962
|
const { agent, scope = "global", projectDir } = options;
|
|
30991
31963
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
30992
|
-
if (!
|
|
31964
|
+
if (!existsSync8(destDir)) {
|
|
30993
31965
|
return false;
|
|
30994
31966
|
}
|
|
30995
31967
|
rmSync2(destDir, { recursive: true, force: true });
|
|
@@ -31281,7 +32253,7 @@ function readConfigFile(path) {
|
|
|
31281
32253
|
if (!existsSync34(path))
|
|
31282
32254
|
return {};
|
|
31283
32255
|
try {
|
|
31284
|
-
const raw =
|
|
32256
|
+
const raw = readFileSync34(path, "utf-8");
|
|
31285
32257
|
const parsed = JSON.parse(raw);
|
|
31286
32258
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
31287
32259
|
return {};
|
|
@@ -31314,7 +32286,7 @@ function saveConfig(key, value, scope = "project") {
|
|
|
31314
32286
|
let existing = {};
|
|
31315
32287
|
if (existsSync34(filePath)) {
|
|
31316
32288
|
try {
|
|
31317
|
-
existing = JSON.parse(
|
|
32289
|
+
existing = JSON.parse(readFileSync34(filePath, "utf-8"));
|
|
31318
32290
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
31319
32291
|
existing = {};
|
|
31320
32292
|
}
|
|
@@ -32913,7 +33885,7 @@ __export(exports_cron_manager, {
|
|
|
32913
33885
|
deleteCronJob: () => deleteCronJob,
|
|
32914
33886
|
createCronJob: () => createCronJob
|
|
32915
33887
|
});
|
|
32916
|
-
import { randomUUID as
|
|
33888
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
32917
33889
|
function ensureCronTable() {
|
|
32918
33890
|
const db2 = getDatabase();
|
|
32919
33891
|
db2.exec(`
|
|
@@ -32942,7 +33914,7 @@ function ensureCronTable() {
|
|
|
32942
33914
|
function createCronJob(schedule, task, name) {
|
|
32943
33915
|
ensureCronTable();
|
|
32944
33916
|
const db2 = getDatabase();
|
|
32945
|
-
const id =
|
|
33917
|
+
const id = randomUUID12();
|
|
32946
33918
|
db2.prepare(`
|
|
32947
33919
|
INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
|
|
32948
33920
|
VALUES (?, ?, ?, ?, 1)
|
|
@@ -33000,7 +33972,7 @@ function getCronEvents(jobId, limit = 10) {
|
|
|
33000
33972
|
async function executeCronJob(job) {
|
|
33001
33973
|
ensureCronTable();
|
|
33002
33974
|
const db2 = getDatabase();
|
|
33003
|
-
const eventId =
|
|
33975
|
+
const eventId = randomUUID12();
|
|
33004
33976
|
const startedAt = new Date().toISOString();
|
|
33005
33977
|
db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
|
|
33006
33978
|
try {
|
|
@@ -33091,7 +34063,7 @@ __export(exports_url_watcher, {
|
|
|
33091
34063
|
deleteWatchJob: () => deleteWatchJob,
|
|
33092
34064
|
createWatchJob: () => createWatchJob
|
|
33093
34065
|
});
|
|
33094
|
-
import { randomUUID as
|
|
34066
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
33095
34067
|
import { createHash } from "crypto";
|
|
33096
34068
|
function ensureWatchTables() {
|
|
33097
34069
|
const db2 = getDatabase();
|
|
@@ -33123,7 +34095,7 @@ function ensureWatchTables() {
|
|
|
33123
34095
|
function createWatchJob(url, schedule, opts) {
|
|
33124
34096
|
ensureWatchTables();
|
|
33125
34097
|
const db2 = getDatabase();
|
|
33126
|
-
const id =
|
|
34098
|
+
const id = randomUUID13();
|
|
33127
34099
|
db2.prepare(`
|
|
33128
34100
|
INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
|
|
33129
34101
|
VALUES (?, ?, ?, ?, ?, ?, 1)
|
|
@@ -33159,7 +34131,7 @@ function getWatchEvents(watchId, limit = 20) {
|
|
|
33159
34131
|
async function checkWatchJob(job) {
|
|
33160
34132
|
ensureWatchTables();
|
|
33161
34133
|
const db2 = getDatabase();
|
|
33162
|
-
const eventId =
|
|
34134
|
+
const eventId = randomUUID13();
|
|
33163
34135
|
const checkedAt = new Date().toISOString();
|
|
33164
34136
|
let newContent = "";
|
|
33165
34137
|
try {
|
|
@@ -33256,7 +34228,7 @@ ${snap.tree.slice(0, 2000)}`;
|
|
|
33256
34228
|
const response = await client.messages.create({
|
|
33257
34229
|
model,
|
|
33258
34230
|
max_tokens: 512,
|
|
33259
|
-
system:
|
|
34231
|
+
system: SYSTEM_PROMPT2,
|
|
33260
34232
|
messages: [{
|
|
33261
34233
|
role: "user",
|
|
33262
34234
|
content: `Task: ${task}
|
|
@@ -33321,7 +34293,7 @@ What actions should I take next? Return JSON array.`
|
|
|
33321
34293
|
}
|
|
33322
34294
|
return { success: false, result: null, steps_taken: steps.length, steps, cost_estimate: totalTokens / 1000 * 0.00025, error: `Reached max steps (${maxSteps}) without completing task` };
|
|
33323
34295
|
}
|
|
33324
|
-
var
|
|
34296
|
+
var SYSTEM_PROMPT2 = `You are a browser automation agent. Given a task and the current page state, decide which browser actions to take.
|
|
33325
34297
|
|
|
33326
34298
|
Return a JSON array of at most 3 actions to execute next:
|
|
33327
34299
|
[{"tool": "navigate|click|type|scroll|evaluate|done", "args": {...}, "reason": "..."}]
|
|
@@ -33335,7 +34307,7 @@ var exports_mcp = {};
|
|
|
33335
34307
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
33336
34308
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
33337
34309
|
import { readFileSync as readFileSync8 } from "fs";
|
|
33338
|
-
import { join as
|
|
34310
|
+
import { join as join14 } from "path";
|
|
33339
34311
|
function json(data) {
|
|
33340
34312
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
33341
34313
|
}
|
|
@@ -33347,6 +34319,34 @@ function err(e) {
|
|
|
33347
34319
|
isError: true
|
|
33348
34320
|
};
|
|
33349
34321
|
}
|
|
34322
|
+
async function errWithScreenshot(e, sessionId) {
|
|
34323
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
34324
|
+
const code = e instanceof BrowserError ? e.code : "ERROR";
|
|
34325
|
+
let screenshot_path;
|
|
34326
|
+
if (sessionId) {
|
|
34327
|
+
try {
|
|
34328
|
+
const sid = resolveSessionId(sessionId);
|
|
34329
|
+
const page = getSessionPage(sid);
|
|
34330
|
+
const result = await takeScreenshot(page, { maxWidth: 800, quality: 50, track: false, thumbnail: false });
|
|
34331
|
+
screenshot_path = result.path;
|
|
34332
|
+
} catch {}
|
|
34333
|
+
}
|
|
34334
|
+
return {
|
|
34335
|
+
content: [{ type: "text", text: JSON.stringify({ error: msg, code, error_screenshot: screenshot_path }) }],
|
|
34336
|
+
isError: true
|
|
34337
|
+
};
|
|
34338
|
+
}
|
|
34339
|
+
function resolveSessionId(sessionId) {
|
|
34340
|
+
if (sessionId)
|
|
34341
|
+
return sessionId;
|
|
34342
|
+
const def = getDefaultSession();
|
|
34343
|
+
if (def)
|
|
34344
|
+
return def.session.id;
|
|
34345
|
+
const count = countActiveSessions2();
|
|
34346
|
+
if (count === 0)
|
|
34347
|
+
throw new BrowserError("No active sessions. Create one with browser_session_create first.", "NO_SESSION");
|
|
34348
|
+
throw new BrowserError(`${count} active sessions \u2014 specify session_id to choose one.`, "AMBIGUOUS_SESSION");
|
|
34349
|
+
}
|
|
33350
34350
|
var _pkg, networkLogCleanup, consoleCaptureCleanup, harCaptures, server, activeWatchHandles2, _startupToolCount, transport;
|
|
33351
34351
|
var init_mcp = __esm(async () => {
|
|
33352
34352
|
init_zod();
|
|
@@ -33368,10 +34368,11 @@ var init_mcp = __esm(async () => {
|
|
|
33368
34368
|
init_snapshot();
|
|
33369
34369
|
init_files_integration();
|
|
33370
34370
|
init_recordings();
|
|
34371
|
+
init_timeline();
|
|
33371
34372
|
init_dialogs();
|
|
33372
34373
|
init_profiles();
|
|
33373
34374
|
init_types();
|
|
33374
|
-
_pkg = JSON.parse(readFileSync8(
|
|
34375
|
+
_pkg = JSON.parse(readFileSync8(join14(import.meta.dir, "../../package.json"), "utf8"));
|
|
33375
34376
|
networkLogCleanup = new Map;
|
|
33376
34377
|
consoleCaptureCleanup = new Map;
|
|
33377
34378
|
harCaptures = new Map;
|
|
@@ -33379,7 +34380,7 @@ var init_mcp = __esm(async () => {
|
|
|
33379
34380
|
name: "@hasna/browser",
|
|
33380
34381
|
version: "0.0.1"
|
|
33381
34382
|
});
|
|
33382
|
-
server.tool("browser_session_create", "Create a new browser session
|
|
34383
|
+
server.tool("browser_session_create", "Create a new browser session. If agent_id is set and already has an active session, returns the existing one (use force_new to override). If session_id is omitted on other tools, the single active session is auto-selected. Use cdp_url to attach to an already-running Chrome instance.", {
|
|
33383
34384
|
engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
|
|
33384
34385
|
use_case: exports_external.string().optional(),
|
|
33385
34386
|
project_id: exports_external.string().optional(),
|
|
@@ -33388,9 +34389,19 @@ var init_mcp = __esm(async () => {
|
|
|
33388
34389
|
headless: exports_external.boolean().optional().default(true),
|
|
33389
34390
|
viewport_width: exports_external.number().optional().default(1280),
|
|
33390
34391
|
viewport_height: exports_external.number().optional().default(720),
|
|
33391
|
-
stealth: exports_external.boolean().optional().default(false)
|
|
33392
|
-
|
|
34392
|
+
stealth: exports_external.boolean().optional().default(false),
|
|
34393
|
+
auto_gallery: exports_external.boolean().optional().default(false),
|
|
34394
|
+
storage_state: exports_external.string().optional().describe("Name of saved storage state to load (restores cookies/auth from previous session)"),
|
|
34395
|
+
force_new: exports_external.boolean().optional().default(false).describe("Force create a new session even if agent already has one"),
|
|
34396
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
34397
|
+
cdp_url: exports_external.string().optional().describe("Connect to existing Chrome via CDP (e.g. http://localhost:9222). Start Chrome with --remote-debugging-port=9222")
|
|
34398
|
+
}, async ({ engine, use_case, project_id, agent_id, start_url, headless, viewport_width, viewport_height, stealth, auto_gallery, storage_state, force_new, tags, cdp_url }) => {
|
|
33393
34399
|
try {
|
|
34400
|
+
if (agent_id && !force_new) {
|
|
34401
|
+
const existing = getActiveSessionForAgent2(agent_id);
|
|
34402
|
+
if (existing)
|
|
34403
|
+
return json({ session: existing.session, reused: true });
|
|
34404
|
+
}
|
|
33394
34405
|
const { session } = await createSession2({
|
|
33395
34406
|
engine,
|
|
33396
34407
|
useCase: use_case,
|
|
@@ -33399,44 +34410,68 @@ var init_mcp = __esm(async () => {
|
|
|
33399
34410
|
startUrl: start_url,
|
|
33400
34411
|
headless,
|
|
33401
34412
|
viewport: { width: viewport_width, height: viewport_height },
|
|
33402
|
-
stealth
|
|
34413
|
+
stealth,
|
|
34414
|
+
autoGallery: auto_gallery,
|
|
34415
|
+
storageState: storage_state,
|
|
34416
|
+
cdpUrl: cdp_url
|
|
33403
34417
|
});
|
|
33404
|
-
|
|
34418
|
+
if (tags?.length) {
|
|
34419
|
+
const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
34420
|
+
for (const tag of tags)
|
|
34421
|
+
addSessionTag2(session.id, tag);
|
|
34422
|
+
}
|
|
34423
|
+
logEvent(session.id, "session_created", { engine: session.engine });
|
|
34424
|
+
return json({ session, reused: false });
|
|
33405
34425
|
} catch (e) {
|
|
33406
34426
|
return err(e);
|
|
33407
34427
|
}
|
|
33408
34428
|
});
|
|
33409
|
-
server.tool("browser_session_list", "List all browser sessions", { status: exports_external.enum(["active", "closed", "error"]).optional(), project_id: exports_external.string().optional() }, async ({ status, project_id }) => {
|
|
34429
|
+
server.tool("browser_session_list", "List all browser sessions. Optionally filter by tag.", { status: exports_external.enum(["active", "closed", "error"]).optional(), project_id: exports_external.string().optional(), tag: exports_external.string().optional() }, async ({ status, project_id, tag }) => {
|
|
33410
34430
|
try {
|
|
34431
|
+
if (tag) {
|
|
34432
|
+
const { listSessionsByTag: listSessionsByTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
34433
|
+
return json({ sessions: listSessionsByTag2(tag) });
|
|
34434
|
+
}
|
|
33411
34435
|
return json({ sessions: listSessions2({ status, projectId: project_id }) });
|
|
33412
34436
|
} catch (e) {
|
|
33413
34437
|
return err(e);
|
|
33414
34438
|
}
|
|
33415
34439
|
});
|
|
33416
|
-
server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34440
|
+
server.tool("browser_session_close", "Close a browser session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33417
34441
|
try {
|
|
33418
|
-
const
|
|
33419
|
-
|
|
33420
|
-
|
|
33421
|
-
|
|
33422
|
-
|
|
33423
|
-
|
|
34442
|
+
const sid = resolveSessionId(session_id);
|
|
34443
|
+
const session = await closeSession2(sid);
|
|
34444
|
+
networkLogCleanup.get(sid)?.();
|
|
34445
|
+
consoleCaptureCleanup.get(sid)?.();
|
|
34446
|
+
networkLogCleanup.delete(sid);
|
|
34447
|
+
consoleCaptureCleanup.delete(sid);
|
|
34448
|
+
harCaptures.delete(sid);
|
|
33424
34449
|
return json({ session });
|
|
33425
34450
|
} catch (e) {
|
|
33426
34451
|
return err(e);
|
|
33427
34452
|
}
|
|
33428
34453
|
});
|
|
34454
|
+
server.tool("browser_session_timeline", "Get chronological action log for a session", { session_id: exports_external.string().optional(), limit: exports_external.number().optional().default(50) }, async ({ session_id, limit }) => {
|
|
34455
|
+
try {
|
|
34456
|
+
const sid = resolveSessionId(session_id);
|
|
34457
|
+
const events = getTimeline(sid, limit);
|
|
34458
|
+
return json({ events, count: events.length });
|
|
34459
|
+
} catch (e) {
|
|
34460
|
+
return err(e);
|
|
34461
|
+
}
|
|
34462
|
+
});
|
|
33429
34463
|
server.tool("browser_navigate", "Navigate to a URL. Auto-detects redirects, auto-names session, returns compact refs + thumbnail.", {
|
|
33430
|
-
session_id: exports_external.string(),
|
|
34464
|
+
session_id: exports_external.string().optional(),
|
|
33431
34465
|
url: exports_external.string(),
|
|
33432
34466
|
timeout: exports_external.number().optional().default(30000),
|
|
33433
34467
|
auto_snapshot: exports_external.boolean().optional().default(true),
|
|
33434
34468
|
auto_thumbnail: exports_external.boolean().optional().default(true)
|
|
33435
34469
|
}, async ({ session_id, url, timeout, auto_snapshot, auto_thumbnail }) => {
|
|
33436
34470
|
try {
|
|
33437
|
-
const
|
|
33438
|
-
|
|
33439
|
-
|
|
34471
|
+
const sid = resolveSessionId(session_id);
|
|
34472
|
+
const page = getSessionPage(sid);
|
|
34473
|
+
if (isBunSession(sid)) {
|
|
34474
|
+
const bunView = getSessionBunView(sid);
|
|
33440
34475
|
await bunView.goto(url, { timeout });
|
|
33441
34476
|
await new Promise((r) => setTimeout(r, 500));
|
|
33442
34477
|
} else {
|
|
@@ -33463,10 +34498,10 @@ var init_mcp = __esm(async () => {
|
|
|
33463
34498
|
} catch {}
|
|
33464
34499
|
}
|
|
33465
34500
|
try {
|
|
33466
|
-
const session = getSession2(
|
|
34501
|
+
const session = getSession2(sid);
|
|
33467
34502
|
if (!session.name) {
|
|
33468
34503
|
const hostname = new URL(current_url).hostname;
|
|
33469
|
-
renameSession2(
|
|
34504
|
+
renameSession2(sid, hostname);
|
|
33470
34505
|
}
|
|
33471
34506
|
} catch {}
|
|
33472
34507
|
const result = {
|
|
@@ -33482,86 +34517,110 @@ var init_mcp = __esm(async () => {
|
|
|
33482
34517
|
result.thumbnail_base64 = ss.base64.length > 50000 ? "" : ss.base64;
|
|
33483
34518
|
} catch {}
|
|
33484
34519
|
}
|
|
33485
|
-
if (
|
|
34520
|
+
if (isAutoGallery(sid)) {
|
|
34521
|
+
try {
|
|
34522
|
+
const ss = await takeScreenshot(page, { maxWidth: 1280, quality: 70, thumbnail: true });
|
|
34523
|
+
const { createEntry: createEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
34524
|
+
createEntry2({ session_id: sid, url: current_url, title, path: ss.path, thumbnail_path: ss.thumbnail_path, format: "webp", width: ss.width, height: ss.height, original_size_bytes: ss.original_size_bytes, compressed_size_bytes: ss.compressed_size_bytes, compression_ratio: ss.compression_ratio, tags: [], is_favorite: false });
|
|
34525
|
+
} catch {}
|
|
34526
|
+
}
|
|
34527
|
+
if (isBunSession(sid) && auto_snapshot) {
|
|
33486
34528
|
await new Promise((r) => setTimeout(r, 200));
|
|
33487
34529
|
}
|
|
33488
34530
|
if (auto_snapshot) {
|
|
33489
34531
|
try {
|
|
33490
|
-
const snap = await takeSnapshot(page,
|
|
33491
|
-
setLastSnapshot(
|
|
34532
|
+
const snap = await takeSnapshot(page, sid);
|
|
34533
|
+
setLastSnapshot(sid, snap);
|
|
33492
34534
|
const refEntries = Object.entries(snap.refs).slice(0, 30);
|
|
33493
34535
|
result.snapshot_refs = refEntries.map(([ref, info]) => `${info.role}:${info.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
33494
34536
|
result.interactive_count = snap.interactive_count;
|
|
33495
|
-
result.has_errors = getConsoleLog(
|
|
34537
|
+
result.has_errors = getConsoleLog(sid, "error").length > 0;
|
|
33496
34538
|
} catch {}
|
|
33497
34539
|
}
|
|
34540
|
+
logEvent(sid, "navigate", { url, title, current_url });
|
|
33498
34541
|
return json(result);
|
|
33499
34542
|
} catch (e) {
|
|
33500
|
-
return
|
|
34543
|
+
return errWithScreenshot(e, session_id);
|
|
33501
34544
|
}
|
|
33502
34545
|
});
|
|
33503
|
-
server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34546
|
+
server.tool("browser_back", "Navigate back in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33504
34547
|
try {
|
|
33505
|
-
const
|
|
34548
|
+
const sid = resolveSessionId(session_id);
|
|
34549
|
+
const page = getSessionPage(sid);
|
|
33506
34550
|
await goBack(page);
|
|
33507
34551
|
return json({ url: page.url() });
|
|
33508
34552
|
} catch (e) {
|
|
33509
34553
|
return err(e);
|
|
33510
34554
|
}
|
|
33511
34555
|
});
|
|
33512
|
-
server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34556
|
+
server.tool("browser_forward", "Navigate forward in browser history", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33513
34557
|
try {
|
|
33514
|
-
const
|
|
34558
|
+
const sid = resolveSessionId(session_id);
|
|
34559
|
+
const page = getSessionPage(sid);
|
|
33515
34560
|
await goForward(page);
|
|
33516
34561
|
return json({ url: page.url() });
|
|
33517
34562
|
} catch (e) {
|
|
33518
34563
|
return err(e);
|
|
33519
34564
|
}
|
|
33520
34565
|
});
|
|
33521
|
-
server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34566
|
+
server.tool("browser_reload", "Reload the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33522
34567
|
try {
|
|
33523
|
-
const
|
|
34568
|
+
const sid = resolveSessionId(session_id);
|
|
34569
|
+
const page = getSessionPage(sid);
|
|
33524
34570
|
await reload(page);
|
|
33525
34571
|
return json({ url: page.url() });
|
|
33526
34572
|
} catch (e) {
|
|
33527
34573
|
return err(e);
|
|
33528
34574
|
}
|
|
33529
34575
|
});
|
|
33530
|
-
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 }) => {
|
|
34576
|
+
server.tool("browser_click", "Click an element by ref (from snapshot) or CSS selector. Prefer ref for reliability. Self-healing auto-tries fallback selectors if element not found.", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), button: exports_external.enum(["left", "right", "middle"]).optional(), timeout: exports_external.number().optional(), self_heal: exports_external.boolean().optional().default(true).describe("Auto-try fallback selectors if element not found") }, async ({ session_id, selector, ref, button, timeout, self_heal }) => {
|
|
33531
34577
|
try {
|
|
33532
|
-
const
|
|
34578
|
+
const sid = resolveSessionId(session_id);
|
|
34579
|
+
const page = getSessionPage(sid);
|
|
33533
34580
|
if (ref) {
|
|
33534
|
-
await clickRef(page,
|
|
34581
|
+
await clickRef(page, sid, ref, { timeout });
|
|
34582
|
+
logEvent(sid, "click", { selector: ref, method: "ref" });
|
|
33535
34583
|
return json({ clicked: ref, method: "ref" });
|
|
33536
34584
|
}
|
|
33537
34585
|
if (!selector)
|
|
33538
34586
|
return err(new Error("Either ref or selector is required"));
|
|
33539
|
-
await click(page, selector, { button, timeout });
|
|
34587
|
+
const healInfo = await click(page, selector, { button, timeout, selfHeal: self_heal });
|
|
34588
|
+
logEvent(sid, "click", { selector, method: healInfo.healed ? "healed" : "selector" });
|
|
34589
|
+
if (healInfo.healed) {
|
|
34590
|
+
return json({ clicked: selector, method: "healed", heal_method: healInfo.method, attempts: healInfo.attempts });
|
|
34591
|
+
}
|
|
33540
34592
|
return json({ clicked: selector, method: "selector" });
|
|
33541
34593
|
} catch (e) {
|
|
33542
|
-
return
|
|
34594
|
+
return errWithScreenshot(e, session_id);
|
|
33543
34595
|
}
|
|
33544
34596
|
});
|
|
33545
|
-
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 }) => {
|
|
34597
|
+
server.tool("browser_type", "Type text into an element by ref or selector. Prefer ref. Self-healing auto-tries fallback selectors if element not found.", { session_id: exports_external.string().optional(), 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(), self_heal: exports_external.boolean().optional().default(true).describe("Auto-try fallback selectors if element not found") }, async ({ session_id, selector, ref, text, clear, delay, self_heal }) => {
|
|
33546
34598
|
try {
|
|
33547
|
-
const
|
|
34599
|
+
const sid = resolveSessionId(session_id);
|
|
34600
|
+
const page = getSessionPage(sid);
|
|
33548
34601
|
if (ref) {
|
|
33549
|
-
await typeRef(page,
|
|
34602
|
+
await typeRef(page, sid, ref, text, { clear, delay });
|
|
34603
|
+
logEvent(sid, "type", { selector: ref, text: text.slice(0, 100) });
|
|
33550
34604
|
return json({ typed: text, ref, method: "ref" });
|
|
33551
34605
|
}
|
|
33552
34606
|
if (!selector)
|
|
33553
34607
|
return err(new Error("Either ref or selector is required"));
|
|
33554
|
-
await type(page, selector, text, { clear, delay });
|
|
34608
|
+
const healInfo = await type(page, selector, text, { clear, delay, selfHeal: self_heal });
|
|
34609
|
+
logEvent(sid, "type", { selector, text: text.slice(0, 100), method: healInfo.healed ? "healed" : "selector" });
|
|
34610
|
+
if (healInfo.healed) {
|
|
34611
|
+
return json({ typed: text, selector, method: "healed", heal_method: healInfo.method, attempts: healInfo.attempts });
|
|
34612
|
+
}
|
|
33555
34613
|
return json({ typed: text, selector, method: "selector" });
|
|
33556
34614
|
} catch (e) {
|
|
33557
|
-
return
|
|
34615
|
+
return errWithScreenshot(e, session_id);
|
|
33558
34616
|
}
|
|
33559
34617
|
});
|
|
33560
|
-
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 }) => {
|
|
34618
|
+
server.tool("browser_hover", "Hover over an element by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional() }, async ({ session_id, selector, ref }) => {
|
|
33561
34619
|
try {
|
|
33562
|
-
const
|
|
34620
|
+
const sid = resolveSessionId(session_id);
|
|
34621
|
+
const page = getSessionPage(sid);
|
|
33563
34622
|
if (ref) {
|
|
33564
|
-
await hoverRef(page,
|
|
34623
|
+
await hoverRef(page, sid, ref);
|
|
33565
34624
|
return json({ hovered: ref, method: "ref" });
|
|
33566
34625
|
}
|
|
33567
34626
|
if (!selector)
|
|
@@ -33572,20 +34631,22 @@ var init_mcp = __esm(async () => {
|
|
|
33572
34631
|
return err(e);
|
|
33573
34632
|
}
|
|
33574
34633
|
});
|
|
33575
|
-
server.tool("browser_scroll", "Scroll the page", { session_id: exports_external.string(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount }) => {
|
|
34634
|
+
server.tool("browser_scroll", "Scroll the page", { session_id: exports_external.string().optional(), direction: exports_external.enum(["up", "down", "left", "right"]).optional().default("down"), amount: exports_external.number().optional().default(300) }, async ({ session_id, direction, amount }) => {
|
|
33576
34635
|
try {
|
|
33577
|
-
const
|
|
34636
|
+
const sid = resolveSessionId(session_id);
|
|
34637
|
+
const page = getSessionPage(sid);
|
|
33578
34638
|
await scroll(page, direction, amount);
|
|
33579
34639
|
return json({ scrolled: direction, amount });
|
|
33580
34640
|
} catch (e) {
|
|
33581
34641
|
return err(e);
|
|
33582
34642
|
}
|
|
33583
34643
|
});
|
|
33584
|
-
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 }) => {
|
|
34644
|
+
server.tool("browser_select", "Select a dropdown option by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), value: exports_external.string() }, async ({ session_id, selector, ref, value }) => {
|
|
33585
34645
|
try {
|
|
33586
|
-
const
|
|
34646
|
+
const sid = resolveSessionId(session_id);
|
|
34647
|
+
const page = getSessionPage(sid);
|
|
33587
34648
|
if (ref) {
|
|
33588
|
-
const selected2 = await selectRef(page,
|
|
34649
|
+
const selected2 = await selectRef(page, sid, ref, value);
|
|
33589
34650
|
return json({ selected: selected2, method: "ref" });
|
|
33590
34651
|
}
|
|
33591
34652
|
if (!selector)
|
|
@@ -33596,11 +34657,12 @@ var init_mcp = __esm(async () => {
|
|
|
33596
34657
|
return err(e);
|
|
33597
34658
|
}
|
|
33598
34659
|
});
|
|
33599
|
-
server.tool("browser_toggle", "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 }) => {
|
|
34660
|
+
server.tool("browser_toggle", "Check or uncheck a checkbox by ref or selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), ref: exports_external.string().optional(), checked: exports_external.boolean() }, async ({ session_id, selector, ref, checked }) => {
|
|
33600
34661
|
try {
|
|
33601
|
-
const
|
|
34662
|
+
const sid = resolveSessionId(session_id);
|
|
34663
|
+
const page = getSessionPage(sid);
|
|
33602
34664
|
if (ref) {
|
|
33603
|
-
await checkRef(page,
|
|
34665
|
+
await checkRef(page, sid, ref, checked);
|
|
33604
34666
|
return json({ checked, ref, method: "ref" });
|
|
33605
34667
|
}
|
|
33606
34668
|
if (!selector)
|
|
@@ -33611,75 +34673,111 @@ var init_mcp = __esm(async () => {
|
|
|
33611
34673
|
return err(e);
|
|
33612
34674
|
}
|
|
33613
34675
|
});
|
|
33614
|
-
server.tool("browser_upload", "Upload a file to an input element", { session_id: exports_external.string(), selector: exports_external.string(), file_path: exports_external.string() }, async ({ session_id, selector, file_path }) => {
|
|
34676
|
+
server.tool("browser_upload", "Upload a file to an input element", { session_id: exports_external.string().optional(), selector: exports_external.string(), file_path: exports_external.string() }, async ({ session_id, selector, file_path }) => {
|
|
33615
34677
|
try {
|
|
33616
|
-
const
|
|
34678
|
+
const sid = resolveSessionId(session_id);
|
|
34679
|
+
const page = getSessionPage(sid);
|
|
33617
34680
|
await uploadFile(page, selector, file_path);
|
|
33618
34681
|
return json({ uploaded: file_path, selector });
|
|
33619
34682
|
} catch (e) {
|
|
33620
34683
|
return err(e);
|
|
33621
34684
|
}
|
|
33622
34685
|
});
|
|
33623
|
-
server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string(), key: exports_external.string() }, async ({ session_id, key }) => {
|
|
34686
|
+
server.tool("browser_press_key", "Press a keyboard key", { session_id: exports_external.string().optional(), key: exports_external.string() }, async ({ session_id, key }) => {
|
|
33624
34687
|
try {
|
|
33625
|
-
const
|
|
34688
|
+
const sid = resolveSessionId(session_id);
|
|
34689
|
+
const page = getSessionPage(sid);
|
|
33626
34690
|
await pressKey(page, key);
|
|
33627
34691
|
return json({ pressed: key });
|
|
33628
34692
|
} catch (e) {
|
|
33629
34693
|
return err(e);
|
|
33630
34694
|
}
|
|
33631
34695
|
});
|
|
33632
|
-
server.tool("browser_wait", "Wait for a selector to appear", { session_id: exports_external.string(), selector: exports_external.string(), state: exports_external.enum(["attached", "detached", "visible", "hidden"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, state, timeout }) => {
|
|
34696
|
+
server.tool("browser_wait", "Wait for a selector to appear", { session_id: exports_external.string().optional(), selector: exports_external.string(), state: exports_external.enum(["attached", "detached", "visible", "hidden"]).optional(), timeout: exports_external.number().optional() }, async ({ session_id, selector, state, timeout }) => {
|
|
33633
34697
|
try {
|
|
33634
|
-
const
|
|
34698
|
+
const sid = resolveSessionId(session_id);
|
|
34699
|
+
const page = getSessionPage(sid);
|
|
33635
34700
|
await waitForSelector(page, selector, { state, timeout });
|
|
33636
34701
|
return json({ ready: selector });
|
|
33637
34702
|
} catch (e) {
|
|
33638
34703
|
return err(e);
|
|
33639
34704
|
}
|
|
33640
34705
|
});
|
|
33641
|
-
server.tool("browser_get_text", "Get text content from the page or a selector", { session_id: exports_external.string(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
|
|
34706
|
+
server.tool("browser_get_text", "Get text content from the page or a selector. Sanitizes prompt injection by default.", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns from text (default: true)") }, async ({ session_id, selector, sanitize }) => {
|
|
33642
34707
|
try {
|
|
33643
|
-
const
|
|
33644
|
-
|
|
34708
|
+
const sid = resolveSessionId(session_id);
|
|
34709
|
+
const page = getSessionPage(sid);
|
|
34710
|
+
const text = await getText(page, selector);
|
|
34711
|
+
if (sanitize) {
|
|
34712
|
+
const { sanitizeText: sanitizeText2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
34713
|
+
const sanitized = sanitizeText2(text);
|
|
34714
|
+
return json({ text: sanitized.text, stripped: sanitized.stripped, warnings: sanitized.warnings });
|
|
34715
|
+
}
|
|
34716
|
+
return json({ text });
|
|
33645
34717
|
} catch (e) {
|
|
33646
34718
|
return err(e);
|
|
33647
34719
|
}
|
|
33648
34720
|
});
|
|
33649
|
-
server.tool("browser_get_html", "Get HTML content from the page or a selector", { session_id: exports_external.string(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
|
|
34721
|
+
server.tool("browser_get_html", "Get HTML content from the page or a selector. Sanitizes prompt injection by default.", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns and hidden elements from HTML (default: true)") }, async ({ session_id, selector, sanitize }) => {
|
|
33650
34722
|
try {
|
|
33651
|
-
const
|
|
33652
|
-
|
|
34723
|
+
const sid = resolveSessionId(session_id);
|
|
34724
|
+
const page = getSessionPage(sid);
|
|
34725
|
+
const html = await getHTML(page, selector);
|
|
34726
|
+
if (sanitize) {
|
|
34727
|
+
const { sanitizeHTML: sanitizeHTML2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
34728
|
+
const sanitized = sanitizeHTML2(html);
|
|
34729
|
+
return json({ html: sanitized.text, stripped: sanitized.stripped, warnings: sanitized.warnings });
|
|
34730
|
+
}
|
|
34731
|
+
return json({ html });
|
|
33653
34732
|
} catch (e) {
|
|
33654
34733
|
return err(e);
|
|
33655
34734
|
}
|
|
33656
34735
|
});
|
|
33657
|
-
server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34736
|
+
server.tool("browser_get_links", "Get all links from the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33658
34737
|
try {
|
|
33659
|
-
const
|
|
34738
|
+
const sid = resolveSessionId(session_id);
|
|
34739
|
+
const page = getSessionPage(sid);
|
|
33660
34740
|
const links = await getLinks(page);
|
|
33661
34741
|
return json({ links, count: links.length });
|
|
33662
34742
|
} catch (e) {
|
|
33663
34743
|
return err(e);
|
|
33664
34744
|
}
|
|
33665
34745
|
});
|
|
33666
|
-
server.tool("browser_extract", "Extract content from the page in a specified format", {
|
|
33667
|
-
session_id: exports_external.string(),
|
|
34746
|
+
server.tool("browser_extract", "Extract content from the page in a specified format. Sanitizes prompt injection by default.", {
|
|
34747
|
+
session_id: exports_external.string().optional(),
|
|
33668
34748
|
format: exports_external.enum(["text", "html", "links", "table", "structured"]).optional().default("text"),
|
|
33669
34749
|
selector: exports_external.string().optional(),
|
|
33670
|
-
schema: exports_external.record(exports_external.string()).optional()
|
|
33671
|
-
|
|
34750
|
+
schema: exports_external.record(exports_external.string()).optional(),
|
|
34751
|
+
sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns from extracted content (default: true)")
|
|
34752
|
+
}, async ({ session_id, format, selector, schema, sanitize }) => {
|
|
33672
34753
|
try {
|
|
33673
|
-
const
|
|
34754
|
+
const sid = resolveSessionId(session_id);
|
|
34755
|
+
const page = getSessionPage(sid);
|
|
33674
34756
|
const result = await extract(page, { format, selector, schema });
|
|
34757
|
+
if (sanitize) {
|
|
34758
|
+
const { sanitizeText: sanitizeText2, sanitizeHTML: sanitizeHTML2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
34759
|
+
if (result.text) {
|
|
34760
|
+
const s = sanitizeText2(result.text);
|
|
34761
|
+
result.text = s.text;
|
|
34762
|
+
result.stripped = s.stripped;
|
|
34763
|
+
result.warnings = s.warnings;
|
|
34764
|
+
}
|
|
34765
|
+
if (result.html) {
|
|
34766
|
+
const s = sanitizeHTML2(result.html);
|
|
34767
|
+
result.html = s.text;
|
|
34768
|
+
result.stripped = s.stripped;
|
|
34769
|
+
result.warnings = s.warnings;
|
|
34770
|
+
}
|
|
34771
|
+
}
|
|
33675
34772
|
return json(result);
|
|
33676
34773
|
} catch (e) {
|
|
33677
34774
|
return err(e);
|
|
33678
34775
|
}
|
|
33679
34776
|
});
|
|
33680
|
-
server.tool("browser_find", "Find elements matching a selector and return their text", { session_id: exports_external.string(), selector: exports_external.string() }, async ({ session_id, selector }) => {
|
|
34777
|
+
server.tool("browser_find", "Find elements matching a selector and return their text", { session_id: exports_external.string().optional(), selector: exports_external.string() }, async ({ session_id, selector }) => {
|
|
33681
34778
|
try {
|
|
33682
|
-
const
|
|
34779
|
+
const sid = resolveSessionId(session_id);
|
|
34780
|
+
const page = getSessionPage(sid);
|
|
33683
34781
|
const elements = await findElements(page, selector);
|
|
33684
34782
|
const texts = await Promise.all(elements.map((el) => el.textContent()));
|
|
33685
34783
|
return json({ count: elements.length, texts });
|
|
@@ -33687,16 +34785,27 @@ var init_mcp = __esm(async () => {
|
|
|
33687
34785
|
return err(e);
|
|
33688
34786
|
}
|
|
33689
34787
|
});
|
|
33690
|
-
server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc.", {
|
|
33691
|
-
session_id: exports_external.string(),
|
|
34788
|
+
server.tool("browser_snapshot", "Get accessibility snapshot with element refs (@e0, @e1...). Use compact=true (default) for token-efficient output. Use refs in browser_click, browser_type, etc. Sanitizes prompt injection by default.", {
|
|
34789
|
+
session_id: exports_external.string().optional(),
|
|
33692
34790
|
compact: exports_external.boolean().optional().default(true),
|
|
33693
34791
|
max_refs: exports_external.number().optional().default(50),
|
|
33694
|
-
full_tree: exports_external.boolean().optional().default(false)
|
|
33695
|
-
|
|
34792
|
+
full_tree: exports_external.boolean().optional().default(false),
|
|
34793
|
+
sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns from snapshot text (default: true)")
|
|
34794
|
+
}, async ({ session_id, compact, max_refs, full_tree, sanitize }) => {
|
|
33696
34795
|
try {
|
|
33697
|
-
const
|
|
33698
|
-
const
|
|
33699
|
-
|
|
34796
|
+
const sid = resolveSessionId(session_id);
|
|
34797
|
+
const page = getSessionPage(sid);
|
|
34798
|
+
const result = await takeSnapshot(page, sid);
|
|
34799
|
+
setLastSnapshot(sid, result);
|
|
34800
|
+
let injection_warnings;
|
|
34801
|
+
if (sanitize) {
|
|
34802
|
+
const { sanitizeText: sanitizeText2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
34803
|
+
const sanitized = sanitizeText2(result.tree);
|
|
34804
|
+
if (sanitized.stripped > 0) {
|
|
34805
|
+
injection_warnings = sanitized.warnings;
|
|
34806
|
+
result.tree = sanitized.text;
|
|
34807
|
+
}
|
|
34808
|
+
}
|
|
33700
34809
|
const refEntries = Object.entries(result.refs).slice(0, max_refs);
|
|
33701
34810
|
const limitedRefs = Object.fromEntries(refEntries);
|
|
33702
34811
|
const truncated = Object.keys(result.refs).length > max_refs;
|
|
@@ -33708,32 +34817,35 @@ var init_mcp = __esm(async () => {
|
|
|
33708
34817
|
interactive_count: result.interactive_count,
|
|
33709
34818
|
shown_count: refEntries.length,
|
|
33710
34819
|
truncated,
|
|
33711
|
-
refs: limitedRefs
|
|
34820
|
+
refs: limitedRefs,
|
|
34821
|
+
...injection_warnings ? { injection_warnings } : {}
|
|
33712
34822
|
});
|
|
33713
34823
|
}
|
|
33714
34824
|
const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
|
|
33715
34825
|
... (truncated \u2014 use full_tree=true for complete)` : "");
|
|
33716
|
-
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
|
|
34826
|
+
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated, ...injection_warnings ? { injection_warnings } : {} });
|
|
33717
34827
|
} catch (e) {
|
|
33718
34828
|
return err(e);
|
|
33719
34829
|
}
|
|
33720
34830
|
});
|
|
33721
|
-
server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overlay numbered labels on interactive elements
|
|
33722
|
-
session_id: exports_external.string(),
|
|
33723
|
-
selector: exports_external.string().optional(),
|
|
34831
|
+
server.tool("browser_screenshot", "Take a screenshot. Use selector to capture a specific element/section instead of the full page. Use detail='high' for AI-readable full image, 'low' for fast thumbnail. Use annotate=true to overlay numbered labels on interactive elements.", {
|
|
34832
|
+
session_id: exports_external.string().optional(),
|
|
34833
|
+
selector: exports_external.string().optional().describe("CSS selector to screenshot a specific section (e.g. '#main', '.header', 'form')"),
|
|
33724
34834
|
full_page: exports_external.boolean().optional().default(false),
|
|
33725
34835
|
format: exports_external.enum(["png", "jpeg", "webp"]).optional().default("webp"),
|
|
33726
|
-
quality: exports_external.number().optional(),
|
|
33727
|
-
max_width: exports_external.number().optional().default(
|
|
34836
|
+
quality: exports_external.number().optional().default(60),
|
|
34837
|
+
max_width: exports_external.number().optional().default(800),
|
|
33728
34838
|
compress: exports_external.boolean().optional().default(true),
|
|
33729
34839
|
thumbnail: exports_external.boolean().optional().default(true),
|
|
33730
|
-
annotate: exports_external.boolean().optional().default(false)
|
|
33731
|
-
|
|
34840
|
+
annotate: exports_external.boolean().optional().default(false),
|
|
34841
|
+
detail: exports_external.enum(["low", "high"]).optional().default("low").describe("'low' = thumbnail only (fast, saves tokens). 'high' = full readable image in base64 (larger but AI can read text).")
|
|
34842
|
+
}, async ({ session_id, selector, full_page, format, quality, max_width, compress, thumbnail, annotate, detail }) => {
|
|
33732
34843
|
try {
|
|
33733
|
-
const
|
|
34844
|
+
const sid = resolveSessionId(session_id);
|
|
34845
|
+
const page = getSessionPage(sid);
|
|
33734
34846
|
if (annotate && !selector && !full_page) {
|
|
33735
34847
|
const { annotateScreenshot: annotateScreenshot2 } = await Promise.resolve().then(() => (init_annotate(), exports_annotate));
|
|
33736
|
-
const annotated = await annotateScreenshot2(page,
|
|
34848
|
+
const annotated = await annotateScreenshot2(page, sid);
|
|
33737
34849
|
const base64 = annotated.buffer.toString("base64");
|
|
33738
34850
|
return json({
|
|
33739
34851
|
base64: base64.length > 50000 ? undefined : base64,
|
|
@@ -33744,37 +34856,42 @@ var init_mcp = __esm(async () => {
|
|
|
33744
34856
|
annotation_count: annotated.annotations.length
|
|
33745
34857
|
});
|
|
33746
34858
|
}
|
|
33747
|
-
const
|
|
34859
|
+
const effectiveMaxWidth = detail === "high" ? 1280 : max_width;
|
|
34860
|
+
const effectiveQuality = detail === "high" ? 75 : quality;
|
|
34861
|
+
const result = await takeScreenshot(page, { selector, fullPage: full_page, format, quality: effectiveQuality, maxWidth: effectiveMaxWidth, compress, thumbnail });
|
|
33748
34862
|
result.url = page.url();
|
|
33749
34863
|
try {
|
|
33750
34864
|
const buf = Buffer.from(result.base64, "base64");
|
|
33751
34865
|
const filename = result.path.split("/").pop() ?? `screenshot.${format ?? "webp"}`;
|
|
33752
|
-
const dl = saveToDownloads(buf, filename, { sessionId:
|
|
34866
|
+
const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "screenshot", sourceUrl: page.url() });
|
|
33753
34867
|
result.download_id = dl.id;
|
|
33754
34868
|
} catch {}
|
|
33755
|
-
|
|
34869
|
+
result.estimated_tokens = Math.ceil(result.base64.length / 4);
|
|
34870
|
+
if (detail !== "high" && result.base64.length > 40000) {
|
|
33756
34871
|
result.base64_truncated = true;
|
|
33757
34872
|
result.full_image_path = result.path;
|
|
33758
34873
|
result.base64 = result.thumbnail_base64 ?? "";
|
|
33759
34874
|
}
|
|
34875
|
+
logEvent(sid, "screenshot", { path: result.path, detail, selector });
|
|
33760
34876
|
return json(result);
|
|
33761
34877
|
} catch (e) {
|
|
33762
34878
|
return err(e);
|
|
33763
34879
|
}
|
|
33764
34880
|
});
|
|
33765
34881
|
server.tool("browser_pdf", "Generate a PDF of the current page", {
|
|
33766
|
-
session_id: exports_external.string(),
|
|
34882
|
+
session_id: exports_external.string().optional(),
|
|
33767
34883
|
format: exports_external.enum(["A4", "Letter", "A3", "A5"]).optional().default("A4"),
|
|
33768
34884
|
landscape: exports_external.boolean().optional().default(false),
|
|
33769
34885
|
print_background: exports_external.boolean().optional().default(true)
|
|
33770
34886
|
}, async ({ session_id, format, landscape, print_background }) => {
|
|
33771
34887
|
try {
|
|
33772
|
-
const
|
|
34888
|
+
const sid = resolveSessionId(session_id);
|
|
34889
|
+
const page = getSessionPage(sid);
|
|
33773
34890
|
const result = await generatePDF(page, { format, landscape, printBackground: print_background });
|
|
33774
34891
|
try {
|
|
33775
34892
|
const buf = Buffer.from(result.base64, "base64");
|
|
33776
34893
|
const filename = result.path.split("/").pop() ?? "document.pdf";
|
|
33777
|
-
const dl = saveToDownloads(buf, filename, { sessionId:
|
|
34894
|
+
const dl = saveToDownloads(buf, filename, { sessionId: sid, type: "pdf", sourceUrl: page.url() });
|
|
33778
34895
|
result.download_id = dl.id;
|
|
33779
34896
|
} catch {}
|
|
33780
34897
|
return json(result);
|
|
@@ -33782,25 +34899,27 @@ var init_mcp = __esm(async () => {
|
|
|
33782
34899
|
return err(e);
|
|
33783
34900
|
}
|
|
33784
34901
|
});
|
|
33785
|
-
server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string(), script: exports_external.string() }, async ({ session_id, script }) => {
|
|
34902
|
+
server.tool("browser_evaluate", "Execute JavaScript in the page context", { session_id: exports_external.string().optional(), script: exports_external.string() }, async ({ session_id, script }) => {
|
|
33786
34903
|
try {
|
|
33787
|
-
const
|
|
34904
|
+
const sid = resolveSessionId(session_id);
|
|
34905
|
+
const page = getSessionPage(sid);
|
|
33788
34906
|
const result = await page.evaluate(script);
|
|
33789
34907
|
return json({ result });
|
|
33790
34908
|
} catch (e) {
|
|
33791
|
-
return
|
|
34909
|
+
return errWithScreenshot(e, session_id);
|
|
33792
34910
|
}
|
|
33793
34911
|
});
|
|
33794
|
-
server.tool("browser_cookies_get", "Get cookies from the current session", { session_id: exports_external.string(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
|
|
34912
|
+
server.tool("browser_cookies_get", "Get cookies from the current session", { session_id: exports_external.string().optional(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
|
|
33795
34913
|
try {
|
|
33796
|
-
const
|
|
34914
|
+
const sid = resolveSessionId(session_id);
|
|
34915
|
+
const page = getSessionPage(sid);
|
|
33797
34916
|
return json({ cookies: await getCookies(page, { name, domain }) });
|
|
33798
34917
|
} catch (e) {
|
|
33799
34918
|
return err(e);
|
|
33800
34919
|
}
|
|
33801
34920
|
});
|
|
33802
34921
|
server.tool("browser_cookies_set", "Set a cookie in the current session", {
|
|
33803
|
-
session_id: exports_external.string(),
|
|
34922
|
+
session_id: exports_external.string().optional(),
|
|
33804
34923
|
name: exports_external.string(),
|
|
33805
34924
|
value: exports_external.string(),
|
|
33806
34925
|
domain: exports_external.string().optional(),
|
|
@@ -33810,7 +34929,8 @@ var init_mcp = __esm(async () => {
|
|
|
33810
34929
|
secure: exports_external.boolean().optional().default(false)
|
|
33811
34930
|
}, async ({ session_id, name, value, domain, path, expires, http_only, secure }) => {
|
|
33812
34931
|
try {
|
|
33813
|
-
const
|
|
34932
|
+
const sid = resolveSessionId(session_id);
|
|
34933
|
+
const page = getSessionPage(sid);
|
|
33814
34934
|
await setCookie(page, {
|
|
33815
34935
|
name,
|
|
33816
34936
|
value,
|
|
@@ -33826,27 +34946,30 @@ var init_mcp = __esm(async () => {
|
|
|
33826
34946
|
return err(e);
|
|
33827
34947
|
}
|
|
33828
34948
|
});
|
|
33829
|
-
server.tool("browser_cookies_clear", "Clear cookies from the current session", { session_id: exports_external.string(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
|
|
34949
|
+
server.tool("browser_cookies_clear", "Clear cookies from the current session", { session_id: exports_external.string().optional(), name: exports_external.string().optional(), domain: exports_external.string().optional() }, async ({ session_id, name, domain }) => {
|
|
33830
34950
|
try {
|
|
33831
|
-
const
|
|
34951
|
+
const sid = resolveSessionId(session_id);
|
|
34952
|
+
const page = getSessionPage(sid);
|
|
33832
34953
|
await clearCookies(page, name || domain ? { name, domain } : undefined);
|
|
33833
34954
|
return json({ cleared: true });
|
|
33834
34955
|
} catch (e) {
|
|
33835
34956
|
return err(e);
|
|
33836
34957
|
}
|
|
33837
34958
|
});
|
|
33838
|
-
server.tool("browser_storage_get", "Get localStorage or sessionStorage values", { session_id: exports_external.string(), key: exports_external.string().optional(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, storage_type }) => {
|
|
34959
|
+
server.tool("browser_storage_get", "Get localStorage or sessionStorage values", { session_id: exports_external.string().optional(), key: exports_external.string().optional(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, storage_type }) => {
|
|
33839
34960
|
try {
|
|
33840
|
-
const
|
|
34961
|
+
const sid = resolveSessionId(session_id);
|
|
34962
|
+
const page = getSessionPage(sid);
|
|
33841
34963
|
const value = storage_type === "session" ? await getSessionStorage(page, key) : await getLocalStorage(page, key);
|
|
33842
34964
|
return json({ value });
|
|
33843
34965
|
} catch (e) {
|
|
33844
34966
|
return err(e);
|
|
33845
34967
|
}
|
|
33846
34968
|
});
|
|
33847
|
-
server.tool("browser_storage_set", "Set a localStorage or sessionStorage value", { session_id: exports_external.string(), key: exports_external.string(), value: exports_external.string(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, value, storage_type }) => {
|
|
34969
|
+
server.tool("browser_storage_set", "Set a localStorage or sessionStorage value", { session_id: exports_external.string().optional(), key: exports_external.string(), value: exports_external.string(), storage_type: exports_external.enum(["local", "session"]).optional().default("local") }, async ({ session_id, key, value, storage_type }) => {
|
|
33848
34970
|
try {
|
|
33849
|
-
const
|
|
34971
|
+
const sid = resolveSessionId(session_id);
|
|
34972
|
+
const page = getSessionPage(sid);
|
|
33850
34973
|
if (storage_type === "session") {
|
|
33851
34974
|
await setSessionStorage(page, key, value);
|
|
33852
34975
|
} else {
|
|
@@ -33857,28 +34980,30 @@ var init_mcp = __esm(async () => {
|
|
|
33857
34980
|
return err(e);
|
|
33858
34981
|
}
|
|
33859
34982
|
});
|
|
33860
|
-
server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
34983
|
+
server.tool("browser_network_log", "Get captured network requests for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33861
34984
|
try {
|
|
33862
|
-
|
|
33863
|
-
|
|
33864
|
-
const
|
|
33865
|
-
|
|
34985
|
+
const sid = resolveSessionId(session_id);
|
|
34986
|
+
if (!networkLogCleanup.has(sid)) {
|
|
34987
|
+
const page = getSessionPage(sid);
|
|
34988
|
+
const cleanup = enableNetworkLogging(page, sid);
|
|
34989
|
+
networkLogCleanup.set(sid, cleanup);
|
|
33866
34990
|
}
|
|
33867
|
-
const log = getNetworkLog(
|
|
34991
|
+
const log = getNetworkLog(sid);
|
|
33868
34992
|
return json({ requests: log, count: log.length });
|
|
33869
34993
|
} catch (e) {
|
|
33870
34994
|
return err(e);
|
|
33871
34995
|
}
|
|
33872
34996
|
});
|
|
33873
34997
|
server.tool("browser_network_intercept", "Add a network interception rule", {
|
|
33874
|
-
session_id: exports_external.string(),
|
|
34998
|
+
session_id: exports_external.string().optional(),
|
|
33875
34999
|
pattern: exports_external.string(),
|
|
33876
35000
|
action: exports_external.enum(["block", "modify", "log"]),
|
|
33877
35001
|
response_status: exports_external.number().optional(),
|
|
33878
35002
|
response_body: exports_external.string().optional()
|
|
33879
35003
|
}, async ({ session_id, pattern, action, response_status, response_body }) => {
|
|
33880
35004
|
try {
|
|
33881
|
-
const
|
|
35005
|
+
const sid = resolveSessionId(session_id);
|
|
35006
|
+
const page = getSessionPage(sid);
|
|
33882
35007
|
await addInterceptRule(page, {
|
|
33883
35008
|
pattern,
|
|
33884
35009
|
action,
|
|
@@ -33889,27 +35014,29 @@ var init_mcp = __esm(async () => {
|
|
|
33889
35014
|
return err(e);
|
|
33890
35015
|
}
|
|
33891
35016
|
});
|
|
33892
|
-
server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35017
|
+
server.tool("browser_har_start", "Start HAR capture for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33893
35018
|
try {
|
|
33894
|
-
const
|
|
35019
|
+
const sid = resolveSessionId(session_id);
|
|
35020
|
+
const page = getSessionPage(sid);
|
|
33895
35021
|
const capture = startHAR(page);
|
|
33896
|
-
harCaptures.set(
|
|
35022
|
+
harCaptures.set(sid, capture);
|
|
33897
35023
|
return json({ started: true });
|
|
33898
35024
|
} catch (e) {
|
|
33899
35025
|
return err(e);
|
|
33900
35026
|
}
|
|
33901
35027
|
});
|
|
33902
|
-
server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35028
|
+
server.tool("browser_har_stop", "Stop HAR capture and return the HAR data", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33903
35029
|
try {
|
|
33904
|
-
const
|
|
35030
|
+
const sid = resolveSessionId(session_id);
|
|
35031
|
+
const capture = harCaptures.get(sid);
|
|
33905
35032
|
if (!capture)
|
|
33906
35033
|
return err(new Error("No active HAR capture for this session"));
|
|
33907
35034
|
const har = capture.stop();
|
|
33908
|
-
harCaptures.delete(
|
|
35035
|
+
harCaptures.delete(sid);
|
|
33909
35036
|
let download_id;
|
|
33910
35037
|
try {
|
|
33911
35038
|
const harBuf = Buffer.from(JSON.stringify(har, null, 2));
|
|
33912
|
-
const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId:
|
|
35039
|
+
const dl = saveToDownloads(harBuf, `capture-${Date.now()}.har`, { sessionId: sid, type: "har" });
|
|
33913
35040
|
download_id = dl.id;
|
|
33914
35041
|
} catch {}
|
|
33915
35042
|
return json({ har, entry_count: har.log.entries.length, download_id });
|
|
@@ -33917,32 +35044,35 @@ var init_mcp = __esm(async () => {
|
|
|
33917
35044
|
return err(e);
|
|
33918
35045
|
}
|
|
33919
35046
|
});
|
|
33920
|
-
server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35047
|
+
server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
33921
35048
|
try {
|
|
33922
|
-
const
|
|
35049
|
+
const sid = resolveSessionId(session_id);
|
|
35050
|
+
const page = getSessionPage(sid);
|
|
33923
35051
|
const metrics = await getPerformanceMetrics(page);
|
|
33924
35052
|
return json({ metrics });
|
|
33925
35053
|
} catch (e) {
|
|
33926
35054
|
return err(e);
|
|
33927
35055
|
}
|
|
33928
35056
|
});
|
|
33929
|
-
server.tool("browser_console_log", "Get captured console messages for a session", { session_id: exports_external.string(), level: exports_external.enum(["log", "warn", "error", "debug", "info"]).optional() }, async ({ session_id, level }) => {
|
|
35057
|
+
server.tool("browser_console_log", "Get captured console messages for a session", { session_id: exports_external.string().optional(), level: exports_external.enum(["log", "warn", "error", "debug", "info"]).optional() }, async ({ session_id, level }) => {
|
|
33930
35058
|
try {
|
|
33931
|
-
|
|
33932
|
-
|
|
33933
|
-
const
|
|
33934
|
-
|
|
35059
|
+
const sid = resolveSessionId(session_id);
|
|
35060
|
+
if (!consoleCaptureCleanup.has(sid)) {
|
|
35061
|
+
const page = getSessionPage(sid);
|
|
35062
|
+
const cleanup = enableConsoleCapture(page, sid);
|
|
35063
|
+
consoleCaptureCleanup.set(sid, cleanup);
|
|
33935
35064
|
}
|
|
33936
|
-
const messages = getConsoleLog(
|
|
35065
|
+
const messages = getConsoleLog(sid, level);
|
|
33937
35066
|
return json({ messages, count: messages.length });
|
|
33938
35067
|
} catch (e) {
|
|
33939
35068
|
return err(e);
|
|
33940
35069
|
}
|
|
33941
35070
|
});
|
|
33942
|
-
server.tool("browser_record_start", "Start recording actions in a session", { session_id: exports_external.string(), name: exports_external.string(), project_id: exports_external.string().optional() }, async ({ session_id, name }) => {
|
|
35071
|
+
server.tool("browser_record_start", "Start recording actions in a session", { session_id: exports_external.string().optional(), name: exports_external.string(), project_id: exports_external.string().optional() }, async ({ session_id, name }) => {
|
|
33943
35072
|
try {
|
|
33944
|
-
const
|
|
33945
|
-
const
|
|
35073
|
+
const sid = resolveSessionId(session_id);
|
|
35074
|
+
const page = getSessionPage(sid);
|
|
35075
|
+
const recording = startRecording(sid, name, page.url());
|
|
33946
35076
|
return json({ recording_id: recording.id, name: recording.name });
|
|
33947
35077
|
} catch (e) {
|
|
33948
35078
|
return err(e);
|
|
@@ -33970,9 +35100,10 @@ var init_mcp = __esm(async () => {
|
|
|
33970
35100
|
return err(e);
|
|
33971
35101
|
}
|
|
33972
35102
|
});
|
|
33973
|
-
server.tool("browser_record_replay", "Replay a recorded sequence in a session", { session_id: exports_external.string(), recording_id: exports_external.string() }, async ({ session_id, recording_id }) => {
|
|
35103
|
+
server.tool("browser_record_replay", "Replay a recorded sequence in a session", { session_id: exports_external.string().optional(), recording_id: exports_external.string() }, async ({ session_id, recording_id }) => {
|
|
33974
35104
|
try {
|
|
33975
|
-
const
|
|
35105
|
+
const sid = resolveSessionId(session_id);
|
|
35106
|
+
const page = getSessionPage(sid);
|
|
33976
35107
|
const result = await replayRecording(recording_id, page);
|
|
33977
35108
|
return json(result);
|
|
33978
35109
|
} catch (e) {
|
|
@@ -34010,7 +35141,7 @@ var init_mcp = __esm(async () => {
|
|
|
34010
35141
|
server.tool("browser_register_agent", "Register an agent with the browser service", {
|
|
34011
35142
|
name: exports_external.string(),
|
|
34012
35143
|
description: exports_external.string().optional(),
|
|
34013
|
-
session_id: exports_external.string().optional(),
|
|
35144
|
+
session_id: exports_external.string().optional().optional(),
|
|
34014
35145
|
project_id: exports_external.string().optional(),
|
|
34015
35146
|
working_dir: exports_external.string().optional()
|
|
34016
35147
|
}, async ({ name, description, session_id, project_id, working_dir }) => {
|
|
@@ -34051,9 +35182,10 @@ var init_mcp = __esm(async () => {
|
|
|
34051
35182
|
return err(e);
|
|
34052
35183
|
}
|
|
34053
35184
|
});
|
|
34054
|
-
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 }) => {
|
|
35185
|
+
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().optional(), 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 }) => {
|
|
34055
35186
|
try {
|
|
34056
|
-
const
|
|
35187
|
+
const sid = resolveSessionId(session_id);
|
|
35188
|
+
const page = getSessionPage(sid);
|
|
34057
35189
|
await scroll(page, direction, amount);
|
|
34058
35190
|
await new Promise((r) => setTimeout(r, wait_ms));
|
|
34059
35191
|
const result = await takeScreenshot(page, { maxWidth: 1280, track: true });
|
|
@@ -34068,9 +35200,10 @@ var init_mcp = __esm(async () => {
|
|
|
34068
35200
|
return err(e);
|
|
34069
35201
|
}
|
|
34070
35202
|
});
|
|
34071
|
-
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 }) => {
|
|
35203
|
+
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().optional(), timeout: exports_external.number().optional().default(30000), url_pattern: exports_external.string().optional() }, async ({ session_id, timeout, url_pattern }) => {
|
|
34072
35204
|
try {
|
|
34073
|
-
const
|
|
35205
|
+
const sid = resolveSessionId(session_id);
|
|
35206
|
+
const page = getSessionPage(sid);
|
|
34074
35207
|
const start = Date.now();
|
|
34075
35208
|
if (url_pattern) {
|
|
34076
35209
|
await page.waitForURL(url_pattern, { timeout });
|
|
@@ -34092,38 +35225,200 @@ var init_mcp = __esm(async () => {
|
|
|
34092
35225
|
return err(e);
|
|
34093
35226
|
}
|
|
34094
35227
|
});
|
|
34095
|
-
server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
35228
|
+
server.tool("browser_session_rename", "Rename a browser session", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
34096
35229
|
try {
|
|
34097
|
-
|
|
35230
|
+
const sid = resolveSessionId(session_id);
|
|
35231
|
+
return json({ session: renameSession2(sid, name) });
|
|
34098
35232
|
} catch (e) {
|
|
34099
35233
|
return err(e);
|
|
34100
35234
|
}
|
|
34101
35235
|
});
|
|
34102
|
-
server.tool("
|
|
35236
|
+
server.tool("browser_session_lock", "Lock a session so only the specified agent can use it", { session_id: exports_external.string().optional(), agent_id: exports_external.string() }, async ({ session_id, agent_id }) => {
|
|
34103
35237
|
try {
|
|
34104
|
-
const
|
|
35238
|
+
const sid = resolveSessionId(session_id);
|
|
35239
|
+
const { lockSession: lockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
35240
|
+
return json({ session: lockSession2(sid, agent_id) });
|
|
35241
|
+
} catch (e) {
|
|
35242
|
+
return err(e);
|
|
35243
|
+
}
|
|
35244
|
+
});
|
|
35245
|
+
server.tool("browser_session_unlock", "Unlock a session", { session_id: exports_external.string().optional(), agent_id: exports_external.string().optional() }, async ({ session_id, agent_id }) => {
|
|
35246
|
+
try {
|
|
35247
|
+
const sid = resolveSessionId(session_id);
|
|
35248
|
+
const { unlockSession: unlockSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
35249
|
+
return json({ session: unlockSession2(sid, agent_id) });
|
|
35250
|
+
} catch (e) {
|
|
35251
|
+
return err(e);
|
|
35252
|
+
}
|
|
35253
|
+
});
|
|
35254
|
+
server.tool("browser_session_transfer", "Transfer session ownership to another agent", { session_id: exports_external.string().optional(), to_agent_id: exports_external.string() }, async ({ session_id, to_agent_id }) => {
|
|
35255
|
+
try {
|
|
35256
|
+
const sid = resolveSessionId(session_id);
|
|
35257
|
+
const { transferSession: transferSession2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
35258
|
+
return json({ session: transferSession2(sid, to_agent_id) });
|
|
35259
|
+
} catch (e) {
|
|
35260
|
+
return err(e);
|
|
35261
|
+
}
|
|
35262
|
+
});
|
|
35263
|
+
server.tool("browser_session_tag", "Add a tag to a session for categorization (e.g. qa, scraping, monitoring)", { session_id: exports_external.string().optional(), tag: exports_external.string() }, async ({ session_id, tag }) => {
|
|
35264
|
+
try {
|
|
35265
|
+
const sid = resolveSessionId(session_id);
|
|
35266
|
+
const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
35267
|
+
return json({ tags: addSessionTag2(sid, tag) });
|
|
35268
|
+
} catch (e) {
|
|
35269
|
+
return err(e);
|
|
35270
|
+
}
|
|
35271
|
+
});
|
|
35272
|
+
server.tool("browser_session_untag", "Remove a tag from a session", { session_id: exports_external.string().optional(), tag: exports_external.string() }, async ({ session_id, tag }) => {
|
|
35273
|
+
try {
|
|
35274
|
+
const sid = resolveSessionId(session_id);
|
|
35275
|
+
const { removeSessionTag: removeSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
35276
|
+
return json({ tags: removeSessionTag2(sid, tag) });
|
|
35277
|
+
} catch (e) {
|
|
35278
|
+
return err(e);
|
|
35279
|
+
}
|
|
35280
|
+
});
|
|
35281
|
+
server.tool("browser_session_save_state", "Save current session's auth state (cookies, localStorage) for reuse. Use after login to avoid re-authenticating.", { session_id: exports_external.string().optional(), name: exports_external.string().describe("Name for this state (e.g. 'github', 'gmail')") }, async ({ session_id, name }) => {
|
|
35282
|
+
try {
|
|
35283
|
+
const sid = resolveSessionId(session_id);
|
|
35284
|
+
const page = getSessionPage(sid);
|
|
35285
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35286
|
+
const path = await saveStateFromPage2(page, name);
|
|
35287
|
+
return json({ saved: true, name, path });
|
|
35288
|
+
} catch (e) {
|
|
35289
|
+
return err(e);
|
|
35290
|
+
}
|
|
35291
|
+
});
|
|
35292
|
+
server.tool("browser_session_list_states", "List all saved storage states (auth snapshots)", {}, async () => {
|
|
35293
|
+
try {
|
|
35294
|
+
const { listStates: listStates2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35295
|
+
const states = listStates2();
|
|
35296
|
+
return json({ states, count: states.length });
|
|
35297
|
+
} catch (e) {
|
|
35298
|
+
return err(e);
|
|
35299
|
+
}
|
|
35300
|
+
});
|
|
35301
|
+
server.tool("browser_session_delete_state", "Delete a saved storage state", { name: exports_external.string() }, async ({ name }) => {
|
|
35302
|
+
try {
|
|
35303
|
+
const { deleteState: deleteState2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35304
|
+
return json({ deleted: deleteState2(name), name });
|
|
35305
|
+
} catch (e) {
|
|
35306
|
+
return err(e);
|
|
35307
|
+
}
|
|
35308
|
+
});
|
|
35309
|
+
server.tool("browser_auth_record", "Start recording a login flow. Navigate to the login page, perform the login, then call browser_auth_stop to save.", { session_id: exports_external.string().optional(), name: exports_external.string().describe("Name for this auth flow (e.g. 'github', 'gmail')"), start_url: exports_external.string().optional().describe("Login page URL") }, async ({ session_id, name, start_url }) => {
|
|
35310
|
+
try {
|
|
35311
|
+
const sid = resolveSessionId(session_id);
|
|
35312
|
+
const page = getSessionPage(sid);
|
|
35313
|
+
if (start_url)
|
|
35314
|
+
await navigate(page, start_url);
|
|
35315
|
+
const recording = startRecording(sid, `auth-${name}`, page.url());
|
|
35316
|
+
return json({ recording_id: recording.id, name, message: "Recording started. Perform login, then call browser_auth_stop." });
|
|
35317
|
+
} catch (e) {
|
|
35318
|
+
return err(e);
|
|
35319
|
+
}
|
|
35320
|
+
});
|
|
35321
|
+
server.tool("browser_auth_stop", "Stop recording a login flow and save as a reusable auth flow with storage state.", { session_id: exports_external.string().optional(), name: exports_external.string(), recording_id: exports_external.string() }, async ({ session_id, name, recording_id }) => {
|
|
35322
|
+
try {
|
|
35323
|
+
const sid = resolveSessionId(session_id);
|
|
35324
|
+
const page = getSessionPage(sid);
|
|
35325
|
+
const recording = stopRecording(recording_id);
|
|
35326
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35327
|
+
const statePath2 = await saveStateFromPage2(page, name);
|
|
35328
|
+
let domain = "";
|
|
35329
|
+
try {
|
|
35330
|
+
domain = new URL(page.url()).hostname;
|
|
35331
|
+
} catch {}
|
|
35332
|
+
const { saveAuthFlow: saveAuthFlow2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
35333
|
+
const flow = saveAuthFlow2({ name, domain, recordingId: recording.id, storageStatePath: statePath2 });
|
|
35334
|
+
return json({ flow, recording_steps: recording.steps.length });
|
|
35335
|
+
} catch (e) {
|
|
35336
|
+
return err(e);
|
|
35337
|
+
}
|
|
35338
|
+
});
|
|
35339
|
+
server.tool("browser_auth_replay", "Manually replay a saved auth flow for a domain", { session_id: exports_external.string().optional(), name: exports_external.string().describe("Auth flow name to replay") }, async ({ session_id, name }) => {
|
|
35340
|
+
try {
|
|
35341
|
+
const sid = resolveSessionId(session_id);
|
|
35342
|
+
const page = getSessionPage(sid);
|
|
35343
|
+
const { getAuthFlowByName: getAuthFlowByName2, tryReplayAuth: tryReplayAuth2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
35344
|
+
const flow = getAuthFlowByName2(name);
|
|
35345
|
+
if (!flow)
|
|
35346
|
+
return err(new Error(`Auth flow '${name}' not found`));
|
|
35347
|
+
const result = await tryReplayAuth2(page, flow.domain);
|
|
35348
|
+
return json(result);
|
|
35349
|
+
} catch (e) {
|
|
35350
|
+
return err(e);
|
|
35351
|
+
}
|
|
35352
|
+
});
|
|
35353
|
+
server.tool("browser_auth_list", "List all saved auth flows", {}, async () => {
|
|
35354
|
+
try {
|
|
35355
|
+
const { listAuthFlows: listAuthFlows2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
35356
|
+
return json({ flows: listAuthFlows2() });
|
|
35357
|
+
} catch (e) {
|
|
35358
|
+
return err(e);
|
|
35359
|
+
}
|
|
35360
|
+
});
|
|
35361
|
+
server.tool("browser_auth_delete", "Delete a saved auth flow", { name: exports_external.string() }, async ({ name }) => {
|
|
35362
|
+
try {
|
|
35363
|
+
const { deleteAuthFlow: deleteAuthFlow2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
35364
|
+
return json({ deleted: deleteAuthFlow2(name) });
|
|
35365
|
+
} catch (e) {
|
|
35366
|
+
return err(e);
|
|
35367
|
+
}
|
|
35368
|
+
});
|
|
35369
|
+
server.tool("browser_click_text", "Click an element by its visible text content", { session_id: exports_external.string().optional(), text: exports_external.string(), exact: exports_external.boolean().optional().default(false), timeout: exports_external.number().optional() }, async ({ session_id, text, exact, timeout }) => {
|
|
35370
|
+
try {
|
|
35371
|
+
const sid = resolveSessionId(session_id);
|
|
35372
|
+
const page = getSessionPage(sid);
|
|
34105
35373
|
await clickText(page, text, { exact, timeout });
|
|
34106
35374
|
return json({ clicked: text });
|
|
34107
35375
|
} catch (e) {
|
|
34108
35376
|
return err(e);
|
|
34109
35377
|
}
|
|
34110
35378
|
});
|
|
34111
|
-
server.tool("browser_fill_form", "Fill multiple form fields in one call. Fields map: { selector: value }. Handles text, checkboxes, selects.", {
|
|
34112
|
-
session_id: exports_external.string(),
|
|
35379
|
+
server.tool("browser_fill_form", "Fill multiple form fields in one call. Fields map: { selector: value }. Handles text, checkboxes, selects. Self-healing auto-tries fallback selectors per field.", {
|
|
35380
|
+
session_id: exports_external.string().optional(),
|
|
34113
35381
|
fields: exports_external.record(exports_external.union([exports_external.string(), exports_external.boolean()])),
|
|
34114
|
-
submit_selector: exports_external.string().optional()
|
|
34115
|
-
|
|
35382
|
+
submit_selector: exports_external.string().optional(),
|
|
35383
|
+
self_heal: exports_external.boolean().optional().default(true).describe("Auto-try fallback selectors if element not found")
|
|
35384
|
+
}, async ({ session_id, fields, submit_selector, self_heal }) => {
|
|
34116
35385
|
try {
|
|
34117
|
-
const
|
|
34118
|
-
const
|
|
35386
|
+
const sid = resolveSessionId(session_id);
|
|
35387
|
+
const page = getSessionPage(sid);
|
|
35388
|
+
const result = await fillForm(page, fields, submit_selector, self_heal);
|
|
34119
35389
|
return json(result);
|
|
35390
|
+
} catch (e) {
|
|
35391
|
+
return errWithScreenshot(e, session_id);
|
|
35392
|
+
}
|
|
35393
|
+
});
|
|
35394
|
+
server.tool("browser_find_visual", "Find an element using AI vision when selectors and a11y refs fail. Useful for canvas, images, custom widgets. Takes a screenshot and asks a vision model to locate the element.", {
|
|
35395
|
+
session_id: exports_external.string().optional(),
|
|
35396
|
+
description: exports_external.string().describe("Natural language description of the element to find (e.g. 'the blue Submit button', 'the search icon in the top right')"),
|
|
35397
|
+
click: exports_external.boolean().optional().default(false).describe("Click the element after finding it"),
|
|
35398
|
+
model: exports_external.string().optional().describe("Vision model to use (default: claude-sonnet-4-5-20250929)")
|
|
35399
|
+
}, async ({ session_id, description, click: doClick, model }) => {
|
|
35400
|
+
try {
|
|
35401
|
+
const sid = resolveSessionId(session_id);
|
|
35402
|
+
const page = getSessionPage(sid);
|
|
35403
|
+
if (doClick) {
|
|
35404
|
+
const { clickByVision: clickByVision2 } = await Promise.resolve().then(() => exports_vision_fallback);
|
|
35405
|
+
const result = await clickByVision2(page, description, { model });
|
|
35406
|
+
logEvent(sid, "vision_click", { query: description, ...result });
|
|
35407
|
+
return json(result);
|
|
35408
|
+
} else {
|
|
35409
|
+
const { findElementByVision: findElementByVision2 } = await Promise.resolve().then(() => exports_vision_fallback);
|
|
35410
|
+
const result = await findElementByVision2(page, description, { model });
|
|
35411
|
+
logEvent(sid, "vision_find", { query: description, ...result });
|
|
35412
|
+
return json(result);
|
|
35413
|
+
}
|
|
34120
35414
|
} catch (e) {
|
|
34121
35415
|
return err(e);
|
|
34122
35416
|
}
|
|
34123
35417
|
});
|
|
34124
|
-
server.tool("browser_wait_for_text", "Wait until specific text appears on the page", { session_id: exports_external.string(), text: exports_external.string(), timeout: exports_external.number().optional().default(1e4), exact: exports_external.boolean().optional().default(false) }, async ({ session_id, text, timeout, exact }) => {
|
|
35418
|
+
server.tool("browser_wait_for_text", "Wait until specific text appears on the page", { session_id: exports_external.string().optional(), text: exports_external.string(), timeout: exports_external.number().optional().default(1e4), exact: exports_external.boolean().optional().default(false) }, async ({ session_id, text, timeout, exact }) => {
|
|
34125
35419
|
try {
|
|
34126
|
-
const
|
|
35420
|
+
const sid = resolveSessionId(session_id);
|
|
35421
|
+
const page = getSessionPage(sid);
|
|
34127
35422
|
const start = Date.now();
|
|
34128
35423
|
await waitForText(page, text, { timeout, exact });
|
|
34129
35424
|
return json({ found: true, elapsed_ms: Date.now() - start });
|
|
@@ -34131,46 +35426,51 @@ var init_mcp = __esm(async () => {
|
|
|
34131
35426
|
return err(e);
|
|
34132
35427
|
}
|
|
34133
35428
|
});
|
|
34134
|
-
server.tool("browser_element_exists", "Check if a selector exists on the page (no throw, returns boolean)", { session_id: exports_external.string(), selector: exports_external.string(), check_visible: exports_external.boolean().optional().default(false) }, async ({ session_id, selector, check_visible }) => {
|
|
35429
|
+
server.tool("browser_element_exists", "Check if a selector exists on the page (no throw, returns boolean)", { session_id: exports_external.string().optional(), selector: exports_external.string(), check_visible: exports_external.boolean().optional().default(false) }, async ({ session_id, selector, check_visible }) => {
|
|
34135
35430
|
try {
|
|
34136
|
-
const
|
|
35431
|
+
const sid = resolveSessionId(session_id);
|
|
35432
|
+
const page = getSessionPage(sid);
|
|
34137
35433
|
return json(await elementExists(page, selector, { visible: check_visible }));
|
|
34138
35434
|
} catch (e) {
|
|
34139
35435
|
return err(e);
|
|
34140
35436
|
}
|
|
34141
35437
|
});
|
|
34142
|
-
server.tool("browser_get_page_info", "Get a full page summary in one call: url, title, meta tags, link/image/form counts, text length", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35438
|
+
server.tool("browser_get_page_info", "Get a full page summary in one call: url, title, meta tags, link/image/form counts, text length", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34143
35439
|
try {
|
|
34144
|
-
const
|
|
35440
|
+
const sid = resolveSessionId(session_id);
|
|
35441
|
+
const page = getSessionPage(sid);
|
|
34145
35442
|
const info = await getPageInfo(page);
|
|
34146
|
-
const errors2 = getConsoleLog(
|
|
35443
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
34147
35444
|
info.has_console_errors = errors2.length > 0;
|
|
34148
35445
|
return json(info);
|
|
34149
35446
|
} catch (e) {
|
|
34150
35447
|
return err(e);
|
|
34151
35448
|
}
|
|
34152
35449
|
});
|
|
34153
|
-
server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35450
|
+
server.tool("browser_has_errors", "Quick check: does the session have any console errors?", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34154
35451
|
try {
|
|
34155
|
-
const
|
|
35452
|
+
const sid = resolveSessionId(session_id);
|
|
35453
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
34156
35454
|
return json({ has_errors: errors2.length > 0, error_count: errors2.length, errors: errors2 });
|
|
34157
35455
|
} catch (e) {
|
|
34158
35456
|
return err(e);
|
|
34159
35457
|
}
|
|
34160
35458
|
});
|
|
34161
|
-
server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35459
|
+
server.tool("browser_clear_errors", "Clear console error log for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34162
35460
|
try {
|
|
35461
|
+
const sid = resolveSessionId(session_id);
|
|
34163
35462
|
const { clearConsoleLog: clearConsoleLog2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
34164
|
-
clearConsoleLog2(
|
|
35463
|
+
clearConsoleLog2(sid);
|
|
34165
35464
|
return json({ cleared: true });
|
|
34166
35465
|
} catch (e) {
|
|
34167
35466
|
return err(e);
|
|
34168
35467
|
}
|
|
34169
35468
|
});
|
|
34170
35469
|
activeWatchHandles2 = new Map;
|
|
34171
|
-
server.tool("browser_watch_start", "Start watching a page for DOM changes", { session_id: exports_external.string(), selector: exports_external.string().optional(), interval_ms: exports_external.number().optional().default(500), max_changes: exports_external.number().optional().default(50) }, async ({ session_id, selector, interval_ms, max_changes }) => {
|
|
35470
|
+
server.tool("browser_watch_start", "Start watching a page for DOM changes", { session_id: exports_external.string().optional(), selector: exports_external.string().optional(), interval_ms: exports_external.number().optional().default(500), max_changes: exports_external.number().optional().default(50) }, async ({ session_id, selector, interval_ms, max_changes }) => {
|
|
34172
35471
|
try {
|
|
34173
|
-
const
|
|
35472
|
+
const sid = resolveSessionId(session_id);
|
|
35473
|
+
const page = getSessionPage(sid);
|
|
34174
35474
|
const handle = watchPage(page, { selector, intervalMs: interval_ms, maxChanges: max_changes });
|
|
34175
35475
|
activeWatchHandles2.set(handle.id, handle);
|
|
34176
35476
|
return json({ watch_id: handle.id });
|
|
@@ -34197,7 +35497,7 @@ var init_mcp = __esm(async () => {
|
|
|
34197
35497
|
});
|
|
34198
35498
|
server.tool("browser_gallery_list", "List screenshot gallery entries with optional filters", {
|
|
34199
35499
|
project_id: exports_external.string().optional(),
|
|
34200
|
-
session_id: exports_external.string().optional(),
|
|
35500
|
+
session_id: exports_external.string().optional().optional(),
|
|
34201
35501
|
tag: exports_external.string().optional(),
|
|
34202
35502
|
is_favorite: exports_external.boolean().optional(),
|
|
34203
35503
|
date_from: exports_external.string().optional(),
|
|
@@ -34285,14 +35585,14 @@ var init_mcp = __esm(async () => {
|
|
|
34285
35585
|
return err(e);
|
|
34286
35586
|
}
|
|
34287
35587
|
});
|
|
34288
|
-
server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
35588
|
+
server.tool("browser_downloads_list", "List all files in the downloads folder", { session_id: exports_external.string().optional().optional() }, async ({ session_id }) => {
|
|
34289
35589
|
try {
|
|
34290
35590
|
return json({ downloads: listDownloads(session_id), count: listDownloads(session_id).length });
|
|
34291
35591
|
} catch (e) {
|
|
34292
35592
|
return err(e);
|
|
34293
35593
|
}
|
|
34294
35594
|
});
|
|
34295
|
-
server.tool("browser_downloads_get", "Get a downloaded file by id, returning base64 content and metadata", { id: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, session_id }) => {
|
|
35595
|
+
server.tool("browser_downloads_get", "Get a downloaded file by id, returning base64 content and metadata", { id: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, session_id }) => {
|
|
34296
35596
|
try {
|
|
34297
35597
|
const file = getDownload(id, session_id);
|
|
34298
35598
|
if (!file)
|
|
@@ -34303,7 +35603,7 @@ var init_mcp = __esm(async () => {
|
|
|
34303
35603
|
return err(e);
|
|
34304
35604
|
}
|
|
34305
35605
|
});
|
|
34306
|
-
server.tool("browser_downloads_delete", "Delete a downloaded file by id", { id: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, session_id }) => {
|
|
35606
|
+
server.tool("browser_downloads_delete", "Delete a downloaded file by id", { id: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, session_id }) => {
|
|
34307
35607
|
try {
|
|
34308
35608
|
const deleted = deleteDownload(id, session_id);
|
|
34309
35609
|
return json({ deleted });
|
|
@@ -34318,7 +35618,7 @@ var init_mcp = __esm(async () => {
|
|
|
34318
35618
|
return err(e);
|
|
34319
35619
|
}
|
|
34320
35620
|
});
|
|
34321
|
-
server.tool("browser_downloads_export", "Copy a downloaded file to a target path", { id: exports_external.string(), target_path: exports_external.string(), session_id: exports_external.string().optional() }, async ({ id, target_path, session_id }) => {
|
|
35621
|
+
server.tool("browser_downloads_export", "Copy a downloaded file to a target path", { id: exports_external.string(), target_path: exports_external.string(), session_id: exports_external.string().optional().optional() }, async ({ id, target_path, session_id }) => {
|
|
34322
35622
|
try {
|
|
34323
35623
|
const finalPath = exportToPath(id, target_path, session_id);
|
|
34324
35624
|
return json({ path: finalPath });
|
|
@@ -34343,12 +35643,13 @@ var init_mcp = __esm(async () => {
|
|
|
34343
35643
|
return err(e);
|
|
34344
35644
|
}
|
|
34345
35645
|
});
|
|
34346
|
-
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 }) => {
|
|
35646
|
+
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().optional() }, async ({ session_id }) => {
|
|
34347
35647
|
try {
|
|
34348
|
-
const
|
|
34349
|
-
const
|
|
34350
|
-
const
|
|
34351
|
-
|
|
35648
|
+
const sid = resolveSessionId(session_id);
|
|
35649
|
+
const page = getSessionPage(sid);
|
|
35650
|
+
const before = getLastSnapshot(sid);
|
|
35651
|
+
const after = await takeSnapshot(page, sid);
|
|
35652
|
+
setLastSnapshot(sid, after);
|
|
34352
35653
|
if (!before) {
|
|
34353
35654
|
return json({
|
|
34354
35655
|
message: "No previous snapshot \u2014 returning current snapshot only.",
|
|
@@ -34371,12 +35672,13 @@ var init_mcp = __esm(async () => {
|
|
|
34371
35672
|
return err(e);
|
|
34372
35673
|
}
|
|
34373
35674
|
});
|
|
34374
|
-
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 }) => {
|
|
35675
|
+
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().optional() }, async ({ session_id }) => {
|
|
34375
35676
|
try {
|
|
34376
|
-
const
|
|
34377
|
-
const
|
|
34378
|
-
const
|
|
34379
|
-
const
|
|
35677
|
+
const sid = resolveSessionId(session_id);
|
|
35678
|
+
const session = getSession2(sid);
|
|
35679
|
+
const networkLog = getNetworkLog(sid);
|
|
35680
|
+
const consoleLog = getConsoleLog(sid);
|
|
35681
|
+
const galleryEntries = listEntries({ sessionId: sid, limit: 1000 });
|
|
34380
35682
|
let totalChars = 0;
|
|
34381
35683
|
for (const req of networkLog) {
|
|
34382
35684
|
totalChars += (req.url?.length ?? 0) + (req.request_headers?.length ?? 0) + (req.response_headers?.length ?? 0) + (req.request_body?.length ?? 0);
|
|
@@ -34388,7 +35690,7 @@ var init_mcp = __esm(async () => {
|
|
|
34388
35690
|
totalChars += (entry.url?.length ?? 0) + (entry.title?.length ?? 0) + (entry.notes?.length ?? 0) + (entry.tags?.join(",").length ?? 0);
|
|
34389
35691
|
}
|
|
34390
35692
|
const estimatedTokens = Math.ceil(totalChars / 4);
|
|
34391
|
-
const tokenBudget = getTokenBudget(
|
|
35693
|
+
const tokenBudget = getTokenBudget(sid);
|
|
34392
35694
|
return json({
|
|
34393
35695
|
session,
|
|
34394
35696
|
network_request_count: networkLog.length,
|
|
@@ -34402,52 +35704,57 @@ var init_mcp = __esm(async () => {
|
|
|
34402
35704
|
return err(e);
|
|
34403
35705
|
}
|
|
34404
35706
|
});
|
|
34405
|
-
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 }) => {
|
|
35707
|
+
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().optional(), url: exports_external.string().optional() }, async ({ session_id, url }) => {
|
|
34406
35708
|
try {
|
|
34407
|
-
const
|
|
35709
|
+
const sid = resolveSessionId(session_id);
|
|
35710
|
+
const page = getSessionPage(sid);
|
|
34408
35711
|
const tab = await newTab(page, url);
|
|
34409
35712
|
return json(tab);
|
|
34410
35713
|
} catch (e) {
|
|
34411
35714
|
return err(e);
|
|
34412
35715
|
}
|
|
34413
35716
|
});
|
|
34414
|
-
server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35717
|
+
server.tool("browser_tab_list", "List all open tabs in the session's browser context", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34415
35718
|
try {
|
|
34416
|
-
const
|
|
35719
|
+
const sid = resolveSessionId(session_id);
|
|
35720
|
+
const page = getSessionPage(sid);
|
|
34417
35721
|
const tabs = await listTabs(page);
|
|
34418
35722
|
return json({ tabs, count: tabs.length });
|
|
34419
35723
|
} catch (e) {
|
|
34420
35724
|
return err(e);
|
|
34421
35725
|
}
|
|
34422
35726
|
});
|
|
34423
|
-
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 }) => {
|
|
35727
|
+
server.tool("browser_tab_switch", "Switch to a different tab by index. Updates the session's active page.", { session_id: exports_external.string().optional(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
|
|
34424
35728
|
try {
|
|
34425
|
-
const
|
|
35729
|
+
const sid = resolveSessionId(session_id);
|
|
35730
|
+
const page = getSessionPage(sid);
|
|
34426
35731
|
const result = await switchTab(page, tab_id);
|
|
34427
|
-
setSessionPage(
|
|
35732
|
+
setSessionPage(sid, result.page);
|
|
34428
35733
|
return json(result.tab);
|
|
34429
35734
|
} catch (e) {
|
|
34430
35735
|
return err(e);
|
|
34431
35736
|
}
|
|
34432
35737
|
});
|
|
34433
|
-
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 }) => {
|
|
35738
|
+
server.tool("browser_tab_close", "Close a tab by index. Cannot close the last tab.", { session_id: exports_external.string().optional(), tab_id: exports_external.number() }, async ({ session_id, tab_id }) => {
|
|
34434
35739
|
try {
|
|
34435
|
-
const
|
|
35740
|
+
const sid = resolveSessionId(session_id);
|
|
35741
|
+
const page = getSessionPage(sid);
|
|
34436
35742
|
const context = page.context();
|
|
34437
35743
|
const result = await closeTab(page, tab_id);
|
|
34438
35744
|
const remainingPages = context.pages();
|
|
34439
35745
|
const newActivePage = remainingPages[result.active_tab.index];
|
|
34440
35746
|
if (newActivePage) {
|
|
34441
|
-
setSessionPage(
|
|
35747
|
+
setSessionPage(sid, newActivePage);
|
|
34442
35748
|
}
|
|
34443
35749
|
return json(result);
|
|
34444
35750
|
} catch (e) {
|
|
34445
35751
|
return err(e);
|
|
34446
35752
|
}
|
|
34447
35753
|
});
|
|
34448
|
-
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 }) => {
|
|
35754
|
+
server.tool("browser_handle_dialog", "Accept or dismiss a pending dialog (alert, confirm, prompt). Handles the oldest pending dialog.", { session_id: exports_external.string().optional(), action: exports_external.enum(["accept", "dismiss"]), prompt_text: exports_external.string().optional() }, async ({ session_id, action, prompt_text }) => {
|
|
34449
35755
|
try {
|
|
34450
|
-
const
|
|
35756
|
+
const sid = resolveSessionId(session_id);
|
|
35757
|
+
const result = await handleDialog(sid, action, prompt_text);
|
|
34451
35758
|
if (!result.handled)
|
|
34452
35759
|
return err(new Error("No pending dialogs for this session"));
|
|
34453
35760
|
return json(result);
|
|
@@ -34455,28 +35762,31 @@ var init_mcp = __esm(async () => {
|
|
|
34455
35762
|
return err(e);
|
|
34456
35763
|
}
|
|
34457
35764
|
});
|
|
34458
|
-
server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
35765
|
+
server.tool("browser_get_dialogs", "Get all pending dialogs for a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34459
35766
|
try {
|
|
34460
|
-
const
|
|
35767
|
+
const sid = resolveSessionId(session_id);
|
|
35768
|
+
const dialogs = getDialogs(sid);
|
|
34461
35769
|
return json({ dialogs, count: dialogs.length });
|
|
34462
35770
|
} catch (e) {
|
|
34463
35771
|
return err(e);
|
|
34464
35772
|
}
|
|
34465
35773
|
});
|
|
34466
|
-
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 }) => {
|
|
35774
|
+
server.tool("browser_profile_save", "Save cookies + localStorage from the current session as a named profile", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
34467
35775
|
try {
|
|
34468
|
-
const
|
|
35776
|
+
const sid = resolveSessionId(session_id);
|
|
35777
|
+
const page = getSessionPage(sid);
|
|
34469
35778
|
const info = await saveProfile(page, name);
|
|
34470
35779
|
return json(info);
|
|
34471
35780
|
} catch (e) {
|
|
34472
35781
|
return err(e);
|
|
34473
35782
|
}
|
|
34474
35783
|
});
|
|
34475
|
-
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 }) => {
|
|
35784
|
+
server.tool("browser_profile_load", "Load a saved profile and apply cookies + localStorage to the current session", { session_id: exports_external.string().optional().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
34476
35785
|
try {
|
|
34477
35786
|
const profileData = loadProfile(name);
|
|
34478
35787
|
if (session_id) {
|
|
34479
|
-
const
|
|
35788
|
+
const sid = resolveSessionId(session_id);
|
|
35789
|
+
const page = getSessionPage(sid);
|
|
34480
35790
|
const applied = await applyProfile(page, profileData);
|
|
34481
35791
|
return json({ ...applied, profile: name });
|
|
34482
35792
|
}
|
|
@@ -34525,6 +35835,7 @@ var init_mcp = __esm(async () => {
|
|
|
34525
35835
|
{ tool: "browser_wait", description: "Wait for a selector to appear" },
|
|
34526
35836
|
{ tool: "browser_wait_for_text", description: "Wait for text to appear" },
|
|
34527
35837
|
{ tool: "browser_fill_form", description: "Fill multiple form fields at once" },
|
|
35838
|
+
{ tool: "browser_find_visual", description: "Find element using AI vision (for canvas, images, custom widgets)" },
|
|
34528
35839
|
{ tool: "browser_handle_dialog", description: "Accept or dismiss a dialog" }
|
|
34529
35840
|
],
|
|
34530
35841
|
Extraction: [
|
|
@@ -34553,7 +35864,10 @@ var init_mcp = __esm(async () => {
|
|
|
34553
35864
|
{ tool: "browser_profile_save", description: "Save cookies + localStorage as profile" },
|
|
34554
35865
|
{ tool: "browser_profile_load", description: "Load and apply a saved profile" },
|
|
34555
35866
|
{ tool: "browser_profile_list", description: "List saved profiles" },
|
|
34556
|
-
{ tool: "browser_profile_delete", description: "Delete a saved profile" }
|
|
35867
|
+
{ tool: "browser_profile_delete", description: "Delete a saved profile" },
|
|
35868
|
+
{ tool: "browser_session_save_state", description: "Save auth state (Playwright storageState) for reuse" },
|
|
35869
|
+
{ tool: "browser_session_list_states", description: "List saved storage states" },
|
|
35870
|
+
{ tool: "browser_session_delete_state", description: "Delete a saved storage state" }
|
|
34557
35871
|
],
|
|
34558
35872
|
Network: [
|
|
34559
35873
|
{ tool: "browser_network_log", description: "Get captured network requests" },
|
|
@@ -34577,6 +35891,13 @@ var init_mcp = __esm(async () => {
|
|
|
34577
35891
|
{ tool: "browser_record_replay", description: "Replay a recorded sequence" },
|
|
34578
35892
|
{ tool: "browser_recordings_list", description: "List all recordings" }
|
|
34579
35893
|
],
|
|
35894
|
+
Auth: [
|
|
35895
|
+
{ tool: "browser_auth_record", description: "Start recording a login flow" },
|
|
35896
|
+
{ tool: "browser_auth_stop", description: "Stop recording and save auth flow" },
|
|
35897
|
+
{ tool: "browser_auth_replay", description: "Replay a saved auth flow" },
|
|
35898
|
+
{ tool: "browser_auth_list", description: "List all saved auth flows" },
|
|
35899
|
+
{ tool: "browser_auth_delete", description: "Delete a saved auth flow" }
|
|
35900
|
+
],
|
|
34580
35901
|
Crawl: [
|
|
34581
35902
|
{ tool: "browser_crawl", description: "Crawl a URL recursively" }
|
|
34582
35903
|
],
|
|
@@ -34614,7 +35935,13 @@ var init_mcp = __esm(async () => {
|
|
|
34614
35935
|
{ tool: "browser_session_close", description: "Close a session" },
|
|
34615
35936
|
{ tool: "browser_session_get_by_name", description: "Get session by name" },
|
|
34616
35937
|
{ tool: "browser_session_rename", description: "Rename a session" },
|
|
35938
|
+
{ tool: "browser_session_lock", description: "Lock a session for an agent" },
|
|
35939
|
+
{ tool: "browser_session_unlock", description: "Unlock a session" },
|
|
35940
|
+
{ tool: "browser_session_transfer", description: "Transfer session to another agent" },
|
|
35941
|
+
{ tool: "browser_session_tag", description: "Add a tag to a session" },
|
|
35942
|
+
{ tool: "browser_session_untag", description: "Remove a tag from a session" },
|
|
34617
35943
|
{ tool: "browser_session_stats", description: "Get session stats and token usage" },
|
|
35944
|
+
{ tool: "browser_session_timeline", description: "Get chronological action log" },
|
|
34618
35945
|
{ tool: "browser_tab_new", description: "Open a new tab" },
|
|
34619
35946
|
{ tool: "browser_tab_list", description: "List all open tabs" },
|
|
34620
35947
|
{ tool: "browser_tab_switch", description: "Switch to a tab by index" },
|
|
@@ -34627,7 +35954,8 @@ var init_mcp = __esm(async () => {
|
|
|
34627
35954
|
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
34628
35955
|
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
34629
35956
|
{ tool: "browser_watch_get_changes", description: "Get captured DOM changes" },
|
|
34630
|
-
{ tool: "browser_watch_stop", description: "Stop DOM watcher" }
|
|
35957
|
+
{ tool: "browser_watch_stop", description: "Stop DOM watcher" },
|
|
35958
|
+
{ tool: "browser_parallel", description: "Execute actions across multiple sessions in parallel" }
|
|
34631
35959
|
]
|
|
34632
35960
|
};
|
|
34633
35961
|
const totalTools = Object.values(groups).reduce((sum, g) => sum + g.length, 0);
|
|
@@ -34652,18 +35980,19 @@ var init_mcp = __esm(async () => {
|
|
|
34652
35980
|
}
|
|
34653
35981
|
});
|
|
34654
35982
|
server.tool("browser_scroll_to_element", "Scroll an element into view (by ref or selector) then optionally take a screenshot of it. Replaces scroll + wait + screenshot pattern.", {
|
|
34655
|
-
session_id: exports_external.string(),
|
|
35983
|
+
session_id: exports_external.string().optional(),
|
|
34656
35984
|
selector: exports_external.string().optional(),
|
|
34657
35985
|
ref: exports_external.string().optional(),
|
|
34658
35986
|
screenshot: exports_external.boolean().optional().default(true),
|
|
34659
35987
|
wait_ms: exports_external.number().optional().default(200)
|
|
34660
35988
|
}, async ({ session_id, selector, ref, screenshot: doScreenshot, wait_ms }) => {
|
|
34661
35989
|
try {
|
|
34662
|
-
const
|
|
35990
|
+
const sid = resolveSessionId(session_id);
|
|
35991
|
+
const page = getSessionPage(sid);
|
|
34663
35992
|
let locator;
|
|
34664
35993
|
if (ref) {
|
|
34665
35994
|
const { getRefLocator: getRefLocator2 } = await Promise.resolve().then(() => (init_snapshot(), exports_snapshot));
|
|
34666
|
-
locator = getRefLocator2(page,
|
|
35995
|
+
locator = getRefLocator2(page, sid, ref);
|
|
34667
35996
|
} else if (selector) {
|
|
34668
35997
|
locator = page.locator(selector).first();
|
|
34669
35998
|
} else {
|
|
@@ -34688,11 +36017,12 @@ var init_mcp = __esm(async () => {
|
|
|
34688
36017
|
return err(e);
|
|
34689
36018
|
}
|
|
34690
36019
|
});
|
|
34691
|
-
server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string() }, async ({ session_id }) => {
|
|
36020
|
+
server.tool("browser_check", "RECOMMENDED FIRST CALL: one-shot page summary \u2014 url, title, errors, performance, thumbnail, refs. Replaces 4+ separate tool calls.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
34692
36021
|
try {
|
|
34693
|
-
const
|
|
36022
|
+
const sid = resolveSessionId(session_id);
|
|
36023
|
+
const page = getSessionPage(sid);
|
|
34694
36024
|
const info = await getPageInfo(page);
|
|
34695
|
-
const errors2 = getConsoleLog(
|
|
36025
|
+
const errors2 = getConsoleLog(sid, "error");
|
|
34696
36026
|
info.has_console_errors = errors2.length > 0;
|
|
34697
36027
|
let perf = {};
|
|
34698
36028
|
try {
|
|
@@ -34706,8 +36036,8 @@ var init_mcp = __esm(async () => {
|
|
|
34706
36036
|
let snapshot_refs = "";
|
|
34707
36037
|
let interactive_count = 0;
|
|
34708
36038
|
try {
|
|
34709
|
-
const snap = await takeSnapshot(page,
|
|
34710
|
-
setLastSnapshot(
|
|
36039
|
+
const snap = await takeSnapshot(page, sid);
|
|
36040
|
+
setLastSnapshot(sid, snap);
|
|
34711
36041
|
interactive_count = snap.interactive_count;
|
|
34712
36042
|
snapshot_refs = Object.entries(snap.refs).slice(0, 30).map(([ref, i]) => `${i.role}:${i.name.slice(0, 50)} [${ref}]`).join(", ");
|
|
34713
36043
|
} catch {}
|
|
@@ -34716,9 +36046,10 @@ var init_mcp = __esm(async () => {
|
|
|
34716
36046
|
return err(e);
|
|
34717
36047
|
}
|
|
34718
36048
|
});
|
|
34719
|
-
server.tool("browser_secrets_login", "Login to a service using credentials from open-secrets vault or ~/.secrets. One call replaces 10+ tool calls.", { session_id: exports_external.string(), service: exports_external.string(), login_url: exports_external.string().optional(), save_profile: exports_external.boolean().optional().default(true) }, async ({ session_id, service, login_url, save_profile }) => {
|
|
36049
|
+
server.tool("browser_secrets_login", "Login to a service using credentials from open-secrets vault or ~/.secrets. One call replaces 10+ tool calls.", { session_id: exports_external.string().optional(), service: exports_external.string(), login_url: exports_external.string().optional(), save_profile: exports_external.boolean().optional().default(true) }, async ({ session_id, service, login_url, save_profile }) => {
|
|
34720
36050
|
try {
|
|
34721
|
-
const
|
|
36051
|
+
const sid = resolveSessionId(session_id);
|
|
36052
|
+
const page = getSessionPage(sid);
|
|
34722
36053
|
const { getCredentials: getCredentials2, loginWithCredentials: loginWithCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
|
|
34723
36054
|
const creds = await getCredentials2(service);
|
|
34724
36055
|
if (!creds)
|
|
@@ -34732,9 +36063,10 @@ var init_mcp = __esm(async () => {
|
|
|
34732
36063
|
return err(e);
|
|
34733
36064
|
}
|
|
34734
36065
|
});
|
|
34735
|
-
server.tool("browser_remember", "Store page facts in open-mementos for future recall. Agents skip re-scraping on repeat visits.", { session_id: exports_external.string(), facts: exports_external.record(exports_external.unknown()), tags: exports_external.array(exports_external.string()).optional() }, async ({ session_id, facts, tags }) => {
|
|
36066
|
+
server.tool("browser_remember", "Store page facts in open-mementos for future recall. Agents skip re-scraping on repeat visits.", { session_id: exports_external.string().optional(), facts: exports_external.record(exports_external.unknown()), tags: exports_external.array(exports_external.string()).optional() }, async ({ session_id, facts, tags }) => {
|
|
34736
36067
|
try {
|
|
34737
|
-
const
|
|
36068
|
+
const sid = resolveSessionId(session_id);
|
|
36069
|
+
const page = getSessionPage(sid);
|
|
34738
36070
|
const { rememberPage: rememberPage2 } = await Promise.resolve().then(() => (init_page_memory(), exports_page_memory));
|
|
34739
36071
|
const url = page.url();
|
|
34740
36072
|
await rememberPage2(url, facts, tags);
|
|
@@ -34752,12 +36084,13 @@ var init_mcp = __esm(async () => {
|
|
|
34752
36084
|
return err(e);
|
|
34753
36085
|
}
|
|
34754
36086
|
});
|
|
34755
|
-
server.tool("browser_session_announce", "Announce to other agents via open-conversations what this session is browsing.", { session_id: exports_external.string(), message: exports_external.string().optional() }, async ({ session_id, message }) => {
|
|
36087
|
+
server.tool("browser_session_announce", "Announce to other agents via open-conversations what this session is browsing.", { session_id: exports_external.string().optional(), message: exports_external.string().optional() }, async ({ session_id, message }) => {
|
|
34756
36088
|
try {
|
|
34757
|
-
const
|
|
36089
|
+
const sid = resolveSessionId(session_id);
|
|
36090
|
+
const page = getSessionPage(sid);
|
|
34758
36091
|
const { announceNavigation: announceNavigation2 } = await Promise.resolve().then(() => (init_coordination(), exports_coordination));
|
|
34759
36092
|
const url = page.url();
|
|
34760
|
-
await announceNavigation2(url,
|
|
36093
|
+
await announceNavigation2(url, sid);
|
|
34761
36094
|
return json({ announced: true, url, message });
|
|
34762
36095
|
} catch (e) {
|
|
34763
36096
|
return err(e);
|
|
@@ -34797,9 +36130,10 @@ var init_mcp = __esm(async () => {
|
|
|
34797
36130
|
return err(e);
|
|
34798
36131
|
}
|
|
34799
36132
|
});
|
|
34800
|
-
server.tool("browser_skill_run", "Run a pre-built browser skill (login, extract-pricing, extract-nav-links, monitor-price, get-metadata). One call replaces 5\u201315 tool calls.", { session_id: exports_external.string(), skill: exports_external.string(), params: exports_external.record(exports_external.unknown()).optional().default({}) }, async ({ session_id, skill, params }) => {
|
|
36133
|
+
server.tool("browser_skill_run", "Run a pre-built browser skill (login, extract-pricing, extract-nav-links, monitor-price, get-metadata). One call replaces 5\u201315 tool calls.", { session_id: exports_external.string().optional(), skill: exports_external.string(), params: exports_external.record(exports_external.unknown()).optional().default({}) }, async ({ session_id, skill, params }) => {
|
|
34801
36134
|
try {
|
|
34802
|
-
const
|
|
36135
|
+
const sid = resolveSessionId(session_id);
|
|
36136
|
+
const page = getSessionPage(sid);
|
|
34803
36137
|
const { runBrowserSkill: runBrowserSkill2 } = await Promise.resolve().then(() => (init_skills_runner(), exports_skills_runner));
|
|
34804
36138
|
return json(await runBrowserSkill2(skill, params, page));
|
|
34805
36139
|
} catch (e) {
|
|
@@ -34815,7 +36149,7 @@ var init_mcp = __esm(async () => {
|
|
|
34815
36149
|
}
|
|
34816
36150
|
});
|
|
34817
36151
|
server.tool("browser_batch", "Execute multiple browser actions in one call. Returns final snapshot. Eliminates 80% of round trips for multi-step flows.", {
|
|
34818
|
-
session_id: exports_external.string(),
|
|
36152
|
+
session_id: exports_external.string().optional(),
|
|
34819
36153
|
actions: exports_external.array(exports_external.object({
|
|
34820
36154
|
tool: exports_external.string(),
|
|
34821
36155
|
args: exports_external.record(exports_external.unknown()).optional().default({})
|
|
@@ -34823,12 +36157,13 @@ var init_mcp = __esm(async () => {
|
|
|
34823
36157
|
}, async ({ session_id, actions }) => {
|
|
34824
36158
|
try {
|
|
34825
36159
|
const results = [];
|
|
34826
|
-
const
|
|
36160
|
+
const sid = resolveSessionId(session_id);
|
|
36161
|
+
const page = getSessionPage(sid);
|
|
34827
36162
|
const t0 = Date.now();
|
|
34828
36163
|
for (const action of actions) {
|
|
34829
36164
|
try {
|
|
34830
36165
|
const toolName = action.tool.replace(/^browser_/, "");
|
|
34831
|
-
const args = { session_id, ...action.args };
|
|
36166
|
+
const args = { session_id: sid, ...action.args };
|
|
34832
36167
|
switch (toolName) {
|
|
34833
36168
|
case "navigate":
|
|
34834
36169
|
await navigate(page, action.args.url);
|
|
@@ -34837,7 +36172,7 @@ var init_mcp = __esm(async () => {
|
|
|
34837
36172
|
case "click":
|
|
34838
36173
|
if (args.ref) {
|
|
34839
36174
|
const { clickRef: clickRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
34840
|
-
await clickRef2(page,
|
|
36175
|
+
await clickRef2(page, sid, args.ref);
|
|
34841
36176
|
} else if (args.selector)
|
|
34842
36177
|
await page.click(args.selector);
|
|
34843
36178
|
results.push({ tool: action.tool, success: true });
|
|
@@ -34845,7 +36180,7 @@ var init_mcp = __esm(async () => {
|
|
|
34845
36180
|
case "type":
|
|
34846
36181
|
if (args.ref && args.text) {
|
|
34847
36182
|
const { typeRef: typeRef2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
34848
|
-
await typeRef2(page,
|
|
36183
|
+
await typeRef2(page, sid, args.ref, args.text);
|
|
34849
36184
|
} else if (args.selector && args.text)
|
|
34850
36185
|
await page.fill(args.selector, args.text);
|
|
34851
36186
|
results.push({ tool: action.tool, success: true });
|
|
@@ -34885,7 +36220,7 @@ var init_mcp = __esm(async () => {
|
|
|
34885
36220
|
}
|
|
34886
36221
|
let final_snapshot = {};
|
|
34887
36222
|
try {
|
|
34888
|
-
const snap = await takeSnapshot(page,
|
|
36223
|
+
const snap = await takeSnapshot(page, sid);
|
|
34889
36224
|
final_snapshot = {
|
|
34890
36225
|
refs: Object.fromEntries(Object.entries(snap.refs).slice(0, 20)),
|
|
34891
36226
|
interactive_count: snap.interactive_count
|
|
@@ -34903,6 +36238,82 @@ var init_mcp = __esm(async () => {
|
|
|
34903
36238
|
return err(e);
|
|
34904
36239
|
}
|
|
34905
36240
|
});
|
|
36241
|
+
server.tool("browser_parallel", "Execute actions across multiple sessions in parallel. Each action targets a different session. Returns results array.", {
|
|
36242
|
+
actions: exports_external.array(exports_external.object({
|
|
36243
|
+
session_id: exports_external.string().describe("Target session ID"),
|
|
36244
|
+
tool: exports_external.string().describe("Tool name (e.g. browser_navigate, browser_screenshot, browser_click)"),
|
|
36245
|
+
args: exports_external.record(exports_external.unknown()).optional().default({})
|
|
36246
|
+
})),
|
|
36247
|
+
timeout: exports_external.number().optional().default(30000).describe("Timeout per action in ms")
|
|
36248
|
+
}, async ({ actions, timeout }) => {
|
|
36249
|
+
try {
|
|
36250
|
+
const t0 = Date.now();
|
|
36251
|
+
const promises = actions.map(async (action, index) => {
|
|
36252
|
+
try {
|
|
36253
|
+
const sid = action.session_id;
|
|
36254
|
+
const page = getSessionPage(sid);
|
|
36255
|
+
const args = action.args;
|
|
36256
|
+
const toolName = action.tool.replace(/^browser_/, "");
|
|
36257
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout));
|
|
36258
|
+
const actionPromise = (async () => {
|
|
36259
|
+
switch (toolName) {
|
|
36260
|
+
case "navigate": {
|
|
36261
|
+
await navigate(page, args.url);
|
|
36262
|
+
const title = await page.title();
|
|
36263
|
+
return { url: page.url(), title };
|
|
36264
|
+
}
|
|
36265
|
+
case "screenshot": {
|
|
36266
|
+
const result2 = await takeScreenshot(page, {
|
|
36267
|
+
maxWidth: args.max_width ?? 800,
|
|
36268
|
+
quality: args.quality ?? 60
|
|
36269
|
+
});
|
|
36270
|
+
return { path: result2.path, size_bytes: result2.size_bytes };
|
|
36271
|
+
}
|
|
36272
|
+
case "click": {
|
|
36273
|
+
if (args.selector)
|
|
36274
|
+
await click(page, args.selector);
|
|
36275
|
+
return { clicked: args.selector };
|
|
36276
|
+
}
|
|
36277
|
+
case "type": {
|
|
36278
|
+
if (args.selector && args.text)
|
|
36279
|
+
await type(page, args.selector, args.text);
|
|
36280
|
+
return { typed: args.text };
|
|
36281
|
+
}
|
|
36282
|
+
case "get_text": {
|
|
36283
|
+
const text = await getText(page);
|
|
36284
|
+
return { text: text.slice(0, 1000), length: text.length };
|
|
36285
|
+
}
|
|
36286
|
+
case "get_links": {
|
|
36287
|
+
const links = await getLinks(page);
|
|
36288
|
+
return { links, count: links.length };
|
|
36289
|
+
}
|
|
36290
|
+
case "snapshot": {
|
|
36291
|
+
const snap = await takeSnapshot(page, sid);
|
|
36292
|
+
return { interactive_count: snap.interactive_count, refs_count: Object.keys(snap.refs).length };
|
|
36293
|
+
}
|
|
36294
|
+
case "evaluate": {
|
|
36295
|
+
const result2 = await page.evaluate(args.expression);
|
|
36296
|
+
return { result: result2 };
|
|
36297
|
+
}
|
|
36298
|
+
default:
|
|
36299
|
+
return { error: `Unknown tool: ${action.tool}` };
|
|
36300
|
+
}
|
|
36301
|
+
})();
|
|
36302
|
+
const result = await Promise.race([actionPromise, timeoutPromise]);
|
|
36303
|
+
return { index, session_id: sid, tool: action.tool, success: true, result };
|
|
36304
|
+
} catch (e) {
|
|
36305
|
+
return { index, session_id: action.session_id, tool: action.tool, success: false, error: e instanceof Error ? e.message : String(e) };
|
|
36306
|
+
}
|
|
36307
|
+
});
|
|
36308
|
+
const results = await Promise.all(promises);
|
|
36309
|
+
const duration_ms = Date.now() - t0;
|
|
36310
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
36311
|
+
const failed = results.filter((r) => !r.success).length;
|
|
36312
|
+
return json({ results, duration_ms, succeeded, failed, total: actions.length });
|
|
36313
|
+
} catch (e) {
|
|
36314
|
+
return err(e);
|
|
36315
|
+
}
|
|
36316
|
+
});
|
|
34906
36317
|
server.tool("browser_pool_status", "Get status of the pre-warmed browser session pool.", {}, async () => {
|
|
34907
36318
|
try {
|
|
34908
36319
|
return json({ message: "Session pool not yet implemented in this version. Coming in v0.0.6+", ready: 0, total: 0 });
|
|
@@ -34982,18 +36393,20 @@ var init_mcp = __esm(async () => {
|
|
|
34982
36393
|
return err(e);
|
|
34983
36394
|
}
|
|
34984
36395
|
});
|
|
34985
|
-
server.tool("browser_task", "Execute a natural language browser task autonomously using Claude Haiku. Returns result + steps taken.", { session_id: exports_external.string(), task: exports_external.string(), max_steps: exports_external.number().optional().default(10), model: exports_external.string().optional() }, async ({ session_id, task, max_steps, model }) => {
|
|
36396
|
+
server.tool("browser_task", "Execute a natural language browser task autonomously using Claude Haiku. Returns result + steps taken.", { session_id: exports_external.string().optional(), task: exports_external.string(), max_steps: exports_external.number().optional().default(10), model: exports_external.string().optional() }, async ({ session_id, task, max_steps, model }) => {
|
|
34986
36397
|
try {
|
|
34987
|
-
const
|
|
36398
|
+
const sid = resolveSessionId(session_id);
|
|
36399
|
+
const page = getSessionPage(sid);
|
|
34988
36400
|
const { executeBrowserTask: executeBrowserTask2 } = await Promise.resolve().then(() => (init_ai_task(), exports_ai_task));
|
|
34989
|
-
return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId:
|
|
36401
|
+
return json(await executeBrowserTask2(page, task, { maxSteps: max_steps, model, sessionId: sid }));
|
|
34990
36402
|
} catch (e) {
|
|
34991
36403
|
return err(e);
|
|
34992
36404
|
}
|
|
34993
36405
|
});
|
|
34994
|
-
server.tool("browser_assert", `Assert page conditions in one call. Conditions: 'url contains X', 'text:"Y" is visible', 'element:"#id" exists', 'count:"a" > 10', 'title contains Z'. Chain with AND.`, { session_id: exports_external.string(), condition: exports_external.string() }, async ({ session_id, condition }) => {
|
|
36406
|
+
server.tool("browser_assert", `Assert page conditions in one call. Conditions: 'url contains X', 'text:"Y" is visible', 'element:"#id" exists', 'count:"a" > 10', 'title contains Z'. Chain with AND.`, { session_id: exports_external.string().optional(), condition: exports_external.string() }, async ({ session_id, condition }) => {
|
|
34995
36407
|
try {
|
|
34996
|
-
const
|
|
36408
|
+
const sid = resolveSessionId(session_id);
|
|
36409
|
+
const page = getSessionPage(sid);
|
|
34997
36410
|
const checks = [];
|
|
34998
36411
|
let passed = true;
|
|
34999
36412
|
for (const part of condition.split(/\s+AND\s+/i)) {
|
|
@@ -35055,10 +36468,10 @@ __export(exports_snapshots, {
|
|
|
35055
36468
|
deleteSnapshot: () => deleteSnapshot,
|
|
35056
36469
|
createSnapshot: () => createSnapshot
|
|
35057
36470
|
});
|
|
35058
|
-
import { randomUUID as
|
|
36471
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
35059
36472
|
function createSnapshot(data) {
|
|
35060
36473
|
const db2 = getDatabase();
|
|
35061
|
-
const id =
|
|
36474
|
+
const id = randomUUID14();
|
|
35062
36475
|
db2.prepare("INSERT INTO snapshots (id, session_id, url, title, html, screenshot_path) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.url, data.title ?? null, data.html ?? null, data.screenshot_path ?? null);
|
|
35063
36476
|
return getSnapshot(id);
|
|
35064
36477
|
}
|
|
@@ -35084,8 +36497,8 @@ var init_snapshots = __esm(() => {
|
|
|
35084
36497
|
|
|
35085
36498
|
// src/server/index.ts
|
|
35086
36499
|
var exports_server = {};
|
|
35087
|
-
import { join as
|
|
35088
|
-
import { existsSync as
|
|
36500
|
+
import { join as join15 } from "path";
|
|
36501
|
+
import { existsSync as existsSync9 } from "fs";
|
|
35089
36502
|
function ok(data, status = 200) {
|
|
35090
36503
|
return new Response(JSON.stringify(data), {
|
|
35091
36504
|
status,
|
|
@@ -35335,14 +36748,14 @@ var init_server = __esm(() => {
|
|
|
35335
36748
|
if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
|
|
35336
36749
|
const id = path.split("/")[3];
|
|
35337
36750
|
const entry = getEntry(id);
|
|
35338
|
-
if (!entry?.thumbnail_path || !
|
|
36751
|
+
if (!entry?.thumbnail_path || !existsSync9(entry.thumbnail_path))
|
|
35339
36752
|
return notFound("Thumbnail not found");
|
|
35340
36753
|
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
|
|
35341
36754
|
}
|
|
35342
36755
|
if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
|
|
35343
36756
|
const id = path.split("/")[3];
|
|
35344
36757
|
const entry = getEntry(id);
|
|
35345
|
-
if (!entry?.path || !
|
|
36758
|
+
if (!entry?.path || !existsSync9(entry.path))
|
|
35346
36759
|
return notFound("Image not found");
|
|
35347
36760
|
return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
|
|
35348
36761
|
}
|
|
@@ -35370,7 +36783,7 @@ var init_server = __esm(() => {
|
|
|
35370
36783
|
if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
|
|
35371
36784
|
const id = path.split("/")[3];
|
|
35372
36785
|
const file = getDownload(id);
|
|
35373
|
-
if (!file || !
|
|
36786
|
+
if (!file || !existsSync9(file.path))
|
|
35374
36787
|
return notFound("Download not found");
|
|
35375
36788
|
return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
|
|
35376
36789
|
}
|
|
@@ -35378,13 +36791,13 @@ var init_server = __esm(() => {
|
|
|
35378
36791
|
const id = path.split("/")[3];
|
|
35379
36792
|
return ok({ deleted: deleteDownload(id) });
|
|
35380
36793
|
}
|
|
35381
|
-
const dashboardDist =
|
|
35382
|
-
if (
|
|
35383
|
-
const filePath = path === "/" ?
|
|
35384
|
-
if (
|
|
36794
|
+
const dashboardDist = join15(import.meta.dir, "../../dashboard/dist");
|
|
36795
|
+
if (existsSync9(dashboardDist)) {
|
|
36796
|
+
const filePath = path === "/" ? join15(dashboardDist, "index.html") : join15(dashboardDist, path);
|
|
36797
|
+
if (existsSync9(filePath)) {
|
|
35385
36798
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
35386
36799
|
}
|
|
35387
|
-
return new Response(Bun.file(
|
|
36800
|
+
return new Response(Bun.file(join15(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
35388
36801
|
}
|
|
35389
36802
|
if (path === "/" || path === "") {
|
|
35390
36803
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
@@ -35427,31 +36840,71 @@ init_recorder();
|
|
|
35427
36840
|
init_recordings();
|
|
35428
36841
|
init_lightpanda();
|
|
35429
36842
|
import { readFileSync as readFileSync9 } from "fs";
|
|
35430
|
-
import { join as
|
|
36843
|
+
import { join as join16 } from "path";
|
|
35431
36844
|
import chalk from "chalk";
|
|
35432
|
-
var pkg = JSON.parse(readFileSync9(
|
|
36845
|
+
var pkg = JSON.parse(readFileSync9(join16(import.meta.dir, "../../package.json"), "utf8"));
|
|
35433
36846
|
var program2 = new Command;
|
|
35434
36847
|
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
|
|
35435
|
-
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("--
|
|
35436
|
-
const { session, page } = await createSession2({ engine: opts.engine, headless:
|
|
35437
|
-
console.log(chalk.gray(`Session: ${session.id} (${session.engine})`));
|
|
36848
|
+
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("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
36849
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
35438
36850
|
await navigate(page, url);
|
|
35439
36851
|
const title = await page.title();
|
|
35440
|
-
|
|
35441
|
-
console.log(chalk.blue(` Title: ${title}`));
|
|
36852
|
+
let screenshotPath;
|
|
35442
36853
|
if (opts.screenshot) {
|
|
35443
36854
|
const result = await takeScreenshot(page);
|
|
35444
|
-
|
|
36855
|
+
screenshotPath = result.path;
|
|
35445
36856
|
}
|
|
36857
|
+
let text;
|
|
35446
36858
|
if (opts.extract) {
|
|
35447
|
-
|
|
35448
|
-
|
|
36859
|
+
text = await getText(page);
|
|
36860
|
+
}
|
|
36861
|
+
if (opts.json) {
|
|
36862
|
+
const output = { session_id: session.id, engine: session.engine, url, title };
|
|
36863
|
+
if (screenshotPath)
|
|
36864
|
+
output.screenshot = screenshotPath;
|
|
36865
|
+
if (text)
|
|
36866
|
+
output.text = text.slice(0, 500);
|
|
36867
|
+
console.log(JSON.stringify(output, null, 2));
|
|
36868
|
+
} else {
|
|
36869
|
+
console.log(chalk.gray(`Session: ${session.id} (${session.engine})`));
|
|
36870
|
+
console.log(chalk.green(`\u2713 Navigated to: ${url}`));
|
|
36871
|
+
console.log(chalk.blue(` Title: ${title}`));
|
|
36872
|
+
if (screenshotPath)
|
|
36873
|
+
console.log(chalk.blue(` Screenshot: ${screenshotPath}`));
|
|
36874
|
+
if (text)
|
|
36875
|
+
console.log(chalk.white(`
|
|
35449
36876
|
${text.slice(0, 500)}...`));
|
|
35450
36877
|
}
|
|
35451
36878
|
await closeSession2(session.id);
|
|
35452
36879
|
});
|
|
35453
|
-
program2.command("
|
|
35454
|
-
const { session, page } = await createSession2({ engine: opts.engine, headless:
|
|
36880
|
+
program2.command("check <url>").description("One-liner page health check: navigate, screenshot, extract info, check errors").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
36881
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
36882
|
+
await navigate(page, url);
|
|
36883
|
+
const title = await page.title();
|
|
36884
|
+
const currentUrl = page.url();
|
|
36885
|
+
const text = await getText(page);
|
|
36886
|
+
const links = await getLinks(page);
|
|
36887
|
+
const result = await takeScreenshot(page);
|
|
36888
|
+
const summary = {
|
|
36889
|
+
url: currentUrl,
|
|
36890
|
+
title,
|
|
36891
|
+
text_length: text.length,
|
|
36892
|
+
links_count: links.length,
|
|
36893
|
+
screenshot: result.path,
|
|
36894
|
+
screenshot_size_kb: +(result.size_bytes / 1024).toFixed(1)
|
|
36895
|
+
};
|
|
36896
|
+
if (opts.json) {
|
|
36897
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
36898
|
+
} else {
|
|
36899
|
+
console.log(chalk.green(`\u2713 ${title}`));
|
|
36900
|
+
console.log(chalk.blue(` URL: ${currentUrl}`));
|
|
36901
|
+
console.log(chalk.gray(` Text: ${text.length} chars, Links: ${links.length}`));
|
|
36902
|
+
console.log(chalk.gray(` Screenshot: ${result.path} (${summary.screenshot_size_kb} KB)`));
|
|
36903
|
+
}
|
|
36904
|
+
await closeSession2(session.id);
|
|
36905
|
+
});
|
|
36906
|
+
program2.command("screenshot <url>").description("Navigate to a URL and take a screenshot").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector for element screenshot").option("--full-page", "Capture full page").option("--format <format>", "Image format: png|jpeg|webp", "png").option("--headed", "Run in headed (visible) mode").action(async (url, opts) => {
|
|
36907
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
35455
36908
|
await navigate(page, url);
|
|
35456
36909
|
const result = await takeScreenshot(page, {
|
|
35457
36910
|
selector: opts.selector,
|
|
@@ -35462,11 +36915,13 @@ program2.command("screenshot <url>").description("Navigate to a URL and take a s
|
|
|
35462
36915
|
console.log(chalk.gray(` Size: ${(result.size_bytes / 1024).toFixed(1)} KB`));
|
|
35463
36916
|
await closeSession2(session.id);
|
|
35464
36917
|
});
|
|
35465
|
-
program2.command("extract <url>").description("Extract content from a URL").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector").option("--format <format>", "Format: text|html|links|table|structured", "text").action(async (url, opts) => {
|
|
35466
|
-
const { session, page } = await createSession2({ engine: opts.engine, headless:
|
|
36918
|
+
program2.command("extract <url>").description("Extract content from a URL").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector").option("--format <format>", "Format: text|html|links|table|structured", "text").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
36919
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
35467
36920
|
await navigate(page, url);
|
|
35468
36921
|
const result = await extract(page, { format: opts.format, selector: opts.selector });
|
|
35469
|
-
if (opts.
|
|
36922
|
+
if (opts.json) {
|
|
36923
|
+
console.log(JSON.stringify(result, null, 2));
|
|
36924
|
+
} else if (opts.format === "links" && result.links) {
|
|
35470
36925
|
result.links.forEach((l) => console.log(l));
|
|
35471
36926
|
} else if (opts.format === "table" && result.table) {
|
|
35472
36927
|
result.table.forEach((row) => console.log(row.join("\t")));
|
|
@@ -35475,8 +36930,8 @@ program2.command("extract <url>").description("Extract content from a URL").opti
|
|
|
35475
36930
|
}
|
|
35476
36931
|
await closeSession2(session.id);
|
|
35477
36932
|
});
|
|
35478
|
-
program2.command("eval <url> <script>").description("Run JavaScript in a page context").option("--engine <engine>", "Browser engine", "auto").action(async (url, script, opts) => {
|
|
35479
|
-
const { session, page } = await createSession2({ engine: opts.engine, headless:
|
|
36933
|
+
program2.command("eval <url> <script>").description("Run JavaScript in a page context").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (url, script, opts) => {
|
|
36934
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
35480
36935
|
await navigate(page, url);
|
|
35481
36936
|
const result = await page.evaluate(script);
|
|
35482
36937
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -35502,14 +36957,16 @@ ${result.errors.length} errors:`));
|
|
|
35502
36957
|
}
|
|
35503
36958
|
});
|
|
35504
36959
|
var sessionCmd = program2.command("session").description("Manage browser sessions");
|
|
35505
|
-
sessionCmd.command("create").description("Create a new browser session").option("--engine <engine>", "Browser engine", "auto").option("--url <url>", "Start URL").action(async (opts) => {
|
|
35506
|
-
const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url });
|
|
36960
|
+
sessionCmd.command("create").description("Create a new browser session").option("--engine <engine>", "Browser engine", "auto").option("--url <url>", "Start URL").option("--headed", "Run in headed (visible) mode").action(async (opts) => {
|
|
36961
|
+
const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
|
|
35507
36962
|
console.log(chalk.green(`\u2713 Session created`));
|
|
35508
36963
|
console.log(JSON.stringify(session, null, 2));
|
|
35509
36964
|
});
|
|
35510
|
-
sessionCmd.command("list").description("List all sessions").option("--status <status>", "Filter by status").action((opts) => {
|
|
36965
|
+
sessionCmd.command("list").description("List all sessions").option("--status <status>", "Filter by status").option("--json", "Output as JSON").action((opts) => {
|
|
35511
36966
|
const sessions = listSessions2(opts.status ? { status: opts.status } : undefined);
|
|
35512
|
-
if (
|
|
36967
|
+
if (opts.json) {
|
|
36968
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
36969
|
+
} else if (sessions.length === 0) {
|
|
35513
36970
|
console.log(chalk.gray("No sessions found"));
|
|
35514
36971
|
} else {
|
|
35515
36972
|
sessions.forEach((s) => console.log(`${s.id} [${s.status}] ${s.engine} ${s.start_url ?? ""}`));
|
|
@@ -35519,9 +36976,25 @@ sessionCmd.command("close <id>").description("Close a session").action(async (id
|
|
|
35519
36976
|
await closeSession2(id);
|
|
35520
36977
|
console.log(chalk.green(`\u2713 Session closed: ${id}`));
|
|
35521
36978
|
});
|
|
36979
|
+
sessionCmd.command("save-state <name>").description("Save current session auth state for reuse").requiredOption("--session <id>", "Session ID").action(async (name, opts) => {
|
|
36980
|
+
const page = getSessionPage(opts.session);
|
|
36981
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36982
|
+
const path = await saveStateFromPage2(page, name);
|
|
36983
|
+
console.log(chalk.green(`\u2713 State saved: ${name}`));
|
|
36984
|
+
console.log(chalk.gray(` Path: ${path}`));
|
|
36985
|
+
});
|
|
36986
|
+
sessionCmd.command("list-states").description("List saved auth states").action(async () => {
|
|
36987
|
+
const { listStates: listStates2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36988
|
+
const states = listStates2();
|
|
36989
|
+
if (states.length === 0) {
|
|
36990
|
+
console.log(chalk.gray("No saved states"));
|
|
36991
|
+
return;
|
|
36992
|
+
}
|
|
36993
|
+
states.forEach((s) => console.log(`${s.name} ${chalk.gray(s.modified)}`));
|
|
36994
|
+
});
|
|
35522
36995
|
var recordCmd = program2.command("record").description("Manage action recordings");
|
|
35523
|
-
recordCmd.command("start <name>").description("Start recording actions in a new session").option("--url <url>", "Start URL").option("--engine <engine>", "Browser engine", "auto").action(async (name, opts) => {
|
|
35524
|
-
const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url });
|
|
36996
|
+
recordCmd.command("start <name>").description("Start recording actions in a new session").option("--url <url>", "Start URL").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (name, opts) => {
|
|
36997
|
+
const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
|
|
35525
36998
|
const recording = startRecording(session.id, name, opts.url);
|
|
35526
36999
|
console.log(chalk.green(`\u2713 Recording started`));
|
|
35527
37000
|
console.log(` Recording ID: ${recording.id}`);
|
|
@@ -35532,8 +37005,8 @@ recordCmd.command("stop <recording_id>").description("Stop an active recording")
|
|
|
35532
37005
|
console.log(chalk.green(`\u2713 Recording stopped: ${recording.name}`));
|
|
35533
37006
|
console.log(` Steps: ${recording.steps.length}`);
|
|
35534
37007
|
});
|
|
35535
|
-
recordCmd.command("replay <recording_id>").description("Replay a recording in a new session").option("--url <url>", "Override start URL").option("--engine <engine>", "Browser engine", "auto").action(async (id, opts) => {
|
|
35536
|
-
const { session, page } = await createSession2({ engine: opts.engine, startUrl: opts.url });
|
|
37008
|
+
recordCmd.command("replay <recording_id>").description("Replay a recording in a new session").option("--url <url>", "Override start URL").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").action(async (id, opts) => {
|
|
37009
|
+
const { session, page } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
|
|
35537
37010
|
const result = await replayRecording(id, page);
|
|
35538
37011
|
console.log(result.success ? chalk.green("\u2713 Replay complete") : chalk.red("\u2717 Replay had errors"));
|
|
35539
37012
|
console.log(` Steps: ${result.steps_executed} executed, ${result.steps_failed} failed`);
|
|
@@ -35581,6 +37054,19 @@ projectCmd.command("list").description("List all projects").action(() => {
|
|
|
35581
37054
|
projects.forEach((p) => console.log(`${p.id} "${p.name}" ${p.path}`));
|
|
35582
37055
|
}
|
|
35583
37056
|
});
|
|
37057
|
+
program2.command("attach").description("Attach to a running Chrome browser via CDP").option("--port <port>", "Chrome debugging port", "9222").option("--host <host>", "Chrome debugging host", "localhost").option("--json", "Output as JSON").action(async (opts) => {
|
|
37058
|
+
const cdpUrl = `http://${opts.host}:${opts.port}`;
|
|
37059
|
+
const { session, page } = await createSession2({ cdpUrl });
|
|
37060
|
+
const title = await page.title();
|
|
37061
|
+
const url = page.url();
|
|
37062
|
+
if (opts.json) {
|
|
37063
|
+
console.log(JSON.stringify({ session_id: session.id, url, title, cdp_url: cdpUrl }));
|
|
37064
|
+
} else {
|
|
37065
|
+
console.log(chalk.green(`\u2713 Attached to Chrome at ${cdpUrl}`));
|
|
37066
|
+
console.log(chalk.blue(` Session: ${session.id}`));
|
|
37067
|
+
console.log(chalk.blue(` Page: ${title} (${url})`));
|
|
37068
|
+
}
|
|
37069
|
+
});
|
|
35584
37070
|
program2.command("install-browser").description("Install a browser engine").option("--engine <engine>", "Engine to install: lightpanda|chromium", "chromium").action(async (opts) => {
|
|
35585
37071
|
if (opts.engine === "chromium") {
|
|
35586
37072
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -35672,11 +37158,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
|
|
|
35672
37158
|
});
|
|
35673
37159
|
galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
|
|
35674
37160
|
const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
35675
|
-
const { existsSync:
|
|
37161
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
35676
37162
|
const entries = listEntries2({ limit: 9999 });
|
|
35677
37163
|
let removed = 0;
|
|
35678
37164
|
for (const e of entries) {
|
|
35679
|
-
if (!
|
|
37165
|
+
if (!existsSync10(e.path)) {
|
|
35680
37166
|
deleteEntry2(e.id);
|
|
35681
37167
|
removed++;
|
|
35682
37168
|
}
|