mcp-scraper 0.4.13 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/bin/api-server.cjs +63 -157
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +2 -2
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +269 -3
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +3 -2
  13. package/dist/bin/mcp-stdio-server.js.map +1 -1
  14. package/dist/chunk-3RTPEQJC.js +7 -0
  15. package/dist/chunk-3RTPEQJC.js.map +1 -0
  16. package/dist/{chunk-SIPXEFTR.js → chunk-APJO2XV5.js} +6 -6
  17. package/dist/chunk-APJO2XV5.js.map +1 -0
  18. package/dist/{chunk-VQV7MB2S.js → chunk-BL7BBSYF.js} +2 -2
  19. package/dist/{chunk-EA2AANFI.js → chunk-J4T5OSCF.js} +7 -4
  20. package/dist/chunk-J4T5OSCF.js.map +1 -0
  21. package/dist/{server-HNZG36IP.js → server-Z4MEISH5.js} +59 -154
  22. package/dist/server-Z4MEISH5.js.map +1 -0
  23. package/dist/{site-extract-repository-6X2C7RLF.js → site-extract-repository-U476J44K.js} +3 -3
  24. package/dist/{worker-WCSKUAOT.js → worker-KJZ3ZN2N.js} +2 -2
  25. package/package.json +1 -1
  26. package/dist/chunk-EA2AANFI.js.map +0 -1
  27. package/dist/chunk-O2D5HGKF.js +0 -7
  28. package/dist/chunk-O2D5HGKF.js.map +0 -1
  29. package/dist/chunk-SIPXEFTR.js.map +0 -1
  30. package/dist/server-HNZG36IP.js.map +0 -1
  31. /package/dist/{chunk-VQV7MB2S.js.map → chunk-BL7BBSYF.js.map} +0 -0
  32. /package/dist/{site-extract-repository-6X2C7RLF.js.map → site-extract-repository-U476J44K.js.map} +0 -0
  33. /package/dist/{worker-WCSKUAOT.js.map → worker-KJZ3ZN2N.js.map} +0 -0
@@ -33,7 +33,7 @@ import {
33
33
  registerSerpIntelligenceCaptureTools,
34
34
  sanitizeAttempts,
35
35
  sanitizeHarvestResult
36
- } from "./chunk-EA2AANFI.js";
36
+ } from "./chunk-J4T5OSCF.js";
37
37
  import {
38
38
  auditImages,
39
39
  getBlobStore
@@ -67,7 +67,7 @@ import {
67
67
  RawMapsOverviewSchema,
68
68
  RawMapsReviewStatsSchema
69
69
  } from "./chunk-XGIPATLV.js";
70
- import "./chunk-O2D5HGKF.js";
70
+ import "./chunk-3RTPEQJC.js";
71
71
  import {
72
72
  completeExtractJob,
73
73
  countSuccessfulPages,
@@ -78,7 +78,7 @@ import {
78
78
  saveExtractPages,
79
79
  setExtractJobTotal,
80
80
  settleExtractJob
81
- } from "./chunk-VQV7MB2S.js";
81
+ } from "./chunk-BL7BBSYF.js";
82
82
  import {
83
83
  BROWSER_OPEN_MIN_BALANCE_MC,
84
84
  CONCURRENCY_PRICE_ID,
@@ -101,7 +101,7 @@ import {
101
101
  browserActiveCostMc,
102
102
  concurrencySlotBillingInfo,
103
103
  insufficientBalanceResponse
104
- } from "./chunk-SIPXEFTR.js";
104
+ } from "./chunk-APJO2XV5.js";
105
105
  import {
106
106
  CaptchaError,
107
107
  RECAPTCHA_INSTRUCTIONS,
@@ -8788,7 +8788,6 @@ async function transcribeMediaUrl(mediaUrl, markdownTitle = "# Media Transcript"
8788
8788
  }
8789
8789
 
8790
8790
  // src/youtube/CaptionFetcher.ts
8791
- import { fal as fal2 } from "@fal-ai/client";
8792
8791
  async function fetchViaYoutubeTranscript(videoId) {
8793
8792
  try {
8794
8793
  const { YoutubeTranscript } = await import("youtube-transcript");
@@ -8861,13 +8860,12 @@ var INNERTUBE_CLIENTS = [
8861
8860
  { name: "IOS", version: "20.10.4", userAgent: "com.google.ios.youtube/20.10.4 (iPhone16,2; U; CPU iOS 18_3_2 like Mac OS X;)" },
8862
8861
  { name: "ANDROID_VR", version: "1.62.27", userAgent: "com.google.android.apps.youtube.vr.oculus/1.62.27 (Linux; U; Android 12L) gzip" }
8863
8862
  ];
8864
- function isBotBlockStatus(status) {
8865
- return status === "LOGIN_REQUIRED" || status === "AGE_VERIFICATION_REQUIRED";
8866
- }
8867
8863
  async function probePlayer(page, videoId) {
8868
8864
  return page.evaluate(
8869
8865
  async ({ vid, clients }) => {
8870
8866
  let lastStatus = null;
8867
+ let sawBotBlock = false;
8868
+ let sawAccessible = false;
8871
8869
  for (const client2 of clients) {
8872
8870
  const resp = await fetch("/youtubei/v1/player?prettyPrint=false", {
8873
8871
  method: "POST",
@@ -8883,98 +8881,24 @@ async function probePlayer(page, videoId) {
8883
8881
  }
8884
8882
  const data = await resp.json();
8885
8883
  lastStatus = data?.playabilityStatus?.status ?? null;
8886
- const audioFormats = (data?.streamingData?.adaptiveFormats ?? []).filter((f) => typeof f.url === "string" && f.url && typeof f.mimeType === "string" && f.mimeType.startsWith("audio/")).sort((a, b) => (Number(a.contentLength) || Infinity) - (Number(b.contentLength) || Infinity));
8887
- const pick = audioFormats[0] ?? null;
8888
- let captions = null;
8884
+ if (lastStatus === "LOGIN_REQUIRED" || lastStatus === "AGE_VERIFICATION_REQUIRED") sawBotBlock = true;
8885
+ else if (lastStatus === "OK") sawAccessible = true;
8889
8886
  const tracks = data?.captions?.playerCaptionsTracklistRenderer?.captionTracks ?? [];
8890
8887
  if (tracks.length > 0) {
8891
8888
  const track = tracks.find((t) => t.languageCode === "en") ?? tracks[0];
8892
8889
  const sep = track.baseUrl.includes("?") ? "&" : "?";
8893
8890
  const xmlResp = await fetch(`${track.baseUrl}${sep}fmt=json3`).catch(() => null);
8894
- if (xmlResp?.ok) captions = await xmlResp.text();
8891
+ if (xmlResp?.ok) {
8892
+ const captions = await xmlResp.text();
8893
+ if (captions) return { captions, status: lastStatus, escalate: false };
8894
+ }
8895
8895
  }
8896
- if (!captions && !pick) continue;
8897
- return {
8898
- captions,
8899
- status: lastStatus,
8900
- audio: pick ? {
8901
- url: pick.url,
8902
- mimeType: pick.mimeType,
8903
- bitrate: pick.bitrate ?? null,
8904
- contentLength: pick.contentLength ? Number(pick.contentLength) || null : null,
8905
- approxDurationMs: pick.approxDurationMs ? Number(pick.approxDurationMs) || null : null
8906
- } : null
8907
- };
8908
8896
  }
8909
- return { captions: null, audio: null, status: lastStatus };
8897
+ return { captions: null, status: lastStatus, escalate: sawBotBlock && !sawAccessible };
8910
8898
  },
8911
8899
  { vid: videoId, clients: INNERTUBE_CLIENTS }
8912
8900
  ).catch(() => null);
8913
8901
  }
8914
- var MAX_AUDIO_BYTES = 96 * 1024 * 1024;
8915
- async function downloadWholeAudioStreamInOneRequestBecauseYouTubeAllowsOnlyOnePerIp(page, audio) {
8916
- if (audio.contentLength && audio.contentLength > MAX_AUDIO_BYTES) {
8917
- console.warn(`[CaptionFetcher] audio stream ${audio.contentLength} bytes exceeds ${MAX_AUDIO_BYTES} byte cap`);
8918
- return null;
8919
- }
8920
- const sep = audio.url.includes("?") ? "&" : "?";
8921
- const url = audio.contentLength ? `${audio.url}${sep}range=0-${audio.contentLength - 1}` : audio.url;
8922
- const result = await page.evaluate(async (u) => {
8923
- try {
8924
- const resp = await fetch(u);
8925
- if (!resp.ok) return { ok: false, error: `http_${resp.status}` };
8926
- const blob = await resp.blob();
8927
- const dataUrl = await new Promise((resolve, reject) => {
8928
- const reader = new FileReader();
8929
- reader.onload = () => resolve(reader.result);
8930
- reader.onerror = () => reject(reader.error);
8931
- reader.readAsDataURL(blob);
8932
- });
8933
- return { ok: true, base64: dataUrl.slice(dataUrl.indexOf(",") + 1) };
8934
- } catch (e) {
8935
- return { ok: false, error: String(e) };
8936
- }
8937
- }, url).catch((e) => ({ ok: false, error: String(e) }));
8938
- if (!result.ok || !result.base64) {
8939
- console.warn(`[CaptionFetcher] audio fetch failed (${result.error ?? "unknown"}) for a ${audio.contentLength ?? 0} byte stream`);
8940
- return null;
8941
- }
8942
- const bytes = Buffer.from(result.base64, "base64");
8943
- if (audio.contentLength && bytes.length < audio.contentLength) {
8944
- console.warn(`[CaptionFetcher] partial audio: ${bytes.length} of ${audio.contentLength} bytes`);
8945
- return null;
8946
- }
8947
- return { bytes, mimeType: audio.mimeType.split(";")[0].trim() };
8948
- }
8949
- async function transcribeWithWizper(videoId, audio, startMs) {
8950
- const falKey = process.env.FAL_KEY;
8951
- if (!falKey) return null;
8952
- try {
8953
- fal2.config({ credentials: falKey });
8954
- const ext = audio.mimeType.includes("webm") ? "webm" : audio.mimeType.includes("mp4") ? "m4a" : "audio";
8955
- const audioFile = new File([new Uint8Array(audio.bytes)], `audio.${ext}`, { type: audio.mimeType });
8956
- const uploadedUrl = await fal2.storage.upload(audioFile);
8957
- const subscribeStartMs = Date.now();
8958
- const result = await fal2.subscribe("fal-ai/wizper", {
8959
- input: { audio_url: uploadedUrl, task: "transcribe", language: "en", chunk_level: "segment" },
8960
- logs: false,
8961
- pollInterval: 3e3
8962
- });
8963
- const subscribeDurationMs = Date.now() - subscribeStartMs;
8964
- const data = result.data;
8965
- const chunks = (data.chunks ?? []).map((c) => ({
8966
- timestamp: c.timestamp,
8967
- text: c.text.trim()
8968
- }));
8969
- void recordVendorUsage({ vendor: "fal_wizper", model: "fal-ai/wizper", units: subscribeDurationMs / 1e3, unitType: "compute_sec" });
8970
- const text = data.text ?? chunks.map((c) => c.text).join(" ");
8971
- if (!text) return null;
8972
- return { videoId, text, chunks, durationMs: Date.now() - startMs, method: "browser-whisper" };
8973
- } catch (err) {
8974
- console.warn(`[CaptionFetcher] wizper transcription failed for ${videoId}: ${err instanceof Error ? err.message : String(err)}`);
8975
- return null;
8976
- }
8977
- }
8978
8902
  var YT_RESIDENTIAL_LOCATION = process.env.YOUTUBE_PROXY_LOCATION ?? "Austin, Texas";
8979
8903
  async function resolveResidentialProxy(kernelApiKey) {
8980
8904
  try {
@@ -8991,14 +8915,13 @@ async function resolveResidentialProxy(kernelApiKey) {
8991
8915
  return {};
8992
8916
  }
8993
8917
  }
8994
- async function probeAndExtract(videoId, opts) {
8918
+ async function probeCaptions(videoId, kernelProxyIdOverride) {
8995
8919
  const kernelApiKey = browserServiceApiKey();
8996
8920
  if (!kernelApiKey) return null;
8997
- const kernelProxyId = opts?.kernelProxyId ?? browserServiceProxyId();
8921
+ const kernelProxyId = kernelProxyIdOverride ?? browserServiceProxyId();
8998
8922
  const driver = new BrowserDriver();
8999
8923
  return runWithCostContext({ ...currentCostContext(), op: "yt_transcription", subOp: "browser-innertube" }, async () => {
9000
8924
  let entries = [];
9001
- let audio = null;
9002
8925
  let retryWithFreshIp = false;
9003
8926
  try {
9004
8927
  await driver.launch({ kernelApiKey, kernelProxyId, headless: true, headlessMode: "headless", viewport: { width: 1280, height: 800 }, locale: "en-US" });
@@ -9010,69 +8933,51 @@ async function probeAndExtract(videoId, opts) {
9010
8933
  );
9011
8934
  const probe = await probePlayer(page, videoId);
9012
8935
  if (!probe) return null;
9013
- if (probe.captions && !opts?.skipCaptions) {
8936
+ if (probe.captions) {
9014
8937
  entries = parseTimedtextJson3(probe.captions);
9015
8938
  if (entries.length === 0) entries = parseTimedtextXml(probe.captions);
9016
8939
  }
9017
8940
  if (entries.length === 0) {
9018
- if (!probe.audio) {
9019
- retryWithFreshIp = isBotBlockStatus(probe.status);
9020
- console.warn(`[CaptionFetcher] no captions and no audio stream for ${videoId} (playability=${probe.status ?? "unknown"}, proxied=${Boolean(kernelProxyId)})`);
9021
- return { entries, audio: null, retryWithFreshIp };
9022
- }
9023
- audio = await downloadWholeAudioStreamInOneRequestBecauseYouTubeAllowsOnlyOnePerIp(page, probe.audio);
9024
- if (!audio) return { entries, audio: null, retryWithFreshIp: true };
8941
+ retryWithFreshIp = probe.escalate;
8942
+ console.warn(`[CaptionFetcher] no caption track for ${videoId} (playability=${probe.status ?? "unknown"}, proxied=${Boolean(kernelProxyId)}, escalate=${probe.escalate})`);
9025
8943
  }
9026
8944
  } catch (err) {
9027
8945
  console.warn(`[CaptionFetcher] kernel session failed for ${videoId}: ${err instanceof Error ? err.message : String(err)}`);
9028
8946
  return null;
9029
8947
  } finally {
9030
- const servedSubOp = audio ? "browser-whisper" : "browser-innertube";
9031
- await runWithCostContext({ ...currentCostContext(), op: "yt_transcription", subOp: servedSubOp }, () => driver.close());
8948
+ await driver.close();
9032
8949
  }
9033
- return { entries, audio, retryWithFreshIp };
8950
+ return { entries, retryWithFreshIp };
9034
8951
  });
9035
8952
  }
9036
- async function extractWithProxyEscalation(videoId, opts) {
9037
- const direct = await probeAndExtract(videoId, opts);
9038
- if (direct && (direct.entries.length > 0 || direct.audio)) return direct;
9039
- const shouldEscalate = direct === null || direct.retryWithFreshIp;
9040
- if (!shouldEscalate) return direct;
9041
- const kernelApiKey = browserServiceApiKey();
9042
- if (!kernelApiKey) return direct;
9043
- const { kernelProxyId, disposableProxyId } = await resolveResidentialProxy(kernelApiKey);
9044
- if (!kernelProxyId) return direct;
9045
- try {
9046
- console.warn(`[CaptionFetcher] ${videoId}: retrying through residential proxy after bot block`);
9047
- const viaProxy = await probeAndExtract(videoId, { ...opts, kernelProxyId });
9048
- return viaProxy ?? direct;
9049
- } finally {
9050
- if (disposableProxyId) {
9051
- await deleteKernelProxyId(kernelApiKey, disposableProxyId).catch(
9052
- (err) => console.warn(`[CaptionFetcher] failed to delete disposable proxy: ${err instanceof Error ? err.message : String(err)}`)
9053
- );
9054
- }
9055
- }
9056
- }
9057
8953
  async function fetchViaKernelSession(videoId) {
9058
8954
  const start = Date.now();
9059
- const outcome = await extractWithProxyEscalation(videoId);
9060
- if (!outcome) return null;
9061
- if (outcome.entries.length > 0) {
9062
- const chunks = outcome.entries.map((e) => ({
9063
- timestamp: [e.start / 1e3, (e.start + e.dur) / 1e3],
9064
- text: e.text
9065
- }));
9066
- const text = chunks.map((c) => c.text).join(" ");
9067
- return { videoId, text, chunks, durationMs: Date.now() - start, method: "browser-innertube" };
9068
- }
9069
- if (outcome.audio) {
9070
- return runWithCostContext(
9071
- { ...currentCostContext(), op: "yt_transcription", subOp: "browser-whisper" },
9072
- () => transcribeWithWizper(videoId, outcome.audio, start)
9073
- );
8955
+ let outcome = await probeCaptions(videoId);
8956
+ if (!outcome || outcome.entries.length === 0 && outcome.retryWithFreshIp) {
8957
+ const kernelApiKey = browserServiceApiKey();
8958
+ if (kernelApiKey) {
8959
+ const { kernelProxyId, disposableProxyId } = await resolveResidentialProxy(kernelApiKey);
8960
+ if (kernelProxyId) {
8961
+ try {
8962
+ console.warn(`[CaptionFetcher] ${videoId}: retrying captions through residential proxy after bot block`);
8963
+ outcome = await probeCaptions(videoId, kernelProxyId) ?? outcome;
8964
+ } finally {
8965
+ if (disposableProxyId) {
8966
+ await deleteKernelProxyId(kernelApiKey, disposableProxyId).catch(
8967
+ (err) => console.warn(`[CaptionFetcher] failed to delete disposable proxy: ${err instanceof Error ? err.message : String(err)}`)
8968
+ );
8969
+ }
8970
+ }
8971
+ }
8972
+ }
9074
8973
  }
9075
- return null;
8974
+ if (!outcome || outcome.entries.length === 0) return null;
8975
+ const chunks = outcome.entries.map((e) => ({
8976
+ timestamp: [e.start / 1e3, (e.start + e.dur) / 1e3],
8977
+ text: e.text
8978
+ }));
8979
+ const text = chunks.map((c) => c.text).join(" ");
8980
+ return { videoId, text, chunks, durationMs: Date.now() - start, method: "browser-innertube" };
9076
8981
  }
9077
8982
  async function fetchViaResolvedAudio(videoId) {
9078
8983
  if (!mediaResolverConfigured()) return null;
@@ -10485,7 +10390,7 @@ async function extractFacebookOrganicVideoFromPage(page, sourceUrl, quality = "b
10485
10390
  }
10486
10391
 
10487
10392
  // src/api/facebook-ad-routes.ts
10488
- import { fal as fal3 } from "@fal-ai/client";
10393
+ import { fal as fal2 } from "@fal-ai/client";
10489
10394
  var FacebookAdBodySchema = z13.object({
10490
10395
  url: z13.string().trim().optional(),
10491
10396
  libraryId: z13.string().trim().optional(),
@@ -10665,7 +10570,7 @@ facebookAdApp.post("/transcribe", createApiKeyAuth(), async (c) => {
10665
10570
  metadata: { videoUrl }
10666
10571
  });
10667
10572
  if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
10668
- fal3.config({ credentials: process.env.FAL_KEY });
10573
+ fal2.config({ credentials: process.env.FAL_KEY });
10669
10574
  const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
10670
10575
  let debited = false;
10671
10576
  try {
@@ -10709,7 +10614,7 @@ facebookAdApp.post("/video-transcribe", createApiKeyAuth(), async (c) => {
10709
10614
  metadata: { url: sourceUrl.href }
10710
10615
  });
10711
10616
  if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
10712
- fal3.config({ credentials: process.env.FAL_KEY });
10617
+ fal2.config({ credentials: process.env.FAL_KEY });
10713
10618
  const driver = new BrowserDriver();
10714
10619
  const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
10715
10620
  let debited = false;
@@ -11062,7 +10967,7 @@ var GoogleAdsExtractor = class {
11062
10967
  };
11063
10968
 
11064
10969
  // src/api/google-ads-routes.ts
11065
- import { fal as fal4 } from "@fal-ai/client";
10970
+ import { fal as fal3 } from "@fal-ai/client";
11066
10971
  var GoogleAdsSearchBodySchema = z14.object({
11067
10972
  query: z14.string().trim().min(1, "query is required"),
11068
10973
  region: z14.string().trim().toUpperCase().length(2).optional(),
@@ -11192,7 +11097,7 @@ googleAdsApp.post("/transcribe", createApiKeyAuth(), async (c) => {
11192
11097
  const user = c.get("user");
11193
11098
  const gate = await acquireConcurrencyGate(user, "google_ads_transcribe", { reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"), metadata: { videoUrl } });
11194
11099
  if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
11195
- fal4.config({ credentials: process.env.FAL_KEY });
11100
+ fal3.config({ credentials: process.env.FAL_KEY });
11196
11101
  const holdMc = MEDIA_TRANSCRIBE_HOLD_MC;
11197
11102
  let debited = false;
11198
11103
  try {
@@ -15532,7 +15437,7 @@ serpIntelligenceApp.post("/capture", async (c) => {
15532
15437
  kernelApiKey: browserServiceApiKey(),
15533
15438
  kernelProxyId: browserServiceProxyId(),
15534
15439
  signal: c.req.raw.signal,
15535
- billing: { creditsUsed: cost / 1e3 }
15440
+ billing: { creditsUsed: cost / MC_PER_CREDIT }
15536
15441
  });
15537
15442
  await logRequestEvent({
15538
15443
  userId: user.id,
@@ -20692,7 +20597,7 @@ app.get("/me", async (c) => {
20692
20597
  api_key: user.key_active ? user.api_key : null,
20693
20598
  created_at: user.created_at,
20694
20599
  balance_mc: user.balance_mc,
20695
- balance_credits: user.balance_mc / 1e3,
20600
+ balance_credits: user.balance_mc / MC_PER_CREDIT,
20696
20601
  extra_concurrency_slots: user.extra_concurrency_slots,
20697
20602
  concurrency_limit: concurrencyLimitForUser(user),
20698
20603
  has_concurrency_sub: !!user.concurrency_stripe_sub_id,
@@ -21854,7 +21759,7 @@ app.post("/billing/subscribe/terminal-checkout", auth2, async (c) => {
21854
21759
  tier: tier.tier,
21855
21760
  label: tier.label,
21856
21761
  monthly_usd: tier.monthly_usd,
21857
- credits_per_month: tier.credits_mc / 1e3,
21762
+ credits_per_month: tier.credits_mc / MC_PER_CREDIT,
21858
21763
  concurrency: tier.concurrency,
21859
21764
  intro: tier.intro_coupon ? "$1 first month" : null,
21860
21765
  next_step: "Open checkout_url in a browser to complete payment."
@@ -21884,7 +21789,7 @@ app.get("/billing/balance", auth2, async (c) => {
21884
21789
  const freeCredits = await getFreeCreditBreakdown(user.id);
21885
21790
  return c.json({
21886
21791
  balance_mc: balanceMc,
21887
- balance_credits: balanceMc / 1e3,
21792
+ balance_credits: balanceMc / MC_PER_CREDIT,
21888
21793
  free_credits: freeCredits,
21889
21794
  ledger
21890
21795
  });
@@ -21906,7 +21811,7 @@ app.post("/billing/credits", auth2, async (c) => {
21906
21811
  })) : void 0;
21907
21812
  return c.json({
21908
21813
  balance_mc: balanceMc,
21909
- balance_credits: balanceMc / 1e3,
21814
+ balance_credits: balanceMc / MC_PER_CREDIT,
21910
21815
  item: body.item ?? null,
21911
21816
  matched_cost: matchedCost ? (({ aliases, ...cost }) => cost)(matchedCost) : null,
21912
21817
  costs,
@@ -21924,7 +21829,7 @@ app.get("/cron/tick", async (c) => {
21924
21829
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
21925
21830
  return c.json({ error: "Unauthorized" }, 401);
21926
21831
  }
21927
- const { drainQueue } = await import("./worker-WCSKUAOT.js");
21832
+ const { drainQueue } = await import("./worker-KJZ3ZN2N.js");
21928
21833
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
21929
21834
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
21930
21835
  const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
@@ -21942,7 +21847,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
21942
21847
  return c.json({ error: "Unauthorized" }, 401);
21943
21848
  }
21944
21849
  const jobId = c.req.param("id");
21945
- const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-6X2C7RLF.js");
21850
+ const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-U476J44K.js");
21946
21851
  const { assembleExtractArtifacts } = await import("./extract-bundle-COS56ZDO.js");
21947
21852
  const job = await getExtractJob2(jobId);
21948
21853
  if (!job) return c.json({ error: "job not found" }, 404);
@@ -21950,7 +21855,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
21950
21855
  await completeExtractJob2(jobId, stored);
21951
21856
  let settlement = "already_settled_or_refunded";
21952
21857
  if (job.billedMc == null && job.userId != null) {
21953
- const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-6X2C7RLF.js");
21858
+ const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-U476J44K.js");
21954
21859
  const heldMc = Number(job.options.heldMc ?? 0);
21955
21860
  const successful = await countSuccessfulPages2(jobId);
21956
21861
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
@@ -22114,4 +22019,4 @@ app.get("/blog/:slug/", (c) => {
22114
22019
  export {
22115
22020
  app
22116
22021
  };
22117
- //# sourceMappingURL=server-HNZG36IP.js.map
22022
+ //# sourceMappingURL=server-Z4MEISH5.js.map