mcp-scraper 0.4.2 → 0.4.4

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 (32) hide show
  1. package/dist/bin/api-server.cjs +2386 -1816
  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 +164 -1
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +2 -2
  13. package/dist/{chunk-NVUBFCJK.js → chunk-4ZZWFI6L.js} +2 -2
  14. package/dist/{chunk-4MFYMUIF.js → chunk-5LO6EHEH.js} +25 -3
  15. package/dist/chunk-5LO6EHEH.js.map +1 -0
  16. package/dist/chunk-TZRPP45I.js +7 -0
  17. package/dist/chunk-TZRPP45I.js.map +1 -0
  18. package/dist/{chunk-GFY6XHZS.js → chunk-Z34NGK64.js} +165 -2
  19. package/dist/chunk-Z34NGK64.js.map +1 -0
  20. package/dist/{server-6IYQQK6X.js → server-IKT3QVFB.js} +556 -198
  21. package/dist/server-IKT3QVFB.js.map +1 -0
  22. package/dist/{site-extract-repository-4NVO5KQA.js → site-extract-repository-THBVEXMP.js} +3 -3
  23. package/dist/{worker-7XRZNLKB.js → worker-YHKUJR5P.js} +2 -2
  24. package/package.json +1 -1
  25. package/dist/chunk-4MFYMUIF.js.map +0 -1
  26. package/dist/chunk-7HICT4GY.js +0 -7
  27. package/dist/chunk-7HICT4GY.js.map +0 -1
  28. package/dist/chunk-GFY6XHZS.js.map +0 -1
  29. package/dist/server-6IYQQK6X.js.map +0 -1
  30. /package/dist/{chunk-NVUBFCJK.js.map → chunk-4ZZWFI6L.js.map} +0 -0
  31. /package/dist/{site-extract-repository-4NVO5KQA.js.map → site-extract-repository-THBVEXMP.js.map} +0 -0
  32. /package/dist/{worker-7XRZNLKB.js.map → worker-YHKUJR5P.js.map} +0 -0
@@ -31,7 +31,7 @@ import {
31
31
  registerSerpIntelligenceCaptureTools,
32
32
  sanitizeAttempts,
33
33
  sanitizeHarvestResult
34
- } from "./chunk-GFY6XHZS.js";
34
+ } from "./chunk-Z34NGK64.js";
35
35
  import {
36
36
  auditImages,
37
37
  getBlobStore
@@ -65,7 +65,7 @@ import {
65
65
  RawMapsOverviewSchema,
66
66
  RawMapsReviewStatsSchema
67
67
  } from "./chunk-XGIPATLV.js";
68
- import "./chunk-7HICT4GY.js";
68
+ import "./chunk-TZRPP45I.js";
69
69
  import {
70
70
  completeExtractJob,
71
71
  countSuccessfulPages,
@@ -76,7 +76,7 @@ import {
76
76
  saveExtractPages,
77
77
  setExtractJobTotal,
78
78
  settleExtractJob
79
- } from "./chunk-NVUBFCJK.js";
79
+ } from "./chunk-4ZZWFI6L.js";
80
80
  import {
81
81
  BROWSER_OPEN_MIN_BALANCE_MC,
82
82
  CONCURRENCY_PRICE_ID,
@@ -95,7 +95,7 @@ import {
95
95
  browserActiveCostMc,
96
96
  concurrencySlotBillingInfo,
97
97
  insufficientBalanceResponse
98
- } from "./chunk-4MFYMUIF.js";
98
+ } from "./chunk-5LO6EHEH.js";
99
99
  import {
100
100
  CaptchaError,
101
101
  RECAPTCHA_INSTRUCTIONS,
@@ -5274,7 +5274,7 @@ async function extractSite(opts) {
5274
5274
  }
5275
5275
 
5276
5276
  // src/api/server.ts
5277
- import { Hono as Hono20 } from "hono";
5277
+ import { Hono as Hono22 } from "hono";
5278
5278
  import { serve as serveInngest } from "inngest/hono";
5279
5279
 
5280
5280
  // src/inngest/client.ts
@@ -13160,13 +13160,369 @@ mapsApp.post("/place", createApiKeyAuth(), async (c) => {
13160
13160
  }
13161
13161
  });
13162
13162
 
13163
- // src/api/directory-routes.ts
13163
+ // src/api/trustpilot-routes.ts
13164
13164
  import { Hono as Hono11 } from "hono";
13165
+ import { z as z19 } from "zod";
13166
+ var TrustpilotReviewsBodySchema = z19.object({
13167
+ domain: z19.string().trim().min(1, "domain is required"),
13168
+ maxPages: z19.number().int().min(1).max(50).default(5)
13169
+ });
13170
+ function invalidRequest6(message) {
13171
+ return { error_code: "invalid_request", message };
13172
+ }
13173
+ function normalizeDomain(input) {
13174
+ return input.trim().replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
13175
+ }
13176
+ function reviewPageUrl(domain, page) {
13177
+ const base = `https://www.trustpilot.com/review/${encodeURIComponent(domain)}`;
13178
+ return page <= 1 ? base : `${base}?page=${page}`;
13179
+ }
13180
+ async function extractReviewCards(page) {
13181
+ return page.evaluate(() => {
13182
+ const cards = Array.from(document.querySelectorAll('article[data-service-review-card-paper="true"]')).filter((a) => a.querySelector('[data-testid="service-review-card-v2"]'));
13183
+ return cards.map((card) => {
13184
+ const profileLink = card.querySelector('a[data-consumer-profile-link="true"]');
13185
+ const profileHref = profileLink?.getAttribute("href") ?? "";
13186
+ const profileId = profileHref.startsWith("/users/") ? profileHref.slice("/users/".length) : null;
13187
+ const name = card.querySelector('[data-consumer-name-typography="true"]')?.textContent?.trim() ?? null;
13188
+ const timeEl = card.querySelector("time[datetime]");
13189
+ const date = timeEl?.getAttribute("datetime") ?? null;
13190
+ const ratingEl = card.querySelector("[data-service-review-rating]");
13191
+ const ratingRaw = ratingEl?.getAttribute("data-service-review-rating");
13192
+ const rating = ratingRaw ? parseInt(ratingRaw, 10) : null;
13193
+ const titleLink = card.querySelector('a[data-review-title-typography="true"]');
13194
+ const titleHref = titleLink?.getAttribute("href") ?? "";
13195
+ const reviewId = titleHref.startsWith("/reviews/") ? titleHref.slice("/reviews/".length) : null;
13196
+ const title = card.querySelector('[data-service-review-title-typography="true"]')?.textContent?.trim() ?? null;
13197
+ const bodyEl = card.querySelector('[data-service-review-text-typography="true"]');
13198
+ const rawBody = bodyEl?.textContent?.trim() ?? "";
13199
+ const seeMoreEl = card.querySelector('[class*="seeMore"]');
13200
+ const truncated = !!seeMoreEl;
13201
+ const body = truncated ? rawBody.replace(/see more\s*$/i, "").trim() : rawBody;
13202
+ const labelEl = card.querySelector('[class*="reviewLabel"]');
13203
+ const labelText = labelEl?.textContent?.trim() ?? null;
13204
+ const origin = labelText && /invit/i.test(labelText) ? "invited" : labelText ? labelText.toLowerCase() : "organic";
13205
+ const companyReplied = !!card.querySelector('[class*="companyReply"]');
13206
+ return {
13207
+ source: "trustpilot",
13208
+ sourceReviewId: reviewId,
13209
+ reviewUrl: reviewId ? `https://www.trustpilot.com/reviews/${reviewId}` : null,
13210
+ reviewer: {
13211
+ name,
13212
+ profileId,
13213
+ title: null,
13214
+ companySegment: null
13215
+ },
13216
+ rating,
13217
+ ratingScale: 5,
13218
+ title,
13219
+ date,
13220
+ body: [{ question: null, answer: body }],
13221
+ truncated,
13222
+ flags: {
13223
+ origin,
13224
+ incentivized: null,
13225
+ validated: null,
13226
+ currentUser: null,
13227
+ companyReplied
13228
+ }
13229
+ };
13230
+ });
13231
+ });
13232
+ }
13233
+ var trustpilotApp = new Hono11();
13234
+ trustpilotApp.post("/reviews", createApiKeyAuth(), async (c) => {
13235
+ const raw = await c.req.json().catch(() => ({}));
13236
+ const parsed = TrustpilotReviewsBodySchema.safeParse(raw);
13237
+ if (!parsed.success) return c.json(invalidRequest6(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
13238
+ const body = parsed.data;
13239
+ const domain = normalizeDomain(body.domain);
13240
+ const user = c.get("user");
13241
+ const gate = await acquireConcurrencyGate(user, "trustpilot_reviews", {
13242
+ reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
13243
+ metadata: { domain, maxPages: body.maxPages }
13244
+ });
13245
+ if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
13246
+ let debited = false;
13247
+ let reviewDebitMc = 0;
13248
+ try {
13249
+ const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS, domain);
13250
+ if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.trustpilot_reviews), 402);
13251
+ debited = true;
13252
+ const startMs = Date.now();
13253
+ const driver = new BrowserDriver();
13254
+ const allReviews = [];
13255
+ let pagesFetched = 0;
13256
+ try {
13257
+ await driver.launch({
13258
+ headless: true,
13259
+ kernelApiKey: browserServiceApiKey(),
13260
+ viewport: { width: 1280, height: 900 },
13261
+ locale: "en-US"
13262
+ });
13263
+ const page = driver.getPage();
13264
+ for (let pageNum = 1; pageNum <= body.maxPages; pageNum++) {
13265
+ const url = reviewPageUrl(domain, pageNum);
13266
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
13267
+ await page.waitForTimeout(2e3);
13268
+ const cards = await extractReviewCards(page);
13269
+ pagesFetched = pageNum;
13270
+ if (cards.length === 0) break;
13271
+ allReviews.push(...cards);
13272
+ }
13273
+ } finally {
13274
+ await driver.close();
13275
+ }
13276
+ if (pagesFetched === 0 || allReviews.length === 0) {
13277
+ await creditMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS_REFUND, "no reviews found");
13278
+ await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "failed", query: domain, error: "no reviews found" });
13279
+ return c.json({ error: "No reviews found for this domain (refunded)", domain }, 404);
13280
+ }
13281
+ reviewDebitMc = allReviews.length * MC_COSTS.trustpilot_review;
13282
+ if (reviewDebitMc > 0) {
13283
+ const reviewDebit = await debitMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW, domain);
13284
+ if (!reviewDebit.ok) {
13285
+ allReviews.length = Math.floor((reviewDebit.balance_mc ?? 0) / MC_COSTS.trustpilot_review);
13286
+ reviewDebitMc = allReviews.length * MC_COSTS.trustpilot_review;
13287
+ if (reviewDebitMc > 0) await debitMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW, domain);
13288
+ }
13289
+ }
13290
+ const result = {
13291
+ domain,
13292
+ reviewUrl: reviewPageUrl(domain, 1),
13293
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
13294
+ requestedMaxPages: body.maxPages,
13295
+ pagesFetched,
13296
+ reviewCount: allReviews.length,
13297
+ reviews: allReviews,
13298
+ durationMs: Date.now() - startMs
13299
+ };
13300
+ await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "done", query: domain, resultCount: allReviews.length, result });
13301
+ return c.json(result);
13302
+ } catch (err) {
13303
+ if (debited) await creditMc(user.id, MC_COSTS.trustpilot_reviews, LedgerOperation.TRUSTPILOT_REVIEWS_REFUND, "failed call");
13304
+ if (reviewDebitMc > 0) await creditMc(user.id, reviewDebitMc, LedgerOperation.TRUSTPILOT_REVIEW_REFUND, "failed call");
13305
+ const msg = err instanceof Error ? err.message : String(err);
13306
+ await logRequestEvent({ userId: user.id, source: "trustpilot_reviews", status: "failed", query: domain, error: msg });
13307
+ return c.json({ error: msg }, 500);
13308
+ } finally {
13309
+ await releaseConcurrencyGate(gate.lockId);
13310
+ }
13311
+ });
13312
+
13313
+ // src/api/g2-routes.ts
13314
+ import { Hono as Hono12 } from "hono";
13315
+ import { z as z20 } from "zod";
13316
+ var G2ReviewsBodySchema = z20.object({
13317
+ product: z20.string().trim().min(1, "product is required"),
13318
+ maxPages: z20.number().int().min(1).max(50).default(5)
13319
+ });
13320
+ function invalidRequest7(message) {
13321
+ return { error_code: "invalid_request", message };
13322
+ }
13323
+ function normalizeProductSlug(input) {
13324
+ return input.trim().replace(/^https?:\/\/(www\.)?g2\.com\/products\//i, "").replace(/\/.*$/, "").replace(/\s+/g, "-").toLowerCase();
13325
+ }
13326
+ function reviewPageUrl2(product, page) {
13327
+ const base = `https://www.g2.com/products/${encodeURIComponent(product)}/reviews`;
13328
+ return page <= 1 ? base : `${base}?page=${page}`;
13329
+ }
13330
+ async function residentialProxyId2(attemptIndex) {
13331
+ try {
13332
+ const r = await resolveKernelProxyId({
13333
+ kernelApiKey: browserServiceApiKey(),
13334
+ proxyMode: "location",
13335
+ location: "Austin, TX",
13336
+ gl: "us",
13337
+ attemptIndex,
13338
+ fresh: true
13339
+ });
13340
+ return r.kernelProxyId;
13341
+ } catch {
13342
+ return void 0;
13343
+ }
13344
+ }
13345
+ async function extractG2ReviewCards(page) {
13346
+ return page.evaluate(() => {
13347
+ const cards = Array.from(document.querySelectorAll("article[data-track-in-viewport-options]"));
13348
+ return cards.map((card) => {
13349
+ let meta = {};
13350
+ try {
13351
+ meta = JSON.parse(card.getAttribute("data-track-in-viewport-options") ?? "{}");
13352
+ } catch {
13353
+ meta = {};
13354
+ }
13355
+ const reviewId = meta.survey_response_id != null ? String(meta.survey_response_id) : null;
13356
+ const publishedDateRaw = typeof meta.published_date === "string" ? meta.published_date : null;
13357
+ const date = publishedDateRaw && /^\d{8}$/.test(publishedDateRaw) ? `${publishedDateRaw.slice(0, 4)}-${publishedDateRaw.slice(4, 6)}-${publishedDateRaw.slice(6, 8)}` : null;
13358
+ const profileLink = card.querySelector('a[href*="/users/"]');
13359
+ const profileHref = profileLink?.getAttribute("href") ?? "";
13360
+ const profileMatch = profileHref.match(/\/users\/([a-f0-9-]+)/i);
13361
+ const profileId = profileMatch ? profileMatch[1] : null;
13362
+ const infoWrapper = profileLink?.parentElement;
13363
+ const infoDiv = infoWrapper ? Array.from(infoWrapper.children).find((el2) => el2 !== profileLink) : null;
13364
+ const name = infoDiv?.querySelector('div[class*="font-bold"]')?.textContent?.trim() ?? null;
13365
+ const subtleDivs = infoDiv ? Array.from(infoDiv.querySelectorAll('div[class*="text-subtle"]')) : [];
13366
+ const reviewerTitle = subtleDivs[0]?.textContent?.trim() ?? null;
13367
+ const companySegment = subtleDivs[1]?.textContent?.trim() ?? null;
13368
+ const ratingEl = card.querySelector('[class*="star-wrapper__desc__rating"]');
13369
+ const ratingText = ratingEl?.textContent?.trim() ?? "";
13370
+ const ratingParts = ratingText.split("/");
13371
+ const rating = ratingParts[0] ? parseFloat(ratingParts[0]) : null;
13372
+ const ratingScale = ratingParts[1] ? parseFloat(ratingParts[1]) : 5;
13373
+ const titleEl = card.querySelector('div[class*="text-lg"][class*="font-bold"]');
13374
+ const title = titleEl?.textContent?.trim().replace(/^["“]+|["”]+$/g, "") ?? null;
13375
+ const sections = Array.from(card.querySelectorAll("section"));
13376
+ const body = sections.map((sec) => {
13377
+ const question = sec.querySelector('div[class*="font-bold"]')?.textContent?.trim() ?? null;
13378
+ const paragraphs = Array.from(sec.querySelectorAll("p")).map((p) => p.textContent?.trim() ?? "");
13379
+ const answer = paragraphs.join("\n\n").replace(/Review collected by and hosted on G2\.com\.\s*$/, "").trim();
13380
+ return { question, answer };
13381
+ }).filter((b) => b.answer.length > 0);
13382
+ const flagButtons = Array.from(card.querySelectorAll("button[title]"));
13383
+ let currentUser = false;
13384
+ let validated = false;
13385
+ let incentivized = false;
13386
+ let origin = null;
13387
+ for (const btn of flagButtons) {
13388
+ const label = btn.querySelector("label")?.textContent?.trim() ?? "";
13389
+ if (/current user/i.test(label)) currentUser = true;
13390
+ else if (/validated/i.test(label)) validated = true;
13391
+ else if (/^incentivized$/i.test(label)) incentivized = true;
13392
+ else if (/^source:/i.test(label)) origin = label.replace(/^source:\s*/i, "").trim();
13393
+ }
13394
+ const copyBtn = card.querySelector('[data-clipboard-text*="/reviews/"]');
13395
+ const reviewUrl = copyBtn?.getAttribute("data-clipboard-text") ?? null;
13396
+ return {
13397
+ source: "g2",
13398
+ sourceReviewId: reviewId,
13399
+ reviewUrl,
13400
+ reviewer: {
13401
+ name,
13402
+ profileId,
13403
+ title: reviewerTitle,
13404
+ companySegment
13405
+ },
13406
+ rating,
13407
+ ratingScale,
13408
+ title,
13409
+ date,
13410
+ body: body.length > 0 ? body : [{ question: null, answer: "" }],
13411
+ truncated: false,
13412
+ flags: {
13413
+ origin,
13414
+ incentivized,
13415
+ validated,
13416
+ currentUser,
13417
+ companyReplied: null
13418
+ }
13419
+ };
13420
+ });
13421
+ });
13422
+ }
13423
+ var g2App = new Hono12();
13424
+ g2App.post("/reviews", createApiKeyAuth(), async (c) => {
13425
+ const raw = await c.req.json().catch(() => ({}));
13426
+ const parsed = G2ReviewsBodySchema.safeParse(raw);
13427
+ if (!parsed.success) return c.json(invalidRequest7(parsed.error.issues[0]?.message ?? "Invalid request"), 400);
13428
+ const body = parsed.data;
13429
+ const product = normalizeProductSlug(body.product);
13430
+ const user = c.get("user");
13431
+ const gate = await acquireConcurrencyGate(user, "g2_reviews", {
13432
+ reuseLockId: c.req.header("x-mcp-scraper-concurrency-lock"),
13433
+ metadata: { product, maxPages: body.maxPages }
13434
+ });
13435
+ if (!gate.ok) return c.json(concurrencyLimitExceededResponse(gate), 429, { "Retry-After": String(gate.retryAfterSeconds) });
13436
+ let debited = false;
13437
+ let reviewDebitMc = 0;
13438
+ try {
13439
+ const { ok, balance_mc } = await debitMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS, product);
13440
+ if (!ok) return c.json(insufficientBalanceResponse(balance_mc, MC_COSTS.g2_reviews), 402);
13441
+ debited = true;
13442
+ const startMs = Date.now();
13443
+ const allReviews = [];
13444
+ let pagesFetched = 0;
13445
+ let blocked = false;
13446
+ for (let attempt = 0; attempt < 3 && pagesFetched === 0; attempt++) {
13447
+ const proxyId = await residentialProxyId2(attempt);
13448
+ const driver = new BrowserDriver();
13449
+ try {
13450
+ await driver.launch({
13451
+ headless: true,
13452
+ kernelApiKey: browserServiceApiKey(),
13453
+ kernelProxyId: proxyId,
13454
+ viewport: { width: 1280, height: 900 },
13455
+ locale: "en-US"
13456
+ });
13457
+ const page = driver.getPage();
13458
+ for (let pageNum = 1; pageNum <= body.maxPages; pageNum++) {
13459
+ const url = reviewPageUrl2(product, pageNum);
13460
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45e3 });
13461
+ await page.waitForTimeout(5e3);
13462
+ await page.evaluate(() => window.scrollTo(0, 1200)).catch(() => {
13463
+ });
13464
+ await page.waitForTimeout(3e3);
13465
+ let cards;
13466
+ try {
13467
+ cards = await extractG2ReviewCards(page);
13468
+ } catch {
13469
+ await page.waitForTimeout(3e3);
13470
+ cards = await extractG2ReviewCards(page);
13471
+ }
13472
+ if (cards.length === 0) break;
13473
+ allReviews.push(...cards);
13474
+ pagesFetched = pageNum;
13475
+ }
13476
+ } catch {
13477
+ blocked = true;
13478
+ } finally {
13479
+ await driver.close();
13480
+ }
13481
+ }
13482
+ if (pagesFetched === 0 || allReviews.length === 0) {
13483
+ await creditMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS_REFUND, blocked ? "blocked after retries" : "no reviews found");
13484
+ await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "failed", query: product, error: blocked ? "blocked after retries" : "no reviews found" });
13485
+ return c.json({ error: "No reviews found for this product (refunded)", product }, 404);
13486
+ }
13487
+ reviewDebitMc = allReviews.length * MC_COSTS.g2_review;
13488
+ if (reviewDebitMc > 0) {
13489
+ const reviewDebit = await debitMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW, product);
13490
+ if (!reviewDebit.ok) {
13491
+ allReviews.length = Math.floor((reviewDebit.balance_mc ?? 0) / MC_COSTS.g2_review);
13492
+ reviewDebitMc = allReviews.length * MC_COSTS.g2_review;
13493
+ if (reviewDebitMc > 0) await debitMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW, product);
13494
+ }
13495
+ }
13496
+ const result = {
13497
+ product,
13498
+ reviewUrl: reviewPageUrl2(product, 1),
13499
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
13500
+ requestedMaxPages: body.maxPages,
13501
+ pagesFetched,
13502
+ reviewCount: allReviews.length,
13503
+ reviews: allReviews,
13504
+ durationMs: Date.now() - startMs
13505
+ };
13506
+ await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "done", query: product, resultCount: allReviews.length, result });
13507
+ return c.json(result);
13508
+ } catch (err) {
13509
+ if (debited) await creditMc(user.id, MC_COSTS.g2_reviews, LedgerOperation.G2_REVIEWS_REFUND, "failed call");
13510
+ if (reviewDebitMc > 0) await creditMc(user.id, reviewDebitMc, LedgerOperation.G2_REVIEW_REFUND, "failed call");
13511
+ const msg = err instanceof Error ? err.message : String(err);
13512
+ await logRequestEvent({ userId: user.id, source: "g2_reviews", status: "failed", query: product, error: msg });
13513
+ return c.json({ error: msg }, 500);
13514
+ } finally {
13515
+ await releaseConcurrencyGate(gate.lockId);
13516
+ }
13517
+ });
13518
+
13519
+ // src/api/directory-routes.ts
13520
+ import { Hono as Hono13 } from "hono";
13165
13521
 
13166
13522
  // src/directory/directory-workflow.ts
13167
13523
  import { mkdir as mkdir2, writeFile } from "fs/promises";
13168
13524
  import { join as join5 } from "path";
13169
- import { z as z19 } from "zod";
13525
+ import { z as z21 } from "zod";
13170
13526
 
13171
13527
  // src/directory/location-db.ts
13172
13528
  import { access, readFile } from "fs/promises";
@@ -13327,24 +13683,24 @@ async function resolveDirectoryMarkets(options) {
13327
13683
  }
13328
13684
 
13329
13685
  // src/directory/directory-workflow.ts
13330
- var DirectoryWorkflowOptionsSchema = z19.object({
13331
- query: z19.string().min(1),
13332
- state: z19.string().min(2).default("TN"),
13333
- minPopulation: z19.number().int().min(0).default(1e5),
13334
- populationYear: z19.union(POPULATION_YEARS.map((year) => z19.literal(year))).default(2025),
13335
- maxCities: z19.number().int().min(1).max(100).default(25),
13336
- maxResultsPerCity: z19.number().int().min(1).max(50).default(50),
13337
- concurrency: z19.number().int().min(1).max(5).default(5),
13338
- includeZipGroups: z19.boolean().default(true),
13339
- usZipsCsvPath: z19.string().optional(),
13340
- saveCsv: z19.boolean().default(true),
13341
- gl: z19.string().length(2).default("us"),
13342
- hl: z19.string().length(2).default("en"),
13343
- proxyMode: z19.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
13344
- proxyZip: z19.string().regex(/^\d{5}$/).optional(),
13345
- debug: z19.boolean().default(false),
13346
- headless: z19.boolean().default(true),
13347
- kernelApiKey: z19.string().optional()
13686
+ var DirectoryWorkflowOptionsSchema = z21.object({
13687
+ query: z21.string().min(1),
13688
+ state: z21.string().min(2).default("TN"),
13689
+ minPopulation: z21.number().int().min(0).default(1e5),
13690
+ populationYear: z21.union(POPULATION_YEARS.map((year) => z21.literal(year))).default(2025),
13691
+ maxCities: z21.number().int().min(1).max(100).default(25),
13692
+ maxResultsPerCity: z21.number().int().min(1).max(50).default(50),
13693
+ concurrency: z21.number().int().min(1).max(5).default(5),
13694
+ includeZipGroups: z21.boolean().default(true),
13695
+ usZipsCsvPath: z21.string().optional(),
13696
+ saveCsv: z21.boolean().default(true),
13697
+ gl: z21.string().length(2).default("us"),
13698
+ hl: z21.string().length(2).default("en"),
13699
+ proxyMode: z21.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
13700
+ proxyZip: z21.string().regex(/^\d{5}$/).optional(),
13701
+ debug: z21.boolean().default(false),
13702
+ headless: z21.boolean().default(true),
13703
+ kernelApiKey: z21.string().optional()
13348
13704
  });
13349
13705
  function errorMessage(err) {
13350
13706
  return err instanceof Error ? err.message : String(err);
@@ -13565,7 +13921,7 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
13565
13921
  }
13566
13922
 
13567
13923
  // src/api/directory-routes.ts
13568
- var directoryApp = new Hono11();
13924
+ var directoryApp = new Hono13();
13569
13925
  directoryApp.post("/run", createApiKeyAuth(), async (c) => {
13570
13926
  const user = c.get("user");
13571
13927
  const body = await c.req.json().catch(() => ({}));
@@ -13638,35 +13994,35 @@ directoryApp.post("/run", createApiKeyAuth(), async (c) => {
13638
13994
  // src/api/workflow-routes.ts
13639
13995
  import { createHmac as createHmac2 } from "crypto";
13640
13996
  import { readFile as readFile2 } from "fs/promises";
13641
- import { Hono as Hono12 } from "hono";
13642
- import { z as z20 } from "zod";
13643
- var workflowApp = new Hono12();
13644
- var WorkflowInputSchema = z20.record(z20.unknown()).default({});
13645
- var WorkflowIdSchema = z20.string().min(1);
13646
- var CadenceSchema = z20.enum(["daily", "weekly", "monthly"]);
13647
- var ScheduleStatusSchema = z20.enum(["active", "paused"]);
13648
- var RunBodySchema = z20.object({
13997
+ import { Hono as Hono14 } from "hono";
13998
+ import { z as z22 } from "zod";
13999
+ var workflowApp = new Hono14();
14000
+ var WorkflowInputSchema = z22.record(z22.unknown()).default({});
14001
+ var WorkflowIdSchema = z22.string().min(1);
14002
+ var CadenceSchema = z22.enum(["daily", "weekly", "monthly"]);
14003
+ var ScheduleStatusSchema = z22.enum(["active", "paused"]);
14004
+ var RunBodySchema = z22.object({
13649
14005
  workflowId: WorkflowIdSchema,
13650
14006
  input: WorkflowInputSchema,
13651
- webhookUrl: z20.string().url().optional()
14007
+ webhookUrl: z22.string().url().optional()
13652
14008
  });
13653
- var ScheduleCreateSchema = z20.object({
14009
+ var ScheduleCreateSchema = z22.object({
13654
14010
  workflowId: WorkflowIdSchema,
13655
- name: z20.string().min(1).max(120).optional(),
14011
+ name: z22.string().min(1).max(120).optional(),
13656
14012
  input: WorkflowInputSchema,
13657
14013
  cadence: CadenceSchema.default("weekly"),
13658
- timezone: z20.string().min(1).max(64).default("UTC"),
13659
- webhookUrl: z20.string().url().optional(),
13660
- nextRunAt: z20.string().datetime().optional()
14014
+ timezone: z22.string().min(1).max(64).default("UTC"),
14015
+ webhookUrl: z22.string().url().optional(),
14016
+ nextRunAt: z22.string().datetime().optional()
13661
14017
  });
13662
- var SchedulePatchSchema = z20.object({
13663
- name: z20.string().min(1).max(120).optional(),
14018
+ var SchedulePatchSchema = z22.object({
14019
+ name: z22.string().min(1).max(120).optional(),
13664
14020
  status: ScheduleStatusSchema.optional(),
13665
14021
  input: WorkflowInputSchema.optional(),
13666
14022
  cadence: CadenceSchema.optional(),
13667
- timezone: z20.string().min(1).max(64).optional(),
13668
- webhookUrl: z20.string().url().nullable().optional(),
13669
- nextRunAt: z20.string().datetime().nullable().optional()
14023
+ timezone: z22.string().min(1).max(64).optional(),
14024
+ webhookUrl: z22.string().url().nullable().optional(),
14025
+ nextRunAt: z22.string().datetime().nullable().optional()
13670
14026
  });
13671
14027
  function hostedWorkflowOutputDir() {
13672
14028
  return workflowOutputBaseDir(process.env.MCP_SCRAPER_WORKFLOW_OUTPUT_DIR?.trim() || "/tmp/mcp-scraper-workflows");
@@ -14170,7 +14526,7 @@ workflowApp.post("/cron/dispatch", async (c) => {
14170
14526
  });
14171
14527
 
14172
14528
  // src/api/serp-intelligence-routes.ts
14173
- import { Hono as Hono13 } from "hono";
14529
+ import { Hono as Hono15 } from "hono";
14174
14530
 
14175
14531
  // src/serp-intelligence/page-snapshot-extractor.ts
14176
14532
  import { createHash as createHash2 } from "crypto";
@@ -14482,7 +14838,7 @@ async function capturePageSnapshots(targets, options = {}) {
14482
14838
  }
14483
14839
 
14484
14840
  // src/serp-intelligence/schemas.ts
14485
- import { z as z21 } from "zod";
14841
+ import { z as z23 } from "zod";
14486
14842
  var SerpIntelligenceDeviceValues = ["desktop", "mobile"];
14487
14843
  var SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
14488
14844
  var SerpIntelligenceAttemptOutcomeValues = [
@@ -14544,171 +14900,171 @@ function isPublicHttpUrl(value) {
14544
14900
  return false;
14545
14901
  }
14546
14902
  }
14547
- var SerpIntelligencePublicHttpUrlSchema = z21.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
14548
- var SerpIntelligenceCaptureBodySchema = z21.object({
14549
- query: z21.string().trim().min(1, "query is required"),
14550
- location: z21.string().trim().min(1).optional(),
14551
- gl: z21.string().trim().length(2).default("us"),
14552
- hl: z21.string().trim().length(2).default("en"),
14553
- device: z21.enum(SerpIntelligenceDeviceValues).default("desktop"),
14554
- proxyMode: z21.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
14555
- proxyZip: z21.string().regex(/^\d{5}$/).optional(),
14556
- pages: z21.number().int().min(1).max(2).default(1),
14557
- debug: z21.boolean().default(false),
14558
- includePageSnapshots: z21.boolean().default(false),
14559
- pageSnapshotLimit: z21.number().int().min(0).max(10).default(0)
14903
+ var SerpIntelligencePublicHttpUrlSchema = z23.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
14904
+ var SerpIntelligenceCaptureBodySchema = z23.object({
14905
+ query: z23.string().trim().min(1, "query is required"),
14906
+ location: z23.string().trim().min(1).optional(),
14907
+ gl: z23.string().trim().length(2).default("us"),
14908
+ hl: z23.string().trim().length(2).default("en"),
14909
+ device: z23.enum(SerpIntelligenceDeviceValues).default("desktop"),
14910
+ proxyMode: z23.enum(SerpIntelligenceProxyModeValues).default(DEFAULT_PROXY_MODE),
14911
+ proxyZip: z23.string().regex(/^\d{5}$/).optional(),
14912
+ pages: z23.number().int().min(1).max(2).default(1),
14913
+ debug: z23.boolean().default(false),
14914
+ includePageSnapshots: z23.boolean().default(false),
14915
+ pageSnapshotLimit: z23.number().int().min(0).max(10).default(0)
14560
14916
  }).strict();
14561
- var SerpIntelligencePageSnapshotRequestSchema = z21.object({
14917
+ var SerpIntelligencePageSnapshotRequestSchema = z23.object({
14562
14918
  url: SerpIntelligencePublicHttpUrlSchema,
14563
- sourceKind: z21.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
14564
- sourcePosition: z21.number().int().min(1).optional()
14919
+ sourceKind: z23.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
14920
+ sourcePosition: z23.number().int().min(1).optional()
14565
14921
  }).strict();
14566
- var SerpIntelligencePageSnapshotsBodySchema = z21.object({
14567
- urls: z21.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
14568
- targets: z21.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
14569
- maxConcurrency: z21.number().int().min(1).max(5).default(2),
14570
- timeoutMs: z21.number().int().min(1e3).max(6e4).default(15e3),
14571
- debug: z21.boolean().default(false)
14922
+ var SerpIntelligencePageSnapshotsBodySchema = z23.object({
14923
+ urls: z23.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
14924
+ targets: z23.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
14925
+ maxConcurrency: z23.number().int().min(1).max(5).default(2),
14926
+ timeoutMs: z23.number().int().min(1e3).max(6e4).default(15e3),
14927
+ debug: z23.boolean().default(false)
14572
14928
  }).strict();
14573
- var SerpIntelligenceAICitationSchema = z21.object({
14574
- text: z21.string(),
14575
- href: z21.string()
14929
+ var SerpIntelligenceAICitationSchema = z23.object({
14930
+ text: z23.string(),
14931
+ href: z23.string()
14576
14932
  }).strict();
14577
- var SerpIntelligenceOrganicResultSchema = z21.object({
14578
- position: z21.number().int().min(1),
14579
- title: z21.string(),
14580
- url: z21.string(),
14581
- domain: z21.string(),
14582
- cite: z21.string().nullable(),
14583
- snippet: z21.string().nullable(),
14584
- isRedditStyle: z21.boolean(),
14585
- inlineRating: z21.object({
14586
- value: z21.string(),
14587
- count: z21.string()
14933
+ var SerpIntelligenceOrganicResultSchema = z23.object({
14934
+ position: z23.number().int().min(1),
14935
+ title: z23.string(),
14936
+ url: z23.string(),
14937
+ domain: z23.string(),
14938
+ cite: z23.string().nullable(),
14939
+ snippet: z23.string().nullable(),
14940
+ isRedditStyle: z23.boolean(),
14941
+ inlineRating: z23.object({
14942
+ value: z23.string(),
14943
+ count: z23.string()
14588
14944
  }).strict().nullable()
14589
14945
  }).strict();
14590
- var SerpIntelligenceLocationEvidenceSchema = z21.object({
14591
- status: z21.enum(SerpIntelligenceLocalizationStatusValues),
14592
- expected: z21.object({
14593
- city: z21.string(),
14594
- regionCode: z21.string().nullable(),
14595
- canonicalLocation: z21.string()
14946
+ var SerpIntelligenceLocationEvidenceSchema = z23.object({
14947
+ status: z23.enum(SerpIntelligenceLocalizationStatusValues),
14948
+ expected: z23.object({
14949
+ city: z23.string(),
14950
+ regionCode: z23.string().nullable(),
14951
+ canonicalLocation: z23.string()
14596
14952
  }).strict().nullable(),
14597
- candidates: z21.array(z21.object({
14598
- city: z21.string(),
14599
- regionCode: z21.string(),
14600
- count: z21.number().int().min(0),
14601
- examples: z21.array(z21.string())
14953
+ candidates: z23.array(z23.object({
14954
+ city: z23.string(),
14955
+ regionCode: z23.string(),
14956
+ count: z23.number().int().min(0),
14957
+ examples: z23.array(z23.string())
14602
14958
  }).strict())
14603
14959
  }).strict();
14604
- var SerpIntelligenceHarvestResultSchema = z21.object({
14605
- seed: z21.string(),
14606
- location: z21.string().nullable(),
14607
- extractedAt: z21.string(),
14608
- totalQuestions: z21.number().int().min(0),
14609
- surface: z21.enum(["web", "aim", "unknown"]),
14610
- aiOverview: z21.object({
14611
- detected: z21.boolean(),
14612
- text: z21.string().nullable(),
14613
- citations: z21.array(SerpIntelligenceAICitationSchema),
14614
- expanded: z21.boolean().optional(),
14615
- fullyExpanded: z21.boolean().optional(),
14616
- sections: z21.array(z21.string()).optional()
14960
+ var SerpIntelligenceHarvestResultSchema = z23.object({
14961
+ seed: z23.string(),
14962
+ location: z23.string().nullable(),
14963
+ extractedAt: z23.string(),
14964
+ totalQuestions: z23.number().int().min(0),
14965
+ surface: z23.enum(["web", "aim", "unknown"]),
14966
+ aiOverview: z23.object({
14967
+ detected: z23.boolean(),
14968
+ text: z23.string().nullable(),
14969
+ citations: z23.array(SerpIntelligenceAICitationSchema),
14970
+ expanded: z23.boolean().optional(),
14971
+ fullyExpanded: z23.boolean().optional(),
14972
+ sections: z23.array(z23.string()).optional()
14617
14973
  }).strict(),
14618
- aiMode: z21.object({
14619
- detected: z21.boolean(),
14620
- text: z21.string().nullable(),
14621
- citations: z21.array(SerpIntelligenceAICitationSchema)
14974
+ aiMode: z23.object({
14975
+ detected: z23.boolean(),
14976
+ text: z23.string().nullable(),
14977
+ citations: z23.array(SerpIntelligenceAICitationSchema)
14622
14978
  }).strict(),
14623
- tree: z21.array(z21.unknown()),
14624
- flat: z21.array(z21.unknown()),
14625
- videos: z21.array(z21.unknown()),
14626
- forums: z21.array(z21.unknown()),
14627
- organicResults: z21.array(SerpIntelligenceOrganicResultSchema),
14628
- localPack: z21.array(z21.unknown()),
14629
- entityIds: z21.object({
14630
- entities: z21.array(z21.object({
14631
- name: z21.string(),
14632
- kgId: z21.string().nullable(),
14633
- cid: z21.string().nullable(),
14634
- gcid: z21.string().nullable()
14979
+ tree: z23.array(z23.unknown()),
14980
+ flat: z23.array(z23.unknown()),
14981
+ videos: z23.array(z23.unknown()),
14982
+ forums: z23.array(z23.unknown()),
14983
+ organicResults: z23.array(SerpIntelligenceOrganicResultSchema),
14984
+ localPack: z23.array(z23.unknown()),
14985
+ entityIds: z23.object({
14986
+ entities: z23.array(z23.object({
14987
+ name: z23.string(),
14988
+ kgId: z23.string().nullable(),
14989
+ cid: z23.string().nullable(),
14990
+ gcid: z23.string().nullable()
14635
14991
  }).strict()),
14636
- kgIds: z21.array(z21.string()),
14637
- cids: z21.array(z21.string()),
14638
- gcids: z21.array(z21.string())
14992
+ kgIds: z23.array(z23.string()),
14993
+ cids: z23.array(z23.string()),
14994
+ gcids: z23.array(z23.string())
14639
14995
  }).strict(),
14640
- stats: z21.object({
14641
- seed: z21.string(),
14642
- totalQuestions: z21.number().int().min(0),
14643
- maxDepthReached: z21.number().int().min(0),
14644
- durationMs: z21.number().min(0),
14645
- errorCount: z21.number().int().min(0)
14996
+ stats: z23.object({
14997
+ seed: z23.string(),
14998
+ totalQuestions: z23.number().int().min(0),
14999
+ maxDepthReached: z23.number().int().min(0),
15000
+ durationMs: z23.number().min(0),
15001
+ errorCount: z23.number().int().min(0)
14646
15002
  }).strict(),
14647
- diagnostics: z21.object({
14648
- completionStatus: z21.enum(["paa_found", "no_paa", "serp_only"]),
14649
- problem: z21.null(),
14650
- warnings: z21.array(z21.unknown()).optional(),
14651
- debug: z21.object({
15003
+ diagnostics: z23.object({
15004
+ completionStatus: z23.enum(["paa_found", "no_paa", "serp_only"]),
15005
+ problem: z23.null(),
15006
+ warnings: z23.array(z23.unknown()).optional(),
15007
+ debug: z23.object({
14652
15008
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
14653
15009
  }).passthrough().optional()
14654
15010
  }).passthrough(),
14655
- whatPeopleSaying: z21.array(z21.unknown())
15011
+ whatPeopleSaying: z23.array(z23.unknown())
14656
15012
  }).strict();
14657
- var SerpIntelligenceCaptureAttemptSchema = z21.object({
14658
- attemptNumber: z21.number().int().min(1),
14659
- outcome: z21.enum(SerpIntelligenceAttemptOutcomeValues),
14660
- startedAt: z21.string().optional(),
14661
- completedAt: z21.string().optional(),
14662
- durationMs: z21.number().min(0).optional(),
14663
- problemCode: z21.string().optional(),
14664
- message: z21.string().optional(),
14665
- kernelSessionId: z21.string().nullable().optional(),
14666
- cleanupSucceeded: z21.boolean().nullable().optional()
15013
+ var SerpIntelligenceCaptureAttemptSchema = z23.object({
15014
+ attemptNumber: z23.number().int().min(1),
15015
+ outcome: z23.enum(SerpIntelligenceAttemptOutcomeValues),
15016
+ startedAt: z23.string().optional(),
15017
+ completedAt: z23.string().optional(),
15018
+ durationMs: z23.number().min(0).optional(),
15019
+ problemCode: z23.string().optional(),
15020
+ message: z23.string().optional(),
15021
+ kernelSessionId: z23.string().nullable().optional(),
15022
+ cleanupSucceeded: z23.boolean().nullable().optional()
14667
15023
  }).strict();
14668
- var SerpPageSnapshotCaptureSchema = z21.object({
15024
+ var SerpPageSnapshotCaptureSchema = z23.object({
14669
15025
  url: SerpIntelligencePublicHttpUrlSchema,
14670
15026
  requestedUrl: SerpIntelligencePublicHttpUrlSchema,
14671
15027
  finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
14672
- sourceKind: z21.enum(SerpPageSnapshotSourceKindValues),
14673
- sourcePosition: z21.number().int().min(1).nullable(),
14674
- status: z21.enum(SerpPageFetchStatusValues),
14675
- fetchedVia: z21.enum(SerpPageFetchedViaValues).nullable(),
14676
- httpStatus: z21.number().int().min(100).max(599).nullable(),
14677
- contentType: z21.string().nullable(),
14678
- title: z21.string().nullable(),
15028
+ sourceKind: z23.enum(SerpPageSnapshotSourceKindValues),
15029
+ sourcePosition: z23.number().int().min(1).nullable(),
15030
+ status: z23.enum(SerpPageFetchStatusValues),
15031
+ fetchedVia: z23.enum(SerpPageFetchedViaValues).nullable(),
15032
+ httpStatus: z23.number().int().min(100).max(599).nullable(),
15033
+ contentType: z23.string().nullable(),
15034
+ title: z23.string().nullable(),
14679
15035
  canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
14680
- metaDescription: z21.string().nullable(),
14681
- headings: z21.array(z21.object({
14682
- level: z21.number().int().min(1).max(6),
14683
- text: z21.string()
15036
+ metaDescription: z23.string().nullable(),
15037
+ headings: z23.array(z23.object({
15038
+ level: z23.number().int().min(1).max(6),
15039
+ text: z23.string()
14684
15040
  }).strict()).default([]),
14685
- artifact: z21.object({
14686
- htmlBlobUrl: z21.string().url().nullable(),
14687
- textBlobUrl: z21.string().url().nullable(),
14688
- markdownBlobUrl: z21.string().url().nullable(),
14689
- screenshotBlobUrl: z21.string().url().nullable(),
14690
- contentSha256: z21.string().nullable(),
14691
- capturedAt: z21.string().nullable()
15041
+ artifact: z23.object({
15042
+ htmlBlobUrl: z23.string().url().nullable(),
15043
+ textBlobUrl: z23.string().url().nullable(),
15044
+ markdownBlobUrl: z23.string().url().nullable(),
15045
+ screenshotBlobUrl: z23.string().url().nullable(),
15046
+ contentSha256: z23.string().nullable(),
15047
+ capturedAt: z23.string().nullable()
14692
15048
  }).strict(),
14693
- error: z21.object({
14694
- code: z21.string(),
14695
- message: z21.string()
15049
+ error: z23.object({
15050
+ code: z23.string(),
15051
+ message: z23.string()
14696
15052
  }).strict().nullable()
14697
15053
  }).strict();
14698
- var SerpIntelligenceCaptureResponseSchema = z21.object({
15054
+ var SerpIntelligenceCaptureResponseSchema = z23.object({
14699
15055
  harvestResult: SerpIntelligenceHarvestResultSchema,
14700
- attempts: z21.array(SerpIntelligenceCaptureAttemptSchema),
15056
+ attempts: z23.array(SerpIntelligenceCaptureAttemptSchema),
14701
15057
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
14702
- pageSnapshotArtifacts: z21.array(SerpPageSnapshotCaptureSchema),
14703
- billing: z21.object({
14704
- creditsUsed: z21.number().min(0).optional(),
14705
- requestId: z21.string().optional(),
14706
- jobId: z21.string().optional()
15058
+ pageSnapshotArtifacts: z23.array(SerpPageSnapshotCaptureSchema),
15059
+ billing: z23.object({
15060
+ creditsUsed: z23.number().min(0).optional(),
15061
+ requestId: z23.string().optional(),
15062
+ jobId: z23.string().optional()
14707
15063
  }).strict().optional()
14708
15064
  }).strict();
14709
- var SerpIntelligencePageSnapshotsResponseSchema = z21.object({
14710
- pageSnapshotArtifacts: z21.array(SerpPageSnapshotCaptureSchema),
14711
- attempts: z21.array(SerpIntelligenceCaptureAttemptSchema).default([])
15065
+ var SerpIntelligencePageSnapshotsResponseSchema = z23.object({
15066
+ pageSnapshotArtifacts: z23.array(SerpPageSnapshotCaptureSchema),
15067
+ attempts: z23.array(SerpIntelligenceCaptureAttemptSchema).default([])
14712
15068
  }).strict();
14713
15069
 
14714
15070
  // src/serp-intelligence/serp-capture-service.ts
@@ -14880,7 +15236,7 @@ var SERP_INTELLIGENCE_RATE_LIMIT = 60;
14880
15236
  var SERP_INTELLIGENCE_RATE_WINDOW_SECONDS = 60;
14881
15237
  var POST_CAPTURE_ROUTE_LABEL = "POST /capture";
14882
15238
  var POST_PAGE_SNAPSHOTS_ROUTE_LABEL = "POST /page-snapshots";
14883
- var serpIntelligenceApp = new Hono13();
15239
+ var serpIntelligenceApp = new Hono15();
14884
15240
  serpIntelligenceApp.use("*", createApiKeyAuth());
14885
15241
  function structuredError(input) {
14886
15242
  return {
@@ -15049,7 +15405,7 @@ serpIntelligenceApp.post("/page-snapshots", async (c) => {
15049
15405
  });
15050
15406
 
15051
15407
  // src/mcp/mcp-routes.ts
15052
- import { Hono as Hono14 } from "hono";
15408
+ import { Hono as Hono16 } from "hono";
15053
15409
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
15054
15410
 
15055
15411
  // src/mcp/memory-inprocess-executor.ts
@@ -15576,7 +15932,7 @@ async function requireMcpCallerKey(c) {
15576
15932
  if (!user) return mcpAuthError();
15577
15933
  return { key: callerKey, user };
15578
15934
  }
15579
- var mcpApp = new Hono14();
15935
+ var mcpApp = new Hono16();
15580
15936
  mcpApp.all("/", async (c) => {
15581
15937
  try {
15582
15938
  const keyOrError = await requireMcpCallerKey(c);
@@ -15602,7 +15958,7 @@ mcpApp.all("/", async (c) => {
15602
15958
  });
15603
15959
 
15604
15960
  // src/api/browser-agent-routes.ts
15605
- import { Hono as Hono15 } from "hono";
15961
+ import { Hono as Hono17 } from "hono";
15606
15962
 
15607
15963
  // src/api/browser-agent-db.ts
15608
15964
  import { randomUUID as randomUUID2 } from "crypto";
@@ -16993,7 +17349,7 @@ async function loadOpenSession(id, userId) {
16993
17349
  return row;
16994
17350
  }
16995
17351
  function buildBrowserAgentRoutes() {
16996
- const app2 = new Hono15();
17352
+ const app2 = new Hono17();
16997
17353
  app2.use("*", async (c, next) => {
16998
17354
  await migrateBrowserAgent();
16999
17355
  return next();
@@ -17727,9 +18083,9 @@ if (state.key) { refreshSessions(); if (state.current) selectSession(state.curre
17727
18083
 
17728
18084
  // src/api/stripe-routes.ts
17729
18085
  import Stripe from "stripe";
17730
- import { Hono as Hono16 } from "hono";
18086
+ import { Hono as Hono18 } from "hono";
17731
18087
  var stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: "2026-02-25.clover" });
17732
- var stripeApp = new Hono16();
18088
+ var stripeApp = new Hono18();
17733
18089
  stripeApp.post("/webhooks", async (c) => {
17734
18090
  const sig = c.req.header("stripe-signature");
17735
18091
  const body = await c.req.text();
@@ -17833,7 +18189,7 @@ async function resolveUser(customerId, emailFallback) {
17833
18189
  }
17834
18190
 
17835
18191
  // src/api/oauth-routes.ts
17836
- import { Hono as Hono17 } from "hono";
18192
+ import { Hono as Hono19 } from "hono";
17837
18193
  import { getCookie, setCookie } from "hono/cookie";
17838
18194
  import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
17839
18195
  import { importPKCS8, exportJWK, SignJWT } from "jose";
@@ -18002,7 +18358,7 @@ function redirectWithError(redirectUri, state, error) {
18002
18358
  if (state) u.searchParams.set("state", state);
18003
18359
  return new Response(null, { status: 302, headers: { Location: u.toString() } });
18004
18360
  }
18005
- var oauthApp = new Hono17();
18361
+ var oauthApp = new Hono19();
18006
18362
  oauthApp.use("*", async (c, next) => {
18007
18363
  c.header("Access-Control-Allow-Origin", "*");
18008
18364
  c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -18256,8 +18612,8 @@ oauthApp.post("/oauth/token", async (c) => {
18256
18612
  });
18257
18613
 
18258
18614
  // src/api/chat-routes.ts
18259
- import { Hono as Hono18 } from "hono";
18260
- var chatApp = new Hono18();
18615
+ import { Hono as Hono20 } from "hono";
18616
+ var chatApp = new Hono20();
18261
18617
  function tokenOk(token) {
18262
18618
  return !!token && token.startsWith("mk_") && token.length > 10;
18263
18619
  }
@@ -18927,8 +19283,8 @@ loadChannels();
18927
19283
  }
18928
19284
 
18929
19285
  // src/api/schedule-routes.ts
18930
- import { Hono as Hono19 } from "hono";
18931
- var scheduleApp = new Hono19();
19286
+ import { Hono as Hono21 } from "hono";
19287
+ var scheduleApp = new Hono21();
18932
19288
  function tokenOk2(token) {
18933
19289
  return !!token && token.startsWith("mk_") && token.length > 10;
18934
19290
  }
@@ -19683,7 +20039,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
19683
20039
  c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
19684
20040
  return next();
19685
20041
  });
19686
- var app = new Hono20();
20042
+ var app = new Hono22();
19687
20043
  var STRIPE_API_VERSION = "2026-02-25.clover";
19688
20044
  function requireStripeSecret() {
19689
20045
  const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
@@ -21044,7 +21400,7 @@ app.get("/cron/tick", async (c) => {
21044
21400
  if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
21045
21401
  return c.json({ error: "Unauthorized" }, 401);
21046
21402
  }
21047
- const { drainQueue } = await import("./worker-7XRZNLKB.js");
21403
+ const { drainQueue } = await import("./worker-YHKUJR5P.js");
21048
21404
  const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
21049
21405
  const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
21050
21406
  const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
@@ -21062,7 +21418,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
21062
21418
  return c.json({ error: "Unauthorized" }, 401);
21063
21419
  }
21064
21420
  const jobId = c.req.param("id");
21065
- const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-4NVO5KQA.js");
21421
+ const { getExtractJob: getExtractJob2, completeExtractJob: completeExtractJob2 } = await import("./site-extract-repository-THBVEXMP.js");
21066
21422
  const { assembleExtractArtifacts } = await import("./extract-bundle-UKE273TV.js");
21067
21423
  const job = await getExtractJob2(jobId);
21068
21424
  if (!job) return c.json({ error: "job not found" }, 404);
@@ -21070,7 +21426,7 @@ app.post("/api/internal/extract-refinalize/:id", async (c) => {
21070
21426
  await completeExtractJob2(jobId, stored);
21071
21427
  let settlement = "already_settled_or_refunded";
21072
21428
  if (job.billedMc == null && job.userId != null) {
21073
- const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-4NVO5KQA.js");
21429
+ const { settleExtractJob: settleExtractJob2, countSuccessfulPages: countSuccessfulPages2 } = await import("./site-extract-repository-THBVEXMP.js");
21074
21430
  const heldMc = Number(job.options.heldMc ?? 0);
21075
21431
  const successful = await countSuccessfulPages2(jobId);
21076
21432
  const usedMc = Math.min(successful * MC_COSTS.page_scrape, heldMc);
@@ -21096,6 +21452,8 @@ app.route("/instagram", instagramApp);
21096
21452
  app.route("/reddit", redditApp);
21097
21453
  app.route("/video", videoApp);
21098
21454
  app.route("/maps", mapsApp);
21455
+ app.route("/trustpilot", trustpilotApp);
21456
+ app.route("/g2", g2App);
21099
21457
  app.route("/directory", directoryApp);
21100
21458
  app.route("/workflows", workflowApp);
21101
21459
  app.route("/serp-intelligence", serpIntelligenceApp);
@@ -21232,4 +21590,4 @@ app.get("/blog/:slug/", (c) => {
21232
21590
  export {
21233
21591
  app
21234
21592
  };
21235
- //# sourceMappingURL=server-6IYQQK6X.js.map
21593
+ //# sourceMappingURL=server-IKT3QVFB.js.map