mcp-scraper 0.3.45 → 0.3.47

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.
@@ -0,0 +1,7 @@
1
+ // src/version.ts
2
+ var PACKAGE_VERSION = "0.3.47";
3
+
4
+ export {
5
+ PACKAGE_VERSION
6
+ };
7
+ //# sourceMappingURL=chunk-NTCAVBHU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = '0.3.47'\n"],"mappings":";AAAO,IAAM,kBAAkB;","names":[]}
@@ -7,7 +7,9 @@ import {
7
7
  computeIssues,
8
8
  configureReportSaving,
9
9
  exportFanout,
10
+ getBlobStore,
10
11
  harvestTimeoutBudget,
12
+ hashOwnerId,
11
13
  outputBaseDir,
12
14
  registerBrowserAgentMcpTools,
13
15
  registerSerpIntelligenceCaptureTools,
@@ -16,7 +18,7 @@ import {
16
18
  renderLinkReport,
17
19
  sanitizeAttempts,
18
20
  sanitizeHarvestResult
19
- } from "./chunk-E7BZFMWB.js";
21
+ } from "./chunk-A6BAV444.js";
20
22
  import {
21
23
  csvRecords,
22
24
  listWorkflowDefinitions,
@@ -29,7 +31,7 @@ import {
29
31
  workflowStepCount,
30
32
  workflowSupportsSteps
31
33
  } from "./chunk-NNEIXK5L.js";
32
- import "./chunk-NVTFLLEG.js";
34
+ import "./chunk-NTCAVBHU.js";
33
35
  import {
34
36
  BROWSER_OPEN_MIN_BALANCE_MC,
35
37
  CONCURRENCY_PRICE_ID,
@@ -52,7 +54,7 @@ import {
52
54
  harvestProblemResponse,
53
55
  insufficientBalanceResponse,
54
56
  serializeHarvestProblem
55
- } from "./chunk-BRVX6RDN.js";
57
+ } from "./chunk-7TFJJAPE.js";
56
58
  import {
57
59
  BrowserDriver,
58
60
  MapsSelectors,
@@ -7420,58 +7422,6 @@ var siteAuditFn = inngest.createFunction(
7420
7422
  }
7421
7423
  );
7422
7424
 
7423
- // src/api/blob-store.ts
7424
- import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
7425
- import { homedir as homedir2 } from "os";
7426
- import { join as join4, dirname as dirname2 } from "path";
7427
- function byteLength(data) {
7428
- return Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data);
7429
- }
7430
- var LocalBlobStore = class {
7431
- constructor(baseDir) {
7432
- this.baseDir = baseDir;
7433
- }
7434
- baseDir;
7435
- kind = "local";
7436
- async put(key, data, contentType = "application/octet-stream") {
7437
- const path5 = join4(this.baseDir, "blobs", key);
7438
- mkdirSync2(dirname2(path5), { recursive: true });
7439
- writeFileSync(path5, data);
7440
- return { key, url: `file://${path5}`, bytes: byteLength(data), contentType };
7441
- }
7442
- };
7443
- function localBaseDir() {
7444
- return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join4(homedir2(), "Downloads", "mcp-scraper");
7445
- }
7446
- var cached = null;
7447
- function getBlobStore() {
7448
- if (cached) return cached;
7449
- if (process.env.BLOB_READ_WRITE_TOKEN) {
7450
- cached = new VercelBlobStore(process.env.BLOB_READ_WRITE_TOKEN);
7451
- } else {
7452
- cached = new LocalBlobStore(localBaseDir());
7453
- }
7454
- return cached;
7455
- }
7456
- var VercelBlobStore = class {
7457
- constructor(token) {
7458
- this.token = token;
7459
- }
7460
- token;
7461
- kind = "vercel-blob";
7462
- async put(key, data, contentType = "application/octet-stream") {
7463
- const { put } = await import("@vercel/blob");
7464
- const body = Buffer.isBuffer(data) ? data : Buffer.from(data);
7465
- const result = await put(key, body, {
7466
- access: "public",
7467
- token: this.token,
7468
- contentType,
7469
- addRandomSuffix: true
7470
- });
7471
- return { key, url: result.url, bytes: byteLength(data), contentType };
7472
- }
7473
- };
7474
-
7475
7425
  // src/api/site-extract-repository.ts
7476
7426
  function rowToJob(r) {
7477
7427
  return {
@@ -11180,9 +11130,9 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
11180
11130
  // src/api/instagram-routes.ts
11181
11131
  import { Hono as Hono7 } from "hono";
11182
11132
  import { z as z15 } from "zod";
11183
- import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
11184
- import { homedir as homedir3 } from "os";
11185
- import { join as join5 } from "path";
11133
+ import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync2, statSync, writeFileSync } from "fs";
11134
+ import { homedir as homedir2 } from "os";
11135
+ import { join as join4 } from "path";
11186
11136
  import { Readable as Readable2 } from "stream";
11187
11137
  import { pipeline as pipeline2 } from "stream/promises";
11188
11138
  import { spawn } from "child_process";
@@ -11609,7 +11559,7 @@ async function resolveInstagramLaunch(body) {
11609
11559
  };
11610
11560
  }
11611
11561
  function outputBaseDir2() {
11612
- return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join5(homedir3(), "Downloads", "mcp-scraper");
11562
+ return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join4(homedir2(), "Downloads", "mcp-scraper");
11613
11563
  }
11614
11564
  function safeFilePart(input) {
11615
11565
  return input.replace(/^https?:\/\//, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "instagram";
@@ -11617,8 +11567,8 @@ function safeFilePart(input) {
11617
11567
  function mediaOutputDir(shortcode, sourceUrl) {
11618
11568
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
11619
11569
  const slug = shortcode ? `instagram-${shortcode}` : safeFilePart(sourceUrl);
11620
- const dir = join5(outputBaseDir2(), "instagram", `${stamp}-${slug}`);
11621
- mkdirSync3(dir, { recursive: true });
11570
+ const dir = join4(outputBaseDir2(), "instagram", `${stamp}-${slug}`);
11571
+ mkdirSync2(dir, { recursive: true });
11622
11572
  return dir;
11623
11573
  }
11624
11574
  async function downloadToFile(url, destPath, referer) {
@@ -11758,8 +11708,8 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11758
11708
  let outputDir = null;
11759
11709
  if (body.downloadMedia) {
11760
11710
  outputDir = mediaOutputDir(extraction.shortcode, sourceUrl.href);
11761
- const textPath = join5(outputDir, `${extraction.shortcode ?? "instagram"}-text.txt`);
11762
- writeFileSync2(textPath, [
11711
+ const textPath = join4(outputDir, `${extraction.shortcode ?? "instagram"}-text.txt`);
11712
+ writeFileSync(textPath, [
11763
11713
  `URL: ${extraction.pageUrl}`,
11764
11714
  extraction.caption ? `Caption: ${extraction.caption}` : "",
11765
11715
  "",
@@ -11767,7 +11717,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11767
11717
  ].filter(Boolean).join("\n"), "utf8");
11768
11718
  downloads.push({ kind: "text", url: null, savedPath: textPath, sizeBytes: statSync(textPath).size, mimeType: "text/plain", error: null });
11769
11719
  if (mediaTypes.has("image") && extraction.imageUrl) {
11770
- const imagePathBase = join5(outputDir, `${extraction.shortcode ?? "instagram"}-image`);
11720
+ const imagePathBase = join4(outputDir, `${extraction.shortcode ?? "instagram"}-image`);
11771
11721
  try {
11772
11722
  const downloaded = await downloadToFile(extraction.imageUrl, imagePathBase, extraction.pageUrl);
11773
11723
  const ext = extFromMime(downloaded.mimeType, "jpg");
@@ -11787,7 +11737,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11787
11737
  if (track.streamType === "video" && !mediaTypes.has("video")) continue;
11788
11738
  if (track.streamType === "audio" && !mediaTypes.has("audio")) continue;
11789
11739
  const kind = track.streamType === "audio" ? "audio" : "video";
11790
- const target = join5(outputDir, trackFilename(track, index, extraction.shortcode));
11740
+ const target = join4(outputDir, trackFilename(track, index, extraction.shortcode));
11791
11741
  try {
11792
11742
  const downloaded = await downloadToFile(track.url, target, extraction.pageUrl);
11793
11743
  downloads.push({ kind, url: track.url, savedPath: downloaded.savedPath, sizeBytes: downloaded.sizeBytes, mimeType: downloaded.mimeType, error: null });
@@ -11797,7 +11747,7 @@ instagramApp.post("/media-download", createApiKeyAuth(), async (c) => {
11797
11747
  }
11798
11748
  }
11799
11749
  if (body.mux && mediaTypes.has("video") && mediaTypes.has("audio") && extraction.selectedVideoTrack && extraction.selectedAudioTrack && savedByUrl.has(extraction.selectedVideoTrack.url) && savedByUrl.has(extraction.selectedAudioTrack.url)) {
11800
- const outPath = join5(outputDir, `${extraction.shortcode ?? "instagram"}-muxed.mp4`);
11750
+ const outPath = join4(outputDir, `${extraction.shortcode ?? "instagram"}-muxed.mp4`);
11801
11751
  const muxed = await runFfmpegMux(savedByUrl.get(extraction.selectedVideoTrack.url), savedByUrl.get(extraction.selectedAudioTrack.url), outPath);
11802
11752
  if (muxed.ok) {
11803
11753
  downloads.push({ kind: "muxed_video", url: null, savedPath: outPath, sizeBytes: statSync(outPath).size, mimeType: "video/mp4", error: null });
@@ -12128,13 +12078,16 @@ var videoApp = new Hono9();
12128
12078
  var AnalyzeBodySchema = z17.object({
12129
12079
  sourceUrl: z17.string().trim().url("sourceUrl must be a direct video file URL"),
12130
12080
  intervalS: z17.number().min(1).max(30).optional(),
12131
- maxFrames: z17.number().int().min(1).max(120).optional(),
12081
+ maxFrames: z17.number().int().min(1).max(480).optional(),
12132
12082
  detail: z17.enum(["fast", "standard", "deep"]).optional(),
12133
12083
  vault: z17.string().trim().min(1).optional()
12134
12084
  });
12135
12085
  function invalidRequest5(message) {
12136
12086
  return { error_code: "invalid_request", message };
12137
12087
  }
12088
+ function tiersFor(frames) {
12089
+ return Math.ceil(Math.min(Math.max(frames, 1), 480) / 120);
12090
+ }
12138
12091
  videoApp.post("/analyze", createApiKeyAuth(), async (c) => {
12139
12092
  const parsed = AnalyzeBodySchema.safeParse(await c.req.json().catch(() => ({})));
12140
12093
  if (!parsed.success) return c.json(invalidRequest5(parsed.error.issues[0]?.message ?? "invalid request"), 400);
@@ -12142,21 +12095,24 @@ videoApp.post("/analyze", createApiKeyAuth(), async (c) => {
12142
12095
  const user = c.get("user");
12143
12096
  const { key: memKey, error: memErr } = await getOrCreateUserMemoryKey(user);
12144
12097
  if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
12145
- const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS, body.sourceUrl);
12146
- if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.video_analysis), 402);
12098
+ const requestedFrames = Math.min(Math.max(body.maxFrames ?? 120, 1), 480);
12099
+ const tiers = tiersFor(requestedFrames);
12100
+ const holdMc = tiers * MC_COSTS.video_analysis;
12101
+ const { ok, balance_mc } = await debitMc(user.id, holdMc, LedgerOperation.VIDEO_ANALYSIS, body.sourceUrl);
12102
+ if (!ok) return c.json(insufficientBalanceResponse(balance_mc, holdMc), 402);
12147
12103
  const started = await memoryCall(
12148
12104
  "videoAnalyzeStartTool",
12149
12105
  {
12150
12106
  sourceUrl: body.sourceUrl,
12151
12107
  intervalS: body.intervalS ?? 2,
12152
- maxFrames: body.maxFrames ?? 120,
12108
+ maxFrames: requestedFrames,
12153
12109
  detail: body.detail ?? "standard",
12154
12110
  vault: body.vault ?? "Library"
12155
12111
  },
12156
12112
  memKey
12157
12113
  );
12158
12114
  if (!started.ok || !started.runId) {
12159
- await creditMc(user.id, MC_COSTS.video_analysis, LedgerOperation.VIDEO_ANALYSIS_REFUND, `start-failed:${body.sourceUrl}`);
12115
+ await creditMc(user.id, holdMc, LedgerOperation.VIDEO_ANALYSIS_REFUND, `start-failed:${body.sourceUrl}`);
12160
12116
  return c.json({ error_code: "video_start_failed", message: started.error ?? "could not start analysis" }, 502);
12161
12117
  }
12162
12118
  return c.json({
@@ -12175,11 +12131,30 @@ videoApp.post("/status", createApiKeyAuth(), async (c) => {
12175
12131
  if (!memKey) return c.json({ error_code: "memory_unavailable", message: memErr ?? "memory unavailable" }, 502);
12176
12132
  const st = await memoryCall("videoAnalyzeStatusTool", { runId }, memKey);
12177
12133
  if (!st.ok) return c.json({ error_code: "not_found", message: st.error ?? "run not found" }, 404);
12134
+ const requestedTiers = tiersFor(st.maxFrames ?? 120);
12135
+ let reconciliation = null;
12178
12136
  if (st.status === "failed") {
12137
+ const refundMc = requestedTiers * MC_COSTS.video_analysis;
12179
12138
  const refundOp = `${LedgerOperation.VIDEO_ANALYSIS_REFUND}:${runId}`;
12180
12139
  if (!await ledgerExistsForOperation(user.id, refundOp)) {
12181
- await creditMc(user.id, MC_COSTS.video_analysis, refundOp, `run-failed:${runId}`);
12140
+ await creditMc(user.id, refundMc, refundOp, `run-failed:${runId}`);
12182
12141
  }
12142
+ reconciliation = { billedMc: requestedTiers * MC_COSTS.video_analysis, refundedMc: refundMc, effectiveFrames: null };
12143
+ }
12144
+ if (st.status === "done") {
12145
+ const effectiveTiers = tiersFor(st.frameCount ?? 120);
12146
+ const diff = requestedTiers - effectiveTiers;
12147
+ if (diff > 0) {
12148
+ const adjustOp = `${LedgerOperation.VIDEO_ANALYSIS_ADJUST}:${runId}`;
12149
+ if (!await ledgerExistsForOperation(user.id, adjustOp)) {
12150
+ await creditMc(user.id, diff * MC_COSTS.video_analysis, adjustOp, `tier-reconcile:${runId}`);
12151
+ }
12152
+ }
12153
+ reconciliation = {
12154
+ billedMc: requestedTiers * MC_COSTS.video_analysis,
12155
+ refundedMc: Math.max(diff, 0) * MC_COSTS.video_analysis,
12156
+ effectiveFrames: st.frameCount ?? null
12157
+ };
12183
12158
  }
12184
12159
  return c.json({
12185
12160
  ok: true,
@@ -12188,7 +12163,8 @@ videoApp.post("/status", createApiKeyAuth(), async (c) => {
12188
12163
  progress: st.progress ?? null,
12189
12164
  frameCount: st.frameCount ?? null,
12190
12165
  ...st.status === "done" ? { artifactPath: st.artifactPath ?? null, report: st.report ?? null } : {},
12191
- ...st.error ? { error: st.error } : {}
12166
+ ...st.error ? { error: st.error } : {},
12167
+ ...reconciliation ? { reconciliation } : {}
12192
12168
  });
12193
12169
  });
12194
12170
 
@@ -13136,7 +13112,7 @@ import { Hono as Hono11 } from "hono";
13136
13112
 
13137
13113
  // src/directory/directory-workflow.ts
13138
13114
  import { mkdir as mkdir2, writeFile } from "fs/promises";
13139
- import { join as join6 } from "path";
13115
+ import { join as join5 } from "path";
13140
13116
  import { z as z19 } from "zod";
13141
13117
 
13142
13118
  // src/directory/location-db.ts
@@ -13472,11 +13448,11 @@ function csvRowsFor(result) {
13472
13448
  return rows;
13473
13449
  }
13474
13450
  async function saveDirectoryCsv(result) {
13475
- const outDir = join6(outputBaseDir(), "directory-workflows");
13451
+ const outDir = join5(outputBaseDir(), "directory-workflows");
13476
13452
  await mkdir2(outDir, { recursive: true });
13477
13453
  const stamp = result.extractedAt.replace(/[:.]/g, "-");
13478
13454
  const slug = `${result.state}-${result.query}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
13479
- const path5 = join6(outDir, `${stamp}-${slug}-directory-workflow.csv`);
13455
+ const path5 = join5(outDir, `${stamp}-${slug}-directory-workflow.csv`);
13480
13456
  const headers = [
13481
13457
  "source_query",
13482
13458
  "source_location",
@@ -15117,9 +15093,9 @@ mcpApp.all("/", async (c) => {
15117
15093
  enableJsonResponse: true
15118
15094
  });
15119
15095
  const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
15120
- const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
15096
+ const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false, ownerId: hashOwnerId(callerKey) });
15121
15097
  registerSerpIntelligenceCaptureTools(server, executor);
15122
- registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
15098
+ registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl, savesReportsLocally: false });
15123
15099
  await server.connect(transport);
15124
15100
  return transport.handleRequest(c.req.raw);
15125
15101
  } catch {
@@ -18876,8 +18852,8 @@ async function persistScrapeBody(user, opts) {
18876
18852
 
18877
18853
  // src/api/scrape-blob-cleanup.ts
18878
18854
  import { readdir, stat, unlink } from "fs/promises";
18879
- import { homedir as homedir4 } from "os";
18880
- import { join as join7 } from "path";
18855
+ import { homedir as homedir3 } from "os";
18856
+ import { join as join6 } from "path";
18881
18857
  async function cleanupVercel(token, cutoff) {
18882
18858
  const { list, del } = await import("@vercel/blob");
18883
18859
  let cursor;
@@ -18894,8 +18870,8 @@ async function cleanupVercel(token, cutoff) {
18894
18870
  return { deleted, store: "vercel-blob" };
18895
18871
  }
18896
18872
  async function cleanupLocal(cutoff) {
18897
- const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join7(homedir4(), "Downloads", "mcp-scraper");
18898
- const dir = join7(baseDir, "blobs", SCRAPE_FALLBACK_PREFIX.replace(/\/$/, ""));
18873
+ const baseDir = process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join6(homedir3(), "Downloads", "mcp-scraper");
18874
+ const dir = join6(baseDir, "blobs", SCRAPE_FALLBACK_PREFIX.replace(/\/$/, ""));
18899
18875
  let deleted = 0;
18900
18876
  let entries;
18901
18877
  try {
@@ -18904,7 +18880,7 @@ async function cleanupLocal(cutoff) {
18904
18880
  return { deleted: 0, store: "local" };
18905
18881
  }
18906
18882
  for (const name of entries) {
18907
- const path5 = join7(dir, name);
18883
+ const path5 = join6(dir, name);
18908
18884
  try {
18909
18885
  const s = await stat(path5);
18910
18886
  if (s.isFile() && s.mtimeMs < cutoff) {
@@ -20572,7 +20548,7 @@ app.get("/cron/tick", async (c) => {
20572
20548
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
20573
20549
  return c.json({ error: "Unauthorized" }, 401);
20574
20550
  }
20575
- const { drainQueue } = await import("./worker-4CVMSUZR.js");
20551
+ const { drainQueue } = await import("./worker-WY7C45MP.js");
20576
20552
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
20577
20553
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
20578
20554
  const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
@@ -20725,4 +20701,4 @@ app.get("/blog/:slug/", (c) => {
20725
20701
  export {
20726
20702
  app
20727
20703
  };
20728
- //# sourceMappingURL=server-MKUN6NQZ.js.map
20704
+ //# sourceMappingURL=server-H6GAAEQ6.js.map