recker 1.0.79 → 1.0.80-next.70ea84d

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 (196) hide show
  1. package/dist/browser/browser/mini.d.ts +2 -2
  2. package/dist/browser/browser/recker-mini.d.ts +8 -8
  3. package/dist/browser/browser/recker.d.ts +11 -8
  4. package/dist/browser/browser/recker.js +54 -6
  5. package/dist/browser/core/client.d.ts +15 -10
  6. package/dist/browser/core/client.js +54 -38
  7. package/dist/browser/index.iife.min.js +129 -130
  8. package/dist/browser/index.min.js +129 -130
  9. package/dist/browser/index.mini.iife.js +5697 -636
  10. package/dist/browser/index.mini.iife.min.js +47 -48
  11. package/dist/browser/index.mini.min.js +47 -48
  12. package/dist/browser/index.mini.umd.js +5697 -636
  13. package/dist/browser/index.mini.umd.min.js +47 -48
  14. package/dist/browser/index.umd.min.js +129 -130
  15. package/dist/browser/mini.d.ts +2 -2
  16. package/dist/browser/plugins/proxy-rotator.d.ts +2 -2
  17. package/dist/browser/plugins/proxy-rotator.js +6 -28
  18. package/dist/browser/recker-mini.d.ts +8 -8
  19. package/dist/browser/recker.d.ts +11 -8
  20. package/dist/browser/recker.js +54 -6
  21. package/dist/browser/scrape/document.js +2 -2
  22. package/dist/browser/scrape/element.js +7 -1
  23. package/dist/browser/scrape/parser/nodes/html.js +1 -1
  24. package/dist/browser/scrape/spider.d.ts +52 -0
  25. package/dist/browser/scrape/spider.js +620 -38
  26. package/dist/browser/scrape/types.d.ts +2 -0
  27. package/dist/browser/search/google.d.ts +26 -1
  28. package/dist/browser/search/google.js +427 -45
  29. package/dist/browser/seo/analyzer.d.ts +1 -0
  30. package/dist/browser/seo/analyzer.js +144 -1
  31. package/dist/browser/seo/index.d.ts +1 -1
  32. package/dist/browser/seo/keyword-campaign-analyzer.d.ts +2 -0
  33. package/dist/browser/seo/keyword-campaign-analyzer.js +538 -0
  34. package/dist/browser/seo/keyword-campaign-seed-advanced.d.ts +17 -0
  35. package/dist/browser/seo/keyword-campaign-seed-advanced.js +269 -0
  36. package/dist/browser/seo/keyword-campaign-seed-core.d.ts +29 -0
  37. package/dist/browser/seo/keyword-campaign-seed-core.js +525 -0
  38. package/dist/browser/seo/keyword-campaign-shared.d.ts +165 -0
  39. package/dist/browser/seo/keyword-campaign-shared.js +59 -0
  40. package/dist/browser/seo/keyword-campaign.d.ts +4 -107
  41. package/dist/browser/seo/keyword-campaign.js +2 -380
  42. package/dist/browser/seo/keywords.js +5 -22
  43. package/dist/browser/seo/types.d.ts +19 -0
  44. package/dist/browser/transport/curl.d.ts +5 -1
  45. package/dist/browser/transport/curl.js +207 -50
  46. package/dist/browser/transport/undici.d.ts +4 -3
  47. package/dist/browser/transport/undici.js +123 -49
  48. package/dist/browser/types/index.d.ts +9 -3
  49. package/dist/browser/utils/binary-manager.js +26 -3
  50. package/dist/browser/utils/block-detector.d.ts +8 -0
  51. package/dist/browser/utils/block-detector.js +542 -7
  52. package/dist/cli/commands/hls-runner.js +5 -4
  53. package/dist/cli/commands/live-runner.js +5 -4
  54. package/dist/cli/commands/loadtest-runner.js +3 -2
  55. package/dist/cli/commands/search.d.ts +2 -0
  56. package/dist/cli/commands/search.js +105 -0
  57. package/dist/cli/commands/seo-runner.js +9 -7
  58. package/dist/cli/commands/seo.js +140 -1
  59. package/dist/cli/commands/serve.js +75 -131
  60. package/dist/cli/commands/server-runner.js +59 -82
  61. package/dist/cli/commands/spider-runner.d.ts +37 -1
  62. package/dist/cli/commands/spider-runner.js +134 -10
  63. package/dist/cli/commands/spider.d.ts +18 -1
  64. package/dist/cli/commands/spider.js +457 -27
  65. package/dist/cli/events/handlers/cli.js +30 -1
  66. package/dist/cli/events/handlers/tui.js +26 -0
  67. package/dist/cli/events/types.d.ts +27 -0
  68. package/dist/cli/handler.d.ts +2 -12
  69. package/dist/cli/handler.js +20 -15
  70. package/dist/cli/handlers/protocols.js +39 -12
  71. package/dist/cli/handlers/search.d.ts +2 -0
  72. package/dist/cli/handlers/search.js +171 -0
  73. package/dist/cli/handlers/seo-analyze.d.ts +1 -0
  74. package/dist/cli/handlers/seo-analyze.js +666 -0
  75. package/dist/cli/handlers/seo-robots.d.ts +1 -0
  76. package/dist/cli/handlers/seo-robots.js +76 -0
  77. package/dist/cli/handlers/seo-serp.d.ts +54 -0
  78. package/dist/cli/handlers/seo-serp.js +243 -0
  79. package/dist/cli/handlers/seo-sitemap.d.ts +1 -0
  80. package/dist/cli/handlers/seo-sitemap.js +55 -0
  81. package/dist/cli/handlers/seo-spider.d.ts +1 -0
  82. package/dist/cli/handlers/seo-spider.js +334 -0
  83. package/dist/cli/handlers/seo.d.ts +8 -4
  84. package/dist/cli/handlers/seo.js +294 -442
  85. package/dist/cli/handlers/spider.js +94 -17
  86. package/dist/cli/handlers/streaming.js +5 -1
  87. package/dist/cli/index.js +11 -2
  88. package/dist/cli/presets.d.ts +1 -1
  89. package/dist/cli/presets.js +15 -4
  90. package/dist/cli/tui/app.js +6 -1
  91. package/dist/cli/tui/components/rich-response.d.ts +72 -0
  92. package/dist/cli/tui/components/rich-response.js +117 -0
  93. package/dist/cli/tui/executor-commands/background.js +30 -24
  94. package/dist/cli/tui/executor-commands/testing.js +3 -2
  95. package/dist/cli/tui/hooks/useDomains.d.ts +17 -0
  96. package/dist/cli/tui/hooks/useHelp.js +15 -2
  97. package/dist/cli/tui/job-manager.d.ts +4 -4
  98. package/dist/cli/tui/job-manager.js +5 -1
  99. package/dist/cli/tui/spider-tui.d.ts +63 -0
  100. package/dist/cli/tui/spider-tui.js +120 -2
  101. package/dist/cli/types.d.ts +12 -0
  102. package/dist/cli/types.js +1 -0
  103. package/dist/cli/utils/option-helpers.d.ts +10 -0
  104. package/dist/cli/utils/option-helpers.js +63 -0
  105. package/dist/cli/utils/score-color.d.ts +11 -0
  106. package/dist/cli/utils/score-color.js +11 -0
  107. package/dist/cli/utils/serp-campaign.d.ts +53 -0
  108. package/dist/cli/utils/serp-campaign.js +53 -0
  109. package/dist/cli/utils/serp-config.d.ts +26 -0
  110. package/dist/cli/utils/serp-config.js +125 -0
  111. package/dist/core/client.d.ts +15 -10
  112. package/dist/core/client.js +54 -38
  113. package/dist/index.d.ts +1 -0
  114. package/dist/index.js +1 -0
  115. package/dist/mcp/cli.js +35 -34
  116. package/dist/mcp/client.d.ts +2 -2
  117. package/dist/mcp/client.js +20 -2
  118. package/dist/mcp/contract.d.ts +1 -1
  119. package/dist/mcp/profiles.js +5 -1
  120. package/dist/mcp/prompts/index.js +8 -4
  121. package/dist/mcp/resources/index.js +46 -23
  122. package/dist/mcp/server.js +9 -6
  123. package/dist/mcp/tools/protocols.js +9 -2
  124. package/dist/mcp/tools/registry.js +13 -3
  125. package/dist/mcp/tools/seo.js +427 -2
  126. package/dist/mcp/types.d.ts +5 -1
  127. package/dist/plugins/proxy-rotator.d.ts +2 -2
  128. package/dist/plugins/proxy-rotator.js +6 -28
  129. package/dist/raffel/client.d.ts +38 -0
  130. package/dist/raffel/client.js +282 -0
  131. package/dist/raffel/index.d.ts +2 -0
  132. package/dist/raffel/index.js +2 -0
  133. package/dist/raffel/types.d.ts +40 -0
  134. package/dist/raffel/types.js +14 -0
  135. package/dist/recker.d.ts +13 -7
  136. package/dist/recker.js +58 -6
  137. package/dist/scrape/document.js +2 -2
  138. package/dist/scrape/element.js +7 -1
  139. package/dist/scrape/parser/nodes/html.js +1 -1
  140. package/dist/scrape/spider.d.ts +52 -0
  141. package/dist/scrape/spider.js +620 -38
  142. package/dist/scrape/types.d.ts +2 -0
  143. package/dist/search/google.d.ts +26 -1
  144. package/dist/search/google.js +427 -45
  145. package/dist/search/index.d.ts +1 -1
  146. package/dist/seo/analyzer.d.ts +1 -0
  147. package/dist/seo/analyzer.js +144 -1
  148. package/dist/seo/index.d.ts +1 -1
  149. package/dist/seo/keyword-campaign-analyzer.d.ts +2 -0
  150. package/dist/seo/keyword-campaign-analyzer.js +538 -0
  151. package/dist/seo/keyword-campaign-seed-advanced.d.ts +17 -0
  152. package/dist/seo/keyword-campaign-seed-advanced.js +269 -0
  153. package/dist/seo/keyword-campaign-seed-core.d.ts +29 -0
  154. package/dist/seo/keyword-campaign-seed-core.js +525 -0
  155. package/dist/seo/keyword-campaign-shared.d.ts +165 -0
  156. package/dist/seo/keyword-campaign-shared.js +59 -0
  157. package/dist/seo/keyword-campaign.d.ts +4 -107
  158. package/dist/seo/keyword-campaign.js +2 -380
  159. package/dist/seo/keywords.js +5 -22
  160. package/dist/seo/types.d.ts +19 -0
  161. package/dist/template/index.d.ts +1 -1
  162. package/dist/template/types.d.ts +0 -2
  163. package/dist/testing/index.d.ts +1 -22
  164. package/dist/testing/index.js +1 -11
  165. package/dist/transport/curl.d.ts +5 -1
  166. package/dist/transport/curl.js +207 -50
  167. package/dist/transport/undici.d.ts +4 -3
  168. package/dist/transport/undici.js +123 -49
  169. package/dist/types/index.d.ts +9 -3
  170. package/dist/utils/binary-manager.js +26 -3
  171. package/dist/utils/block-detector.d.ts +8 -0
  172. package/dist/utils/block-detector.js +542 -7
  173. package/dist/version.js +1 -1
  174. package/package.json +12 -1
  175. package/dist/testing/mock-dns-server.d.ts +0 -69
  176. package/dist/testing/mock-dns-server.js +0 -269
  177. package/dist/testing/mock-ftp-server.d.ts +0 -89
  178. package/dist/testing/mock-ftp-server.js +0 -562
  179. package/dist/testing/mock-hls-server.d.ts +0 -80
  180. package/dist/testing/mock-hls-server.js +0 -381
  181. package/dist/testing/mock-http-server.d.ts +0 -124
  182. package/dist/testing/mock-http-server.js +0 -343
  183. package/dist/testing/mock-proxy-server.d.ts +0 -108
  184. package/dist/testing/mock-proxy-server.js +0 -615
  185. package/dist/testing/mock-sse-server.d.ts +0 -76
  186. package/dist/testing/mock-sse-server.js +0 -291
  187. package/dist/testing/mock-telnet-server.d.ts +0 -59
  188. package/dist/testing/mock-telnet-server.js +0 -274
  189. package/dist/testing/mock-udp-server.d.ts +0 -43
  190. package/dist/testing/mock-udp-server.js +0 -188
  191. package/dist/testing/mock-websocket-server.d.ts +0 -76
  192. package/dist/testing/mock-websocket-server.js +0 -334
  193. package/dist/testing/mock-whois-server.d.ts +0 -56
  194. package/dist/testing/mock-whois-server.js +0 -234
  195. package/dist/testing/proxy-certs.d.ts +0 -19
  196. package/dist/testing/proxy-certs.js +0 -208
@@ -0,0 +1,269 @@
1
+ import { DEFAULT_SERP_MIN_KEYWORD_LENGTH, normalizeKeyword, tokenizeForLongTail, } from './keyword-campaign-shared.js';
2
+ function isLowValueLongTail(tokens) {
3
+ const normalized = tokens.map((value) => normalizeKeyword(value)).filter(Boolean);
4
+ if (normalized.length === 0)
5
+ return true;
6
+ if (normalized.length === 1)
7
+ return true;
8
+ if (new Set(normalized).size === 1)
9
+ return true;
10
+ if (normalized.every((token) => token.length <= 3))
11
+ return true;
12
+ if (normalized.length <= 3 && normalized.every((token) => token.length <= 4))
13
+ return true;
14
+ const shortTokenCount = normalized.filter((token) => token.length <= 2).length;
15
+ if (shortTokenCount >= Math.max(1, Math.floor(normalized.length / 2)))
16
+ return true;
17
+ return normalized.some((token) => token.match(/^\d+$/));
18
+ }
19
+ export function deduplicateKeywordPermutations(seeds) {
20
+ const deduplicated = new Map();
21
+ const pickBetterSeed = (candidate, existing) => {
22
+ if (candidate.weight !== existing.weight) {
23
+ return candidate.weight > existing.weight ? candidate : existing;
24
+ }
25
+ const candidateWords = getSeedWordCount(candidate);
26
+ const existingWords = getSeedWordCount(existing);
27
+ if (candidateWords !== existingWords) {
28
+ return candidateWords < existingWords ? candidate : existing;
29
+ }
30
+ return candidate.normalizedKeyword.localeCompare(existing.normalizedKeyword) < 0
31
+ ? candidate
32
+ : existing;
33
+ };
34
+ for (const seed of seeds) {
35
+ const key = normalizeSeedPermutationKey(seed.normalizedKeyword);
36
+ const existing = deduplicated.get(key);
37
+ if (!existing) {
38
+ deduplicated.set(key, seed);
39
+ continue;
40
+ }
41
+ deduplicated.set(key, pickBetterSeed(seed, existing));
42
+ }
43
+ return [...deduplicated.values()];
44
+ }
45
+ function normalizeSeedPermutationKey(value) {
46
+ const normalized = normalizeKeyword(value);
47
+ const tokens = normalized.split(/\s+/).map((token) => token.trim()).filter(Boolean);
48
+ if (tokens.length === 0)
49
+ return '';
50
+ if (tokens.length === 1)
51
+ return tokens[0];
52
+ return [...tokens].sort().join(' ');
53
+ }
54
+ export function getSeedWordCount(seed) {
55
+ return seed.normalizedKeyword.split(/\s+/).filter(Boolean).length;
56
+ }
57
+ export function extractSchemaOrgLongTailSeeds(report, options) {
58
+ const items = report.structuredData?.items;
59
+ if (!items?.length)
60
+ return [];
61
+ const candidates = [];
62
+ for (const item of items) {
63
+ const type = (item['@type'] ?? '').toLowerCase();
64
+ if (type === 'faqpage') {
65
+ const entities = item['mainEntity'];
66
+ if (Array.isArray(entities)) {
67
+ for (const entity of entities) {
68
+ const q = entity['name']?.trim();
69
+ const normalized = q ? normalizeKeyword(q) : '';
70
+ const words = normalized.length > 0 ? normalized.split(/\s+/).filter(Boolean).length : 0;
71
+ if (q
72
+ && q.length >= options.minKeywordLength
73
+ && words >= 2
74
+ && words <= 4) {
75
+ candidates.push({ phrase: q, weight: 3.5 });
76
+ }
77
+ }
78
+ }
79
+ }
80
+ if (type === 'howto') {
81
+ const steps = item['step'];
82
+ if (Array.isArray(steps)) {
83
+ for (const step of steps) {
84
+ const s = step;
85
+ const text = (s['name'] ?? s['text'])?.trim();
86
+ const normalized = text ? normalizeKeyword(text) : '';
87
+ const words = normalized.length > 0 ? normalized.split(/\s+/).filter(Boolean).length : 0;
88
+ if (text
89
+ && text.length >= options.minKeywordLength
90
+ && words >= 2
91
+ && words <= 4) {
92
+ candidates.push({ phrase: text, weight: 2.8 });
93
+ }
94
+ }
95
+ }
96
+ }
97
+ if (type === 'product') {
98
+ const name = item['name']?.trim();
99
+ if (name && name.length >= options.minKeywordLength)
100
+ candidates.push({ phrase: name, weight: 3.0 });
101
+ const category = item['category']?.trim();
102
+ if (category && category.length >= options.minKeywordLength)
103
+ candidates.push({ phrase: category, weight: 2.5 });
104
+ const brand = item['brand']?.['name']?.trim();
105
+ if (brand && brand.length >= options.minKeywordLength)
106
+ candidates.push({ phrase: brand, weight: 2.0 });
107
+ }
108
+ if (type === 'breadcrumblist') {
109
+ const elements = item['itemListElement'];
110
+ if (Array.isArray(elements)) {
111
+ const names = elements
112
+ .map((el) => el['name']?.trim())
113
+ .filter((n) => typeof n === 'string' && n.length >= options.minKeywordLength);
114
+ for (const name of names) {
115
+ candidates.push({ phrase: name, weight: 2.0 });
116
+ }
117
+ for (let i = 0; i < names.length - 1; i++) {
118
+ const composed = `${names[i]} ${names[i + 1]}`;
119
+ if (tokenizeForLongTail(composed).length <= 5) {
120
+ candidates.push({ phrase: composed, weight: 2.2 });
121
+ }
122
+ }
123
+ }
124
+ }
125
+ if (type === 'article' || type === 'blogposting' || type === 'newsarticle') {
126
+ const headline = item['headline']?.trim();
127
+ if (headline && headline.length >= options.minKeywordLength)
128
+ candidates.push({ phrase: headline, weight: 2.8 });
129
+ const keywords = item['keywords'];
130
+ const kwList = typeof keywords === 'string'
131
+ ? keywords.split(/[,;]/).map((k) => k.trim()).filter(Boolean)
132
+ : Array.isArray(keywords)
133
+ ? keywords.filter((k) => typeof k === 'string')
134
+ : [];
135
+ for (const kw of kwList) {
136
+ if (kw.length >= options.minKeywordLength)
137
+ candidates.push({ phrase: kw, weight: 2.5 });
138
+ }
139
+ }
140
+ }
141
+ const seen = new Set();
142
+ const seeds = [];
143
+ for (const { phrase, weight } of candidates.sort((a, b) => b.weight - a.weight)) {
144
+ const normalized = normalizeKeyword(phrase);
145
+ if (!normalized || normalized.length < options.minKeywordLength)
146
+ continue;
147
+ const words = normalized.split(/\s+/).filter(Boolean).length;
148
+ if (words > 4 || words < 2)
149
+ continue;
150
+ if (seen.has(normalized))
151
+ continue;
152
+ if (seeds.length >= options.maxPhrases)
153
+ break;
154
+ seen.add(normalized);
155
+ seeds.push({
156
+ keyword: phrase,
157
+ normalizedKeyword: normalized,
158
+ source: 'discovered',
159
+ sourcePage: options.sourcePage ? `${options.sourcePage}#schema` : undefined,
160
+ weight: Math.max(1, Math.round(weight * 100)),
161
+ });
162
+ }
163
+ return seeds;
164
+ }
165
+ export function extractHeadingPathCompositionSeeds(report, options) {
166
+ const sections = report.contentSections;
167
+ if (!sections?.length)
168
+ return [];
169
+ const phraseCounts = new Map();
170
+ const upsert = (phrase, weight) => {
171
+ const tokens = tokenizeForLongTail(phrase);
172
+ if (tokens.length < 2 || tokens.length > 4)
173
+ return;
174
+ if (isLowValueLongTail(tokens))
175
+ return;
176
+ const normalized = normalizeKeyword(tokens.join(' '));
177
+ if (!normalized || normalized.length < options.minKeywordLength)
178
+ return;
179
+ const current = phraseCounts.get(normalized);
180
+ if (!current) {
181
+ phraseCounts.set(normalized, { phrase: tokens.join(' '), normalized, weight });
182
+ return;
183
+ }
184
+ current.weight += weight * 0.5;
185
+ };
186
+ const levelWeight = (level) => level <= 1 ? 3.0 : level === 2 ? 2.2 : level === 3 ? 1.8 : 1.2;
187
+ for (const section of sections) {
188
+ const path = section.headingPath;
189
+ if (!path?.length)
190
+ continue;
191
+ if (section.linkDensity > 0.6)
192
+ continue;
193
+ const w = section.weight * levelWeight(section.headingLevel);
194
+ const pathAnchors = path.map(heading => tokenizeForLongTail(heading).filter(t => options.topKeywordSet.has(t)).slice(0, 2));
195
+ const activeAnchors = pathAnchors[pathAnchors.length - 1] ?? [];
196
+ if (pathAnchors.length >= 2) {
197
+ const parentAnchors = pathAnchors[pathAnchors.length - 2];
198
+ for (const pa of parentAnchors) {
199
+ for (const ca of activeAnchors) {
200
+ if (pa !== ca)
201
+ upsert(`${pa} ${ca}`, w);
202
+ }
203
+ if (activeAnchors.length >= 2) {
204
+ upsert(`${pa} ${activeAnchors[0]} ${activeAnchors[1]}`, w * 0.9);
205
+ }
206
+ }
207
+ }
208
+ const allAnchors = pathAnchors.flat().slice(0, 4);
209
+ if (allAnchors.length >= 2 && new Set(allAnchors).size === allAnchors.length) {
210
+ upsert(allAnchors.join(' '), w * 0.85);
211
+ }
212
+ if (section.source !== 'heading' && activeAnchors.length > 0) {
213
+ const bodyAnchors = tokenizeForLongTail(section.text)
214
+ .filter(t => options.topKeywordSet.has(t) && !activeAnchors.includes(t))
215
+ .slice(0, 5);
216
+ for (const headAnchor of activeAnchors) {
217
+ for (const bodyAnchor of bodyAnchors) {
218
+ upsert(`${headAnchor} ${bodyAnchor}`, section.weight * 1.3);
219
+ const parentAnchor = pathAnchors.length >= 2 ? pathAnchors[pathAnchors.length - 2][0] : undefined;
220
+ if (parentAnchor && parentAnchor !== headAnchor && parentAnchor !== bodyAnchor) {
221
+ upsert(`${parentAnchor} ${headAnchor} ${bodyAnchor}`, section.weight * 1.1);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ return [...phraseCounts.values()]
228
+ .sort((a, b) => b.weight - a.weight || a.normalized.localeCompare(b.normalized))
229
+ .slice(0, options.maxPhrases)
230
+ .map((entry, index) => ({
231
+ keyword: entry.phrase,
232
+ normalizedKeyword: entry.normalized,
233
+ source: 'discovered',
234
+ sourcePage: options.sourcePage ? `${options.sourcePage}#path-${index}` : undefined,
235
+ weight: Math.max(1, Math.round(entry.weight * 20)),
236
+ }));
237
+ }
238
+ export function sortSeedsByWeight(a, b) {
239
+ if (a.weight === b.weight) {
240
+ return a.normalizedKeyword.localeCompare(b.normalizedKeyword);
241
+ }
242
+ return b.weight - a.weight;
243
+ }
244
+ export function mergeKeywordSeeds(discovered, preset, minKeywordLength = DEFAULT_SERP_MIN_KEYWORD_LENGTH, minWeight = 1) {
245
+ const merged = new Map();
246
+ const upsert = (seed) => {
247
+ if (!seed.normalizedKeyword || seed.normalizedKeyword.length < minKeywordLength)
248
+ return;
249
+ if (seed.weight < minWeight)
250
+ return;
251
+ const existing = merged.get(seed.normalizedKeyword);
252
+ if (!existing) {
253
+ merged.set(seed.normalizedKeyword, seed);
254
+ return;
255
+ }
256
+ if (existing.source === 'preset' && seed.source === 'discovered') {
257
+ merged.set(seed.normalizedKeyword, seed);
258
+ return;
259
+ }
260
+ if (seed.source === existing.source && seed.weight > existing.weight) {
261
+ merged.set(seed.normalizedKeyword, seed);
262
+ }
263
+ };
264
+ for (const seed of discovered)
265
+ upsert(seed);
266
+ for (const seed of preset)
267
+ upsert(seed);
268
+ return [...merged.values()].sort(sortSeedsByWeight);
269
+ }
@@ -0,0 +1,29 @@
1
+ import type { SeoReport } from './types.js';
2
+ import type { KeywordCampaignExtractionOptions, KeywordCampaignSeed } from './keyword-campaign-shared.js';
3
+ export declare function extractKeywordCampaignSeedsFromReport(report: SeoReport, options?: KeywordCampaignExtractionOptions & {
4
+ sourcePage?: string;
5
+ }): KeywordCampaignSeed[];
6
+ export declare const SERP_BLOCK_CONFIDENCE = 0.6;
7
+ export declare const SERP_CAPTCHA_CONFIDENCE = 0.6;
8
+ export declare const DEFAULT_SERP_SEARCH_DELAY_MS = 450;
9
+ export declare const DEFAULT_SERP_SEARCH_JITTER_MS = 250;
10
+ export declare const DEFAULT_SERP_SEARCH_CAPTCHA_COOLDOWN_MS = 1200;
11
+ export declare const DEFAULT_SERP_SEARCH_RETRY_COUNT = 1;
12
+ export declare const DEFAULT_SERP_SEARCH_RETRY_DELAY_MS = 900;
13
+ export declare const DEFAULT_SERP_MAX_CONSECUTIVE_BLOCKS = 3;
14
+ export declare function isLikelyBlocked(block: {
15
+ blocked?: boolean;
16
+ confidence?: number;
17
+ } | undefined): boolean;
18
+ export declare function isLikelyCaptcha(captcha: {
19
+ detected?: boolean;
20
+ confidence?: number;
21
+ } | undefined): boolean;
22
+ export declare function sleep(ms: number): Promise<void>;
23
+ export declare function buildSerpBlockReason(block: {
24
+ reason?: string;
25
+ description?: string;
26
+ } | undefined, captcha: {
27
+ provider?: string;
28
+ description?: string;
29
+ } | undefined): string | undefined;