mcp-scraper 0.3.24 → 0.3.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +8220 -5991
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +320 -58
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +4 -3
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +186 -49
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.cjs +284 -10
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +5 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-GZ4W72WF.js → chunk-2GHI2D6Q.js} +184 -48
- package/dist/chunk-2GHI2D6Q.js.map +1 -0
- package/dist/{chunk-ZAP7MYVO.js → chunk-3PRO376E.js} +2 -3
- package/dist/chunk-3PRO376E.js.map +1 -0
- package/dist/{chunk-LFATOGDF.js → chunk-BP27CZ5Q.js} +4 -3
- package/dist/chunk-BP27CZ5Q.js.map +1 -0
- package/dist/{chunk-DE7WP4UU.js → chunk-DOBQN3EY.js} +3 -3
- package/dist/chunk-DOBQN3EY.js.map +1 -0
- package/dist/{chunk-IGOEAKC7.js → chunk-G7PQ64LM.js} +274 -9
- package/dist/chunk-G7PQ64LM.js.map +1 -0
- package/dist/chunk-K443GQY5.js +24 -0
- package/dist/chunk-K443GQY5.js.map +1 -0
- package/dist/chunk-MS2UZ6HR.js +7 -0
- package/dist/chunk-MS2UZ6HR.js.map +1 -0
- package/dist/{chunk-H2R232HK.js → chunk-NNEIXK5L.js} +307 -62
- package/dist/chunk-NNEIXK5L.js.map +1 -0
- package/dist/{chunk-3KYRG7O7.js → chunk-TRNSXR46.js} +185 -2
- package/dist/chunk-TRNSXR46.js.map +1 -0
- package/dist/{chunk-JTWQGPKF.js → chunk-WYCER2HW.js} +33 -10
- package/dist/chunk-WYCER2HW.js.map +1 -0
- package/dist/{db-EG5ETPTY.js → db-FP2ABUU4.js} +32 -2
- package/dist/index.cjs +284 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-2SRARAO5.js → server-HIGHKTEV.js} +1563 -275
- package/dist/server-HIGHKTEV.js.map +1 -0
- package/dist/{worker-UM5UM6O5.js → worker-UC6D2756.js} +7 -6
- package/dist/{worker-UM5UM6O5.js.map → worker-UC6D2756.js.map} +1 -1
- package/docs/kernel-proxy-future-enhancements.md +80 -0
- package/docs/spec-browser-routing.md +108 -0
- package/docs/spec-mcp-tools.md +146 -0
- package/docs/spec-server-instructions.md +94 -0
- package/docs/specs/audit-visual-demo.md +309 -0
- package/package.json +5 -2
- package/dist/chunk-3KYRG7O7.js.map +0 -1
- package/dist/chunk-DE7WP4UU.js.map +0 -1
- package/dist/chunk-GZ4W72WF.js.map +0 -1
- package/dist/chunk-H2R232HK.js.map +0 -1
- package/dist/chunk-H7R4PPMG.js +0 -7
- package/dist/chunk-H7R4PPMG.js.map +0 -1
- package/dist/chunk-IGOEAKC7.js.map +0 -1
- package/dist/chunk-JTWQGPKF.js.map +0 -1
- package/dist/chunk-LFATOGDF.js.map +0 -1
- package/dist/chunk-ZAP7MYVO.js.map +0 -1
- package/dist/server-2SRARAO5.js.map +0 -1
- /package/dist/{db-EG5ETPTY.js.map → db-FP2ABUU4.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["export const PACKAGE_VERSION = '0.3.26'\n"],"mappings":";AAAO,IAAM,kBAAkB;","names":[]}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
postToMemoryLibrary
|
|
3
|
+
} from "./chunk-K443GQY5.js";
|
|
1
4
|
import {
|
|
2
5
|
DEFAULT_MAPS_PROXY_MODE
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-BP27CZ5Q.js";
|
|
7
|
+
|
|
8
|
+
// src/lib/slugify.ts
|
|
9
|
+
function slugify(s) {
|
|
10
|
+
return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
// src/workflows/artifact-writer.ts
|
|
6
14
|
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
@@ -72,11 +80,6 @@ function rowsToCsv(headers, rows) {
|
|
|
72
80
|
].join("\n") + "\n";
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
// src/lib/slugify.ts
|
|
76
|
-
function slugify(s) {
|
|
77
|
-
return s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
83
|
// src/workflows/artifact-writer.ts
|
|
81
84
|
function workflowOutputBaseDir(outputDir) {
|
|
82
85
|
return outputDir?.trim() || process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || join(homedir(), "Downloads", "mcp-scraper");
|
|
@@ -117,6 +120,7 @@ var ArtifactWriter = class _ArtifactWriter {
|
|
|
117
120
|
async remember(kind, label, path, rows) {
|
|
118
121
|
const size = await stat(path).then((s) => s.size).catch(() => void 0);
|
|
119
122
|
this.artifacts.push({ kind, label, path, bytes: size, rows });
|
|
123
|
+
await postToMemoryLibrary({ title: `${this.title} ${label}`, content: `artifact: ${path}`, source: `mcp-scraper-workflow:${this.workflowId}:${this.runId}` });
|
|
120
124
|
return path;
|
|
121
125
|
}
|
|
122
126
|
async writeJson(label, relativePath, data) {
|
|
@@ -211,7 +215,7 @@ async function openWorkflowReport(id, outputDir) {
|
|
|
211
215
|
|
|
212
216
|
// src/workflows/registry.ts
|
|
213
217
|
import { readFile as readFile2 } from "fs/promises";
|
|
214
|
-
import { z as
|
|
218
|
+
import { z as z6 } from "zod";
|
|
215
219
|
|
|
216
220
|
// src/workflows/http-client.ts
|
|
217
221
|
var WorkflowHttpClient = class {
|
|
@@ -662,20 +666,41 @@ ${summary}
|
|
|
662
666
|
}
|
|
663
667
|
};
|
|
664
668
|
|
|
665
|
-
// src/workflows/workflows/
|
|
669
|
+
// src/workflows/workflows/get-leads.ts
|
|
666
670
|
import { z as z3 } from "zod";
|
|
667
|
-
var
|
|
668
|
-
query: z3.string().min(1),
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
concurrency: z3.number().int().min(1).max(5).default(5),
|
|
676
|
-
proxyMode: z3.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
677
|
-
returnPartial: z3.boolean().default(true)
|
|
671
|
+
var GetLeadsInputSchema = z3.object({
|
|
672
|
+
query: z3.string().min(1).describe('Business category, niche, or keyword to search on Google Maps, e.g. "roofers", "med spas", "dentists". Do not include the city here.'),
|
|
673
|
+
location: z3.string().min(1).describe('City / market to search, e.g. "Houston, TX" or "Austin, Texas".'),
|
|
674
|
+
maxResults: z3.number().int().min(1).max(50).default(25).describe("How many Maps businesses to collect for the market. Maximum 50."),
|
|
675
|
+
enrichWebsites: z3.boolean().default(true).describe("Visit each business website (home + contact pages) to harvest email and social links. Uses the proxy/browser-backed extractor so blocked sites still resolve."),
|
|
676
|
+
hydrateReviewCounts: z3.boolean().default(true).describe("Deep-dive each profile to confirm the review count and booking URL that the Maps search list omits."),
|
|
677
|
+
concurrency: z3.number().int().min(1).max(4).default(3).describe("How many businesses to enrich in parallel. Keep low to respect per-account concurrency limits."),
|
|
678
|
+
proxyMode: z3.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE).describe("Proxy targeting for the Maps search. location targets the market; none is for local debugging.")
|
|
678
679
|
});
|
|
680
|
+
var LEADS_CSV_HEADERS = [
|
|
681
|
+
"position",
|
|
682
|
+
"business_name",
|
|
683
|
+
"review_stars",
|
|
684
|
+
"review_count",
|
|
685
|
+
"category",
|
|
686
|
+
"address",
|
|
687
|
+
"phone",
|
|
688
|
+
"website",
|
|
689
|
+
"email",
|
|
690
|
+
"email_domain_match",
|
|
691
|
+
"email_status",
|
|
692
|
+
"socials",
|
|
693
|
+
"booking_url",
|
|
694
|
+
"place_url",
|
|
695
|
+
"cid",
|
|
696
|
+
"source_location"
|
|
697
|
+
];
|
|
698
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
699
|
+
var MAILTO_RE = /mailto:([^"'?>\s]+)/gi;
|
|
700
|
+
var SOCIAL_RE = /https?:\/\/(?:www\.)?(?:facebook|instagram|linkedin|twitter|youtube|tiktok)\.com\/[^\s"'<>)]+/gi;
|
|
701
|
+
var EMAIL_BAD = [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".ico", "sentry", "wix.com", "wixpress", "example.", "godaddy", "squarespace", "schema.org", "@2x", "u003e", "core-js"];
|
|
702
|
+
var EMAIL_PREFIX = ["info@", "office@", "contact@", "sales@", "hello@", "admin@", "service@", "support@"];
|
|
703
|
+
var SOCIAL_SKIP = ["/sharer", "/share?", "/intent/", "plugins/", "/dialog/", "platform.", "badge", "developers."];
|
|
679
704
|
async function mapLimit(items, limit, fn) {
|
|
680
705
|
const out = new Array(items.length);
|
|
681
706
|
let next = 0;
|
|
@@ -689,6 +714,224 @@ async function mapLimit(items, limit, fn) {
|
|
|
689
714
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
690
715
|
return out;
|
|
691
716
|
}
|
|
717
|
+
function hostRoot(url) {
|
|
718
|
+
try {
|
|
719
|
+
return new URL(url).hostname.replace(/^www\./, "").split(".")[0].toLowerCase();
|
|
720
|
+
} catch {
|
|
721
|
+
return "";
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function cleanEmails(raw) {
|
|
725
|
+
const out = /* @__PURE__ */ new Set();
|
|
726
|
+
for (const value of raw) {
|
|
727
|
+
const email = value.trim().toLowerCase().replace(/\.$/, "");
|
|
728
|
+
if (email.split("@").length !== 2) continue;
|
|
729
|
+
if (email.length > 60) continue;
|
|
730
|
+
if (EMAIL_BAD.some((bad) => email.includes(bad))) continue;
|
|
731
|
+
out.add(email);
|
|
732
|
+
}
|
|
733
|
+
return [...out];
|
|
734
|
+
}
|
|
735
|
+
function pickEmail(emails, host) {
|
|
736
|
+
if (!emails.length) return "";
|
|
737
|
+
const root = hostRoot(host);
|
|
738
|
+
for (const prefix of EMAIL_PREFIX) {
|
|
739
|
+
const hit = emails.find((e) => e.startsWith(prefix) && root && e.split("@")[1].includes(root));
|
|
740
|
+
if (hit) return hit;
|
|
741
|
+
}
|
|
742
|
+
const domainHit = emails.find((e) => root && e.split("@")[1].includes(root));
|
|
743
|
+
if (domainHit) return domainHit;
|
|
744
|
+
for (const prefix of EMAIL_PREFIX) {
|
|
745
|
+
const hit = emails.find((e) => e.startsWith(prefix));
|
|
746
|
+
if (hit) return hit;
|
|
747
|
+
}
|
|
748
|
+
return emails[0];
|
|
749
|
+
}
|
|
750
|
+
function parseContacts(html) {
|
|
751
|
+
const mailtos = [...html.matchAll(MAILTO_RE)].map((m) => decodeURIComponent(m[1]).split("?")[0]);
|
|
752
|
+
const inline = html.match(EMAIL_RE) ?? [];
|
|
753
|
+
const emails = cleanEmails([...mailtos, ...inline]);
|
|
754
|
+
const socials = [...new Set((html.match(SOCIAL_RE) ?? []).map((s) => s.replace(/[)"'<>]+$/, "")).filter((s) => !SOCIAL_SKIP.some((skip) => s.toLowerCase().includes(skip))))].slice(0, 5);
|
|
755
|
+
return { emails, socials };
|
|
756
|
+
}
|
|
757
|
+
function buildAttemptUrls(website) {
|
|
758
|
+
const urls = /* @__PURE__ */ new Set();
|
|
759
|
+
urls.add(website);
|
|
760
|
+
try {
|
|
761
|
+
const origin = new URL(website).origin;
|
|
762
|
+
urls.add(`${origin}/contact`);
|
|
763
|
+
urls.add(`${origin}/contact-us`);
|
|
764
|
+
} catch {
|
|
765
|
+
return [...urls];
|
|
766
|
+
}
|
|
767
|
+
return [...urls].slice(0, 3);
|
|
768
|
+
}
|
|
769
|
+
var getLeadsWorkflowDefinition = {
|
|
770
|
+
id: "get-leads",
|
|
771
|
+
title: "Get Leads",
|
|
772
|
+
description: "Build an outreach-ready local lead list: Google Maps search for a niche in a market, confirm review counts + booking URLs, then visit each business website (home + contact) to harvest email and social links, with proxy/browser-backed extraction so blocked sites still resolve. Saves a leads CSV.",
|
|
773
|
+
inputSchema: GetLeadsInputSchema,
|
|
774
|
+
async run(input, ctx) {
|
|
775
|
+
await ctx.artifacts.writeManifest("running", {}, [], []);
|
|
776
|
+
const warnings = [];
|
|
777
|
+
const errors = [];
|
|
778
|
+
const search = await ctx.client.post("/maps/search", {
|
|
779
|
+
query: input.query,
|
|
780
|
+
location: input.location,
|
|
781
|
+
maxResults: input.maxResults,
|
|
782
|
+
proxyMode: input.proxyMode
|
|
783
|
+
}, 18e4);
|
|
784
|
+
await ctx.artifacts.writeJson("Maps search raw", "raw/maps-search.json", search);
|
|
785
|
+
const seen = /* @__PURE__ */ new Set();
|
|
786
|
+
const deduped = search.results.filter((result) => {
|
|
787
|
+
const key = result.cid ?? result.placeUrl ?? result.name.toLowerCase();
|
|
788
|
+
if (seen.has(key)) return false;
|
|
789
|
+
seen.add(key);
|
|
790
|
+
return true;
|
|
791
|
+
});
|
|
792
|
+
const rows = await mapLimit(deduped, input.concurrency, async (result) => {
|
|
793
|
+
let reviewCount = result.reviewCount ?? "";
|
|
794
|
+
let bookingUrl = "";
|
|
795
|
+
let website = result.websiteUrl ?? "";
|
|
796
|
+
if (input.hydrateReviewCounts) {
|
|
797
|
+
try {
|
|
798
|
+
const place = await ctx.client.post("/maps/place", {
|
|
799
|
+
businessName: result.name,
|
|
800
|
+
location: input.location,
|
|
801
|
+
includeReviews: false
|
|
802
|
+
}, 18e4);
|
|
803
|
+
reviewCount = place.reviewCount ?? reviewCount;
|
|
804
|
+
bookingUrl = place.bookingUrl ?? "";
|
|
805
|
+
website = website || (place.website ?? "");
|
|
806
|
+
} catch (err) {
|
|
807
|
+
warnings.push(`Review hydration failed for ${result.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
let email = "";
|
|
811
|
+
let socials = [];
|
|
812
|
+
let emailStatus = website ? "no email found" : "no website";
|
|
813
|
+
let domainMatch = "";
|
|
814
|
+
if (input.enrichWebsites && website) {
|
|
815
|
+
const attempts = buildAttemptUrls(website);
|
|
816
|
+
let reached = false;
|
|
817
|
+
const allEmails = [];
|
|
818
|
+
const allSocials = /* @__PURE__ */ new Set();
|
|
819
|
+
for (const attemptUrl of attempts) {
|
|
820
|
+
try {
|
|
821
|
+
const page = await ctx.client.post("/extract-url", { url: attemptUrl }, 12e4);
|
|
822
|
+
reached = true;
|
|
823
|
+
const parsed = parseContacts(page.bodyHtml ?? page.bodyMarkdown ?? "");
|
|
824
|
+
allEmails.push(...parsed.emails);
|
|
825
|
+
parsed.socials.forEach((s) => allSocials.add(s));
|
|
826
|
+
if (parsed.emails.length) break;
|
|
827
|
+
} catch {
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
socials = [...allSocials].slice(0, 5);
|
|
832
|
+
email = pickEmail([...new Set(allEmails)], website);
|
|
833
|
+
if (email) {
|
|
834
|
+
domainMatch = hostRoot(website) && email.split("@")[1].includes(hostRoot(website)) ? "yes" : "NO-verify";
|
|
835
|
+
emailStatus = domainMatch === "yes" ? "ok" : "ok (off-domain \u2014 verify)";
|
|
836
|
+
} else if (!reached) {
|
|
837
|
+
emailStatus = "site unreachable";
|
|
838
|
+
} else if (socials.length) {
|
|
839
|
+
emailStatus = "form/social only";
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return {
|
|
843
|
+
position: result.position,
|
|
844
|
+
business_name: result.name,
|
|
845
|
+
review_stars: result.rating ?? "",
|
|
846
|
+
review_count: reviewCount,
|
|
847
|
+
category: result.category ?? "",
|
|
848
|
+
address: result.address ?? "",
|
|
849
|
+
phone: result.phone ?? "",
|
|
850
|
+
website,
|
|
851
|
+
email,
|
|
852
|
+
email_domain_match: domainMatch,
|
|
853
|
+
email_status: emailStatus,
|
|
854
|
+
socials: socials.join(" ; "),
|
|
855
|
+
booking_url: bookingUrl,
|
|
856
|
+
place_url: result.placeUrl,
|
|
857
|
+
cid: result.cid ?? "",
|
|
858
|
+
source_location: input.location
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
await ctx.artifacts.writeCsv("Leads CSV", "exports/leads.csv", LEADS_CSV_HEADERS, rows);
|
|
862
|
+
await ctx.artifacts.writeJson("Leads evidence", "evidence.json", { input, search: { query: search.searchQuery, resultCount: search.resultCount }, rows });
|
|
863
|
+
const withEmail = rows.filter((r) => r.email).length;
|
|
864
|
+
const domainMatched = rows.filter((r) => r.email_domain_match === "yes").length;
|
|
865
|
+
const withReviewCount = rows.filter((r) => r.review_count).length;
|
|
866
|
+
const withSocials = rows.filter((r) => r.socials).length;
|
|
867
|
+
const withWebsite = rows.filter((r) => r.website).length;
|
|
868
|
+
const summary = `${rows.length} ${input.query} in ${input.location}: ${withEmail} emails (${domainMatched} domain-matched), ${withReviewCount} review counts, ${withSocials} with socials.`;
|
|
869
|
+
await ctx.artifacts.writeText("Summary", "summary.md", `# Get Leads
|
|
870
|
+
|
|
871
|
+
${summary}
|
|
872
|
+
`);
|
|
873
|
+
await ctx.artifacts.writeText("Agent tasks", "tasks.md", [
|
|
874
|
+
"# Get Leads \u2014 next steps",
|
|
875
|
+
"",
|
|
876
|
+
"- [ ] Verify deliverability of the harvested emails before outreach (MX/SMTP check).",
|
|
877
|
+
'- [ ] For "form/social only" rows, use the website contact form or the captured social profile.',
|
|
878
|
+
'- [ ] Treat "off-domain \u2014 verify" emails with care; they may belong to a web developer or parent brand.',
|
|
879
|
+
"- [ ] Respect CAN-SPAM / GDPR \u2014 scraped contact data is not consent to email."
|
|
880
|
+
].join("\n"));
|
|
881
|
+
const reportPath = await ctx.artifacts.writeHtml("HTML report", "report.html", renderWorkflowReport({
|
|
882
|
+
title: "Get Leads",
|
|
883
|
+
subtitle: `${input.query} \xB7 ${input.location}`,
|
|
884
|
+
summary,
|
|
885
|
+
warnings,
|
|
886
|
+
tables: [
|
|
887
|
+
{
|
|
888
|
+
title: "Leads",
|
|
889
|
+
columns: ["position", "business_name", "review_stars", "review_count", "phone", "website", "email", "email_status", "socials", "booking_url"],
|
|
890
|
+
rows
|
|
891
|
+
}
|
|
892
|
+
]
|
|
893
|
+
}));
|
|
894
|
+
const status = errors.length ? "failed" : warnings.length ? "partial" : "succeeded";
|
|
895
|
+
const counts = {
|
|
896
|
+
businesses: rows.length,
|
|
897
|
+
withWebsite,
|
|
898
|
+
emails: withEmail,
|
|
899
|
+
domainMatchedEmails: domainMatched,
|
|
900
|
+
reviewCounts: withReviewCount,
|
|
901
|
+
withSocials
|
|
902
|
+
};
|
|
903
|
+
await ctx.artifacts.writeManifest(status, counts, warnings, errors);
|
|
904
|
+
return { title: "Get Leads", summary, status, counts, warnings, errors, reportPath };
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/workflows/workflows/local-competitive-audit.ts
|
|
909
|
+
import { z as z4 } from "zod";
|
|
910
|
+
var LocalCompetitiveAuditInputSchema = z4.object({
|
|
911
|
+
query: z4.string().min(1),
|
|
912
|
+
state: z4.string().min(2).default("TN"),
|
|
913
|
+
minPopulation: z4.number().int().min(0).default(1e5),
|
|
914
|
+
maxCities: z4.number().int().min(1).max(100).default(25),
|
|
915
|
+
maxResultsPerCity: z4.number().int().min(1).max(50).default(20),
|
|
916
|
+
hydrateTop: z4.number().int().min(0).max(10).default(5),
|
|
917
|
+
maxReviews: z4.number().int().min(0).max(500).default(50),
|
|
918
|
+
concurrency: z4.number().int().min(1).max(5).default(5),
|
|
919
|
+
proxyMode: z4.enum(["location", "configured", "none"]).default(DEFAULT_MAPS_PROXY_MODE),
|
|
920
|
+
returnPartial: z4.boolean().default(true)
|
|
921
|
+
});
|
|
922
|
+
async function mapLimit2(items, limit, fn) {
|
|
923
|
+
const out = new Array(items.length);
|
|
924
|
+
let next = 0;
|
|
925
|
+
async function worker() {
|
|
926
|
+
while (next < items.length) {
|
|
927
|
+
const index = next;
|
|
928
|
+
next += 1;
|
|
929
|
+
out[index] = await fn(items[index], index);
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
|
|
933
|
+
return out;
|
|
934
|
+
}
|
|
692
935
|
function numberFrom(value) {
|
|
693
936
|
if (value === null || value === void 0 || value === "") return null;
|
|
694
937
|
const parsed = Number(String(value).replace(/[^\d.]/g, ""));
|
|
@@ -742,7 +985,7 @@ var localCompetitiveAuditWorkflowDefinition = {
|
|
|
742
985
|
seen.add(key);
|
|
743
986
|
return true;
|
|
744
987
|
});
|
|
745
|
-
const hydrated = await
|
|
988
|
+
const hydrated = await mapLimit2(deduped, 3, async ({ city, result }, index) => {
|
|
746
989
|
try {
|
|
747
990
|
const detail = await ctx.client.post("/maps/place", {
|
|
748
991
|
businessName: result.name,
|
|
@@ -862,7 +1105,7 @@ ${summary}
|
|
|
862
1105
|
};
|
|
863
1106
|
|
|
864
1107
|
// src/workflows/workflows/comparison-briefs.ts
|
|
865
|
-
import { z as
|
|
1108
|
+
import { z as z5 } from "zod";
|
|
866
1109
|
|
|
867
1110
|
// src/workflows/workflows/seo-workflow-utils.ts
|
|
868
1111
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -1005,7 +1248,7 @@ function pageSummaryRow(page, source) {
|
|
|
1005
1248
|
schema_types: (page.schemaTypes ?? []).join("; ")
|
|
1006
1249
|
};
|
|
1007
1250
|
}
|
|
1008
|
-
async function
|
|
1251
|
+
async function mapLimit3(items, limit, fn) {
|
|
1009
1252
|
const out = new Array(items.length);
|
|
1010
1253
|
let next = 0;
|
|
1011
1254
|
async function worker() {
|
|
@@ -1020,49 +1263,49 @@ async function mapLimit2(items, limit, fn) {
|
|
|
1020
1263
|
}
|
|
1021
1264
|
|
|
1022
1265
|
// src/workflows/workflows/comparison-briefs.ts
|
|
1023
|
-
var ProxyModeSchema =
|
|
1024
|
-
var MapComparisonInputSchema =
|
|
1025
|
-
query:
|
|
1026
|
-
location:
|
|
1027
|
-
state:
|
|
1028
|
-
minPopulation:
|
|
1029
|
-
maxCities:
|
|
1030
|
-
maxResultsPerCity:
|
|
1031
|
-
hydrateTop:
|
|
1032
|
-
maxReviews:
|
|
1033
|
-
concurrency:
|
|
1266
|
+
var ProxyModeSchema = z5.enum(["location", "configured", "none"]);
|
|
1267
|
+
var MapComparisonInputSchema = z5.object({
|
|
1268
|
+
query: z5.string().min(1),
|
|
1269
|
+
location: z5.string().optional(),
|
|
1270
|
+
state: z5.string().optional(),
|
|
1271
|
+
minPopulation: z5.number().int().min(0).default(1e5),
|
|
1272
|
+
maxCities: z5.number().int().min(1).max(100).default(5),
|
|
1273
|
+
maxResultsPerCity: z5.number().int().min(1).max(50).default(20),
|
|
1274
|
+
hydrateTop: z5.number().int().min(0).max(10).default(5),
|
|
1275
|
+
maxReviews: z5.number().int().min(0).max(500).default(25),
|
|
1276
|
+
concurrency: z5.number().int().min(1).max(5).default(5),
|
|
1034
1277
|
proxyMode: ProxyModeSchema.default(DEFAULT_MAPS_PROXY_MODE),
|
|
1035
|
-
returnPartial:
|
|
1278
|
+
returnPartial: z5.boolean().default(true)
|
|
1036
1279
|
}).refine((input) => input.location || input.state, {
|
|
1037
1280
|
message: "Either location or state is required for map-comparison"
|
|
1038
1281
|
});
|
|
1039
|
-
var SerpComparisonInputSchema =
|
|
1040
|
-
keyword:
|
|
1041
|
-
domain:
|
|
1042
|
-
url:
|
|
1043
|
-
location:
|
|
1044
|
-
maxResults:
|
|
1045
|
-
maxQuestions:
|
|
1046
|
-
extractTop:
|
|
1047
|
-
includePaa:
|
|
1048
|
-
includeAiOverview:
|
|
1049
|
-
returnPartial:
|
|
1282
|
+
var SerpComparisonInputSchema = z5.object({
|
|
1283
|
+
keyword: z5.string().min(1),
|
|
1284
|
+
domain: z5.string().optional(),
|
|
1285
|
+
url: z5.string().url().optional(),
|
|
1286
|
+
location: z5.string().optional(),
|
|
1287
|
+
maxResults: z5.number().int().min(1).max(20).default(10),
|
|
1288
|
+
maxQuestions: z5.number().int().min(1).max(200).default(40),
|
|
1289
|
+
extractTop: z5.number().int().min(0).max(10).default(5),
|
|
1290
|
+
includePaa: z5.boolean().default(true),
|
|
1291
|
+
includeAiOverview: z5.boolean().default(true),
|
|
1292
|
+
returnPartial: z5.boolean().default(true)
|
|
1050
1293
|
});
|
|
1051
|
-
var PaaExpansionBriefInputSchema =
|
|
1052
|
-
keyword:
|
|
1053
|
-
location:
|
|
1054
|
-
maxQuestions:
|
|
1055
|
-
depth:
|
|
1056
|
-
returnPartial:
|
|
1294
|
+
var PaaExpansionBriefInputSchema = z5.object({
|
|
1295
|
+
keyword: z5.string().min(1),
|
|
1296
|
+
location: z5.string().optional(),
|
|
1297
|
+
maxQuestions: z5.number().int().min(1).max(300).default(80),
|
|
1298
|
+
depth: z5.number().int().min(1).max(6).default(3),
|
|
1299
|
+
returnPartial: z5.boolean().default(true)
|
|
1057
1300
|
});
|
|
1058
|
-
var AiOverviewLanguageInputSchema =
|
|
1059
|
-
keyword:
|
|
1060
|
-
domain:
|
|
1061
|
-
url:
|
|
1062
|
-
location:
|
|
1063
|
-
maxQuestions:
|
|
1064
|
-
extractTop:
|
|
1065
|
-
returnPartial:
|
|
1301
|
+
var AiOverviewLanguageInputSchema = z5.object({
|
|
1302
|
+
keyword: z5.string().min(1),
|
|
1303
|
+
domain: z5.string().optional(),
|
|
1304
|
+
url: z5.string().url().optional(),
|
|
1305
|
+
location: z5.string().optional(),
|
|
1306
|
+
maxQuestions: z5.number().int().min(1).max(200).default(40),
|
|
1307
|
+
extractTop: z5.number().int().min(0).max(8).default(3),
|
|
1308
|
+
returnPartial: z5.boolean().default(true)
|
|
1066
1309
|
});
|
|
1067
1310
|
function businessRowsFromMaps(location, query, results) {
|
|
1068
1311
|
return results.map((result) => ({
|
|
@@ -1179,7 +1422,7 @@ function pageGapRows(targetPage, competitorPages) {
|
|
|
1179
1422
|
return rows.slice(0, 150);
|
|
1180
1423
|
}
|
|
1181
1424
|
async function extractPages(ctx, sources, warnings, labelPrefix) {
|
|
1182
|
-
return
|
|
1425
|
+
return mapLimit3(sources.filter((source) => source.url), 2, async (source, index) => {
|
|
1183
1426
|
try {
|
|
1184
1427
|
const page = await ctx.client.post("/extract-url", { url: source.url }, 18e4);
|
|
1185
1428
|
await ctx.artifacts.writeJson(`${labelPrefix} ${index + 1}`, `raw/extract-url/${labelPrefix.toLowerCase()}-${index + 1}.json`, page);
|
|
@@ -1228,7 +1471,7 @@ var mapComparisonWorkflowDefinition = {
|
|
|
1228
1471
|
}
|
|
1229
1472
|
const compareRows = comparisonRows(rows);
|
|
1230
1473
|
const selected = compareRows.slice(0, input.hydrateTop * Math.max(1, input.location ? 1 : input.maxCities));
|
|
1231
|
-
const hydrated = await
|
|
1474
|
+
const hydrated = await mapLimit3(selected, 3, async (row, index) => {
|
|
1232
1475
|
try {
|
|
1233
1476
|
const detail = await ctx.client.post("/maps/place", {
|
|
1234
1477
|
businessName: row.business_name,
|
|
@@ -1563,6 +1806,7 @@ var aiOverviewLanguageWorkflowDefinition = {
|
|
|
1563
1806
|
// src/workflows/registry.ts
|
|
1564
1807
|
var DEFINITIONS = [
|
|
1565
1808
|
directoryWorkflowDefinition,
|
|
1809
|
+
getLeadsWorkflowDefinition,
|
|
1566
1810
|
agentPacketWorkflowDefinition,
|
|
1567
1811
|
localCompetitiveAuditWorkflowDefinition,
|
|
1568
1812
|
mapComparisonWorkflowDefinition,
|
|
@@ -1669,6 +1913,7 @@ async function runWorkflowStep(id, rawInput, opts) {
|
|
|
1669
1913
|
export {
|
|
1670
1914
|
csvRecords,
|
|
1671
1915
|
rowsToCsv,
|
|
1916
|
+
slugify,
|
|
1672
1917
|
workflowOutputBaseDir,
|
|
1673
1918
|
listWorkflowReports,
|
|
1674
1919
|
findWorkflowReport,
|
|
@@ -1680,4 +1925,4 @@ export {
|
|
|
1680
1925
|
runWorkflow,
|
|
1681
1926
|
runWorkflowStep
|
|
1682
1927
|
};
|
|
1683
|
-
//# sourceMappingURL=chunk-
|
|
1928
|
+
//# sourceMappingURL=chunk-NNEIXK5L.js.map
|