mcp-scraper 0.2.11 → 0.2.13

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 (38) hide show
  1. package/README.md +6 -2
  2. package/dist/bin/api-server.cjs +1175 -458
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +1 -1
  5. package/dist/bin/browser-agent-stdio-server.cjs +1 -1
  6. package/dist/bin/browser-agent-stdio-server.cjs.map +1 -1
  7. package/dist/bin/browser-agent-stdio-server.js +2 -2
  8. package/dist/bin/mcp-scraper-cli.cjs +754 -10
  9. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-cli.js +50 -9
  11. package/dist/bin/mcp-scraper-cli.js.map +1 -1
  12. package/dist/bin/mcp-scraper-combined-stdio-server.cjs +1 -1
  13. package/dist/bin/mcp-scraper-combined-stdio-server.cjs.map +1 -1
  14. package/dist/bin/mcp-scraper-combined-stdio-server.js +3 -3
  15. package/dist/bin/mcp-scraper-install.cjs +7 -3
  16. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  17. package/dist/bin/mcp-scraper-install.js +7 -3
  18. package/dist/bin/mcp-scraper-install.js.map +1 -1
  19. package/dist/bin/mcp-stdio-server.cjs +1 -1
  20. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  21. package/dist/bin/mcp-stdio-server.js +2 -2
  22. package/dist/chunk-AYTOCZBS.js +1572 -0
  23. package/dist/chunk-AYTOCZBS.js.map +1 -0
  24. package/dist/{chunk-GDS6FMVQ.js → chunk-BQTWXY6G.js} +2 -2
  25. package/dist/{chunk-5YLKXKJA.js → chunk-DKJ2XCY7.js} +2 -2
  26. package/dist/chunk-RDROUQ4E.js +7 -0
  27. package/dist/chunk-RDROUQ4E.js.map +1 -0
  28. package/dist/{server-6KMHJXOL.js → server-LUNOI26E.js} +4 -4
  29. package/docs/specs/cli-agent-wiring-spec.md +4 -1
  30. package/docs/specs/seo-cli-growth-roadmap-spec.md +4 -2
  31. package/package.json +1 -1
  32. package/dist/chunk-BLLZQP4Z.js +0 -7
  33. package/dist/chunk-BLLZQP4Z.js.map +0 -1
  34. package/dist/chunk-L6IS63WS.js +0 -869
  35. package/dist/chunk-L6IS63WS.js.map +0 -1
  36. /package/dist/{chunk-GDS6FMVQ.js.map → chunk-BQTWXY6G.js.map} +0 -0
  37. /package/dist/{chunk-5YLKXKJA.js.map → chunk-DKJ2XCY7.js.map} +0 -0
  38. /package/dist/{server-6KMHJXOL.js.map → server-LUNOI26E.js.map} +0 -0
@@ -14095,7 +14095,7 @@ function formatDirectoryWorkflow(raw, input) {
14095
14095
  const csvPath = d.csvPath ?? null;
14096
14096
  const totalResultCount = d.totalResultCount ?? cities.reduce((sum, city) => sum + city.resultCount, 0);
14097
14097
  const durationMs = d.durationMs;
14098
- const marketRows = cities.map((city) => {
14098
+ const marketRows2 = cities.map((city) => {
14099
14099
  const zips = city.zips?.length ? city.zips.slice(0, 8).join(" ") + (city.zips.length > 8 ? ` +${city.zips.length - 8}` : "") : "\u2014";
14100
14100
  return `| ${cell(city.city)} | ${city.population.toLocaleString()} | ${city.zips?.length ?? 0} | ${city.resultCount} | ${city.status} | ${cell(zips)} |`;
14101
14101
  }).join("\n");
@@ -14116,7 +14116,7 @@ ${warnings.map((w) => `- ${w}`).join("\n")}` : "";
14116
14116
  ## Markets
14117
14117
  | City | Population | ZIPs | Maps Results | Status | ZIP Sample |
14118
14118
  |---|---:|---:|---:|---|---|
14119
- ${marketRows}`,
14119
+ ${marketRows2}`,
14120
14120
  businessRows ? `
14121
14121
  ## Top Candidates By City
14122
14122
  | City | # | Name | Category | Rating | Website | Maps |
@@ -15646,6 +15646,718 @@ ${summary}
15646
15646
  }
15647
15647
  });
15648
15648
 
15649
+ // src/workflows/workflows/seo-workflow-utils.ts
15650
+ function normalizeDomain2(value) {
15651
+ if (!value) return null;
15652
+ try {
15653
+ const url = new URL(value.includes("://") ? value : `https://${value}`);
15654
+ return url.hostname.replace(/^www\./, "").toLowerCase();
15655
+ } catch {
15656
+ return value.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0]?.toLowerCase() || null;
15657
+ }
15658
+ }
15659
+ function domainFromUrl2(url) {
15660
+ return normalizeDomain2(url) ?? "";
15661
+ }
15662
+ function numberFrom2(value) {
15663
+ if (value === null || value === void 0 || value === "") return null;
15664
+ const parsed = Number(String(value).replace(/[^\d.]/g, ""));
15665
+ return Number.isFinite(parsed) ? parsed : null;
15666
+ }
15667
+ function median2(values) {
15668
+ const nums = values.filter((v) => v !== null).sort((a, b) => a - b);
15669
+ if (!nums.length) return null;
15670
+ return nums[Math.floor(nums.length / 2)] ?? null;
15671
+ }
15672
+ function textTerms(text, limit = 12) {
15673
+ const counts = /* @__PURE__ */ new Map();
15674
+ for (const token of text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/)) {
15675
+ if (token.length < 4 || STOP_WORDS.has(token)) continue;
15676
+ counts.set(token, (counts.get(token) ?? 0) + 1);
15677
+ }
15678
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([term, count]) => `${term} (${count})`).join("; ");
15679
+ }
15680
+ function classifyQuestion(question) {
15681
+ const q = question.toLowerCase();
15682
+ if (/\b(cost|price|pricing|charge|expensive|cheap)\b/.test(q)) return "cost";
15683
+ if (/\b(best|top|recommended|reviews?|compare|versus|vs)\b/.test(q)) return "comparison";
15684
+ if (/\b(how|steps?|process|way to)\b/.test(q)) return "process";
15685
+ if (/\b(why|worth|important|benefit)\b/.test(q)) return "why";
15686
+ if (/\b(near me|city|local|nearby)\b/.test(q)) return "local";
15687
+ if (/\b(can|does|do|is|are|should|will)\b/.test(q)) return "decision";
15688
+ return "definition";
15689
+ }
15690
+ function questionRows(paa) {
15691
+ return (paa?.flat ?? []).filter((row) => row.question).map((row, index) => {
15692
+ const url = row.source_cite ?? "";
15693
+ const domain = normalizeDomain2(row.source_site ?? "") ?? domainFromUrl2(url);
15694
+ return {
15695
+ position: index + 1,
15696
+ intent: classifyQuestion(row.question ?? ""),
15697
+ question: row.question ?? "",
15698
+ answer_excerpt: (row.answer ?? "").slice(0, 320),
15699
+ source_title: row.source_title ?? "",
15700
+ source_domain: domain,
15701
+ source_url: url
15702
+ };
15703
+ });
15704
+ }
15705
+ function sourceDomainRows(rows) {
15706
+ const byDomain = /* @__PURE__ */ new Map();
15707
+ for (const row of rows) {
15708
+ const domain = String(row.source_domain ?? row.domain ?? "");
15709
+ if (!domain) continue;
15710
+ const entry = byDomain.get(domain) ?? { questions: 0, urls: /* @__PURE__ */ new Set(), intents: /* @__PURE__ */ new Map() };
15711
+ entry.questions += row.question ? 1 : 0;
15712
+ if (row.source_url || row.url) entry.urls.add(String(row.source_url ?? row.url));
15713
+ if (row.intent) entry.intents.set(String(row.intent), (entry.intents.get(String(row.intent)) ?? 0) + 1);
15714
+ byDomain.set(domain, entry);
15715
+ }
15716
+ return [...byDomain.entries()].map(([domain, entry]) => ({
15717
+ domain,
15718
+ question_mentions: entry.questions,
15719
+ source_url_count: entry.urls.size,
15720
+ top_intents: [...entry.intents.entries()].sort((a, b) => b[1] - a[1]).map(([intent, count]) => `${intent} (${count})`).join("; ")
15721
+ })).sort((a, b) => Number(b.question_mentions) - Number(a.question_mentions) || String(a.domain).localeCompare(String(b.domain)));
15722
+ }
15723
+ function splitSentences(text) {
15724
+ return (text ?? "").replace(/\s+/g, " ").split(/(?<=[.!?])\s+/).map((sentence) => sentence.trim()).filter((sentence) => sentence.length > 20).slice(0, 20);
15725
+ }
15726
+ function classifySentence(sentence) {
15727
+ const s = sentence.toLowerCase();
15728
+ if (/\bis\b|\bare\b|\bmeans\b|\brefers to\b/.test(s)) return "definition";
15729
+ if (/\binclude\b|\bconsider\b|\bfactors?\b|\bcriteria\b/.test(s)) return "criteria";
15730
+ if (/\bfirst\b|\bthen\b|\bsteps?\b|\bprocess\b/.test(s)) return "process";
15731
+ if (/\bvs\b|\bthan\b|\bcompare\b|\bdifference\b/.test(s)) return "comparison";
15732
+ if (/\bcost\b|\bprice\b|\baverage\b|\brange\b/.test(s)) return "cost";
15733
+ return "claim";
15734
+ }
15735
+ function pageSummaryRow(page, source) {
15736
+ return {
15737
+ position: source.position ?? "",
15738
+ domain: source.domain,
15739
+ url: source.url,
15740
+ serp_title: source.title ?? "",
15741
+ page_title: page.title ?? "",
15742
+ h1: page.h1 ?? "",
15743
+ meta_description: page.metaDescription ?? "",
15744
+ word_count: page.wordCount ?? "",
15745
+ heading_count: page.headings?.length ?? 0,
15746
+ schema_types: (page.schemaTypes ?? []).join("; ")
15747
+ };
15748
+ }
15749
+ async function mapLimit3(items, limit, fn) {
15750
+ const out = new Array(items.length);
15751
+ let next = 0;
15752
+ async function worker() {
15753
+ while (next < items.length) {
15754
+ const index = next;
15755
+ next += 1;
15756
+ out[index] = await fn(items[index], index);
15757
+ }
15758
+ }
15759
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, limit), items.length) }, () => worker()));
15760
+ return out;
15761
+ }
15762
+ var STOP_WORDS;
15763
+ var init_seo_workflow_utils = __esm({
15764
+ "src/workflows/workflows/seo-workflow-utils.ts"() {
15765
+ "use strict";
15766
+ STOP_WORDS = /* @__PURE__ */ new Set([
15767
+ "about",
15768
+ "after",
15769
+ "also",
15770
+ "because",
15771
+ "been",
15772
+ "best",
15773
+ "both",
15774
+ "from",
15775
+ "have",
15776
+ "into",
15777
+ "more",
15778
+ "most",
15779
+ "near",
15780
+ "only",
15781
+ "over",
15782
+ "than",
15783
+ "that",
15784
+ "their",
15785
+ "them",
15786
+ "then",
15787
+ "there",
15788
+ "these",
15789
+ "they",
15790
+ "this",
15791
+ "what",
15792
+ "when",
15793
+ "where",
15794
+ "which",
15795
+ "while",
15796
+ "with",
15797
+ "your",
15798
+ "will",
15799
+ "would",
15800
+ "should",
15801
+ "could",
15802
+ "does",
15803
+ "were",
15804
+ "cost",
15805
+ "costs"
15806
+ ]);
15807
+ }
15808
+ });
15809
+
15810
+ // src/workflows/workflows/comparison-briefs.ts
15811
+ function businessRowsFromMaps(location2, query, results) {
15812
+ return results.map((result) => ({
15813
+ source_query: query,
15814
+ source_location: location2,
15815
+ city: location2.split(",")[0]?.trim() ?? location2,
15816
+ state: location2.split(",")[1]?.trim() ?? "",
15817
+ population: "",
15818
+ result_position: result.position,
15819
+ business_name: result.name,
15820
+ review_stars: result.rating ?? "",
15821
+ review_count: result.reviewCount ?? "",
15822
+ category: result.category ?? "",
15823
+ address: result.address ?? "",
15824
+ phone: result.phone ?? "",
15825
+ website_url: result.websiteUrl ?? "",
15826
+ place_url: result.placeUrl ?? "",
15827
+ cid: result.cid ?? "",
15828
+ cid_decimal: result.cidDecimal ?? "",
15829
+ result_status: "ok",
15830
+ error: ""
15831
+ }));
15832
+ }
15833
+ function marketRows(rows) {
15834
+ const byLocation = /* @__PURE__ */ new Map();
15835
+ for (const row of rows) {
15836
+ const key = String(row.source_location ?? row.location ?? "");
15837
+ if (!key) continue;
15838
+ const list = byLocation.get(key) ?? [];
15839
+ list.push(row);
15840
+ byLocation.set(key, list);
15841
+ }
15842
+ return [...byLocation.entries()].map(([location2, list]) => {
15843
+ const counts = list.map((row) => numberFrom2(row.review_count));
15844
+ const ratings = list.map((row) => numberFrom2(row.review_stars));
15845
+ const topThree = list.slice(0, 3).map((row) => numberFrom2(row.review_count)).filter((v) => v !== null);
15846
+ const categories = /* @__PURE__ */ new Map();
15847
+ for (const row of list) {
15848
+ const category = String(row.category ?? "");
15849
+ if (category) categories.set(category, (categories.get(category) ?? 0) + 1);
15850
+ }
15851
+ return {
15852
+ source_location: location2,
15853
+ result_count: list.filter((row) => row.business_name).length,
15854
+ median_review_count: median2(counts) ?? "",
15855
+ median_rating: median2(ratings) ?? "",
15856
+ top_three_average_review_count: topThree.length ? Math.round(topThree.reduce((a, b) => a + b, 0) / topThree.length) : "",
15857
+ top_categories: [...categories.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([cat, count]) => `${cat} (${count})`).join("; "),
15858
+ websites_present: list.filter((row) => row.website_url).length
15859
+ };
15860
+ });
15861
+ }
15862
+ function comparisonRows(rows) {
15863
+ const benchmarkByLocation = /* @__PURE__ */ new Map();
15864
+ for (const market of marketRows(rows)) {
15865
+ benchmarkByLocation.set(String(market.source_location), numberFrom2(market.top_three_average_review_count) ?? 0);
15866
+ }
15867
+ return rows.filter((row) => row.business_name).map((row) => {
15868
+ const reviews = numberFrom2(row.review_count) ?? 0;
15869
+ const benchmark = benchmarkByLocation.get(String(row.source_location)) ?? 0;
15870
+ const websiteMissing = !row.website_url;
15871
+ const rank = numberFrom2(row.result_position) ?? 999;
15872
+ return {
15873
+ source_location: row.source_location,
15874
+ result_position: row.result_position,
15875
+ business_name: row.business_name,
15876
+ category: row.category,
15877
+ review_stars: row.review_stars,
15878
+ review_count: row.review_count,
15879
+ review_gap_to_top3_average: benchmark ? Math.max(0, benchmark - reviews) : "",
15880
+ website_url: row.website_url,
15881
+ place_url: row.place_url,
15882
+ comparison_note: rank <= 3 ? "visible leader" : websiteMissing ? "ranking without website" : reviews < benchmark ? "review-light competitor" : "visible competitor"
15883
+ };
15884
+ });
15885
+ }
15886
+ function organicRows(serp, targetDomain) {
15887
+ return (serp?.organicResults ?? []).map((result) => {
15888
+ const url = result.url ?? "";
15889
+ const domain = normalizeDomain2(result.domain ?? "") ?? domainFromUrl2(url);
15890
+ return {
15891
+ position: result.position ?? "",
15892
+ title: result.title ?? "",
15893
+ url,
15894
+ domain,
15895
+ snippet: result.snippet ?? "",
15896
+ is_target: targetDomain ? domain === targetDomain : false
15897
+ };
15898
+ });
15899
+ }
15900
+ function pageGapRows(targetPage, competitorPages) {
15901
+ const targetHeadingText = new Set((targetPage?.headings ?? []).map((h) => h.text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").trim()));
15902
+ const targetTerms = new Set((targetPage?.headings ?? []).flatMap((h) => h.text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean)));
15903
+ const rows = [];
15904
+ for (const { source, page } of competitorPages) {
15905
+ for (const heading of page.headings ?? []) {
15906
+ if (heading.level > 3 || !heading.text) continue;
15907
+ const normalized = heading.text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").trim();
15908
+ const terms = normalized.split(/\s+/).filter((term) => term.length > 3);
15909
+ const overlap = terms.filter((term) => targetTerms.has(term)).length;
15910
+ const covered = targetHeadingText.has(normalized) || overlap >= Math.max(2, Math.ceil(terms.length / 2));
15911
+ if (covered && targetPage) continue;
15912
+ rows.push({
15913
+ source_position: source.position ?? "",
15914
+ source_domain: normalizeDomain2(source.domain ?? "") ?? domainFromUrl2(source.url),
15915
+ source_url: source.url ?? "",
15916
+ heading_level: heading.level,
15917
+ competitor_heading: heading.text,
15918
+ target_coverage: targetPage ? "not found in target headings" : "no target page extracted",
15919
+ terms: textTerms(heading.text, 6)
15920
+ });
15921
+ }
15922
+ }
15923
+ return rows.slice(0, 150);
15924
+ }
15925
+ async function extractPages(ctx, sources, warnings, labelPrefix) {
15926
+ return mapLimit3(sources.filter((source) => source.url), 2, async (source, index) => {
15927
+ try {
15928
+ const page = await ctx.client.post("/extract-url", { url: source.url }, 18e4);
15929
+ await ctx.artifacts.writeJson(`${labelPrefix} ${index + 1}`, `raw/extract-url/${labelPrefix.toLowerCase()}-${index + 1}.json`, page);
15930
+ return { source, page };
15931
+ } catch (err) {
15932
+ warnings.push(`Page extraction failed for ${source.url}: ${err instanceof Error ? err.message : String(err)}`);
15933
+ return null;
15934
+ }
15935
+ }).then((items) => items.filter((item) => item !== null));
15936
+ }
15937
+ var import_zod22, ProxyModeSchema, MapComparisonInputSchema, SerpComparisonInputSchema, PaaExpansionBriefInputSchema, AiOverviewLanguageInputSchema, mapComparisonWorkflowDefinition, serpComparisonWorkflowDefinition, paaExpansionBriefWorkflowDefinition, aiOverviewLanguageWorkflowDefinition;
15938
+ var init_comparison_briefs = __esm({
15939
+ "src/workflows/workflows/comparison-briefs.ts"() {
15940
+ "use strict";
15941
+ import_zod22 = require("zod");
15942
+ init_report_renderer();
15943
+ init_directory();
15944
+ init_seo_workflow_utils();
15945
+ ProxyModeSchema = import_zod22.z.enum(["location", "configured", "none"]);
15946
+ MapComparisonInputSchema = import_zod22.z.object({
15947
+ query: import_zod22.z.string().min(1),
15948
+ location: import_zod22.z.string().optional(),
15949
+ state: import_zod22.z.string().optional(),
15950
+ minPopulation: import_zod22.z.number().int().min(0).default(1e5),
15951
+ maxCities: import_zod22.z.number().int().min(1).max(100).default(5),
15952
+ maxResultsPerCity: import_zod22.z.number().int().min(1).max(50).default(20),
15953
+ hydrateTop: import_zod22.z.number().int().min(0).max(10).default(5),
15954
+ maxReviews: import_zod22.z.number().int().min(0).max(500).default(25),
15955
+ concurrency: import_zod22.z.number().int().min(1).max(5).default(5),
15956
+ proxyMode: ProxyModeSchema.default("location"),
15957
+ returnPartial: import_zod22.z.boolean().default(true)
15958
+ }).refine((input) => input.location || input.state, {
15959
+ message: "Either location or state is required for map-comparison"
15960
+ });
15961
+ SerpComparisonInputSchema = import_zod22.z.object({
15962
+ keyword: import_zod22.z.string().min(1),
15963
+ domain: import_zod22.z.string().optional(),
15964
+ url: import_zod22.z.string().url().optional(),
15965
+ location: import_zod22.z.string().optional(),
15966
+ maxResults: import_zod22.z.number().int().min(1).max(20).default(10),
15967
+ maxQuestions: import_zod22.z.number().int().min(1).max(200).default(40),
15968
+ extractTop: import_zod22.z.number().int().min(0).max(10).default(5),
15969
+ includePaa: import_zod22.z.boolean().default(true),
15970
+ includeAiOverview: import_zod22.z.boolean().default(true),
15971
+ returnPartial: import_zod22.z.boolean().default(true)
15972
+ });
15973
+ PaaExpansionBriefInputSchema = import_zod22.z.object({
15974
+ keyword: import_zod22.z.string().min(1),
15975
+ location: import_zod22.z.string().optional(),
15976
+ maxQuestions: import_zod22.z.number().int().min(1).max(300).default(80),
15977
+ depth: import_zod22.z.number().int().min(1).max(6).default(3),
15978
+ returnPartial: import_zod22.z.boolean().default(true)
15979
+ });
15980
+ AiOverviewLanguageInputSchema = import_zod22.z.object({
15981
+ keyword: import_zod22.z.string().min(1),
15982
+ domain: import_zod22.z.string().optional(),
15983
+ url: import_zod22.z.string().url().optional(),
15984
+ location: import_zod22.z.string().optional(),
15985
+ maxQuestions: import_zod22.z.number().int().min(1).max(200).default(40),
15986
+ extractTop: import_zod22.z.number().int().min(0).max(8).default(3),
15987
+ returnPartial: import_zod22.z.boolean().default(true)
15988
+ });
15989
+ mapComparisonWorkflowDefinition = {
15990
+ id: "map-comparison",
15991
+ title: "Maps Comparison",
15992
+ description: "Compare Google Maps competitors by rank, reviews, stars, categories, websites, and profile/review signals.",
15993
+ inputSchema: MapComparisonInputSchema,
15994
+ async run(input, ctx) {
15995
+ await ctx.artifacts.writeManifest("running", {}, [], []);
15996
+ const warnings = [];
15997
+ let rows;
15998
+ let directory = null;
15999
+ let mapsSearch = null;
16000
+ if (input.location) {
16001
+ mapsSearch = await ctx.client.post("/maps/search", {
16002
+ query: input.query,
16003
+ location: input.location,
16004
+ maxResults: input.maxResultsPerCity,
16005
+ proxyMode: input.proxyMode
16006
+ }, 24e4);
16007
+ rows = businessRowsFromMaps(input.location, input.query, mapsSearch.results);
16008
+ await ctx.artifacts.writeJson("Maps search raw JSON", "raw/maps-search.json", mapsSearch);
16009
+ } else {
16010
+ directory = await ctx.client.post("/directory/run", {
16011
+ query: input.query,
16012
+ state: input.state,
16013
+ minPopulation: input.minPopulation,
16014
+ maxCities: input.maxCities,
16015
+ maxResultsPerCity: input.maxResultsPerCity,
16016
+ concurrency: input.concurrency,
16017
+ proxyMode: input.proxyMode,
16018
+ saveCsv: true
16019
+ }, 9e5);
16020
+ rows = directoryRows(directory);
16021
+ await ctx.artifacts.writeJson("Directory raw JSON", "raw/directory-workflow.json", directory);
16022
+ await ctx.artifacts.writeCsv("Directory CSV", "exports/directory.csv", DIRECTORY_CSV_HEADERS, rows);
16023
+ warnings.push(...directory.warnings);
16024
+ }
16025
+ const compareRows = comparisonRows(rows);
16026
+ const selected = compareRows.slice(0, input.hydrateTop * Math.max(1, input.location ? 1 : input.maxCities));
16027
+ const hydrated = await mapLimit3(selected, 3, async (row, index) => {
16028
+ try {
16029
+ const detail = await ctx.client.post("/maps/place", {
16030
+ businessName: row.business_name,
16031
+ location: row.source_location,
16032
+ includeReviews: input.maxReviews > 0,
16033
+ maxReviews: Math.max(1, input.maxReviews)
16034
+ }, 18e4);
16035
+ await ctx.artifacts.writeJson(`${row.business_name} profile`, `raw/maps-place-intel/${index + 1}-${String(row.business_name).toLowerCase().replace(/[^a-z0-9]+/g, "-")}.json`, detail);
16036
+ return { row, detail, error: "" };
16037
+ } catch (err) {
16038
+ const message = err instanceof Error ? err.message : String(err);
16039
+ warnings.push(`Profile hydration failed for ${row.business_name}: ${message}`);
16040
+ return { row, detail: null, error: message };
16041
+ }
16042
+ });
16043
+ const profileRows = hydrated.map(({ row, detail, error }) => ({
16044
+ source_location: row.source_location,
16045
+ result_position: row.result_position,
16046
+ business_name: row.business_name,
16047
+ category: detail?.category ?? row.category,
16048
+ review_stars: detail?.rating ?? row.review_stars,
16049
+ review_count: detail?.reviewCount ?? row.review_count,
16050
+ website_url: detail?.website ?? row.website_url,
16051
+ review_topics: (detail?.reviewTopics ?? []).map((t) => `${t.label} (${t.count})`).join("; "),
16052
+ about_attributes: (detail?.aboutAttributes ?? []).map((a) => `${a.section}: ${a.attribute}`).join("; "),
16053
+ reviews_status: detail?.reviewsStatus ?? "",
16054
+ error
16055
+ }));
16056
+ const markets = marketRows(rows);
16057
+ await ctx.artifacts.writeCsv("Maps results CSV", "maps-results.csv", ["source_query", "source_location", "city", "state", "population", "result_position", "business_name", "review_stars", "review_count", "category", "address", "phone", "website_url", "place_url", "cid", "cid_decimal", "result_status", "error"], rows);
16058
+ await ctx.artifacts.writeCsv("Comparison CSV", "map-comparison.csv", ["source_location", "result_position", "business_name", "category", "review_stars", "review_count", "review_gap_to_top3_average", "website_url", "place_url", "comparison_note"], compareRows);
16059
+ await ctx.artifacts.writeCsv("Profile insights CSV", "profile-insights.csv", ["source_location", "result_position", "business_name", "category", "review_stars", "review_count", "website_url", "review_topics", "about_attributes", "reviews_status", "error"], profileRows);
16060
+ await ctx.artifacts.writeJson("Maps comparison evidence", "evidence.json", { input, directory, mapsSearch, rows, compareRows, profileRows, markets, warnings });
16061
+ await ctx.artifacts.writeText("Brief", "brief.md", [
16062
+ `# Maps Comparison: ${input.query}`,
16063
+ "",
16064
+ `Markets: ${markets.map((row) => row.source_location).join(", ")}`,
16065
+ "",
16066
+ "## How to Use",
16067
+ "- Compare rank position against review count and category patterns.",
16068
+ "- Treat review gaps and missing websites as opportunity signals, not guarantees.",
16069
+ "- Use profile topics and attributes as evidence for local content and GBP improvements."
16070
+ ].join("\n"));
16071
+ const summary = `${compareRows.length} Maps competitors compared across ${markets.length} market(s); ${profileRows.filter((row) => !row.error).length} profiles hydrated.`;
16072
+ const reportPath = await ctx.artifacts.writeHtml("HTML report", "report.html", renderWorkflowReport({
16073
+ title: "Maps Comparison",
16074
+ subtitle: `${input.query}${input.location ? ` \xB7 ${input.location}` : input.state ? ` \xB7 ${input.state}` : ""}`,
16075
+ summary,
16076
+ warnings,
16077
+ tables: [
16078
+ { title: "Market Benchmarks", columns: ["source_location", "result_count", "median_review_count", "median_rating", "top_three_average_review_count", "top_categories", "websites_present"], rows: markets },
16079
+ { title: "Competitor Comparison", columns: ["source_location", "result_position", "business_name", "category", "review_stars", "review_count", "review_gap_to_top3_average", "comparison_note"], rows: compareRows.slice(0, 120) },
16080
+ { title: "Profile Insights", columns: ["source_location", "business_name", "review_topics", "about_attributes", "error"], rows: profileRows }
16081
+ ]
16082
+ }));
16083
+ const status = warnings.length ? "partial" : "succeeded";
16084
+ const counts = { markets: markets.length, competitors: compareRows.length, hydratedProfiles: profileRows.filter((row) => !row.error).length };
16085
+ await ctx.artifacts.writeManifest(status, counts, warnings, []);
16086
+ return { title: "Maps Comparison", summary, status, counts, warnings, errors: [], reportPath };
16087
+ }
16088
+ };
16089
+ serpComparisonWorkflowDefinition = {
16090
+ id: "serp-comparison",
16091
+ title: "SERP Comparison",
16092
+ description: "Compare ranking pages, SERP features, PAA evidence, AI Overview citations, and page-level content gaps.",
16093
+ inputSchema: SerpComparisonInputSchema,
16094
+ async run(input, ctx) {
16095
+ await ctx.artifacts.writeManifest("running", {}, [], []);
16096
+ const warnings = [];
16097
+ const targetDomain = normalizeDomain2(input.domain ?? input.url ?? null);
16098
+ const serp = await ctx.client.post("/harvest/sync", {
16099
+ query: input.keyword,
16100
+ location: input.location,
16101
+ serpOnly: true,
16102
+ maxQuestions: 1,
16103
+ format: "json"
16104
+ }, 18e4);
16105
+ await ctx.artifacts.writeJson("SERP raw JSON", "raw/serp.json", serp);
16106
+ let paa = null;
16107
+ if (input.includePaa) {
16108
+ try {
16109
+ paa = await ctx.client.post("/harvest/sync", {
16110
+ query: input.keyword,
16111
+ location: input.location,
16112
+ maxQuestions: input.maxQuestions,
16113
+ format: "json"
16114
+ }, 28e4);
16115
+ await ctx.artifacts.writeJson("PAA raw JSON", "raw/paa.json", paa);
16116
+ } catch (err) {
16117
+ warnings.push(`PAA evidence unavailable: ${err instanceof Error ? err.message : String(err)}`);
16118
+ }
16119
+ }
16120
+ const organic = organicRows(serp, targetDomain).slice(0, input.maxResults);
16121
+ const organicSources = (serp.organicResults ?? []).slice(0, input.maxResults);
16122
+ const targetSource = input.url ? { position: 0, title: "Target page", url: input.url, domain: domainFromUrl2(input.url) } : organicSources.find((result) => targetDomain && (normalizeDomain2(result.domain ?? "") ?? domainFromUrl2(result.url)) === targetDomain);
16123
+ const competitorSources = organicSources.filter((result) => result.url && result.url !== targetSource?.url).filter((result) => !targetDomain || (normalizeDomain2(result.domain ?? "") ?? domainFromUrl2(result.url)) !== targetDomain).slice(0, input.extractTop);
16124
+ const targetPages = targetSource ? await extractPages(ctx, [targetSource], warnings, "Target") : [];
16125
+ const competitorPages = input.extractTop > 0 ? await extractPages(ctx, competitorSources, warnings, "Competitor") : [];
16126
+ const targetPage = targetPages[0]?.page ?? null;
16127
+ const pageRows = [
16128
+ ...targetPages.map(({ source, page }) => pageSummaryRow(page, { url: source.url ?? "", domain: normalizeDomain2(source.domain ?? "") ?? domainFromUrl2(source.url), position: source.position, title: source.title })),
16129
+ ...competitorPages.map(({ source, page }) => pageSummaryRow(page, { url: source.url ?? "", domain: normalizeDomain2(source.domain ?? "") ?? domainFromUrl2(source.url), position: source.position, title: source.title }))
16130
+ ];
16131
+ const gaps = pageGapRows(targetPage, competitorPages);
16132
+ const questions = questionRows(paa);
16133
+ const aiRows = (serp.aiOverview?.citations ?? []).map((citation, index) => ({
16134
+ citation_position: index + 1,
16135
+ citation_text: citation.text ?? "",
16136
+ url: citation.href ?? "",
16137
+ domain: domainFromUrl2(citation.href ?? ""),
16138
+ is_target: targetDomain ? domainFromUrl2(citation.href ?? "") === targetDomain : false
16139
+ }));
16140
+ await ctx.artifacts.writeCsv("Organic results CSV", "organic-results.csv", ["position", "title", "url", "domain", "snippet", "is_target"], organic);
16141
+ await ctx.artifacts.writeCsv("Page comparison CSV", "page-comparison.csv", ["position", "domain", "url", "serp_title", "page_title", "h1", "meta_description", "word_count", "heading_count", "schema_types"], pageRows);
16142
+ await ctx.artifacts.writeCsv("Content gaps CSV", "content-gaps.csv", ["source_position", "source_domain", "source_url", "heading_level", "competitor_heading", "target_coverage", "terms"], gaps);
16143
+ await ctx.artifacts.writeCsv("PAA questions CSV", "paa-questions.csv", ["position", "intent", "question", "answer_excerpt", "source_title", "source_domain", "source_url"], questions);
16144
+ await ctx.artifacts.writeCsv("AI Overview citations CSV", "ai-overview-citations.csv", ["citation_position", "citation_text", "url", "domain", "is_target"], aiRows);
16145
+ await ctx.artifacts.writeJson("SERP comparison evidence", "evidence.json", { input, serp, paa, organic, pageRows, gaps, questions, aiRows, warnings });
16146
+ await ctx.artifacts.writeText("Writer brief", "brief.md", [
16147
+ `# SERP Comparison Brief: ${input.keyword}`,
16148
+ "",
16149
+ `Target: ${targetDomain ?? input.url ?? "not specified"}`,
16150
+ `Location: ${input.location ?? "not specified"}`,
16151
+ "",
16152
+ "## Recommended Actions",
16153
+ "- Use `content-gaps.csv` to decide which missing sections deserve coverage.",
16154
+ "- Use `paa-questions.csv` for FAQ and answer-block candidates.",
16155
+ "- Use `ai-overview-citations.csv` to see whether the target is cited in AI Overview evidence.",
16156
+ "- Treat extracted page headings as evidence, not a complete semantic analysis."
16157
+ ].join("\n"));
16158
+ const summary = `${organic.length} organic results, ${pageRows.length} extracted pages, ${gaps.length} heading gaps, ${questions.length} PAA questions.`;
16159
+ const reportPath = await ctx.artifacts.writeHtml("HTML report", "report.html", renderWorkflowReport({
16160
+ title: "SERP Comparison",
16161
+ subtitle: `${input.keyword}${input.location ? ` \xB7 ${input.location}` : ""}`,
16162
+ summary,
16163
+ warnings,
16164
+ tables: [
16165
+ { title: "Organic Results", columns: ["position", "title", "domain", "is_target"], rows: organic },
16166
+ { title: "Page Comparison", columns: ["position", "domain", "h1", "word_count", "heading_count", "schema_types"], rows: pageRows },
16167
+ { title: "Content Gaps", columns: ["source_position", "source_domain", "competitor_heading", "target_coverage", "terms"], rows: gaps.slice(0, 80) },
16168
+ { title: "PAA Questions", columns: ["position", "intent", "question", "source_domain"], rows: questions.slice(0, 80) }
16169
+ ]
16170
+ }));
16171
+ const status = warnings.length ? "partial" : "succeeded";
16172
+ const counts = { organic: organic.length, pages: pageRows.length, gaps: gaps.length, questions: questions.length, aiCitations: aiRows.length };
16173
+ await ctx.artifacts.writeManifest(status, counts, warnings, []);
16174
+ return { title: "SERP Comparison", summary, status, counts, warnings, errors: [], reportPath };
16175
+ }
16176
+ };
16177
+ paaExpansionBriefWorkflowDefinition = {
16178
+ id: "paa-expansion-brief",
16179
+ title: "PAA Expansion Brief",
16180
+ description: "Expand People Also Ask questions into an evidence-backed writer brief, section map, and source table.",
16181
+ inputSchema: PaaExpansionBriefInputSchema,
16182
+ async run(input, ctx) {
16183
+ await ctx.artifacts.writeManifest("running", {}, [], []);
16184
+ const warnings = [];
16185
+ const paa = await ctx.client.post("/harvest/sync", {
16186
+ query: input.keyword,
16187
+ location: input.location,
16188
+ maxQuestions: input.maxQuestions,
16189
+ depth: input.depth,
16190
+ format: "json"
16191
+ }, 3e5);
16192
+ await ctx.artifacts.writeJson("PAA raw JSON", "raw/paa.json", paa);
16193
+ const questions = questionRows(paa);
16194
+ const sourceRows2 = sourceDomainRows(questions);
16195
+ const byIntent = /* @__PURE__ */ new Map();
16196
+ for (const row of questions) {
16197
+ const list = byIntent.get(String(row.intent)) ?? [];
16198
+ list.push(row);
16199
+ byIntent.set(String(row.intent), list);
16200
+ }
16201
+ const sectionRows = [...byIntent.entries()].map(([intent, rows]) => ({
16202
+ recommended_section: intent,
16203
+ question_count: rows.length,
16204
+ sample_questions: rows.slice(0, 5).map((row) => row.question).join(" | "),
16205
+ source_domains: [...new Set(rows.map((row) => row.source_domain).filter(Boolean))].slice(0, 5).join("; "),
16206
+ terms: textTerms(rows.map((row) => `${row.question} ${row.answer_excerpt}`).join(" "), 10)
16207
+ })).sort((a, b) => Number(b.question_count) - Number(a.question_count));
16208
+ await ctx.artifacts.writeCsv("PAA questions CSV", "paa-questions.csv", ["position", "intent", "question", "answer_excerpt", "source_title", "source_domain", "source_url"], questions);
16209
+ await ctx.artifacts.writeCsv("Source domains CSV", "source-domains.csv", ["domain", "question_mentions", "source_url_count", "top_intents"], sourceRows2);
16210
+ await ctx.artifacts.writeCsv("Section map CSV", "section-map.csv", ["recommended_section", "question_count", "sample_questions", "source_domains", "terms"], sectionRows);
16211
+ await ctx.artifacts.writeJson("PAA brief evidence", "evidence.json", { input, paa, questions, sourceRows: sourceRows2, sectionRows });
16212
+ await ctx.artifacts.writeText("Writer brief", "writer-brief.md", [
16213
+ `# PAA Expansion Brief: ${input.keyword}`,
16214
+ "",
16215
+ `Location: ${input.location ?? "not specified"}`,
16216
+ "",
16217
+ "## Suggested Page Structure",
16218
+ ...sectionRows.map((row) => `- ${row.recommended_section}: answer ${row.question_count} related question(s). Sample: ${row.sample_questions}`),
16219
+ "",
16220
+ "## Writing Rules",
16221
+ "- Answer the highest-frequency question in the first 60 words of each section.",
16222
+ "- Use exact customer question language from `paa-questions.csv` for H2/H3 candidates.",
16223
+ "- Use `source-domains.csv` to identify which source types Google is already rewarding.",
16224
+ "- Do not invent citations; cite only rows that have a source URL."
16225
+ ].join("\n"));
16226
+ const summary = `${questions.length} PAA questions grouped into ${sectionRows.length} writing sections.`;
16227
+ const reportPath = await ctx.artifacts.writeHtml("HTML report", "report.html", renderWorkflowReport({
16228
+ title: "PAA Expansion Brief",
16229
+ subtitle: `${input.keyword}${input.location ? ` \xB7 ${input.location}` : ""}`,
16230
+ summary,
16231
+ warnings,
16232
+ tables: [
16233
+ { title: "Section Map", columns: ["recommended_section", "question_count", "sample_questions", "source_domains", "terms"], rows: sectionRows },
16234
+ { title: "Questions", columns: ["position", "intent", "question", "source_domain"], rows: questions.slice(0, 120) },
16235
+ { title: "Source Domains", columns: ["domain", "question_mentions", "source_url_count", "top_intents"], rows: sourceRows2 }
16236
+ ]
16237
+ }));
16238
+ const counts = { questions: questions.length, sections: sectionRows.length, sourceDomains: sourceRows2.length };
16239
+ await ctx.artifacts.writeManifest("succeeded", counts, warnings, []);
16240
+ return { title: "PAA Expansion Brief", summary, status: "succeeded", counts, warnings, errors: [], reportPath };
16241
+ }
16242
+ };
16243
+ aiOverviewLanguageWorkflowDefinition = {
16244
+ id: "ai-overview-language",
16245
+ title: "AI Overview Language Brief",
16246
+ description: "Turn AI Overview, citation, PAA, and ranking-page evidence into answer-block and citation-hook guidance.",
16247
+ inputSchema: AiOverviewLanguageInputSchema,
16248
+ async run(input, ctx) {
16249
+ await ctx.artifacts.writeManifest("running", {}, [], []);
16250
+ const warnings = [];
16251
+ const targetDomain = normalizeDomain2(input.domain ?? input.url ?? null);
16252
+ const serp = await ctx.client.post("/harvest/sync", {
16253
+ query: input.keyword,
16254
+ location: input.location,
16255
+ serpOnly: true,
16256
+ maxQuestions: 1,
16257
+ format: "json"
16258
+ }, 18e4);
16259
+ await ctx.artifacts.writeJson("SERP raw JSON", "raw/serp.json", serp);
16260
+ let paa = null;
16261
+ try {
16262
+ paa = await ctx.client.post("/harvest/sync", {
16263
+ query: input.keyword,
16264
+ location: input.location,
16265
+ maxQuestions: input.maxQuestions,
16266
+ format: "json"
16267
+ }, 28e4);
16268
+ await ctx.artifacts.writeJson("PAA raw JSON", "raw/paa.json", paa);
16269
+ } catch (err) {
16270
+ warnings.push(`PAA evidence unavailable: ${err instanceof Error ? err.message : String(err)}`);
16271
+ }
16272
+ const citations = (serp.aiOverview?.citations ?? []).map((citation, index) => {
16273
+ const domain = domainFromUrl2(citation.href ?? "");
16274
+ return {
16275
+ citation_position: index + 1,
16276
+ citation_text: citation.text ?? "",
16277
+ url: citation.href ?? "",
16278
+ domain,
16279
+ is_target: targetDomain ? domain === targetDomain : false
16280
+ };
16281
+ });
16282
+ const aioSentences = splitSentences(serp.aiOverview?.text);
16283
+ const claimRows = aioSentences.map((sentence, index) => ({
16284
+ position: index + 1,
16285
+ claim_type: classifySentence(sentence),
16286
+ sentence,
16287
+ reusable_pattern: sentence.length > 140 ? `${sentence.slice(0, 140)}...` : sentence
16288
+ }));
16289
+ const questions = questionRows(paa);
16290
+ const citationSources = (serp.aiOverview?.citations ?? []).filter((citation) => citation.href).map((citation, index) => ({ position: index + 1, title: citation.text, url: citation.href, domain: domainFromUrl2(citation.href) })).slice(0, input.extractTop);
16291
+ const extractedCitations = input.extractTop > 0 ? await extractPages(ctx, citationSources, warnings, "Citation") : [];
16292
+ const extractedRows = extractedCitations.map(({ source, page }) => pageSummaryRow(page, { url: source.url ?? "", domain: normalizeDomain2(source.domain ?? "") ?? domainFromUrl2(source.url), position: source.position, title: source.title }));
16293
+ const languageRows = [
16294
+ {
16295
+ block: "direct_answer",
16296
+ guidance: "Open with a 40-70 word answer that directly resolves the query before adding context.",
16297
+ evidence_basis: questions[0]?.question ?? input.keyword
16298
+ },
16299
+ {
16300
+ block: "criteria_or_steps",
16301
+ guidance: "List the criteria, steps, or decision factors Google is already compressing into AI Overview language.",
16302
+ evidence_basis: claimRows.filter((row) => ["criteria", "process"].includes(String(row.claim_type))).map((row) => row.sentence).slice(0, 3).join(" | ")
16303
+ },
16304
+ {
16305
+ block: "citation_hook",
16306
+ guidance: "Add source-worthy details competitors can cite: definitions, numbers, examples, process details, and named entity relationships.",
16307
+ evidence_basis: citations.map((row) => `${row.domain}: ${row.citation_text}`).slice(0, 5).join(" | ")
16308
+ },
16309
+ {
16310
+ block: "faq_followups",
16311
+ guidance: "Use PAA phrasing for follow-up sections so the page answers adjacent questions in Google language.",
16312
+ evidence_basis: questions.slice(0, 5).map((row) => row.question).join(" | ")
16313
+ }
16314
+ ];
16315
+ await ctx.artifacts.writeCsv("AI Overview citations CSV", "ai-overview-citations.csv", ["citation_position", "citation_text", "url", "domain", "is_target"], citations);
16316
+ await ctx.artifacts.writeCsv("AI Overview claim patterns CSV", "claim-patterns.csv", ["position", "claim_type", "sentence", "reusable_pattern"], claimRows);
16317
+ await ctx.artifacts.writeCsv("Language guidance CSV", "language-guidance.csv", ["block", "guidance", "evidence_basis"], languageRows);
16318
+ await ctx.artifacts.writeCsv("PAA questions CSV", "paa-questions.csv", ["position", "intent", "question", "answer_excerpt", "source_title", "source_domain", "source_url"], questions);
16319
+ await ctx.artifacts.writeCsv("Extracted citation pages CSV", "citation-pages.csv", ["position", "domain", "url", "serp_title", "page_title", "h1", "meta_description", "word_count", "heading_count", "schema_types"], extractedRows);
16320
+ await ctx.artifacts.writeJson("AI Overview language evidence", "evidence.json", { input, serp, paa, citations, claimRows, languageRows, extractedRows, warnings });
16321
+ await ctx.artifacts.writeText("Answer block template", "answer-block-template.md", [
16322
+ `# AI Overview Language Brief: ${input.keyword}`,
16323
+ "",
16324
+ `AI Overview detected: ${serp.aiOverview?.detected ? "yes" : "no"}`,
16325
+ `Target cited: ${targetDomain ? citations.some((row) => row.is_target) ? "yes" : "no" : "target not specified"}`,
16326
+ "",
16327
+ "## Direct Answer Block",
16328
+ "Write one compact answer block that starts with the answer, not background. Keep it clear enough that Google could lift it as a standalone summary.",
16329
+ "",
16330
+ "## Suggested Follow-Up Blocks",
16331
+ ...languageRows.map((row) => `- ${row.block}: ${row.guidance}`),
16332
+ "",
16333
+ "## Evidence to Mirror",
16334
+ ...claimRows.slice(0, 8).map((row) => `- ${row.claim_type}: ${row.sentence}`),
16335
+ "",
16336
+ "## Citation Hooks",
16337
+ ...citations.slice(0, 8).map((row) => `- ${row.domain}: ${row.citation_text}`)
16338
+ ].join("\n"));
16339
+ const summary = `${citations.length} AI Overview citations, ${claimRows.length} claim patterns, ${questions.length} PAA questions, ${extractedRows.length} citation pages extracted.`;
16340
+ const reportPath = await ctx.artifacts.writeHtml("HTML report", "report.html", renderWorkflowReport({
16341
+ title: "AI Overview Language Brief",
16342
+ subtitle: `${input.keyword}${input.location ? ` \xB7 ${input.location}` : ""}`,
16343
+ summary,
16344
+ warnings,
16345
+ tables: [
16346
+ { title: "Language Guidance", columns: ["block", "guidance", "evidence_basis"], rows: languageRows },
16347
+ { title: "AI Overview Citations", columns: ["citation_position", "citation_text", "domain", "is_target"], rows: citations },
16348
+ { title: "Claim Patterns", columns: ["position", "claim_type", "sentence"], rows: claimRows },
16349
+ { title: "PAA Follow-Ups", columns: ["position", "intent", "question", "source_domain"], rows: questions.slice(0, 60) }
16350
+ ]
16351
+ }));
16352
+ const status = warnings.length || !serp.aiOverview?.detected ? "partial" : "succeeded";
16353
+ const counts = { citations: citations.length, claimPatterns: claimRows.length, questions: questions.length, extractedCitationPages: extractedRows.length };
16354
+ await ctx.artifacts.writeManifest(status, counts, warnings, []);
16355
+ return { title: "AI Overview Language Brief", summary, status, counts, warnings, errors: [], reportPath };
16356
+ }
16357
+ };
16358
+ }
16359
+ });
16360
+
15649
16361
  // src/workflows/registry.ts
15650
16362
  function listWorkflowDefinitions() {
15651
16363
  return DEFINITIONS.map(({ id, title, description }) => ({ id, title, description }));
@@ -15681,20 +16393,25 @@ ${message}
15681
16393
  throw err;
15682
16394
  }
15683
16395
  }
15684
- var import_zod22, DEFINITIONS;
16396
+ var import_zod23, DEFINITIONS;
15685
16397
  var init_registry2 = __esm({
15686
16398
  "src/workflows/registry.ts"() {
15687
16399
  "use strict";
15688
- import_zod22 = require("zod");
16400
+ import_zod23 = require("zod");
15689
16401
  init_artifact_writer();
15690
16402
  init_http_client2();
15691
16403
  init_agent_packet();
15692
16404
  init_directory();
15693
16405
  init_local_competitive_audit();
16406
+ init_comparison_briefs();
15694
16407
  DEFINITIONS = [
15695
16408
  directoryWorkflowDefinition,
15696
16409
  agentPacketWorkflowDefinition,
15697
- localCompetitiveAuditWorkflowDefinition
16410
+ localCompetitiveAuditWorkflowDefinition,
16411
+ mapComparisonWorkflowDefinition,
16412
+ serpComparisonWorkflowDefinition,
16413
+ paaExpansionBriefWorkflowDefinition,
16414
+ aiOverviewLanguageWorkflowDefinition
15698
16415
  ];
15699
16416
  }
15700
16417
  });
@@ -15868,46 +16585,46 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
15868
16585
  }
15869
16586
  return { dispatched: results.length, results };
15870
16587
  }
15871
- var import_node_crypto2, import_promises7, import_hono7, import_zod23, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema;
16588
+ var import_node_crypto2, import_promises7, import_hono7, import_zod24, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema;
15872
16589
  var init_workflow_routes = __esm({
15873
16590
  "src/api/workflow-routes.ts"() {
15874
16591
  "use strict";
15875
16592
  import_node_crypto2 = require("crypto");
15876
16593
  import_promises7 = require("fs/promises");
15877
16594
  import_hono7 = require("hono");
15878
- import_zod23 = require("zod");
16595
+ import_zod24 = require("zod");
15879
16596
  init_artifact_writer();
15880
16597
  init_registry2();
15881
16598
  init_api_auth();
15882
16599
  init_db();
15883
16600
  init_url_utils();
15884
16601
  workflowApp = new import_hono7.Hono();
15885
- WorkflowInputSchema = import_zod23.z.record(import_zod23.z.unknown()).default({});
15886
- WorkflowIdSchema = import_zod23.z.string().min(1);
15887
- CadenceSchema = import_zod23.z.enum(["daily", "weekly", "monthly"]);
15888
- ScheduleStatusSchema = import_zod23.z.enum(["active", "paused"]);
15889
- RunBodySchema = import_zod23.z.object({
16602
+ WorkflowInputSchema = import_zod24.z.record(import_zod24.z.unknown()).default({});
16603
+ WorkflowIdSchema = import_zod24.z.string().min(1);
16604
+ CadenceSchema = import_zod24.z.enum(["daily", "weekly", "monthly"]);
16605
+ ScheduleStatusSchema = import_zod24.z.enum(["active", "paused"]);
16606
+ RunBodySchema = import_zod24.z.object({
15890
16607
  workflowId: WorkflowIdSchema,
15891
16608
  input: WorkflowInputSchema,
15892
- webhookUrl: import_zod23.z.string().url().optional()
16609
+ webhookUrl: import_zod24.z.string().url().optional()
15893
16610
  });
15894
- ScheduleCreateSchema = import_zod23.z.object({
16611
+ ScheduleCreateSchema = import_zod24.z.object({
15895
16612
  workflowId: WorkflowIdSchema,
15896
- name: import_zod23.z.string().min(1).max(120).optional(),
16613
+ name: import_zod24.z.string().min(1).max(120).optional(),
15897
16614
  input: WorkflowInputSchema,
15898
16615
  cadence: CadenceSchema.default("weekly"),
15899
- timezone: import_zod23.z.string().min(1).max(64).default("UTC"),
15900
- webhookUrl: import_zod23.z.string().url().optional(),
15901
- nextRunAt: import_zod23.z.string().datetime().optional()
16616
+ timezone: import_zod24.z.string().min(1).max(64).default("UTC"),
16617
+ webhookUrl: import_zod24.z.string().url().optional(),
16618
+ nextRunAt: import_zod24.z.string().datetime().optional()
15902
16619
  });
15903
- SchedulePatchSchema = import_zod23.z.object({
15904
- name: import_zod23.z.string().min(1).max(120).optional(),
16620
+ SchedulePatchSchema = import_zod24.z.object({
16621
+ name: import_zod24.z.string().min(1).max(120).optional(),
15905
16622
  status: ScheduleStatusSchema.optional(),
15906
16623
  input: WorkflowInputSchema.optional(),
15907
16624
  cadence: CadenceSchema.optional(),
15908
- timezone: import_zod23.z.string().min(1).max(64).optional(),
15909
- webhookUrl: import_zod23.z.string().url().nullable().optional(),
15910
- nextRunAt: import_zod23.z.string().datetime().nullable().optional()
16625
+ timezone: import_zod24.z.string().min(1).max(64).optional(),
16626
+ webhookUrl: import_zod24.z.string().url().nullable().optional(),
16627
+ nextRunAt: import_zod24.z.string().datetime().nullable().optional()
15911
16628
  });
15912
16629
  workflowApp.get("/definitions", createApiKeyAuth(), (c) => {
15913
16630
  return c.json({ workflows: listWorkflowDefinitions() });
@@ -18246,11 +18963,11 @@ function isPublicHttpUrl(value) {
18246
18963
  return false;
18247
18964
  }
18248
18965
  }
18249
- var import_zod24, SerpIntelligenceDeviceValues, SerpIntelligenceProxyModeValues, SerpIntelligenceAttemptOutcomeValues, SerpIntelligenceLocalizationStatusValues, SerpPageSnapshotSourceKindValues, SerpPageFetchStatusValues, SerpPageFetchedViaValues, HostnameSuffixPattern, Ipv4Pattern, SerpIntelligencePublicHttpUrlSchema, SerpIntelligenceCaptureBodySchema, SerpIntelligencePageSnapshotRequestSchema, SerpIntelligencePageSnapshotsBodySchema, SerpIntelligenceAICitationSchema, SerpIntelligenceOrganicResultSchema, SerpIntelligenceLocationEvidenceSchema, SerpIntelligenceHarvestResultSchema, SerpIntelligenceCaptureAttemptSchema, SerpPageSnapshotCaptureSchema, SerpIntelligenceCaptureResponseSchema, SerpIntelligencePageSnapshotsResponseSchema;
18966
+ var import_zod25, SerpIntelligenceDeviceValues, SerpIntelligenceProxyModeValues, SerpIntelligenceAttemptOutcomeValues, SerpIntelligenceLocalizationStatusValues, SerpPageSnapshotSourceKindValues, SerpPageFetchStatusValues, SerpPageFetchedViaValues, HostnameSuffixPattern, Ipv4Pattern, SerpIntelligencePublicHttpUrlSchema, SerpIntelligenceCaptureBodySchema, SerpIntelligencePageSnapshotRequestSchema, SerpIntelligencePageSnapshotsBodySchema, SerpIntelligenceAICitationSchema, SerpIntelligenceOrganicResultSchema, SerpIntelligenceLocationEvidenceSchema, SerpIntelligenceHarvestResultSchema, SerpIntelligenceCaptureAttemptSchema, SerpPageSnapshotCaptureSchema, SerpIntelligenceCaptureResponseSchema, SerpIntelligencePageSnapshotsResponseSchema;
18250
18967
  var init_schemas4 = __esm({
18251
18968
  "src/serp-intelligence/schemas.ts"() {
18252
18969
  "use strict";
18253
- import_zod24 = require("zod");
18970
+ import_zod25 = require("zod");
18254
18971
  SerpIntelligenceDeviceValues = ["desktop", "mobile"];
18255
18972
  SerpIntelligenceProxyModeValues = ["location", "configured", "none"];
18256
18973
  SerpIntelligenceAttemptOutcomeValues = [
@@ -18284,171 +19001,171 @@ var init_schemas4 = __esm({
18284
19001
  SerpPageFetchedViaValues = ["fetch", "headless", "browser", "mcp"];
18285
19002
  HostnameSuffixPattern = /(^|\.)localhost$/i;
18286
19003
  Ipv4Pattern = /^\d{1,3}(?:\.\d{1,3}){3}$/;
18287
- SerpIntelligencePublicHttpUrlSchema = import_zod24.z.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
18288
- SerpIntelligenceCaptureBodySchema = import_zod24.z.object({
18289
- query: import_zod24.z.string().trim().min(1, "query is required"),
18290
- location: import_zod24.z.string().trim().min(1).optional(),
18291
- gl: import_zod24.z.string().trim().length(2).default("us"),
18292
- hl: import_zod24.z.string().trim().length(2).default("en"),
18293
- device: import_zod24.z.enum(SerpIntelligenceDeviceValues).default("desktop"),
18294
- proxyMode: import_zod24.z.enum(SerpIntelligenceProxyModeValues).default("location"),
18295
- proxyZip: import_zod24.z.string().regex(/^\d{5}$/).optional(),
18296
- pages: import_zod24.z.number().int().min(1).max(2).default(1),
18297
- debug: import_zod24.z.boolean().default(false),
18298
- includePageSnapshots: import_zod24.z.boolean().default(false),
18299
- pageSnapshotLimit: import_zod24.z.number().int().min(0).max(10).default(0)
19004
+ SerpIntelligencePublicHttpUrlSchema = import_zod25.z.string().url().refine(isPublicHttpUrl, "url must be a public HTTP or HTTPS URL");
19005
+ SerpIntelligenceCaptureBodySchema = import_zod25.z.object({
19006
+ query: import_zod25.z.string().trim().min(1, "query is required"),
19007
+ location: import_zod25.z.string().trim().min(1).optional(),
19008
+ gl: import_zod25.z.string().trim().length(2).default("us"),
19009
+ hl: import_zod25.z.string().trim().length(2).default("en"),
19010
+ device: import_zod25.z.enum(SerpIntelligenceDeviceValues).default("desktop"),
19011
+ proxyMode: import_zod25.z.enum(SerpIntelligenceProxyModeValues).default("location"),
19012
+ proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional(),
19013
+ pages: import_zod25.z.number().int().min(1).max(2).default(1),
19014
+ debug: import_zod25.z.boolean().default(false),
19015
+ includePageSnapshots: import_zod25.z.boolean().default(false),
19016
+ pageSnapshotLimit: import_zod25.z.number().int().min(0).max(10).default(0)
18300
19017
  }).strict();
18301
- SerpIntelligencePageSnapshotRequestSchema = import_zod24.z.object({
19018
+ SerpIntelligencePageSnapshotRequestSchema = import_zod25.z.object({
18302
19019
  url: SerpIntelligencePublicHttpUrlSchema,
18303
- sourceKind: import_zod24.z.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
18304
- sourcePosition: import_zod24.z.number().int().min(1).optional()
19020
+ sourceKind: import_zod25.z.enum(SerpPageSnapshotSourceKindValues).default("configured_target"),
19021
+ sourcePosition: import_zod25.z.number().int().min(1).optional()
18305
19022
  }).strict();
18306
- SerpIntelligencePageSnapshotsBodySchema = import_zod24.z.object({
18307
- urls: import_zod24.z.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
18308
- targets: import_zod24.z.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
18309
- maxConcurrency: import_zod24.z.number().int().min(1).max(5).default(2),
18310
- timeoutMs: import_zod24.z.number().int().min(1e3).max(6e4).default(15e3),
18311
- debug: import_zod24.z.boolean().default(false)
19023
+ SerpIntelligencePageSnapshotsBodySchema = import_zod25.z.object({
19024
+ urls: import_zod25.z.array(SerpIntelligencePublicHttpUrlSchema).min(1).max(25),
19025
+ targets: import_zod25.z.array(SerpIntelligencePageSnapshotRequestSchema).min(1).max(25).optional(),
19026
+ maxConcurrency: import_zod25.z.number().int().min(1).max(5).default(2),
19027
+ timeoutMs: import_zod25.z.number().int().min(1e3).max(6e4).default(15e3),
19028
+ debug: import_zod25.z.boolean().default(false)
18312
19029
  }).strict();
18313
- SerpIntelligenceAICitationSchema = import_zod24.z.object({
18314
- text: import_zod24.z.string(),
18315
- href: import_zod24.z.string()
19030
+ SerpIntelligenceAICitationSchema = import_zod25.z.object({
19031
+ text: import_zod25.z.string(),
19032
+ href: import_zod25.z.string()
18316
19033
  }).strict();
18317
- SerpIntelligenceOrganicResultSchema = import_zod24.z.object({
18318
- position: import_zod24.z.number().int().min(1),
18319
- title: import_zod24.z.string(),
18320
- url: import_zod24.z.string(),
18321
- domain: import_zod24.z.string(),
18322
- cite: import_zod24.z.string().nullable(),
18323
- snippet: import_zod24.z.string().nullable(),
18324
- isRedditStyle: import_zod24.z.boolean(),
18325
- inlineRating: import_zod24.z.object({
18326
- value: import_zod24.z.string(),
18327
- count: import_zod24.z.string()
19034
+ SerpIntelligenceOrganicResultSchema = import_zod25.z.object({
19035
+ position: import_zod25.z.number().int().min(1),
19036
+ title: import_zod25.z.string(),
19037
+ url: import_zod25.z.string(),
19038
+ domain: import_zod25.z.string(),
19039
+ cite: import_zod25.z.string().nullable(),
19040
+ snippet: import_zod25.z.string().nullable(),
19041
+ isRedditStyle: import_zod25.z.boolean(),
19042
+ inlineRating: import_zod25.z.object({
19043
+ value: import_zod25.z.string(),
19044
+ count: import_zod25.z.string()
18328
19045
  }).strict().nullable()
18329
19046
  }).strict();
18330
- SerpIntelligenceLocationEvidenceSchema = import_zod24.z.object({
18331
- status: import_zod24.z.enum(SerpIntelligenceLocalizationStatusValues),
18332
- expected: import_zod24.z.object({
18333
- city: import_zod24.z.string(),
18334
- regionCode: import_zod24.z.string().nullable(),
18335
- canonicalLocation: import_zod24.z.string()
19047
+ SerpIntelligenceLocationEvidenceSchema = import_zod25.z.object({
19048
+ status: import_zod25.z.enum(SerpIntelligenceLocalizationStatusValues),
19049
+ expected: import_zod25.z.object({
19050
+ city: import_zod25.z.string(),
19051
+ regionCode: import_zod25.z.string().nullable(),
19052
+ canonicalLocation: import_zod25.z.string()
18336
19053
  }).strict().nullable(),
18337
- candidates: import_zod24.z.array(import_zod24.z.object({
18338
- city: import_zod24.z.string(),
18339
- regionCode: import_zod24.z.string(),
18340
- count: import_zod24.z.number().int().min(0),
18341
- examples: import_zod24.z.array(import_zod24.z.string())
19054
+ candidates: import_zod25.z.array(import_zod25.z.object({
19055
+ city: import_zod25.z.string(),
19056
+ regionCode: import_zod25.z.string(),
19057
+ count: import_zod25.z.number().int().min(0),
19058
+ examples: import_zod25.z.array(import_zod25.z.string())
18342
19059
  }).strict())
18343
19060
  }).strict();
18344
- SerpIntelligenceHarvestResultSchema = import_zod24.z.object({
18345
- seed: import_zod24.z.string(),
18346
- location: import_zod24.z.string().nullable(),
18347
- extractedAt: import_zod24.z.string(),
18348
- totalQuestions: import_zod24.z.number().int().min(0),
18349
- surface: import_zod24.z.enum(["web", "aim", "unknown"]),
18350
- aiOverview: import_zod24.z.object({
18351
- detected: import_zod24.z.boolean(),
18352
- text: import_zod24.z.string().nullable(),
18353
- citations: import_zod24.z.array(SerpIntelligenceAICitationSchema),
18354
- expanded: import_zod24.z.boolean().optional(),
18355
- fullyExpanded: import_zod24.z.boolean().optional(),
18356
- sections: import_zod24.z.array(import_zod24.z.string()).optional()
19061
+ SerpIntelligenceHarvestResultSchema = import_zod25.z.object({
19062
+ seed: import_zod25.z.string(),
19063
+ location: import_zod25.z.string().nullable(),
19064
+ extractedAt: import_zod25.z.string(),
19065
+ totalQuestions: import_zod25.z.number().int().min(0),
19066
+ surface: import_zod25.z.enum(["web", "aim", "unknown"]),
19067
+ aiOverview: import_zod25.z.object({
19068
+ detected: import_zod25.z.boolean(),
19069
+ text: import_zod25.z.string().nullable(),
19070
+ citations: import_zod25.z.array(SerpIntelligenceAICitationSchema),
19071
+ expanded: import_zod25.z.boolean().optional(),
19072
+ fullyExpanded: import_zod25.z.boolean().optional(),
19073
+ sections: import_zod25.z.array(import_zod25.z.string()).optional()
18357
19074
  }).strict(),
18358
- aiMode: import_zod24.z.object({
18359
- detected: import_zod24.z.boolean(),
18360
- text: import_zod24.z.string().nullable(),
18361
- citations: import_zod24.z.array(SerpIntelligenceAICitationSchema)
19075
+ aiMode: import_zod25.z.object({
19076
+ detected: import_zod25.z.boolean(),
19077
+ text: import_zod25.z.string().nullable(),
19078
+ citations: import_zod25.z.array(SerpIntelligenceAICitationSchema)
18362
19079
  }).strict(),
18363
- tree: import_zod24.z.array(import_zod24.z.unknown()),
18364
- flat: import_zod24.z.array(import_zod24.z.unknown()),
18365
- videos: import_zod24.z.array(import_zod24.z.unknown()),
18366
- forums: import_zod24.z.array(import_zod24.z.unknown()),
18367
- organicResults: import_zod24.z.array(SerpIntelligenceOrganicResultSchema),
18368
- localPack: import_zod24.z.array(import_zod24.z.unknown()),
18369
- entityIds: import_zod24.z.object({
18370
- entities: import_zod24.z.array(import_zod24.z.object({
18371
- name: import_zod24.z.string(),
18372
- kgId: import_zod24.z.string().nullable(),
18373
- cid: import_zod24.z.string().nullable(),
18374
- gcid: import_zod24.z.string().nullable()
19080
+ tree: import_zod25.z.array(import_zod25.z.unknown()),
19081
+ flat: import_zod25.z.array(import_zod25.z.unknown()),
19082
+ videos: import_zod25.z.array(import_zod25.z.unknown()),
19083
+ forums: import_zod25.z.array(import_zod25.z.unknown()),
19084
+ organicResults: import_zod25.z.array(SerpIntelligenceOrganicResultSchema),
19085
+ localPack: import_zod25.z.array(import_zod25.z.unknown()),
19086
+ entityIds: import_zod25.z.object({
19087
+ entities: import_zod25.z.array(import_zod25.z.object({
19088
+ name: import_zod25.z.string(),
19089
+ kgId: import_zod25.z.string().nullable(),
19090
+ cid: import_zod25.z.string().nullable(),
19091
+ gcid: import_zod25.z.string().nullable()
18375
19092
  }).strict()),
18376
- kgIds: import_zod24.z.array(import_zod24.z.string()),
18377
- cids: import_zod24.z.array(import_zod24.z.string()),
18378
- gcids: import_zod24.z.array(import_zod24.z.string())
19093
+ kgIds: import_zod25.z.array(import_zod25.z.string()),
19094
+ cids: import_zod25.z.array(import_zod25.z.string()),
19095
+ gcids: import_zod25.z.array(import_zod25.z.string())
18379
19096
  }).strict(),
18380
- stats: import_zod24.z.object({
18381
- seed: import_zod24.z.string(),
18382
- totalQuestions: import_zod24.z.number().int().min(0),
18383
- maxDepthReached: import_zod24.z.number().int().min(0),
18384
- durationMs: import_zod24.z.number().min(0),
18385
- errorCount: import_zod24.z.number().int().min(0)
19097
+ stats: import_zod25.z.object({
19098
+ seed: import_zod25.z.string(),
19099
+ totalQuestions: import_zod25.z.number().int().min(0),
19100
+ maxDepthReached: import_zod25.z.number().int().min(0),
19101
+ durationMs: import_zod25.z.number().min(0),
19102
+ errorCount: import_zod25.z.number().int().min(0)
18386
19103
  }).strict(),
18387
- diagnostics: import_zod24.z.object({
18388
- completionStatus: import_zod24.z.enum(["paa_found", "no_paa", "serp_only"]),
18389
- problem: import_zod24.z.null(),
18390
- warnings: import_zod24.z.array(import_zod24.z.unknown()).optional(),
18391
- debug: import_zod24.z.object({
19104
+ diagnostics: import_zod25.z.object({
19105
+ completionStatus: import_zod25.z.enum(["paa_found", "no_paa", "serp_only"]),
19106
+ problem: import_zod25.z.null(),
19107
+ warnings: import_zod25.z.array(import_zod25.z.unknown()).optional(),
19108
+ debug: import_zod25.z.object({
18392
19109
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.optional()
18393
19110
  }).passthrough().optional()
18394
19111
  }).passthrough(),
18395
- whatPeopleSaying: import_zod24.z.array(import_zod24.z.unknown())
19112
+ whatPeopleSaying: import_zod25.z.array(import_zod25.z.unknown())
18396
19113
  }).strict();
18397
- SerpIntelligenceCaptureAttemptSchema = import_zod24.z.object({
18398
- attemptNumber: import_zod24.z.number().int().min(1),
18399
- outcome: import_zod24.z.enum(SerpIntelligenceAttemptOutcomeValues),
18400
- startedAt: import_zod24.z.string().optional(),
18401
- completedAt: import_zod24.z.string().optional(),
18402
- durationMs: import_zod24.z.number().min(0).optional(),
18403
- problemCode: import_zod24.z.string().optional(),
18404
- message: import_zod24.z.string().optional(),
18405
- kernelSessionId: import_zod24.z.string().nullable().optional(),
18406
- cleanupSucceeded: import_zod24.z.boolean().nullable().optional()
19114
+ SerpIntelligenceCaptureAttemptSchema = import_zod25.z.object({
19115
+ attemptNumber: import_zod25.z.number().int().min(1),
19116
+ outcome: import_zod25.z.enum(SerpIntelligenceAttemptOutcomeValues),
19117
+ startedAt: import_zod25.z.string().optional(),
19118
+ completedAt: import_zod25.z.string().optional(),
19119
+ durationMs: import_zod25.z.number().min(0).optional(),
19120
+ problemCode: import_zod25.z.string().optional(),
19121
+ message: import_zod25.z.string().optional(),
19122
+ kernelSessionId: import_zod25.z.string().nullable().optional(),
19123
+ cleanupSucceeded: import_zod25.z.boolean().nullable().optional()
18407
19124
  }).strict();
18408
- SerpPageSnapshotCaptureSchema = import_zod24.z.object({
19125
+ SerpPageSnapshotCaptureSchema = import_zod25.z.object({
18409
19126
  url: SerpIntelligencePublicHttpUrlSchema,
18410
19127
  requestedUrl: SerpIntelligencePublicHttpUrlSchema,
18411
19128
  finalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
18412
- sourceKind: import_zod24.z.enum(SerpPageSnapshotSourceKindValues),
18413
- sourcePosition: import_zod24.z.number().int().min(1).nullable(),
18414
- status: import_zod24.z.enum(SerpPageFetchStatusValues),
18415
- fetchedVia: import_zod24.z.enum(SerpPageFetchedViaValues).nullable(),
18416
- httpStatus: import_zod24.z.number().int().min(100).max(599).nullable(),
18417
- contentType: import_zod24.z.string().nullable(),
18418
- title: import_zod24.z.string().nullable(),
19129
+ sourceKind: import_zod25.z.enum(SerpPageSnapshotSourceKindValues),
19130
+ sourcePosition: import_zod25.z.number().int().min(1).nullable(),
19131
+ status: import_zod25.z.enum(SerpPageFetchStatusValues),
19132
+ fetchedVia: import_zod25.z.enum(SerpPageFetchedViaValues).nullable(),
19133
+ httpStatus: import_zod25.z.number().int().min(100).max(599).nullable(),
19134
+ contentType: import_zod25.z.string().nullable(),
19135
+ title: import_zod25.z.string().nullable(),
18419
19136
  canonicalUrl: SerpIntelligencePublicHttpUrlSchema.nullable(),
18420
- metaDescription: import_zod24.z.string().nullable(),
18421
- headings: import_zod24.z.array(import_zod24.z.object({
18422
- level: import_zod24.z.number().int().min(1).max(6),
18423
- text: import_zod24.z.string()
19137
+ metaDescription: import_zod25.z.string().nullable(),
19138
+ headings: import_zod25.z.array(import_zod25.z.object({
19139
+ level: import_zod25.z.number().int().min(1).max(6),
19140
+ text: import_zod25.z.string()
18424
19141
  }).strict()).default([]),
18425
- artifact: import_zod24.z.object({
18426
- htmlBlobUrl: import_zod24.z.string().url().nullable(),
18427
- textBlobUrl: import_zod24.z.string().url().nullable(),
18428
- markdownBlobUrl: import_zod24.z.string().url().nullable(),
18429
- screenshotBlobUrl: import_zod24.z.string().url().nullable(),
18430
- contentSha256: import_zod24.z.string().nullable(),
18431
- capturedAt: import_zod24.z.string().nullable()
19142
+ artifact: import_zod25.z.object({
19143
+ htmlBlobUrl: import_zod25.z.string().url().nullable(),
19144
+ textBlobUrl: import_zod25.z.string().url().nullable(),
19145
+ markdownBlobUrl: import_zod25.z.string().url().nullable(),
19146
+ screenshotBlobUrl: import_zod25.z.string().url().nullable(),
19147
+ contentSha256: import_zod25.z.string().nullable(),
19148
+ capturedAt: import_zod25.z.string().nullable()
18432
19149
  }).strict(),
18433
- error: import_zod24.z.object({
18434
- code: import_zod24.z.string(),
18435
- message: import_zod24.z.string()
19150
+ error: import_zod25.z.object({
19151
+ code: import_zod25.z.string(),
19152
+ message: import_zod25.z.string()
18436
19153
  }).strict().nullable()
18437
19154
  }).strict();
18438
- SerpIntelligenceCaptureResponseSchema = import_zod24.z.object({
19155
+ SerpIntelligenceCaptureResponseSchema = import_zod25.z.object({
18439
19156
  harvestResult: SerpIntelligenceHarvestResultSchema,
18440
- attempts: import_zod24.z.array(SerpIntelligenceCaptureAttemptSchema),
19157
+ attempts: import_zod25.z.array(SerpIntelligenceCaptureAttemptSchema),
18441
19158
  locationEvidence: SerpIntelligenceLocationEvidenceSchema.nullable(),
18442
- pageSnapshotArtifacts: import_zod24.z.array(SerpPageSnapshotCaptureSchema),
18443
- billing: import_zod24.z.object({
18444
- creditsUsed: import_zod24.z.number().min(0).optional(),
18445
- requestId: import_zod24.z.string().optional(),
18446
- jobId: import_zod24.z.string().optional()
19159
+ pageSnapshotArtifacts: import_zod25.z.array(SerpPageSnapshotCaptureSchema),
19160
+ billing: import_zod25.z.object({
19161
+ creditsUsed: import_zod25.z.number().min(0).optional(),
19162
+ requestId: import_zod25.z.string().optional(),
19163
+ jobId: import_zod25.z.string().optional()
18447
19164
  }).strict().optional()
18448
19165
  }).strict();
18449
- SerpIntelligencePageSnapshotsResponseSchema = import_zod24.z.object({
18450
- pageSnapshotArtifacts: import_zod24.z.array(SerpPageSnapshotCaptureSchema),
18451
- attempts: import_zod24.z.array(SerpIntelligenceCaptureAttemptSchema).default([])
19166
+ SerpIntelligencePageSnapshotsResponseSchema = import_zod25.z.object({
19167
+ pageSnapshotArtifacts: import_zod25.z.array(SerpPageSnapshotCaptureSchema),
19168
+ attempts: import_zod25.z.array(SerpIntelligenceCaptureAttemptSchema).default([])
18452
19169
  }).strict();
18453
19170
  }
18454
19171
  });
@@ -18826,133 +19543,133 @@ var PACKAGE_VERSION;
18826
19543
  var init_version = __esm({
18827
19544
  "src/version.ts"() {
18828
19545
  "use strict";
18829
- PACKAGE_VERSION = "0.2.11";
19546
+ PACKAGE_VERSION = "0.2.13";
18830
19547
  }
18831
19548
  });
18832
19549
 
18833
19550
  // src/mcp/mcp-tool-schemas.ts
18834
- var import_zod25, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, CreditsInfoInputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
19551
+ var import_zod26, HarvestPaaInputSchema, ExtractUrlInputSchema, MapSiteUrlsInputSchema, ExtractSiteInputSchema, YoutubeHarvestInputSchema, YoutubeTranscribeInputSchema, FacebookPageIntelInputSchema, FacebookAdSearchInputSchema, FacebookAdTranscribeInputSchema, MapsPlaceIntelInputSchema, MapsSearchInputSchema, DirectoryWorkflowInputSchema, RankTrackerModeSchema, RankTrackerBlueprintInputSchema, NullableString, MapsSearchAttemptOutput, MapsSearchOutputSchema, DirectoryMapsBusinessOutput, DirectoryWorkflowOutputSchema, RankTrackerToolPlanOutput, RankTrackerTableOutput, RankTrackerCronJobOutput, RankTrackerBlueprintOutputSchema, OrganicResultOutput, AiOverviewOutput, EntityIdsOutput, HarvestPaaOutputSchema, SearchSerpOutputSchema, ExtractUrlOutputSchema, ExtractSiteOutputSchema, MapsPlaceIntelOutputSchema, CreditsInfoOutputSchema, MapSiteUrlsOutputSchema, YoutubeHarvestOutputSchema, FacebookAdSearchOutputSchema, FacebookPageIntelOutputSchema, CreditsInfoInputSchema, SearchSerpInputSchema, CaptureSerpSnapshotInputSchema, ScreenshotInputSchema, CaptureSerpPageSnapshotsInputSchema;
18835
19552
  var init_mcp_tool_schemas = __esm({
18836
19553
  "src/mcp/mcp-tool-schemas.ts"() {
18837
19554
  "use strict";
18838
- import_zod25 = require("zod");
19555
+ import_zod26 = require("zod");
18839
19556
  HarvestPaaInputSchema = {
18840
- query: import_zod25.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
18841
- location: import_zod25.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
18842
- maxQuestions: import_zod25.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
18843
- gl: import_zod25.z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
18844
- hl: import_zod25.z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
18845
- device: import_zod25.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
18846
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
18847
- proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
18848
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
19557
+ query: import_zod26.z.string().min(1).describe('Core search topic only. If the user says "best hvac company in Denver CO", use query="best hvac company" and location="Denver, CO". Do not include the location in query when it can be separated.'),
19558
+ location: import_zod26.z.string().optional().describe('City, region, or country for geo-targeted results, inferred from the user request when present, e.g. "Denver, CO", "Tokyo, Japan", "London, UK".'),
19559
+ maxQuestions: import_zod26.z.number().int().min(1).max(200).default(30).describe("Number of PAA questions to extract. Default 30. Maximum 200. Use 10 for quick probes, 30 for normal research, 100-200 when the user asks for everything/full/deep research. Larger harvests get a longer server time budget (151-200 questions \u2192 up to 280s). Credits are charged by extracted question; unused request hold is refunded."),
19560
+ gl: import_zod26.z.string().length(2).default("us").describe("Google country code inferred from location or user language. Examples: United States us, United Kingdom gb, Japan jp, Canada ca, Australia au."),
19561
+ hl: import_zod26.z.string().default("en").describe("Google interface/content language inferred from the user request. Use en unless the user asks for another language or locale."),
19562
+ device: import_zod26.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
19563
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
19564
+ proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
19565
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior.")
18849
19566
  };
18850
19567
  ExtractUrlInputSchema = {
18851
- url: import_zod25.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
18852
- screenshot: import_zod25.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
18853
- screenshotDevice: import_zod25.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
18854
- extractBranding: import_zod25.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
18855
- downloadMedia: import_zod25.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
18856
- mediaTypes: import_zod25.z.array(import_zod25.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
18857
- allowLocal: import_zod25.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
19568
+ url: import_zod26.z.string().url().describe("Public http/https URL to extract. Use this when the user provides one specific page URL."),
19569
+ screenshot: import_zod26.z.boolean().default(false).describe("Also capture a full-page screenshot of the URL. Saved to ~/Downloads/mcp-scraper/screenshots/ and returned inline. Use when the user asks to see or capture the page visually."),
19570
+ screenshotDevice: import_zod26.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport for screenshot. desktop = 1440\xD7900. mobile = 390\xD7844. Default desktop."),
19571
+ extractBranding: import_zod26.z.boolean().default(false).describe("Extract brand colors, fonts, logo, and favicon using a rendered browser session. Returns colorScheme (light/dark), colors (primary/accent/background/text/heading as hex), fonts (heading/body family names), and assets (logo URL, favicon URL). Use when the user asks about brand colors, site theme, or brand assets."),
19572
+ downloadMedia: import_zod26.z.boolean().default(false).describe("Extract and download all page media (images, video, audio) to ~/Downloads/mcp-scraper/media/. Ad networks, tracking pixels, and noise URLs are filtered automatically. Use when the user asks to download or harvest assets from a page."),
19573
+ mediaTypes: import_zod26.z.array(import_zod26.z.enum(["image", "video", "audio"])).default(["image", "video", "audio"]).describe("Which media types to download. Default all three."),
19574
+ allowLocal: import_zod26.z.boolean().default(false).describe("Allow localhost and private-network URLs. For local development only.")
18858
19575
  };
18859
19576
  MapSiteUrlsInputSchema = {
18860
- url: import_zod25.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
18861
- maxUrls: import_zod25.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
19577
+ url: import_zod26.z.string().url().describe("Public website URL or domain to crawl for internal URLs. Use before extract_site when the user asks to audit/map/crawl a site."),
19578
+ maxUrls: import_zod26.z.number().int().min(1).max(500).optional().describe("Maximum URLs to discover. Use 100 for normal maps, higher when the user asks for a full inventory.")
18862
19579
  };
18863
19580
  ExtractSiteInputSchema = {
18864
- url: import_zod25.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
18865
- maxPages: import_zod25.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
19581
+ url: import_zod26.z.string().url().describe("Public website URL or domain to extract across multiple pages. Use when the user asks for a site audit, website crawl, or full-site content/schema extraction."),
19582
+ maxPages: import_zod26.z.number().int().min(1).max(50).optional().describe("Maximum pages to extract. Use 50 when the user asks for full results or a complete crawl within MCP limits.")
18866
19583
  };
18867
19584
  YoutubeHarvestInputSchema = {
18868
- mode: import_zod25.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
18869
- query: import_zod25.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
18870
- channelHandle: import_zod25.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
18871
- maxVideos: import_zod25.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50. Increase when user asks for full channel/history.")
19585
+ mode: import_zod26.z.enum(["search", "channel"]).describe("Use search for topic/keyword requests. Use channel when the user provides @handle, channel ID, or channel URL."),
19586
+ query: import_zod26.z.string().optional().describe("Required when mode is search. The YouTube search topic in the user\u2019s words."),
19587
+ channelHandle: import_zod26.z.string().optional().describe("YouTube channel handle, channel ID, or URL. Examples: @mkbhd, UC..., https://youtube.com/@mkbhd."),
19588
+ maxVideos: import_zod26.z.number().int().min(1).max(500).default(50).describe("Number of videos to return. Default 50. Increase when user asks for full channel/history.")
18872
19589
  };
18873
19590
  YoutubeTranscribeInputSchema = {
18874
- videoId: import_zod25.z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
19591
+ videoId: import_zod26.z.string().min(1).describe("YouTube video ID, e.g. dQw4w9WgXcQ")
18875
19592
  };
18876
19593
  FacebookPageIntelInputSchema = {
18877
- pageId: import_zod25.z.string().optional(),
18878
- libraryId: import_zod25.z.string().optional(),
18879
- query: import_zod25.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
18880
- maxAds: import_zod25.z.number().int().min(1).max(200).default(50),
18881
- country: import_zod25.z.string().length(2).default("US")
19594
+ pageId: import_zod26.z.string().optional(),
19595
+ libraryId: import_zod26.z.string().optional(),
19596
+ query: import_zod26.z.string().optional().describe("Advertiser or brand name when pageId/libraryId is not known. One of pageId, libraryId, or query is required."),
19597
+ maxAds: import_zod26.z.number().int().min(1).max(200).default(50),
19598
+ country: import_zod26.z.string().length(2).default("US")
18882
19599
  };
18883
19600
  FacebookAdSearchInputSchema = {
18884
- query: import_zod25.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
18885
- country: import_zod25.z.string().length(2).default("US"),
18886
- maxResults: import_zod25.z.number().int().min(1).max(20).default(10)
19601
+ query: import_zod26.z.string().min(1).describe("Advertiser, brand, competitor, niche, or keyword to search in Facebook Ad Library."),
19602
+ country: import_zod26.z.string().length(2).default("US"),
19603
+ maxResults: import_zod26.z.number().int().min(1).max(20).default(10)
18887
19604
  };
18888
19605
  FacebookAdTranscribeInputSchema = {
18889
- videoUrl: import_zod25.z.string().url().describe("Facebook CDN video URL from a facebook_page_intel result")
19606
+ videoUrl: import_zod26.z.string().url().describe("Facebook CDN video URL from a facebook_page_intel result")
18890
19607
  };
18891
19608
  MapsPlaceIntelInputSchema = {
18892
- businessName: import_zod25.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
18893
- location: import_zod25.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
18894
- gl: import_zod25.z.string().length(2).default("us").describe("Google country code inferred from location."),
18895
- hl: import_zod25.z.string().length(2).default("en").describe("Language inferred from user request."),
18896
- includeReviews: import_zod25.z.boolean().default(false).describe("Whether to fetch individual review cards"),
18897
- maxReviews: import_zod25.z.number().int().min(1).max(500).default(50).describe("Max review cards to return (requires includeReviews: true)")
19609
+ businessName: import_zod26.z.string().min(1).describe('Business name only. If user says "Elite Roofing Denver CO", use businessName="Elite Roofing" and location="Denver, CO".'),
19610
+ location: import_zod26.z.string().min(1).describe('City/region/country where the business should be searched, e.g. "Denver, CO". Infer from the user request when possible.'),
19611
+ gl: import_zod26.z.string().length(2).default("us").describe("Google country code inferred from location."),
19612
+ hl: import_zod26.z.string().length(2).default("en").describe("Language inferred from user request."),
19613
+ includeReviews: import_zod26.z.boolean().default(false).describe("Whether to fetch individual review cards"),
19614
+ maxReviews: import_zod26.z.number().int().min(1).max(500).default(50).describe("Max review cards to return (requires includeReviews: true)")
18898
19615
  };
18899
19616
  MapsSearchInputSchema = {
18900
- query: import_zod25.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
18901
- location: import_zod25.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
18902
- gl: import_zod25.z.string().length(2).default("us").describe("Google country code inferred from location."),
18903
- hl: import_zod25.z.string().length(2).default("en").describe("Language inferred from user request."),
18904
- maxResults: import_zod25.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
18905
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state Maps searches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts. Use configured for the server proxy ID, and none only for local direct-network debugging."),
18906
- proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
18907
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
19617
+ query: import_zod26.z.string().min(1).describe('Business category, niche, keyword, or search term. If the user says "roofers in Denver CO", use query="roofers" and location="Denver, CO". Do not put the location here when it can be separated.'),
19618
+ location: import_zod26.z.string().optional().describe('City, region, country, or service area for the Maps search, e.g. "Denver, CO". Infer from the user request when present.'),
19619
+ gl: import_zod26.z.string().length(2).default("us").describe("Google country code inferred from location."),
19620
+ hl: import_zod26.z.string().length(2).default("en").describe("Language inferred from user request."),
19621
+ maxResults: import_zod26.z.number().int().min(1).max(50).default(10).describe("Number of Google Maps business/profile candidates to return. Default 10. Maximum 50. Use 10 unless the user asks for more."),
19622
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state Maps searches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts. Use configured for the server proxy ID, and none only for local direct-network debugging."),
19623
+ proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or city-center ZIP."),
19624
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics when debugging Maps localization, CAPTCHA, or proxy behavior.")
18908
19625
  };
18909
19626
  DirectoryWorkflowInputSchema = {
18910
- query: import_zod25.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every selected market, e.g. roofers, dentists, med spas. Do not include the city here."),
18911
- state: import_zod25.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
18912
- minPopulation: import_zod25.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
18913
- populationYear: import_zod25.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
18914
- maxCities: import_zod25.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
18915
- maxResultsPerCity: import_zod25.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
18916
- concurrency: import_zod25.z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
18917
- includeZipGroups: import_zod25.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
18918
- usZipsCsvPath: import_zod25.z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
18919
- saveCsv: import_zod25.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
18920
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode for every city Maps search. Use location by default for US city/state batches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts per city. Use configured for the server proxy ID, and none only for local direct-network debugging."),
18921
- proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its Lead Magician ZIP group or city/state location."),
18922
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
19627
+ query: import_zod26.z.string().min(1).describe("Business category, niche, or keyword to search on Google Maps for every selected market, e.g. roofers, dentists, med spas. Do not include the city here."),
19628
+ state: import_zod26.z.string().min(2).default("TN").describe("US state abbreviation or state name used to select Census places, e.g. TN or Tennessee."),
19629
+ minPopulation: import_zod26.z.number().int().min(0).default(1e5).describe('Minimum Census place population for market selection. Use 100000 for "cities above 100k population".'),
19630
+ populationYear: import_zod26.z.number().int().min(2020).max(2025).default(2025).describe("Census population estimate year from the 2020-2025 Population Estimates Program city/place dataset."),
19631
+ maxCities: import_zod26.z.number().int().min(1).max(100).default(25).describe("Maximum number of markets to process after sorting by population descending."),
19632
+ maxResultsPerCity: import_zod26.z.number().int().min(1).max(50).default(50).describe("Google Maps business/profile candidates to collect for each city. Maximum 50."),
19633
+ concurrency: import_zod26.z.number().int().min(1).max(5).default(5).describe("How many city Maps searches to run in parallel. Use 5 for broad directory batches unless debugging."),
19634
+ includeZipGroups: import_zod26.z.boolean().default(true).describe("Attach ZIP groups from a configured US ZIPS CSV when available. Set MCP_SCRAPER_USZIPS_CSV_PATH on the API server or pass usZipsCsvPath in local/test mode."),
19635
+ usZipsCsvPath: import_zod26.z.string().optional().describe("Local/test-only path to a US ZIPS CSV with state_abbr, zipcode, county, city columns, such as Lead Magician tools/analytics/data/uszips.csv. Deployed APIs should use MCP_SCRAPER_USZIPS_CSV_PATH instead."),
19636
+ saveCsv: import_zod26.z.boolean().default(true).describe("Save a directory-ready CSV to the MCP Scraper output directory and return its path. CSV rows include source_location, result_position, business_name, review_stars, review_count, category, address, phone, hours_status, website_url, directions_url, place_url, CID fields, population, and ZIP groups."),
19637
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode for every city Maps search. Use location by default for US city/state batches; retryable failures create a new residential proxy ID and new browser session for up to 5 attempts per city. Use configured for the server proxy ID, and none only for local direct-network debugging."),
19638
+ proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional().describe("Optional ZIP override for proxy targeting. Normally omit it so each city can use its Lead Magician ZIP group or city/state location."),
19639
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics in each Maps browser session when supported.")
18923
19640
  };
18924
- RankTrackerModeSchema = import_zod25.z.enum(["maps", "organic", "ai_overview", "paa"]);
19641
+ RankTrackerModeSchema = import_zod26.z.enum(["maps", "organic", "ai_overview", "paa"]);
18925
19642
  RankTrackerBlueprintInputSchema = {
18926
- projectName: import_zod25.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
18927
- targetDomain: import_zod25.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
18928
- targetBusinessName: import_zod25.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
18929
- trackingModes: import_zod25.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
18930
- keywords: import_zod25.z.array(import_zod25.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
18931
- locations: import_zod25.z.array(import_zod25.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
18932
- competitors: import_zod25.z.array(import_zod25.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
18933
- database: import_zod25.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
18934
- scheduleCadence: import_zod25.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
18935
- customCron: import_zod25.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
18936
- timezone: import_zod25.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
18937
- includeCron: import_zod25.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
18938
- includeDashboard: import_zod25.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
18939
- includeAlerts: import_zod25.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
18940
- notes: import_zod25.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
19643
+ projectName: import_zod26.z.string().min(1).optional().describe("Optional name for the rank tracker project, client, or campaign."),
19644
+ targetDomain: import_zod26.z.string().min(1).optional().describe("Primary domain to track in organic results, AI Overview citations, and PAA sources, e.g. example.com."),
19645
+ targetBusinessName: import_zod26.z.string().min(1).optional().describe("Primary Google Business Profile or brand/business name to match in Maps results. Required for reliable Maps rank tracking."),
19646
+ trackingModes: import_zod26.z.array(RankTrackerModeSchema).min(1).max(4).default(["maps", "organic", "ai_overview", "paa"]).describe("Rank tracker surfaces to build. maps uses directory_workflow/maps_search. organic uses search_serp. ai_overview uses search_serp/harvest_paa. paa uses harvest_paa source presence."),
19647
+ keywords: import_zod26.z.array(import_zod26.z.string().min(1)).max(200).default([]).describe("Seed keywords or service queries to track. Leave empty when the downstream AI should create the keyword table from user input."),
19648
+ locations: import_zod26.z.array(import_zod26.z.string().min(1)).max(100).default([]).describe("Markets, cities, ZIPs, or service areas to track. Use city/state strings like Denver, CO for localized SERP and Maps checks."),
19649
+ competitors: import_zod26.z.array(import_zod26.z.string().min(1)).max(100).default([]).describe("Optional competitor domains or business names to persist as comparison targets."),
19650
+ database: import_zod26.z.enum(["postgres", "neon", "supabase", "sqlite", "mysql"]).default("postgres").describe("Database family the downstream AI should target when generating migrations."),
19651
+ scheduleCadence: import_zod26.z.enum(["daily", "weekly", "monthly", "custom"]).default("weekly").describe("Default recurring rank check cadence for the generated cron/heartbeat plan."),
19652
+ customCron: import_zod26.z.string().min(1).optional().describe("Cron expression to use when scheduleCadence is custom."),
19653
+ timezone: import_zod26.z.string().min(1).default("UTC").describe("IANA timezone for scheduled rank checks, e.g. America/Denver."),
19654
+ includeCron: import_zod26.z.boolean().default(true).describe("Include a cron or heartbeat worker plan. Keep true for production rank trackers."),
19655
+ includeDashboard: import_zod26.z.boolean().default(true).describe("Include dashboard/reporting requirements in the generated prompt."),
19656
+ includeAlerts: import_zod26.z.boolean().default(true).describe("Include alert rules for rank movement and SERP feature gains/losses."),
19657
+ notes: import_zod26.z.string().max(4e3).optional().describe("Extra product, client, stack, or hosting requirements to include in the implementation prompt.")
18941
19658
  };
18942
- NullableString = import_zod25.z.string().nullable();
18943
- MapsSearchAttemptOutput = import_zod25.z.object({
18944
- attemptNumber: import_zod25.z.number().int().min(1),
18945
- maxAttempts: import_zod25.z.number().int().min(1),
18946
- status: import_zod25.z.enum(["ok", "failed"]),
18947
- outcome: import_zod25.z.string(),
18948
- willRetry: import_zod25.z.boolean(),
18949
- durationMs: import_zod25.z.number().int().min(0),
18950
- resultCount: import_zod25.z.number().int().min(0),
19659
+ NullableString = import_zod26.z.string().nullable();
19660
+ MapsSearchAttemptOutput = import_zod26.z.object({
19661
+ attemptNumber: import_zod26.z.number().int().min(1),
19662
+ maxAttempts: import_zod26.z.number().int().min(1),
19663
+ status: import_zod26.z.enum(["ok", "failed"]),
19664
+ outcome: import_zod26.z.string(),
19665
+ willRetry: import_zod26.z.boolean(),
19666
+ durationMs: import_zod26.z.number().int().min(0),
19667
+ resultCount: import_zod26.z.number().int().min(0),
18951
19668
  error: NullableString,
18952
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]),
18953
- proxyResolutionSource: import_zod25.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
19669
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]),
19670
+ proxyResolutionSource: import_zod26.z.enum(["disabled", "location_reused", "location_created", "configured_fallback", "unavailable"]).nullable(),
18954
19671
  proxyIdSuffix: NullableString,
18955
- proxyTargetLevel: import_zod25.z.enum(["zip", "city", "state"]).nullable(),
19672
+ proxyTargetLevel: import_zod26.z.enum(["zip", "city", "state"]).nullable(),
18956
19673
  proxyTargetLocation: NullableString,
18957
19674
  proxyTargetZip: NullableString,
18958
19675
  browserSessionIdSuffix: NullableString,
@@ -18961,17 +19678,17 @@ var init_mcp_tool_schemas = __esm({
18961
19678
  observedRegion: NullableString
18962
19679
  });
18963
19680
  MapsSearchOutputSchema = {
18964
- query: import_zod25.z.string(),
18965
- location: import_zod25.z.string().nullable(),
18966
- searchQuery: import_zod25.z.string(),
18967
- searchUrl: import_zod25.z.string().url(),
18968
- extractedAt: import_zod25.z.string(),
18969
- requestedMaxResults: import_zod25.z.number().int().min(1).max(50),
18970
- resultCount: import_zod25.z.number().int().min(0).max(50),
18971
- results: import_zod25.z.array(import_zod25.z.object({
18972
- position: import_zod25.z.number().int().min(1),
18973
- name: import_zod25.z.string(),
18974
- placeUrl: import_zod25.z.string().url(),
19681
+ query: import_zod26.z.string(),
19682
+ location: import_zod26.z.string().nullable(),
19683
+ searchQuery: import_zod26.z.string(),
19684
+ searchUrl: import_zod26.z.string().url(),
19685
+ extractedAt: import_zod26.z.string(),
19686
+ requestedMaxResults: import_zod26.z.number().int().min(1).max(50),
19687
+ resultCount: import_zod26.z.number().int().min(0).max(50),
19688
+ results: import_zod26.z.array(import_zod26.z.object({
19689
+ position: import_zod26.z.number().int().min(1),
19690
+ name: import_zod26.z.string(),
19691
+ placeUrl: import_zod26.z.string().url(),
18975
19692
  cid: NullableString,
18976
19693
  cidDecimal: NullableString,
18977
19694
  rating: NullableString,
@@ -18982,15 +19699,15 @@ var init_mcp_tool_schemas = __esm({
18982
19699
  hoursStatus: NullableString,
18983
19700
  websiteUrl: NullableString,
18984
19701
  directionsUrl: NullableString,
18985
- metadata: import_zod25.z.array(import_zod25.z.string())
19702
+ metadata: import_zod26.z.array(import_zod26.z.string())
18986
19703
  })),
18987
- attempts: import_zod25.z.array(MapsSearchAttemptOutput),
18988
- durationMs: import_zod25.z.number().int().min(0)
19704
+ attempts: import_zod26.z.array(MapsSearchAttemptOutput),
19705
+ durationMs: import_zod26.z.number().int().min(0)
18989
19706
  };
18990
- DirectoryMapsBusinessOutput = import_zod25.z.object({
18991
- position: import_zod25.z.number().int().min(1),
18992
- name: import_zod25.z.string(),
18993
- placeUrl: import_zod25.z.string().url(),
19707
+ DirectoryMapsBusinessOutput = import_zod26.z.object({
19708
+ position: import_zod26.z.number().int().min(1),
19709
+ name: import_zod26.z.string(),
19710
+ placeUrl: import_zod26.z.string().url(),
18994
19711
  cid: NullableString,
18995
19712
  cidDecimal: NullableString,
18996
19713
  rating: NullableString,
@@ -19001,113 +19718,113 @@ var init_mcp_tool_schemas = __esm({
19001
19718
  hoursStatus: NullableString,
19002
19719
  websiteUrl: NullableString,
19003
19720
  directionsUrl: NullableString,
19004
- metadata: import_zod25.z.array(import_zod25.z.string())
19721
+ metadata: import_zod26.z.array(import_zod26.z.string())
19005
19722
  });
19006
19723
  DirectoryWorkflowOutputSchema = {
19007
- query: import_zod25.z.string(),
19008
- state: import_zod25.z.string(),
19009
- minPopulation: import_zod25.z.number().int().min(0),
19010
- populationYear: import_zod25.z.number().int().min(2020).max(2025),
19011
- maxResultsPerCity: import_zod25.z.number().int().min(1).max(50),
19012
- concurrency: import_zod25.z.number().int().min(1).max(5),
19013
- censusSourceUrl: import_zod25.z.string().url(),
19724
+ query: import_zod26.z.string(),
19725
+ state: import_zod26.z.string(),
19726
+ minPopulation: import_zod26.z.number().int().min(0),
19727
+ populationYear: import_zod26.z.number().int().min(2020).max(2025),
19728
+ maxResultsPerCity: import_zod26.z.number().int().min(1).max(50),
19729
+ concurrency: import_zod26.z.number().int().min(1).max(5),
19730
+ censusSourceUrl: import_zod26.z.string().url(),
19014
19731
  usZipsSourcePath: NullableString,
19015
- warnings: import_zod25.z.array(import_zod25.z.string()),
19016
- extractedAt: import_zod25.z.string(),
19017
- selectedCityCount: import_zod25.z.number().int().min(0),
19018
- totalResultCount: import_zod25.z.number().int().min(0),
19732
+ warnings: import_zod26.z.array(import_zod26.z.string()),
19733
+ extractedAt: import_zod26.z.string(),
19734
+ selectedCityCount: import_zod26.z.number().int().min(0),
19735
+ totalResultCount: import_zod26.z.number().int().min(0),
19019
19736
  csvPath: NullableString,
19020
- cities: import_zod25.z.array(import_zod25.z.object({
19021
- city: import_zod25.z.string(),
19022
- state: import_zod25.z.string(),
19023
- location: import_zod25.z.string(),
19024
- cityKey: import_zod25.z.string(),
19025
- censusName: import_zod25.z.string(),
19026
- population: import_zod25.z.number().int().min(0),
19027
- populationYear: import_zod25.z.number().int().min(2020).max(2025),
19028
- zips: import_zod25.z.array(import_zod25.z.string()),
19029
- counties: import_zod25.z.array(import_zod25.z.string()),
19030
- status: import_zod25.z.enum(["ok", "empty", "failed"]),
19737
+ cities: import_zod26.z.array(import_zod26.z.object({
19738
+ city: import_zod26.z.string(),
19739
+ state: import_zod26.z.string(),
19740
+ location: import_zod26.z.string(),
19741
+ cityKey: import_zod26.z.string(),
19742
+ censusName: import_zod26.z.string(),
19743
+ population: import_zod26.z.number().int().min(0),
19744
+ populationYear: import_zod26.z.number().int().min(2020).max(2025),
19745
+ zips: import_zod26.z.array(import_zod26.z.string()),
19746
+ counties: import_zod26.z.array(import_zod26.z.string()),
19747
+ status: import_zod26.z.enum(["ok", "empty", "failed"]),
19031
19748
  error: NullableString,
19032
- resultCount: import_zod25.z.number().int().min(0),
19033
- durationMs: import_zod25.z.number().int().min(0),
19034
- attempts: import_zod25.z.array(MapsSearchAttemptOutput),
19035
- results: import_zod25.z.array(DirectoryMapsBusinessOutput)
19749
+ resultCount: import_zod26.z.number().int().min(0),
19750
+ durationMs: import_zod26.z.number().int().min(0),
19751
+ attempts: import_zod26.z.array(MapsSearchAttemptOutput),
19752
+ results: import_zod26.z.array(DirectoryMapsBusinessOutput)
19036
19753
  })),
19037
- durationMs: import_zod25.z.number().int().min(0)
19754
+ durationMs: import_zod26.z.number().int().min(0)
19038
19755
  };
19039
- RankTrackerToolPlanOutput = import_zod25.z.object({
19040
- tool: import_zod25.z.string(),
19041
- purpose: import_zod25.z.string()
19756
+ RankTrackerToolPlanOutput = import_zod26.z.object({
19757
+ tool: import_zod26.z.string(),
19758
+ purpose: import_zod26.z.string()
19042
19759
  });
19043
- RankTrackerTableOutput = import_zod25.z.object({
19044
- name: import_zod25.z.string(),
19045
- purpose: import_zod25.z.string(),
19046
- keyColumns: import_zod25.z.array(import_zod25.z.string())
19760
+ RankTrackerTableOutput = import_zod26.z.object({
19761
+ name: import_zod26.z.string(),
19762
+ purpose: import_zod26.z.string(),
19763
+ keyColumns: import_zod26.z.array(import_zod26.z.string())
19047
19764
  });
19048
- RankTrackerCronJobOutput = import_zod25.z.object({
19049
- name: import_zod25.z.string(),
19050
- purpose: import_zod25.z.string(),
19051
- modes: import_zod25.z.array(RankTrackerModeSchema),
19052
- recommendedTools: import_zod25.z.array(import_zod25.z.string())
19765
+ RankTrackerCronJobOutput = import_zod26.z.object({
19766
+ name: import_zod26.z.string(),
19767
+ purpose: import_zod26.z.string(),
19768
+ modes: import_zod26.z.array(RankTrackerModeSchema),
19769
+ recommendedTools: import_zod26.z.array(import_zod26.z.string())
19053
19770
  });
19054
19771
  RankTrackerBlueprintOutputSchema = {
19055
- projectName: import_zod25.z.string(),
19772
+ projectName: import_zod26.z.string(),
19056
19773
  targetDomain: NullableString,
19057
19774
  targetBusinessName: NullableString,
19058
- trackingModes: import_zod25.z.array(RankTrackerModeSchema),
19059
- database: import_zod25.z.string(),
19060
- recommendedTools: import_zod25.z.array(RankTrackerToolPlanOutput),
19061
- tables: import_zod25.z.array(RankTrackerTableOutput),
19062
- cron: import_zod25.z.object({
19063
- enabled: import_zod25.z.boolean(),
19064
- cadence: import_zod25.z.string(),
19065
- expression: import_zod25.z.string(),
19066
- timezone: import_zod25.z.string(),
19067
- jobs: import_zod25.z.array(RankTrackerCronJobOutput)
19775
+ trackingModes: import_zod26.z.array(RankTrackerModeSchema),
19776
+ database: import_zod26.z.string(),
19777
+ recommendedTools: import_zod26.z.array(RankTrackerToolPlanOutput),
19778
+ tables: import_zod26.z.array(RankTrackerTableOutput),
19779
+ cron: import_zod26.z.object({
19780
+ enabled: import_zod26.z.boolean(),
19781
+ cadence: import_zod26.z.string(),
19782
+ expression: import_zod26.z.string(),
19783
+ timezone: import_zod26.z.string(),
19784
+ jobs: import_zod26.z.array(RankTrackerCronJobOutput)
19068
19785
  }),
19069
- metrics: import_zod25.z.array(import_zod25.z.string()),
19070
- implementationPrompt: import_zod25.z.string()
19786
+ metrics: import_zod26.z.array(import_zod26.z.string()),
19787
+ implementationPrompt: import_zod26.z.string()
19071
19788
  };
19072
- OrganicResultOutput = import_zod25.z.object({
19073
- position: import_zod25.z.number().int(),
19074
- title: import_zod25.z.string(),
19075
- url: import_zod25.z.string(),
19076
- domain: import_zod25.z.string(),
19789
+ OrganicResultOutput = import_zod26.z.object({
19790
+ position: import_zod26.z.number().int(),
19791
+ title: import_zod26.z.string(),
19792
+ url: import_zod26.z.string(),
19793
+ domain: import_zod26.z.string(),
19077
19794
  snippet: NullableString
19078
19795
  });
19079
- AiOverviewOutput = import_zod25.z.object({
19080
- detected: import_zod25.z.boolean(),
19796
+ AiOverviewOutput = import_zod26.z.object({
19797
+ detected: import_zod26.z.boolean(),
19081
19798
  text: NullableString
19082
19799
  }).nullable();
19083
- EntityIdsOutput = import_zod25.z.object({
19084
- kgIds: import_zod25.z.array(import_zod25.z.string()),
19085
- cids: import_zod25.z.array(import_zod25.z.string()),
19086
- gcids: import_zod25.z.array(import_zod25.z.string())
19800
+ EntityIdsOutput = import_zod26.z.object({
19801
+ kgIds: import_zod26.z.array(import_zod26.z.string()),
19802
+ cids: import_zod26.z.array(import_zod26.z.string()),
19803
+ gcids: import_zod26.z.array(import_zod26.z.string())
19087
19804
  }).nullable();
19088
19805
  HarvestPaaOutputSchema = {
19089
- query: import_zod25.z.string(),
19806
+ query: import_zod26.z.string(),
19090
19807
  location: NullableString,
19091
- questionCount: import_zod25.z.number().int().min(0),
19808
+ questionCount: import_zod26.z.number().int().min(0),
19092
19809
  completionStatus: NullableString,
19093
- questions: import_zod25.z.array(import_zod25.z.object({
19094
- question: import_zod25.z.string(),
19810
+ questions: import_zod26.z.array(import_zod26.z.object({
19811
+ question: import_zod26.z.string(),
19095
19812
  answer: NullableString,
19096
19813
  sourceTitle: NullableString,
19097
19814
  sourceSite: NullableString
19098
19815
  })),
19099
- organicResults: import_zod25.z.array(OrganicResultOutput),
19816
+ organicResults: import_zod26.z.array(OrganicResultOutput),
19100
19817
  aiOverview: AiOverviewOutput,
19101
19818
  entityIds: EntityIdsOutput,
19102
- durationMs: import_zod25.z.number().min(0).nullable()
19819
+ durationMs: import_zod26.z.number().min(0).nullable()
19103
19820
  };
19104
19821
  SearchSerpOutputSchema = {
19105
- query: import_zod25.z.string(),
19822
+ query: import_zod26.z.string(),
19106
19823
  location: NullableString,
19107
- organicResults: import_zod25.z.array(OrganicResultOutput),
19108
- localPack: import_zod25.z.array(import_zod25.z.object({
19109
- position: import_zod25.z.number().int(),
19110
- name: import_zod25.z.string(),
19824
+ organicResults: import_zod26.z.array(OrganicResultOutput),
19825
+ localPack: import_zod26.z.array(import_zod26.z.object({
19826
+ position: import_zod26.z.number().int(),
19827
+ name: import_zod26.z.string(),
19111
19828
  rating: NullableString,
19112
19829
  reviewCount: NullableString,
19113
19830
  websiteUrl: NullableString
@@ -19116,31 +19833,31 @@ var init_mcp_tool_schemas = __esm({
19116
19833
  entityIds: EntityIdsOutput
19117
19834
  };
19118
19835
  ExtractUrlOutputSchema = {
19119
- url: import_zod25.z.string(),
19836
+ url: import_zod26.z.string(),
19120
19837
  title: NullableString,
19121
- headings: import_zod25.z.array(import_zod25.z.object({
19122
- level: import_zod25.z.number().int(),
19123
- text: import_zod25.z.string()
19838
+ headings: import_zod26.z.array(import_zod26.z.object({
19839
+ level: import_zod26.z.number().int(),
19840
+ text: import_zod26.z.string()
19124
19841
  })),
19125
- schemaBlockCount: import_zod25.z.number().int().min(0),
19842
+ schemaBlockCount: import_zod26.z.number().int().min(0),
19126
19843
  entityName: NullableString,
19127
- entityTypes: import_zod25.z.array(import_zod25.z.string()),
19128
- napScore: import_zod25.z.number().nullable(),
19129
- missingSchemaFields: import_zod25.z.array(import_zod25.z.string()),
19844
+ entityTypes: import_zod26.z.array(import_zod26.z.string()),
19845
+ napScore: import_zod26.z.number().nullable(),
19846
+ missingSchemaFields: import_zod26.z.array(import_zod26.z.string()),
19130
19847
  screenshotSaved: NullableString
19131
19848
  };
19132
19849
  ExtractSiteOutputSchema = {
19133
- url: import_zod25.z.string(),
19134
- pageCount: import_zod25.z.number().int().min(0),
19135
- pages: import_zod25.z.array(import_zod25.z.object({
19136
- url: import_zod25.z.string(),
19850
+ url: import_zod26.z.string(),
19851
+ pageCount: import_zod26.z.number().int().min(0),
19852
+ pages: import_zod26.z.array(import_zod26.z.object({
19853
+ url: import_zod26.z.string(),
19137
19854
  title: NullableString,
19138
- schemaTypes: import_zod25.z.array(import_zod25.z.string())
19855
+ schemaTypes: import_zod26.z.array(import_zod26.z.string())
19139
19856
  })),
19140
- durationMs: import_zod25.z.number().min(0)
19857
+ durationMs: import_zod26.z.number().min(0)
19141
19858
  };
19142
19859
  MapsPlaceIntelOutputSchema = {
19143
- name: import_zod25.z.string(),
19860
+ name: import_zod26.z.string(),
19144
19861
  rating: NullableString,
19145
19862
  reviewCount: NullableString,
19146
19863
  category: NullableString,
@@ -19152,60 +19869,60 @@ var init_mcp_tool_schemas = __esm({
19152
19869
  kgmid: NullableString,
19153
19870
  cidDecimal: NullableString,
19154
19871
  cidUrl: NullableString,
19155
- lat: import_zod25.z.number().nullable(),
19156
- lng: import_zod25.z.number().nullable(),
19157
- reviewsStatus: import_zod25.z.string(),
19158
- reviewsCollected: import_zod25.z.number().int().min(0),
19159
- reviewTopics: import_zod25.z.array(import_zod25.z.object({
19160
- label: import_zod25.z.string(),
19161
- count: import_zod25.z.string()
19872
+ lat: import_zod26.z.number().nullable(),
19873
+ lng: import_zod26.z.number().nullable(),
19874
+ reviewsStatus: import_zod26.z.string(),
19875
+ reviewsCollected: import_zod26.z.number().int().min(0),
19876
+ reviewTopics: import_zod26.z.array(import_zod26.z.object({
19877
+ label: import_zod26.z.string(),
19878
+ count: import_zod26.z.string()
19162
19879
  }))
19163
19880
  };
19164
19881
  CreditsInfoOutputSchema = {
19165
- balanceCredits: import_zod25.z.number().nullable(),
19166
- matchedCost: import_zod25.z.object({
19167
- label: import_zod25.z.string(),
19168
- credits: import_zod25.z.number(),
19169
- unit: import_zod25.z.string(),
19882
+ balanceCredits: import_zod26.z.number().nullable(),
19883
+ matchedCost: import_zod26.z.object({
19884
+ label: import_zod26.z.string(),
19885
+ credits: import_zod26.z.number(),
19886
+ unit: import_zod26.z.string(),
19170
19887
  notes: NullableString
19171
19888
  }).nullable(),
19172
- costs: import_zod25.z.array(import_zod25.z.object({
19173
- key: import_zod25.z.string(),
19174
- label: import_zod25.z.string(),
19175
- credits: import_zod25.z.number(),
19176
- unit: import_zod25.z.string(),
19889
+ costs: import_zod26.z.array(import_zod26.z.object({
19890
+ key: import_zod26.z.string(),
19891
+ label: import_zod26.z.string(),
19892
+ credits: import_zod26.z.number(),
19893
+ unit: import_zod26.z.string(),
19177
19894
  notes: NullableString
19178
19895
  })),
19179
- ledger: import_zod25.z.array(import_zod25.z.object({
19180
- createdAt: import_zod25.z.string(),
19181
- operation: import_zod25.z.string(),
19182
- credits: import_zod25.z.number(),
19896
+ ledger: import_zod26.z.array(import_zod26.z.object({
19897
+ createdAt: import_zod26.z.string(),
19898
+ operation: import_zod26.z.string(),
19899
+ credits: import_zod26.z.number(),
19183
19900
  description: NullableString
19184
19901
  }))
19185
19902
  };
19186
19903
  MapSiteUrlsOutputSchema = {
19187
- startUrl: import_zod25.z.string(),
19188
- totalFound: import_zod25.z.number().int().min(0),
19189
- truncated: import_zod25.z.boolean(),
19190
- okCount: import_zod25.z.number().int().min(0),
19191
- redirectCount: import_zod25.z.number().int().min(0),
19192
- brokenCount: import_zod25.z.number().int().min(0),
19193
- urls: import_zod25.z.array(import_zod25.z.object({
19194
- url: import_zod25.z.string(),
19195
- status: import_zod25.z.number().int().nullable()
19904
+ startUrl: import_zod26.z.string(),
19905
+ totalFound: import_zod26.z.number().int().min(0),
19906
+ truncated: import_zod26.z.boolean(),
19907
+ okCount: import_zod26.z.number().int().min(0),
19908
+ redirectCount: import_zod26.z.number().int().min(0),
19909
+ brokenCount: import_zod26.z.number().int().min(0),
19910
+ urls: import_zod26.z.array(import_zod26.z.object({
19911
+ url: import_zod26.z.string(),
19912
+ status: import_zod26.z.number().int().nullable()
19196
19913
  })),
19197
- durationMs: import_zod25.z.number().min(0)
19914
+ durationMs: import_zod26.z.number().min(0)
19198
19915
  };
19199
19916
  YoutubeHarvestOutputSchema = {
19200
- mode: import_zod25.z.string(),
19201
- videoCount: import_zod25.z.number().int().min(0),
19202
- channel: import_zod25.z.object({
19917
+ mode: import_zod26.z.string(),
19918
+ videoCount: import_zod26.z.number().int().min(0),
19919
+ channel: import_zod26.z.object({
19203
19920
  title: NullableString,
19204
19921
  subscriberCount: NullableString
19205
19922
  }).nullable(),
19206
- videos: import_zod25.z.array(import_zod25.z.object({
19207
- videoId: import_zod25.z.string(),
19208
- title: import_zod25.z.string(),
19923
+ videos: import_zod26.z.array(import_zod26.z.object({
19924
+ videoId: import_zod26.z.string(),
19925
+ title: import_zod26.z.string(),
19209
19926
  channelName: NullableString,
19210
19927
  views: NullableString,
19211
19928
  duration: NullableString,
@@ -19213,21 +19930,21 @@ var init_mcp_tool_schemas = __esm({
19213
19930
  }))
19214
19931
  };
19215
19932
  FacebookAdSearchOutputSchema = {
19216
- query: import_zod25.z.string(),
19217
- advertiserCount: import_zod25.z.number().int().min(0),
19218
- advertisers: import_zod25.z.array(import_zod25.z.object({
19933
+ query: import_zod26.z.string(),
19934
+ advertiserCount: import_zod26.z.number().int().min(0),
19935
+ advertisers: import_zod26.z.array(import_zod26.z.object({
19219
19936
  name: NullableString,
19220
- adCount: import_zod25.z.number().int().nullable(),
19937
+ adCount: import_zod26.z.number().int().nullable(),
19221
19938
  libraryId: NullableString
19222
19939
  }))
19223
19940
  };
19224
19941
  FacebookPageIntelOutputSchema = {
19225
19942
  advertiserName: NullableString,
19226
- totalAds: import_zod25.z.number().int().min(0),
19227
- activeCount: import_zod25.z.number().int().min(0),
19228
- videoCount: import_zod25.z.number().int().min(0),
19229
- imageCount: import_zod25.z.number().int().min(0),
19230
- ads: import_zod25.z.array(import_zod25.z.object({
19943
+ totalAds: import_zod26.z.number().int().min(0),
19944
+ activeCount: import_zod26.z.number().int().min(0),
19945
+ videoCount: import_zod26.z.number().int().min(0),
19946
+ imageCount: import_zod26.z.number().int().min(0),
19947
+ ads: import_zod26.z.array(import_zod26.z.object({
19231
19948
  libraryId: NullableString,
19232
19949
  status: NullableString,
19233
19950
  creativeType: NullableString,
@@ -19235,58 +19952,58 @@ var init_mcp_tool_schemas = __esm({
19235
19952
  cta: NullableString,
19236
19953
  startDate: NullableString,
19237
19954
  videoUrl: NullableString,
19238
- variations: import_zod25.z.number().int().nullable()
19955
+ variations: import_zod26.z.number().int().nullable()
19239
19956
  }))
19240
19957
  };
19241
19958
  CreditsInfoInputSchema = {
19242
- item: import_zod25.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", or "YouTube transcription"'),
19243
- includeLedger: import_zod25.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
19959
+ item: import_zod26.z.string().optional().describe('Optional tool, action, or feature to look up, e.g. "maps reviews", "extract_url", or "YouTube transcription"'),
19960
+ includeLedger: import_zod26.z.boolean().default(false).describe("Whether to include recent credit ledger entries")
19244
19961
  };
19245
19962
  SearchSerpInputSchema = {
19246
- query: import_zod25.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
19247
- location: import_zod25.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
19248
- gl: import_zod25.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
19249
- hl: import_zod25.z.string().default("en").describe("Google interface/content language inferred from user request."),
19250
- device: import_zod25.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
19251
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
19252
- proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
19253
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
19254
- pages: import_zod25.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
19963
+ query: import_zod26.z.string().min(1).describe('Core search topic only. Separate location when possible. If user says "best dentist in Brooklyn NY serp", use query="best dentist" and location="Brooklyn, NY".'),
19964
+ location: import_zod26.z.string().optional().describe("City, region, or country for geo-targeted results, inferred from user request when present."),
19965
+ gl: import_zod26.z.string().length(2).default("us").describe("Google country code inferred from location or user language."),
19966
+ hl: import_zod26.z.string().default("en").describe("Google interface/content language inferred from user request."),
19967
+ device: import_zod26.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use desktop by default; use mobile only when the user asks for mobile rankings."),
19968
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default("location").describe("Proxy targeting mode. Use location by default for US city/state SERPs; it creates a fresh residential proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static configured proxy. Use none only for direct-network debugging."),
19969
+ proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting. Use when the user gives a specific ZIP or when city-center targeting needs to be forced. With proxyMode location this ZIP is used for each fresh proxy attempt."),
19970
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser/session/location diagnostics in the response. Use true when debugging localization, CAPTCHA, or proxy behavior."),
19971
+ pages: import_zod26.z.number().int().min(1).max(2).default(1).describe("Number of result pages to fetch (1\u20132)")
19255
19972
  };
19256
19973
  CaptureSerpSnapshotInputSchema = {
19257
- query: import_zod25.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
19258
- location: import_zod25.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
19259
- gl: import_zod25.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
19260
- hl: import_zod25.z.string().default("en").describe("Google interface/content language inferred from the user request."),
19261
- device: import_zod25.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
19262
- proxyMode: import_zod25.z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized US residential evidence; it creates a fresh proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static residential proxy, and none only for direct-network debugging."),
19263
- proxyZip: import_zod25.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed. With proxyMode location this ZIP is used for each fresh proxy attempt."),
19264
- pages: import_zod25.z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
19265
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
19266
- includePageSnapshots: import_zod25.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
19267
- pageSnapshotLimit: import_zod25.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
19974
+ query: import_zod26.z.string().min(1).describe("Core search query to capture as a structured SERP Intelligence snapshot. Separate the place into location when the user gives a city, region, country, or ZIP."),
19975
+ location: import_zod26.z.string().optional().describe("City, region, country, or service area used for localized Google results. MCP Scraper records location evidence; UULE alone is not proof of localization."),
19976
+ gl: import_zod26.z.string().length(2).default("us").describe("Google country code inferred from the requested market, e.g. us, gb, ca, au."),
19977
+ hl: import_zod26.z.string().default("en").describe("Google interface/content language inferred from the user request."),
19978
+ device: import_zod26.z.enum(["desktop", "mobile"]).default("desktop").describe("SERP device context. Use mobile only when the user asks for mobile rankings or mobile SERP evidence."),
19979
+ proxyMode: import_zod26.z.enum(["location", "configured", "none"]).default("location").describe("Proxy behavior for capture. Use location for localized US residential evidence; it creates a fresh proxy ID per attempt. If Google shows a CAPTCHA/challenge, browser-service sessions briefly wait for the automatic solver first, then retry with a fresh proxy/session if the challenge does not clear. Also retries proxy tunnel failure and wrong-location evidence before returning. Use configured only for the static residential proxy, and none only for direct-network debugging."),
19980
+ proxyZip: import_zod26.z.string().regex(/^\d{5}$/).optional().describe("Optional US ZIP override for residential location proxy targeting when a precise city-center or ZIP proxy is needed. With proxyMode location this ZIP is used for each fresh proxy attempt."),
19981
+ pages: import_zod26.z.number().int().min(1).max(2).default(1).describe("Number of Google result pages to capture. Use 1 normally and 2 only when the user needs deeper ranking evidence."),
19982
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser, proxy, and location diagnostics. Use true when debugging localization, CAPTCHA, proxy selection, or capture reliability."),
19983
+ includePageSnapshots: import_zod26.z.boolean().default(false).describe("Also capture ranking-page snapshots for selected SERP URLs through the same product capture path."),
19984
+ pageSnapshotLimit: import_zod26.z.number().int().min(0).max(10).default(0).describe("Maximum ranking-page snapshots to capture when includePageSnapshots is true. Use 0 when only SERP evidence is needed.")
19268
19985
  };
19269
19986
  ScreenshotInputSchema = {
19270
- url: import_zod25.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
19271
- device: import_zod25.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
19272
- allowLocal: import_zod25.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
19987
+ url: import_zod26.z.string().url().describe("URL to capture as a full-page screenshot. Use http or https. Pass allowLocal: true to capture localhost or private-network URLs during development."),
19988
+ device: import_zod26.z.enum(["desktop", "mobile"]).default("desktop").describe("Viewport profile. desktop = 1440\xD7900. mobile = 390\xD7844. Use desktop by default; use mobile when the user asks for a mobile view."),
19989
+ allowLocal: import_zod26.z.boolean().default(false).describe("Allow localhost and private-network URLs (127.x, 192.168.x, 10.x, etc.). For local development only \u2014 not for production use.")
19273
19990
  };
19274
19991
  CaptureSerpPageSnapshotsInputSchema = {
19275
- urls: import_zod25.z.array(import_zod25.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
19276
- targets: import_zod25.z.array(import_zod25.z.object({
19277
- url: import_zod25.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
19278
- sourceKind: import_zod25.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
19279
- sourcePosition: import_zod25.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
19992
+ urls: import_zod26.z.array(import_zod26.z.string().url()).min(1).max(25).describe("Public HTTP/HTTPS URLs to capture as SERP Intelligence page snapshots. Do not pass localhost, private IPs, file URLs, or internal admin URLs."),
19993
+ targets: import_zod26.z.array(import_zod26.z.object({
19994
+ url: import_zod26.z.string().url().describe("Public HTTP/HTTPS URL to capture."),
19995
+ sourceKind: import_zod26.z.enum(["organic", "ai_citation", "local_pack_website", "configured_target", "site_subject"]).default("configured_target").describe("Why this page is being captured for SERP Intelligence evidence."),
19996
+ sourcePosition: import_zod26.z.number().int().min(1).optional().describe("Ranking or citation position when the page came from SERP evidence.")
19280
19997
  }).strict()).min(1).max(25).optional().describe("Structured page snapshot targets. Use this instead of urls when source kind or position should be preserved."),
19281
- maxConcurrency: import_zod25.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
19282
- timeoutMs: import_zod25.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
19283
- debug: import_zod25.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
19998
+ maxConcurrency: import_zod26.z.number().int().min(1).max(5).default(2).describe("Parallel page captures. Use 2 normally; higher values can increase proxy/browser pressure."),
19999
+ timeoutMs: import_zod26.z.number().int().min(1e3).max(6e4).default(15e3).describe("Per-page capture timeout in milliseconds. Increase for slow pages; timeout artifacts are returned as structured capture failures."),
20000
+ debug: import_zod26.z.boolean().default(false).describe("Include sanitized browser/proxy diagnostics for page snapshot debugging. Use true for capture, network, or proxy troubleshooting.")
19284
20001
  };
19285
20002
  }
19286
20003
  });
19287
20004
 
19288
20005
  // src/mcp/rank-tracker-blueprint.ts
19289
- function normalizeDomain2(value) {
20006
+ function normalizeDomain3(value) {
19290
20007
  const trimmed = value?.trim();
19291
20008
  if (!trimmed) return null;
19292
20009
  try {
@@ -19548,7 +20265,7 @@ function buildPrompt(input, modes, tools, tables, jobs, metrics, expression, tar
19548
20265
  }
19549
20266
  function buildRankTrackerBlueprint(input) {
19550
20267
  const trackingModes = uniqueModes(input.trackingModes);
19551
- const targetDomain = normalizeDomain2(input.targetDomain);
20268
+ const targetDomain = normalizeDomain3(input.targetDomain);
19552
20269
  const targetBusinessName = input.targetBusinessName?.trim() || null;
19553
20270
  const projectName = input.projectName?.trim() || "MCP Scraper Rank Tracker";
19554
20271
  const database = input.database || "postgres";
@@ -21170,31 +21887,31 @@ var init_site_audit_worker = __esm({
21170
21887
  });
21171
21888
 
21172
21889
  // src/api/billing-schemas.ts
21173
- var import_zod26, BillingCheckoutBodySchema, FreeCreditBreakdownSchema, BillingBalanceResponseSchema, MonthlyRefreshSweepResultSchema;
21890
+ var import_zod27, BillingCheckoutBodySchema, FreeCreditBreakdownSchema, BillingBalanceResponseSchema, MonthlyRefreshSweepResultSchema;
21174
21891
  var init_billing_schemas = __esm({
21175
21892
  "src/api/billing-schemas.ts"() {
21176
21893
  "use strict";
21177
- import_zod26 = require("zod");
21178
- BillingCheckoutBodySchema = import_zod26.z.object({
21179
- priceId: import_zod26.z.string().min(1)
21894
+ import_zod27 = require("zod");
21895
+ BillingCheckoutBodySchema = import_zod27.z.object({
21896
+ priceId: import_zod27.z.string().min(1)
21180
21897
  });
21181
- FreeCreditBreakdownSchema = import_zod26.z.object({
21182
- signup_grant_mc: import_zod26.z.number().int().nonnegative(),
21183
- monthly_refresh_mc: import_zod26.z.number().int().nonnegative(),
21184
- total_free_mc: import_zod26.z.number().int().nonnegative(),
21185
- signup_grant_credits: import_zod26.z.number().nonnegative(),
21186
- monthly_refresh_credits: import_zod26.z.number().nonnegative(),
21187
- total_free_credits: import_zod26.z.number().nonnegative()
21898
+ FreeCreditBreakdownSchema = import_zod27.z.object({
21899
+ signup_grant_mc: import_zod27.z.number().int().nonnegative(),
21900
+ monthly_refresh_mc: import_zod27.z.number().int().nonnegative(),
21901
+ total_free_mc: import_zod27.z.number().int().nonnegative(),
21902
+ signup_grant_credits: import_zod27.z.number().nonnegative(),
21903
+ monthly_refresh_credits: import_zod27.z.number().nonnegative(),
21904
+ total_free_credits: import_zod27.z.number().nonnegative()
21188
21905
  });
21189
- BillingBalanceResponseSchema = import_zod26.z.object({
21190
- balance_mc: import_zod26.z.number().int().nonnegative(),
21191
- balance_credits: import_zod26.z.number().nonnegative(),
21906
+ BillingBalanceResponseSchema = import_zod27.z.object({
21907
+ balance_mc: import_zod27.z.number().int().nonnegative(),
21908
+ balance_credits: import_zod27.z.number().nonnegative(),
21192
21909
  free_credits: FreeCreditBreakdownSchema,
21193
- ledger: import_zod26.z.array(import_zod26.z.any())
21910
+ ledger: import_zod27.z.array(import_zod27.z.any())
21194
21911
  });
21195
- MonthlyRefreshSweepResultSchema = import_zod26.z.object({
21196
- usersRefreshed: import_zod26.z.number().int().nonnegative(),
21197
- totalMcGranted: import_zod26.z.number().int().nonnegative()
21912
+ MonthlyRefreshSweepResultSchema = import_zod27.z.object({
21913
+ usersRefreshed: import_zod27.z.number().int().nonnegative(),
21914
+ totalMcGranted: import_zod27.z.number().int().nonnegative()
21198
21915
  });
21199
21916
  }
21200
21917
  });