@taodigital/crawler 0.0.0-stage → 1.2.1

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 (494) hide show
  1. package/Dockerfile +82 -0
  2. package/README.md +10 -2
  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/accessibility-tree-crawler.d.ts +44 -0
  7. package/dist/accessibility-tree-crawler.js +156 -0
  8. package/dist/accessibility-tree-crawler.js.map +7 -0
  9. package/dist/advanced-ui-probes.d.ts +47 -0
  10. package/dist/advanced-ui-probes.js +310 -0
  11. package/dist/advanced-ui-probes.js.map +7 -0
  12. package/dist/ai-login.d.ts +32 -0
  13. package/dist/ai-login.js +250 -0
  14. package/dist/ai-login.js.map +7 -0
  15. package/dist/auth-token-rotator.d.ts +47 -0
  16. package/dist/auth-token-rotator.js +58 -0
  17. package/dist/auth-token-rotator.js.map +7 -0
  18. package/dist/auth.d.ts +20 -0
  19. package/dist/auth.js +348 -0
  20. package/dist/auth.js.map +7 -0
  21. package/dist/browser-extension.d.ts +37 -0
  22. package/dist/browser-extension.js +113 -0
  23. package/dist/browser-extension.js.map +7 -0
  24. package/dist/captcha-solver.d.ts +16 -0
  25. package/dist/captcha-solver.js +105 -0
  26. package/dist/captcha-solver.js.map +7 -0
  27. package/dist/color-contrast.d.ts +30 -0
  28. package/dist/color-contrast.js +78 -0
  29. package/dist/color-contrast.js.map +7 -0
  30. package/dist/content-chunker.d.ts +18 -0
  31. package/dist/content-chunker.js +152 -0
  32. package/dist/content-chunker.js.map +7 -0
  33. package/dist/content-dedup.d.ts +23 -0
  34. package/dist/content-dedup.js +60 -0
  35. package/dist/content-dedup.js.map +7 -0
  36. package/dist/cookie-consent.d.ts +25 -0
  37. package/dist/cookie-consent.js +78 -0
  38. package/dist/cookie-consent.js.map +7 -0
  39. package/dist/cookie-jar.d.ts +20 -0
  40. package/dist/cookie-jar.js +61 -0
  41. package/dist/cookie-jar.js.map +7 -0
  42. package/dist/crawl-session-manager.d.ts +44 -0
  43. package/dist/crawl-session-manager.js +145 -0
  44. package/dist/crawl-session-manager.js.map +7 -0
  45. package/dist/crawler.d.ts +233 -0
  46. package/dist/crawler.js +6239 -0
  47. package/dist/crawler.js.map +7 -0
  48. package/dist/crud-interaction.d.ts +31 -0
  49. package/dist/crud-interaction.js +264 -0
  50. package/dist/crud-interaction.js.map +7 -0
  51. package/dist/custom-js-injector.d.ts +42 -0
  52. package/dist/custom-js-injector.js +50 -0
  53. package/dist/custom-js-injector.js.map +7 -0
  54. package/dist/dark-mode-capture.d.ts +23 -0
  55. package/dist/dark-mode-capture.js +75 -0
  56. package/dist/dark-mode-capture.js.map +7 -0
  57. package/dist/dead-link-detector.d.ts +37 -0
  58. package/dist/dead-link-detector.js +50 -0
  59. package/dist/dead-link-detector.js.map +7 -0
  60. package/dist/dropdown-enumerator.d.ts +20 -0
  61. package/dist/dropdown-enumerator.js +230 -0
  62. package/dist/dropdown-enumerator.js.map +7 -0
  63. package/dist/element-hash.d.ts +9 -0
  64. package/dist/element-hash.js +9 -0
  65. package/dist/element-hash.js.map +7 -0
  66. package/dist/email-otp.d.ts +42 -0
  67. package/dist/email-otp.js +177 -0
  68. package/dist/email-otp.js.map +7 -0
  69. package/dist/engine-router.d.ts +47 -0
  70. package/dist/engine-router.js +94 -0
  71. package/dist/engine-router.js.map +7 -0
  72. package/dist/export/bulk-export.d.ts +26 -0
  73. package/dist/export/bulk-export.js +76 -0
  74. package/dist/export/bulk-export.js.map +7 -0
  75. package/dist/export/vector-export.d.ts +76 -0
  76. package/dist/export/vector-export.js +85 -0
  77. package/dist/export/vector-export.js.map +7 -0
  78. package/dist/extractors/advanced.d.ts +64 -0
  79. package/dist/extractors/advanced.js +265 -0
  80. package/dist/extractors/advanced.js.map +7 -0
  81. package/dist/extractors/artifact-envelope.d.ts +63 -0
  82. package/dist/extractors/artifact-envelope.js +63 -0
  83. package/dist/extractors/artifact-envelope.js.map +7 -0
  84. package/dist/extractors/branding-extractor.d.ts +46 -0
  85. package/dist/extractors/branding-extractor.js +104 -0
  86. package/dist/extractors/branding-extractor.js.map +7 -0
  87. package/dist/extractors/summary-extractor.d.ts +10 -0
  88. package/dist/extractors/summary-extractor.js +36 -0
  89. package/dist/extractors/summary-extractor.js.map +7 -0
  90. package/dist/failure-analysis-agent.d.ts +16 -0
  91. package/dist/failure-analysis-agent.js +42 -0
  92. package/dist/failure-analysis-agent.js.map +7 -0
  93. package/dist/feature-flag-detector.d.ts +30 -0
  94. package/dist/feature-flag-detector.js +55 -0
  95. package/dist/feature-flag-detector.js.map +7 -0
  96. package/dist/filter-pagination-crawler.d.ts +52 -0
  97. package/dist/filter-pagination-crawler.js +299 -0
  98. package/dist/filter-pagination-crawler.js.map +7 -0
  99. package/dist/form-detector.d.ts +34 -0
  100. package/dist/form-detector.js +58 -0
  101. package/dist/form-detector.js.map +7 -0
  102. package/dist/frontier.d.ts +40 -0
  103. package/dist/frontier.js +103 -0
  104. package/dist/frontier.js.map +7 -0
  105. package/dist/gap-pass.d.ts +36 -0
  106. package/dist/gap-pass.js +115 -0
  107. package/dist/gap-pass.js.map +7 -0
  108. package/dist/graphql-detector.d.ts +39 -0
  109. package/dist/graphql-detector.js +86 -0
  110. package/dist/graphql-detector.js.map +7 -0
  111. package/dist/http-engine.d.ts +80 -0
  112. package/dist/http-engine.js +318 -0
  113. package/dist/http-engine.js.map +7 -0
  114. package/dist/incremental-diff.d.ts +116 -0
  115. package/dist/incremental-diff.js +137 -0
  116. package/dist/incremental-diff.js.map +7 -0
  117. package/dist/index.d.ts +52 -0
  118. package/dist/index.js +83 -0
  119. package/dist/index.js.map +7 -0
  120. package/dist/input-fuzzer.d.ts +30 -0
  121. package/dist/input-fuzzer.js +105 -0
  122. package/dist/input-fuzzer.js.map +7 -0
  123. package/dist/interactive-probe.d.ts +144 -0
  124. package/dist/interactive-probe.js +865 -0
  125. package/dist/interactive-probe.js.map +7 -0
  126. package/dist/job-queue.d.ts +297 -0
  127. package/dist/job-queue.js +3812 -0
  128. package/dist/job-queue.js.map +7 -0
  129. package/dist/js-coverage.d.ts +29 -0
  130. package/dist/js-coverage.js +56 -0
  131. package/dist/js-coverage.js.map +7 -0
  132. package/dist/js-router-detector.d.ts +17 -0
  133. package/dist/js-router-detector.js +90 -0
  134. package/dist/js-router-detector.js.map +7 -0
  135. package/dist/language-detector.d.ts +22 -0
  136. package/dist/language-detector.js +57 -0
  137. package/dist/language-detector.js.map +7 -0
  138. package/dist/link-classifier.d.ts +23 -0
  139. package/dist/link-classifier.js +40 -0
  140. package/dist/link-classifier.js.map +7 -0
  141. package/dist/live-view.d.ts +36 -0
  142. package/dist/live-view.js +105 -0
  143. package/dist/live-view.js.map +7 -0
  144. package/dist/locator-extractor.d.ts +46 -0
  145. package/dist/locator-extractor.js +220 -0
  146. package/dist/locator-extractor.js.map +7 -0
  147. package/dist/logger.d.ts +2 -0
  148. package/dist/logger.js +10 -0
  149. package/dist/logger.js.map +7 -0
  150. package/dist/markdown-extract.d.ts +8 -0
  151. package/dist/markdown-extract.js +54 -0
  152. package/dist/markdown-extract.js.map +7 -0
  153. package/dist/multi-context-handler.d.ts +55 -0
  154. package/dist/multi-context-handler.js +182 -0
  155. package/dist/multi-context-handler.js.map +7 -0
  156. package/dist/native-crawler.d.ts +69 -0
  157. package/dist/native-crawler.js +839 -0
  158. package/dist/native-crawler.js.map +7 -0
  159. package/dist/nested-window-crawler.d.ts +41 -0
  160. package/dist/nested-window-crawler.js +172 -0
  161. package/dist/nested-window-crawler.js.map +7 -0
  162. package/dist/network-action-correlator.d.ts +47 -0
  163. package/dist/network-action-correlator.js +209 -0
  164. package/dist/network-action-correlator.js.map +7 -0
  165. package/dist/network-observer.d.ts +49 -0
  166. package/dist/network-observer.js +149 -0
  167. package/dist/network-observer.js.map +7 -0
  168. package/dist/network-throttle.d.ts +25 -0
  169. package/dist/network-throttle.js +34 -0
  170. package/dist/network-throttle.js.map +7 -0
  171. package/dist/openapi-generator.d.ts +49 -0
  172. package/dist/openapi-generator.js +89 -0
  173. package/dist/openapi-generator.js.map +7 -0
  174. package/dist/pdf-extractor.d.ts +28 -0
  175. package/dist/pdf-extractor.js +81 -0
  176. package/dist/pdf-extractor.js.map +7 -0
  177. package/dist/performance-metrics-collector.d.ts +50 -0
  178. package/dist/performance-metrics-collector.js +155 -0
  179. package/dist/performance-metrics-collector.js.map +7 -0
  180. package/dist/performance-metrics.d.ts +52 -0
  181. package/dist/performance-metrics.js +83 -0
  182. package/dist/performance-metrics.js.map +7 -0
  183. package/dist/phash.d.ts +50 -0
  184. package/dist/phash.js +136 -0
  185. package/dist/phash.js.map +7 -0
  186. package/dist/pii-scrubber.d.ts +21 -0
  187. package/dist/pii-scrubber.js +75 -0
  188. package/dist/pii-scrubber.js.map +7 -0
  189. package/dist/playwright-recorder.d.ts +39 -0
  190. package/dist/playwright-recorder.js +123 -0
  191. package/dist/playwright-recorder.js.map +7 -0
  192. package/dist/policies/crawl-safety.d.ts +9 -0
  193. package/dist/policies/crawl-safety.js +41 -0
  194. package/dist/policies/crawl-safety.js.map +7 -0
  195. package/dist/profiles/domain-profile.d.ts +19 -0
  196. package/dist/profiles/domain-profile.js +160 -0
  197. package/dist/profiles/domain-profile.js.map +7 -0
  198. package/dist/proxy.d.ts +50 -0
  199. package/dist/proxy.js +165 -0
  200. package/dist/proxy.js.map +7 -0
  201. package/dist/rate-limiter.d.ts +31 -0
  202. package/dist/rate-limiter.js +64 -0
  203. package/dist/rate-limiter.js.map +7 -0
  204. package/dist/redirect-tracker.d.ts +37 -0
  205. package/dist/redirect-tracker.js +65 -0
  206. package/dist/redirect-tracker.js.map +7 -0
  207. package/dist/redis-frontier.d.ts +48 -0
  208. package/dist/redis-frontier.js +126 -0
  209. package/dist/redis-frontier.js.map +7 -0
  210. package/dist/redis-queue.d.ts +48 -0
  211. package/dist/redis-queue.js +165 -0
  212. package/dist/redis-queue.js.map +7 -0
  213. package/dist/response-cache.d.ts +38 -0
  214. package/dist/response-cache.js +140 -0
  215. package/dist/response-cache.js.map +7 -0
  216. package/dist/responsive-detector.d.ts +29 -0
  217. package/dist/responsive-detector.js +46 -0
  218. package/dist/responsive-detector.js.map +7 -0
  219. package/dist/retry-policy.d.ts +20 -0
  220. package/dist/retry-policy.js +54 -0
  221. package/dist/retry-policy.js.map +7 -0
  222. package/dist/robots-txt.d.ts +9 -0
  223. package/dist/robots-txt.js +77 -0
  224. package/dist/robots-txt.js.map +7 -0
  225. package/dist/screen-reader.d.ts +15 -0
  226. package/dist/screen-reader.js +48 -0
  227. package/dist/screen-reader.js.map +7 -0
  228. package/dist/screenshot-masker.d.ts +28 -0
  229. package/dist/screenshot-masker.js +62 -0
  230. package/dist/screenshot-masker.js.map +7 -0
  231. package/dist/seo-checker.d.ts +33 -0
  232. package/dist/seo-checker.js +91 -0
  233. package/dist/seo-checker.js.map +7 -0
  234. package/dist/service-worker-interceptor.d.ts +36 -0
  235. package/dist/service-worker-interceptor.js +136 -0
  236. package/dist/service-worker-interceptor.js.map +7 -0
  237. package/dist/session-capture.d.ts +39 -0
  238. package/dist/session-capture.js +274 -0
  239. package/dist/session-capture.js.map +7 -0
  240. package/dist/session-export.d.ts +49 -0
  241. package/dist/session-export.js +117 -0
  242. package/dist/session-export.js.map +7 -0
  243. package/dist/shadow-dom-crawler.d.ts +35 -0
  244. package/dist/shadow-dom-crawler.js +132 -0
  245. package/dist/shadow-dom-crawler.js.map +7 -0
  246. package/dist/sitemap-writer.d.ts +21 -0
  247. package/dist/sitemap-writer.js +47 -0
  248. package/dist/sitemap-writer.js.map +7 -0
  249. package/dist/sitemap.d.ts +51 -0
  250. package/dist/sitemap.js +160 -0
  251. package/dist/sitemap.js.map +7 -0
  252. package/dist/skyvern.d.ts +56 -0
  253. package/dist/skyvern.js +129 -0
  254. package/dist/skyvern.js.map +7 -0
  255. package/dist/sqs-queue.d.ts +13 -0
  256. package/dist/sqs-queue.js +115 -0
  257. package/dist/sqs-queue.js.map +7 -0
  258. package/dist/sso-token.d.ts +29 -0
  259. package/dist/sso-token.js +76 -0
  260. package/dist/sso-token.js.map +7 -0
  261. package/dist/state-graph.d.ts +59 -0
  262. package/dist/state-graph.js +193 -0
  263. package/dist/state-graph.js.map +7 -0
  264. package/dist/stealth/human-behavior.d.ts +20 -0
  265. package/dist/stealth/human-behavior.js +202 -0
  266. package/dist/stealth/human-behavior.js.map +7 -0
  267. package/dist/telemetry.d.ts +34 -0
  268. package/dist/telemetry.js +77 -0
  269. package/dist/telemetry.js.map +7 -0
  270. package/dist/test-executor.d.ts +81 -0
  271. package/dist/test-executor.js +443 -0
  272. package/dist/test-executor.js.map +7 -0
  273. package/dist/tls-fingerprint.d.ts +50 -0
  274. package/dist/tls-fingerprint.js +81 -0
  275. package/dist/tls-fingerprint.js.map +7 -0
  276. package/dist/url-pattern-completer.d.ts +28 -0
  277. package/dist/url-pattern-completer.js +122 -0
  278. package/dist/url-pattern-completer.js.map +7 -0
  279. package/dist/visual-diff-verdict.d.ts +40 -0
  280. package/dist/visual-diff-verdict.js +27 -0
  281. package/dist/visual-diff-verdict.js.map +7 -0
  282. package/dist/webhook-notifier.d.ts +30 -0
  283. package/dist/webhook-notifier.js +76 -0
  284. package/dist/webhook-notifier.js.map +7 -0
  285. package/dist/websocket-detector.d.ts +24 -0
  286. package/dist/websocket-detector.js +60 -0
  287. package/dist/websocket-detector.js.map +7 -0
  288. package/dist/worker.d.ts +1 -0
  289. package/dist/worker.js +33 -0
  290. package/dist/worker.js.map +7 -0
  291. package/package.json +49 -4
  292. package/src/accessibility-auditor.ts +102 -0
  293. package/src/accessibility-tree-crawler.ts +246 -0
  294. package/src/advanced-ui-probes.ts +470 -0
  295. package/src/ai-login.ts +298 -0
  296. package/src/auth-token-rotator.ts +89 -0
  297. package/src/auth.ts +418 -0
  298. package/src/browser-extension.ts +186 -0
  299. package/src/captcha-solver.ts +143 -0
  300. package/src/color-contrast.ts +116 -0
  301. package/src/content-chunker.ts +170 -0
  302. package/src/content-dedup.ts +99 -0
  303. package/src/cookie-consent.ts +106 -0
  304. package/src/cookie-jar.ts +91 -0
  305. package/src/crawl-session-manager.ts +231 -0
  306. package/src/crawler.ts +7123 -0
  307. package/src/crawler.ts.rej +12 -0
  308. package/src/crud-interaction.ts +386 -0
  309. package/src/custom-js-injector.ts +93 -0
  310. package/src/dark-mode-capture.ts +110 -0
  311. package/src/dead-link-detector.ts +76 -0
  312. package/src/dropdown-enumerator.ts +312 -0
  313. package/src/element-hash.ts +16 -0
  314. package/src/email-otp.ts +252 -0
  315. package/src/engine-router.ts +141 -0
  316. package/src/export/bulk-export.ts +85 -0
  317. package/src/export/vector-export.ts +160 -0
  318. package/src/extractors/advanced.ts +309 -0
  319. package/src/extractors/artifact-envelope.ts +111 -0
  320. package/src/extractors/branding-extractor.ts +159 -0
  321. package/src/extractors/summary-extractor.ts +49 -0
  322. package/src/failure-analysis-agent.ts +51 -0
  323. package/src/feature-flag-detector.ts +97 -0
  324. package/src/filter-pagination-crawler.ts +500 -0
  325. package/src/form-detector.ts +103 -0
  326. package/src/frontier.test.ts +64 -0
  327. package/src/frontier.ts +144 -0
  328. package/src/gap-pass.ts +165 -0
  329. package/src/graphql-detector.ts +129 -0
  330. package/src/http-engine.ts +453 -0
  331. package/src/incremental-diff.ts +259 -0
  332. package/src/index.ts +52 -0
  333. package/src/input-fuzzer.ts +129 -0
  334. package/src/interactive-probe.ts +1136 -0
  335. package/src/job-queue.test.ts +187 -0
  336. package/src/job-queue.ts +4504 -0
  337. package/src/js-coverage.ts +87 -0
  338. package/src/js-router-detector.ts +112 -0
  339. package/src/language-detector.ts +82 -0
  340. package/src/link-classifier.ts +65 -0
  341. package/src/live-view.ts +146 -0
  342. package/src/locator-extractor.ts +309 -0
  343. package/src/logger.ts +9 -0
  344. package/src/markdown-extract.test.ts +73 -0
  345. package/src/markdown-extract.ts +76 -0
  346. package/src/multi-context-handler.ts +296 -0
  347. package/src/native-crawler.ts +1108 -0
  348. package/src/nested-window-crawler.ts +310 -0
  349. package/src/network-action-correlator.ts +320 -0
  350. package/src/network-observer.ts +204 -0
  351. package/src/network-throttle.ts +69 -0
  352. package/src/openapi-generator.ts +135 -0
  353. package/src/pdf-extractor.ts +123 -0
  354. package/src/performance-metrics-collector.ts +243 -0
  355. package/src/performance-metrics.ts +134 -0
  356. package/src/phash.ts +184 -0
  357. package/src/pii-scrubber.ts +99 -0
  358. package/src/playwright-recorder.ts +170 -0
  359. package/src/policies/crawl-safety.ts +52 -0
  360. package/src/profiles/domain-profile.ts +190 -0
  361. package/src/proxy.ts +238 -0
  362. package/src/rate-limiter.ts +87 -0
  363. package/src/redirect-tracker.ts +97 -0
  364. package/src/redis-frontier.ts +143 -0
  365. package/src/redis-queue.ts +208 -0
  366. package/src/response-cache.ts +150 -0
  367. package/src/responsive-detector.ts +92 -0
  368. package/src/retry-policy.ts +87 -0
  369. package/src/robots-txt.ts +96 -0
  370. package/src/screen-reader.ts +98 -0
  371. package/src/screenshot-masker.ts +98 -0
  372. package/src/seo-checker.ts +134 -0
  373. package/src/service-worker-interceptor.ts +185 -0
  374. package/src/session-capture.ts +335 -0
  375. package/src/session-export.test.ts +75 -0
  376. package/src/session-export.ts +172 -0
  377. package/src/shadow-dom-crawler.ts +198 -0
  378. package/src/sitemap-writer.ts +65 -0
  379. package/src/sitemap.test.ts +127 -0
  380. package/src/sitemap.ts +233 -0
  381. package/src/skyvern.ts +199 -0
  382. package/src/sqs-queue.ts +129 -0
  383. package/src/sso-token.ts +120 -0
  384. package/src/state-graph.ts +280 -0
  385. package/src/stealth/human-behavior.ts +227 -0
  386. package/src/telemetry.ts +122 -0
  387. package/src/test-executor.test.ts +72 -0
  388. package/src/test-executor.ts +578 -0
  389. package/src/tls-fingerprint.ts +111 -0
  390. package/src/url-pattern-completer.ts +161 -0
  391. package/src/vendor.d.ts +20 -0
  392. package/src/visual-diff-verdict.test.ts +80 -0
  393. package/src/visual-diff-verdict.ts +59 -0
  394. package/src/webhook-notifier.ts +109 -0
  395. package/src/websocket-detector.ts +83 -0
  396. package/src/worker.ts +50 -0
  397. package/tsconfig.json +13 -0
  398. package/types/accessibility-auditor.d.ts +29 -0
  399. package/types/accessibility-tree-crawler.d.ts +44 -0
  400. package/types/advanced-ui-probes.d.ts +47 -0
  401. package/types/ai-login.d.ts +32 -0
  402. package/types/auth-token-rotator.d.ts +47 -0
  403. package/types/auth.d.ts +20 -0
  404. package/types/browser-extension.d.ts +37 -0
  405. package/types/bull-worker.d.ts +6 -0
  406. package/types/captcha-solver.d.ts +16 -0
  407. package/types/color-contrast.d.ts +30 -0
  408. package/types/content-chunker.d.ts +18 -0
  409. package/types/content-dedup.d.ts +23 -0
  410. package/types/cookie-consent.d.ts +25 -0
  411. package/types/cookie-jar.d.ts +20 -0
  412. package/types/crawl-session-manager.d.ts +44 -0
  413. package/types/crawler.d.ts +233 -0
  414. package/types/crud-interaction.d.ts +31 -0
  415. package/types/custom-js-injector.d.ts +42 -0
  416. package/types/dark-mode-capture.d.ts +23 -0
  417. package/types/dead-link-detector.d.ts +37 -0
  418. package/types/dropdown-enumerator.d.ts +20 -0
  419. package/types/element-hash.d.ts +9 -0
  420. package/types/email-otp.d.ts +42 -0
  421. package/types/engine-router.d.ts +47 -0
  422. package/types/export/bulk-export.d.ts +26 -0
  423. package/types/export/vector-export.d.ts +76 -0
  424. package/types/extractors/advanced.d.ts +64 -0
  425. package/types/extractors/artifact-envelope.d.ts +63 -0
  426. package/types/extractors/branding-extractor.d.ts +46 -0
  427. package/types/extractors/summary-extractor.d.ts +10 -0
  428. package/types/failure-analysis-agent.d.ts +16 -0
  429. package/types/feature-flag-detector.d.ts +30 -0
  430. package/types/filter-pagination-crawler.d.ts +52 -0
  431. package/types/form-detector.d.ts +34 -0
  432. package/types/frontier.d.ts +40 -0
  433. package/types/gap-pass.d.ts +36 -0
  434. package/types/graphql-detector.d.ts +39 -0
  435. package/types/http-engine.d.ts +80 -0
  436. package/types/incremental-diff.d.ts +116 -0
  437. package/types/index.d.ts +52 -0
  438. package/types/input-fuzzer.d.ts +30 -0
  439. package/types/interactive-probe.d.ts +144 -0
  440. package/types/job-queue.d.ts +308 -0
  441. package/types/js-coverage.d.ts +29 -0
  442. package/types/js-router-detector.d.ts +17 -0
  443. package/types/language-detector.d.ts +22 -0
  444. package/types/link-classifier.d.ts +23 -0
  445. package/types/live-view.d.ts +36 -0
  446. package/types/locator-extractor.d.ts +46 -0
  447. package/types/logger.d.ts +2 -0
  448. package/types/markdown-extract.d.ts +8 -0
  449. package/types/multi-context-handler.d.ts +55 -0
  450. package/types/native-crawler.d.ts +69 -0
  451. package/types/nested-window-crawler.d.ts +41 -0
  452. package/types/network-action-correlator.d.ts +47 -0
  453. package/types/network-observer.d.ts +49 -0
  454. package/types/network-throttle.d.ts +25 -0
  455. package/types/openapi-generator.d.ts +49 -0
  456. package/types/pdf-extractor.d.ts +28 -0
  457. package/types/performance-metrics-collector.d.ts +50 -0
  458. package/types/performance-metrics.d.ts +52 -0
  459. package/types/phash.d.ts +50 -0
  460. package/types/pii-scrubber.d.ts +21 -0
  461. package/types/playwright-recorder.d.ts +39 -0
  462. package/types/policies/crawl-safety.d.ts +9 -0
  463. package/types/profiles/domain-profile.d.ts +19 -0
  464. package/types/proxy.d.ts +50 -0
  465. package/types/rate-limiter.d.ts +31 -0
  466. package/types/redirect-tracker.d.ts +37 -0
  467. package/types/redis-frontier.d.ts +48 -0
  468. package/types/redis-queue.d.ts +48 -0
  469. package/types/response-cache.d.ts +38 -0
  470. package/types/responsive-detector.d.ts +29 -0
  471. package/types/retry-policy.d.ts +20 -0
  472. package/types/robots-txt.d.ts +9 -0
  473. package/types/screen-reader.d.ts +15 -0
  474. package/types/screenshot-masker.d.ts +28 -0
  475. package/types/seo-checker.d.ts +33 -0
  476. package/types/service-worker-interceptor.d.ts +36 -0
  477. package/types/session-capture.d.ts +39 -0
  478. package/types/session-export.d.ts +49 -0
  479. package/types/shadow-dom-crawler.d.ts +35 -0
  480. package/types/sitemap-writer.d.ts +21 -0
  481. package/types/sitemap.d.ts +51 -0
  482. package/types/skyvern.d.ts +56 -0
  483. package/types/sqs-queue.d.ts +13 -0
  484. package/types/sso-token.d.ts +29 -0
  485. package/types/state-graph.d.ts +59 -0
  486. package/types/stealth/human-behavior.d.ts +20 -0
  487. package/types/telemetry.d.ts +34 -0
  488. package/types/test-executor.d.ts +84 -0
  489. package/types/tls-fingerprint.d.ts +50 -0
  490. package/types/url-pattern-completer.d.ts +28 -0
  491. package/types/visual-diff-verdict.d.ts +40 -0
  492. package/types/webhook-notifier.d.ts +30 -0
  493. package/types/websocket-detector.d.ts +24 -0
  494. package/types/worker.d.ts +1 -0
@@ -0,0 +1,113 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { createWriteStream, existsSync, mkdirSync } from "node:fs";
3
+ import { writeFile, mkdir, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { pipeline } from "node:stream/promises";
7
+ import { Readable } from "node:stream";
8
+ const PROFILE_BASE = process.env.EXTENSION_PROFILE_DIR ?? join(tmpdir(), "zeta-ext-profiles");
9
+ async function downloadCrx(url) {
10
+ const dest = join(tmpdir(), `zeta-ext-${randomBytes(6).toString("hex")}.crx`);
11
+ const res = await fetch(url);
12
+ if (!res.ok) throw new Error(`Failed to download extension from ${url}: ${res.status}`);
13
+ const writer = createWriteStream(dest);
14
+ await pipeline(Readable.fromWeb(res.body), writer);
15
+ return dest;
16
+ }
17
+ async function unpackCrx(crxPath, destDir) {
18
+ const { readFile } = await import("node:fs/promises");
19
+ const buf = await readFile(crxPath);
20
+ const magic = buf.readUInt32LE(0);
21
+ if (magic !== 1131557428) {
22
+ await mkdir(destDir, { recursive: true });
23
+ await writeFile(join(destDir, "__raw.crx"), buf);
24
+ return;
25
+ }
26
+ const version = buf.readUInt32LE(4);
27
+ let zipStart;
28
+ if (version === 3) {
29
+ const headerSize = buf.readUInt32LE(8);
30
+ zipStart = 12 + headerSize;
31
+ } else if (version === 2) {
32
+ const pubKeyLen = buf.readUInt32LE(8);
33
+ const sigLen = buf.readUInt32LE(12);
34
+ zipStart = 16 + pubKeyLen + sigLen;
35
+ } else {
36
+ throw new Error(`Unknown CRX version ${version}`);
37
+ }
38
+ const zipBuf = buf.subarray(zipStart);
39
+ const tmpZip = join(tmpdir(), `zeta-ext-${randomBytes(4).toString("hex")}.zip`);
40
+ await writeFile(tmpZip, zipBuf);
41
+ await mkdir(destDir, { recursive: true });
42
+ const unzipper = await import("unzipper").catch(() => null);
43
+ if (unzipper) {
44
+ await unzipper.Open.file(tmpZip).then((d) => d.extract({ path: destDir }));
45
+ } else {
46
+ const { execFile } = await import("node:child_process");
47
+ await new Promise(
48
+ (res, rej) => execFile("unzip", ["-o", tmpZip, "-d", destDir], (err) => err ? rej(err) : res())
49
+ );
50
+ }
51
+ await rm(tmpZip, { force: true });
52
+ }
53
+ async function prepareExtensionProfile(extensions) {
54
+ const key = createHash("md5").update(extensions.map((e) => e.source).sort().join("|")).digest("hex").slice(0, 12);
55
+ const profileDir = join(PROFILE_BASE, key);
56
+ const extensionDirs = [];
57
+ mkdirSync(profileDir, { recursive: true });
58
+ for (const ext of extensions) {
59
+ const extHash = createHash("md5").update(ext.source).digest("hex").slice(0, 8);
60
+ const extDir = join(profileDir, `ext-${extHash}`);
61
+ if (!existsSync(extDir)) {
62
+ let crxPath = ext.source;
63
+ if (ext.source.startsWith("http://") || ext.source.startsWith("https://")) {
64
+ crxPath = await downloadCrx(ext.source);
65
+ await unpackCrx(crxPath, extDir);
66
+ await rm(crxPath, { force: true });
67
+ } else if (ext.source.endsWith(".crx")) {
68
+ await unpackCrx(ext.source, extDir);
69
+ } else {
70
+ extensionDirs.push(ext.source);
71
+ continue;
72
+ }
73
+ }
74
+ extensionDirs.push(extDir);
75
+ }
76
+ const userDataDir = join(profileDir, "user-data");
77
+ mkdirSync(userDataDir, { recursive: true });
78
+ const defaultsDir = join(userDataDir, "Default");
79
+ mkdirSync(defaultsDir, { recursive: true });
80
+ const prefsPath = join(defaultsDir, "Preferences");
81
+ if (!existsSync(prefsPath)) {
82
+ await writeFile(prefsPath, JSON.stringify({
83
+ browser: { has_seen_welcome_page: true },
84
+ extensions: { alerts: { initialized: true } }
85
+ }), "utf8");
86
+ }
87
+ const loadExtensionArg = extensionDirs.join(",");
88
+ const args = [
89
+ `--load-extension=${loadExtensionArg}`,
90
+ `--disable-extensions-except=${loadExtensionArg}`,
91
+ `--user-data-dir=${userDataDir}`,
92
+ "--no-first-run",
93
+ "--no-default-browser-check"
94
+ ];
95
+ return {
96
+ userDataDir,
97
+ args,
98
+ cleanup: async () => {
99
+ await rm(profileDir, { recursive: true, force: true });
100
+ }
101
+ };
102
+ }
103
+ async function adBlockerProfile() {
104
+ return prepareExtensionProfile([{
105
+ name: "uBlock Origin",
106
+ source: "https://github.com/gorhill/uBlock/releases/download/1.57.2/uBlock0_1.57.2.chromium.zip"
107
+ }]);
108
+ }
109
+ export {
110
+ adBlockerProfile,
111
+ prepareExtensionProfile
112
+ };
113
+ //# sourceMappingURL=browser-extension.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/browser-extension.ts"],
4
+ "sourcesContent": ["/**\n * Browser extension injection support.\n *\n * Chrome extensions require a persistent profile directory \u2014 incompatible\n * with headless Docker by default. This module handles:\n * 1. Persistent profile directory per extension set\n * 2. Extension download from .crx URL or load from local path\n * 3. Launch args for Playwright / Stagehand\n *\n * Usage:\n * const profile = await prepareExtensionProfile(['/path/to/ext.crx']);\n * // pass profile.userDataDir and profile.args to browser launch\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport { createWriteStream, existsSync, mkdirSync } from 'node:fs';\nimport { writeFile, mkdir, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { pipeline } from 'node:stream/promises';\nimport { Readable } from 'node:stream';\n\nconst PROFILE_BASE = process.env.EXTENSION_PROFILE_DIR ?? join(tmpdir(), 'zeta-ext-profiles');\n\nexport interface ExtensionSource {\n /** Local .crx file path or https:// URL to download from */\n source: string;\n /** Friendly name for logging */\n name?: string;\n}\n\nexport interface ExtensionProfile {\n /** Chrome user-data-dir containing Preferences + extension unpacked dirs */\n userDataDir: string;\n /** Args to append to chromium launch \u2014 includes --load-extension and --disable-extensions-except */\n args: string[];\n /** Cleanup the profile directory when done */\n cleanup: () => Promise<void>;\n}\n\n/**\n * Download a .crx from a URL to a temp file. Returns path.\n */\nasync function downloadCrx(url: string): Promise<string> {\n const dest = join(tmpdir(), `zeta-ext-${randomBytes(6).toString('hex')}.crx`);\n const res = await fetch(url);\n if (!res.ok) throw new Error(`Failed to download extension from ${url}: ${res.status}`);\n const writer = createWriteStream(dest);\n await pipeline(Readable.fromWeb(res.body as any), writer);\n return dest;\n}\n\n/**\n * Unpack a .crx file to a directory Chrome can load as an unpacked extension.\n * CRX3 format: 4-byte magic + 4-byte version + 4-byte header_size + proto header + zip body\n */\nasync function unpackCrx(crxPath: string, destDir: string): Promise<void> {\n const { readFile } = await import('node:fs/promises');\n const buf = await readFile(crxPath);\n\n const magic = buf.readUInt32LE(0);\n if (magic !== 0x43723234) {\n // Not CRX \u2014 assume it's already a zip (unpacked extension)\n await mkdir(destDir, { recursive: true });\n await writeFile(join(destDir, '__raw.crx'), buf);\n return;\n }\n\n const version = buf.readUInt32LE(4);\n let zipStart: number;\n if (version === 3) {\n const headerSize = buf.readUInt32LE(8);\n zipStart = 12 + headerSize;\n } else if (version === 2) {\n const pubKeyLen = buf.readUInt32LE(8);\n const sigLen = buf.readUInt32LE(12);\n zipStart = 16 + pubKeyLen + sigLen;\n } else {\n throw new Error(`Unknown CRX version ${version}`);\n }\n\n const zipBuf = buf.subarray(zipStart);\n const tmpZip = join(tmpdir(), `zeta-ext-${randomBytes(4).toString('hex')}.zip`);\n await writeFile(tmpZip, zipBuf);\n\n await mkdir(destDir, { recursive: true });\n\n // Extract using unzipper (available in Node without native deps)\n // @ts-ignore \u2013 no types for unzipper\n const unzipper = await import('unzipper').catch(() => null);\n if (unzipper) {\n await unzipper.Open.file(tmpZip).then((d: any) => d.extract({ path: destDir }));\n } else {\n // Fallback: shell unzip\n const { execFile } = await import('node:child_process');\n await new Promise<void>((res, rej) =>\n execFile('unzip', ['-o', tmpZip, '-d', destDir], (err) => err ? rej(err) : res())\n );\n }\n await rm(tmpZip, { force: true });\n}\n\n/**\n * Prepare a Chrome user-data-dir with the given extensions loaded.\n * Returns profile info + cleanup function.\n */\nexport async function prepareExtensionProfile(\n extensions: ExtensionSource[]\n): Promise<ExtensionProfile> {\n // Stable profile key based on extension sources\n const key = createHash('md5')\n .update(extensions.map(e => e.source).sort().join('|'))\n .digest('hex')\n .slice(0, 12);\n\n const profileDir = join(PROFILE_BASE, key);\n const extensionDirs: string[] = [];\n\n mkdirSync(profileDir, { recursive: true });\n\n for (const ext of extensions) {\n const extHash = createHash('md5').update(ext.source).digest('hex').slice(0, 8);\n const extDir = join(profileDir, `ext-${extHash}`);\n\n if (!existsSync(extDir)) {\n let crxPath = ext.source;\n\n if (ext.source.startsWith('http://') || ext.source.startsWith('https://')) {\n crxPath = await downloadCrx(ext.source);\n await unpackCrx(crxPath, extDir);\n await rm(crxPath, { force: true });\n } else if (ext.source.endsWith('.crx')) {\n await unpackCrx(ext.source, extDir);\n } else {\n // Assume already-unpacked extension directory \u2014 symlink or copy\n extensionDirs.push(ext.source);\n continue;\n }\n }\n\n extensionDirs.push(extDir);\n }\n\n const userDataDir = join(profileDir, 'user-data');\n mkdirSync(userDataDir, { recursive: true });\n\n // Write a minimal Preferences file so Chrome doesn't show first-run UI\n const defaultsDir = join(userDataDir, 'Default');\n mkdirSync(defaultsDir, { recursive: true });\n const prefsPath = join(defaultsDir, 'Preferences');\n if (!existsSync(prefsPath)) {\n await writeFile(prefsPath, JSON.stringify({\n browser: { has_seen_welcome_page: true },\n extensions: { alerts: { initialized: true } },\n }), 'utf8');\n }\n\n const loadExtensionArg = extensionDirs.join(',');\n const args: string[] = [\n `--load-extension=${loadExtensionArg}`,\n `--disable-extensions-except=${loadExtensionArg}`,\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check',\n ];\n\n return {\n userDataDir,\n args,\n cleanup: async () => {\n await rm(profileDir, { recursive: true, force: true });\n },\n };\n}\n\n/**\n * Ad-blocker extension profile using the uBlock Origin CRX.\n * Reduces noise from ad networks during crawls.\n */\nexport async function adBlockerProfile(): Promise<ExtensionProfile> {\n // uBlock Origin 1.57.2 from GitHub release (MIT licensed)\n return prepareExtensionProfile([{\n name: 'uBlock Origin',\n source: 'https://github.com/gorhill/uBlock/releases/download/1.57.2/uBlock0_1.57.2.chromium.zip',\n }]);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CAPTCHA auto-solving via 2captcha or CapSolver APIs.
3
+ * Supports reCAPTCHA v2, hCaptcha, and Cloudflare Turnstile.
4
+ */
5
+ export type CaptchaSolverProvider = '2captcha' | 'capsolver';
6
+ interface SolveResult {
7
+ token: string;
8
+ provider: CaptchaSolverProvider;
9
+ }
10
+ /**
11
+ * Detect CAPTCHA type and sitekey on the page, then solve it.
12
+ * Returns null if no CAPTCHA found or solver not configured.
13
+ * Throws on solve failure.
14
+ */
15
+ export declare function solveCaptcha(page: any, pageUrl: string, apiKey: string, provider?: CaptchaSolverProvider): Promise<SolveResult | null>;
16
+ export {};
@@ -0,0 +1,105 @@
1
+ const POLL_INTERVAL_MS = 3e3;
2
+ const MAX_ATTEMPTS = 40;
3
+ async function solveCaptcha(page, pageUrl, apiKey, provider = "2captcha") {
4
+ const captchaInfo = await page.evaluate(() => {
5
+ const recaptchaEl = document.querySelector(".g-recaptcha, [data-sitekey]");
6
+ if (recaptchaEl) {
7
+ const sitekey = recaptchaEl.getAttribute("data-sitekey") || "";
8
+ return { type: "recaptcha", sitekey };
9
+ }
10
+ const hcaptchaEl = document.querySelector(".h-captcha, [data-hcaptcha-sitekey]");
11
+ if (hcaptchaEl) {
12
+ const sitekey = hcaptchaEl.getAttribute("data-sitekey") || hcaptchaEl.getAttribute("data-hcaptcha-sitekey") || "";
13
+ return { type: "hcaptcha", sitekey };
14
+ }
15
+ const turnstileEl = document.querySelector(".cf-turnstile");
16
+ if (turnstileEl) {
17
+ const sitekey = turnstileEl.getAttribute("data-sitekey") || "";
18
+ return { type: "turnstile", sitekey };
19
+ }
20
+ return null;
21
+ });
22
+ if (!captchaInfo?.sitekey) return null;
23
+ const token = provider === "capsolver" ? await solveWithCapSolver(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey) : await solveWith2Captcha(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey);
24
+ await page.evaluate((t, type) => {
25
+ if (type === "recaptcha") {
26
+ const el = document.querySelector("textarea.g-recaptcha-response");
27
+ if (el) {
28
+ el.value = t;
29
+ }
30
+ if (window.grecaptcha?.getResponse && document.querySelector("[data-callback]")) {
31
+ const cb = document.querySelector("[data-callback]")?.getAttribute("data-callback");
32
+ if (cb && window[cb]) window[cb](t);
33
+ }
34
+ }
35
+ if (type === "hcaptcha") {
36
+ const el = document.querySelector('textarea[name="h-captcha-response"]');
37
+ if (el) {
38
+ el.value = t;
39
+ }
40
+ }
41
+ if (type === "turnstile") {
42
+ const el = document.querySelector('[name="cf-turnstile-response"]');
43
+ if (el) {
44
+ el.value = t;
45
+ }
46
+ }
47
+ }, token, captchaInfo.type);
48
+ return { token, provider };
49
+ }
50
+ async function solveWith2Captcha(type, sitekey, pageUrl, apiKey) {
51
+ const method = type === "hcaptcha" ? "hcaptcha" : type === "turnstile" ? "turnstile" : "userrecaptcha";
52
+ const submitRes = await fetch("http://2captcha.com/in.php", {
53
+ method: "POST",
54
+ body: new URLSearchParams({
55
+ key: apiKey,
56
+ method,
57
+ googlekey: sitekey,
58
+ pageurl: pageUrl,
59
+ json: "1"
60
+ })
61
+ });
62
+ const submitData = await submitRes.json();
63
+ if (submitData.status !== 1) throw new Error(`2captcha submit failed: ${submitData.request}`);
64
+ const taskId = submitData.request;
65
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
66
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
67
+ const pollRes = await fetch(`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`);
68
+ const pollData = await pollRes.json();
69
+ if (pollData.status === 1) return pollData.request;
70
+ if (pollData.request !== "CAPCHA_NOT_READY") throw new Error(`2captcha error: ${pollData.request}`);
71
+ }
72
+ throw new Error("2captcha timed out after 2 minutes");
73
+ }
74
+ async function solveWithCapSolver(type, sitekey, pageUrl, apiKey) {
75
+ const taskType = type === "hcaptcha" ? "HCaptchaTaskProxyless" : type === "turnstile" ? "AntiTurnstileTaskProxyless" : "ReCaptchaV2TaskProxyless";
76
+ const createRes = await fetch("https://api.capsolver.com/createTask", {
77
+ method: "POST",
78
+ headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({
80
+ clientKey: apiKey,
81
+ task: { type: taskType, websiteURL: pageUrl, websiteKey: sitekey }
82
+ })
83
+ });
84
+ const createData = await createRes.json();
85
+ if (createData.errorId !== 0) throw new Error(`CapSolver create failed: ${createData.errorDescription}`);
86
+ const taskId = createData.taskId;
87
+ for (let i = 0; i < MAX_ATTEMPTS; i++) {
88
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
89
+ const pollRes = await fetch("https://api.capsolver.com/getTaskResult", {
90
+ method: "POST",
91
+ headers: { "Content-Type": "application/json" },
92
+ body: JSON.stringify({ clientKey: apiKey, taskId })
93
+ });
94
+ const pollData = await pollRes.json();
95
+ if (pollData.errorId !== 0) throw new Error(`CapSolver error: ${pollData.errorDescription}`);
96
+ if (pollData.status === "ready") {
97
+ return pollData.solution?.gRecaptchaResponse ?? pollData.solution?.token ?? "";
98
+ }
99
+ }
100
+ throw new Error("CapSolver timed out after 2 minutes");
101
+ }
102
+ export {
103
+ solveCaptcha
104
+ };
105
+ //# sourceMappingURL=captcha-solver.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/captcha-solver.ts"],
4
+ "sourcesContent": ["/**\n * CAPTCHA auto-solving via 2captcha or CapSolver APIs.\n * Supports reCAPTCHA v2, hCaptcha, and Cloudflare Turnstile.\n */\n\nexport type CaptchaSolverProvider = '2captcha' | 'capsolver';\n\ninterface SolveResult {\n token: string;\n provider: CaptchaSolverProvider;\n}\n\n// Poll interval and max attempts\nconst POLL_INTERVAL_MS = 3_000;\nconst MAX_ATTEMPTS = 40; // 2 min max\n\n/**\n * Detect CAPTCHA type and sitekey on the page, then solve it.\n * Returns null if no CAPTCHA found or solver not configured.\n * Throws on solve failure.\n */\nexport async function solveCaptcha(\n page: any,\n pageUrl: string,\n apiKey: string,\n provider: CaptchaSolverProvider = '2captcha'\n): Promise<SolveResult | null> {\n // Detect CAPTCHA type\n const captchaInfo = await page.evaluate(() => {\n // reCAPTCHA v2\n const recaptchaEl = document.querySelector('.g-recaptcha, [data-sitekey]') as HTMLElement | null;\n if (recaptchaEl) {\n const sitekey = recaptchaEl.getAttribute('data-sitekey') || '';\n return { type: 'recaptcha', sitekey };\n }\n // hCaptcha\n const hcaptchaEl = document.querySelector('.h-captcha, [data-hcaptcha-sitekey]') as HTMLElement | null;\n if (hcaptchaEl) {\n const sitekey = hcaptchaEl.getAttribute('data-sitekey') || hcaptchaEl.getAttribute('data-hcaptcha-sitekey') || '';\n return { type: 'hcaptcha', sitekey };\n }\n // Cloudflare Turnstile\n const turnstileEl = document.querySelector('.cf-turnstile') as HTMLElement | null;\n if (turnstileEl) {\n const sitekey = turnstileEl.getAttribute('data-sitekey') || '';\n return { type: 'turnstile', sitekey };\n }\n return null;\n });\n\n if (!captchaInfo?.sitekey) return null;\n\n const token = provider === 'capsolver'\n ? await solveWithCapSolver(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey)\n : await solveWith2Captcha(captchaInfo.type, captchaInfo.sitekey, pageUrl, apiKey);\n\n // Inject the token into the page\n await page.evaluate((t: string, type: string) => {\n // reCAPTCHA\n if (type === 'recaptcha') {\n const el = document.querySelector('textarea.g-recaptcha-response') as HTMLTextAreaElement | null;\n if (el) { el.value = t; }\n // Also trigger the callback if registered\n if ((window as any).grecaptcha?.getResponse && document.querySelector('[data-callback]')) {\n const cb = (document.querySelector('[data-callback]') as HTMLElement)?.getAttribute('data-callback');\n if (cb && (window as any)[cb]) (window as any)[cb](t);\n }\n }\n // hCaptcha\n if (type === 'hcaptcha') {\n const el = document.querySelector('textarea[name=\"h-captcha-response\"]') as HTMLTextAreaElement | null;\n if (el) { el.value = t; }\n }\n // Turnstile\n if (type === 'turnstile') {\n const el = document.querySelector('[name=\"cf-turnstile-response\"]') as HTMLInputElement | null;\n if (el) { el.value = t; }\n }\n }, token, captchaInfo.type);\n\n return { token, provider };\n}\n\nasync function solveWith2Captcha(type: string, sitekey: string, pageUrl: string, apiKey: string): Promise<string> {\n const method = type === 'hcaptcha' ? 'hcaptcha' : type === 'turnstile' ? 'turnstile' : 'userrecaptcha';\n\n const submitRes = await fetch('http://2captcha.com/in.php', {\n method: 'POST',\n body: new URLSearchParams({\n key: apiKey,\n method,\n googlekey: sitekey,\n pageurl: pageUrl,\n json: '1',\n }),\n });\n const submitData = await submitRes.json() as { status: number; request: string };\n if (submitData.status !== 1) throw new Error(`2captcha submit failed: ${submitData.request}`);\n const taskId = submitData.request;\n\n // Poll for result\n for (let i = 0; i < MAX_ATTEMPTS; i++) {\n await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));\n const pollRes = await fetch(`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${taskId}&json=1`);\n const pollData = await pollRes.json() as { status: number; request: string };\n if (pollData.status === 1) return pollData.request;\n if (pollData.request !== 'CAPCHA_NOT_READY') throw new Error(`2captcha error: ${pollData.request}`);\n }\n throw new Error('2captcha timed out after 2 minutes');\n}\n\nasync function solveWithCapSolver(type: string, sitekey: string, pageUrl: string, apiKey: string): Promise<string> {\n const taskType = type === 'hcaptcha' ? 'HCaptchaTaskProxyless'\n : type === 'turnstile' ? 'AntiTurnstileTaskProxyless'\n : 'ReCaptchaV2TaskProxyless';\n\n const createRes = await fetch('https://api.capsolver.com/createTask', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n clientKey: apiKey,\n task: { type: taskType, websiteURL: pageUrl, websiteKey: sitekey },\n }),\n });\n const createData = await createRes.json() as { errorId: number; taskId?: string; errorDescription?: string };\n if (createData.errorId !== 0) throw new Error(`CapSolver create failed: ${createData.errorDescription}`);\n const taskId = createData.taskId!;\n\n for (let i = 0; i < MAX_ATTEMPTS; i++) {\n await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));\n const pollRes = await fetch('https://api.capsolver.com/getTaskResult', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ clientKey: apiKey, taskId }),\n });\n const pollData = await pollRes.json() as { errorId: number; status: string; solution?: { gRecaptchaResponse?: string; token?: string }; errorDescription?: string };\n if (pollData.errorId !== 0) throw new Error(`CapSolver error: ${pollData.errorDescription}`);\n if (pollData.status === 'ready') {\n return pollData.solution?.gRecaptchaResponse ?? pollData.solution?.token ?? '';\n }\n }\n throw new Error('CapSolver timed out after 2 minutes');\n}\n"],
5
+ "mappings": "AAaA,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAOrB,eAAsB,aACpB,MACA,SACA,QACA,WAAkC,YACL;AAE7B,QAAM,cAAc,MAAM,KAAK,SAAS,MAAM;AAE5C,UAAM,cAAc,SAAS,cAAc,8BAA8B;AACzE,QAAI,aAAa;AACf,YAAM,UAAU,YAAY,aAAa,cAAc,KAAK;AAC5D,aAAO,EAAE,MAAM,aAAa,QAAQ;AAAA,IACtC;AAEA,UAAM,aAAa,SAAS,cAAc,qCAAqC;AAC/E,QAAI,YAAY;AACd,YAAM,UAAU,WAAW,aAAa,cAAc,KAAK,WAAW,aAAa,uBAAuB,KAAK;AAC/G,aAAO,EAAE,MAAM,YAAY,QAAQ;AAAA,IACrC;AAEA,UAAM,cAAc,SAAS,cAAc,eAAe;AAC1D,QAAI,aAAa;AACf,YAAM,UAAU,YAAY,aAAa,cAAc,KAAK;AAC5D,aAAO,EAAE,MAAM,aAAa,QAAQ;AAAA,IACtC;AACA,WAAO;AAAA,EACT,CAAC;AAED,MAAI,CAAC,aAAa,QAAS,QAAO;AAElC,QAAM,QAAQ,aAAa,cACvB,MAAM,mBAAmB,YAAY,MAAM,YAAY,SAAS,SAAS,MAAM,IAC/E,MAAM,kBAAkB,YAAY,MAAM,YAAY,SAAS,SAAS,MAAM;AAGlF,QAAM,KAAK,SAAS,CAAC,GAAW,SAAiB;AAE/C,QAAI,SAAS,aAAa;AACxB,YAAM,KAAK,SAAS,cAAc,+BAA+B;AACjE,UAAI,IAAI;AAAE,WAAG,QAAQ;AAAA,MAAG;AAExB,UAAK,OAAe,YAAY,eAAe,SAAS,cAAc,iBAAiB,GAAG;AACxF,cAAM,KAAM,SAAS,cAAc,iBAAiB,GAAmB,aAAa,eAAe;AACnG,YAAI,MAAO,OAAe,EAAE,EAAG,CAAC,OAAe,EAAE,EAAE,CAAC;AAAA,MACtD;AAAA,IACF;AAEA,QAAI,SAAS,YAAY;AACvB,YAAM,KAAK,SAAS,cAAc,qCAAqC;AACvE,UAAI,IAAI;AAAE,WAAG,QAAQ;AAAA,MAAG;AAAA,IAC1B;AAEA,QAAI,SAAS,aAAa;AACxB,YAAM,KAAK,SAAS,cAAc,gCAAgC;AAClE,UAAI,IAAI;AAAE,WAAG,QAAQ;AAAA,MAAG;AAAA,IAC1B;AAAA,EACF,GAAG,OAAO,YAAY,IAAI;AAE1B,SAAO,EAAE,OAAO,SAAS;AAC3B;AAEA,eAAe,kBAAkB,MAAc,SAAiB,SAAiB,QAAiC;AAChH,QAAM,SAAS,SAAS,aAAa,aAAa,SAAS,cAAc,cAAc;AAEvF,QAAM,YAAY,MAAM,MAAM,8BAA8B;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,IAAI,gBAAgB;AAAA,MACxB,KAAK;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAAA,EACH,CAAC;AACD,QAAM,aAAa,MAAM,UAAU,KAAK;AACxC,MAAI,WAAW,WAAW,EAAG,OAAM,IAAI,MAAM,2BAA2B,WAAW,OAAO,EAAE;AAC5F,QAAM,SAAS,WAAW;AAG1B,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,gBAAgB,CAAC;AACtD,UAAM,UAAU,MAAM,MAAM,mCAAmC,MAAM,kBAAkB,MAAM,SAAS;AACtG,UAAM,WAAW,MAAM,QAAQ,KAAK;AACpC,QAAI,SAAS,WAAW,EAAG,QAAO,SAAS;AAC3C,QAAI,SAAS,YAAY,mBAAoB,OAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,EAAE;AAAA,EACpG;AACA,QAAM,IAAI,MAAM,oCAAoC;AACtD;AAEA,eAAe,mBAAmB,MAAc,SAAiB,SAAiB,QAAiC;AACjH,QAAM,WAAW,SAAS,aAAa,0BACnC,SAAS,cAAc,+BACvB;AAEJ,QAAM,YAAY,MAAM,MAAM,wCAAwC;AAAA,IACpE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACnB,WAAW;AAAA,MACX,MAAM,EAAE,MAAM,UAAU,YAAY,SAAS,YAAY,QAAQ;AAAA,IACnE,CAAC;AAAA,EACH,CAAC;AACD,QAAM,aAAa,MAAM,UAAU,KAAK;AACxC,MAAI,WAAW,YAAY,EAAG,OAAM,IAAI,MAAM,4BAA4B,WAAW,gBAAgB,EAAE;AACvG,QAAM,SAAS,WAAW;AAE1B,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,gBAAgB,CAAC;AACtD,UAAM,UAAU,MAAM,MAAM,2CAA2C;AAAA,MACrE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,WAAW,QAAQ,OAAO,CAAC;AAAA,IACpD,CAAC;AACD,UAAM,WAAW,MAAM,QAAQ,KAAK;AACpC,QAAI,SAAS,YAAY,EAAG,OAAM,IAAI,MAAM,oBAAoB,SAAS,gBAAgB,EAAE;AAC3F,QAAI,SAAS,WAAW,SAAS;AAC/B,aAAO,SAAS,UAAU,sBAAsB,SAAS,UAAU,SAAS;AAAA,IAC9E;AAAA,EACF;AACA,QAAM,IAAI,MAAM,qCAAqC;AACvD;",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * WCAG color contrast checker.
3
+ *
4
+ * Evaluates text contrast ratios against backgrounds for all visible text elements.
5
+ * Reports elements that fail WCAG 2.1 AA (4.5:1 for normal text, 3:1 for large text)
6
+ * or AAA (7:1 for normal, 4.5:1 for large text) requirements.
7
+ *
8
+ * Uses computedStyle to get actual rendered colors — accounts for CSS variables
9
+ * and inheritance.
10
+ */
11
+ import type { Page } from 'playwright';
12
+ export interface ContrastViolation {
13
+ selector: string;
14
+ text: string;
15
+ foreground: string;
16
+ background: string;
17
+ ratio: number;
18
+ required: number;
19
+ level: 'AA' | 'AAA';
20
+ isLargeText: boolean;
21
+ severity: 'fail' | 'warn';
22
+ }
23
+ export interface ContrastAuditResult {
24
+ url: string;
25
+ violations: ContrastViolation[];
26
+ passCount: number;
27
+ score: number;
28
+ auditedAt: string;
29
+ }
30
+ export declare function auditColorContrast(page: Page, url: string): Promise<ContrastAuditResult>;
@@ -0,0 +1,78 @@
1
+ async function auditColorContrast(page, url) {
2
+ const auditedAt = (/* @__PURE__ */ new Date()).toISOString();
3
+ const violations = await page.evaluate(() => {
4
+ function parseRgb(color) {
5
+ const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?/);
6
+ return m ? [+m[1], +m[2], +m[3], m[4] !== void 0 ? +m[4] : 1] : null;
7
+ }
8
+ function luminance([r, g, b]) {
9
+ const toLinear = (c) => {
10
+ const n = c / 255;
11
+ return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
12
+ };
13
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
14
+ }
15
+ function contrastRatio(fg, bg) {
16
+ const l1 = luminance(fg);
17
+ const l2 = luminance(bg);
18
+ const lighter = Math.max(l1, l2);
19
+ const darker = Math.min(l1, l2);
20
+ return Math.round((lighter + 0.05) / (darker + 0.05) * 100) / 100;
21
+ }
22
+ const violations2 = [];
23
+ const elements = Array.from(document.querySelectorAll("p, h1, h2, h3, h4, h5, h6, a, span, li, td, th, label, button"));
24
+ let checked = 0;
25
+ for (const el of elements) {
26
+ if (checked >= 100) break;
27
+ const text = (el.textContent ?? "").trim().slice(0, 60);
28
+ if (!text) continue;
29
+ const style = window.getComputedStyle(el);
30
+ if (style.display === "none" || style.visibility === "hidden") continue;
31
+ const fg = parseRgb(style.color);
32
+ const bg = parseRgb(style.backgroundColor);
33
+ if (!fg || !bg || bg[3] === 0) continue;
34
+ const fontSize = parseFloat(style.fontSize);
35
+ const fontWeight = style.fontWeight;
36
+ const isLargeText = fontSize >= 18 || fontSize >= 14 && (fontWeight === "bold" || +fontWeight >= 700);
37
+ const ratio = contrastRatio(fg, bg);
38
+ const aaRequired = isLargeText ? 3 : 4.5;
39
+ const aaaRequired = isLargeText ? 4.5 : 7;
40
+ if (ratio < aaRequired) {
41
+ violations2.push({
42
+ selector: el.tagName.toLowerCase(),
43
+ text: text.slice(0, 50),
44
+ foreground: style.color,
45
+ background: style.backgroundColor,
46
+ ratio,
47
+ required: aaRequired,
48
+ level: "AA",
49
+ isLargeText,
50
+ severity: "fail"
51
+ });
52
+ } else if (ratio < aaaRequired) {
53
+ violations2.push({
54
+ selector: el.tagName.toLowerCase(),
55
+ text: text.slice(0, 50),
56
+ foreground: style.color,
57
+ background: style.backgroundColor,
58
+ ratio,
59
+ required: aaaRequired,
60
+ level: "AAA",
61
+ isLargeText,
62
+ severity: "warn"
63
+ });
64
+ } else {
65
+ checked++;
66
+ }
67
+ }
68
+ return violations2;
69
+ }).catch(() => []);
70
+ const passCount = Math.max(0, 100 - violations.length);
71
+ const criticals = violations.filter((v) => v.severity === "fail").length;
72
+ const score = Math.max(0, 100 - criticals * 15 - violations.filter((v) => v.severity === "warn").length * 3);
73
+ return { url, violations, passCount, score, auditedAt };
74
+ }
75
+ export {
76
+ auditColorContrast
77
+ };
78
+ //# sourceMappingURL=color-contrast.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/color-contrast.ts"],
4
+ "sourcesContent": ["/**\n * WCAG color contrast checker.\n *\n * Evaluates text contrast ratios against backgrounds for all visible text elements.\n * Reports elements that fail WCAG 2.1 AA (4.5:1 for normal text, 3:1 for large text)\n * or AAA (7:1 for normal, 4.5:1 for large text) requirements.\n *\n * Uses computedStyle to get actual rendered colors \u2014 accounts for CSS variables\n * and inheritance.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface ContrastViolation {\n selector: string;\n text: string;\n foreground: string; // rgb(r, g, b)\n background: string; // rgb(r, g, b)\n ratio: number; // actual contrast ratio\n required: number; // required ratio (4.5 or 3.0)\n level: 'AA' | 'AAA';\n isLargeText: boolean; // font-size >= 18px or bold >= 14px\n severity: 'fail' | 'warn'; // fail = below AA, warn = below AAA\n}\n\nexport interface ContrastAuditResult {\n url: string;\n violations: ContrastViolation[];\n passCount: number;\n score: number; // 0-100\n auditedAt: string;\n}\n\nexport async function auditColorContrast(page: Page, url: string): Promise<ContrastAuditResult> {\n const auditedAt = new Date().toISOString();\n\n const violations: ContrastViolation[] = await page.evaluate(() => {\n function parseRgb(color: string): [number, number, number, number] | null {\n const m = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?/);\n return m ? [+m[1], +m[2], +m[3], m[4] !== undefined ? +m[4] : 1] : null;\n }\n function luminance([r, g, b]: [number, number, number, number]): number {\n const toLinear = (c: number) => {\n const n = c / 255;\n return n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);\n };\n return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n }\n function contrastRatio(fg: [number,number,number,number], bg: [number,number,number,number]): number {\n const l1 = luminance(fg);\n const l2 = luminance(bg);\n const lighter = Math.max(l1, l2);\n const darker = Math.min(l1, l2);\n return Math.round(((lighter + 0.05) / (darker + 0.05)) * 100) / 100;\n }\n\n const violations: any[] = [];\n const elements = Array.from(document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, a, span, li, td, th, label, button'));\n let checked = 0;\n\n for (const el of elements as HTMLElement[]) {\n if (checked >= 100) break; // limit to avoid perf hit\n const text = (el.textContent ?? '').trim().slice(0, 60);\n if (!text) continue;\n\n const style = window.getComputedStyle(el);\n if (style.display === 'none' || style.visibility === 'hidden') continue;\n\n const fg = parseRgb(style.color);\n const bg = parseRgb(style.backgroundColor);\n if (!fg || !bg || bg[3] === 0) continue; // transparent bg \u2014 can't check\n\n const fontSize = parseFloat(style.fontSize);\n const fontWeight = style.fontWeight;\n const isLargeText = fontSize >= 18 || (fontSize >= 14 && (fontWeight === 'bold' || +fontWeight >= 700));\n const ratio = contrastRatio(fg, bg);\n const aaRequired = isLargeText ? 3.0 : 4.5;\n const aaaRequired = isLargeText ? 4.5 : 7.0;\n\n if (ratio < aaRequired) {\n violations.push({\n selector: el.tagName.toLowerCase(),\n text: text.slice(0, 50),\n foreground: style.color,\n background: style.backgroundColor,\n ratio,\n required: aaRequired,\n level: 'AA',\n isLargeText,\n severity: 'fail',\n });\n } else if (ratio < aaaRequired) {\n violations.push({\n selector: el.tagName.toLowerCase(),\n text: text.slice(0, 50),\n foreground: style.color,\n background: style.backgroundColor,\n ratio,\n required: aaaRequired,\n level: 'AAA',\n isLargeText,\n severity: 'warn',\n });\n } else {\n checked++;\n }\n }\n return violations;\n }).catch(() => [] as ContrastViolation[]);\n\n const passCount = Math.max(0, 100 - violations.length);\n const criticals = violations.filter(v => v.severity === 'fail').length;\n const score = Math.max(0, 100 - criticals * 15 - violations.filter(v => v.severity === 'warn').length * 3);\n\n return { url, violations, passCount, score, auditedAt };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["violations"]
7
+ }
@@ -0,0 +1,18 @@
1
+ export type ChunkingStrategy = 'fixed' | 'overlap' | 'sentence' | 'paragraph' | 'markdown-heading' | 'semantic' | 'recursive';
2
+ export interface ChunkOpts {
3
+ strategy: ChunkingStrategy;
4
+ chunkSize?: number;
5
+ overlap?: number;
6
+ separators?: string[];
7
+ }
8
+ export interface Chunk {
9
+ index: number;
10
+ text: string;
11
+ tokenEstimate: number;
12
+ startChar: number;
13
+ endChar: number;
14
+ heading?: string;
15
+ }
16
+ export declare function estimateTokens(text: string): number;
17
+ export declare function chunkContent(text: string, opts: ChunkOpts): Chunk[];
18
+ export declare function mergeSmallChunks(chunks: Chunk[], minTokens: number): Chunk[];
@@ -0,0 +1,152 @@
1
+ function estimateTokens(text) {
2
+ return Math.ceil(text.length / 4);
3
+ }
4
+ function chunkContent(text, opts) {
5
+ const size = opts.chunkSize ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_SIZE ?? "") || 2e3);
6
+ const charSize = size * 4;
7
+ const overlap = opts.overlap ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_OVERLAP ?? "") || 200);
8
+ const overlapChars = overlap * 4;
9
+ switch (opts.strategy) {
10
+ case "fixed":
11
+ return fixedChunk(text, charSize);
12
+ case "overlap":
13
+ return overlapChunk(text, charSize, overlapChars);
14
+ case "sentence":
15
+ return sentenceChunk(text, charSize);
16
+ case "paragraph":
17
+ return paragraphChunk(text, charSize);
18
+ case "markdown-heading":
19
+ return markdownHeadingChunk(text, charSize);
20
+ case "semantic":
21
+ return semanticChunk(text, charSize);
22
+ case "recursive":
23
+ return recursiveChunk(text, charSize, opts.separators ?? ["\n\n", "\n", ". ", " "]);
24
+ default:
25
+ return fixedChunk(text, charSize);
26
+ }
27
+ }
28
+ function makeChunk(text, index, startChar, heading) {
29
+ return { index, text, tokenEstimate: estimateTokens(text), startChar, endChar: startChar + text.length, heading };
30
+ }
31
+ function fixedChunk(text, charSize) {
32
+ const chunks = [];
33
+ let i = 0;
34
+ while (i < text.length) {
35
+ const slice = text.slice(i, i + charSize);
36
+ chunks.push(makeChunk(slice, chunks.length, i));
37
+ i += charSize;
38
+ }
39
+ return chunks;
40
+ }
41
+ function overlapChunk(text, charSize, overlapChars) {
42
+ const chunks = [];
43
+ let i = 0;
44
+ while (i < text.length) {
45
+ const slice = text.slice(i, i + charSize);
46
+ chunks.push(makeChunk(slice, chunks.length, i));
47
+ i += charSize - overlapChars;
48
+ if (i <= 0) break;
49
+ }
50
+ return chunks;
51
+ }
52
+ function sentenceChunk(text, charSize) {
53
+ const sentences = text.match(/[^.!?]*[.!?]+["']?\s*/g) ?? [text];
54
+ return groupIntoChunks(sentences, charSize);
55
+ }
56
+ function paragraphChunk(text, charSize) {
57
+ const paras = text.split(/\n\n+/).filter((p) => p.trim());
58
+ return groupIntoChunks(paras, charSize);
59
+ }
60
+ function markdownHeadingChunk(text, charSize) {
61
+ const chunks = [];
62
+ const headingRe = /^(#{1,3}\s.+)$/m;
63
+ const sections = text.split(/(?=^#{1,3}\s)/m).filter((s) => s.trim());
64
+ let pos = 0;
65
+ for (const section of sections) {
66
+ const headingMatch = section.match(headingRe);
67
+ const heading = headingMatch ? headingMatch[1].replace(/^#+\s*/, "").trim() : void 0;
68
+ if (section.length <= charSize) {
69
+ chunks.push({ ...makeChunk(section.trim(), chunks.length, pos), heading });
70
+ } else {
71
+ const sub = paragraphChunk(section, charSize);
72
+ for (const s of sub) {
73
+ chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar, heading });
74
+ }
75
+ }
76
+ pos += section.length;
77
+ }
78
+ return chunks.length > 0 ? chunks : fixedChunk(text, charSize);
79
+ }
80
+ function semanticChunk(text, charSize) {
81
+ const paras = text.split(/\n\n+/).filter((p) => p.trim());
82
+ const merged = [];
83
+ let current = "";
84
+ for (const para of paras) {
85
+ if (current.length + para.length + 2 <= charSize) {
86
+ current = current ? current + "\n\n" + para : para;
87
+ } else {
88
+ if (current) merged.push(current);
89
+ current = para;
90
+ }
91
+ }
92
+ if (current) merged.push(current);
93
+ return groupIntoChunks(merged, charSize);
94
+ }
95
+ function recursiveChunk(text, charSize, separators) {
96
+ if (text.length <= charSize) return [makeChunk(text, 0, 0)];
97
+ const [sep, ...rest] = separators;
98
+ if (!sep) return fixedChunk(text, charSize);
99
+ const parts = text.split(sep).filter((p) => p);
100
+ const chunks = [];
101
+ let pos = 0;
102
+ for (const part of parts) {
103
+ if (part.length <= charSize) {
104
+ chunks.push(makeChunk(part, chunks.length, pos));
105
+ } else {
106
+ const sub = recursiveChunk(part, charSize, rest);
107
+ for (const s of sub) chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar });
108
+ }
109
+ pos += part.length + sep.length;
110
+ }
111
+ return chunks;
112
+ }
113
+ function groupIntoChunks(parts, charSize) {
114
+ const chunks = [];
115
+ let current = "";
116
+ let startChar = 0;
117
+ let pos = 0;
118
+ for (const part of parts) {
119
+ if (current.length + part.length + 1 > charSize && current) {
120
+ chunks.push(makeChunk(current.trim(), chunks.length, startChar));
121
+ startChar = pos;
122
+ current = part;
123
+ } else {
124
+ current = current ? current + " " + part : part;
125
+ }
126
+ pos += part.length + 1;
127
+ }
128
+ if (current.trim()) chunks.push(makeChunk(current.trim(), chunks.length, startChar));
129
+ return chunks;
130
+ }
131
+ function mergeSmallChunks(chunks, minTokens) {
132
+ const minChars = minTokens * 4;
133
+ const result = [];
134
+ let i = 0;
135
+ while (i < chunks.length) {
136
+ if (chunks[i].text.length < minChars && i + 1 < chunks.length) {
137
+ const merged = chunks[i].text + "\n\n" + chunks[i + 1].text;
138
+ result.push(makeChunk(merged, result.length, chunks[i].startChar, chunks[i].heading ?? chunks[i + 1].heading));
139
+ i += 2;
140
+ } else {
141
+ result.push({ ...chunks[i], index: result.length });
142
+ i++;
143
+ }
144
+ }
145
+ return result;
146
+ }
147
+ export {
148
+ chunkContent,
149
+ estimateTokens,
150
+ mergeSmallChunks
151
+ };
152
+ //# sourceMappingURL=content-chunker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/content-chunker.ts"],
4
+ "sourcesContent": ["export type ChunkingStrategy = 'fixed' | 'overlap' | 'sentence' | 'paragraph' | 'markdown-heading' | 'semantic' | 'recursive';\n\nexport interface ChunkOpts {\n strategy: ChunkingStrategy;\n chunkSize?: number; // token estimate target (1 token \u2248 4 chars). Default 2000.\n overlap?: number; // overlap tokens for 'overlap' strategy. Default 200.\n separators?: string[]; // for 'recursive' strategy. Default ['\\\\n\\\\n', '\\\\n', '. ', ' ']\n}\n\nexport interface Chunk {\n index: number;\n text: string;\n tokenEstimate: number;\n startChar: number;\n endChar: number;\n heading?: string;\n}\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function chunkContent(text: string, opts: ChunkOpts): Chunk[] {\n const size = opts.chunkSize ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_SIZE ?? '') || 2000);\n const charSize = size * 4;\n const overlap = opts.overlap ?? (parseInt(process.env.CRAWLER_CONTENT_CHUNK_OVERLAP ?? '') || 200);\n const overlapChars = overlap * 4;\n\n switch (opts.strategy) {\n case 'fixed': return fixedChunk(text, charSize);\n case 'overlap': return overlapChunk(text, charSize, overlapChars);\n case 'sentence': return sentenceChunk(text, charSize);\n case 'paragraph': return paragraphChunk(text, charSize);\n case 'markdown-heading': return markdownHeadingChunk(text, charSize);\n case 'semantic': return semanticChunk(text, charSize);\n case 'recursive': return recursiveChunk(text, charSize, opts.separators ?? ['\\n\\n', '\\n', '. ', ' ']);\n default: return fixedChunk(text, charSize);\n }\n}\n\nfunction makeChunk(text: string, index: number, startChar: number, heading?: string): Chunk {\n return { index, text, tokenEstimate: estimateTokens(text), startChar, endChar: startChar + text.length, heading };\n}\n\nfunction fixedChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize;\n }\n return chunks;\n}\n\nfunction overlapChunk(text: string, charSize: number, overlapChars: number): Chunk[] {\n const chunks: Chunk[] = [];\n let i = 0;\n while (i < text.length) {\n const slice = text.slice(i, i + charSize);\n chunks.push(makeChunk(slice, chunks.length, i));\n i += charSize - overlapChars;\n if (i <= 0) break;\n }\n return chunks;\n}\n\nfunction sentenceChunk(text: string, charSize: number): Chunk[] {\n const sentences = text.match(/[^.!?]*[.!?]+[\"']?\\s*/g) ?? [text];\n return groupIntoChunks(sentences, charSize);\n}\n\nfunction paragraphChunk(text: string, charSize: number): Chunk[] {\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n return groupIntoChunks(paras, charSize);\n}\n\nfunction markdownHeadingChunk(text: string, charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n const headingRe = /^(#{1,3}\\s.+)$/m;\n const sections = text.split(/(?=^#{1,3}\\s)/m).filter(s => s.trim());\n let pos = 0;\n for (const section of sections) {\n const headingMatch = section.match(headingRe);\n const heading = headingMatch ? headingMatch[1].replace(/^#+\\s*/, '').trim() : undefined;\n if (section.length <= charSize) {\n chunks.push({ ...makeChunk(section.trim(), chunks.length, pos), heading });\n } else {\n // Section too large \u2014 sub-chunk by paragraph\n const sub = paragraphChunk(section, charSize);\n for (const s of sub) {\n chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar, heading });\n }\n }\n pos += section.length;\n }\n return chunks.length > 0 ? chunks : fixedChunk(text, charSize);\n}\n\nfunction semanticChunk(text: string, charSize: number): Chunk[] {\n // Like paragraph but merges short paragraphs with the next\n const paras = text.split(/\\n\\n+/).filter(p => p.trim());\n const merged: string[] = [];\n let current = '';\n for (const para of paras) {\n if (current.length + para.length + 2 <= charSize) {\n current = current ? current + '\\n\\n' + para : para;\n } else {\n if (current) merged.push(current);\n current = para;\n }\n }\n if (current) merged.push(current);\n return groupIntoChunks(merged, charSize);\n}\n\nfunction recursiveChunk(text: string, charSize: number, separators: string[]): Chunk[] {\n if (text.length <= charSize) return [makeChunk(text, 0, 0)];\n const [sep, ...rest] = separators;\n if (!sep) return fixedChunk(text, charSize);\n const parts = text.split(sep).filter(p => p);\n const chunks: Chunk[] = [];\n let pos = 0;\n for (const part of parts) {\n if (part.length <= charSize) {\n chunks.push(makeChunk(part, chunks.length, pos));\n } else {\n const sub = recursiveChunk(part, charSize, rest);\n for (const s of sub) chunks.push({ ...s, index: chunks.length, startChar: pos + s.startChar, endChar: pos + s.endChar });\n }\n pos += part.length + sep.length;\n }\n return chunks;\n}\n\nfunction groupIntoChunks(parts: string[], charSize: number): Chunk[] {\n const chunks: Chunk[] = [];\n let current = '';\n let startChar = 0;\n let pos = 0;\n for (const part of parts) {\n if (current.length + part.length + 1 > charSize && current) {\n chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n startChar = pos;\n current = part;\n } else {\n current = current ? current + ' ' + part : part;\n }\n pos += part.length + 1;\n }\n if (current.trim()) chunks.push(makeChunk(current.trim(), chunks.length, startChar));\n return chunks;\n}\n\nexport function mergeSmallChunks(chunks: Chunk[], minTokens: number): Chunk[] {\n const minChars = minTokens * 4;\n const result: Chunk[] = [];\n let i = 0;\n while (i < chunks.length) {\n if (chunks[i].text.length < minChars && i + 1 < chunks.length) {\n const merged = chunks[i].text + '\\n\\n' + chunks[i + 1].text;\n result.push(makeChunk(merged, result.length, chunks[i].startChar, chunks[i].heading ?? chunks[i + 1].heading));\n i += 2;\n } else {\n result.push({ ...chunks[i], index: result.length });\n i++;\n }\n }\n return result;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Content deduplication — detect near-duplicate pages during crawl.
3
+ *
4
+ * Uses MinHash-inspired approach: instead of full MinHash (needs many hash functions),
5
+ * we use 8 structural fingerprints extracted from the markdown to detect pages that
6
+ * share the same layout/template even if the data differs.
7
+ *
8
+ * Typical duplicates: /products?page=1 vs /products?page=2 (same template, different data).
9
+ * These waste crawl budget and generate redundant test cases.
10
+ */
11
+ export interface DedupResult {
12
+ isDuplicate: boolean;
13
+ similarUrl?: string;
14
+ similarityScore: number;
15
+ }
16
+ export declare class ContentDeduplicator {
17
+ private seen;
18
+ private readonly threshold;
19
+ constructor(threshold?: number);
20
+ check(url: string, markdown: string): DedupResult;
21
+ size(): number;
22
+ reset(): void;
23
+ }