recker 1.0.42 → 1.0.43-next.7a08602

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 (458) hide show
  1. package/dist/bin/recker-linux-x64 +0 -0
  2. package/dist/bin/recker-macos-x64 +0 -0
  3. package/dist/bin/recker-win-x64.exe +0 -0
  4. package/dist/bin/rek.cjs +93958 -0
  5. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  6. package/dist/browser/ai/adaptive-timeout.js +208 -0
  7. package/dist/browser/ai/client.d.ts +22 -0
  8. package/dist/browser/ai/client.js +294 -0
  9. package/dist/browser/ai/index.d.ts +14 -0
  10. package/dist/browser/ai/index.js +11 -0
  11. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  12. package/dist/browser/ai/providers/anthropic.js +370 -0
  13. package/dist/browser/ai/providers/base.d.ts +48 -0
  14. package/dist/browser/ai/providers/base.js +150 -0
  15. package/dist/browser/ai/providers/google.d.ts +59 -0
  16. package/dist/browser/ai/providers/google.js +305 -0
  17. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  18. package/dist/browser/ai/providers/ollama.js +240 -0
  19. package/dist/browser/ai/providers/openai.d.ts +64 -0
  20. package/dist/browser/ai/providers/openai.js +298 -0
  21. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  22. package/dist/browser/ai/rate-limiter.js +215 -0
  23. package/dist/browser/ai/vector/index.d.ts +2 -0
  24. package/dist/browser/ai/vector/index.js +2 -0
  25. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  26. package/dist/browser/ai/vector/similarity.js +27 -0
  27. package/dist/browser/ai/vector/store.d.ts +27 -0
  28. package/dist/browser/ai/vector/store.js +82 -0
  29. package/dist/browser/browser/cache.d.ts +2 -40
  30. package/dist/browser/browser/cache.js +2 -199
  31. package/dist/browser/browser/index.d.ts +8 -0
  32. package/dist/browser/browser/index.js +8 -0
  33. package/dist/browser/browser/recker.d.ts +8 -1
  34. package/dist/browser/browser/recker.js +8 -2
  35. package/dist/browser/cache/indexed-db.d.ts +10 -0
  36. package/dist/browser/cache/indexed-db.js +88 -0
  37. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  38. package/dist/browser/cache/service-worker-cache.js +103 -0
  39. package/dist/browser/cache.d.ts +2 -40
  40. package/dist/browser/cache.js +2 -199
  41. package/dist/browser/constants/user-agents.d.ts +7 -0
  42. package/dist/browser/constants/user-agents.js +7 -0
  43. package/dist/browser/core/client.d.ts +2 -0
  44. package/dist/browser/core/client.js +19 -1
  45. package/dist/browser/index.d.ts +8 -0
  46. package/dist/browser/index.js +8 -0
  47. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  48. package/dist/browser/plugins/har-recorder.js +120 -0
  49. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  50. package/dist/browser/plugins/network-simulation.js +13 -0
  51. package/dist/browser/presets/android.d.ts +2 -0
  52. package/dist/browser/presets/android.js +16 -0
  53. package/dist/browser/presets/anthropic.d.ts +8 -0
  54. package/dist/browser/presets/anthropic.js +27 -0
  55. package/dist/browser/presets/aws.d.ts +19 -0
  56. package/dist/browser/presets/aws.js +68 -0
  57. package/dist/browser/presets/azure-openai.d.ts +10 -0
  58. package/dist/browser/presets/azure-openai.js +35 -0
  59. package/dist/browser/presets/azure.d.ts +41 -0
  60. package/dist/browser/presets/azure.js +104 -0
  61. package/dist/browser/presets/chaturbate.d.ts +2 -0
  62. package/dist/browser/presets/chaturbate.js +17 -0
  63. package/dist/browser/presets/cloudflare.d.ts +12 -0
  64. package/dist/browser/presets/cloudflare.js +39 -0
  65. package/dist/browser/presets/cohere.d.ts +7 -0
  66. package/dist/browser/presets/cohere.js +22 -0
  67. package/dist/browser/presets/deepseek.d.ts +7 -0
  68. package/dist/browser/presets/deepseek.js +22 -0
  69. package/dist/browser/presets/digitalocean.d.ts +5 -0
  70. package/dist/browser/presets/digitalocean.js +16 -0
  71. package/dist/browser/presets/discord.d.ts +6 -0
  72. package/dist/browser/presets/discord.js +17 -0
  73. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  74. package/dist/browser/presets/elevenlabs.js +20 -0
  75. package/dist/browser/presets/enhancers.d.ts +20 -0
  76. package/dist/browser/presets/enhancers.js +85 -0
  77. package/dist/browser/presets/fireworks.d.ts +7 -0
  78. package/dist/browser/presets/fireworks.js +22 -0
  79. package/dist/browser/presets/gcp.d.ts +34 -0
  80. package/dist/browser/presets/gcp.js +91 -0
  81. package/dist/browser/presets/gemini.d.ts +7 -0
  82. package/dist/browser/presets/gemini.js +23 -0
  83. package/dist/browser/presets/github.d.ts +6 -0
  84. package/dist/browser/presets/github.js +17 -0
  85. package/dist/browser/presets/gitlab.d.ts +6 -0
  86. package/dist/browser/presets/gitlab.js +16 -0
  87. package/dist/browser/presets/groq.d.ts +7 -0
  88. package/dist/browser/presets/groq.js +22 -0
  89. package/dist/browser/presets/hubspot.d.ts +9 -0
  90. package/dist/browser/presets/hubspot.js +28 -0
  91. package/dist/browser/presets/huggingface.d.ts +7 -0
  92. package/dist/browser/presets/huggingface.js +23 -0
  93. package/dist/browser/presets/index.d.ts +47 -0
  94. package/dist/browser/presets/index.js +47 -0
  95. package/dist/browser/presets/ios.d.ts +2 -0
  96. package/dist/browser/presets/ios.js +13 -0
  97. package/dist/browser/presets/linear.d.ts +5 -0
  98. package/dist/browser/presets/linear.js +16 -0
  99. package/dist/browser/presets/mailgun.d.ts +7 -0
  100. package/dist/browser/presets/mailgun.js +20 -0
  101. package/dist/browser/presets/meta.d.ts +10 -0
  102. package/dist/browser/presets/meta.js +33 -0
  103. package/dist/browser/presets/mistral.d.ts +7 -0
  104. package/dist/browser/presets/mistral.js +22 -0
  105. package/dist/browser/presets/notion.d.ts +6 -0
  106. package/dist/browser/presets/notion.js +17 -0
  107. package/dist/browser/presets/openai.d.ts +9 -0
  108. package/dist/browser/presets/openai.js +30 -0
  109. package/dist/browser/presets/oracle.d.ts +19 -0
  110. package/dist/browser/presets/oracle.js +117 -0
  111. package/dist/browser/presets/perplexity.d.ts +7 -0
  112. package/dist/browser/presets/perplexity.js +22 -0
  113. package/dist/browser/presets/pinecone.d.ts +8 -0
  114. package/dist/browser/presets/pinecone.js +42 -0
  115. package/dist/browser/presets/registry.d.ts +23 -0
  116. package/dist/browser/presets/registry.js +519 -0
  117. package/dist/browser/presets/replicate.d.ts +7 -0
  118. package/dist/browser/presets/replicate.js +23 -0
  119. package/dist/browser/presets/sendgrid.d.ts +6 -0
  120. package/dist/browser/presets/sendgrid.js +20 -0
  121. package/dist/browser/presets/sentry.d.ts +11 -0
  122. package/dist/browser/presets/sentry.js +48 -0
  123. package/dist/browser/presets/sinch.d.ts +9 -0
  124. package/dist/browser/presets/sinch.js +39 -0
  125. package/dist/browser/presets/slack.d.ts +5 -0
  126. package/dist/browser/presets/slack.js +16 -0
  127. package/dist/browser/presets/square.d.ts +10 -0
  128. package/dist/browser/presets/square.js +33 -0
  129. package/dist/browser/presets/stripe.d.ts +7 -0
  130. package/dist/browser/presets/stripe.js +23 -0
  131. package/dist/browser/presets/supabase.d.ts +6 -0
  132. package/dist/browser/presets/supabase.js +18 -0
  133. package/dist/browser/presets/tiktok.d.ts +10 -0
  134. package/dist/browser/presets/tiktok.js +38 -0
  135. package/dist/browser/presets/together.d.ts +7 -0
  136. package/dist/browser/presets/together.js +22 -0
  137. package/dist/browser/presets/twilio.d.ts +6 -0
  138. package/dist/browser/presets/twilio.js +17 -0
  139. package/dist/browser/presets/vercel.d.ts +6 -0
  140. package/dist/browser/presets/vercel.js +23 -0
  141. package/dist/browser/presets/vultr.d.ts +5 -0
  142. package/dist/browser/presets/vultr.js +16 -0
  143. package/dist/browser/presets/xai.d.ts +8 -0
  144. package/dist/browser/presets/xai.js +23 -0
  145. package/dist/browser/presets/youtube.d.ts +5 -0
  146. package/dist/browser/presets/youtube.js +20 -0
  147. package/dist/browser/recker.d.ts +8 -1
  148. package/dist/browser/recker.js +8 -2
  149. package/dist/browser/scrape/document.d.ts +5 -4
  150. package/dist/browser/scrape/document.js +89 -76
  151. package/dist/browser/scrape/element.d.ts +10 -8
  152. package/dist/browser/scrape/element.js +295 -81
  153. package/dist/browser/scrape/extractors.d.ts +11 -11
  154. package/dist/browser/scrape/extractors.js +145 -113
  155. package/dist/browser/scrape/parser/back.d.ts +1 -0
  156. package/dist/browser/scrape/parser/back.js +3 -0
  157. package/dist/browser/scrape/parser/index.d.ts +20 -0
  158. package/dist/browser/scrape/parser/index.js +19 -0
  159. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  160. package/dist/browser/scrape/parser/matcher.js +99 -0
  161. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  162. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  163. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  164. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  165. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  166. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  167. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  168. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  169. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  170. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  171. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  172. package/dist/browser/scrape/parser/parse.js +1 -0
  173. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  174. package/dist/browser/scrape/parser/valid.js +5 -0
  175. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  176. package/dist/browser/scrape/parser/void-tag.js +43 -0
  177. package/dist/browser/scrape/types.d.ts +7 -0
  178. package/dist/browser/seo/analyzer.d.ts +59 -0
  179. package/dist/browser/seo/analyzer.js +1399 -0
  180. package/dist/browser/seo/keywords.d.ts +16 -0
  181. package/dist/browser/seo/keywords.js +55 -0
  182. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  183. package/dist/browser/seo/rules/accessibility.js +722 -0
  184. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  185. package/dist/browser/seo/rules/ai-search.js +436 -0
  186. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  187. package/dist/browser/seo/rules/analytics.js +306 -0
  188. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  189. package/dist/browser/seo/rules/best-practices.js +195 -0
  190. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  191. package/dist/browser/seo/rules/canonical.js +258 -0
  192. package/dist/browser/seo/rules/content.d.ts +2 -0
  193. package/dist/browser/seo/rules/content.js +424 -0
  194. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  195. package/dist/browser/seo/rules/crawl.js +435 -0
  196. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  197. package/dist/browser/seo/rules/cwv.js +248 -0
  198. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  199. package/dist/browser/seo/rules/ecommerce.js +283 -0
  200. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  201. package/dist/browser/seo/rules/i18n.js +277 -0
  202. package/dist/browser/seo/rules/images.d.ts +2 -0
  203. package/dist/browser/seo/rules/images.js +235 -0
  204. package/dist/browser/seo/rules/index.d.ts +52 -0
  205. package/dist/browser/seo/rules/index.js +159 -0
  206. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  207. package/dist/browser/seo/rules/internal-linking.js +394 -0
  208. package/dist/browser/seo/rules/links.d.ts +2 -0
  209. package/dist/browser/seo/rules/links.js +490 -0
  210. package/dist/browser/seo/rules/local.d.ts +2 -0
  211. package/dist/browser/seo/rules/local.js +289 -0
  212. package/dist/browser/seo/rules/meta.d.ts +2 -0
  213. package/dist/browser/seo/rules/meta.js +646 -0
  214. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  215. package/dist/browser/seo/rules/mobile.js +161 -0
  216. package/dist/browser/seo/rules/performance.d.ts +2 -0
  217. package/dist/browser/seo/rules/performance.js +625 -0
  218. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  219. package/dist/browser/seo/rules/pwa.js +299 -0
  220. package/dist/browser/seo/rules/readability.d.ts +2 -0
  221. package/dist/browser/seo/rules/readability.js +264 -0
  222. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  223. package/dist/browser/seo/rules/redirects.js +199 -0
  224. package/dist/browser/seo/rules/resources.d.ts +2 -0
  225. package/dist/browser/seo/rules/resources.js +390 -0
  226. package/dist/browser/seo/rules/schema.d.ts +2 -0
  227. package/dist/browser/seo/rules/schema.js +379 -0
  228. package/dist/browser/seo/rules/security.d.ts +2 -0
  229. package/dist/browser/seo/rules/security.js +877 -0
  230. package/dist/browser/seo/rules/social.d.ts +2 -0
  231. package/dist/browser/seo/rules/social.js +603 -0
  232. package/dist/browser/seo/rules/structural.d.ts +2 -0
  233. package/dist/browser/seo/rules/structural.js +179 -0
  234. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  235. package/dist/browser/seo/rules/technical-advanced.js +288 -0
  236. package/dist/browser/seo/rules/technical.d.ts +2 -0
  237. package/dist/browser/seo/rules/technical.js +480 -0
  238. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  239. package/dist/browser/seo/rules/thresholds.js +118 -0
  240. package/dist/browser/seo/rules/types.d.ts +498 -0
  241. package/dist/browser/seo/rules/types.js +11 -0
  242. package/dist/browser/seo/types.d.ts +211 -0
  243. package/dist/browser/seo/types.js +1 -0
  244. package/dist/browser/transport/curl.d.ts +4 -0
  245. package/dist/browser/transport/curl.js +101 -0
  246. package/dist/browser/transport/undici.js +1 -2
  247. package/dist/browser/transport/worker.d.ts +18 -0
  248. package/dist/browser/transport/worker.js +278 -0
  249. package/dist/browser/types/index.d.ts +4 -1
  250. package/dist/browser/utils/binary-manager.d.ts +4 -0
  251. package/dist/browser/utils/binary-manager.js +72 -0
  252. package/dist/browser/utils/user-agent.js +2 -13
  253. package/dist/cache/indexed-db.d.ts +10 -0
  254. package/dist/cache/indexed-db.js +88 -0
  255. package/dist/cache/service-worker-cache.d.ts +18 -0
  256. package/dist/cache/service-worker-cache.js +103 -0
  257. package/dist/cli/commands/ai.d.ts +2 -0
  258. package/dist/cli/commands/ai.js +162 -0
  259. package/dist/cli/commands/bench.d.ts +2 -0
  260. package/dist/cli/commands/bench.js +51 -0
  261. package/dist/cli/commands/dns.d.ts +2 -0
  262. package/dist/cli/commands/dns.js +295 -0
  263. package/dist/cli/commands/har.d.ts +2 -0
  264. package/dist/cli/commands/har.js +171 -0
  265. package/dist/cli/commands/hls.d.ts +2 -0
  266. package/dist/cli/commands/hls.js +192 -0
  267. package/dist/cli/commands/network.d.ts +2 -0
  268. package/dist/cli/commands/network.js +288 -0
  269. package/dist/cli/commands/protocols.d.ts +2 -0
  270. package/dist/cli/commands/protocols.js +344 -0
  271. package/dist/cli/commands/scrape.d.ts +2 -0
  272. package/dist/cli/commands/scrape.js +176 -0
  273. package/dist/cli/commands/security.d.ts +2 -0
  274. package/dist/cli/commands/security.js +57 -0
  275. package/dist/cli/commands/seo.d.ts +2 -0
  276. package/dist/cli/commands/seo.js +125 -0
  277. package/dist/cli/commands/serve.d.ts +2 -0
  278. package/dist/cli/commands/serve.js +531 -0
  279. package/dist/cli/commands/spider.d.ts +3 -0
  280. package/dist/cli/commands/spider.js +456 -0
  281. package/dist/cli/commands/utils.d.ts +2 -0
  282. package/dist/cli/commands/utils.js +176 -0
  283. package/dist/cli/commands/vector.d.ts +2 -0
  284. package/dist/cli/commands/vector.js +158 -0
  285. package/dist/cli/handler.d.ts +2 -2
  286. package/dist/cli/handler.js +6 -6
  287. package/dist/cli/helpers.d.ts +7 -0
  288. package/dist/cli/helpers.js +128 -0
  289. package/dist/cli/index.js +96 -5228
  290. package/dist/cli/parser/help.d.ts +2 -0
  291. package/dist/cli/parser/help.js +52 -0
  292. package/dist/cli/parser/index.d.ts +3 -0
  293. package/dist/cli/parser/index.js +3 -0
  294. package/dist/cli/parser/parser.d.ts +4 -0
  295. package/dist/cli/parser/parser.js +146 -0
  296. package/dist/cli/parser/types.d.ts +41 -0
  297. package/dist/cli/parser/types.js +1 -0
  298. package/dist/cli/presets.d.ts +1 -1
  299. package/dist/cli/presets.js +1 -1
  300. package/dist/cli/router.d.ts +36 -0
  301. package/dist/cli/router.js +195 -0
  302. package/dist/cli/tui/ai-chat.js +1 -1
  303. package/dist/cli/tui/commands/context.d.ts +9 -0
  304. package/dist/cli/tui/commands/context.js +1 -0
  305. package/dist/cli/tui/commands/dns.d.ts +10 -0
  306. package/dist/cli/tui/commands/dns.js +461 -0
  307. package/dist/cli/tui/commands/hls.d.ts +2 -0
  308. package/dist/cli/tui/commands/hls.js +162 -0
  309. package/dist/cli/tui/commands/ip.d.ts +2 -0
  310. package/dist/cli/tui/commands/ip.js +45 -0
  311. package/dist/cli/tui/commands/network.d.ts +3 -0
  312. package/dist/cli/tui/commands/network.js +81 -0
  313. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  314. package/dist/cli/tui/commands/protocols.js +531 -0
  315. package/dist/cli/tui/commands/security.d.ts +2 -0
  316. package/dist/cli/tui/commands/security.js +48 -0
  317. package/dist/cli/tui/commands/seo.d.ts +2 -0
  318. package/dist/cli/tui/commands/seo.js +74 -0
  319. package/dist/cli/tui/context.d.ts +12 -0
  320. package/dist/cli/tui/context.js +1 -0
  321. package/dist/cli/tui/shell.d.ts +11 -20
  322. package/dist/cli/tui/shell.js +216 -1873
  323. package/dist/constants/user-agents.d.ts +7 -0
  324. package/dist/constants/user-agents.js +7 -0
  325. package/dist/core/client.d.ts +2 -0
  326. package/dist/core/client.js +19 -1
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.js +1 -0
  329. package/dist/mcp/cli.js +2 -3
  330. package/dist/mcp/data/embeddings.json +1 -0
  331. package/dist/mcp/tools/network.js +298 -158
  332. package/dist/plugins/har-player.d.ts +23 -0
  333. package/dist/plugins/har-player.js +49 -0
  334. package/dist/plugins/har-recorder.d.ts +37 -3
  335. package/dist/plugins/har-recorder.js +116 -63
  336. package/dist/plugins/network-simulation.d.ts +7 -0
  337. package/dist/plugins/network-simulation.js +13 -0
  338. package/dist/presets/android.d.ts +2 -0
  339. package/dist/presets/android.js +16 -0
  340. package/dist/presets/chaturbate.d.ts +2 -0
  341. package/dist/presets/chaturbate.js +17 -0
  342. package/dist/presets/elevenlabs.d.ts +6 -0
  343. package/dist/presets/elevenlabs.js +20 -0
  344. package/dist/presets/enhancers.d.ts +20 -0
  345. package/dist/presets/enhancers.js +85 -0
  346. package/dist/presets/hubspot.d.ts +9 -0
  347. package/dist/presets/hubspot.js +28 -0
  348. package/dist/presets/index.d.ts +10 -0
  349. package/dist/presets/index.js +10 -0
  350. package/dist/presets/ios.d.ts +2 -0
  351. package/dist/presets/ios.js +13 -0
  352. package/dist/presets/pinecone.d.ts +8 -0
  353. package/dist/presets/pinecone.js +42 -0
  354. package/dist/presets/registry.js +60 -0
  355. package/dist/presets/sendgrid.d.ts +6 -0
  356. package/dist/presets/sendgrid.js +20 -0
  357. package/dist/presets/sentry.d.ts +11 -0
  358. package/dist/presets/sentry.js +48 -0
  359. package/dist/presets/square.d.ts +10 -0
  360. package/dist/presets/square.js +33 -0
  361. package/dist/recker.d.ts +3 -0
  362. package/dist/recker.js +4 -0
  363. package/dist/scrape/document.d.ts +5 -4
  364. package/dist/scrape/document.js +89 -76
  365. package/dist/scrape/element.d.ts +10 -8
  366. package/dist/scrape/element.js +295 -81
  367. package/dist/scrape/extractors.d.ts +11 -11
  368. package/dist/scrape/extractors.js +145 -113
  369. package/dist/scrape/index.d.ts +2 -0
  370. package/dist/scrape/index.js +1 -0
  371. package/dist/scrape/parser/back.d.ts +1 -0
  372. package/dist/scrape/parser/back.js +3 -0
  373. package/dist/scrape/parser/index.d.ts +20 -0
  374. package/dist/scrape/parser/index.js +19 -0
  375. package/dist/scrape/parser/matcher.d.ts +30 -0
  376. package/dist/scrape/parser/matcher.js +99 -0
  377. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  378. package/dist/scrape/parser/nodes/comment.js +21 -0
  379. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  380. package/dist/scrape/parser/nodes/html.js +978 -0
  381. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  382. package/dist/scrape/parser/nodes/node.js +31 -0
  383. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  384. package/dist/scrape/parser/nodes/text.js +30 -0
  385. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  386. package/dist/scrape/parser/nodes/type.js +7 -0
  387. package/dist/scrape/parser/parse.d.ts +1 -0
  388. package/dist/scrape/parser/parse.js +1 -0
  389. package/dist/scrape/parser/valid.d.ts +2 -0
  390. package/dist/scrape/parser/valid.js +5 -0
  391. package/dist/scrape/parser/void-tag.d.ts +7 -0
  392. package/dist/scrape/parser/void-tag.js +43 -0
  393. package/dist/scrape/spider.d.ts +19 -0
  394. package/dist/scrape/spider.js +28 -3
  395. package/dist/scrape/types.d.ts +7 -0
  396. package/dist/seo/analyzer.d.ts +15 -5
  397. package/dist/seo/analyzer.js +636 -175
  398. package/dist/seo/formatter.d.ts +16 -0
  399. package/dist/seo/formatter.js +228 -0
  400. package/dist/seo/index.d.ts +2 -0
  401. package/dist/seo/index.js +1 -0
  402. package/dist/seo/keywords.d.ts +16 -0
  403. package/dist/seo/keywords.js +55 -0
  404. package/dist/seo/rules/accessibility.js +85 -57
  405. package/dist/seo/rules/ai-search.js +44 -31
  406. package/dist/seo/rules/analytics.d.ts +2 -0
  407. package/dist/seo/rules/analytics.js +306 -0
  408. package/dist/seo/rules/best-practices.js +21 -14
  409. package/dist/seo/rules/canonical.js +31 -22
  410. package/dist/seo/rules/content.js +207 -19
  411. package/dist/seo/rules/crawl.js +55 -40
  412. package/dist/seo/rules/cwv.js +21 -15
  413. package/dist/seo/rules/ecommerce.js +51 -20
  414. package/dist/seo/rules/i18n.js +61 -33
  415. package/dist/seo/rules/images.js +87 -28
  416. package/dist/seo/rules/index.js +2 -0
  417. package/dist/seo/rules/internal-linking.js +58 -39
  418. package/dist/seo/rules/links.js +70 -51
  419. package/dist/seo/rules/local.js +49 -25
  420. package/dist/seo/rules/meta.js +161 -62
  421. package/dist/seo/rules/mobile.js +112 -2
  422. package/dist/seo/rules/performance.js +309 -54
  423. package/dist/seo/rules/pwa.js +36 -39
  424. package/dist/seo/rules/readability.js +31 -22
  425. package/dist/seo/rules/redirects.js +21 -15
  426. package/dist/seo/rules/resources.js +59 -42
  427. package/dist/seo/rules/schema.js +333 -8
  428. package/dist/seo/rules/security.js +142 -80
  429. package/dist/seo/rules/social.js +277 -47
  430. package/dist/seo/rules/structural.js +40 -16
  431. package/dist/seo/rules/technical-advanced.js +27 -22
  432. package/dist/seo/rules/technical.js +243 -42
  433. package/dist/seo/rules/types.d.ts +53 -1
  434. package/dist/seo/seo-spider.d.ts +22 -0
  435. package/dist/seo/seo-spider.js +77 -13
  436. package/dist/seo/types.d.ts +8 -1
  437. package/dist/seo/validators/llms-txt.js +19 -0
  438. package/dist/seo/validators/rss.d.ts +11 -0
  439. package/dist/seo/validators/rss.js +93 -0
  440. package/dist/seo/validators/sitemap.js +36 -26
  441. package/dist/transport/curl.d.ts +4 -0
  442. package/dist/transport/curl.js +101 -0
  443. package/dist/transport/udp.js +0 -1
  444. package/dist/transport/undici.js +1 -2
  445. package/dist/transport/worker.d.ts +18 -0
  446. package/dist/transport/worker.js +278 -0
  447. package/dist/types/index.d.ts +4 -1
  448. package/dist/utils/binary-manager.d.ts +4 -0
  449. package/dist/utils/binary-manager.js +72 -0
  450. package/dist/utils/optional-require.d.ts +7 -8
  451. package/dist/utils/optional-require.js +2 -21
  452. package/dist/utils/upload.d.ts +6 -0
  453. package/dist/utils/upload.js +11 -0
  454. package/dist/utils/user-agent.js +2 -13
  455. package/dist/version.js +1 -1
  456. package/package.json +14 -5
  457. package/dist/browser/utils/optional-require.d.ts +0 -19
  458. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,424 @@
1
+ import { createResult } from './types.js';
2
+ import { SEO_THRESHOLDS } from './thresholds.js';
3
+ export const contentRules = [
4
+ {
5
+ id: 'content-depth-word-count',
6
+ name: 'Content Depth (Word Count)',
7
+ category: 'content',
8
+ severity: 'warning',
9
+ description: 'Page should meet minimum word count for its purpose.',
10
+ check: (ctx) => {
11
+ if (ctx.wordCount === undefined) {
12
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth (Word Count)', category: 'content', severity: 'warning' }, 'info', 'Not applicable (word count not available)', { recommendation: 'This rule checks content depth based on word count when available' });
13
+ }
14
+ const { minWordsSimple, minWordsRanking, minWordsAuthority } = SEO_THRESHOLDS.content;
15
+ const veryThinWords = SEO_THRESHOLDS.thinContent.veryThinWords;
16
+ if (ctx.wordCount < veryThinWords) {
17
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth', category: 'content', severity: 'error' }, 'fail', `Very thin content (${ctx.wordCount} words, min: ${veryThinWords})`, { recommendation: 'Add more substantial content (at least 150-300 words).' });
18
+ }
19
+ if (ctx.wordCount < minWordsSimple) {
20
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth', category: 'content', severity: 'warning' }, 'warn', `Thin content (${ctx.wordCount} words, min for simple: ${minWordsSimple})`, { recommendation: `Consider expanding to at least ${minWordsSimple} words for simple pages, or more for ranking.` });
21
+ }
22
+ if (ctx.wordCount < minWordsRanking) {
23
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth', category: 'content', severity: 'info' }, 'warn', `Content may be too short to rank (${ctx.wordCount} words, min for ranking: ${minWordsRanking})`, { recommendation: `Expand content to at least ${minWordsRanking} words for competitive keywords.` });
24
+ }
25
+ if (ctx.wordCount < minWordsAuthority) {
26
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth', category: 'content', severity: 'info' }, 'info', `Content is not authority-level (${ctx.wordCount} words, min for authority: ${minWordsAuthority})`, { recommendation: `For authority content, aim for ${minWordsAuthority} words or more.` });
27
+ }
28
+ return createResult({ id: 'content-depth-word-count', name: 'Content Depth', category: 'content', severity: 'info' }, 'pass', `Good content depth (${ctx.wordCount} words)`, { value: ctx.wordCount });
29
+ },
30
+ },
31
+ {
32
+ id: 'content-readability-sentence-length',
33
+ name: 'Readability (Sentence Length)',
34
+ category: 'content',
35
+ severity: 'info',
36
+ description: 'Sentences should average under 25 words for better readability.',
37
+ check: (ctx) => {
38
+ if (ctx.avgWordsPerSentence === undefined) {
39
+ return createResult({ id: 'content-readability-sentence-length', name: 'Readability (Sentence Length)', category: 'content', severity: 'info' }, 'info', 'Not applicable (sentence metrics not available)', { recommendation: 'This rule checks average sentence length when metrics are available' });
40
+ }
41
+ const max = SEO_THRESHOLDS.content.maxWordsPerSentence;
42
+ if (ctx.avgWordsPerSentence > max) {
43
+ return createResult({ id: 'content-readability-sentence-length', name: 'Readability', category: 'content', severity: 'info' }, 'warn', `Long sentences (avg ${ctx.avgWordsPerSentence} words/sentence)`, { value: ctx.avgWordsPerSentence, recommendation: `Aim for under ${max} words per sentence for better readability.` });
44
+ }
45
+ return createResult({ id: 'content-readability-sentence-length', name: 'Readability', category: 'content', severity: 'info' }, 'pass', `Good sentence length (avg ${ctx.avgWordsPerSentence} words)`, { value: ctx.avgWordsPerSentence });
46
+ },
47
+ },
48
+ {
49
+ id: 'content-paragraph-length',
50
+ name: 'Paragraph Length',
51
+ category: 'content',
52
+ severity: 'info',
53
+ description: 'Paragraphs should be concise (ideal 40-90 words) for mobile readability.',
54
+ check: (ctx) => {
55
+ if (!ctx.paragraphWordCounts || ctx.paragraphWordCounts.length === 0) {
56
+ return createResult({ id: 'content-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'info' }, 'info', 'Not applicable (paragraph data not available)', { recommendation: 'This rule checks paragraph length when paragraph data is available' });
57
+ }
58
+ const { minWordsPerParagraph, maxWordsPerParagraph } = SEO_THRESHOLDS.content;
59
+ let tooShort = 0;
60
+ let tooLong = 0;
61
+ ctx.paragraphWordCounts.forEach(count => {
62
+ if (count < minWordsPerParagraph)
63
+ tooShort++;
64
+ if (count > maxWordsPerParagraph)
65
+ tooLong++;
66
+ });
67
+ if (tooShort > 0 || tooLong > 0) {
68
+ let message = '';
69
+ const recs = [];
70
+ if (tooShort > 0) {
71
+ message += `${tooShort} paragraph(s) are too short (min: ${minWordsPerParagraph} words). `;
72
+ recs.push('Expand short paragraphs.');
73
+ }
74
+ if (tooLong > 0) {
75
+ message += `${tooLong} paragraph(s) are too long (max: ${maxWordsPerParagraph} words).`;
76
+ recs.push('Break long paragraphs into smaller ones.');
77
+ }
78
+ return createResult({ id: 'content-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'warning' }, 'warn', message.trim(), { recommendation: `Aim for paragraphs between ${minWordsPerParagraph}-${maxWordsPerParagraph} words. ${recs.join(' ')}` });
79
+ }
80
+ return createResult({ id: 'content-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'warning' }, 'info', 'Not applicable (paragraph lengths are appropriate)', { recommendation: 'This rule checks for paragraphs that are too short or too long' });
81
+ },
82
+ },
83
+ {
84
+ id: 'content-lists-presence',
85
+ name: 'Lists Usage',
86
+ category: 'content',
87
+ severity: 'info',
88
+ description: 'Use lists (ul/ol) to improve readability and SGE compatibility.',
89
+ check: (ctx) => {
90
+ if (ctx.listCount === undefined || ctx.listCount === 0) {
91
+ return createResult({ id: 'content-lists-presence', name: 'Lists Usage', category: 'content', severity: 'info' }, 'info', 'No lists (ul/ol) found', { recommendation: 'Consider using bullet points or numbered lists for better scannability and AI summarization.' });
92
+ }
93
+ return createResult({ id: 'content-lists-presence', name: 'Lists Usage', category: 'content', severity: 'info' }, 'info', 'Not applicable (page has lists)', { recommendation: 'This rule checks for the presence of ul/ol lists' });
94
+ },
95
+ },
96
+ {
97
+ id: 'content-subheading-frequency',
98
+ name: 'Subheading Frequency',
99
+ category: 'content',
100
+ severity: 'info',
101
+ description: 'Subheadings (H2/H3) should be used frequently to break up content.',
102
+ check: (ctx) => {
103
+ const idealFrequencyPer100Words = 0.5;
104
+ if (ctx.wordCount && ctx.wordCount > 300 && (ctx.subheadingFrequency ?? 0) < idealFrequencyPer100Words) {
105
+ return createResult({ id: 'content-subheading-frequency', name: 'Subheading Frequency', category: 'content', severity: 'info' }, 'warn', `Low subheading frequency (${(ctx.subheadingFrequency ?? 0).toFixed(2)} per 100 words)`, { recommendation: `Add more subheadings (H2/H3) to break up long text blocks.` });
106
+ }
107
+ return createResult({ id: 'content-subheading-frequency', name: 'Subheading Frequency', category: 'content', severity: 'info' }, 'info', 'Not applicable (subheading frequency is adequate or content is short)', { recommendation: 'This rule checks subheading frequency for long content' });
108
+ },
109
+ },
110
+ {
111
+ id: 'content-emphasis-tags',
112
+ name: 'Emphasis Tags Usage',
113
+ category: 'content',
114
+ severity: 'info',
115
+ description: 'Use strong/em tags moderately for emphasis, avoid keyword stuffing.',
116
+ check: (ctx) => {
117
+ const totalEmphasisTags = (ctx.strongTagCount ?? 0) + (ctx.emTagCount ?? 0);
118
+ if (ctx.wordCount && ctx.wordCount > 200 && totalEmphasisTags === 0) {
119
+ return createResult({ id: 'content-emphasis-tags', name: 'Emphasis Tags Usage', category: 'content', severity: 'info' }, 'info', 'No strong/em tags found in substantial content', { recommendation: 'Use <strong> or <em> tags to highlight important keywords or phrases.' });
120
+ }
121
+ const emphasisRatio = totalEmphasisTags / (ctx.wordCount || 1);
122
+ const maxEmphasisRatio = 0.05;
123
+ if (ctx.wordCount && ctx.wordCount > 100 && emphasisRatio > maxEmphasisRatio) {
124
+ return createResult({ id: 'content-emphasis-tags', name: 'Emphasis Tags Usage', category: 'content', severity: 'warning' }, 'warn', `Potentially excessive emphasis tags (${totalEmphasisTags} tags for ${ctx.wordCount} words)`, { recommendation: 'Moderate the use of <strong> and <em> tags to avoid over-optimization.' });
125
+ }
126
+ return createResult({ id: 'content-emphasis-tags', name: 'Emphasis Tags Usage', category: 'content', severity: 'info' }, 'info', 'Not applicable (emphasis tag usage is appropriate)', { recommendation: 'This rule checks for proper use of strong/em tags' });
127
+ },
128
+ },
129
+ {
130
+ id: 'multimedia-video-audio',
131
+ name: 'Multimedia Content',
132
+ category: 'content',
133
+ severity: 'info',
134
+ description: 'Rich content with videos and audio can enhance user experience.',
135
+ check: (ctx) => {
136
+ const totalMultimedia = (ctx.videoCount ?? 0) + (ctx.audioCount ?? 0);
137
+ if (totalMultimedia === 0 && ctx.wordCount && ctx.wordCount > 500) {
138
+ return createResult({ id: 'multimedia-video-audio', name: 'Multimedia Content', category: 'content', severity: 'info' }, 'info', 'No video or audio elements found for substantial content', { recommendation: 'Consider adding relevant videos, audio, or other rich media to engage users.' });
139
+ }
140
+ return createResult({ id: 'multimedia-video-audio', name: 'Multimedia Content', category: 'content', severity: 'info' }, 'info', 'Not applicable (page has multimedia or content is short)', { recommendation: 'This rule checks for video/audio elements in substantial content' });
141
+ },
142
+ },
143
+ {
144
+ id: 'content-sge-optimization',
145
+ name: 'AI Overview (SGE) Optimization',
146
+ category: 'content',
147
+ severity: 'info',
148
+ description: 'Optimize content for Google AI Overviews by using question-based headings and clear lists.',
149
+ check: (ctx) => {
150
+ if (ctx.wordCount && ctx.wordCount > 300) {
151
+ let messages = [];
152
+ let recommendation = '';
153
+ if (!ctx.hasQuestionHeadings) {
154
+ messages.push('No question-based headings (H2/H3) found.');
155
+ recommendation += 'Use H2/H3 headings that phrase common questions (e.g., "What is X?", "How to do Y?"). ';
156
+ }
157
+ if (ctx.listCount === 0) {
158
+ messages.push('No lists (ul/ol) found.');
159
+ recommendation += 'Incorporate clear numbered or bulleted lists for steps, features, or summaries.';
160
+ }
161
+ if (messages.length > 0) {
162
+ return createResult({ id: 'content-sge-optimization', name: 'AI Overview Optimization', category: 'content', severity: 'info' }, 'info', `Content could be better optimized for AI Overviews: ${messages.join(' ')}`, { recommendation: recommendation.trim() });
163
+ }
164
+ }
165
+ return createResult({ id: 'content-sge-optimization', name: 'AI Overview (SGE) Optimization', category: 'content', severity: 'info' }, 'info', 'Not applicable (content is optimized for AI Overviews or content is short)', { recommendation: 'This rule checks for question-based headings and lists for AI compatibility' });
166
+ },
167
+ },
168
+ {
169
+ id: 'content-flesch-readability',
170
+ name: 'Flesch Reading Ease Score',
171
+ category: 'content',
172
+ severity: 'info',
173
+ description: 'Measures readability, aiming for a Flesch score above 60 for broad audiences.',
174
+ check: (ctx) => {
175
+ if (ctx.fleschReadingEase === undefined) {
176
+ return createResult({ id: 'content-flesch-readability', name: 'Flesch Reading Ease', category: 'content', severity: 'info' }, 'info', 'Flesch Reading Ease score could not be calculated.', { recommendation: 'To enable Flesch score analysis, ensure the analyzer has capabilities to count syllables per word.' });
177
+ }
178
+ const score = ctx.fleschReadingEase;
179
+ if (score < 60) {
180
+ return createResult({ id: 'content-flesch-readability', name: 'Flesch Reading Ease', category: 'content', severity: 'warning' }, 'warn', `Low Flesch Reading Ease score: ${score.toFixed(2)} (target > 60)`, { value: score, recommendation: 'Simplify sentence structure and vocabulary to improve readability for a broader audience.' });
181
+ }
182
+ return createResult({ id: 'content-flesch-readability', name: 'Flesch Reading Ease', category: 'content', severity: 'info' }, 'pass', `Good Flesch Reading Ease score: ${score.toFixed(2)}`, { value: score });
183
+ },
184
+ },
185
+ {
186
+ id: 'content-faq-mandatory',
187
+ name: 'Mandatory FAQ Section',
188
+ category: 'content',
189
+ severity: 'info',
190
+ description: 'For comprehensive content, an FAQ section is recommended.',
191
+ check: (ctx) => {
192
+ if (ctx.wordCount && ctx.wordCount > SEO_THRESHOLDS.content.minWordsAuthority && (ctx.faqCount ?? 0) < 3) {
193
+ return createResult({ id: 'content-faq-mandatory', name: 'FAQ Section', category: 'content', severity: 'info' }, 'warn', 'Consider adding a dedicated FAQ section for comprehensive content', { recommendation: 'Include 3-7 common questions as H3s and optionally use Schema.org FAQPage markup for rich results.' });
194
+ }
195
+ return createResult({ id: 'content-faq-mandatory', name: 'Mandatory FAQ Section', category: 'content', severity: 'info' }, 'info', 'Not applicable (page has FAQ or content is not comprehensive)', { recommendation: 'This rule checks for FAQ sections in comprehensive content' });
196
+ },
197
+ },
198
+ {
199
+ id: 'content-image-text-proportion',
200
+ name: 'Image-Text Proportion',
201
+ category: 'content',
202
+ severity: 'info',
203
+ description: 'Maintain a healthy image-to-text ratio for engaging content.',
204
+ check: (ctx) => {
205
+ if (ctx.wordCount === undefined || ctx.wordCount < 200 || ctx.totalImages === undefined || ctx.totalImages === 0) {
206
+ return createResult({ id: 'content-image-text-proportion', name: 'Image-Text Proportion', category: 'content', severity: 'info' }, 'info', 'Not applicable (insufficient content, word count, or no images)', { recommendation: 'This rule checks image-to-text ratio for substantial content with images' });
207
+ }
208
+ const { min: minWords, max: maxWords } = SEO_THRESHOLDS.content.imageWordRatio;
209
+ const actualWordsPerImage = ctx.wordCount / ctx.totalImages;
210
+ if (actualWordsPerImage > maxWords) {
211
+ return createResult({ id: 'content-image-text-proportion', name: 'Image-Text Proportion', category: 'content', severity: 'info' }, 'warn', `Low image density (1 image per ${actualWordsPerImage.toFixed(0)} words, ideal: 1 per ${minWords}-${maxWords})`, { recommendation: 'Add more relevant images to break up text and improve engagement.' });
212
+ }
213
+ if (actualWordsPerImage < minWords) {
214
+ return createResult({ id: 'content-image-text-proportion', name: 'Image-Text Proportion', category: 'content', severity: 'info' }, 'warn', `High image density (1 image per ${actualWordsPerImage.toFixed(0)} words, ideal: 1 per ${minWords}-${maxWords})`, { recommendation: 'Ensure images are relevant and not excessive, as too many can be distracting.' });
215
+ }
216
+ return createResult({ id: 'content-image-text-proportion', name: 'Image-Text Proportion', category: 'content', severity: 'info' }, 'info', 'Not applicable (image-text proportion is balanced)', { recommendation: 'This rule checks if images are proportional to content' });
217
+ },
218
+ },
219
+ {
220
+ id: 'content-main-keyword-redundancy',
221
+ name: 'Main Keyword Redundancy',
222
+ category: 'content',
223
+ severity: 'info',
224
+ description: 'Avoid keyword stuffing in key areas (title, H1, first paragraph, image alt).',
225
+ check: (ctx) => {
226
+ if (!ctx.mainKeyword || !ctx.title || !ctx.h1Text || !ctx.metaDescription || !ctx.paragraphWordCounts || ctx.paragraphWordCounts.length === 0) {
227
+ return createResult({ id: 'content-main-keyword-redundancy', name: 'Main Keyword Redundancy', category: 'content', severity: 'info' }, 'info', 'Not applicable (insufficient data for keyword redundancy check)', { recommendation: 'This rule checks keyword stuffing when main keyword and content data are available' });
228
+ }
229
+ const keyword = ctx.mainKeyword.toLowerCase();
230
+ let redundancyCount = 0;
231
+ if (ctx.title.toLowerCase().includes(keyword))
232
+ redundancyCount++;
233
+ if (ctx.h1Text.toLowerCase().includes(keyword))
234
+ redundancyCount++;
235
+ if (redundancyCount > SEO_THRESHOLDS.content.redundancyTolerance) {
236
+ return createResult({ id: 'content-main-keyword-redundancy', name: 'Main Keyword Redundancy', category: 'content', severity: 'warning' }, 'warn', `Main keyword "${ctx.mainKeyword}" appears too often in key SEO elements.`, { recommendation: 'Ensure natural language use of keywords. Avoid over-optimization (keyword stuffing) in title, H1, meta description, and alt texts.' });
237
+ }
238
+ return createResult({ id: 'content-main-keyword-redundancy', name: 'Main Keyword Redundancy', category: 'content', severity: 'warning' }, 'info', 'Not applicable (keyword usage is natural)', { recommendation: 'This rule checks for excessive keyword repetition in key SEO elements' });
239
+ },
240
+ },
241
+ {
242
+ id: 'content-freshness',
243
+ name: 'Content Freshness',
244
+ category: 'content',
245
+ severity: 'info',
246
+ description: 'Content should have indicators of recency (dates).',
247
+ check: (ctx) => {
248
+ const hasDate = ctx.lastModified || ctx.ogArticlePublishedTime;
249
+ if (!hasDate) {
250
+ return createResult({ id: 'content-freshness', name: 'Content Freshness', category: 'content', severity: 'info' }, 'info', 'No content freshness information found (Last-Modified, og:updated_time)', { recommendation: 'Keep content updated and ensure dates are visible to crawlers (meta tags, sitemap, or headers).' });
251
+ }
252
+ return createResult({ id: 'content-freshness', name: 'Content Freshness', category: 'content', severity: 'info' }, 'pass', 'Content freshness signal found', { value: hasDate });
253
+ },
254
+ },
255
+ {
256
+ id: 'email-privacy',
257
+ name: 'Email Privacy',
258
+ category: 'content',
259
+ severity: 'warning',
260
+ description: 'Avoid plain text email addresses to prevent spam.',
261
+ check: (ctx) => {
262
+ if (ctx.emailsFound && ctx.emailsFound.length > 0) {
263
+ return createResult({ id: 'email-privacy', name: 'Email Privacy', category: 'content', severity: 'warning' }, 'warn', `${ctx.emailsFound.length} plain text email address(es) found`, {
264
+ value: ctx.emailsFound.length,
265
+ recommendation: 'Remove plain text emails. Use contact forms or obfuscation (e.g., "user [at] domain").',
266
+ evidence: {
267
+ found: ctx.emailsFound.slice(0, 3),
268
+ impact: 'Spam bots scrape plain text emails.'
269
+ }
270
+ });
271
+ }
272
+ return createResult({ id: 'email-privacy', name: 'Email Privacy', category: 'content', severity: 'info' }, 'pass', 'No plain text emails found');
273
+ },
274
+ },
275
+ {
276
+ id: 'keyword-in-url',
277
+ name: 'Keyword in URL',
278
+ category: 'content',
279
+ severity: 'info',
280
+ description: 'URLs should contain relevant keywords for better SEO signals.',
281
+ check: (ctx) => {
282
+ if (!ctx.topKeywords || ctx.topKeywords.length === 0) {
283
+ return createResult({ id: 'keyword-in-url', name: 'Keyword in URL', category: 'content', severity: 'info' }, 'info', 'Not applicable (no keyword data available)', { recommendation: 'This rule checks if keywords are present in URL when keyword data is available' });
284
+ }
285
+ if (ctx.keywordsInUrl === undefined) {
286
+ return createResult({ id: 'keyword-in-url', name: 'Keyword in URL', category: 'content', severity: 'info' }, 'info', 'Not applicable (keyword URL check not performed)', { recommendation: 'This rule checks URL for relevant keywords when data is available' });
287
+ }
288
+ const mainKeyword = ctx.mainKeyword || ctx.topKeywords[0];
289
+ if (!ctx.keywordsInUrl) {
290
+ return createResult({ id: 'keyword-in-url', name: 'Keyword in URL', category: 'content', severity: 'info' }, 'warn', 'Main keyword not found in URL', {
291
+ recommendation: `Include "${mainKeyword}" or related keywords in your URL slug for better SEO.`,
292
+ evidence: {
293
+ found: 'No keywords in URL path',
294
+ expected: 'URL slug should contain target keywords',
295
+ impact: 'URLs with keywords rank slightly better and are more descriptive to users.',
296
+ learnMore: 'https://developers.google.com/search/docs/crawling-indexing/url-structure',
297
+ },
298
+ });
299
+ }
300
+ return createResult({ id: 'keyword-in-url', name: 'Keyword in URL', category: 'content', severity: 'info' }, 'pass', 'URL contains relevant keywords');
301
+ },
302
+ },
303
+ {
304
+ id: 'keyword-consistency',
305
+ name: 'Keyword Consistency Score',
306
+ category: 'content',
307
+ severity: 'warning',
308
+ description: 'Main keyword should appear consistently across title, description, H1, URL, first paragraph, and image alt text.',
309
+ check: (ctx) => {
310
+ if (ctx.keywordConsistencyScore === undefined || !ctx.keywordConsistencyDetails) {
311
+ return createResult({ id: 'keyword-consistency', name: 'Keyword Consistency Score', category: 'content', severity: 'warning' }, 'info', 'Not applicable (keyword consistency data not available)', { recommendation: 'This rule checks keyword consistency when data is available' });
312
+ }
313
+ if (!ctx.mainKeyword) {
314
+ return createResult({ id: 'keyword-consistency', name: 'Keyword Consistency Score', category: 'content', severity: 'warning' }, 'info', 'Not applicable (no main keyword specified)', { recommendation: 'This rule checks keyword consistency when a main keyword is provided' });
315
+ }
316
+ const score = ctx.keywordConsistencyScore;
317
+ const details = ctx.keywordConsistencyDetails;
318
+ const maxScore = 6;
319
+ const missing = [];
320
+ if (!details.inTitle)
321
+ missing.push('title');
322
+ if (!details.inDescription)
323
+ missing.push('meta description');
324
+ if (!details.inH1)
325
+ missing.push('H1 heading');
326
+ if (!details.inUrl)
327
+ missing.push('URL');
328
+ if (!details.inFirstParagraph)
329
+ missing.push('first paragraph');
330
+ if (!details.inAltText)
331
+ missing.push('image alt text');
332
+ const present = [];
333
+ if (details.inTitle)
334
+ present.push('title');
335
+ if (details.inDescription)
336
+ present.push('meta description');
337
+ if (details.inH1)
338
+ present.push('H1');
339
+ if (details.inUrl)
340
+ present.push('URL');
341
+ if (details.inFirstParagraph)
342
+ present.push('first paragraph');
343
+ if (details.inAltText)
344
+ present.push('alt text');
345
+ if (score <= 2) {
346
+ return createResult({ id: 'keyword-consistency', name: 'Keyword Consistency', category: 'content', severity: 'warning' }, 'fail', `Low keyword consistency (${score}/${maxScore}): "${ctx.mainKeyword}" missing from most key locations`, {
347
+ value: score,
348
+ recommendation: `Add "${ctx.mainKeyword}" to: ${missing.join(', ')}`,
349
+ evidence: {
350
+ found: present.length > 0 ? `Found in: ${present.join(', ')}` : 'Not found in any key location',
351
+ expected: 'Keyword should appear in at least 4-5 of: title, description, H1, URL, first paragraph, image alt',
352
+ impact: 'Consistent keyword placement signals topical relevance to search engines.',
353
+ },
354
+ });
355
+ }
356
+ if (score <= 4) {
357
+ return createResult({ id: 'keyword-consistency', name: 'Keyword Consistency', category: 'content', severity: 'info' }, 'warn', `Moderate keyword consistency (${score}/${maxScore}): "${ctx.mainKeyword}" missing from some locations`, {
358
+ value: score,
359
+ recommendation: `Consider adding "${ctx.mainKeyword}" to: ${missing.join(', ')}`,
360
+ evidence: {
361
+ found: `Found in: ${present.join(', ')}`,
362
+ expected: 'Keyword should appear in at least 5-6 key locations for optimal SEO',
363
+ },
364
+ });
365
+ }
366
+ return createResult({ id: 'keyword-consistency', name: 'Keyword Consistency', category: 'content', severity: 'info' }, 'pass', `Excellent keyword consistency (${score}/${maxScore}): "${ctx.mainKeyword}" found in ${present.join(', ')}`, { value: score });
367
+ },
368
+ },
369
+ {
370
+ id: 'keyword-in-first-paragraph',
371
+ name: 'Keyword in First Paragraph',
372
+ category: 'content',
373
+ severity: 'info',
374
+ description: 'Including the main keyword in the first paragraph helps establish topic relevance.',
375
+ check: (ctx) => {
376
+ if (!ctx.topKeywords || ctx.topKeywords.length === 0) {
377
+ return createResult({ id: 'keyword-in-first-paragraph', name: 'Keyword in First Paragraph', category: 'content', severity: 'info' }, 'info', 'Not applicable (no keyword data available)', { recommendation: 'This rule checks first paragraph for keywords when keyword data is available' });
378
+ }
379
+ if (ctx.keywordsInFirstParagraph === undefined) {
380
+ return createResult({ id: 'keyword-in-first-paragraph', name: 'Keyword in First Paragraph', category: 'content', severity: 'info' }, 'info', 'Not applicable (first paragraph keyword check not performed)', { recommendation: 'This rule checks for keywords in first paragraph when data is available' });
381
+ }
382
+ const mainKeyword = ctx.mainKeyword || ctx.topKeywords[0];
383
+ if (!ctx.keywordsInFirstParagraph) {
384
+ return createResult({ id: 'keyword-in-first-paragraph', name: 'Keyword in First Paragraph', category: 'content', severity: 'info' }, 'warn', 'Main keyword not found in first paragraph', {
385
+ recommendation: `Include "${mainKeyword}" naturally in your opening paragraph to establish topic relevance.`,
386
+ evidence: {
387
+ expected: 'Main keyword should appear in the first 100 words',
388
+ impact: 'Early keyword placement signals the main topic to search engines and readers.',
389
+ },
390
+ });
391
+ }
392
+ return createResult({ id: 'keyword-in-first-paragraph', name: 'Keyword in First Paragraph', category: 'content', severity: 'info' }, 'pass', 'Keyword found in first paragraph');
393
+ },
394
+ },
395
+ {
396
+ id: 'keyword-in-image-alt',
397
+ name: 'Keyword in Image Alt Text',
398
+ category: 'content',
399
+ severity: 'info',
400
+ description: 'At least one image should have alt text containing the main keyword.',
401
+ check: (ctx) => {
402
+ if (!ctx.topKeywords || ctx.topKeywords.length === 0) {
403
+ return createResult({ id: 'keyword-in-image-alt', name: 'Keyword in Image Alt Text', category: 'content', severity: 'info' }, 'info', 'Not applicable (no keyword data available)', { recommendation: 'This rule checks image alt text for keywords when keyword data is available' });
404
+ }
405
+ if (ctx.keywordsInAltText === undefined) {
406
+ return createResult({ id: 'keyword-in-image-alt', name: 'Keyword in Image Alt Text', category: 'content', severity: 'info' }, 'info', 'Not applicable (alt text keyword check not performed)', { recommendation: 'This rule checks for keywords in image alt text when data is available' });
407
+ }
408
+ if (ctx.totalImages === 0) {
409
+ return createResult({ id: 'keyword-in-image-alt', name: 'Keyword in Image Alt Text', category: 'content', severity: 'info' }, 'info', 'Not applicable (no images on page)', { recommendation: 'This rule checks image alt text for keywords when images are present' });
410
+ }
411
+ const mainKeyword = ctx.mainKeyword || ctx.topKeywords[0];
412
+ if (!ctx.keywordsInAltText) {
413
+ return createResult({ id: 'keyword-in-image-alt', name: 'Keyword in Image Alt', category: 'content', severity: 'info' }, 'warn', 'Main keyword not found in any image alt text', {
414
+ recommendation: `Include "${mainKeyword}" naturally in at least one image alt attribute.`,
415
+ evidence: {
416
+ expected: 'At least one image alt should contain the target keyword',
417
+ impact: 'Image alt text contributes to keyword relevance and helps with image search rankings.',
418
+ },
419
+ });
420
+ }
421
+ return createResult({ id: 'keyword-in-image-alt', name: 'Keyword in Image Alt', category: 'content', severity: 'info' }, 'pass', 'Keyword found in image alt text');
422
+ },
423
+ },
424
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const crawlRules: SeoRule[];