@detiq/crawler 0.1.3

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 (412) hide show
  1. package/Dockerfile +49 -0
  2. package/README.md +11 -0
  3. package/dist/accessibility-auditor.d.ts +29 -0
  4. package/dist/accessibility-auditor.js +57 -0
  5. package/dist/accessibility-auditor.js.map +7 -0
  6. package/dist/ai-login.d.ts +32 -0
  7. package/dist/ai-login.js +177 -0
  8. package/dist/ai-login.js.map +7 -0
  9. package/dist/auth-token-rotator.d.ts +47 -0
  10. package/dist/auth-token-rotator.js +58 -0
  11. package/dist/auth-token-rotator.js.map +7 -0
  12. package/dist/auth.d.ts +20 -0
  13. package/dist/auth.js +329 -0
  14. package/dist/auth.js.map +7 -0
  15. package/dist/browser-extension.d.ts +37 -0
  16. package/dist/browser-extension.js +113 -0
  17. package/dist/browser-extension.js.map +7 -0
  18. package/dist/captcha-solver.d.ts +16 -0
  19. package/dist/captcha-solver.js +105 -0
  20. package/dist/captcha-solver.js.map +7 -0
  21. package/dist/color-contrast.d.ts +30 -0
  22. package/dist/color-contrast.js +78 -0
  23. package/dist/color-contrast.js.map +7 -0
  24. package/dist/content-chunker.d.ts +18 -0
  25. package/dist/content-chunker.js +152 -0
  26. package/dist/content-chunker.js.map +7 -0
  27. package/dist/content-dedup.d.ts +23 -0
  28. package/dist/content-dedup.js +60 -0
  29. package/dist/content-dedup.js.map +7 -0
  30. package/dist/cookie-consent.d.ts +25 -0
  31. package/dist/cookie-consent.js +78 -0
  32. package/dist/cookie-consent.js.map +7 -0
  33. package/dist/cookie-jar.d.ts +20 -0
  34. package/dist/cookie-jar.js +61 -0
  35. package/dist/cookie-jar.js.map +7 -0
  36. package/dist/crawler.d.ts +179 -0
  37. package/dist/crawler.js +3672 -0
  38. package/dist/crawler.js.map +7 -0
  39. package/dist/custom-js-injector.d.ts +42 -0
  40. package/dist/custom-js-injector.js +50 -0
  41. package/dist/custom-js-injector.js.map +7 -0
  42. package/dist/dark-mode-capture.d.ts +23 -0
  43. package/dist/dark-mode-capture.js +75 -0
  44. package/dist/dark-mode-capture.js.map +7 -0
  45. package/dist/dead-link-detector.d.ts +37 -0
  46. package/dist/dead-link-detector.js +50 -0
  47. package/dist/dead-link-detector.js.map +7 -0
  48. package/dist/element-hash.d.ts +9 -0
  49. package/dist/element-hash.js +9 -0
  50. package/dist/element-hash.js.map +7 -0
  51. package/dist/email-otp.d.ts +42 -0
  52. package/dist/email-otp.js +177 -0
  53. package/dist/email-otp.js.map +7 -0
  54. package/dist/engine-router.d.ts +47 -0
  55. package/dist/engine-router.js +94 -0
  56. package/dist/engine-router.js.map +7 -0
  57. package/dist/export/bulk-export.d.ts +26 -0
  58. package/dist/export/bulk-export.js +76 -0
  59. package/dist/export/bulk-export.js.map +7 -0
  60. package/dist/export/vector-export.d.ts +76 -0
  61. package/dist/export/vector-export.js +85 -0
  62. package/dist/export/vector-export.js.map +7 -0
  63. package/dist/extractors/advanced.d.ts +54 -0
  64. package/dist/extractors/advanced.js +245 -0
  65. package/dist/extractors/advanced.js.map +7 -0
  66. package/dist/extractors/artifact-envelope.d.ts +63 -0
  67. package/dist/extractors/artifact-envelope.js +63 -0
  68. package/dist/extractors/artifact-envelope.js.map +7 -0
  69. package/dist/failure-analysis-agent.d.ts +16 -0
  70. package/dist/failure-analysis-agent.js +40 -0
  71. package/dist/failure-analysis-agent.js.map +7 -0
  72. package/dist/feature-flag-detector.d.ts +30 -0
  73. package/dist/feature-flag-detector.js +55 -0
  74. package/dist/feature-flag-detector.js.map +7 -0
  75. package/dist/form-detector.d.ts +34 -0
  76. package/dist/form-detector.js +58 -0
  77. package/dist/form-detector.js.map +7 -0
  78. package/dist/frontier.d.ts +32 -0
  79. package/dist/frontier.js +83 -0
  80. package/dist/frontier.js.map +7 -0
  81. package/dist/gap-pass.d.ts +35 -0
  82. package/dist/gap-pass.js +81 -0
  83. package/dist/gap-pass.js.map +7 -0
  84. package/dist/graphql-detector.d.ts +39 -0
  85. package/dist/graphql-detector.js +86 -0
  86. package/dist/graphql-detector.js.map +7 -0
  87. package/dist/http-engine.d.ts +80 -0
  88. package/dist/http-engine.js +304 -0
  89. package/dist/http-engine.js.map +7 -0
  90. package/dist/incremental-diff.d.ts +59 -0
  91. package/dist/incremental-diff.js +61 -0
  92. package/dist/incremental-diff.js.map +7 -0
  93. package/dist/index.d.ts +35 -0
  94. package/dist/index.js +61 -0
  95. package/dist/index.js.map +7 -0
  96. package/dist/input-fuzzer.d.ts +30 -0
  97. package/dist/input-fuzzer.js +105 -0
  98. package/dist/input-fuzzer.js.map +7 -0
  99. package/dist/interactive-probe.d.ts +104 -0
  100. package/dist/interactive-probe.js +442 -0
  101. package/dist/interactive-probe.js.map +7 -0
  102. package/dist/job-queue.d.ts +171 -0
  103. package/dist/job-queue.js +2256 -0
  104. package/dist/job-queue.js.map +7 -0
  105. package/dist/js-coverage.d.ts +29 -0
  106. package/dist/js-coverage.js +56 -0
  107. package/dist/js-coverage.js.map +7 -0
  108. package/dist/js-router-detector.d.ts +17 -0
  109. package/dist/js-router-detector.js +90 -0
  110. package/dist/js-router-detector.js.map +7 -0
  111. package/dist/language-detector.d.ts +22 -0
  112. package/dist/language-detector.js +57 -0
  113. package/dist/language-detector.js.map +7 -0
  114. package/dist/link-classifier.d.ts +23 -0
  115. package/dist/link-classifier.js +40 -0
  116. package/dist/link-classifier.js.map +7 -0
  117. package/dist/live-view.d.ts +36 -0
  118. package/dist/live-view.js +100 -0
  119. package/dist/live-view.js.map +7 -0
  120. package/dist/logger.d.ts +2 -0
  121. package/dist/logger.js +10 -0
  122. package/dist/logger.js.map +7 -0
  123. package/dist/markdown-extract.d.ts +8 -0
  124. package/dist/markdown-extract.js +54 -0
  125. package/dist/markdown-extract.js.map +7 -0
  126. package/dist/native-crawler.d.ts +69 -0
  127. package/dist/native-crawler.js +839 -0
  128. package/dist/native-crawler.js.map +7 -0
  129. package/dist/network-observer.d.ts +49 -0
  130. package/dist/network-observer.js +149 -0
  131. package/dist/network-observer.js.map +7 -0
  132. package/dist/network-throttle.d.ts +25 -0
  133. package/dist/network-throttle.js +34 -0
  134. package/dist/network-throttle.js.map +7 -0
  135. package/dist/openapi-generator.d.ts +49 -0
  136. package/dist/openapi-generator.js +89 -0
  137. package/dist/openapi-generator.js.map +7 -0
  138. package/dist/pdf-extractor.d.ts +28 -0
  139. package/dist/pdf-extractor.js +81 -0
  140. package/dist/pdf-extractor.js.map +7 -0
  141. package/dist/performance-metrics.d.ts +53 -0
  142. package/dist/performance-metrics.js +83 -0
  143. package/dist/performance-metrics.js.map +7 -0
  144. package/dist/phash.d.ts +38 -0
  145. package/dist/phash.js +115 -0
  146. package/dist/phash.js.map +7 -0
  147. package/dist/pii-scrubber.d.ts +21 -0
  148. package/dist/pii-scrubber.js +75 -0
  149. package/dist/pii-scrubber.js.map +7 -0
  150. package/dist/playwright-recorder.d.ts +39 -0
  151. package/dist/playwright-recorder.js +123 -0
  152. package/dist/playwright-recorder.js.map +7 -0
  153. package/dist/policies/crawl-safety.d.ts +9 -0
  154. package/dist/policies/crawl-safety.js +37 -0
  155. package/dist/policies/crawl-safety.js.map +7 -0
  156. package/dist/profiles/domain-profile.d.ts +19 -0
  157. package/dist/profiles/domain-profile.js +140 -0
  158. package/dist/profiles/domain-profile.js.map +7 -0
  159. package/dist/proxy.d.ts +50 -0
  160. package/dist/proxy.js +165 -0
  161. package/dist/proxy.js.map +7 -0
  162. package/dist/rate-limiter.d.ts +31 -0
  163. package/dist/rate-limiter.js +64 -0
  164. package/dist/rate-limiter.js.map +7 -0
  165. package/dist/redirect-tracker.d.ts +37 -0
  166. package/dist/redirect-tracker.js +65 -0
  167. package/dist/redirect-tracker.js.map +7 -0
  168. package/dist/redis-frontier.d.ts +48 -0
  169. package/dist/redis-frontier.js +126 -0
  170. package/dist/redis-frontier.js.map +7 -0
  171. package/dist/redis-queue.d.ts +48 -0
  172. package/dist/redis-queue.js +165 -0
  173. package/dist/redis-queue.js.map +7 -0
  174. package/dist/response-cache.d.ts +38 -0
  175. package/dist/response-cache.js +140 -0
  176. package/dist/response-cache.js.map +7 -0
  177. package/dist/responsive-detector.d.ts +29 -0
  178. package/dist/responsive-detector.js +46 -0
  179. package/dist/responsive-detector.js.map +7 -0
  180. package/dist/retry-policy.d.ts +20 -0
  181. package/dist/retry-policy.js +54 -0
  182. package/dist/retry-policy.js.map +7 -0
  183. package/dist/robots-txt.d.ts +9 -0
  184. package/dist/robots-txt.js +77 -0
  185. package/dist/robots-txt.js.map +7 -0
  186. package/dist/screen-reader.d.ts +16 -0
  187. package/dist/screen-reader.js +46 -0
  188. package/dist/screen-reader.js.map +7 -0
  189. package/dist/screenshot-masker.d.ts +28 -0
  190. package/dist/screenshot-masker.js +62 -0
  191. package/dist/screenshot-masker.js.map +7 -0
  192. package/dist/seo-checker.d.ts +33 -0
  193. package/dist/seo-checker.js +91 -0
  194. package/dist/seo-checker.js.map +7 -0
  195. package/dist/session-capture.d.ts +35 -0
  196. package/dist/session-capture.js +267 -0
  197. package/dist/session-capture.js.map +7 -0
  198. package/dist/session-export.d.ts +49 -0
  199. package/dist/session-export.js +117 -0
  200. package/dist/session-export.js.map +7 -0
  201. package/dist/sitemap-writer.d.ts +21 -0
  202. package/dist/sitemap-writer.js +47 -0
  203. package/dist/sitemap-writer.js.map +7 -0
  204. package/dist/sitemap.d.ts +51 -0
  205. package/dist/sitemap.js +152 -0
  206. package/dist/sitemap.js.map +7 -0
  207. package/dist/skyvern.d.ts +56 -0
  208. package/dist/skyvern.js +129 -0
  209. package/dist/skyvern.js.map +7 -0
  210. package/dist/sqs-queue.d.ts +2 -0
  211. package/dist/sqs-queue.js +47 -0
  212. package/dist/sqs-queue.js.map +7 -0
  213. package/dist/sso-token.d.ts +29 -0
  214. package/dist/sso-token.js +76 -0
  215. package/dist/sso-token.js.map +7 -0
  216. package/dist/stealth/human-behavior.d.ts +20 -0
  217. package/dist/stealth/human-behavior.js +168 -0
  218. package/dist/stealth/human-behavior.js.map +7 -0
  219. package/dist/telemetry.d.ts +34 -0
  220. package/dist/telemetry.js +77 -0
  221. package/dist/telemetry.js.map +7 -0
  222. package/dist/test-executor.d.ts +67 -0
  223. package/dist/test-executor.js +311 -0
  224. package/dist/test-executor.js.map +7 -0
  225. package/dist/tls-fingerprint.d.ts +50 -0
  226. package/dist/tls-fingerprint.js +81 -0
  227. package/dist/tls-fingerprint.js.map +7 -0
  228. package/dist/url-pattern-completer.d.ts +28 -0
  229. package/dist/url-pattern-completer.js +87 -0
  230. package/dist/url-pattern-completer.js.map +7 -0
  231. package/dist/visual-diff-verdict.d.ts +40 -0
  232. package/dist/visual-diff-verdict.js +25 -0
  233. package/dist/visual-diff-verdict.js.map +7 -0
  234. package/dist/webhook-notifier.d.ts +30 -0
  235. package/dist/webhook-notifier.js +76 -0
  236. package/dist/webhook-notifier.js.map +7 -0
  237. package/dist/websocket-detector.d.ts +24 -0
  238. package/dist/websocket-detector.js +60 -0
  239. package/dist/websocket-detector.js.map +7 -0
  240. package/dist/worker.d.ts +1 -0
  241. package/dist/worker.js +33 -0
  242. package/dist/worker.js.map +7 -0
  243. package/package.json +45 -0
  244. package/src/accessibility-auditor.ts +102 -0
  245. package/src/ai-login.ts +218 -0
  246. package/src/auth-token-rotator.ts +89 -0
  247. package/src/auth.ts +396 -0
  248. package/src/browser-extension.ts +186 -0
  249. package/src/captcha-solver.ts +143 -0
  250. package/src/color-contrast.ts +116 -0
  251. package/src/content-chunker.ts +170 -0
  252. package/src/content-dedup.ts +99 -0
  253. package/src/cookie-consent.ts +106 -0
  254. package/src/cookie-jar.ts +91 -0
  255. package/src/crawler.ts +4305 -0
  256. package/src/custom-js-injector.ts +93 -0
  257. package/src/dark-mode-capture.ts +110 -0
  258. package/src/dead-link-detector.ts +76 -0
  259. package/src/element-hash.ts +16 -0
  260. package/src/email-otp.ts +252 -0
  261. package/src/engine-router.ts +141 -0
  262. package/src/export/bulk-export.ts +85 -0
  263. package/src/export/vector-export.ts +160 -0
  264. package/src/extractors/advanced.ts +283 -0
  265. package/src/extractors/artifact-envelope.ts +111 -0
  266. package/src/failure-analysis-agent.ts +49 -0
  267. package/src/feature-flag-detector.ts +97 -0
  268. package/src/form-detector.ts +103 -0
  269. package/src/frontier.test.ts +64 -0
  270. package/src/frontier.ts +112 -0
  271. package/src/gap-pass.ts +122 -0
  272. package/src/graphql-detector.ts +129 -0
  273. package/src/http-engine.ts +433 -0
  274. package/src/incremental-diff.ts +121 -0
  275. package/src/index.ts +35 -0
  276. package/src/input-fuzzer.ts +129 -0
  277. package/src/interactive-probe.ts +623 -0
  278. package/src/job-queue.test.ts +120 -0
  279. package/src/job-queue.ts +2701 -0
  280. package/src/js-coverage.ts +87 -0
  281. package/src/js-router-detector.ts +112 -0
  282. package/src/language-detector.ts +82 -0
  283. package/src/link-classifier.ts +65 -0
  284. package/src/live-view.ts +141 -0
  285. package/src/logger.ts +9 -0
  286. package/src/markdown-extract.test.ts +73 -0
  287. package/src/markdown-extract.ts +76 -0
  288. package/src/native-crawler.ts +1106 -0
  289. package/src/network-observer.ts +204 -0
  290. package/src/network-throttle.ts +69 -0
  291. package/src/openapi-generator.ts +135 -0
  292. package/src/pdf-extractor.ts +123 -0
  293. package/src/performance-metrics.ts +134 -0
  294. package/src/phash.ts +151 -0
  295. package/src/pii-scrubber.ts +99 -0
  296. package/src/playwright-recorder.ts +170 -0
  297. package/src/policies/crawl-safety.ts +46 -0
  298. package/src/profiles/domain-profile.ts +167 -0
  299. package/src/proxy.ts +238 -0
  300. package/src/rate-limiter.ts +87 -0
  301. package/src/redirect-tracker.ts +97 -0
  302. package/src/redis-frontier.ts +143 -0
  303. package/src/redis-queue.ts +208 -0
  304. package/src/response-cache.ts +150 -0
  305. package/src/responsive-detector.ts +92 -0
  306. package/src/retry-policy.ts +87 -0
  307. package/src/robots-txt.ts +96 -0
  308. package/src/screen-reader.ts +89 -0
  309. package/src/screenshot-masker.ts +98 -0
  310. package/src/seo-checker.ts +134 -0
  311. package/src/session-capture.ts +325 -0
  312. package/src/session-export.test.ts +75 -0
  313. package/src/session-export.ts +172 -0
  314. package/src/sitemap-writer.ts +65 -0
  315. package/src/sitemap.test.ts +131 -0
  316. package/src/sitemap.ts +224 -0
  317. package/src/skyvern.ts +199 -0
  318. package/src/sqs-queue.ts +49 -0
  319. package/src/sso-token.ts +120 -0
  320. package/src/stealth/human-behavior.ts +191 -0
  321. package/src/telemetry.ts +122 -0
  322. package/src/test-executor.test.ts +72 -0
  323. package/src/test-executor.ts +424 -0
  324. package/src/tls-fingerprint.ts +111 -0
  325. package/src/url-pattern-completer.ts +114 -0
  326. package/src/visual-diff-verdict.test.ts +80 -0
  327. package/src/visual-diff-verdict.ts +57 -0
  328. package/src/webhook-notifier.ts +109 -0
  329. package/src/websocket-detector.ts +83 -0
  330. package/src/worker.ts +48 -0
  331. package/tsconfig.json +13 -0
  332. package/types/accessibility-auditor.d.ts +29 -0
  333. package/types/ai-login.d.ts +32 -0
  334. package/types/auth-token-rotator.d.ts +47 -0
  335. package/types/auth.d.ts +20 -0
  336. package/types/browser-extension.d.ts +37 -0
  337. package/types/bull-worker.d.ts +6 -0
  338. package/types/captcha-solver.d.ts +16 -0
  339. package/types/color-contrast.d.ts +30 -0
  340. package/types/content-chunker.d.ts +18 -0
  341. package/types/content-dedup.d.ts +23 -0
  342. package/types/cookie-consent.d.ts +25 -0
  343. package/types/cookie-jar.d.ts +20 -0
  344. package/types/crawler.d.ts +179 -0
  345. package/types/custom-js-injector.d.ts +42 -0
  346. package/types/dark-mode-capture.d.ts +23 -0
  347. package/types/dead-link-detector.d.ts +37 -0
  348. package/types/element-hash.d.ts +9 -0
  349. package/types/email-otp.d.ts +42 -0
  350. package/types/engine-router.d.ts +47 -0
  351. package/types/export/bulk-export.d.ts +26 -0
  352. package/types/export/vector-export.d.ts +76 -0
  353. package/types/extractors/advanced.d.ts +54 -0
  354. package/types/extractors/artifact-envelope.d.ts +63 -0
  355. package/types/failure-analysis-agent.d.ts +16 -0
  356. package/types/feature-flag-detector.d.ts +30 -0
  357. package/types/form-detector.d.ts +34 -0
  358. package/types/frontier.d.ts +32 -0
  359. package/types/gap-pass.d.ts +35 -0
  360. package/types/graphql-detector.d.ts +39 -0
  361. package/types/http-engine.d.ts +80 -0
  362. package/types/incremental-diff.d.ts +59 -0
  363. package/types/index.d.ts +35 -0
  364. package/types/input-fuzzer.d.ts +30 -0
  365. package/types/interactive-probe.d.ts +104 -0
  366. package/types/job-queue.d.ts +171 -0
  367. package/types/js-coverage.d.ts +29 -0
  368. package/types/js-router-detector.d.ts +17 -0
  369. package/types/language-detector.d.ts +22 -0
  370. package/types/link-classifier.d.ts +23 -0
  371. package/types/live-view.d.ts +36 -0
  372. package/types/logger.d.ts +2 -0
  373. package/types/markdown-extract.d.ts +8 -0
  374. package/types/native-crawler.d.ts +69 -0
  375. package/types/network-observer.d.ts +49 -0
  376. package/types/network-throttle.d.ts +25 -0
  377. package/types/openapi-generator.d.ts +49 -0
  378. package/types/pdf-extractor.d.ts +28 -0
  379. package/types/performance-metrics.d.ts +53 -0
  380. package/types/phash.d.ts +38 -0
  381. package/types/pii-scrubber.d.ts +21 -0
  382. package/types/playwright-recorder.d.ts +39 -0
  383. package/types/policies/crawl-safety.d.ts +9 -0
  384. package/types/profiles/domain-profile.d.ts +19 -0
  385. package/types/proxy.d.ts +50 -0
  386. package/types/rate-limiter.d.ts +31 -0
  387. package/types/redirect-tracker.d.ts +37 -0
  388. package/types/redis-frontier.d.ts +48 -0
  389. package/types/redis-queue.d.ts +48 -0
  390. package/types/response-cache.d.ts +38 -0
  391. package/types/responsive-detector.d.ts +29 -0
  392. package/types/retry-policy.d.ts +20 -0
  393. package/types/robots-txt.d.ts +9 -0
  394. package/types/screen-reader.d.ts +16 -0
  395. package/types/screenshot-masker.d.ts +28 -0
  396. package/types/seo-checker.d.ts +33 -0
  397. package/types/session-capture.d.ts +38 -0
  398. package/types/session-export.d.ts +49 -0
  399. package/types/sitemap-writer.d.ts +21 -0
  400. package/types/sitemap.d.ts +51 -0
  401. package/types/skyvern.d.ts +56 -0
  402. package/types/sqs-queue.d.ts +2 -0
  403. package/types/sso-token.d.ts +29 -0
  404. package/types/stealth/human-behavior.d.ts +20 -0
  405. package/types/telemetry.d.ts +34 -0
  406. package/types/test-executor.d.ts +67 -0
  407. package/types/tls-fingerprint.d.ts +50 -0
  408. package/types/url-pattern-completer.d.ts +28 -0
  409. package/types/visual-diff-verdict.d.ts +40 -0
  410. package/types/webhook-notifier.d.ts +30 -0
  411. package/types/websocket-detector.d.ts +24 -0
  412. package/types/worker.d.ts +1 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * HTTP-only crawl engine — 10-50x faster than browser for SSR/static sites.
3
+ *
4
+ * Uses native Node.js fetch (HTTP/2), linkedom, Readability, and Turndown.
5
+ * No browser overhead. Falls back to browser engine on JS-heavy sites.
6
+ *
7
+ * Features:
8
+ * - HTTP/2 native (Node 20 built-in fetch)
9
+ * - ETag / Last-Modified response caching
10
+ * - 429 / 503 exponential backoff with jitter
11
+ * - BFS / DFS traversal
12
+ * - URL regex filtering
13
+ * - Robots.txt compliance
14
+ * - Link discovery (href, action, src)
15
+ * - Markdown extraction (Readability + Turndown)
16
+ * - OG / schema.org / meta extraction
17
+ * - Table → JSON extraction
18
+ * - JSON Lines, CSV, raw HTML output formats
19
+ * - Streaming progress via onProgress callback
20
+ */
21
+
22
+ import { createHash } from 'node:crypto';
23
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
24
+ import { join } from 'node:path';
25
+ import { Readability } from '@mozilla/readability';
26
+ import TurndownService from 'turndown';
27
+ import { parseHTML } from 'linkedom';
28
+ import { RobotsCache } from './robots-txt.js';
29
+ import { ResponseCache } from './response-cache.js';
30
+ import { extractMetadata, extractTables, extractImages } from './extractors/advanced.js';
31
+ import { CHROME_CIPHERS, CHROME_CURVES } from './tls-fingerprint.js';
32
+ import { renderingPredictor } from './engine-router.js';
33
+
34
+ // Chrome-level TLS dispatcher: impit (true JA3/JA4 BoringSSL match) with undici fallback
35
+ let chromeFetchDispatcher: any;
36
+ async function getChromeFetchDispatcher() {
37
+ if (!chromeFetchDispatcher) {
38
+ try {
39
+ // impit provides native Chrome BoringSSL TLS fingerprint — true JA3/JA4 match
40
+ // @ts-ignore — impit is an optional dependency
41
+ const impitMod = await import('impit');
42
+ const ImpitClass = impitMod.Impit ?? impitMod.default;
43
+ if (ImpitClass) {
44
+ chromeFetchDispatcher = new ImpitClass({ browser: 'chrome' });
45
+ return chromeFetchDispatcher;
46
+ }
47
+ } catch { /* impit not installed — fall through to undici */ }
48
+ const { Agent } = await import('undici');
49
+ chromeFetchDispatcher = new Agent({
50
+ connect: {
51
+ ciphers: CHROME_CIPHERS,
52
+ ecdhCurve: CHROME_CURVES,
53
+ minVersion: 'TLSv1.2' as any,
54
+ maxVersion: 'TLSv1.3' as any,
55
+ },
56
+ keepAliveTimeout: 10_000,
57
+ keepAliveMaxTimeout: 30_000,
58
+ });
59
+ }
60
+ return chromeFetchDispatcher;
61
+ }
62
+
63
+ const turndown = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced' });
64
+
65
+ // Pages unlikely to have testable UI — skip from HTTP crawl queue
66
+ const SKIP_PATH = /\/(terms|privacy|dpa|legal|cookie|changelog|blog|help|docs|about|contact|careers|press|imprint|gdpr|compliance|acceptable-use|security-policy|accessibility)([-/]|$)/i;
67
+
68
+ export type HttpOutputFormat = 'json' | 'jsonlines' | 'csv' | 'markdown' | 'html';
69
+
70
+ export interface HttpScreen {
71
+ url: string;
72
+ title: string | null;
73
+ markdown: string;
74
+ html?: string;
75
+ metadata: {
76
+ description: string | null;
77
+ ogTitle: string | null;
78
+ ogDescription: string | null;
79
+ ogImage: string | null;
80
+ canonical: string | null;
81
+ schemaTypes: string[];
82
+ lang: string | null;
83
+ };
84
+ tables: Array<Record<string, string>[]>;
85
+ links: string[];
86
+ images: Array<{ src: string; alt: string }>;
87
+ statusCode: number;
88
+ fromCache: boolean;
89
+ requiresBrowser?: boolean;
90
+ durationMs: number;
91
+ }
92
+
93
+ export interface HttpCrawlOpts {
94
+ appUrl: string;
95
+ maxScreens?: number; // default 100
96
+ urlFilter?: string; // regex pattern — only crawl matching URLs
97
+ traversalStrategy?: 'BFS' | 'DFS'; // default BFS
98
+ outputFormats?: HttpOutputFormat[];
99
+ cacheDir?: string; // default process.env.CRAWLER_CACHE_DIR || '/tmp/zeta-cache'
100
+ userAgent?: string;
101
+ headers?: Record<string, string>;
102
+ respectRobotsTxt?: boolean; // default true
103
+ includeRawHtml?: boolean;
104
+ timeoutMs?: number; // per-request timeout, default 15000
105
+ concurrency?: number; // default 5 parallel fetches
106
+ onProgress?: (update: { url: string; screensFound: number; total: number; cached: boolean }) => void;
107
+ }
108
+
109
+ export interface HttpCrawlResult {
110
+ screens: HttpScreen[];
111
+ stats: {
112
+ total: number;
113
+ cached: number;
114
+ errors: number;
115
+ rateLimited: number;
116
+ durationMs: number;
117
+ cacheHitRate: number;
118
+ };
119
+ jsonLines?: string;
120
+ csv?: string;
121
+ }
122
+
123
+ function canonicalize(url: string): string {
124
+ try {
125
+ const u = new URL(url);
126
+ u.hash = '';
127
+ // Strip tracking params
128
+ for (const p of ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'fbclid', 'gclid', 'ref', 'referrer']) {
129
+ u.searchParams.delete(p);
130
+ }
131
+ return u.href.toLowerCase().replace(/\/$/, '');
132
+ } catch {
133
+ return url.toLowerCase();
134
+ }
135
+ }
136
+
137
+ function extractLinks(html: string, baseUrl: string): string[] {
138
+ const links: string[] = [];
139
+ const seen = new Set<string>();
140
+ try {
141
+ const base = new URL(baseUrl);
142
+ const { document } = parseHTML(html);
143
+ const anchors = Array.from(document.querySelectorAll('a[href]'));
144
+ for (const a of anchors) {
145
+ const href = (a as any).getAttribute('href') ?? '';
146
+ if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('javascript:')) continue;
147
+ try {
148
+ const resolved = new URL(href, base).href;
149
+ // Same origin only
150
+ if (new URL(resolved).origin !== base.origin) continue;
151
+ const c = canonicalize(resolved);
152
+ if (!seen.has(c)) { seen.add(c); links.push(resolved); }
153
+ } catch { /* skip */ }
154
+ }
155
+ } catch { /* non-fatal */ }
156
+ return links;
157
+ }
158
+
159
+ function extractMarkdown(html: string, url: string): { markdown: string; title: string | null } {
160
+ try {
161
+ const { document } = parseHTML(html);
162
+ // Remove script/style tags before Readability
163
+ for (const tag of Array.from(document.querySelectorAll('script, style, noscript'))) (tag as any).remove();
164
+ const reader = new Readability(document as any, { charThreshold: 50 });
165
+ const article = reader.parse();
166
+ if (article && article.textContent && article.textContent.split(/\s+/).length >= 30) {
167
+ const md = turndown.turndown(article.content ?? html);
168
+ return { markdown: md, title: article.title ?? null };
169
+ }
170
+ } catch { /* fall through */ }
171
+ // Full-page fallback
172
+ try {
173
+ const { document } = parseHTML(html);
174
+ for (const tag of Array.from(document.querySelectorAll('script, style, noscript, nav, footer'))) (tag as any).remove();
175
+ const bodyHtml = (document as any).body?.innerHTML ?? html;
176
+ const md = turndown.turndown(bodyHtml);
177
+ const titleEl = (document as any).querySelector('title');
178
+ return { markdown: md, title: titleEl?.textContent?.trim() ?? null };
179
+ } catch {
180
+ return { markdown: '', title: null };
181
+ }
182
+ }
183
+
184
+ interface FetchResult {
185
+ body: string;
186
+ statusCode: number;
187
+ contentType: string;
188
+ fromCache: boolean;
189
+ durationMs: number;
190
+ etag?: string;
191
+ lastModified?: string;
192
+ }
193
+
194
+ async function fetchWithBackoff(
195
+ url: string,
196
+ cache: ResponseCache,
197
+ opts: { userAgent: string; timeoutMs: number; extraHeaders?: Record<string, string> },
198
+ retryCount = 0
199
+ ): Promise<FetchResult> {
200
+ const t0 = performance.now();
201
+
202
+ if (cache.isFresh(url)) {
203
+ const cached = cache.get(url)!;
204
+ return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs: 0 };
205
+ }
206
+
207
+ const validationHeaders = cache.getValidationHeaders(url);
208
+
209
+ const headers: Record<string, string> = {
210
+ 'User-Agent': opts.userAgent,
211
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
212
+ 'Accept-Language': 'en-US,en;q=0.9',
213
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
214
+ // Chrome-like security headers reduce bot-detection fingerprint
215
+ 'sec-ch-ua': '"Google Chrome";v="120", "Chromium";v="120", "Not-A.Brand";v="99"',
216
+ 'sec-ch-ua-mobile': '?0',
217
+ 'sec-ch-ua-platform': '"Windows"',
218
+ 'Sec-Fetch-Dest': 'document',
219
+ 'Sec-Fetch-Mode': 'navigate',
220
+ 'Sec-Fetch-Site': 'none',
221
+ 'Sec-Fetch-User': '?1',
222
+ 'Upgrade-Insecure-Requests': '1',
223
+ ...validationHeaders,
224
+ ...(opts.extraHeaders ?? {}),
225
+ };
226
+
227
+ let response: Response;
228
+ try {
229
+ const dispatcher = await getChromeFetchDispatcher();
230
+ response = await (fetch as any)(url, {
231
+ headers,
232
+ signal: AbortSignal.timeout(opts.timeoutMs),
233
+ redirect: 'follow',
234
+ dispatcher,
235
+ });
236
+ } catch (e: any) {
237
+ throw new Error(`[http-engine] fetch failed for ${url}: ${e.message}`);
238
+ }
239
+
240
+ const durationMs = performance.now() - t0;
241
+
242
+ // 304 Not Modified — use cached body
243
+ if (response.status === 304) {
244
+ const cached = cache.get(url);
245
+ if (cached) {
246
+ cache.markHit(url);
247
+ return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs };
248
+ }
249
+ }
250
+
251
+ // 429 / 503 — exponential backoff with jitter
252
+ if (response.status === 429 || response.status === 503) {
253
+ if (retryCount >= 5) throw new Error(`[http-engine] rate-limited after 5 retries: ${url}`);
254
+ const retryAfter = parseInt(response.headers.get('retry-after') ?? '0') || 0;
255
+ const baseDelay = retryAfter > 0 ? retryAfter * 1000 : Math.min(60_000, 1_000 * Math.pow(2, retryCount));
256
+ const jitter = Math.random() * 1000;
257
+ console.log(`[http-engine] ${response.status} on ${url} — backing off ${Math.round(baseDelay + jitter)}ms (retry ${retryCount + 1}/5)`);
258
+ await new Promise(r => setTimeout(r, baseDelay + jitter));
259
+ return fetchWithBackoff(url, cache, opts, retryCount + 1);
260
+ }
261
+
262
+ // 5xx — retry up to 3 times
263
+ if (response.status >= 500) {
264
+ if (retryCount < 3) {
265
+ await new Promise(r => setTimeout(r, 2_000 * (retryCount + 1)));
266
+ return fetchWithBackoff(url, cache, opts, retryCount + 1);
267
+ }
268
+ if (cache.staleIfError(url)) {
269
+ const cached = cache.get(url)!;
270
+ return { body: cached.body, statusCode: 200, contentType: cached.contentType, fromCache: true, durationMs: performance.now() - t0 };
271
+ }
272
+ }
273
+
274
+ const contentType = response.headers.get('content-type') ?? '';
275
+ const body = await response.text();
276
+ const etag = response.headers.get('etag') ?? undefined;
277
+ const lastModified = response.headers.get('last-modified') ?? undefined;
278
+ const cacheControl = response.headers.get('cache-control') ?? undefined;
279
+
280
+ // Cache successful HTML responses
281
+ if (response.ok && contentType.includes('html')) {
282
+ cache.set(url, { url, etag, lastModified, cacheControl, body, contentType, statusCode: response.status });
283
+ }
284
+
285
+ return { body, statusCode: response.status, contentType, fromCache: false, durationMs, etag, lastModified };
286
+ }
287
+
288
+ export async function httpCrawl(opts: HttpCrawlOpts): Promise<HttpCrawlResult> {
289
+ const startTime = performance.now();
290
+ const maxScreens = opts.maxScreens ?? 100;
291
+ const cacheDir = opts.cacheDir ?? process.env.CRAWLER_CACHE_DIR ?? '/tmp/zeta-cache';
292
+ const timeoutMs = opts.timeoutMs ?? 15_000;
293
+ const userAgent = opts.userAgent ?? 'ZeTa-Crawler/1.0 (+https://zetaai.dpdns.org)';
294
+ const cache = new ResponseCache(cacheDir);
295
+ const robots = opts.respectRobotsTxt !== false ? new RobotsCache() : null;
296
+ const urlFilterRe = opts.urlFilter ? (() => { try { return new RegExp(opts.urlFilter!, 'i'); } catch { return null; } })() : null;
297
+
298
+ const visited = new Set<string>();
299
+ const queue: string[] = [opts.appUrl];
300
+ const queued = new Set<string>([canonicalize(opts.appUrl)]);
301
+ const screens: HttpScreen[] = [];
302
+ let cachedCount = 0;
303
+ let errorCount = 0;
304
+ let rateLimitedCount = 0;
305
+
306
+ const batchFetch = async (urls: string[]): Promise<void> => {
307
+ await Promise.all(urls.map(async (url) => {
308
+ const canonUrl = canonicalize(url);
309
+ if (visited.has(canonUrl)) return;
310
+ visited.add(canonUrl);
311
+
312
+ // Robots.txt check
313
+ if (robots && !(await robots.isAllowed(url).catch(() => true))) {
314
+ console.log(`[http-engine] robots.txt blocked: ${url}`);
315
+ return;
316
+ }
317
+
318
+ // Skip content/legal pages
319
+ try {
320
+ const { pathname } = new URL(url);
321
+ if (SKIP_PATH.test(pathname)) return;
322
+ } catch { /* skip */ }
323
+
324
+ // URL filter
325
+ if (urlFilterRe) {
326
+ try {
327
+ const { pathname, search } = new URL(url);
328
+ if (!urlFilterRe.test(pathname + search)) {
329
+ console.log(`[http-engine] filtered out: ${url}`);
330
+ return;
331
+ }
332
+ } catch { /* skip */ }
333
+ }
334
+
335
+ let fetchResult: FetchResult;
336
+ try {
337
+ fetchResult = await fetchWithBackoff(url, cache, { userAgent, timeoutMs, extraHeaders: opts.headers });
338
+ if (fetchResult.statusCode === 429 || fetchResult.statusCode === 503) rateLimitedCount++;
339
+ } catch (err: any) {
340
+ console.error(`[http-engine] error fetching ${url}: ${err.message}`);
341
+ errorCount++;
342
+ return;
343
+ }
344
+
345
+ if (!fetchResult.contentType.includes('html')) return; // skip non-HTML
346
+ if (fetchResult.statusCode >= 400) return;
347
+ if (fetchResult.fromCache) cachedCount++;
348
+
349
+ const { markdown, title } = extractMarkdown(fetchResult.body, url);
350
+ const fullMeta = extractMetadata(fetchResult.body, url);
351
+ const links = extractLinks(fetchResult.body, url);
352
+ const tables = extractTables(fetchResult.body);
353
+ const images = extractImages(fetchResult.body, url);
354
+
355
+ const screen: HttpScreen = {
356
+ url,
357
+ title: title ?? fullMeta.title,
358
+ markdown,
359
+ html: opts.includeRawHtml ? fetchResult.body : undefined,
360
+ metadata: {
361
+ description: fullMeta.description,
362
+ ogTitle: fullMeta.ogTitle,
363
+ ogDescription: fullMeta.ogDescription,
364
+ ogImage: fullMeta.ogImage,
365
+ canonical: fullMeta.canonical,
366
+ schemaTypes: fullMeta.schemaTypes,
367
+ lang: fullMeta.lang,
368
+ },
369
+ tables,
370
+ links,
371
+ images: images.map(i => ({ src: i.src, alt: i.alt })),
372
+ statusCode: fetchResult.statusCode,
373
+ fromCache: fetchResult.fromCache,
374
+ requiresBrowser: renderingPredictor.getCached(url) === 'browser',
375
+ durationMs: fetchResult.durationMs,
376
+ };
377
+ screens.push(screen);
378
+
379
+ // Record rendering prediction: SPA signals → browser needed; content-rich → http engine works
380
+ renderingPredictor.predict(url, fetchResult.body, fetchResult.statusCode);
381
+
382
+ opts.onProgress?.({ url, screensFound: screens.length, total: maxScreens, cached: fetchResult.fromCache });
383
+
384
+ // Enqueue discovered links
385
+ for (const link of links) {
386
+ const c = canonicalize(link);
387
+ if (!queued.has(c) && !visited.has(c) && screens.length + queue.length < maxScreens * 2) {
388
+ queued.add(c);
389
+ if (opts.traversalStrategy === 'DFS') queue.unshift(link);
390
+ else queue.push(link);
391
+ }
392
+ }
393
+ }));
394
+ };
395
+
396
+ const concurrency = opts.concurrency ?? 5;
397
+
398
+ while (queue.length > 0 && screens.length < maxScreens) {
399
+ const batch = queue.splice(0, concurrency);
400
+ await batchFetch(batch);
401
+ }
402
+
403
+ // Output formats
404
+ let jsonLines: string | undefined;
405
+ let csv: string | undefined;
406
+
407
+ const formats = opts.outputFormats ?? [];
408
+ if (formats.includes('jsonlines')) {
409
+ jsonLines = screens.map(s => JSON.stringify({ url: s.url, title: s.title, markdown: s.markdown, metadata: s.metadata })).join('\n');
410
+ }
411
+ if (formats.includes('csv')) {
412
+ const header = 'url,title,description,ogTitle,schemaTypes,statusCode,fromCache,durationMs';
413
+ const rows = screens.map(s =>
414
+ [s.url, s.title ?? '', s.metadata.description ?? '', s.metadata.ogTitle ?? '', s.metadata.schemaTypes.join('|'), s.statusCode, s.fromCache, Math.round(s.durationMs)].map(v => `"${String(v).replace(/"/g, '""')}"`).join(',')
415
+ );
416
+ csv = [header, ...rows].join('\n');
417
+ }
418
+
419
+ const cacheStats = cache.stats();
420
+ return {
421
+ screens,
422
+ stats: {
423
+ total: screens.length,
424
+ cached: cachedCount,
425
+ errors: errorCount,
426
+ rateLimited: rateLimitedCount,
427
+ durationMs: performance.now() - startTime,
428
+ cacheHitRate: cacheStats.hitRate,
429
+ },
430
+ jsonLines,
431
+ csv,
432
+ };
433
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Incremental crawl diff — compute stable content hashes and skip unchanged screens.
3
+ *
4
+ * Each crawled page gets a content fingerprint (murmurhash-free, pure JS).
5
+ * On the next crawl of the same project, pages with the same fingerprint are
6
+ * marked SKIPPED instead of re-processed, drastically reducing crawl time and
7
+ * LLM token spend for sites with mostly-static content.
8
+ *
9
+ * Design: "same content hash" means the meaningful text hasn't changed.
10
+ * Layout/CSS/JS changes that don't affect visible text are ignored intentionally.
11
+ */
12
+
13
+ export interface ContentHash {
14
+ url: string;
15
+ hash: string;
16
+ textLength: number;
17
+ computedAt: string; // ISO timestamp
18
+ }
19
+
20
+ export interface DiffResult {
21
+ url: string;
22
+ changed: boolean;
23
+ previousHash?: string;
24
+ currentHash: string;
25
+ reason: 'new' | 'changed' | 'unchanged';
26
+ }
27
+
28
+ /**
29
+ * Compute a stable 64-bit-ish hash from text content.
30
+ * djb2 variant — fast, no deps, collision-resistant enough for content fingerprinting.
31
+ */
32
+ export function hashContent(text: string): string {
33
+ let h1 = 5381;
34
+ let h2 = 52711;
35
+ for (let i = 0; i < text.length; i++) {
36
+ const c = text.charCodeAt(i);
37
+ h1 = (Math.imul(h1, 33) ^ c) >>> 0;
38
+ h2 = (Math.imul(h2, 31) ^ c) >>> 0;
39
+ }
40
+ return (h1 >>> 0).toString(16).padStart(8, '0') + (h2 >>> 0).toString(16).padStart(8, '0');
41
+ }
42
+
43
+ /**
44
+ * Normalize text before hashing — strips whitespace-only lines, trims each line,
45
+ * collapses runs of blank lines. Makes hash stable across minor formatting changes.
46
+ */
47
+ export function normalizeText(text: string): string {
48
+ return text
49
+ .split('\n')
50
+ .map(l => l.trim())
51
+ .filter(l => l.length > 0)
52
+ .join('\n');
53
+ }
54
+
55
+ export function computeContentHash(url: string, markdownText: string): ContentHash {
56
+ const normalized = normalizeText(markdownText);
57
+ return {
58
+ url,
59
+ hash: hashContent(normalized),
60
+ textLength: normalized.length,
61
+ computedAt: new Date().toISOString(),
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Compare a page's current content against prior hashes.
67
+ * Returns { changed: false } for unchanged pages — caller should skip re-processing.
68
+ */
69
+ export function diffContent(
70
+ url: string,
71
+ currentMarkdown: string,
72
+ priorHashes: Map<string, string>, // url → hash
73
+ ): DiffResult {
74
+ const current = computeContentHash(url, currentMarkdown);
75
+ const prior = priorHashes.get(url);
76
+
77
+ if (!prior) {
78
+ return { url, changed: true, currentHash: current.hash, reason: 'new' };
79
+ }
80
+ if (prior === current.hash) {
81
+ return { url, changed: false, previousHash: prior, currentHash: current.hash, reason: 'unchanged' };
82
+ }
83
+ return { url, changed: true, previousHash: prior, currentHash: current.hash, reason: 'changed' };
84
+ }
85
+
86
+ /**
87
+ * Build a hash map from prior screen records (from DB query result).
88
+ * screenRecords: Array<{ url: string; elementHash?: string | null }>
89
+ */
90
+ export function buildPriorHashMap(screenRecords: Array<{ url: string; elementHash?: string | null }>): Map<string, string> {
91
+ const map = new Map<string, string>();
92
+ for (const s of screenRecords) {
93
+ if (s.url && s.elementHash) map.set(s.url, s.elementHash);
94
+ }
95
+ return map;
96
+ }
97
+
98
+ /**
99
+ * Filter a batch of discovered URLs against prior hashes.
100
+ * Returns { toProcess, toSkip } — caller enqueues toProcess, logs toSkip count.
101
+ *
102
+ * Note: filtering at URL level only works if the prior crawl already visited the URL.
103
+ * For new URLs, they're always in toProcess.
104
+ */
105
+ export function partitionByChange(
106
+ urls: string[],
107
+ priorHashes: Map<string, string>,
108
+ ): { toProcess: string[]; toSkip: string[] } {
109
+ const toProcess: string[] = [];
110
+ const toSkip: string[] = [];
111
+ for (const url of urls) {
112
+ if (priorHashes.has(url)) {
113
+ // Can't know if changed without fetching — still needs fetch, but hash after fetch
114
+ // will determine skip. Include all — hash check happens post-fetch.
115
+ toProcess.push(url);
116
+ } else {
117
+ toProcess.push(url);
118
+ }
119
+ }
120
+ return { toProcess, toSkip };
121
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ export * from './crawler.js';
2
+ export * from './visual-diff-verdict.js';
3
+ export * from './auth.js';
4
+ export * from './screen-reader.js';
5
+ export * from './job-queue.js';
6
+ export { startSqsWorker } from './sqs-queue.js';
7
+ export * from './session-capture.js';
8
+ export * from './session-export.js';
9
+ export * from './test-executor.js';
10
+ export { startLiveView, getFrame, liveViewNavigate, completeLiveView, closeLiveView, sendInput } from './live-view.js';
11
+ export { waitForEmailOTP, getInboxEmail } from './email-otp.js';
12
+ export { getSsoToken, getSsoClientCredentialsToken } from './sso-token.js';
13
+ export type { SsoConfig, SsoProvider } from './sso-token.js';
14
+ export * from './engine-router.js';
15
+ export * from './profiles/domain-profile.js';
16
+ export * from './policies/crawl-safety.js';
17
+ export * from './extractors/artifact-envelope.js';
18
+ export * from './network-observer.js';
19
+ export { createChromeTlsAgent, probeTlsFingerprint, chromeTlsAgent, CHROME_TLS_HEADERS } from './tls-fingerprint.js';
20
+ export type { TlsFingerprintOptions } from './tls-fingerprint.js';
21
+ export { prepareExtensionProfile, adBlockerProfile } from './browser-extension.js';
22
+ export type { ExtensionSource, ExtensionProfile } from './browser-extension.js';
23
+ export { createRedisQueue, maybeCreateRedisQueue, isRedisQueueEnabled } from './redis-queue.js';
24
+ export type { RedisQueueJob, RedisQueueClient } from './redis-queue.js';
25
+ export { discoverProjectUrls, discoverSitemapUrls } from './sitemap.js';
26
+ export type { UrlCandidate } from './sitemap.js';
27
+ export { httpCrawl } from './http-engine.js';
28
+ export { extractMarkdown } from './markdown-extract.js';
29
+ export type { ExtractedMarkdown } from './markdown-extract.js';
30
+ export type { HttpScreen, HttpCrawlOpts, HttpCrawlResult, HttpOutputFormat } from './http-engine.js';
31
+ export { TieredProxyManager } from './proxy.js';
32
+ export type { ProxyTier, ProxyTierConfig } from './proxy.js';
33
+ export { RedisCrawlFrontier, isRedisFrontierEnabled } from './redis-frontier.js';
34
+ export { startSpan, endSpan, withSpan, increment, flushMetrics } from './telemetry.js';
35
+ export type { Span, FinishedSpan } from './telemetry.js';