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
@@ -1,442 +1,14 @@
1
- import { withHandler, getNumber, getBoolean, colors, } from '../output.js';
2
- function normalizeUrl(url) {
3
- return url.startsWith('http') ? url : `https://${url}`;
4
- }
5
- function extractDomain(url) {
6
- try {
7
- return new URL(url).hostname;
8
- }
9
- catch {
10
- return url.replace(/^https?:\/\//, '').split('/')[0];
11
- }
12
- }
13
- function getScoreColor(score) {
14
- if (score >= 90)
15
- return colors.green;
16
- if (score >= 70)
17
- return colors.blue;
18
- if (score >= 50)
19
- return colors.yellow;
20
- return colors.red;
21
- }
22
- export const seoAnalyzeHandler = withHandler({ loading: true }, async (ctx, out, extCtx) => {
23
- let url = ctx.result.positional.url;
24
- if (!url) {
25
- const base = extCtx?.baseUrl?.();
26
- if (base) {
27
- url = base;
28
- }
29
- else {
30
- out.error('Usage: seo <url> or set base URL first');
31
- return;
32
- }
33
- }
34
- url = normalizeUrl(url);
35
- const domain = extractDomain(url);
36
- const jsonOutput = getBoolean(ctx.result.options.json);
37
- const outputPath = ctx.result.options.output;
38
- const outputDir = ctx.result.options.outputDir;
39
- out.log(colors.gray(`Analyzing SEO for ${url}...`));
40
- const { analyzeSeo, resolveOutputPath, writeReport, formatSeoReportJson } = await import('../../seo/index.js');
41
- const { formatReportForJson } = await import('../../seo/output.js');
42
- const finalOutputPath = resolveOutputPath({
43
- output: outputPath,
44
- outputDir,
45
- type: 'seo',
46
- domain,
47
- });
48
- const startTime = performance.now();
49
- let html;
50
- let timings = {};
51
- if (extCtx?.client) {
52
- out.log(colors.gray('Fetching page...'));
53
- const res = await extCtx.client.get(url);
54
- html = await res.text();
55
- timings = res.timings || {};
56
- }
57
- else {
58
- const res = await fetch(url);
59
- html = await res.text();
60
- }
61
- const duration = Math.round(performance.now() - startTime);
62
- out.log(colors.gray(`Analyzing ${Math.round(html.length / 1024)}KB of HTML...`));
63
- const report = await analyzeSeo(html, { baseUrl: url });
64
- report.timing = {
65
- ttfb: timings.firstByte ? Math.round(timings.firstByte) : undefined,
66
- total: timings.total ? Math.round(timings.total) : duration,
67
- dns: timings.dns ? Math.round(timings.dns) : undefined,
68
- tcp: timings.tcp ? Math.round(timings.tcp) : undefined,
69
- tls: timings.tls ? Math.round(timings.tls) : undefined,
70
- download: timings.content ? Math.round(timings.content) : undefined,
71
- };
72
- extCtx?.track.seo(domain, {
73
- score: report.score,
74
- issues: report.summary.errors + report.summary.warnings,
75
- categories: {
76
- technical: 0,
77
- content: 0,
78
- meta: 0,
79
- },
80
- });
81
- if (jsonOutput || finalOutputPath) {
82
- const jsonResult = formatReportForJson(formatSeoReportJson(report, url), url, 'seo');
83
- if (finalOutputPath) {
84
- const savedPath = await writeReport(finalOutputPath, jsonResult);
85
- out.success(`Report saved to: ${savedPath}`);
86
- }
87
- if (jsonOutput) {
88
- out.json(jsonResult);
89
- return;
90
- }
91
- }
92
- if (extCtx) {
93
- out.response({
94
- url,
95
- score: report.score,
96
- grade: report.grade,
97
- title: report.title,
98
- metaDescription: report.metaDescription,
99
- timing: report.timing,
100
- openGraph: report.openGraph,
101
- twitterCard: report.twitterCard,
102
- keywords: report.keywords,
103
- summary: {
104
- passed: report.summary.passed,
105
- warnings: report.summary.warnings,
106
- errors: report.summary.errors,
107
- infos: report.summary.infos,
108
- notApplicable: report.summary.notApplicable,
109
- suggestions: report.summary.suggestions,
110
- vitals: report.summary.vitals,
111
- topIssues: report.summary.topIssues,
112
- quickWins: report.summary.quickWins,
113
- completeness: report.summary.completeness,
114
- },
115
- technical: report.technical,
116
- content: report.content,
117
- headings: report.headings,
118
- links: report.links,
119
- structuredData: report.structuredData,
120
- checks: report.checks,
121
- }, { responseType: 'seo', time: duration });
122
- }
123
- else {
124
- out.title('SEO Analysis Results', '🔍');
125
- out.keyValue([
126
- { key: 'URL', value: url },
127
- { key: 'Score', value: `${report.score}/100 (${report.grade})`, color: report.score >= 70 ? 'green' : report.score >= 50 ? 'yellow' : 'red' },
128
- { key: 'Title', value: report.title || colors.yellow('Missing') },
129
- ]);
130
- out.blank();
131
- out.subtitle('Summary');
132
- out.checklist([
133
- { text: `Passed: ${report.summary.passed}`, checked: true },
134
- { text: `Warnings: ${report.summary.warnings}`, checked: report.summary.warnings === 0 },
135
- { text: `Errors: ${report.summary.errors}`, checked: report.summary.errors === 0 },
136
- {
137
- text: `Info: ${report.summary.infos} (N/A: ${report.summary.notApplicable || 0}, Sugestões: ${report.summary.suggestions || 0})`,
138
- checked: report.summary.infos === 0,
139
- },
140
- ]);
141
- out.blank();
142
- if (report.summary.topIssues?.length > 0) {
143
- out.subtitle('Top Issues');
144
- for (const issue of report.summary.topIssues.slice(0, 5)) {
145
- out.status(issue.severity === 'error' ? 'error' : 'warning', issue.message);
146
- }
147
- out.blank();
148
- }
149
- if (report.timing.total) {
150
- out.log(colors.gray(`Analyzed in ${report.timing.total}ms`));
151
- }
152
- }
153
- });
154
- export const spiderHandler = withHandler({ loading: true }, async (ctx, out, extCtx) => {
155
- let url = ctx.result.positional.url;
156
- if (!url) {
157
- out.error('URL is required');
158
- return;
159
- }
160
- url = normalizeUrl(url);
161
- const domain = extractDomain(url);
162
- const depth = getNumber(ctx.result.options.depth, 5);
163
- const limit = getNumber(ctx.result.options.limit, 100);
164
- const concurrency = getNumber(ctx.result.options.concurrency, 5);
165
- const seoEnabled = getBoolean(ctx.result.options.seo);
166
- const jsonOutput = getBoolean(ctx.result.options.json);
167
- const jsonlEnabled = getBoolean(ctx.result.options.jsonl);
168
- const outputPath = ctx.result.options.output;
169
- const outputDir = ctx.result.options.outputDir;
170
- const focus = ctx.result.options.focus;
171
- const extract = ctx.result.options.extract;
172
- const include = ctx.result.options.include;
173
- const exclude = ctx.result.options.exclude;
174
- out.log(colors.gray(`Starting spider on ${url}... (depth=${depth}, limit=${limit})`));
175
- const { runSpiderWithEvents } = await import('../commands/spider-runner.js');
176
- const result = await runSpiderWithEvents(url, {
177
- depth,
178
- limit,
179
- concurrency,
180
- seo: seoEnabled,
181
- focus: focus,
182
- json: true,
183
- extract,
184
- include,
185
- exclude,
186
- });
187
- extCtx?.track.spider(domain, {
188
- pagesFound: result.pagesVisited,
189
- internalLinks: result.internalLinks,
190
- externalLinks: result.externalLinks,
191
- images: result.images,
192
- scripts: result.scripts,
193
- stylesheets: result.stylesheets,
194
- errors: result.errors,
195
- });
196
- const { resolveOutputPath, writeReport, formatReportForJson } = await import('../../seo/output.js');
197
- const resolvedOutputPath = resolveOutputPath({
198
- output: outputPath,
199
- outputDir,
200
- type: seoEnabled ? 'seo-spider' : 'spider',
201
- domain,
202
- });
203
- if (resolvedOutputPath) {
204
- if (jsonlEnabled) {
205
- const { JsonlWriter } = await import('../commands/spider-runner.js');
206
- const writer = new JsonlWriter({ output: resolvedOutputPath });
207
- await writer.init();
208
- writer.write({
209
- type: 'start',
210
- url,
211
- startedAt: new Date().toISOString(),
212
- config: { depth, limit, seo: seoEnabled },
213
- });
214
- for (const page of result.pages) {
215
- writer.write({
216
- type: 'page',
217
- url: page.url,
218
- status: page.status,
219
- title: page.title,
220
- depth: page.depth,
221
- links: page.links,
222
- duration: page.duration,
223
- seoScore: page.seoScore,
224
- seoGrade: page.seoGrade,
225
- seoErrors: page.seoErrors,
226
- seoWarnings: page.seoWarnings,
227
- extracted: page.extracted,
228
- });
229
- }
230
- writer.write({
231
- type: 'complete',
232
- url: result.url,
233
- completedAt: new Date().toISOString(),
234
- pagesVisited: result.pagesVisited,
235
- duration: result.duration,
236
- errors: result.errors,
237
- internalLinks: result.internalLinks,
238
- externalLinks: result.externalLinks,
239
- seo: result.seo,
240
- extraction: result.extraction,
241
- });
242
- await writer.close();
243
- out.success(`Report saved to: ${writer.getPath() || resolvedOutputPath}`);
244
- }
245
- else {
246
- const jsonResult = formatReportForJson(result, url, seoEnabled ? 'seo-spider' : 'spider');
247
- const savedPath = await writeReport(resolvedOutputPath, jsonResult);
248
- out.success(`Report saved to: ${savedPath}`);
249
- }
250
- }
251
- const summary = {
252
- url: result.url,
253
- pages: result.pagesVisited,
254
- duration: `${(result.duration / 1000).toFixed(1)}s`,
255
- errors: result.errors,
256
- links: {
257
- internal: result.internalLinks,
258
- external: result.externalLinks,
259
- },
260
- assets: {
261
- images: result.images,
262
- scripts: result.scripts,
263
- stylesheets: result.stylesheets,
264
- },
265
- topPages: result.pages.slice(0, 10).map((p) => ({
266
- url: p.url.replace(url, ''),
267
- status: p.status,
268
- title: p.title?.slice(0, 40),
269
- seoScore: p.seoScore,
270
- seoGrade: p.seoGrade,
271
- })),
272
- seo: result.seo ? {
273
- avgScore: result.seo.avgScore,
274
- pagesWithErrors: result.seo.pagesWithErrors,
275
- pagesWithWarnings: result.seo.pagesWithWarnings,
276
- duplicateTitles: result.seo.duplicateTitles,
277
- duplicateDescriptions: result.seo.duplicateDescriptions,
278
- } : undefined,
279
- extraction: result.extraction ? {
280
- schema: result.extraction.schema,
281
- totalItems: result.extraction.totalItems,
282
- pagesWithData: Object.keys(result.extraction.byPage).length,
283
- } : undefined,
284
- };
285
- if (extCtx) {
286
- out.response(summary, {
287
- responseType: seoEnabled ? 'seo-spider' : 'spider',
288
- time: result.duration,
289
- });
290
- }
291
- else {
292
- out.title('Spider Results', '🕷️');
293
- out.keyValue({
294
- URL: result.url,
295
- Pages: result.pagesVisited,
296
- Duration: `${(result.duration / 1000).toFixed(1)}s`,
297
- Errors: result.errors,
298
- });
299
- out.blank();
300
- out.keyValue({
301
- 'Internal Links': result.internalLinks,
302
- 'External Links': result.externalLinks,
303
- Images: result.images,
304
- Scripts: result.scripts,
305
- Stylesheets: result.stylesheets,
306
- });
307
- out.blank();
308
- if (jsonOutput) {
309
- out.json(summary);
310
- }
311
- }
312
- });
313
- export const robotsHandler = withHandler({ loading: true }, async (ctx, out, extCtx) => {
314
- let url = ctx.result.positional.url;
315
- if (!url) {
316
- const base = extCtx?.baseUrl?.();
317
- if (base) {
318
- url = base;
319
- }
320
- else {
321
- out.error('Usage: robots <url> or set base URL first');
322
- return;
323
- }
324
- }
325
- url = normalizeUrl(url);
326
- const robotsUrl = new URL('/robots.txt', url).toString();
327
- let response;
328
- if (extCtx?.client) {
329
- response = await extCtx.client.get(robotsUrl);
330
- }
331
- else {
332
- response = await fetch(robotsUrl);
333
- }
334
- const text = await response.text();
335
- if (response.status === 404) {
336
- out.warn(`No robots.txt found at ${robotsUrl}`);
337
- return;
338
- }
339
- const lines = text.split('\n');
340
- const directives = [];
341
- let currentAgent = '*';
342
- for (const line of lines) {
343
- const trimmed = line.trim();
344
- if (trimmed.toLowerCase().startsWith('user-agent:')) {
345
- currentAgent = trimmed.slice(11).trim();
346
- }
347
- else if (trimmed.toLowerCase().startsWith('disallow:')) {
348
- directives.push({ agent: currentAgent, type: 'disallow', path: trimmed.slice(9).trim() });
349
- }
350
- else if (trimmed.toLowerCase().startsWith('allow:')) {
351
- directives.push({ agent: currentAgent, type: 'allow', path: trimmed.slice(6).trim() });
352
- }
353
- else if (trimmed.toLowerCase().startsWith('sitemap:')) {
354
- directives.push({ type: 'sitemap', url: trimmed.slice(8).trim() });
355
- }
356
- }
357
- const result = {
358
- url: robotsUrl,
359
- rules: directives.length,
360
- sitemaps: directives.filter(d => d.type === 'sitemap').map(d => d.url),
361
- disallowed: directives.filter(d => d.type === 'disallow').slice(0, 10).map(d => d.path),
362
- };
363
- if (extCtx) {
364
- out.response(result, { responseType: 'robots' });
365
- }
366
- else {
367
- out.title('robots.txt Analysis', '🤖');
368
- out.keyValue({
369
- URL: robotsUrl,
370
- Rules: directives.length,
371
- });
372
- out.blank();
373
- if (result.sitemaps.length > 0) {
374
- out.subtitle('Sitemaps');
375
- out.list(result.sitemaps);
376
- out.blank();
377
- }
378
- if (result.disallowed.length > 0) {
379
- out.subtitle('Disallowed Paths');
380
- for (const p of result.disallowed) {
381
- out.status('error', p);
382
- }
383
- out.blank();
384
- }
385
- }
386
- });
387
- export const sitemapHandler = withHandler({ loading: true }, async (ctx, out, extCtx) => {
388
- let url = ctx.result.positional.url;
389
- if (!url) {
390
- const base = extCtx?.baseUrl?.();
391
- if (base) {
392
- url = base;
393
- }
394
- else {
395
- out.error('Usage: sitemap <url> or set base URL first');
396
- return;
397
- }
398
- }
399
- url = normalizeUrl(url);
400
- const sitemapUrl = url.endsWith('.xml') ? url : new URL('/sitemap.xml', url).toString();
401
- let response;
402
- if (extCtx?.client) {
403
- response = await extCtx.client.get(sitemapUrl);
404
- }
405
- else {
406
- response = await fetch(sitemapUrl);
407
- }
408
- const text = await response.text();
409
- if (response.status === 404) {
410
- out.warn(`No sitemap found at ${sitemapUrl}`);
411
- return;
412
- }
413
- const urlMatches = text.match(/<loc>([^<]+)<\/loc>/g) || [];
414
- const urls = urlMatches.map(m => m.replace(/<\/?loc>/g, ''));
415
- const result = {
416
- url: sitemapUrl,
417
- totalUrls: urls.length,
418
- sample: urls.slice(0, 10),
419
- };
420
- if (extCtx) {
421
- out.response(result, { responseType: 'sitemap' });
422
- }
423
- else {
424
- out.title('Sitemap Analysis', '🗺️');
425
- out.keyValue({
426
- URL: sitemapUrl,
427
- 'Total URLs': urls.length,
428
- });
429
- out.blank();
430
- if (urls.length > 0) {
431
- out.subtitle('Sample URLs');
432
- out.list(urls.slice(0, 10));
433
- if (urls.length > 10) {
434
- out.log(colors.gray(` ... and ${urls.length - 10} more`));
435
- }
436
- out.blank();
437
- }
438
- }
439
- });
1
+ import { parseSpiderSerpConfig } from '../utils/serp-config.js';
2
+ export { normalizeUrl, extractDomain, buildSerpCampaignSeeds, runCrawlerSerpCampaign, formatSerpSummaryRows, getSerpComparisonRows, getSerpCompetitorRows, formatKeywordList, } from './seo-serp.js';
3
+ export { seoAnalyzeHandler } from './seo-analyze.js';
4
+ export { spiderHandler } from './seo-spider.js';
5
+ export { robotsHandler } from './seo-robots.js';
6
+ export { sitemapHandler } from './seo-sitemap.js';
7
+ export { parseSpiderSerpConfig as parseSeoCrawlerSerpConfig };
8
+ import { seoAnalyzeHandler } from './seo-analyze.js';
9
+ import { spiderHandler } from './seo-spider.js';
10
+ import { robotsHandler } from './seo-robots.js';
11
+ import { sitemapHandler } from './seo-sitemap.js';
440
12
  export const seoCommands = {
441
13
  description: 'SEO analysis and web crawling tools',
442
14
  category: 'analysis',
@@ -471,12 +43,89 @@ export const seoCommands = {
471
43
  category: {
472
44
  type: 'string',
473
45
  description: 'Filter by category (performance, security, content, etc.)'
474
- }
46
+ },
47
+ serp: {
48
+ type: 'boolean',
49
+ description: 'Run SERP checks for extracted top keywords'
50
+ },
51
+ 'serp-top-keywords': {
52
+ default: 5,
53
+ type: 'number',
54
+ description: 'Top keywords per page used to seed SERP'
55
+ },
56
+ 'serp-query-limit': {
57
+ default: 10,
58
+ type: 'number',
59
+ description: 'Number of keywords to query on SERP'
60
+ },
61
+ 'serp-results-per-query': {
62
+ default: 10,
63
+ type: 'number',
64
+ description: 'SERP results fetched per query'
65
+ },
66
+ 'serp-concurrency': {
67
+ default: 1,
68
+ type: 'number',
69
+ description: 'Number of SERP queries to execute in parallel'
70
+ },
71
+ 'serp-delay-ms': {
72
+ default: 1200,
73
+ type: 'number',
74
+ description: 'Delay in ms between SERP queries'
75
+ },
76
+ 'serp-delay-jitter-ms': {
77
+ default: 450,
78
+ type: 'number',
79
+ description: 'Random delay jitter in ms between SERP queries'
80
+ },
81
+ 'serp-max-consecutive-blocks': {
82
+ default: 3,
83
+ type: 'number',
84
+ description: 'Stop campaign after this many blocked/captcha responses in a row'
85
+ },
86
+ 'serp-captcha-cooldown-ms': {
87
+ default: 2400,
88
+ type: 'number',
89
+ description: 'Cooldown in ms after captcha before continuing SERP'
90
+ },
91
+ 'serp-retry-count': {
92
+ default: 1,
93
+ type: 'number',
94
+ description: 'Retry count per SERP query'
95
+ },
96
+ 'serp-retry-delay-ms': {
97
+ default: 1200,
98
+ type: 'number',
99
+ description: 'Base retry delay in ms for SERP queries'
100
+ },
101
+ 'serp-gl': {
102
+ type: 'string',
103
+ description: 'Google gl parameter'
104
+ },
105
+ 'serp-hl': {
106
+ type: 'string',
107
+ description: 'Google hl parameter'
108
+ },
109
+ 'serp-transport': {
110
+ type: 'string',
111
+ default: 'curl',
112
+ description: 'Search transport (auto | undici | curl)'
113
+ },
114
+ 'serp-timeout': {
115
+ type: 'number',
116
+ description: 'SERP request timeout in ms'
117
+ },
118
+ 'serp-country': {
119
+ type: 'string',
120
+ description: 'Country code for SERP analysis'
121
+ },
475
122
  },
476
123
  examples: [
477
124
  { cmd: 'rek seo analyze google.com', desc: 'Analyze Google homepage' },
478
125
  { cmd: 'rek seo analyze example.com --all', desc: 'Show all checks' },
479
- { cmd: 'rek seo analyze example.com -o report.json', desc: 'Save to file' }
126
+ { cmd: 'rek seo analyze example.com -o report.json', desc: 'Save to file' },
127
+ { cmd: 'rek seo analyze google.com --serp', desc: 'Run SERP checks for this page' },
128
+ { cmd: 'rek seo analyze google.com --serp --serp-top-keywords 12 --serp-query-limit 10', desc: 'Run SERP with custom limits' },
480
129
  ],
481
130
  handler: seoAnalyzeHandler
482
131
  },
@@ -550,13 +199,216 @@ export const seoCommands = {
550
199
  focus: {
551
200
  type: 'string',
552
201
  description: 'Focus mode: links, duplicates, security, ai'
202
+ },
203
+ serp: {
204
+ type: 'boolean',
205
+ description: 'Run SERP checks for extracted top keywords'
206
+ },
207
+ 'serp-country': {
208
+ type: 'string',
209
+ description: 'Country code for SERP analysis'
210
+ },
211
+ 'serp-region': {
212
+ type: 'string',
213
+ description: 'Region code for SERP'
214
+ },
215
+ 'serp-gl': {
216
+ type: 'string',
217
+ description: 'Google gl parameter'
218
+ },
219
+ 'serp-hl': {
220
+ type: 'string',
221
+ description: 'Google hl parameter'
222
+ },
223
+ 'serp-transport': {
224
+ type: 'string',
225
+ default: 'curl',
226
+ description: 'Search transport (auto | undici | curl)'
227
+ },
228
+ 'serp-timeout': {
229
+ type: 'number',
230
+ description: 'SERP request timeout in ms'
231
+ },
232
+ 'serp-top-keywords': {
233
+ default: 5,
234
+ type: 'number',
235
+ description: 'Top keywords per page used to seed SERP'
236
+ },
237
+ 'serp-query-limit': {
238
+ default: 10,
239
+ type: 'number',
240
+ description: 'Number of keywords to query on SERP'
241
+ },
242
+ 'serp-results-per-query': {
243
+ default: 10,
244
+ type: 'number',
245
+ description: 'SERP results fetched per query'
246
+ },
247
+ 'serp-concurrency': {
248
+ default: 1,
249
+ type: 'number',
250
+ description: 'Number of SERP queries to execute in parallel'
251
+ },
252
+ 'serp-delay-ms': {
253
+ default: 1200,
254
+ type: 'number',
255
+ description: 'Delay in ms between SERP queries'
256
+ },
257
+ 'serp-delay-jitter-ms': {
258
+ default: 450,
259
+ type: 'number',
260
+ description: 'Random delay jitter in ms between SERP queries'
261
+ },
262
+ 'serp-max-consecutive-blocks': {
263
+ default: 3,
264
+ type: 'number',
265
+ description: 'Stop campaign after this many blocked/captcha responses in a row'
266
+ },
267
+ 'serp-captcha-cooldown-ms': {
268
+ default: 2400,
269
+ type: 'number',
270
+ description: 'Cooldown in ms after captcha before continuing SERP'
271
+ },
272
+ 'serp-retry-count': {
273
+ default: 1,
274
+ type: 'number',
275
+ description: 'Retry count per SERP query'
276
+ },
277
+ 'serp-retry-delay-ms': {
278
+ default: 1200,
279
+ type: 'number',
280
+ description: 'Base retry delay in ms for SERP queries'
281
+ },
282
+ 'serp-safe': {
283
+ type: 'string',
284
+ description: 'Safe search level (active | images | strict)'
285
+ },
286
+ 'serp-lr': {
287
+ type: 'string',
288
+ description: 'Google language restrict parameter'
289
+ },
290
+ 'serp-cr': {
291
+ type: 'string',
292
+ description: 'Google country restrict parameter'
293
+ },
294
+ 'serp-tbs': {
295
+ type: 'string',
296
+ description: 'Google result filter params'
297
+ },
298
+ 'serp-tbm': {
299
+ type: 'string',
300
+ description: 'Google search type (shop, news, images, etc.)'
301
+ },
302
+ 'serp-as-q': {
303
+ type: 'string',
304
+ description: 'SERP as_q parameter'
305
+ },
306
+ 'serp-as-epq': {
307
+ type: 'string',
308
+ description: 'SERP as_epq parameter'
309
+ },
310
+ 'serp-as-oq': {
311
+ type: 'string',
312
+ description: 'SERP as_oq parameter'
313
+ },
314
+ 'serp-as-eq': {
315
+ type: 'string',
316
+ description: 'SERP as_eq parameter'
317
+ },
318
+ 'serp-as-sitesearch': {
319
+ type: 'string',
320
+ description: 'SERP as_sitesearch parameter'
321
+ },
322
+ 'serp-as-filetype': {
323
+ type: 'string',
324
+ description: 'SERP as_filetype parameter'
325
+ },
326
+ 'serp-as-rights': {
327
+ type: 'string',
328
+ description: 'SERP as_rights parameter'
329
+ },
330
+ 'serp-as-nlo': {
331
+ type: 'string',
332
+ description: 'SERP as_nlo parameter'
333
+ },
334
+ 'serp-as-nhi': {
335
+ type: 'string',
336
+ description: 'SERP as_nhi parameter'
337
+ },
338
+ 'serp-extra': {
339
+ type: 'string',
340
+ description: 'Extra query params key=value,key2=value2'
341
+ },
342
+ transport: {
343
+ type: 'string',
344
+ description: 'Crawler transport (auto | undici | curl)',
345
+ default: 'auto',
346
+ },
347
+ 'prefer-curl-first': {
348
+ type: 'boolean',
349
+ description: 'Prefer curl-impersonate first in auto mode',
350
+ default: true,
351
+ },
352
+ timeout: {
353
+ type: 'number',
354
+ description: 'Request timeout in ms',
355
+ default: 10000,
356
+ },
357
+ delay: {
358
+ type: 'number',
359
+ description: 'Delay between requests in ms',
360
+ default: 100,
361
+ },
362
+ 'max-retry-attempts': {
363
+ type: 'number',
364
+ description: 'Max retry attempts per request',
365
+ default: 3,
366
+ },
367
+ 'base-retry-delay-ms': {
368
+ type: 'number',
369
+ description: 'Base retry delay in ms',
370
+ default: 1000,
371
+ },
372
+ 'max-retry-delay-ms': {
373
+ type: 'number',
374
+ description: 'Maximum retry delay in ms',
375
+ default: 12000,
376
+ },
377
+ 'retry-backoff-multiplier': {
378
+ type: 'number',
379
+ description: 'Retry backoff multiplier',
380
+ default: 2,
381
+ },
382
+ 'retry-jitter-ms': {
383
+ type: 'number',
384
+ description: 'Retry jitter in ms',
385
+ default: 250,
386
+ },
387
+ 'max-domain-block-strikes': {
388
+ type: 'number',
389
+ description: 'Force curl after this many block signals',
390
+ default: 2,
391
+ },
392
+ 'rotate-user-agent': {
393
+ type: 'boolean',
394
+ description: 'Rotate user-agent per request',
395
+ default: true,
396
+ },
397
+ 'randomize-headers': {
398
+ type: 'boolean',
399
+ description: 'Randomize request headers',
400
+ default: true,
553
401
  }
554
402
  },
555
403
  examples: [
556
404
  { cmd: 'rek seo spider example.com', desc: 'Basic crawl' },
557
405
  { cmd: 'rek seo spider example.com -d 3 -l 50', desc: 'Limited crawl' },
558
406
  { cmd: 'rek seo spider example.com --seo -o report.json', desc: 'SEO crawl with output' },
559
- { cmd: 'rek seo spider example.com -E h1 -E h2', desc: 'Extract headings' }
407
+ { cmd: 'rek seo spider example.com -E h1 -E h2', desc: 'Extract headings' },
408
+ {
409
+ cmd: 'rek seo spider example.com --seo --serp --serp-top-keywords 5 --serp-query-limit 10 --serp-results-per-query 10',
410
+ desc: 'Run SERP on top extracted keywords'
411
+ },
560
412
  ],
561
413
  handler: spiderHandler
562
414
  },