@hasna/browser 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1834 -439
- package/dist/index.js +48 -0
- package/dist/lib/api-detector.d.ts +17 -0
- package/dist/lib/api-detector.d.ts.map +1 -0
- package/dist/lib/daemon-client.d.ts +16 -0
- package/dist/lib/daemon-client.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/structured-extract.d.ts +26 -0
- package/dist/lib/structured-extract.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 +1076 -86
- package/dist/server/index.js +57 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2358,6 +2358,54 @@ function runMigrations(db) {
|
|
|
2358
2358
|
CREATE INDEX IF NOT EXISTS idx_auth_flows_domain ON auth_flows(domain);
|
|
2359
2359
|
CREATE INDEX IF NOT EXISTS idx_auth_flows_name ON auth_flows(name);
|
|
2360
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
|
+
`
|
|
2361
2409
|
}
|
|
2362
2410
|
];
|
|
2363
2411
|
for (const m of migrations) {
|
|
@@ -3322,14 +3370,14 @@ function enableConsoleCapture(page, sessionId) {
|
|
|
3322
3370
|
warning: "warn"
|
|
3323
3371
|
};
|
|
3324
3372
|
const level = levelMap[msg.type()] ?? "log";
|
|
3325
|
-
const
|
|
3373
|
+
const location2 = msg.location();
|
|
3326
3374
|
try {
|
|
3327
3375
|
logConsoleMessage({
|
|
3328
3376
|
session_id: sessionId,
|
|
3329
3377
|
level,
|
|
3330
3378
|
message: msg.text(),
|
|
3331
|
-
source:
|
|
3332
|
-
line_number:
|
|
3379
|
+
source: location2.url || undefined,
|
|
3380
|
+
line_number: location2.lineNumber || undefined
|
|
3333
3381
|
});
|
|
3334
3382
|
} catch {}
|
|
3335
3383
|
};
|
|
@@ -12116,6 +12164,743 @@ var init_recorder = __esm(() => {
|
|
|
12116
12164
|
activeRecordings = new Map;
|
|
12117
12165
|
});
|
|
12118
12166
|
|
|
12167
|
+
// src/lib/env-detector.ts
|
|
12168
|
+
var exports_env_detector = {};
|
|
12169
|
+
__export(exports_env_detector, {
|
|
12170
|
+
detectEnvironment: () => detectEnvironment
|
|
12171
|
+
});
|
|
12172
|
+
async function detectEnvironment(page) {
|
|
12173
|
+
const url = page.url();
|
|
12174
|
+
const signals = [];
|
|
12175
|
+
let score = { local: 0, dev: 0, staging: 0, prod: 0 };
|
|
12176
|
+
try {
|
|
12177
|
+
const u = new URL(url);
|
|
12178
|
+
if (u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "0.0.0.0" || u.hostname.endsWith(".local")) {
|
|
12179
|
+
score.local += 5;
|
|
12180
|
+
signals.push(`URL hostname: ${u.hostname} \u2192 local`);
|
|
12181
|
+
} else if (u.hostname.match(/^(dev|development)\./i) || u.port !== "") {
|
|
12182
|
+
score.dev += 4;
|
|
12183
|
+
signals.push(`URL pattern: ${u.hostname}:${u.port} \u2192 dev`);
|
|
12184
|
+
} else if (u.hostname.match(/^(staging|stg|stage|preprod|uat)\./i)) {
|
|
12185
|
+
score.staging += 4;
|
|
12186
|
+
signals.push(`URL pattern: ${u.hostname} \u2192 staging`);
|
|
12187
|
+
} else {
|
|
12188
|
+
score.prod += 2;
|
|
12189
|
+
signals.push(`URL looks production: ${u.hostname}`);
|
|
12190
|
+
}
|
|
12191
|
+
if (u.port && !["80", "443", ""].includes(u.port)) {
|
|
12192
|
+
score.dev += 2;
|
|
12193
|
+
signals.push(`Non-standard port: ${u.port}`);
|
|
12194
|
+
}
|
|
12195
|
+
if (u.protocol === "https:") {
|
|
12196
|
+
score.prod += 1;
|
|
12197
|
+
signals.push("HTTPS \u2192 likely prod");
|
|
12198
|
+
} else {
|
|
12199
|
+
score.dev += 2;
|
|
12200
|
+
signals.push("HTTP \u2192 likely dev/local");
|
|
12201
|
+
}
|
|
12202
|
+
} catch {}
|
|
12203
|
+
try {
|
|
12204
|
+
const pageSignals = await page.evaluate(() => {
|
|
12205
|
+
const s = [];
|
|
12206
|
+
const w = window;
|
|
12207
|
+
const envVars = ["__ENV__", "__NEXT_DATA__", "__NUXT__", "process"];
|
|
12208
|
+
for (const v of envVars) {
|
|
12209
|
+
if (w[v]) {
|
|
12210
|
+
const env2 = w[v]?.env?.NODE_ENV ?? w[v]?.runtimeConfig?.public?.env ?? w[v]?.props?.pageProps?.env;
|
|
12211
|
+
if (env2)
|
|
12212
|
+
s.push(`window.${v}: ${env2}`);
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
if (w.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.size > 0) {
|
|
12216
|
+
const fiber = document.querySelector("[data-reactroot]") || document.getElementById("__next") || document.getElementById("root");
|
|
12217
|
+
if (fiber)
|
|
12218
|
+
s.push("React app detected");
|
|
12219
|
+
}
|
|
12220
|
+
const envMeta = document.querySelector('meta[name="environment"], meta[name="env"], meta[name="deploy-env"]');
|
|
12221
|
+
if (envMeta)
|
|
12222
|
+
s.push(`meta[environment]: ${envMeta.getAttribute("content")}`);
|
|
12223
|
+
const scripts = document.querySelectorAll("script[src]");
|
|
12224
|
+
let minified = 0, unminified = 0;
|
|
12225
|
+
scripts.forEach((s2) => {
|
|
12226
|
+
const src = s2.getAttribute("src") ?? "";
|
|
12227
|
+
if (src.includes(".min.") || src.match(/\.[a-f0-9]{8,}\./))
|
|
12228
|
+
minified++;
|
|
12229
|
+
else if (src.endsWith(".js") && !src.includes("chunk"))
|
|
12230
|
+
unminified++;
|
|
12231
|
+
});
|
|
12232
|
+
if (unminified > minified && unminified > 2)
|
|
12233
|
+
s.push(`Unminified scripts (${unminified}/${minified + unminified}) \u2192 likely dev`);
|
|
12234
|
+
else if (minified > 0)
|
|
12235
|
+
s.push(`Minified/hashed scripts (${minified}/${minified + unminified}) \u2192 likely prod`);
|
|
12236
|
+
if (document.querySelector("[data-testid]"))
|
|
12237
|
+
s.push("data-testid attributes present \u2192 dev/staging");
|
|
12238
|
+
if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
|
|
12239
|
+
s.push("Service worker active \u2192 likely prod");
|
|
12240
|
+
}
|
|
12241
|
+
if (w.Sentry)
|
|
12242
|
+
s.push("Sentry SDK loaded \u2192 prod monitoring");
|
|
12243
|
+
if (w.__DATADOG_SYNTHETICS_INLINED_SCRIPT)
|
|
12244
|
+
s.push("Datadog loaded \u2192 prod monitoring");
|
|
12245
|
+
if (w.LogRocket)
|
|
12246
|
+
s.push("LogRocket loaded \u2192 prod monitoring");
|
|
12247
|
+
if (w._lr_loaded)
|
|
12248
|
+
s.push("LogRocket loaded \u2192 prod monitoring");
|
|
12249
|
+
if (w.gtag || w.ga)
|
|
12250
|
+
s.push("Google Analytics loaded \u2192 likely prod");
|
|
12251
|
+
if (w.posthog || w._ph)
|
|
12252
|
+
s.push("PostHog loaded \u2192 prod analytics");
|
|
12253
|
+
if (w.mixpanel)
|
|
12254
|
+
s.push("Mixpanel loaded \u2192 prod analytics");
|
|
12255
|
+
const robots = document.querySelector('meta[name="robots"]');
|
|
12256
|
+
if (robots) {
|
|
12257
|
+
const content = robots.getAttribute("content") ?? "";
|
|
12258
|
+
if (content.includes("noindex"))
|
|
12259
|
+
s.push(`robots: noindex \u2192 staging/dev`);
|
|
12260
|
+
}
|
|
12261
|
+
return s;
|
|
12262
|
+
});
|
|
12263
|
+
for (const signal of pageSignals) {
|
|
12264
|
+
signals.push(signal);
|
|
12265
|
+
if (signal.includes("development") || signal.includes("\u2192 dev") || signal.includes("\u2192 likely dev"))
|
|
12266
|
+
score.dev += 2;
|
|
12267
|
+
if (signal.includes("production") || signal.includes("\u2192 prod") || signal.includes("\u2192 likely prod"))
|
|
12268
|
+
score.prod += 2;
|
|
12269
|
+
if (signal.includes("staging") || signal.includes("\u2192 staging"))
|
|
12270
|
+
score.staging += 2;
|
|
12271
|
+
if (signal.includes("monitoring") || signal.includes("analytics"))
|
|
12272
|
+
score.prod += 1;
|
|
12273
|
+
if (signal.includes("noindex")) {
|
|
12274
|
+
score.staging += 2;
|
|
12275
|
+
score.dev += 1;
|
|
12276
|
+
}
|
|
12277
|
+
}
|
|
12278
|
+
} catch {}
|
|
12279
|
+
const entries = Object.entries(score);
|
|
12280
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
12281
|
+
const [env, topScore] = entries[0];
|
|
12282
|
+
const [, secondScore] = entries[1];
|
|
12283
|
+
const confidence = topScore >= 5 ? "high" : topScore > secondScore + 1 ? "medium" : "low";
|
|
12284
|
+
return { env, confidence, signals };
|
|
12285
|
+
}
|
|
12286
|
+
|
|
12287
|
+
// src/lib/deep-performance.ts
|
|
12288
|
+
var exports_deep_performance = {};
|
|
12289
|
+
__export(exports_deep_performance, {
|
|
12290
|
+
getDeepPerformance: () => getDeepPerformance
|
|
12291
|
+
});
|
|
12292
|
+
function categorizeThirdParty(domain) {
|
|
12293
|
+
for (const [pattern, category] of Object.entries(THIRD_PARTY_CATEGORIES)) {
|
|
12294
|
+
if (domain.includes(pattern))
|
|
12295
|
+
return category;
|
|
12296
|
+
}
|
|
12297
|
+
return "other";
|
|
12298
|
+
}
|
|
12299
|
+
async function getDeepPerformance(page) {
|
|
12300
|
+
return page.evaluate(() => {
|
|
12301
|
+
const perf = performance;
|
|
12302
|
+
const entries = perf.getEntriesByType("resource");
|
|
12303
|
+
const navEntry = perf.getEntriesByType("navigation")[0];
|
|
12304
|
+
const paintEntries = perf.getEntriesByType("paint");
|
|
12305
|
+
const fcp = paintEntries.find((e) => e.name === "first-contentful-paint")?.startTime;
|
|
12306
|
+
const ttfb = navEntry?.responseStart;
|
|
12307
|
+
const web_vitals = { fcp, ttfb };
|
|
12308
|
+
try {
|
|
12309
|
+
const lcpEntries = perf.getEntriesByType("largest-contentful-paint");
|
|
12310
|
+
if (lcpEntries.length > 0)
|
|
12311
|
+
web_vitals.lcp = lcpEntries[lcpEntries.length - 1].startTime;
|
|
12312
|
+
} catch {}
|
|
12313
|
+
const byType = {};
|
|
12314
|
+
let totalBytes = 0;
|
|
12315
|
+
const resourceList = [];
|
|
12316
|
+
const pageDomain = location.hostname;
|
|
12317
|
+
const thirdPartyMap = new Map;
|
|
12318
|
+
for (const entry of entries) {
|
|
12319
|
+
const size = entry.transferSize || entry.encodedBodySize || 0;
|
|
12320
|
+
totalBytes += size;
|
|
12321
|
+
let type2 = entry.initiatorType || "other";
|
|
12322
|
+
if (type2 === "xmlhttprequest" || type2 === "fetch")
|
|
12323
|
+
type2 = "xhr";
|
|
12324
|
+
if (type2 === "link" && entry.name.match(/\.css/))
|
|
12325
|
+
type2 = "css";
|
|
12326
|
+
if (type2 === "img" || entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp|avif|ico)/i))
|
|
12327
|
+
type2 = "image";
|
|
12328
|
+
if (type2 === "script" || entry.name.match(/\.js/))
|
|
12329
|
+
type2 = "script";
|
|
12330
|
+
if (entry.name.match(/\.(woff2?|ttf|otf|eot)/i))
|
|
12331
|
+
type2 = "font";
|
|
12332
|
+
if (!byType[type2])
|
|
12333
|
+
byType[type2] = { count: 0, size_bytes: 0 };
|
|
12334
|
+
byType[type2].count++;
|
|
12335
|
+
byType[type2].size_bytes += size;
|
|
12336
|
+
resourceList.push({ url: entry.name, size_bytes: size, type: type2 });
|
|
12337
|
+
try {
|
|
12338
|
+
const domain = new URL(entry.name).hostname;
|
|
12339
|
+
if (domain !== pageDomain && !domain.endsWith(`.${pageDomain}`)) {
|
|
12340
|
+
if (!thirdPartyMap.has(domain))
|
|
12341
|
+
thirdPartyMap.set(domain, { scripts: 0, total_bytes: 0 });
|
|
12342
|
+
const tp = thirdPartyMap.get(domain);
|
|
12343
|
+
tp.scripts++;
|
|
12344
|
+
tp.total_bytes += size;
|
|
12345
|
+
}
|
|
12346
|
+
} catch {}
|
|
12347
|
+
}
|
|
12348
|
+
resourceList.sort((a, b) => b.size_bytes - a.size_bytes);
|
|
12349
|
+
const largest = resourceList.slice(0, 10).map((r) => ({
|
|
12350
|
+
url: r.url.length > 120 ? r.url.slice(0, 117) + "..." : r.url,
|
|
12351
|
+
size_bytes: r.size_bytes,
|
|
12352
|
+
type: r.type
|
|
12353
|
+
}));
|
|
12354
|
+
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);
|
|
12355
|
+
const allNodes = document.querySelectorAll("*");
|
|
12356
|
+
let maxDepth = 0;
|
|
12357
|
+
let textNodes = 0;
|
|
12358
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL);
|
|
12359
|
+
let node = walker.currentNode;
|
|
12360
|
+
while (node) {
|
|
12361
|
+
if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim())
|
|
12362
|
+
textNodes++;
|
|
12363
|
+
let depth = 0;
|
|
12364
|
+
let parent = node.parentNode;
|
|
12365
|
+
while (parent) {
|
|
12366
|
+
depth++;
|
|
12367
|
+
parent = parent.parentNode;
|
|
12368
|
+
}
|
|
12369
|
+
if (depth > maxDepth)
|
|
12370
|
+
maxDepth = depth;
|
|
12371
|
+
node = walker.nextNode();
|
|
12372
|
+
}
|
|
12373
|
+
const mem = performance.memory;
|
|
12374
|
+
const memory = {
|
|
12375
|
+
js_heap_used_mb: mem ? Math.round(mem.usedJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
|
|
12376
|
+
js_heap_total_mb: mem ? Math.round(mem.totalJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
|
|
12377
|
+
js_heap_limit_mb: mem ? Math.round(mem.jsHeapSizeLimit / 1024 / 1024 * 100) / 100 : 0
|
|
12378
|
+
};
|
|
12379
|
+
return {
|
|
12380
|
+
web_vitals,
|
|
12381
|
+
resources: { total_transfer_bytes: totalBytes, total_resources: entries.length, by_type: byType, largest },
|
|
12382
|
+
third_party,
|
|
12383
|
+
dom: { node_count: document.all.length, max_depth: maxDepth, element_count: allNodes.length, text_node_count: textNodes },
|
|
12384
|
+
main_thread: { long_tasks: 0, total_blocking_ms: 0 },
|
|
12385
|
+
memory
|
|
12386
|
+
};
|
|
12387
|
+
}).then((result) => {
|
|
12388
|
+
for (const tp of result.third_party) {
|
|
12389
|
+
tp.category = categorizeThirdParty(tp.domain);
|
|
12390
|
+
}
|
|
12391
|
+
return result;
|
|
12392
|
+
});
|
|
12393
|
+
}
|
|
12394
|
+
var THIRD_PARTY_CATEGORIES;
|
|
12395
|
+
var init_deep_performance = __esm(() => {
|
|
12396
|
+
THIRD_PARTY_CATEGORIES = {
|
|
12397
|
+
"google-analytics.com": "analytics",
|
|
12398
|
+
"googletagmanager.com": "analytics",
|
|
12399
|
+
gtag: "analytics",
|
|
12400
|
+
"facebook.net": "social",
|
|
12401
|
+
"connect.facebook": "social",
|
|
12402
|
+
"stripe.com": "payment",
|
|
12403
|
+
"js.stripe.com": "payment",
|
|
12404
|
+
"sentry.io": "monitoring",
|
|
12405
|
+
"sentry-cdn": "monitoring",
|
|
12406
|
+
"posthog.com": "analytics",
|
|
12407
|
+
"ph.": "analytics",
|
|
12408
|
+
"intercom.io": "chat",
|
|
12409
|
+
"crisp.chat": "chat",
|
|
12410
|
+
"hotjar.com": "analytics",
|
|
12411
|
+
"clarity.ms": "analytics",
|
|
12412
|
+
"cdn.jsdelivr.net": "cdn",
|
|
12413
|
+
"cdnjs.cloudflare.com": "cdn",
|
|
12414
|
+
"unpkg.com": "cdn",
|
|
12415
|
+
"fonts.googleapis.com": "fonts",
|
|
12416
|
+
"fonts.gstatic.com": "fonts"
|
|
12417
|
+
};
|
|
12418
|
+
});
|
|
12419
|
+
|
|
12420
|
+
// src/lib/api-detector.ts
|
|
12421
|
+
var exports_api_detector = {};
|
|
12422
|
+
__export(exports_api_detector, {
|
|
12423
|
+
listDiscoveredAPIs: () => listDiscoveredAPIs,
|
|
12424
|
+
detectAPIs: () => detectAPIs
|
|
12425
|
+
});
|
|
12426
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
12427
|
+
function detectAPIs(sessionId) {
|
|
12428
|
+
const db = getDatabase();
|
|
12429
|
+
const requests = db.query(`SELECT method, url, status_code, response_headers, body_size
|
|
12430
|
+
FROM network_log
|
|
12431
|
+
WHERE session_id = ?
|
|
12432
|
+
AND (response_headers LIKE '%application/json%' OR response_headers LIKE '%text/json%')
|
|
12433
|
+
AND status_code >= 200 AND status_code < 400
|
|
12434
|
+
ORDER BY timestamp DESC`).all(sessionId);
|
|
12435
|
+
const seen = new Map;
|
|
12436
|
+
for (const req of requests) {
|
|
12437
|
+
try {
|
|
12438
|
+
const urlObj = new URL(req.url);
|
|
12439
|
+
if (urlObj.pathname.match(/\.(js|css|png|jpg|svg|woff|ico)$/))
|
|
12440
|
+
continue;
|
|
12441
|
+
if (urlObj.hostname.includes("googleapis.com/identitytoolkit"))
|
|
12442
|
+
continue;
|
|
12443
|
+
if (urlObj.hostname.includes("posthog"))
|
|
12444
|
+
continue;
|
|
12445
|
+
if (urlObj.hostname.includes("sentry"))
|
|
12446
|
+
continue;
|
|
12447
|
+
const key = `${req.method} ${urlObj.origin}${urlObj.pathname}`;
|
|
12448
|
+
if (!seen.has(key)) {
|
|
12449
|
+
seen.set(key, {
|
|
12450
|
+
url: `${urlObj.origin}${urlObj.pathname}`,
|
|
12451
|
+
method: req.method,
|
|
12452
|
+
status_code: req.status_code,
|
|
12453
|
+
content_type: "application/json",
|
|
12454
|
+
response_schema: {},
|
|
12455
|
+
sample_size: req.body_size ?? 0
|
|
12456
|
+
});
|
|
12457
|
+
}
|
|
12458
|
+
} catch {}
|
|
12459
|
+
}
|
|
12460
|
+
const apis = Array.from(seen.values());
|
|
12461
|
+
for (const api of apis) {
|
|
12462
|
+
const id = randomUUID9();
|
|
12463
|
+
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);
|
|
12464
|
+
}
|
|
12465
|
+
return apis;
|
|
12466
|
+
}
|
|
12467
|
+
function listDiscoveredAPIs(sessionId) {
|
|
12468
|
+
const db = getDatabase();
|
|
12469
|
+
if (sessionId) {
|
|
12470
|
+
return db.query("SELECT * FROM api_endpoints WHERE session_id = ? ORDER BY discovered_at DESC").all(sessionId);
|
|
12471
|
+
}
|
|
12472
|
+
return db.query("SELECT * FROM api_endpoints ORDER BY discovered_at DESC LIMIT 100").all();
|
|
12473
|
+
}
|
|
12474
|
+
var init_api_detector = __esm(() => {
|
|
12475
|
+
init_schema();
|
|
12476
|
+
});
|
|
12477
|
+
|
|
12478
|
+
// src/lib/structured-extract.ts
|
|
12479
|
+
var exports_structured_extract = {};
|
|
12480
|
+
__export(exports_structured_extract, {
|
|
12481
|
+
extractStructuredData: () => extractStructuredData
|
|
12482
|
+
});
|
|
12483
|
+
async function extractStructuredData(page) {
|
|
12484
|
+
return page.evaluate(() => {
|
|
12485
|
+
const result = { tables: [], lists: [], jsonLd: [], openGraph: {}, metaTags: {}, repeatedElements: [] };
|
|
12486
|
+
document.querySelectorAll("table").forEach((table, idx) => {
|
|
12487
|
+
const headers = [];
|
|
12488
|
+
table.querySelectorAll("thead th, thead td, tr:first-child th").forEach((th) => {
|
|
12489
|
+
headers.push(th.textContent?.trim() ?? "");
|
|
12490
|
+
});
|
|
12491
|
+
const rows = [];
|
|
12492
|
+
table.querySelectorAll("tbody tr, tr:not(:first-child)").forEach((tr) => {
|
|
12493
|
+
const row = [];
|
|
12494
|
+
tr.querySelectorAll("td, th").forEach((td) => {
|
|
12495
|
+
row.push(td.textContent?.trim() ?? "");
|
|
12496
|
+
});
|
|
12497
|
+
if (row.length > 0 && row.some((c) => c !== ""))
|
|
12498
|
+
rows.push(row);
|
|
12499
|
+
});
|
|
12500
|
+
if (rows.length > 0) {
|
|
12501
|
+
result.tables.push({ headers, rows, selector: `table:nth-of-type(${idx + 1})` });
|
|
12502
|
+
}
|
|
12503
|
+
});
|
|
12504
|
+
document.querySelectorAll("ul, ol").forEach((list, idx) => {
|
|
12505
|
+
const items = [];
|
|
12506
|
+
list.querySelectorAll(":scope > li").forEach((li) => {
|
|
12507
|
+
const text = li.textContent?.trim() ?? "";
|
|
12508
|
+
if (text)
|
|
12509
|
+
items.push(text);
|
|
12510
|
+
});
|
|
12511
|
+
if (items.length >= 3) {
|
|
12512
|
+
const tag = list.tagName.toLowerCase();
|
|
12513
|
+
result.lists.push({ items, selector: `${tag}:nth-of-type(${idx + 1})` });
|
|
12514
|
+
}
|
|
12515
|
+
});
|
|
12516
|
+
document.querySelectorAll('script[type="application/ld+json"]').forEach((script) => {
|
|
12517
|
+
try {
|
|
12518
|
+
result.jsonLd.push(JSON.parse(script.textContent ?? ""));
|
|
12519
|
+
} catch {}
|
|
12520
|
+
});
|
|
12521
|
+
document.querySelectorAll('meta[property^="og:"]').forEach((meta) => {
|
|
12522
|
+
const prop = meta.getAttribute("property")?.replace("og:", "") ?? "";
|
|
12523
|
+
result.openGraph[prop] = meta.getAttribute("content") ?? "";
|
|
12524
|
+
});
|
|
12525
|
+
document.querySelectorAll("meta[name]").forEach((meta) => {
|
|
12526
|
+
const name = meta.getAttribute("name") ?? "";
|
|
12527
|
+
if (name)
|
|
12528
|
+
result.metaTags[name] = meta.getAttribute("content") ?? "";
|
|
12529
|
+
});
|
|
12530
|
+
const classCounts = new Map;
|
|
12531
|
+
document.querySelectorAll("[class]").forEach((el) => {
|
|
12532
|
+
const cls = el.className.toString().trim();
|
|
12533
|
+
if (cls && cls.length > 5 && cls.length < 100) {
|
|
12534
|
+
if (!classCounts.has(cls))
|
|
12535
|
+
classCounts.set(cls, []);
|
|
12536
|
+
classCounts.get(cls).push(el);
|
|
12537
|
+
}
|
|
12538
|
+
});
|
|
12539
|
+
for (const [cls, elements] of classCounts) {
|
|
12540
|
+
if (elements.length >= 3 && elements.length <= 200) {
|
|
12541
|
+
const sample = elements.slice(0, 3).map((el) => el.textContent?.trim().slice(0, 100) ?? "");
|
|
12542
|
+
if (sample.some((s) => s.length > 10)) {
|
|
12543
|
+
result.repeatedElements.push({
|
|
12544
|
+
selector: `.${cls.split(" ")[0]}`,
|
|
12545
|
+
count: elements.length,
|
|
12546
|
+
sample
|
|
12547
|
+
});
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
}
|
|
12551
|
+
result.repeatedElements.sort((a, b) => b.count - a.count);
|
|
12552
|
+
result.repeatedElements = result.repeatedElements.slice(0, 10);
|
|
12553
|
+
return result;
|
|
12554
|
+
});
|
|
12555
|
+
}
|
|
12556
|
+
|
|
12557
|
+
// src/lib/gallery-diff.ts
|
|
12558
|
+
var exports_gallery_diff = {};
|
|
12559
|
+
__export(exports_gallery_diff, {
|
|
12560
|
+
diffImages: () => diffImages
|
|
12561
|
+
});
|
|
12562
|
+
import { join as join5 } from "path";
|
|
12563
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
12564
|
+
import { homedir as homedir5 } from "os";
|
|
12565
|
+
async function diffImages(path1, path2) {
|
|
12566
|
+
const img1 = import_sharp2.default(path1);
|
|
12567
|
+
const img2 = import_sharp2.default(path2);
|
|
12568
|
+
const [meta1, meta2] = await Promise.all([img1.metadata(), img2.metadata()]);
|
|
12569
|
+
const w = Math.min(meta1.width ?? 1280, meta2.width ?? 1280);
|
|
12570
|
+
const h = Math.min(meta1.height ?? 720, meta2.height ?? 720);
|
|
12571
|
+
const [raw1, raw2] = await Promise.all([
|
|
12572
|
+
import_sharp2.default(path1).resize(w, h, { fit: "fill" }).raw().toBuffer(),
|
|
12573
|
+
import_sharp2.default(path2).resize(w, h, { fit: "fill" }).raw().toBuffer()
|
|
12574
|
+
]);
|
|
12575
|
+
const totalPixels = w * h;
|
|
12576
|
+
const channels = 3;
|
|
12577
|
+
const diffBuffer = Buffer.alloc(raw1.length);
|
|
12578
|
+
let changedPixels = 0;
|
|
12579
|
+
for (let i = 0;i < raw1.length; i += channels) {
|
|
12580
|
+
const dr = Math.abs(raw1[i] - raw2[i]);
|
|
12581
|
+
const dg = Math.abs(raw1[i + 1] - raw2[i + 1]);
|
|
12582
|
+
const db = Math.abs(raw1[i + 2] - raw2[i + 2]);
|
|
12583
|
+
const diff = (dr + dg + db) / 3;
|
|
12584
|
+
if (diff > 10) {
|
|
12585
|
+
changedPixels++;
|
|
12586
|
+
diffBuffer[i] = 255;
|
|
12587
|
+
diffBuffer[i + 1] = 0;
|
|
12588
|
+
diffBuffer[i + 2] = 0;
|
|
12589
|
+
} else {
|
|
12590
|
+
diffBuffer[i] = Math.round(raw1[i] * 0.4);
|
|
12591
|
+
diffBuffer[i + 1] = Math.round(raw1[i + 1] * 0.4);
|
|
12592
|
+
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
12593
|
+
}
|
|
12594
|
+
}
|
|
12595
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join5(homedir5(), ".browser");
|
|
12596
|
+
const diffDir = join5(dataDir, "diffs");
|
|
12597
|
+
mkdirSync5(diffDir, { recursive: true });
|
|
12598
|
+
const diffPath = join5(diffDir, `diff-${Date.now()}.webp`);
|
|
12599
|
+
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
12600
|
+
await Bun.write(diffPath, diffImageBuffer);
|
|
12601
|
+
return {
|
|
12602
|
+
diff_path: diffPath,
|
|
12603
|
+
diff_base64: diffImageBuffer.toString("base64"),
|
|
12604
|
+
changed_pixels: changedPixels,
|
|
12605
|
+
total_pixels: totalPixels,
|
|
12606
|
+
changed_percent: changedPixels / totalPixels * 100
|
|
12607
|
+
};
|
|
12608
|
+
}
|
|
12609
|
+
var import_sharp2;
|
|
12610
|
+
var init_gallery_diff = __esm(() => {
|
|
12611
|
+
import_sharp2 = __toESM(require_lib(), 1);
|
|
12612
|
+
});
|
|
12613
|
+
|
|
12614
|
+
// src/lib/profiles.ts
|
|
12615
|
+
var exports_profiles = {};
|
|
12616
|
+
__export(exports_profiles, {
|
|
12617
|
+
saveProfile: () => saveProfile,
|
|
12618
|
+
loadProfile: () => loadProfile,
|
|
12619
|
+
listProfiles: () => listProfiles,
|
|
12620
|
+
deleteProfile: () => deleteProfile,
|
|
12621
|
+
applyProfile: () => applyProfile
|
|
12622
|
+
});
|
|
12623
|
+
import { mkdirSync as mkdirSync6, existsSync as existsSync3, readdirSync as readdirSync2, rmSync, readFileSync, writeFileSync } from "fs";
|
|
12624
|
+
import { join as join6 } from "path";
|
|
12625
|
+
import { homedir as homedir6 } from "os";
|
|
12626
|
+
function getProfilesDir() {
|
|
12627
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
|
|
12628
|
+
const dir = join6(dataDir, "profiles");
|
|
12629
|
+
mkdirSync6(dir, { recursive: true });
|
|
12630
|
+
return dir;
|
|
12631
|
+
}
|
|
12632
|
+
function getProfileDir2(name) {
|
|
12633
|
+
return join6(getProfilesDir(), name);
|
|
12634
|
+
}
|
|
12635
|
+
async function saveProfile(page, name) {
|
|
12636
|
+
const dir = getProfileDir2(name);
|
|
12637
|
+
mkdirSync6(dir, { recursive: true });
|
|
12638
|
+
const cookies = await page.context().cookies();
|
|
12639
|
+
writeFileSync(join6(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
|
|
12640
|
+
let localStorage2 = {};
|
|
12641
|
+
try {
|
|
12642
|
+
localStorage2 = await page.evaluate(() => {
|
|
12643
|
+
const result = {};
|
|
12644
|
+
for (let i = 0;i < window.localStorage.length; i++) {
|
|
12645
|
+
const key = window.localStorage.key(i);
|
|
12646
|
+
result[key] = window.localStorage.getItem(key);
|
|
12647
|
+
}
|
|
12648
|
+
return result;
|
|
12649
|
+
});
|
|
12650
|
+
} catch {}
|
|
12651
|
+
writeFileSync(join6(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
|
|
12652
|
+
const savedAt = new Date().toISOString();
|
|
12653
|
+
const url = page.url();
|
|
12654
|
+
const meta = { saved_at: savedAt, url };
|
|
12655
|
+
writeFileSync(join6(dir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
12656
|
+
return {
|
|
12657
|
+
name,
|
|
12658
|
+
saved_at: savedAt,
|
|
12659
|
+
url,
|
|
12660
|
+
cookie_count: cookies.length,
|
|
12661
|
+
storage_key_count: Object.keys(localStorage2).length
|
|
12662
|
+
};
|
|
12663
|
+
}
|
|
12664
|
+
function loadProfile(name) {
|
|
12665
|
+
const dir = getProfileDir2(name);
|
|
12666
|
+
if (!existsSync3(dir)) {
|
|
12667
|
+
throw new Error(`Profile not found: ${name}`);
|
|
12668
|
+
}
|
|
12669
|
+
const cookiesPath = join6(dir, "cookies.json");
|
|
12670
|
+
const storagePath = join6(dir, "storage.json");
|
|
12671
|
+
const metaPath = join6(dir, "meta.json");
|
|
12672
|
+
const cookies = existsSync3(cookiesPath) ? JSON.parse(readFileSync(cookiesPath, "utf8")) : [];
|
|
12673
|
+
const localStorage2 = existsSync3(storagePath) ? JSON.parse(readFileSync(storagePath, "utf8")) : {};
|
|
12674
|
+
let savedAt = new Date().toISOString();
|
|
12675
|
+
let url;
|
|
12676
|
+
if (existsSync3(metaPath)) {
|
|
12677
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
12678
|
+
savedAt = meta.saved_at ?? savedAt;
|
|
12679
|
+
url = meta.url;
|
|
12680
|
+
}
|
|
12681
|
+
return { cookies, localStorage: localStorage2, saved_at: savedAt, url };
|
|
12682
|
+
}
|
|
12683
|
+
async function applyProfile(page, profileData) {
|
|
12684
|
+
if (profileData.cookies.length > 0) {
|
|
12685
|
+
await page.context().addCookies(profileData.cookies);
|
|
12686
|
+
}
|
|
12687
|
+
const storageKeys = Object.keys(profileData.localStorage);
|
|
12688
|
+
if (storageKeys.length > 0) {
|
|
12689
|
+
try {
|
|
12690
|
+
await page.evaluate((storage) => {
|
|
12691
|
+
for (const [key, value] of Object.entries(storage)) {
|
|
12692
|
+
window.localStorage.setItem(key, value);
|
|
12693
|
+
}
|
|
12694
|
+
}, profileData.localStorage);
|
|
12695
|
+
} catch {}
|
|
12696
|
+
}
|
|
12697
|
+
return {
|
|
12698
|
+
cookies_applied: profileData.cookies.length,
|
|
12699
|
+
storage_keys_applied: storageKeys.length
|
|
12700
|
+
};
|
|
12701
|
+
}
|
|
12702
|
+
function listProfiles() {
|
|
12703
|
+
const dir = getProfilesDir();
|
|
12704
|
+
if (!existsSync3(dir))
|
|
12705
|
+
return [];
|
|
12706
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
12707
|
+
const profiles = [];
|
|
12708
|
+
for (const entry of entries) {
|
|
12709
|
+
if (!entry.isDirectory())
|
|
12710
|
+
continue;
|
|
12711
|
+
const name = entry.name;
|
|
12712
|
+
const profileDir = join6(dir, name);
|
|
12713
|
+
let savedAt = "";
|
|
12714
|
+
let url;
|
|
12715
|
+
let cookieCount = 0;
|
|
12716
|
+
let storageKeyCount = 0;
|
|
12717
|
+
try {
|
|
12718
|
+
const metaPath = join6(profileDir, "meta.json");
|
|
12719
|
+
if (existsSync3(metaPath)) {
|
|
12720
|
+
const meta = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
12721
|
+
savedAt = meta.saved_at ?? "";
|
|
12722
|
+
url = meta.url;
|
|
12723
|
+
}
|
|
12724
|
+
const cookiesPath = join6(profileDir, "cookies.json");
|
|
12725
|
+
if (existsSync3(cookiesPath)) {
|
|
12726
|
+
const cookies = JSON.parse(readFileSync(cookiesPath, "utf8"));
|
|
12727
|
+
cookieCount = Array.isArray(cookies) ? cookies.length : 0;
|
|
12728
|
+
}
|
|
12729
|
+
const storagePath = join6(profileDir, "storage.json");
|
|
12730
|
+
if (existsSync3(storagePath)) {
|
|
12731
|
+
const storage = JSON.parse(readFileSync(storagePath, "utf8"));
|
|
12732
|
+
storageKeyCount = Object.keys(storage).length;
|
|
12733
|
+
}
|
|
12734
|
+
} catch {}
|
|
12735
|
+
profiles.push({
|
|
12736
|
+
name,
|
|
12737
|
+
saved_at: savedAt,
|
|
12738
|
+
url,
|
|
12739
|
+
cookie_count: cookieCount,
|
|
12740
|
+
storage_key_count: storageKeyCount
|
|
12741
|
+
});
|
|
12742
|
+
}
|
|
12743
|
+
return profiles.sort((a, b) => b.saved_at.localeCompare(a.saved_at));
|
|
12744
|
+
}
|
|
12745
|
+
function deleteProfile(name) {
|
|
12746
|
+
const dir = getProfileDir2(name);
|
|
12747
|
+
if (!existsSync3(dir))
|
|
12748
|
+
return false;
|
|
12749
|
+
try {
|
|
12750
|
+
rmSync(dir, { recursive: true, force: true });
|
|
12751
|
+
return true;
|
|
12752
|
+
} catch {
|
|
12753
|
+
return false;
|
|
12754
|
+
}
|
|
12755
|
+
}
|
|
12756
|
+
var init_profiles = () => {};
|
|
12757
|
+
|
|
12758
|
+
// src/lib/auth.ts
|
|
12759
|
+
var exports_auth = {};
|
|
12760
|
+
__export(exports_auth, {
|
|
12761
|
+
loginWithCredentials: () => loginWithCredentials,
|
|
12762
|
+
getCredentials: () => getCredentials
|
|
12763
|
+
});
|
|
12764
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
12765
|
+
import { join as join7 } from "path";
|
|
12766
|
+
import { homedir as homedir7 } from "os";
|
|
12767
|
+
async function getCredentials(service) {
|
|
12768
|
+
try {
|
|
12769
|
+
const { getSecret } = await import(`${homedir7()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
|
|
12770
|
+
const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
|
|
12771
|
+
const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
|
|
12772
|
+
if (email?.value && password?.value) {
|
|
12773
|
+
return { email: email.value, password: password.value };
|
|
12774
|
+
}
|
|
12775
|
+
} catch {}
|
|
12776
|
+
const secretsPath = join7(homedir7(), ".secrets");
|
|
12777
|
+
if (existsSync4(secretsPath)) {
|
|
12778
|
+
const content = readFileSync2(secretsPath, "utf8");
|
|
12779
|
+
const lines = content.split(`
|
|
12780
|
+
`);
|
|
12781
|
+
const prefix = service.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
12782
|
+
const vars = {};
|
|
12783
|
+
for (const line of lines) {
|
|
12784
|
+
const match = line.match(/^export\s+([A-Z_]+)=["']?(.+?)["']?\s*$/);
|
|
12785
|
+
if (match)
|
|
12786
|
+
vars[match[1]] = match[2];
|
|
12787
|
+
}
|
|
12788
|
+
const email = vars[`${prefix}_EMAIL`] ?? vars[`${prefix}_USERNAME`];
|
|
12789
|
+
const password = vars[`${prefix}_PASSWORD`];
|
|
12790
|
+
if (email && password)
|
|
12791
|
+
return { email, password };
|
|
12792
|
+
}
|
|
12793
|
+
const envPrefix = service.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
12794
|
+
const envEmail = process.env[`${envPrefix}_EMAIL`] ?? process.env[`${envPrefix}_USERNAME`];
|
|
12795
|
+
const envPass = process.env[`${envPrefix}_PASSWORD`];
|
|
12796
|
+
if (envEmail && envPass)
|
|
12797
|
+
return { email: envEmail, password: envPass };
|
|
12798
|
+
return null;
|
|
12799
|
+
}
|
|
12800
|
+
async function loginWithCredentials(page, credentials, opts) {
|
|
12801
|
+
const { fillForm: fillForm2 } = await Promise.resolve().then(() => (init_actions(), exports_actions));
|
|
12802
|
+
const { saveProfile: saveProfile2 } = await Promise.resolve().then(() => (init_profiles(), exports_profiles));
|
|
12803
|
+
try {
|
|
12804
|
+
if (opts?.loginUrl) {
|
|
12805
|
+
await page.goto(opts.loginUrl, { waitUntil: "domcontentloaded" });
|
|
12806
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
12807
|
+
}
|
|
12808
|
+
const emailSel = opts?.emailSelector ?? 'input[type="email"], input[name="email"], input[id*="email"], input[placeholder*="email" i]';
|
|
12809
|
+
const passSel = opts?.passwordSelector ?? 'input[type="password"]';
|
|
12810
|
+
const submitSel = opts?.submitSelector ?? 'button[type="submit"], input[type="submit"], button:contains("Sign in"), button:contains("Log in"), button:contains("Login")';
|
|
12811
|
+
const fields = {};
|
|
12812
|
+
if (credentials.email)
|
|
12813
|
+
fields[emailSel] = credentials.email;
|
|
12814
|
+
else if (credentials.username)
|
|
12815
|
+
fields[emailSel] = credentials.username;
|
|
12816
|
+
if (credentials.password)
|
|
12817
|
+
fields[passSel] = credentials.password;
|
|
12818
|
+
const fillResult = await fillForm2(page, fields, submitSel);
|
|
12819
|
+
const successText = opts?.waitForText ?? "dashboard|profile|account|welcome|signed in|logout";
|
|
12820
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
12821
|
+
const currentUrl = page.url?.() ?? "";
|
|
12822
|
+
const logged_in = fillResult.errors.length === 0;
|
|
12823
|
+
let profile_saved = false;
|
|
12824
|
+
if (opts?.saveProfile && logged_in) {
|
|
12825
|
+
try {
|
|
12826
|
+
await saveProfile2(page, opts.saveProfile);
|
|
12827
|
+
profile_saved = true;
|
|
12828
|
+
} catch {}
|
|
12829
|
+
}
|
|
12830
|
+
return {
|
|
12831
|
+
logged_in,
|
|
12832
|
+
redirect_url: currentUrl,
|
|
12833
|
+
profile_saved,
|
|
12834
|
+
method: "secrets_vault"
|
|
12835
|
+
};
|
|
12836
|
+
} catch (err) {
|
|
12837
|
+
return {
|
|
12838
|
+
logged_in: false,
|
|
12839
|
+
redirect_url: "",
|
|
12840
|
+
profile_saved: false,
|
|
12841
|
+
method: "not_found",
|
|
12842
|
+
error: err instanceof Error ? err.message : String(err)
|
|
12843
|
+
};
|
|
12844
|
+
}
|
|
12845
|
+
}
|
|
12846
|
+
var init_auth = () => {};
|
|
12847
|
+
|
|
12848
|
+
// src/lib/daemon-client.ts
|
|
12849
|
+
var exports_daemon_client = {};
|
|
12850
|
+
__export(exports_daemon_client, {
|
|
12851
|
+
isDaemonRunning: () => isDaemonRunning,
|
|
12852
|
+
getDaemonStatus: () => getDaemonStatus,
|
|
12853
|
+
getDaemonPort: () => getDaemonPort,
|
|
12854
|
+
getDaemonPidFile: () => getDaemonPidFile,
|
|
12855
|
+
getDaemonPid: () => getDaemonPid
|
|
12856
|
+
});
|
|
12857
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
12858
|
+
import { join as join8 } from "path";
|
|
12859
|
+
import { homedir as homedir8 } from "os";
|
|
12860
|
+
function getDaemonPidFile() {
|
|
12861
|
+
return PID_FILE;
|
|
12862
|
+
}
|
|
12863
|
+
function getDaemonPort() {
|
|
12864
|
+
return parseInt(process.env["BROWSER_DAEMON_PORT"] ?? String(DEFAULT_PORT), 10);
|
|
12865
|
+
}
|
|
12866
|
+
function isDaemonRunning() {
|
|
12867
|
+
if (!existsSync5(PID_FILE))
|
|
12868
|
+
return false;
|
|
12869
|
+
try {
|
|
12870
|
+
const pid = parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
|
|
12871
|
+
process.kill(pid, 0);
|
|
12872
|
+
return true;
|
|
12873
|
+
} catch {
|
|
12874
|
+
return false;
|
|
12875
|
+
}
|
|
12876
|
+
}
|
|
12877
|
+
function getDaemonPid() {
|
|
12878
|
+
if (!existsSync5(PID_FILE))
|
|
12879
|
+
return null;
|
|
12880
|
+
try {
|
|
12881
|
+
return parseInt(readFileSync3(PID_FILE, "utf8").trim(), 10);
|
|
12882
|
+
} catch {
|
|
12883
|
+
return null;
|
|
12884
|
+
}
|
|
12885
|
+
}
|
|
12886
|
+
async function getDaemonStatus() {
|
|
12887
|
+
const pid = getDaemonPid();
|
|
12888
|
+
const port = getDaemonPort();
|
|
12889
|
+
if (!isDaemonRunning())
|
|
12890
|
+
return { running: false, pid: null, port };
|
|
12891
|
+
try {
|
|
12892
|
+
const res = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(2000) });
|
|
12893
|
+
const data = await res.json();
|
|
12894
|
+
return { running: true, pid, port, sessions: data.active_sessions ?? 0, uptime_ms: data.uptime_ms };
|
|
12895
|
+
} catch {
|
|
12896
|
+
return { running: true, pid, port };
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
var PID_FILE, DEFAULT_PORT = 7030;
|
|
12900
|
+
var init_daemon_client = __esm(() => {
|
|
12901
|
+
PID_FILE = join8(process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser"), "daemon.pid");
|
|
12902
|
+
});
|
|
12903
|
+
|
|
12119
12904
|
// node_modules/zod/v3/helpers/util.js
|
|
12120
12905
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
12121
12906
|
const t = typeof data;
|
|
@@ -16186,17 +16971,17 @@ __export(exports_downloads, {
|
|
|
16186
16971
|
deleteDownload: () => deleteDownload,
|
|
16187
16972
|
cleanStaleDownloads: () => cleanStaleDownloads
|
|
16188
16973
|
});
|
|
16189
|
-
import { randomUUID as
|
|
16190
|
-
import { join as
|
|
16191
|
-
import { mkdirSync as
|
|
16192
|
-
import { homedir as
|
|
16974
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
16975
|
+
import { join as join9, basename, extname } from "path";
|
|
16976
|
+
import { mkdirSync as mkdirSync7, existsSync as existsSync6, readdirSync as readdirSync3, statSync, unlinkSync as unlinkSync2, copyFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync4 } from "fs";
|
|
16977
|
+
import { homedir as homedir9 } from "os";
|
|
16193
16978
|
function getDataDir3() {
|
|
16194
|
-
return process.env["BROWSER_DATA_DIR"] ??
|
|
16979
|
+
return process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser");
|
|
16195
16980
|
}
|
|
16196
16981
|
function getDownloadsDir(sessionId) {
|
|
16197
|
-
const base =
|
|
16198
|
-
const dir = sessionId ?
|
|
16199
|
-
|
|
16982
|
+
const base = join9(getDataDir3(), "downloads");
|
|
16983
|
+
const dir = sessionId ? join9(base, sessionId) : base;
|
|
16984
|
+
mkdirSync7(dir, { recursive: true });
|
|
16200
16985
|
return dir;
|
|
16201
16986
|
}
|
|
16202
16987
|
function ensureDownloadsDir() {
|
|
@@ -16207,12 +16992,12 @@ function metaPath(filePath) {
|
|
|
16207
16992
|
}
|
|
16208
16993
|
function saveToDownloads(buffer, filename, opts) {
|
|
16209
16994
|
const dir = getDownloadsDir(opts?.sessionId);
|
|
16210
|
-
const id =
|
|
16995
|
+
const id = randomUUID10();
|
|
16211
16996
|
const ext = extname(filename) || "";
|
|
16212
16997
|
const stem = basename(filename, ext);
|
|
16213
16998
|
const uniqueName = `${stem}-${id.slice(0, 8)}${ext}`;
|
|
16214
|
-
const filePath =
|
|
16215
|
-
|
|
16999
|
+
const filePath = join9(dir, uniqueName);
|
|
17000
|
+
writeFileSync2(filePath, buffer);
|
|
16216
17001
|
const meta = {
|
|
16217
17002
|
id,
|
|
16218
17003
|
type: opts?.type ?? detectType(filename),
|
|
@@ -16223,7 +17008,7 @@ function saveToDownloads(buffer, filename, opts) {
|
|
|
16223
17008
|
original_name: filename,
|
|
16224
17009
|
...opts?.metadata
|
|
16225
17010
|
};
|
|
16226
|
-
|
|
17011
|
+
writeFileSync2(metaPath(filePath), JSON.stringify(meta, null, 2));
|
|
16227
17012
|
return {
|
|
16228
17013
|
id,
|
|
16229
17014
|
path: filePath,
|
|
@@ -16240,23 +17025,23 @@ function listDownloads(sessionId) {
|
|
|
16240
17025
|
const dir = getDownloadsDir(sessionId);
|
|
16241
17026
|
const results = [];
|
|
16242
17027
|
function scanDir(d) {
|
|
16243
|
-
if (!
|
|
17028
|
+
if (!existsSync6(d))
|
|
16244
17029
|
return;
|
|
16245
|
-
const entries =
|
|
17030
|
+
const entries = readdirSync3(d);
|
|
16246
17031
|
for (const entry of entries) {
|
|
16247
17032
|
if (entry.endsWith(".meta.json"))
|
|
16248
17033
|
continue;
|
|
16249
|
-
const full =
|
|
17034
|
+
const full = join9(d, entry);
|
|
16250
17035
|
const stat = statSync(full);
|
|
16251
17036
|
if (stat.isDirectory()) {
|
|
16252
17037
|
scanDir(full);
|
|
16253
17038
|
continue;
|
|
16254
17039
|
}
|
|
16255
17040
|
const mpath = metaPath(full);
|
|
16256
|
-
if (!
|
|
17041
|
+
if (!existsSync6(mpath))
|
|
16257
17042
|
continue;
|
|
16258
17043
|
try {
|
|
16259
|
-
const meta = JSON.parse(
|
|
17044
|
+
const meta = JSON.parse(readFileSync4(mpath, "utf8"));
|
|
16260
17045
|
results.push({
|
|
16261
17046
|
id: meta.id,
|
|
16262
17047
|
path: full,
|
|
@@ -16284,7 +17069,7 @@ function deleteDownload(id, sessionId) {
|
|
|
16284
17069
|
return false;
|
|
16285
17070
|
try {
|
|
16286
17071
|
unlinkSync2(file.path);
|
|
16287
|
-
if (
|
|
17072
|
+
if (existsSync6(file.meta_path))
|
|
16288
17073
|
unlinkSync2(file.meta_path);
|
|
16289
17074
|
return true;
|
|
16290
17075
|
} catch {
|
|
@@ -16330,67 +17115,10 @@ function detectType(filename) {
|
|
|
16330
17115
|
}
|
|
16331
17116
|
var init_downloads = () => {};
|
|
16332
17117
|
|
|
16333
|
-
// src/lib/gallery-diff.ts
|
|
16334
|
-
var exports_gallery_diff = {};
|
|
16335
|
-
__export(exports_gallery_diff, {
|
|
16336
|
-
diffImages: () => diffImages
|
|
16337
|
-
});
|
|
16338
|
-
import { join as join6 } from "path";
|
|
16339
|
-
import { mkdirSync as mkdirSync6 } from "fs";
|
|
16340
|
-
import { homedir as homedir6 } from "os";
|
|
16341
|
-
async function diffImages(path1, path2) {
|
|
16342
|
-
const img1 = import_sharp2.default(path1);
|
|
16343
|
-
const img2 = import_sharp2.default(path2);
|
|
16344
|
-
const [meta1, meta2] = await Promise.all([img1.metadata(), img2.metadata()]);
|
|
16345
|
-
const w = Math.min(meta1.width ?? 1280, meta2.width ?? 1280);
|
|
16346
|
-
const h = Math.min(meta1.height ?? 720, meta2.height ?? 720);
|
|
16347
|
-
const [raw1, raw2] = await Promise.all([
|
|
16348
|
-
import_sharp2.default(path1).resize(w, h, { fit: "fill" }).raw().toBuffer(),
|
|
16349
|
-
import_sharp2.default(path2).resize(w, h, { fit: "fill" }).raw().toBuffer()
|
|
16350
|
-
]);
|
|
16351
|
-
const totalPixels = w * h;
|
|
16352
|
-
const channels = 3;
|
|
16353
|
-
const diffBuffer = Buffer.alloc(raw1.length);
|
|
16354
|
-
let changedPixels = 0;
|
|
16355
|
-
for (let i = 0;i < raw1.length; i += channels) {
|
|
16356
|
-
const dr = Math.abs(raw1[i] - raw2[i]);
|
|
16357
|
-
const dg = Math.abs(raw1[i + 1] - raw2[i + 1]);
|
|
16358
|
-
const db = Math.abs(raw1[i + 2] - raw2[i + 2]);
|
|
16359
|
-
const diff = (dr + dg + db) / 3;
|
|
16360
|
-
if (diff > 10) {
|
|
16361
|
-
changedPixels++;
|
|
16362
|
-
diffBuffer[i] = 255;
|
|
16363
|
-
diffBuffer[i + 1] = 0;
|
|
16364
|
-
diffBuffer[i + 2] = 0;
|
|
16365
|
-
} else {
|
|
16366
|
-
diffBuffer[i] = Math.round(raw1[i] * 0.4);
|
|
16367
|
-
diffBuffer[i + 1] = Math.round(raw1[i + 1] * 0.4);
|
|
16368
|
-
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
16369
|
-
}
|
|
16370
|
-
}
|
|
16371
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join6(homedir6(), ".browser");
|
|
16372
|
-
const diffDir = join6(dataDir, "diffs");
|
|
16373
|
-
mkdirSync6(diffDir, { recursive: true });
|
|
16374
|
-
const diffPath = join6(diffDir, `diff-${Date.now()}.webp`);
|
|
16375
|
-
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
16376
|
-
await Bun.write(diffPath, diffImageBuffer);
|
|
16377
|
-
return {
|
|
16378
|
-
diff_path: diffPath,
|
|
16379
|
-
diff_base64: diffImageBuffer.toString("base64"),
|
|
16380
|
-
changed_pixels: changedPixels,
|
|
16381
|
-
total_pixels: totalPixels,
|
|
16382
|
-
changed_percent: changedPixels / totalPixels * 100
|
|
16383
|
-
};
|
|
16384
|
-
}
|
|
16385
|
-
var import_sharp2;
|
|
16386
|
-
var init_gallery_diff = __esm(() => {
|
|
16387
|
-
import_sharp2 = __toESM(require_lib(), 1);
|
|
16388
|
-
});
|
|
16389
|
-
|
|
16390
17118
|
// src/lib/files-integration.ts
|
|
16391
|
-
import { join as
|
|
16392
|
-
import { mkdirSync as
|
|
16393
|
-
import { homedir as
|
|
17119
|
+
import { join as join10 } from "path";
|
|
17120
|
+
import { mkdirSync as mkdirSync8, copyFileSync as copyFileSync2 } from "fs";
|
|
17121
|
+
import { homedir as homedir10 } from "os";
|
|
16394
17122
|
async function persistFile(localPath, opts) {
|
|
16395
17123
|
try {
|
|
16396
17124
|
const mod = await import("@hasna/files");
|
|
@@ -16399,12 +17127,12 @@ async function persistFile(localPath, opts) {
|
|
|
16399
17127
|
return { id: ref.id, path: ref.path ?? localPath, permanent: true, provider: "open-files" };
|
|
16400
17128
|
}
|
|
16401
17129
|
} catch {}
|
|
16402
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ??
|
|
17130
|
+
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join10(homedir10(), ".browser");
|
|
16403
17131
|
const date = new Date().toISOString().split("T")[0];
|
|
16404
|
-
const dir =
|
|
16405
|
-
|
|
17132
|
+
const dir = join10(dataDir, "persistent", date);
|
|
17133
|
+
mkdirSync8(dir, { recursive: true });
|
|
16406
17134
|
const filename = localPath.split("/").pop() ?? "file";
|
|
16407
|
-
const targetPath =
|
|
17135
|
+
const targetPath = join10(dir, filename);
|
|
16408
17136
|
copyFileSync2(localPath, targetPath);
|
|
16409
17137
|
return {
|
|
16410
17138
|
id: `local-${Date.now()}`,
|
|
@@ -16510,150 +17238,6 @@ async function closeTab(page, index) {
|
|
|
16510
17238
|
};
|
|
16511
17239
|
}
|
|
16512
17240
|
|
|
16513
|
-
// src/lib/profiles.ts
|
|
16514
|
-
var exports_profiles = {};
|
|
16515
|
-
__export(exports_profiles, {
|
|
16516
|
-
saveProfile: () => saveProfile,
|
|
16517
|
-
loadProfile: () => loadProfile,
|
|
16518
|
-
listProfiles: () => listProfiles,
|
|
16519
|
-
deleteProfile: () => deleteProfile,
|
|
16520
|
-
applyProfile: () => applyProfile
|
|
16521
|
-
});
|
|
16522
|
-
import { mkdirSync as mkdirSync8, existsSync as existsSync4, readdirSync as readdirSync3, rmSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
16523
|
-
import { join as join8 } from "path";
|
|
16524
|
-
import { homedir as homedir8 } from "os";
|
|
16525
|
-
function getProfilesDir() {
|
|
16526
|
-
const dataDir = process.env["BROWSER_DATA_DIR"] ?? join8(homedir8(), ".browser");
|
|
16527
|
-
const dir = join8(dataDir, "profiles");
|
|
16528
|
-
mkdirSync8(dir, { recursive: true });
|
|
16529
|
-
return dir;
|
|
16530
|
-
}
|
|
16531
|
-
function getProfileDir2(name) {
|
|
16532
|
-
return join8(getProfilesDir(), name);
|
|
16533
|
-
}
|
|
16534
|
-
async function saveProfile(page, name) {
|
|
16535
|
-
const dir = getProfileDir2(name);
|
|
16536
|
-
mkdirSync8(dir, { recursive: true });
|
|
16537
|
-
const cookies = await page.context().cookies();
|
|
16538
|
-
writeFileSync2(join8(dir, "cookies.json"), JSON.stringify(cookies, null, 2));
|
|
16539
|
-
let localStorage2 = {};
|
|
16540
|
-
try {
|
|
16541
|
-
localStorage2 = await page.evaluate(() => {
|
|
16542
|
-
const result = {};
|
|
16543
|
-
for (let i = 0;i < window.localStorage.length; i++) {
|
|
16544
|
-
const key = window.localStorage.key(i);
|
|
16545
|
-
result[key] = window.localStorage.getItem(key);
|
|
16546
|
-
}
|
|
16547
|
-
return result;
|
|
16548
|
-
});
|
|
16549
|
-
} catch {}
|
|
16550
|
-
writeFileSync2(join8(dir, "storage.json"), JSON.stringify(localStorage2, null, 2));
|
|
16551
|
-
const savedAt = new Date().toISOString();
|
|
16552
|
-
const url = page.url();
|
|
16553
|
-
const meta = { saved_at: savedAt, url };
|
|
16554
|
-
writeFileSync2(join8(dir, "meta.json"), JSON.stringify(meta, null, 2));
|
|
16555
|
-
return {
|
|
16556
|
-
name,
|
|
16557
|
-
saved_at: savedAt,
|
|
16558
|
-
url,
|
|
16559
|
-
cookie_count: cookies.length,
|
|
16560
|
-
storage_key_count: Object.keys(localStorage2).length
|
|
16561
|
-
};
|
|
16562
|
-
}
|
|
16563
|
-
function loadProfile(name) {
|
|
16564
|
-
const dir = getProfileDir2(name);
|
|
16565
|
-
if (!existsSync4(dir)) {
|
|
16566
|
-
throw new Error(`Profile not found: ${name}`);
|
|
16567
|
-
}
|
|
16568
|
-
const cookiesPath = join8(dir, "cookies.json");
|
|
16569
|
-
const storagePath = join8(dir, "storage.json");
|
|
16570
|
-
const metaPath2 = join8(dir, "meta.json");
|
|
16571
|
-
const cookies = existsSync4(cookiesPath) ? JSON.parse(readFileSync2(cookiesPath, "utf8")) : [];
|
|
16572
|
-
const localStorage2 = existsSync4(storagePath) ? JSON.parse(readFileSync2(storagePath, "utf8")) : {};
|
|
16573
|
-
let savedAt = new Date().toISOString();
|
|
16574
|
-
let url;
|
|
16575
|
-
if (existsSync4(metaPath2)) {
|
|
16576
|
-
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
16577
|
-
savedAt = meta.saved_at ?? savedAt;
|
|
16578
|
-
url = meta.url;
|
|
16579
|
-
}
|
|
16580
|
-
return { cookies, localStorage: localStorage2, saved_at: savedAt, url };
|
|
16581
|
-
}
|
|
16582
|
-
async function applyProfile(page, profileData) {
|
|
16583
|
-
if (profileData.cookies.length > 0) {
|
|
16584
|
-
await page.context().addCookies(profileData.cookies);
|
|
16585
|
-
}
|
|
16586
|
-
const storageKeys = Object.keys(profileData.localStorage);
|
|
16587
|
-
if (storageKeys.length > 0) {
|
|
16588
|
-
try {
|
|
16589
|
-
await page.evaluate((storage) => {
|
|
16590
|
-
for (const [key, value] of Object.entries(storage)) {
|
|
16591
|
-
window.localStorage.setItem(key, value);
|
|
16592
|
-
}
|
|
16593
|
-
}, profileData.localStorage);
|
|
16594
|
-
} catch {}
|
|
16595
|
-
}
|
|
16596
|
-
return {
|
|
16597
|
-
cookies_applied: profileData.cookies.length,
|
|
16598
|
-
storage_keys_applied: storageKeys.length
|
|
16599
|
-
};
|
|
16600
|
-
}
|
|
16601
|
-
function listProfiles() {
|
|
16602
|
-
const dir = getProfilesDir();
|
|
16603
|
-
if (!existsSync4(dir))
|
|
16604
|
-
return [];
|
|
16605
|
-
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
16606
|
-
const profiles = [];
|
|
16607
|
-
for (const entry of entries) {
|
|
16608
|
-
if (!entry.isDirectory())
|
|
16609
|
-
continue;
|
|
16610
|
-
const name = entry.name;
|
|
16611
|
-
const profileDir = join8(dir, name);
|
|
16612
|
-
let savedAt = "";
|
|
16613
|
-
let url;
|
|
16614
|
-
let cookieCount = 0;
|
|
16615
|
-
let storageKeyCount = 0;
|
|
16616
|
-
try {
|
|
16617
|
-
const metaPath2 = join8(profileDir, "meta.json");
|
|
16618
|
-
if (existsSync4(metaPath2)) {
|
|
16619
|
-
const meta = JSON.parse(readFileSync2(metaPath2, "utf8"));
|
|
16620
|
-
savedAt = meta.saved_at ?? "";
|
|
16621
|
-
url = meta.url;
|
|
16622
|
-
}
|
|
16623
|
-
const cookiesPath = join8(profileDir, "cookies.json");
|
|
16624
|
-
if (existsSync4(cookiesPath)) {
|
|
16625
|
-
const cookies = JSON.parse(readFileSync2(cookiesPath, "utf8"));
|
|
16626
|
-
cookieCount = Array.isArray(cookies) ? cookies.length : 0;
|
|
16627
|
-
}
|
|
16628
|
-
const storagePath = join8(profileDir, "storage.json");
|
|
16629
|
-
if (existsSync4(storagePath)) {
|
|
16630
|
-
const storage = JSON.parse(readFileSync2(storagePath, "utf8"));
|
|
16631
|
-
storageKeyCount = Object.keys(storage).length;
|
|
16632
|
-
}
|
|
16633
|
-
} catch {}
|
|
16634
|
-
profiles.push({
|
|
16635
|
-
name,
|
|
16636
|
-
saved_at: savedAt,
|
|
16637
|
-
url,
|
|
16638
|
-
cookie_count: cookieCount,
|
|
16639
|
-
storage_key_count: storageKeyCount
|
|
16640
|
-
});
|
|
16641
|
-
}
|
|
16642
|
-
return profiles.sort((a, b) => b.saved_at.localeCompare(a.saved_at));
|
|
16643
|
-
}
|
|
16644
|
-
function deleteProfile(name) {
|
|
16645
|
-
const dir = getProfileDir2(name);
|
|
16646
|
-
if (!existsSync4(dir))
|
|
16647
|
-
return false;
|
|
16648
|
-
try {
|
|
16649
|
-
rmSync(dir, { recursive: true, force: true });
|
|
16650
|
-
return true;
|
|
16651
|
-
} catch {
|
|
16652
|
-
return false;
|
|
16653
|
-
}
|
|
16654
|
-
}
|
|
16655
|
-
var init_profiles = () => {};
|
|
16656
|
-
|
|
16657
17241
|
// src/lib/sanitize.ts
|
|
16658
17242
|
var exports_sanitize = {};
|
|
16659
17243
|
__export(exports_sanitize, {
|
|
@@ -16805,6 +17389,166 @@ var init_annotate = __esm(() => {
|
|
|
16805
17389
|
import_sharp3 = __toESM(require_lib(), 1);
|
|
16806
17390
|
});
|
|
16807
17391
|
|
|
17392
|
+
// src/lib/workflows.ts
|
|
17393
|
+
var exports_workflows = {};
|
|
17394
|
+
__export(exports_workflows, {
|
|
17395
|
+
saveWorkflowFromRecording: () => saveWorkflowFromRecording,
|
|
17396
|
+
saveWorkflow: () => saveWorkflow,
|
|
17397
|
+
runWorkflow: () => runWorkflow,
|
|
17398
|
+
listWorkflows: () => listWorkflows,
|
|
17399
|
+
getWorkflowByName: () => getWorkflowByName,
|
|
17400
|
+
getWorkflow: () => getWorkflow,
|
|
17401
|
+
deleteWorkflow: () => deleteWorkflow
|
|
17402
|
+
});
|
|
17403
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
17404
|
+
function saveWorkflow(data) {
|
|
17405
|
+
const db = getDatabase();
|
|
17406
|
+
const id = randomUUID11();
|
|
17407
|
+
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);
|
|
17408
|
+
return getWorkflow(id);
|
|
17409
|
+
}
|
|
17410
|
+
function saveWorkflowFromRecording(recordingId, name, description) {
|
|
17411
|
+
const db = getDatabase();
|
|
17412
|
+
const rec = db.query("SELECT steps, start_url FROM recordings WHERE id = ?").get(recordingId);
|
|
17413
|
+
if (!rec)
|
|
17414
|
+
throw new Error(`Recording not found: ${recordingId}`);
|
|
17415
|
+
const steps = JSON.parse(rec.steps);
|
|
17416
|
+
return saveWorkflow({ name, description, steps, startUrl: rec.start_url ?? undefined });
|
|
17417
|
+
}
|
|
17418
|
+
function getWorkflow(id) {
|
|
17419
|
+
const db = getDatabase();
|
|
17420
|
+
const row = db.query("SELECT * FROM workflows WHERE id = ?").get(id);
|
|
17421
|
+
if (!row)
|
|
17422
|
+
return null;
|
|
17423
|
+
return { ...row, steps: JSON.parse(row.steps) };
|
|
17424
|
+
}
|
|
17425
|
+
function getWorkflowByName(name) {
|
|
17426
|
+
const db = getDatabase();
|
|
17427
|
+
const row = db.query("SELECT * FROM workflows WHERE name = ?").get(name);
|
|
17428
|
+
if (!row)
|
|
17429
|
+
return null;
|
|
17430
|
+
return { ...row, steps: JSON.parse(row.steps) };
|
|
17431
|
+
}
|
|
17432
|
+
function listWorkflows() {
|
|
17433
|
+
const db = getDatabase();
|
|
17434
|
+
return db.query("SELECT * FROM workflows ORDER BY updated_at DESC").all().map((row) => ({ ...row, steps: JSON.parse(row.steps) }));
|
|
17435
|
+
}
|
|
17436
|
+
function deleteWorkflow(name) {
|
|
17437
|
+
const db = getDatabase();
|
|
17438
|
+
return db.prepare("DELETE FROM workflows WHERE name = ?").run(name).changes > 0;
|
|
17439
|
+
}
|
|
17440
|
+
function recordRun(id, healed) {
|
|
17441
|
+
const db = getDatabase();
|
|
17442
|
+
if (healed) {
|
|
17443
|
+
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);
|
|
17444
|
+
} else {
|
|
17445
|
+
db.prepare("UPDATE workflows SET last_run = datetime('now'), run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(id);
|
|
17446
|
+
}
|
|
17447
|
+
}
|
|
17448
|
+
async function runWorkflow(workflow, page) {
|
|
17449
|
+
const t0 = Date.now();
|
|
17450
|
+
let executed = 0;
|
|
17451
|
+
let failed = 0;
|
|
17452
|
+
let healed = 0;
|
|
17453
|
+
const healedDetails = [];
|
|
17454
|
+
const errors2 = [];
|
|
17455
|
+
const updatedSteps = [...workflow.steps];
|
|
17456
|
+
for (let i = 0;i < workflow.steps.length; i++) {
|
|
17457
|
+
const step = workflow.steps[i];
|
|
17458
|
+
try {
|
|
17459
|
+
switch (step.type) {
|
|
17460
|
+
case "navigate":
|
|
17461
|
+
if (step.url)
|
|
17462
|
+
await page.goto(step.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
17463
|
+
break;
|
|
17464
|
+
case "click":
|
|
17465
|
+
if (step.selector) {
|
|
17466
|
+
try {
|
|
17467
|
+
await page.click(step.selector, { timeout: 5000 });
|
|
17468
|
+
} catch {
|
|
17469
|
+
const result = await healSelector(page, step.selector);
|
|
17470
|
+
if (result.found && result.locator) {
|
|
17471
|
+
await result.locator.click();
|
|
17472
|
+
healed++;
|
|
17473
|
+
const healedSelector = `[healed:${result.method}]${step.selector}`;
|
|
17474
|
+
healedDetails.push({ step: i, original: step.selector, healed_to: healedSelector, method: result.method });
|
|
17475
|
+
} else {
|
|
17476
|
+
throw new Error(`Click failed: ${step.selector} (self-healing exhausted)`);
|
|
17477
|
+
}
|
|
17478
|
+
}
|
|
17479
|
+
}
|
|
17480
|
+
break;
|
|
17481
|
+
case "type":
|
|
17482
|
+
if (step.selector && step.value) {
|
|
17483
|
+
try {
|
|
17484
|
+
await page.fill(step.selector, step.value);
|
|
17485
|
+
} catch {
|
|
17486
|
+
const result = await healSelector(page, step.selector);
|
|
17487
|
+
if (result.found && result.locator) {
|
|
17488
|
+
await result.locator.fill(step.value);
|
|
17489
|
+
healed++;
|
|
17490
|
+
healedDetails.push({ step: i, original: step.selector, healed_to: `[healed:${result.method}]`, method: result.method });
|
|
17491
|
+
} else {
|
|
17492
|
+
throw new Error(`Type failed: ${step.selector} (self-healing exhausted)`);
|
|
17493
|
+
}
|
|
17494
|
+
}
|
|
17495
|
+
}
|
|
17496
|
+
break;
|
|
17497
|
+
case "scroll":
|
|
17498
|
+
if (step.y)
|
|
17499
|
+
await page.mouse.wheel(0, step.y);
|
|
17500
|
+
break;
|
|
17501
|
+
case "hover":
|
|
17502
|
+
if (step.selector) {
|
|
17503
|
+
try {
|
|
17504
|
+
await page.hover(step.selector);
|
|
17505
|
+
} catch {}
|
|
17506
|
+
}
|
|
17507
|
+
break;
|
|
17508
|
+
case "select":
|
|
17509
|
+
if (step.selector && step.value) {
|
|
17510
|
+
try {
|
|
17511
|
+
await page.selectOption(step.selector, step.value);
|
|
17512
|
+
} catch {}
|
|
17513
|
+
}
|
|
17514
|
+
break;
|
|
17515
|
+
case "wait":
|
|
17516
|
+
if (step.selector) {
|
|
17517
|
+
try {
|
|
17518
|
+
await page.waitForSelector(step.selector, { timeout: 1e4 });
|
|
17519
|
+
} catch {}
|
|
17520
|
+
} else {
|
|
17521
|
+
await new Promise((r) => setTimeout(r, step.timestamp || 1000));
|
|
17522
|
+
}
|
|
17523
|
+
break;
|
|
17524
|
+
case "evaluate":
|
|
17525
|
+
if (step.value)
|
|
17526
|
+
await page.evaluate(step.value);
|
|
17527
|
+
break;
|
|
17528
|
+
default:
|
|
17529
|
+
break;
|
|
17530
|
+
}
|
|
17531
|
+
executed++;
|
|
17532
|
+
} catch (err) {
|
|
17533
|
+
failed++;
|
|
17534
|
+
errors2.push(`Step ${i} (${step.type}): ${err instanceof Error ? err.message : String(err)}`);
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
recordRun(workflow.id, healed > 0);
|
|
17538
|
+
return {
|
|
17539
|
+
success: failed === 0,
|
|
17540
|
+
steps_executed: executed,
|
|
17541
|
+
steps_failed: failed,
|
|
17542
|
+
steps_healed: healed,
|
|
17543
|
+
healed_details: healedDetails,
|
|
17544
|
+
errors: errors2,
|
|
17545
|
+
duration_ms: Date.now() - t0
|
|
17546
|
+
};
|
|
17547
|
+
}
|
|
17548
|
+
var init_workflows = __esm(() => {
|
|
17549
|
+
init_schema();
|
|
17550
|
+
});
|
|
17551
|
+
|
|
16808
17552
|
// src/lib/auth-flow.ts
|
|
16809
17553
|
var exports_auth_flow = {};
|
|
16810
17554
|
__export(exports_auth_flow, {
|
|
@@ -16819,10 +17563,10 @@ __export(exports_auth_flow, {
|
|
|
16819
17563
|
getAuthFlow: () => getAuthFlow,
|
|
16820
17564
|
deleteAuthFlow: () => deleteAuthFlow
|
|
16821
17565
|
});
|
|
16822
|
-
import { randomUUID as
|
|
17566
|
+
import { randomUUID as randomUUID12 } from "crypto";
|
|
16823
17567
|
function saveAuthFlow(data) {
|
|
16824
17568
|
const db = getDatabase();
|
|
16825
|
-
const id =
|
|
17569
|
+
const id = randomUUID12();
|
|
16826
17570
|
db.prepare("INSERT OR REPLACE INTO auth_flows (id, name, domain, recording_id, storage_state_path) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.domain, data.recordingId ?? null, data.storageStatePath ?? null);
|
|
16827
17571
|
return getAuthFlow(id);
|
|
16828
17572
|
}
|
|
@@ -16865,9 +17609,9 @@ async function tryReplayAuth(page, domain) {
|
|
|
16865
17609
|
return { replayed: false };
|
|
16866
17610
|
if (flow.storage_state_path) {
|
|
16867
17611
|
try {
|
|
16868
|
-
const { existsSync:
|
|
16869
|
-
if (
|
|
16870
|
-
const state = JSON.parse(
|
|
17612
|
+
const { existsSync: existsSync7, readFileSync: readFileSync5 } = await import("fs");
|
|
17613
|
+
if (existsSync7(flow.storage_state_path)) {
|
|
17614
|
+
const state = JSON.parse(readFileSync5(flow.storage_state_path, "utf8"));
|
|
16871
17615
|
if (state.cookies?.length) {
|
|
16872
17616
|
await page.context().addCookies(state.cookies);
|
|
16873
17617
|
await page.reload();
|
|
@@ -16992,95 +17736,88 @@ async function clickByVision(page, description, opts) {
|
|
|
16992
17736
|
}
|
|
16993
17737
|
var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
|
|
16994
17738
|
|
|
16995
|
-
// src/lib/
|
|
16996
|
-
var
|
|
16997
|
-
__export(
|
|
16998
|
-
|
|
16999
|
-
|
|
17739
|
+
// src/lib/datasets.ts
|
|
17740
|
+
var exports_datasets = {};
|
|
17741
|
+
__export(exports_datasets, {
|
|
17742
|
+
saveDataset: () => saveDataset,
|
|
17743
|
+
listDatasets: () => listDatasets,
|
|
17744
|
+
getDatasetByName: () => getDatasetByName,
|
|
17745
|
+
getDataset: () => getDataset,
|
|
17746
|
+
exportDataset: () => exportDataset,
|
|
17747
|
+
deleteDataset: () => deleteDataset
|
|
17000
17748
|
});
|
|
17001
|
-
import {
|
|
17002
|
-
import {
|
|
17003
|
-
import {
|
|
17004
|
-
|
|
17005
|
-
|
|
17006
|
-
|
|
17007
|
-
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17011
|
-
|
|
17012
|
-
} catch {}
|
|
17013
|
-
const secretsPath = join9(homedir9(), ".secrets");
|
|
17014
|
-
if (existsSync5(secretsPath)) {
|
|
17015
|
-
const content = readFileSync3(secretsPath, "utf8");
|
|
17016
|
-
const lines = content.split(`
|
|
17017
|
-
`);
|
|
17018
|
-
const prefix = service.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
17019
|
-
const vars = {};
|
|
17020
|
-
for (const line of lines) {
|
|
17021
|
-
const match = line.match(/^export\s+([A-Z_]+)=["']?(.+?)["']?\s*$/);
|
|
17022
|
-
if (match)
|
|
17023
|
-
vars[match[1]] = match[2];
|
|
17024
|
-
}
|
|
17025
|
-
const email = vars[`${prefix}_EMAIL`] ?? vars[`${prefix}_USERNAME`];
|
|
17026
|
-
const password = vars[`${prefix}_PASSWORD`];
|
|
17027
|
-
if (email && password)
|
|
17028
|
-
return { email, password };
|
|
17749
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
17750
|
+
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
17751
|
+
import { join as join11 } from "path";
|
|
17752
|
+
import { homedir as homedir11 } from "os";
|
|
17753
|
+
function saveDataset(data) {
|
|
17754
|
+
const db = getDatabase();
|
|
17755
|
+
const id = randomUUID13();
|
|
17756
|
+
const existing = db.query("SELECT id FROM datasets WHERE name = ?").get(data.name);
|
|
17757
|
+
if (existing) {
|
|
17758
|
+
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);
|
|
17759
|
+
return getDataset(existing.id);
|
|
17029
17760
|
}
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
const envPass = process.env[`${envPrefix}_PASSWORD`];
|
|
17033
|
-
if (envEmail && envPass)
|
|
17034
|
-
return { email: envEmail, password: envPass };
|
|
17035
|
-
return null;
|
|
17761
|
+
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);
|
|
17762
|
+
return getDataset(id);
|
|
17036
17763
|
}
|
|
17037
|
-
|
|
17038
|
-
const
|
|
17039
|
-
const
|
|
17040
|
-
|
|
17041
|
-
|
|
17042
|
-
|
|
17043
|
-
|
|
17764
|
+
function getDataset(id) {
|
|
17765
|
+
const db = getDatabase();
|
|
17766
|
+
const row = db.query("SELECT * FROM datasets WHERE id = ?").get(id);
|
|
17767
|
+
if (!row)
|
|
17768
|
+
return null;
|
|
17769
|
+
return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
|
|
17770
|
+
}
|
|
17771
|
+
function getDatasetByName(name) {
|
|
17772
|
+
const db = getDatabase();
|
|
17773
|
+
const row = db.query("SELECT * FROM datasets WHERE name = ?").get(name);
|
|
17774
|
+
if (!row)
|
|
17775
|
+
return null;
|
|
17776
|
+
return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
|
|
17777
|
+
}
|
|
17778
|
+
function listDatasets() {
|
|
17779
|
+
const db = getDatabase();
|
|
17780
|
+
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 }));
|
|
17781
|
+
}
|
|
17782
|
+
function deleteDataset(name) {
|
|
17783
|
+
const db = getDatabase();
|
|
17784
|
+
return db.prepare("DELETE FROM datasets WHERE name = ?").run(name).changes > 0;
|
|
17785
|
+
}
|
|
17786
|
+
function exportDataset(name, format) {
|
|
17787
|
+
const dataset = getDatasetByName(name);
|
|
17788
|
+
if (!dataset)
|
|
17789
|
+
throw new Error(`Dataset '${name}' not found`);
|
|
17790
|
+
const dir = join11(process.env["BROWSER_DATA_DIR"] ?? join11(homedir11(), ".browser"), "exports");
|
|
17791
|
+
mkdirSync9(dir, { recursive: true });
|
|
17792
|
+
const filename = `${name}.${format}`;
|
|
17793
|
+
const path = join11(dir, filename);
|
|
17794
|
+
if (format === "csv") {
|
|
17795
|
+
const rows = dataset.data;
|
|
17796
|
+
if (rows.length === 0) {
|
|
17797
|
+
writeFileSync3(path, "");
|
|
17798
|
+
return { path, size: 0 };
|
|
17044
17799
|
}
|
|
17045
|
-
const
|
|
17046
|
-
const
|
|
17047
|
-
|
|
17048
|
-
|
|
17049
|
-
|
|
17050
|
-
|
|
17051
|
-
|
|
17052
|
-
fields[emailSel] = credentials.username;
|
|
17053
|
-
if (credentials.password)
|
|
17054
|
-
fields[passSel] = credentials.password;
|
|
17055
|
-
const fillResult = await fillForm2(page, fields, submitSel);
|
|
17056
|
-
const successText = opts?.waitForText ?? "dashboard|profile|account|welcome|signed in|logout";
|
|
17057
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
17058
|
-
const currentUrl = page.url?.() ?? "";
|
|
17059
|
-
const logged_in = fillResult.errors.length === 0;
|
|
17060
|
-
let profile_saved = false;
|
|
17061
|
-
if (opts?.saveProfile && logged_in) {
|
|
17062
|
-
try {
|
|
17063
|
-
await saveProfile2(page, opts.saveProfile);
|
|
17064
|
-
profile_saved = true;
|
|
17065
|
-
} catch {}
|
|
17800
|
+
const headers = Object.keys(rows[0]);
|
|
17801
|
+
const csvLines = [headers.join(",")];
|
|
17802
|
+
for (const row of rows) {
|
|
17803
|
+
csvLines.push(headers.map((h) => {
|
|
17804
|
+
const val = String(row[h] ?? "");
|
|
17805
|
+
return val.includes(",") || val.includes('"') ? `"${val.replace(/"/g, '""')}"` : val;
|
|
17806
|
+
}).join(","));
|
|
17066
17807
|
}
|
|
17067
|
-
|
|
17068
|
-
|
|
17069
|
-
|
|
17070
|
-
|
|
17071
|
-
|
|
17072
|
-
|
|
17073
|
-
|
|
17074
|
-
return {
|
|
17075
|
-
logged_in: false,
|
|
17076
|
-
redirect_url: "",
|
|
17077
|
-
profile_saved: false,
|
|
17078
|
-
method: "not_found",
|
|
17079
|
-
error: err instanceof Error ? err.message : String(err)
|
|
17080
|
-
};
|
|
17808
|
+
const content = csvLines.join(`
|
|
17809
|
+
`);
|
|
17810
|
+
writeFileSync3(path, content);
|
|
17811
|
+
return { path, size: content.length };
|
|
17812
|
+
} else {
|
|
17813
|
+
const content = JSON.stringify(dataset.data, null, 2);
|
|
17814
|
+
writeFileSync3(path, content);
|
|
17815
|
+
return { path, size: content.length };
|
|
17081
17816
|
}
|
|
17082
17817
|
}
|
|
17083
|
-
var
|
|
17818
|
+
var init_datasets = __esm(() => {
|
|
17819
|
+
init_schema();
|
|
17820
|
+
});
|
|
17084
17821
|
|
|
17085
17822
|
// ../open-mementos/dist/index.js
|
|
17086
17823
|
var exports_dist = {};
|
|
@@ -17192,10 +17929,10 @@ __export(exports_dist, {
|
|
|
17192
17929
|
DEFAULT_CONFIG: () => DEFAULT_CONFIG
|
|
17193
17930
|
});
|
|
17194
17931
|
import { Database as Database2 } from "bun:sqlite";
|
|
17195
|
-
import { existsSync as
|
|
17196
|
-
import { dirname, join as
|
|
17197
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as
|
|
17198
|
-
import { homedir as
|
|
17932
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync10 } from "fs";
|
|
17933
|
+
import { dirname, join as join12, resolve } from "path";
|
|
17934
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync5, readdirSync as readdirSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
17935
|
+
import { homedir as homedir12 } from "os";
|
|
17199
17936
|
import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve2 } from "path";
|
|
17200
17937
|
import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
|
|
17201
17938
|
import { homedir as homedir22 } from "os";
|
|
@@ -17209,8 +17946,8 @@ function isInMemoryDb(path) {
|
|
|
17209
17946
|
function findNearestMementosDb(startDir) {
|
|
17210
17947
|
let dir = resolve(startDir);
|
|
17211
17948
|
while (true) {
|
|
17212
|
-
const candidate =
|
|
17213
|
-
if (
|
|
17949
|
+
const candidate = join12(dir, ".mementos", "mementos.db");
|
|
17950
|
+
if (existsSync7(candidate))
|
|
17214
17951
|
return candidate;
|
|
17215
17952
|
const parent = dirname(dir);
|
|
17216
17953
|
if (parent === dir)
|
|
@@ -17222,7 +17959,7 @@ function findNearestMementosDb(startDir) {
|
|
|
17222
17959
|
function findGitRoot(startDir) {
|
|
17223
17960
|
let dir = resolve(startDir);
|
|
17224
17961
|
while (true) {
|
|
17225
|
-
if (
|
|
17962
|
+
if (existsSync7(join12(dir, ".git")))
|
|
17226
17963
|
return dir;
|
|
17227
17964
|
const parent = dirname(dir);
|
|
17228
17965
|
if (parent === dir)
|
|
@@ -17242,18 +17979,18 @@ function getDbPath() {
|
|
|
17242
17979
|
if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
|
|
17243
17980
|
const gitRoot = findGitRoot(cwd);
|
|
17244
17981
|
if (gitRoot) {
|
|
17245
|
-
return
|
|
17982
|
+
return join12(gitRoot, ".mementos", "mementos.db");
|
|
17246
17983
|
}
|
|
17247
17984
|
}
|
|
17248
17985
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
17249
|
-
return
|
|
17986
|
+
return join12(home, ".mementos", "mementos.db");
|
|
17250
17987
|
}
|
|
17251
17988
|
function ensureDir2(filePath) {
|
|
17252
17989
|
if (isInMemoryDb(filePath))
|
|
17253
17990
|
return;
|
|
17254
17991
|
const dir = dirname(resolve(filePath));
|
|
17255
|
-
if (!
|
|
17256
|
-
|
|
17992
|
+
if (!existsSync7(dir)) {
|
|
17993
|
+
mkdirSync10(dir, { recursive: true });
|
|
17257
17994
|
}
|
|
17258
17995
|
}
|
|
17259
17996
|
function getDatabase2(dbPath) {
|
|
@@ -19103,11 +19840,11 @@ function isValidCategory(value) {
|
|
|
19103
19840
|
return VALID_CATEGORIES.includes(value);
|
|
19104
19841
|
}
|
|
19105
19842
|
function loadConfig() {
|
|
19106
|
-
const configPath = join22(
|
|
19843
|
+
const configPath = join22(homedir12(), ".mementos", "config.json");
|
|
19107
19844
|
let fileConfig = {};
|
|
19108
19845
|
if (existsSync22(configPath)) {
|
|
19109
19846
|
try {
|
|
19110
|
-
const raw =
|
|
19847
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
19111
19848
|
fileConfig = JSON.parse(raw);
|
|
19112
19849
|
} catch {}
|
|
19113
19850
|
}
|
|
@@ -19130,17 +19867,17 @@ function loadConfig() {
|
|
|
19130
19867
|
return merged;
|
|
19131
19868
|
}
|
|
19132
19869
|
function profilesDir() {
|
|
19133
|
-
return join22(
|
|
19870
|
+
return join22(homedir12(), ".mementos", "profiles");
|
|
19134
19871
|
}
|
|
19135
19872
|
function globalConfigPath() {
|
|
19136
|
-
return join22(
|
|
19873
|
+
return join22(homedir12(), ".mementos", "config.json");
|
|
19137
19874
|
}
|
|
19138
19875
|
function readGlobalConfig() {
|
|
19139
19876
|
const p = globalConfigPath();
|
|
19140
19877
|
if (!existsSync22(p))
|
|
19141
19878
|
return {};
|
|
19142
19879
|
try {
|
|
19143
|
-
return JSON.parse(
|
|
19880
|
+
return JSON.parse(readFileSync5(p, "utf-8"));
|
|
19144
19881
|
} catch {
|
|
19145
19882
|
return {};
|
|
19146
19883
|
}
|
|
@@ -19148,7 +19885,7 @@ function readGlobalConfig() {
|
|
|
19148
19885
|
function writeGlobalConfig(data) {
|
|
19149
19886
|
const p = globalConfigPath();
|
|
19150
19887
|
ensureDir22(dirname2(p));
|
|
19151
|
-
|
|
19888
|
+
writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
|
|
19152
19889
|
}
|
|
19153
19890
|
function getActiveProfile() {
|
|
19154
19891
|
const envProfile = process.env["MEMENTOS_PROFILE"];
|
|
@@ -21813,30 +22550,30 @@ __export(exports_dist2, {
|
|
|
21813
22550
|
acquireLock: () => acquireLock2
|
|
21814
22551
|
});
|
|
21815
22552
|
import { Database as Database3 } from "bun:sqlite";
|
|
21816
|
-
import { mkdirSync as
|
|
21817
|
-
import { join as
|
|
21818
|
-
import { homedir as
|
|
21819
|
-
import { randomUUID as
|
|
22553
|
+
import { mkdirSync as mkdirSync11 } from "fs";
|
|
22554
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
22555
|
+
import { homedir as homedir13 } from "os";
|
|
22556
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
21820
22557
|
import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
|
|
21821
22558
|
import { join as join33 } from "path";
|
|
21822
22559
|
import { homedir as homedir33 } from "os";
|
|
21823
|
-
import { readFileSync as
|
|
22560
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
21824
22561
|
import { join as join23 } from "path";
|
|
21825
22562
|
import { homedir as homedir23 } from "os";
|
|
21826
22563
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
21827
|
-
import { readFileSync as readFileSync23, writeFileSync as
|
|
22564
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync33 } from "fs";
|
|
21828
22565
|
import { join as join43, dirname as dirname22 } from "path";
|
|
21829
22566
|
import { homedir as homedir42 } from "os";
|
|
21830
22567
|
function getDbPath2() {
|
|
21831
22568
|
if (process.env.CONVERSATIONS_DB_PATH)
|
|
21832
22569
|
return process.env.CONVERSATIONS_DB_PATH;
|
|
21833
|
-
return
|
|
22570
|
+
return join13(homedir13(), ".conversations", "messages.db");
|
|
21834
22571
|
}
|
|
21835
22572
|
function getDb() {
|
|
21836
22573
|
if (db)
|
|
21837
22574
|
return db;
|
|
21838
22575
|
const dbPath = getDbPath2();
|
|
21839
|
-
|
|
22576
|
+
mkdirSync11(dirname3(dbPath), { recursive: true });
|
|
21840
22577
|
db = new Database3(dbPath, { create: true });
|
|
21841
22578
|
db.exec("PRAGMA journal_mode = WAL");
|
|
21842
22579
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -22078,7 +22815,7 @@ function loadConfig2() {
|
|
|
22078
22815
|
if (cachedConfig && now2 - configLoadedAt < CONFIG_CACHE_MS)
|
|
22079
22816
|
return cachedConfig;
|
|
22080
22817
|
try {
|
|
22081
|
-
const raw =
|
|
22818
|
+
const raw = readFileSync6(getConfigPath(), "utf-8");
|
|
22082
22819
|
cachedConfig = JSON.parse(raw);
|
|
22083
22820
|
configLoadedAt = now2;
|
|
22084
22821
|
return cachedConfig;
|
|
@@ -22196,7 +22933,7 @@ function guessMimeType(name) {
|
|
|
22196
22933
|
function sendMessage(opts) {
|
|
22197
22934
|
const db2 = getDb();
|
|
22198
22935
|
const explicitSession = opts.session_id && opts.session_id.trim().length > 0 ? opts.session_id : undefined;
|
|
22199
|
-
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${
|
|
22936
|
+
const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID14().slice(0, 8)}`);
|
|
22200
22937
|
const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
|
|
22201
22938
|
const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
|
|
22202
22939
|
const blocking = opts.blocking ? 1 : 0;
|
|
@@ -23024,7 +23761,7 @@ function getAutoName() {
|
|
|
23024
23761
|
cachedAutoName = name;
|
|
23025
23762
|
try {
|
|
23026
23763
|
mkdirSync33(dirname22(AGENT_ID_FILE), { recursive: true });
|
|
23027
|
-
|
|
23764
|
+
writeFileSync5(AGENT_ID_FILE, name + `
|
|
23028
23765
|
`, "utf-8");
|
|
23029
23766
|
} catch {}
|
|
23030
23767
|
return name;
|
|
@@ -24979,7 +25716,7 @@ Your code should look like:
|
|
|
24979
25716
|
}
|
|
24980
25717
|
}
|
|
24981
25718
|
}
|
|
24982
|
-
function checkPropTypes(typeSpecs, values,
|
|
25719
|
+
function checkPropTypes(typeSpecs, values, location2, componentName, element) {
|
|
24983
25720
|
{
|
|
24984
25721
|
var has = Function.call.bind(hasOwnProperty);
|
|
24985
25722
|
for (var typeSpecName in typeSpecs) {
|
|
@@ -24987,23 +25724,23 @@ Your code should look like:
|
|
|
24987
25724
|
var error$1 = undefined;
|
|
24988
25725
|
try {
|
|
24989
25726
|
if (typeof typeSpecs[typeSpecName] !== "function") {
|
|
24990
|
-
var err = Error((componentName || "React class") + ": " +
|
|
25727
|
+
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`.");
|
|
24991
25728
|
err.name = "Invariant Violation";
|
|
24992
25729
|
throw err;
|
|
24993
25730
|
}
|
|
24994
|
-
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName,
|
|
25731
|
+
error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location2, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
|
|
24995
25732
|
} catch (ex) {
|
|
24996
25733
|
error$1 = ex;
|
|
24997
25734
|
}
|
|
24998
25735
|
if (error$1 && !(error$1 instanceof Error)) {
|
|
24999
25736
|
setCurrentlyValidatingElement(element);
|
|
25000
|
-
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",
|
|
25737
|
+
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);
|
|
25001
25738
|
setCurrentlyValidatingElement(null);
|
|
25002
25739
|
}
|
|
25003
25740
|
if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
|
|
25004
25741
|
loggedTypeFailures[error$1.message] = true;
|
|
25005
25742
|
setCurrentlyValidatingElement(element);
|
|
25006
|
-
error("Failed %s type: %s",
|
|
25743
|
+
error("Failed %s type: %s", location2, error$1.message);
|
|
25007
25744
|
setCurrentlyValidatingElement(null);
|
|
25008
25745
|
}
|
|
25009
25746
|
}
|
|
@@ -26238,11 +26975,11 @@ __export(exports_dist3, {
|
|
|
26238
26975
|
AgentNotFoundError: () => AgentNotFoundError2
|
|
26239
26976
|
});
|
|
26240
26977
|
import { Database as Database4 } from "bun:sqlite";
|
|
26241
|
-
import { existsSync as
|
|
26242
|
-
import { dirname as dirname5, join as
|
|
26978
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync12 } from "fs";
|
|
26979
|
+
import { dirname as dirname5, join as join14, resolve as resolve3 } from "path";
|
|
26243
26980
|
import { existsSync as existsSync33 } from "fs";
|
|
26244
26981
|
import { join as join34 } from "path";
|
|
26245
|
-
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as
|
|
26982
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
26246
26983
|
import { join as join24 } from "path";
|
|
26247
26984
|
import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
|
|
26248
26985
|
import { join as join44 } from "path";
|
|
@@ -26472,8 +27209,8 @@ function isInMemoryDb2(path) {
|
|
|
26472
27209
|
function findNearestTodosDb(startDir) {
|
|
26473
27210
|
let dir = resolve3(startDir);
|
|
26474
27211
|
while (true) {
|
|
26475
|
-
const candidate =
|
|
26476
|
-
if (
|
|
27212
|
+
const candidate = join14(dir, ".todos", "todos.db");
|
|
27213
|
+
if (existsSync8(candidate))
|
|
26477
27214
|
return candidate;
|
|
26478
27215
|
const parent = dirname5(dir);
|
|
26479
27216
|
if (parent === dir)
|
|
@@ -26485,7 +27222,7 @@ function findNearestTodosDb(startDir) {
|
|
|
26485
27222
|
function findGitRoot2(startDir) {
|
|
26486
27223
|
let dir = resolve3(startDir);
|
|
26487
27224
|
while (true) {
|
|
26488
|
-
if (
|
|
27225
|
+
if (existsSync8(join14(dir, ".git")))
|
|
26489
27226
|
return dir;
|
|
26490
27227
|
const parent = dirname5(dir);
|
|
26491
27228
|
if (parent === dir)
|
|
@@ -26505,18 +27242,18 @@ function getDbPath3() {
|
|
|
26505
27242
|
if (process.env["TODOS_DB_SCOPE"] === "project") {
|
|
26506
27243
|
const gitRoot = findGitRoot2(cwd);
|
|
26507
27244
|
if (gitRoot) {
|
|
26508
|
-
return
|
|
27245
|
+
return join14(gitRoot, ".todos", "todos.db");
|
|
26509
27246
|
}
|
|
26510
27247
|
}
|
|
26511
27248
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
|
|
26512
|
-
return
|
|
27249
|
+
return join14(home, ".todos", "todos.db");
|
|
26513
27250
|
}
|
|
26514
27251
|
function ensureDir3(filePath) {
|
|
26515
27252
|
if (isInMemoryDb2(filePath))
|
|
26516
27253
|
return;
|
|
26517
27254
|
const dir = dirname5(resolve3(filePath));
|
|
26518
|
-
if (!
|
|
26519
|
-
|
|
27255
|
+
if (!existsSync8(dir)) {
|
|
27256
|
+
mkdirSync12(dir, { recursive: true });
|
|
26520
27257
|
}
|
|
26521
27258
|
}
|
|
26522
27259
|
function getDatabase3(dbPath) {
|
|
@@ -26986,24 +27723,24 @@ function listJsonFiles(dir) {
|
|
|
26986
27723
|
}
|
|
26987
27724
|
function readJsonFile(path) {
|
|
26988
27725
|
try {
|
|
26989
|
-
return JSON.parse(
|
|
27726
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
26990
27727
|
} catch {
|
|
26991
27728
|
return null;
|
|
26992
27729
|
}
|
|
26993
27730
|
}
|
|
26994
27731
|
function writeJsonFile(path, data) {
|
|
26995
|
-
|
|
27732
|
+
writeFileSync6(path, JSON.stringify(data, null, 2) + `
|
|
26996
27733
|
`);
|
|
26997
27734
|
}
|
|
26998
27735
|
function readHighWaterMark(dir) {
|
|
26999
27736
|
const path = join24(dir, ".highwatermark");
|
|
27000
27737
|
if (!existsSync23(path))
|
|
27001
27738
|
return 1;
|
|
27002
|
-
const val = parseInt(
|
|
27739
|
+
const val = parseInt(readFileSync7(path, "utf-8").trim(), 10);
|
|
27003
27740
|
return isNaN(val) ? 1 : val;
|
|
27004
27741
|
}
|
|
27005
27742
|
function writeHighWaterMark(dir, value) {
|
|
27006
|
-
|
|
27743
|
+
writeFileSync6(join24(dir, ".highwatermark"), String(value));
|
|
27007
27744
|
}
|
|
27008
27745
|
function getFileMtimeMs(path) {
|
|
27009
27746
|
try {
|
|
@@ -31593,9 +32330,9 @@ __export(exports_dist4, {
|
|
|
31593
32330
|
CATEGORIES: () => CATEGORIES,
|
|
31594
32331
|
AGENT_TARGETS: () => AGENT_TARGETS
|
|
31595
32332
|
});
|
|
31596
|
-
import { existsSync as
|
|
31597
|
-
import { join as
|
|
31598
|
-
import { homedir as
|
|
32333
|
+
import { existsSync as existsSync9, cpSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync7, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync8, accessSync, constants } from "fs";
|
|
32334
|
+
import { join as join15, dirname as dirname6 } from "path";
|
|
32335
|
+
import { homedir as homedir14 } from "os";
|
|
31599
32336
|
import { fileURLToPath } from "url";
|
|
31600
32337
|
import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
|
|
31601
32338
|
import { join as join25 } from "path";
|
|
@@ -31706,35 +32443,35 @@ function normalizeSkillName(name) {
|
|
|
31706
32443
|
function findSkillsDir() {
|
|
31707
32444
|
let dir = __dirname2;
|
|
31708
32445
|
for (let i = 0;i < 5; i++) {
|
|
31709
|
-
const candidate =
|
|
31710
|
-
if (
|
|
32446
|
+
const candidate = join15(dir, "skills");
|
|
32447
|
+
if (existsSync9(candidate)) {
|
|
31711
32448
|
return candidate;
|
|
31712
32449
|
}
|
|
31713
32450
|
dir = dirname6(dir);
|
|
31714
32451
|
}
|
|
31715
|
-
return
|
|
32452
|
+
return join15(__dirname2, "..", "skills");
|
|
31716
32453
|
}
|
|
31717
32454
|
function getSkillPath(name) {
|
|
31718
32455
|
const skillName = normalizeSkillName(name);
|
|
31719
|
-
return
|
|
32456
|
+
return join15(SKILLS_DIR, skillName);
|
|
31720
32457
|
}
|
|
31721
32458
|
function skillExists(name) {
|
|
31722
|
-
return
|
|
32459
|
+
return existsSync9(getSkillPath(name));
|
|
31723
32460
|
}
|
|
31724
32461
|
function installSkill(name, options = {}) {
|
|
31725
32462
|
const { targetDir = process.cwd(), overwrite = false } = options;
|
|
31726
32463
|
const skillName = normalizeSkillName(name);
|
|
31727
32464
|
const sourcePath = getSkillPath(name);
|
|
31728
|
-
const destDir =
|
|
31729
|
-
const destPath =
|
|
31730
|
-
if (!
|
|
32465
|
+
const destDir = join15(targetDir, ".skills");
|
|
32466
|
+
const destPath = join15(destDir, skillName);
|
|
32467
|
+
if (!existsSync9(sourcePath)) {
|
|
31731
32468
|
return {
|
|
31732
32469
|
skill: name,
|
|
31733
32470
|
success: false,
|
|
31734
32471
|
error: `Skill '${name}' not found`
|
|
31735
32472
|
};
|
|
31736
32473
|
}
|
|
31737
|
-
if (
|
|
32474
|
+
if (existsSync9(destPath) && !overwrite) {
|
|
31738
32475
|
return {
|
|
31739
32476
|
skill: name,
|
|
31740
32477
|
success: false,
|
|
@@ -31743,10 +32480,10 @@ function installSkill(name, options = {}) {
|
|
|
31743
32480
|
};
|
|
31744
32481
|
}
|
|
31745
32482
|
try {
|
|
31746
|
-
if (!
|
|
31747
|
-
|
|
32483
|
+
if (!existsSync9(destDir)) {
|
|
32484
|
+
mkdirSync13(destDir, { recursive: true });
|
|
31748
32485
|
}
|
|
31749
|
-
if (
|
|
32486
|
+
if (existsSync9(destPath) && overwrite) {
|
|
31750
32487
|
rmSync2(destPath, { recursive: true, force: true });
|
|
31751
32488
|
}
|
|
31752
32489
|
cpSync(sourcePath, destPath, {
|
|
@@ -31785,7 +32522,7 @@ function installSkills(names, options = {}) {
|
|
|
31785
32522
|
return names.map((name) => installSkill(name, options));
|
|
31786
32523
|
}
|
|
31787
32524
|
function updateSkillsIndex(skillsDir) {
|
|
31788
|
-
const indexPath =
|
|
32525
|
+
const indexPath = join15(skillsDir, "index.ts");
|
|
31789
32526
|
const meta = loadMeta(skillsDir);
|
|
31790
32527
|
const disabledSet = new Set(meta.disabled || []);
|
|
31791
32528
|
const skills = readdirSync6(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
|
|
@@ -31801,31 +32538,31 @@ function updateSkillsIndex(skillsDir) {
|
|
|
31801
32538
|
|
|
31802
32539
|
${exports}
|
|
31803
32540
|
`;
|
|
31804
|
-
|
|
32541
|
+
writeFileSync7(indexPath, content);
|
|
31805
32542
|
}
|
|
31806
32543
|
function getMetaPath(skillsDir) {
|
|
31807
|
-
return
|
|
32544
|
+
return join15(skillsDir, ".meta.json");
|
|
31808
32545
|
}
|
|
31809
32546
|
function loadMeta(skillsDir) {
|
|
31810
32547
|
const metaPath2 = getMetaPath(skillsDir);
|
|
31811
|
-
if (
|
|
32548
|
+
if (existsSync9(metaPath2)) {
|
|
31812
32549
|
try {
|
|
31813
|
-
return JSON.parse(
|
|
32550
|
+
return JSON.parse(readFileSync8(metaPath2, "utf-8"));
|
|
31814
32551
|
} catch {}
|
|
31815
32552
|
}
|
|
31816
32553
|
return { skills: {} };
|
|
31817
32554
|
}
|
|
31818
32555
|
function saveMeta(skillsDir, meta) {
|
|
31819
|
-
|
|
32556
|
+
writeFileSync7(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
|
|
31820
32557
|
}
|
|
31821
32558
|
function recordInstall(skillsDir, name) {
|
|
31822
32559
|
const meta = loadMeta(skillsDir);
|
|
31823
32560
|
const skillName = normalizeSkillName(name);
|
|
31824
32561
|
let version = "unknown";
|
|
31825
32562
|
try {
|
|
31826
|
-
const pkgPath =
|
|
31827
|
-
if (
|
|
31828
|
-
const pkg = JSON.parse(
|
|
32563
|
+
const pkgPath = join15(skillsDir, skillName, "package.json");
|
|
32564
|
+
if (existsSync9(pkgPath)) {
|
|
32565
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
31829
32566
|
version = pkg.version || "unknown";
|
|
31830
32567
|
}
|
|
31831
32568
|
} catch {}
|
|
@@ -31838,12 +32575,12 @@ function recordRemove(skillsDir, name) {
|
|
|
31838
32575
|
saveMeta(skillsDir, meta);
|
|
31839
32576
|
}
|
|
31840
32577
|
function getInstallMeta(targetDir = process.cwd()) {
|
|
31841
|
-
return loadMeta(
|
|
32578
|
+
return loadMeta(join15(targetDir, ".skills"));
|
|
31842
32579
|
}
|
|
31843
32580
|
function disableSkill(name, targetDir = process.cwd()) {
|
|
31844
|
-
const skillsDir =
|
|
32581
|
+
const skillsDir = join15(targetDir, ".skills");
|
|
31845
32582
|
const skillName = normalizeSkillName(name);
|
|
31846
|
-
if (!
|
|
32583
|
+
if (!existsSync9(join15(skillsDir, skillName)))
|
|
31847
32584
|
return false;
|
|
31848
32585
|
const meta = loadMeta(skillsDir);
|
|
31849
32586
|
const disabled = new Set(meta.disabled || []);
|
|
@@ -31856,7 +32593,7 @@ function disableSkill(name, targetDir = process.cwd()) {
|
|
|
31856
32593
|
return true;
|
|
31857
32594
|
}
|
|
31858
32595
|
function enableSkill(name, targetDir = process.cwd()) {
|
|
31859
|
-
const skillsDir =
|
|
32596
|
+
const skillsDir = join15(targetDir, ".skills");
|
|
31860
32597
|
const meta = loadMeta(skillsDir);
|
|
31861
32598
|
const disabled = new Set(meta.disabled || []);
|
|
31862
32599
|
if (!disabled.has(name))
|
|
@@ -31868,24 +32605,24 @@ function enableSkill(name, targetDir = process.cwd()) {
|
|
|
31868
32605
|
return true;
|
|
31869
32606
|
}
|
|
31870
32607
|
function getDisabledSkills(targetDir = process.cwd()) {
|
|
31871
|
-
const meta = loadMeta(
|
|
32608
|
+
const meta = loadMeta(join15(targetDir, ".skills"));
|
|
31872
32609
|
return meta.disabled || [];
|
|
31873
32610
|
}
|
|
31874
32611
|
function getInstalledSkills(targetDir = process.cwd()) {
|
|
31875
|
-
const skillsDir =
|
|
31876
|
-
if (!
|
|
32612
|
+
const skillsDir = join15(targetDir, ".skills");
|
|
32613
|
+
if (!existsSync9(skillsDir)) {
|
|
31877
32614
|
return [];
|
|
31878
32615
|
}
|
|
31879
32616
|
return readdirSync6(skillsDir).filter((f) => {
|
|
31880
|
-
const fullPath =
|
|
32617
|
+
const fullPath = join15(skillsDir, f);
|
|
31881
32618
|
return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
|
|
31882
32619
|
}).map((f) => f.replace("skill-", ""));
|
|
31883
32620
|
}
|
|
31884
32621
|
function removeSkill(name, targetDir = process.cwd()) {
|
|
31885
32622
|
const skillName = normalizeSkillName(name);
|
|
31886
|
-
const skillsDir =
|
|
31887
|
-
const skillPath =
|
|
31888
|
-
if (!
|
|
32623
|
+
const skillsDir = join15(targetDir, ".skills");
|
|
32624
|
+
const skillPath = join15(skillsDir, skillName);
|
|
32625
|
+
if (!existsSync9(skillPath)) {
|
|
31889
32626
|
return false;
|
|
31890
32627
|
}
|
|
31891
32628
|
rmSync2(skillPath, { recursive: true, force: true });
|
|
@@ -31896,25 +32633,25 @@ function removeSkill(name, targetDir = process.cwd()) {
|
|
|
31896
32633
|
function getAgentSkillsDir(agent, scope = "global", projectDir) {
|
|
31897
32634
|
const agentDir = `.${agent}`;
|
|
31898
32635
|
if (scope === "project") {
|
|
31899
|
-
return
|
|
32636
|
+
return join15(projectDir || process.cwd(), agentDir, "skills");
|
|
31900
32637
|
}
|
|
31901
|
-
return
|
|
32638
|
+
return join15(homedir14(), agentDir, "skills");
|
|
31902
32639
|
}
|
|
31903
32640
|
function getAgentSkillPath(name, agent, scope = "global", projectDir) {
|
|
31904
32641
|
const skillName = normalizeSkillName(name);
|
|
31905
|
-
return
|
|
32642
|
+
return join15(getAgentSkillsDir(agent, scope, projectDir), skillName);
|
|
31906
32643
|
}
|
|
31907
32644
|
function installSkillForAgent(name, options, generateSkillMd) {
|
|
31908
32645
|
const { agent, scope = "global", projectDir } = options;
|
|
31909
32646
|
const skillName = normalizeSkillName(name);
|
|
31910
32647
|
const sourcePath = getSkillPath(name);
|
|
31911
|
-
if (!
|
|
32648
|
+
if (!existsSync9(sourcePath)) {
|
|
31912
32649
|
return { skill: name, success: false, error: `Skill '${name}' not found` };
|
|
31913
32650
|
}
|
|
31914
32651
|
let skillMdContent = null;
|
|
31915
|
-
const skillMdPath =
|
|
31916
|
-
if (
|
|
31917
|
-
skillMdContent =
|
|
32652
|
+
const skillMdPath = join15(sourcePath, "SKILL.md");
|
|
32653
|
+
if (existsSync9(skillMdPath)) {
|
|
32654
|
+
skillMdContent = readFileSync8(skillMdPath, "utf-8");
|
|
31918
32655
|
} else if (generateSkillMd) {
|
|
31919
32656
|
skillMdContent = generateSkillMd(name);
|
|
31920
32657
|
}
|
|
@@ -31923,8 +32660,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31923
32660
|
}
|
|
31924
32661
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
31925
32662
|
if (scope === "global") {
|
|
31926
|
-
const agentBaseDir2 =
|
|
31927
|
-
if (!
|
|
32663
|
+
const agentBaseDir2 = join15(homedir14(), `.${agent}`);
|
|
32664
|
+
if (!existsSync9(agentBaseDir2)) {
|
|
31928
32665
|
const agentLabels = {
|
|
31929
32666
|
claude: "Claude Code",
|
|
31930
32667
|
codex: "Codex CLI",
|
|
@@ -31947,8 +32684,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31947
32684
|
}
|
|
31948
32685
|
}
|
|
31949
32686
|
try {
|
|
31950
|
-
|
|
31951
|
-
|
|
32687
|
+
mkdirSync13(destDir, { recursive: true });
|
|
32688
|
+
writeFileSync7(join15(destDir, "SKILL.md"), skillMdContent);
|
|
31952
32689
|
return { skill: name, success: true, path: destDir };
|
|
31953
32690
|
} catch (error) {
|
|
31954
32691
|
return {
|
|
@@ -31961,7 +32698,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
|
|
|
31961
32698
|
function removeSkillForAgent(name, options) {
|
|
31962
32699
|
const { agent, scope = "global", projectDir } = options;
|
|
31963
32700
|
const destDir = getAgentSkillPath(name, agent, scope, projectDir);
|
|
31964
|
-
if (!
|
|
32701
|
+
if (!existsSync9(destDir)) {
|
|
31965
32702
|
return false;
|
|
31966
32703
|
}
|
|
31967
32704
|
rmSync2(destDir, { recursive: true, force: true });
|
|
@@ -33885,7 +34622,7 @@ __export(exports_cron_manager, {
|
|
|
33885
34622
|
deleteCronJob: () => deleteCronJob,
|
|
33886
34623
|
createCronJob: () => createCronJob
|
|
33887
34624
|
});
|
|
33888
|
-
import { randomUUID as
|
|
34625
|
+
import { randomUUID as randomUUID15 } from "crypto";
|
|
33889
34626
|
function ensureCronTable() {
|
|
33890
34627
|
const db2 = getDatabase();
|
|
33891
34628
|
db2.exec(`
|
|
@@ -33914,7 +34651,7 @@ function ensureCronTable() {
|
|
|
33914
34651
|
function createCronJob(schedule, task, name) {
|
|
33915
34652
|
ensureCronTable();
|
|
33916
34653
|
const db2 = getDatabase();
|
|
33917
|
-
const id =
|
|
34654
|
+
const id = randomUUID15();
|
|
33918
34655
|
db2.prepare(`
|
|
33919
34656
|
INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
|
|
33920
34657
|
VALUES (?, ?, ?, ?, 1)
|
|
@@ -33972,7 +34709,7 @@ function getCronEvents(jobId, limit = 10) {
|
|
|
33972
34709
|
async function executeCronJob(job) {
|
|
33973
34710
|
ensureCronTable();
|
|
33974
34711
|
const db2 = getDatabase();
|
|
33975
|
-
const eventId =
|
|
34712
|
+
const eventId = randomUUID15();
|
|
33976
34713
|
const startedAt = new Date().toISOString();
|
|
33977
34714
|
db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
|
|
33978
34715
|
try {
|
|
@@ -34063,7 +34800,7 @@ __export(exports_url_watcher, {
|
|
|
34063
34800
|
deleteWatchJob: () => deleteWatchJob,
|
|
34064
34801
|
createWatchJob: () => createWatchJob
|
|
34065
34802
|
});
|
|
34066
|
-
import { randomUUID as
|
|
34803
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
34067
34804
|
import { createHash } from "crypto";
|
|
34068
34805
|
function ensureWatchTables() {
|
|
34069
34806
|
const db2 = getDatabase();
|
|
@@ -34095,7 +34832,7 @@ function ensureWatchTables() {
|
|
|
34095
34832
|
function createWatchJob(url, schedule, opts) {
|
|
34096
34833
|
ensureWatchTables();
|
|
34097
34834
|
const db2 = getDatabase();
|
|
34098
|
-
const id =
|
|
34835
|
+
const id = randomUUID16();
|
|
34099
34836
|
db2.prepare(`
|
|
34100
34837
|
INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
|
|
34101
34838
|
VALUES (?, ?, ?, ?, ?, ?, 1)
|
|
@@ -34131,7 +34868,7 @@ function getWatchEvents(watchId, limit = 20) {
|
|
|
34131
34868
|
async function checkWatchJob(job) {
|
|
34132
34869
|
ensureWatchTables();
|
|
34133
34870
|
const db2 = getDatabase();
|
|
34134
|
-
const eventId =
|
|
34871
|
+
const eventId = randomUUID16();
|
|
34135
34872
|
const checkedAt = new Date().toISOString();
|
|
34136
34873
|
let newContent = "";
|
|
34137
34874
|
try {
|
|
@@ -34306,8 +35043,8 @@ var init_ai_task = () => {};
|
|
|
34306
35043
|
var exports_mcp = {};
|
|
34307
35044
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
34308
35045
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
34309
|
-
import { readFileSync as
|
|
34310
|
-
import { join as
|
|
35046
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
35047
|
+
import { join as join16 } from "path";
|
|
34311
35048
|
function json(data) {
|
|
34312
35049
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
34313
35050
|
}
|
|
@@ -34372,7 +35109,7 @@ var init_mcp = __esm(async () => {
|
|
|
34372
35109
|
init_dialogs();
|
|
34373
35110
|
init_profiles();
|
|
34374
35111
|
init_types();
|
|
34375
|
-
_pkg = JSON.parse(
|
|
35112
|
+
_pkg = JSON.parse(readFileSync9(join16(import.meta.dir, "../../package.json"), "utf8"));
|
|
34376
35113
|
networkLogCleanup = new Map;
|
|
34377
35114
|
consoleCaptureCleanup = new Map;
|
|
34378
35115
|
harCaptures = new Map;
|
|
@@ -34451,6 +35188,25 @@ var init_mcp = __esm(async () => {
|
|
|
34451
35188
|
return err(e);
|
|
34452
35189
|
}
|
|
34453
35190
|
});
|
|
35191
|
+
server.tool("browser_session_fork", "Fork a session: create a new session with the same auth state (cookies, storage) and URL as an existing one. Like git branch for browser sessions.", { source_session_id: exports_external.string(), name: exports_external.string().optional() }, async ({ source_session_id, name }) => {
|
|
35192
|
+
try {
|
|
35193
|
+
const sourcePage = getSessionPage(source_session_id);
|
|
35194
|
+
const sourceUrl = sourcePage.url();
|
|
35195
|
+
const tempName = `_fork_${Date.now()}`;
|
|
35196
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35197
|
+
await saveStateFromPage2(sourcePage, tempName);
|
|
35198
|
+
const { session, page } = await createSession2({
|
|
35199
|
+
storageState: tempName,
|
|
35200
|
+
startUrl: sourceUrl,
|
|
35201
|
+
name: name ?? `fork-of-${source_session_id.slice(0, 8)}`
|
|
35202
|
+
});
|
|
35203
|
+
const { deleteState: deleteState2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
35204
|
+
deleteState2(tempName);
|
|
35205
|
+
return json({ forked_session: session, source_url: sourceUrl });
|
|
35206
|
+
} catch (e) {
|
|
35207
|
+
return err(e);
|
|
35208
|
+
}
|
|
35209
|
+
});
|
|
34454
35210
|
server.tool("browser_session_timeline", "Get chronological action log for a session", { session_id: exports_external.string().optional(), limit: exports_external.number().optional().default(50) }, async ({ session_id, limit }) => {
|
|
34455
35211
|
try {
|
|
34456
35212
|
const sid = resolveSessionId(session_id);
|
|
@@ -35044,6 +35800,52 @@ var init_mcp = __esm(async () => {
|
|
|
35044
35800
|
return err(e);
|
|
35045
35801
|
}
|
|
35046
35802
|
});
|
|
35803
|
+
server.tool("browser_intercept_response", "Intercept and modify API responses for testing. Mock data, simulate errors, add latency.", {
|
|
35804
|
+
session_id: exports_external.string().optional(),
|
|
35805
|
+
url_pattern: exports_external.string().describe("URL pattern to intercept (e.g. '**/api/users*')"),
|
|
35806
|
+
action: exports_external.enum(["mock", "delay", "error"]).describe("What to do with matched requests"),
|
|
35807
|
+
mock_body: exports_external.string().optional().describe("Response body for mock action"),
|
|
35808
|
+
mock_content_type: exports_external.string().optional().default("application/json"),
|
|
35809
|
+
status_code: exports_external.number().optional().default(200).describe("HTTP status code (for mock/error)"),
|
|
35810
|
+
delay_ms: exports_external.number().optional().default(3000).describe("Delay in ms (for delay action)")
|
|
35811
|
+
}, async ({ session_id, url_pattern, action, mock_body, mock_content_type, status_code, delay_ms }) => {
|
|
35812
|
+
try {
|
|
35813
|
+
const sid = resolveSessionId(session_id);
|
|
35814
|
+
const page = getSessionPage(sid);
|
|
35815
|
+
await page.route(url_pattern, async (route) => {
|
|
35816
|
+
if (action === "mock") {
|
|
35817
|
+
await route.fulfill({
|
|
35818
|
+
status: status_code,
|
|
35819
|
+
contentType: mock_content_type,
|
|
35820
|
+
body: mock_body ?? "{}"
|
|
35821
|
+
});
|
|
35822
|
+
} else if (action === "error") {
|
|
35823
|
+
await route.fulfill({
|
|
35824
|
+
status: status_code ?? 500,
|
|
35825
|
+
contentType: "application/json",
|
|
35826
|
+
body: JSON.stringify({ error: "Intercepted error", status: status_code })
|
|
35827
|
+
});
|
|
35828
|
+
} else if (action === "delay") {
|
|
35829
|
+
await new Promise((r) => setTimeout(r, delay_ms));
|
|
35830
|
+
await route.continue();
|
|
35831
|
+
}
|
|
35832
|
+
});
|
|
35833
|
+
logEvent(sid, "intercept_set", { url_pattern, action, status_code });
|
|
35834
|
+
return json({ intercepted: true, url_pattern, action });
|
|
35835
|
+
} catch (e) {
|
|
35836
|
+
return err(e);
|
|
35837
|
+
}
|
|
35838
|
+
});
|
|
35839
|
+
server.tool("browser_intercept_clear", "Remove all response intercepts from a session", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
35840
|
+
try {
|
|
35841
|
+
const sid = resolveSessionId(session_id);
|
|
35842
|
+
const page = getSessionPage(sid);
|
|
35843
|
+
await page.unrouteAll({ behavior: "ignoreErrors" });
|
|
35844
|
+
return json({ cleared: true });
|
|
35845
|
+
} catch (e) {
|
|
35846
|
+
return err(e);
|
|
35847
|
+
}
|
|
35848
|
+
});
|
|
35047
35849
|
server.tool("browser_performance", "Get performance metrics for the current page", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
|
|
35048
35850
|
try {
|
|
35049
35851
|
const sid = resolveSessionId(session_id);
|
|
@@ -35054,6 +35856,72 @@ var init_mcp = __esm(async () => {
|
|
|
35054
35856
|
return err(e);
|
|
35055
35857
|
}
|
|
35056
35858
|
});
|
|
35859
|
+
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 }) => {
|
|
35860
|
+
try {
|
|
35861
|
+
const sid = resolveSessionId(session_id);
|
|
35862
|
+
const page = getSessionPage(sid);
|
|
35863
|
+
const { detectEnvironment: detectEnvironment2 } = await Promise.resolve().then(() => exports_env_detector);
|
|
35864
|
+
const result = await detectEnvironment2(page);
|
|
35865
|
+
return json(result);
|
|
35866
|
+
} catch (e) {
|
|
35867
|
+
return err(e);
|
|
35868
|
+
}
|
|
35869
|
+
});
|
|
35870
|
+
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 }) => {
|
|
35871
|
+
try {
|
|
35872
|
+
const sid = resolveSessionId(session_id);
|
|
35873
|
+
const page = getSessionPage(sid);
|
|
35874
|
+
const { getDeepPerformance: getDeepPerformance2 } = await Promise.resolve().then(() => (init_deep_performance(), exports_deep_performance));
|
|
35875
|
+
const result = await getDeepPerformance2(page);
|
|
35876
|
+
return json(result);
|
|
35877
|
+
} catch (e) {
|
|
35878
|
+
return err(e);
|
|
35879
|
+
}
|
|
35880
|
+
});
|
|
35881
|
+
server.tool("browser_accessibility_audit", "Run accessibility audit on the page. Injects axe-core and returns violations grouped by severity (critical, serious, moderate, minor).", { session_id: exports_external.string().optional(), selector: exports_external.string().optional().describe("Scope audit to a specific element") }, async ({ session_id, selector }) => {
|
|
35882
|
+
try {
|
|
35883
|
+
const sid = resolveSessionId(session_id);
|
|
35884
|
+
const page = getSessionPage(sid);
|
|
35885
|
+
await page.evaluate(`
|
|
35886
|
+
if (!window.axe) {
|
|
35887
|
+
const script = document.createElement('script');
|
|
35888
|
+
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js';
|
|
35889
|
+
document.head.appendChild(script);
|
|
35890
|
+
await new Promise((resolve, reject) => {
|
|
35891
|
+
script.onload = resolve;
|
|
35892
|
+
script.onerror = reject;
|
|
35893
|
+
});
|
|
35894
|
+
}
|
|
35895
|
+
`);
|
|
35896
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
35897
|
+
const results = await page.evaluate((sel) => {
|
|
35898
|
+
const opts = {};
|
|
35899
|
+
if (sel)
|
|
35900
|
+
opts.include = [sel];
|
|
35901
|
+
return window.axe.run(opts.include ? { include: [sel] } : document).then((r) => ({
|
|
35902
|
+
violations: r.violations.map((v) => ({
|
|
35903
|
+
id: v.id,
|
|
35904
|
+
impact: v.impact,
|
|
35905
|
+
description: v.description,
|
|
35906
|
+
help: v.help,
|
|
35907
|
+
helpUrl: v.helpUrl,
|
|
35908
|
+
nodes_count: v.nodes.length,
|
|
35909
|
+
selectors: v.nodes.slice(0, 3).map((n) => n.target?.[0] ?? "")
|
|
35910
|
+
})),
|
|
35911
|
+
passes: r.passes.length,
|
|
35912
|
+
violations_count: r.violations.length,
|
|
35913
|
+
incomplete: r.incomplete.length
|
|
35914
|
+
}));
|
|
35915
|
+
}, selector);
|
|
35916
|
+
const byImpact = { critical: 0, serious: 0, moderate: 0, minor: 0 };
|
|
35917
|
+
for (const v of results.violations) {
|
|
35918
|
+
byImpact[v.impact] = (byImpact[v.impact] || 0) + 1;
|
|
35919
|
+
}
|
|
35920
|
+
return json({ ...results, by_impact: byImpact, score: Math.max(0, 100 - results.violations_count * 5) });
|
|
35921
|
+
} catch (e) {
|
|
35922
|
+
return err(e);
|
|
35923
|
+
}
|
|
35924
|
+
});
|
|
35057
35925
|
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 }) => {
|
|
35058
35926
|
try {
|
|
35059
35927
|
const sid = resolveSessionId(session_id);
|
|
@@ -35117,6 +35985,46 @@ var init_mcp = __esm(async () => {
|
|
|
35117
35985
|
return err(e);
|
|
35118
35986
|
}
|
|
35119
35987
|
});
|
|
35988
|
+
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 }) => {
|
|
35989
|
+
try {
|
|
35990
|
+
const { saveWorkflowFromRecording: saveWorkflowFromRecording2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35991
|
+
return json(saveWorkflowFromRecording2(recording_id, name, description));
|
|
35992
|
+
} catch (e) {
|
|
35993
|
+
return err(e);
|
|
35994
|
+
}
|
|
35995
|
+
});
|
|
35996
|
+
server.tool("browser_workflow_list", "List all saved workflows", {}, async () => {
|
|
35997
|
+
try {
|
|
35998
|
+
const { listWorkflows: listWorkflows2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
35999
|
+
const workflows = listWorkflows2();
|
|
36000
|
+
return json({ workflows: workflows.map((w) => ({ ...w, steps: `${w.steps.length} steps` })), count: workflows.length });
|
|
36001
|
+
} catch (e) {
|
|
36002
|
+
return err(e);
|
|
36003
|
+
}
|
|
36004
|
+
});
|
|
36005
|
+
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 }) => {
|
|
36006
|
+
try {
|
|
36007
|
+
const sid = resolveSessionId(session_id);
|
|
36008
|
+
const page = getSessionPage(sid);
|
|
36009
|
+
const { getWorkflowByName: getWorkflowByName2, runWorkflow: runWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
36010
|
+
const workflow = getWorkflowByName2(name);
|
|
36011
|
+
if (!workflow)
|
|
36012
|
+
return err(new Error(`Workflow '${name}' not found`));
|
|
36013
|
+
const result = await runWorkflow2(workflow, page);
|
|
36014
|
+
logEvent(sid, "workflow_run", { name, ...result });
|
|
36015
|
+
return json(result);
|
|
36016
|
+
} catch (e) {
|
|
36017
|
+
return err(e);
|
|
36018
|
+
}
|
|
36019
|
+
});
|
|
36020
|
+
server.tool("browser_workflow_delete", "Delete a saved workflow", { name: exports_external.string() }, async ({ name }) => {
|
|
36021
|
+
try {
|
|
36022
|
+
const { deleteWorkflow: deleteWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
|
|
36023
|
+
return json({ deleted: deleteWorkflow2(name) });
|
|
36024
|
+
} catch (e) {
|
|
36025
|
+
return err(e);
|
|
36026
|
+
}
|
|
36027
|
+
});
|
|
35120
36028
|
server.tool("browser_crawl", "Crawl a URL recursively and return discovered pages", {
|
|
35121
36029
|
url: exports_external.string(),
|
|
35122
36030
|
max_depth: exports_external.number().optional().default(2),
|
|
@@ -35415,6 +36323,61 @@ var init_mcp = __esm(async () => {
|
|
|
35415
36323
|
return err(e);
|
|
35416
36324
|
}
|
|
35417
36325
|
});
|
|
36326
|
+
server.tool("browser_wait_for_idle", "Wait until no network requests are in-flight for a specified duration. Essential for SPAs that load data after navigation.", {
|
|
36327
|
+
session_id: exports_external.string().optional(),
|
|
36328
|
+
idle_time: exports_external.number().optional().default(2000).describe("How long (ms) network must be idle to consider page loaded"),
|
|
36329
|
+
timeout: exports_external.number().optional().default(30000).describe("Max wait time (ms) before giving up")
|
|
36330
|
+
}, async ({ session_id, idle_time, timeout }) => {
|
|
36331
|
+
try {
|
|
36332
|
+
const sid = resolveSessionId(session_id);
|
|
36333
|
+
const page = getSessionPage(sid);
|
|
36334
|
+
const t0 = Date.now();
|
|
36335
|
+
let lastActivity = Date.now();
|
|
36336
|
+
let pending = 0;
|
|
36337
|
+
const onRequest = () => {
|
|
36338
|
+
pending++;
|
|
36339
|
+
lastActivity = Date.now();
|
|
36340
|
+
};
|
|
36341
|
+
const onResponse = () => {
|
|
36342
|
+
pending = Math.max(0, pending - 1);
|
|
36343
|
+
if (pending === 0)
|
|
36344
|
+
lastActivity = Date.now();
|
|
36345
|
+
};
|
|
36346
|
+
const onFailed = () => {
|
|
36347
|
+
pending = Math.max(0, pending - 1);
|
|
36348
|
+
if (pending === 0)
|
|
36349
|
+
lastActivity = Date.now();
|
|
36350
|
+
};
|
|
36351
|
+
page.on("request", onRequest);
|
|
36352
|
+
page.on("response", onResponse);
|
|
36353
|
+
page.on("requestfailed", onFailed);
|
|
36354
|
+
try {
|
|
36355
|
+
await new Promise((resolve4, reject) => {
|
|
36356
|
+
const check = () => {
|
|
36357
|
+
const now3 = Date.now();
|
|
36358
|
+
if (now3 - t0 > timeout) {
|
|
36359
|
+
reject(new Error(`Timeout after ${timeout}ms (${pending} requests still pending)`));
|
|
36360
|
+
return;
|
|
36361
|
+
}
|
|
36362
|
+
if (pending === 0 && now3 - lastActivity >= idle_time) {
|
|
36363
|
+
resolve4();
|
|
36364
|
+
return;
|
|
36365
|
+
}
|
|
36366
|
+
setTimeout(check, 100);
|
|
36367
|
+
};
|
|
36368
|
+
check();
|
|
36369
|
+
});
|
|
36370
|
+
} finally {
|
|
36371
|
+
page.removeListener("request", onRequest);
|
|
36372
|
+
page.removeListener("response", onResponse);
|
|
36373
|
+
page.removeListener("requestfailed", onFailed);
|
|
36374
|
+
}
|
|
36375
|
+
const waited_ms = Date.now() - t0;
|
|
36376
|
+
return json({ idle: true, waited_ms, pending_requests: 0 });
|
|
36377
|
+
} catch (e) {
|
|
36378
|
+
return err(e);
|
|
36379
|
+
}
|
|
36380
|
+
});
|
|
35418
36381
|
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 }) => {
|
|
35419
36382
|
try {
|
|
35420
36383
|
const sid = resolveSessionId(session_id);
|
|
@@ -35812,6 +36775,68 @@ var init_mcp = __esm(async () => {
|
|
|
35812
36775
|
return err(e);
|
|
35813
36776
|
}
|
|
35814
36777
|
});
|
|
36778
|
+
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 }) => {
|
|
36779
|
+
try {
|
|
36780
|
+
const sid = resolveSessionId(session_id);
|
|
36781
|
+
const { detectAPIs: detectAPIs2 } = await Promise.resolve().then(() => (init_api_detector(), exports_api_detector));
|
|
36782
|
+
const apis = detectAPIs2(sid);
|
|
36783
|
+
return json({ apis, count: apis.length });
|
|
36784
|
+
} catch (e) {
|
|
36785
|
+
return err(e);
|
|
36786
|
+
}
|
|
36787
|
+
});
|
|
36788
|
+
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 }) => {
|
|
36789
|
+
try {
|
|
36790
|
+
const sid = resolveSessionId(session_id);
|
|
36791
|
+
const page = getSessionPage(sid);
|
|
36792
|
+
const { extractStructuredData: extractStructuredData2 } = await Promise.resolve().then(() => exports_structured_extract);
|
|
36793
|
+
const data = await extractStructuredData2(page);
|
|
36794
|
+
return json({
|
|
36795
|
+
tables: data.tables.length,
|
|
36796
|
+
lists: data.lists.length,
|
|
36797
|
+
json_ld: data.jsonLd.length,
|
|
36798
|
+
open_graph: Object.keys(data.openGraph).length,
|
|
36799
|
+
meta_tags: Object.keys(data.metaTags).length,
|
|
36800
|
+
repeated_elements: data.repeatedElements.length,
|
|
36801
|
+
data
|
|
36802
|
+
});
|
|
36803
|
+
} catch (e) {
|
|
36804
|
+
return err(e);
|
|
36805
|
+
}
|
|
36806
|
+
});
|
|
36807
|
+
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 }) => {
|
|
36808
|
+
try {
|
|
36809
|
+
const { saveDataset: saveDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36810
|
+
const dataset = saveDataset2({ name, rows: data, sourceUrl: source_url });
|
|
36811
|
+
return json({ id: dataset.id, name: dataset.name, row_count: dataset.row_count });
|
|
36812
|
+
} catch (e) {
|
|
36813
|
+
return err(e);
|
|
36814
|
+
}
|
|
36815
|
+
});
|
|
36816
|
+
server.tool("browser_dataset_list", "List all saved datasets", {}, async () => {
|
|
36817
|
+
try {
|
|
36818
|
+
const { listDatasets: listDatasets2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36819
|
+
return json({ datasets: listDatasets2() });
|
|
36820
|
+
} catch (e) {
|
|
36821
|
+
return err(e);
|
|
36822
|
+
}
|
|
36823
|
+
});
|
|
36824
|
+
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 }) => {
|
|
36825
|
+
try {
|
|
36826
|
+
const { exportDataset: exportDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36827
|
+
return json(exportDataset2(name, format));
|
|
36828
|
+
} catch (e) {
|
|
36829
|
+
return err(e);
|
|
36830
|
+
}
|
|
36831
|
+
});
|
|
36832
|
+
server.tool("browser_dataset_delete", "Delete a saved dataset", { name: exports_external.string() }, async ({ name }) => {
|
|
36833
|
+
try {
|
|
36834
|
+
const { deleteDataset: deleteDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
|
|
36835
|
+
return json({ deleted: deleteDataset2(name) });
|
|
36836
|
+
} catch (e) {
|
|
36837
|
+
return err(e);
|
|
36838
|
+
}
|
|
36839
|
+
});
|
|
35815
36840
|
server.tool("browser_help", "Show all available browser tools grouped by category with one-line descriptions", {}, async () => {
|
|
35816
36841
|
try {
|
|
35817
36842
|
const groups = {
|
|
@@ -35820,7 +36845,8 @@ var init_mcp = __esm(async () => {
|
|
|
35820
36845
|
{ tool: "browser_back", description: "Navigate back in history" },
|
|
35821
36846
|
{ tool: "browser_forward", description: "Navigate forward in history" },
|
|
35822
36847
|
{ tool: "browser_reload", description: "Reload the current page" },
|
|
35823
|
-
{ tool: "browser_wait_for_navigation", description: "Wait for URL change after action" }
|
|
36848
|
+
{ tool: "browser_wait_for_navigation", description: "Wait for URL change after action" },
|
|
36849
|
+
{ tool: "browser_wait_for_idle", description: "Wait for network idle (no pending requests)" }
|
|
35824
36850
|
],
|
|
35825
36851
|
Interaction: [
|
|
35826
36852
|
{ tool: "browser_click", description: "Click element by ref or selector" },
|
|
@@ -35873,7 +36899,9 @@ var init_mcp = __esm(async () => {
|
|
|
35873
36899
|
{ tool: "browser_network_log", description: "Get captured network requests" },
|
|
35874
36900
|
{ tool: "browser_network_intercept", description: "Add a network interception rule" },
|
|
35875
36901
|
{ tool: "browser_har_start", description: "Start HAR capture" },
|
|
35876
|
-
{ tool: "browser_har_stop", description: "Stop HAR capture and get data" }
|
|
36902
|
+
{ tool: "browser_har_stop", description: "Stop HAR capture and get data" },
|
|
36903
|
+
{ tool: "browser_intercept_response", description: "Mock/delay/error API responses for testing" },
|
|
36904
|
+
{ tool: "browser_intercept_clear", description: "Remove all response intercepts" }
|
|
35877
36905
|
],
|
|
35878
36906
|
Performance: [
|
|
35879
36907
|
{ tool: "browser_performance", description: "Get performance metrics" }
|
|
@@ -35898,6 +36926,20 @@ var init_mcp = __esm(async () => {
|
|
|
35898
36926
|
{ tool: "browser_auth_list", description: "List all saved auth flows" },
|
|
35899
36927
|
{ tool: "browser_auth_delete", description: "Delete a saved auth flow" }
|
|
35900
36928
|
],
|
|
36929
|
+
Workflows: [
|
|
36930
|
+
{ tool: "browser_workflow_save", description: "Save a recording as a reusable workflow" },
|
|
36931
|
+
{ tool: "browser_workflow_list", description: "List all saved workflows" },
|
|
36932
|
+
{ tool: "browser_workflow_run", description: "Run a workflow with self-healing replay" },
|
|
36933
|
+
{ tool: "browser_workflow_delete", description: "Delete a saved workflow" }
|
|
36934
|
+
],
|
|
36935
|
+
Data: [
|
|
36936
|
+
{ tool: "browser_extract_structured", description: "Extract tables, lists, JSON-LD, Open Graph, meta tags, repeated elements" },
|
|
36937
|
+
{ tool: "browser_detect_apis", description: "Scan network traffic for JSON API endpoints" },
|
|
36938
|
+
{ tool: "browser_dataset_save", description: "Save extracted data as a named dataset" },
|
|
36939
|
+
{ tool: "browser_dataset_list", description: "List all saved datasets" },
|
|
36940
|
+
{ tool: "browser_dataset_export", description: "Export dataset as JSON or CSV" },
|
|
36941
|
+
{ tool: "browser_dataset_delete", description: "Delete a saved dataset" }
|
|
36942
|
+
],
|
|
35901
36943
|
Crawl: [
|
|
35902
36944
|
{ tool: "browser_crawl", description: "Crawl a URL recursively" }
|
|
35903
36945
|
],
|
|
@@ -35942,6 +36984,7 @@ var init_mcp = __esm(async () => {
|
|
|
35942
36984
|
{ tool: "browser_session_untag", description: "Remove a tag from a session" },
|
|
35943
36985
|
{ tool: "browser_session_stats", description: "Get session stats and token usage" },
|
|
35944
36986
|
{ tool: "browser_session_timeline", description: "Get chronological action log" },
|
|
36987
|
+
{ tool: "browser_session_fork", description: "Fork a session (same auth state + URL)" },
|
|
35945
36988
|
{ tool: "browser_tab_new", description: "Open a new tab" },
|
|
35946
36989
|
{ tool: "browser_tab_list", description: "List all open tabs" },
|
|
35947
36990
|
{ tool: "browser_tab_switch", description: "Switch to a tab by index" },
|
|
@@ -35951,6 +36994,9 @@ var init_mcp = __esm(async () => {
|
|
|
35951
36994
|
{ tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
|
|
35952
36995
|
{ tool: "browser_version", description: "Show running binary version and tool count" },
|
|
35953
36996
|
{ tool: "browser_help", description: "Show this help (all tools)" },
|
|
36997
|
+
{ tool: "browser_detect_env", description: "Detect environment (prod/dev/staging/local)" },
|
|
36998
|
+
{ tool: "browser_performance_deep", description: "Deep performance: resources, third-party, DOM, memory" },
|
|
36999
|
+
{ tool: "browser_accessibility_audit", description: "Run axe-core accessibility audit with severity breakdown" },
|
|
35954
37000
|
{ tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
|
|
35955
37001
|
{ tool: "browser_watch_start", description: "Watch page for DOM changes" },
|
|
35956
37002
|
{ tool: "browser_watch_get_changes", description: "Get captured DOM changes" },
|
|
@@ -36468,10 +37514,10 @@ __export(exports_snapshots, {
|
|
|
36468
37514
|
deleteSnapshot: () => deleteSnapshot,
|
|
36469
37515
|
createSnapshot: () => createSnapshot
|
|
36470
37516
|
});
|
|
36471
|
-
import { randomUUID as
|
|
37517
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
36472
37518
|
function createSnapshot(data) {
|
|
36473
37519
|
const db2 = getDatabase();
|
|
36474
|
-
const id =
|
|
37520
|
+
const id = randomUUID17();
|
|
36475
37521
|
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);
|
|
36476
37522
|
return getSnapshot(id);
|
|
36477
37523
|
}
|
|
@@ -36497,8 +37543,8 @@ var init_snapshots = __esm(() => {
|
|
|
36497
37543
|
|
|
36498
37544
|
// src/server/index.ts
|
|
36499
37545
|
var exports_server = {};
|
|
36500
|
-
import { join as
|
|
36501
|
-
import { existsSync as
|
|
37546
|
+
import { join as join17 } from "path";
|
|
37547
|
+
import { existsSync as existsSync10 } from "fs";
|
|
36502
37548
|
function ok(data, status = 200) {
|
|
36503
37549
|
return new Response(JSON.stringify(data), {
|
|
36504
37550
|
status,
|
|
@@ -36524,7 +37570,7 @@ function serverError(e) {
|
|
|
36524
37570
|
headers: { "Content-Type": "application/json", ...CORS_HEADERS }
|
|
36525
37571
|
});
|
|
36526
37572
|
}
|
|
36527
|
-
var PORT, CORS_HEADERS, networkCleanup, consoleCleanup, harCaptures2, server2;
|
|
37573
|
+
var PORT, startTime, CORS_HEADERS, networkCleanup, consoleCleanup, harCaptures2, server2;
|
|
36528
37574
|
var init_server = __esm(() => {
|
|
36529
37575
|
init_session();
|
|
36530
37576
|
init_actions();
|
|
@@ -36543,6 +37589,7 @@ var init_server = __esm(() => {
|
|
|
36543
37589
|
init_downloads();
|
|
36544
37590
|
init_gallery_diff();
|
|
36545
37591
|
PORT = parseInt(process.env["BROWSER_SERVER_PORT"] ?? "7030");
|
|
37592
|
+
startTime = Date.now();
|
|
36546
37593
|
CORS_HEADERS = {
|
|
36547
37594
|
"Access-Control-Allow-Origin": "*",
|
|
36548
37595
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
@@ -36561,6 +37608,14 @@ var init_server = __esm(() => {
|
|
|
36561
37608
|
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
|
36562
37609
|
}
|
|
36563
37610
|
try {
|
|
37611
|
+
if (path === "/health" && method === "GET") {
|
|
37612
|
+
const activeSessions = listSessions2({ status: "active" });
|
|
37613
|
+
return ok({
|
|
37614
|
+
status: "ok",
|
|
37615
|
+
active_sessions: activeSessions.length,
|
|
37616
|
+
uptime_ms: Date.now() - startTime
|
|
37617
|
+
});
|
|
37618
|
+
}
|
|
36564
37619
|
if (path === "/api/sessions" && method === "GET") {
|
|
36565
37620
|
const status = url.searchParams.get("status");
|
|
36566
37621
|
const projectId = url.searchParams.get("project_id") ?? undefined;
|
|
@@ -36748,14 +37803,14 @@ var init_server = __esm(() => {
|
|
|
36748
37803
|
if (path.match(/^\/api\/gallery\/([^/]+)\/thumbnail$/) && method === "GET") {
|
|
36749
37804
|
const id = path.split("/")[3];
|
|
36750
37805
|
const entry = getEntry(id);
|
|
36751
|
-
if (!entry?.thumbnail_path || !
|
|
37806
|
+
if (!entry?.thumbnail_path || !existsSync10(entry.thumbnail_path))
|
|
36752
37807
|
return notFound("Thumbnail not found");
|
|
36753
37808
|
return new Response(Bun.file(entry.thumbnail_path), { headers: { ...CORS_HEADERS } });
|
|
36754
37809
|
}
|
|
36755
37810
|
if (path.match(/^\/api\/gallery\/([^/]+)\/image$/) && method === "GET") {
|
|
36756
37811
|
const id = path.split("/")[3];
|
|
36757
37812
|
const entry = getEntry(id);
|
|
36758
|
-
if (!entry?.path || !
|
|
37813
|
+
if (!entry?.path || !existsSync10(entry.path))
|
|
36759
37814
|
return notFound("Image not found");
|
|
36760
37815
|
return new Response(Bun.file(entry.path), { headers: { ...CORS_HEADERS } });
|
|
36761
37816
|
}
|
|
@@ -36783,7 +37838,7 @@ var init_server = __esm(() => {
|
|
|
36783
37838
|
if (path.match(/^\/api\/downloads\/([^/]+)\/raw$/) && method === "GET") {
|
|
36784
37839
|
const id = path.split("/")[3];
|
|
36785
37840
|
const file = getDownload(id);
|
|
36786
|
-
if (!file || !
|
|
37841
|
+
if (!file || !existsSync10(file.path))
|
|
36787
37842
|
return notFound("Download not found");
|
|
36788
37843
|
return new Response(Bun.file(file.path), { headers: { ...CORS_HEADERS } });
|
|
36789
37844
|
}
|
|
@@ -36791,13 +37846,13 @@ var init_server = __esm(() => {
|
|
|
36791
37846
|
const id = path.split("/")[3];
|
|
36792
37847
|
return ok({ deleted: deleteDownload(id) });
|
|
36793
37848
|
}
|
|
36794
|
-
const dashboardDist =
|
|
36795
|
-
if (
|
|
36796
|
-
const filePath = path === "/" ?
|
|
36797
|
-
if (
|
|
37849
|
+
const dashboardDist = join17(import.meta.dir, "../../dashboard/dist");
|
|
37850
|
+
if (existsSync10(dashboardDist)) {
|
|
37851
|
+
const filePath = path === "/" ? join17(dashboardDist, "index.html") : join17(dashboardDist, path);
|
|
37852
|
+
if (existsSync10(filePath)) {
|
|
36798
37853
|
return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
|
|
36799
37854
|
}
|
|
36800
|
-
return new Response(Bun.file(
|
|
37855
|
+
return new Response(Bun.file(join17(dashboardDist, "index.html")), { headers: CORS_HEADERS });
|
|
36801
37856
|
}
|
|
36802
37857
|
if (path === "/" || path === "") {
|
|
36803
37858
|
return new Response("@hasna/browser REST API running. Dashboard not built.", {
|
|
@@ -36839,10 +37894,10 @@ init_projects();
|
|
|
36839
37894
|
init_recorder();
|
|
36840
37895
|
init_recordings();
|
|
36841
37896
|
init_lightpanda();
|
|
36842
|
-
import { readFileSync as
|
|
36843
|
-
import { join as
|
|
37897
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
37898
|
+
import { join as join18 } from "path";
|
|
36844
37899
|
import chalk from "chalk";
|
|
36845
|
-
var pkg = JSON.parse(
|
|
37900
|
+
var pkg = JSON.parse(readFileSync10(join18(import.meta.dir, "../../package.json"), "utf8"));
|
|
36846
37901
|
var program2 = new Command;
|
|
36847
37902
|
program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
|
|
36848
37903
|
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) => {
|
|
@@ -36903,6 +37958,148 @@ program2.command("check <url>").description("One-liner page health check: naviga
|
|
|
36903
37958
|
}
|
|
36904
37959
|
await closeSession2(session.id);
|
|
36905
37960
|
});
|
|
37961
|
+
program2.command("audit <url>").description("Full site audit: env detection, performance, errors, APIs, data extraction, screenshot").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
37962
|
+
const t0 = Date.now();
|
|
37963
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed, captureNetwork: true, captureConsole: true });
|
|
37964
|
+
if (!opts.json)
|
|
37965
|
+
console.log(chalk.gray(`Auditing: ${url}
|
|
37966
|
+
`));
|
|
37967
|
+
await navigate(page, url);
|
|
37968
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
37969
|
+
const title = await page.title();
|
|
37970
|
+
const currentUrl = page.url();
|
|
37971
|
+
let env = {};
|
|
37972
|
+
try {
|
|
37973
|
+
const { detectEnvironment: detectEnvironment2 } = await Promise.resolve().then(() => exports_env_detector);
|
|
37974
|
+
env = await detectEnvironment2(page);
|
|
37975
|
+
} catch {}
|
|
37976
|
+
let perf = {};
|
|
37977
|
+
try {
|
|
37978
|
+
const { getDeepPerformance: getDeepPerformance2 } = await Promise.resolve().then(() => (init_deep_performance(), exports_deep_performance));
|
|
37979
|
+
perf = await getDeepPerformance2(page);
|
|
37980
|
+
} catch {}
|
|
37981
|
+
const { getConsoleLog: getConsoleLog2 } = await Promise.resolve().then(() => (init_console_log(), exports_console_log));
|
|
37982
|
+
const errors2 = getConsoleLog2(session.id, "error");
|
|
37983
|
+
let apis = [];
|
|
37984
|
+
try {
|
|
37985
|
+
const { detectAPIs: detectAPIs2 } = await Promise.resolve().then(() => (init_api_detector(), exports_api_detector));
|
|
37986
|
+
apis = detectAPIs2(session.id);
|
|
37987
|
+
} catch {}
|
|
37988
|
+
let structured = {};
|
|
37989
|
+
try {
|
|
37990
|
+
const { extractStructuredData: extractStructuredData2 } = await Promise.resolve().then(() => exports_structured_extract);
|
|
37991
|
+
structured = await extractStructuredData2(page);
|
|
37992
|
+
} catch {}
|
|
37993
|
+
const screenshot = await takeScreenshot(page, { maxWidth: 1280, quality: 75 });
|
|
37994
|
+
const duration = Date.now() - t0;
|
|
37995
|
+
const report = {
|
|
37996
|
+
url: currentUrl,
|
|
37997
|
+
title,
|
|
37998
|
+
duration_ms: duration,
|
|
37999
|
+
environment: env,
|
|
38000
|
+
performance: {
|
|
38001
|
+
fcp_ms: perf.web_vitals?.fcp,
|
|
38002
|
+
ttfb_ms: perf.web_vitals?.ttfb,
|
|
38003
|
+
total_resources: perf.resources?.total_resources,
|
|
38004
|
+
total_transfer_kb: perf.resources ? +(perf.resources.total_transfer_bytes / 1024).toFixed(1) : 0,
|
|
38005
|
+
resource_breakdown: perf.resources?.by_type,
|
|
38006
|
+
third_party_count: perf.third_party?.length ?? 0,
|
|
38007
|
+
third_party: perf.third_party,
|
|
38008
|
+
dom_nodes: perf.dom?.node_count,
|
|
38009
|
+
dom_max_depth: perf.dom?.max_depth,
|
|
38010
|
+
memory_mb: perf.memory?.js_heap_used_mb
|
|
38011
|
+
},
|
|
38012
|
+
errors: { count: errors2.length, sample: errors2.slice(0, 3).map((e) => e.message?.slice(0, 100)) },
|
|
38013
|
+
apis: { count: apis.length, endpoints: apis.map((a) => `${a.method} ${a.url}`) },
|
|
38014
|
+
data: {
|
|
38015
|
+
tables: structured.tables?.length ?? 0,
|
|
38016
|
+
lists: structured.lists?.length ?? 0,
|
|
38017
|
+
json_ld: structured.jsonLd?.length ?? 0,
|
|
38018
|
+
open_graph: Object.keys(structured.openGraph ?? {}).length,
|
|
38019
|
+
repeated_elements: structured.repeatedElements?.length ?? 0
|
|
38020
|
+
},
|
|
38021
|
+
screenshot: screenshot.path
|
|
38022
|
+
};
|
|
38023
|
+
if (opts.json) {
|
|
38024
|
+
console.log(JSON.stringify(report, null, 2));
|
|
38025
|
+
} else {
|
|
38026
|
+
console.log(chalk.bold(`${title}`));
|
|
38027
|
+
console.log(chalk.blue(` ${currentUrl}
|
|
38028
|
+
`));
|
|
38029
|
+
const envColor = env.env === "prod" ? chalk.green : env.env === "staging" ? chalk.yellow : chalk.cyan;
|
|
38030
|
+
console.log(` Environment: ${envColor(env.env ?? "unknown")} (${env.confidence ?? "?"} confidence)`);
|
|
38031
|
+
console.log(` Performance: FCP ${perf.web_vitals?.fcp ? perf.web_vitals.fcp + "ms" : "?"}, TTFB ${perf.web_vitals?.ttfb ? Math.round(perf.web_vitals.ttfb) + "ms" : "?"}`);
|
|
38032
|
+
console.log(` Resources: ${perf.resources?.total_resources ?? "?"} resources (${report.performance.total_transfer_kb} KB)`);
|
|
38033
|
+
console.log(` Third-party: ${perf.third_party?.length ?? 0} scripts`);
|
|
38034
|
+
if (perf.third_party?.length > 0) {
|
|
38035
|
+
perf.third_party.slice(0, 5).forEach((tp) => {
|
|
38036
|
+
console.log(chalk.gray(` ${tp.domain} (${tp.category}, ${(tp.total_bytes / 1024).toFixed(1)}KB)`));
|
|
38037
|
+
});
|
|
38038
|
+
}
|
|
38039
|
+
console.log(` DOM: ${perf.dom?.node_count ?? "?"} nodes, depth ${perf.dom?.max_depth ?? "?"}`);
|
|
38040
|
+
console.log(` Memory: ${perf.memory?.js_heap_used_mb ?? "?"} MB heap`);
|
|
38041
|
+
const errColor = errors2.length > 0 ? chalk.red : chalk.green;
|
|
38042
|
+
console.log(` Errors: ${errColor(errors2.length + " console errors")}`);
|
|
38043
|
+
console.log(` APIs: ${apis.length} JSON endpoints detected`);
|
|
38044
|
+
apis.slice(0, 3).forEach((a) => console.log(chalk.gray(` ${a.method} ${a.url}`)));
|
|
38045
|
+
console.log(` Data: ${report.data.tables} tables, ${report.data.lists} lists, ${report.data.json_ld} JSON-LD, ${report.data.repeated_elements} repeated elements`);
|
|
38046
|
+
console.log(`
|
|
38047
|
+
Screenshot: ${screenshot.path}`);
|
|
38048
|
+
console.log(chalk.gray(` Completed in ${duration}ms`));
|
|
38049
|
+
}
|
|
38050
|
+
await closeSession2(session.id);
|
|
38051
|
+
});
|
|
38052
|
+
program2.command("compare <url1> <url2>").description("Compare two URLs: side-by-side screenshots + pixel diff + text diff").option("--engine <engine>", "Browser engine", "auto").option("--json", "Output as JSON").action(async (url1, url2, opts) => {
|
|
38053
|
+
const [s1, s2] = await Promise.all([
|
|
38054
|
+
createSession2({ engine: opts.engine, headless: true }),
|
|
38055
|
+
createSession2({ engine: opts.engine, headless: true })
|
|
38056
|
+
]);
|
|
38057
|
+
await Promise.all([
|
|
38058
|
+
navigate(s1.page, url1),
|
|
38059
|
+
navigate(s2.page, url2)
|
|
38060
|
+
]);
|
|
38061
|
+
const [ss1, ss2, text1, text2] = await Promise.all([
|
|
38062
|
+
takeScreenshot(s1.page, { format: "png" }),
|
|
38063
|
+
takeScreenshot(s2.page, { format: "png" }),
|
|
38064
|
+
getText(s1.page),
|
|
38065
|
+
getText(s2.page)
|
|
38066
|
+
]);
|
|
38067
|
+
const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
|
|
38068
|
+
const diff = await diffImages2(ss1.path, ss2.path);
|
|
38069
|
+
const words1 = text1.split(/\s+/).filter(Boolean);
|
|
38070
|
+
const words2 = text2.split(/\s+/).filter(Boolean);
|
|
38071
|
+
const set1 = new Set(words1);
|
|
38072
|
+
const set2 = new Set(words2);
|
|
38073
|
+
const common = words1.filter((w) => set2.has(w)).length;
|
|
38074
|
+
const textSimilarity = words1.length > 0 ? Math.round(common / Math.max(words1.length, words2.length) * 100) : 0;
|
|
38075
|
+
const result = {
|
|
38076
|
+
url1,
|
|
38077
|
+
url2,
|
|
38078
|
+
screenshot1: ss1.path,
|
|
38079
|
+
screenshot2: ss2.path,
|
|
38080
|
+
diff_image: diff.diff_path,
|
|
38081
|
+
pixel_change_percent: +diff.changed_percent.toFixed(2),
|
|
38082
|
+
text_similarity_percent: textSimilarity,
|
|
38083
|
+
text1_length: text1.length,
|
|
38084
|
+
text2_length: text2.length
|
|
38085
|
+
};
|
|
38086
|
+
if (opts.json) {
|
|
38087
|
+
console.log(JSON.stringify(result, null, 2));
|
|
38088
|
+
} else {
|
|
38089
|
+
console.log(chalk.bold(`URL Comparison:
|
|
38090
|
+
`));
|
|
38091
|
+
console.log(chalk.blue(` 1: ${url1}`));
|
|
38092
|
+
console.log(chalk.blue(` 2: ${url2}
|
|
38093
|
+
`));
|
|
38094
|
+
console.log(` Pixel diff: ${diff.changed_percent > 5 ? chalk.red(result.pixel_change_percent + "%") : chalk.green(result.pixel_change_percent + "%")} changed`);
|
|
38095
|
+
console.log(` Text similarity: ${textSimilarity > 80 ? chalk.green(textSimilarity + "%") : chalk.yellow(textSimilarity + "%")}`);
|
|
38096
|
+
console.log(chalk.gray(`
|
|
38097
|
+
Screenshot 1: ${ss1.path}`));
|
|
38098
|
+
console.log(chalk.gray(` Screenshot 2: ${ss2.path}`));
|
|
38099
|
+
console.log(chalk.gray(` Diff image: ${diff.diff_path}`));
|
|
38100
|
+
}
|
|
38101
|
+
await Promise.all([closeSession2(s1.session.id), closeSession2(s2.session.id)]);
|
|
38102
|
+
});
|
|
36906
38103
|
program2.command("screenshot <url>").description("Navigate to a URL and take a screenshot").option("--engine <engine>", "Browser engine", "auto").option("--selector <selector>", "CSS selector for element screenshot").option("--full-page", "Capture full page").option("--format <format>", "Image format: png|jpeg|webp", "png").option("--headed", "Run in headed (visible) mode").action(async (url, opts) => {
|
|
36907
38104
|
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
36908
38105
|
await navigate(page, url);
|
|
@@ -37067,6 +38264,86 @@ program2.command("attach").description("Attach to a running Chrome browser via C
|
|
|
37067
38264
|
console.log(chalk.blue(` Page: ${title} (${url})`));
|
|
37068
38265
|
}
|
|
37069
38266
|
});
|
|
38267
|
+
program2.command("login <url>").description("Login to a site: detect form, fill credentials from secrets, save auth state").option("--email <email>", "Email to login with").option("--save-as <name>", "Name to save storage state as").option("--engine <engine>", "Browser engine", "auto").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
38268
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
38269
|
+
await navigate(page, url);
|
|
38270
|
+
const formInfo = await page.evaluate(() => {
|
|
38271
|
+
const emailInput = document.querySelector('input[type="email"], input[name="email"], input[name="username"], input[autocomplete="email"], input[autocomplete="username"]');
|
|
38272
|
+
const passwordInput = document.querySelector('input[type="password"]');
|
|
38273
|
+
const submitBtn = document.querySelector('button[type="submit"], input[type="submit"], button:has(span)');
|
|
38274
|
+
return {
|
|
38275
|
+
hasEmailInput: !!emailInput,
|
|
38276
|
+
hasPasswordInput: !!passwordInput,
|
|
38277
|
+
hasSubmitButton: !!submitBtn,
|
|
38278
|
+
emailSelector: emailInput ? emailInput.id ? `#${emailInput.id}` : emailInput.name ? `input[name="${emailInput.name}"]` : 'input[type="email"]' : null,
|
|
38279
|
+
passwordSelector: passwordInput ? passwordInput.id ? `#${passwordInput.id}` : 'input[type="password"]' : null,
|
|
38280
|
+
submitSelector: submitBtn ? submitBtn.id ? `#${submitBtn.id}` : 'button[type="submit"]' : null,
|
|
38281
|
+
pageTitle: document.title
|
|
38282
|
+
};
|
|
38283
|
+
});
|
|
38284
|
+
if (!opts.json) {
|
|
38285
|
+
console.log(chalk.gray(`Page: ${formInfo.pageTitle}`));
|
|
38286
|
+
console.log(chalk.gray(` Email input: ${formInfo.hasEmailInput ? "\u2713" : "\u2717"}`));
|
|
38287
|
+
console.log(chalk.gray(` Password input: ${formInfo.hasPasswordInput ? "\u2713" : "\u2717"}`));
|
|
38288
|
+
console.log(chalk.gray(` Submit button: ${formInfo.hasSubmitButton ? "\u2713" : "\u2717"}`));
|
|
38289
|
+
}
|
|
38290
|
+
let email = opts.email;
|
|
38291
|
+
let password;
|
|
38292
|
+
if (!email) {
|
|
38293
|
+
try {
|
|
38294
|
+
const { getCredentials: getCredentials2 } = await Promise.resolve().then(() => (init_auth(), exports_auth));
|
|
38295
|
+
const hostname = new URL(url).hostname;
|
|
38296
|
+
const creds = await getCredentials2(hostname);
|
|
38297
|
+
if (creds) {
|
|
38298
|
+
email = creds.email ?? creds.username;
|
|
38299
|
+
password = creds.password;
|
|
38300
|
+
if (!opts.json)
|
|
38301
|
+
console.log(chalk.blue(` Credentials found for ${hostname}`));
|
|
38302
|
+
}
|
|
38303
|
+
} catch {}
|
|
38304
|
+
}
|
|
38305
|
+
if (email && formInfo.emailSelector) {
|
|
38306
|
+
await page.fill(formInfo.emailSelector, email);
|
|
38307
|
+
if (!opts.json)
|
|
38308
|
+
console.log(chalk.green(` \u2713 Filled email: ${email}`));
|
|
38309
|
+
}
|
|
38310
|
+
if (password && formInfo.passwordSelector) {
|
|
38311
|
+
await page.fill(formInfo.passwordSelector, password);
|
|
38312
|
+
if (!opts.json)
|
|
38313
|
+
console.log(chalk.green(` \u2713 Filled password`));
|
|
38314
|
+
}
|
|
38315
|
+
if (formInfo.hasSubmitButton && formInfo.submitSelector) {
|
|
38316
|
+
await page.click(formInfo.submitSelector);
|
|
38317
|
+
if (!opts.json)
|
|
38318
|
+
console.log(chalk.green(` \u2713 Submitted form`));
|
|
38319
|
+
try {
|
|
38320
|
+
await page.waitForNavigation({ timeout: 1e4 });
|
|
38321
|
+
} catch {}
|
|
38322
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
38323
|
+
}
|
|
38324
|
+
const finalUrl = page.url();
|
|
38325
|
+
const loggedIn = finalUrl !== url;
|
|
38326
|
+
let savedAs;
|
|
38327
|
+
if (opts.saveAs || loggedIn) {
|
|
38328
|
+
const name = opts.saveAs ?? new URL(url).hostname.replace(/\./g, "-");
|
|
38329
|
+
try {
|
|
38330
|
+
const { saveStateFromPage: saveStateFromPage2 } = await Promise.resolve().then(() => (init_storage_state(), exports_storage_state));
|
|
38331
|
+
await saveStateFromPage2(page, name);
|
|
38332
|
+
savedAs = name;
|
|
38333
|
+
if (!opts.json)
|
|
38334
|
+
console.log(chalk.green(` \u2713 State saved as: ${name}`));
|
|
38335
|
+
} catch {}
|
|
38336
|
+
}
|
|
38337
|
+
if (opts.json) {
|
|
38338
|
+
console.log(JSON.stringify({ session_id: session.id, url: finalUrl, logged_in: loggedIn, form_detected: formInfo.hasEmailInput, saved_as: savedAs }));
|
|
38339
|
+
} else {
|
|
38340
|
+
console.log(loggedIn ? chalk.green(`
|
|
38341
|
+
\u2713 Login successful \u2192 ${finalUrl}`) : chalk.yellow(`
|
|
38342
|
+
\u26A0 May need manual steps (magic link, 2FA, etc)`));
|
|
38343
|
+
}
|
|
38344
|
+
if (!opts.headed)
|
|
38345
|
+
await closeSession2(session.id);
|
|
38346
|
+
});
|
|
37070
38347
|
program2.command("install-browser").description("Install a browser engine").option("--engine <engine>", "Engine to install: lightpanda|chromium", "chromium").action(async (opts) => {
|
|
37071
38348
|
if (opts.engine === "chromium") {
|
|
37072
38349
|
const { execSync: execSync3 } = await import("child_process");
|
|
@@ -37082,6 +38359,124 @@ program2.command("install-browser").description("Install a browser engine").opti
|
|
|
37082
38359
|
}
|
|
37083
38360
|
}
|
|
37084
38361
|
});
|
|
38362
|
+
var daemonCmd = program2.command("daemon").description("Manage the browser daemon (persistent background sessions)");
|
|
38363
|
+
daemonCmd.command("start").description("Start the browser daemon in the background").option("--port <port>", "Port to listen on", "7030").action(async (opts) => {
|
|
38364
|
+
const { isDaemonRunning: isDaemonRunning2, getDaemonPidFile: getDaemonPidFile2, getDaemonStatus: getDaemonStatus2 } = await Promise.resolve().then(() => (init_daemon_client(), exports_daemon_client));
|
|
38365
|
+
if (isDaemonRunning2()) {
|
|
38366
|
+
console.log(chalk.yellow("Daemon is already running."));
|
|
38367
|
+
const status = await getDaemonStatus2();
|
|
38368
|
+
console.log(chalk.gray(` PID: ${status.pid}, Port: ${status.port}, Sessions: ${status.sessions ?? "?"}`));
|
|
38369
|
+
return;
|
|
38370
|
+
}
|
|
38371
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
38372
|
+
const { writeFileSync: writeFileSync8, mkdirSync: mkdirSync14 } = await import("fs");
|
|
38373
|
+
const { dirname: dirname4 } = await import("path");
|
|
38374
|
+
const pidFile = getDaemonPidFile2();
|
|
38375
|
+
mkdirSync14(dirname4(pidFile), { recursive: true });
|
|
38376
|
+
const child = spawn2(process.execPath, [import.meta.dir + "/../server/index.js"], {
|
|
38377
|
+
detached: true,
|
|
38378
|
+
stdio: "ignore",
|
|
38379
|
+
env: { ...process.env, BROWSER_SERVER_PORT: opts.port }
|
|
38380
|
+
});
|
|
38381
|
+
child.unref();
|
|
38382
|
+
if (child.pid) {
|
|
38383
|
+
writeFileSync8(pidFile, String(child.pid));
|
|
38384
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
38385
|
+
console.log(chalk.green(`\u2713 Daemon started`));
|
|
38386
|
+
console.log(chalk.gray(` PID: ${child.pid}, Port: ${opts.port}`));
|
|
38387
|
+
console.log(chalk.gray(` Sessions will persist across CLI invocations.`));
|
|
38388
|
+
console.log(chalk.gray(` Stop with: browser daemon stop`));
|
|
38389
|
+
} else {
|
|
38390
|
+
console.log(chalk.red("Failed to start daemon"));
|
|
38391
|
+
}
|
|
38392
|
+
});
|
|
38393
|
+
daemonCmd.command("stop").description("Stop the browser daemon").action(async () => {
|
|
38394
|
+
const { isDaemonRunning: isDaemonRunning2, getDaemonPid: getDaemonPid2, getDaemonPidFile: getDaemonPidFile2 } = await Promise.resolve().then(() => (init_daemon_client(), exports_daemon_client));
|
|
38395
|
+
const { unlinkSync: unlinkSync4 } = await import("fs");
|
|
38396
|
+
if (!isDaemonRunning2()) {
|
|
38397
|
+
console.log(chalk.gray("Daemon is not running."));
|
|
38398
|
+
return;
|
|
38399
|
+
}
|
|
38400
|
+
const pid = getDaemonPid2();
|
|
38401
|
+
if (pid) {
|
|
38402
|
+
try {
|
|
38403
|
+
process.kill(pid, "SIGTERM");
|
|
38404
|
+
} catch {}
|
|
38405
|
+
try {
|
|
38406
|
+
unlinkSync4(getDaemonPidFile2());
|
|
38407
|
+
} catch {}
|
|
38408
|
+
console.log(chalk.green(`\u2713 Daemon stopped (PID: ${pid})`));
|
|
38409
|
+
}
|
|
38410
|
+
});
|
|
38411
|
+
daemonCmd.command("status").description("Check daemon status").option("--json", "Output as JSON").action(async (opts) => {
|
|
38412
|
+
const { getDaemonStatus: getDaemonStatus2 } = await Promise.resolve().then(() => (init_daemon_client(), exports_daemon_client));
|
|
38413
|
+
const status = await getDaemonStatus2();
|
|
38414
|
+
if (opts.json) {
|
|
38415
|
+
console.log(JSON.stringify(status, null, 2));
|
|
38416
|
+
} else if (status.running) {
|
|
38417
|
+
console.log(chalk.green("\u25CF Daemon running"));
|
|
38418
|
+
console.log(chalk.gray(` PID: ${status.pid}`));
|
|
38419
|
+
console.log(chalk.gray(` Port: ${status.port}`));
|
|
38420
|
+
if (status.sessions != null)
|
|
38421
|
+
console.log(chalk.gray(` Active sessions: ${status.sessions}`));
|
|
38422
|
+
if (status.uptime_ms != null)
|
|
38423
|
+
console.log(chalk.gray(` Uptime: ${Math.round(status.uptime_ms / 1000)}s`));
|
|
38424
|
+
} else {
|
|
38425
|
+
console.log(chalk.gray("\u25CB Daemon not running"));
|
|
38426
|
+
console.log(chalk.gray(` Start with: browser daemon start`));
|
|
38427
|
+
}
|
|
38428
|
+
});
|
|
38429
|
+
program2.command("watch <url>").description("Monitor a URL for changes \u2014 periodic screenshot + diff").option("--engine <engine>", "Browser engine", "auto").option("--interval <seconds>", "Check interval in seconds", "30").option("--threshold <percent>", "Change threshold percent to report", "5").option("--headed", "Run in headed mode").option("--json", "Output as JSON").action(async (url, opts) => {
|
|
38430
|
+
const intervalMs = parseInt(opts.interval) * 1000;
|
|
38431
|
+
const threshold = parseFloat(opts.threshold);
|
|
38432
|
+
const { session, page } = await createSession2({ engine: opts.engine, headless: !opts.headed });
|
|
38433
|
+
console.log(chalk.gray(`Watching: ${url} (every ${opts.interval}s, threshold ${opts.threshold}%)`));
|
|
38434
|
+
console.log(chalk.gray(`Session: ${session.id} \u2014 Press Ctrl+C to stop
|
|
38435
|
+
`));
|
|
38436
|
+
await navigate(page, url);
|
|
38437
|
+
let baselineResult = await takeScreenshot(page, { format: "png" });
|
|
38438
|
+
let baselinePath = baselineResult.path;
|
|
38439
|
+
let checkCount = 0;
|
|
38440
|
+
if (!opts.json)
|
|
38441
|
+
console.log(chalk.blue(`[${new Date().toISOString()}] Baseline captured: ${baselinePath}`));
|
|
38442
|
+
const check = async () => {
|
|
38443
|
+
checkCount++;
|
|
38444
|
+
try {
|
|
38445
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
38446
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
38447
|
+
const newResult = await takeScreenshot(page, { format: "png" });
|
|
38448
|
+
const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
|
|
38449
|
+
const diff = await diffImages2(baselinePath, newResult.path);
|
|
38450
|
+
const changed = diff.changed_percent > threshold;
|
|
38451
|
+
const timestamp = new Date().toISOString();
|
|
38452
|
+
if (opts.json) {
|
|
38453
|
+
console.log(JSON.stringify({ timestamp, check: checkCount, changed_percent: diff.changed_percent, changed, screenshot: newResult.path, diff_path: changed ? diff.diff_path : undefined }));
|
|
38454
|
+
} else if (changed) {
|
|
38455
|
+
console.log(chalk.red(`[${timestamp}] CHANGED: ${diff.changed_percent.toFixed(2)}% (${diff.changed_pixels} pixels)`));
|
|
38456
|
+
console.log(chalk.gray(` Screenshot: ${newResult.path}`));
|
|
38457
|
+
console.log(chalk.gray(` Diff: ${diff.diff_path}`));
|
|
38458
|
+
baselinePath = newResult.path;
|
|
38459
|
+
} else {
|
|
38460
|
+
console.log(chalk.green(`[${timestamp}] No change (${diff.changed_percent.toFixed(2)}%)`));
|
|
38461
|
+
}
|
|
38462
|
+
} catch (err2) {
|
|
38463
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
38464
|
+
if (opts.json) {
|
|
38465
|
+
console.log(JSON.stringify({ timestamp: new Date().toISOString(), check: checkCount, error: msg }));
|
|
38466
|
+
} else {
|
|
38467
|
+
console.log(chalk.red(`[${new Date().toISOString()}] Error: ${msg}`));
|
|
38468
|
+
}
|
|
38469
|
+
}
|
|
38470
|
+
};
|
|
38471
|
+
const timer = setInterval(check, intervalMs);
|
|
38472
|
+
process.on("SIGINT", async () => {
|
|
38473
|
+
clearInterval(timer);
|
|
38474
|
+
console.log(chalk.gray(`
|
|
38475
|
+
Stopping watch. ${checkCount} checks performed.`));
|
|
38476
|
+
await closeSession2(session.id);
|
|
38477
|
+
process.exit(0);
|
|
38478
|
+
});
|
|
38479
|
+
});
|
|
37085
38480
|
program2.command("mcp").description("Start the MCP server (stdio)").action(async () => {
|
|
37086
38481
|
await init_mcp().then(() => exports_mcp);
|
|
37087
38482
|
});
|
|
@@ -37158,11 +38553,11 @@ galleryCmd.command("stats").description("Show gallery statistics").option("--pro
|
|
|
37158
38553
|
});
|
|
37159
38554
|
galleryCmd.command("clean").description("Delete gallery entries with missing files").action(async () => {
|
|
37160
38555
|
const { listEntries: listEntries2, deleteEntry: deleteEntry2 } = await Promise.resolve().then(() => (init_gallery(), exports_gallery));
|
|
37161
|
-
const { existsSync:
|
|
38556
|
+
const { existsSync: existsSync11 } = await import("fs");
|
|
37162
38557
|
const entries = listEntries2({ limit: 9999 });
|
|
37163
38558
|
let removed = 0;
|
|
37164
38559
|
for (const e of entries) {
|
|
37165
|
-
if (!
|
|
38560
|
+
if (!existsSync11(e.path)) {
|
|
37166
38561
|
deleteEntry2(e.id);
|
|
37167
38562
|
removed++;
|
|
37168
38563
|
}
|