@hasna/browser 0.0.9 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +2090 -364
- package/dist/engines/cdp.d.ts +2 -1
- package/dist/engines/cdp.d.ts.map +1 -1
- package/dist/index.js +545 -214
- package/dist/lib/actions.d.ts +22 -4
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/api-detector.d.ts +17 -0
- package/dist/lib/api-detector.d.ts.map +1 -0
- package/dist/lib/auth-flow.d.ts +43 -0
- package/dist/lib/auth-flow.d.ts.map +1 -0
- package/dist/lib/datasets.d.ts +33 -0
- package/dist/lib/datasets.d.ts.map +1 -0
- package/dist/lib/deep-performance.d.ts +49 -0
- package/dist/lib/deep-performance.d.ts.map +1 -0
- package/dist/lib/env-detector.d.ts +12 -0
- package/dist/lib/env-detector.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.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/structured-extract.d.ts +26 -0
- package/dist/lib/structured-extract.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/lib/workflows.d.ts +46 -0
- package/dist/lib/workflows.d.ts.map +1 -0
- package/dist/mcp/index.js +2196 -485
- package/dist/server/index.js +422 -158
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2341,6 +2341,71 @@ function runMigrations(db) {
|
|
|
2341
2341
|
);
|
|
2342
2342
|
CREATE INDEX IF NOT EXISTS idx_session_tags_tag ON session_tags(tag);
|
|
2343
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
|
+
`
|
|
2361
|
+
},
|
|
2362
|
+
{
|
|
2363
|
+
version: 7,
|
|
2364
|
+
sql: `
|
|
2365
|
+
CREATE TABLE IF NOT EXISTS workflows (
|
|
2366
|
+
id TEXT PRIMARY KEY,
|
|
2367
|
+
name TEXT NOT NULL UNIQUE,
|
|
2368
|
+
description TEXT,
|
|
2369
|
+
steps TEXT NOT NULL DEFAULT '[]',
|
|
2370
|
+
start_url TEXT,
|
|
2371
|
+
last_run TEXT,
|
|
2372
|
+
last_heal TEXT,
|
|
2373
|
+
heal_count INTEGER DEFAULT 0,
|
|
2374
|
+
run_count INTEGER DEFAULT 0,
|
|
2375
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
2376
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
2377
|
+
);
|
|
2378
|
+
`
|
|
2379
|
+
},
|
|
2380
|
+
{
|
|
2381
|
+
version: 8,
|
|
2382
|
+
sql: `
|
|
2383
|
+
CREATE TABLE IF NOT EXISTS datasets (
|
|
2384
|
+
id TEXT PRIMARY KEY,
|
|
2385
|
+
name TEXT NOT NULL UNIQUE,
|
|
2386
|
+
source_url TEXT,
|
|
2387
|
+
source_type TEXT NOT NULL DEFAULT 'page',
|
|
2388
|
+
data TEXT NOT NULL DEFAULT '[]',
|
|
2389
|
+
schema TEXT,
|
|
2390
|
+
row_count INTEGER DEFAULT 0,
|
|
2391
|
+
last_refresh TEXT,
|
|
2392
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
2393
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
2394
|
+
);
|
|
2395
|
+
|
|
2396
|
+
CREATE TABLE IF NOT EXISTS api_endpoints (
|
|
2397
|
+
id TEXT PRIMARY KEY,
|
|
2398
|
+
session_id TEXT,
|
|
2399
|
+
url TEXT NOT NULL,
|
|
2400
|
+
method TEXT DEFAULT 'GET',
|
|
2401
|
+
response_schema TEXT,
|
|
2402
|
+
sample_response TEXT,
|
|
2403
|
+
status_code INTEGER,
|
|
2404
|
+
content_type TEXT,
|
|
2405
|
+
discovered_at TEXT DEFAULT (datetime('now'))
|
|
2406
|
+
);
|
|
2407
|
+
CREATE INDEX IF NOT EXISTS idx_api_endpoints_session ON api_endpoints(session_id);
|
|
2408
|
+
`
|
|
2344
2409
|
}
|
|
2345
2410
|
];
|
|
2346
2411
|
for (const m of migrations) {
|
|
@@ -3305,14 +3370,14 @@ function enableConsoleCapture(page, sessionId) {
|
|
|
3305
3370
|
warning: "warn"
|
|
3306
3371
|
};
|
|
3307
3372
|
const level = levelMap[msg.type()] ?? "log";
|
|
3308
|
-
const
|
|
3373
|
+
const location2 = msg.location();
|
|
3309
3374
|
try {
|
|
3310
3375
|
logConsoleMessage({
|
|
3311
3376
|
session_id: sessionId,
|
|
3312
3377
|
level,
|
|
3313
3378
|
message: msg.text(),
|
|
3314
|
-
source:
|
|
3315
|
-
line_number:
|
|
3379
|
+
source: location2.url || undefined,
|
|
3380
|
+
line_number: location2.lineNumber || undefined
|
|
3316
3381
|
});
|
|
3317
3382
|
} catch {}
|
|
3318
3383
|
};
|
|
@@ -3456,6 +3521,188 @@ var init_dialogs = __esm(() => {
|
|
|
3456
3521
|
pendingDialogs = new Map;
|
|
3457
3522
|
});
|
|
3458
3523
|
|
|
3524
|
+
// src/engines/cdp.ts
|
|
3525
|
+
var exports_cdp = {};
|
|
3526
|
+
__export(exports_cdp, {
|
|
3527
|
+
connectToExistingBrowser: () => connectToExistingBrowser,
|
|
3528
|
+
CDPClient: () => CDPClient
|
|
3529
|
+
});
|
|
3530
|
+
async function connectToExistingBrowser(cdpUrl) {
|
|
3531
|
+
const { chromium: chromium3 } = await import("playwright");
|
|
3532
|
+
try {
|
|
3533
|
+
return await chromium3.connectOverCDP(cdpUrl);
|
|
3534
|
+
} catch (err) {
|
|
3535
|
+
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);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
|
|
3539
|
+
class CDPClient {
|
|
3540
|
+
session;
|
|
3541
|
+
networkEnabled = false;
|
|
3542
|
+
performanceEnabled = false;
|
|
3543
|
+
constructor(session) {
|
|
3544
|
+
this.session = session;
|
|
3545
|
+
}
|
|
3546
|
+
static async fromPage(page) {
|
|
3547
|
+
try {
|
|
3548
|
+
const session = await page.context().newCDPSession(page);
|
|
3549
|
+
return new CDPClient(session);
|
|
3550
|
+
} catch (err) {
|
|
3551
|
+
throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
async send(method, params) {
|
|
3555
|
+
try {
|
|
3556
|
+
return await this.session.send(method, params);
|
|
3557
|
+
} catch (err) {
|
|
3558
|
+
throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
on(event, handler) {
|
|
3562
|
+
this.session.on(event, handler);
|
|
3563
|
+
}
|
|
3564
|
+
off(event, handler) {
|
|
3565
|
+
this.session.off(event, handler);
|
|
3566
|
+
}
|
|
3567
|
+
async enableNetwork() {
|
|
3568
|
+
if (!this.networkEnabled) {
|
|
3569
|
+
await this.send("Network.enable");
|
|
3570
|
+
this.networkEnabled = true;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
async enablePerformance() {
|
|
3574
|
+
if (!this.performanceEnabled) {
|
|
3575
|
+
await this.send("Performance.enable");
|
|
3576
|
+
this.performanceEnabled = true;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
async getPerformanceMetrics() {
|
|
3580
|
+
await this.enablePerformance();
|
|
3581
|
+
const result = await this.send("Performance.getMetrics");
|
|
3582
|
+
const m = {};
|
|
3583
|
+
for (const metric of result.metrics) {
|
|
3584
|
+
m[metric.name] = metric.value;
|
|
3585
|
+
}
|
|
3586
|
+
return {
|
|
3587
|
+
js_heap_size_used: m["JSHeapUsedSize"],
|
|
3588
|
+
js_heap_size_total: m["JSHeapTotalSize"],
|
|
3589
|
+
dom_interactive: m["DOMInteractive"],
|
|
3590
|
+
dom_complete: m["DOMComplete"],
|
|
3591
|
+
load_event: m["LoadEventEnd"]
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
async startJSCoverage() {
|
|
3595
|
+
await this.send("Profiler.enable");
|
|
3596
|
+
await this.send("Debugger.enable");
|
|
3597
|
+
await this.send("Profiler.startPreciseCoverage", {
|
|
3598
|
+
callCount: false,
|
|
3599
|
+
detailed: true
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
async stopJSCoverage() {
|
|
3603
|
+
const result = await this.send("Profiler.takePreciseCoverage");
|
|
3604
|
+
await this.send("Profiler.stopPreciseCoverage");
|
|
3605
|
+
return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
|
|
3606
|
+
url: r.url,
|
|
3607
|
+
text: "",
|
|
3608
|
+
ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
|
|
3609
|
+
}));
|
|
3610
|
+
}
|
|
3611
|
+
async getCoverage() {
|
|
3612
|
+
await this.startJSCoverage();
|
|
3613
|
+
const js = await this.stopJSCoverage();
|
|
3614
|
+
const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
|
|
3615
|
+
return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
|
|
3616
|
+
}
|
|
3617
|
+
async captureHAREntries(page, handler) {
|
|
3618
|
+
await this.enableNetwork();
|
|
3619
|
+
const requestTimings = new Map;
|
|
3620
|
+
const onRequest = (params) => {
|
|
3621
|
+
requestTimings.set(params.requestId, params.timestamp);
|
|
3622
|
+
};
|
|
3623
|
+
const onResponse = (params) => {
|
|
3624
|
+
const start = requestTimings.get(params.requestId);
|
|
3625
|
+
const duration = start != null ? (params.timestamp - start) * 1000 : 0;
|
|
3626
|
+
handler({
|
|
3627
|
+
method: "GET",
|
|
3628
|
+
url: params.response.url,
|
|
3629
|
+
status: params.response.status,
|
|
3630
|
+
duration
|
|
3631
|
+
});
|
|
3632
|
+
};
|
|
3633
|
+
this.on("Network.requestWillBeSent", onRequest);
|
|
3634
|
+
this.on("Network.responseReceived", onResponse);
|
|
3635
|
+
return () => {
|
|
3636
|
+
this.off("Network.requestWillBeSent", onRequest);
|
|
3637
|
+
this.off("Network.responseReceived", onResponse);
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
async detach() {
|
|
3641
|
+
try {
|
|
3642
|
+
await this.session.detach();
|
|
3643
|
+
} catch {}
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
var init_cdp = __esm(() => {
|
|
3647
|
+
init_types();
|
|
3648
|
+
});
|
|
3649
|
+
|
|
3650
|
+
// src/lib/storage-state.ts
|
|
3651
|
+
var exports_storage_state = {};
|
|
3652
|
+
__export(exports_storage_state, {
|
|
3653
|
+
saveStateFromPage: () => saveStateFromPage,
|
|
3654
|
+
saveState: () => saveState,
|
|
3655
|
+
loadStatePath: () => loadStatePath,
|
|
3656
|
+
listStates: () => listStates,
|
|
3657
|
+
deleteState: () => deleteState
|
|
3658
|
+
});
|
|
3659
|
+
import { mkdirSync as mkdirSync3, existsSync, readdirSync, unlinkSync } from "fs";
|
|
3660
|
+
import { join as join3 } from "path";
|
|
3661
|
+
import { homedir as homedir3 } from "os";
|
|
3662
|
+
function ensureDir() {
|
|
3663
|
+
mkdirSync3(STATES_DIR, { recursive: true });
|
|
3664
|
+
}
|
|
3665
|
+
function statePath(name) {
|
|
3666
|
+
return join3(STATES_DIR, `${name}.json`);
|
|
3667
|
+
}
|
|
3668
|
+
async function saveState(context, name) {
|
|
3669
|
+
ensureDir();
|
|
3670
|
+
const path = statePath(name);
|
|
3671
|
+
const state = await context.storageState({ path });
|
|
3672
|
+
return path;
|
|
3673
|
+
}
|
|
3674
|
+
async function saveStateFromPage(page, name) {
|
|
3675
|
+
return saveState(page.context(), name);
|
|
3676
|
+
}
|
|
3677
|
+
function loadStatePath(name) {
|
|
3678
|
+
const path = statePath(name);
|
|
3679
|
+
return existsSync(path) ? path : null;
|
|
3680
|
+
}
|
|
3681
|
+
function listStates() {
|
|
3682
|
+
ensureDir();
|
|
3683
|
+
return readdirSync(STATES_DIR).filter((f) => f.endsWith(".json")).map((f) => {
|
|
3684
|
+
const path = join3(STATES_DIR, f);
|
|
3685
|
+
const stat = Bun.file(path);
|
|
3686
|
+
return {
|
|
3687
|
+
name: f.replace(".json", ""),
|
|
3688
|
+
path,
|
|
3689
|
+
modified: new Date(stat.lastModified).toISOString()
|
|
3690
|
+
};
|
|
3691
|
+
}).sort((a, b) => b.modified.localeCompare(a.modified));
|
|
3692
|
+
}
|
|
3693
|
+
function deleteState(name) {
|
|
3694
|
+
const path = statePath(name);
|
|
3695
|
+
if (existsSync(path)) {
|
|
3696
|
+
unlinkSync(path);
|
|
3697
|
+
return true;
|
|
3698
|
+
}
|
|
3699
|
+
return false;
|
|
3700
|
+
}
|
|
3701
|
+
var STATES_DIR;
|
|
3702
|
+
var init_storage_state = __esm(() => {
|
|
3703
|
+
STATES_DIR = join3(process.env["BROWSER_DATA_DIR"] ?? join3(homedir3(), ".browser"), "states");
|
|
3704
|
+
});
|
|
3705
|
+
|
|
3459
3706
|
// src/lib/session.ts
|
|
3460
3707
|
var exports_session = {};
|
|
3461
3708
|
__export(exports_session, {
|
|
@@ -3485,6 +3732,37 @@ function createBunProxy(view) {
|
|
|
3485
3732
|
return view;
|
|
3486
3733
|
}
|
|
3487
3734
|
async function createSession2(opts = {}) {
|
|
3735
|
+
if (opts.cdpUrl) {
|
|
3736
|
+
const { connectToExistingBrowser: connectToExistingBrowser2 } = await Promise.resolve().then(() => (init_cdp(), exports_cdp));
|
|
3737
|
+
const cdpBrowser = await connectToExistingBrowser2(opts.cdpUrl);
|
|
3738
|
+
const contexts = cdpBrowser.contexts();
|
|
3739
|
+
const context = contexts.length > 0 ? contexts[0] : await cdpBrowser.newContext();
|
|
3740
|
+
const pages = context.pages();
|
|
3741
|
+
const page2 = pages.length > 0 ? pages[0] : await context.newPage();
|
|
3742
|
+
const session2 = createSession({
|
|
3743
|
+
engine: "cdp",
|
|
3744
|
+
projectId: opts.projectId,
|
|
3745
|
+
agentId: opts.agentId,
|
|
3746
|
+
startUrl: page2.url(),
|
|
3747
|
+
name: opts.name ?? "attached"
|
|
3748
|
+
});
|
|
3749
|
+
const cleanups2 = [];
|
|
3750
|
+
if (opts.captureNetwork !== false) {
|
|
3751
|
+
try {
|
|
3752
|
+
cleanups2.push(enableNetworkLogging(page2, session2.id));
|
|
3753
|
+
} catch {}
|
|
3754
|
+
}
|
|
3755
|
+
if (opts.captureConsole !== false) {
|
|
3756
|
+
try {
|
|
3757
|
+
cleanups2.push(enableConsoleCapture(page2, session2.id));
|
|
3758
|
+
} catch {}
|
|
3759
|
+
}
|
|
3760
|
+
try {
|
|
3761
|
+
cleanups2.push(setupDialogHandler(page2, session2.id));
|
|
3762
|
+
} catch {}
|
|
3763
|
+
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 });
|
|
3764
|
+
return { session: session2, page: page2 };
|
|
3765
|
+
}
|
|
3488
3766
|
const engine = opts.engine === "auto" || !opts.engine ? selectEngine(opts.useCase ?? "spa_navigate" /* SPA_NAVIGATE */, opts.engine) : opts.engine;
|
|
3489
3767
|
const resolvedEngine = engine === "auto" ? "playwright" : engine;
|
|
3490
3768
|
let browser = null;
|
|
@@ -3510,7 +3788,22 @@ async function createSession2(opts = {}) {
|
|
|
3510
3788
|
page = await context.newPage();
|
|
3511
3789
|
} else {
|
|
3512
3790
|
browser = await pool.acquire(opts.headless ?? true);
|
|
3513
|
-
|
|
3791
|
+
if (opts.storageState) {
|
|
3792
|
+
const { loadStatePath: loadStatePath2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
3793
|
+
const statePath2 = loadStatePath2(opts.storageState);
|
|
3794
|
+
if (statePath2) {
|
|
3795
|
+
const context = await browser.newContext({
|
|
3796
|
+
viewport: opts.viewport ?? { width: 1280, height: 720 },
|
|
3797
|
+
userAgent: opts.userAgent,
|
|
3798
|
+
storageState: statePath2
|
|
3799
|
+
});
|
|
3800
|
+
page = await context.newPage();
|
|
3801
|
+
} else {
|
|
3802
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
3803
|
+
}
|
|
3804
|
+
} else {
|
|
3805
|
+
page = await getPage(browser, { viewport: opts.viewport, userAgent: opts.userAgent });
|
|
3806
|
+
}
|
|
3514
3807
|
}
|
|
3515
3808
|
const sessionName = opts.name ?? (opts.startUrl ? (() => {
|
|
3516
3809
|
try {
|
|
@@ -4015,6 +4308,66 @@ var init_snapshot = __esm(() => {
|
|
|
4015
4308
|
];
|
|
4016
4309
|
});
|
|
4017
4310
|
|
|
4311
|
+
// src/lib/self-heal.ts
|
|
4312
|
+
async function healSelector(page, selector, sessionId) {
|
|
4313
|
+
const attempts = [];
|
|
4314
|
+
attempts.push(`selector: ${selector}`);
|
|
4315
|
+
try {
|
|
4316
|
+
const loc = page.locator(selector).first();
|
|
4317
|
+
if (await loc.count() > 0) {
|
|
4318
|
+
return { found: true, locator: loc, method: "original", healed: false, attempts };
|
|
4319
|
+
}
|
|
4320
|
+
} catch {}
|
|
4321
|
+
if (!selector.startsWith("#") && !selector.startsWith(".") && !selector.startsWith("[") && !selector.includes(">") && !selector.includes(" ")) {
|
|
4322
|
+
attempts.push(`text: "${selector}"`);
|
|
4323
|
+
try {
|
|
4324
|
+
const loc = page.getByText(selector, { exact: false }).first();
|
|
4325
|
+
if (await loc.count() > 0) {
|
|
4326
|
+
return { found: true, locator: loc, method: "text", healed: true, attempts };
|
|
4327
|
+
}
|
|
4328
|
+
} catch {}
|
|
4329
|
+
}
|
|
4330
|
+
const roleMap = {
|
|
4331
|
+
button: ["button", "submit", "reset"],
|
|
4332
|
+
link: ["a"],
|
|
4333
|
+
input: ["input", "textarea"],
|
|
4334
|
+
heading: ["h1", "h2", "h3", "h4", "h5", "h6"]
|
|
4335
|
+
};
|
|
4336
|
+
const nameHint = selector.replace(/^[#.]/, "").replace(/[-_]/g, " ").toLowerCase();
|
|
4337
|
+
for (const [role, tags] of Object.entries(roleMap)) {
|
|
4338
|
+
attempts.push(`role: ${role} name~="${nameHint}"`);
|
|
4339
|
+
try {
|
|
4340
|
+
const loc = page.getByRole(role, { name: new RegExp(nameHint.split(" ")[0], "i") }).first();
|
|
4341
|
+
if (await loc.count() > 0) {
|
|
4342
|
+
return { found: true, locator: loc, method: "role", healed: true, attempts };
|
|
4343
|
+
}
|
|
4344
|
+
} catch {}
|
|
4345
|
+
}
|
|
4346
|
+
if (selector.startsWith("#")) {
|
|
4347
|
+
const idPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
|
|
4348
|
+
const partialSel = `[id*="${idPart}"]`;
|
|
4349
|
+
attempts.push(`partial_id: ${partialSel}`);
|
|
4350
|
+
try {
|
|
4351
|
+
const loc = page.locator(partialSel).first();
|
|
4352
|
+
if (await loc.count() > 0) {
|
|
4353
|
+
return { found: true, locator: loc, method: "partial_id", healed: true, attempts };
|
|
4354
|
+
}
|
|
4355
|
+
} catch {}
|
|
4356
|
+
}
|
|
4357
|
+
if (selector.startsWith(".")) {
|
|
4358
|
+
const classPart = selector.slice(1).split("-").pop() ?? selector.slice(1);
|
|
4359
|
+
const partialSel = `[class*="${classPart}"]`;
|
|
4360
|
+
attempts.push(`partial_class: ${partialSel}`);
|
|
4361
|
+
try {
|
|
4362
|
+
const loc = page.locator(partialSel).first();
|
|
4363
|
+
if (await loc.count() > 0) {
|
|
4364
|
+
return { found: true, locator: loc, method: "partial_class", healed: true, attempts };
|
|
4365
|
+
}
|
|
4366
|
+
} catch {}
|
|
4367
|
+
}
|
|
4368
|
+
return { found: false, locator: null, method: "none", healed: false, attempts };
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4018
4371
|
// src/lib/actions.ts
|
|
4019
4372
|
var exports_actions = {};
|
|
4020
4373
|
__export(exports_actions, {
|
|
@@ -4056,11 +4409,22 @@ async function click(page, selector, opts) {
|
|
|
4056
4409
|
delay: opts?.delay,
|
|
4057
4410
|
timeout: opts?.timeout ?? 1e4
|
|
4058
4411
|
});
|
|
4059
|
-
|
|
4060
|
-
|
|
4412
|
+
return {};
|
|
4413
|
+
} catch (originalError) {
|
|
4414
|
+
if (opts?.selfHeal !== false) {
|
|
4415
|
+
const result = await healSelector(page, selector);
|
|
4416
|
+
if (result.found && result.locator) {
|
|
4417
|
+
await result.locator.click({
|
|
4418
|
+
button: opts?.button ?? "left",
|
|
4419
|
+
timeout: opts?.timeout ?? 1e4
|
|
4420
|
+
});
|
|
4421
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4422
|
+
}
|
|
4423
|
+
}
|
|
4424
|
+
if (originalError instanceof Error && originalError.message.includes("not found")) {
|
|
4061
4425
|
throw new ElementNotFoundError(selector);
|
|
4062
4426
|
}
|
|
4063
|
-
throw new BrowserError(`Click failed on '${selector}': ${
|
|
4427
|
+
throw new BrowserError(`Click failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "CLICK_FAILED");
|
|
4064
4428
|
}
|
|
4065
4429
|
}
|
|
4066
4430
|
async function type(page, selector, text, opts) {
|
|
@@ -4069,17 +4433,35 @@ async function type(page, selector, text, opts) {
|
|
|
4069
4433
|
await page.fill(selector, "", { timeout: opts?.timeout ?? 1e4 });
|
|
4070
4434
|
}
|
|
4071
4435
|
await page.type(selector, text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
4072
|
-
|
|
4073
|
-
|
|
4436
|
+
return {};
|
|
4437
|
+
} catch (originalError) {
|
|
4438
|
+
if (opts?.selfHeal !== false) {
|
|
4439
|
+
const result = await healSelector(page, selector);
|
|
4440
|
+
if (result.found && result.locator) {
|
|
4441
|
+
if (opts?.clear)
|
|
4442
|
+
await result.locator.fill("", { timeout: opts?.timeout ?? 1e4 });
|
|
4443
|
+
await result.locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
4444
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
if (originalError instanceof Error && originalError.message.includes("not found")) {
|
|
4074
4448
|
throw new ElementNotFoundError(selector);
|
|
4075
4449
|
}
|
|
4076
|
-
throw new BrowserError(`Type failed on '${selector}': ${
|
|
4450
|
+
throw new BrowserError(`Type failed on '${selector}': ${originalError instanceof Error ? originalError.message : String(originalError)}`, "TYPE_FAILED");
|
|
4077
4451
|
}
|
|
4078
4452
|
}
|
|
4079
|
-
async function fill(page, selector, value, timeout = 1e4) {
|
|
4453
|
+
async function fill(page, selector, value, timeout = 1e4, selfHeal = true) {
|
|
4080
4454
|
try {
|
|
4081
4455
|
await page.fill(selector, value, { timeout });
|
|
4082
|
-
|
|
4456
|
+
return {};
|
|
4457
|
+
} catch (originalError) {
|
|
4458
|
+
if (selfHeal) {
|
|
4459
|
+
const result = await healSelector(page, selector);
|
|
4460
|
+
if (result.found && result.locator) {
|
|
4461
|
+
await result.locator.fill(value, { timeout });
|
|
4462
|
+
return { healed: true, method: result.method, attempts: result.attempts };
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4083
4465
|
throw new ElementNotFoundError(selector);
|
|
4084
4466
|
}
|
|
4085
4467
|
}
|
|
@@ -4204,12 +4586,39 @@ async function clickText(page, text, opts) {
|
|
|
4204
4586
|
}
|
|
4205
4587
|
}, { retries: opts?.retries ?? 1 });
|
|
4206
4588
|
}
|
|
4207
|
-
async function fillForm(page, fields, submitSelector) {
|
|
4589
|
+
async function fillForm(page, fields, submitSelector, selfHeal = true) {
|
|
4208
4590
|
let filled = 0;
|
|
4209
4591
|
const errors = [];
|
|
4592
|
+
const healedFields = [];
|
|
4210
4593
|
for (const [selector, value] of Object.entries(fields)) {
|
|
4211
4594
|
try {
|
|
4212
|
-
|
|
4595
|
+
let el = await page.$(selector);
|
|
4596
|
+
if (!el && selfHeal) {
|
|
4597
|
+
const result = await healSelector(page, selector);
|
|
4598
|
+
if (result.found && result.locator) {
|
|
4599
|
+
const handle = await result.locator.elementHandle();
|
|
4600
|
+
if (handle) {
|
|
4601
|
+
el = handle;
|
|
4602
|
+
healedFields.push(selector);
|
|
4603
|
+
const tagName2 = await result.locator.evaluate((e) => e.tagName.toLowerCase());
|
|
4604
|
+
const inputType2 = await result.locator.evaluate((e) => e.type?.toLowerCase() ?? "text");
|
|
4605
|
+
if (tagName2 === "select") {
|
|
4606
|
+
await result.locator.selectOption(String(value));
|
|
4607
|
+
} else if (tagName2 === "input" && (inputType2 === "checkbox" || inputType2 === "radio")) {
|
|
4608
|
+
if (Boolean(value))
|
|
4609
|
+
await result.locator.check();
|
|
4610
|
+
else
|
|
4611
|
+
await result.locator.uncheck();
|
|
4612
|
+
} else {
|
|
4613
|
+
await result.locator.fill(String(value));
|
|
4614
|
+
}
|
|
4615
|
+
filled++;
|
|
4616
|
+
continue;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
errors.push(`${selector}: element not found`);
|
|
4620
|
+
continue;
|
|
4621
|
+
}
|
|
4213
4622
|
if (!el) {
|
|
4214
4623
|
errors.push(`${selector}: element not found`);
|
|
4215
4624
|
continue;
|
|
@@ -4236,11 +4645,21 @@ async function fillForm(page, fields, submitSelector) {
|
|
|
4236
4645
|
if (submitSelector) {
|
|
4237
4646
|
try {
|
|
4238
4647
|
await page.click(submitSelector);
|
|
4239
|
-
} catch (
|
|
4240
|
-
|
|
4648
|
+
} catch (submitErr) {
|
|
4649
|
+
if (selfHeal) {
|
|
4650
|
+
const result = await healSelector(page, submitSelector);
|
|
4651
|
+
if (result.found && result.locator) {
|
|
4652
|
+
await result.locator.click();
|
|
4653
|
+
healedFields.push(submitSelector);
|
|
4654
|
+
} else {
|
|
4655
|
+
errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
4656
|
+
}
|
|
4657
|
+
} else {
|
|
4658
|
+
errors.push(`submit(${submitSelector}): ${submitErr instanceof Error ? submitErr.message : String(submitErr)}`);
|
|
4659
|
+
}
|
|
4241
4660
|
}
|
|
4242
4661
|
}
|
|
4243
|
-
return { filled, errors, fields_attempted: Object.keys(fields).length };
|
|
4662
|
+
return { filled, errors, fields_attempted: Object.keys(fields).length, ...healedFields.length > 0 ? { healed_fields: healedFields } : {} };
|
|
4244
4663
|
}
|
|
4245
4664
|
async function waitForText(page, text, opts) {
|
|
4246
4665
|
const timeout = opts?.timeout ?? 1e4;
|
|
@@ -11096,17 +11515,17 @@ var init_gallery = __esm(() => {
|
|
|
11096
11515
|
});
|
|
11097
11516
|
|
|
11098
11517
|
// src/lib/screenshot.ts
|
|
11099
|
-
import { join as
|
|
11100
|
-
import { mkdirSync as
|
|
11101
|
-
import { homedir as
|
|
11518
|
+
import { join as join4 } from "path";
|
|
11519
|
+
import { mkdirSync as mkdirSync4 } from "fs";
|
|
11520
|
+
import { homedir as homedir4 } from "os";
|
|
11102
11521
|
function getDataDir2() {
|
|
11103
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
11522
|
+
return process.env["BROWSER_DATA_DIR"] ?? join4(homedir4(), ".browser");
|
|
11104
11523
|
}
|
|
11105
11524
|
function getScreenshotDir(projectId) {
|
|
11106
|
-
const base =
|
|
11525
|
+
const base = join4(getDataDir2(), "screenshots");
|
|
11107
11526
|
const date = new Date().toISOString().split("T")[0];
|
|
11108
|
-
const dir = projectId ?
|
|
11109
|
-
|
|
11527
|
+
const dir = projectId ? join4(base, projectId, date) : join4(base, date);
|
|
11528
|
+
mkdirSync4(dir, { recursive: true });
|
|
11110
11529
|
return dir;
|
|
11111
11530
|
}
|
|
11112
11531
|
async function compressBuffer(raw, format, quality, maxWidth) {
|
|
@@ -11121,7 +11540,7 @@ async function compressBuffer(raw, format, quality, maxWidth) {
|
|
|
11121
11540
|
}
|
|
11122
11541
|
}
|
|
11123
11542
|
async function generateThumbnail(raw, dir, stem) {
|
|
11124
|
-
const thumbPath =
|
|
11543
|
+
const thumbPath = join4(dir, `${stem}.thumb.webp`);
|
|
11125
11544
|
const thumbBuffer = await import_sharp.default(raw).resize({ width: 200, withoutEnlargement: true }).webp({ quality: 70, effort: 3 }).toBuffer();
|
|
11126
11545
|
await Bun.write(thumbPath, thumbBuffer);
|
|
11127
11546
|
return { path: thumbPath, base64: thumbBuffer.toString("base64") };
|
|
@@ -11178,7 +11597,7 @@ async function takeScreenshot(page, opts) {
|
|
|
11178
11597
|
const compressedSizeBytes = finalBuffer.length;
|
|
11179
11598
|
const compressionRatio = originalSizeBytes > 0 ? compressedSizeBytes / originalSizeBytes : 1;
|
|
11180
11599
|
const ext = format;
|
|
11181
|
-
const screenshotPath = opts?.path ??
|
|
11600
|
+
const screenshotPath = opts?.path ?? join4(dir, `${stem}.${ext}`);
|
|
11182
11601
|
await Bun.write(screenshotPath, finalBuffer);
|
|
11183
11602
|
let thumbnailPath;
|
|
11184
11603
|
let thumbnailBase64;
|
|
@@ -11238,12 +11657,12 @@ async function takeScreenshot(page, opts) {
|
|
|
11238
11657
|
}
|
|
11239
11658
|
async function generatePDF(page, opts) {
|
|
11240
11659
|
try {
|
|
11241
|
-
const base =
|
|
11660
|
+
const base = join4(getDataDir2(), "pdfs");
|
|
11242
11661
|
const date = new Date().toISOString().split("T")[0];
|
|
11243
|
-
const dir = opts?.projectId ?
|
|
11244
|
-
|
|
11662
|
+
const dir = opts?.projectId ? join4(base, opts.projectId, date) : join4(base, date);
|
|
11663
|
+
mkdirSync4(dir, { recursive: true });
|
|
11245
11664
|
const timestamp = Date.now();
|
|
11246
|
-
const pdfPath = opts?.path ??
|
|
11665
|
+
const pdfPath = opts?.path ?? join4(dir, `${timestamp}.pdf`);
|
|
11247
11666
|
const buffer = await page.pdf({
|
|
11248
11667
|
path: pdfPath,
|
|
11249
11668
|
format: opts?.format ?? "A4",
|
|
@@ -11570,6 +11989,17 @@ var init_recordings = __esm(() => {
|
|
|
11570
11989
|
});
|
|
11571
11990
|
|
|
11572
11991
|
// src/lib/recorder.ts
|
|
11992
|
+
var exports_recorder = {};
|
|
11993
|
+
__export(exports_recorder, {
|
|
11994
|
+
stopRecording: () => stopRecording,
|
|
11995
|
+
startRecording: () => startRecording,
|
|
11996
|
+
replayRecording: () => replayRecording,
|
|
11997
|
+
recordStep: () => recordStep,
|
|
11998
|
+
listRecordings: () => listRecordings,
|
|
11999
|
+
getRecording: () => getRecording,
|
|
12000
|
+
exportRecording: () => exportRecording,
|
|
12001
|
+
attachPageListeners: () => attachPageListeners
|
|
12002
|
+
});
|
|
11573
12003
|
function startRecording(sessionId, name, startUrl) {
|
|
11574
12004
|
const steps = [];
|
|
11575
12005
|
const recording = createRecording({ name, start_url: startUrl, steps });
|
|
@@ -11580,6 +12010,23 @@ function startRecording(sessionId, name, startUrl) {
|
|
|
11580
12010
|
});
|
|
11581
12011
|
return recording;
|
|
11582
12012
|
}
|
|
12013
|
+
function attachPageListeners(page, recordingId) {
|
|
12014
|
+
const active = activeRecordings.get(recordingId);
|
|
12015
|
+
if (!active)
|
|
12016
|
+
throw new BrowserError(`No active recording: ${recordingId}`, "RECORDING_NOT_ACTIVE");
|
|
12017
|
+
const onFrameNav = () => {
|
|
12018
|
+
active.steps.push({
|
|
12019
|
+
type: "navigate",
|
|
12020
|
+
url: page.url(),
|
|
12021
|
+
timestamp: Date.now()
|
|
12022
|
+
});
|
|
12023
|
+
};
|
|
12024
|
+
page.on("framenavigated", onFrameNav);
|
|
12025
|
+
const cleanup = () => {
|
|
12026
|
+
page.off("framenavigated", onFrameNav);
|
|
12027
|
+
};
|
|
12028
|
+
active.cleanup = cleanup;
|
|
12029
|
+
}
|
|
11583
12030
|
function recordStep(recordingId, step) {
|
|
11584
12031
|
const active = activeRecordings.get(recordingId);
|
|
11585
12032
|
if (!active)
|
|
@@ -11651,6 +12098,64 @@ async function replayRecording(recordingId, page) {
|
|
|
11651
12098
|
duration_ms: Date.now() - startTime
|
|
11652
12099
|
};
|
|
11653
12100
|
}
|
|
12101
|
+
function exportRecording(recordingId, format = "json") {
|
|
12102
|
+
const recording = getRecording(recordingId);
|
|
12103
|
+
if (format === "json") {
|
|
12104
|
+
return JSON.stringify(recording, null, 2);
|
|
12105
|
+
}
|
|
12106
|
+
if (format === "playwright") {
|
|
12107
|
+
const lines2 = [
|
|
12108
|
+
`import { test, expect } from '@playwright/test';`,
|
|
12109
|
+
``,
|
|
12110
|
+
`test('${recording.name}', async ({ page }) => {`
|
|
12111
|
+
];
|
|
12112
|
+
for (const step of recording.steps) {
|
|
12113
|
+
switch (step.type) {
|
|
12114
|
+
case "navigate":
|
|
12115
|
+
lines2.push(` await page.goto('${step.url}');`);
|
|
12116
|
+
break;
|
|
12117
|
+
case "click":
|
|
12118
|
+
lines2.push(` await page.click('${step.selector}');`);
|
|
12119
|
+
break;
|
|
12120
|
+
case "type":
|
|
12121
|
+
lines2.push(` await page.type('${step.selector}', '${step.value}');`);
|
|
12122
|
+
break;
|
|
12123
|
+
case "scroll":
|
|
12124
|
+
lines2.push(` await page.evaluate(() => window.scrollBy(0, 300));`);
|
|
12125
|
+
break;
|
|
12126
|
+
case "evaluate":
|
|
12127
|
+
lines2.push(` await page.evaluate(${step.value});`);
|
|
12128
|
+
break;
|
|
12129
|
+
}
|
|
12130
|
+
}
|
|
12131
|
+
lines2.push(`});`);
|
|
12132
|
+
return lines2.join(`
|
|
12133
|
+
`);
|
|
12134
|
+
}
|
|
12135
|
+
const lines = [
|
|
12136
|
+
`const puppeteer = require('puppeteer');`,
|
|
12137
|
+
``,
|
|
12138
|
+
`(async () => {`,
|
|
12139
|
+
` const browser = await puppeteer.launch();`,
|
|
12140
|
+
` const page = await browser.newPage();`
|
|
12141
|
+
];
|
|
12142
|
+
for (const step of recording.steps) {
|
|
12143
|
+
switch (step.type) {
|
|
12144
|
+
case "navigate":
|
|
12145
|
+
lines.push(` await page.goto('${step.url}');`);
|
|
12146
|
+
break;
|
|
12147
|
+
case "click":
|
|
12148
|
+
lines.push(` await page.click('${step.selector}');`);
|
|
12149
|
+
break;
|
|
12150
|
+
case "type":
|
|
12151
|
+
lines.push(` await page.type('${step.selector}', '${step.value}');`);
|
|
12152
|
+
break;
|
|
12153
|
+
}
|
|
12154
|
+
}
|
|
12155
|
+
lines.push(` await browser.close();`, `})();`);
|
|
12156
|
+
return lines.join(`
|
|
12157
|
+
`);
|
|
12158
|
+
}
|
|
11654
12159
|
var activeRecordings;
|
|
11655
12160
|
var init_recorder = __esm(() => {
|
|
11656
12161
|
init_recordings();
|
|
@@ -15625,118 +16130,6 @@ var init_zod = __esm(() => {
|
|
|
15625
16130
|
init_external();
|
|
15626
16131
|
});
|
|
15627
16132
|
|
|
15628
|
-
// src/engines/cdp.ts
|
|
15629
|
-
class CDPClient {
|
|
15630
|
-
session;
|
|
15631
|
-
networkEnabled = false;
|
|
15632
|
-
performanceEnabled = false;
|
|
15633
|
-
constructor(session) {
|
|
15634
|
-
this.session = session;
|
|
15635
|
-
}
|
|
15636
|
-
static async fromPage(page) {
|
|
15637
|
-
try {
|
|
15638
|
-
const session = await page.context().newCDPSession(page);
|
|
15639
|
-
return new CDPClient(session);
|
|
15640
|
-
} catch (err) {
|
|
15641
|
-
throw new BrowserError(`Failed to create CDP session: ${err instanceof Error ? err.message : String(err)}`, "CDP_SESSION_FAILED");
|
|
15642
|
-
}
|
|
15643
|
-
}
|
|
15644
|
-
async send(method, params) {
|
|
15645
|
-
try {
|
|
15646
|
-
return await this.session.send(method, params);
|
|
15647
|
-
} catch (err) {
|
|
15648
|
-
throw new BrowserError(`CDP command '${method}' failed: ${err instanceof Error ? err.message : String(err)}`, "CDP_COMMAND_FAILED");
|
|
15649
|
-
}
|
|
15650
|
-
}
|
|
15651
|
-
on(event, handler) {
|
|
15652
|
-
this.session.on(event, handler);
|
|
15653
|
-
}
|
|
15654
|
-
off(event, handler) {
|
|
15655
|
-
this.session.off(event, handler);
|
|
15656
|
-
}
|
|
15657
|
-
async enableNetwork() {
|
|
15658
|
-
if (!this.networkEnabled) {
|
|
15659
|
-
await this.send("Network.enable");
|
|
15660
|
-
this.networkEnabled = true;
|
|
15661
|
-
}
|
|
15662
|
-
}
|
|
15663
|
-
async enablePerformance() {
|
|
15664
|
-
if (!this.performanceEnabled) {
|
|
15665
|
-
await this.send("Performance.enable");
|
|
15666
|
-
this.performanceEnabled = true;
|
|
15667
|
-
}
|
|
15668
|
-
}
|
|
15669
|
-
async getPerformanceMetrics() {
|
|
15670
|
-
await this.enablePerformance();
|
|
15671
|
-
const result = await this.send("Performance.getMetrics");
|
|
15672
|
-
const m = {};
|
|
15673
|
-
for (const metric of result.metrics) {
|
|
15674
|
-
m[metric.name] = metric.value;
|
|
15675
|
-
}
|
|
15676
|
-
return {
|
|
15677
|
-
js_heap_size_used: m["JSHeapUsedSize"],
|
|
15678
|
-
js_heap_size_total: m["JSHeapTotalSize"],
|
|
15679
|
-
dom_interactive: m["DOMInteractive"],
|
|
15680
|
-
dom_complete: m["DOMComplete"],
|
|
15681
|
-
load_event: m["LoadEventEnd"]
|
|
15682
|
-
};
|
|
15683
|
-
}
|
|
15684
|
-
async startJSCoverage() {
|
|
15685
|
-
await this.send("Profiler.enable");
|
|
15686
|
-
await this.send("Debugger.enable");
|
|
15687
|
-
await this.send("Profiler.startPreciseCoverage", {
|
|
15688
|
-
callCount: false,
|
|
15689
|
-
detailed: true
|
|
15690
|
-
});
|
|
15691
|
-
}
|
|
15692
|
-
async stopJSCoverage() {
|
|
15693
|
-
const result = await this.send("Profiler.takePreciseCoverage");
|
|
15694
|
-
await this.send("Profiler.stopPreciseCoverage");
|
|
15695
|
-
return result.result.filter((r) => r.url && !r.url.startsWith("v8-snapshot://")).map((r) => ({
|
|
15696
|
-
url: r.url,
|
|
15697
|
-
text: "",
|
|
15698
|
-
ranges: r.functions.flatMap((f) => f.ranges.filter((rng) => rng.count > 0).map((rng) => ({ start: rng.startOffset, end: rng.endOffset })))
|
|
15699
|
-
}));
|
|
15700
|
-
}
|
|
15701
|
-
async getCoverage() {
|
|
15702
|
-
await this.startJSCoverage();
|
|
15703
|
-
const js = await this.stopJSCoverage();
|
|
15704
|
-
const totalBytes = js.reduce((acc, e) => acc + e.ranges.reduce((sum, r) => sum + (r.end - r.start), 0), 0);
|
|
15705
|
-
return { js, css: [], totalBytes, usedBytes: totalBytes, unusedPercent: 0 };
|
|
15706
|
-
}
|
|
15707
|
-
async captureHAREntries(page, handler) {
|
|
15708
|
-
await this.enableNetwork();
|
|
15709
|
-
const requestTimings = new Map;
|
|
15710
|
-
const onRequest = (params) => {
|
|
15711
|
-
requestTimings.set(params.requestId, params.timestamp);
|
|
15712
|
-
};
|
|
15713
|
-
const onResponse = (params) => {
|
|
15714
|
-
const start = requestTimings.get(params.requestId);
|
|
15715
|
-
const duration = start != null ? (params.timestamp - start) * 1000 : 0;
|
|
15716
|
-
handler({
|
|
15717
|
-
method: "GET",
|
|
15718
|
-
url: params.response.url,
|
|
15719
|
-
status: params.response.status,
|
|
15720
|
-
duration
|
|
15721
|
-
});
|
|
15722
|
-
};
|
|
15723
|
-
this.on("Network.requestWillBeSent", onRequest);
|
|
15724
|
-
this.on("Network.responseReceived", onResponse);
|
|
15725
|
-
return () => {
|
|
15726
|
-
this.off("Network.requestWillBeSent", onRequest);
|
|
15727
|
-
this.off("Network.responseReceived", onResponse);
|
|
15728
|
-
};
|
|
15729
|
-
}
|
|
15730
|
-
async detach() {
|
|
15731
|
-
try {
|
|
15732
|
-
await this.session.detach();
|
|
15733
|
-
} catch {}
|
|
15734
|
-
}
|
|
15735
|
-
}
|
|
15736
|
-
var init_cdp = __esm(() => {
|
|
15737
|
-
init_types();
|
|
15738
|
-
});
|
|
15739
|
-
|
|
15740
16133
|
// src/lib/performance.ts
|
|
15741
16134
|
async function getPerformanceMetrics(page) {
|
|
15742
16135
|
const navTiming = await page.evaluate(() => {
|
|
@@ -15842,16 +16235,16 @@ __export(exports_downloads, {
|
|
|
15842
16235
|
cleanStaleDownloads: () => cleanStaleDownloads
|
|
15843
16236
|
});
|
|
15844
16237
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
15845
|
-
import { join as
|
|
15846
|
-
import { mkdirSync as
|
|
15847
|
-
import { homedir as
|
|
16238
|
+
import { join as join5, basename, extname } from "path";
|
|
16239
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync, readFileSync } from "fs";
|
|
16240
|
+
import { homedir as homedir5 } from "os";
|
|
15848
16241
|
function getDataDir3() {
|
|
15849
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
16242
|
+
return process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
|
|
15850
16243
|
}
|
|
15851
16244
|
function getDownloadsDir(sessionId) {
|
|
15852
|
-
const base =
|
|
15853
|
-
const dir = sessionId ?
|
|
15854
|
-
|
|
16245
|
+
const base = join5(getDataDir3(), "downloads");
|
|
16246
|
+
const dir = sessionId ? join5(base, sessionId) : base;
|
|
16247
|
+
mkdirSync5(dir, { recursive: true });
|
|
15855
16248
|
return dir;
|
|
15856
16249
|
}
|
|
15857
16250
|
function ensureDownloadsDir() {
|
|
@@ -15866,7 +16259,7 @@ function saveToDownloads(buffer, filename, opts) {
|
|
|
15866
16259
|
const ext = extname(filename) || "";
|
|
15867
16260
|
const stem = basename(filename, ext);
|
|
15868
16261
|
const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
|
|
15869
|
-
const filePath =
|
|
16262
|
+
const filePath = join5(dir, uniqueName);
|
|
15870
16263
|
writeFileSync(filePath, buffer);
|
|
15871
16264
|
const meta = {
|
|
15872
16265
|
id,
|
|
@@ -15895,20 +16288,20 @@ function listDownloads(sessionId) {
|
|
|
15895
16288
|
const dir = getDownloadsDir(sessionId);
|
|
15896
16289
|
const results = [];
|
|
15897
16290
|
function scanDir(d) {
|
|
15898
|
-
if (!
|
|
16291
|
+
if (!existsSync2(d))
|
|
15899
16292
|
return;
|
|
15900
|
-
const entries =
|
|
16293
|
+
const entries = readdirSync2(d);
|
|
15901
16294
|
for (const entry of entries) {
|
|
15902
16295
|
if (entry.endsWith(".meta.json"))
|
|
15903
16296
|
continue;
|
|
15904
|
-
const full =
|
|
16297
|
+
const full = join5(d, entry);
|
|
15905
16298
|
const stat = statSync(full);
|
|
15906
16299
|
if (stat.isDirectory()) {
|
|
15907
16300
|
scanDir(full);
|
|
15908
16301
|
continue;
|
|
15909
16302
|
}
|
|
15910
16303
|
const mpath = metaPath(full);
|
|
15911
|
-
if (!
|
|
16304
|
+
if (!existsSync2(mpath))
|
|
15912
16305
|
continue;
|
|
15913
16306
|
try {
|
|
15914
16307
|
const meta = JSON.parse(readFileSync(mpath, "utf8"));
|
|
@@ -15938,9 +16331,9 @@ function deleteDownload(id, sessionId) {
|
|
|
15938
16331
|
if (!file)
|
|
15939
16332
|
return false;
|
|
15940
16333
|
try {
|
|
15941
|
-
|
|
15942
|
-
if (
|
|
15943
|
-
|
|
16334
|
+
unlinkSync2(file.path);
|
|
16335
|
+
if (existsSync2(file.meta_path))
|
|
16336
|
+
unlinkSync2(file.meta_path);
|
|
15944
16337
|
return true;
|
|
15945
16338
|
} catch {
|
|
15946
16339
|
return false;
|
|
@@ -15990,9 +16383,9 @@ var exports_gallery_diff = {};
|
|
|
15990
16383
|
__export(exports_gallery_diff, {
|
|
15991
16384
|
diffImages: () => diffImages
|
|
15992
16385
|
});
|
|
15993
|
-
import { join as
|
|
15994
|
-
import { mkdirSync as
|
|
15995
|
-
import { homedir as
|
|
16386
|
+
import { join as join6 } from "path";
|
|
16387
|
+
import { mkdirSync as mkdirSync6 } from "fs";
|
|
16388
|
+
import { homedir as homedir6 } from "os";
|
|
15996
16389
|
async function diffImages(path1, path2) {
|
|
15997
16390
|
const img1 = import_sharp2.default(path1);
|
|
15998
16391
|
const img2 = import_sharp2.default(path2);
|
|
@@ -16023,10 +16416,10 @@ async function diffImages(path1, path2) {
|
|
|
16023
16416
|
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
16024
16417
|
}
|
|
16025
16418
|
}
|
|
16026
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
16027
|
-
const diffDir =
|
|
16028
|
-
|
|
16029
|
-
const diffPath =
|
|
16419
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
|
|
16420
|
+
const diffDir = join6(dataDir, "diffs");
|
|
16421
|
+
mkdirSync6(diffDir, { recursive: true });
|
|
16422
|
+
const diffPath = join6(diffDir, `diff-${Date.now()}.webp`);
|
|
16030
16423
|
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
16031
16424
|
await Bun.write(diffPath, diffImageBuffer);
|
|
16032
16425
|
return {
|
|
@@ -16043,9 +16436,9 @@ var init_gallery_diff = __esm(() => {
|
|
|
16043
16436
|
});
|
|
16044
16437
|
|
|
16045
16438
|
// src/lib/files-integration.ts
|
|
16046
|
-
import { join as
|
|
16047
|
-
import { mkdirSync as
|
|
16048
|
-
import { homedir as
|
|
16439
|
+
import { join as join7 } from "path";
|
|
16440
|
+
import { mkdirSync as mkdirSync7, copyFileSync as copyFileSync2 } from "fs";
|
|
16441
|
+
import { homedir as homedir7 } from "os";
|
|
16049
16442
|
async function persistFile(localPath, opts) {
|
|
16050
16443
|
try {
|
|
16051
16444
|
const mod = await import("@hasna/files");
|
|
@@ -16054,12 +16447,12 @@ async function persistFile(localPath, opts) {
|
|
|
16054
16447
|
return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
|
|
16055
16448
|
}
|
|
16056
16449
|
} catch {}
|
|
16057
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
16450
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join7(homedir7(), ".browser");
|
|
16058
16451
|
const date = new Date().toISOString().split("T")[0];
|
|
16059
|
-
const dir =
|
|
16060
|
-
|
|
16452
|
+
const dir = join7(dataDir, "persistent", date);
|
|
16453
|
+
mkdirSync7(dir, { recursive: true });
|
|
16061
16454
|
const filename = localPath.split("/").pop() ?? "file";
|
|
16062
|
-
const targetPath =
|
|
16455
|
+
const targetPath = join7(dir, filename);
|
|
16063
16456
|
copyFileSync2(localPath, targetPath);
|
|
16064
16457
|
return {
|
|
16065
16458
|
id: `local-${Date.now()}`,
|
|
@@ -16174,23 +16567,23 @@ __export(exports_profiles, {
|
|
|
16174
16567
|
deleteProfile: () => deleteProfile,
|
|
16175
16568
|
applyProfile: () => applyProfile
|
|
16176
16569
|
});
|
|
16177
|
-
import { mkdirSync as
|
|
16178
|
-
import { join as
|
|
16179
|
-
import { homedir as
|
|
16570
|
+
import { mkdirSync as mkdirSync8, existsSync as existsSync4, readdirSync as readdirSync3, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16571
|
+
import { join as join8 } from "path";
|
|
16572
|
+
import { homedir as homedir8 } from "os";
|
|
16180
16573
|
function getProfilesDir() {
|
|
16181
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
16182
|
-
const dir =
|
|
16183
|
-
|
|
16574
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser");
|
|
16575
|
+
const dir = join8(dataDir, "profiles");
|
|
16576
|
+
mkdirSync8(dir, { recursive: true });
|
|
16184
16577
|
return dir;
|
|
16185
16578
|
}
|
|
16186
16579
|
function getProfileDir2(name) {
|
|
16187
|
-
return
|
|
16580
|
+
return join8(getProfilesDir(), name);
|
|
16188
16581
|
}
|
|
16189
16582
|
async function saveProfile(page, name) {
|
|
16190
16583
|
const dir = getProfileDir2(name);
|
|
16191
|
-
|
|
16584
|
+
mkdirSync8(dir, { recursive: true });
|
|
16192
16585
|
const cookies = await page.context().cookies();
|
|
16193
|
-
writeFileSync2(
|
|
16586
|
+
writeFileSync2(join8(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
|
|
16194
16587
|
let localStorage2 = {};
|
|
16195
16588
|
try {
|
|
16196
16589
|
localStorage2 = await page.evaluate(() => {
|
|
@@ -16202,11 +16595,11 @@ async function saveProfile(page, name) {
|
|
|
16202
16595
|
return result;
|
|
16203
16596
|
});
|
|
16204
16597
|
} catch {}
|
|
16205
|
-
writeFileSync2(
|
|
16598
|
+
writeFileSync2(join8(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
|
|
16206
16599
|
const savedAt = new Date().toISOString();
|
|
16207
16600
|
const url = page.url();
|
|
16208
16601
|
const meta = { saved_at: savedAt, url };
|
|
16209
|
-
writeFileSync2(
|
|
16602
|
+
writeFileSync2(join8(dir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
16210
16603
|
return {
|
|
16211
16604
|
name,
|
|
16212
16605
|
saved_at: savedAt,
|
|
@@ -16217,17 +16610,17 @@ async function saveProfile(page, name) {
|
|
|
16217
16610
|
}
|
|
16218
16611
|
function loadProfile(name) {
|
|
16219
16612
|
const dir = getProfileDir2(name);
|
|
16220
|
-
if (!
|
|
16613
|
+
if (!existsSync4(dir)) {
|
|
16221
16614
|
throw new Error(`Profile not found: ${name}`);
|
|
16222
16615
|
}
|
|
16223
|
-
const cookiesPath =
|
|
16224
|
-
const storagePath =
|
|
16225
|
-
const metaPath2 =
|
|
16226
|
-
const cookies =
|
|
16227
|
-
const localStorage2 =
|
|
16616
|
+
const cookiesPath = join8(dir, "cookies.json");
|
|
16617
|
+
const storagePath = join8(dir, "storage.json");
|
|
16618
|
+
const metaPath2 = join8(dir, "meta.json");
|
|
16619
|
+
const cookies = existsSync4(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
|
|
16620
|
+
const localStorage2 = existsSync4(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
|
|
16228
16621
|
let savedAt = new Date().toISOString();
|
|
16229
16622
|
let url;
|
|
16230
|
-
if (
|
|
16623
|
+
if (existsSync4(metaPath2)) {
|
|
16231
16624
|
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
16232
16625
|
savedAt = meta.saved_at ?? savedAt;
|
|
16233
16626
|
url = meta.url;
|
|
@@ -16255,33 +16648,33 @@ async function applyProfile(page, profileData) {
|
|
|
16255
16648
|
}
|
|
16256
16649
|
function listProfiles() {
|
|
16257
16650
|
const dir = getProfilesDir();
|
|
16258
|
-
if (!
|
|
16651
|
+
if (!existsSync4(dir))
|
|
16259
16652
|
return [];
|
|
16260
|
-
const entries =
|
|
16653
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
16261
16654
|
const profiles = [];
|
|
16262
16655
|
for (const entry of entries) {
|
|
16263
16656
|
if (!entry.isDirectory())
|
|
16264
16657
|
continue;
|
|
16265
16658
|
const name = entry.name;
|
|
16266
|
-
const profileDir =
|
|
16659
|
+
const profileDir = join8(dir, name);
|
|
16267
16660
|
let savedAt = "";
|
|
16268
16661
|
let url;
|
|
16269
16662
|
let cookieCount = 0;
|
|
16270
16663
|
let storageKeyCount = 0;
|
|
16271
16664
|
try {
|
|
16272
|
-
const metaPath2 =
|
|
16273
|
-
if (
|
|
16665
|
+
const metaPath2 = join8(profileDir, "meta.json");
|
|
16666
|
+
if (existsSync4(metaPath2)) {
|
|
16274
16667
|
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
16275
16668
|
savedAt = meta.saved_at ?? "";
|
|
16276
16669
|
url = meta.url;
|
|
16277
16670
|
}
|
|
16278
|
-
const cookiesPath =
|
|
16279
|
-
if (
|
|
16671
|
+
const cookiesPath = join8(profileDir, "cookies.json");
|
|
16672
|
+
if (existsSync4(cookiesPath)) {
|
|
16280
16673
|
const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
|
|
16281
16674
|
cookieCount = Array.isArray(cookies) ? cookies.length : 0;
|
|
16282
16675
|
}
|
|
16283
|
-
const storagePath =
|
|
16284
|
-
if (
|
|
16676
|
+
const storagePath = join8(profileDir, "storage.json");
|
|
16677
|
+
if (existsSync4(storagePath)) {
|
|
16285
16678
|
const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
|
|
16286
16679
|
storageKeyCount = Object.keys(storage).length;
|
|
16287
16680
|
}
|
|
@@ -16298,7 +16691,7 @@ function listProfiles() {
|
|
|
16298
16691
|
}
|
|
16299
16692
|
function deleteProfile(name) {
|
|
16300
16693
|
const dir = getProfileDir2(name);
|
|
16301
|
-
if (!
|
|
16694
|
+
if (!existsSync4(dir))
|
|
16302
16695
|
return false;
|
|
16303
16696
|
try {
|
|
16304
16697
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -16309,6 +16702,97 @@ function deleteProfile(name) {
|
|
|
16309
16702
|
}
|
|
16310
16703
|
var init_profiles = () => {};
|
|
16311
16704
|
|
|
16705
|
+
// src/lib/sanitize.ts
|
|
16706
|
+
var exports_sanitize = {};
|
|
16707
|
+
__export(exports_sanitize, {
|
|
16708
|
+
sanitizeText: () => sanitizeText,
|
|
16709
|
+
sanitizeHTML: () => sanitizeHTML
|
|
16710
|
+
});
|
|
16711
|
+
function sanitizeText(text) {
|
|
16712
|
+
let stripped = 0;
|
|
16713
|
+
const warnings = [];
|
|
16714
|
+
let clean = text;
|
|
16715
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
16716
|
+
pattern.lastIndex = 0;
|
|
16717
|
+
const matches = clean.match(pattern);
|
|
16718
|
+
if (matches) {
|
|
16719
|
+
stripped += matches.length;
|
|
16720
|
+
warnings.push(`Stripped ${matches.length}x: ${pattern.source}`);
|
|
16721
|
+
pattern.lastIndex = 0;
|
|
16722
|
+
clean = clean.replace(pattern, "[STRIPPED]");
|
|
16723
|
+
}
|
|
16724
|
+
}
|
|
16725
|
+
return { text: clean, stripped, warnings };
|
|
16726
|
+
}
|
|
16727
|
+
function sanitizeHTML(html) {
|
|
16728
|
+
let stripped = 0;
|
|
16729
|
+
const warnings = [];
|
|
16730
|
+
let clean = html;
|
|
16731
|
+
const commentMatches = clean.match(/<!--[\s\S]*?-->/g);
|
|
16732
|
+
if (commentMatches) {
|
|
16733
|
+
for (const comment of commentMatches) {
|
|
16734
|
+
if (comment.replace(/<!--\s*-->/g, "").trim().length > 20) {
|
|
16735
|
+
stripped++;
|
|
16736
|
+
warnings.push(`Stripped HTML comment (${comment.length} chars)`);
|
|
16737
|
+
}
|
|
16738
|
+
}
|
|
16739
|
+
clean = clean.replace(/<!--[\s\S]*?-->/g, "");
|
|
16740
|
+
}
|
|
16741
|
+
const hiddenPatterns = [
|
|
16742
|
+
/style\s*=\s*"[^"]*display\s*:\s*none[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16743
|
+
/style\s*=\s*"[^"]*visibility\s*:\s*hidden[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16744
|
+
/style\s*=\s*"[^"]*opacity\s*:\s*0[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16745
|
+
/style\s*=\s*"[^"]*font-size\s*:\s*0[^"]*"[^>]*>[\s\S]*?<\//gi,
|
|
16746
|
+
/style\s*=\s*"[^"]*position\s*:\s*absolute[^"]*left\s*:\s*-\d{4,}[^"]*"[^>]*>[\s\S]*?<\//gi
|
|
16747
|
+
];
|
|
16748
|
+
for (const pattern of hiddenPatterns) {
|
|
16749
|
+
pattern.lastIndex = 0;
|
|
16750
|
+
const matches = clean.match(pattern);
|
|
16751
|
+
if (matches) {
|
|
16752
|
+
stripped += matches.length;
|
|
16753
|
+
warnings.push(`Stripped ${matches.length} hidden elements`);
|
|
16754
|
+
pattern.lastIndex = 0;
|
|
16755
|
+
clean = clean.replace(pattern, "");
|
|
16756
|
+
}
|
|
16757
|
+
}
|
|
16758
|
+
const ariaHiddenPattern = /aria-hidden\s*=\s*"true"[^>]*>[\s\S]*?<\//gi;
|
|
16759
|
+
const ariaHidden = clean.match(ariaHiddenPattern);
|
|
16760
|
+
if (ariaHidden) {
|
|
16761
|
+
stripped += ariaHidden.length;
|
|
16762
|
+
warnings.push(`Stripped ${ariaHidden.length} aria-hidden elements`);
|
|
16763
|
+
ariaHiddenPattern.lastIndex = 0;
|
|
16764
|
+
clean = clean.replace(ariaHiddenPattern, "");
|
|
16765
|
+
}
|
|
16766
|
+
const textResult = sanitizeText(clean);
|
|
16767
|
+
return {
|
|
16768
|
+
text: textResult.text,
|
|
16769
|
+
stripped: stripped + textResult.stripped,
|
|
16770
|
+
warnings: [...warnings, ...textResult.warnings]
|
|
16771
|
+
};
|
|
16772
|
+
}
|
|
16773
|
+
var INJECTION_PATTERNS;
|
|
16774
|
+
var init_sanitize = __esm(() => {
|
|
16775
|
+
INJECTION_PATTERNS = [
|
|
16776
|
+
/ignore\s+(all\s+)?previous\s+instructions/gi,
|
|
16777
|
+
/ignore\s+(all\s+)?prior\s+instructions/gi,
|
|
16778
|
+
/disregard\s+(all\s+)?previous/gi,
|
|
16779
|
+
/forget\s+(all\s+)?previous/gi,
|
|
16780
|
+
/you\s+are\s+now\s+/gi,
|
|
16781
|
+
/new\s+instructions?\s*:/gi,
|
|
16782
|
+
/system\s+prompt\s*:/gi,
|
|
16783
|
+
/\[INST\]/gi,
|
|
16784
|
+
/\[\/INST\]/gi,
|
|
16785
|
+
/<\|im_start\|>/gi,
|
|
16786
|
+
/<\|im_end\|>/gi,
|
|
16787
|
+
/<<SYS>>/gi,
|
|
16788
|
+
/<<\/SYS>>/gi,
|
|
16789
|
+
/IMPORTANT:\s*ignore/gi,
|
|
16790
|
+
/CRITICAL:\s*override/gi,
|
|
16791
|
+
/assistant:\s/gi,
|
|
16792
|
+
/human:\s/gi
|
|
16793
|
+
];
|
|
16794
|
+
});
|
|
16795
|
+
|
|
16312
16796
|
// src/lib/annotate.ts
|
|
16313
16797
|
var exports_annotate = {};
|
|
16314
16798
|
__export(exports_annotate, {
|
|
@@ -16369,26 +16853,846 @@ var init_annotate = __esm(() => {
|
|
|
16369
16853
|
import_sharp3 = __toESM(require_lib(), 1);
|
|
16370
16854
|
});
|
|
16371
16855
|
|
|
16856
|
+
// src/lib/env-detector.ts
|
|
16857
|
+
var exports_env_detector = {};
|
|
16858
|
+
__export(exports_env_detector, {
|
|
16859
|
+
detectEnvironment: () => detectEnvironment
|
|
16860
|
+
});
|
|
16861
|
+
async function detectEnvironment(page) {
|
|
16862
|
+
const url = page.url();
|
|
16863
|
+
const signals = [];
|
|
16864
|
+
let score = { local: 0, dev: 0, staging: 0, prod: 0 };
|
|
16865
|
+
try {
|
|
16866
|
+
const u = new URL(url);
|
|
16867
|
+
if (u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "0.0.0.0" || u.hostname.endsWith(".local")) {
|
|
16868
|
+
score.local += 5;
|
|
16869
|
+
signals.push(`URL hostname: ${u.hostname} \u2192 local`);
|
|
16870
|
+
} else if (u.hostname.match(/^(dev|development)\./i) || u.port !== "") {
|
|
16871
|
+
score.dev += 4;
|
|
16872
|
+
signals.push(`URL pattern: ${u.hostname}:${u.port} \u2192 dev`);
|
|
16873
|
+
} else if (u.hostname.match(/^(staging|stg|stage|preprod|uat)\./i)) {
|
|
16874
|
+
score.staging += 4;
|
|
16875
|
+
signals.push(`URL pattern: ${u.hostname} \u2192 staging`);
|
|
16876
|
+
} else {
|
|
16877
|
+
score.prod += 2;
|
|
16878
|
+
signals.push(`URL looks production: ${u.hostname}`);
|
|
16879
|
+
}
|
|
16880
|
+
if (u.port && !["80", "443", ""].includes(u.port)) {
|
|
16881
|
+
score.dev += 2;
|
|
16882
|
+
signals.push(`Non-standard port: ${u.port}`);
|
|
16883
|
+
}
|
|
16884
|
+
if (u.protocol === "https:") {
|
|
16885
|
+
score.prod += 1;
|
|
16886
|
+
signals.push("HTTPS \u2192 likely prod");
|
|
16887
|
+
} else {
|
|
16888
|
+
score.dev += 2;
|
|
16889
|
+
signals.push("HTTP \u2192 likely dev/local");
|
|
16890
|
+
}
|
|
16891
|
+
} catch {}
|
|
16892
|
+
try {
|
|
16893
|
+
const pageSignals = await page.evaluate(() => {
|
|
16894
|
+
const s = [];
|
|
16895
|
+
const w = window;
|
|
16896
|
+
const envVars = ["__ENV__", "__NEXT_DATA__", "__NUXT__", "process"];
|
|
16897
|
+
for (const v of envVars) {
|
|
16898
|
+
if (w[v]) {
|
|
16899
|
+
const env2 = w[v]?.env?.NODE_ENV ?? w[v]?.runtimeConfig?.public?.env ?? w[v]?.props?.pageProps?.env;
|
|
16900
|
+
if (env2)
|
|
16901
|
+
s.push(`window.${v}: ${env2}`);
|
|
16902
|
+
}
|
|
16903
|
+
}
|
|
16904
|
+
if (w.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.size > 0) {
|
|
16905
|
+
const fiber = document.querySelector("[data-reactroot]") || document.getElementById("__next") || document.getElementById("root");
|
|
16906
|
+
if (fiber)
|
|
16907
|
+
s.push("React app detected");
|
|
16908
|
+
}
|
|
16909
|
+
const envMeta = document.querySelector('meta[name="environment"], meta[name="env"], meta[name="deploy-env"]');
|
|
16910
|
+
if (envMeta)
|
|
16911
|
+
s.push(`meta[environment]: ${envMeta.getAttribute("content")}`);
|
|
16912
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
16913
|
+
let minified = 0, unminified = 0;
|
|
16914
|
+
scripts.forEach((s2) => {
|
|
16915
|
+
const src = s2.getAttribute("src") ?? "";
|
|
16916
|
+
if (src.includes(".min.") || src.match(/\.[a-f0-9]{8,}\./))
|
|
16917
|
+
minified++;
|
|
16918
|
+
else if (src.endsWith(".js") && !src.includes("chunk"))
|
|
16919
|
+
unminified++;
|
|
16920
|
+
});
|
|
16921
|
+
if (unminified > minified && unminified > 2)
|
|
16922
|
+
s.push(`Unminified scripts (${unminified}/${minified + unminified}) \u2192 likely dev`);
|
|
16923
|
+
else if (minified > 0)
|
|
16924
|
+
s.push(`Minified/hashed scripts (${minified}/${minified + unminified}) \u2192 likely prod`);
|
|
16925
|
+
if (document.querySelector("[data-testid]"))
|
|
16926
|
+
s.push("data-testid attributes present \u2192 dev/staging");
|
|
16927
|
+
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
16928
|
+
s.push("Service worker active \u2192 likely prod");
|
|
16929
|
+
}
|
|
16930
|
+
if (w.Sentry)
|
|
16931
|
+
s.push("Sentry SDK loaded \u2192 prod monitoring");
|
|
16932
|
+
if (w.__DATADOG_SYNTHETICS_INLINED_SCRIPT)
|
|
16933
|
+
s.push("Datadog loaded \u2192 prod monitoring");
|
|
16934
|
+
if (w.LogRocket)
|
|
16935
|
+
s.push("LogRocket loaded \u2192 prod monitoring");
|
|
16936
|
+
if (w._lr_loaded)
|
|
16937
|
+
s.push("LogRocket loaded \u2192 prod monitoring");
|
|
16938
|
+
if (w.gtag || w.ga)
|
|
16939
|
+
s.push("Google Analytics loaded \u2192 likely prod");
|
|
16940
|
+
if (w.posthog || w._ph)
|
|
16941
|
+
s.push("PostHog loaded \u2192 prod analytics");
|
|
16942
|
+
if (w.mixpanel)
|
|
16943
|
+
s.push("Mixpanel loaded \u2192 prod analytics");
|
|
16944
|
+
const robots = document.querySelector('meta[name="robots"]');
|
|
16945
|
+
if (robots) {
|
|
16946
|
+
const content = robots.getAttribute("content") ?? "";
|
|
16947
|
+
if (content.includes("noindex"))
|
|
16948
|
+
s.push(`robots: noindex \u2192 staging/dev`);
|
|
16949
|
+
}
|
|
16950
|
+
return s;
|
|
16951
|
+
});
|
|
16952
|
+
for (const signal of pageSignals) {
|
|
16953
|
+
signals.push(signal);
|
|
16954
|
+
if (signal.includes("development") || signal.includes("\u2192 dev") || signal.includes("\u2192 likely dev"))
|
|
16955
|
+
score.dev += 2;
|
|
16956
|
+
if (signal.includes("production") || signal.includes("\u2192 prod") || signal.includes("\u2192 likely prod"))
|
|
16957
|
+
score.prod += 2;
|
|
16958
|
+
if (signal.includes("staging") || signal.includes("\u2192 staging"))
|
|
16959
|
+
score.staging += 2;
|
|
16960
|
+
if (signal.includes("monitoring") || signal.includes("analytics"))
|
|
16961
|
+
score.prod += 1;
|
|
16962
|
+
if (signal.includes("noindex")) {
|
|
16963
|
+
score.staging += 2;
|
|
16964
|
+
score.dev += 1;
|
|
16965
|
+
}
|
|
16966
|
+
}
|
|
16967
|
+
} catch {}
|
|
16968
|
+
const entries = Object.entries(score);
|
|
16969
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
16970
|
+
const [env, topScore] = entries[0];
|
|
16971
|
+
const [, secondScore] = entries[1];
|
|
16972
|
+
const confidence = topScore >= 5 ? "high" : topScore > secondScore + 1 ? "medium" : "low";
|
|
16973
|
+
return { env, confidence, signals };
|
|
16974
|
+
}
|
|
16975
|
+
|
|
16976
|
+
// src/lib/deep-performance.ts
|
|
16977
|
+
var exports_deep_performance = {};
|
|
16978
|
+
__export(exports_deep_performance, {
|
|
16979
|
+
getDeepPerformance: () => getDeepPerformance
|
|
16980
|
+
});
|
|
16981
|
+
function categorizeThirdParty(domain) {
|
|
16982
|
+
for (const [pattern, category] of Object.entries(THIRD_PARTY_CATEGORIES)) {
|
|
16983
|
+
if (domain.includes(pattern))
|
|
16984
|
+
return category;
|
|
16985
|
+
}
|
|
16986
|
+
return "other";
|
|
16987
|
+
}
|
|
16988
|
+
async function getDeepPerformance(page) {
|
|
16989
|
+
return page.evaluate(() => {
|
|
16990
|
+
const perf = performance;
|
|
16991
|
+
const entries = perf.getEntriesByType("resource");
|
|
16992
|
+
const navEntry = perf.getEntriesByType("navigation")[0];
|
|
16993
|
+
const paintEntries = perf.getEntriesByType("paint");
|
|
16994
|
+
const fcp = paintEntries.find((e) => e.name === "first-contentful-paint")?.startTime;
|
|
16995
|
+
const ttfb = navEntry?.responseStart;
|
|
16996
|
+
const web_vitals = { fcp, ttfb };
|
|
16997
|
+
try {
|
|
16998
|
+
const lcpEntries = perf.getEntriesByType("largest-contentful-paint");
|
|
16999
|
+
if (lcpEntries.length > 0)
|
|
17000
|
+
web_vitals.lcp = lcpEntries[lcpEntries.length - 1].startTime;
|
|
17001
|
+
} catch {}
|
|
17002
|
+
const byType = {};
|
|
17003
|
+
let totalBytes = 0;
|
|
17004
|
+
const resourceList = [];
|
|
17005
|
+
const pageDomain = location.hostname;
|
|
17006
|
+
const thirdPartyMap = new Map;
|
|
17007
|
+
for (const entry of entries) {
|
|
17008
|
+
const size = entry.transferSize || entry.encodedBodySize || 0;
|
|
17009
|
+
totalBytes += size;
|
|
17010
|
+
let type2 = entry.initiatorType || "other";
|
|
17011
|
+
if (type2 === "xmlhttprequest" || type2 === "fetch")
|
|
17012
|
+
type2 = "xhr";
|
|
17013
|
+
if (type2 === "link" && entry.name.match(/\.css/))
|
|
17014
|
+
type2 = "css";
|
|
17015
|
+
if (type2 === "img" || entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp|avif|ico)/i))
|
|
17016
|
+
type2 = "image";
|
|
17017
|
+
if (type2 === "script" || entry.name.match(/\.js/))
|
|
17018
|
+
type2 = "script";
|
|
17019
|
+
if (entry.name.match(/\.(woff2?|ttf|otf|eot)/i))
|
|
17020
|
+
type2 = "font";
|
|
17021
|
+
if (!byType[type2])
|
|
17022
|
+
byType[type2] = { count: 0, size_bytes: 0 };
|
|
17023
|
+
byType[type2].count++;
|
|
17024
|
+
byType[type2].size_bytes += size;
|
|
17025
|
+
resourceList.push({ url: entry.name, size_bytes: size, type: type2 });
|
|
17026
|
+
try {
|
|
17027
|
+
const domain = new URL(entry.name).hostname;
|
|
17028
|
+
if (domain !== pageDomain && !domain.endsWith(`.${pageDomain}`)) {
|
|
17029
|
+
if (!thirdPartyMap.has(domain))
|
|
17030
|
+
thirdPartyMap.set(domain, { scripts: 0, total_bytes: 0 });
|
|
17031
|
+
const tp = thirdPartyMap.get(domain);
|
|
17032
|
+
tp.scripts++;
|
|
17033
|
+
tp.total_bytes += size;
|
|
17034
|
+
}
|
|
17035
|
+
} catch {}
|
|
17036
|
+
}
|
|
17037
|
+
resourceList.sort((a, b) => b.size_bytes - a.size_bytes);
|
|
17038
|
+
const largest = resourceList.slice(0, 10).map((r) => ({
|
|
17039
|
+
url: r.url.length > 120 ? r.url.slice(0, 117) + "..." : r.url,
|
|
17040
|
+
size_bytes: r.size_bytes,
|
|
17041
|
+
type: r.type
|
|
17042
|
+
}));
|
|
17043
|
+
const third_party = Array.from(thirdPartyMap.entries()).map(([domain, data]) => ({ domain, ...data, category: "" })).sort((a, b) => b.total_bytes - a.total_bytes).slice(0, 15);
|
|
17044
|
+
const allNodes = document.querySelectorAll("*");
|
|
17045
|
+
let maxDepth = 0;
|
|
17046
|
+
let textNodes = 0;
|
|
17047
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL);
|
|
17048
|
+
let node = walker.currentNode;
|
|
17049
|
+
while (node) {
|
|
17050
|
+
if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim())
|
|
17051
|
+
textNodes++;
|
|
17052
|
+
let depth = 0;
|
|
17053
|
+
let parent = node.parentNode;
|
|
17054
|
+
while (parent) {
|
|
17055
|
+
depth++;
|
|
17056
|
+
parent = parent.parentNode;
|
|
17057
|
+
}
|
|
17058
|
+
if (depth > maxDepth)
|
|
17059
|
+
maxDepth = depth;
|
|
17060
|
+
node = walker.nextNode();
|
|
17061
|
+
}
|
|
17062
|
+
const mem = performance.memory;
|
|
17063
|
+
const memory = {
|
|
17064
|
+
js_heap_used_mb: mem ? Math.round(mem.usedJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
|
|
17065
|
+
js_heap_total_mb: mem ? Math.round(mem.totalJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
|
|
17066
|
+
js_heap_limit_mb: mem ? Math.round(mem.jsHeapSizeLimit / 1024 / 1024 * 100) / 100 : 0
|
|
17067
|
+
};
|
|
17068
|
+
return {
|
|
17069
|
+
web_vitals,
|
|
17070
|
+
resources: { total_transfer_bytes: totalBytes, total_resources: entries.length, by_type: byType, largest },
|
|
17071
|
+
third_party,
|
|
17072
|
+
dom: { node_count: document.all.length, max_depth: maxDepth, element_count: allNodes.length, text_node_count: textNodes },
|
|
17073
|
+
main_thread: { long_tasks: 0, total_blocking_ms: 0 },
|
|
17074
|
+
memory
|
|
17075
|
+
};
|
|
17076
|
+
}).then((result) => {
|
|
17077
|
+
for (const tp of result.third_party) {
|
|
17078
|
+
tp.category = categorizeThirdParty(tp.domain);
|
|
17079
|
+
}
|
|
17080
|
+
return result;
|
|
17081
|
+
});
|
|
17082
|
+
}
|
|
17083
|
+
var THIRD_PARTY_CATEGORIES;
|
|
17084
|
+
var init_deep_performance = __esm(() => {
|
|
17085
|
+
THIRD_PARTY_CATEGORIES = {
|
|
17086
|
+
"google-analytics.com": "analytics",
|
|
17087
|
+
"googletagmanager.com": "analytics",
|
|
17088
|
+
gtag: "analytics",
|
|
17089
|
+
"facebook.net": "social",
|
|
17090
|
+
"connect.facebook": "social",
|
|
17091
|
+
"stripe.com": "payment",
|
|
17092
|
+
"js.stripe.com": "payment",
|
|
17093
|
+
"sentry.io": "monitoring",
|
|
17094
|
+
"sentry-cdn": "monitoring",
|
|
17095
|
+
"posthog.com": "analytics",
|
|
17096
|
+
"ph.": "analytics",
|
|
17097
|
+
"intercom.io": "chat",
|
|
17098
|
+
"crisp.chat": "chat",
|
|
17099
|
+
"hotjar.com": "analytics",
|
|
17100
|
+
"clarity.ms": "analytics",
|
|
17101
|
+
"cdn.jsdelivr.net": "cdn",
|
|
17102
|
+
"cdnjs.cloudflare.com": "cdn",
|
|
17103
|
+
"unpkg.com": "cdn",
|
|
17104
|
+
"fonts.googleapis.com": "fonts",
|
|
17105
|
+
"fonts.gstatic.com": "fonts"
|
|
17106
|
+
};
|
|
17107
|
+
});
|
|
17108
|
+
|
|
17109
|
+
// src/lib/workflows.ts
|
|
17110
|
+
var exports_workflows = {};
|
|
17111
|
+
__export(exports_workflows, {
|
|
17112
|
+
saveWorkflowFromRecording: () => saveWorkflowFromRecording,
|
|
17113
|
+
saveWorkflow: () => saveWorkflow,
|
|
17114
|
+
runWorkflow: () => runWorkflow,
|
|
17115
|
+
listWorkflows: () => listWorkflows,
|
|
17116
|
+
getWorkflowByName: () => getWorkflowByName,
|
|
17117
|
+
getWorkflow: () => getWorkflow,
|
|
17118
|
+
deleteWorkflow: () => deleteWorkflow
|
|
17119
|
+
});
|
|
17120
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
17121
|
+
function saveWorkflow(data) {
|
|
17122
|
+
const db = getDatabase();
|
|
17123
|
+
const id = randomUUID10();
|
|
17124
|
+
db.prepare("INSERT OR REPLACE INTO workflows (id, name, description, steps, start_url) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.description ?? null, JSON.stringify(data.steps), data.startUrl ?? null);
|
|
17125
|
+
return getWorkflow(id);
|
|
17126
|
+
}
|
|
17127
|
+
function saveWorkflowFromRecording(recordingId, name, description) {
|
|
17128
|
+
const db = getDatabase();
|
|
17129
|
+
const rec = db.query("SELECT steps, start_url FROM recordings WHERE id = ?").get(recordingId);
|
|
17130
|
+
if (!rec)
|
|
17131
|
+
throw new Error(`Recording not found: ${recordingId}`);
|
|
17132
|
+
const steps = JSON.parse(rec.steps);
|
|
17133
|
+
return saveWorkflow({ name, description, steps, startUrl: rec.start_url ?? undefined });
|
|
17134
|
+
}
|
|
17135
|
+
function getWorkflow(id) {
|
|
17136
|
+
const db = getDatabase();
|
|
17137
|
+
const row = db.query("SELECT * FROM workflows WHERE id = ?").get(id);
|
|
17138
|
+
if (!row)
|
|
17139
|
+
return null;
|
|
17140
|
+
return { ...row, steps: JSON.parse(row.steps) };
|
|
17141
|
+
}
|
|
17142
|
+
function getWorkflowByName(name) {
|
|
17143
|
+
const db = getDatabase();
|
|
17144
|
+
const row = db.query("SELECT * FROM workflows WHERE name = ?").get(name);
|
|
17145
|
+
if (!row)
|
|
17146
|
+
return null;
|
|
17147
|
+
return { ...row, steps: JSON.parse(row.steps) };
|
|
17148
|
+
}
|
|
17149
|
+
function listWorkflows() {
|
|
17150
|
+
const db = getDatabase();
|
|
17151
|
+
return db.query("SELECT * FROM workflows ORDER BY updated_at DESC").all().map((row) => ({ ...row, steps: JSON.parse(row.steps) }));
|
|
17152
|
+
}
|
|
17153
|
+
function deleteWorkflow(name) {
|
|
17154
|
+
const db = getDatabase();
|
|
17155
|
+
return db.prepare("DELETE FROM workflows WHERE name = ?").run(name).changes > 0;
|
|
17156
|
+
}
|
|
17157
|
+
function recordRun(id, healed) {
|
|
17158
|
+
const db = getDatabase();
|
|
17159
|
+
if (healed) {
|
|
17160
|
+
db.prepare("UPDATE workflows SET last_run = datetime('now'), last_heal = datetime('now'), heal_count = heal_count + 1, run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
17161
|
+
} else {
|
|
17162
|
+
db.prepare("UPDATE workflows SET last_run = datetime('now'), run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
17163
|
+
}
|
|
17164
|
+
}
|
|
17165
|
+
async function runWorkflow(workflow, page) {
|
|
17166
|
+
const t0 = Date.now();
|
|
17167
|
+
let executed = 0;
|
|
17168
|
+
let failed = 0;
|
|
17169
|
+
let healed = 0;
|
|
17170
|
+
const healedDetails = [];
|
|
17171
|
+
const errors2 = [];
|
|
17172
|
+
const updatedSteps = [...workflow.steps];
|
|
17173
|
+
for (let i = 0;i < workflow.steps.length; i++) {
|
|
17174
|
+
const step = workflow.steps[i];
|
|
17175
|
+
try {
|
|
17176
|
+
switch (step.type) {
|
|
17177
|
+
case "navigate":
|
|
17178
|
+
if (step.url)
|
|
17179
|
+
await page.goto(step.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
17180
|
+
break;
|
|
17181
|
+
case "click":
|
|
17182
|
+
if (step.selector) {
|
|
17183
|
+
try {
|
|
17184
|
+
await page.click(step.selector, { timeout: 5000 });
|
|
17185
|
+
} catch {
|
|
17186
|
+
const result = await healSelector(page, step.selector);
|
|
17187
|
+
if (result.found && result.locator) {
|
|
17188
|
+
await result.locator.click();
|
|
17189
|
+
healed++;
|
|
17190
|
+
const healedSelector = `[healed:${result.method}]${step.selector}`;
|
|
17191
|
+
healedDetails.push({ step: i, original: step.selector, healed_to: healedSelector, method: result.method });
|
|
17192
|
+
} else {
|
|
17193
|
+
throw new Error(`Click failed: ${step.selector} (self-healing exhausted)`);
|
|
17194
|
+
}
|
|
17195
|
+
}
|
|
17196
|
+
}
|
|
17197
|
+
break;
|
|
17198
|
+
case "type":
|
|
17199
|
+
if (step.selector && step.value) {
|
|
17200
|
+
try {
|
|
17201
|
+
await page.fill(step.selector, step.value);
|
|
17202
|
+
} catch {
|
|
17203
|
+
const result = await healSelector(page, step.selector);
|
|
17204
|
+
if (result.found && result.locator) {
|
|
17205
|
+
await result.locator.fill(step.value);
|
|
17206
|
+
healed++;
|
|
17207
|
+
healedDetails.push({ step: i, original: step.selector, healed_to: `[healed:${result.method}]`, method: result.method });
|
|
17208
|
+
} else {
|
|
17209
|
+
throw new Error(`Type failed: ${step.selector} (self-healing exhausted)`);
|
|
17210
|
+
}
|
|
17211
|
+
}
|
|
17212
|
+
}
|
|
17213
|
+
break;
|
|
17214
|
+
case "scroll":
|
|
17215
|
+
if (step.y)
|
|
17216
|
+
await page.mouse.wheel(0, step.y);
|
|
17217
|
+
break;
|
|
17218
|
+
case "hover":
|
|
17219
|
+
if (step.selector) {
|
|
17220
|
+
try {
|
|
17221
|
+
await page.hover(step.selector);
|
|
17222
|
+
} catch {}
|
|
17223
|
+
}
|
|
17224
|
+
break;
|
|
17225
|
+
case "select":
|
|
17226
|
+
if (step.selector && step.value) {
|
|
17227
|
+
try {
|
|
17228
|
+
await page.selectOption(step.selector, step.value);
|
|
17229
|
+
} catch {}
|
|
17230
|
+
}
|
|
17231
|
+
break;
|
|
17232
|
+
case "wait":
|
|
17233
|
+
if (step.selector) {
|
|
17234
|
+
try {
|
|
17235
|
+
await page.waitForSelector(step.selector, { timeout: 1e4 });
|
|
17236
|
+
} catch {}
|
|
17237
|
+
} else {
|
|
17238
|
+
await new Promise((r) => setTimeout(r, step.timestamp || 1000));
|
|
17239
|
+
}
|
|
17240
|
+
break;
|
|
17241
|
+
case "evaluate":
|
|
17242
|
+
if (step.value)
|
|
17243
|
+
await page.evaluate(step.value);
|
|
17244
|
+
break;
|
|
17245
|
+
default:
|
|
17246
|
+
break;
|
|
17247
|
+
}
|
|
17248
|
+
executed++;
|
|
17249
|
+
} catch (err) {
|
|
17250
|
+
failed++;
|
|
17251
|
+
errors2.push(`Step ${i} (${step.type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
17252
|
+
}
|
|
17253
|
+
}
|
|
17254
|
+
recordRun(workflow.id, healed > 0);
|
|
17255
|
+
return {
|
|
17256
|
+
success: failed === 0,
|
|
17257
|
+
steps_executed: executed,
|
|
17258
|
+
steps_failed: failed,
|
|
17259
|
+
steps_healed: healed,
|
|
17260
|
+
healed_details: healedDetails,
|
|
17261
|
+
errors: errors2,
|
|
17262
|
+
duration_ms: Date.now() - t0
|
|
17263
|
+
};
|
|
17264
|
+
}
|
|
17265
|
+
var init_workflows = __esm(() => {
|
|
17266
|
+
init_schema();
|
|
17267
|
+
});
|
|
17268
|
+
|
|
17269
|
+
// src/lib/auth-flow.ts
|
|
17270
|
+
var exports_auth_flow = {};
|
|
17271
|
+
__export(exports_auth_flow, {
|
|
17272
|
+
tryReplayAuth: () => tryReplayAuth,
|
|
17273
|
+
touchAuthFlow: () => touchAuthFlow,
|
|
17274
|
+
saveAuthFlow: () => saveAuthFlow,
|
|
17275
|
+
listAuthFlows: () => listAuthFlows,
|
|
17276
|
+
isAuthRedirect: () => isAuthRedirect,
|
|
17277
|
+
isAuthPage: () => isAuthPage,
|
|
17278
|
+
getAuthFlowByName: () => getAuthFlowByName,
|
|
17279
|
+
getAuthFlowByDomain: () => getAuthFlowByDomain,
|
|
17280
|
+
getAuthFlow: () => getAuthFlow,
|
|
17281
|
+
deleteAuthFlow: () => deleteAuthFlow
|
|
17282
|
+
});
|
|
17283
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
17284
|
+
function saveAuthFlow(data) {
|
|
17285
|
+
const db = getDatabase();
|
|
17286
|
+
const id = randomUUID11();
|
|
17287
|
+
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);
|
|
17288
|
+
return getAuthFlow(id);
|
|
17289
|
+
}
|
|
17290
|
+
function getAuthFlow(id) {
|
|
17291
|
+
const db = getDatabase();
|
|
17292
|
+
return db.query("SELECT * FROM auth_flows WHERE id = ?").get(id) ?? null;
|
|
17293
|
+
}
|
|
17294
|
+
function getAuthFlowByName(name) {
|
|
17295
|
+
const db = getDatabase();
|
|
17296
|
+
return db.query("SELECT * FROM auth_flows WHERE name = ?").get(name) ?? null;
|
|
17297
|
+
}
|
|
17298
|
+
function getAuthFlowByDomain(domain) {
|
|
17299
|
+
const db = getDatabase();
|
|
17300
|
+
return db.query("SELECT * FROM auth_flows WHERE domain = ? ORDER BY last_used DESC LIMIT 1").get(domain) ?? null;
|
|
17301
|
+
}
|
|
17302
|
+
function listAuthFlows() {
|
|
17303
|
+
const db = getDatabase();
|
|
17304
|
+
return db.query("SELECT * FROM auth_flows ORDER BY last_used DESC, created_at DESC").all();
|
|
17305
|
+
}
|
|
17306
|
+
function deleteAuthFlow(name) {
|
|
17307
|
+
const db = getDatabase();
|
|
17308
|
+
const result = db.prepare("DELETE FROM auth_flows WHERE name = ?").run(name);
|
|
17309
|
+
return result.changes > 0;
|
|
17310
|
+
}
|
|
17311
|
+
function touchAuthFlow(id) {
|
|
17312
|
+
const db = getDatabase();
|
|
17313
|
+
db.prepare("UPDATE auth_flows SET last_used = datetime('now') WHERE id = ?").run(id);
|
|
17314
|
+
}
|
|
17315
|
+
function isAuthPage(url) {
|
|
17316
|
+
return AUTH_URL_PATTERNS.some((pattern) => pattern.test(url));
|
|
17317
|
+
}
|
|
17318
|
+
function isAuthRedirect(fromUrl, toUrl) {
|
|
17319
|
+
if (fromUrl === toUrl)
|
|
17320
|
+
return false;
|
|
17321
|
+
return isAuthPage(toUrl) && !isAuthPage(fromUrl);
|
|
17322
|
+
}
|
|
17323
|
+
async function tryReplayAuth(page, domain) {
|
|
17324
|
+
const flow = getAuthFlowByDomain(domain);
|
|
17325
|
+
if (!flow)
|
|
17326
|
+
return { replayed: false };
|
|
17327
|
+
if (flow.storage_state_path) {
|
|
17328
|
+
try {
|
|
17329
|
+
const { existsSync: existsSync5, readFileSync: readFileSync3 } = await import("fs");
|
|
17330
|
+
if (existsSync5(flow.storage_state_path)) {
|
|
17331
|
+
const state = JSON.parse(readFileSync3(flow.storage_state_path, "utf8"));
|
|
17332
|
+
if (state.cookies?.length) {
|
|
17333
|
+
await page.context().addCookies(state.cookies);
|
|
17334
|
+
await page.reload();
|
|
17335
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
17336
|
+
if (!isAuthPage(page.url())) {
|
|
17337
|
+
touchAuthFlow(flow.id);
|
|
17338
|
+
return { replayed: true, flow, method: "storage_state" };
|
|
17339
|
+
}
|
|
17340
|
+
}
|
|
17341
|
+
}
|
|
17342
|
+
} catch {}
|
|
17343
|
+
}
|
|
17344
|
+
if (flow.recording_id) {
|
|
17345
|
+
try {
|
|
17346
|
+
const { replayRecording: replayRecording2 } = await Promise.resolve().then(() => (init_recorder(), exports_recorder));
|
|
17347
|
+
const result = await replayRecording2(flow.recording_id, page);
|
|
17348
|
+
if (result.success) {
|
|
17349
|
+
try {
|
|
17350
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
17351
|
+
const path = await saveStateFromPage2(page, flow.name);
|
|
17352
|
+
const db = getDatabase();
|
|
17353
|
+
db.prepare("UPDATE auth_flows SET storage_state_path = ?, last_used = datetime('now') WHERE id = ?").run(path, flow.id);
|
|
17354
|
+
} catch {}
|
|
17355
|
+
touchAuthFlow(flow.id);
|
|
17356
|
+
return { replayed: true, flow, method: "recording_replay" };
|
|
17357
|
+
}
|
|
17358
|
+
} catch {}
|
|
17359
|
+
}
|
|
17360
|
+
return { replayed: false, flow };
|
|
17361
|
+
}
|
|
17362
|
+
var AUTH_URL_PATTERNS;
|
|
17363
|
+
var init_auth_flow = __esm(() => {
|
|
17364
|
+
init_schema();
|
|
17365
|
+
AUTH_URL_PATTERNS = [
|
|
17366
|
+
/\/login/i,
|
|
17367
|
+
/\/signin/i,
|
|
17368
|
+
/\/sign-in/i,
|
|
17369
|
+
/\/auth/i,
|
|
17370
|
+
/\/sso/i,
|
|
17371
|
+
/\/oauth/i,
|
|
17372
|
+
/\/cas\/login/i,
|
|
17373
|
+
/accounts\.google\.com/i,
|
|
17374
|
+
/login\.microsoftonline\.com/i,
|
|
17375
|
+
/github\.com\/login/i,
|
|
17376
|
+
/auth0\.com/i
|
|
17377
|
+
];
|
|
17378
|
+
});
|
|
17379
|
+
|
|
17380
|
+
// src/lib/vision-fallback.ts
|
|
17381
|
+
var exports_vision_fallback = {};
|
|
17382
|
+
__export(exports_vision_fallback, {
|
|
17383
|
+
findElementByVision: () => findElementByVision,
|
|
17384
|
+
clickByVision: () => clickByVision
|
|
17385
|
+
});
|
|
17386
|
+
async function findElementByVision(page, description, opts) {
|
|
17387
|
+
const model = opts?.model ?? process.env["BROWSER_VISION_MODEL"] ?? DEFAULT_MODEL;
|
|
17388
|
+
const screenshot = await page.screenshot({ type: "jpeg", quality: 80 });
|
|
17389
|
+
const base64 = screenshot.toString("base64");
|
|
17390
|
+
const viewport = page.viewportSize() ?? { width: 1280, height: 720 };
|
|
17391
|
+
const apiKey = process.env["ANTHROPIC_API_KEY"];
|
|
17392
|
+
if (!apiKey) {
|
|
17393
|
+
return { found: false, x: 0, y: 0, confidence: "none", description, model, error: "ANTHROPIC_API_KEY not set" };
|
|
17394
|
+
}
|
|
17395
|
+
try {
|
|
17396
|
+
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
17397
|
+
method: "POST",
|
|
17398
|
+
headers: {
|
|
17399
|
+
"content-type": "application/json",
|
|
17400
|
+
"x-api-key": apiKey,
|
|
17401
|
+
"anthropic-version": "2023-06-01"
|
|
17402
|
+
},
|
|
17403
|
+
body: JSON.stringify({
|
|
17404
|
+
model,
|
|
17405
|
+
max_tokens: 256,
|
|
17406
|
+
messages: [{
|
|
17407
|
+
role: "user",
|
|
17408
|
+
content: [
|
|
17409
|
+
{
|
|
17410
|
+
type: "image",
|
|
17411
|
+
source: { type: "base64", media_type: "image/jpeg", data: base64 }
|
|
17412
|
+
},
|
|
17413
|
+
{
|
|
17414
|
+
type: "text",
|
|
17415
|
+
text: `Find the element matching this description: "${description}"
|
|
17416
|
+
|
|
17417
|
+
The screenshot is ${viewport.width}x${viewport.height} pixels.
|
|
17418
|
+
|
|
17419
|
+
Reply with ONLY a JSON object (no markdown, no explanation):
|
|
17420
|
+
{"found": true, "x": <pixel_x>, "y": <pixel_y>, "confidence": "high|medium|low", "description": "<what you found>"}
|
|
17421
|
+
|
|
17422
|
+
If you cannot find the element:
|
|
17423
|
+
{"found": false, "x": 0, "y": 0, "confidence": "none", "description": "not found"}`
|
|
17424
|
+
}
|
|
17425
|
+
]
|
|
17426
|
+
}]
|
|
17427
|
+
})
|
|
17428
|
+
});
|
|
17429
|
+
const data = await response.json();
|
|
17430
|
+
const text = data.content?.[0]?.text ?? "";
|
|
17431
|
+
const jsonStr = text.replace(/```json?\n?/g, "").replace(/```/g, "").trim();
|
|
17432
|
+
const result = JSON.parse(jsonStr);
|
|
17433
|
+
result.model = model;
|
|
17434
|
+
return result;
|
|
17435
|
+
} catch (err) {
|
|
17436
|
+
return {
|
|
17437
|
+
found: false,
|
|
17438
|
+
x: 0,
|
|
17439
|
+
y: 0,
|
|
17440
|
+
confidence: "none",
|
|
17441
|
+
description,
|
|
17442
|
+
model,
|
|
17443
|
+
error: err instanceof Error ? err.message : String(err)
|
|
17444
|
+
};
|
|
17445
|
+
}
|
|
17446
|
+
}
|
|
17447
|
+
async function clickByVision(page, description, opts) {
|
|
17448
|
+
const result = await findElementByVision(page, description, opts);
|
|
17449
|
+
if (result.found && result.x > 0 && result.y > 0) {
|
|
17450
|
+
await page.mouse.click(result.x, result.y);
|
|
17451
|
+
}
|
|
17452
|
+
return result;
|
|
17453
|
+
}
|
|
17454
|
+
var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
|
|
17455
|
+
|
|
17456
|
+
// src/lib/api-detector.ts
|
|
17457
|
+
var exports_api_detector = {};
|
|
17458
|
+
__export(exports_api_detector, {
|
|
17459
|
+
listDiscoveredAPIs: () => listDiscoveredAPIs,
|
|
17460
|
+
detectAPIs: () => detectAPIs
|
|
17461
|
+
});
|
|
17462
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
17463
|
+
function detectAPIs(sessionId) {
|
|
17464
|
+
const db = getDatabase();
|
|
17465
|
+
const requests = db.query(`SELECT method, url, status_code, response_headers, body_size
|
|
17466
|
+
FROM network_log
|
|
17467
|
+
WHERE session_id = ?
|
|
17468
|
+
AND (response_headers LIKE '%application/json%' OR response_headers LIKE '%text/json%')
|
|
17469
|
+
AND status_code >= 200 AND status_code < 400
|
|
17470
|
+
ORDER BY timestamp DESC`).all(sessionId);
|
|
17471
|
+
const seen = new Map;
|
|
17472
|
+
for (const req of requests) {
|
|
17473
|
+
try {
|
|
17474
|
+
const urlObj = new URL(req.url);
|
|
17475
|
+
if (urlObj.pathname.match(/\.(js|css|png|jpg|svg|woff|ico)$/))
|
|
17476
|
+
continue;
|
|
17477
|
+
if (urlObj.hostname.includes("googleapis.com/identitytoolkit"))
|
|
17478
|
+
continue;
|
|
17479
|
+
if (urlObj.hostname.includes("posthog"))
|
|
17480
|
+
continue;
|
|
17481
|
+
if (urlObj.hostname.includes("sentry"))
|
|
17482
|
+
continue;
|
|
17483
|
+
const key = `${req.method} ${urlObj.origin}${urlObj.pathname}`;
|
|
17484
|
+
if (!seen.has(key)) {
|
|
17485
|
+
seen.set(key, {
|
|
17486
|
+
url: `${urlObj.origin}${urlObj.pathname}`,
|
|
17487
|
+
method: req.method,
|
|
17488
|
+
status_code: req.status_code,
|
|
17489
|
+
content_type: "application/json",
|
|
17490
|
+
response_schema: {},
|
|
17491
|
+
sample_size: req.body_size ?? 0
|
|
17492
|
+
});
|
|
17493
|
+
}
|
|
17494
|
+
} catch {}
|
|
17495
|
+
}
|
|
17496
|
+
const apis = Array.from(seen.values());
|
|
17497
|
+
for (const api of apis) {
|
|
17498
|
+
const id = randomUUID12();
|
|
17499
|
+
db.prepare("INSERT OR IGNORE INTO api_endpoints (id, session_id, url, method, status_code, content_type) VALUES (?, ?, ?, ?, ?, ?)").run(id, sessionId, api.url, api.method, api.status_code, api.content_type);
|
|
17500
|
+
}
|
|
17501
|
+
return apis;
|
|
17502
|
+
}
|
|
17503
|
+
function listDiscoveredAPIs(sessionId) {
|
|
17504
|
+
const db = getDatabase();
|
|
17505
|
+
if (sessionId) {
|
|
17506
|
+
return db.query("SELECT * FROM api_endpoints WHERE session_id = ? ORDER BY discovered_at DESC").all(sessionId);
|
|
17507
|
+
}
|
|
17508
|
+
return db.query("SELECT * FROM api_endpoints ORDER BY discovered_at DESC LIMIT 100").all();
|
|
17509
|
+
}
|
|
17510
|
+
var init_api_detector = __esm(() => {
|
|
17511
|
+
init_schema();
|
|
17512
|
+
});
|
|
17513
|
+
|
|
17514
|
+
// src/lib/structured-extract.ts
|
|
17515
|
+
var exports_structured_extract = {};
|
|
17516
|
+
__export(exports_structured_extract, {
|
|
17517
|
+
extractStructuredData: () => extractStructuredData
|
|
17518
|
+
});
|
|
17519
|
+
async function extractStructuredData(page) {
|
|
17520
|
+
return page.evaluate(() => {
|
|
17521
|
+
const result = { tables: [], lists: [], jsonLd: [], openGraph: {}, metaTags: {}, repeatedElements: [] };
|
|
17522
|
+
document.querySelectorAll("table").forEach((table, idx) => {
|
|
17523
|
+
const headers = [];
|
|
17524
|
+
table.querySelectorAll("thead th, thead td, tr:first-child th").forEach((th) => {
|
|
17525
|
+
headers.push(th.textContent?.trim() ?? "");
|
|
17526
|
+
});
|
|
17527
|
+
const rows = [];
|
|
17528
|
+
table.querySelectorAll("tbody tr, tr:not(:first-child)").forEach((tr) => {
|
|
17529
|
+
const row = [];
|
|
17530
|
+
tr.querySelectorAll("td, th").forEach((td) => {
|
|
17531
|
+
row.push(td.textContent?.trim() ?? "");
|
|
17532
|
+
});
|
|
17533
|
+
if (row.length > 0 && row.some((c) => c !== ""))
|
|
17534
|
+
rows.push(row);
|
|
17535
|
+
});
|
|
17536
|
+
if (rows.length > 0) {
|
|
17537
|
+
result.tables.push({ headers, rows, selector: `table:nth-of-type(${idx + 1})` });
|
|
17538
|
+
}
|
|
17539
|
+
});
|
|
17540
|
+
document.querySelectorAll("ul, ol").forEach((list, idx) => {
|
|
17541
|
+
const items = [];
|
|
17542
|
+
list.querySelectorAll(":scope > li").forEach((li) => {
|
|
17543
|
+
const text = li.textContent?.trim() ?? "";
|
|
17544
|
+
if (text)
|
|
17545
|
+
items.push(text);
|
|
17546
|
+
});
|
|
17547
|
+
if (items.length >= 3) {
|
|
17548
|
+
const tag = list.tagName.toLowerCase();
|
|
17549
|
+
result.lists.push({ items, selector: `${tag}:nth-of-type(${idx + 1})` });
|
|
17550
|
+
}
|
|
17551
|
+
});
|
|
17552
|
+
document.querySelectorAll('script[type="application/ld+json"]').forEach((script) => {
|
|
17553
|
+
try {
|
|
17554
|
+
result.jsonLd.push(JSON.parse(script.textContent ?? ""));
|
|
17555
|
+
} catch {}
|
|
17556
|
+
});
|
|
17557
|
+
document.querySelectorAll('meta[property^="og:"]').forEach((meta) => {
|
|
17558
|
+
const prop = meta.getAttribute("property")?.replace("og:", "") ?? "";
|
|
17559
|
+
result.openGraph[prop] = meta.getAttribute("content") ?? "";
|
|
17560
|
+
});
|
|
17561
|
+
document.querySelectorAll("meta[name]").forEach((meta) => {
|
|
17562
|
+
const name = meta.getAttribute("name") ?? "";
|
|
17563
|
+
if (name)
|
|
17564
|
+
result.metaTags[name] = meta.getAttribute("content") ?? "";
|
|
17565
|
+
});
|
|
17566
|
+
const classCounts = new Map;
|
|
17567
|
+
document.querySelectorAll("[class]").forEach((el) => {
|
|
17568
|
+
const cls = el.className.toString().trim();
|
|
17569
|
+
if (cls && cls.length > 5 && cls.length < 100) {
|
|
17570
|
+
if (!classCounts.has(cls))
|
|
17571
|
+
classCounts.set(cls, []);
|
|
17572
|
+
classCounts.get(cls).push(el);
|
|
17573
|
+
}
|
|
17574
|
+
});
|
|
17575
|
+
for (const [cls, elements] of classCounts) {
|
|
17576
|
+
if (elements.length >= 3 && elements.length <= 200) {
|
|
17577
|
+
const sample = elements.slice(0, 3).map((el) => el.textContent?.trim().slice(0, 100) ?? "");
|
|
17578
|
+
if (sample.some((s) => s.length > 10)) {
|
|
17579
|
+
result.repeatedElements.push({
|
|
17580
|
+
selector: `.${cls.split(" ")[0]}`,
|
|
17581
|
+
count: elements.length,
|
|
17582
|
+
sample
|
|
17583
|
+
});
|
|
17584
|
+
}
|
|
17585
|
+
}
|
|
17586
|
+
}
|
|
17587
|
+
result.repeatedElements.sort((a, b) => b.count - a.count);
|
|
17588
|
+
result.repeatedElements = result.repeatedElements.slice(0, 10);
|
|
17589
|
+
return result;
|
|
17590
|
+
});
|
|
17591
|
+
}
|
|
17592
|
+
|
|
17593
|
+
// src/lib/datasets.ts
|
|
17594
|
+
var exports_datasets = {};
|
|
17595
|
+
__export(exports_datasets, {
|
|
17596
|
+
saveDataset: () => saveDataset,
|
|
17597
|
+
listDatasets: () => listDatasets,
|
|
17598
|
+
getDatasetByName: () => getDatasetByName,
|
|
17599
|
+
getDataset: () => getDataset,
|
|
17600
|
+
exportDataset: () => exportDataset,
|
|
17601
|
+
deleteDataset: () => deleteDataset
|
|
17602
|
+
});
|
|
17603
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
17604
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
17605
|
+
import { join as join9 } from "path";
|
|
17606
|
+
import { homedir as homedir9 } from "os";
|
|
17607
|
+
function saveDataset(data) {
|
|
17608
|
+
const db = getDatabase();
|
|
17609
|
+
const id = randomUUID13();
|
|
17610
|
+
const existing = db.query("SELECT id FROM datasets WHERE name = ?").get(data.name);
|
|
17611
|
+
if (existing) {
|
|
17612
|
+
db.prepare("UPDATE datasets SET data = ?, row_count = ?, source_url = ?, schema = ?, last_refresh = datetime('now'), updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(data.rows), data.rows.length, data.sourceUrl ?? null, data.schema ? JSON.stringify(data.schema) : null, data.name);
|
|
17613
|
+
return getDataset(existing.id);
|
|
17614
|
+
}
|
|
17615
|
+
db.prepare("INSERT INTO datasets (id, name, source_url, source_type, data, row_count, schema) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, data.name, data.sourceUrl ?? null, data.sourceType ?? "page", JSON.stringify(data.rows), data.rows.length, data.schema ? JSON.stringify(data.schema) : null);
|
|
17616
|
+
return getDataset(id);
|
|
17617
|
+
}
|
|
17618
|
+
function getDataset(id) {
|
|
17619
|
+
const db = getDatabase();
|
|
17620
|
+
const row = db.query("SELECT * FROM datasets WHERE id = ?").get(id);
|
|
17621
|
+
if (!row)
|
|
17622
|
+
return null;
|
|
17623
|
+
return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
|
|
17624
|
+
}
|
|
17625
|
+
function getDatasetByName(name) {
|
|
17626
|
+
const db = getDatabase();
|
|
17627
|
+
const row = db.query("SELECT * FROM datasets WHERE name = ?").get(name);
|
|
17628
|
+
if (!row)
|
|
17629
|
+
return null;
|
|
17630
|
+
return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
|
|
17631
|
+
}
|
|
17632
|
+
function listDatasets() {
|
|
17633
|
+
const db = getDatabase();
|
|
17634
|
+
return db.query("SELECT id, name, source_url, source_type, row_count, last_refresh, created_at, updated_at FROM datasets ORDER BY updated_at DESC").all().map((row) => ({ ...row, data: `${row.row_count} rows`, schema: null }));
|
|
17635
|
+
}
|
|
17636
|
+
function deleteDataset(name) {
|
|
17637
|
+
const db = getDatabase();
|
|
17638
|
+
return db.prepare("DELETE FROM datasets WHERE name = ?").run(name).changes > 0;
|
|
17639
|
+
}
|
|
17640
|
+
function exportDataset(name, format) {
|
|
17641
|
+
const dataset = getDatasetByName(name);
|
|
17642
|
+
if (!dataset)
|
|
17643
|
+
throw new Error(`Dataset '${name}' not found`);
|
|
17644
|
+
const dir = join9(process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser"), "exports");
|
|
17645
|
+
mkdirSync9(dir, { recursive: true });
|
|
17646
|
+
const filename = `${name}.${format}`;
|
|
17647
|
+
const path = join9(dir, filename);
|
|
17648
|
+
if (format === "csv") {
|
|
17649
|
+
const rows = dataset.data;
|
|
17650
|
+
if (rows.length === 0) {
|
|
17651
|
+
writeFileSync3(path, "");
|
|
17652
|
+
return { path, size: 0 };
|
|
17653
|
+
}
|
|
17654
|
+
const headers = Object.keys(rows[0]);
|
|
17655
|
+
const csvLines = [headers.join(",")];
|
|
17656
|
+
for (const row of rows) {
|
|
17657
|
+
csvLines.push(headers.map((h) => {
|
|
17658
|
+
const val = String(row[h] ?? "");
|
|
17659
|
+
return val.includes(",") || val.includes('"') ? `"${val.replace(/"/g, '""')}"` : val;
|
|
17660
|
+
}).join(","));
|
|
17661
|
+
}
|
|
17662
|
+
const content = csvLines.join(`
|
|
17663
|
+
`);
|
|
17664
|
+
writeFileSync3(path, content);
|
|
17665
|
+
return { path, size: content.length };
|
|
17666
|
+
} else {
|
|
17667
|
+
const content = JSON.stringify(dataset.data, null, 2);
|
|
17668
|
+
writeFileSync3(path, content);
|
|
17669
|
+
return { path, size: content.length };
|
|
17670
|
+
}
|
|
17671
|
+
}
|
|
17672
|
+
var init_datasets = __esm(() => {
|
|
17673
|
+
init_schema();
|
|
17674
|
+
});
|
|
17675
|
+
|
|
16372
17676
|
// src/lib/auth.ts
|
|
16373
17677
|
var exports_auth = {};
|
|
16374
17678
|
__export(exports_auth, {
|
|
16375
17679
|
loginWithCredentials: () => loginWithCredentials,
|
|
16376
17680
|
getCredentials: () => getCredentials
|
|
16377
17681
|
});
|
|
16378
|
-
import { existsSync as
|
|
16379
|
-
import { join as
|
|
16380
|
-
import { homedir as
|
|
17682
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
17683
|
+
import { join as join10 } from "path";
|
|
17684
|
+
import { homedir as homedir10 } from "os";
|
|
16381
17685
|
async function getCredentials(service) {
|
|
16382
17686
|
try {
|
|
16383
|
-
const { getSecret } = await import(`${
|
|
17687
|
+
const { getSecret } = await import(`${homedir10()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
|
|
16384
17688
|
const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
|
|
16385
17689
|
const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
|
|
16386
17690
|
if (email?.value && password?.value) {
|
|
16387
17691
|
return { email: email.value, password: password.value };
|
|
16388
17692
|
}
|
|
16389
17693
|
} catch {}
|
|
16390
|
-
const secretsPath =
|
|
16391
|
-
if (
|
|
17694
|
+
const secretsPath = join10(homedir10(), ".secrets");
|
|
17695
|
+
if (existsSync5(secretsPath)) {
|
|
16392
17696
|
const content = readFileSync3(secretsPath, "utf8");
|
|
16393
17697
|
const lines = content.split(`
|
|
16394
17698
|
`);
|
|
@@ -16565,14 +17869,14 @@ __export(exports_dist, {
|
|
|
16565
17869
|
InvalidScopeError: () => InvalidScopeError,
|
|
16566
17870
|
EntityNotFoundError: () => EntityNotFoundError,
|
|
16567
17871
|
DuplicateMemoryError: () => DuplicateMemoryError,
|
|
16568
|
-
DEFAULT_MODEL: () =>
|
|
17872
|
+
DEFAULT_MODEL: () => DEFAULT_MODEL2,
|
|
16569
17873
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
16570
17874
|
});
|
|
16571
17875
|
import { Database as Database2 } from "bun:sqlite";
|
|
16572
|
-
import { existsSync as
|
|
16573
|
-
import { dirname, join as
|
|
16574
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as
|
|
16575
|
-
import { homedir as
|
|
17876
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync10 } from "fs";
|
|
17877
|
+
import { dirname, join as join11, resolve } from "path";
|
|
17878
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
17879
|
+
import { homedir as homedir11 } from "os";
|
|
16576
17880
|
import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve2 } from "path";
|
|
16577
17881
|
import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
16578
17882
|
import { homedir as homedir22 } from "os";
|
|
@@ -16586,8 +17890,8 @@ function isInMemoryDb(path) {
|
|
|
16586
17890
|
function findNearestMementosDb(startDir) {
|
|
16587
17891
|
let dir = resolve(startDir);
|
|
16588
17892
|
while (true) {
|
|
16589
|
-
const candidate =
|
|
16590
|
-
if (
|
|
17893
|
+
const candidate = join11(dir, ".mementos", "mementos.db");
|
|
17894
|
+
if (existsSync6(candidate))
|
|
16591
17895
|
return candidate;
|
|
16592
17896
|
const parent = dirname(dir);
|
|
16593
17897
|
if (parent === dir)
|
|
@@ -16599,7 +17903,7 @@ function findNearestMementosDb(startDir) {
|
|
|
16599
17903
|
function findGitRoot(startDir) {
|
|
16600
17904
|
let dir = resolve(startDir);
|
|
16601
17905
|
while (true) {
|
|
16602
|
-
if (
|
|
17906
|
+
if (existsSync6(join11(dir, ".git")))
|
|
16603
17907
|
return dir;
|
|
16604
17908
|
const parent = dirname(dir);
|
|
16605
17909
|
if (parent === dir)
|
|
@@ -16619,25 +17923,25 @@ function getDbPath() {
|
|
|
16619
17923
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
16620
17924
|
const gitRoot = findGitRoot(cwd);
|
|
16621
17925
|
if (gitRoot) {
|
|
16622
|
-
return
|
|
17926
|
+
return join11(gitRoot, ".mementos", "mementos.db");
|
|
16623
17927
|
}
|
|
16624
17928
|
}
|
|
16625
17929
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
16626
|
-
return
|
|
17930
|
+
return join11(home, ".mementos", "mementos.db");
|
|
16627
17931
|
}
|
|
16628
|
-
function
|
|
17932
|
+
function ensureDir2(filePath) {
|
|
16629
17933
|
if (isInMemoryDb(filePath))
|
|
16630
17934
|
return;
|
|
16631
17935
|
const dir = dirname(resolve(filePath));
|
|
16632
|
-
if (!
|
|
16633
|
-
|
|
17936
|
+
if (!existsSync6(dir)) {
|
|
17937
|
+
mkdirSync10(dir, { recursive: true });
|
|
16634
17938
|
}
|
|
16635
17939
|
}
|
|
16636
17940
|
function getDatabase2(dbPath) {
|
|
16637
17941
|
if (_db2)
|
|
16638
17942
|
return _db2;
|
|
16639
17943
|
const path = dbPath || getDbPath();
|
|
16640
|
-
|
|
17944
|
+
ensureDir2(path);
|
|
16641
17945
|
_db2 = new Database2(path, { create: true });
|
|
16642
17946
|
_db2.run("PRAGMA journal_mode = WAL");
|
|
16643
17947
|
_db2.run("PRAGMA busy_timeout = 5000");
|
|
@@ -18480,7 +19784,7 @@ function isValidCategory(value) {
|
|
|
18480
19784
|
return VALID_CATEGORIES.includes(value);
|
|
18481
19785
|
}
|
|
18482
19786
|
function loadConfig() {
|
|
18483
|
-
const configPath = join22(
|
|
19787
|
+
const configPath = join22(homedir11(), ".mementos", "config.json");
|
|
18484
19788
|
let fileConfig = {};
|
|
18485
19789
|
if (existsSync22(configPath)) {
|
|
18486
19790
|
try {
|
|
@@ -18507,10 +19811,10 @@ function loadConfig() {
|
|
|
18507
19811
|
return merged;
|
|
18508
19812
|
}
|
|
18509
19813
|
function profilesDir() {
|
|
18510
|
-
return join22(
|
|
19814
|
+
return join22(homedir11(), ".mementos", "profiles");
|
|
18511
19815
|
}
|
|
18512
19816
|
function globalConfigPath() {
|
|
18513
|
-
return join22(
|
|
19817
|
+
return join22(homedir11(), ".mementos", "config.json");
|
|
18514
19818
|
}
|
|
18515
19819
|
function readGlobalConfig() {
|
|
18516
19820
|
const p = globalConfigPath();
|
|
@@ -18524,8 +19828,8 @@ function readGlobalConfig() {
|
|
|
18524
19828
|
}
|
|
18525
19829
|
function writeGlobalConfig(data) {
|
|
18526
19830
|
const p = globalConfigPath();
|
|
18527
|
-
|
|
18528
|
-
|
|
19831
|
+
ensureDir22(dirname2(p));
|
|
19832
|
+
writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
|
|
18529
19833
|
}
|
|
18530
19834
|
function getActiveProfile() {
|
|
18531
19835
|
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
@@ -18547,18 +19851,18 @@ function listProfiles2() {
|
|
|
18547
19851
|
const dir = profilesDir();
|
|
18548
19852
|
if (!existsSync22(dir))
|
|
18549
19853
|
return [];
|
|
18550
|
-
return
|
|
19854
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".db")).map((f) => basename2(f, ".db")).sort();
|
|
18551
19855
|
}
|
|
18552
19856
|
function deleteProfile2(name) {
|
|
18553
19857
|
const dbPath = join22(profilesDir(), `${name}.db`);
|
|
18554
19858
|
if (!existsSync22(dbPath))
|
|
18555
19859
|
return false;
|
|
18556
|
-
|
|
19860
|
+
unlinkSync3(dbPath);
|
|
18557
19861
|
if (getActiveProfile() === name)
|
|
18558
19862
|
setActiveProfile(null);
|
|
18559
19863
|
return true;
|
|
18560
19864
|
}
|
|
18561
|
-
function
|
|
19865
|
+
function ensureDir22(dir) {
|
|
18562
19866
|
if (!existsSync22(dir)) {
|
|
18563
19867
|
mkdirSync22(dir, { recursive: true });
|
|
18564
19868
|
}
|
|
@@ -19680,7 +20984,7 @@ function writeConfig(config) {
|
|
|
19680
20984
|
}
|
|
19681
20985
|
function getActiveModel() {
|
|
19682
20986
|
const config = readConfig();
|
|
19683
|
-
return config.activeModel ??
|
|
20987
|
+
return config.activeModel ?? DEFAULT_MODEL2;
|
|
19684
20988
|
}
|
|
19685
20989
|
function setActiveModel(modelId) {
|
|
19686
20990
|
const config = readConfig();
|
|
@@ -19754,7 +21058,7 @@ Return JSON with this exact shape:
|
|
|
19754
21058
|
examples: finalExamples,
|
|
19755
21059
|
count: finalExamples.length
|
|
19756
21060
|
};
|
|
19757
|
-
},
|
|
21061
|
+
}, DEFAULT_MODEL2 = "gpt-4o-mini", CONFIG_DIR, CONFIG_PATH;
|
|
19758
21062
|
var init_dist = __esm(() => {
|
|
19759
21063
|
__defProp2 = Object.defineProperty;
|
|
19760
21064
|
exports_database = {};
|
|
@@ -21190,10 +22494,10 @@ __export(exports_dist2, {
|
|
|
21190
22494
|
acquireLock: () => acquireLock2
|
|
21191
22495
|
});
|
|
21192
22496
|
import { Database as Database3 } from "bun:sqlite";
|
|
21193
|
-
import { mkdirSync as
|
|
21194
|
-
import { join as
|
|
21195
|
-
import { homedir as
|
|
21196
|
-
import { randomUUID as
|
|
22497
|
+
import { mkdirSync as mkdirSync11 } from "fs";
|
|
22498
|
+
import { join as join12, dirname as dirname3 } from "path";
|
|
22499
|
+
import { homedir as homedir12 } from "os";
|
|
22500
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
21197
22501
|
import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
|
|
21198
22502
|
import { join as join33 } from "path";
|
|
21199
22503
|
import { homedir as homedir33 } from "os";
|
|
@@ -21201,19 +22505,19 @@ import { readFileSync as readFileSync5 } from "fs";
|
|
|
21201
22505
|
import { join as join23 } from "path";
|
|
21202
22506
|
import { homedir as homedir23 } from "os";
|
|
21203
22507
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
21204
|
-
import { readFileSync as readFileSync23, writeFileSync as
|
|
22508
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync33 } from "fs";
|
|
21205
22509
|
import { join as join43, dirname as dirname22 } from "path";
|
|
21206
22510
|
import { homedir as homedir42 } from "os";
|
|
21207
22511
|
function getDbPath2() {
|
|
21208
22512
|
if (process.env.CONVERSATIONS_DB_PATH)
|
|
21209
22513
|
return process.env.CONVERSATIONS_DB_PATH;
|
|
21210
|
-
return
|
|
22514
|
+
return join12(homedir12(), ".conversations", "messages.db");
|
|
21211
22515
|
}
|
|
21212
22516
|
function getDb() {
|
|
21213
22517
|
if (db)
|
|
21214
22518
|
return db;
|
|
21215
22519
|
const dbPath = getDbPath2();
|
|
21216
|
-
|
|
22520
|
+
mkdirSync11(dirname3(dbPath), { recursive: true });
|
|
21217
22521
|
db = new Database3(dbPath, { create: true });
|
|
21218
22522
|
db.exec("PRAGMA journal_mode = WAL");
|
|
21219
22523
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -21573,7 +22877,7 @@ function guessMimeType(name) {
|
|
|
21573
22877
|
function sendMessage(opts) {
|
|
21574
22878
|
const db2 = getDb();
|
|
21575
22879
|
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
21576
|
-
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${
|
|
22880
|
+
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID14().slice(0, 8)}`);
|
|
21577
22881
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
21578
22882
|
const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
|
|
21579
22883
|
const blocking = opts.blocking ? 1 : 0;
|
|
@@ -22401,7 +23705,7 @@ function getAutoName() {
|
|
|
22401
23705
|
cachedAutoName = name;
|
|
22402
23706
|
try {
|
|
22403
23707
|
mkdirSync33(dirname22(AGENT_ID_FILE), { recursive: true });
|
|
22404
|
-
|
|
23708
|
+
writeFileSync5(AGENT_ID_FILE, name + `
|
|
22405
23709
|
`, "utf-8");
|
|
22406
23710
|
} catch {}
|
|
22407
23711
|
return name;
|
|
@@ -24356,7 +25660,7 @@ Your code should look like:
|
|
|
24356
25660
|
}
|
|
24357
25661
|
}
|
|
24358
25662
|
}
|
|
24359
|
-
function checkPropTypes(typeSpecs, values,
|
|
25663
|
+
function checkPropTypes(typeSpecs, values, location2, componentName, element) {
|
|
24360
25664
|
{
|
|
24361
25665
|
var has = Function.call.bind(hasOwnProperty);
|
|
24362
25666
|
for (var typeSpecName in typeSpecs) {
|
|
@@ -24364,23 +25668,23 @@ Your code should look like:
|
|
|
24364
25668
|
var error$1 = undefined;
|
|
24365
25669
|
try {
|
|
24366
25670
|
if (typeof typeSpecs[typeSpecName] !== "function") {
|
|
24367
|
-
var err = Error((componentName || "React class") + ": " +
|
|
25671
|
+
var err = Error((componentName || "React class") + ": " + location2 + " type `" + typeSpecName + "` is invalid; " + "it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`." + "This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
|
|
24368
25672
|
err.name = "Invariant Violation";
|
|
24369
25673
|
throw err;
|
|
24370
25674
|
}
|
|
24371
|
-
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName,
|
|
25675
|
+
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location2, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
|
|
24372
25676
|
} catch (ex) {
|
|
24373
25677
|
error$1 = ex;
|
|
24374
25678
|
}
|
|
24375
25679
|
if (error$1 && !(error$1 instanceof Error)) {
|
|
24376
25680
|
setCurrentlyValidatingElement(element);
|
|
24377
|
-
error("%s: type specification of %s" + " `%s` is invalid; the type checker " + "function must return `null` or an `Error` but returned a %s. " + "You may have forgotten to pass an argument to the type checker " + "creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and " + "shape all require an argument).", componentName || "React class",
|
|
25681
|
+
error("%s: type specification of %s" + " `%s` is invalid; the type checker " + "function must return `null` or an `Error` but returned a %s. " + "You may have forgotten to pass an argument to the type checker " + "creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and " + "shape all require an argument).", componentName || "React class", location2, typeSpecName, typeof error$1);
|
|
24378
25682
|
setCurrentlyValidatingElement(null);
|
|
24379
25683
|
}
|
|
24380
25684
|
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
|
|
24381
25685
|
loggedTypeFailures[error$1.message] = true;
|
|
24382
25686
|
setCurrentlyValidatingElement(element);
|
|
24383
|
-
error("Failed %s type: %s",
|
|
25687
|
+
error("Failed %s type: %s", location2, error$1.message);
|
|
24384
25688
|
setCurrentlyValidatingElement(null);
|
|
24385
25689
|
}
|
|
24386
25690
|
}
|
|
@@ -25615,11 +26919,11 @@ __export(exports_dist3, {
|
|
|
25615
26919
|
AgentNotFoundError: () => AgentNotFoundError2
|
|
25616
26920
|
});
|
|
25617
26921
|
import { Database as Database4 } from "bun:sqlite";
|
|
25618
|
-
import { existsSync as
|
|
25619
|
-
import { dirname as dirname5, join as
|
|
26922
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync12 } from "fs";
|
|
26923
|
+
import { dirname as dirname5, join as join13, resolve as resolve3 } from "path";
|
|
25620
26924
|
import { existsSync as existsSync33 } from "fs";
|
|
25621
26925
|
import { join as join34 } from "path";
|
|
25622
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as
|
|
26926
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
25623
26927
|
import { join as join24 } from "path";
|
|
25624
26928
|
import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
|
|
25625
26929
|
import { join as join44 } from "path";
|
|
@@ -25849,8 +27153,8 @@ function isInMemoryDb2(path) {
|
|
|
25849
27153
|
function findNearestTodosDb(startDir) {
|
|
25850
27154
|
let dir = resolve3(startDir);
|
|
25851
27155
|
while (true) {
|
|
25852
|
-
const candidate =
|
|
25853
|
-
if (
|
|
27156
|
+
const candidate = join13(dir, ".todos", "todos.db");
|
|
27157
|
+
if (existsSync7(candidate))
|
|
25854
27158
|
return candidate;
|
|
25855
27159
|
const parent = dirname5(dir);
|
|
25856
27160
|
if (parent === dir)
|
|
@@ -25862,7 +27166,7 @@ function findNearestTodosDb(startDir) {
|
|
|
25862
27166
|
function findGitRoot2(startDir) {
|
|
25863
27167
|
let dir = resolve3(startDir);
|
|
25864
27168
|
while (true) {
|
|
25865
|
-
if (
|
|
27169
|
+
if (existsSync7(join13(dir, ".git")))
|
|
25866
27170
|
return dir;
|
|
25867
27171
|
const parent = dirname5(dir);
|
|
25868
27172
|
if (parent === dir)
|
|
@@ -25882,18 +27186,18 @@ function getDbPath3() {
|
|
|
25882
27186
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
25883
27187
|
const gitRoot = findGitRoot2(cwd);
|
|
25884
27188
|
if (gitRoot) {
|
|
25885
|
-
return
|
|
27189
|
+
return join13(gitRoot, ".todos", "todos.db");
|
|
25886
27190
|
}
|
|
25887
27191
|
}
|
|
25888
27192
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
25889
|
-
return
|
|
27193
|
+
return join13(home, ".todos", "todos.db");
|
|
25890
27194
|
}
|
|
25891
27195
|
function ensureDir3(filePath) {
|
|
25892
27196
|
if (isInMemoryDb2(filePath))
|
|
25893
27197
|
return;
|
|
25894
27198
|
const dir = dirname5(resolve3(filePath));
|
|
25895
|
-
if (!
|
|
25896
|
-
|
|
27199
|
+
if (!existsSync7(dir)) {
|
|
27200
|
+
mkdirSync12(dir, { recursive: true });
|
|
25897
27201
|
}
|
|
25898
27202
|
}
|
|
25899
27203
|
function getDatabase3(dbPath) {
|
|
@@ -26352,14 +27656,14 @@ function ensureProject2(name, path, db2) {
|
|
|
26352
27656
|
}
|
|
26353
27657
|
return createProject3({ name, path }, d);
|
|
26354
27658
|
}
|
|
26355
|
-
function
|
|
27659
|
+
function ensureDir23(dir) {
|
|
26356
27660
|
if (!existsSync23(dir))
|
|
26357
27661
|
mkdirSync24(dir, { recursive: true });
|
|
26358
27662
|
}
|
|
26359
27663
|
function listJsonFiles(dir) {
|
|
26360
27664
|
if (!existsSync23(dir))
|
|
26361
27665
|
return [];
|
|
26362
|
-
return
|
|
27666
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".json"));
|
|
26363
27667
|
}
|
|
26364
27668
|
function readJsonFile(path) {
|
|
26365
27669
|
try {
|
|
@@ -26369,7 +27673,7 @@ function readJsonFile(path) {
|
|
|
26369
27673
|
}
|
|
26370
27674
|
}
|
|
26371
27675
|
function writeJsonFile(path, data) {
|
|
26372
|
-
|
|
27676
|
+
writeFileSync6(path, JSON.stringify(data, null, 2) + `
|
|
26373
27677
|
`);
|
|
26374
27678
|
}
|
|
26375
27679
|
function readHighWaterMark(dir) {
|
|
@@ -26380,7 +27684,7 @@ function readHighWaterMark(dir) {
|
|
|
26380
27684
|
return isNaN(val) ? 1 : val;
|
|
26381
27685
|
}
|
|
26382
27686
|
function writeHighWaterMark(dir, value) {
|
|
26383
|
-
|
|
27687
|
+
writeFileSync6(join24(dir, ".highwatermark"), String(value));
|
|
26384
27688
|
}
|
|
26385
27689
|
function getFileMtimeMs(path) {
|
|
26386
27690
|
try {
|
|
@@ -29224,7 +30528,7 @@ function taskToClaudeTask(task, claudeTaskId, existingMeta) {
|
|
|
29224
30528
|
function pushToClaudeTaskList(taskListId, projectId, options = {}) {
|
|
29225
30529
|
const dir = getTaskListDir(taskListId);
|
|
29226
30530
|
if (!existsSync43(dir))
|
|
29227
|
-
|
|
30531
|
+
ensureDir23(dir);
|
|
29228
30532
|
const filter = {};
|
|
29229
30533
|
if (projectId)
|
|
29230
30534
|
filter["project_id"] = projectId;
|
|
@@ -29442,7 +30746,7 @@ function metadataKey(agent) {
|
|
|
29442
30746
|
function pushToAgentTaskList(agent, taskListId, projectId, options = {}) {
|
|
29443
30747
|
const dir = getTaskListDir2(agent, taskListId);
|
|
29444
30748
|
if (!existsSync52(dir))
|
|
29445
|
-
|
|
30749
|
+
ensureDir23(dir);
|
|
29446
30750
|
const filter = {};
|
|
29447
30751
|
if (projectId)
|
|
29448
30752
|
filter["project_id"] = projectId;
|
|
@@ -30970,9 +32274,9 @@ __export(exports_dist4, {
|
|
|
30970
32274
|
CATEGORIES: () => CATEGORIES,
|
|
30971
32275
|
AGENT_TARGETS: () => AGENT_TARGETS
|
|
30972
32276
|
});
|
|
30973
|
-
import { existsSync as
|
|
30974
|
-
import { join as
|
|
30975
|
-
import { homedir as
|
|
32277
|
+
import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync7, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync7, accessSync, constants } from "fs";
|
|
32278
|
+
import { join as join14, dirname as dirname6 } from "path";
|
|
32279
|
+
import { homedir as homedir13 } from "os";
|
|
30976
32280
|
import { fileURLToPath } from "url";
|
|
30977
32281
|
import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
|
|
30978
32282
|
import { join as join25 } from "path";
|
|
@@ -31083,35 +32387,35 @@ function normalizeSkillName(name) {
|
|
|
31083
32387
|
function findSkillsDir() {
|
|
31084
32388
|
let dir = __dirname2;
|
|
31085
32389
|
for (let i = 0;i < 5; i++) {
|
|
31086
|
-
const candidate =
|
|
31087
|
-
if (
|
|
32390
|
+
const candidate = join14(dir, "skills");
|
|
32391
|
+
if (existsSync8(candidate)) {
|
|
31088
32392
|
return candidate;
|
|
31089
32393
|
}
|
|
31090
32394
|
dir = dirname6(dir);
|
|
31091
32395
|
}
|
|
31092
|
-
return
|
|
32396
|
+
return join14(__dirname2, "..", "skills");
|
|
31093
32397
|
}
|
|
31094
32398
|
function getSkillPath(name) {
|
|
31095
32399
|
const skillName = normalizeSkillName(name);
|
|
31096
|
-
return
|
|
32400
|
+
return join14(SKILLS_DIR, skillName);
|
|
31097
32401
|
}
|
|
31098
32402
|
function skillExists(name) {
|
|
31099
|
-
return
|
|
32403
|
+
return existsSync8(getSkillPath(name));
|
|
31100
32404
|
}
|
|
31101
32405
|
function installSkill(name, options = {}) {
|
|
31102
32406
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
31103
32407
|
const skillName = normalizeSkillName(name);
|
|
31104
32408
|
const sourcePath = getSkillPath(name);
|
|
31105
|
-
const destDir =
|
|
31106
|
-
const destPath =
|
|
31107
|
-
if (!
|
|
32409
|
+
const destDir = join14(targetDir, ".skills");
|
|
32410
|
+
const destPath = join14(destDir, skillName);
|
|
32411
|
+
if (!existsSync8(sourcePath)) {
|
|
31108
32412
|
return {
|
|
31109
32413
|
skill: name,
|
|
31110
32414
|
success: false,
|
|
31111
32415
|
error: `Skill '${name}' not found`
|
|
31112
32416
|
};
|
|
31113
32417
|
}
|
|
31114
|
-
if (
|
|
32418
|
+
if (existsSync8(destPath) && !overwrite) {
|
|
31115
32419
|
return {
|
|
31116
32420
|
skill: name,
|
|
31117
32421
|
success: false,
|
|
@@ -31120,10 +32424,10 @@ function installSkill(name, options = {}) {
|
|
|
31120
32424
|
};
|
|
31121
32425
|
}
|
|
31122
32426
|
try {
|
|
31123
|
-
if (!
|
|
31124
|
-
|
|
32427
|
+
if (!existsSync8(destDir)) {
|
|
32428
|
+
mkdirSync13(destDir, { recursive: true });
|
|
31125
32429
|
}
|
|
31126
|
-
if (
|
|
32430
|
+
if (existsSync8(destPath) && overwrite) {
|
|
31127
32431
|
rmSync2(destPath, { recursive: true, force: true });
|
|
31128
32432
|
}
|
|
31129
32433
|
cpSync(sourcePath, destPath, {
|
|
@@ -31162,10 +32466,10 @@ function installSkills(names, options = {}) {
|
|
|
31162
32466
|
return names.map((name) => installSkill(name, options));
|
|
31163
32467
|
}
|
|
31164
32468
|
function updateSkillsIndex(skillsDir) {
|
|
31165
|
-
const indexPath =
|
|
32469
|
+
const indexPath = join14(skillsDir, "index.ts");
|
|
31166
32470
|
const meta = loadMeta(skillsDir);
|
|
31167
32471
|
const disabledSet = new Set(meta.disabled || []);
|
|
31168
|
-
const skills =
|
|
32472
|
+
const skills = readdirSync6(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
31169
32473
|
const exports = skills.map((s) => {
|
|
31170
32474
|
const name = s.replace("skill-", "").replace(/-/g, "_");
|
|
31171
32475
|
return `export * as ${name} from './${s}/src/index.js';`;
|
|
@@ -31178,14 +32482,14 @@ function updateSkillsIndex(skillsDir) {
|
|
|
31178
32482
|
|
|
31179
32483
|
${exports}
|
|
31180
32484
|
`;
|
|
31181
|
-
|
|
32485
|
+
writeFileSync7(indexPath, content);
|
|
31182
32486
|
}
|
|
31183
32487
|
function getMetaPath(skillsDir) {
|
|
31184
|
-
return
|
|
32488
|
+
return join14(skillsDir, ".meta.json");
|
|
31185
32489
|
}
|
|
31186
32490
|
function loadMeta(skillsDir) {
|
|
31187
32491
|
const metaPath2 = getMetaPath(skillsDir);
|
|
31188
|
-
if (
|
|
32492
|
+
if (existsSync8(metaPath2)) {
|
|
31189
32493
|
try {
|
|
31190
32494
|
return JSON.parse(readFileSync7(metaPath2, "utf-8"));
|
|
31191
32495
|
} catch {}
|
|
@@ -31193,15 +32497,15 @@ function loadMeta(skillsDir) {
|
|
|
31193
32497
|
return { skills: {} };
|
|
31194
32498
|
}
|
|
31195
32499
|
function saveMeta(skillsDir, meta) {
|
|
31196
|
-
|
|
32500
|
+
writeFileSync7(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
|
|
31197
32501
|
}
|
|
31198
32502
|
function recordInstall(skillsDir, name) {
|
|
31199
32503
|
const meta = loadMeta(skillsDir);
|
|
31200
32504
|
const skillName = normalizeSkillName(name);
|
|
31201
32505
|
let version = "unknown";
|
|
31202
32506
|
try {
|
|
31203
|
-
const pkgPath =
|
|
31204
|
-
if (
|
|
32507
|
+
const pkgPath = join14(skillsDir, skillName, "package.json");
|
|
32508
|
+
if (existsSync8(pkgPath)) {
|
|
31205
32509
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
31206
32510
|
version = pkg.version || "unknown";
|
|
31207
32511
|
}
|
|
@@ -31215,12 +32519,12 @@ function recordRemove(skillsDir, name) {
|
|
|
31215
32519
|
saveMeta(skillsDir, meta);
|
|
31216
32520
|
}
|
|
31217
32521
|
function getInstallMeta(targetDir = process.cwd()) {
|
|
31218
|
-
return loadMeta(
|
|
32522
|
+
return loadMeta(join14(targetDir, ".skills"));
|
|
31219
32523
|
}
|
|
31220
32524
|
function disableSkill(name, targetDir = process.cwd()) {
|
|
31221
|
-
const skillsDir =
|
|
32525
|
+
const skillsDir = join14(targetDir, ".skills");
|
|
31222
32526
|
const skillName = normalizeSkillName(name);
|
|
31223
|
-
if (!
|
|
32527
|
+
if (!existsSync8(join14(skillsDir, skillName)))
|
|
31224
32528
|
return false;
|
|
31225
32529
|
const meta = loadMeta(skillsDir);
|
|
31226
32530
|
const disabled = new Set(meta.disabled || []);
|
|
@@ -31233,7 +32537,7 @@ function disableSkill(name, targetDir = process.cwd()) {
|
|
|
31233
32537
|
return true;
|
|
31234
32538
|
}
|
|
31235
32539
|
function enableSkill(name, targetDir = process.cwd()) {
|
|
31236
|
-
const skillsDir =
|
|
32540
|
+
const skillsDir = join14(targetDir, ".skills");
|
|
31237
32541
|
const meta = loadMeta(skillsDir);
|
|
31238
32542
|
const disabled = new Set(meta.disabled || []);
|
|
31239
32543
|
if (!disabled.has(name))
|
|
@@ -31245,24 +32549,24 @@ function enableSkill(name, targetDir = process.cwd()) {
|
|
|
31245
32549
|
return true;
|
|
31246
32550
|
}
|
|
31247
32551
|
function getDisabledSkills(targetDir = process.cwd()) {
|
|
31248
|
-
const meta = loadMeta(
|
|
32552
|
+
const meta = loadMeta(join14(targetDir, ".skills"));
|
|
31249
32553
|
return meta.disabled || [];
|
|
31250
32554
|
}
|
|
31251
32555
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
31252
|
-
const skillsDir =
|
|
31253
|
-
if (!
|
|
32556
|
+
const skillsDir = join14(targetDir, ".skills");
|
|
32557
|
+
if (!existsSync8(skillsDir)) {
|
|
31254
32558
|
return [];
|
|
31255
32559
|
}
|
|
31256
|
-
return
|
|
31257
|
-
const fullPath =
|
|
32560
|
+
return readdirSync6(skillsDir).filter((f) => {
|
|
32561
|
+
const fullPath = join14(skillsDir, f);
|
|
31258
32562
|
return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
|
|
31259
32563
|
}).map((f) => f.replace("skill-", ""));
|
|
31260
32564
|
}
|
|
31261
32565
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
31262
32566
|
const skillName = normalizeSkillName(name);
|
|
31263
|
-
const skillsDir =
|
|
31264
|
-
const skillPath =
|
|
31265
|
-
if (!
|
|
32567
|
+
const skillsDir = join14(targetDir, ".skills");
|
|
32568
|
+
const skillPath = join14(skillsDir, skillName);
|
|
32569
|
+
if (!existsSync8(skillPath)) {
|
|
31266
32570
|
return false;
|
|
31267
32571
|
}
|
|
31268
32572
|
rmSync2(skillPath, { recursive: true, force: true });
|
|
@@ -31273,24 +32577,24 @@ function removeSkill(name, targetDir = process.cwd()) {
|
|
|
31273
32577
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
31274
32578
|
const agentDir = `.${agent}`;
|
|
31275
32579
|
if (scope === "project") {
|
|
31276
|
-
return
|
|
32580
|
+
return join14(projectDir || process.cwd(), agentDir, "skills");
|
|
31277
32581
|
}
|
|
31278
|
-
return
|
|
32582
|
+
return join14(homedir13(), agentDir, "skills");
|
|
31279
32583
|
}
|
|
31280
32584
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
31281
32585
|
const skillName = normalizeSkillName(name);
|
|
31282
|
-
return
|
|
32586
|
+
return join14(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
31283
32587
|
}
|
|
31284
32588
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
31285
32589
|
const { agent, scope = "global", projectDir } = options;
|
|
31286
32590
|
const skillName = normalizeSkillName(name);
|
|
31287
32591
|
const sourcePath = getSkillPath(name);
|
|
31288
|
-
if (!
|
|
32592
|
+
if (!existsSync8(sourcePath)) {
|
|
31289
32593
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
31290
32594
|
}
|
|
31291
32595
|
let skillMdContent = null;
|
|
31292
|
-
const skillMdPath =
|
|
31293
|
-
if (
|
|
32596
|
+
const skillMdPath = join14(sourcePath, "SKILL.md");
|
|
32597
|
+
if (existsSync8(skillMdPath)) {
|
|
31294
32598
|
skillMdContent = readFileSync7(skillMdPath, "utf-8");
|
|
31295
32599
|
} else if (generateSkillMd) {
|
|
31296
32600
|
skillMdContent = generateSkillMd(name);
|
|
@@ -31300,8 +32604,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31300
32604
|
}
|
|
31301
32605
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
31302
32606
|
if (scope === "global") {
|
|
31303
|
-
const agentBaseDir2 =
|
|
31304
|
-
if (!
|
|
32607
|
+
const agentBaseDir2 = join14(homedir13(), `.${agent}`);
|
|
32608
|
+
if (!existsSync8(agentBaseDir2)) {
|
|
31305
32609
|
const agentLabels = {
|
|
31306
32610
|
claude: "Claude Code",
|
|
31307
32611
|
codex: "Codex CLI",
|
|
@@ -31324,8 +32628,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31324
32628
|
}
|
|
31325
32629
|
}
|
|
31326
32630
|
try {
|
|
31327
|
-
|
|
31328
|
-
|
|
32631
|
+
mkdirSync13(destDir, { recursive: true });
|
|
32632
|
+
writeFileSync7(join14(destDir, "SKILL.md"), skillMdContent);
|
|
31329
32633
|
return { skill: name, success: true, path: destDir };
|
|
31330
32634
|
} catch (error) {
|
|
31331
32635
|
return {
|
|
@@ -31338,7 +32642,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31338
32642
|
function removeSkillForAgent(name, options) {
|
|
31339
32643
|
const { agent, scope = "global", projectDir } = options;
|
|
31340
32644
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
31341
|
-
if (!
|
|
32645
|
+
if (!existsSync8(destDir)) {
|
|
31342
32646
|
return false;
|
|
31343
32647
|
}
|
|
31344
32648
|
rmSync2(destDir, { recursive: true, force: true });
|
|
@@ -33262,7 +34566,7 @@ __export(exports_cron_manager, {
|
|
|
33262
34566
|
deleteCronJob: () => deleteCronJob,
|
|
33263
34567
|
createCronJob: () => createCronJob
|
|
33264
34568
|
});
|
|
33265
|
-
import { randomUUID as
|
|
34569
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
33266
34570
|
function ensureCronTable() {
|
|
33267
34571
|
const db2 = getDatabase();
|
|
33268
34572
|
db2.exec(`
|
|
@@ -33291,7 +34595,7 @@ function ensureCronTable() {
|
|
|
33291
34595
|
function createCronJob(schedule, task, name) {
|
|
33292
34596
|
ensureCronTable();
|
|
33293
34597
|
const db2 = getDatabase();
|
|
33294
|
-
const id =
|
|
34598
|
+
const id = randomUUID15();
|
|
33295
34599
|
db2.prepare(`
|
|
33296
34600
|
INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
|
|
33297
34601
|
VALUES (?, ?, ?, ?, 1)
|
|
@@ -33349,7 +34653,7 @@ function getCronEvents(jobId, limit = 10) {
|
|
|
33349
34653
|
async function executeCronJob(job) {
|
|
33350
34654
|
ensureCronTable();
|
|
33351
34655
|
const db2 = getDatabase();
|
|
33352
|
-
const eventId =
|
|
34656
|
+
const eventId = randomUUID15();
|
|
33353
34657
|
const startedAt = new Date().toISOString();
|
|
33354
34658
|
db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
|
|
33355
34659
|
try {
|
|
@@ -33440,7 +34744,7 @@ __export(exports_url_watcher, {
|
|
|
33440
34744
|
deleteWatchJob: () => deleteWatchJob,
|
|
33441
34745
|
createWatchJob: () => createWatchJob
|
|
33442
34746
|
});
|
|
33443
|
-
import { randomUUID as
|
|
34747
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
33444
34748
|
import { createHash } from "crypto";
|
|
33445
34749
|
function ensureWatchTables() {
|
|
33446
34750
|
const db2 = getDatabase();
|
|
@@ -33472,7 +34776,7 @@ function ensureWatchTables() {
|
|
|
33472
34776
|
function createWatchJob(url, schedule, opts) {
|
|
33473
34777
|
ensureWatchTables();
|
|
33474
34778
|
const db2 = getDatabase();
|
|
33475
|
-
const id =
|
|
34779
|
+
const id = randomUUID16();
|
|
33476
34780
|
db2.prepare(`
|
|
33477
34781
|
INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
|
|
33478
34782
|
VALUES (?, ?, ?, ?, ?, ?, 1)
|
|
@@ -33508,7 +34812,7 @@ function getWatchEvents(watchId, limit = 20) {
|
|
|
33508
34812
|
async function checkWatchJob(job) {
|
|
33509
34813
|
ensureWatchTables();
|
|
33510
34814
|
const db2 = getDatabase();
|
|
33511
|
-
const eventId =
|
|
34815
|
+
const eventId = randomUUID16();
|
|
33512
34816
|
const checkedAt = new Date().toISOString();
|
|
33513
34817
|
let newContent = "";
|
|
33514
34818
|
try {
|
|
@@ -33684,7 +34988,7 @@ var exports_mcp = {};
|
|
|
33684
34988
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
33685
34989
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
33686
34990
|
import { readFileSync as readFileSync8 } from "fs";
|
|
33687
|
-
import { join as
|
|
34991
|
+
import { join as join15 } from "path";
|
|
33688
34992
|
function json(data) {
|
|
33689
34993
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
33690
34994
|
}
|
|
@@ -33749,7 +35053,7 @@ var init_mcp = __esm(async () => {
|
|
|
33749
35053
|
init_dialogs();
|
|
33750
35054
|
init_profiles();
|
|
33751
35055
|
init_types();
|
|
33752
|
-
_pkg = JSON.parse(readFileSync8(
|
|
35056
|
+
_pkg = JSON.parse(readFileSync8(join15(import.meta.dir, "../../package.json"), "utf8"));
|
|
33753
35057
|
networkLogCleanup = new Map;
|
|
33754
35058
|
consoleCaptureCleanup = new Map;
|
|
33755
35059
|
harCaptures = new Map;
|
|
@@ -33757,7 +35061,7 @@ var init_mcp = __esm(async () => {
|
|
|
33757
35061
|
name: "@hasna/browser",
|
|
33758
35062
|
version: "0.0.1"
|
|
33759
35063
|
});
|
|
33760
|
-
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.", {
|
|
35064
|
+
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.", {
|
|
33761
35065
|
engine: exports_external.enum(["playwright", "cdp", "lightpanda", "bun", "auto"]).optional().default("auto"),
|
|
33762
35066
|
use_case: exports_external.string().optional(),
|
|
33763
35067
|
project_id: exports_external.string().optional(),
|
|
@@ -33768,9 +35072,11 @@ var init_mcp = __esm(async () => {
|
|
|
33768
35072
|
viewport_height: exports_external.number().optional().default(720),
|
|
33769
35073
|
stealth: exports_external.boolean().optional().default(false),
|
|
33770
35074
|
auto_gallery: exports_external.boolean().optional().default(false),
|
|
35075
|
+
storage_state: exports_external.string().optional().describe("Name of saved storage state to load (restores cookies/auth from previous session)"),
|
|
33771
35076
|
force_new: exports_external.boolean().optional().default(false).describe("Force create a new session even if agent already has one"),
|
|
33772
|
-
tags: exports_external.array(exports_external.string()).optional()
|
|
33773
|
-
|
|
35077
|
+
tags: exports_external.array(exports_external.string()).optional(),
|
|
35078
|
+
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")
|
|
35079
|
+
}, 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 }) => {
|
|
33774
35080
|
try {
|
|
33775
35081
|
if (agent_id && !force_new) {
|
|
33776
35082
|
const existing = getActiveSessionForAgent2(agent_id);
|
|
@@ -33786,7 +35092,9 @@ var init_mcp = __esm(async () => {
|
|
|
33786
35092
|
headless,
|
|
33787
35093
|
viewport: { width: viewport_width, height: viewport_height },
|
|
33788
35094
|
stealth,
|
|
33789
|
-
autoGallery: auto_gallery
|
|
35095
|
+
autoGallery: auto_gallery,
|
|
35096
|
+
storageState: storage_state,
|
|
35097
|
+
cdpUrl: cdp_url
|
|
33790
35098
|
});
|
|
33791
35099
|
if (tags?.length) {
|
|
33792
35100
|
const { addSessionTag: addSessionTag2 } = await Promise.resolve().then(() => (init_sessions(), exports_sessions));
|
|
@@ -33946,7 +35254,7 @@ var init_mcp = __esm(async () => {
|
|
|
33946
35254
|
return err(e);
|
|
33947
35255
|
}
|
|
33948
35256
|
});
|
|
33949
|
-
server.tool("browser_click", "Click an element by ref (from snapshot) or CSS selector. Prefer ref for reliability.", { 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() }, async ({ session_id, selector, ref, button, timeout }) => {
|
|
35257
|
+
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 }) => {
|
|
33950
35258
|
try {
|
|
33951
35259
|
const sid = resolveSessionId(session_id);
|
|
33952
35260
|
const page = getSessionPage(sid);
|
|
@@ -33957,14 +35265,17 @@ var init_mcp = __esm(async () => {
|
|
|
33957
35265
|
}
|
|
33958
35266
|
if (!selector)
|
|
33959
35267
|
return err(new Error("Either ref or selector is required"));
|
|
33960
|
-
await click(page, selector, { button, timeout });
|
|
33961
|
-
logEvent(sid, "click", { selector, method: "selector" });
|
|
35268
|
+
const healInfo = await click(page, selector, { button, timeout, selfHeal: self_heal });
|
|
35269
|
+
logEvent(sid, "click", { selector, method: healInfo.healed ? "healed" : "selector" });
|
|
35270
|
+
if (healInfo.healed) {
|
|
35271
|
+
return json({ clicked: selector, method: "healed", heal_method: healInfo.method, attempts: healInfo.attempts });
|
|
35272
|
+
}
|
|
33962
35273
|
return json({ clicked: selector, method: "selector" });
|
|
33963
35274
|
} catch (e) {
|
|
33964
35275
|
return errWithScreenshot(e, session_id);
|
|
33965
35276
|
}
|
|
33966
35277
|
});
|
|
33967
|
-
server.tool("browser_type", "Type text into an element by ref or selector. Prefer ref.", { 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() }, async ({ session_id, selector, ref, text, clear, delay }) => {
|
|
35278
|
+
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 }) => {
|
|
33968
35279
|
try {
|
|
33969
35280
|
const sid = resolveSessionId(session_id);
|
|
33970
35281
|
const page = getSessionPage(sid);
|
|
@@ -33975,8 +35286,11 @@ var init_mcp = __esm(async () => {
|
|
|
33975
35286
|
}
|
|
33976
35287
|
if (!selector)
|
|
33977
35288
|
return err(new Error("Either ref or selector is required"));
|
|
33978
|
-
await type(page, selector, text, { clear, delay });
|
|
33979
|
-
logEvent(sid, "type", { selector, text: text.slice(0, 100) });
|
|
35289
|
+
const healInfo = await type(page, selector, text, { clear, delay, selfHeal: self_heal });
|
|
35290
|
+
logEvent(sid, "type", { selector, text: text.slice(0, 100), method: healInfo.healed ? "healed" : "selector" });
|
|
35291
|
+
if (healInfo.healed) {
|
|
35292
|
+
return json({ typed: text, selector, method: "healed", heal_method: healInfo.method, attempts: healInfo.attempts });
|
|
35293
|
+
}
|
|
33980
35294
|
return json({ typed: text, selector, method: "selector" });
|
|
33981
35295
|
} catch (e) {
|
|
33982
35296
|
return errWithScreenshot(e, session_id);
|
|
@@ -34070,20 +35384,32 @@ var init_mcp = __esm(async () => {
|
|
|
34070
35384
|
return err(e);
|
|
34071
35385
|
}
|
|
34072
35386
|
});
|
|
34073
|
-
server.tool("browser_get_text", "Get text content from the page or a selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
|
|
35387
|
+
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 }) => {
|
|
34074
35388
|
try {
|
|
34075
35389
|
const sid = resolveSessionId(session_id);
|
|
34076
35390
|
const page = getSessionPage(sid);
|
|
34077
|
-
|
|
35391
|
+
const text = await getText(page, selector);
|
|
35392
|
+
if (sanitize) {
|
|
35393
|
+
const { sanitizeText: sanitizeText2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
35394
|
+
const sanitized = sanitizeText2(text);
|
|
35395
|
+
return json({ text: sanitized.text, stripped: sanitized.stripped, warnings: sanitized.warnings });
|
|
35396
|
+
}
|
|
35397
|
+
return json({ text });
|
|
34078
35398
|
} catch (e) {
|
|
34079
35399
|
return err(e);
|
|
34080
35400
|
}
|
|
34081
35401
|
});
|
|
34082
|
-
server.tool("browser_get_html", "Get HTML content from the page or a selector", { session_id: exports_external.string().optional(), selector: exports_external.string().optional() }, async ({ session_id, selector }) => {
|
|
35402
|
+
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 }) => {
|
|
34083
35403
|
try {
|
|
34084
35404
|
const sid = resolveSessionId(session_id);
|
|
34085
35405
|
const page = getSessionPage(sid);
|
|
34086
|
-
|
|
35406
|
+
const html = await getHTML(page, selector);
|
|
35407
|
+
if (sanitize) {
|
|
35408
|
+
const { sanitizeHTML: sanitizeHTML2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
35409
|
+
const sanitized = sanitizeHTML2(html);
|
|
35410
|
+
return json({ html: sanitized.text, stripped: sanitized.stripped, warnings: sanitized.warnings });
|
|
35411
|
+
}
|
|
35412
|
+
return json({ html });
|
|
34087
35413
|
} catch (e) {
|
|
34088
35414
|
return err(e);
|
|
34089
35415
|
}
|
|
@@ -34098,16 +35424,32 @@ var init_mcp = __esm(async () => {
|
|
|
34098
35424
|
return err(e);
|
|
34099
35425
|
}
|
|
34100
35426
|
});
|
|
34101
|
-
server.tool("browser_extract", "Extract content from the page in a specified format", {
|
|
35427
|
+
server.tool("browser_extract", "Extract content from the page in a specified format. Sanitizes prompt injection by default.", {
|
|
34102
35428
|
session_id: exports_external.string().optional(),
|
|
34103
35429
|
format: exports_external.enum(["text", "html", "links", "table", "structured"]).optional().default("text"),
|
|
34104
35430
|
selector: exports_external.string().optional(),
|
|
34105
|
-
schema: exports_external.record(exports_external.string()).optional()
|
|
34106
|
-
|
|
35431
|
+
schema: exports_external.record(exports_external.string()).optional(),
|
|
35432
|
+
sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns from extracted content (default: true)")
|
|
35433
|
+
}, async ({ session_id, format, selector, schema, sanitize }) => {
|
|
34107
35434
|
try {
|
|
34108
35435
|
const sid = resolveSessionId(session_id);
|
|
34109
35436
|
const page = getSessionPage(sid);
|
|
34110
35437
|
const result = await extract(page, { format, selector, schema });
|
|
35438
|
+
if (sanitize) {
|
|
35439
|
+
const { sanitizeText: sanitizeText2, sanitizeHTML: sanitizeHTML2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
35440
|
+
if (result.text) {
|
|
35441
|
+
const s = sanitizeText2(result.text);
|
|
35442
|
+
result.text = s.text;
|
|
35443
|
+
result.stripped = s.stripped;
|
|
35444
|
+
result.warnings = s.warnings;
|
|
35445
|
+
}
|
|
35446
|
+
if (result.html) {
|
|
35447
|
+
const s = sanitizeHTML2(result.html);
|
|
35448
|
+
result.html = s.text;
|
|
35449
|
+
result.stripped = s.stripped;
|
|
35450
|
+
result.warnings = s.warnings;
|
|
35451
|
+
}
|
|
35452
|
+
}
|
|
34111
35453
|
return json(result);
|
|
34112
35454
|
} catch (e) {
|
|
34113
35455
|
return err(e);
|
|
@@ -34124,17 +35466,27 @@ var init_mcp = __esm(async () => {
|
|
|
34124
35466
|
return err(e);
|
|
34125
35467
|
}
|
|
34126
35468
|
});
|
|
34127
|
-
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.", {
|
|
35469
|
+
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.", {
|
|
34128
35470
|
session_id: exports_external.string().optional(),
|
|
34129
35471
|
compact: exports_external.boolean().optional().default(true),
|
|
34130
35472
|
max_refs: exports_external.number().optional().default(50),
|
|
34131
|
-
full_tree: exports_external.boolean().optional().default(false)
|
|
34132
|
-
|
|
35473
|
+
full_tree: exports_external.boolean().optional().default(false),
|
|
35474
|
+
sanitize: exports_external.boolean().optional().default(true).describe("Strip prompt injection patterns from snapshot text (default: true)")
|
|
35475
|
+
}, async ({ session_id, compact, max_refs, full_tree, sanitize }) => {
|
|
34133
35476
|
try {
|
|
34134
35477
|
const sid = resolveSessionId(session_id);
|
|
34135
35478
|
const page = getSessionPage(sid);
|
|
34136
35479
|
const result = await takeSnapshot(page, sid);
|
|
34137
35480
|
setLastSnapshot(sid, result);
|
|
35481
|
+
let injection_warnings;
|
|
35482
|
+
if (sanitize) {
|
|
35483
|
+
const { sanitizeText: sanitizeText2 } = await Promise.resolve().then(() => (init_sanitize(), exports_sanitize));
|
|
35484
|
+
const sanitized = sanitizeText2(result.tree);
|
|
35485
|
+
if (sanitized.stripped > 0) {
|
|
35486
|
+
injection_warnings = sanitized.warnings;
|
|
35487
|
+
result.tree = sanitized.text;
|
|
35488
|
+
}
|
|
35489
|
+
}
|
|
34138
35490
|
const refEntries = Object.entries(result.refs).slice(0, max_refs);
|
|
34139
35491
|
const limitedRefs = Object.fromEntries(refEntries);
|
|
34140
35492
|
const truncated = Object.keys(result.refs).length > max_refs;
|
|
@@ -34146,27 +35498,29 @@ var init_mcp = __esm(async () => {
|
|
|
34146
35498
|
interactive_count: result.interactive_count,
|
|
34147
35499
|
shown_count: refEntries.length,
|
|
34148
35500
|
truncated,
|
|
34149
|
-
refs: limitedRefs
|
|
35501
|
+
refs: limitedRefs,
|
|
35502
|
+
...injection_warnings ? { injection_warnings } : {}
|
|
34150
35503
|
});
|
|
34151
35504
|
}
|
|
34152
35505
|
const tree = full_tree ? result.tree : result.tree.slice(0, 5000) + (result.tree.length > 5000 ? `
|
|
34153
35506
|
... (truncated \u2014 use full_tree=true for complete)` : "");
|
|
34154
|
-
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated });
|
|
35507
|
+
return json({ snapshot: tree, refs: limitedRefs, interactive_count: result.interactive_count, truncated, ...injection_warnings ? { injection_warnings } : {} });
|
|
34155
35508
|
} catch (e) {
|
|
34156
35509
|
return err(e);
|
|
34157
35510
|
}
|
|
34158
35511
|
});
|
|
34159
|
-
server.tool("browser_screenshot", "Take a screenshot. Use annotate=true to overlay numbered labels on interactive elements
|
|
35512
|
+
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.", {
|
|
34160
35513
|
session_id: exports_external.string().optional(),
|
|
34161
|
-
selector: exports_external.string().optional(),
|
|
35514
|
+
selector: exports_external.string().optional().describe("CSS selector to screenshot a specific section (e.g. '#main', '.header', 'form')"),
|
|
34162
35515
|
full_page: exports_external.boolean().optional().default(false),
|
|
34163
35516
|
format: exports_external.enum(["png", "jpeg", "webp"]).optional().default("webp"),
|
|
34164
35517
|
quality: exports_external.number().optional().default(60),
|
|
34165
35518
|
max_width: exports_external.number().optional().default(800),
|
|
34166
35519
|
compress: exports_external.boolean().optional().default(true),
|
|
34167
35520
|
thumbnail: exports_external.boolean().optional().default(true),
|
|
34168
|
-
annotate: exports_external.boolean().optional().default(false)
|
|
34169
|
-
|
|
35521
|
+
annotate: exports_external.boolean().optional().default(false),
|
|
35522
|
+
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).")
|
|
35523
|
+
}, async ({ session_id, selector, full_page, format, quality, max_width, compress, thumbnail, annotate, detail }) => {
|
|
34170
35524
|
try {
|
|
34171
35525
|
const sid = resolveSessionId(session_id);
|
|
34172
35526
|
const page = getSessionPage(sid);
|
|
@@ -34183,7 +35537,9 @@ var init_mcp = __esm(async () => {
|
|
|
34183
35537
|
annotation_count: annotated.annotations.length
|
|
34184
35538
|
});
|
|
34185
35539
|
}
|
|
34186
|
-
const
|
|
35540
|
+
const effectiveMaxWidth = detail === "high" ? 1280 : max_width;
|
|
35541
|
+
const effectiveQuality = detail === "high" ? 75 : quality;
|
|
35542
|
+
const result = await takeScreenshot(page, { selector, fullPage: full_page, format, quality: effectiveQuality, maxWidth: effectiveMaxWidth, compress, thumbnail });
|
|
34187
35543
|
result.url = page.url();
|
|
34188
35544
|
try {
|
|
34189
35545
|
const buf = Buffer.from(result.base64, "base64");
|
|
@@ -34192,12 +35548,12 @@ var init_mcp = __esm(async () => {
|
|
|
34192
35548
|
result.download_id = dl.id;
|
|
34193
35549
|
} catch {}
|
|
34194
35550
|
result.estimated_tokens = Math.ceil(result.base64.length / 4);
|
|
34195
|
-
if (result.base64.length >
|
|
35551
|
+
if (detail !== "high" && result.base64.length > 40000) {
|
|
34196
35552
|
result.base64_truncated = true;
|
|
34197
35553
|
result.full_image_path = result.path;
|
|
34198
35554
|
result.base64 = result.thumbnail_base64 ?? "";
|
|
34199
35555
|
}
|
|
34200
|
-
logEvent(sid, "screenshot", { path: result.path });
|
|
35556
|
+
logEvent(sid, "screenshot", { path: result.path, detail, selector });
|
|
34201
35557
|
return json(result);
|
|
34202
35558
|
} catch (e) {
|
|
34203
35559
|
return err(e);
|
|
@@ -34379,6 +35735,28 @@ var init_mcp = __esm(async () => {
|
|
|
34379
35735
|
return err(e);
|
|
34380
35736
|
}
|
|
34381
35737
|
});
|
|
35738
|
+
server.tool("browser_detect_env", "Detect if the current page is running in production, development, staging, or local environment. Analyzes URL, meta tags, source maps, analytics SDKs, and more.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
35739
|
+
try {
|
|
35740
|
+
const sid = resolveSessionId(session_id);
|
|
35741
|
+
const page = getSessionPage(sid);
|
|
35742
|
+
const { detectEnvironment: detectEnvironment2 } = await Promise.resolve().then(() => exports_env_detector);
|
|
35743
|
+
const result = await detectEnvironment2(page);
|
|
35744
|
+
return json(result);
|
|
35745
|
+
} catch (e) {
|
|
35746
|
+
return err(e);
|
|
35747
|
+
}
|
|
35748
|
+
});
|
|
35749
|
+
server.tool("browser_performance_deep", "Deep performance analysis: Web Vitals, resource breakdown by type, largest resources, third-party scripts with categories, DOM complexity, memory usage.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
35750
|
+
try {
|
|
35751
|
+
const sid = resolveSessionId(session_id);
|
|
35752
|
+
const page = getSessionPage(sid);
|
|
35753
|
+
const { getDeepPerformance: getDeepPerformance2 } = await Promise.resolve().then(() => (init_deep_performance(), exports_deep_performance));
|
|
35754
|
+
const result = await getDeepPerformance2(page);
|
|
35755
|
+
return json(result);
|
|
35756
|
+
} catch (e) {
|
|
35757
|
+
return err(e);
|
|
35758
|
+
}
|
|
35759
|
+
});
|
|
34382
35760
|
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 }) => {
|
|
34383
35761
|
try {
|
|
34384
35762
|
const sid = resolveSessionId(session_id);
|
|
@@ -34442,6 +35820,46 @@ var init_mcp = __esm(async () => {
|
|
|
34442
35820
|
return err(e);
|
|
34443
35821
|
}
|
|
34444
35822
|
});
|
|
35823
|
+
server.tool("browser_workflow_save", "Save a recording as a reusable workflow with self-healing replay", { recording_id: exports_external.string(), name: exports_external.string(), description: exports_external.string().optional() }, async ({ recording_id, name, description }) => {
|
|
35824
|
+
try {
|
|
35825
|
+
const { saveWorkflowFromRecording: saveWorkflowFromRecording2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35826
|
+
return json(saveWorkflowFromRecording2(recording_id, name, description));
|
|
35827
|
+
} catch (e) {
|
|
35828
|
+
return err(e);
|
|
35829
|
+
}
|
|
35830
|
+
});
|
|
35831
|
+
server.tool("browser_workflow_list", "List all saved workflows", {}, async () => {
|
|
35832
|
+
try {
|
|
35833
|
+
const { listWorkflows: listWorkflows2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35834
|
+
const workflows = listWorkflows2();
|
|
35835
|
+
return json({ workflows: workflows.map((w) => ({ ...w, steps: `${w.steps.length} steps` })), count: workflows.length });
|
|
35836
|
+
} catch (e) {
|
|
35837
|
+
return err(e);
|
|
35838
|
+
}
|
|
35839
|
+
});
|
|
35840
|
+
server.tool("browser_workflow_run", "Run a saved workflow with self-healing. If selectors changed, auto-adapts and reports what was healed.", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
|
|
35841
|
+
try {
|
|
35842
|
+
const sid = resolveSessionId(session_id);
|
|
35843
|
+
const page = getSessionPage(sid);
|
|
35844
|
+
const { getWorkflowByName: getWorkflowByName2, runWorkflow: runWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35845
|
+
const workflow = getWorkflowByName2(name);
|
|
35846
|
+
if (!workflow)
|
|
35847
|
+
return err(new Error(`Workflow '${name}' not found`));
|
|
35848
|
+
const result = await runWorkflow2(workflow, page);
|
|
35849
|
+
logEvent(sid, "workflow_run", { name, ...result });
|
|
35850
|
+
return json(result);
|
|
35851
|
+
} catch (e) {
|
|
35852
|
+
return err(e);
|
|
35853
|
+
}
|
|
35854
|
+
});
|
|
35855
|
+
server.tool("browser_workflow_delete", "Delete a saved workflow", { name: exports_external.string() }, async ({ name }) => {
|
|
35856
|
+
try {
|
|
35857
|
+
const { deleteWorkflow: deleteWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35858
|
+
return json({ deleted: deleteWorkflow2(name) });
|
|
35859
|
+
} catch (e) {
|
|
35860
|
+
return err(e);
|
|
35861
|
+
}
|
|
35862
|
+
});
|
|
34445
35863
|
server.tool("browser_crawl", "Crawl a URL recursively and return discovered pages", {
|
|
34446
35864
|
url: exports_external.string(),
|
|
34447
35865
|
max_depth: exports_external.number().optional().default(2),
|
|
@@ -34603,6 +36021,94 @@ var init_mcp = __esm(async () => {
|
|
|
34603
36021
|
return err(e);
|
|
34604
36022
|
}
|
|
34605
36023
|
});
|
|
36024
|
+
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 }) => {
|
|
36025
|
+
try {
|
|
36026
|
+
const sid = resolveSessionId(session_id);
|
|
36027
|
+
const page = getSessionPage(sid);
|
|
36028
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36029
|
+
const path = await saveStateFromPage2(page, name);
|
|
36030
|
+
return json({ saved: true, name, path });
|
|
36031
|
+
} catch (e) {
|
|
36032
|
+
return err(e);
|
|
36033
|
+
}
|
|
36034
|
+
});
|
|
36035
|
+
server.tool("browser_session_list_states", "List all saved storage states (auth snapshots)", {}, async () => {
|
|
36036
|
+
try {
|
|
36037
|
+
const { listStates: listStates2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36038
|
+
const states = listStates2();
|
|
36039
|
+
return json({ states, count: states.length });
|
|
36040
|
+
} catch (e) {
|
|
36041
|
+
return err(e);
|
|
36042
|
+
}
|
|
36043
|
+
});
|
|
36044
|
+
server.tool("browser_session_delete_state", "Delete a saved storage state", { name: exports_external.string() }, async ({ name }) => {
|
|
36045
|
+
try {
|
|
36046
|
+
const { deleteState: deleteState2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36047
|
+
return json({ deleted: deleteState2(name), name });
|
|
36048
|
+
} catch (e) {
|
|
36049
|
+
return err(e);
|
|
36050
|
+
}
|
|
36051
|
+
});
|
|
36052
|
+
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 }) => {
|
|
36053
|
+
try {
|
|
36054
|
+
const sid = resolveSessionId(session_id);
|
|
36055
|
+
const page = getSessionPage(sid);
|
|
36056
|
+
if (start_url)
|
|
36057
|
+
await navigate(page, start_url);
|
|
36058
|
+
const recording = startRecording(sid, `auth-${name}`, page.url());
|
|
36059
|
+
return json({ recording_id: recording.id, name, message: "Recording started. Perform login, then call browser_auth_stop." });
|
|
36060
|
+
} catch (e) {
|
|
36061
|
+
return err(e);
|
|
36062
|
+
}
|
|
36063
|
+
});
|
|
36064
|
+
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 }) => {
|
|
36065
|
+
try {
|
|
36066
|
+
const sid = resolveSessionId(session_id);
|
|
36067
|
+
const page = getSessionPage(sid);
|
|
36068
|
+
const recording = stopRecording(recording_id);
|
|
36069
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
36070
|
+
const statePath2 = await saveStateFromPage2(page, name);
|
|
36071
|
+
let domain = "";
|
|
36072
|
+
try {
|
|
36073
|
+
domain = new URL(page.url()).hostname;
|
|
36074
|
+
} catch {}
|
|
36075
|
+
const { saveAuthFlow: saveAuthFlow2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
36076
|
+
const flow = saveAuthFlow2({ name, domain, recordingId: recording.id, storageStatePath: statePath2 });
|
|
36077
|
+
return json({ flow, recording_steps: recording.steps.length });
|
|
36078
|
+
} catch (e) {
|
|
36079
|
+
return err(e);
|
|
36080
|
+
}
|
|
36081
|
+
});
|
|
36082
|
+
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 }) => {
|
|
36083
|
+
try {
|
|
36084
|
+
const sid = resolveSessionId(session_id);
|
|
36085
|
+
const page = getSessionPage(sid);
|
|
36086
|
+
const { getAuthFlowByName: getAuthFlowByName2, tryReplayAuth: tryReplayAuth2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
36087
|
+
const flow = getAuthFlowByName2(name);
|
|
36088
|
+
if (!flow)
|
|
36089
|
+
return err(new Error(`Auth flow '${name}' not found`));
|
|
36090
|
+
const result = await tryReplayAuth2(page, flow.domain);
|
|
36091
|
+
return json(result);
|
|
36092
|
+
} catch (e) {
|
|
36093
|
+
return err(e);
|
|
36094
|
+
}
|
|
36095
|
+
});
|
|
36096
|
+
server.tool("browser_auth_list", "List all saved auth flows", {}, async () => {
|
|
36097
|
+
try {
|
|
36098
|
+
const { listAuthFlows: listAuthFlows2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
36099
|
+
return json({ flows: listAuthFlows2() });
|
|
36100
|
+
} catch (e) {
|
|
36101
|
+
return err(e);
|
|
36102
|
+
}
|
|
36103
|
+
});
|
|
36104
|
+
server.tool("browser_auth_delete", "Delete a saved auth flow", { name: exports_external.string() }, async ({ name }) => {
|
|
36105
|
+
try {
|
|
36106
|
+
const { deleteAuthFlow: deleteAuthFlow2 } = await Promise.resolve().then(() => (init_auth_flow(), exports_auth_flow));
|
|
36107
|
+
return json({ deleted: deleteAuthFlow2(name) });
|
|
36108
|
+
} catch (e) {
|
|
36109
|
+
return err(e);
|
|
36110
|
+
}
|
|
36111
|
+
});
|
|
34606
36112
|
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 }) => {
|
|
34607
36113
|
try {
|
|
34608
36114
|
const sid = resolveSessionId(session_id);
|
|
@@ -34613,20 +36119,45 @@ var init_mcp = __esm(async () => {
|
|
|
34613
36119
|
return err(e);
|
|
34614
36120
|
}
|
|
34615
36121
|
});
|
|
34616
|
-
server.tool("browser_fill_form", "Fill multiple form fields in one call. Fields map: { selector: value }. Handles text, checkboxes, selects.", {
|
|
36122
|
+
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.", {
|
|
34617
36123
|
session_id: exports_external.string().optional(),
|
|
34618
36124
|
fields: exports_external.record(exports_external.union([exports_external.string(), exports_external.boolean()])),
|
|
34619
|
-
submit_selector: exports_external.string().optional()
|
|
34620
|
-
|
|
36125
|
+
submit_selector: exports_external.string().optional(),
|
|
36126
|
+
self_heal: exports_external.boolean().optional().default(true).describe("Auto-try fallback selectors if element not found")
|
|
36127
|
+
}, async ({ session_id, fields, submit_selector, self_heal }) => {
|
|
34621
36128
|
try {
|
|
34622
36129
|
const sid = resolveSessionId(session_id);
|
|
34623
36130
|
const page = getSessionPage(sid);
|
|
34624
|
-
const result = await fillForm(page, fields, submit_selector);
|
|
36131
|
+
const result = await fillForm(page, fields, submit_selector, self_heal);
|
|
34625
36132
|
return json(result);
|
|
34626
36133
|
} catch (e) {
|
|
34627
36134
|
return errWithScreenshot(e, session_id);
|
|
34628
36135
|
}
|
|
34629
36136
|
});
|
|
36137
|
+
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.", {
|
|
36138
|
+
session_id: exports_external.string().optional(),
|
|
36139
|
+
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')"),
|
|
36140
|
+
click: exports_external.boolean().optional().default(false).describe("Click the element after finding it"),
|
|
36141
|
+
model: exports_external.string().optional().describe("Vision model to use (default: claude-sonnet-4-5-20250929)")
|
|
36142
|
+
}, async ({ session_id, description, click: doClick, model }) => {
|
|
36143
|
+
try {
|
|
36144
|
+
const sid = resolveSessionId(session_id);
|
|
36145
|
+
const page = getSessionPage(sid);
|
|
36146
|
+
if (doClick) {
|
|
36147
|
+
const { clickByVision: clickByVision2 } = await Promise.resolve().then(() => exports_vision_fallback);
|
|
36148
|
+
const result = await clickByVision2(page, description, { model });
|
|
36149
|
+
logEvent(sid, "vision_click", { query: description, ...result });
|
|
36150
|
+
return json(result);
|
|
36151
|
+
} else {
|
|
36152
|
+
const { findElementByVision: findElementByVision2 } = await Promise.resolve().then(() => exports_vision_fallback);
|
|
36153
|
+
const result = await findElementByVision2(page, description, { model });
|
|
36154
|
+
logEvent(sid, "vision_find", { query: description, ...result });
|
|
36155
|
+
return json(result);
|
|
36156
|
+
}
|
|
36157
|
+
} catch (e) {
|
|
36158
|
+
return err(e);
|
|
36159
|
+
}
|
|
36160
|
+
});
|
|
34630
36161
|
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 }) => {
|
|
34631
36162
|
try {
|
|
34632
36163
|
const sid = resolveSessionId(session_id);
|
|
@@ -35024,6 +36555,68 @@ var init_mcp = __esm(async () => {
|
|
|
35024
36555
|
return err(e);
|
|
35025
36556
|
}
|
|
35026
36557
|
});
|
|
36558
|
+
server.tool("browser_detect_apis", "Scan network traffic for JSON API endpoints. Returns discovered endpoints with methods, status codes, and URLs.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
36559
|
+
try {
|
|
36560
|
+
const sid = resolveSessionId(session_id);
|
|
36561
|
+
const { detectAPIs: detectAPIs2 } = await Promise.resolve().then(() => (init_api_detector(), exports_api_detector));
|
|
36562
|
+
const apis = detectAPIs2(sid);
|
|
36563
|
+
return json({ apis, count: apis.length });
|
|
36564
|
+
} catch (e) {
|
|
36565
|
+
return err(e);
|
|
36566
|
+
}
|
|
36567
|
+
});
|
|
36568
|
+
server.tool("browser_extract_structured", "Extract structured data from page: tables, lists, JSON-LD, Open Graph, meta tags, and repeated elements (cards/items).", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
36569
|
+
try {
|
|
36570
|
+
const sid = resolveSessionId(session_id);
|
|
36571
|
+
const page = getSessionPage(sid);
|
|
36572
|
+
const { extractStructuredData: extractStructuredData2 } = await Promise.resolve().then(() => exports_structured_extract);
|
|
36573
|
+
const data = await extractStructuredData2(page);
|
|
36574
|
+
return json({
|
|
36575
|
+
tables: data.tables.length,
|
|
36576
|
+
lists: data.lists.length,
|
|
36577
|
+
json_ld: data.jsonLd.length,
|
|
36578
|
+
open_graph: Object.keys(data.openGraph).length,
|
|
36579
|
+
meta_tags: Object.keys(data.metaTags).length,
|
|
36580
|
+
repeated_elements: data.repeatedElements.length,
|
|
36581
|
+
data
|
|
36582
|
+
});
|
|
36583
|
+
} catch (e) {
|
|
36584
|
+
return err(e);
|
|
36585
|
+
}
|
|
36586
|
+
});
|
|
36587
|
+
server.tool("browser_dataset_save", "Save extracted data as a named dataset for later use", { name: exports_external.string(), data: exports_external.array(exports_external.record(exports_external.unknown())), source_url: exports_external.string().optional() }, async ({ name, data, source_url }) => {
|
|
36588
|
+
try {
|
|
36589
|
+
const { saveDataset: saveDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36590
|
+
const dataset = saveDataset2({ name, rows: data, sourceUrl: source_url });
|
|
36591
|
+
return json({ id: dataset.id, name: dataset.name, row_count: dataset.row_count });
|
|
36592
|
+
} catch (e) {
|
|
36593
|
+
return err(e);
|
|
36594
|
+
}
|
|
36595
|
+
});
|
|
36596
|
+
server.tool("browser_dataset_list", "List all saved datasets", {}, async () => {
|
|
36597
|
+
try {
|
|
36598
|
+
const { listDatasets: listDatasets2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36599
|
+
return json({ datasets: listDatasets2() });
|
|
36600
|
+
} catch (e) {
|
|
36601
|
+
return err(e);
|
|
36602
|
+
}
|
|
36603
|
+
});
|
|
36604
|
+
server.tool("browser_dataset_export", "Export a dataset as JSON or CSV file", { name: exports_external.string(), format: exports_external.enum(["json", "csv"]).optional().default("json") }, async ({ name, format }) => {
|
|
36605
|
+
try {
|
|
36606
|
+
const { exportDataset: exportDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36607
|
+
return json(exportDataset2(name, format));
|
|
36608
|
+
} catch (e) {
|
|
36609
|
+
return err(e);
|
|
36610
|
+
}
|
|
36611
|
+
});
|
|
36612
|
+
server.tool("browser_dataset_delete", "Delete a saved dataset", { name: exports_external.string() }, async ({ name }) => {
|
|
36613
|
+
try {
|
|
36614
|
+
const { deleteDataset: deleteDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36615
|
+
return json({ deleted: deleteDataset2(name) });
|
|
36616
|
+
} catch (e) {
|
|
36617
|
+
return err(e);
|
|
36618
|
+
}
|
|
36619
|
+
});
|
|
35027
36620
|
server.tool("browser_help", "Show all available browser tools grouped by category with one-line descriptions", {}, async () => {
|
|
35028
36621
|
try {
|
|
35029
36622
|
const groups = {
|
|
@@ -35047,6 +36640,7 @@ var init_mcp = __esm(async () => {
|
|
|
35047
36640
|
{ tool: "browser_wait", description: "Wait for a selector to appear" },
|
|
35048
36641
|
{ tool: "browser_wait_for_text", description: "Wait for text to appear" },
|
|
35049
36642
|
{ tool: "browser_fill_form", description: "Fill multiple form fields at once" },
|
|
36643
|
+
{ tool: "browser_find_visual", description: "Find element using AI vision (for canvas, images, custom widgets)" },
|
|
35050
36644
|
{ tool: "browser_handle_dialog", description: "Accept or dismiss a dialog" }
|
|
35051
36645
|
],
|
|
35052
36646
|
Extraction: [
|
|
@@ -35075,7 +36669,10 @@ var init_mcp = __esm(async () => {
|
|
|
35075
36669
|
{ tool: "browser_profile_save", description: "Save cookies + localStorage as profile" },
|
|
35076
36670
|
{ tool: "browser_profile_load", description: "Load and apply a saved profile" },
|
|
35077
36671
|
{ tool: "browser_profile_list", description: "List saved profiles" },
|
|
35078
|
-
{ tool: "browser_profile_delete", description: "Delete a saved profile" }
|
|
36672
|
+
{ tool: "browser_profile_delete", description: "Delete a saved profile" },
|
|
36673
|
+
{ tool: "browser_session_save_state", description: "Save auth state (Playwright storageState) for reuse" },
|
|
36674
|
+
{ tool: "browser_session_list_states", description: "List saved storage states" },
|
|
36675
|
+
{ tool: "browser_session_delete_state", description: "Delete a saved storage state" }
|
|
35079
36676
|
],
|
|
35080
36677
|
Network: [
|
|
35081
36678
|
{ tool: "browser_network_log", description: "Get captured network requests" },
|
|
@@ -35099,6 +36696,27 @@ var init_mcp = __esm(async () => {
|
|
|
35099
36696
|
{ tool: "browser_record_replay", description: "Replay a recorded sequence" },
|
|
35100
36697
|
{ tool: "browser_recordings_list", description: "List all recordings" }
|
|
35101
36698
|
],
|
|
36699
|
+
Auth: [
|
|
36700
|
+
{ tool: "browser_auth_record", description: "Start recording a login flow" },
|
|
36701
|
+
{ tool: "browser_auth_stop", description: "Stop recording and save auth flow" },
|
|
36702
|
+
{ tool: "browser_auth_replay", description: "Replay a saved auth flow" },
|
|
36703
|
+
{ tool: "browser_auth_list", description: "List all saved auth flows" },
|
|
36704
|
+
{ tool: "browser_auth_delete", description: "Delete a saved auth flow" }
|
|
36705
|
+
],
|
|
36706
|
+
Workflows: [
|
|
36707
|
+
{ tool: "browser_workflow_save", description: "Save a recording as a reusable workflow" },
|
|
36708
|
+
{ tool: "browser_workflow_list", description: "List all saved workflows" },
|
|
36709
|
+
{ tool: "browser_workflow_run", description: "Run a workflow with self-healing replay" },
|
|
36710
|
+
{ tool: "browser_workflow_delete", description: "Delete a saved workflow" }
|
|
36711
|
+
],
|
|
36712
|
+
Data: [
|
|
36713
|
+
{ tool: "browser_extract_structured", description: "Extract tables, lists, JSON-LD, Open Graph, meta tags, repeated elements" },
|
|
36714
|
+
{ tool: "browser_detect_apis", description: "Scan network traffic for JSON API endpoints" },
|
|
36715
|
+
{ tool: "browser_dataset_save", description: "Save extracted data as a named dataset" },
|
|
36716
|
+
{ tool: "browser_dataset_list", description: "List all saved datasets" },
|
|
36717
|
+
{ tool: "browser_dataset_export", description: "Export dataset as JSON or CSV" },
|
|
36718
|
+
{ tool: "browser_dataset_delete", description: "Delete a saved dataset" }
|
|
36719
|
+
],
|
|
35102
36720
|
Crawl: [
|
|
35103
36721
|
{ tool: "browser_crawl", description: "Crawl a URL recursively" }
|
|
35104
36722
|
],
|
|
@@ -35152,10 +36770,13 @@ var init_mcp = __esm(async () => {
|
|
|
35152
36770
|
{ tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
|
|
35153
36771
|
{ tool: "browser_version", description: "Show running binary version and tool count" },
|
|
35154
36772
|
{ tool: "browser_help", description: "Show this help (all tools)" },
|
|
36773
|
+
{ tool: "browser_detect_env", description: "Detect environment (prod/dev/staging/local)" },
|
|
36774
|
+
{ tool: "browser_performance_deep", description: "Deep performance: resources, third-party, DOM, memory" },
|
|
35155
36775
|
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
35156
36776
|
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
35157
36777
|
{ tool: "browser_watch_get_changes", description: "Get captured DOM changes" },
|
|
35158
|
-
{ tool: "browser_watch_stop", description: "Stop DOM watcher" }
|
|
36778
|
+
{ tool: "browser_watch_stop", description: "Stop DOM watcher" },
|
|
36779
|
+
{ tool: "browser_parallel", description: "Execute actions across multiple sessions in parallel" }
|
|
35159
36780
|
]
|
|
35160
36781
|
};
|
|
35161
36782
|
const totalTools = Object.values(groups).reduce((sum, g) => sum + g.length, 0);
|
|
@@ -35438,6 +37059,82 @@ var init_mcp = __esm(async () => {
|
|
|
35438
37059
|
return err(e);
|
|
35439
37060
|
}
|
|
35440
37061
|
});
|
|
37062
|
+
server.tool("browser_parallel", "Execute actions across multiple sessions in parallel. Each action targets a different session. Returns results array.", {
|
|
37063
|
+
actions: exports_external.array(exports_external.object({
|
|
37064
|
+
session_id: exports_external.string().describe("Target session ID"),
|
|
37065
|
+
tool: exports_external.string().describe("Tool name (e.g. browser_navigate, browser_screenshot, browser_click)"),
|
|
37066
|
+
args: exports_external.record(exports_external.unknown()).optional().default({})
|
|
37067
|
+
})),
|
|
37068
|
+
timeout: exports_external.number().optional().default(30000).describe("Timeout per action in ms")
|
|
37069
|
+
}, async ({ actions, timeout }) => {
|
|
37070
|
+
try {
|
|
37071
|
+
const t0 = Date.now();
|
|
37072
|
+
const promises = actions.map(async (action, index) => {
|
|
37073
|
+
try {
|
|
37074
|
+
const sid = action.session_id;
|
|
37075
|
+
const page = getSessionPage(sid);
|
|
37076
|
+
const args = action.args;
|
|
37077
|
+
const toolName = action.tool.replace(/^browser_/, "");
|
|
37078
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout));
|
|
37079
|
+
const actionPromise = (async () => {
|
|
37080
|
+
switch (toolName) {
|
|
37081
|
+
case "navigate": {
|
|
37082
|
+
await navigate(page, args.url);
|
|
37083
|
+
const title = await page.title();
|
|
37084
|
+
return { url: page.url(), title };
|
|
37085
|
+
}
|
|
37086
|
+
case "screenshot": {
|
|
37087
|
+
const result2 = await takeScreenshot(page, {
|
|
37088
|
+
maxWidth: args.max_width ?? 800,
|
|
37089
|
+
quality: args.quality ?? 60
|
|
37090
|
+
});
|
|
37091
|
+
return { path: result2.path, size_bytes: result2.size_bytes };
|
|
37092
|
+
}
|
|
37093
|
+
case "click": {
|
|
37094
|
+
if (args.selector)
|
|
37095
|
+
await click(page, args.selector);
|
|
37096
|
+
return { clicked: args.selector };
|
|
37097
|
+
}
|
|
37098
|
+
case "type": {
|
|
37099
|
+
if (args.selector && args.text)
|
|
37100
|
+
await type(page, args.selector, args.text);
|
|
37101
|
+
return { typed: args.text };
|
|
37102
|
+
}
|
|
37103
|
+
case "get_text": {
|
|
37104
|
+
const text = await getText(page);
|
|
37105
|
+
return { text: text.slice(0, 1000), length: text.length };
|
|
37106
|
+
}
|
|
37107
|
+
case "get_links": {
|
|
37108
|
+
const links = await getLinks(page);
|
|
37109
|
+
return { links, count: links.length };
|
|
37110
|
+
}
|
|
37111
|
+
case "snapshot": {
|
|
37112
|
+
const snap = await takeSnapshot(page, sid);
|
|
37113
|
+
return { interactive_count: snap.interactive_count, refs_count: Object.keys(snap.refs).length };
|
|
37114
|
+
}
|
|
37115
|
+
case "evaluate": {
|
|
37116
|
+
const result2 = await page.evaluate(args.expression);
|
|
37117
|
+
return { result: result2 };
|
|
37118
|
+
}
|
|
37119
|
+
default:
|
|
37120
|
+
return { error: `Unknown tool: ${action.tool}` };
|
|
37121
|
+
}
|
|
37122
|
+
})();
|
|
37123
|
+
const result = await Promise.race([actionPromise, timeoutPromise]);
|
|
37124
|
+
return { index, session_id: sid, tool: action.tool, success: true, result };
|
|
37125
|
+
} catch (e) {
|
|
37126
|
+
return { index, session_id: action.session_id, tool: action.tool, success: false, error: e instanceof Error ? e.message : String(e) };
|
|
37127
|
+
}
|
|
37128
|
+
});
|
|
37129
|
+
const results = await Promise.all(promises);
|
|
37130
|
+
const duration_ms = Date.now() - t0;
|
|
37131
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
37132
|
+
const failed = results.filter((r) => !r.success).length;
|
|
37133
|
+
return json({ results, duration_ms, succeeded, failed, total: actions.length });
|
|
37134
|
+
} catch (e) {
|
|
37135
|
+
return err(e);
|
|
37136
|
+
}
|
|
37137
|
+
});
|
|
35441
37138
|
server.tool("browser_pool_status", "Get status of the pre-warmed browser session pool.", {}, async () => {
|
|
35442
37139
|
try {
|
|
35443
37140
|
return json({ message: "Session pool not yet implemented in this version. Coming in v0.0.6+", ready: 0, total: 0 });
|
|
@@ -35592,10 +37289,10 @@ __export(exports_snapshots, {
|
|
|
35592
37289
|
deleteSnapshot: () => deleteSnapshot,
|
|
35593
37290
|
createSnapshot: () => createSnapshot
|
|
35594
37291
|
});
|
|
35595
|
-
import { randomUUID as
|
|
37292
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
35596
37293
|
function createSnapshot(data) {
|
|
35597
37294
|
const db2 = getDatabase();
|
|
35598
|
-
const id =
|
|
37295
|
+
const id = randomUUID17();
|
|
35599
37296
|
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);
|
|
35600
37297
|
return getSnapshot(id);
|
|
35601
37298
|
}
|
|
@@ -35621,8 +37318,8 @@ var init_snapshots = __esm(() => {
|
|
|
35621
37318
|
|
|
35622
37319
|
// src/server/index.ts
|
|
35623
37320
|
var exports_server = {};
|
|
35624
|
-
import { join as
|
|
35625
|
-
import { existsSync as
|
|
37321
|
+
import { join as join16 } from "path";
|
|
37322
|
+
import { existsSync as existsSync9 } from "fs";
|
|
35626
37323
|
function ok(data, status = 200) {
|
|
35627
37324
|
return new Response(JSON.stringify(data), {
|
|
35628
37325
|
status,
|
|
@@ -35872,14 +37569,14 @@ var init_server = __esm(() => {
|
|
|
35872
37569
|
if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
|
|
35873
37570
|
const id = path.split("/")[3];
|
|
35874
37571
|
const entry = getEntry(id);
|
|
35875
|
-
if (!entry?.thumbnail_path || !
|
|
37572
|
+
if (!entry?.thumbnail_path || !existsSync9(entry.thumbnail_path))
|
|
35876
37573
|
return notFound("Thumbnail not found");
|
|
35877
37574
|
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
|
|
35878
37575
|
}
|
|
35879
37576
|
if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
|
|
35880
37577
|
const id = path.split("/")[3];
|
|
35881
37578
|
const entry = getEntry(id);
|
|
35882
|
-
if (!entry?.path || !
|
|
37579
|
+
if (!entry?.path || !existsSync9(entry.path))
|
|
35883
37580
|
return notFound("Image not found");
|
|
35884
37581
|
return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
|
|
35885
37582
|
}
|
|
@@ -35907,7 +37604,7 @@ var init_server = __esm(() => {
|
|
|
35907
37604
|
if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
|
|
35908
37605
|
const id = path.split("/")[3];
|
|
35909
37606
|
const file = getDownload(id);
|
|
35910
|
-
if (!file || !
|
|
37607
|
+
if (!file || !existsSync9(file.path))
|
|
35911
37608
|
return notFound("Download not found");
|
|
35912
37609
|
return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
|
|
35913
37610
|
}
|
|
@@ -35915,13 +37612,13 @@ var init_server = __esm(() => {
|
|
|
35915
37612
|
const id = path.split("/")[3];
|
|
35916
37613
|
return ok({ deleted: deleteDownload(id) });
|
|
35917
37614
|
}
|
|
35918
|
-
const dashboardDist =
|
|
35919
|
-
if (
|
|
35920
|
-
const filePath = path === "/" ?
|
|
35921
|
-
if (
|
|
37615
|
+
const dashboardDist = join16(import.meta.dir, "../../dashboard/dist");
|
|
37616
|
+
if (existsSync9(dashboardDist)) {
|
|
37617
|
+
const filePath = path === "/" ? join16(dashboardDist, "index.html") : join16(dashboardDist, path);
|
|
37618
|
+
if (existsSync9(filePath)) {
|
|
35922
37619
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
35923
37620
|
}
|
|
35924
|
-
return new Response(Bun.file(
|
|
37621
|
+
return new Response(Bun.file(join16(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
35925
37622
|
}
|
|
35926
37623
|
if (path === "/" || path === "") {
|
|
35927
37624
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
@@ -35964,9 +37661,9 @@ init_recorder();
|
|
|
35964
37661
|
init_recordings();
|
|
35965
37662
|
init_lightpanda();
|
|
35966
37663
|
import { readFileSync as readFileSync9 } from "fs";
|
|
35967
|
-
import { join as
|
|
37664
|
+
import { join as join17 } from "path";
|
|
35968
37665
|
import chalk from "chalk";
|
|
35969
|
-
var pkg = JSON.parse(readFileSync9(
|
|
37666
|
+
var pkg = JSON.parse(readFileSync9(join17(import.meta.dir, "../../package.json"), "utf8"));
|
|
35970
37667
|
var program2 = new Command;
|
|
35971
37668
|
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
|
|
35972
37669
|
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) => {
|
|
@@ -36100,6 +37797,22 @@ sessionCmd.command("close <id>").description("Close a session").action(async (id
|
|
|
36100
37797
|
await closeSession2(id);
|
|
36101
37798
|
console.log(chalk.green(`\u2713 Session closed: ${id}`));
|
|
36102
37799
|
});
|
|
37800
|
+
sessionCmd.command("save-state <name>").description("Save current session auth state for reuse").requiredOption("--session <id>", "Session ID").action(async (name, opts) => {
|
|
37801
|
+
const page = getSessionPage(opts.session);
|
|
37802
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
37803
|
+
const path = await saveStateFromPage2(page, name);
|
|
37804
|
+
console.log(chalk.green(`\u2713 State saved: ${name}`));
|
|
37805
|
+
console.log(chalk.gray(` Path: ${path}`));
|
|
37806
|
+
});
|
|
37807
|
+
sessionCmd.command("list-states").description("List saved auth states").action(async () => {
|
|
37808
|
+
const { listStates: listStates2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
37809
|
+
const states = listStates2();
|
|
37810
|
+
if (states.length === 0) {
|
|
37811
|
+
console.log(chalk.gray("No saved states"));
|
|
37812
|
+
return;
|
|
37813
|
+
}
|
|
37814
|
+
states.forEach((s) => console.log(`${s.name} ${chalk.gray(s.modified)}`));
|
|
37815
|
+
});
|
|
36103
37816
|
var recordCmd = program2.command("record").description("Manage action recordings");
|
|
36104
37817
|
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) => {
|
|
36105
37818
|
const { session } = await createSession2({ engine: opts.engine, startUrl: opts.url, headless: !opts.headed });
|
|
@@ -36162,6 +37875,19 @@ projectCmd.command("list").description("List all projects").action(() => {
|
|
|
36162
37875
|
projects.forEach((p) => console.log(`${p.id} "${p.name}" ${p.path}`));
|
|
36163
37876
|
}
|
|
36164
37877
|
});
|
|
37878
|
+
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) => {
|
|
37879
|
+
const cdpUrl = `http://${opts.host}:${opts.port}`;
|
|
37880
|
+
const { session, page } = await createSession2({ cdpUrl });
|
|
37881
|
+
const title = await page.title();
|
|
37882
|
+
const url = page.url();
|
|
37883
|
+
if (opts.json) {
|
|
37884
|
+
console.log(JSON.stringify({ session_id: session.id, url, title, cdp_url: cdpUrl }));
|
|
37885
|
+
} else {
|
|
37886
|
+
console.log(chalk.green(`\u2713 Attached to Chrome at ${cdpUrl}`));
|
|
37887
|
+
console.log(chalk.blue(` Session: ${session.id}`));
|
|
37888
|
+
console.log(chalk.blue(` Page: ${title} (${url})`));
|
|
37889
|
+
}
|
|
37890
|
+
});
|
|
36165
37891
|
program2.command("install-browser").description("Install a browser engine").option("--engine <engine>", "Engine to install: lightpanda|chromium", "chromium").action(async (opts) => {
|
|
36166
37892
|
if (opts.engine === "chromium") {
|
|
36167
37893
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -36253,11 +37979,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
|
|
|
36253
37979
|
});
|
|
36254
37980
|
galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
|
|
36255
37981
|
const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
36256
|
-
const { existsSync:
|
|
37982
|
+
const { existsSync: existsSync10 } = await import("fs");
|
|
36257
37983
|
const entries = listEntries2({ limit: 9999 });
|
|
36258
37984
|
let removed = 0;
|
|
36259
37985
|
for (const e of entries) {
|
|
36260
|
-
if (!
|
|
37986
|
+
if (!existsSync10(e.path)) {
|
|
36261
37987
|
deleteEntry2(e.id);
|
|
36262
37988
|
removed++;
|
|
36263
37989
|
}
|