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
@@ -1,5 +1,4 @@
1
1
  import readline from 'node:readline';
2
- import { promises as dns } from 'node:dns';
3
2
  import { promises as fs } from 'node:fs';
4
3
  import { join } from 'node:path';
5
4
  import { requireOptional } from '../../utils/optional-require.js';
@@ -7,18 +6,22 @@ import { createClient } from '../../core/client.js';
7
6
  import { startInteractiveWebSocket } from './websocket.js';
8
7
  import { whois, isDomainAvailable } from '../../utils/whois.js';
9
8
  import { inspectTLS } from '../../utils/tls-inspector.js';
10
- import { getSecurityRecords } from '../../utils/dns-toolkit.js';
11
- import { rdap } from '../../utils/rdap.js';
12
9
  import { ScrapeDocument } from '../../scrape/document.js';
13
- import { Spider } from '../../scrape/spider.js';
14
10
  import colors from '../../utils/colors.js';
15
11
  import { getShellSearch } from './shell-search.js';
16
12
  import { openSearchPanel } from './search-panel.js';
17
13
  import { ScrollBuffer, parseScrollKey, parseMouseScroll, disableMouseReporting } from './scroll-buffer.js';
18
- import { analyzeSeo, SeoSpider } from '../../seo/index.js';
19
14
  import { resolvePreset } from '../presets.js';
20
- import { summarizeErrors, formatErrorSummary } from '../helpers.js';
21
15
  import { getVersion } from '../../version.js';
16
+ import { parseEnhancerPresets } from '../helpers.js';
17
+ import { runHls } from './commands/hls.js';
18
+ import { runSeo } from './commands/seo.js';
19
+ import { runIpIntelligence } from './commands/ip.js';
20
+ import { runSecurityGrader } from './commands/security.js';
21
+ import { runSpider } from '../commands/spider.js';
22
+ import { runRDAP, runPing } from './commands/network.js';
23
+ import { runDns, runDnsPropagation, runDnsEmailCheck, runDnsHealth, runDnsSpf, runDnsDmarc, runDnsDkim, runDnsDig, runDnsGenerate } from './commands/dns.js';
24
+ import { runFtp, runTelnet, runGraphQL, runJsonRpc, runHar } from './commands/protocols.js';
22
25
  let highlight;
23
26
  async function initDependencies() {
24
27
  if (!highlight) {
@@ -45,6 +48,12 @@ export class RekShell {
45
48
  currentDocUrl = '';
46
49
  scrollBuffer;
47
50
  originalStdoutWrite = null;
51
+ printResponse(res) {
52
+ console.log(JSON.stringify(res, null, 2));
53
+ }
54
+ printError(err) {
55
+ console.error(colors.red(err.message || String(err)));
56
+ }
48
57
  inScrollMode = false;
49
58
  aiClients = new Map();
50
59
  constructor() {
@@ -349,7 +358,13 @@ export class RekShell {
349
358
  }
350
359
  if (input.includes('=') && !input.includes(' ') && !input.startsWith('http')) {
351
360
  }
352
- const parts = this.parseLine(input);
361
+ const rawParts = this.parseLine(input);
362
+ const { clientOptions, remainingArgs: parts } = await parseEnhancerPresets(rawParts);
363
+ const enhancerHeaders = clientOptions.headers || {};
364
+ const headerArgs = Object.entries(enhancerHeaders).map(([k, v]) => `${k}:${v}`);
365
+ if (headerArgs.length > 0) {
366
+ parts.push(...headerArgs);
367
+ }
353
368
  const cmd = parts[0].toLowerCase();
354
369
  switch (cmd) {
355
370
  case 'help':
@@ -397,13 +412,13 @@ export class RekShell {
397
412
  await this.runTLS(parts[1], parts[2] ? parseInt(parts[2]) : 443);
398
413
  return;
399
414
  case 'security':
400
- await this.runSecurityGrader(parts[1]);
415
+ await runSecurityGrader(this, parts[1]);
401
416
  return;
402
417
  case 'seo':
403
- await this.runSeo(parts[1], parts.includes('-a') || parts.includes('--all'), parts.includes('--format') && parts[parts.indexOf('--format') + 1] === 'json');
418
+ await runSeo(this, parts.slice(1));
404
419
  return;
405
420
  case 'ip':
406
- await this.runIpIntelligence(parts[1]);
421
+ await runIpIntelligence(this, parts[1]);
407
422
  return;
408
423
  case 'dns':
409
424
  await this.runDNS(parts[1]);
@@ -430,31 +445,31 @@ export class RekShell {
430
445
  await this.runDnsDig(parts.slice(1));
431
446
  return;
432
447
  case 'dns:generate':
433
- await this.runDnsGenerate(parts.slice(1));
448
+ await runDnsGenerate(this, parts.slice(1));
434
449
  return;
435
450
  case 'rdap':
436
- await this.runRDAP(parts[1]);
451
+ await runRDAP(this, parts[1]);
437
452
  return;
438
453
  case 'ping':
439
- await this.runPing(parts[1]);
454
+ await runPing(this, parts[1]);
440
455
  return;
441
456
  case 'ftp':
442
- await this.runFtp(parts.slice(1));
457
+ await runFtp(this, parts.slice(1));
443
458
  return;
444
459
  case 'telnet':
445
- await this.runTelnet(parts[1], parts[2]);
460
+ await runTelnet(this, parts[1], parts[2]);
446
461
  return;
447
462
  case 'graphql':
448
- await this.runGraphQL(parts.slice(1));
463
+ await runGraphQL(this, parts.slice(1));
449
464
  return;
450
465
  case 'jsonrpc':
451
- await this.runJsonRpc(parts.slice(1));
466
+ await runJsonRpc(this, parts.slice(1));
452
467
  return;
453
468
  case 'hls':
454
- await this.runHls(parts.slice(1));
469
+ await runHls(this, parts.slice(1));
455
470
  return;
456
471
  case 'har':
457
- await this.runHar(parts.slice(1));
472
+ await runHar(this, parts.slice(1));
458
473
  return;
459
474
  case 'har:record':
460
475
  await this.runHarRecord(parts.slice(1));
@@ -502,7 +517,7 @@ export class RekShell {
502
517
  await this.runScrap(parts[1]);
503
518
  return;
504
519
  case 'spider':
505
- await this.runSpider(parts.slice(1));
520
+ await runSpider(parts.slice(1), this.baseUrl);
506
521
  return;
507
522
  case '$':
508
523
  await this.runSelect(parts.slice(1).join(' '));
@@ -643,7 +658,7 @@ export class RekShell {
643
658
  try {
644
659
  let client = this.aiClients.get(presetName);
645
660
  if (!client) {
646
- const presetConfig = resolvePreset(presetName);
661
+ const presetConfig = await resolvePreset(presetName);
647
662
  if (!presetConfig) {
648
663
  console.log(colors.red(`Unknown AI preset: @${presetName}`));
649
664
  console.log(colors.gray('Available AI presets: openai, anthropic, groq, google, xai, mistral, cohere, deepseek, fireworks, together, perplexity'));
@@ -911,19 +926,22 @@ export class RekShell {
911
926
  }
912
927
  return;
913
928
  }
914
- if (url.startsWith('udp')) {
915
- const { UDPTransport } = await import('../../transport/udp.js');
916
- const transport = new UDPTransport(url);
929
+ if (url.startsWith('udp://')) {
917
930
  const msg = Object.keys(body).length ? JSON.stringify(body) : 'ping';
918
931
  console.log(colors.gray(`UDP packet -> ${url}`));
919
- const res = await transport.dispatch({
920
- url, method: 'GET', headers: new Headers(),
921
- body: msg, withHeader: () => ({}), withBody: () => ({})
922
- });
923
- const text = await res.text();
924
- console.log(colors.green('✔ Sent/Received'));
925
- if (text)
926
- console.log(text);
932
+ try {
933
+ const res = await this.client.request(url, {
934
+ method: 'POST',
935
+ body: msg
936
+ });
937
+ const text = await res.text();
938
+ console.log(colors.green('✔ Sent/Received'));
939
+ if (text)
940
+ console.log(text);
941
+ }
942
+ catch (e) {
943
+ console.error(colors.red(e.message));
944
+ }
927
945
  return;
928
946
  }
929
947
  console.log(colors.gray(`${method} ${url}...`));
@@ -1127,1541 +1145,227 @@ export class RekShell {
1127
1145
  }
1128
1146
  console.log('');
1129
1147
  }
1130
- async runSecurityGrader(url) {
1148
+ async runDNS(domain) {
1149
+ await runDns(this, domain);
1150
+ }
1151
+ async runDNSPropagation(domain, type = 'A') {
1152
+ await runDnsPropagation(this, domain, type);
1153
+ }
1154
+ async runDnsEmailCheck(domain, selector) {
1155
+ await runDnsEmailCheck(this, domain, selector);
1156
+ }
1157
+ async runDnsHealth(domain) {
1158
+ await runDnsHealth(this, domain);
1159
+ }
1160
+ async runDnsSpf(domain) {
1161
+ await runDnsSpf(this, domain);
1162
+ }
1163
+ async runDnsDmarc(domain) {
1164
+ await runDnsDmarc(this, domain);
1165
+ }
1166
+ async runDnsDkim(domain, selector) {
1167
+ await runDnsDkim(this, domain, selector);
1168
+ }
1169
+ async runDnsDig(args) {
1170
+ await runDnsDig(this, args);
1171
+ }
1172
+ async runDnsGenerate(args) {
1173
+ await runDnsGenerate(this, args);
1174
+ }
1175
+ async runScrap(url) {
1131
1176
  if (!url) {
1132
- url = this.baseUrl || '';
1133
- if (!url) {
1134
- console.log(colors.yellow('Usage: security <url>'));
1135
- console.log(colors.gray(' Examples: security google.com | security https://example.com'));
1177
+ if (!this.baseUrl) {
1178
+ console.log(colors.yellow('Usage: scrap <url>'));
1179
+ console.log(colors.gray(' Examples: scrap https://news.ycombinator.com'));
1136
1180
  console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1137
1181
  return;
1138
1182
  }
1183
+ url = this.baseUrl;
1139
1184
  }
1140
1185
  else if (!url.startsWith('http')) {
1141
- url = `https://${url}`;
1186
+ url = this.baseUrl ? `${this.baseUrl}${url.startsWith('/') ? '' : '/'}${url}` : `https://${url}`;
1142
1187
  }
1143
- console.log(colors.gray(`Analyzing security headers for ${url}...`));
1188
+ console.log(colors.gray(`Fetching ${url}...`));
1189
+ const startTime = performance.now();
1144
1190
  try {
1145
- const { analyzeSecurityHeaders } = await import('../../utils/security-grader.js');
1146
- const res = await this.client.get(url);
1147
- const report = analyzeSecurityHeaders(res.headers);
1148
- let gradeColor = colors.red;
1149
- if (report.grade.startsWith('A'))
1150
- gradeColor = colors.green;
1151
- else if (report.grade.startsWith('B'))
1152
- gradeColor = colors.blue;
1153
- else if (report.grade.startsWith('C'))
1154
- gradeColor = colors.yellow;
1155
- console.log(`
1156
- ${colors.bold(colors.cyan('🛡️ Security Headers Report'))}
1157
- Grade: ${gradeColor(colors.bold(report.grade))} (${report.score}/100)
1158
-
1159
- ${colors.bold('Details:')}`);
1160
- report.details.forEach(item => {
1161
- const icon = item.status === 'pass' ? colors.green('✔') : item.status === 'warn' ? colors.yellow('⚠') : colors.red('✖');
1162
- const headerName = colors.bold(item.header);
1163
- const value = item.value ? colors.gray(`= ${item.value.length > 50 ? item.value.slice(0, 47) + '...' : item.value}`) : colors.gray('(missing)');
1164
- console.log(` ${icon} ${headerName} ${value}`);
1165
- if (item.status !== 'pass') {
1166
- console.log(` ${colors.red('→')} ${item.message}`);
1167
- }
1168
- });
1169
- console.log('');
1170
- this.lastResponse = report;
1191
+ const response = await this.client.get(url);
1192
+ const html = await response.text();
1193
+ const duration = Math.round(performance.now() - startTime);
1194
+ this.currentDoc = await ScrapeDocument.create(html, { baseUrl: url });
1195
+ this.currentDocUrl = url;
1196
+ const title = this.currentDoc.selectFirst('title').text() || 'No title';
1197
+ const og = this.currentDoc.openGraph();
1198
+ console.log(colors.green(`✔ Loaded`) + colors.gray(` (${duration}ms)`));
1199
+ console.log(` ${colors.cyan('Title')}: ${title}`);
1200
+ if (og.title)
1201
+ console.log(` ${colors.cyan('OG Title')}: ${og.title}`);
1202
+ if (og.description)
1203
+ console.log(` ${colors.cyan('OG Desc')}: ${og.description}`);
1204
+ if (og.image) {
1205
+ const images = Array.isArray(og.image) ? og.image : [og.image];
1206
+ console.log(` ${colors.magenta('Image')}: ${images[0]}`);
1207
+ if (images.length > 1)
1208
+ console.log(colors.gray(` (+${images.length - 1} more)`));
1209
+ }
1210
+ if (og.url && og.url !== url)
1211
+ console.log(` ${colors.magenta('URL')}: ${og.url}`);
1212
+ console.log(colors.gray('\n Use $ <selector> to query, $text, $attr, $links, $images, $scripts, $css, $sourcemaps, $table'));
1171
1213
  }
1172
1214
  catch (error) {
1173
- console.error(colors.red(`Analysis failed: ${error.message}`));
1215
+ console.error(colors.red(`Scrape failed: ${error.message}`));
1174
1216
  }
1175
1217
  console.log('');
1176
1218
  }
1177
- async runSeo(url, showAll = false, jsonOutput = false) {
1178
- if (!url) {
1179
- url = this.currentDocUrl || this.baseUrl || '';
1180
- if (!url) {
1181
- console.log(colors.yellow('Usage: seo <url> [-a] [--format json]'));
1182
- console.log(colors.gray(' Examples: seo google.com | seo https://example.com -a'));
1183
- console.log(colors.gray(' -a, --all Show all checks (including passed)'));
1184
- console.log(colors.gray(' --format json Output raw JSON for programmatic use'));
1185
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1186
- return;
1187
- }
1188
- }
1189
- else if (!url.startsWith('http') && !url.startsWith('-')) {
1190
- url = `https://${url}`;
1219
+ async runSelect(selector) {
1220
+ if (!this.currentDoc) {
1221
+ console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
1222
+ return;
1191
1223
  }
1192
- if (!jsonOutput) {
1193
- console.log(colors.gray(`Analyzing SEO for ${url}...`));
1224
+ if (!selector) {
1225
+ console.log(colors.yellow('Usage: $ <selector>'));
1226
+ console.log(colors.gray(' Examples: $ h1 | $ .title | $ a[href*="article"]'));
1227
+ return;
1194
1228
  }
1195
- const startTime = performance.now();
1196
1229
  try {
1197
- const res = await this.client.get(url);
1198
- const html = await res.text();
1199
- const duration = Math.round(performance.now() - startTime);
1200
- const report = await analyzeSeo(html, { baseUrl: url });
1201
- const t = res.timings;
1202
- report.timing = {
1203
- ttfb: t?.firstByte ? Math.round(t.firstByte) : undefined,
1204
- total: t?.total ? Math.round(t.total) : duration,
1205
- dns: t?.dns ? Math.round(t.dns) : undefined,
1206
- tcp: t?.tcp ? Math.round(t.tcp) : undefined,
1207
- tls: t?.tls ? Math.round(t.tls) : undefined,
1208
- download: t?.content ? Math.round(t.content) : undefined,
1209
- };
1210
- if (jsonOutput) {
1211
- const jsonResult = {
1212
- url,
1213
- analyzedAt: new Date().toISOString(),
1214
- timing: report.timing,
1215
- score: report.score,
1216
- grade: report.grade,
1217
- title: report.title,
1218
- metaDescription: report.metaDescription,
1219
- content: report.content,
1220
- headings: report.headings,
1221
- links: report.links,
1222
- images: report.images,
1223
- openGraph: report.openGraph,
1224
- twitterCard: report.twitterCard,
1225
- social: report.social,
1226
- structuredData: report.structuredData,
1227
- technical: report.technical,
1228
- checks: report.checks,
1229
- summary: {
1230
- total: report.checks.length,
1231
- passed: report.checks.filter(c => c.status === 'pass').length,
1232
- warnings: report.checks.filter(c => c.status === 'warn').length,
1233
- errors: report.checks.filter(c => c.status === 'fail').length,
1234
- info: report.checks.filter(c => c.status === 'info').length,
1235
- },
1236
- };
1237
- console.log(JSON.stringify(jsonResult, null, 2));
1238
- this.lastResponse = jsonResult;
1239
- return;
1240
- }
1241
- let gradeColor = colors.red;
1242
- if (report.grade === 'A')
1243
- gradeColor = colors.green;
1244
- else if (report.grade === 'B')
1245
- gradeColor = colors.blue;
1246
- else if (report.grade === 'C')
1247
- gradeColor = colors.yellow;
1248
- else if (report.grade === 'D')
1249
- gradeColor = colors.magenta;
1250
- console.log(`
1251
- ${colors.bold(colors.cyan('🔍 SEO Analysis Report'))} ${colors.gray(`(${duration}ms)`)}
1252
- Grade: ${gradeColor(colors.bold(report.grade))} (${report.score}/100)
1253
- `);
1254
- if (report.title) {
1255
- console.log(colors.bold('Title:') + ` ${report.title.text} ` + colors.gray(`(${report.title.length} chars)`));
1256
- }
1257
- if (report.metaDescription) {
1258
- const desc = report.metaDescription.text.length > 80
1259
- ? report.metaDescription.text.slice(0, 77) + '...'
1260
- : report.metaDescription.text;
1261
- console.log(colors.bold('Description:') + ` ${desc} ` + colors.gray(`(${report.metaDescription.length} chars)`));
1262
- }
1263
- if (report.openGraph && Object.values(report.openGraph).some(v => v)) {
1264
- console.log('');
1265
- console.log(colors.bold(colors.cyan('OpenGraph:')));
1266
- if (report.openGraph.title) {
1267
- const ogTitle = report.openGraph.title.length > 60
1268
- ? report.openGraph.title.slice(0, 57) + '...'
1269
- : report.openGraph.title;
1270
- console.log(` ${colors.gray('og:title:')} ${ogTitle}`);
1271
- }
1272
- if (report.openGraph.description) {
1273
- const ogDesc = report.openGraph.description.length > 60
1274
- ? report.openGraph.description.slice(0, 57) + '...'
1275
- : report.openGraph.description;
1276
- console.log(` ${colors.gray('og:description:')} ${ogDesc}`);
1277
- }
1278
- if (report.openGraph.image) {
1279
- const ogImg = report.openGraph.image.length > 50
1280
- ? '...' + report.openGraph.image.slice(-47)
1281
- : report.openGraph.image;
1282
- console.log(` ${colors.gray('og:image:')} ${colors.blue(ogImg)}`);
1283
- }
1284
- if (report.openGraph.type) {
1285
- console.log(` ${colors.gray('og:type:')} ${report.openGraph.type}`);
1286
- }
1287
- }
1288
- if (report.timing) {
1289
- const t = report.timing;
1290
- console.log('');
1291
- console.log(colors.bold('Timing:'));
1292
- const timings = [];
1293
- if (t.dns !== undefined)
1294
- timings.push(`DNS ${t.dns}ms`);
1295
- if (t.tcp !== undefined)
1296
- timings.push(`TCP ${t.tcp}ms`);
1297
- if (t.tls !== undefined)
1298
- timings.push(`TLS ${t.tls}ms`);
1299
- if (t.ttfb !== undefined)
1300
- timings.push(`TTFB ${t.ttfb}ms`);
1301
- if (t.download !== undefined)
1302
- timings.push(`Download ${t.download}ms`);
1303
- if (t.total !== undefined)
1304
- timings.push(`Total ${t.total}ms`);
1305
- console.log(` ${timings.join(' → ')}`);
1306
- }
1307
- if (report.content) {
1308
- console.log(colors.bold('Content:') + ` ${report.content.wordCount} words, ${report.content.paragraphCount} paragraphs, ~${report.content.readingTimeMinutes} min read`);
1309
- }
1310
- console.log('');
1311
- console.log(colors.bold('Checks:'));
1312
- const checksToShow = showAll
1313
- ? report.checks
1314
- : report.checks.filter(c => c.status !== 'pass');
1315
- const failed = checksToShow.filter(c => c.status === 'fail');
1316
- const warnings = checksToShow.filter(c => c.status === 'warn');
1317
- const info = checksToShow.filter(c => c.status === 'info');
1318
- const passed = showAll ? checksToShow.filter(c => c.status === 'pass') : [];
1319
- const displayCheck = (check) => {
1320
- let icon;
1321
- let nameColor;
1322
- switch (check.status) {
1323
- case 'pass':
1324
- icon = colors.green('✔');
1325
- nameColor = colors.green;
1326
- break;
1327
- case 'warn':
1328
- icon = colors.yellow('⚠');
1329
- nameColor = colors.yellow;
1330
- break;
1331
- case 'fail':
1332
- icon = colors.red('✖');
1333
- nameColor = colors.red;
1334
- break;
1335
- default:
1336
- icon = colors.blue('ℹ');
1337
- nameColor = colors.blue;
1338
- }
1339
- console.log(` ${icon} ${nameColor(check.name.padEnd(22))} ${check.message}`);
1340
- if (check.recommendation && check.status !== 'pass') {
1341
- console.log(` ${colors.gray('→')} ${colors.gray(check.recommendation)}`);
1342
- }
1343
- const evidence = check.evidence;
1344
- if (evidence && check.status !== 'pass') {
1345
- if (evidence.found && Array.isArray(evidence.found) && evidence.found.length > 0) {
1346
- const items = evidence.found.slice(0, 3);
1347
- console.log(` ${colors.gray('Found:')} ${colors.red(items.join(', '))}${evidence.found.length > 3 ? ` (+${evidence.found.length - 3} more)` : ''}`);
1348
- }
1349
- if (evidence.example) {
1350
- console.log(` ${colors.gray('Example:')} ${colors.cyan(evidence.example.split('\n')[0])}`);
1351
- }
1352
- }
1353
- };
1354
- if (failed.length > 0) {
1355
- console.log(colors.red(`\n Errors (${failed.length}):`));
1356
- failed.forEach(displayCheck);
1357
- }
1358
- if (warnings.length > 0) {
1359
- console.log(colors.yellow(`\n Warnings (${warnings.length}):`));
1360
- warnings.forEach(displayCheck);
1361
- }
1362
- if (info.length > 0) {
1363
- console.log(colors.blue(`\n Info (${info.length}):`));
1364
- info.forEach(displayCheck);
1365
- }
1366
- if (passed.length > 0) {
1367
- console.log(colors.green(`\n Passed (${passed.length}):`));
1368
- passed.forEach(displayCheck);
1230
+ const elements = this.currentDoc.select(selector);
1231
+ const count = elements.length;
1232
+ console.log(colors.cyan(`Found ${count} element(s)`));
1233
+ if (count > 0 && count <= 10) {
1234
+ elements.each((el, i) => {
1235
+ const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
1236
+ console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
1237
+ });
1369
1238
  }
1370
- if (!showAll && report.checks.filter(c => c.status === 'pass').length > 0) {
1371
- console.log(colors.gray(`\n ${report.checks.filter(c => c.status === 'pass').length} checks passed. Use -a to show all.`));
1239
+ else if (count > 10) {
1240
+ console.log(colors.gray(' (showing first 10)'));
1241
+ let shown = 0;
1242
+ elements.each((el, i) => {
1243
+ if (shown >= 10)
1244
+ return;
1245
+ const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
1246
+ console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
1247
+ shown++;
1248
+ });
1372
1249
  }
1373
- console.log('');
1374
- this.lastResponse = report;
1250
+ this.lastResponse = { count, selector };
1375
1251
  }
1376
1252
  catch (error) {
1377
- console.error(colors.red(`SEO analysis failed: ${error.message}`));
1253
+ console.error(colors.red(`Query failed: ${error.message}`));
1378
1254
  }
1379
1255
  console.log('');
1380
1256
  }
1381
- async runIpIntelligence(address) {
1382
- if (!address) {
1383
- console.log(colors.yellow('Usage: ip <address>'));
1384
- console.log(colors.gray(' Examples: ip 8.8.8.8 | ip 192.168.1.1'));
1257
+ async runSelectText(selector) {
1258
+ if (!this.currentDoc) {
1259
+ console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
1260
+ return;
1261
+ }
1262
+ if (!selector) {
1263
+ console.log(colors.yellow('Usage: $text <selector>'));
1385
1264
  return;
1386
1265
  }
1387
- console.log(colors.gray(`Looking up ${address} using local GeoLite2 database...`));
1388
1266
  try {
1389
- const { getIpInfo, isGeoIPAvailable } = await import('../../mcp/ip-intel.js');
1390
- if (!isGeoIPAvailable()) {
1391
- console.log(colors.gray(`Downloading GeoLite2 database...`));
1392
- }
1393
- const info = await getIpInfo(address);
1394
- if (info.bogon) {
1395
- console.log(colors.yellow(`\n⚠ ${address} is a Bogon/Private IP.`));
1396
- console.log(colors.gray(` Type: ${info.bogonType}`));
1397
- this.lastResponse = info;
1398
- return;
1399
- }
1400
- console.log(`
1401
- ${colors.bold(colors.cyan('🌍 IP Intelligence Report'))}
1402
-
1403
- ${colors.bold('Location:')}
1404
- ${colors.gray('City:')} ${info.city || 'N/A'}
1405
- ${colors.gray('Region:')} ${info.region || 'N/A'}
1406
- ${colors.gray('Country:')} ${info.country || 'N/A'} ${info.countryCode ? `(${info.countryCode})` : ''}
1407
- ${colors.gray('Continent:')} ${info.continent || 'N/A'}
1408
- ${colors.gray('Timezone:')} ${info.timezone || 'N/A'}
1409
- ${colors.gray('Coords:')} ${info.loc ? colors.cyan(info.loc) : 'N/A'}
1410
- ${colors.gray('Accuracy:')} ${info.accuracy ? `~${info.accuracy} km` : 'N/A'}
1411
-
1412
- ${colors.bold('Network:')}
1413
- ${colors.gray('IP:')} ${info.ip}
1414
- ${colors.gray('Type:')} ${info.isIPv6 ? 'IPv6' : 'IPv4'}
1415
- ${colors.gray('Postal:')} ${info.postal || 'N/A'}
1416
- `);
1417
- this.lastResponse = info;
1267
+ const elements = this.currentDoc.select(selector);
1268
+ const texts = [];
1269
+ elements.each((el, i) => {
1270
+ const text = el.text().trim();
1271
+ if (text) {
1272
+ texts.push(text);
1273
+ console.log(`${colors.gray(`${i + 1}.`)} ${text.slice(0, 200)}${text.length > 200 ? '...' : ''}`);
1274
+ }
1275
+ });
1276
+ this.lastResponse = texts;
1277
+ console.log(colors.gray(`\n ${texts.length} text item(s) extracted`));
1418
1278
  }
1419
1279
  catch (error) {
1420
- console.error(colors.red(`IP Lookup Failed: ${error.message}`));
1280
+ console.error(colors.red(`Query failed: ${error.message}`));
1421
1281
  }
1422
1282
  console.log('');
1423
1283
  }
1424
- async runDNS(domain) {
1425
- if (!domain) {
1426
- domain = this.getBaseDomain() || '';
1427
- if (!domain) {
1428
- console.log(colors.yellow('Usage: dns <domain>'));
1429
- console.log(colors.gray(' Examples: dns google.com | dns github.com'));
1430
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1431
- return;
1432
- }
1284
+ async runSelectAttr(attrName, selector) {
1285
+ if (!this.currentDoc) {
1286
+ console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
1287
+ return;
1288
+ }
1289
+ if (!attrName || !selector) {
1290
+ console.log(colors.yellow('Usage: $attr <attribute> <selector>'));
1291
+ console.log(colors.gray(' Examples: $attr href a | $attr src img'));
1292
+ return;
1433
1293
  }
1434
- console.log(colors.gray(`Resolving DNS for ${domain}...`));
1435
- const startTime = performance.now();
1436
1294
  try {
1437
- const [a, aaaa, mx, ns, txt, security] = await Promise.all([
1438
- dns.resolve4(domain).catch(() => []),
1439
- dns.resolve6(domain).catch(() => []),
1440
- dns.resolveMx(domain).catch(() => []),
1441
- dns.resolveNs(domain).catch(() => []),
1442
- dns.resolveTxt(domain).catch(() => []),
1443
- getSecurityRecords(domain).catch(() => ({}))
1444
- ]);
1445
- const duration = Math.round(performance.now() - startTime);
1446
- console.log(colors.green(`✔ DNS resolved`) + colors.gray(` (${duration}ms)\n`));
1447
- if (a.length) {
1448
- console.log(colors.bold(' A Records (IPv4):'));
1449
- a.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
1450
- }
1451
- if (aaaa.length) {
1452
- console.log(colors.bold(' AAAA Records (IPv6):'));
1453
- aaaa.forEach(ip => console.log(` ${colors.cyan('→')} ${ip}`));
1454
- }
1455
- if (ns.length) {
1456
- console.log(colors.bold(' NS Records:'));
1457
- ns.forEach(n => console.log(` ${colors.cyan('→')} ${n}`));
1458
- }
1459
- if (mx.length) {
1460
- console.log(colors.bold(' MX Records:'));
1461
- mx.sort((a, b) => a.priority - b.priority)
1462
- .forEach(m => console.log(` ${colors.cyan(String(m.priority).padStart(3))} ${m.exchange}`));
1463
- }
1464
- const sec = security;
1465
- if (sec.spf?.length) {
1466
- console.log(colors.bold(' SPF:'));
1467
- console.log(` ${colors.gray(sec.spf[0].slice(0, 80))}${sec.spf[0].length > 80 ? '...' : ''}`);
1468
- }
1469
- if (sec.dmarc) {
1470
- console.log(colors.bold(' DMARC:'));
1471
- console.log(` ${colors.gray(sec.dmarc.slice(0, 80))}${sec.dmarc.length > 80 ? '...' : ''}`);
1472
- }
1473
- if (sec.caa?.issue?.length) {
1474
- console.log(colors.bold(' CAA:'));
1475
- sec.caa.issue.forEach((ca) => console.log(` ${colors.cyan('issue')} ${ca}`));
1476
- }
1477
- this.lastResponse = { a, aaaa, mx, ns, txt, security };
1295
+ const elements = this.currentDoc.select(selector);
1296
+ const attrs = [];
1297
+ elements.each((el, i) => {
1298
+ const value = el.attr(attrName);
1299
+ if (value) {
1300
+ attrs.push(value);
1301
+ console.log(`${colors.gray(`${i + 1}.`)} ${value}`);
1302
+ }
1303
+ });
1304
+ this.lastResponse = attrs;
1305
+ console.log(colors.gray(`\n ${attrs.length} attribute(s) extracted`));
1478
1306
  }
1479
1307
  catch (error) {
1480
- console.error(colors.red(`DNS lookup failed: ${error.message}`));
1308
+ console.error(colors.red(`Query failed: ${error.message}`));
1481
1309
  }
1482
1310
  console.log('');
1483
1311
  }
1484
- async runDNSPropagation(domain, type = 'A') {
1485
- if (!domain) {
1486
- domain = this.getBaseDomain() || '';
1487
- if (!domain) {
1488
- console.log(colors.yellow('Usage: dns:propagate <domain> [type]'));
1489
- console.log(colors.gray(' Examples: dns:propagate google.com | dns:propagate github.com TXT'));
1490
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1491
- return;
1492
- }
1493
- }
1494
- console.log(colors.gray(`Checking DNS propagation for ${domain} (${type})...`));
1495
- try {
1496
- const { checkPropagation, formatPropagationReport } = await import('../../dns/propagation.js');
1497
- const results = await checkPropagation(domain, type);
1498
- console.log(formatPropagationReport(results, domain, type));
1499
- this.lastResponse = results;
1500
- }
1501
- catch (error) {
1502
- console.error(colors.red(`Propagation check failed: ${error.message}`));
1312
+ async runSelectHtml(selector) {
1313
+ if (!this.currentDoc) {
1314
+ console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
1315
+ return;
1503
1316
  }
1504
- }
1505
- async runDnsEmailCheck(domain, selector) {
1506
- if (!domain) {
1507
- domain = this.getBaseDomain() || '';
1508
- if (!domain) {
1509
- console.log(colors.yellow('Usage: dns:email <domain> [dkim-selector]'));
1510
- console.log(colors.gray(' Examples: dns:email google.com | dns:email github.com google'));
1511
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1512
- return;
1513
- }
1317
+ if (!selector) {
1318
+ console.log(colors.yellow('Usage: $html <selector>'));
1319
+ return;
1514
1320
  }
1515
- console.log(colors.gray(`Checking email security for ${domain}...`));
1516
- const startTime = performance.now();
1517
1321
  try {
1518
- const { validateSpf, validateDmarc, checkDkim } = await import('../../utils/dns-toolkit.js');
1519
- const [spf, dmarc, dkim] = await Promise.all([
1520
- validateSpf(domain),
1521
- validateDmarc(domain),
1522
- checkDkim(domain, selector || 'default')
1523
- ]);
1524
- const duration = Math.round(performance.now() - startTime);
1525
- console.log(colors.green(`✔ Email security check completed`) + colors.gray(` (${duration}ms)\n`));
1526
- console.log(colors.bold('SPF:'));
1527
- if (spf.valid) {
1528
- console.log(` ${colors.green('✔')} ${spf.record || 'No record'}`);
1529
- }
1530
- else {
1531
- console.log(` ${colors.red('✖')} ${spf.errors?.join(', ') || 'Invalid'}`);
1532
- }
1533
- if (spf.warnings?.length) {
1534
- spf.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1535
- }
1536
- console.log(colors.bold('\nDMARC:'));
1537
- if (dmarc.valid) {
1538
- console.log(` ${colors.green('✔')} Policy: ${dmarc.policy || 'none'}`);
1539
- if (dmarc.percentage !== undefined && dmarc.percentage < 100) {
1540
- console.log(` ${colors.yellow('⚠')} Only ${dmarc.percentage}% of emails affected`);
1541
- }
1542
- }
1543
- else {
1544
- console.log(` ${colors.red('✖')} No DMARC record found`);
1545
- }
1546
- if (dmarc.warnings?.length) {
1547
- dmarc.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1548
- }
1549
- console.log(colors.bold(`\nDKIM (${selector || 'default'}):`));
1550
- if (dkim.found) {
1551
- console.log(` ${colors.green('✔')} Record found`);
1552
- if (dkim.publicKey) {
1553
- const keyPreview = dkim.publicKey.substring(0, 40) + '...';
1554
- console.log(` ${colors.gray('Key:')} ${keyPreview}`);
1322
+ const element = this.currentDoc.selectFirst(selector);
1323
+ const html = element.html();
1324
+ if (html) {
1325
+ console.log(html.slice(0, 1000));
1326
+ if (html.length > 1000) {
1327
+ console.log(colors.gray(`\n ... (${html.length} chars total)`));
1555
1328
  }
1329
+ this.lastResponse = html;
1556
1330
  }
1557
1331
  else {
1558
- console.log(` ${colors.yellow('⚠')} No DKIM record for selector "${selector || 'default'}"`);
1559
- console.log(` ${colors.gray('Try: dns:email ' + domain + ' <selector>')}`);
1332
+ console.log(colors.gray('No element found'));
1560
1333
  }
1561
- console.log('');
1562
- this.lastResponse = { spf, dmarc, dkim };
1563
1334
  }
1564
1335
  catch (error) {
1565
- console.error(colors.red(`Email security check failed: ${error.message}`));
1336
+ console.error(colors.red(`Query failed: ${error.message}`));
1566
1337
  }
1338
+ console.log('');
1567
1339
  }
1568
- async runDnsHealth(domain) {
1569
- if (!domain) {
1570
- domain = this.getBaseDomain() || '';
1571
- if (!domain) {
1572
- console.log(colors.yellow('Usage: dns:health <domain>'));
1573
- console.log(colors.gray(' Example: dns:health google.com'));
1574
- return;
1575
- }
1340
+ async runSelectLinks(selector) {
1341
+ if (!this.currentDoc) {
1342
+ console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
1343
+ return;
1576
1344
  }
1577
- console.log(colors.gray(`Checking DNS health for ${domain}...`));
1578
- const startTime = performance.now();
1579
1345
  try {
1580
- const { checkDnsHealth } = await import('../../utils/dns-toolkit.js');
1581
- const result = await checkDnsHealth(domain);
1582
- const duration = Math.round(performance.now() - startTime);
1583
- console.log(colors.green(`✔ DNS health check completed`) + colors.gray(` (${duration}ms)\n`));
1584
- const gradeColor = result.grade === 'A' ? colors.green :
1585
- result.grade === 'B' ? colors.cyan :
1586
- result.grade === 'C' ? colors.yellow : colors.red;
1587
- console.log(`${colors.bold('DNS Health Report')}`);
1588
- console.log(` ${colors.gray('Grade:')} ${gradeColor(result.grade)} (${result.score}/100)`);
1589
- console.log(` ${colors.gray('Checks:')} ${result.checks?.filter((c) => c.passed).length || 0} passed, ${result.checks?.filter((c) => !c.passed).length || 0} failed`);
1590
- if (result.checks) {
1591
- console.log('');
1592
- result.checks.forEach((check) => {
1593
- const icon = check.passed ? colors.green('✔') : colors.red('✖');
1594
- console.log(` ${icon} ${check.name}: ${check.message || (check.passed ? 'OK' : 'Failed')}`);
1595
- });
1346
+ const linkSelector = selector || 'a[href]';
1347
+ const elements = this.currentDoc.select(linkSelector);
1348
+ const links = [];
1349
+ elements.each((el, i) => {
1350
+ const href = el.attr('href');
1351
+ const text = el.text().trim().slice(0, 50);
1352
+ if (href) {
1353
+ links.push({ text, href });
1354
+ if (i < 20) {
1355
+ console.log(`${colors.gray(`${i + 1}.`)} ${colors.cyan(text || '(no text)')} ${colors.gray('→')} ${href}`);
1356
+ }
1357
+ }
1358
+ });
1359
+ if (links.length > 20) {
1360
+ console.log(colors.gray(` ... and ${links.length - 20} more links`));
1596
1361
  }
1597
- console.log('');
1598
- this.lastResponse = result;
1362
+ this.lastResponse = links;
1363
+ console.log(colors.gray(`\n ${links.length} link(s) found`));
1599
1364
  }
1600
1365
  catch (error) {
1601
- console.error(colors.red(`DNS health check failed: ${error.message}`));
1366
+ console.error(colors.red(`Query failed: ${error.message}`));
1602
1367
  }
1603
- }
1604
- async runDnsSpf(domain) {
1605
- if (!domain) {
1606
- domain = this.getBaseDomain() || '';
1607
- if (!domain) {
1608
- console.log(colors.yellow('Usage: dns:spf <domain>'));
1609
- console.log(colors.gray(' Example: dns:spf google.com'));
1610
- return;
1611
- }
1612
- }
1613
- console.log(colors.gray(`Validating SPF for ${domain}...`));
1614
- try {
1615
- const { validateSpf } = await import('../../utils/dns-toolkit.js');
1616
- const result = await validateSpf(domain);
1617
- console.log('');
1618
- console.log(colors.bold('SPF Validation'));
1619
- if (result.valid) {
1620
- console.log(` ${colors.green('✔')} Valid SPF record`);
1621
- }
1622
- else {
1623
- console.log(` ${colors.red('✖')} Invalid SPF record`);
1624
- }
1625
- if (result.record) {
1626
- console.log(` ${colors.gray('Record:')} ${result.record}`);
1627
- }
1628
- if (result.lookupCount !== undefined) {
1629
- const lookupColor = result.lookupCount > 10 ? colors.red : result.lookupCount > 7 ? colors.yellow : colors.green;
1630
- console.log(` ${colors.gray('DNS Lookups:')} ${lookupColor(result.lookupCount.toString())}/10`);
1631
- }
1632
- if (result.mechanisms && result.mechanisms.length > 0) {
1633
- console.log(` ${colors.gray('Mechanisms:')} ${result.mechanisms.join(', ')}`);
1634
- }
1635
- if (result.includes && result.includes.length > 0) {
1636
- console.log(` ${colors.gray('Includes:')} ${result.includes.join(', ')}`);
1637
- }
1638
- if (result.warnings && result.warnings.length > 0) {
1639
- console.log('');
1640
- result.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1641
- }
1642
- if (result.errors && result.errors.length > 0) {
1643
- console.log('');
1644
- result.errors.forEach((e) => console.log(` ${colors.red('✖')} ${e}`));
1645
- }
1646
- console.log('');
1647
- this.lastResponse = result;
1648
- }
1649
- catch (error) {
1650
- console.error(colors.red(`SPF validation failed: ${error.message}`));
1651
- }
1652
- }
1653
- async runDnsDmarc(domain) {
1654
- if (!domain) {
1655
- domain = this.getBaseDomain() || '';
1656
- if (!domain) {
1657
- console.log(colors.yellow('Usage: dns:dmarc <domain>'));
1658
- console.log(colors.gray(' Example: dns:dmarc google.com'));
1659
- return;
1660
- }
1661
- }
1662
- console.log(colors.gray(`Validating DMARC for ${domain}...`));
1663
- try {
1664
- const { validateDmarc } = await import('../../utils/dns-toolkit.js');
1665
- const result = await validateDmarc(domain);
1666
- console.log('');
1667
- console.log(colors.bold('DMARC Validation'));
1668
- if (result.valid) {
1669
- console.log(` ${colors.green('✔')} Valid DMARC record`);
1670
- }
1671
- else {
1672
- console.log(` ${colors.red('✖')} No DMARC record found`);
1673
- }
1674
- if (result.record) {
1675
- console.log(` ${colors.gray('Record:')} ${result.record}`);
1676
- }
1677
- if (result.policy) {
1678
- const policyColor = result.policy === 'reject' ? colors.green :
1679
- result.policy === 'quarantine' ? colors.yellow : colors.gray;
1680
- console.log(` ${colors.gray('Policy:')} ${policyColor(result.policy)}`);
1681
- }
1682
- if (result.subdomainPolicy) {
1683
- console.log(` ${colors.gray('Subdomain Policy:')} ${result.subdomainPolicy}`);
1684
- }
1685
- if (result.percentage !== undefined && result.percentage < 100) {
1686
- console.log(` ${colors.yellow('⚠')} Only ${result.percentage}% of emails affected`);
1687
- }
1688
- if (result.rua) {
1689
- console.log(` ${colors.gray('Aggregate Reports:')} ${result.rua}`);
1690
- }
1691
- if (result.ruf) {
1692
- console.log(` ${colors.gray('Forensic Reports:')} ${result.ruf}`);
1693
- }
1694
- if (result.warnings && result.warnings.length > 0) {
1695
- console.log('');
1696
- result.warnings.forEach((w) => console.log(` ${colors.yellow('⚠')} ${w}`));
1697
- }
1698
- console.log('');
1699
- this.lastResponse = result;
1700
- }
1701
- catch (error) {
1702
- console.error(colors.red(`DMARC validation failed: ${error.message}`));
1703
- }
1704
- }
1705
- async runDnsDkim(domain, selector) {
1706
- if (!domain) {
1707
- domain = this.getBaseDomain() || '';
1708
- if (!domain) {
1709
- console.log(colors.yellow('Usage: dns:dkim <domain> [selector]'));
1710
- console.log(colors.gray(' Example: dns:dkim google.com | dns:dkim google.com google'));
1711
- return;
1712
- }
1713
- }
1714
- const dkimSelector = selector || 'default';
1715
- console.log(colors.gray(`Checking DKIM for ${domain} (selector: ${dkimSelector})...`));
1716
- try {
1717
- const { checkDkim } = await import('../../utils/dns-toolkit.js');
1718
- const result = await checkDkim(domain, dkimSelector);
1719
- console.log('');
1720
- console.log(colors.bold(`DKIM Check (selector: ${dkimSelector})`));
1721
- if (result.found) {
1722
- console.log(` ${colors.green('✔')} DKIM record found`);
1723
- if (result.publicKey) {
1724
- const keyPreview = result.publicKey.substring(0, 50) + '...';
1725
- console.log(` ${colors.gray('Public Key:')} ${keyPreview}`);
1726
- }
1727
- if (result.record) {
1728
- console.log(` ${colors.gray('Record:')} ${result.record.substring(0, 80)}...`);
1729
- }
1730
- }
1731
- else {
1732
- console.log(` ${colors.yellow('⚠')} No DKIM record found for selector "${dkimSelector}"`);
1733
- console.log(` ${colors.gray('Common selectors: google, selector1, selector2, k1, default')}`);
1734
- }
1735
- console.log('');
1736
- this.lastResponse = result;
1737
- }
1738
- catch (error) {
1739
- console.error(colors.red(`DKIM check failed: ${error.message}`));
1740
- }
1741
- }
1742
- async runDnsDig(args) {
1743
- let server = '';
1744
- let domain = '';
1745
- let recordType = 'A';
1746
- let shortMode = false;
1747
- for (const arg of args) {
1748
- if (arg.startsWith('@')) {
1749
- server = arg.slice(1);
1750
- }
1751
- else if (arg === '+short') {
1752
- shortMode = true;
1753
- }
1754
- else if (['A', 'AAAA', 'MX', 'NS', 'TXT', 'CNAME', 'SOA', 'CAA', 'SRV', 'PTR', 'ANY'].includes(arg.toUpperCase())) {
1755
- recordType = arg.toUpperCase();
1756
- }
1757
- else if (!domain) {
1758
- domain = arg;
1759
- }
1760
- }
1761
- if (!domain) {
1762
- domain = this.getBaseDomain() || '';
1763
- if (!domain) {
1764
- console.log(colors.yellow('Usage: dns:dig [@server] <domain> [type] [+short]'));
1765
- console.log(colors.gray(' Examples:'));
1766
- console.log(colors.gray(' dns:dig google.com'));
1767
- console.log(colors.gray(' dns:dig google.com MX'));
1768
- console.log(colors.gray(' dns:dig @8.8.8.8 google.com A'));
1769
- console.log(colors.gray(' dns:dig google.com TXT +short'));
1770
- return;
1771
- }
1772
- }
1773
- console.log(colors.gray(`Querying ${recordType} record for ${domain}${server ? ` via ${server}` : ''}...`));
1774
- try {
1775
- const { dig, formatDigOutput } = await import('../../utils/dns-toolkit.js');
1776
- const result = await dig(domain, { type: recordType, server: server || undefined });
1777
- console.log('');
1778
- if (shortMode) {
1779
- if (result.answer && result.answer.length > 0) {
1780
- result.answer.forEach((ans) => {
1781
- console.log(ans.data || ans.address || ans.exchange || JSON.stringify(ans));
1782
- });
1783
- }
1784
- else {
1785
- console.log(colors.gray('(no results)'));
1786
- }
1787
- }
1788
- else {
1789
- console.log(formatDigOutput(result, shortMode));
1790
- }
1791
- this.lastResponse = result;
1792
- }
1793
- catch (error) {
1794
- console.error(colors.red(`DNS lookup failed: ${error.message}`));
1795
- }
1796
- }
1797
- async runDnsGenerate(args) {
1798
- if (args.length === 0 || args[0] === 'help') {
1799
- console.log(colors.bold('DMARC Record Generator'));
1800
- console.log('');
1801
- console.log(colors.yellow('Usage: dns:generate <policy> [options]'));
1802
- console.log('');
1803
- console.log(colors.gray('Policies:'));
1804
- console.log(' none - Monitor only, take no action');
1805
- console.log(' quarantine - Mark suspicious emails as spam');
1806
- console.log(' reject - Block suspicious emails');
1807
- console.log('');
1808
- console.log(colors.gray('Options (key=value format):'));
1809
- console.log(' rua=<email> - Aggregate report address(es), comma-separated');
1810
- console.log(' ruf=<email> - Forensic report address(es), comma-separated');
1811
- console.log(' sp=<policy> - Subdomain policy (none|quarantine|reject)');
1812
- console.log(' pct=<0-100> - Percentage of messages to apply policy');
1813
- console.log(' adkim=<s|r> - DKIM alignment (s=strict, r=relaxed)');
1814
- console.log(' aspf=<s|r> - SPF alignment (s=strict, r=relaxed)');
1815
- console.log(' ri=<seconds> - Report interval (default: 86400 = 1 day)');
1816
- console.log('');
1817
- console.log(colors.gray('Examples:'));
1818
- console.log(' dns:generate reject');
1819
- console.log(' dns:generate reject rua=reports@example.com');
1820
- console.log(' dns:generate quarantine sp=reject pct=50');
1821
- console.log(' dns:generate reject rua=dmarc@example.com,backup@example.com');
1822
- return;
1823
- }
1824
- const policy = args[0].toLowerCase();
1825
- if (!['none', 'quarantine', 'reject'].includes(policy)) {
1826
- console.log(colors.red(`Invalid policy: ${policy}`));
1827
- console.log(colors.gray('Valid policies: none, quarantine, reject'));
1828
- return;
1829
- }
1830
- const options = {};
1831
- for (let i = 1; i < args.length; i++) {
1832
- const [key, ...valueParts] = args[i].split('=');
1833
- if (valueParts.length > 0) {
1834
- options[key.toLowerCase()] = valueParts.join('=');
1835
- }
1836
- }
1837
- try {
1838
- const { generateDmarc } = await import('../../utils/dns-toolkit.js');
1839
- const dmarcOptions = {
1840
- policy: policy,
1841
- };
1842
- if (options.sp) {
1843
- dmarcOptions.subdomainPolicy = options.sp;
1844
- }
1845
- if (options.pct) {
1846
- dmarcOptions.percentage = parseInt(options.pct, 10);
1847
- }
1848
- if (options.rua) {
1849
- dmarcOptions.aggregateReports = options.rua.split(',').map(e => e.trim());
1850
- }
1851
- if (options.ruf) {
1852
- dmarcOptions.forensicReports = options.ruf.split(',').map(e => e.trim());
1853
- }
1854
- if (options.adkim) {
1855
- dmarcOptions.alignmentDkim = options.adkim === 's' ? 'strict' : 'relaxed';
1856
- }
1857
- if (options.aspf) {
1858
- dmarcOptions.alignmentSpf = options.aspf === 's' ? 'strict' : 'relaxed';
1859
- }
1860
- if (options.ri) {
1861
- dmarcOptions.reportInterval = parseInt(options.ri, 10);
1862
- }
1863
- const record = generateDmarc(dmarcOptions);
1864
- console.log('');
1865
- console.log(colors.bold(colors.green('Generated DMARC Record')));
1866
- console.log('');
1867
- console.log(colors.gray('DNS Record Name:'));
1868
- console.log(` _dmarc.yourdomain.com`);
1869
- console.log('');
1870
- console.log(colors.gray('TXT Record Value:'));
1871
- console.log(` ${colors.cyan(record)}`);
1872
- console.log('');
1873
- console.log(colors.gray('Policy Summary:'));
1874
- console.log(` ${colors.gray('Policy:')} ${policy}`);
1875
- if (dmarcOptions.subdomainPolicy) {
1876
- console.log(` ${colors.gray('Subdomain Policy:')} ${dmarcOptions.subdomainPolicy}`);
1877
- }
1878
- if (dmarcOptions.percentage !== undefined && dmarcOptions.percentage !== 100) {
1879
- console.log(` ${colors.gray('Percentage:')} ${dmarcOptions.percentage}%`);
1880
- }
1881
- if (dmarcOptions.aggregateReports) {
1882
- console.log(` ${colors.gray('Aggregate Reports:')} ${dmarcOptions.aggregateReports.join(', ')}`);
1883
- }
1884
- if (dmarcOptions.forensicReports) {
1885
- console.log(` ${colors.gray('Forensic Reports:')} ${dmarcOptions.forensicReports.join(', ')}`);
1886
- }
1887
- console.log('');
1888
- this.lastResponse = { record, options: dmarcOptions };
1889
- }
1890
- catch (error) {
1891
- console.error(colors.red(`DMARC generation failed: ${error.message}`));
1892
- }
1893
- }
1894
- async runRDAP(domain) {
1895
- if (!domain) {
1896
- domain = this.getRootDomain() || '';
1897
- if (!domain) {
1898
- console.log(colors.yellow('Usage: rdap <domain>'));
1899
- console.log(colors.gray(' Examples: rdap google.com | rdap 8.8.8.8'));
1900
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1901
- return;
1902
- }
1903
- }
1904
- console.log(colors.gray(`RDAP lookup for ${domain}...`));
1905
- const startTime = performance.now();
1906
- try {
1907
- const result = await rdap(this.client, domain);
1908
- const duration = Math.round(performance.now() - startTime);
1909
- console.log(colors.green(`✔ RDAP lookup completed`) + colors.gray(` (${duration}ms)\n`));
1910
- if (result.status?.length) {
1911
- console.log(colors.bold(' Status:'));
1912
- result.status.forEach((s) => console.log(` ${colors.cyan('→')} ${s}`));
1913
- }
1914
- if (result.events?.length) {
1915
- console.log(colors.bold(' Events:'));
1916
- result.events.forEach((e) => {
1917
- const date = new Date(e.eventDate).toISOString().split('T')[0];
1918
- console.log(` ${colors.cyan(e.eventAction.padEnd(15))} ${date}`);
1919
- });
1920
- }
1921
- if (result.entities?.length) {
1922
- console.log(colors.bold(' Entities:'));
1923
- result.entities.forEach((e) => {
1924
- const roles = e.roles?.join(', ') || 'unknown';
1925
- console.log(` ${colors.cyan(roles.padEnd(15))} ${e.handle || 'N/A'}`);
1926
- });
1927
- }
1928
- if (result.handle) {
1929
- console.log(` ${colors.cyan('Handle')}: ${result.handle}`);
1930
- }
1931
- if (result.name) {
1932
- console.log(` ${colors.cyan('Name')}: ${result.name}`);
1933
- }
1934
- if (result.startAddress && result.endAddress) {
1935
- console.log(` ${colors.cyan('Range')}: ${result.startAddress} - ${result.endAddress}`);
1936
- }
1937
- this.lastResponse = result;
1938
- }
1939
- catch (error) {
1940
- console.error(colors.red(`RDAP lookup failed: ${error.message}`));
1941
- console.log(colors.gray(' Tip: RDAP may not be available for all TLDs. Try "whois" instead.'));
1942
- }
1943
- console.log('');
1944
- }
1945
- async runPing(host) {
1946
- if (!host) {
1947
- host = this.getBaseDomain() || '';
1948
- if (!host) {
1949
- console.log(colors.yellow('Usage: ping <host>'));
1950
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
1951
- return;
1952
- }
1953
- }
1954
- else {
1955
- host = host.replace(/^https?:\/\//, '').split('/')[0];
1956
- }
1957
- console.log(colors.gray(`Pinging ${host}...`));
1958
- try {
1959
- const { connect } = await import('node:net');
1960
- const port = 443;
1961
- const startTime = performance.now();
1962
- await new Promise((resolve, reject) => {
1963
- const socket = connect(port, host, () => {
1964
- const duration = Math.round(performance.now() - startTime);
1965
- console.log(colors.green(`✔ ${host}:${port} is reachable`) + colors.gray(` (${duration}ms)`));
1966
- socket.end();
1967
- resolve();
1968
- });
1969
- socket.on('error', reject);
1970
- socket.setTimeout(5000, () => {
1971
- socket.destroy();
1972
- reject(new Error('Connection timed out'));
1973
- });
1974
- });
1975
- }
1976
- catch (error) {
1977
- console.error(colors.red(`✖ ${host} is unreachable: ${error.message}`));
1978
- }
1979
- console.log('');
1980
- }
1981
- async runFtp(args) {
1982
- if (args.length === 0 || args[0] === 'help') {
1983
- console.log(colors.bold('FTP Client'));
1984
- console.log('');
1985
- console.log(colors.yellow('Usage: ftp <host> [command] [args...]'));
1986
- console.log('');
1987
- console.log(colors.gray('Commands:'));
1988
- console.log(' ftp <host> ls [path] - List directory');
1989
- console.log(' ftp <host> get <remote> - Download file');
1990
- console.log(' ftp <host> put <local> [remote]- Upload file');
1991
- console.log(' ftp <host> rm <path> - Delete file');
1992
- console.log(' ftp <host> mkdir <path> - Create directory');
1993
- console.log('');
1994
- console.log(colors.gray('Options (add after host):'));
1995
- console.log(' user=<username> - FTP username (default: anonymous)');
1996
- console.log(' pass=<password> - FTP password (default: anonymous@)');
1997
- console.log(' port=<number> - Port number (default: 21)');
1998
- console.log(' secure - Use FTPS (explicit TLS)');
1999
- console.log('');
2000
- console.log(colors.gray('Examples:'));
2001
- console.log(' ftp ftp.example.com ls');
2002
- console.log(' ftp ftp.example.com ls /pub');
2003
- console.log(' ftp ftp.example.com get /pub/file.txt');
2004
- console.log(' ftp ftp.example.com user=admin pass=secret ls');
2005
- return;
2006
- }
2007
- const host = args[0];
2008
- let command = 'ls';
2009
- let commandArgs = [];
2010
- const options = {};
2011
- for (let i = 1; i < args.length; i++) {
2012
- const arg = args[i];
2013
- if (arg.includes('=')) {
2014
- const [key, value] = arg.split('=');
2015
- options[key] = value;
2016
- }
2017
- else if (['ls', 'get', 'put', 'rm', 'mkdir'].includes(arg)) {
2018
- command = arg;
2019
- commandArgs = args.slice(i + 1).filter(a => !a.includes('='));
2020
- break;
2021
- }
2022
- else {
2023
- command = arg;
2024
- commandArgs = args.slice(i + 1).filter(a => !a.includes('='));
2025
- break;
2026
- }
2027
- }
2028
- const { createFTP } = await import('../../protocols/ftp.js');
2029
- const client = createFTP({
2030
- host,
2031
- port: parseInt(options.port || '21'),
2032
- user: options.user || 'anonymous',
2033
- password: options.pass || 'anonymous@',
2034
- secure: args.includes('secure'),
2035
- });
2036
- console.log(colors.gray(`Connecting to ${host}...`));
2037
- try {
2038
- const connectResult = await client.connect();
2039
- if (!connectResult.success) {
2040
- console.error(colors.red(`Connection failed: ${connectResult.message}`));
2041
- return;
2042
- }
2043
- console.log(colors.green('Connected'));
2044
- switch (command) {
2045
- case 'ls': {
2046
- const path = commandArgs[0] || '/';
2047
- console.log(colors.gray(`Listing ${path}...`));
2048
- const result = await client.list(path);
2049
- if (!result.success || !result.data) {
2050
- console.error(colors.red(`List failed: ${result.message}`));
2051
- break;
2052
- }
2053
- console.log('');
2054
- for (const item of result.data) {
2055
- const typeChar = item.type === 'directory' ? 'd' : item.type === 'link' ? 'l' : '-';
2056
- const perms = item.permissions || 'rwxr-xr-x';
2057
- const size = item.size.toString().padStart(10);
2058
- const date = item.rawModifiedAt || '';
2059
- const nameColor = item.type === 'directory' ? colors.blue : item.type === 'link' ? colors.cyan : (t) => t;
2060
- console.log(`${typeChar}${perms} ${size} ${date.padEnd(12)} ${nameColor(item.name)}`);
2061
- }
2062
- console.log('');
2063
- console.log(colors.gray(`Total: ${result.data.length} items`));
2064
- this.lastResponse = result.data;
2065
- break;
2066
- }
2067
- case 'get': {
2068
- const remote = commandArgs[0];
2069
- if (!remote) {
2070
- console.log(colors.yellow('Usage: ftp <host> get <remote-path>'));
2071
- break;
2072
- }
2073
- const path = await import('node:path');
2074
- const local = commandArgs[1] || path.basename(remote);
2075
- console.log(colors.gray(`Downloading ${remote} → ${local}...`));
2076
- const result = await client.download(remote, local);
2077
- if (!result.success) {
2078
- console.error(colors.red(`Download failed: ${result.message}`));
2079
- }
2080
- else {
2081
- console.log(colors.green(`✔ Downloaded to ${local}`));
2082
- }
2083
- break;
2084
- }
2085
- case 'put': {
2086
- const local = commandArgs[0];
2087
- if (!local) {
2088
- console.log(colors.yellow('Usage: ftp <host> put <local-path> [remote-path]'));
2089
- break;
2090
- }
2091
- const path = await import('node:path');
2092
- const remote = commandArgs[1] || '/' + path.basename(local);
2093
- console.log(colors.gray(`Uploading ${local} → ${remote}...`));
2094
- const result = await client.upload(local, remote);
2095
- if (!result.success) {
2096
- console.error(colors.red(`Upload failed: ${result.message}`));
2097
- }
2098
- else {
2099
- console.log(colors.green(`✔ Uploaded to ${remote}`));
2100
- }
2101
- break;
2102
- }
2103
- case 'rm': {
2104
- const remotePath = commandArgs[0];
2105
- if (!remotePath) {
2106
- console.log(colors.yellow('Usage: ftp <host> rm <remote-path>'));
2107
- break;
2108
- }
2109
- console.log(colors.gray(`Deleting ${remotePath}...`));
2110
- const result = await client.delete(remotePath);
2111
- if (!result.success) {
2112
- console.error(colors.red(`Delete failed: ${result.message}`));
2113
- }
2114
- else {
2115
- console.log(colors.green(`✔ Deleted ${remotePath}`));
2116
- }
2117
- break;
2118
- }
2119
- case 'mkdir': {
2120
- const remotePath = commandArgs[0];
2121
- if (!remotePath) {
2122
- console.log(colors.yellow('Usage: ftp <host> mkdir <remote-path>'));
2123
- break;
2124
- }
2125
- console.log(colors.gray(`Creating ${remotePath}...`));
2126
- const result = await client.mkdir(remotePath);
2127
- if (!result.success) {
2128
- console.error(colors.red(`Mkdir failed: ${result.message}`));
2129
- }
2130
- else {
2131
- console.log(colors.green(`✔ Created ${remotePath}`));
2132
- }
2133
- break;
2134
- }
2135
- default:
2136
- console.log(colors.yellow(`Unknown FTP command: ${command}`));
2137
- console.log(colors.gray('Valid commands: ls, get, put, rm, mkdir'));
2138
- }
2139
- await client.close();
2140
- }
2141
- catch (error) {
2142
- console.error(colors.red(`FTP Error: ${error.message}`));
2143
- }
2144
- console.log('');
2145
- }
2146
- async runTelnet(host, portStr) {
2147
- if (!host) {
2148
- console.log(colors.bold('Telnet Client'));
2149
- console.log('');
2150
- console.log(colors.yellow('Usage: telnet <host> [port]'));
2151
- console.log('');
2152
- console.log(colors.gray('Examples:'));
2153
- console.log(' telnet towel.blinkenlights.nl');
2154
- console.log(' telnet localhost 8023');
2155
- console.log(' telnet mail.example.com 25');
2156
- console.log('');
2157
- console.log(colors.gray('Note: Type "exit" or Ctrl+C to disconnect'));
2158
- return;
2159
- }
2160
- const port = parseInt(portStr || '23');
2161
- console.log(colors.gray(`Connecting to ${host}:${port}...`));
2162
- try {
2163
- const { createTelnet } = await import('../../protocols/telnet.js');
2164
- const client = createTelnet({
2165
- host,
2166
- port,
2167
- timeout: 30000,
2168
- });
2169
- await client.connect();
2170
- console.log(colors.green(`Connected to ${host}:${port}`));
2171
- console.log(colors.gray('Interactive mode. Type "exit" to disconnect.'));
2172
- console.log('');
2173
- const originalPrompt = this.rl.getPrompt();
2174
- client.on('data', (data) => {
2175
- process.stdout.write(data);
2176
- });
2177
- client.on('close', () => {
2178
- console.log(colors.yellow('\nConnection closed'));
2179
- this.rl.setPrompt(originalPrompt);
2180
- this.prompt();
2181
- });
2182
- const telnetPrompt = () => {
2183
- this.rl.question('', async (input) => {
2184
- if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
2185
- console.log(colors.yellow('Disconnecting...'));
2186
- await client.close();
2187
- this.rl.setPrompt(originalPrompt);
2188
- this.prompt();
2189
- return;
2190
- }
2191
- await client.send(input + '\r\n');
2192
- telnetPrompt();
2193
- });
2194
- };
2195
- telnetPrompt();
2196
- }
2197
- catch (error) {
2198
- console.error(colors.red(`Telnet Error: ${error.message}`));
2199
- console.log('');
2200
- }
2201
- }
2202
- async runScrap(url) {
2203
- if (!url) {
2204
- if (!this.baseUrl) {
2205
- console.log(colors.yellow('Usage: scrap <url>'));
2206
- console.log(colors.gray(' Examples: scrap https://news.ycombinator.com'));
2207
- console.log(colors.gray(' Or set a base URL first: url https://example.com'));
2208
- return;
2209
- }
2210
- url = this.baseUrl;
2211
- }
2212
- else if (!url.startsWith('http')) {
2213
- url = this.baseUrl ? `${this.baseUrl}${url.startsWith('/') ? '' : '/'}${url}` : `https://${url}`;
2214
- }
2215
- console.log(colors.gray(`Fetching ${url}...`));
2216
- const startTime = performance.now();
2217
- try {
2218
- const response = await this.client.get(url);
2219
- const html = await response.text();
2220
- const duration = Math.round(performance.now() - startTime);
2221
- this.currentDoc = await ScrapeDocument.create(html, { baseUrl: url });
2222
- this.currentDocUrl = url;
2223
- const elementCount = this.currentDoc.select('*').length;
2224
- const title = this.currentDoc.selectFirst('title').text() || 'No title';
2225
- const meta = this.currentDoc.meta();
2226
- const og = this.currentDoc.openGraph();
2227
- console.log(colors.green(`✔ Loaded`) + colors.gray(` (${duration}ms)`));
2228
- console.log(` ${colors.cyan('Title')}: ${title}`);
2229
- console.log(` ${colors.cyan('Elements')}: ${elementCount}`);
2230
- console.log(` ${colors.cyan('Size')}: ${(html.length / 1024).toFixed(1)}kb`);
2231
- if (meta.description) {
2232
- const desc = meta.description.length > 100 ? meta.description.slice(0, 100) + '...' : meta.description;
2233
- console.log(` ${colors.cyan('Description')}: ${desc}`);
2234
- }
2235
- const hasOg = og.title || og.description || og.image || og.siteName;
2236
- if (hasOg) {
2237
- console.log(colors.bold('\n OpenGraph:'));
2238
- if (og.siteName)
2239
- console.log(` ${colors.magenta('Site')}: ${og.siteName}`);
2240
- if (og.title && og.title !== title)
2241
- console.log(` ${colors.magenta('Title')}: ${og.title}`);
2242
- if (og.type)
2243
- console.log(` ${colors.magenta('Type')}: ${og.type}`);
2244
- if (og.description) {
2245
- const ogDesc = og.description.length > 80 ? og.description.slice(0, 80) + '...' : og.description;
2246
- console.log(` ${colors.magenta('Description')}: ${ogDesc}`);
2247
- }
2248
- if (og.image) {
2249
- const images = Array.isArray(og.image) ? og.image : [og.image];
2250
- console.log(` ${colors.magenta('Image')}: ${images[0]}`);
2251
- if (images.length > 1)
2252
- console.log(colors.gray(` (+${images.length - 1} more)`));
2253
- }
2254
- if (og.url && og.url !== url)
2255
- console.log(` ${colors.magenta('URL')}: ${og.url}`);
2256
- }
2257
- console.log(colors.gray('\n Use $ <selector> to query, $text, $attr, $links, $images, $scripts, $css, $sourcemaps, $table'));
2258
- }
2259
- catch (error) {
2260
- console.error(colors.red(`Scrape failed: ${error.message}`));
2261
- }
2262
- console.log('');
2263
- }
2264
- async runSpider(args) {
2265
- let url = '';
2266
- let maxDepth = 5;
2267
- let maxPages = 100;
2268
- let concurrency = 5;
2269
- let seoEnabled = false;
2270
- let outputFile = '';
2271
- for (let i = 0; i < args.length; i++) {
2272
- const arg = args[i];
2273
- if (arg.startsWith('depth=')) {
2274
- maxDepth = parseInt(arg.split('=')[1]) || 5;
2275
- }
2276
- else if (arg.startsWith('limit=')) {
2277
- maxPages = parseInt(arg.split('=')[1]) || 100;
2278
- }
2279
- else if (arg.startsWith('concurrency=')) {
2280
- concurrency = parseInt(arg.split('=')[1]) || 5;
2281
- }
2282
- else if (arg === 'seo') {
2283
- seoEnabled = true;
2284
- }
2285
- else if (arg.startsWith('output=')) {
2286
- outputFile = arg.split('=')[1] || '';
2287
- }
2288
- else if (!arg.includes('=')) {
2289
- url = arg;
2290
- }
2291
- }
2292
- if (!url) {
2293
- if (!this.baseUrl) {
2294
- console.log(colors.yellow('Usage: spider <url> [options]'));
2295
- console.log(colors.gray(' Options:'));
2296
- console.log(colors.gray(' depth=5 Max crawl depth'));
2297
- console.log(colors.gray(' limit=100 Max pages to crawl'));
2298
- console.log(colors.gray(' concurrency=5 Concurrent requests'));
2299
- console.log(colors.gray(' seo Enable SEO analysis'));
2300
- console.log(colors.gray(' output=file.json Save JSON report'));
2301
- console.log(colors.gray(' Examples:'));
2302
- console.log(colors.gray(' spider example.com'));
2303
- console.log(colors.gray(' spider example.com depth=2 limit=50'));
2304
- console.log(colors.gray(' spider example.com seo output=seo-report.json'));
2305
- return;
2306
- }
2307
- url = this.baseUrl;
2308
- }
2309
- else if (!url.startsWith('http')) {
2310
- url = `https://${url}`;
2311
- }
2312
- console.log(colors.cyan(`\nSpider starting: ${url}`));
2313
- const modeLabel = seoEnabled ? colors.magenta(' + SEO') : '';
2314
- console.log(colors.gray(` Depth: ${maxDepth} | Limit: ${maxPages} | Concurrency: ${concurrency}${modeLabel}`));
2315
- if (outputFile) {
2316
- console.log(colors.gray(` Output: ${outputFile}`));
2317
- }
2318
- console.log('');
2319
- if (seoEnabled) {
2320
- const seoSpider = new SeoSpider({
2321
- maxDepth,
2322
- maxPages,
2323
- concurrency,
2324
- sameDomain: true,
2325
- delay: 100,
2326
- seo: true,
2327
- output: outputFile || undefined,
2328
- onProgress: (progress) => {
2329
- process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
2330
- },
2331
- });
2332
- try {
2333
- const result = await seoSpider.crawl(url);
2334
- process.stdout.write('\r' + ' '.repeat(80) + '\r');
2335
- console.log(colors.green(`\n✔ SEO Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
2336
- console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
2337
- console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
2338
- console.log(` ${colors.cyan('Avg SEO Score')}: ${result.summary.avgScore}/100`);
2339
- const responseTimes = result.pages.filter(p => p.duration > 0).map(p => p.duration);
2340
- const avgResponseTime = responseTimes.length > 0
2341
- ? Math.round(responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length)
2342
- : 0;
2343
- const minResponseTime = responseTimes.length > 0 ? Math.min(...responseTimes) : 0;
2344
- const maxResponseTime = responseTimes.length > 0 ? Math.max(...responseTimes) : 0;
2345
- const reqPerSec = result.duration > 0 ? (result.pages.length / (result.duration / 1000)).toFixed(1) : '0';
2346
- const statusCounts = new Map();
2347
- for (const page of result.pages) {
2348
- const status = page.status || 0;
2349
- statusCounts.set(status, (statusCounts.get(status) || 0) + 1);
2350
- }
2351
- let totalInternalLinks = 0;
2352
- let totalExternalLinks = 0;
2353
- let totalImages = 0;
2354
- let imagesWithoutAlt = 0;
2355
- let pagesWithoutTitle = 0;
2356
- let pagesWithoutDescription = 0;
2357
- for (const page of result.pages) {
2358
- if (page.seoReport) {
2359
- totalInternalLinks += page.seoReport.links?.internal || 0;
2360
- totalExternalLinks += page.seoReport.links?.external || 0;
2361
- totalImages += page.seoReport.images?.total || 0;
2362
- imagesWithoutAlt += page.seoReport.images?.withoutAlt || 0;
2363
- if (!page.seoReport.title?.text)
2364
- pagesWithoutTitle++;
2365
- if (!page.seoReport.metaDescription?.text)
2366
- pagesWithoutDescription++;
2367
- }
2368
- }
2369
- console.log(colors.bold('\n Performance:'));
2370
- console.log(` ${colors.gray('Avg Response:')} ${avgResponseTime}ms`);
2371
- console.log(` ${colors.gray('Min/Max:')} ${minResponseTime}ms / ${maxResponseTime}ms`);
2372
- console.log(` ${colors.gray('Throughput:')} ${reqPerSec} req/s`);
2373
- console.log(colors.bold('\n HTTP Status:'));
2374
- const sortedStatuses = Array.from(statusCounts.entries()).sort((a, b) => b[1] - a[1]);
2375
- for (const [status, count] of sortedStatuses.slice(0, 5)) {
2376
- const statusLabel = status === 0 ? 'Error' : status.toString();
2377
- const statusColor = status >= 400 || status === 0 ? colors.red :
2378
- status >= 300 ? colors.yellow : colors.green;
2379
- const pct = ((count / result.pages.length) * 100).toFixed(0);
2380
- console.log(` ${statusColor(statusLabel.padEnd(5))} ${count.toString().padStart(3)} (${pct}%)`);
2381
- }
2382
- console.log(colors.bold('\n Content:'));
2383
- console.log(` ${colors.gray('Internal links:')} ${totalInternalLinks.toLocaleString()}`);
2384
- console.log(` ${colors.gray('External links:')} ${totalExternalLinks.toLocaleString()}`);
2385
- console.log(` ${colors.gray('Images:')} ${totalImages.toLocaleString()} (${imagesWithoutAlt} missing alt)`);
2386
- console.log(` ${colors.gray('Missing title:')} ${pagesWithoutTitle}`);
2387
- console.log(` ${colors.gray('Missing desc:')} ${pagesWithoutDescription}`);
2388
- console.log(colors.bold('\n SEO Summary:'));
2389
- const { summary } = result;
2390
- console.log(` ${colors.red('✗')} Pages with errors: ${summary.pagesWithErrors}`);
2391
- console.log(` ${colors.yellow('⚠')} Pages with warnings: ${summary.pagesWithWarnings}`);
2392
- console.log(` ${colors.magenta('⚐')} Duplicate titles: ${summary.duplicateTitles}`);
2393
- console.log(` ${colors.magenta('⚐')} Duplicate descriptions:${summary.duplicateDescriptions}`);
2394
- console.log(` ${colors.magenta('⚐')} Duplicate H1s: ${summary.duplicateH1s}`);
2395
- console.log(` ${colors.gray('○')} Orphan pages: ${summary.orphanPages}`);
2396
- if (result.siteWideIssues.length > 0) {
2397
- console.log(colors.bold('\n Site-Wide Issues:'));
2398
- for (const issue of result.siteWideIssues.slice(0, 10)) {
2399
- const icon = issue.severity === 'error' ? colors.red('✗') :
2400
- issue.severity === 'warning' ? colors.yellow('⚠') : colors.gray('○');
2401
- console.log(` ${icon} ${issue.message}`);
2402
- if (issue.value) {
2403
- const truncatedValue = issue.value.length > 50 ? issue.value.slice(0, 47) + '...' : issue.value;
2404
- console.log(` ${colors.gray(`"${truncatedValue}"`)}`);
2405
- }
2406
- const uniquePaths = [...new Set(issue.affectedUrls.map(u => new URL(u).pathname))];
2407
- if (uniquePaths.length <= 3) {
2408
- for (const path of uniquePaths) {
2409
- console.log(` ${colors.gray('→')} ${path}`);
2410
- }
2411
- }
2412
- else {
2413
- console.log(` ${colors.gray(`→ ${uniquePaths.length} pages affected`)}`);
2414
- }
2415
- }
2416
- if (result.siteWideIssues.length > 10) {
2417
- console.log(colors.gray(` ... and ${result.siteWideIssues.length - 10} more issues`));
2418
- }
2419
- }
2420
- const pagesWithScores = result.pages
2421
- .filter(p => p.seoReport)
2422
- .sort((a, b) => (a.seoReport?.score || 0) - (b.seoReport?.score || 0));
2423
- const seenPaths = new Set();
2424
- const uniquePages = pagesWithScores.filter(page => {
2425
- const path = new URL(page.url).pathname;
2426
- if (seenPaths.has(path))
2427
- return false;
2428
- seenPaths.add(path);
2429
- return true;
2430
- });
2431
- if (uniquePages.length > 0) {
2432
- console.log(colors.bold('\n Pages by SEO Score:'));
2433
- const worstPages = uniquePages.slice(0, 5);
2434
- for (const page of worstPages) {
2435
- const score = page.seoReport?.score || 0;
2436
- const grade = page.seoReport?.grade || '?';
2437
- const path = new URL(page.url).pathname;
2438
- const scoreColor = score >= 80 ? colors.green : score >= 60 ? colors.yellow : colors.red;
2439
- console.log(` ${scoreColor(`${score.toString().padStart(3)}`)} ${colors.gray(`[${grade}]`)} ${path.slice(0, 50)}`);
2440
- }
2441
- if (uniquePages.length > 5) {
2442
- console.log(colors.gray(` ... and ${uniquePages.length - 5} more pages`));
2443
- }
2444
- }
2445
- if (outputFile) {
2446
- console.log(colors.green(`\n Report saved to: ${outputFile}`));
2447
- }
2448
- this.lastResponse = result;
2449
- console.log(colors.gray('\n Result stored in lastResponse.'));
2450
- }
2451
- catch (error) {
2452
- console.error(colors.red(`SEO Spider failed: ${error.message}`));
2453
- }
2454
- }
2455
- else {
2456
- const spider = new Spider({
2457
- maxDepth,
2458
- maxPages,
2459
- concurrency,
2460
- sameDomain: true,
2461
- delay: 100,
2462
- onProgress: (progress) => {
2463
- process.stdout.write(`\r${colors.gray(' Crawling:')} ${colors.cyan(progress.crawled.toString())} pages | ${colors.gray('Queue:')} ${progress.queued} | ${colors.gray('Depth:')} ${progress.depth} `);
2464
- },
2465
- });
2466
- try {
2467
- const result = await spider.crawl(url);
2468
- process.stdout.write('\r' + ' '.repeat(80) + '\r');
2469
- console.log(colors.green(`\n✔ Spider complete`) + colors.gray(` (${(result.duration / 1000).toFixed(1)}s)`));
2470
- console.log(` ${colors.cyan('Pages crawled')}: ${result.pages.length}`);
2471
- console.log(` ${colors.cyan('Unique URLs')}: ${result.visited.size}`);
2472
- console.log(` ${colors.cyan('Errors')}: ${result.errors.length}`);
2473
- const byDepth = new Map();
2474
- for (const page of result.pages) {
2475
- byDepth.set(page.depth, (byDepth.get(page.depth) || 0) + 1);
2476
- }
2477
- console.log(colors.bold('\n Pages by depth:'));
2478
- for (const [depth, count] of Array.from(byDepth.entries()).sort((a, b) => a[0] - b[0])) {
2479
- const bar = '█'.repeat(Math.min(count, 40));
2480
- console.log(` ${colors.gray(`d${depth}:`)} ${bar} ${count}`);
2481
- }
2482
- const topPages = [...result.pages]
2483
- .filter(p => !p.error)
2484
- .sort((a, b) => b.links.length - a.links.length)
2485
- .slice(0, 10);
2486
- if (topPages.length > 0) {
2487
- console.log(colors.bold('\n Top pages by outgoing links:'));
2488
- for (const page of topPages) {
2489
- const title = page.title.slice(0, 40) || new URL(page.url).pathname;
2490
- console.log(` ${colors.cyan(page.links.length.toString().padStart(3))} ${title}`);
2491
- }
2492
- }
2493
- if (result.errors.length > 0) {
2494
- const errorSummary = summarizeErrors(result.errors);
2495
- console.log(formatErrorSummary(errorSummary));
2496
- }
2497
- if (outputFile) {
2498
- const reportData = {
2499
- ...result,
2500
- visited: Array.from(result.visited),
2501
- generatedAt: new Date().toISOString(),
2502
- };
2503
- await fs.writeFile(outputFile, JSON.stringify(reportData, null, 2), 'utf-8');
2504
- console.log(colors.green(`\n Report saved to: ${outputFile}`));
2505
- }
2506
- this.lastResponse = result;
2507
- console.log(colors.gray('\n Result stored in lastResponse. Use $links to explore.'));
2508
- }
2509
- catch (error) {
2510
- console.error(colors.red(`Spider failed: ${error.message}`));
2511
- }
2512
- }
2513
- console.log('');
2514
- }
2515
- async runSelect(selector) {
2516
- if (!this.currentDoc) {
2517
- console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
2518
- return;
2519
- }
2520
- if (!selector) {
2521
- console.log(colors.yellow('Usage: $ <selector>'));
2522
- console.log(colors.gray(' Examples: $ h1 | $ .title | $ a[href*="article"]'));
2523
- return;
2524
- }
2525
- try {
2526
- const elements = this.currentDoc.select(selector);
2527
- const count = elements.length;
2528
- console.log(colors.cyan(`Found ${count} element(s)`));
2529
- if (count > 0 && count <= 10) {
2530
- elements.each((el, i) => {
2531
- const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
2532
- console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
2533
- });
2534
- }
2535
- else if (count > 10) {
2536
- console.log(colors.gray(' (showing first 10)'));
2537
- let shown = 0;
2538
- elements.each((el, i) => {
2539
- if (shown >= 10)
2540
- return;
2541
- const text = el.text().slice(0, 80).replace(/\s+/g, ' ').trim();
2542
- console.log(` ${colors.gray(`${i + 1}.`)} ${text}${text.length >= 80 ? '...' : ''}`);
2543
- shown++;
2544
- });
2545
- }
2546
- this.lastResponse = { count, selector };
2547
- }
2548
- catch (error) {
2549
- console.error(colors.red(`Query failed: ${error.message}`));
2550
- }
2551
- console.log('');
2552
- }
2553
- async runSelectText(selector) {
2554
- if (!this.currentDoc) {
2555
- console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
2556
- return;
2557
- }
2558
- if (!selector) {
2559
- console.log(colors.yellow('Usage: $text <selector>'));
2560
- return;
2561
- }
2562
- try {
2563
- const elements = this.currentDoc.select(selector);
2564
- const texts = [];
2565
- elements.each((el, i) => {
2566
- const text = el.text().trim();
2567
- if (text) {
2568
- texts.push(text);
2569
- console.log(`${colors.gray(`${i + 1}.`)} ${text.slice(0, 200)}${text.length > 200 ? '...' : ''}`);
2570
- }
2571
- });
2572
- this.lastResponse = texts;
2573
- console.log(colors.gray(`\n ${texts.length} text item(s) extracted`));
2574
- }
2575
- catch (error) {
2576
- console.error(colors.red(`Query failed: ${error.message}`));
2577
- }
2578
- console.log('');
2579
- }
2580
- async runSelectAttr(attrName, selector) {
2581
- if (!this.currentDoc) {
2582
- console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
2583
- return;
2584
- }
2585
- if (!attrName || !selector) {
2586
- console.log(colors.yellow('Usage: $attr <attribute> <selector>'));
2587
- console.log(colors.gray(' Examples: $attr href a | $attr src img'));
2588
- return;
2589
- }
2590
- try {
2591
- const elements = this.currentDoc.select(selector);
2592
- const attrs = [];
2593
- elements.each((el, i) => {
2594
- const value = el.attr(attrName);
2595
- if (value) {
2596
- attrs.push(value);
2597
- console.log(`${colors.gray(`${i + 1}.`)} ${value}`);
2598
- }
2599
- });
2600
- this.lastResponse = attrs;
2601
- console.log(colors.gray(`\n ${attrs.length} attribute(s) extracted`));
2602
- }
2603
- catch (error) {
2604
- console.error(colors.red(`Query failed: ${error.message}`));
2605
- }
2606
- console.log('');
2607
- }
2608
- async runSelectHtml(selector) {
2609
- if (!this.currentDoc) {
2610
- console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
2611
- return;
2612
- }
2613
- if (!selector) {
2614
- console.log(colors.yellow('Usage: $html <selector>'));
2615
- return;
2616
- }
2617
- try {
2618
- const element = this.currentDoc.selectFirst(selector);
2619
- const html = element.html();
2620
- if (html) {
2621
- console.log(html.slice(0, 1000));
2622
- if (html.length > 1000) {
2623
- console.log(colors.gray(`\n ... (${html.length} chars total)`));
2624
- }
2625
- this.lastResponse = html;
2626
- }
2627
- else {
2628
- console.log(colors.gray('No element found'));
2629
- }
2630
- }
2631
- catch (error) {
2632
- console.error(colors.red(`Query failed: ${error.message}`));
2633
- }
2634
- console.log('');
2635
- }
2636
- async runSelectLinks(selector) {
2637
- if (!this.currentDoc) {
2638
- console.log(colors.yellow('No document loaded. Use "scrap <url>" first.'));
2639
- return;
2640
- }
2641
- try {
2642
- const linkSelector = selector || 'a[href]';
2643
- const elements = this.currentDoc.select(linkSelector);
2644
- const links = [];
2645
- elements.each((el, i) => {
2646
- const href = el.attr('href');
2647
- const text = el.text().trim().slice(0, 50);
2648
- if (href) {
2649
- links.push({ text, href });
2650
- if (i < 20) {
2651
- console.log(`${colors.gray(`${i + 1}.`)} ${colors.cyan(text || '(no text)')} ${colors.gray('→')} ${href}`);
2652
- }
2653
- }
2654
- });
2655
- if (links.length > 20) {
2656
- console.log(colors.gray(` ... and ${links.length - 20} more links`));
2657
- }
2658
- this.lastResponse = links;
2659
- console.log(colors.gray(`\n ${links.length} link(s) found`));
2660
- }
2661
- catch (error) {
2662
- console.error(colors.red(`Query failed: ${error.message}`));
2663
- }
2664
- console.log('');
1368
+ console.log('');
2665
1369
  }
2666
1370
  async runSelectImages(selector) {
2667
1371
  if (!this.currentDoc) {
@@ -3378,44 +2082,6 @@ ${colors.bold('Network:')}
3378
2082
  harRecording = false;
3379
2083
  harFile = '';
3380
2084
  harEntries = [];
3381
- async runHar(args) {
3382
- if (args.length === 0 || args[0] === 'help') {
3383
- console.log(colors.bold('HAR Recording & Playback'));
3384
- console.log('');
3385
- console.log(colors.yellow('Commands:'));
3386
- console.log(' har:record <file> Start recording requests to HAR file');
3387
- console.log(' har:play <file> Replay requests from HAR file');
3388
- console.log(' har:info <file> Show HAR file information');
3389
- console.log('');
3390
- console.log(colors.gray('Examples:'));
3391
- console.log(' har:record api.har Start recording session');
3392
- console.log(' har:play api.har Replay recorded requests');
3393
- console.log(' har:info api.har Inspect HAR file contents');
3394
- console.log('');
3395
- console.log(colors.gray('Recording mode:'));
3396
- console.log(' While recording, all HTTP requests are saved to the HAR file.');
3397
- console.log(' Use "har:stop" to end recording.');
3398
- return;
3399
- }
3400
- const [subCmd, ...rest] = args;
3401
- switch (subCmd) {
3402
- case 'record':
3403
- await this.runHarRecord(rest);
3404
- break;
3405
- case 'play':
3406
- await this.runHarPlay(rest);
3407
- break;
3408
- case 'info':
3409
- await this.runHarInfo(rest[0]);
3410
- break;
3411
- case 'stop':
3412
- await this.runHarStop();
3413
- break;
3414
- default:
3415
- console.log(colors.yellow(`Unknown HAR command: ${subCmd}`));
3416
- console.log(colors.gray('Use "har help" for usage'));
3417
- }
3418
- }
3419
2085
  async runHarRecord(args) {
3420
2086
  if (args.length === 0) {
3421
2087
  console.log(colors.yellow('Usage: har:record <file>'));
@@ -3547,329 +2213,6 @@ ${colors.bold('Network:')}
3547
2213
  }
3548
2214
  console.log('');
3549
2215
  }
3550
- async runGraphQL(args) {
3551
- if (args.length === 0 || args[0] === 'help') {
3552
- console.log(colors.bold('GraphQL Client'));
3553
- console.log('');
3554
- console.log(colors.yellow('Usage: graphql <endpoint> <query> [variables...]'));
3555
- console.log('');
3556
- console.log(colors.gray('Arguments:'));
3557
- console.log(' <endpoint> GraphQL endpoint URL or path');
3558
- console.log(' <query> GraphQL query string (inline or @file.graphql)');
3559
- console.log(' [variables] Variables as key=value pairs');
3560
- console.log('');
3561
- console.log(colors.gray('Examples:'));
3562
- console.log(' graphql /graphql "{ users { id name } }"');
3563
- console.log(' graphql https://api.example.com/graphql "query GetUser($id: ID!) { user(id: $id) { name } }" id=123');
3564
- console.log(' graphql /graphql @query.graphql userId=abc');
3565
- console.log('');
3566
- console.log(colors.gray('Notes:'));
3567
- console.log(' - Use @filename to load query from file');
3568
- console.log(' - Variables are passed as key=value, JSON supported');
3569
- return;
3570
- }
3571
- let endpoint = args[0];
3572
- let query = args[1];
3573
- const variables = {};
3574
- if (!query) {
3575
- console.log(colors.yellow('Missing GraphQL query. Use "graphql help" for usage.'));
3576
- return;
3577
- }
3578
- if (!endpoint.startsWith('http')) {
3579
- if (this.baseUrl) {
3580
- endpoint = `${this.baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
3581
- }
3582
- else {
3583
- console.log(colors.yellow('No base URL set. Provide full URL or use "url <baseUrl>" first.'));
3584
- return;
3585
- }
3586
- }
3587
- if (query.startsWith('@')) {
3588
- const filePath = query.slice(1);
3589
- try {
3590
- const { promises: fs } = await import('node:fs');
3591
- query = await fs.readFile(filePath, 'utf-8');
3592
- console.log(colors.gray(`Loaded query from ${filePath}`));
3593
- }
3594
- catch (err) {
3595
- console.log(colors.red(`Failed to load query file: ${err.message}`));
3596
- return;
3597
- }
3598
- }
3599
- for (let i = 2; i < args.length; i++) {
3600
- const arg = args[i];
3601
- const eqIndex = arg.indexOf('=');
3602
- if (eqIndex > 0) {
3603
- const key = arg.slice(0, eqIndex);
3604
- let value = arg.slice(eqIndex + 1);
3605
- try {
3606
- value = JSON.parse(value);
3607
- }
3608
- catch {
3609
- }
3610
- variables[key] = value;
3611
- }
3612
- }
3613
- console.log(colors.gray(`POST ${endpoint}`));
3614
- if (Object.keys(variables).length > 0) {
3615
- console.log(colors.gray(`Variables: ${JSON.stringify(variables)}`));
3616
- }
3617
- const startTime = performance.now();
3618
- try {
3619
- const response = await this.client.post(endpoint, {
3620
- query,
3621
- variables: Object.keys(variables).length > 0 ? variables : undefined,
3622
- });
3623
- const duration = Math.round(performance.now() - startTime);
3624
- const result = await response.json();
3625
- if (result.errors && result.errors.length > 0) {
3626
- console.log(colors.yellow(`\nGraphQL Errors (${duration}ms):`));
3627
- for (const error of result.errors) {
3628
- console.log(colors.red(` • ${error.message}`));
3629
- }
3630
- if (result.data) {
3631
- console.log(colors.gray('\nPartial data:'));
3632
- console.log(highlight(JSON.stringify(result.data, null, 2)));
3633
- }
3634
- }
3635
- else {
3636
- console.log(colors.green(`\n✔ Success`) + colors.gray(` (${duration}ms)`));
3637
- console.log('');
3638
- console.log(highlight(JSON.stringify(result.data, null, 2)));
3639
- }
3640
- this.lastResponse = result;
3641
- }
3642
- catch (error) {
3643
- console.error(colors.red(`GraphQL Error: ${error.message}`));
3644
- }
3645
- console.log('');
3646
- }
3647
- async runJsonRpc(args) {
3648
- if (args.length === 0 || args[0] === 'help') {
3649
- console.log(colors.bold('JSON-RPC 2.0 Client'));
3650
- console.log('');
3651
- console.log(colors.yellow('Usage: jsonrpc <endpoint> <method> [params...]'));
3652
- console.log('');
3653
- console.log(colors.gray('Arguments:'));
3654
- console.log(' <endpoint> JSON-RPC endpoint URL or path');
3655
- console.log(' <method> RPC method name');
3656
- console.log(' [params] Positional args or key=value for named params');
3657
- console.log('');
3658
- console.log(colors.gray('Examples:'));
3659
- console.log(' jsonrpc /rpc add 1 2');
3660
- console.log(' jsonrpc /rpc getUser id=123');
3661
- console.log(' jsonrpc https://api.example.com/rpc eth_blockNumber');
3662
- console.log('');
3663
- console.log(colors.gray('Notes:'));
3664
- console.log(' - Positional params: jsonrpc /rpc add 1 2 → params: [1, 2]');
3665
- console.log(' - Named params: jsonrpc /rpc getUser id=123 → params: {id: 123}');
3666
- console.log(' - Values are auto-parsed (numbers, booleans, JSON)');
3667
- return;
3668
- }
3669
- let endpoint = args[0];
3670
- const method = args[1];
3671
- if (!method) {
3672
- console.log(colors.yellow('Missing RPC method. Use "jsonrpc help" for usage.'));
3673
- return;
3674
- }
3675
- if (!endpoint.startsWith('http')) {
3676
- if (this.baseUrl) {
3677
- endpoint = `${this.baseUrl}${endpoint.startsWith('/') ? '' : '/'}${endpoint}`;
3678
- }
3679
- else {
3680
- console.log(colors.yellow('No base URL set. Provide full URL or use "url <baseUrl>" first.'));
3681
- return;
3682
- }
3683
- }
3684
- let params;
3685
- const positional = [];
3686
- const named = {};
3687
- let hasNamed = false;
3688
- for (let i = 2; i < args.length; i++) {
3689
- const arg = args[i];
3690
- const eqIndex = arg.indexOf('=');
3691
- if (eqIndex > 0) {
3692
- hasNamed = true;
3693
- const key = arg.slice(0, eqIndex);
3694
- let value = arg.slice(eqIndex + 1);
3695
- try {
3696
- value = JSON.parse(value);
3697
- }
3698
- catch {
3699
- }
3700
- named[key] = value;
3701
- }
3702
- else {
3703
- let value = arg;
3704
- try {
3705
- value = JSON.parse(arg);
3706
- }
3707
- catch {
3708
- }
3709
- positional.push(value);
3710
- }
3711
- }
3712
- if (hasNamed && positional.length === 0) {
3713
- params = named;
3714
- }
3715
- else if (!hasNamed && positional.length > 0) {
3716
- params = positional;
3717
- }
3718
- else if (hasNamed && positional.length > 0) {
3719
- console.log(colors.yellow('Warning: Mixed params detected. Using named params only.'));
3720
- params = named;
3721
- }
3722
- const rpcRequest = {
3723
- jsonrpc: '2.0',
3724
- method,
3725
- params,
3726
- id: Date.now(),
3727
- };
3728
- console.log(colors.gray(`POST ${endpoint}`));
3729
- console.log(colors.gray(`Method: ${method}`));
3730
- if (params) {
3731
- console.log(colors.gray(`Params: ${JSON.stringify(params)}`));
3732
- }
3733
- const startTime = performance.now();
3734
- try {
3735
- const response = await this.client.post(endpoint, rpcRequest);
3736
- const duration = Math.round(performance.now() - startTime);
3737
- const result = await response.json();
3738
- if (result.error) {
3739
- console.log(colors.red(`\nRPC Error (${duration}ms):`));
3740
- console.log(colors.red(` Code: ${result.error.code}`));
3741
- console.log(colors.red(` Message: ${result.error.message}`));
3742
- if (result.error.data) {
3743
- console.log(colors.gray(' Data:'));
3744
- console.log(highlight(JSON.stringify(result.error.data, null, 2)));
3745
- }
3746
- }
3747
- else {
3748
- console.log(colors.green(`\n✔ Success`) + colors.gray(` (${duration}ms)`));
3749
- console.log('');
3750
- console.log(highlight(JSON.stringify(result.result, null, 2)));
3751
- }
3752
- this.lastResponse = result;
3753
- }
3754
- catch (error) {
3755
- console.error(colors.red(`JSON-RPC Error: ${error.message}`));
3756
- }
3757
- console.log('');
3758
- }
3759
- async runHls(args) {
3760
- if (args.length === 0 || args[0] === 'help') {
3761
- console.log(colors.bold('HLS Streaming Client'));
3762
- console.log('');
3763
- console.log(colors.yellow('Usage: hls <url> [command] [options...]'));
3764
- console.log('');
3765
- console.log(colors.gray('Commands:'));
3766
- console.log(' info Show stream information (default)');
3767
- console.log(' download Download the stream to a file');
3768
- console.log('');
3769
- console.log(colors.gray('Options:'));
3770
- console.log(' output=<file> Output file for download (default: stream.ts)');
3771
- console.log(' quality=<level> Quality selection: highest, lowest (default: highest)');
3772
- console.log('');
3773
- console.log(colors.gray('Examples:'));
3774
- console.log(' hls https://example.com/stream.m3u8');
3775
- console.log(' hls https://example.com/live.m3u8 info');
3776
- console.log(' hls https://example.com/vod.m3u8 download output=video.ts');
3777
- console.log(' hls https://example.com/stream.m3u8 download quality=lowest');
3778
- return;
3779
- }
3780
- let url = args[0];
3781
- let command = 'info';
3782
- const options = {};
3783
- for (let i = 1; i < args.length; i++) {
3784
- const arg = args[i];
3785
- if (arg.includes('=')) {
3786
- const [key, value] = arg.split('=');
3787
- options[key] = value;
3788
- }
3789
- else if (['info', 'download'].includes(arg)) {
3790
- command = arg;
3791
- }
3792
- }
3793
- if (!url.startsWith('http')) {
3794
- if (this.baseUrl) {
3795
- url = `${this.baseUrl}${url.startsWith('/') ? '' : '/'}${url}`;
3796
- }
3797
- else {
3798
- url = `https://${url}`;
3799
- }
3800
- }
3801
- console.log(colors.gray(`Fetching playlist: ${url}`));
3802
- try {
3803
- const { hls } = await import('../../plugins/hls.js');
3804
- const hlsClient = hls(this.client, url, {
3805
- quality: options.quality || 'highest',
3806
- });
3807
- if (command === 'info') {
3808
- const info = await hlsClient.info();
3809
- console.log(colors.green('\n✔ HLS Stream Info'));
3810
- console.log('');
3811
- if (info.master) {
3812
- console.log(colors.bold(' Master Playlist:'));
3813
- console.log(` ${colors.cyan('Variants')}: ${info.master.variants.length}`);
3814
- console.log('');
3815
- info.master.variants.forEach((v, i) => {
3816
- const bandwidth = v.bandwidth ? `${Math.round(v.bandwidth / 1000)}kbps` : 'unknown';
3817
- const resolution = v.resolution || 'unknown';
3818
- const selected = info.selectedVariant?.url === v.url ? colors.green(' ★ selected') : '';
3819
- console.log(` ${colors.gray(`${i + 1}.`)} ${bandwidth} @ ${resolution}${selected}`);
3820
- if (v.codecs) {
3821
- console.log(` ${colors.gray('Codecs:')} ${v.codecs}`);
3822
- }
3823
- });
3824
- console.log('');
3825
- }
3826
- if (info.playlist) {
3827
- console.log(colors.bold(' Media Playlist:'));
3828
- console.log(` ${colors.cyan('Segments')}: ${info.playlist.segments.length}`);
3829
- console.log(` ${colors.cyan('Target Duration')}: ${info.playlist.targetDuration}s`);
3830
- console.log(` ${colors.cyan('Type')}: ${info.isLive ? colors.yellow('LIVE') : colors.green('VOD')}`);
3831
- if (info.totalDuration) {
3832
- const minutes = Math.floor(info.totalDuration / 60);
3833
- const seconds = Math.round(info.totalDuration % 60);
3834
- console.log(` ${colors.cyan('Total Duration')}: ${minutes}m ${seconds}s`);
3835
- }
3836
- if (info.playlist.segments.length > 0) {
3837
- const firstSeg = info.playlist.segments[0];
3838
- const lastSeg = info.playlist.segments[info.playlist.segments.length - 1];
3839
- console.log(` ${colors.cyan('Sequence Range')}: ${firstSeg.sequence} - ${lastSeg.sequence}`);
3840
- }
3841
- }
3842
- this.lastResponse = info;
3843
- }
3844
- else if (command === 'download') {
3845
- const outputFile = options.output || 'stream.ts';
3846
- console.log(colors.gray(`Downloading to ${outputFile}...`));
3847
- console.log('');
3848
- let lastProgress = 0;
3849
- const startTime = Date.now();
3850
- await hls(this.client, url, {
3851
- quality: options.quality || 'highest',
3852
- onProgress: (progress) => {
3853
- const now = Date.now();
3854
- if (now - lastProgress > 500) {
3855
- lastProgress = now;
3856
- const kb = Math.round(progress.downloadedBytes / 1024);
3857
- const total = progress.totalSegments ? ` / ${progress.totalSegments}` : '';
3858
- process.stdout.write(`\r ${colors.cyan('Segments')}: ${progress.downloadedSegments}${total} | ${colors.cyan('Downloaded')}: ${kb}kb `);
3859
- }
3860
- },
3861
- }).download(outputFile);
3862
- const duration = Math.round((Date.now() - startTime) / 1000);
3863
- process.stdout.write('\r' + ' '.repeat(80) + '\r');
3864
- console.log(colors.green(`✔ Downloaded to ${outputFile}`) + colors.gray(` (${duration}s)`));
3865
- this.lastResponse = { file: outputFile, duration };
3866
- }
3867
- }
3868
- catch (error) {
3869
- console.error(colors.red(`HLS Error: ${error.message}`));
3870
- }
3871
- console.log('');
3872
- }
3873
2216
  async runRobots(url) {
3874
2217
  const targetUrl = url || this.baseUrl;
3875
2218
  if (!targetUrl) {