mcp-scraper 0.65.1 → 0.66.1

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 (89) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/README.md +11 -6
  3. package/dist/{analytics-repository-IKE53S3E.js → analytics-repository-RUXBFSFX.js} +1 -2
  4. package/dist/bin/api-server.cjs +1844 -71
  5. package/dist/bin/api-server.js +4 -5
  6. package/dist/bin/mcp-scraper-cli.cjs +1 -2
  7. package/dist/bin/mcp-scraper-cli.js +6 -7
  8. package/dist/bin/mcp-scraper-install.cjs +7 -3
  9. package/dist/bin/mcp-scraper-install.js +2 -3
  10. package/dist/bin/mcp-stdio-server.cjs +5150 -3467
  11. package/dist/bin/mcp-stdio-server.js +11 -11
  12. package/dist/bin/paa-harvest.cjs +8 -3
  13. package/dist/bin/paa-harvest.js +4 -5
  14. package/dist/{chunk-5PIO7QBG.js → chunk-3PIXKLGZ.js} +3 -4
  15. package/dist/{chunk-VIWT35S6.js → chunk-4OT7BAIE.js} +1 -2
  16. package/dist/{chunk-ORKXE7VX.js → chunk-4P3PHFPQ.js} +11 -6
  17. package/dist/{chunk-AMLFKPLL.js → chunk-75VW7CWE.js} +4 -7
  18. package/dist/{chunk-FG6LJGJ7.js → chunk-77LQV73B.js} +0 -1
  19. package/dist/{chunk-DKIG2TEE.js → chunk-AQKJOSIB.js} +2 -3
  20. package/dist/{chunk-2XUXVKT4.js → chunk-AYJHI2FK.js} +0 -1
  21. package/dist/{chunk-5BEPOFKG.js → chunk-CNLLTD5B.js} +0 -1
  22. package/dist/{chunk-EKJ65E3Z.js → chunk-DNM65UCK.js} +0 -1
  23. package/dist/{chunk-PDAKCYHZ.js → chunk-E4LMY5OJ.js} +6258 -3747
  24. package/dist/{chunk-MQGBCWZD.js → chunk-GGZEC22A.js} +64 -1
  25. package/dist/{chunk-4VG3NUUY.js → chunk-GTGSD4YS.js} +2 -2
  26. package/dist/{chunk-MQQS7ONH.js → chunk-KBTUPRTF.js} +8 -296
  27. package/dist/{chunk-XPEN6OG5.js → chunk-NS754CRS.js} +15 -3
  28. package/dist/{chunk-UN4LZVCB.js → chunk-OZJMVCDK.js} +1 -2
  29. package/dist/{chunk-342OUCSG.js → chunk-PGJQDMC2.js} +1 -2
  30. package/dist/{chunk-G3P3ZDB4.js → chunk-RJ7JVYKU.js} +0 -1
  31. package/dist/{chunk-N54JS5WP.js → chunk-TNJIPV74.js} +1 -2
  32. package/dist/{chunk-4LINSJNI.js → chunk-TNLGHNLX.js} +1 -2
  33. package/dist/chunk-VRJ7CO6L.js +6 -0
  34. package/dist/chunk-VXLU74YZ.js +293 -0
  35. package/dist/{chunk-DCWXVAQT.js → chunk-WID3MFRS.js} +6 -2
  36. package/dist/{db-BRI465XQ.js → db-PO7N7ZP3.js} +1 -2
  37. package/dist/{extract-bundle-UWKJT4MU.js → extract-bundle-SIBFUA6I.js} +8 -10
  38. package/dist/index.cjs +8 -3
  39. package/dist/index.js +4 -5
  40. package/dist/{lead-list-enrichment-repository-ODWGRVCQ.js → lead-list-enrichment-repository-OTAUQONJ.js} +2 -3
  41. package/dist/{location-data-repository-67MMEVZQ.js → location-data-repository-RM2KJ3H5.js} +3 -4
  42. package/dist/{server-RSHSZBUZ.js → server-ELEJNRRE.js} +875 -1629
  43. package/dist/{site-extract-repository-J3RH3MOS.js → site-extract-repository-ASYEJO7Z.js} +4 -5
  44. package/dist/{worker-YKRIK5LS.js → worker-MAVYYWM7.js} +8 -9
  45. package/package.json +3 -1
  46. package/dist/analytics-repository-IKE53S3E.js.map +0 -1
  47. package/dist/bin/api-server.cjs.map +0 -1
  48. package/dist/bin/api-server.js.map +0 -1
  49. package/dist/bin/mcp-scraper-cli.cjs.map +0 -1
  50. package/dist/bin/mcp-scraper-cli.js.map +0 -1
  51. package/dist/bin/mcp-scraper-install.cjs.map +0 -1
  52. package/dist/bin/mcp-scraper-install.js.map +0 -1
  53. package/dist/bin/mcp-stdio-server.cjs.map +0 -1
  54. package/dist/bin/mcp-stdio-server.js.map +0 -1
  55. package/dist/bin/paa-harvest.cjs.map +0 -1
  56. package/dist/bin/paa-harvest.js.map +0 -1
  57. package/dist/chunk-2XUXVKT4.js.map +0 -1
  58. package/dist/chunk-342OUCSG.js.map +0 -1
  59. package/dist/chunk-4LINSJNI.js.map +0 -1
  60. package/dist/chunk-4VG3NUUY.js.map +0 -1
  61. package/dist/chunk-5BEPOFKG.js.map +0 -1
  62. package/dist/chunk-5PIO7QBG.js.map +0 -1
  63. package/dist/chunk-AMLFKPLL.js.map +0 -1
  64. package/dist/chunk-CGKWNXJR.js +0 -7
  65. package/dist/chunk-CGKWNXJR.js.map +0 -1
  66. package/dist/chunk-DCWXVAQT.js.map +0 -1
  67. package/dist/chunk-DKIG2TEE.js.map +0 -1
  68. package/dist/chunk-EKJ65E3Z.js.map +0 -1
  69. package/dist/chunk-FG6LJGJ7.js.map +0 -1
  70. package/dist/chunk-G3P3ZDB4.js.map +0 -1
  71. package/dist/chunk-MQGBCWZD.js.map +0 -1
  72. package/dist/chunk-MQQS7ONH.js.map +0 -1
  73. package/dist/chunk-N54JS5WP.js.map +0 -1
  74. package/dist/chunk-ORKXE7VX.js.map +0 -1
  75. package/dist/chunk-PDAKCYHZ.js.map +0 -1
  76. package/dist/chunk-UN4LZVCB.js.map +0 -1
  77. package/dist/chunk-UVFB24RZ.js +0 -67
  78. package/dist/chunk-UVFB24RZ.js.map +0 -1
  79. package/dist/chunk-VIWT35S6.js.map +0 -1
  80. package/dist/chunk-XPEN6OG5.js.map +0 -1
  81. package/dist/db-BRI465XQ.js.map +0 -1
  82. package/dist/extract-bundle-UWKJT4MU.js.map +0 -1
  83. package/dist/index.cjs.map +0 -1
  84. package/dist/index.js.map +0 -1
  85. package/dist/lead-list-enrichment-repository-ODWGRVCQ.js.map +0 -1
  86. package/dist/location-data-repository-67MMEVZQ.js.map +0 -1
  87. package/dist/server-RSHSZBUZ.js.map +0 -1
  88. package/dist/site-extract-repository-J3RH3MOS.js.map +0 -1
  89. package/dist/worker-YKRIK5LS.js.map +0 -1
@@ -0,0 +1,293 @@
1
+ import {
2
+ isPrivateIpAddress,
3
+ loadHtmlDocument
4
+ } from "./chunk-GGZEC22A.js";
5
+
6
+ // src/lib/contact-evidence.ts
7
+ var MAX_EMAILS = 20;
8
+ var MAX_SOCIAL_PROFILES = 20;
9
+ var MAX_CONTACT_FORMS = 10;
10
+ var MAX_CANDIDATE_PAGE_URLS = 20;
11
+ var MAX_CONTEXT_CHARS = 280;
12
+ var EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
13
+ var EMAIL_NOISE = [
14
+ ".png",
15
+ ".jpg",
16
+ ".jpeg",
17
+ ".gif",
18
+ ".svg",
19
+ ".webp",
20
+ ".ico",
21
+ "@2x",
22
+ "sentry",
23
+ "wix.com",
24
+ "wixpress",
25
+ "example.",
26
+ "godaddy",
27
+ "squarespace",
28
+ "schema.org",
29
+ "u003e",
30
+ "core-js"
31
+ ];
32
+ var DEVELOPER_CONTEXT = /\b(?:website|site|web)\s+(?:by|built|design(?:ed)?|develop(?:ed)?|powered)\b|\b(?:developer|webmaster)\b/i;
33
+ var CONTACT_PATH = /(?:^|\/)(?:about|bio|contact|contact-us|meet|our-team|people|profile|staff|team)(?:\/|$)/i;
34
+ var SOCIAL_HOSTS = {
35
+ "facebook.com": "facebook",
36
+ "fb.com": "facebook",
37
+ "instagram.com": "instagram",
38
+ "linkedin.com": "linkedin",
39
+ "twitter.com": "twitter",
40
+ "x.com": "x",
41
+ "youtube.com": "youtube",
42
+ "youtu.be": "youtube",
43
+ "tiktok.com": "tiktok",
44
+ "pinterest.com": "pinterest"
45
+ };
46
+ var SOCIAL_NOISE = /(?:\/shar(?:e|er)|\/intent\/|\/dialog\/|\/plugins\/|\/share\?|\/developers?\/|platform\.)/i;
47
+ function compact(value) {
48
+ return (value ?? "").replace(/\s+/g, " ").trim();
49
+ }
50
+ function safeHttpUrl(value, base) {
51
+ try {
52
+ const parsed = new URL(value, base);
53
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
54
+ return parsed;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ function normalizeEmail(value) {
60
+ let decoded = value;
61
+ try {
62
+ decoded = decodeURIComponent(value);
63
+ } catch {
64
+ }
65
+ const email = decoded.split("?")[0].trim().toLowerCase().replace(/[),.;:]+$/, "");
66
+ if (email.length > 254 || email.split("@").length !== 2) return null;
67
+ if (!/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9.-]+\.[a-z]{2,}$/i.test(email)) return null;
68
+ if (EMAIL_NOISE.some((part) => email.includes(part))) return null;
69
+ return email;
70
+ }
71
+ function nearbyContext(node) {
72
+ const parent = node.closest("p,li,address,section,article,div").first();
73
+ return compact(parent.length ? parent.text() : node.text()).slice(0, MAX_CONTEXT_CHARS);
74
+ }
75
+ function addEmail(target, raw, sourceUrl, context) {
76
+ const value = normalizeEmail(raw);
77
+ if (!value || DEVELOPER_CONTEXT.test(context)) return;
78
+ if (!target.has(value)) {
79
+ target.set(value, {
80
+ value,
81
+ sourceUrl,
82
+ context: compact(context).slice(0, MAX_CONTEXT_CHARS),
83
+ sourceType: "first_party_page"
84
+ });
85
+ }
86
+ }
87
+ function normalizedSocialUrl(parsed) {
88
+ parsed.hash = "";
89
+ for (const key of [...parsed.searchParams.keys()]) {
90
+ if (/^(?:utm_|fbclid|gclid)/i.test(key)) parsed.searchParams.delete(key);
91
+ }
92
+ return parsed.href;
93
+ }
94
+ function socialNetwork(hostname) {
95
+ const host = hostname.toLowerCase().replace(/^www\./, "");
96
+ const match = Object.entries(SOCIAL_HOSTS).find(([domain]) => host === domain || host.endsWith(`.${domain}`));
97
+ return match?.[1] ?? null;
98
+ }
99
+ function extractContactEvidence(html, pageUrl) {
100
+ const source = safeHttpUrl(pageUrl);
101
+ if (!source) throw new Error("pageUrl must be a public HTTP(S) URL");
102
+ const { $ } = loadHtmlDocument(html);
103
+ const emails = /* @__PURE__ */ new Map();
104
+ const socialProfiles = /* @__PURE__ */ new Map();
105
+ const contactForms = /* @__PURE__ */ new Map();
106
+ const candidatePageUrls = /* @__PURE__ */ new Map();
107
+ $('a[href^="mailto:" i]').each((_index, element) => {
108
+ const node = $(element);
109
+ addEmail(emails, (node.attr("href") ?? "").slice(7), source.href, nearbyContext(node));
110
+ });
111
+ $("body").find("*").addBack().contents().filter((_index, node) => node.type === "text").each((_index, node) => {
112
+ const parent = $(node.parent ?? node);
113
+ const text = String(node.data ?? "");
114
+ for (const match of text.match(EMAIL_PATTERN) ?? []) addEmail(emails, match, source.href, nearbyContext(parent));
115
+ });
116
+ $("a[href]").each((_index, element) => {
117
+ const node = $(element);
118
+ const parsed = safeHttpUrl(node.attr("href") ?? "", source.href);
119
+ if (!parsed) return;
120
+ const context = compact(`${node.text()} ${nearbyContext(node)}`).slice(0, MAX_CONTEXT_CHARS);
121
+ const network = socialNetwork(parsed.hostname);
122
+ if (network && !SOCIAL_NOISE.test(`${parsed.pathname}${parsed.search}`)) {
123
+ const url2 = normalizedSocialUrl(parsed);
124
+ if (!socialProfiles.has(url2)) socialProfiles.set(url2, { url: url2, network, sourceUrl: source.href, context });
125
+ return;
126
+ }
127
+ if (parsed.origin !== source.origin || !CONTACT_PATH.test(parsed.pathname)) return;
128
+ parsed.hash = "";
129
+ const url = parsed.href;
130
+ if (!candidatePageUrls.has(url)) candidatePageUrls.set(url, { url, sourceUrl: source.href, context });
131
+ });
132
+ $("form").each((_index, element) => {
133
+ const node = $(element);
134
+ const action = safeHttpUrl(node.attr("action") || source.href, source.href);
135
+ if (!action || action.origin !== source.origin) return;
136
+ action.hash = "";
137
+ const url = action.href;
138
+ if (!contactForms.has(url)) contactForms.set(url, { url, sourceUrl: source.href, context: nearbyContext(node) });
139
+ });
140
+ return {
141
+ emails: [...emails.values()].slice(0, MAX_EMAILS),
142
+ socialProfiles: [...socialProfiles.values()].slice(0, MAX_SOCIAL_PROFILES),
143
+ contactForms: [...contactForms.values()].slice(0, MAX_CONTACT_FORMS),
144
+ candidatePageUrls: [...candidatePageUrls.values()].slice(0, MAX_CANDIDATE_PAGE_URLS)
145
+ };
146
+ }
147
+
148
+ // src/leads/contact-enrichment.ts
149
+ var GENERIC_EMAIL_PREFIXES = /* @__PURE__ */ new Set([
150
+ "admin",
151
+ "contact",
152
+ "customerservice",
153
+ "hello",
154
+ "help",
155
+ "info",
156
+ "inquiries",
157
+ "mail",
158
+ "office",
159
+ "reception",
160
+ "sales",
161
+ "service",
162
+ "support",
163
+ "team"
164
+ ]);
165
+ var CONTACT_PATH2 = /(?:^|\/)(?:about|bio|contact|contact-us|meet|our-team|people|profile|staff|team)(?:\/|$)/i;
166
+ var TEAM_CONTEXT = /\b(?:our team|meet the team|staff|agents?|brokers?|realtors?|people)\b/i;
167
+ var OFFICE_CONTEXT = /\b(?:brokerage|company|corporate|front desk|main office|office|organization|reception)\b/i;
168
+ function normalizeWords(value) {
169
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(/\s+/).filter(Boolean);
170
+ }
171
+ function safeHttpUrl2(value, base) {
172
+ try {
173
+ const parsed = new URL(value, base);
174
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
175
+ return parsed;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+ function classifyEmailScope(evidence, entity) {
181
+ const local = evidence.value.split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, " ");
182
+ const nameWords = normalizeWords(entity.name).filter((word) => word.length > 1);
183
+ const pagePath = safeHttpUrl2(evidence.sourceUrl)?.pathname.toLowerCase() ?? "";
184
+ const combinedContext = `${evidence.context} ${pagePath}`.toLowerCase();
185
+ const generic = GENERIC_EMAIL_PREFIXES.has(local.replace(/\s+/g, ""));
186
+ const nameHits = nameWords.filter((word) => new RegExp(`(?:^|[^a-z0-9])${word}(?:$|[^a-z0-9])`, "i").test(`${local} ${combinedContext}`)).length;
187
+ const hasExactPersonContext = entity.entityType === "person" && nameWords.length >= 2 && nameHits >= Math.min(2, nameWords.length) && (local.includes(nameWords[0]) || pagePath.includes(nameWords.join("-")) || pagePath.includes(nameWords.join("/")));
188
+ if (!generic && hasExactPersonContext) return { scope: "person", reasons: ["named_person_page_context"] };
189
+ if (generic || OFFICE_CONTEXT.test(combinedContext)) return { scope: "office", reasons: generic ? ["generic_mailbox_prefix"] : ["organization_page_context"] };
190
+ if (TEAM_CONTEXT.test(combinedContext)) return { scope: "team", reasons: ["team_page_context"] };
191
+ if (entity.entityType === "business") {
192
+ const organizationWords = normalizeWords(entity.organization || entity.name).filter((word) => word.length > 2);
193
+ if (organizationWords.some((word) => combinedContext.includes(word))) return { scope: "team", reasons: ["business_context"] };
194
+ }
195
+ return { scope: "unknown", reasons: ["public_occurrence_without_identity_evidence"] };
196
+ }
197
+ function selectPrimaryEmail(evidence, entity) {
198
+ const scoped = evidence.map((item) => ({ ...item, ...classifyEmailScope(item, entity) }));
199
+ const allowed = entity.entityType === "person" ? ["person", "team"] : ["person", "team", "office"];
200
+ const sourcePriority = ["provided", "first_party_page", "structured_data", "serp_snippet"];
201
+ const priority = ["person", "team", "office"];
202
+ return scoped.filter((item) => allowed.includes(item.scope)).sort((a, b) => {
203
+ const sourceDelta = sourcePriority.indexOf(a.sourceType) - sourcePriority.indexOf(b.sourceType);
204
+ return sourceDelta || priority.indexOf(a.scope) - priority.indexOf(b.scope);
205
+ })[0] ?? null;
206
+ }
207
+ function contactAttemptUrls(seedUrl, discoveredLinks, maxPages) {
208
+ if (!Number.isInteger(maxPages) || maxPages < 1 || maxPages > 4) throw new Error("maxPages must be an integer from 1 to 4");
209
+ const seed = safeHttpUrl2(seedUrl);
210
+ if (!seed) return [];
211
+ seed.hash = "";
212
+ const urls = /* @__PURE__ */ new Set([seed.href]);
213
+ const discovered = discoveredLinks.map((item) => typeof item === "string" ? item : item.url).map((item) => safeHttpUrl2(item, seed.href)).filter((item) => Boolean(item && item.origin === seed.origin && CONTACT_PATH2.test(item.pathname)));
214
+ for (const item of discovered) {
215
+ item.hash = "";
216
+ urls.add(item.href);
217
+ }
218
+ for (const pathname of ["/contact", "/contact-us"]) urls.add(new URL(pathname, seed.origin).href);
219
+ return [...urls].slice(0, maxPages);
220
+ }
221
+
222
+ // src/leads/serp-email-evidence.ts
223
+ var EMAIL_CANDIDATE = /[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
224
+ var DEVELOPER_CONTEXT2 = /\b(?:website|site|web)\s+(?:by|built|design(?:ed)?|develop(?:ed)?|powered)\b|\b(?:developer|webmaster)\b/i;
225
+ var MAX_CONTEXT_CHARS2 = 280;
226
+ var MAX_EMAILS2 = 10;
227
+ function normalizeHostname(hostname) {
228
+ return hostname.toLowerCase().replace(/^www\./, "").replace(/\.$/, "");
229
+ }
230
+ function isLocalHostname(hostname) {
231
+ const host = normalizeHostname(hostname);
232
+ return host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || !host.includes(".") && !host.includes(":");
233
+ }
234
+ function parsePublicLiteralUrl(websiteUrl) {
235
+ try {
236
+ const parsed = new URL(websiteUrl.trim());
237
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password) return null;
238
+ if (isLocalHostname(parsed.hostname) || isPrivateIpAddress(parsed.hostname)) return null;
239
+ return parsed;
240
+ } catch {
241
+ return null;
242
+ }
243
+ }
244
+ function compactContext(title, snippet) {
245
+ return `${title} ${snippet ?? ""}`.replace(/\s+/g, " ").trim().slice(0, MAX_CONTEXT_CHARS2);
246
+ }
247
+ function buildSerpEmailQuery(websiteUrl) {
248
+ const parsed = parsePublicLiteralUrl(websiteUrl);
249
+ if (!parsed) return null;
250
+ parsed.hostname = normalizeHostname(parsed.hostname);
251
+ return `email for ${parsed.protocol}//${parsed.host}/`;
252
+ }
253
+ function extractSerpEmailEvidence({
254
+ websiteUrl,
255
+ results
256
+ }) {
257
+ const parsedWebsite = parsePublicLiteralUrl(websiteUrl);
258
+ if (!parsedWebsite) return [];
259
+ const websiteDomain = normalizeHostname(parsedWebsite.hostname);
260
+ const accepted = /* @__PURE__ */ new Map();
261
+ const orderedResults = results.map((result, index) => ({ result, index })).sort((left, right) => {
262
+ const leftPosition = Number.isFinite(left.result.position) ? left.result.position : Number.MAX_SAFE_INTEGER;
263
+ const rightPosition = Number.isFinite(right.result.position) ? right.result.position : Number.MAX_SAFE_INTEGER;
264
+ return leftPosition - rightPosition || left.index - right.index;
265
+ });
266
+ for (const { result } of orderedResults) {
267
+ const searchableText = `${result.title} ${result.snippet ?? ""}`;
268
+ const context = compactContext(result.title, result.snippet);
269
+ for (const rawCandidate of searchableText.match(EMAIL_CANDIDATE) ?? []) {
270
+ const value = normalizeEmail(rawCandidate);
271
+ if (!value || DEVELOPER_CONTEXT2.test(searchableText) || normalizeHostname(value.slice(value.lastIndexOf("@") + 1)) !== websiteDomain) continue;
272
+ if (accepted.has(value)) continue;
273
+ accepted.set(value, {
274
+ value,
275
+ sourceUrl: result.url,
276
+ sourceTitle: result.title,
277
+ sourcePosition: result.position,
278
+ context,
279
+ sourceType: "serp_snippet"
280
+ });
281
+ }
282
+ }
283
+ return [...accepted.values()].slice(0, MAX_EMAILS2);
284
+ }
285
+
286
+ export {
287
+ extractContactEvidence,
288
+ classifyEmailScope,
289
+ selectPrimaryEmail,
290
+ contactAttemptUrls,
291
+ buildSerpEmailQuery,
292
+ extractSerpEmailEvidence
293
+ };
@@ -1,3 +1,8 @@
1
+ // release-message.json
2
+ var release_message_default = {
3
+ message: "Long PAA research now continues as a durable MCP Task with preserved progress, while transient browser interruptions return specific retry guidance instead of a generic outage."
4
+ };
5
+
1
6
  // src/install-terminal.ts
2
7
  var CODES = {
3
8
  reset: "\x1B[0m",
@@ -48,7 +53,7 @@ function renderInstallTerminal(options) {
48
53
  colorize(ascii, "amber", color),
49
54
  `${colorize("MCP Scraper Agent", "cyan", color)} . v${options.version} . mcpscraper.dev`,
50
55
  "1/1 install surfaces ready",
51
- colorize("Newest: any approved connection read can become an indexed Memory snapshot in one call. OAuth stays tenant-isolated and provider content is redacted and marked untrusted.", "lime", color),
56
+ colorize(`Newest in v${options.version}: ${release_message_default.message}`, "lime", color),
52
57
  "",
53
58
  `${colorize("Tools", "cyan", color)} ${colorize("(259 MCP tools)", "muted", color)}`,
54
59
  toolRow("search", ["harvest_paa", "search_serp", "maps_search", "maps_place_intel"], color),
@@ -106,4 +111,3 @@ function renderInstallTerminal(options) {
106
111
  export {
107
112
  renderInstallTerminal
108
113
  };
109
- //# sourceMappingURL=chunk-DCWXVAQT.js.map
@@ -126,7 +126,7 @@ import {
126
126
  updateKpoJobState,
127
127
  upsertPageSnapshot,
128
128
  verifyPassword
129
- } from "./chunk-4VG3NUUY.js";
129
+ } from "./chunk-GTGSD4YS.js";
130
130
  export {
131
131
  CORE_SCHEMA_VERSION,
132
132
  CREDIT_LOTS_EPOCH,
@@ -256,4 +256,3 @@ export {
256
256
  upsertPageSnapshot,
257
257
  verifyPassword
258
258
  };
259
- //# sourceMappingURL=db-BRI465XQ.js.map
@@ -4,27 +4,26 @@ import {
4
4
  downloadAsset,
5
5
  embedCommonsTexts,
6
6
  publicizeExtractionFailure
7
- } from "./chunk-AMLFKPLL.js";
8
- import "./chunk-UN4LZVCB.js";
7
+ } from "./chunk-75VW7CWE.js";
8
+ import "./chunk-OZJMVCDK.js";
9
9
  import {
10
10
  computeIssues,
11
11
  renderImageSection,
12
12
  renderIssueReport,
13
13
  renderLinkReport
14
- } from "./chunk-342OUCSG.js";
15
- import "./chunk-MQGBCWZD.js";
16
- import "./chunk-UVFB24RZ.js";
14
+ } from "./chunk-PGJQDMC2.js";
15
+ import "./chunk-GGZEC22A.js";
17
16
  import {
18
17
  createSiteExtractBundleArtifactStream,
19
18
  createSiteExtractContentReader,
20
19
  createSiteExtractImageArtifact,
21
20
  extractJobLimitInfo
22
- } from "./chunk-5PIO7QBG.js";
23
- import "./chunk-EKJ65E3Z.js";
24
- import "./chunk-2XUXVKT4.js";
21
+ } from "./chunk-3PIXKLGZ.js";
22
+ import "./chunk-DNM65UCK.js";
23
+ import "./chunk-AYJHI2FK.js";
25
24
  import {
26
25
  getDb
27
- } from "./chunk-4VG3NUUY.js";
26
+ } from "./chunk-GTGSD4YS.js";
28
27
 
29
28
  // src/api/extract-bundle.ts
30
29
  import { createWriteStream, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
@@ -1002,4 +1001,3 @@ export {
1002
1001
  SITE_EXTRACT_PAGE_CHUNK,
1003
1002
  assembleExtractArtifacts
1004
1003
  };
1005
- //# sourceMappingURL=extract-bundle-UWKJT4MU.js.map
package/dist/index.cjs CHANGED
@@ -4073,6 +4073,9 @@ function classifyAttemptError(err) {
4073
4073
  if (err instanceof RequestAbortedError) return "request_aborted";
4074
4074
  if (err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError")) return "timeout";
4075
4075
  const message = err instanceof Error ? err.message : String(err);
4076
+ if (/browser (?:has been )?closed|context (?:has been )?closed|target page, context or browser has been closed|session closed|page has been closed/i.test(message)) {
4077
+ return "browser_session_interrupted";
4078
+ }
4076
4079
  if (looksLikeProxyTunnelFailure(message)) return "proxy_tunnel_failed";
4077
4080
  if (looksLikeProxyUnavailable(message)) return "proxy_unavailable";
4078
4081
  return /timeout|timed out|Timeout \d+ms exceeded|deadline/i.test(message) ? "timeout" : "error";
@@ -4120,6 +4123,9 @@ function looksLikeProxyUnavailable(message) {
4120
4123
  function retryableLocationProxyError(outcome) {
4121
4124
  return outcome === "captcha" || outcome === "proxy_tunnel_failed" || outcome === "proxy_unavailable";
4122
4125
  }
4126
+ function retryableFreshBrowserError(outcome) {
4127
+ return outcome === "browser_session_interrupted";
4128
+ }
4123
4129
  function locationMismatchMessage(result) {
4124
4130
  const evidence = result.diagnostics.debug?.locationEvidence;
4125
4131
  const expected = evidence?.expected?.canonicalLocation ?? result.location ?? "requested location";
@@ -4303,7 +4309,7 @@ async function harvest(rawOptions) {
4303
4309
  proxyFailures++;
4304
4310
  if (proxyFailures >= 2) effectiveProxyMode = "none";
4305
4311
  }
4306
- const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome2));
4312
+ const willRetry2 = i < maxAttempts - 1 && (outcome2 === "captcha" || retryableFreshBrowserError(outcome2) || proxyMode === "location" && retryableLocationProxyError(outcome2));
4307
4313
  if (outcome2 === "captcha") {
4308
4314
  console.warn(JSON.stringify({
4309
4315
  event: "harvest_attempt_captcha",
@@ -4421,7 +4427,7 @@ async function harvest(rawOptions) {
4421
4427
  proxyFailures++;
4422
4428
  if (proxyFailures >= 2) effectiveProxyMode = "none";
4423
4429
  }
4424
- const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
4430
+ const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || retryableFreshBrowserError(outcome) || proxyMode === "location" && retryableLocationProxyError(outcome));
4425
4431
  if (outcome === "captcha") {
4426
4432
  console.warn(JSON.stringify({
4427
4433
  event: "harvest_attempt_captcha",
@@ -4803,4 +4809,3 @@ var VideoGenerator = class {
4803
4809
  buildClipPrompts,
4804
4810
  harvest
4805
4811
  });
4806
- //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  harvest
3
- } from "./chunk-ORKXE7VX.js";
3
+ } from "./chunk-4P3PHFPQ.js";
4
4
  import {
5
5
  recordVendorUsage
6
- } from "./chunk-4LINSJNI.js";
7
- import "./chunk-FG6LJGJ7.js";
8
- import "./chunk-4VG3NUUY.js";
6
+ } from "./chunk-TNLGHNLX.js";
7
+ import "./chunk-77LQV73B.js";
8
+ import "./chunk-GTGSD4YS.js";
9
9
 
10
10
  // src/video/VideoGenerator.ts
11
11
  import { execSync as execSync2 } from "child_process";
@@ -333,4 +333,3 @@ export {
333
333
  buildClipPrompts,
334
334
  harvest
335
335
  };
336
- //# sourceMappingURL=index.js.map
@@ -30,8 +30,8 @@ import {
30
30
  saveLeadListEnrichmentRowResult,
31
31
  setInitializingLeadListEnrichmentConcurrencyLock,
32
32
  stableCanonicalJson
33
- } from "./chunk-N54JS5WP.js";
34
- import "./chunk-4VG3NUUY.js";
33
+ } from "./chunk-TNJIPV74.js";
34
+ import "./chunk-GTGSD4YS.js";
35
35
  export {
36
36
  LEAD_LIST_MAX_DISPATCH_ATTEMPTS,
37
37
  LEAD_LIST_RUNNING_STALE_MINUTES,
@@ -65,4 +65,3 @@ export {
65
65
  setInitializingLeadListEnrichmentConcurrencyLock,
66
66
  stableCanonicalJson
67
67
  };
68
- //# sourceMappingURL=lead-list-enrichment-repository-ODWGRVCQ.js.map
@@ -13,9 +13,9 @@ import {
13
13
  parseHostedCensusPlacesCsv,
14
14
  parseHostedZipGroupsCsv,
15
15
  queryHostedLocationMarkets
16
- } from "./chunk-DKIG2TEE.js";
17
- import "./chunk-G3P3ZDB4.js";
18
- import "./chunk-4VG3NUUY.js";
16
+ } from "./chunk-AQKJOSIB.js";
17
+ import "./chunk-RJ7JVYKU.js";
18
+ import "./chunk-GTGSD4YS.js";
19
19
  export {
20
20
  HOSTED_CENSUS_DATASET_PREFIX,
21
21
  HOSTED_ZIP_DATASET_KIND,
@@ -32,4 +32,3 @@ export {
32
32
  parseHostedZipGroupsCsv,
33
33
  queryHostedLocationMarkets
34
34
  };
35
- //# sourceMappingURL=location-data-repository-67MMEVZQ.js.map