mcp-scraper 0.26.3 → 0.28.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 (42) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/api-server.cjs +525 -283
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +2 -2
  5. package/dist/bin/mcp-scraper-cli.cjs +3 -2
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +3 -3
  8. package/dist/bin/mcp-scraper-install.cjs +2 -2
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +2 -2
  11. package/dist/bin/mcp-stdio-server.cjs +62 -10
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +4 -4
  14. package/dist/bin/paa-harvest.cjs +2 -1
  15. package/dist/bin/paa-harvest.cjs.map +1 -1
  16. package/dist/bin/paa-harvest.js +2 -2
  17. package/dist/{chunk-TBRX7VOP.js → chunk-DUS4ENOY.js} +2 -2
  18. package/dist/{chunk-TBRX7VOP.js.map → chunk-DUS4ENOY.js.map} +1 -1
  19. package/dist/{chunk-FUWZWKGO.js → chunk-KAMKTSJQ.js} +2 -2
  20. package/dist/{chunk-73YM3DEB.js → chunk-LTT7QX3C.js} +61 -10
  21. package/dist/chunk-LTT7QX3C.js.map +1 -0
  22. package/dist/chunk-QG77M3YJ.js +7 -0
  23. package/dist/chunk-QG77M3YJ.js.map +1 -0
  24. package/dist/{chunk-E5J4HJBO.js → chunk-YH5DRFWQ.js} +4 -2
  25. package/dist/{chunk-XGIPATLV.js → chunk-ZQSHKWV4.js} +3 -2
  26. package/dist/chunk-ZQSHKWV4.js.map +1 -0
  27. package/dist/index.cjs +2 -1
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +2 -2
  30. package/dist/{server-GN34MGAE.js → server-QQB4BNAB.js} +449 -267
  31. package/dist/server-QQB4BNAB.js.map +1 -0
  32. package/dist/{worker-E3KHQH2F.js → worker-RYP5EOV5.js} +3 -3
  33. package/docs/mcp-tool-manifest.generated.json +128 -9
  34. package/package.json +2 -2
  35. package/dist/chunk-27VXTOD2.js +0 -7
  36. package/dist/chunk-27VXTOD2.js.map +0 -1
  37. package/dist/chunk-73YM3DEB.js.map +0 -1
  38. package/dist/chunk-XGIPATLV.js.map +0 -1
  39. package/dist/server-GN34MGAE.js.map +0 -1
  40. /package/dist/{chunk-FUWZWKGO.js.map → chunk-KAMKTSJQ.js.map} +0 -0
  41. /package/dist/{chunk-E5J4HJBO.js.map → chunk-YH5DRFWQ.js.map} +0 -0
  42. /package/dist/{worker-E3KHQH2F.js.map → worker-RYP5EOV5.js.map} +0 -0
@@ -17330,7 +17330,7 @@ var init_schemas3 = __esm({
17330
17330
  "use strict";
17331
17331
  import_zod17 = require("zod");
17332
17332
  DEFAULT_PROXY_MODE = "none";
17333
- DEFAULT_MAPS_PROXY_MODE = "location";
17333
+ DEFAULT_MAPS_PROXY_MODE = "none";
17334
17334
  HarvestOptionsSchema = import_zod17.z.object({
17335
17335
  query: import_zod17.z.string().min(1),
17336
17336
  location: import_zod17.z.string().optional(),
@@ -17372,6 +17372,7 @@ var init_schemas3 = __esm({
17372
17372
  gl: import_zod17.z.string().length(2).default("us"),
17373
17373
  hl: import_zod17.z.string().length(2).default("en"),
17374
17374
  maxResults: import_zod17.z.number().int().min(1).max(50).default(10),
17375
+ includeServices: import_zod17.z.boolean().default(false),
17375
17376
  proxyMode: import_zod17.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
17376
17377
  proxyZip: import_zod17.z.string().regex(/^\d{5}$/).optional(),
17377
17378
  debug: import_zod17.z.boolean().default(false),
@@ -19227,12 +19228,277 @@ var init_video_routes = __esm({
19227
19228
  }
19228
19229
  });
19229
19230
 
19231
+ // src/extractor/MapsSearchExtractor.ts
19232
+ function buildGoogleLocalResultsUrl(input) {
19233
+ const query = [input.query, input.location].filter(Boolean).join(" ");
19234
+ const params = new URLSearchParams({ q: query, udm: "1", gl: input.gl, hl: input.hl, pws: "0" });
19235
+ if (input.location) params.set("uule", encodeUule(normalizeLocation(input.location)));
19236
+ return `https://www.google.com/search?${params.toString()}`;
19237
+ }
19238
+ var LOCAL_RESULTS_PAGE_LIMIT, LOCAL_RESULTS_WAIT_MS, MapsSearchExtractor;
19239
+ var init_MapsSearchExtractor = __esm({
19240
+ "src/extractor/MapsSearchExtractor.ts"() {
19241
+ "use strict";
19242
+ init_errors();
19243
+ init_uule();
19244
+ LOCAL_RESULTS_PAGE_LIMIT = 8;
19245
+ LOCAL_RESULTS_WAIT_MS = 1e3;
19246
+ MapsSearchExtractor = class {
19247
+ constructor(driver) {
19248
+ this.driver = driver;
19249
+ }
19250
+ driver;
19251
+ async extract(options) {
19252
+ const startMs = Date.now();
19253
+ const searchQuery = [options.query, options.location].filter(Boolean).join(" ");
19254
+ const searchUrl = buildGoogleLocalResultsUrl(options);
19255
+ const config = {
19256
+ headless: options.headless,
19257
+ kernelApiKey: options.kernelApiKey,
19258
+ kernelProxyId: options.kernelProxyId,
19259
+ kernelProxyResolution: options.kernelProxyResolution,
19260
+ proxyMode: options.proxyMode,
19261
+ viewport: { width: 1280, height: 900 },
19262
+ locale: `${options.hl}-${options.gl.toUpperCase()}`,
19263
+ debug: options.debug
19264
+ };
19265
+ try {
19266
+ await this.driver.launch(config);
19267
+ const page = this.driver.getPage();
19268
+ await page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
19269
+ await page.waitForTimeout(LOCAL_RESULTS_WAIT_MS);
19270
+ if (await this.detectBlock(page)) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
19271
+ const results = await this.collectResults(page, options);
19272
+ return {
19273
+ query: options.query,
19274
+ location: options.location ?? null,
19275
+ searchQuery,
19276
+ searchUrl,
19277
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
19278
+ requestedMaxResults: options.maxResults,
19279
+ resultCount: results.length,
19280
+ results,
19281
+ durationMs: Date.now() - startMs
19282
+ };
19283
+ } finally {
19284
+ await this.driver.close();
19285
+ }
19286
+ }
19287
+ async detectBlock(page) {
19288
+ return page.evaluate(() => {
19289
+ const text2 = document.body.innerText.slice(0, 2e3);
19290
+ return /unusual traffic|captcha|recaptcha|about this page/i.test(text2) || /\/sorry\//.test(location.href);
19291
+ });
19292
+ }
19293
+ async collectResults(page, options) {
19294
+ const seen = /* @__PURE__ */ new Set();
19295
+ const results = [];
19296
+ let pageCount = 0;
19297
+ while (results.length < options.maxResults && pageCount < LOCAL_RESULTS_PAGE_LIMIT) {
19298
+ if (await this.detectBlock(page)) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
19299
+ const cards = await this.extractVisibleCards(page, options.location);
19300
+ for (const card of cards) {
19301
+ if (results.length >= options.maxResults || seen.has(card.cardKey)) continue;
19302
+ seen.add(card.cardKey);
19303
+ const details = await this.openCardAndExtractDialog(page, card, options.includeServices);
19304
+ results.push({
19305
+ position: results.length + 1,
19306
+ name: card.name,
19307
+ placeUrl: details?.placeUrl ?? card.placeUrl,
19308
+ cid: details?.cid ?? card.cid,
19309
+ cidDecimal: details?.cidDecimal ?? card.cidDecimal,
19310
+ rating: details?.rating ?? card.rating,
19311
+ reviewCount: details?.reviewCount ?? card.reviewCount,
19312
+ category: details?.category ?? card.category,
19313
+ address: details?.address ?? card.address,
19314
+ phone: details?.phone ?? card.phone,
19315
+ hoursStatus: details?.hoursStatus ?? card.hoursStatus,
19316
+ websiteUrl: details?.websiteUrl ?? card.websiteUrl,
19317
+ directionsUrl: card.directionsUrl,
19318
+ metadata: card.metadata,
19319
+ services: details?.services ?? [],
19320
+ areasServed: details?.areasServed ?? [],
19321
+ profileDetailsStatus: details ? "collected" : "unavailable"
19322
+ });
19323
+ }
19324
+ if (results.length >= options.maxResults) break;
19325
+ const advanced = await this.advanceToMoreResults(page);
19326
+ if (!advanced) break;
19327
+ pageCount += 1;
19328
+ }
19329
+ return results;
19330
+ }
19331
+ async extractVisibleCards(page, location2) {
19332
+ const fallbackLocation = location2 ?? "";
19333
+ return page.evaluate((locationHint) => {
19334
+ const normalize4 = (value) => {
19335
+ const text2 = value?.replace(/\s+/g, " ").trim() ?? "";
19336
+ return text2 || null;
19337
+ };
19338
+ const phonePattern = /(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/;
19339
+ const addressPattern = /\b[A-Z]{2}\s+\d{5}(?:-\d{4})?\b|\b(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Dr|Drive|Ln|Lane|Way|Pkwy|Parkway)\b/i;
19340
+ const cards = Array.from(document.querySelectorAll("button")).filter((button) => {
19341
+ const name = normalize4(button.querySelector("h3")?.textContent);
19342
+ return Boolean(name) && !button.closest('[role="dialog"], dialog') && !/\bsponsored\b/i.test(button.innerText);
19343
+ });
19344
+ const seen = /* @__PURE__ */ new Set();
19345
+ const out = [];
19346
+ for (const [cardIndex, card] of cards.entries()) {
19347
+ const name = normalize4(card.querySelector("h3")?.textContent);
19348
+ if (!name) continue;
19349
+ const text2 = normalize4(card.innerText) ?? name;
19350
+ const key = `${name.toLowerCase()}|${text2.toLowerCase()}`;
19351
+ if (seen.has(key)) continue;
19352
+ seen.add(key);
19353
+ const lines = text2.split(/\n+/).map((line) => normalize4(line)).filter((line) => Boolean(line));
19354
+ const aria = normalize4(card.getAttribute("aria-label"));
19355
+ const rating = (aria ?? text2).match(/(?:rated\s+)?(\d(?:\.\d)?)\s*(?:out of 5)?/i)?.[1] ?? null;
19356
+ const reviewCount = (aria ?? text2).match(/(?:\(|·\s*)([\d,]+)\s+(?:user\s+)?reviews?\b/i)?.[1]?.replace(/,/g, "") ?? null;
19357
+ const phone = lines.find((line) => phonePattern.test(line)) ?? null;
19358
+ const address = lines.find((line) => addressPattern.test(line)) ?? null;
19359
+ const hoursStatus = lines.find((line) => /\b(?:open|closed|opens|closes)\b/i.test(line)) ?? null;
19360
+ const category = lines.find(
19361
+ (line) => line !== name && line !== phone && line !== address && line !== hoursStatus && !/^(?:rated|\d[\d.,]*\s*(?:out of 5|reviews?)?)\b/i.test(line) && !/sponsored|website|directions|call|save|share|more/i.test(line)
19362
+ ) ?? null;
19363
+ const mapsSearchUrl = `https://www.google.com/maps/search/${encodeURIComponent([name, locationHint].filter(Boolean).join(" "))}`;
19364
+ out.push({
19365
+ cardIndex,
19366
+ cardKey: key,
19367
+ name,
19368
+ placeUrl: mapsSearchUrl,
19369
+ cid: null,
19370
+ cidDecimal: null,
19371
+ rating,
19372
+ reviewCount,
19373
+ category,
19374
+ address,
19375
+ phone,
19376
+ hoursStatus,
19377
+ websiteUrl: null,
19378
+ directionsUrl: `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", "))}`,
19379
+ metadata: lines.slice(0, 20)
19380
+ });
19381
+ }
19382
+ return out;
19383
+ }, fallbackLocation);
19384
+ }
19385
+ async openCardAndExtractDialog(page, card, includeServices) {
19386
+ const clicked = await page.evaluate((input) => {
19387
+ const normalize4 = (value) => (value ?? "").replace(/\s+/g, " ").trim().toLowerCase();
19388
+ const cards = Array.from(document.querySelectorAll("button")).filter(
19389
+ (button) => Boolean(button.querySelector("h3")) && !button.closest('[role="dialog"], dialog') && !/\bsponsored\b/i.test(button.innerText)
19390
+ );
19391
+ const exact = cards.find((button) => `${normalize4(button.querySelector("h3")?.textContent)}|${normalize4(button.innerText)}` === input.cardKey);
19392
+ const target = exact ?? cards[input.cardIndex];
19393
+ if (!target) return false;
19394
+ target.click();
19395
+ return true;
19396
+ }, { cardIndex: card.cardIndex, cardKey: card.cardKey });
19397
+ if (!clicked) return null;
19398
+ const dialog = page.locator('dialog, [role="dialog"]').filter({ has: page.locator('h1, h2, [role="heading"]') }).last();
19399
+ await dialog.waitFor({ state: "visible", timeout: 6e3 }).catch(() => {
19400
+ });
19401
+ if (await dialog.count() === 0 || !await dialog.isVisible().catch(() => false)) return null;
19402
+ const details = await dialog.evaluate((root, includeServicesValue) => {
19403
+ const text2 = (value) => value?.replace(/\s+/g, " ").trim() ?? "";
19404
+ const phonePattern = /(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/;
19405
+ const buttons = Array.from(root.querySelectorAll("button"));
19406
+ const buttonTexts = buttons.map((button) => text2(button.innerText)).filter(Boolean);
19407
+ const servicesText = includeServicesValue ? buttonTexts.find((value) => /^services\s*:/i.test(value)) : null;
19408
+ const services = servicesText ? [...new Set(servicesText.replace(/^services\s*:\s*/i, "").split(/\s*[,·]\s*/).map((value) => value.trim()).filter(Boolean))] : [];
19409
+ const areaText = includeServicesValue ? buttonTexts.find((value) => /^serves\b/i.test(value)) : null;
19410
+ const areasServed = areaText ? [areaText.replace(/^serves\s*/i, "").trim()].filter(Boolean) : [];
19411
+ const externalLinks = Array.from(root.querySelectorAll("a[href]"));
19412
+ const website = externalLinks.find((link) => /^website$/i.test(text2(link.innerText)) || /^website$/i.test(link.getAttribute("aria-label") ?? ""));
19413
+ const mapLink = externalLinks.find((link) => /google\.[^/]+\/maps|\/maps\//i.test(link.href));
19414
+ const addressLink = externalLinks.find((link) => /\b[A-Z]{2}\s+\d{5}(?:-\d{4})?\b|\b(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Boulevard|Dr|Drive|Ln|Lane|Way|Pkwy|Parkway)\b/i.test(text2(link.innerText)));
19415
+ const allText = text2(root.innerText);
19416
+ const rating = allText.match(/\b(?:rated\s+)?(\d(?:\.\d)?)\s+out of 5\b/i)?.[1] ?? allText.match(/\b(\d(?:\.\d)?)\s+rated\b/i)?.[1] ?? null;
19417
+ const reviewCount = allText.match(/([\d,]+)\s+(?:user\s+)?reviews?\b/i)?.[1]?.replace(/,/g, "") ?? null;
19418
+ const phoneButton = buttonTexts.find((value) => /^call\b/i.test(value) || phonePattern.test(value)) ?? "";
19419
+ const phone = phoneButton.match(phonePattern)?.[0] ?? null;
19420
+ const hoursStatus = buttonTexts.find((value) => /\b(?:open|closed|opens|closes)\b/i.test(value)) ?? null;
19421
+ const placeUrl = mapLink?.href ?? addressLink?.href ?? location.href;
19422
+ const fid = placeUrl.match(/!1s(0x[0-9a-f]+):(0x[0-9a-f]+)/i);
19423
+ const cid = fid ? `${fid[1]}:${fid[2]}` : null;
19424
+ let cidDecimal = null;
19425
+ if (fid) {
19426
+ try {
19427
+ cidDecimal = BigInt(fid[2]).toString();
19428
+ } catch {
19429
+ }
19430
+ }
19431
+ if (!cidDecimal) {
19432
+ const cidFromQuery = new URL(placeUrl).searchParams.get("cid");
19433
+ cidDecimal = cidFromQuery && /^\d+$/.test(cidFromQuery) ? cidFromQuery : null;
19434
+ }
19435
+ return {
19436
+ placeUrl,
19437
+ cid,
19438
+ cidDecimal,
19439
+ rating,
19440
+ reviewCount,
19441
+ category: null,
19442
+ address: text2(addressLink?.innerText) || null,
19443
+ phone,
19444
+ websiteUrl: website?.href ?? null,
19445
+ hoursStatus,
19446
+ services,
19447
+ areasServed
19448
+ };
19449
+ }, includeServices);
19450
+ await this.closeDialog(page);
19451
+ return details;
19452
+ }
19453
+ async closeDialog(page) {
19454
+ const closed = await page.evaluate(() => {
19455
+ const dialogs = Array.from(document.querySelectorAll('dialog, [role="dialog"]'));
19456
+ const dialog = dialogs.find((candidate) => candidate.offsetParent !== null);
19457
+ const close = dialog && Array.from(dialog.querySelectorAll("button")).find(
19458
+ (button) => /^close$/i.test(button.getAttribute("aria-label") ?? "") || /^close$/i.test(button.innerText.trim())
19459
+ );
19460
+ close?.click();
19461
+ return Boolean(close);
19462
+ });
19463
+ if (closed) await page.waitForTimeout(250);
19464
+ }
19465
+ async advanceToMoreResults(page) {
19466
+ const before = await page.evaluate(() => document.body.innerText.slice(-2e3));
19467
+ const advanced = await page.evaluate(() => {
19468
+ const visible = (element) => element.offsetParent !== null;
19469
+ const mobile = Array.from(document.querySelectorAll("button, a")).find(
19470
+ (element) => visible(element) && /^(more search results|more results)$/i.test(element.innerText.trim())
19471
+ );
19472
+ if (mobile) {
19473
+ mobile.click();
19474
+ return true;
19475
+ }
19476
+ const next = Array.from(document.querySelectorAll("a[href]")).find(
19477
+ (link) => visible(link) && (/^next$/i.test(link.innerText.trim()) || /[?&]start=\d+/.test(link.href))
19478
+ );
19479
+ if (next) {
19480
+ next.click();
19481
+ return true;
19482
+ }
19483
+ return false;
19484
+ });
19485
+ if (!advanced) return false;
19486
+ await page.waitForTimeout(LOCAL_RESULTS_WAIT_MS);
19487
+ await page.waitForFunction((previous) => document.body.innerText.slice(-2e3) !== previous, before, { timeout: 5e3 }).catch(() => {
19488
+ });
19489
+ return true;
19490
+ }
19491
+ };
19492
+ }
19493
+ });
19494
+
19230
19495
  // src/extractor/MapsNavigator.ts
19231
19496
  var MapsNavigator;
19232
19497
  var init_MapsNavigator = __esm({
19233
19498
  "src/extractor/MapsNavigator.ts"() {
19234
19499
  "use strict";
19235
19500
  init_selectors();
19501
+ init_MapsSearchExtractor();
19236
19502
  MapsNavigator = class {
19237
19503
  constructor(page) {
19238
19504
  this.page = page;
@@ -19290,32 +19556,26 @@ var init_MapsNavigator = __esm({
19290
19556
  await this.page.waitForSelector('[role="main"] h2', { timeout: 5e3 }).catch(() => {
19291
19557
  });
19292
19558
  }
19293
- async navigateToSerpBusinessPanel(businessName, location2) {
19294
- const query = `${businessName} ${location2}`;
19295
- const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&udm=1`;
19559
+ async navigateToSerpBusinessPanel(businessName, location2, gl, hl) {
19560
+ const searchUrl = buildGoogleLocalResultsUrl({ query: businessName, location: location2, gl, hl });
19296
19561
  await this.page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 45e3 }).catch(() => {
19297
19562
  });
19298
- await this.page.waitForTimeout(2e3);
19563
+ await this.page.waitForTimeout(1e3);
19299
19564
  const clicked = await this.page.evaluate((name) => {
19300
- const trimmedName = name.trim();
19301
- const words = trimmedName.split(/\s+/);
19302
- const prefix = words.slice(0, Math.min(2, words.length)).join(" ");
19303
- const headings = Array.from(document.querySelectorAll('h3, div[role="heading"]'));
19304
- const heading = headings.find((el2) => el2.innerText?.trim() === trimmedName) ?? headings.find((el2) => el2.innerText?.trim().startsWith(prefix));
19305
- const clickable = heading?.closest('a, div[role="link"], div[jsaction]');
19306
- if (clickable) {
19307
- clickable.click();
19308
- return true;
19309
- }
19310
- if (heading) {
19311
- heading.click();
19565
+ const normalized = name.replace(/\s+/g, " ").trim().toLowerCase();
19566
+ const card = Array.from(document.querySelectorAll("button")).find(
19567
+ (button) => button.querySelector("h3")?.textContent?.replace(/\s+/g, " ").trim().toLowerCase() === normalized
19568
+ );
19569
+ if (card) {
19570
+ card.click();
19312
19571
  return true;
19313
19572
  }
19314
19573
  return false;
19315
19574
  }, businessName);
19316
19575
  if (!clicked) return false;
19317
- await this.page.waitForTimeout(2e3);
19318
- return true;
19576
+ await this.page.waitForSelector('dialog, [role="dialog"]', { timeout: 5e3 }).catch(() => {
19577
+ });
19578
+ return this.page.locator('dialog, [role="dialog"]').count().then((count) => count > 0);
19319
19579
  }
19320
19580
  buildReviewsUrl(placeUrl) {
19321
19581
  const fid = placeUrl.match(/!1s(0x[0-9a-f]+:0x[0-9a-f]+)/i);
@@ -19508,7 +19768,7 @@ var init_MapsExtractor = __esm({
19508
19768
  let servicesStatus = "not_requested";
19509
19769
  if (options.includeServices) {
19510
19770
  try {
19511
- const opened = await navigator.navigateToSerpBusinessPanel(options.businessName, options.location);
19771
+ const opened = await navigator.navigateToSerpBusinessPanel(options.businessName, options.location, options.gl, options.hl);
19512
19772
  if (opened) {
19513
19773
  const result = await this.extractServicesAndAreasServed(page);
19514
19774
  services = result.services;
@@ -19729,249 +19989,15 @@ var init_MapsExtractor = __esm({
19729
19989
  return result.data;
19730
19990
  }
19731
19991
  async extractServicesAndAreasServed(page) {
19732
- await page.waitForTimeout(1500);
19733
- let services = [];
19734
- if (await this.clickRowStartingWithRetry(page, "Services:")) {
19735
- await page.waitForSelector('[role="dialog"]', { timeout: 5e3 }).catch(() => {
19736
- });
19737
- await page.waitForTimeout(800);
19738
- services = await this.extractOpenDialogItems(page, "Services");
19739
- await this.closeOpenDialog(page, "Services");
19740
- await page.waitForTimeout(500);
19741
- }
19742
- let areasServed = [];
19743
- if (await this.clickRowStartingWithRetry(page, "Serves ")) {
19744
- await page.waitForSelector('[role="dialog"]', { timeout: 5e3 }).catch(() => {
19745
- });
19746
- await page.waitForTimeout(800);
19747
- areasServed = await this.extractOpenDialogItems(page, "Areas Served");
19748
- await this.closeOpenDialog(page, "Areas Served");
19749
- }
19750
- return { services, areasServed };
19751
- }
19752
- async clickRowStartingWithRetry(page, prefix) {
19753
- if (await this.clickRowStartingWith(page, prefix)) return true;
19754
- await page.waitForTimeout(2e3);
19755
- return this.clickRowStartingWith(page, prefix);
19756
- }
19757
- async clickRowStartingWith(page, prefix) {
19758
- return page.evaluate((p) => {
19759
- const el2 = Array.from(document.querySelectorAll('button, a, div[role="button"]')).find(
19760
- (e) => (e.innerText ?? "").trim().startsWith(p)
19761
- );
19762
- if (!el2) return false;
19763
- el2.click();
19764
- return true;
19765
- }, prefix);
19766
- }
19767
- async extractOpenDialogItems(page, expectedHeading) {
19768
- return page.evaluate((expected) => {
19769
- const dialogs = Array.from(document.querySelectorAll('[role="dialog"]'));
19770
- const withHeading = dialogs.find((d) => {
19771
- const h = d.querySelector('h1, h2, [role="heading"]');
19772
- const headingText = h?.innerText?.trim() ?? "";
19773
- return headingText.startsWith(expected);
19774
- });
19775
- if (!withHeading) return [];
19776
- const headingEl = withHeading.querySelector('h1, h2, [role="heading"]');
19777
- const headingLines = new Set(
19778
- (headingEl?.innerText ?? "").split("\n").map((s) => s.trim()).filter(Boolean)
19779
- );
19780
- const seen = /* @__PURE__ */ new Set();
19781
- const items = [];
19782
- withHeading.querySelectorAll("*").forEach((el2) => {
19783
- if (el2.children.length > 0) return;
19784
- const text2 = el2.innerText?.trim() ?? el2.textContent?.trim() ?? "";
19785
- if (!text2 || headingLines.has(text2)) return;
19786
- if (/^(back|close|done|thanks)$/i.test(text2)) return;
19787
- if (/^serves\s.*following/i.test(text2)) return;
19788
- if (seen.has(text2)) return;
19789
- seen.add(text2);
19790
- items.push(text2);
19791
- });
19792
- return items;
19793
- }, expectedHeading);
19794
- }
19795
- async closeOpenDialog(page, expectedHeading) {
19796
- await page.evaluate((expected) => {
19797
- const dialog = Array.from(document.querySelectorAll('[role="dialog"]')).find((d) => {
19798
- const h = d.querySelector('h1, h2, [role="heading"]');
19799
- const headingText = h?.innerText?.trim() ?? "";
19800
- return headingText.startsWith(expected);
19801
- });
19802
- const closeBtn = dialog?.querySelector('[aria-label*="Close" i], button[aria-label*="close" i]');
19803
- closeBtn?.click();
19804
- }, expectedHeading);
19805
- }
19806
- };
19807
- }
19808
- });
19809
-
19810
- // src/extractor/MapsSearchExtractor.ts
19811
- var MAPS_SEARCH_SCROLL_BUDGET_MS, MAPS_SEARCH_SCROLL_STEP_MS, MAPS_SEARCH_MAX_NO_GROWTH_ROUNDS, MapsSearchExtractor;
19812
- var init_MapsSearchExtractor = __esm({
19813
- "src/extractor/MapsSearchExtractor.ts"() {
19814
- "use strict";
19815
- init_errors();
19816
- MAPS_SEARCH_SCROLL_BUDGET_MS = 6e4;
19817
- MAPS_SEARCH_SCROLL_STEP_MS = 1200;
19818
- MAPS_SEARCH_MAX_NO_GROWTH_ROUNDS = 4;
19819
- MapsSearchExtractor = class {
19820
- constructor(driver) {
19821
- this.driver = driver;
19822
- }
19823
- driver;
19824
- async extract(options) {
19825
- const startMs = Date.now();
19826
- const searchQuery = [options.query, options.location].filter(Boolean).join(" ");
19827
- const searchUrl = `https://www.google.com/maps/search/${encodeURIComponent(searchQuery)}?hl=${encodeURIComponent(options.hl)}`;
19828
- const config = {
19829
- headless: options.headless,
19830
- kernelApiKey: options.kernelApiKey,
19831
- kernelProxyId: options.kernelProxyId,
19832
- kernelProxyResolution: options.kernelProxyResolution,
19833
- proxyMode: options.proxyMode,
19834
- viewport: { width: 1280, height: 900 },
19835
- locale: `${options.hl}-${options.gl.toUpperCase()}`,
19836
- debug: options.debug
19837
- };
19838
- try {
19839
- await this.driver.launch(config);
19840
- const page = this.driver.getPage();
19841
- await page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 6e4 });
19842
- await page.waitForTimeout(3e3);
19843
- const blocked = await this.detectBlock(page);
19844
- if (blocked) throw new CaptchaError(RECAPTCHA_INSTRUCTIONS);
19845
- const results = await this.collectResults(page, options.maxResults);
19846
- return {
19847
- query: options.query,
19848
- location: options.location ?? null,
19849
- searchQuery,
19850
- searchUrl,
19851
- extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
19852
- requestedMaxResults: options.maxResults,
19853
- resultCount: results.length,
19854
- results,
19855
- durationMs: Date.now() - startMs
19856
- };
19857
- } finally {
19858
- await this.driver.close();
19859
- }
19860
- }
19861
- async detectBlock(page) {
19862
19992
  return page.evaluate(() => {
19863
- const text2 = document.body.innerText.slice(0, 2e3);
19864
- return /unusual traffic|captcha|recaptcha|about this page/i.test(text2) || /\/sorry\//.test(location.href);
19865
- });
19866
- }
19867
- async collectResults(page, maxResults) {
19868
- const seen = /* @__PURE__ */ new Map();
19869
- const started = Date.now();
19870
- let noGrowthRounds = 0;
19871
- while (Date.now() - started < MAPS_SEARCH_SCROLL_BUDGET_MS) {
19872
- const before = seen.size;
19873
- const batch = await this.extractVisibleResults(page);
19874
- for (const result of batch) {
19875
- const key = this.resultKey(result);
19876
- if (!seen.has(key)) seen.set(key, { ...result, position: seen.size + 1 });
19877
- if (seen.size >= maxResults) break;
19878
- }
19879
- if (seen.size >= maxResults) break;
19880
- if (seen.size === before) noGrowthRounds += 1;
19881
- else noGrowthRounds = 0;
19882
- if (noGrowthRounds >= MAPS_SEARCH_MAX_NO_GROWTH_ROUNDS) break;
19883
- await page.evaluate(() => {
19884
- const feed = document.querySelector('[role="feed"]');
19885
- if (feed) {
19886
- feed.scrollTop = feed.scrollHeight;
19887
- } else {
19888
- window.scrollTo(0, document.body.scrollHeight);
19889
- }
19890
- });
19891
- await page.waitForTimeout(MAPS_SEARCH_SCROLL_STEP_MS);
19892
- }
19893
- return [...seen.values()].slice(0, maxResults);
19894
- }
19895
- resultKey(result) {
19896
- return result.cidDecimal ?? result.placeUrl.replace(/[?&].*$/, "") ?? result.name;
19897
- }
19898
- async extractVisibleResults(page) {
19899
- return page.evaluate(() => {
19900
- function normalizeText(value) {
19901
- const text2 = value?.replace(/\s+/g, " ").trim() ?? "";
19902
- return text2 || null;
19903
- }
19904
- function cidFromUrl(url) {
19905
- const fid = url.match(/!1s(0x[0-9a-f]+):(0x[0-9a-f]+)/i);
19906
- if (!fid) return { cid: null, cidDecimal: null };
19907
- let cidDecimal = null;
19908
- try {
19909
- cidDecimal = BigInt(fid[2]).toString();
19910
- } catch {
19911
- }
19912
- return { cid: `${fid[1]}:${fid[2]}`, cidDecimal };
19913
- }
19914
- function textParts(card) {
19915
- if (!card) return [];
19916
- const parts = [];
19917
- card.querySelectorAll("div, span").forEach((el2) => {
19918
- const text2 = Array.from(el2.childNodes).filter((node) => node.nodeType === 3).map((node) => node.textContent?.trim() ?? "").filter((text3) => text3.length > 1 && text3.length < 140).join(" ");
19919
- if (text2 && !parts.includes(text2)) parts.push(text2);
19920
- });
19921
- return parts;
19922
- }
19923
- function firstMatching(parts, pattern) {
19924
- const value = parts.find((part) => pattern.test(part));
19925
- return value ?? null;
19926
- }
19927
- function normalizedSet(values) {
19928
- return new Set(values.filter(Boolean).map((value) => value.toLowerCase()));
19929
- }
19930
- const out = [];
19931
- const seen = /* @__PURE__ */ new Set();
19932
- const anchors = Array.from(document.querySelectorAll('a[href*="/maps/place/"]'));
19933
- for (const anchor of anchors) {
19934
- const placeUrl = anchor.href;
19935
- const stableUrl = placeUrl.replace(/[?&].*$/, "");
19936
- if (seen.has(stableUrl)) continue;
19937
- seen.add(stableUrl);
19938
- const card = anchor.closest('.Nv2PK, [role="article"], .bfdHYd') ?? anchor.parentElement;
19939
- const parts = textParts(card);
19940
- const aria = normalizeText(anchor.getAttribute("aria-label"));
19941
- const heading = normalizeText(card?.querySelector('.qBF1Pd, .fontHeadlineSmall, [role="heading"]')?.textContent);
19942
- const name = aria ?? heading ?? parts[0] ?? stableUrl;
19943
- const links = Array.from(card?.querySelectorAll("a[href]") ?? []);
19944
- const websiteUrl = links.find((link) => link.href.startsWith("http") && !link.href.includes("google."))?.href ?? null;
19945
- const rating = firstMatching(parts, /^\d(?:\.\d)?$/);
19946
- const reviewCountRaw = firstMatching(parts, /^\(?[\d,]+\)?(?:\s+reviews?)?$/i);
19947
- const phone = firstMatching(parts, /(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/);
19948
- const hoursStatus = parts.find((part) => /^(open|closed|closes|opens)\b|^·\s*(opens|closes)\b/i.test(part)) ?? null;
19949
- const address = parts.find((part) => /\b[A-Z]{2}\s+\d{5}\b|\b(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Drive|Dr)\b/i.test(part)) ?? null;
19950
- const directionsUrl = links.find((link) => /google\.[^/]+\/maps\/dir|\/dir\//i.test(link.href))?.href ?? `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent([name, address].filter(Boolean).join(", ") || name)}`;
19951
- const excluded = normalizedSet([name, rating, reviewCountRaw, phone, hoursStatus, address, "Website", "Directions"]);
19952
- const category = parts.find((part) => {
19953
- const normalized = part.toLowerCase();
19954
- return !excluded.has(normalized) && !/^\d(?:\.\d)?$|^\(?[\d,]+\)?(?:\s+reviews?)?$/i.test(part) && !/(?:\+?1[\s.-]?)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}/.test(part) && !/\b[A-Z]{2}\s+\d{5}\b|\b(?:St|Street|Ave|Avenue|Rd|Road|Blvd|Drive|Dr)\b/i.test(part) && !/^(open|closed|closes|opens)\b|^·\s*(opens|closes)\b|directions|website|book online|sponsored|visit site|financing/i.test(part);
19955
- }) ?? null;
19956
- const { cid, cidDecimal } = cidFromUrl(placeUrl);
19957
- out.push({
19958
- position: out.length + 1,
19959
- name,
19960
- placeUrl,
19961
- cid,
19962
- cidDecimal,
19963
- rating,
19964
- reviewCount: reviewCountRaw ? reviewCountRaw.replace(/[()]/g, "") : null,
19965
- category,
19966
- address,
19967
- phone,
19968
- hoursStatus,
19969
- websiteUrl,
19970
- directionsUrl,
19971
- metadata: parts.slice(0, 20)
19972
- });
19973
- }
19974
- return out;
19993
+ const visible = Array.from(document.querySelectorAll('dialog, [role="dialog"]')).find((dialog) => dialog.offsetParent !== null);
19994
+ if (!visible) return { services: [], areasServed: [] };
19995
+ const values = Array.from(visible.querySelectorAll("button")).map((button) => button.innerText.replace(/\s+/g, " ").trim()).filter(Boolean);
19996
+ const servicesText = values.find((value) => /^services\s*:/i.test(value));
19997
+ const services = servicesText ? [...new Set(servicesText.replace(/^services\s*:\s*/i, "").split(/\s*[,·]\s*/).map((value) => value.trim()).filter(Boolean))] : [];
19998
+ const served = values.find((value) => /^serves\b/i.test(value));
19999
+ const areasServed = served ? [served.replace(/^serves\s*/i, "").trim()].filter(Boolean) : [];
20000
+ return { services, areasServed };
19975
20001
  });
19976
20002
  }
19977
20003
  };
@@ -22721,7 +22747,10 @@ function formatMapsSearch(raw, input) {
22721
22747
  const normalizedResults = results.map((result) => ({
22722
22748
  ...result,
22723
22749
  phone: result.phone ?? null,
22724
- hoursStatus: result.hoursStatus ?? null
22750
+ hoursStatus: result.hoursStatus ?? null,
22751
+ services: result.services ?? [],
22752
+ areasServed: result.areasServed ?? [],
22753
+ profileDetailsStatus: result.profileDetailsStatus ?? "unavailable"
22725
22754
  }));
22726
22755
  const searchQuery = d.searchQuery ?? [input.query, input.location].filter(Boolean).join(" ");
22727
22756
  const requestedMax = d.requestedMaxResults ?? input.maxResults ?? 10;
@@ -22749,9 +22778,14 @@ ${meta}`;
22749
22778
  |---|------|----------|--------|---------|-----|---------|------|
22750
22779
  ${rows}`,
22751
22780
  metadataSection,
22781
+ results.some((result) => result.services?.length || result.areasServed?.length) ? `
22782
+ ## Services and Areas Served
22783
+ ${results.filter((result) => result.services?.length || result.areasServed?.length).map((result) => `### ${result.position}. ${result.name}
22784
+ ${result.services?.length ? `- **Services:** ${result.services.join(", ")}` : ""}${result.areasServed?.length ? `
22785
+ - **Serves:** ${result.areasServed.join("; ")}` : ""}`).join("\n\n")}` : null,
22752
22786
  `
22753
22787
  ---
22754
- \u{1F4A1} **Next step:** use \`maps_place_intel\` with a selected business name and location to hydrate full hours, phone, review topics, and optional review cards.`,
22788
+ \u{1F4A1} **Next step:** use \`maps_place_intel\` with a selected business name and location for full hours, review topics, and optional review cards. Set \`includeServices: true\` on this search to enrich the ranked list in one local-results session.`,
22755
22789
  durationMs != null ? `
22756
22790
  *Extracted in ${(durationMs / 1e3).toFixed(1)}s*` : null
22757
22791
  ].filter(Boolean).join("\n");
@@ -23802,6 +23836,7 @@ async function searchCity(options, market) {
23802
23836
  gl: options.gl,
23803
23837
  hl: options.hl,
23804
23838
  maxResults: options.maxResultsPerCity,
23839
+ includeServices: false,
23805
23840
  headless: options.headless,
23806
23841
  kernelApiKey: options.kernelApiKey,
23807
23842
  proxyMode: options.proxyMode,
@@ -29688,7 +29723,7 @@ var PACKAGE_VERSION;
29688
29723
  var init_version = __esm({
29689
29724
  "src/version.ts"() {
29690
29725
  "use strict";
29691
- PACKAGE_VERSION = "0.26.3";
29726
+ PACKAGE_VERSION = "0.28.0";
29692
29727
  }
29693
29728
  });
29694
29729
 
@@ -30279,8 +30314,9 @@ var init_mcp_tool_schemas = __esm({
30279
30314
  gl: import_zod35.z.string().length(2).default("us").describe("Google country code inferred from location."),
30280
30315
  hl: import_zod35.z.string().length(2).default("en").describe("Language inferred from user request."),
30281
30316
  maxResults: import_zod35.z.number().int().min(1).max(50).default(10).describe("Number of candidates to return. Default 10, maximum 50."),
30282
- proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Defaults to location (city/state residential proxy targeting). configured forces the service proxy without city/ZIP targeting; none is local debugging only."),
30283
- proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential proxy targeting."),
30317
+ includeServices: import_zod35.z.boolean().default(false).describe("Open each returned Google Business Profile and include configured services and areas served when available. Does not collect review cards."),
30318
+ proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Leave unset for the default direct browser route. Google localization comes from the city in the query plus UULE, gl, and hl. location is an explicit residential-proxy override."),
30319
+ proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override when proxyMode is location."),
30284
30320
  debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
30285
30321
  };
30286
30322
  DirectoryWorkflowInputSchema = {
@@ -30294,7 +30330,7 @@ var init_mcp_tool_schemas = __esm({
30294
30330
  includeZipGroups: import_zod35.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available (MCP_SCRAPER_USZIPS_CSV_PATH or usZipsCsvPath)."),
30295
30331
  usZipsCsvPath: import_zod35.z.string().optional().describe("Local/test-only path to a US ZIPS CSV (state_abbr, zipcode, county, city columns). Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead. For ZIP enrichment, set MCP_SCRAPER_USZIPS_CSV_PATH on the server, or pass this in local/test mode."),
30296
30332
  saveCsv: import_zod35.z.boolean().default(true).describe("Save a directory-ready CSV of results to the MCP Scraper output directory and return its path."),
30297
- proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting per city search. Defaults to location (city/ZIP-group residential targeting); configured forces the service proxy; none is local debugging only."),
30333
+ proxyMode: import_zod35.z.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy behavior per city search. Leave unset for the direct localized Google route; location is an explicit residential-proxy override."),
30298
30334
  proxyZip: import_zod35.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting; normally omitted."),
30299
30335
  debug: import_zod35.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics.")
30300
30336
  };
@@ -30365,7 +30401,10 @@ var init_mcp_tool_schemas = __esm({
30365
30401
  hoursStatus: NullableString,
30366
30402
  websiteUrl: NullableString,
30367
30403
  directionsUrl: NullableString,
30368
- metadata: import_zod35.z.array(import_zod35.z.string())
30404
+ metadata: import_zod35.z.array(import_zod35.z.string()),
30405
+ services: import_zod35.z.array(import_zod35.z.string()).default([]),
30406
+ areasServed: import_zod35.z.array(import_zod35.z.string()).default([]),
30407
+ profileDetailsStatus: import_zod35.z.enum(["collected", "none_exist", "unavailable", "not_requested"]).default("unavailable")
30369
30408
  })),
30370
30409
  attempts: import_zod35.z.array(MapsSearchAttemptOutput),
30371
30410
  durationMs: import_zod35.z.number().int().min(0)
@@ -32111,7 +32150,7 @@ function registerPaaExtractorMcpTools(server, executor, options = {}) {
32111
32150
  }, async (input) => formatMapsPlaceIntel(await executor.mapsPlaceIntel(input), input));
32112
32151
  server.registerTool("maps_search", {
32113
32152
  title: "Google Maps Business Search",
32114
- description: "Search Google Maps for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack. Leave proxyMode unset. Returns up to 50 candidates (default 10) with names, place URLs, CIDs, ratings.",
32153
+ description: "Search Google local results for multiple businesses by category, niche, or local market \u2014 leads, prospects, competitors, or beyond the 3-pack. It paginates the rendered local-results list, opens each exact business card, then closes the profile dialog before continuing. Set includeServices for services and areas served; review cards are never collected by this tool.",
32115
32154
  inputSchema: MapsSearchInputSchema,
32116
32155
  outputSchema: recordOutputSchema("maps_search", MapsSearchOutputSchema),
32117
32156
  annotations: liveWebToolAnnotations("Google Maps Business Search")
@@ -34331,7 +34370,7 @@ var init_browser_agent_mcp_server = __esm({
34331
34370
  });
34332
34371
 
34333
34372
  // src/mcp/memory-tool-schemas.ts
34334
- var import_zod37, AcceptShareSchema, ApproveSenderSchema, DeclineShareSchema, GetChatLinkSchema, InboxSettingsSchema, InviteAccountSchema, issueKeyTool_scopeShape, IssueKeySchema, ListApprovedSendersSchema, ListKeysSchema, NoteInboxSchema, RemoveApprovedSenderSchema, RevokeChatLinkSchema, RevokeKeySchema, RevokeShareSchema, SetAgentIdentitySchema, SetScopeSchema, ShareNoteSchema, ShareVaultSchema, SwapVaultSchema, SwitchAccountSchema, UnlinkShareSchema, memoryCaptureTool_notePropsSchema, MemoryCaptureSchema, MemoryQuestionsSchema, PrepareMemoryWriteSchema, validateMemoryWriteTool_notePropsSchema, ValidateMemoryWriteSchema, CreateChannelSchema, GetMessageNoteSchema, ListChannelMembersSchema, ListChannelMessagesSchema, MyMentionsSchema, PollChannelSchema, PostMessageSchema, ReactMessageSchema, RemoveChannelMemberSchema, ReplyMessageSchema, factHistoryTool_entryShape, FactHistorySchema, recordFactTool_factShape, RecordFactSchema, NoteBacklinksSchema, GraphPathSchema, GraphUniverseSchema, LibraryIngestSchema, bulkDeleteNotesTool_FilterSchema, BulkDeleteNotesSchema, DeleteNoteSchema, ExportSchema, getTool_notePropsSchema, GetSchema, ListSchema, putTool_notePropsSchema, PutSchema, searchTool_primitiveValue, SearchSchema, SuggestSchema, UploadSchema, TemporalRecallSchema, CreateScheduledActionSchema, DeleteScheduledActionSchema, GetScheduleLinkSchema, GetScheduleStatusSchema, ListScheduledActionsSchema, PauseScheduledActionSchema, ProposeScheduledActionSchema, ResumeScheduledActionSchema, RevokeScheduleLinkSchema, SetScheduleDefaultsSchema, SetScheduleEntitlementSchema, CostUsageSchema, StorageUsageSchema, CreateTableSchema, deleteTableRowsTool_FilterSchema, DeleteTableRowsSchema, DescribeTableSchema, DropTableSchema, InsertTableRowsSchema, ListTablesSchema, queryTableTool_FilterSchema, QueryTableSchema, ListTagsSchema, ResolveTagsSchema, UpsertTagSchema, AddVaultSchema, CreateSecureVaultSchema, DeleteVaultSchema, GetVaultContractSchema, ListSharedWithMeSchema, ListVaultsSchema, ProvisionDefaultsSchema, RouteMemorySchema, VideoAnalyzeStartSchema, VideoAnalyzeStatusSchema, CreateWebhookSchema, ListWebhooksSchema, RevokeWebhookSchema, MEMORY_TOOL_SCHEMAS;
34373
+ var import_zod37, AcceptShareSchema, ApproveSenderSchema, DeclineShareSchema, GetChatLinkSchema, GetVaultAppLinkSchema, InboxSettingsSchema, InviteAccountSchema, issueKeyTool_scopeShape, IssueKeySchema, ListApprovedSendersSchema, ListKeysSchema, NoteInboxSchema, RemoveApprovedSenderSchema, RevokeChatLinkSchema, RevokeKeySchema, RevokeShareSchema, RevokeVaultAppLinkSchema, SetAgentIdentitySchema, SetScopeSchema, ShareNoteSchema, ShareVaultSchema, SwapVaultSchema, SwitchAccountSchema, UnlinkShareSchema, memoryCaptureTool_notePropsSchema, MemoryCaptureSchema, MemoryQuestionsSchema, PrepareMemoryWriteSchema, validateMemoryWriteTool_notePropsSchema, ValidateMemoryWriteSchema, CreateChannelSchema, GetMessageNoteSchema, ListChannelMembersSchema, ListChannelMessagesSchema, MyMentionsSchema, PollChannelSchema, PostMessageSchema, ReactMessageSchema, RemoveChannelMemberSchema, ReplyMessageSchema, factHistoryTool_entryShape, FactHistorySchema, recordFactTool_factShape, RecordFactSchema, NoteBacklinksSchema, GraphPathSchema, GraphUniverseSchema, LibraryIngestSchema, bulkDeleteNotesTool_FilterSchema, BulkDeleteNotesSchema, DeleteNoteSchema, ExportSchema, getTool_notePropsSchema, GetSchema, ListSchema, putTool_notePropsSchema, PutSchema, searchTool_primitiveValue, SearchSchema, SuggestSchema, UploadSchema, TemporalRecallSchema, CreateScheduledActionSchema, DeleteScheduledActionSchema, GetScheduleLinkSchema, GetScheduleStatusSchema, ListScheduledActionsSchema, PauseScheduledActionSchema, ProposeScheduledActionSchema, ResumeScheduledActionSchema, RevokeScheduleLinkSchema, SetScheduleDefaultsSchema, SetScheduleEntitlementSchema, CostUsageSchema, StorageUsageSchema, CreateTableSchema, deleteTableRowsTool_FilterSchema, DeleteTableRowsSchema, DescribeTableSchema, DropTableSchema, InsertTableRowsSchema, ListTablesSchema, queryTableTool_FilterSchema, QueryTableSchema, ListTagsSchema, ResolveTagsSchema, UpsertTagSchema, AddVaultSchema, CreateSecureVaultSchema, DeleteVaultSchema, GetVaultContractSchema, ListSharedWithMeSchema, ListVaultsSchema, ProvisionDefaultsSchema, RouteMemorySchema, VideoAnalyzeStartSchema, VideoAnalyzeStatusSchema, CreateWebhookSchema, ListWebhooksSchema, RevokeWebhookSchema, MEMORY_TOOL_SCHEMAS;
34335
34374
  var init_memory_tool_schemas = __esm({
34336
34375
  "src/mcp/memory-tool-schemas.ts"() {
34337
34376
  "use strict";
@@ -34416,6 +34455,25 @@ var init_memory_tool_schemas = __esm({
34416
34455
  openWorldHint: false
34417
34456
  }
34418
34457
  };
34458
+ GetVaultAppLinkSchema = {
34459
+ id: "get-vault-app-link",
34460
+ upstreamName: "getVaultAppLinkTool",
34461
+ description: "Get the durable, bookmarkable link to the mobile-first Vault App for People, Projects, and Tasks. The embedded secret is shown only once; revoke-vault-app-link then call this tool again to replace a link that was shared or leaked. This link is independent from Omni-Chat. Anyone holding it can use the Vault App as this identity.",
34462
+ input: {},
34463
+ output: {
34464
+ ok: import_zod37.z.boolean().describe("True when the request succeeded."),
34465
+ url: import_zod37.z.string().optional().describe("Vault App link. Present only when the secret is first minted; anyone holding it can use the Vault App as this identity."),
34466
+ alreadyExists: import_zod37.z.boolean().optional().describe("True when an active Vault App link exists but its secret cannot be shown again."),
34467
+ error: import_zod37.z.string().optional().describe("Human-readable error when the request cannot be completed.")
34468
+ },
34469
+ annotations: {
34470
+ title: "Get Vault App Link",
34471
+ readOnlyHint: false,
34472
+ destructiveHint: false,
34473
+ idempotentHint: false,
34474
+ openWorldHint: false
34475
+ }
34476
+ };
34419
34477
  InboxSettingsSchema = {
34420
34478
  id: "access-inbox-settings",
34421
34479
  upstreamName: "inboxSettingsTool",
@@ -34664,6 +34722,24 @@ var init_memory_tool_schemas = __esm({
34664
34722
  openWorldHint: false
34665
34723
  }
34666
34724
  };
34725
+ RevokeVaultAppLinkSchema = {
34726
+ id: "revoke-vault-app-link",
34727
+ upstreamName: "revokeVaultAppLinkTool",
34728
+ description: "Immediately revoke the current Vault App link without touching the separate Omni-Chat link. Call get-vault-app-link afterward to mint a replacement.",
34729
+ input: {},
34730
+ output: {
34731
+ ok: import_zod37.z.boolean().describe("True when the request succeeded."),
34732
+ revoked: import_zod37.z.boolean().optional().describe("True when an active Vault App link was revoked."),
34733
+ error: import_zod37.z.string().optional().describe("Human-readable error when the request cannot be completed.")
34734
+ },
34735
+ annotations: {
34736
+ title: "Revoke Vault App Link",
34737
+ readOnlyHint: false,
34738
+ destructiveHint: false,
34739
+ idempotentHint: true,
34740
+ openWorldHint: false
34741
+ }
34742
+ };
34667
34743
  SetAgentIdentitySchema = {
34668
34744
  id: "set-agent-identity",
34669
34745
  upstreamName: "setAgentIdentityTool",
@@ -36639,6 +36715,7 @@ var init_memory_tool_schemas = __esm({
36639
36715
  ApproveSenderSchema,
36640
36716
  DeclineShareSchema,
36641
36717
  GetChatLinkSchema,
36718
+ GetVaultAppLinkSchema,
36642
36719
  InboxSettingsSchema,
36643
36720
  InviteAccountSchema,
36644
36721
  IssueKeySchema,
@@ -36649,6 +36726,7 @@ var init_memory_tool_schemas = __esm({
36649
36726
  RevokeChatLinkSchema,
36650
36727
  RevokeKeySchema,
36651
36728
  RevokeShareSchema,
36729
+ RevokeVaultAppLinkSchema,
36652
36730
  SetAgentIdentitySchema,
36653
36731
  SetScopeSchema,
36654
36732
  ShareNoteSchema,
@@ -37251,6 +37329,14 @@ function registerBillingCutovers() {
37251
37329
  const result = await import_revoke_chat_link.revokeChatLinkTool.execute({ ...input, apiKey }, {});
37252
37330
  return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
37253
37331
  });
37332
+ registerCutover("getVaultAppLinkTool", async (input, apiKey) => {
37333
+ const result = await import_get_vault_app_link.getVaultAppLinkTool.execute({ ...input, apiKey }, {});
37334
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
37335
+ });
37336
+ registerCutover("revokeVaultAppLinkTool", async (input, apiKey) => {
37337
+ const result = await import_revoke_vault_app_link.revokeVaultAppLinkTool.execute({ ...input, apiKey }, {});
37338
+ return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
37339
+ });
37254
37340
  registerCutover("provisionDefaultsTool", async (input, apiKey) => {
37255
37341
  const result = await import_provision_defaults2.provisionDefaultsTool.execute({ ...input, apiKey }, {});
37256
37342
  return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
@@ -37288,7 +37374,7 @@ function registerBillingCutovers() {
37288
37374
  return { content: [{ type: "text", text: JSON.stringify(result ?? {}) }], structuredContent: result ?? {}, isError: false };
37289
37375
  });
37290
37376
  }
37291
- var import_issue_key, import_list_keys, import_revoke_key, import_set_scope, import_invite_account, import_get_chat_link, import_revoke_chat_link, import_provision_defaults2, import_create_secure_vault, import_create_scheduled_action, import_get_schedule_status, import_get_schedule_link, import_revoke_schedule_link, import_set_schedule_entitlement2, import_create_webhook, import_revoke_webhook, registered3;
37377
+ var import_issue_key, import_list_keys, import_revoke_key, import_set_scope, import_invite_account, import_get_chat_link, import_revoke_chat_link, import_get_vault_app_link, import_revoke_vault_app_link, import_provision_defaults2, import_create_secure_vault, import_create_scheduled_action, import_get_schedule_status, import_get_schedule_link, import_revoke_schedule_link, import_set_schedule_entitlement2, import_create_webhook, import_revoke_webhook, registered3;
37292
37378
  var init_billing = __esm({
37293
37379
  "src/mcp/memory-cutover/billing.ts"() {
37294
37380
  "use strict";
@@ -37300,6 +37386,8 @@ var init_billing = __esm({
37300
37386
  import_invite_account = require("mcpscraper-memory-tools/tools/access/invite-account");
37301
37387
  import_get_chat_link = require("mcpscraper-memory-tools/tools/access/get-chat-link");
37302
37388
  import_revoke_chat_link = require("mcpscraper-memory-tools/tools/access/revoke-chat-link");
37389
+ import_get_vault_app_link = require("mcpscraper-memory-tools/tools/access/get-vault-app-link");
37390
+ import_revoke_vault_app_link = require("mcpscraper-memory-tools/tools/access/revoke-vault-app-link");
37303
37391
  import_provision_defaults2 = require("mcpscraper-memory-tools/tools/vaults/provision-defaults");
37304
37392
  import_create_secure_vault = require("mcpscraper-memory-tools/tools/vaults/create-secure-vault");
37305
37393
  import_create_scheduled_action = require("mcpscraper-memory-tools/tools/schedule/create-scheduled-action");
@@ -41085,6 +41173,158 @@ var init_chat_routes = __esm({
41085
41173
  }
41086
41174
  });
41087
41175
 
41176
+ // src/api/vault-routes.ts
41177
+ function validToken(token) {
41178
+ return !!token && token.startsWith("mk_") && token.length > 20 && token.length < 256;
41179
+ }
41180
+ function isVault(value) {
41181
+ return typeof value === "string" && VAULTS.includes(value);
41182
+ }
41183
+ function stringValue(value, max = 1e4) {
41184
+ if (typeof value !== "string") return null;
41185
+ const trimmed = value.trim();
41186
+ return trimmed && trimmed.length <= max ? trimmed : null;
41187
+ }
41188
+ function propsValue(value) {
41189
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
41190
+ const entries = Object.entries(value);
41191
+ if (entries.length > 80) return null;
41192
+ return Object.fromEntries(entries.filter(([key]) => key.length <= 96));
41193
+ }
41194
+ function cors(c) {
41195
+ const origin = c.req.header("origin");
41196
+ const configured = (process.env.VAULT_APP_ALLOWED_ORIGINS ?? "https://vault.mcpscraper.dev").split(",").map((value) => value.trim()).filter(Boolean);
41197
+ const local = !!origin && /^http:\/\/localhost(?::\d+)?$/.test(origin);
41198
+ if (origin && (configured.includes(origin) || local)) c.header("Access-Control-Allow-Origin", origin);
41199
+ c.header("Vary", "Origin");
41200
+ c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
41201
+ c.header("Access-Control-Allow-Headers", "content-type");
41202
+ }
41203
+ function workspaceRecord(vault, note) {
41204
+ const props = note.props ?? {};
41205
+ return {
41206
+ id: `${vault}:${note.path}`,
41207
+ vault,
41208
+ path: note.path,
41209
+ title: note.title,
41210
+ content: note.content,
41211
+ type: typeof props.type === "string" ? props.type : defaultType(vault),
41212
+ status: typeof props.status === "string" ? props.status : defaultStatus(vault),
41213
+ props,
41214
+ revision: note.revision,
41215
+ updatedAt: note.updatedAt,
41216
+ capturedAt: note.capturedAt
41217
+ };
41218
+ }
41219
+ function defaultType(vault) {
41220
+ if (vault === "People") return "person";
41221
+ if (vault === "Projects") return "project";
41222
+ if (vault === "Tasks") return "task";
41223
+ return "activity";
41224
+ }
41225
+ function defaultStatus(vault) {
41226
+ if (vault === "People") return "active";
41227
+ if (vault === "Projects") return "active";
41228
+ if (vault === "Tasks") return "to_do";
41229
+ return "triaged";
41230
+ }
41231
+ async function readVault(vault, token) {
41232
+ const list = await memoryCall("listTool", { vault }, token);
41233
+ if (!list.ok) throw new Error(list.error ?? `unable to read ${vault}`);
41234
+ const noteRefs = (list.notes ?? []).slice(0, 200);
41235
+ const notes = await Promise.all(
41236
+ noteRefs.map(async ({ path: path6 }) => memoryCall("getTool", { vault, path: path6 }, token))
41237
+ );
41238
+ return notes.filter((result) => result.ok && !!result.note).map((result) => workspaceRecord(vault, result.note));
41239
+ }
41240
+ var import_hono22, VAULTS, vaultApp;
41241
+ var init_vault_routes = __esm({
41242
+ "src/api/vault-routes.ts"() {
41243
+ "use strict";
41244
+ import_hono22 = require("hono");
41245
+ init_memory();
41246
+ VAULTS = ["People", "Projects", "Tasks", "Communications"];
41247
+ vaultApp = new import_hono22.Hono();
41248
+ vaultApp.use("/api/*", async (c, next) => {
41249
+ cors(c);
41250
+ if (c.req.method === "OPTIONS") return c.body(null, 204);
41251
+ await next();
41252
+ });
41253
+ vaultApp.get("/api/workspace", async (c) => {
41254
+ const token = c.req.query("token");
41255
+ if (!validToken(token)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
41256
+ try {
41257
+ const requested = c.req.query("vault");
41258
+ const vaults = isVault(requested) ? [requested] : VAULTS;
41259
+ const results = await Promise.all(vaults.map((vault) => readVault(vault, token)));
41260
+ const records = results.flat().sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
41261
+ return c.json({ ok: true, records, limited: results.some((result) => result.length >= 200) });
41262
+ } catch (error) {
41263
+ return c.json({ ok: false, error: error instanceof Error ? error.message : "unable to load vault workspace" }, 502);
41264
+ }
41265
+ });
41266
+ vaultApp.post("/api/records", async (c) => {
41267
+ const body = await c.req.json().catch(() => ({}));
41268
+ const token = typeof body.token === "string" ? body.token : void 0;
41269
+ if (!validToken(token)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
41270
+ const vault = body.vault;
41271
+ const path6 = stringValue(body.path, 320);
41272
+ const title = stringValue(body.title, 320);
41273
+ const content = typeof body.content === "string" && body.content.length <= 1e5 ? body.content : "";
41274
+ const props = propsValue(body.props) ?? {};
41275
+ if (!isVault(vault) || !path6 || !title) return c.json({ ok: false, error: "vault, path, and title are required" }, 400);
41276
+ const baseRevision = typeof body.baseRevision === "number" && Number.isInteger(body.baseRevision) ? body.baseRevision : void 0;
41277
+ const result = await memoryCall("putTool", { vault, path: path6, title, content: content || title, props, ...baseRevision === void 0 ? {} : { baseRevision } }, token);
41278
+ return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
41279
+ });
41280
+ vaultApp.post("/api/records/transition", async (c) => {
41281
+ const body = await c.req.json().catch(() => ({}));
41282
+ const token = typeof body.token === "string" ? body.token : void 0;
41283
+ if (!validToken(token)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
41284
+ const vault = typeof body.vault === "string" ? body.vault : "";
41285
+ const path6 = typeof body.path === "string" ? body.path : "";
41286
+ const status = stringValue(body.status, 80);
41287
+ const baseRevision = typeof body.baseRevision === "number" && Number.isInteger(body.baseRevision) ? body.baseRevision : void 0;
41288
+ if (!isVault(vault) || !path6 || !status) return c.json({ ok: false, error: "valid vault, path, and status are required" }, 400);
41289
+ const current = await memoryCall("getTool", { vault, path: path6 }, token);
41290
+ if (!current.ok || !current.note) return c.json(current, current.ok ? 404 : 502);
41291
+ const result = await memoryCall("putTool", {
41292
+ vault,
41293
+ path: path6,
41294
+ title: current.note.title,
41295
+ content: current.note.content,
41296
+ props: { ...current.note.props ?? {}, status },
41297
+ baseRevision: baseRevision ?? current.note.revision
41298
+ }, token);
41299
+ return c.json(result, result.ok ? 200 : result.conflict ? 409 : 502);
41300
+ });
41301
+ vaultApp.post("/api/activity", async (c) => {
41302
+ const body = await c.req.json().catch(() => ({}));
41303
+ const token = typeof body.token === "string" ? body.token : void 0;
41304
+ if (!validToken(token)) return c.json({ ok: false, error: "missing or malformed Vault App link" }, 401);
41305
+ const title = stringValue(body.title, 320);
41306
+ const content = typeof body.content === "string" && body.content.length <= 2e4 ? body.content : "";
41307
+ const props = propsValue(body.props) ?? {};
41308
+ if (!title) return c.json({ ok: false, error: "title is required" }, 400);
41309
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
41310
+ const result = await memoryCall("putTool", {
41311
+ vault: "Communications",
41312
+ path: `Inbox/activity-${stamp}`,
41313
+ title,
41314
+ content: content || title,
41315
+ props: { status: "triaged", type: "decision", occurred_at: (/* @__PURE__ */ new Date()).toISOString(), ...props }
41316
+ }, token);
41317
+ return c.json(result, result.ok ? 200 : 502);
41318
+ });
41319
+ vaultApp.get("/:token", (c) => {
41320
+ const token = c.req.param("token");
41321
+ if (!validToken(token)) return c.html('<!doctype html><html><body style="font-family:system-ui;padding:40px"><h2>This Vault App link is not valid.</h2><p>Ask the AI to run get-vault-app-link again, or revoke-vault-app-link and issue a replacement.</p></body></html>', 401);
41322
+ const base = (process.env.VAULT_APP_BASE_URL ?? "https://vault.mcpscraper.dev").replace(/\/$/, "");
41323
+ return c.redirect(`${base}/vault/${encodeURIComponent(token)}`, 302);
41324
+ });
41325
+ }
41326
+ });
41327
+
41088
41328
  // src/api/schedule-routes.ts
41089
41329
  function tokenOk2(token) {
41090
41330
  return !!token && token.startsWith("mk_") && token.length > 10;
@@ -41355,13 +41595,13 @@ loadActions();
41355
41595
  </body>
41356
41596
  </html>`;
41357
41597
  }
41358
- var import_hono22, scheduleApp;
41598
+ var import_hono23, scheduleApp;
41359
41599
  var init_schedule_routes = __esm({
41360
41600
  "src/api/schedule-routes.ts"() {
41361
41601
  "use strict";
41362
- import_hono22 = require("hono");
41602
+ import_hono23 = require("hono");
41363
41603
  init_memory();
41364
- scheduleApp = new import_hono22.Hono();
41604
+ scheduleApp = new import_hono23.Hono();
41365
41605
  scheduleApp.get("/api/status", async (c) => {
41366
41606
  const token = c.req.query("token");
41367
41607
  if (!tokenOk2(token)) return jsonError2("missing or malformed token", 401);
@@ -41596,17 +41836,17 @@ async function forwardReceivedEmail(resend, email, destination, label) {
41596
41836
  if (sent.error) throw new Error(`forward failed: ${sent.error.message}`);
41597
41837
  return sent.data.id;
41598
41838
  }
41599
- var import_hono23, import_resend, SUPPORT_ADDRESS, DMARC_ADDRESS, FORWARD_FROM, resendInboundApp;
41839
+ var import_hono24, import_resend, SUPPORT_ADDRESS, DMARC_ADDRESS, FORWARD_FROM, resendInboundApp;
41600
41840
  var init_resend_inbound_routes = __esm({
41601
41841
  "src/api/resend-inbound-routes.ts"() {
41602
41842
  "use strict";
41603
- import_hono23 = require("hono");
41843
+ import_hono24 = require("hono");
41604
41844
  import_resend = require("resend");
41605
41845
  init_memory();
41606
41846
  SUPPORT_ADDRESS = "support@mcpscraper.dev";
41607
41847
  DMARC_ADDRESS = "dmarc@mcpscraper.dev";
41608
41848
  FORWARD_FROM = "MCP Scraper Support <support@updates.mcpscraper.dev>";
41609
- resendInboundApp = new import_hono23.Hono();
41849
+ resendInboundApp = new import_hono24.Hono();
41610
41850
  resendInboundApp.post("/webhooks", async (c) => {
41611
41851
  const apiKey = process.env.RESEND_API_KEY?.trim();
41612
41852
  const webhookSecret = process.env.RESEND_WEBHOOK_SECRET?.trim();
@@ -45662,7 +45902,7 @@ async function checkHarvestLimits(user, reuseLockId) {
45662
45902
  }
45663
45903
  return null;
45664
45904
  }
45665
- var import_resend2, import_hono24, import_hono25, import_factory6, import_cookie2, import_stripe3, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, requireIntegrationsTier, requirePaidSchedulingTier, app, STRIPE_API_VERSION2, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
45905
+ var import_resend2, import_hono25, import_hono26, import_factory6, import_cookie2, import_stripe3, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, requireIntegrationsTier, requirePaidSchedulingTier, app, STRIPE_API_VERSION2, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
45666
45906
  var init_server = __esm({
45667
45907
  "src/api/server.ts"() {
45668
45908
  "use strict";
@@ -45680,8 +45920,8 @@ var init_server = __esm({
45680
45920
  init_media_extractor();
45681
45921
  init_site_mapper();
45682
45922
  init_site_extractor();
45683
- import_hono24 = require("hono");
45684
- import_hono25 = require("inngest/hono");
45923
+ import_hono25 = require("hono");
45924
+ import_hono26 = require("inngest/hono");
45685
45925
  init_client();
45686
45926
  init_site_audit();
45687
45927
  init_site_extract();
@@ -45712,6 +45952,7 @@ var init_server = __esm({
45712
45952
  init_stripe_routes();
45713
45953
  init_oauth_routes();
45714
45954
  init_chat_routes();
45955
+ init_vault_routes();
45715
45956
  init_schedule_routes();
45716
45957
  init_resend_inbound_routes();
45717
45958
  init_site_audit_worker();
@@ -45802,7 +46043,7 @@ var init_server = __esm({
45802
46043
  if (!hasPaidSchedulingAccess(user)) return c.json(paidPlanRequiredPayload(), 403);
45803
46044
  return next();
45804
46045
  });
45805
- app = new import_hono24.Hono();
46046
+ app = new import_hono25.Hono();
45806
46047
  STRIPE_API_VERSION2 = "2026-02-25.clover";
45807
46048
  app.use("*", async (c, next) => {
45808
46049
  await next();
@@ -47830,7 +48071,7 @@ var init_server = __esm({
47830
48071
  const bundle = stored.find((a) => a.key.endsWith("bundle.zip"));
47831
48072
  return c.json({ ok: true, jobId, artifacts: stored.length, settlement, bundleUrl: bundle?.url ?? null, bundleBytes: bundle?.bytes ?? null });
47832
48073
  });
47833
- app.on(["GET", "POST", "PUT"], "/api/inngest", (0, import_hono25.serve)({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
48074
+ app.on(["GET", "POST", "PUT"], "/api/inngest", (0, import_hono26.serve)({ client: inngest, functions: [siteAuditFn, siteExtractFn] }));
47834
48075
  app.route("/api/internal/site-architecture-auditor", siteAuditApp);
47835
48076
  app.route("/api/internal/memory", internalMemoryApp);
47836
48077
  app.route("/api/internal/billing", internalBillingApp);
@@ -47850,6 +48091,7 @@ var init_server = __esm({
47850
48091
  app.route("/mcp", mcpApp);
47851
48092
  app.route("/agent", buildBrowserAgentRoutes());
47852
48093
  app.route("/chat", chatApp);
48094
+ app.route("/vault", vaultApp);
47853
48095
  app.route("/schedule", scheduleApp);
47854
48096
  app.route("/resend", resendInboundApp);
47855
48097
  app.get("/console", (c) => c.html(renderConsoleHtml()));