mcp-scraper 0.3.16 → 0.3.18

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.
Files changed (62) hide show
  1. package/README.md +26 -58
  2. package/dist/bin/api-server.cjs +418 -53
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +2 -2
  5. package/dist/bin/mcp-scraper-cli.cjs +4 -4
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +5 -5
  8. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs +12 -12
  10. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  11. package/dist/bin/mcp-scraper-install.js +3 -3
  12. package/dist/bin/mcp-scraper-install.js.map +1 -1
  13. package/dist/bin/mcp-stdio-server.cjs +2554 -781
  14. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  15. package/dist/bin/mcp-stdio-server.js +1377 -9
  16. package/dist/bin/mcp-stdio-server.js.map +1 -1
  17. package/dist/bin/paa-harvest.js +2 -3
  18. package/dist/bin/paa-harvest.js.map +1 -1
  19. package/dist/{chunk-7XARJHS6.js → chunk-2XCNU22L.js} +332 -13
  20. package/dist/chunk-2XCNU22L.js.map +1 -0
  21. package/dist/chunk-C7ZNIXOA.js +7 -0
  22. package/dist/chunk-C7ZNIXOA.js.map +1 -0
  23. package/dist/{chunk-ACIUCZ27.js → chunk-DE7WP4UU.js} +11 -11
  24. package/dist/chunk-DE7WP4UU.js.map +1 -0
  25. package/dist/{chunk-ZAUMSBV3.js → chunk-HDPWLYK7.js} +6 -6
  26. package/dist/{chunk-4OPKIDON.js → chunk-IGOEAKC7.js} +4 -6
  27. package/dist/{chunk-4OPKIDON.js.map → chunk-IGOEAKC7.js.map} +1 -1
  28. package/dist/{chunk-M2S27J6Z.js → chunk-ZAP7MYVO.js} +26 -1
  29. package/dist/chunk-ZAP7MYVO.js.map +1 -0
  30. package/dist/index.js +2 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/{server-LPVBSE2E.js → server-3M3XCVOT.js} +328 -157
  33. package/dist/server-3M3XCVOT.js.map +1 -0
  34. package/dist/{worker-2WVKKCC7.js → worker-J6I5XP2F.js} +9 -10
  35. package/dist/{worker-2WVKKCC7.js.map → worker-J6I5XP2F.js.map} +1 -1
  36. package/docs/mcp-tool-manifest.generated.json +28 -27
  37. package/package.json +3 -3
  38. package/dist/bin/browser-agent-stdio-server.cjs +0 -1523
  39. package/dist/bin/browser-agent-stdio-server.cjs.map +0 -1
  40. package/dist/bin/browser-agent-stdio-server.d.cts +0 -1
  41. package/dist/bin/browser-agent-stdio-server.d.ts +0 -1
  42. package/dist/bin/browser-agent-stdio-server.js +0 -43
  43. package/dist/bin/browser-agent-stdio-server.js.map +0 -1
  44. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +0 -5601
  45. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +0 -1
  46. package/dist/bin/mcp-scraper-combined-stdio-server.d.cts +0 -1
  47. package/dist/bin/mcp-scraper-combined-stdio-server.d.ts +0 -1
  48. package/dist/bin/mcp-scraper-combined-stdio-server.js +0 -70
  49. package/dist/bin/mcp-scraper-combined-stdio-server.js.map +0 -1
  50. package/dist/chunk-3YGKXXUG.js +0 -133
  51. package/dist/chunk-3YGKXXUG.js.map +0 -1
  52. package/dist/chunk-7XARJHS6.js.map +0 -1
  53. package/dist/chunk-ACIUCZ27.js.map +0 -1
  54. package/dist/chunk-ISZWGIWL.js +0 -1332
  55. package/dist/chunk-ISZWGIWL.js.map +0 -1
  56. package/dist/chunk-M2S27J6Z.js.map +0 -1
  57. package/dist/chunk-SOMBZK3V.js +0 -7
  58. package/dist/chunk-SOMBZK3V.js.map +0 -1
  59. package/dist/chunk-WN7PBKMV.js +0 -28
  60. package/dist/chunk-WN7PBKMV.js.map +0 -1
  61. package/dist/server-LPVBSE2E.js.map +0 -1
  62. /package/dist/{chunk-ZAUMSBV3.js.map → chunk-HDPWLYK7.js.map} +0 -0
@@ -1,3 +1,39 @@
1
+ import {
2
+ CaptureSerpPageSnapshotsInputSchema,
3
+ CaptureSerpPageSnapshotsOutputSchema,
4
+ CaptureSerpSnapshotInputSchema,
5
+ CaptureSerpSnapshotOutputSchema,
6
+ HttpMcpToolExecutor,
7
+ auditImages,
8
+ buildLinkGraph,
9
+ buildLinkReport,
10
+ buildPaaExtractorMcpServer,
11
+ computeIssues,
12
+ configureReportSaving,
13
+ exportFanout,
14
+ formatCaptureSerpPageSnapshots,
15
+ formatCaptureSerpSnapshot,
16
+ harvestTimeoutBudget,
17
+ liveWebToolAnnotations,
18
+ outputBaseDir,
19
+ renderImageSection,
20
+ renderIssueReport,
21
+ renderLinkReport,
22
+ sanitizeAttempts,
23
+ sanitizeHarvestResult
24
+ } from "./chunk-2XCNU22L.js";
25
+ import {
26
+ csvRecords,
27
+ listWorkflowDefinitions,
28
+ rowsToCsv,
29
+ runWorkflow,
30
+ runWorkflowStep,
31
+ workflowDefinition,
32
+ workflowOutputBaseDir,
33
+ workflowStepCount,
34
+ workflowSupportsSteps
35
+ } from "./chunk-H2R232HK.js";
36
+ import "./chunk-C7ZNIXOA.js";
1
37
  import {
2
38
  BALANCE_PACK_LABELS,
3
39
  BALANCE_PRICE_IDS,
@@ -16,7 +52,34 @@ import {
16
52
  harvestProblemResponse,
17
53
  insufficientBalanceResponse,
18
54
  serializeHarvestProblem
19
- } from "./chunk-ZAUMSBV3.js";
55
+ } from "./chunk-HDPWLYK7.js";
56
+ import {
57
+ BrowserDriver,
58
+ MapsSelectors,
59
+ buildYouTubeChannelVideosUrl,
60
+ deleteKernelProxyId,
61
+ harvest,
62
+ resolveKernelProxyId
63
+ } from "./chunk-IGOEAKC7.js";
64
+ import {
65
+ CaptchaError,
66
+ RECAPTCHA_INSTRUCTIONS,
67
+ browserServiceApiKey,
68
+ browserServiceProfileName,
69
+ browserServiceProfileSaveChanges,
70
+ browserServiceProxyId,
71
+ sanitizeVendorName
72
+ } from "./chunk-ZAP7MYVO.js";
73
+ import {
74
+ DEFAULT_MAPS_PROXY_MODE,
75
+ DEFAULT_PROXY_MODE,
76
+ MapsPlaceOptionsSchema,
77
+ MapsSearchOptionsSchema,
78
+ RawMapsAboutAttributeSchema,
79
+ RawMapsHoursRowSchema,
80
+ RawMapsOverviewSchema,
81
+ RawMapsReviewStatsSchema
82
+ } from "./chunk-LFATOGDF.js";
20
83
  import {
21
84
  SiteAuditJobRowSchema,
22
85
  advanceWorkflowRunStep,
@@ -78,116 +141,6 @@ import {
78
141
  setStripeCustomerId,
79
142
  verifyPassword
80
143
  } from "./chunk-5H22TOXQ.js";
81
- import {
82
- BrowserDriver,
83
- MapsSelectors,
84
- buildYouTubeChannelVideosUrl,
85
- deleteKernelProxyId,
86
- harvest,
87
- resolveKernelProxyId
88
- } from "./chunk-4OPKIDON.js";
89
- import {
90
- CaptureSerpPageSnapshotsInputSchema,
91
- CaptureSerpPageSnapshotsOutputSchema,
92
- CaptureSerpSnapshotInputSchema,
93
- CaptureSerpSnapshotOutputSchema,
94
- HttpMcpToolExecutor,
95
- auditImages,
96
- buildLinkGraph,
97
- buildPaaExtractorMcpServer,
98
- computeIssues,
99
- configureReportSaving,
100
- formatCaptureSerpPageSnapshots,
101
- formatCaptureSerpSnapshot,
102
- harvestTimeoutBudget,
103
- liveWebToolAnnotations,
104
- outputBaseDir,
105
- renderImageSection,
106
- renderIssueReport
107
- } from "./chunk-7XARJHS6.js";
108
- import {
109
- CaptchaError,
110
- RECAPTCHA_INSTRUCTIONS,
111
- sanitizeVendorName
112
- } from "./chunk-M2S27J6Z.js";
113
- import {
114
- exportFanout
115
- } from "./chunk-3YGKXXUG.js";
116
- import {
117
- browserServiceApiKey,
118
- browserServiceProfileName,
119
- browserServiceProfileSaveChanges,
120
- browserServiceProxyId
121
- } from "./chunk-WN7PBKMV.js";
122
- import {
123
- csvRecords,
124
- listWorkflowDefinitions,
125
- rowsToCsv,
126
- runWorkflow,
127
- runWorkflowStep,
128
- workflowDefinition,
129
- workflowOutputBaseDir,
130
- workflowStepCount,
131
- workflowSupportsSteps
132
- } from "./chunk-H2R232HK.js";
133
- import {
134
- DEFAULT_MAPS_PROXY_MODE,
135
- DEFAULT_PROXY_MODE,
136
- MapsPlaceOptionsSchema,
137
- MapsSearchOptionsSchema,
138
- RawMapsAboutAttributeSchema,
139
- RawMapsHoursRowSchema,
140
- RawMapsOverviewSchema,
141
- RawMapsReviewStatsSchema
142
- } from "./chunk-LFATOGDF.js";
143
- import "./chunk-SOMBZK3V.js";
144
-
145
- // src/api/outbound-sanitize.ts
146
- var KEY_RENAMES = {
147
- kernel: "browserRuntime",
148
- kernel_session_id: "browser_session_id",
149
- kernel_delete_started: "session_cleanup_started",
150
- kernel_delete_succeeded: "session_cleanup_succeeded",
151
- kernel_delete_error: "session_cleanup_error",
152
- kernelSessionId: "browserSessionId",
153
- kernelDeleteStarted: "sessionCleanupStarted",
154
- kernelDeleteSucceeded: "sessionCleanupSucceeded",
155
- kernelDeleteError: "sessionCleanupError",
156
- kernelProxyId: "proxyId"
157
- };
158
- var SANITIZED_VALUE_KEYS = /error|message/i;
159
- function sanitizeOutboundDiagnostics(value, parentKey = "") {
160
- if (typeof value === "string") {
161
- if (SANITIZED_VALUE_KEYS.test(parentKey) && /kernel/i.test(value)) {
162
- return sanitizeVendorName(value);
163
- }
164
- return value;
165
- }
166
- if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
167
- if (value !== null && typeof value === "object") {
168
- const out = {};
169
- for (const [key, val] of Object.entries(value)) {
170
- const renamed = KEY_RENAMES[key] ?? key;
171
- out[renamed] = sanitizeOutboundDiagnostics(val, key);
172
- }
173
- return out;
174
- }
175
- return value;
176
- }
177
- function sanitizeAttempts(attempts) {
178
- return attempts.map((a) => sanitizeOutboundDiagnostics(a));
179
- }
180
- function sanitizeHarvestResult(result) {
181
- const diagnostics = result?.diagnostics;
182
- if (!diagnostics?.debug) return result;
183
- return {
184
- ...result,
185
- diagnostics: {
186
- ...diagnostics,
187
- debug: sanitizeOutboundDiagnostics(diagnostics.debug)
188
- }
189
- };
190
- }
191
144
 
192
145
  // src/blog/registry.ts
193
146
  var posts = [
@@ -7584,14 +7537,10 @@ async function settleExtractJob(jobId, userId, refundMc, netChargeMc, reference)
7584
7537
 
7585
7538
  // src/inngest/functions/site-extract.ts
7586
7539
  var BATCH = 30;
7587
- function chunk(items, size) {
7588
- const out = [];
7589
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
7590
- return out;
7591
- }
7592
7540
  function buildArtifacts(job, pages, branding, imageAudit) {
7593
7541
  const { edges, metrics } = buildLinkGraph(pages, job.startUrl);
7594
7542
  const issues = computeIssues(pages, metrics);
7543
+ const linkReport = buildLinkReport(edges, [...metrics.values()], job.startUrl);
7595
7544
  let reportMd = renderIssueReport(job.startUrl, pages, issues, metrics);
7596
7545
  if (imageAudit) reportMd += `
7597
7546
 
@@ -7607,7 +7556,10 @@ ${renderImageSection(imageAudit)}`;
7607
7556
  { name: "issues.json", body: JSON.stringify(issues, null, 2), type: "application/json" },
7608
7557
  { name: "pages.jsonl", body: jsonl(pageRows), type: "application/x-ndjson" },
7609
7558
  { name: "links.jsonl", body: jsonl(edges), type: "application/x-ndjson" },
7610
- { name: "link-metrics.jsonl", body: jsonl([...metrics.values()]), type: "application/x-ndjson" }
7559
+ { name: "link-metrics.jsonl", body: jsonl([...metrics.values()]), type: "application/x-ndjson" },
7560
+ { name: "link-report.md", body: renderLinkReport(linkReport), type: "text/markdown" },
7561
+ { name: "links-summary.json", body: JSON.stringify(linkReport.summary, null, 2), type: "application/json" },
7562
+ { name: "external-domains.json", body: JSON.stringify(linkReport.externalDomains, null, 2), type: "application/json" }
7611
7563
  ];
7612
7564
  if (imageAudit) {
7613
7565
  files.push({ name: "images.jsonl", body: jsonl(imageAudit.rows), type: "application/x-ndjson" });
@@ -7643,18 +7595,62 @@ var siteExtractFn = inngest.createFunction(
7643
7595
  const maxPages = Number(job.options.maxPages ?? 1e4);
7644
7596
  const concurrency = Number(job.options.concurrency ?? 1);
7645
7597
  const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
7646
- const urls = await step.run("discover", async () => {
7598
+ const seed = await step.run("discover", async () => {
7647
7599
  const discovered = await discoverUrls(job.startUrl, maxPages, key);
7648
- await setExtractJobTotal(jobId, discovered.length);
7649
- return discovered;
7600
+ const out = discovered.length ? discovered : [job.startUrl];
7601
+ await setExtractJobTotal(jobId, out.length);
7602
+ return out;
7650
7603
  });
7651
- const batches = chunk(urls, BATCH);
7652
- for (let i = 0; i < batches.length; i++) {
7653
- await step.run(`crawl-batch-${i}`, async () => {
7654
- const pages = await extractPagesRotating(batches[i], { kernelApiKey: key, concurrency, urlsPerBrowser });
7604
+ const siteHost = (() => {
7605
+ try {
7606
+ return new URL(job.startUrl).hostname.replace(/^www\./, "");
7607
+ } catch {
7608
+ return "";
7609
+ }
7610
+ })();
7611
+ const norm = (u) => {
7612
+ try {
7613
+ const x = new URL(u);
7614
+ x.hash = "";
7615
+ return (x.origin + x.pathname).replace(/\/+$/, "") + x.search;
7616
+ } catch {
7617
+ return u;
7618
+ }
7619
+ };
7620
+ const sameSite = (u) => {
7621
+ try {
7622
+ return new URL(u).hostname.replace(/^www\./, "") === siteHost;
7623
+ } catch {
7624
+ return false;
7625
+ }
7626
+ };
7627
+ const queue = [...seed];
7628
+ const seen = new Set(seed.map(norm));
7629
+ let crawled = 0;
7630
+ const MAX_BATCHES = Math.ceil(maxPages / BATCH) + 30;
7631
+ for (let i = 0; i < MAX_BATCHES && crawled < maxPages && crawled < queue.length; i++) {
7632
+ const batch = queue.slice(crawled, Math.min(crawled + BATCH, maxPages));
7633
+ if (!batch.length) break;
7634
+ const newLinks = await step.run(`crawl-batch-${i}`, async () => {
7635
+ const pages = await extractPagesRotating(batch, { kernelApiKey: key, concurrency, urlsPerBrowser });
7655
7636
  await saveExtractPages(jobId, pages);
7656
- return pages.length;
7637
+ const found = [];
7638
+ for (const p of pages) for (const l of p.outlinks ?? []) {
7639
+ if (l.internal && l.href && sameSite(l.href)) found.push(l.href);
7640
+ }
7641
+ return [...new Set(found)];
7657
7642
  });
7643
+ crawled += batch.length;
7644
+ let added = false;
7645
+ for (const href of newLinks) {
7646
+ const n = norm(href);
7647
+ if (!seen.has(n) && seen.size < maxPages) {
7648
+ seen.add(n);
7649
+ queue.push(href);
7650
+ added = true;
7651
+ }
7652
+ }
7653
+ if (added) await setExtractJobTotal(jobId, Math.min(seen.size, maxPages));
7658
7654
  }
7659
7655
  const branding = Array.isArray(job.options.formats) && job.options.formats.includes("branding") ? await step.run("branding", () => extractBranding(job.startUrl, key).catch(() => null)) : null;
7660
7656
  const imageAudit = Array.isArray(job.options.formats) && job.options.formats.includes("images") ? await step.run("image-audit", async () => {
@@ -9337,10 +9333,10 @@ function buildTranscriptMarkdown(result) {
9337
9333
  if (result.chunks?.length) {
9338
9334
  lines.push("## Timestamped Segments");
9339
9335
  lines.push("");
9340
- for (const chunk2 of result.chunks) {
9341
- const start = fmtTs(chunk2.timestamp[0]);
9342
- const end = fmtTs(chunk2.timestamp[1]);
9343
- lines.push(`**[${start} \u2192 ${end}]** ${chunk2.text.trim()}`);
9336
+ for (const chunk of result.chunks) {
9337
+ const start = fmtTs(chunk.timestamp[0]);
9338
+ const end = fmtTs(chunk.timestamp[1]);
9339
+ lines.push(`**[${start} \u2192 ${end}]** ${chunk.text.trim()}`);
9344
9340
  lines.push("");
9345
9341
  }
9346
9342
  }
@@ -9841,20 +9837,20 @@ var FacebookAdExtractor = class {
9841
9837
  if (splitRe.lastIndex === m.index) splitRe.lastIndex++;
9842
9838
  }
9843
9839
  if (last < bodyText.length) adChunks.push(bodyText.slice(last));
9844
- const ads = adChunks.filter((c) => /Library ID[:\s]+\d{10,20}/i.test(c)).slice(0, maxAds).map((chunk2) => {
9845
- const lidM = chunk2.match(/Library ID[:\s]+([0-9]{10,20})/i);
9840
+ const ads = adChunks.filter((c) => /Library ID[:\s]+\d{10,20}/i.test(c)).slice(0, maxAds).map((chunk) => {
9841
+ const lidM = chunk.match(/Library ID[:\s]+([0-9]{10,20})/i);
9846
9842
  const libraryId = lidM ? lidM[1] : null;
9847
- const statusM = chunk2.match(/^(Active|Inactive)/i);
9843
+ const statusM = chunk.match(/^(Active|Inactive)/i);
9848
9844
  const status = statusM ? statusM[1] : null;
9849
- const startM = chunk2.match(/Started running on\s+([A-Za-z]+\.?\s+\d{1,2},?\s*\d{4})/i);
9845
+ const startM = chunk.match(/Started running on\s+([A-Za-z]+\.?\s+\d{1,2},?\s*\d{4})/i);
9850
9846
  const started = startM ? startM[1].replace(/\s+/g, " ").trim() : null;
9851
9847
  const visual = adVisuals[libraryId ?? ""] ?? { imageSrc: null, videoSrc: null, videoPoster: null };
9852
- const hasVideoText = /0:00\s*\/\s*0:00|\d+:\d+\s*\/\s*\d+:\d+/.test(chunk2);
9848
+ const hasVideoText = /0:00\s*\/\s*0:00|\d+:\d+\s*\/\s*\d+:\d+/.test(chunk);
9853
9849
  const creativeType = visual.videoSrc || hasVideoText ? "video" : visual.imageSrc ? "image" : "unknown";
9854
9850
  let domain = null;
9855
9851
  const domainRe = /\b([A-Z0-9]{2,}(?:\.[A-Z]{2,})+)\b/g;
9856
9852
  let dm;
9857
- while ((dm = domainRe.exec(chunk2)) !== null) {
9853
+ while ((dm = domainRe.exec(chunk)) !== null) {
9858
9854
  const c = dm[1];
9859
9855
  if (c.length >= 6 && c !== "LIBRARY") {
9860
9856
  domain = c;
@@ -9863,9 +9859,9 @@ var FacebookAdExtractor = class {
9863
9859
  }
9864
9860
  let primaryText = null;
9865
9861
  let headline = null;
9866
- const sponsoredIdx = chunk2.search(/\bSponsored\b/i);
9862
+ const sponsoredIdx = chunk.search(/\bSponsored\b/i);
9867
9863
  if (sponsoredIdx >= 0) {
9868
- let afterSponsored = chunk2.slice(sponsoredIdx + "Sponsored".length).trim();
9864
+ let afterSponsored = chunk.slice(sponsoredIdx + "Sponsored".length).trim();
9869
9865
  afterSponsored = afterSponsored.replace(/\s*\d*:?\d+\s*\/\s*\d*:?\d+.*$/, "");
9870
9866
  if (domain) {
9871
9867
  const di = afterSponsored.indexOf(domain);
@@ -9876,18 +9872,18 @@ var FacebookAdExtractor = class {
9876
9872
  }
9877
9873
  const ctaRe2 = new RegExp("\\b(" + CTA_LABELS.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")\\b", "i");
9878
9874
  if (domain) {
9879
- const di = chunk2.indexOf(domain);
9875
+ const di = chunk.indexOf(domain);
9880
9876
  if (di >= 0) {
9881
- let afterDomain = chunk2.slice(di + domain.length).trim();
9877
+ let afterDomain = chunk.slice(di + domain.length).trim();
9882
9878
  const ctaMatch = afterDomain.search(ctaRe2);
9883
9879
  if (ctaMatch >= 0) afterDomain = afterDomain.slice(0, ctaMatch).trim();
9884
9880
  if (afterDomain.length > 3 && afterDomain.length < 120) headline = afterDomain;
9885
9881
  }
9886
9882
  }
9887
9883
  if (!headline) {
9888
- const ctaIdx = chunk2.search(ctaRe2);
9884
+ const ctaIdx = chunk.search(ctaRe2);
9889
9885
  if (ctaIdx > 0) {
9890
- const beforeCta = chunk2.slice(0, ctaIdx).trimEnd();
9886
+ const beforeCta = chunk.slice(0, ctaIdx).trimEnd();
9891
9887
  const sentenceEnd = Math.max(beforeCta.lastIndexOf(". "), beforeCta.lastIndexOf("! "), beforeCta.lastIndexOf("? "));
9892
9888
  if (sentenceEnd >= 0) {
9893
9889
  const candidate = beforeCta.slice(sentenceEnd + 2).trim();
@@ -9899,12 +9895,12 @@ var FacebookAdExtractor = class {
9899
9895
  }
9900
9896
  let cta = null;
9901
9897
  for (const lbl of CTA_LABELS) {
9902
- if (chunk2.toLowerCase().includes(lbl.toLowerCase())) {
9898
+ if (chunk.toLowerCase().includes(lbl.toLowerCase())) {
9903
9899
  cta = lbl;
9904
9900
  break;
9905
9901
  }
9906
9902
  }
9907
- const multiM = chunk2.match(/(\d+)\s+ad[s]?\s+use[s]?\s+this/i);
9903
+ const multiM = chunk.match(/(\d+)\s+ad[s]?\s+use[s]?\s+this/i);
9908
9904
  const clusterCount = multiM ? parseInt(multiM[1]) : null;
9909
9905
  return {
9910
9906
  libraryId,
@@ -9927,8 +9923,8 @@ var FacebookAdExtractor = class {
9927
9923
  const unknownCreativeCount = ads.filter((a) => a.creativeType === "unknown").length;
9928
9924
  const advertiserPageId = new URL(page.url()).searchParams.get("view_all_page_id") ?? null;
9929
9925
  const nameFreq = /* @__PURE__ */ new Map();
9930
- for (const chunk2 of adChunks) {
9931
- const detailsParts = chunk2.split("See ad details ");
9926
+ for (const chunk of adChunks) {
9927
+ const detailsParts = chunk.split("See ad details ");
9932
9928
  if (detailsParts.length < 2) continue;
9933
9929
  const sponsoredIdx = detailsParts[1].indexOf(" Sponsored");
9934
9930
  if (sponsoredIdx < 0) continue;
@@ -10668,11 +10664,11 @@ facebookAdApp.post("/search", createApiKeyAuth(), async (c) => {
10668
10664
  }
10669
10665
  if (last < bodyText.length) adChunks.push(bodyText.slice(last));
10670
10666
  const advertiserMap = /* @__PURE__ */ new Map();
10671
- for (const chunk2 of adChunks) {
10672
- const lidM = chunk2.match(/Library ID[:\s]+([0-9]{10,20})/i);
10667
+ for (const chunk of adChunks) {
10668
+ const lidM = chunk.match(/Library ID[:\s]+([0-9]{10,20})/i);
10673
10669
  if (!lidM) continue;
10674
10670
  const libraryId = lidM[1];
10675
- const detailsParts = chunk2.split("See ad details ");
10671
+ const detailsParts = chunk.split("See ad details ");
10676
10672
  if (detailsParts.length < 2) continue;
10677
10673
  const afterDetails = detailsParts[1];
10678
10674
  const sponsoredIdx = afterDetails.indexOf(" Sponsored");
@@ -11224,8 +11220,8 @@ function runFfmpegMux(videoPath, audioPath, outPath) {
11224
11220
  stdio: ["ignore", "ignore", "pipe"]
11225
11221
  });
11226
11222
  let stderr = "";
11227
- child.stderr.on("data", (chunk2) => {
11228
- stderr += String(chunk2);
11223
+ child.stderr.on("data", (chunk) => {
11224
+ stderr += String(chunk);
11229
11225
  });
11230
11226
  child.on("error", (err) => resolve({ ok: false, error: err.message }));
11231
11227
  child.on("close", (code) => resolve({ ok: code === 0, error: code === 0 ? null : stderr.trim() || `ffmpeg exited ${code}` }));
@@ -14357,8 +14353,64 @@ async function migrateBrowserAgent() {
14357
14353
  )
14358
14354
  `);
14359
14355
  await db.execute(`CREATE INDEX IF NOT EXISTS browser_agent_replays_session ON browser_agent_replays(session_id)`);
14356
+ await db.execute(`
14357
+ CREATE TABLE IF NOT EXISTS browser_auth_connections (
14358
+ connection_id TEXT PRIMARY KEY,
14359
+ hosted_url TEXT,
14360
+ domain TEXT,
14361
+ profile TEXT,
14362
+ account_email TEXT,
14363
+ note TEXT,
14364
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
14365
+ updated_at TEXT
14366
+ )
14367
+ `);
14368
+ try {
14369
+ await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN account_email TEXT`);
14370
+ } catch {
14371
+ }
14372
+ try {
14373
+ await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN note TEXT`);
14374
+ } catch {
14375
+ }
14376
+ try {
14377
+ await db.execute(`ALTER TABLE browser_auth_connections ADD COLUMN updated_at TEXT`);
14378
+ } catch {
14379
+ }
14380
+ await db.execute(`CREATE INDEX IF NOT EXISTS browser_auth_connections_profile ON browser_auth_connections(profile)`);
14360
14381
  _ready = true;
14361
14382
  }
14383
+ async function saveAuthConnection(input) {
14384
+ if (!input.connectionId) return;
14385
+ const db = getDb();
14386
+ await db.execute({
14387
+ sql: `INSERT INTO browser_auth_connections (connection_id, hosted_url, domain, profile, account_email, note, updated_at)
14388
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
14389
+ ON CONFLICT(connection_id) DO UPDATE SET
14390
+ hosted_url = COALESCE(excluded.hosted_url, browser_auth_connections.hosted_url),
14391
+ domain = COALESCE(excluded.domain, browser_auth_connections.domain),
14392
+ profile = COALESCE(excluded.profile, browser_auth_connections.profile),
14393
+ account_email = COALESCE(excluded.account_email, browser_auth_connections.account_email),
14394
+ note = COALESCE(excluded.note, browser_auth_connections.note),
14395
+ updated_at = datetime('now')`,
14396
+ args: [input.connectionId, input.hostedUrl, input.domain, input.profile, input.accountEmail ?? null, input.note ?? null]
14397
+ });
14398
+ }
14399
+ async function getAuthConnectionRow(connectionId) {
14400
+ if (!connectionId) return null;
14401
+ const db = getDb();
14402
+ const res = await db.execute({ sql: `SELECT * FROM browser_auth_connections WHERE connection_id = ?`, args: [connectionId] });
14403
+ return res.rows[0] ?? null;
14404
+ }
14405
+ async function listAuthConnectionsByProfile(profile) {
14406
+ if (!profile) return [];
14407
+ const db = getDb();
14408
+ const res = await db.execute({
14409
+ sql: `SELECT * FROM browser_auth_connections WHERE profile = ? ORDER BY COALESCE(updated_at, created_at) DESC`,
14410
+ args: [profile]
14411
+ });
14412
+ return res.rows;
14413
+ }
14362
14414
  async function createSessionRow(input) {
14363
14415
  const db = getDb();
14364
14416
  const id = `bas_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
@@ -15191,6 +15243,17 @@ async function findProfileConnection(k, profileName, domain) {
15191
15243
  }
15192
15244
  return null;
15193
15245
  }
15246
+ async function listProfileConnections(profileName) {
15247
+ if (!profileName) return [];
15248
+ const k = client();
15249
+ const out = [];
15250
+ for await (const connection of k.auth.connections.list({ profile_name: profileName })) {
15251
+ const data = connection;
15252
+ if (data.profile_name && data.profile_name !== profileName) continue;
15253
+ out.push(profileOnboardResult(connection));
15254
+ }
15255
+ return out;
15256
+ }
15194
15257
  async function findOrCreateProfileConnection(k, opts) {
15195
15258
  try {
15196
15259
  return await k.auth.connections.create({
@@ -15584,7 +15647,7 @@ function publicSession(row) {
15584
15647
  session_id: row.id,
15585
15648
  status: row.status,
15586
15649
  label: row.label,
15587
- live_view_url: row.live_view_url,
15650
+ watch_url: `/console/${row.id}`,
15588
15651
  created_at: row.created_at,
15589
15652
  last_action_at: row.last_action_at,
15590
15653
  closed_at: row.closed_at,
@@ -15640,10 +15703,23 @@ function buildBrowserAgentRoutes() {
15640
15703
  const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
15641
15704
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : "chatgpt.com";
15642
15705
  const loginUrl = typeof body.login_url === "string" ? body.login_url.trim() : void 0;
15706
+ const accountEmail = typeof body.account_email === "string" ? body.account_email.trim() || null : null;
15707
+ const note = typeof body.note === "string" ? body.note.trim() || null : null;
15643
15708
  if (!profileName) return c.json({ error: "profile is required" }, 400);
15644
15709
  if (!domain) return c.json({ error: "domain is required" }, 400);
15645
15710
  try {
15646
15711
  const result = await startProfileOnboarding({ profileName, domain, loginUrl });
15712
+ try {
15713
+ await saveAuthConnection({
15714
+ connectionId: result.connectionId,
15715
+ hostedUrl: result.hostedUrl,
15716
+ domain: result.domain,
15717
+ profile: result.profileName,
15718
+ accountEmail,
15719
+ note
15720
+ });
15721
+ } catch {
15722
+ }
15647
15723
  return c.json({
15648
15724
  connection_id: result.connectionId,
15649
15725
  profile: result.profileName,
@@ -15652,8 +15728,6 @@ function buildBrowserAgentRoutes() {
15652
15728
  flow_status: result.flowStatus,
15653
15729
  flow_step: result.flowStep,
15654
15730
  flow_expires_at: result.flowExpiresAt,
15655
- hosted_url: result.hostedUrl,
15656
- live_view_url: result.liveViewUrl,
15657
15731
  browser_session_id: result.browserSessionId,
15658
15732
  post_login_url: result.postLoginUrl
15659
15733
  });
@@ -15668,6 +15742,17 @@ function buildBrowserAgentRoutes() {
15668
15742
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
15669
15743
  try {
15670
15744
  const result = await getProfileOnboardingStatus({ connectionId, profileName, domain });
15745
+ if (result.hostedUrl) {
15746
+ try {
15747
+ await saveAuthConnection({
15748
+ connectionId: result.connectionId,
15749
+ hostedUrl: result.hostedUrl,
15750
+ domain: result.domain,
15751
+ profile: result.profileName
15752
+ });
15753
+ } catch {
15754
+ }
15755
+ }
15671
15756
  return c.json({
15672
15757
  connection_id: result.connectionId,
15673
15758
  profile: result.profileName,
@@ -15676,8 +15761,6 @@ function buildBrowserAgentRoutes() {
15676
15761
  flow_status: result.flowStatus,
15677
15762
  flow_step: result.flowStep,
15678
15763
  flow_expires_at: result.flowExpiresAt,
15679
- hosted_url: result.hostedUrl,
15680
- live_view_url: result.liveViewUrl,
15681
15764
  browser_session_id: result.browserSessionId,
15682
15765
  post_login_url: result.postLoginUrl
15683
15766
  });
@@ -15685,6 +15768,37 @@ function buildBrowserAgentRoutes() {
15685
15768
  return c.json(failure(err), 404);
15686
15769
  }
15687
15770
  });
15771
+ app2.post("/profiles/list", async (c) => {
15772
+ const body = await c.req.json().catch(() => ({}));
15773
+ const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
15774
+ if (!profileName) return c.json({ error: "profile is required" }, 400);
15775
+ const domainFilter = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
15776
+ const connectionIdFilter = typeof body.connection_id === "string" ? body.connection_id.trim() : void 0;
15777
+ try {
15778
+ const [live, stored] = await Promise.all([
15779
+ listProfileConnections(profileName),
15780
+ listAuthConnectionsByProfile(profileName).catch(() => [])
15781
+ ]);
15782
+ const notesByDomain = new Map(stored.map((row) => [row.domain ?? "", row]));
15783
+ const notesByConnection = new Map(stored.map((row) => [row.connection_id, row]));
15784
+ let connections = live.map((conn) => {
15785
+ const record = notesByConnection.get(conn.connectionId) ?? notesByDomain.get(conn.domain);
15786
+ return {
15787
+ connection_id: conn.connectionId,
15788
+ domain: conn.domain,
15789
+ status: conn.status,
15790
+ account_email: record?.account_email ?? null,
15791
+ note: record?.note ?? null,
15792
+ last_connected_at: record?.updated_at ?? record?.created_at ?? null
15793
+ };
15794
+ });
15795
+ if (connectionIdFilter) connections = connections.filter((c2) => c2.connection_id === connectionIdFilter);
15796
+ if (domainFilter) connections = connections.filter((c2) => c2.domain === domainFilter);
15797
+ return c.json({ profile: profileName, connections });
15798
+ } catch (err) {
15799
+ return c.json(failure(err), 502);
15800
+ }
15801
+ });
15688
15802
  app2.post("/sessions", async (c) => {
15689
15803
  const user = c.get("user");
15690
15804
  if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
@@ -16041,6 +16155,50 @@ function buildBrowserAgentRoutes() {
16041
16155
  }
16042
16156
 
16043
16157
  // src/api/browser-agent-console.ts
16158
+ function renderAuthConsoleHtml(connectionId, hostedUrl) {
16159
+ const safeId = String(connectionId).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
16160
+ const goUrl = `/console/auth/${encodeURIComponent(connectionId)}/go`;
16161
+ const body = hostedUrl ? `<iframe class="stage" allow="clipboard-read; clipboard-write" src="${goUrl}"></iframe>
16162
+ <div class="fallback">
16163
+ <span>Sign-in window not showing?</span>
16164
+ <a class="btn" href="${goUrl}" target="_blank" rel="noopener">Open sign-in in a new tab</a>
16165
+ </div>` : `<div class="empty">
16166
+ <div>This sign-in link is no longer active.</div>
16167
+ <div class="muted">Ask the assistant to reconnect the profile, then open the new link.</div>
16168
+ </div>`;
16169
+ return `<!DOCTYPE html>
16170
+ <html lang="en">
16171
+ <head>
16172
+ <meta charset="UTF-8" />
16173
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
16174
+ <title>MCP Scraper \u2014 Secure Sign-in</title>
16175
+ <style>
16176
+ :root { color-scheme: dark; }
16177
+ :where(*) { box-sizing: border-box; }
16178
+ body { margin: 0; height: 100vh; display: flex; flex-direction: column; font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif; background: #0b0e14; color: #d7dce5; }
16179
+ header { display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-bottom: 1px solid #1c2230; background: #0f131c; }
16180
+ header h1 { font-size: 14px; margin: 0; font-weight: 600; color: #fff; }
16181
+ header .muted { color: #6b7689; font-size: 12px; }
16182
+ .spacer { flex: 1; }
16183
+ .stage { flex: 1; width: 100%; border: 0; display: block; background: #05070b; }
16184
+ .fallback { display: flex; align-items: center; gap: 12px; padding: 10px 18px; border-top: 1px solid #1c2230; background: #0f131c; font-size: 13px; color: #8893a7; }
16185
+ .btn { background: #2b6cff; color: #fff; border-radius: 7px; padding: 7px 12px; text-decoration: none; font-weight: 500; }
16186
+ .empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; color: #8893a7; }
16187
+ .muted { color: #6b7689; font-size: 12px; }
16188
+ </style>
16189
+ ${ATTRIBUTION_PIXEL_SCRIPT}
16190
+ </head>
16191
+ <body>
16192
+ <header>
16193
+ <h1>MCP Scraper</h1>
16194
+ <span class="muted">Secure sign-in \xB7 ${safeId}</span>
16195
+ <span class="spacer"></span>
16196
+ <span class="muted">Complete the login below \u2014 your session is saved to your profile.</span>
16197
+ </header>
16198
+ ${body}
16199
+ </body>
16200
+ </html>`;
16201
+ }
16044
16202
  function renderConsoleHtml(initialSessionId) {
16045
16203
  const initial = JSON.stringify(initialSessionId ?? "");
16046
16204
  return `<!DOCTYPE html>
@@ -17372,7 +17530,7 @@ app.get("/cron/tick", async (c) => {
17372
17530
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
17373
17531
  return c.json({ error: "Unauthorized" }, 401);
17374
17532
  }
17375
- const { drainQueue } = await import("./worker-2WVKKCC7.js");
17533
+ const { drainQueue } = await import("./worker-J6I5XP2F.js");
17376
17534
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
17377
17535
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
17378
17536
  const [results, sweepResult] = await Promise.all([
@@ -17394,6 +17552,19 @@ app.route("/serp-intelligence", serpIntelligenceApp);
17394
17552
  app.route("/mcp", mcpApp);
17395
17553
  app.route("/agent", buildBrowserAgentRoutes());
17396
17554
  app.get("/console", (c) => c.html(renderConsoleHtml()));
17555
+ app.get("/console/auth/:id", async (c) => {
17556
+ const id = c.req.param("id");
17557
+ await migrateBrowserAgent();
17558
+ const row = await getAuthConnectionRow(id);
17559
+ return c.html(renderAuthConsoleHtml(id, row?.hosted_url ?? null));
17560
+ });
17561
+ app.get("/console/auth/:id/go", async (c) => {
17562
+ const id = c.req.param("id");
17563
+ await migrateBrowserAgent();
17564
+ const row = await getAuthConnectionRow(id);
17565
+ if (!row?.hosted_url) return c.text("This sign-in link is no longer active.", 404);
17566
+ return c.redirect(row.hosted_url, 302);
17567
+ });
17397
17568
  app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
17398
17569
  app.route("/stripe", stripeApp);
17399
17570
  if (!process.env.INNGEST_EVENT_KEY) {
@@ -17502,4 +17673,4 @@ app.get("/blog/:slug/", (c) => {
17502
17673
  export {
17503
17674
  app
17504
17675
  };
17505
- //# sourceMappingURL=server-LPVBSE2E.js.map
17676
+ //# sourceMappingURL=server-3M3XCVOT.js.map