@hasna/browser 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js 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 location = msg.location();
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: location.url || undefined,
3332
- line_number: location.lineNumber || undefined
3379
+ source: location2.url || undefined,
3380
+ line_number: location2.lineNumber || undefined
3333
3381
  });
3334
3382
  } catch {}
3335
3383
  };
@@ -16805,6 +16853,419 @@ var init_annotate = __esm(() => {
16805
16853
  import_sharp3 = __toESM(require_lib(), 1);
16806
16854
  });
16807
16855
 
16856
+ // src/lib/env-detector.ts
16857
+ var exports_env_detector = {};
16858
+ __export(exports_env_detector, {
16859
+ detectEnvironment: () => detectEnvironment
16860
+ });
16861
+ async function detectEnvironment(page) {
16862
+ const url = page.url();
16863
+ const signals = [];
16864
+ let score = { local: 0, dev: 0, staging: 0, prod: 0 };
16865
+ try {
16866
+ const u = new URL(url);
16867
+ if (u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "0.0.0.0" || u.hostname.endsWith(".local")) {
16868
+ score.local += 5;
16869
+ signals.push(`URL hostname: ${u.hostname} \u2192 local`);
16870
+ } else if (u.hostname.match(/^(dev|development)\./i) || u.port !== "") {
16871
+ score.dev += 4;
16872
+ signals.push(`URL pattern: ${u.hostname}:${u.port} \u2192 dev`);
16873
+ } else if (u.hostname.match(/^(staging|stg|stage|preprod|uat)\./i)) {
16874
+ score.staging += 4;
16875
+ signals.push(`URL pattern: ${u.hostname} \u2192 staging`);
16876
+ } else {
16877
+ score.prod += 2;
16878
+ signals.push(`URL looks production: ${u.hostname}`);
16879
+ }
16880
+ if (u.port && !["80", "443", ""].includes(u.port)) {
16881
+ score.dev += 2;
16882
+ signals.push(`Non-standard port: ${u.port}`);
16883
+ }
16884
+ if (u.protocol === "https:") {
16885
+ score.prod += 1;
16886
+ signals.push("HTTPS \u2192 likely prod");
16887
+ } else {
16888
+ score.dev += 2;
16889
+ signals.push("HTTP \u2192 likely dev/local");
16890
+ }
16891
+ } catch {}
16892
+ try {
16893
+ const pageSignals = await page.evaluate(() => {
16894
+ const s = [];
16895
+ const w = window;
16896
+ const envVars = ["__ENV__", "__NEXT_DATA__", "__NUXT__", "process"];
16897
+ for (const v of envVars) {
16898
+ if (w[v]) {
16899
+ const env2 = w[v]?.env?.NODE_ENV ?? w[v]?.runtimeConfig?.public?.env ?? w[v]?.props?.pageProps?.env;
16900
+ if (env2)
16901
+ s.push(`window.${v}: ${env2}`);
16902
+ }
16903
+ }
16904
+ if (w.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.size > 0) {
16905
+ const fiber = document.querySelector("[data-reactroot]") || document.getElementById("__next") || document.getElementById("root");
16906
+ if (fiber)
16907
+ s.push("React app detected");
16908
+ }
16909
+ const envMeta = document.querySelector('meta[name="environment"], meta[name="env"], meta[name="deploy-env"]');
16910
+ if (envMeta)
16911
+ s.push(`meta[environment]: ${envMeta.getAttribute("content")}`);
16912
+ const scripts = document.querySelectorAll("script[src]");
16913
+ let minified = 0, unminified = 0;
16914
+ scripts.forEach((s2) => {
16915
+ const src = s2.getAttribute("src") ?? "";
16916
+ if (src.includes(".min.") || src.match(/\.[a-f0-9]{8,}\./))
16917
+ minified++;
16918
+ else if (src.endsWith(".js") && !src.includes("chunk"))
16919
+ unminified++;
16920
+ });
16921
+ if (unminified > minified && unminified > 2)
16922
+ s.push(`Unminified scripts (${unminified}/${minified + unminified}) \u2192 likely dev`);
16923
+ else if (minified > 0)
16924
+ s.push(`Minified/hashed scripts (${minified}/${minified + unminified}) \u2192 likely prod`);
16925
+ if (document.querySelector("[data-testid]"))
16926
+ s.push("data-testid attributes present \u2192 dev/staging");
16927
+ if ("serviceWorker" in navigator && navigator.serviceWorker.controller) {
16928
+ s.push("Service worker active \u2192 likely prod");
16929
+ }
16930
+ if (w.Sentry)
16931
+ s.push("Sentry SDK loaded \u2192 prod monitoring");
16932
+ if (w.__DATADOG_SYNTHETICS_INLINED_SCRIPT)
16933
+ s.push("Datadog loaded \u2192 prod monitoring");
16934
+ if (w.LogRocket)
16935
+ s.push("LogRocket loaded \u2192 prod monitoring");
16936
+ if (w._lr_loaded)
16937
+ s.push("LogRocket loaded \u2192 prod monitoring");
16938
+ if (w.gtag || w.ga)
16939
+ s.push("Google Analytics loaded \u2192 likely prod");
16940
+ if (w.posthog || w._ph)
16941
+ s.push("PostHog loaded \u2192 prod analytics");
16942
+ if (w.mixpanel)
16943
+ s.push("Mixpanel loaded \u2192 prod analytics");
16944
+ const robots = document.querySelector('meta[name="robots"]');
16945
+ if (robots) {
16946
+ const content = robots.getAttribute("content") ?? "";
16947
+ if (content.includes("noindex"))
16948
+ s.push(`robots: noindex \u2192 staging/dev`);
16949
+ }
16950
+ return s;
16951
+ });
16952
+ for (const signal of pageSignals) {
16953
+ signals.push(signal);
16954
+ if (signal.includes("development") || signal.includes("\u2192 dev") || signal.includes("\u2192 likely dev"))
16955
+ score.dev += 2;
16956
+ if (signal.includes("production") || signal.includes("\u2192 prod") || signal.includes("\u2192 likely prod"))
16957
+ score.prod += 2;
16958
+ if (signal.includes("staging") || signal.includes("\u2192 staging"))
16959
+ score.staging += 2;
16960
+ if (signal.includes("monitoring") || signal.includes("analytics"))
16961
+ score.prod += 1;
16962
+ if (signal.includes("noindex")) {
16963
+ score.staging += 2;
16964
+ score.dev += 1;
16965
+ }
16966
+ }
16967
+ } catch {}
16968
+ const entries = Object.entries(score);
16969
+ entries.sort((a, b) => b[1] - a[1]);
16970
+ const [env, topScore] = entries[0];
16971
+ const [, secondScore] = entries[1];
16972
+ const confidence = topScore >= 5 ? "high" : topScore > secondScore + 1 ? "medium" : "low";
16973
+ return { env, confidence, signals };
16974
+ }
16975
+
16976
+ // src/lib/deep-performance.ts
16977
+ var exports_deep_performance = {};
16978
+ __export(exports_deep_performance, {
16979
+ getDeepPerformance: () => getDeepPerformance
16980
+ });
16981
+ function categorizeThirdParty(domain) {
16982
+ for (const [pattern, category] of Object.entries(THIRD_PARTY_CATEGORIES)) {
16983
+ if (domain.includes(pattern))
16984
+ return category;
16985
+ }
16986
+ return "other";
16987
+ }
16988
+ async function getDeepPerformance(page) {
16989
+ return page.evaluate(() => {
16990
+ const perf = performance;
16991
+ const entries = perf.getEntriesByType("resource");
16992
+ const navEntry = perf.getEntriesByType("navigation")[0];
16993
+ const paintEntries = perf.getEntriesByType("paint");
16994
+ const fcp = paintEntries.find((e) => e.name === "first-contentful-paint")?.startTime;
16995
+ const ttfb = navEntry?.responseStart;
16996
+ const web_vitals = { fcp, ttfb };
16997
+ try {
16998
+ const lcpEntries = perf.getEntriesByType("largest-contentful-paint");
16999
+ if (lcpEntries.length > 0)
17000
+ web_vitals.lcp = lcpEntries[lcpEntries.length - 1].startTime;
17001
+ } catch {}
17002
+ const byType = {};
17003
+ let totalBytes = 0;
17004
+ const resourceList = [];
17005
+ const pageDomain = location.hostname;
17006
+ const thirdPartyMap = new Map;
17007
+ for (const entry of entries) {
17008
+ const size = entry.transferSize || entry.encodedBodySize || 0;
17009
+ totalBytes += size;
17010
+ let type2 = entry.initiatorType || "other";
17011
+ if (type2 === "xmlhttprequest" || type2 === "fetch")
17012
+ type2 = "xhr";
17013
+ if (type2 === "link" && entry.name.match(/\.css/))
17014
+ type2 = "css";
17015
+ if (type2 === "img" || entry.name.match(/\.(png|jpg|jpeg|gif|svg|webp|avif|ico)/i))
17016
+ type2 = "image";
17017
+ if (type2 === "script" || entry.name.match(/\.js/))
17018
+ type2 = "script";
17019
+ if (entry.name.match(/\.(woff2?|ttf|otf|eot)/i))
17020
+ type2 = "font";
17021
+ if (!byType[type2])
17022
+ byType[type2] = { count: 0, size_bytes: 0 };
17023
+ byType[type2].count++;
17024
+ byType[type2].size_bytes += size;
17025
+ resourceList.push({ url: entry.name, size_bytes: size, type: type2 });
17026
+ try {
17027
+ const domain = new URL(entry.name).hostname;
17028
+ if (domain !== pageDomain && !domain.endsWith(`.${pageDomain}`)) {
17029
+ if (!thirdPartyMap.has(domain))
17030
+ thirdPartyMap.set(domain, { scripts: 0, total_bytes: 0 });
17031
+ const tp = thirdPartyMap.get(domain);
17032
+ tp.scripts++;
17033
+ tp.total_bytes += size;
17034
+ }
17035
+ } catch {}
17036
+ }
17037
+ resourceList.sort((a, b) => b.size_bytes - a.size_bytes);
17038
+ const largest = resourceList.slice(0, 10).map((r) => ({
17039
+ url: r.url.length > 120 ? r.url.slice(0, 117) + "..." : r.url,
17040
+ size_bytes: r.size_bytes,
17041
+ type: r.type
17042
+ }));
17043
+ const third_party = Array.from(thirdPartyMap.entries()).map(([domain, data]) => ({ domain, ...data, category: "" })).sort((a, b) => b.total_bytes - a.total_bytes).slice(0, 15);
17044
+ const allNodes = document.querySelectorAll("*");
17045
+ let maxDepth = 0;
17046
+ let textNodes = 0;
17047
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL);
17048
+ let node = walker.currentNode;
17049
+ while (node) {
17050
+ if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim())
17051
+ textNodes++;
17052
+ let depth = 0;
17053
+ let parent = node.parentNode;
17054
+ while (parent) {
17055
+ depth++;
17056
+ parent = parent.parentNode;
17057
+ }
17058
+ if (depth > maxDepth)
17059
+ maxDepth = depth;
17060
+ node = walker.nextNode();
17061
+ }
17062
+ const mem = performance.memory;
17063
+ const memory = {
17064
+ js_heap_used_mb: mem ? Math.round(mem.usedJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
17065
+ js_heap_total_mb: mem ? Math.round(mem.totalJSHeapSize / 1024 / 1024 * 100) / 100 : 0,
17066
+ js_heap_limit_mb: mem ? Math.round(mem.jsHeapSizeLimit / 1024 / 1024 * 100) / 100 : 0
17067
+ };
17068
+ return {
17069
+ web_vitals,
17070
+ resources: { total_transfer_bytes: totalBytes, total_resources: entries.length, by_type: byType, largest },
17071
+ third_party,
17072
+ dom: { node_count: document.all.length, max_depth: maxDepth, element_count: allNodes.length, text_node_count: textNodes },
17073
+ main_thread: { long_tasks: 0, total_blocking_ms: 0 },
17074
+ memory
17075
+ };
17076
+ }).then((result) => {
17077
+ for (const tp of result.third_party) {
17078
+ tp.category = categorizeThirdParty(tp.domain);
17079
+ }
17080
+ return result;
17081
+ });
17082
+ }
17083
+ var THIRD_PARTY_CATEGORIES;
17084
+ var init_deep_performance = __esm(() => {
17085
+ THIRD_PARTY_CATEGORIES = {
17086
+ "google-analytics.com": "analytics",
17087
+ "googletagmanager.com": "analytics",
17088
+ gtag: "analytics",
17089
+ "facebook.net": "social",
17090
+ "connect.facebook": "social",
17091
+ "stripe.com": "payment",
17092
+ "js.stripe.com": "payment",
17093
+ "sentry.io": "monitoring",
17094
+ "sentry-cdn": "monitoring",
17095
+ "posthog.com": "analytics",
17096
+ "ph.": "analytics",
17097
+ "intercom.io": "chat",
17098
+ "crisp.chat": "chat",
17099
+ "hotjar.com": "analytics",
17100
+ "clarity.ms": "analytics",
17101
+ "cdn.jsdelivr.net": "cdn",
17102
+ "cdnjs.cloudflare.com": "cdn",
17103
+ "unpkg.com": "cdn",
17104
+ "fonts.googleapis.com": "fonts",
17105
+ "fonts.gstatic.com": "fonts"
17106
+ };
17107
+ });
17108
+
17109
+ // src/lib/workflows.ts
17110
+ var exports_workflows = {};
17111
+ __export(exports_workflows, {
17112
+ saveWorkflowFromRecording: () => saveWorkflowFromRecording,
17113
+ saveWorkflow: () => saveWorkflow,
17114
+ runWorkflow: () => runWorkflow,
17115
+ listWorkflows: () => listWorkflows,
17116
+ getWorkflowByName: () => getWorkflowByName,
17117
+ getWorkflow: () => getWorkflow,
17118
+ deleteWorkflow: () => deleteWorkflow
17119
+ });
17120
+ import { randomUUID as randomUUID10 } from "crypto";
17121
+ function saveWorkflow(data) {
17122
+ const db = getDatabase();
17123
+ const id = randomUUID10();
17124
+ db.prepare("INSERT OR REPLACE INTO workflows (id, name, description, steps, start_url) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.description ?? null, JSON.stringify(data.steps), data.startUrl ?? null);
17125
+ return getWorkflow(id);
17126
+ }
17127
+ function saveWorkflowFromRecording(recordingId, name, description) {
17128
+ const db = getDatabase();
17129
+ const rec = db.query("SELECT steps, start_url FROM recordings WHERE id = ?").get(recordingId);
17130
+ if (!rec)
17131
+ throw new Error(`Recording not found: ${recordingId}`);
17132
+ const steps = JSON.parse(rec.steps);
17133
+ return saveWorkflow({ name, description, steps, startUrl: rec.start_url ?? undefined });
17134
+ }
17135
+ function getWorkflow(id) {
17136
+ const db = getDatabase();
17137
+ const row = db.query("SELECT * FROM workflows WHERE id = ?").get(id);
17138
+ if (!row)
17139
+ return null;
17140
+ return { ...row, steps: JSON.parse(row.steps) };
17141
+ }
17142
+ function getWorkflowByName(name) {
17143
+ const db = getDatabase();
17144
+ const row = db.query("SELECT * FROM workflows WHERE name = ?").get(name);
17145
+ if (!row)
17146
+ return null;
17147
+ return { ...row, steps: JSON.parse(row.steps) };
17148
+ }
17149
+ function listWorkflows() {
17150
+ const db = getDatabase();
17151
+ return db.query("SELECT * FROM workflows ORDER BY updated_at DESC").all().map((row) => ({ ...row, steps: JSON.parse(row.steps) }));
17152
+ }
17153
+ function deleteWorkflow(name) {
17154
+ const db = getDatabase();
17155
+ return db.prepare("DELETE FROM workflows WHERE name = ?").run(name).changes > 0;
17156
+ }
17157
+ function recordRun(id, healed) {
17158
+ const db = getDatabase();
17159
+ if (healed) {
17160
+ db.prepare("UPDATE workflows SET last_run = datetime('now'), last_heal = datetime('now'), heal_count = heal_count + 1, run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(id);
17161
+ } else {
17162
+ db.prepare("UPDATE workflows SET last_run = datetime('now'), run_count = run_count + 1, updated_at = datetime('now') WHERE id = ?").run(id);
17163
+ }
17164
+ }
17165
+ async function runWorkflow(workflow, page) {
17166
+ const t0 = Date.now();
17167
+ let executed = 0;
17168
+ let failed = 0;
17169
+ let healed = 0;
17170
+ const healedDetails = [];
17171
+ const errors2 = [];
17172
+ const updatedSteps = [...workflow.steps];
17173
+ for (let i = 0;i < workflow.steps.length; i++) {
17174
+ const step = workflow.steps[i];
17175
+ try {
17176
+ switch (step.type) {
17177
+ case "navigate":
17178
+ if (step.url)
17179
+ await page.goto(step.url, { waitUntil: "domcontentloaded", timeout: 30000 });
17180
+ break;
17181
+ case "click":
17182
+ if (step.selector) {
17183
+ try {
17184
+ await page.click(step.selector, { timeout: 5000 });
17185
+ } catch {
17186
+ const result = await healSelector(page, step.selector);
17187
+ if (result.found && result.locator) {
17188
+ await result.locator.click();
17189
+ healed++;
17190
+ const healedSelector = `[healed:${result.method}]${step.selector}`;
17191
+ healedDetails.push({ step: i, original: step.selector, healed_to: healedSelector, method: result.method });
17192
+ } else {
17193
+ throw new Error(`Click failed: ${step.selector} (self-healing exhausted)`);
17194
+ }
17195
+ }
17196
+ }
17197
+ break;
17198
+ case "type":
17199
+ if (step.selector && step.value) {
17200
+ try {
17201
+ await page.fill(step.selector, step.value);
17202
+ } catch {
17203
+ const result = await healSelector(page, step.selector);
17204
+ if (result.found && result.locator) {
17205
+ await result.locator.fill(step.value);
17206
+ healed++;
17207
+ healedDetails.push({ step: i, original: step.selector, healed_to: `[healed:${result.method}]`, method: result.method });
17208
+ } else {
17209
+ throw new Error(`Type failed: ${step.selector} (self-healing exhausted)`);
17210
+ }
17211
+ }
17212
+ }
17213
+ break;
17214
+ case "scroll":
17215
+ if (step.y)
17216
+ await page.mouse.wheel(0, step.y);
17217
+ break;
17218
+ case "hover":
17219
+ if (step.selector) {
17220
+ try {
17221
+ await page.hover(step.selector);
17222
+ } catch {}
17223
+ }
17224
+ break;
17225
+ case "select":
17226
+ if (step.selector && step.value) {
17227
+ try {
17228
+ await page.selectOption(step.selector, step.value);
17229
+ } catch {}
17230
+ }
17231
+ break;
17232
+ case "wait":
17233
+ if (step.selector) {
17234
+ try {
17235
+ await page.waitForSelector(step.selector, { timeout: 1e4 });
17236
+ } catch {}
17237
+ } else {
17238
+ await new Promise((r) => setTimeout(r, step.timestamp || 1000));
17239
+ }
17240
+ break;
17241
+ case "evaluate":
17242
+ if (step.value)
17243
+ await page.evaluate(step.value);
17244
+ break;
17245
+ default:
17246
+ break;
17247
+ }
17248
+ executed++;
17249
+ } catch (err) {
17250
+ failed++;
17251
+ errors2.push(`Step ${i} (${step.type}): ${err instanceof Error ? err.message : String(err)}`);
17252
+ }
17253
+ }
17254
+ recordRun(workflow.id, healed > 0);
17255
+ return {
17256
+ success: failed === 0,
17257
+ steps_executed: executed,
17258
+ steps_failed: failed,
17259
+ steps_healed: healed,
17260
+ healed_details: healedDetails,
17261
+ errors: errors2,
17262
+ duration_ms: Date.now() - t0
17263
+ };
17264
+ }
17265
+ var init_workflows = __esm(() => {
17266
+ init_schema();
17267
+ });
17268
+
16808
17269
  // src/lib/auth-flow.ts
16809
17270
  var exports_auth_flow = {};
16810
17271
  __export(exports_auth_flow, {
@@ -16819,10 +17280,10 @@ __export(exports_auth_flow, {
16819
17280
  getAuthFlow: () => getAuthFlow,
16820
17281
  deleteAuthFlow: () => deleteAuthFlow
16821
17282
  });
16822
- import { randomUUID as randomUUID10 } from "crypto";
17283
+ import { randomUUID as randomUUID11 } from "crypto";
16823
17284
  function saveAuthFlow(data) {
16824
17285
  const db = getDatabase();
16825
- const id = randomUUID10();
17286
+ const id = randomUUID11();
16826
17287
  db.prepare("INSERT OR REPLACE INTO auth_flows (id, name, domain, recording_id, storage_state_path) VALUES (?, ?, ?, ?, ?)").run(id, data.name, data.domain, data.recordingId ?? null, data.storageStatePath ?? null);
16827
17288
  return getAuthFlow(id);
16828
17289
  }
@@ -16992,6 +17453,226 @@ async function clickByVision(page, description, opts) {
16992
17453
  }
16993
17454
  var DEFAULT_MODEL = "claude-sonnet-4-5-20250929";
16994
17455
 
17456
+ // src/lib/api-detector.ts
17457
+ var exports_api_detector = {};
17458
+ __export(exports_api_detector, {
17459
+ listDiscoveredAPIs: () => listDiscoveredAPIs,
17460
+ detectAPIs: () => detectAPIs
17461
+ });
17462
+ import { randomUUID as randomUUID12 } from "crypto";
17463
+ function detectAPIs(sessionId) {
17464
+ const db = getDatabase();
17465
+ const requests = db.query(`SELECT method, url, status_code, response_headers, body_size
17466
+ FROM network_log
17467
+ WHERE session_id = ?
17468
+ AND (response_headers LIKE '%application/json%' OR response_headers LIKE '%text/json%')
17469
+ AND status_code >= 200 AND status_code < 400
17470
+ ORDER BY timestamp DESC`).all(sessionId);
17471
+ const seen = new Map;
17472
+ for (const req of requests) {
17473
+ try {
17474
+ const urlObj = new URL(req.url);
17475
+ if (urlObj.pathname.match(/\.(js|css|png|jpg|svg|woff|ico)$/))
17476
+ continue;
17477
+ if (urlObj.hostname.includes("googleapis.com/identitytoolkit"))
17478
+ continue;
17479
+ if (urlObj.hostname.includes("posthog"))
17480
+ continue;
17481
+ if (urlObj.hostname.includes("sentry"))
17482
+ continue;
17483
+ const key = `${req.method} ${urlObj.origin}${urlObj.pathname}`;
17484
+ if (!seen.has(key)) {
17485
+ seen.set(key, {
17486
+ url: `${urlObj.origin}${urlObj.pathname}`,
17487
+ method: req.method,
17488
+ status_code: req.status_code,
17489
+ content_type: "application/json",
17490
+ response_schema: {},
17491
+ sample_size: req.body_size ?? 0
17492
+ });
17493
+ }
17494
+ } catch {}
17495
+ }
17496
+ const apis = Array.from(seen.values());
17497
+ for (const api of apis) {
17498
+ const id = randomUUID12();
17499
+ db.prepare("INSERT OR IGNORE INTO api_endpoints (id, session_id, url, method, status_code, content_type) VALUES (?, ?, ?, ?, ?, ?)").run(id, sessionId, api.url, api.method, api.status_code, api.content_type);
17500
+ }
17501
+ return apis;
17502
+ }
17503
+ function listDiscoveredAPIs(sessionId) {
17504
+ const db = getDatabase();
17505
+ if (sessionId) {
17506
+ return db.query("SELECT * FROM api_endpoints WHERE session_id = ? ORDER BY discovered_at DESC").all(sessionId);
17507
+ }
17508
+ return db.query("SELECT * FROM api_endpoints ORDER BY discovered_at DESC LIMIT 100").all();
17509
+ }
17510
+ var init_api_detector = __esm(() => {
17511
+ init_schema();
17512
+ });
17513
+
17514
+ // src/lib/structured-extract.ts
17515
+ var exports_structured_extract = {};
17516
+ __export(exports_structured_extract, {
17517
+ extractStructuredData: () => extractStructuredData
17518
+ });
17519
+ async function extractStructuredData(page) {
17520
+ return page.evaluate(() => {
17521
+ const result = { tables: [], lists: [], jsonLd: [], openGraph: {}, metaTags: {}, repeatedElements: [] };
17522
+ document.querySelectorAll("table").forEach((table, idx) => {
17523
+ const headers = [];
17524
+ table.querySelectorAll("thead th, thead td, tr:first-child th").forEach((th) => {
17525
+ headers.push(th.textContent?.trim() ?? "");
17526
+ });
17527
+ const rows = [];
17528
+ table.querySelectorAll("tbody tr, tr:not(:first-child)").forEach((tr) => {
17529
+ const row = [];
17530
+ tr.querySelectorAll("td, th").forEach((td) => {
17531
+ row.push(td.textContent?.trim() ?? "");
17532
+ });
17533
+ if (row.length > 0 && row.some((c) => c !== ""))
17534
+ rows.push(row);
17535
+ });
17536
+ if (rows.length > 0) {
17537
+ result.tables.push({ headers, rows, selector: `table:nth-of-type(${idx + 1})` });
17538
+ }
17539
+ });
17540
+ document.querySelectorAll("ul, ol").forEach((list, idx) => {
17541
+ const items = [];
17542
+ list.querySelectorAll(":scope > li").forEach((li) => {
17543
+ const text = li.textContent?.trim() ?? "";
17544
+ if (text)
17545
+ items.push(text);
17546
+ });
17547
+ if (items.length >= 3) {
17548
+ const tag = list.tagName.toLowerCase();
17549
+ result.lists.push({ items, selector: `${tag}:nth-of-type(${idx + 1})` });
17550
+ }
17551
+ });
17552
+ document.querySelectorAll('script[type="application/ld+json"]').forEach((script) => {
17553
+ try {
17554
+ result.jsonLd.push(JSON.parse(script.textContent ?? ""));
17555
+ } catch {}
17556
+ });
17557
+ document.querySelectorAll('meta[property^="og:"]').forEach((meta) => {
17558
+ const prop = meta.getAttribute("property")?.replace("og:", "") ?? "";
17559
+ result.openGraph[prop] = meta.getAttribute("content") ?? "";
17560
+ });
17561
+ document.querySelectorAll("meta[name]").forEach((meta) => {
17562
+ const name = meta.getAttribute("name") ?? "";
17563
+ if (name)
17564
+ result.metaTags[name] = meta.getAttribute("content") ?? "";
17565
+ });
17566
+ const classCounts = new Map;
17567
+ document.querySelectorAll("[class]").forEach((el) => {
17568
+ const cls = el.className.toString().trim();
17569
+ if (cls && cls.length > 5 && cls.length < 100) {
17570
+ if (!classCounts.has(cls))
17571
+ classCounts.set(cls, []);
17572
+ classCounts.get(cls).push(el);
17573
+ }
17574
+ });
17575
+ for (const [cls, elements] of classCounts) {
17576
+ if (elements.length >= 3 && elements.length <= 200) {
17577
+ const sample = elements.slice(0, 3).map((el) => el.textContent?.trim().slice(0, 100) ?? "");
17578
+ if (sample.some((s) => s.length > 10)) {
17579
+ result.repeatedElements.push({
17580
+ selector: `.${cls.split(" ")[0]}`,
17581
+ count: elements.length,
17582
+ sample
17583
+ });
17584
+ }
17585
+ }
17586
+ }
17587
+ result.repeatedElements.sort((a, b) => b.count - a.count);
17588
+ result.repeatedElements = result.repeatedElements.slice(0, 10);
17589
+ return result;
17590
+ });
17591
+ }
17592
+
17593
+ // src/lib/datasets.ts
17594
+ var exports_datasets = {};
17595
+ __export(exports_datasets, {
17596
+ saveDataset: () => saveDataset,
17597
+ listDatasets: () => listDatasets,
17598
+ getDatasetByName: () => getDatasetByName,
17599
+ getDataset: () => getDataset,
17600
+ exportDataset: () => exportDataset,
17601
+ deleteDataset: () => deleteDataset
17602
+ });
17603
+ import { randomUUID as randomUUID13 } from "crypto";
17604
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync9 } from "fs";
17605
+ import { join as join9 } from "path";
17606
+ import { homedir as homedir9 } from "os";
17607
+ function saveDataset(data) {
17608
+ const db = getDatabase();
17609
+ const id = randomUUID13();
17610
+ const existing = db.query("SELECT id FROM datasets WHERE name = ?").get(data.name);
17611
+ if (existing) {
17612
+ db.prepare("UPDATE datasets SET data = ?, row_count = ?, source_url = ?, schema = ?, last_refresh = datetime('now'), updated_at = datetime('now') WHERE name = ?").run(JSON.stringify(data.rows), data.rows.length, data.sourceUrl ?? null, data.schema ? JSON.stringify(data.schema) : null, data.name);
17613
+ return getDataset(existing.id);
17614
+ }
17615
+ db.prepare("INSERT INTO datasets (id, name, source_url, source_type, data, row_count, schema) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, data.name, data.sourceUrl ?? null, data.sourceType ?? "page", JSON.stringify(data.rows), data.rows.length, data.schema ? JSON.stringify(data.schema) : null);
17616
+ return getDataset(id);
17617
+ }
17618
+ function getDataset(id) {
17619
+ const db = getDatabase();
17620
+ const row = db.query("SELECT * FROM datasets WHERE id = ?").get(id);
17621
+ if (!row)
17622
+ return null;
17623
+ return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
17624
+ }
17625
+ function getDatasetByName(name) {
17626
+ const db = getDatabase();
17627
+ const row = db.query("SELECT * FROM datasets WHERE name = ?").get(name);
17628
+ if (!row)
17629
+ return null;
17630
+ return { ...row, data: JSON.parse(row.data), schema: row.schema ? JSON.parse(row.schema) : null };
17631
+ }
17632
+ function listDatasets() {
17633
+ const db = getDatabase();
17634
+ return db.query("SELECT id, name, source_url, source_type, row_count, last_refresh, created_at, updated_at FROM datasets ORDER BY updated_at DESC").all().map((row) => ({ ...row, data: `${row.row_count} rows`, schema: null }));
17635
+ }
17636
+ function deleteDataset(name) {
17637
+ const db = getDatabase();
17638
+ return db.prepare("DELETE FROM datasets WHERE name = ?").run(name).changes > 0;
17639
+ }
17640
+ function exportDataset(name, format) {
17641
+ const dataset = getDatasetByName(name);
17642
+ if (!dataset)
17643
+ throw new Error(`Dataset '${name}' not found`);
17644
+ const dir = join9(process.env["BROWSER_DATA_DIR"] ?? join9(homedir9(), ".browser"), "exports");
17645
+ mkdirSync9(dir, { recursive: true });
17646
+ const filename = `${name}.${format}`;
17647
+ const path = join9(dir, filename);
17648
+ if (format === "csv") {
17649
+ const rows = dataset.data;
17650
+ if (rows.length === 0) {
17651
+ writeFileSync3(path, "");
17652
+ return { path, size: 0 };
17653
+ }
17654
+ const headers = Object.keys(rows[0]);
17655
+ const csvLines = [headers.join(",")];
17656
+ for (const row of rows) {
17657
+ csvLines.push(headers.map((h) => {
17658
+ const val = String(row[h] ?? "");
17659
+ return val.includes(",") || val.includes('"') ? `"${val.replace(/"/g, '""')}"` : val;
17660
+ }).join(","));
17661
+ }
17662
+ const content = csvLines.join(`
17663
+ `);
17664
+ writeFileSync3(path, content);
17665
+ return { path, size: content.length };
17666
+ } else {
17667
+ const content = JSON.stringify(dataset.data, null, 2);
17668
+ writeFileSync3(path, content);
17669
+ return { path, size: content.length };
17670
+ }
17671
+ }
17672
+ var init_datasets = __esm(() => {
17673
+ init_schema();
17674
+ });
17675
+
16995
17676
  // src/lib/auth.ts
16996
17677
  var exports_auth = {};
16997
17678
  __export(exports_auth, {
@@ -16999,18 +17680,18 @@ __export(exports_auth, {
16999
17680
  getCredentials: () => getCredentials
17000
17681
  });
17001
17682
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
17002
- import { join as join9 } from "path";
17003
- import { homedir as homedir9 } from "os";
17683
+ import { join as join10 } from "path";
17684
+ import { homedir as homedir10 } from "os";
17004
17685
  async function getCredentials(service) {
17005
17686
  try {
17006
- const { getSecret } = await import(`${homedir9()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
17687
+ const { getSecret } = await import(`${homedir10()}/Workspace/hasna/opensource/opensourcedev/open-secrets/src/store.js`);
17007
17688
  const email = getSecret(`${service}_email`) ?? getSecret(`${service}_username`) ?? getSecret(`${service}_login`);
17008
17689
  const password = getSecret(`${service}_password`) ?? getSecret(`${service}_pass`);
17009
17690
  if (email?.value && password?.value) {
17010
17691
  return { email: email.value, password: password.value };
17011
17692
  }
17012
17693
  } catch {}
17013
- const secretsPath = join9(homedir9(), ".secrets");
17694
+ const secretsPath = join10(homedir10(), ".secrets");
17014
17695
  if (existsSync5(secretsPath)) {
17015
17696
  const content = readFileSync3(secretsPath, "utf8");
17016
17697
  const lines = content.split(`
@@ -17192,10 +17873,10 @@ __export(exports_dist, {
17192
17873
  DEFAULT_CONFIG: () => DEFAULT_CONFIG
17193
17874
  });
17194
17875
  import { Database as Database2 } from "bun:sqlite";
17195
- import { existsSync as existsSync6, mkdirSync as mkdirSync9 } from "fs";
17196
- import { dirname, join as join10, resolve } from "path";
17197
- import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
17198
- import { homedir as homedir10 } from "os";
17876
+ import { existsSync as existsSync6, mkdirSync as mkdirSync10 } from "fs";
17877
+ import { dirname, join as join11, resolve } from "path";
17878
+ import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3 } from "fs";
17879
+ import { homedir as homedir11 } from "os";
17199
17880
  import { basename as basename2, dirname as dirname2, join as join22, resolve as resolve2 } from "path";
17200
17881
  import { existsSync as existsSync32, mkdirSync as mkdirSync32, readFileSync as readFileSync22, writeFileSync as writeFileSync22 } from "fs";
17201
17882
  import { homedir as homedir22 } from "os";
@@ -17209,7 +17890,7 @@ function isInMemoryDb(path) {
17209
17890
  function findNearestMementosDb(startDir) {
17210
17891
  let dir = resolve(startDir);
17211
17892
  while (true) {
17212
- const candidate = join10(dir, ".mementos", "mementos.db");
17893
+ const candidate = join11(dir, ".mementos", "mementos.db");
17213
17894
  if (existsSync6(candidate))
17214
17895
  return candidate;
17215
17896
  const parent = dirname(dir);
@@ -17222,7 +17903,7 @@ function findNearestMementosDb(startDir) {
17222
17903
  function findGitRoot(startDir) {
17223
17904
  let dir = resolve(startDir);
17224
17905
  while (true) {
17225
- if (existsSync6(join10(dir, ".git")))
17906
+ if (existsSync6(join11(dir, ".git")))
17226
17907
  return dir;
17227
17908
  const parent = dirname(dir);
17228
17909
  if (parent === dir)
@@ -17242,18 +17923,18 @@ function getDbPath() {
17242
17923
  if (process.env["MEMENTOS_DB_SCOPE"] === "project") {
17243
17924
  const gitRoot = findGitRoot(cwd);
17244
17925
  if (gitRoot) {
17245
- return join10(gitRoot, ".mementos", "mementos.db");
17926
+ return join11(gitRoot, ".mementos", "mementos.db");
17246
17927
  }
17247
17928
  }
17248
17929
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
17249
- return join10(home, ".mementos", "mementos.db");
17930
+ return join11(home, ".mementos", "mementos.db");
17250
17931
  }
17251
17932
  function ensureDir2(filePath) {
17252
17933
  if (isInMemoryDb(filePath))
17253
17934
  return;
17254
17935
  const dir = dirname(resolve(filePath));
17255
17936
  if (!existsSync6(dir)) {
17256
- mkdirSync9(dir, { recursive: true });
17937
+ mkdirSync10(dir, { recursive: true });
17257
17938
  }
17258
17939
  }
17259
17940
  function getDatabase2(dbPath) {
@@ -19103,7 +19784,7 @@ function isValidCategory(value) {
19103
19784
  return VALID_CATEGORIES.includes(value);
19104
19785
  }
19105
19786
  function loadConfig() {
19106
- const configPath = join22(homedir10(), ".mementos", "config.json");
19787
+ const configPath = join22(homedir11(), ".mementos", "config.json");
19107
19788
  let fileConfig = {};
19108
19789
  if (existsSync22(configPath)) {
19109
19790
  try {
@@ -19130,10 +19811,10 @@ function loadConfig() {
19130
19811
  return merged;
19131
19812
  }
19132
19813
  function profilesDir() {
19133
- return join22(homedir10(), ".mementos", "profiles");
19814
+ return join22(homedir11(), ".mementos", "profiles");
19134
19815
  }
19135
19816
  function globalConfigPath() {
19136
- return join22(homedir10(), ".mementos", "config.json");
19817
+ return join22(homedir11(), ".mementos", "config.json");
19137
19818
  }
19138
19819
  function readGlobalConfig() {
19139
19820
  const p = globalConfigPath();
@@ -19148,7 +19829,7 @@ function readGlobalConfig() {
19148
19829
  function writeGlobalConfig(data) {
19149
19830
  const p = globalConfigPath();
19150
19831
  ensureDir22(dirname2(p));
19151
- writeFileSync3(p, JSON.stringify(data, null, 2), "utf-8");
19832
+ writeFileSync4(p, JSON.stringify(data, null, 2), "utf-8");
19152
19833
  }
19153
19834
  function getActiveProfile() {
19154
19835
  const envProfile = process.env["MEMENTOS_PROFILE"];
@@ -21813,10 +22494,10 @@ __export(exports_dist2, {
21813
22494
  acquireLock: () => acquireLock2
21814
22495
  });
21815
22496
  import { Database as Database3 } from "bun:sqlite";
21816
- import { mkdirSync as mkdirSync10 } from "fs";
21817
- import { join as join11, dirname as dirname3 } from "path";
21818
- import { homedir as homedir11 } from "os";
21819
- import { randomUUID as randomUUID11 } from "crypto";
22497
+ import { mkdirSync as mkdirSync11 } from "fs";
22498
+ import { join as join12, dirname as dirname3 } from "path";
22499
+ import { homedir as homedir12 } from "os";
22500
+ import { randomUUID as randomUUID14 } from "crypto";
21820
22501
  import { mkdirSync as mkdirSync23, copyFileSync as copyFileSync3, statSync as statSync2 } from "fs";
21821
22502
  import { join as join33 } from "path";
21822
22503
  import { homedir as homedir33 } from "os";
@@ -21824,19 +22505,19 @@ import { readFileSync as readFileSync5 } from "fs";
21824
22505
  import { join as join23 } from "path";
21825
22506
  import { homedir as homedir23 } from "os";
21826
22507
  import { randomUUID as randomUUID22 } from "crypto";
21827
- import { readFileSync as readFileSync23, writeFileSync as writeFileSync4, mkdirSync as mkdirSync33 } from "fs";
22508
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync5, mkdirSync as mkdirSync33 } from "fs";
21828
22509
  import { join as join43, dirname as dirname22 } from "path";
21829
22510
  import { homedir as homedir42 } from "os";
21830
22511
  function getDbPath2() {
21831
22512
  if (process.env.CONVERSATIONS_DB_PATH)
21832
22513
  return process.env.CONVERSATIONS_DB_PATH;
21833
- return join11(homedir11(), ".conversations", "messages.db");
22514
+ return join12(homedir12(), ".conversations", "messages.db");
21834
22515
  }
21835
22516
  function getDb() {
21836
22517
  if (db)
21837
22518
  return db;
21838
22519
  const dbPath = getDbPath2();
21839
- mkdirSync10(dirname3(dbPath), { recursive: true });
22520
+ mkdirSync11(dirname3(dbPath), { recursive: true });
21840
22521
  db = new Database3(dbPath, { create: true });
21841
22522
  db.exec("PRAGMA journal_mode = WAL");
21842
22523
  db.exec("PRAGMA busy_timeout = 5000");
@@ -22196,7 +22877,7 @@ function guessMimeType(name) {
22196
22877
  function sendMessage(opts) {
22197
22878
  const db2 = getDb();
22198
22879
  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("-")}-${randomUUID11().slice(0, 8)}`);
22880
+ const sessionId = explicitSession ?? (opts.space ? `space:${opts.space}` : `${[opts.from, opts.to].sort().join("-")}-${randomUUID14().slice(0, 8)}`);
22200
22881
  const metadata = opts.metadata ? JSON.stringify(opts.metadata) : null;
22201
22882
  const normalizedPriority = opts.priority === "low" || opts.priority === "normal" || opts.priority === "high" || opts.priority === "urgent" ? opts.priority : "normal";
22202
22883
  const blocking = opts.blocking ? 1 : 0;
@@ -23024,7 +23705,7 @@ function getAutoName() {
23024
23705
  cachedAutoName = name;
23025
23706
  try {
23026
23707
  mkdirSync33(dirname22(AGENT_ID_FILE), { recursive: true });
23027
- writeFileSync4(AGENT_ID_FILE, name + `
23708
+ writeFileSync5(AGENT_ID_FILE, name + `
23028
23709
  `, "utf-8");
23029
23710
  } catch {}
23030
23711
  return name;
@@ -24979,7 +25660,7 @@ Your code should look like:
24979
25660
  }
24980
25661
  }
24981
25662
  }
24982
- function checkPropTypes(typeSpecs, values, location, componentName, element) {
25663
+ function checkPropTypes(typeSpecs, values, location2, componentName, element) {
24983
25664
  {
24984
25665
  var has = Function.call.bind(hasOwnProperty);
24985
25666
  for (var typeSpecName in typeSpecs) {
@@ -24987,23 +25668,23 @@ Your code should look like:
24987
25668
  var error$1 = undefined;
24988
25669
  try {
24989
25670
  if (typeof typeSpecs[typeSpecName] !== "function") {
24990
- var err = Error((componentName || "React class") + ": " + location + " 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`.");
25671
+ var err = Error((componentName || "React class") + ": " + location2 + " type `" + typeSpecName + "` is invalid; " + "it must be a function, usually from the `prop-types` package, but received `" + typeof typeSpecs[typeSpecName] + "`." + "This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");
24991
25672
  err.name = "Invariant Violation";
24992
25673
  throw err;
24993
25674
  }
24994
- error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
25675
+ error$1 = typeSpecs[typeSpecName](values, typeSpecName, componentName, location2, null, "SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED");
24995
25676
  } catch (ex) {
24996
25677
  error$1 = ex;
24997
25678
  }
24998
25679
  if (error$1 && !(error$1 instanceof Error)) {
24999
25680
  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", location, typeSpecName, typeof error$1);
25681
+ error("%s: type specification of %s" + " `%s` is invalid; the type checker " + "function must return `null` or an `Error` but returned a %s. " + "You may have forgotten to pass an argument to the type checker " + "creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and " + "shape all require an argument).", componentName || "React class", location2, typeSpecName, typeof error$1);
25001
25682
  setCurrentlyValidatingElement(null);
25002
25683
  }
25003
25684
  if (error$1 instanceof Error && !(error$1.message in loggedTypeFailures)) {
25004
25685
  loggedTypeFailures[error$1.message] = true;
25005
25686
  setCurrentlyValidatingElement(element);
25006
- error("Failed %s type: %s", location, error$1.message);
25687
+ error("Failed %s type: %s", location2, error$1.message);
25007
25688
  setCurrentlyValidatingElement(null);
25008
25689
  }
25009
25690
  }
@@ -26238,11 +26919,11 @@ __export(exports_dist3, {
26238
26919
  AgentNotFoundError: () => AgentNotFoundError2
26239
26920
  });
26240
26921
  import { Database as Database4 } from "bun:sqlite";
26241
- import { existsSync as existsSync7, mkdirSync as mkdirSync11 } from "fs";
26242
- import { dirname as dirname5, join as join12, resolve as resolve3 } from "path";
26922
+ import { existsSync as existsSync7, mkdirSync as mkdirSync12 } from "fs";
26923
+ import { dirname as dirname5, join as join13, resolve as resolve3 } from "path";
26243
26924
  import { existsSync as existsSync33 } from "fs";
26244
26925
  import { join as join34 } from "path";
26245
- import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
26926
+ import { existsSync as existsSync23, mkdirSync as mkdirSync24, readFileSync as readFileSync6, readdirSync as readdirSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
26246
26927
  import { join as join24 } from "path";
26247
26928
  import { existsSync as existsSync43, readFileSync as readFileSync24, readdirSync as readdirSync22, writeFileSync as writeFileSync23 } from "fs";
26248
26929
  import { join as join44 } from "path";
@@ -26472,7 +27153,7 @@ function isInMemoryDb2(path) {
26472
27153
  function findNearestTodosDb(startDir) {
26473
27154
  let dir = resolve3(startDir);
26474
27155
  while (true) {
26475
- const candidate = join12(dir, ".todos", "todos.db");
27156
+ const candidate = join13(dir, ".todos", "todos.db");
26476
27157
  if (existsSync7(candidate))
26477
27158
  return candidate;
26478
27159
  const parent = dirname5(dir);
@@ -26485,7 +27166,7 @@ function findNearestTodosDb(startDir) {
26485
27166
  function findGitRoot2(startDir) {
26486
27167
  let dir = resolve3(startDir);
26487
27168
  while (true) {
26488
- if (existsSync7(join12(dir, ".git")))
27169
+ if (existsSync7(join13(dir, ".git")))
26489
27170
  return dir;
26490
27171
  const parent = dirname5(dir);
26491
27172
  if (parent === dir)
@@ -26505,18 +27186,18 @@ function getDbPath3() {
26505
27186
  if (process.env["TODOS_DB_SCOPE"] === "project") {
26506
27187
  const gitRoot = findGitRoot2(cwd);
26507
27188
  if (gitRoot) {
26508
- return join12(gitRoot, ".todos", "todos.db");
27189
+ return join13(gitRoot, ".todos", "todos.db");
26509
27190
  }
26510
27191
  }
26511
27192
  const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
26512
- return join12(home, ".todos", "todos.db");
27193
+ return join13(home, ".todos", "todos.db");
26513
27194
  }
26514
27195
  function ensureDir3(filePath) {
26515
27196
  if (isInMemoryDb2(filePath))
26516
27197
  return;
26517
27198
  const dir = dirname5(resolve3(filePath));
26518
27199
  if (!existsSync7(dir)) {
26519
- mkdirSync11(dir, { recursive: true });
27200
+ mkdirSync12(dir, { recursive: true });
26520
27201
  }
26521
27202
  }
26522
27203
  function getDatabase3(dbPath) {
@@ -26992,7 +27673,7 @@ function readJsonFile(path) {
26992
27673
  }
26993
27674
  }
26994
27675
  function writeJsonFile(path, data) {
26995
- writeFileSync5(path, JSON.stringify(data, null, 2) + `
27676
+ writeFileSync6(path, JSON.stringify(data, null, 2) + `
26996
27677
  `);
26997
27678
  }
26998
27679
  function readHighWaterMark(dir) {
@@ -27003,7 +27684,7 @@ function readHighWaterMark(dir) {
27003
27684
  return isNaN(val) ? 1 : val;
27004
27685
  }
27005
27686
  function writeHighWaterMark(dir, value) {
27006
- writeFileSync5(join24(dir, ".highwatermark"), String(value));
27687
+ writeFileSync6(join24(dir, ".highwatermark"), String(value));
27007
27688
  }
27008
27689
  function getFileMtimeMs(path) {
27009
27690
  try {
@@ -31593,9 +32274,9 @@ __export(exports_dist4, {
31593
32274
  CATEGORIES: () => CATEGORIES,
31594
32275
  AGENT_TARGETS: () => AGENT_TARGETS
31595
32276
  });
31596
- import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync7, accessSync, constants } from "fs";
31597
- import { join as join13, dirname as dirname6 } from "path";
31598
- import { homedir as homedir12 } from "os";
32277
+ import { existsSync as existsSync8, cpSync, mkdirSync as mkdirSync13, writeFileSync as writeFileSync7, rmSync as rmSync2, readdirSync as readdirSync6, statSync as statSync4, readFileSync as readFileSync7, accessSync, constants } from "fs";
32278
+ import { join as join14, dirname as dirname6 } from "path";
32279
+ import { homedir as homedir13 } from "os";
31599
32280
  import { fileURLToPath } from "url";
31600
32281
  import { existsSync as existsSync24, readFileSync as readFileSync25, readdirSync as readdirSync23 } from "fs";
31601
32282
  import { join as join25 } from "path";
@@ -31706,17 +32387,17 @@ function normalizeSkillName(name) {
31706
32387
  function findSkillsDir() {
31707
32388
  let dir = __dirname2;
31708
32389
  for (let i = 0;i < 5; i++) {
31709
- const candidate = join13(dir, "skills");
32390
+ const candidate = join14(dir, "skills");
31710
32391
  if (existsSync8(candidate)) {
31711
32392
  return candidate;
31712
32393
  }
31713
32394
  dir = dirname6(dir);
31714
32395
  }
31715
- return join13(__dirname2, "..", "skills");
32396
+ return join14(__dirname2, "..", "skills");
31716
32397
  }
31717
32398
  function getSkillPath(name) {
31718
32399
  const skillName = normalizeSkillName(name);
31719
- return join13(SKILLS_DIR, skillName);
32400
+ return join14(SKILLS_DIR, skillName);
31720
32401
  }
31721
32402
  function skillExists(name) {
31722
32403
  return existsSync8(getSkillPath(name));
@@ -31725,8 +32406,8 @@ function installSkill(name, options = {}) {
31725
32406
  const { targetDir = process.cwd(), overwrite = false } = options;
31726
32407
  const skillName = normalizeSkillName(name);
31727
32408
  const sourcePath = getSkillPath(name);
31728
- const destDir = join13(targetDir, ".skills");
31729
- const destPath = join13(destDir, skillName);
32409
+ const destDir = join14(targetDir, ".skills");
32410
+ const destPath = join14(destDir, skillName);
31730
32411
  if (!existsSync8(sourcePath)) {
31731
32412
  return {
31732
32413
  skill: name,
@@ -31744,7 +32425,7 @@ function installSkill(name, options = {}) {
31744
32425
  }
31745
32426
  try {
31746
32427
  if (!existsSync8(destDir)) {
31747
- mkdirSync12(destDir, { recursive: true });
32428
+ mkdirSync13(destDir, { recursive: true });
31748
32429
  }
31749
32430
  if (existsSync8(destPath) && overwrite) {
31750
32431
  rmSync2(destPath, { recursive: true, force: true });
@@ -31785,7 +32466,7 @@ function installSkills(names, options = {}) {
31785
32466
  return names.map((name) => installSkill(name, options));
31786
32467
  }
31787
32468
  function updateSkillsIndex(skillsDir) {
31788
- const indexPath = join13(skillsDir, "index.ts");
32469
+ const indexPath = join14(skillsDir, "index.ts");
31789
32470
  const meta = loadMeta(skillsDir);
31790
32471
  const disabledSet = new Set(meta.disabled || []);
31791
32472
  const skills = readdirSync6(skillsDir).filter((f) => f.startsWith("skill-") && !f.includes(".") && !disabledSet.has(f.replace("skill-", "")));
@@ -31801,10 +32482,10 @@ function updateSkillsIndex(skillsDir) {
31801
32482
 
31802
32483
  ${exports}
31803
32484
  `;
31804
- writeFileSync6(indexPath, content);
32485
+ writeFileSync7(indexPath, content);
31805
32486
  }
31806
32487
  function getMetaPath(skillsDir) {
31807
- return join13(skillsDir, ".meta.json");
32488
+ return join14(skillsDir, ".meta.json");
31808
32489
  }
31809
32490
  function loadMeta(skillsDir) {
31810
32491
  const metaPath2 = getMetaPath(skillsDir);
@@ -31816,14 +32497,14 @@ function loadMeta(skillsDir) {
31816
32497
  return { skills: {} };
31817
32498
  }
31818
32499
  function saveMeta(skillsDir, meta) {
31819
- writeFileSync6(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
32500
+ writeFileSync7(getMetaPath(skillsDir), JSON.stringify(meta, null, 2));
31820
32501
  }
31821
32502
  function recordInstall(skillsDir, name) {
31822
32503
  const meta = loadMeta(skillsDir);
31823
32504
  const skillName = normalizeSkillName(name);
31824
32505
  let version = "unknown";
31825
32506
  try {
31826
- const pkgPath = join13(skillsDir, skillName, "package.json");
32507
+ const pkgPath = join14(skillsDir, skillName, "package.json");
31827
32508
  if (existsSync8(pkgPath)) {
31828
32509
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
31829
32510
  version = pkg.version || "unknown";
@@ -31838,12 +32519,12 @@ function recordRemove(skillsDir, name) {
31838
32519
  saveMeta(skillsDir, meta);
31839
32520
  }
31840
32521
  function getInstallMeta(targetDir = process.cwd()) {
31841
- return loadMeta(join13(targetDir, ".skills"));
32522
+ return loadMeta(join14(targetDir, ".skills"));
31842
32523
  }
31843
32524
  function disableSkill(name, targetDir = process.cwd()) {
31844
- const skillsDir = join13(targetDir, ".skills");
32525
+ const skillsDir = join14(targetDir, ".skills");
31845
32526
  const skillName = normalizeSkillName(name);
31846
- if (!existsSync8(join13(skillsDir, skillName)))
32527
+ if (!existsSync8(join14(skillsDir, skillName)))
31847
32528
  return false;
31848
32529
  const meta = loadMeta(skillsDir);
31849
32530
  const disabled = new Set(meta.disabled || []);
@@ -31856,7 +32537,7 @@ function disableSkill(name, targetDir = process.cwd()) {
31856
32537
  return true;
31857
32538
  }
31858
32539
  function enableSkill(name, targetDir = process.cwd()) {
31859
- const skillsDir = join13(targetDir, ".skills");
32540
+ const skillsDir = join14(targetDir, ".skills");
31860
32541
  const meta = loadMeta(skillsDir);
31861
32542
  const disabled = new Set(meta.disabled || []);
31862
32543
  if (!disabled.has(name))
@@ -31868,23 +32549,23 @@ function enableSkill(name, targetDir = process.cwd()) {
31868
32549
  return true;
31869
32550
  }
31870
32551
  function getDisabledSkills(targetDir = process.cwd()) {
31871
- const meta = loadMeta(join13(targetDir, ".skills"));
32552
+ const meta = loadMeta(join14(targetDir, ".skills"));
31872
32553
  return meta.disabled || [];
31873
32554
  }
31874
32555
  function getInstalledSkills(targetDir = process.cwd()) {
31875
- const skillsDir = join13(targetDir, ".skills");
32556
+ const skillsDir = join14(targetDir, ".skills");
31876
32557
  if (!existsSync8(skillsDir)) {
31877
32558
  return [];
31878
32559
  }
31879
32560
  return readdirSync6(skillsDir).filter((f) => {
31880
- const fullPath = join13(skillsDir, f);
32561
+ const fullPath = join14(skillsDir, f);
31881
32562
  return f.startsWith("skill-") && statSync4(fullPath).isDirectory();
31882
32563
  }).map((f) => f.replace("skill-", ""));
31883
32564
  }
31884
32565
  function removeSkill(name, targetDir = process.cwd()) {
31885
32566
  const skillName = normalizeSkillName(name);
31886
- const skillsDir = join13(targetDir, ".skills");
31887
- const skillPath = join13(skillsDir, skillName);
32567
+ const skillsDir = join14(targetDir, ".skills");
32568
+ const skillPath = join14(skillsDir, skillName);
31888
32569
  if (!existsSync8(skillPath)) {
31889
32570
  return false;
31890
32571
  }
@@ -31896,13 +32577,13 @@ function removeSkill(name, targetDir = process.cwd()) {
31896
32577
  function getAgentSkillsDir(agent, scope = "global", projectDir) {
31897
32578
  const agentDir = `.${agent}`;
31898
32579
  if (scope === "project") {
31899
- return join13(projectDir || process.cwd(), agentDir, "skills");
32580
+ return join14(projectDir || process.cwd(), agentDir, "skills");
31900
32581
  }
31901
- return join13(homedir12(), agentDir, "skills");
32582
+ return join14(homedir13(), agentDir, "skills");
31902
32583
  }
31903
32584
  function getAgentSkillPath(name, agent, scope = "global", projectDir) {
31904
32585
  const skillName = normalizeSkillName(name);
31905
- return join13(getAgentSkillsDir(agent, scope, projectDir), skillName);
32586
+ return join14(getAgentSkillsDir(agent, scope, projectDir), skillName);
31906
32587
  }
31907
32588
  function installSkillForAgent(name, options, generateSkillMd) {
31908
32589
  const { agent, scope = "global", projectDir } = options;
@@ -31912,7 +32593,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
31912
32593
  return { skill: name, success: false, error: `Skill '${name}' not found` };
31913
32594
  }
31914
32595
  let skillMdContent = null;
31915
- const skillMdPath = join13(sourcePath, "SKILL.md");
32596
+ const skillMdPath = join14(sourcePath, "SKILL.md");
31916
32597
  if (existsSync8(skillMdPath)) {
31917
32598
  skillMdContent = readFileSync7(skillMdPath, "utf-8");
31918
32599
  } else if (generateSkillMd) {
@@ -31923,7 +32604,7 @@ function installSkillForAgent(name, options, generateSkillMd) {
31923
32604
  }
31924
32605
  const destDir = getAgentSkillPath(name, agent, scope, projectDir);
31925
32606
  if (scope === "global") {
31926
- const agentBaseDir2 = join13(homedir12(), `.${agent}`);
32607
+ const agentBaseDir2 = join14(homedir13(), `.${agent}`);
31927
32608
  if (!existsSync8(agentBaseDir2)) {
31928
32609
  const agentLabels = {
31929
32610
  claude: "Claude Code",
@@ -31947,8 +32628,8 @@ function installSkillForAgent(name, options, generateSkillMd) {
31947
32628
  }
31948
32629
  }
31949
32630
  try {
31950
- mkdirSync12(destDir, { recursive: true });
31951
- writeFileSync6(join13(destDir, "SKILL.md"), skillMdContent);
32631
+ mkdirSync13(destDir, { recursive: true });
32632
+ writeFileSync7(join14(destDir, "SKILL.md"), skillMdContent);
31952
32633
  return { skill: name, success: true, path: destDir };
31953
32634
  } catch (error) {
31954
32635
  return {
@@ -33885,7 +34566,7 @@ __export(exports_cron_manager, {
33885
34566
  deleteCronJob: () => deleteCronJob,
33886
34567
  createCronJob: () => createCronJob
33887
34568
  });
33888
- import { randomUUID as randomUUID12 } from "crypto";
34569
+ import { randomUUID as randomUUID15 } from "crypto";
33889
34570
  function ensureCronTable() {
33890
34571
  const db2 = getDatabase();
33891
34572
  db2.exec(`
@@ -33914,7 +34595,7 @@ function ensureCronTable() {
33914
34595
  function createCronJob(schedule, task, name) {
33915
34596
  ensureCronTable();
33916
34597
  const db2 = getDatabase();
33917
- const id = randomUUID12();
34598
+ const id = randomUUID15();
33918
34599
  db2.prepare(`
33919
34600
  INSERT INTO cron_jobs (id, name, schedule, task_json, enabled)
33920
34601
  VALUES (?, ?, ?, ?, 1)
@@ -33972,7 +34653,7 @@ function getCronEvents(jobId, limit = 10) {
33972
34653
  async function executeCronJob(job) {
33973
34654
  ensureCronTable();
33974
34655
  const db2 = getDatabase();
33975
- const eventId = randomUUID12();
34656
+ const eventId = randomUUID15();
33976
34657
  const startedAt = new Date().toISOString();
33977
34658
  db2.prepare("INSERT INTO cron_events (id, job_id, started_at) VALUES (?, ?, ?)").run(eventId, job.id, startedAt);
33978
34659
  try {
@@ -34063,7 +34744,7 @@ __export(exports_url_watcher, {
34063
34744
  deleteWatchJob: () => deleteWatchJob,
34064
34745
  createWatchJob: () => createWatchJob
34065
34746
  });
34066
- import { randomUUID as randomUUID13 } from "crypto";
34747
+ import { randomUUID as randomUUID16 } from "crypto";
34067
34748
  import { createHash } from "crypto";
34068
34749
  function ensureWatchTables() {
34069
34750
  const db2 = getDatabase();
@@ -34095,7 +34776,7 @@ function ensureWatchTables() {
34095
34776
  function createWatchJob(url, schedule, opts) {
34096
34777
  ensureWatchTables();
34097
34778
  const db2 = getDatabase();
34098
- const id = randomUUID13();
34779
+ const id = randomUUID16();
34099
34780
  db2.prepare(`
34100
34781
  INSERT INTO watch_jobs (id, name, url, schedule, selector, extract_schema, enabled)
34101
34782
  VALUES (?, ?, ?, ?, ?, ?, 1)
@@ -34131,7 +34812,7 @@ function getWatchEvents(watchId, limit = 20) {
34131
34812
  async function checkWatchJob(job) {
34132
34813
  ensureWatchTables();
34133
34814
  const db2 = getDatabase();
34134
- const eventId = randomUUID13();
34815
+ const eventId = randomUUID16();
34135
34816
  const checkedAt = new Date().toISOString();
34136
34817
  let newContent = "";
34137
34818
  try {
@@ -34307,7 +34988,7 @@ var exports_mcp = {};
34307
34988
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
34308
34989
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
34309
34990
  import { readFileSync as readFileSync8 } from "fs";
34310
- import { join as join14 } from "path";
34991
+ import { join as join15 } from "path";
34311
34992
  function json(data) {
34312
34993
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
34313
34994
  }
@@ -34372,7 +35053,7 @@ var init_mcp = __esm(async () => {
34372
35053
  init_dialogs();
34373
35054
  init_profiles();
34374
35055
  init_types();
34375
- _pkg = JSON.parse(readFileSync8(join14(import.meta.dir, "../../package.json"), "utf8"));
35056
+ _pkg = JSON.parse(readFileSync8(join15(import.meta.dir, "../../package.json"), "utf8"));
34376
35057
  networkLogCleanup = new Map;
34377
35058
  consoleCaptureCleanup = new Map;
34378
35059
  harCaptures = new Map;
@@ -35054,6 +35735,28 @@ var init_mcp = __esm(async () => {
35054
35735
  return err(e);
35055
35736
  }
35056
35737
  });
35738
+ server.tool("browser_detect_env", "Detect if the current page is running in production, development, staging, or local environment. Analyzes URL, meta tags, source maps, analytics SDKs, and more.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
35739
+ try {
35740
+ const sid = resolveSessionId(session_id);
35741
+ const page = getSessionPage(sid);
35742
+ const { detectEnvironment: detectEnvironment2 } = await Promise.resolve().then(() => exports_env_detector);
35743
+ const result = await detectEnvironment2(page);
35744
+ return json(result);
35745
+ } catch (e) {
35746
+ return err(e);
35747
+ }
35748
+ });
35749
+ server.tool("browser_performance_deep", "Deep performance analysis: Web Vitals, resource breakdown by type, largest resources, third-party scripts with categories, DOM complexity, memory usage.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
35750
+ try {
35751
+ const sid = resolveSessionId(session_id);
35752
+ const page = getSessionPage(sid);
35753
+ const { getDeepPerformance: getDeepPerformance2 } = await Promise.resolve().then(() => (init_deep_performance(), exports_deep_performance));
35754
+ const result = await getDeepPerformance2(page);
35755
+ return json(result);
35756
+ } catch (e) {
35757
+ return err(e);
35758
+ }
35759
+ });
35057
35760
  server.tool("browser_console_log", "Get captured console messages for a session", { session_id: exports_external.string().optional(), level: exports_external.enum(["log", "warn", "error", "debug", "info"]).optional() }, async ({ session_id, level }) => {
35058
35761
  try {
35059
35762
  const sid = resolveSessionId(session_id);
@@ -35117,6 +35820,46 @@ var init_mcp = __esm(async () => {
35117
35820
  return err(e);
35118
35821
  }
35119
35822
  });
35823
+ server.tool("browser_workflow_save", "Save a recording as a reusable workflow with self-healing replay", { recording_id: exports_external.string(), name: exports_external.string(), description: exports_external.string().optional() }, async ({ recording_id, name, description }) => {
35824
+ try {
35825
+ const { saveWorkflowFromRecording: saveWorkflowFromRecording2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
35826
+ return json(saveWorkflowFromRecording2(recording_id, name, description));
35827
+ } catch (e) {
35828
+ return err(e);
35829
+ }
35830
+ });
35831
+ server.tool("browser_workflow_list", "List all saved workflows", {}, async () => {
35832
+ try {
35833
+ const { listWorkflows: listWorkflows2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
35834
+ const workflows = listWorkflows2();
35835
+ return json({ workflows: workflows.map((w) => ({ ...w, steps: `${w.steps.length} steps` })), count: workflows.length });
35836
+ } catch (e) {
35837
+ return err(e);
35838
+ }
35839
+ });
35840
+ server.tool("browser_workflow_run", "Run a saved workflow with self-healing. If selectors changed, auto-adapts and reports what was healed.", { session_id: exports_external.string().optional(), name: exports_external.string() }, async ({ session_id, name }) => {
35841
+ try {
35842
+ const sid = resolveSessionId(session_id);
35843
+ const page = getSessionPage(sid);
35844
+ const { getWorkflowByName: getWorkflowByName2, runWorkflow: runWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
35845
+ const workflow = getWorkflowByName2(name);
35846
+ if (!workflow)
35847
+ return err(new Error(`Workflow '${name}' not found`));
35848
+ const result = await runWorkflow2(workflow, page);
35849
+ logEvent(sid, "workflow_run", { name, ...result });
35850
+ return json(result);
35851
+ } catch (e) {
35852
+ return err(e);
35853
+ }
35854
+ });
35855
+ server.tool("browser_workflow_delete", "Delete a saved workflow", { name: exports_external.string() }, async ({ name }) => {
35856
+ try {
35857
+ const { deleteWorkflow: deleteWorkflow2 } = await Promise.resolve().then(() => (init_workflows(), exports_workflows));
35858
+ return json({ deleted: deleteWorkflow2(name) });
35859
+ } catch (e) {
35860
+ return err(e);
35861
+ }
35862
+ });
35120
35863
  server.tool("browser_crawl", "Crawl a URL recursively and return discovered pages", {
35121
35864
  url: exports_external.string(),
35122
35865
  max_depth: exports_external.number().optional().default(2),
@@ -35812,6 +36555,68 @@ var init_mcp = __esm(async () => {
35812
36555
  return err(e);
35813
36556
  }
35814
36557
  });
36558
+ server.tool("browser_detect_apis", "Scan network traffic for JSON API endpoints. Returns discovered endpoints with methods, status codes, and URLs.", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
36559
+ try {
36560
+ const sid = resolveSessionId(session_id);
36561
+ const { detectAPIs: detectAPIs2 } = await Promise.resolve().then(() => (init_api_detector(), exports_api_detector));
36562
+ const apis = detectAPIs2(sid);
36563
+ return json({ apis, count: apis.length });
36564
+ } catch (e) {
36565
+ return err(e);
36566
+ }
36567
+ });
36568
+ server.tool("browser_extract_structured", "Extract structured data from page: tables, lists, JSON-LD, Open Graph, meta tags, and repeated elements (cards/items).", { session_id: exports_external.string().optional() }, async ({ session_id }) => {
36569
+ try {
36570
+ const sid = resolveSessionId(session_id);
36571
+ const page = getSessionPage(sid);
36572
+ const { extractStructuredData: extractStructuredData2 } = await Promise.resolve().then(() => exports_structured_extract);
36573
+ const data = await extractStructuredData2(page);
36574
+ return json({
36575
+ tables: data.tables.length,
36576
+ lists: data.lists.length,
36577
+ json_ld: data.jsonLd.length,
36578
+ open_graph: Object.keys(data.openGraph).length,
36579
+ meta_tags: Object.keys(data.metaTags).length,
36580
+ repeated_elements: data.repeatedElements.length,
36581
+ data
36582
+ });
36583
+ } catch (e) {
36584
+ return err(e);
36585
+ }
36586
+ });
36587
+ server.tool("browser_dataset_save", "Save extracted data as a named dataset for later use", { name: exports_external.string(), data: exports_external.array(exports_external.record(exports_external.unknown())), source_url: exports_external.string().optional() }, async ({ name, data, source_url }) => {
36588
+ try {
36589
+ const { saveDataset: saveDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
36590
+ const dataset = saveDataset2({ name, rows: data, sourceUrl: source_url });
36591
+ return json({ id: dataset.id, name: dataset.name, row_count: dataset.row_count });
36592
+ } catch (e) {
36593
+ return err(e);
36594
+ }
36595
+ });
36596
+ server.tool("browser_dataset_list", "List all saved datasets", {}, async () => {
36597
+ try {
36598
+ const { listDatasets: listDatasets2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
36599
+ return json({ datasets: listDatasets2() });
36600
+ } catch (e) {
36601
+ return err(e);
36602
+ }
36603
+ });
36604
+ server.tool("browser_dataset_export", "Export a dataset as JSON or CSV file", { name: exports_external.string(), format: exports_external.enum(["json", "csv"]).optional().default("json") }, async ({ name, format }) => {
36605
+ try {
36606
+ const { exportDataset: exportDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
36607
+ return json(exportDataset2(name, format));
36608
+ } catch (e) {
36609
+ return err(e);
36610
+ }
36611
+ });
36612
+ server.tool("browser_dataset_delete", "Delete a saved dataset", { name: exports_external.string() }, async ({ name }) => {
36613
+ try {
36614
+ const { deleteDataset: deleteDataset2 } = await Promise.resolve().then(() => (init_datasets(), exports_datasets));
36615
+ return json({ deleted: deleteDataset2(name) });
36616
+ } catch (e) {
36617
+ return err(e);
36618
+ }
36619
+ });
35815
36620
  server.tool("browser_help", "Show all available browser tools grouped by category with one-line descriptions", {}, async () => {
35816
36621
  try {
35817
36622
  const groups = {
@@ -35898,6 +36703,20 @@ var init_mcp = __esm(async () => {
35898
36703
  { tool: "browser_auth_list", description: "List all saved auth flows" },
35899
36704
  { tool: "browser_auth_delete", description: "Delete a saved auth flow" }
35900
36705
  ],
36706
+ Workflows: [
36707
+ { tool: "browser_workflow_save", description: "Save a recording as a reusable workflow" },
36708
+ { tool: "browser_workflow_list", description: "List all saved workflows" },
36709
+ { tool: "browser_workflow_run", description: "Run a workflow with self-healing replay" },
36710
+ { tool: "browser_workflow_delete", description: "Delete a saved workflow" }
36711
+ ],
36712
+ Data: [
36713
+ { tool: "browser_extract_structured", description: "Extract tables, lists, JSON-LD, Open Graph, meta tags, repeated elements" },
36714
+ { tool: "browser_detect_apis", description: "Scan network traffic for JSON API endpoints" },
36715
+ { tool: "browser_dataset_save", description: "Save extracted data as a named dataset" },
36716
+ { tool: "browser_dataset_list", description: "List all saved datasets" },
36717
+ { tool: "browser_dataset_export", description: "Export dataset as JSON or CSV" },
36718
+ { tool: "browser_dataset_delete", description: "Delete a saved dataset" }
36719
+ ],
35901
36720
  Crawl: [
35902
36721
  { tool: "browser_crawl", description: "Crawl a URL recursively" }
35903
36722
  ],
@@ -35951,6 +36770,8 @@ var init_mcp = __esm(async () => {
35951
36770
  { tool: "browser_check", description: "RECOMMENDED: One-call page summary with diagnostics" },
35952
36771
  { tool: "browser_version", description: "Show running binary version and tool count" },
35953
36772
  { tool: "browser_help", description: "Show this help (all tools)" },
36773
+ { tool: "browser_detect_env", description: "Detect environment (prod/dev/staging/local)" },
36774
+ { tool: "browser_performance_deep", description: "Deep performance: resources, third-party, DOM, memory" },
35954
36775
  { tool: "browser_snapshot_diff", description: "Diff current snapshot vs previous" },
35955
36776
  { tool: "browser_watch_start", description: "Watch page for DOM changes" },
35956
36777
  { tool: "browser_watch_get_changes", description: "Get captured DOM changes" },
@@ -36468,10 +37289,10 @@ __export(exports_snapshots, {
36468
37289
  deleteSnapshot: () => deleteSnapshot,
36469
37290
  createSnapshot: () => createSnapshot
36470
37291
  });
36471
- import { randomUUID as randomUUID14 } from "crypto";
37292
+ import { randomUUID as randomUUID17 } from "crypto";
36472
37293
  function createSnapshot(data) {
36473
37294
  const db2 = getDatabase();
36474
- const id = randomUUID14();
37295
+ const id = randomUUID17();
36475
37296
  db2.prepare("INSERT INTO snapshots (id, session_id, url, title, html, screenshot_path) VALUES (?, ?, ?, ?, ?, ?)").run(id, data.session_id, data.url, data.title ?? null, data.html ?? null, data.screenshot_path ?? null);
36476
37297
  return getSnapshot(id);
36477
37298
  }
@@ -36497,7 +37318,7 @@ var init_snapshots = __esm(() => {
36497
37318
 
36498
37319
  // src/server/index.ts
36499
37320
  var exports_server = {};
36500
- import { join as join15 } from "path";
37321
+ import { join as join16 } from "path";
36501
37322
  import { existsSync as existsSync9 } from "fs";
36502
37323
  function ok(data, status = 200) {
36503
37324
  return new Response(JSON.stringify(data), {
@@ -36791,13 +37612,13 @@ var init_server = __esm(() => {
36791
37612
  const id = path.split("/")[3];
36792
37613
  return ok({ deleted: deleteDownload(id) });
36793
37614
  }
36794
- const dashboardDist = join15(import.meta.dir, "../../dashboard/dist");
37615
+ const dashboardDist = join16(import.meta.dir, "../../dashboard/dist");
36795
37616
  if (existsSync9(dashboardDist)) {
36796
- const filePath = path === "/" ? join15(dashboardDist, "index.html") : join15(dashboardDist, path);
37617
+ const filePath = path === "/" ? join16(dashboardDist, "index.html") : join16(dashboardDist, path);
36797
37618
  if (existsSync9(filePath)) {
36798
37619
  return new Response(Bun.file(filePath), { headers: CORS_HEADERS });
36799
37620
  }
36800
- return new Response(Bun.file(join15(dashboardDist, "index.html")), { headers: CORS_HEADERS });
37621
+ return new Response(Bun.file(join16(dashboardDist, "index.html")), { headers: CORS_HEADERS });
36801
37622
  }
36802
37623
  if (path === "/" || path === "") {
36803
37624
  return new Response("@hasna/browser REST API running. Dashboard not built.", {
@@ -36840,9 +37661,9 @@ init_recorder();
36840
37661
  init_recordings();
36841
37662
  init_lightpanda();
36842
37663
  import { readFileSync as readFileSync9 } from "fs";
36843
- import { join as join16 } from "path";
37664
+ import { join as join17 } from "path";
36844
37665
  import chalk from "chalk";
36845
- var pkg = JSON.parse(readFileSync9(join16(import.meta.dir, "../../package.json"), "utf8"));
37666
+ var pkg = JSON.parse(readFileSync9(join17(import.meta.dir, "../../package.json"), "utf8"));
36846
37667
  var program2 = new Command;
36847
37668
  program2.name("browser").description("@hasna/browser \u2014 general-purpose browser agent CLI").version(pkg.version);
36848
37669
  program2.command("navigate <url>").description("Navigate to a URL and optionally take a screenshot").option("--engine <engine>", "Browser engine: playwright|cdp|lightpanda|auto", "auto").option("--screenshot", "Take a screenshot after navigation").option("--extract", "Extract page text after navigation").option("--headed", "Run in headed (visible) mode").option("--json", "Output as JSON").action(async (url, opts) => {