mcp-scraper 0.3.17 → 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 (61) hide show
  1. package/README.md +26 -58
  2. package/dist/bin/api-server.cjs +286 -49
  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 +2441 -785
  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-N33YEY6W.js → chunk-2XCNU22L.js} +210 -10
  20. package/dist/{chunk-N33YEY6W.js.map → chunk-2XCNU22L.js.map} +1 -1
  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-TKWY47JA.js → server-3M3XCVOT.js} +323 -158
  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 -5718
  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-ACIUCZ27.js.map +0 -1
  53. package/dist/chunk-M2S27J6Z.js.map +0 -1
  54. package/dist/chunk-PVWWY5NV.js +0 -7
  55. package/dist/chunk-PVWWY5NV.js.map +0 -1
  56. package/dist/chunk-UFD7N36R.js +0 -1332
  57. package/dist/chunk-UFD7N36R.js.map +0 -1
  58. package/dist/chunk-WN7PBKMV.js +0 -28
  59. package/dist/chunk-WN7PBKMV.js.map +0 -1
  60. package/dist/server-TKWY47JA.js.map +0 -1
  61. /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,118 +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
- buildLinkReport,
98
- buildPaaExtractorMcpServer,
99
- computeIssues,
100
- configureReportSaving,
101
- formatCaptureSerpPageSnapshots,
102
- formatCaptureSerpSnapshot,
103
- harvestTimeoutBudget,
104
- liveWebToolAnnotations,
105
- outputBaseDir,
106
- renderImageSection,
107
- renderIssueReport,
108
- renderLinkReport
109
- } from "./chunk-N33YEY6W.js";
110
- import {
111
- CaptchaError,
112
- RECAPTCHA_INSTRUCTIONS,
113
- sanitizeVendorName
114
- } from "./chunk-M2S27J6Z.js";
115
- import {
116
- exportFanout
117
- } from "./chunk-3YGKXXUG.js";
118
- import {
119
- browserServiceApiKey,
120
- browserServiceProfileName,
121
- browserServiceProfileSaveChanges,
122
- browserServiceProxyId
123
- } from "./chunk-WN7PBKMV.js";
124
- import {
125
- csvRecords,
126
- listWorkflowDefinitions,
127
- rowsToCsv,
128
- runWorkflow,
129
- runWorkflowStep,
130
- workflowDefinition,
131
- workflowOutputBaseDir,
132
- workflowStepCount,
133
- workflowSupportsSteps
134
- } from "./chunk-H2R232HK.js";
135
- import {
136
- DEFAULT_MAPS_PROXY_MODE,
137
- DEFAULT_PROXY_MODE,
138
- MapsPlaceOptionsSchema,
139
- MapsSearchOptionsSchema,
140
- RawMapsAboutAttributeSchema,
141
- RawMapsHoursRowSchema,
142
- RawMapsOverviewSchema,
143
- RawMapsReviewStatsSchema
144
- } from "./chunk-LFATOGDF.js";
145
- import "./chunk-PVWWY5NV.js";
146
-
147
- // src/api/outbound-sanitize.ts
148
- var KEY_RENAMES = {
149
- kernel: "browserRuntime",
150
- kernel_session_id: "browser_session_id",
151
- kernel_delete_started: "session_cleanup_started",
152
- kernel_delete_succeeded: "session_cleanup_succeeded",
153
- kernel_delete_error: "session_cleanup_error",
154
- kernelSessionId: "browserSessionId",
155
- kernelDeleteStarted: "sessionCleanupStarted",
156
- kernelDeleteSucceeded: "sessionCleanupSucceeded",
157
- kernelDeleteError: "sessionCleanupError",
158
- kernelProxyId: "proxyId"
159
- };
160
- var SANITIZED_VALUE_KEYS = /error|message/i;
161
- function sanitizeOutboundDiagnostics(value, parentKey = "") {
162
- if (typeof value === "string") {
163
- if (SANITIZED_VALUE_KEYS.test(parentKey) && /kernel/i.test(value)) {
164
- return sanitizeVendorName(value);
165
- }
166
- return value;
167
- }
168
- if (Array.isArray(value)) return value.map((v) => sanitizeOutboundDiagnostics(v, parentKey));
169
- if (value !== null && typeof value === "object") {
170
- const out = {};
171
- for (const [key, val] of Object.entries(value)) {
172
- const renamed = KEY_RENAMES[key] ?? key;
173
- out[renamed] = sanitizeOutboundDiagnostics(val, key);
174
- }
175
- return out;
176
- }
177
- return value;
178
- }
179
- function sanitizeAttempts(attempts) {
180
- return attempts.map((a) => sanitizeOutboundDiagnostics(a));
181
- }
182
- function sanitizeHarvestResult(result) {
183
- const diagnostics = result?.diagnostics;
184
- if (!diagnostics?.debug) return result;
185
- return {
186
- ...result,
187
- diagnostics: {
188
- ...diagnostics,
189
- debug: sanitizeOutboundDiagnostics(diagnostics.debug)
190
- }
191
- };
192
- }
193
144
 
194
145
  // src/blog/registry.ts
195
146
  var posts = [
@@ -7586,11 +7537,6 @@ async function settleExtractJob(jobId, userId, refundMc, netChargeMc, reference)
7586
7537
 
7587
7538
  // src/inngest/functions/site-extract.ts
7588
7539
  var BATCH = 30;
7589
- function chunk(items, size) {
7590
- const out = [];
7591
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
7592
- return out;
7593
- }
7594
7540
  function buildArtifacts(job, pages, branding, imageAudit) {
7595
7541
  const { edges, metrics } = buildLinkGraph(pages, job.startUrl);
7596
7542
  const issues = computeIssues(pages, metrics);
@@ -7649,18 +7595,62 @@ var siteExtractFn = inngest.createFunction(
7649
7595
  const maxPages = Number(job.options.maxPages ?? 1e4);
7650
7596
  const concurrency = Number(job.options.concurrency ?? 1);
7651
7597
  const urlsPerBrowser = Number(job.options.urlsPerBrowser ?? job.options.rotateProxyEvery ?? 10);
7652
- const urls = await step.run("discover", async () => {
7598
+ const seed = await step.run("discover", async () => {
7653
7599
  const discovered = await discoverUrls(job.startUrl, maxPages, key);
7654
- await setExtractJobTotal(jobId, discovered.length);
7655
- return discovered;
7600
+ const out = discovered.length ? discovered : [job.startUrl];
7601
+ await setExtractJobTotal(jobId, out.length);
7602
+ return out;
7656
7603
  });
7657
- const batches = chunk(urls, BATCH);
7658
- for (let i = 0; i < batches.length; i++) {
7659
- await step.run(`crawl-batch-${i}`, async () => {
7660
- 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 });
7661
7636
  await saveExtractPages(jobId, pages);
7662
- 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)];
7663
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));
7664
7654
  }
7665
7655
  const branding = Array.isArray(job.options.formats) && job.options.formats.includes("branding") ? await step.run("branding", () => extractBranding(job.startUrl, key).catch(() => null)) : null;
7666
7656
  const imageAudit = Array.isArray(job.options.formats) && job.options.formats.includes("images") ? await step.run("image-audit", async () => {
@@ -9343,10 +9333,10 @@ function buildTranscriptMarkdown(result) {
9343
9333
  if (result.chunks?.length) {
9344
9334
  lines.push("## Timestamped Segments");
9345
9335
  lines.push("");
9346
- for (const chunk2 of result.chunks) {
9347
- const start = fmtTs(chunk2.timestamp[0]);
9348
- const end = fmtTs(chunk2.timestamp[1]);
9349
- 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()}`);
9350
9340
  lines.push("");
9351
9341
  }
9352
9342
  }
@@ -9847,20 +9837,20 @@ var FacebookAdExtractor = class {
9847
9837
  if (splitRe.lastIndex === m.index) splitRe.lastIndex++;
9848
9838
  }
9849
9839
  if (last < bodyText.length) adChunks.push(bodyText.slice(last));
9850
- const ads = adChunks.filter((c) => /Library ID[:\s]+\d{10,20}/i.test(c)).slice(0, maxAds).map((chunk2) => {
9851
- 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);
9852
9842
  const libraryId = lidM ? lidM[1] : null;
9853
- const statusM = chunk2.match(/^(Active|Inactive)/i);
9843
+ const statusM = chunk.match(/^(Active|Inactive)/i);
9854
9844
  const status = statusM ? statusM[1] : null;
9855
- 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);
9856
9846
  const started = startM ? startM[1].replace(/\s+/g, " ").trim() : null;
9857
9847
  const visual = adVisuals[libraryId ?? ""] ?? { imageSrc: null, videoSrc: null, videoPoster: null };
9858
- 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);
9859
9849
  const creativeType = visual.videoSrc || hasVideoText ? "video" : visual.imageSrc ? "image" : "unknown";
9860
9850
  let domain = null;
9861
9851
  const domainRe = /\b([A-Z0-9]{2,}(?:\.[A-Z]{2,})+)\b/g;
9862
9852
  let dm;
9863
- while ((dm = domainRe.exec(chunk2)) !== null) {
9853
+ while ((dm = domainRe.exec(chunk)) !== null) {
9864
9854
  const c = dm[1];
9865
9855
  if (c.length >= 6 && c !== "LIBRARY") {
9866
9856
  domain = c;
@@ -9869,9 +9859,9 @@ var FacebookAdExtractor = class {
9869
9859
  }
9870
9860
  let primaryText = null;
9871
9861
  let headline = null;
9872
- const sponsoredIdx = chunk2.search(/\bSponsored\b/i);
9862
+ const sponsoredIdx = chunk.search(/\bSponsored\b/i);
9873
9863
  if (sponsoredIdx >= 0) {
9874
- let afterSponsored = chunk2.slice(sponsoredIdx + "Sponsored".length).trim();
9864
+ let afterSponsored = chunk.slice(sponsoredIdx + "Sponsored".length).trim();
9875
9865
  afterSponsored = afterSponsored.replace(/\s*\d*:?\d+\s*\/\s*\d*:?\d+.*$/, "");
9876
9866
  if (domain) {
9877
9867
  const di = afterSponsored.indexOf(domain);
@@ -9882,18 +9872,18 @@ var FacebookAdExtractor = class {
9882
9872
  }
9883
9873
  const ctaRe2 = new RegExp("\\b(" + CTA_LABELS.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")\\b", "i");
9884
9874
  if (domain) {
9885
- const di = chunk2.indexOf(domain);
9875
+ const di = chunk.indexOf(domain);
9886
9876
  if (di >= 0) {
9887
- let afterDomain = chunk2.slice(di + domain.length).trim();
9877
+ let afterDomain = chunk.slice(di + domain.length).trim();
9888
9878
  const ctaMatch = afterDomain.search(ctaRe2);
9889
9879
  if (ctaMatch >= 0) afterDomain = afterDomain.slice(0, ctaMatch).trim();
9890
9880
  if (afterDomain.length > 3 && afterDomain.length < 120) headline = afterDomain;
9891
9881
  }
9892
9882
  }
9893
9883
  if (!headline) {
9894
- const ctaIdx = chunk2.search(ctaRe2);
9884
+ const ctaIdx = chunk.search(ctaRe2);
9895
9885
  if (ctaIdx > 0) {
9896
- const beforeCta = chunk2.slice(0, ctaIdx).trimEnd();
9886
+ const beforeCta = chunk.slice(0, ctaIdx).trimEnd();
9897
9887
  const sentenceEnd = Math.max(beforeCta.lastIndexOf(". "), beforeCta.lastIndexOf("! "), beforeCta.lastIndexOf("? "));
9898
9888
  if (sentenceEnd >= 0) {
9899
9889
  const candidate = beforeCta.slice(sentenceEnd + 2).trim();
@@ -9905,12 +9895,12 @@ var FacebookAdExtractor = class {
9905
9895
  }
9906
9896
  let cta = null;
9907
9897
  for (const lbl of CTA_LABELS) {
9908
- if (chunk2.toLowerCase().includes(lbl.toLowerCase())) {
9898
+ if (chunk.toLowerCase().includes(lbl.toLowerCase())) {
9909
9899
  cta = lbl;
9910
9900
  break;
9911
9901
  }
9912
9902
  }
9913
- 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);
9914
9904
  const clusterCount = multiM ? parseInt(multiM[1]) : null;
9915
9905
  return {
9916
9906
  libraryId,
@@ -9933,8 +9923,8 @@ var FacebookAdExtractor = class {
9933
9923
  const unknownCreativeCount = ads.filter((a) => a.creativeType === "unknown").length;
9934
9924
  const advertiserPageId = new URL(page.url()).searchParams.get("view_all_page_id") ?? null;
9935
9925
  const nameFreq = /* @__PURE__ */ new Map();
9936
- for (const chunk2 of adChunks) {
9937
- const detailsParts = chunk2.split("See ad details ");
9926
+ for (const chunk of adChunks) {
9927
+ const detailsParts = chunk.split("See ad details ");
9938
9928
  if (detailsParts.length < 2) continue;
9939
9929
  const sponsoredIdx = detailsParts[1].indexOf(" Sponsored");
9940
9930
  if (sponsoredIdx < 0) continue;
@@ -10674,11 +10664,11 @@ facebookAdApp.post("/search", createApiKeyAuth(), async (c) => {
10674
10664
  }
10675
10665
  if (last < bodyText.length) adChunks.push(bodyText.slice(last));
10676
10666
  const advertiserMap = /* @__PURE__ */ new Map();
10677
- for (const chunk2 of adChunks) {
10678
- 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);
10679
10669
  if (!lidM) continue;
10680
10670
  const libraryId = lidM[1];
10681
- const detailsParts = chunk2.split("See ad details ");
10671
+ const detailsParts = chunk.split("See ad details ");
10682
10672
  if (detailsParts.length < 2) continue;
10683
10673
  const afterDetails = detailsParts[1];
10684
10674
  const sponsoredIdx = afterDetails.indexOf(" Sponsored");
@@ -11230,8 +11220,8 @@ function runFfmpegMux(videoPath, audioPath, outPath) {
11230
11220
  stdio: ["ignore", "ignore", "pipe"]
11231
11221
  });
11232
11222
  let stderr = "";
11233
- child.stderr.on("data", (chunk2) => {
11234
- stderr += String(chunk2);
11223
+ child.stderr.on("data", (chunk) => {
11224
+ stderr += String(chunk);
11235
11225
  });
11236
11226
  child.on("error", (err) => resolve({ ok: false, error: err.message }));
11237
11227
  child.on("close", (code) => resolve({ ok: code === 0, error: code === 0 ? null : stderr.trim() || `ffmpeg exited ${code}` }));
@@ -14363,8 +14353,64 @@ async function migrateBrowserAgent() {
14363
14353
  )
14364
14354
  `);
14365
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)`);
14366
14381
  _ready = true;
14367
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
+ }
14368
14414
  async function createSessionRow(input) {
14369
14415
  const db = getDb();
14370
14416
  const id = `bas_${randomUUID2().replace(/-/g, "").slice(0, 20)}`;
@@ -15197,6 +15243,17 @@ async function findProfileConnection(k, profileName, domain) {
15197
15243
  }
15198
15244
  return null;
15199
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
+ }
15200
15257
  async function findOrCreateProfileConnection(k, opts) {
15201
15258
  try {
15202
15259
  return await k.auth.connections.create({
@@ -15590,7 +15647,7 @@ function publicSession(row) {
15590
15647
  session_id: row.id,
15591
15648
  status: row.status,
15592
15649
  label: row.label,
15593
- live_view_url: row.live_view_url,
15650
+ watch_url: `/console/${row.id}`,
15594
15651
  created_at: row.created_at,
15595
15652
  last_action_at: row.last_action_at,
15596
15653
  closed_at: row.closed_at,
@@ -15646,10 +15703,23 @@ function buildBrowserAgentRoutes() {
15646
15703
  const profileName = typeof body.profile === "string" ? body.profile.trim() : "";
15647
15704
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : "chatgpt.com";
15648
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;
15649
15708
  if (!profileName) return c.json({ error: "profile is required" }, 400);
15650
15709
  if (!domain) return c.json({ error: "domain is required" }, 400);
15651
15710
  try {
15652
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
+ }
15653
15723
  return c.json({
15654
15724
  connection_id: result.connectionId,
15655
15725
  profile: result.profileName,
@@ -15658,8 +15728,6 @@ function buildBrowserAgentRoutes() {
15658
15728
  flow_status: result.flowStatus,
15659
15729
  flow_step: result.flowStep,
15660
15730
  flow_expires_at: result.flowExpiresAt,
15661
- hosted_url: result.hostedUrl,
15662
- live_view_url: result.liveViewUrl,
15663
15731
  browser_session_id: result.browserSessionId,
15664
15732
  post_login_url: result.postLoginUrl
15665
15733
  });
@@ -15674,6 +15742,17 @@ function buildBrowserAgentRoutes() {
15674
15742
  const domain = typeof body.domain === "string" ? body.domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "") : void 0;
15675
15743
  try {
15676
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
+ }
15677
15756
  return c.json({
15678
15757
  connection_id: result.connectionId,
15679
15758
  profile: result.profileName,
@@ -15682,8 +15761,6 @@ function buildBrowserAgentRoutes() {
15682
15761
  flow_status: result.flowStatus,
15683
15762
  flow_step: result.flowStep,
15684
15763
  flow_expires_at: result.flowExpiresAt,
15685
- hosted_url: result.hostedUrl,
15686
- live_view_url: result.liveViewUrl,
15687
15764
  browser_session_id: result.browserSessionId,
15688
15765
  post_login_url: result.postLoginUrl
15689
15766
  });
@@ -15691,6 +15768,37 @@ function buildBrowserAgentRoutes() {
15691
15768
  return c.json(failure(err), 404);
15692
15769
  }
15693
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
+ });
15694
15802
  app2.post("/sessions", async (c) => {
15695
15803
  const user = c.get("user");
15696
15804
  if (Number(user.balance_mc ?? 0) < BROWSER_OPEN_MIN_BALANCE_MC) {
@@ -16047,6 +16155,50 @@ function buildBrowserAgentRoutes() {
16047
16155
  }
16048
16156
 
16049
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
+ }
16050
16202
  function renderConsoleHtml(initialSessionId) {
16051
16203
  const initial = JSON.stringify(initialSessionId ?? "");
16052
16204
  return `<!DOCTYPE html>
@@ -17378,7 +17530,7 @@ app.get("/cron/tick", async (c) => {
17378
17530
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
17379
17531
  return c.json({ error: "Unauthorized" }, 401);
17380
17532
  }
17381
- const { drainQueue } = await import("./worker-2WVKKCC7.js");
17533
+ const { drainQueue } = await import("./worker-J6I5XP2F.js");
17382
17534
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
17383
17535
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
17384
17536
  const [results, sweepResult] = await Promise.all([
@@ -17400,6 +17552,19 @@ app.route("/serp-intelligence", serpIntelligenceApp);
17400
17552
  app.route("/mcp", mcpApp);
17401
17553
  app.route("/agent", buildBrowserAgentRoutes());
17402
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
+ });
17403
17568
  app.get("/console/:id", (c) => c.html(renderConsoleHtml(c.req.param("id"))));
17404
17569
  app.route("/stripe", stripeApp);
17405
17570
  if (!process.env.INNGEST_EVENT_KEY) {
@@ -17508,4 +17673,4 @@ app.get("/blog/:slug/", (c) => {
17508
17673
  export {
17509
17674
  app
17510
17675
  };
17511
- //# sourceMappingURL=server-TKWY47JA.js.map
17676
+ //# sourceMappingURL=server-3M3XCVOT.js.map