recker 1.0.43 → 1.0.44

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 (459) hide show
  1. package/README.md +47 -0
  2. package/dist/bin/recker-linux-x64 +0 -0
  3. package/dist/bin/recker-macos-x64 +0 -0
  4. package/dist/bin/recker-win-x64.exe +0 -0
  5. package/dist/bin/rek.cjs +85152 -100207
  6. package/dist/browser/ai/adaptive-timeout.d.ts +50 -0
  7. package/dist/browser/ai/adaptive-timeout.js +208 -0
  8. package/dist/browser/ai/client.d.ts +22 -0
  9. package/dist/browser/ai/client.js +294 -0
  10. package/dist/browser/ai/index.d.ts +14 -0
  11. package/dist/browser/ai/index.js +11 -0
  12. package/dist/browser/ai/providers/anthropic.d.ts +63 -0
  13. package/dist/browser/ai/providers/anthropic.js +370 -0
  14. package/dist/browser/ai/providers/base.d.ts +48 -0
  15. package/dist/browser/ai/providers/base.js +150 -0
  16. package/dist/browser/ai/providers/google.d.ts +59 -0
  17. package/dist/browser/ai/providers/google.js +305 -0
  18. package/dist/browser/ai/providers/ollama.d.ts +44 -0
  19. package/dist/browser/ai/providers/ollama.js +240 -0
  20. package/dist/browser/ai/providers/openai.d.ts +64 -0
  21. package/dist/browser/ai/providers/openai.js +298 -0
  22. package/dist/browser/ai/rate-limiter.d.ts +43 -0
  23. package/dist/browser/ai/rate-limiter.js +215 -0
  24. package/dist/browser/ai/vector/index.d.ts +2 -0
  25. package/dist/browser/ai/vector/index.js +2 -0
  26. package/dist/browser/ai/vector/similarity.d.ts +2 -0
  27. package/dist/browser/ai/vector/similarity.js +27 -0
  28. package/dist/browser/ai/vector/store.d.ts +27 -0
  29. package/dist/browser/ai/vector/store.js +82 -0
  30. package/dist/browser/browser/cache.d.ts +2 -40
  31. package/dist/browser/browser/cache.js +2 -199
  32. package/dist/browser/browser/index.d.ts +8 -0
  33. package/dist/browser/browser/index.js +8 -0
  34. package/dist/browser/browser/recker.d.ts +8 -1
  35. package/dist/browser/browser/recker.js +8 -2
  36. package/dist/browser/cache/indexed-db.d.ts +10 -0
  37. package/dist/browser/cache/indexed-db.js +88 -0
  38. package/dist/browser/cache/service-worker-cache.d.ts +18 -0
  39. package/dist/browser/cache/service-worker-cache.js +103 -0
  40. package/dist/browser/cache.d.ts +2 -40
  41. package/dist/browser/cache.js +2 -199
  42. package/dist/browser/constants/user-agents.d.ts +7 -0
  43. package/dist/browser/constants/user-agents.js +7 -0
  44. package/dist/browser/core/client.d.ts +2 -0
  45. package/dist/browser/core/client.js +19 -1
  46. package/dist/browser/index.d.ts +8 -0
  47. package/dist/browser/index.js +8 -0
  48. package/dist/browser/plugins/har-recorder.d.ts +40 -0
  49. package/dist/browser/plugins/har-recorder.js +120 -0
  50. package/dist/browser/plugins/network-simulation.d.ts +7 -0
  51. package/dist/browser/plugins/network-simulation.js +13 -0
  52. package/dist/browser/presets/android.d.ts +2 -0
  53. package/dist/browser/presets/android.js +16 -0
  54. package/dist/browser/presets/anthropic.d.ts +8 -0
  55. package/dist/browser/presets/anthropic.js +27 -0
  56. package/dist/browser/presets/aws.d.ts +19 -0
  57. package/dist/browser/presets/aws.js +68 -0
  58. package/dist/browser/presets/azure-openai.d.ts +10 -0
  59. package/dist/browser/presets/azure-openai.js +35 -0
  60. package/dist/browser/presets/azure.d.ts +41 -0
  61. package/dist/browser/presets/azure.js +104 -0
  62. package/dist/browser/presets/chaturbate.d.ts +2 -0
  63. package/dist/browser/presets/chaturbate.js +17 -0
  64. package/dist/browser/presets/cloudflare.d.ts +12 -0
  65. package/dist/browser/presets/cloudflare.js +39 -0
  66. package/dist/browser/presets/cohere.d.ts +7 -0
  67. package/dist/browser/presets/cohere.js +22 -0
  68. package/dist/browser/presets/deepseek.d.ts +7 -0
  69. package/dist/browser/presets/deepseek.js +22 -0
  70. package/dist/browser/presets/digitalocean.d.ts +5 -0
  71. package/dist/browser/presets/digitalocean.js +16 -0
  72. package/dist/browser/presets/discord.d.ts +6 -0
  73. package/dist/browser/presets/discord.js +17 -0
  74. package/dist/browser/presets/elevenlabs.d.ts +6 -0
  75. package/dist/browser/presets/elevenlabs.js +20 -0
  76. package/dist/browser/presets/enhancers.d.ts +20 -0
  77. package/dist/browser/presets/enhancers.js +85 -0
  78. package/dist/browser/presets/fireworks.d.ts +7 -0
  79. package/dist/browser/presets/fireworks.js +22 -0
  80. package/dist/browser/presets/gcp.d.ts +34 -0
  81. package/dist/browser/presets/gcp.js +91 -0
  82. package/dist/browser/presets/gemini.d.ts +7 -0
  83. package/dist/browser/presets/gemini.js +23 -0
  84. package/dist/browser/presets/github.d.ts +6 -0
  85. package/dist/browser/presets/github.js +17 -0
  86. package/dist/browser/presets/gitlab.d.ts +6 -0
  87. package/dist/browser/presets/gitlab.js +16 -0
  88. package/dist/browser/presets/groq.d.ts +7 -0
  89. package/dist/browser/presets/groq.js +22 -0
  90. package/dist/browser/presets/hubspot.d.ts +9 -0
  91. package/dist/browser/presets/hubspot.js +28 -0
  92. package/dist/browser/presets/huggingface.d.ts +7 -0
  93. package/dist/browser/presets/huggingface.js +23 -0
  94. package/dist/browser/presets/index.d.ts +47 -0
  95. package/dist/browser/presets/index.js +47 -0
  96. package/dist/browser/presets/ios.d.ts +2 -0
  97. package/dist/browser/presets/ios.js +13 -0
  98. package/dist/browser/presets/linear.d.ts +5 -0
  99. package/dist/browser/presets/linear.js +16 -0
  100. package/dist/browser/presets/mailgun.d.ts +7 -0
  101. package/dist/browser/presets/mailgun.js +20 -0
  102. package/dist/browser/presets/meta.d.ts +10 -0
  103. package/dist/browser/presets/meta.js +33 -0
  104. package/dist/browser/presets/mistral.d.ts +7 -0
  105. package/dist/browser/presets/mistral.js +22 -0
  106. package/dist/browser/presets/notion.d.ts +6 -0
  107. package/dist/browser/presets/notion.js +17 -0
  108. package/dist/browser/presets/openai.d.ts +9 -0
  109. package/dist/browser/presets/openai.js +30 -0
  110. package/dist/browser/presets/oracle.d.ts +19 -0
  111. package/dist/browser/presets/oracle.js +117 -0
  112. package/dist/browser/presets/perplexity.d.ts +7 -0
  113. package/dist/browser/presets/perplexity.js +22 -0
  114. package/dist/browser/presets/pinecone.d.ts +8 -0
  115. package/dist/browser/presets/pinecone.js +42 -0
  116. package/dist/browser/presets/registry.d.ts +23 -0
  117. package/dist/browser/presets/registry.js +519 -0
  118. package/dist/browser/presets/replicate.d.ts +7 -0
  119. package/dist/browser/presets/replicate.js +23 -0
  120. package/dist/browser/presets/sendgrid.d.ts +6 -0
  121. package/dist/browser/presets/sendgrid.js +20 -0
  122. package/dist/browser/presets/sentry.d.ts +11 -0
  123. package/dist/browser/presets/sentry.js +48 -0
  124. package/dist/browser/presets/sinch.d.ts +9 -0
  125. package/dist/browser/presets/sinch.js +39 -0
  126. package/dist/browser/presets/slack.d.ts +5 -0
  127. package/dist/browser/presets/slack.js +16 -0
  128. package/dist/browser/presets/square.d.ts +10 -0
  129. package/dist/browser/presets/square.js +33 -0
  130. package/dist/browser/presets/stripe.d.ts +7 -0
  131. package/dist/browser/presets/stripe.js +23 -0
  132. package/dist/browser/presets/supabase.d.ts +6 -0
  133. package/dist/browser/presets/supabase.js +18 -0
  134. package/dist/browser/presets/tiktok.d.ts +10 -0
  135. package/dist/browser/presets/tiktok.js +38 -0
  136. package/dist/browser/presets/together.d.ts +7 -0
  137. package/dist/browser/presets/together.js +22 -0
  138. package/dist/browser/presets/twilio.d.ts +6 -0
  139. package/dist/browser/presets/twilio.js +17 -0
  140. package/dist/browser/presets/vercel.d.ts +6 -0
  141. package/dist/browser/presets/vercel.js +23 -0
  142. package/dist/browser/presets/vultr.d.ts +5 -0
  143. package/dist/browser/presets/vultr.js +16 -0
  144. package/dist/browser/presets/xai.d.ts +8 -0
  145. package/dist/browser/presets/xai.js +23 -0
  146. package/dist/browser/presets/youtube.d.ts +5 -0
  147. package/dist/browser/presets/youtube.js +20 -0
  148. package/dist/browser/recker.d.ts +8 -1
  149. package/dist/browser/recker.js +8 -2
  150. package/dist/browser/scrape/document.d.ts +5 -4
  151. package/dist/browser/scrape/document.js +89 -76
  152. package/dist/browser/scrape/element.d.ts +10 -8
  153. package/dist/browser/scrape/element.js +295 -81
  154. package/dist/browser/scrape/extractors.d.ts +11 -11
  155. package/dist/browser/scrape/extractors.js +145 -113
  156. package/dist/browser/scrape/parser/back.d.ts +1 -0
  157. package/dist/browser/scrape/parser/back.js +3 -0
  158. package/dist/browser/scrape/parser/index.d.ts +20 -0
  159. package/dist/browser/scrape/parser/index.js +19 -0
  160. package/dist/browser/scrape/parser/matcher.d.ts +30 -0
  161. package/dist/browser/scrape/parser/matcher.js +99 -0
  162. package/dist/browser/scrape/parser/nodes/comment.d.ts +12 -0
  163. package/dist/browser/scrape/parser/nodes/comment.js +21 -0
  164. package/dist/browser/scrape/parser/nodes/html.d.ts +110 -0
  165. package/dist/browser/scrape/parser/nodes/html.js +978 -0
  166. package/dist/browser/scrape/parser/nodes/node.d.ts +18 -0
  167. package/dist/browser/scrape/parser/nodes/node.js +31 -0
  168. package/dist/browser/scrape/parser/nodes/text.d.ts +14 -0
  169. package/dist/browser/scrape/parser/nodes/text.js +30 -0
  170. package/dist/browser/scrape/parser/nodes/type.d.ts +6 -0
  171. package/dist/browser/scrape/parser/nodes/type.js +7 -0
  172. package/dist/browser/scrape/parser/parse.d.ts +1 -0
  173. package/dist/browser/scrape/parser/parse.js +1 -0
  174. package/dist/browser/scrape/parser/valid.d.ts +2 -0
  175. package/dist/browser/scrape/parser/valid.js +5 -0
  176. package/dist/browser/scrape/parser/void-tag.d.ts +7 -0
  177. package/dist/browser/scrape/parser/void-tag.js +43 -0
  178. package/dist/browser/scrape/types.d.ts +7 -0
  179. package/dist/browser/seo/analyzer.d.ts +59 -0
  180. package/dist/browser/seo/analyzer.js +1399 -0
  181. package/dist/browser/seo/keywords.d.ts +16 -0
  182. package/dist/browser/seo/keywords.js +55 -0
  183. package/dist/browser/seo/rules/accessibility.d.ts +2 -0
  184. package/dist/browser/seo/rules/accessibility.js +733 -0
  185. package/dist/browser/seo/rules/ai-search.d.ts +2 -0
  186. package/dist/browser/seo/rules/ai-search.js +436 -0
  187. package/dist/browser/seo/rules/analytics.d.ts +2 -0
  188. package/dist/browser/seo/rules/analytics.js +306 -0
  189. package/dist/browser/seo/rules/best-practices.d.ts +2 -0
  190. package/dist/browser/seo/rules/best-practices.js +195 -0
  191. package/dist/browser/seo/rules/canonical.d.ts +12 -0
  192. package/dist/browser/seo/rules/canonical.js +270 -0
  193. package/dist/browser/seo/rules/content.d.ts +2 -0
  194. package/dist/browser/seo/rules/content.js +522 -0
  195. package/dist/browser/seo/rules/crawl.d.ts +2 -0
  196. package/dist/browser/seo/rules/crawl.js +435 -0
  197. package/dist/browser/seo/rules/cwv.d.ts +2 -0
  198. package/dist/browser/seo/rules/cwv.js +248 -0
  199. package/dist/browser/seo/rules/ecommerce.d.ts +2 -0
  200. package/dist/browser/seo/rules/ecommerce.js +312 -0
  201. package/dist/browser/seo/rules/i18n.d.ts +2 -0
  202. package/dist/browser/seo/rules/i18n.js +288 -0
  203. package/dist/browser/seo/rules/images.d.ts +2 -0
  204. package/dist/browser/seo/rules/images.js +255 -0
  205. package/dist/browser/seo/rules/index.d.ts +52 -0
  206. package/dist/browser/seo/rules/index.js +159 -0
  207. package/dist/browser/seo/rules/internal-linking.d.ts +2 -0
  208. package/dist/browser/seo/rules/internal-linking.js +394 -0
  209. package/dist/browser/seo/rules/links.d.ts +2 -0
  210. package/dist/browser/seo/rules/links.js +498 -0
  211. package/dist/browser/seo/rules/local.d.ts +2 -0
  212. package/dist/browser/seo/rules/local.js +289 -0
  213. package/dist/browser/seo/rules/meta.d.ts +2 -0
  214. package/dist/browser/seo/rules/meta.js +805 -0
  215. package/dist/browser/seo/rules/mobile.d.ts +2 -0
  216. package/dist/browser/seo/rules/mobile.js +161 -0
  217. package/dist/browser/seo/rules/performance.d.ts +2 -0
  218. package/dist/browser/seo/rules/performance.js +738 -0
  219. package/dist/browser/seo/rules/pwa.d.ts +2 -0
  220. package/dist/browser/seo/rules/pwa.js +299 -0
  221. package/dist/browser/seo/rules/readability.d.ts +2 -0
  222. package/dist/browser/seo/rules/readability.js +264 -0
  223. package/dist/browser/seo/rules/redirects.d.ts +16 -0
  224. package/dist/browser/seo/rules/redirects.js +199 -0
  225. package/dist/browser/seo/rules/resources.d.ts +2 -0
  226. package/dist/browser/seo/rules/resources.js +390 -0
  227. package/dist/browser/seo/rules/schema.d.ts +2 -0
  228. package/dist/browser/seo/rules/schema.js +379 -0
  229. package/dist/browser/seo/rules/security.d.ts +2 -0
  230. package/dist/browser/seo/rules/security.js +877 -0
  231. package/dist/browser/seo/rules/social.d.ts +2 -0
  232. package/dist/browser/seo/rules/social.js +603 -0
  233. package/dist/browser/seo/rules/structural.d.ts +2 -0
  234. package/dist/browser/seo/rules/structural.js +223 -0
  235. package/dist/browser/seo/rules/technical-advanced.d.ts +10 -0
  236. package/dist/browser/seo/rules/technical-advanced.js +289 -0
  237. package/dist/browser/seo/rules/technical.d.ts +2 -0
  238. package/dist/browser/seo/rules/technical.js +480 -0
  239. package/dist/browser/seo/rules/thresholds.d.ts +196 -0
  240. package/dist/browser/seo/rules/thresholds.js +118 -0
  241. package/dist/browser/seo/rules/types.d.ts +498 -0
  242. package/dist/browser/seo/rules/types.js +11 -0
  243. package/dist/browser/seo/types.d.ts +211 -0
  244. package/dist/browser/seo/types.js +1 -0
  245. package/dist/browser/transport/curl.d.ts +4 -0
  246. package/dist/browser/transport/curl.js +101 -0
  247. package/dist/browser/transport/undici.js +1 -2
  248. package/dist/browser/transport/worker.d.ts +18 -0
  249. package/dist/browser/transport/worker.js +278 -0
  250. package/dist/browser/types/index.d.ts +4 -1
  251. package/dist/browser/utils/binary-manager.d.ts +4 -0
  252. package/dist/browser/utils/binary-manager.js +72 -0
  253. package/dist/browser/utils/user-agent.js +2 -13
  254. package/dist/cache/indexed-db.d.ts +10 -0
  255. package/dist/cache/indexed-db.js +88 -0
  256. package/dist/cache/service-worker-cache.d.ts +18 -0
  257. package/dist/cache/service-worker-cache.js +103 -0
  258. package/dist/cli/commands/ai.d.ts +2 -0
  259. package/dist/cli/commands/ai.js +162 -0
  260. package/dist/cli/commands/bench.d.ts +2 -0
  261. package/dist/cli/commands/bench.js +51 -0
  262. package/dist/cli/commands/dns.d.ts +2 -0
  263. package/dist/cli/commands/dns.js +295 -0
  264. package/dist/cli/commands/har.d.ts +2 -0
  265. package/dist/cli/commands/har.js +171 -0
  266. package/dist/cli/commands/hls.d.ts +2 -0
  267. package/dist/cli/commands/hls.js +192 -0
  268. package/dist/cli/commands/network.d.ts +2 -0
  269. package/dist/cli/commands/network.js +288 -0
  270. package/dist/cli/commands/protocols.d.ts +2 -0
  271. package/dist/cli/commands/protocols.js +344 -0
  272. package/dist/cli/commands/scrape.d.ts +2 -0
  273. package/dist/cli/commands/scrape.js +176 -0
  274. package/dist/cli/commands/security.d.ts +2 -0
  275. package/dist/cli/commands/security.js +57 -0
  276. package/dist/cli/commands/seo.d.ts +2 -0
  277. package/dist/cli/commands/seo.js +125 -0
  278. package/dist/cli/commands/serve.d.ts +2 -0
  279. package/dist/cli/commands/serve.js +531 -0
  280. package/dist/cli/commands/spider.d.ts +3 -0
  281. package/dist/cli/commands/spider.js +456 -0
  282. package/dist/cli/commands/utils.d.ts +2 -0
  283. package/dist/cli/commands/utils.js +176 -0
  284. package/dist/cli/commands/vector.d.ts +2 -0
  285. package/dist/cli/commands/vector.js +158 -0
  286. package/dist/cli/handler.d.ts +2 -2
  287. package/dist/cli/handler.js +6 -6
  288. package/dist/cli/helpers.d.ts +7 -0
  289. package/dist/cli/helpers.js +128 -0
  290. package/dist/cli/index.js +96 -5228
  291. package/dist/cli/parser/help.d.ts +2 -0
  292. package/dist/cli/parser/help.js +52 -0
  293. package/dist/cli/parser/index.d.ts +3 -0
  294. package/dist/cli/parser/index.js +3 -0
  295. package/dist/cli/parser/parser.d.ts +4 -0
  296. package/dist/cli/parser/parser.js +146 -0
  297. package/dist/cli/parser/types.d.ts +41 -0
  298. package/dist/cli/parser/types.js +1 -0
  299. package/dist/cli/presets.d.ts +1 -1
  300. package/dist/cli/presets.js +1 -1
  301. package/dist/cli/router.d.ts +36 -0
  302. package/dist/cli/router.js +195 -0
  303. package/dist/cli/tui/ai-chat.js +1 -1
  304. package/dist/cli/tui/commands/context.d.ts +9 -0
  305. package/dist/cli/tui/commands/context.js +1 -0
  306. package/dist/cli/tui/commands/dns.d.ts +10 -0
  307. package/dist/cli/tui/commands/dns.js +461 -0
  308. package/dist/cli/tui/commands/hls.d.ts +2 -0
  309. package/dist/cli/tui/commands/hls.js +162 -0
  310. package/dist/cli/tui/commands/ip.d.ts +2 -0
  311. package/dist/cli/tui/commands/ip.js +45 -0
  312. package/dist/cli/tui/commands/network.d.ts +3 -0
  313. package/dist/cli/tui/commands/network.js +81 -0
  314. package/dist/cli/tui/commands/protocols.d.ts +6 -0
  315. package/dist/cli/tui/commands/protocols.js +531 -0
  316. package/dist/cli/tui/commands/security.d.ts +2 -0
  317. package/dist/cli/tui/commands/security.js +48 -0
  318. package/dist/cli/tui/commands/seo.d.ts +2 -0
  319. package/dist/cli/tui/commands/seo.js +74 -0
  320. package/dist/cli/tui/context.d.ts +12 -0
  321. package/dist/cli/tui/context.js +1 -0
  322. package/dist/cli/tui/shell.d.ts +11 -20
  323. package/dist/cli/tui/shell.js +216 -1873
  324. package/dist/constants/user-agents.d.ts +7 -0
  325. package/dist/constants/user-agents.js +7 -0
  326. package/dist/core/client.d.ts +2 -0
  327. package/dist/core/client.js +19 -1
  328. package/dist/index.d.ts +1 -0
  329. package/dist/index.js +1 -0
  330. package/dist/mcp/cli.js +2 -3
  331. package/dist/mcp/data/embeddings.json +1 -1
  332. package/dist/mcp/tools/network.js +298 -158
  333. package/dist/plugins/har-player.d.ts +23 -0
  334. package/dist/plugins/har-player.js +49 -0
  335. package/dist/plugins/har-recorder.d.ts +37 -3
  336. package/dist/plugins/har-recorder.js +116 -63
  337. package/dist/plugins/network-simulation.d.ts +7 -0
  338. package/dist/plugins/network-simulation.js +13 -0
  339. package/dist/presets/android.d.ts +2 -0
  340. package/dist/presets/android.js +16 -0
  341. package/dist/presets/chaturbate.d.ts +2 -0
  342. package/dist/presets/chaturbate.js +17 -0
  343. package/dist/presets/elevenlabs.d.ts +6 -0
  344. package/dist/presets/elevenlabs.js +20 -0
  345. package/dist/presets/enhancers.d.ts +20 -0
  346. package/dist/presets/enhancers.js +85 -0
  347. package/dist/presets/hubspot.d.ts +9 -0
  348. package/dist/presets/hubspot.js +28 -0
  349. package/dist/presets/index.d.ts +10 -0
  350. package/dist/presets/index.js +10 -0
  351. package/dist/presets/ios.d.ts +2 -0
  352. package/dist/presets/ios.js +13 -0
  353. package/dist/presets/pinecone.d.ts +8 -0
  354. package/dist/presets/pinecone.js +42 -0
  355. package/dist/presets/registry.js +60 -0
  356. package/dist/presets/sendgrid.d.ts +6 -0
  357. package/dist/presets/sendgrid.js +20 -0
  358. package/dist/presets/sentry.d.ts +11 -0
  359. package/dist/presets/sentry.js +48 -0
  360. package/dist/presets/square.d.ts +10 -0
  361. package/dist/presets/square.js +33 -0
  362. package/dist/recker.d.ts +3 -0
  363. package/dist/recker.js +4 -0
  364. package/dist/scrape/document.d.ts +5 -4
  365. package/dist/scrape/document.js +89 -76
  366. package/dist/scrape/element.d.ts +10 -8
  367. package/dist/scrape/element.js +295 -81
  368. package/dist/scrape/extractors.d.ts +11 -11
  369. package/dist/scrape/extractors.js +145 -113
  370. package/dist/scrape/index.d.ts +2 -0
  371. package/dist/scrape/index.js +1 -0
  372. package/dist/scrape/parser/back.d.ts +1 -0
  373. package/dist/scrape/parser/back.js +3 -0
  374. package/dist/scrape/parser/index.d.ts +20 -0
  375. package/dist/scrape/parser/index.js +19 -0
  376. package/dist/scrape/parser/matcher.d.ts +30 -0
  377. package/dist/scrape/parser/matcher.js +99 -0
  378. package/dist/scrape/parser/nodes/comment.d.ts +12 -0
  379. package/dist/scrape/parser/nodes/comment.js +21 -0
  380. package/dist/scrape/parser/nodes/html.d.ts +110 -0
  381. package/dist/scrape/parser/nodes/html.js +978 -0
  382. package/dist/scrape/parser/nodes/node.d.ts +18 -0
  383. package/dist/scrape/parser/nodes/node.js +31 -0
  384. package/dist/scrape/parser/nodes/text.d.ts +14 -0
  385. package/dist/scrape/parser/nodes/text.js +30 -0
  386. package/dist/scrape/parser/nodes/type.d.ts +6 -0
  387. package/dist/scrape/parser/nodes/type.js +7 -0
  388. package/dist/scrape/parser/parse.d.ts +1 -0
  389. package/dist/scrape/parser/parse.js +1 -0
  390. package/dist/scrape/parser/valid.d.ts +2 -0
  391. package/dist/scrape/parser/valid.js +5 -0
  392. package/dist/scrape/parser/void-tag.d.ts +7 -0
  393. package/dist/scrape/parser/void-tag.js +43 -0
  394. package/dist/scrape/spider.d.ts +19 -0
  395. package/dist/scrape/spider.js +28 -3
  396. package/dist/scrape/types.d.ts +7 -0
  397. package/dist/seo/analyzer.d.ts +15 -5
  398. package/dist/seo/analyzer.js +636 -175
  399. package/dist/seo/formatter.d.ts +16 -0
  400. package/dist/seo/formatter.js +228 -0
  401. package/dist/seo/index.d.ts +2 -0
  402. package/dist/seo/index.js +1 -0
  403. package/dist/seo/keywords.d.ts +16 -0
  404. package/dist/seo/keywords.js +55 -0
  405. package/dist/seo/rules/accessibility.js +96 -57
  406. package/dist/seo/rules/ai-search.js +44 -31
  407. package/dist/seo/rules/analytics.d.ts +2 -0
  408. package/dist/seo/rules/analytics.js +306 -0
  409. package/dist/seo/rules/best-practices.js +21 -14
  410. package/dist/seo/rules/canonical.js +53 -32
  411. package/dist/seo/rules/content.js +317 -31
  412. package/dist/seo/rules/crawl.js +55 -40
  413. package/dist/seo/rules/cwv.js +21 -15
  414. package/dist/seo/rules/ecommerce.js +82 -22
  415. package/dist/seo/rules/i18n.js +75 -36
  416. package/dist/seo/rules/images.js +109 -30
  417. package/dist/seo/rules/index.js +2 -0
  418. package/dist/seo/rules/internal-linking.js +58 -39
  419. package/dist/seo/rules/links.js +79 -52
  420. package/dist/seo/rules/local.js +49 -25
  421. package/dist/seo/rules/meta.js +339 -81
  422. package/dist/seo/rules/mobile.js +112 -2
  423. package/dist/seo/rules/performance.js +434 -66
  424. package/dist/seo/rules/pwa.js +36 -39
  425. package/dist/seo/rules/readability.js +31 -22
  426. package/dist/seo/rules/redirects.js +21 -15
  427. package/dist/seo/rules/resources.js +59 -42
  428. package/dist/seo/rules/schema.js +333 -8
  429. package/dist/seo/rules/security.js +142 -80
  430. package/dist/seo/rules/social.js +277 -47
  431. package/dist/seo/rules/structural.js +87 -19
  432. package/dist/seo/rules/technical-advanced.js +30 -24
  433. package/dist/seo/rules/technical.js +243 -42
  434. package/dist/seo/rules/types.d.ts +53 -1
  435. package/dist/seo/seo-spider.d.ts +22 -0
  436. package/dist/seo/seo-spider.js +77 -13
  437. package/dist/seo/types.d.ts +8 -1
  438. package/dist/seo/validators/llms-txt.js +19 -0
  439. package/dist/seo/validators/rss.d.ts +11 -0
  440. package/dist/seo/validators/rss.js +93 -0
  441. package/dist/seo/validators/sitemap.js +36 -26
  442. package/dist/transport/curl.d.ts +4 -0
  443. package/dist/transport/curl.js +101 -0
  444. package/dist/transport/udp.js +0 -1
  445. package/dist/transport/undici.js +1 -2
  446. package/dist/transport/worker.d.ts +18 -0
  447. package/dist/transport/worker.js +278 -0
  448. package/dist/types/index.d.ts +4 -1
  449. package/dist/utils/binary-manager.d.ts +4 -0
  450. package/dist/utils/binary-manager.js +72 -0
  451. package/dist/utils/optional-require.d.ts +7 -8
  452. package/dist/utils/optional-require.js +2 -21
  453. package/dist/utils/upload.d.ts +6 -0
  454. package/dist/utils/upload.js +11 -0
  455. package/dist/utils/user-agent.js +2 -13
  456. package/dist/version.js +1 -1
  457. package/package.json +12 -6
  458. package/dist/browser/utils/optional-require.d.ts +0 -19
  459. package/dist/browser/utils/optional-require.js +0 -105
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const pwaRules: SeoRule[];
@@ -0,0 +1,299 @@
1
+ import { createResult } from './types.js';
2
+ export const pwaRules = [
3
+ {
4
+ id: 'pwa-manifest-link',
5
+ name: 'Web App Manifest',
6
+ category: 'technical',
7
+ severity: 'info',
8
+ description: 'Pages should link to a web app manifest for PWA support',
9
+ check: (ctx) => {
10
+ if (!ctx.hasManifest) {
11
+ return createResult({ id: 'pwa-manifest-link', name: 'Web App Manifest', category: 'technical', severity: 'info' }, 'info', 'No web app manifest linked', {
12
+ recommendation: 'Add a manifest.json for PWA installability',
13
+ evidence: {
14
+ expected: '<link rel="manifest" href="/manifest.json">',
15
+ impact: 'Required for "Add to Home Screen" and PWA features',
16
+ learnMore: 'https://web.dev/add-manifest/',
17
+ },
18
+ });
19
+ }
20
+ return createResult({ id: 'pwa-manifest-link', name: 'Web App Manifest', category: 'technical', severity: 'info' }, 'pass', `Manifest linked${ctx.manifestUrl ? `: ${ctx.manifestUrl}` : ''}`);
21
+ },
22
+ },
23
+ {
24
+ id: 'pwa-theme-color',
25
+ name: 'Theme Color',
26
+ category: 'mobile',
27
+ severity: 'info',
28
+ description: 'Pages should define a theme color for browser UI',
29
+ check: (ctx) => {
30
+ if (!ctx.themeColor) {
31
+ return createResult({ id: 'pwa-theme-color', name: 'Theme Color', category: 'mobile', severity: 'info' }, 'info', 'No theme-color meta tag', {
32
+ recommendation: 'Add theme-color for browser UI customization',
33
+ evidence: {
34
+ expected: '<meta name="theme-color" content="#4285f4">',
35
+ impact: 'Controls browser toolbar color on mobile devices',
36
+ },
37
+ });
38
+ }
39
+ return createResult({ id: 'pwa-theme-color', name: 'Theme Color', category: 'mobile', severity: 'info' }, 'pass', `Theme color: ${ctx.themeColor}`);
40
+ },
41
+ },
42
+ {
43
+ id: 'pwa-apple-touch-icon',
44
+ name: 'Apple Touch Icon',
45
+ category: 'mobile',
46
+ severity: 'info',
47
+ description: 'iOS devices need apple-touch-icon for home screen',
48
+ check: (ctx) => {
49
+ if (!ctx.hasAppleTouchIcon) {
50
+ return createResult({ id: 'pwa-apple-touch-icon', name: 'Apple Touch Icon', category: 'mobile', severity: 'info' }, 'info', 'No apple-touch-icon found', {
51
+ recommendation: 'Add apple-touch-icon for iOS home screen',
52
+ evidence: {
53
+ expected: '<link rel="apple-touch-icon" href="/apple-touch-icon.png">',
54
+ impact: 'iOS uses this icon when user adds site to home screen',
55
+ example: 'Recommended size: 180x180 pixels',
56
+ },
57
+ });
58
+ }
59
+ return createResult({ id: 'pwa-apple-touch-icon', name: 'Apple Touch Icon', category: 'mobile', severity: 'info' }, 'pass', 'Apple touch icon present');
60
+ },
61
+ },
62
+ {
63
+ id: 'pwa-apple-mobile-capable',
64
+ name: 'Apple Mobile Web App',
65
+ category: 'mobile',
66
+ severity: 'info',
67
+ description: 'Enable standalone mode on iOS devices',
68
+ check: (ctx) => {
69
+ if (!ctx.hasAppleMobileWebAppCapable) {
70
+ return createResult({ id: 'pwa-apple-mobile-capable', name: 'Apple Mobile Web App', category: 'mobile', severity: 'info' }, 'info', 'No apple-mobile-web-app-capable meta tag', {
71
+ recommendation: 'Add for full-screen iOS experience',
72
+ evidence: {
73
+ expected: '<meta name="apple-mobile-web-app-capable" content="yes">',
74
+ impact: 'Enables standalone app mode when launched from home screen',
75
+ },
76
+ });
77
+ }
78
+ return createResult({ id: 'pwa-apple-mobile-capable', name: 'Apple Mobile Web App', category: 'mobile', severity: 'info' }, 'pass', 'Apple mobile web app capable enabled');
79
+ },
80
+ },
81
+ {
82
+ id: 'pwa-apple-status-bar',
83
+ name: 'Apple Status Bar Style',
84
+ category: 'mobile',
85
+ severity: 'info',
86
+ description: 'Configure iOS status bar appearance',
87
+ check: (ctx) => {
88
+ if (!ctx.hasAppleMobileWebAppCapable) {
89
+ return createResult({ id: 'pwa-apple-status-bar', name: 'Apple Status Bar Style', category: 'mobile', severity: 'info' }, 'info', 'Not applicable (apple-mobile-web-app-capable not set)', {
90
+ recommendation: 'First enable apple-mobile-web-app-capable, then configure status bar',
91
+ });
92
+ }
93
+ if (!ctx.appleStatusBarStyle) {
94
+ return createResult({ id: 'pwa-apple-status-bar', name: 'Apple Status Bar Style', category: 'mobile', severity: 'info' }, 'info', 'No apple-mobile-web-app-status-bar-style defined', {
95
+ recommendation: 'Define status bar style for iOS',
96
+ evidence: {
97
+ expected: '<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">',
98
+ example: 'Options: default, black, black-translucent',
99
+ },
100
+ });
101
+ }
102
+ return createResult({ id: 'pwa-apple-status-bar', name: 'Apple Status Bar Style', category: 'mobile', severity: 'info' }, 'pass', `Status bar style: ${ctx.appleStatusBarStyle}`);
103
+ },
104
+ },
105
+ {
106
+ id: 'pwa-maskable-icon',
107
+ name: 'Maskable Icon',
108
+ category: 'mobile',
109
+ severity: 'info',
110
+ description: 'Manifest should include maskable icons for Android',
111
+ check: (ctx) => {
112
+ if (!ctx.hasMaskableIcon) {
113
+ return createResult({ id: 'pwa-maskable-icon', name: 'Maskable Icon', category: 'mobile', severity: 'info' }, 'info', 'No maskable icon in manifest', {
114
+ recommendation: 'Add maskable icon for adaptive icons on Android',
115
+ evidence: {
116
+ example: `{
117
+ "icons": [{
118
+ "src": "/icon-maskable.png",
119
+ "sizes": "512x512",
120
+ "type": "image/png",
121
+ "purpose": "maskable"
122
+ }]
123
+ }`,
124
+ impact: 'Maskable icons adapt to different Android icon shapes',
125
+ learnMore: 'https://web.dev/maskable-icon/',
126
+ },
127
+ });
128
+ }
129
+ return createResult({ id: 'pwa-maskable-icon', name: 'Maskable Icon', category: 'mobile', severity: 'info' }, 'pass', 'Maskable icon defined');
130
+ },
131
+ },
132
+ {
133
+ id: 'pwa-start-url',
134
+ name: 'Start URL',
135
+ category: 'technical',
136
+ severity: 'info',
137
+ description: 'Manifest should define a start_url',
138
+ check: (ctx) => {
139
+ if (!ctx.hasManifest) {
140
+ return createResult({ id: 'pwa-start-url', name: 'Start URL', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
141
+ recommendation: 'First add a web app manifest, then define start_url',
142
+ });
143
+ }
144
+ if (!ctx.manifestStartUrl) {
145
+ return createResult({ id: 'pwa-start-url', name: 'Start URL', category: 'technical', severity: 'info' }, 'info', 'Manifest missing start_url', {
146
+ recommendation: 'Define start_url in manifest for PWA launch',
147
+ evidence: {
148
+ expected: '"start_url": "/?source=pwa"',
149
+ impact: 'Controls which page opens when PWA is launched',
150
+ },
151
+ });
152
+ }
153
+ return createResult({ id: 'pwa-start-url', name: 'Start URL', category: 'technical', severity: 'info' }, 'pass', `Start URL: ${ctx.manifestStartUrl}`);
154
+ },
155
+ },
156
+ {
157
+ id: 'pwa-display-mode',
158
+ name: 'Display Mode',
159
+ category: 'technical',
160
+ severity: 'info',
161
+ description: 'Manifest should define display mode',
162
+ check: (ctx) => {
163
+ if (!ctx.hasManifest) {
164
+ return createResult({ id: 'pwa-display-mode', name: 'Display Mode', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
165
+ recommendation: 'First add a web app manifest, then define display mode',
166
+ });
167
+ }
168
+ const display = ctx.manifestDisplay;
169
+ if (!display) {
170
+ return createResult({ id: 'pwa-display-mode', name: 'Display Mode', category: 'technical', severity: 'info' }, 'info', 'Manifest missing display mode', {
171
+ recommendation: 'Set display mode for app-like experience',
172
+ evidence: {
173
+ expected: '"display": "standalone"',
174
+ example: 'Options: fullscreen, standalone, minimal-ui, browser',
175
+ },
176
+ });
177
+ }
178
+ const goodModes = ['standalone', 'fullscreen', 'minimal-ui'];
179
+ if (!goodModes.includes(display)) {
180
+ return createResult({ id: 'pwa-display-mode', name: 'Display Mode', category: 'technical', severity: 'info' }, 'info', `Display mode: ${display} (browser-like)`, {
181
+ recommendation: 'Consider standalone or fullscreen for app-like experience',
182
+ });
183
+ }
184
+ return createResult({ id: 'pwa-display-mode', name: 'Display Mode', category: 'technical', severity: 'info' }, 'pass', `Display mode: ${display}`);
185
+ },
186
+ },
187
+ {
188
+ id: 'pwa-scope',
189
+ name: 'Navigation Scope',
190
+ category: 'technical',
191
+ severity: 'info',
192
+ description: 'Manifest should define navigation scope',
193
+ check: (ctx) => {
194
+ if (!ctx.hasManifest) {
195
+ return createResult({ id: 'pwa-scope', name: 'Navigation Scope', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
196
+ recommendation: 'First add a web app manifest, then define scope',
197
+ });
198
+ }
199
+ if (!ctx.manifestScope) {
200
+ return createResult({ id: 'pwa-scope', name: 'Navigation Scope', category: 'technical', severity: 'info' }, 'info', 'Manifest missing scope', {
201
+ recommendation: 'Define scope to control PWA navigation boundaries',
202
+ evidence: {
203
+ expected: '"scope": "/"',
204
+ impact: 'Limits which URLs are part of the app experience',
205
+ },
206
+ });
207
+ }
208
+ return createResult({ id: 'pwa-scope', name: 'Navigation Scope', category: 'technical', severity: 'info' }, 'pass', `Scope: ${ctx.manifestScope}`);
209
+ },
210
+ },
211
+ {
212
+ id: 'pwa-icons-sizes',
213
+ name: 'Icon Sizes',
214
+ category: 'mobile',
215
+ severity: 'info',
216
+ description: 'Manifest should include multiple icon sizes',
217
+ check: (ctx) => {
218
+ if (!ctx.hasManifest) {
219
+ return createResult({ id: 'pwa-icons-sizes', name: 'Icon Sizes', category: 'mobile', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
220
+ recommendation: 'First add a web app manifest with icons',
221
+ });
222
+ }
223
+ const sizes = ctx.manifestIconSizes || [];
224
+ const requiredSizes = [192, 512];
225
+ const missingSizes = requiredSizes.filter(s => !sizes.includes(s));
226
+ if (missingSizes.length > 0) {
227
+ return createResult({ id: 'pwa-icons-sizes', name: 'Icon Sizes', category: 'mobile', severity: 'info' }, 'info', `Missing icon sizes: ${missingSizes.join(', ')}px`, {
228
+ recommendation: 'Add icons in required sizes',
229
+ evidence: {
230
+ found: sizes.length > 0 ? sizes.map(s => `${s}px`).join(', ') : 'No icons',
231
+ expected: '192x192 and 512x512 minimum',
232
+ impact: 'Required for Chrome "Add to Home Screen" prompt',
233
+ },
234
+ });
235
+ }
236
+ return createResult({ id: 'pwa-icons-sizes', name: 'Icon Sizes', category: 'mobile', severity: 'info' }, 'pass', `Icon sizes: ${sizes.map(s => `${s}px`).join(', ')}`);
237
+ },
238
+ },
239
+ {
240
+ id: 'pwa-short-name',
241
+ name: 'Short Name',
242
+ category: 'technical',
243
+ severity: 'info',
244
+ description: 'Manifest should have a short_name for home screen',
245
+ check: (ctx) => {
246
+ if (!ctx.hasManifest) {
247
+ return createResult({ id: 'pwa-short-name', name: 'Short Name', category: 'technical', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
248
+ recommendation: 'First add a web app manifest with name/short_name',
249
+ });
250
+ }
251
+ const shortName = ctx.manifestShortName;
252
+ const name = ctx.manifestName;
253
+ if (!shortName && !name) {
254
+ return createResult({ id: 'pwa-short-name', name: 'Short Name', category: 'technical', severity: 'info' }, 'info', 'Manifest missing name and short_name', {
255
+ recommendation: 'Add short_name (max 12 chars) for home screen label',
256
+ evidence: {
257
+ expected: '"short_name": "MyApp"',
258
+ impact: 'Used as app label on home screen',
259
+ },
260
+ });
261
+ }
262
+ if (shortName && shortName.length > 12) {
263
+ return createResult({ id: 'pwa-short-name', name: 'Short Name', category: 'technical', severity: 'info' }, 'info', `Short name too long: ${shortName.length} chars`, {
264
+ recommendation: 'Keep short_name under 12 characters',
265
+ evidence: {
266
+ found: shortName,
267
+ expected: 'Max 12 characters',
268
+ impact: 'May be truncated on home screen',
269
+ },
270
+ });
271
+ }
272
+ return createResult({ id: 'pwa-short-name', name: 'Short Name', category: 'technical', severity: 'info' }, 'pass', `App name: ${shortName || name}`);
273
+ },
274
+ },
275
+ {
276
+ id: 'pwa-background-color',
277
+ name: 'Background Color',
278
+ category: 'mobile',
279
+ severity: 'info',
280
+ description: 'Manifest should define background_color for splash screen',
281
+ check: (ctx) => {
282
+ if (!ctx.hasManifest) {
283
+ return createResult({ id: 'pwa-background-color', name: 'Background Color', category: 'mobile', severity: 'info' }, 'info', 'Not applicable (no manifest)', {
284
+ recommendation: 'First add a web app manifest, then define background_color',
285
+ });
286
+ }
287
+ if (!ctx.manifestBackgroundColor) {
288
+ return createResult({ id: 'pwa-background-color', name: 'Background Color', category: 'mobile', severity: 'info' }, 'info', 'Manifest missing background_color', {
289
+ recommendation: 'Define background_color for splash screen',
290
+ evidence: {
291
+ expected: '"background_color": "#ffffff"',
292
+ impact: 'Shown as splash screen background during app launch',
293
+ },
294
+ });
295
+ }
296
+ return createResult({ id: 'pwa-background-color', name: 'Background Color', category: 'mobile', severity: 'info' }, 'pass', `Background color: ${ctx.manifestBackgroundColor}`);
297
+ },
298
+ },
299
+ ];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const readabilityRules: SeoRule[];
@@ -0,0 +1,264 @@
1
+ import { createResult } from './types.js';
2
+ export const readabilityRules = [
3
+ {
4
+ id: 'readability-flesch-score',
5
+ name: 'Flesch Reading Ease',
6
+ category: 'content',
7
+ severity: 'info',
8
+ description: 'Content should be easy to read for the target audience',
9
+ check: (ctx) => {
10
+ if (ctx.fleschReadingEase === undefined) {
11
+ return createResult({ id: 'readability-flesch-score', name: 'Flesch Reading Ease', category: 'content', severity: 'info' }, 'info', 'Not applicable (Flesch Reading Ease data unavailable)', { recommendation: 'This rule analyzes content readability using the Flesch Reading Ease formula' });
12
+ }
13
+ const score = ctx.fleschReadingEase;
14
+ let level;
15
+ let status;
16
+ if (score >= 60) {
17
+ level = score >= 80 ? 'Easy' : score >= 70 ? 'Fairly Easy' : 'Standard';
18
+ status = 'pass';
19
+ }
20
+ else if (score >= 50) {
21
+ level = 'Fairly Difficult';
22
+ status = 'info';
23
+ }
24
+ else if (score >= 30) {
25
+ level = 'Difficult';
26
+ status = 'warn';
27
+ }
28
+ else {
29
+ level = 'Very Difficult';
30
+ status = 'warn';
31
+ }
32
+ if (status === 'warn') {
33
+ return createResult({ id: 'readability-flesch-score', name: 'Flesch Reading Ease', category: 'content', severity: 'info' }, status, `Flesch score: ${Math.round(score)} (${level})`, {
34
+ recommendation: 'Simplify content for better readability',
35
+ evidence: {
36
+ found: Math.round(score),
37
+ expected: '60-70 for general web content',
38
+ impact: 'Difficult content has higher bounce rates',
39
+ learnMore: 'https://en.wikipedia.org/wiki/Flesch%E2%80%93Kincaid_readability_tests',
40
+ },
41
+ });
42
+ }
43
+ return createResult({ id: 'readability-flesch-score', name: 'Flesch Reading Ease', category: 'content', severity: 'info' }, status, `Flesch score: ${Math.round(score)} (${level})`);
44
+ },
45
+ },
46
+ {
47
+ id: 'readability-sentence-length',
48
+ name: 'Sentence Length',
49
+ category: 'content',
50
+ severity: 'info',
51
+ description: 'Sentences should be concise for better readability',
52
+ check: (ctx) => {
53
+ if (ctx.avgSentenceLength === undefined) {
54
+ return createResult({ id: 'readability-sentence-length', name: 'Sentence Length', category: 'content', severity: 'info' }, 'info', 'Not applicable (sentence length data unavailable)', { recommendation: 'This rule checks average sentence length for optimal readability' });
55
+ }
56
+ const avgLength = ctx.avgSentenceLength;
57
+ if (avgLength > 30) {
58
+ return createResult({ id: 'readability-sentence-length', name: 'Sentence Length', category: 'content', severity: 'info' }, 'warn', `Average sentence length: ${Math.round(avgLength)} words (too long)`, {
59
+ recommendation: 'Break long sentences into shorter ones',
60
+ evidence: {
61
+ found: Math.round(avgLength),
62
+ expected: '15-20 words per sentence ideal, max 25',
63
+ impact: 'Long sentences are harder to understand on mobile',
64
+ },
65
+ });
66
+ }
67
+ if (avgLength > 25) {
68
+ return createResult({ id: 'readability-sentence-length', name: 'Sentence Length', category: 'content', severity: 'info' }, 'info', `Average sentence length: ${Math.round(avgLength)} words (slightly long)`, {
69
+ recommendation: 'Consider shortening some sentences',
70
+ evidence: {
71
+ found: Math.round(avgLength),
72
+ expected: '15-20 words per sentence',
73
+ },
74
+ });
75
+ }
76
+ return createResult({ id: 'readability-sentence-length', name: 'Sentence Length', category: 'content', severity: 'info' }, 'pass', `Average sentence length: ${Math.round(avgLength)} words`);
77
+ },
78
+ },
79
+ {
80
+ id: 'readability-paragraph-length',
81
+ name: 'Paragraph Length',
82
+ category: 'content',
83
+ severity: 'info',
84
+ description: 'Paragraphs should be short for web readability',
85
+ check: (ctx) => {
86
+ if (ctx.avgParagraphLength === undefined) {
87
+ return createResult({ id: 'readability-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'info' }, 'info', 'Not applicable (paragraph length data unavailable)', { recommendation: 'This rule checks average paragraph length for web readability' });
88
+ }
89
+ const avgLength = ctx.avgParagraphLength;
90
+ if (avgLength > 150) {
91
+ return createResult({ id: 'readability-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'info' }, 'warn', `Average paragraph length: ${Math.round(avgLength)} words (too long)`, {
92
+ recommendation: 'Break paragraphs into smaller chunks',
93
+ evidence: {
94
+ found: Math.round(avgLength),
95
+ expected: '40-80 words per paragraph for web',
96
+ impact: 'Wall of text reduces engagement and increases bounce rate',
97
+ },
98
+ });
99
+ }
100
+ if (avgLength > 100) {
101
+ return createResult({ id: 'readability-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'info' }, 'info', `Average paragraph length: ${Math.round(avgLength)} words`, {
102
+ recommendation: 'Consider breaking longer paragraphs',
103
+ });
104
+ }
105
+ return createResult({ id: 'readability-paragraph-length', name: 'Paragraph Length', category: 'content', severity: 'info' }, 'pass', `Average paragraph length: ${Math.round(avgLength)} words`);
106
+ },
107
+ },
108
+ {
109
+ id: 'readability-passive-voice',
110
+ name: 'Passive Voice',
111
+ category: 'content',
112
+ severity: 'info',
113
+ description: 'Limit passive voice for clearer writing',
114
+ check: (ctx) => {
115
+ if (ctx.passiveVoicePercentage === undefined) {
116
+ return createResult({ id: 'readability-passive-voice', name: 'Passive Voice', category: 'content', severity: 'info' }, 'info', 'Not applicable (passive voice data unavailable)', { recommendation: 'This rule checks passive voice usage for clearer writing' });
117
+ }
118
+ const percentage = ctx.passiveVoicePercentage;
119
+ if (percentage > 20) {
120
+ return createResult({ id: 'readability-passive-voice', name: 'Passive Voice', category: 'content', severity: 'info' }, 'warn', `Passive voice: ${Math.round(percentage)}% of sentences`, {
121
+ recommendation: 'Convert passive sentences to active voice',
122
+ evidence: {
123
+ found: `${Math.round(percentage)}%`,
124
+ expected: 'Less than 10% passive voice',
125
+ example: 'Instead of "The button was clicked by the user" → "The user clicked the button"',
126
+ },
127
+ });
128
+ }
129
+ if (percentage > 15) {
130
+ return createResult({ id: 'readability-passive-voice', name: 'Passive Voice', category: 'content', severity: 'info' }, 'info', `Passive voice: ${Math.round(percentage)}% of sentences`, {
131
+ recommendation: 'Consider reducing passive voice usage',
132
+ });
133
+ }
134
+ return createResult({ id: 'readability-passive-voice', name: 'Passive Voice', category: 'content', severity: 'info' }, 'pass', `Passive voice usage acceptable (${Math.round(percentage)}%)`);
135
+ },
136
+ },
137
+ {
138
+ id: 'readability-transition-words',
139
+ name: 'Transition Words',
140
+ category: 'content',
141
+ severity: 'info',
142
+ description: 'Use transition words for better flow',
143
+ check: (ctx) => {
144
+ if (ctx.transitionWordPercentage === undefined) {
145
+ return createResult({ id: 'readability-transition-words', name: 'Transition Words', category: 'content', severity: 'info' }, 'info', 'Not applicable (transition word data unavailable)', { recommendation: 'This rule checks for transition words that improve content flow' });
146
+ }
147
+ const percentage = ctx.transitionWordPercentage;
148
+ if (percentage < 20) {
149
+ return createResult({ id: 'readability-transition-words', name: 'Transition Words', category: 'content', severity: 'info' }, 'info', `Transition words: ${Math.round(percentage)}% of sentences`, {
150
+ recommendation: 'Add transition words for better content flow',
151
+ evidence: {
152
+ found: `${Math.round(percentage)}%`,
153
+ expected: 'At least 30% of sentences',
154
+ example: 'Words like: however, therefore, additionally, furthermore, for example',
155
+ impact: 'Transition words improve comprehension and engagement',
156
+ },
157
+ });
158
+ }
159
+ return createResult({ id: 'readability-transition-words', name: 'Transition Words', category: 'content', severity: 'info' }, 'pass', `Transition words: ${Math.round(percentage)}% of sentences`);
160
+ },
161
+ },
162
+ {
163
+ id: 'readability-subheading-distribution',
164
+ name: 'Subheading Distribution',
165
+ category: 'content',
166
+ severity: 'info',
167
+ description: 'Break content with subheadings every 300 words',
168
+ check: (ctx) => {
169
+ if (!ctx.wordCount || !ctx.h2Count) {
170
+ return createResult({ id: 'readability-subheading-distribution', name: 'Subheading Distribution', category: 'content', severity: 'info' }, 'info', 'Not applicable (word count or heading data unavailable)', { recommendation: 'This rule checks for proper subheading distribution to break up content' });
171
+ }
172
+ const wordsPerSubheading = ctx.wordCount / (ctx.h2Count + 1);
173
+ if (ctx.wordCount > 500 && wordsPerSubheading > 400) {
174
+ return createResult({ id: 'readability-subheading-distribution', name: 'Subheading Distribution', category: 'content', severity: 'info' }, 'warn', `${Math.round(wordsPerSubheading)} words between subheadings (too many)`, {
175
+ recommendation: 'Add more H2/H3 subheadings to break up content',
176
+ evidence: {
177
+ found: `${ctx.h2Count} subheadings for ${ctx.wordCount} words`,
178
+ expected: 'One subheading every 250-350 words',
179
+ impact: 'Subheadings improve scannability and featured snippet eligibility',
180
+ },
181
+ });
182
+ }
183
+ if (ctx.wordCount > 300 && wordsPerSubheading > 300) {
184
+ return createResult({ id: 'readability-subheading-distribution', name: 'Subheading Distribution', category: 'content', severity: 'info' }, 'info', `${Math.round(wordsPerSubheading)} words per section`, {
185
+ recommendation: 'Consider adding more subheadings',
186
+ });
187
+ }
188
+ return createResult({ id: 'readability-subheading-distribution', name: 'Subheading Distribution', category: 'content', severity: 'info' }, 'pass', `${Math.round(wordsPerSubheading)} words per section (good)`);
189
+ },
190
+ },
191
+ {
192
+ id: 'readability-text-variety',
193
+ name: 'Text Variety',
194
+ category: 'content',
195
+ severity: 'info',
196
+ description: 'Use varied sentence structures and word choices',
197
+ check: (ctx) => {
198
+ if (ctx.consecutiveSentenceStarts === undefined) {
199
+ return createResult({ id: 'readability-text-variety', name: 'Text Variety', category: 'content', severity: 'info' }, 'info', 'Not applicable (sentence variety data unavailable)', { recommendation: 'This rule checks for varied sentence structures and beginnings' });
200
+ }
201
+ if (ctx.consecutiveSentenceStarts > 3) {
202
+ return createResult({ id: 'readability-text-variety', name: 'Text Variety', category: 'content', severity: 'info' }, 'info', `${ctx.consecutiveSentenceStarts} consecutive sentences start similarly`, {
203
+ recommendation: 'Vary sentence beginnings for better flow',
204
+ evidence: {
205
+ impact: 'Repetitive sentence structures feel monotonous',
206
+ },
207
+ });
208
+ }
209
+ return createResult({ id: 'readability-text-variety', name: 'Text Variety', category: 'content', severity: 'info' }, 'pass', 'Good sentence variety');
210
+ },
211
+ },
212
+ {
213
+ id: 'readability-word-complexity',
214
+ name: 'Word Complexity',
215
+ category: 'content',
216
+ severity: 'info',
217
+ description: 'Avoid overly complex vocabulary',
218
+ check: (ctx) => {
219
+ if (ctx.complexWordPercentage === undefined) {
220
+ return createResult({ id: 'readability-word-complexity', name: 'Word Complexity', category: 'content', severity: 'info' }, 'info', 'Not applicable (word complexity data unavailable)', { recommendation: 'This rule checks for complex vocabulary that may reduce readability' });
221
+ }
222
+ if (ctx.complexWordPercentage > 15) {
223
+ return createResult({ id: 'readability-word-complexity', name: 'Word Complexity', category: 'content', severity: 'info' }, 'warn', `Complex words: ${Math.round(ctx.complexWordPercentage)}%`, {
224
+ recommendation: 'Use simpler vocabulary where possible',
225
+ evidence: {
226
+ found: `${Math.round(ctx.complexWordPercentage)}% words with 3+ syllables`,
227
+ expected: 'Less than 10% complex words for general audience',
228
+ impact: 'Complex vocabulary reduces comprehension',
229
+ },
230
+ });
231
+ }
232
+ if (ctx.complexWordPercentage > 10) {
233
+ return createResult({ id: 'readability-word-complexity', name: 'Word Complexity', category: 'content', severity: 'info' }, 'info', `Complex words: ${Math.round(ctx.complexWordPercentage)}%`);
234
+ }
235
+ return createResult({ id: 'readability-word-complexity', name: 'Word Complexity', category: 'content', severity: 'info' }, 'pass', `Word complexity acceptable (${Math.round(ctx.complexWordPercentage)}%)`);
236
+ },
237
+ },
238
+ {
239
+ id: 'readability-list-usage',
240
+ name: 'List Usage',
241
+ category: 'content',
242
+ severity: 'info',
243
+ description: 'Use lists to improve scannability',
244
+ check: (ctx) => {
245
+ if (!ctx.wordCount || ctx.listCount === undefined) {
246
+ return createResult({ id: 'readability-list-usage', name: 'List Usage', category: 'content', severity: 'info' }, 'info', 'Not applicable (word count or list data unavailable)', { recommendation: 'This rule checks for proper use of lists to improve scannability' });
247
+ }
248
+ if (ctx.wordCount > 500 && ctx.listCount === 0) {
249
+ return createResult({ id: 'readability-list-usage', name: 'List Usage', category: 'content', severity: 'info' }, 'info', 'No lists found in long-form content', {
250
+ recommendation: 'Consider using bullet points or numbered lists',
251
+ evidence: {
252
+ found: `${ctx.wordCount} words with 0 lists`,
253
+ expected: 'At least 1 list per 500 words for long content',
254
+ impact: 'Lists improve scannability and featured snippet eligibility',
255
+ },
256
+ });
257
+ }
258
+ if (ctx.listCount > 0) {
259
+ return createResult({ id: 'readability-list-usage', name: 'List Usage', category: 'content', severity: 'info' }, 'pass', `${ctx.listCount} list(s) found`);
260
+ }
261
+ return createResult({ id: 'readability-list-usage', name: 'List Usage', category: 'content', severity: 'info' }, 'pass', 'Content length does not require lists');
262
+ },
263
+ },
264
+ ];
@@ -0,0 +1,16 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const redirectRules: SeoRule[];
3
+ declare module './types.js' {
4
+ interface RuleContext {
5
+ redirectChain?: Array<{
6
+ status: number;
7
+ from: string;
8
+ to: string;
9
+ }>;
10
+ wwwConsistency?: {
11
+ canonicalHasWww: boolean;
12
+ urlHasWww: boolean;
13
+ redirectsToCanonical: boolean;
14
+ };
15
+ }
16
+ }