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.
- package/README.md +4 -4
- package/dist/bin/api-server.cjs +525 -283
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +3 -2
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +3 -3
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +62 -10
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +4 -4
- package/dist/bin/paa-harvest.cjs +2 -1
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +2 -2
- package/dist/{chunk-TBRX7VOP.js → chunk-DUS4ENOY.js} +2 -2
- package/dist/{chunk-TBRX7VOP.js.map → chunk-DUS4ENOY.js.map} +1 -1
- package/dist/{chunk-FUWZWKGO.js → chunk-KAMKTSJQ.js} +2 -2
- package/dist/{chunk-73YM3DEB.js → chunk-LTT7QX3C.js} +61 -10
- package/dist/chunk-LTT7QX3C.js.map +1 -0
- package/dist/chunk-QG77M3YJ.js +7 -0
- package/dist/chunk-QG77M3YJ.js.map +1 -0
- package/dist/{chunk-E5J4HJBO.js → chunk-YH5DRFWQ.js} +4 -2
- package/dist/{chunk-XGIPATLV.js → chunk-ZQSHKWV4.js} +3 -2
- package/dist/chunk-ZQSHKWV4.js.map +1 -0
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-GN34MGAE.js → server-QQB4BNAB.js} +449 -267
- package/dist/server-QQB4BNAB.js.map +1 -0
- package/dist/{worker-E3KHQH2F.js → worker-RYP5EOV5.js} +3 -3
- package/docs/mcp-tool-manifest.generated.json +128 -9
- package/package.json +2 -2
- package/dist/chunk-27VXTOD2.js +0 -7
- package/dist/chunk-27VXTOD2.js.map +0 -1
- package/dist/chunk-73YM3DEB.js.map +0 -1
- package/dist/chunk-XGIPATLV.js.map +0 -1
- package/dist/server-GN34MGAE.js.map +0 -1
- /package/dist/{chunk-FUWZWKGO.js.map → chunk-KAMKTSJQ.js.map} +0 -0
- /package/dist/{chunk-E5J4HJBO.js.map → chunk-YH5DRFWQ.js.map} +0 -0
- /package/dist/{worker-E3KHQH2F.js.map → worker-RYP5EOV5.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -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 = "
|
|
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
|
|
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(
|
|
19563
|
+
await this.page.waitForTimeout(1e3);
|
|
19299
19564
|
const clicked = await this.page.evaluate((name) => {
|
|
19300
|
-
const
|
|
19301
|
-
const
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
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.
|
|
19318
|
-
|
|
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
|
|
19864
|
-
return
|
|
19865
|
-
|
|
19866
|
-
|
|
19867
|
-
|
|
19868
|
-
|
|
19869
|
-
|
|
19870
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
30283
|
-
|
|
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
|
|
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
|
|
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
|
|
41598
|
+
var import_hono23, scheduleApp;
|
|
41359
41599
|
var init_schedule_routes = __esm({
|
|
41360
41600
|
"src/api/schedule-routes.ts"() {
|
|
41361
41601
|
"use strict";
|
|
41362
|
-
|
|
41602
|
+
import_hono23 = require("hono");
|
|
41363
41603
|
init_memory();
|
|
41364
|
-
scheduleApp = new
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
45684
|
-
|
|
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
|
|
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,
|
|
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()));
|