@taodigital/crawler 0.0.0-stage → 1.2.0

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,81 @@
1
+ import { Agent as HttpsAgent } from "node:https";
2
+ import { connect as tlsConnect } from "node:tls";
3
+ const CHROME_CIPHERS = [
4
+ "TLS_AES_128_GCM_SHA256",
5
+ "TLS_AES_256_GCM_SHA384",
6
+ "TLS_CHACHA20_POLY1305_SHA256",
7
+ "ECDHE-ECDSA-AES128-GCM-SHA256",
8
+ "ECDHE-RSA-AES128-GCM-SHA256",
9
+ "ECDHE-ECDSA-AES256-GCM-SHA384",
10
+ "ECDHE-RSA-AES256-GCM-SHA384",
11
+ "ECDHE-ECDSA-CHACHA20-POLY1305",
12
+ "ECDHE-RSA-CHACHA20-POLY1305",
13
+ "ECDHE-RSA-AES128-SHA",
14
+ "ECDHE-RSA-AES256-SHA",
15
+ "AES128-GCM-SHA256",
16
+ "AES256-GCM-SHA384",
17
+ "AES128-SHA",
18
+ "AES256-SHA"
19
+ ].join(":");
20
+ const CHROME_CURVES = "X25519:P-256:P-384";
21
+ function createChromeTlsAgent(opts = {}) {
22
+ return new HttpsAgent({
23
+ ciphers: CHROME_CIPHERS,
24
+ ecdhCurve: CHROME_CURVES,
25
+ minVersion: "TLSv1.2",
26
+ maxVersion: "TLSv1.3",
27
+ rejectUnauthorized: opts.rejectUnauthorized ?? true,
28
+ keepAlive: opts.keepAlive ?? true,
29
+ keepAliveMsecs: opts.keepAliveMsecs ?? 1e4,
30
+ maxSockets: opts.maxSockets ?? 50
31
+ });
32
+ }
33
+ async function probeTlsFingerprint(host, port = 443) {
34
+ return new Promise((resolve, reject) => {
35
+ const socket = tlsConnect({
36
+ host,
37
+ port,
38
+ servername: host,
39
+ ciphers: CHROME_CIPHERS,
40
+ ecdhCurve: CHROME_CURVES,
41
+ minVersion: "TLSv1.2",
42
+ rejectUnauthorized: false
43
+ }, () => {
44
+ const cert = socket.getPeerCertificate();
45
+ resolve({
46
+ protocol: socket.getProtocol() ?? "unknown",
47
+ cipher: socket.getCipher()?.name ?? "unknown",
48
+ authorized: socket.authorized,
49
+ subjectCN: cert?.subject?.CN?.toString() ?? null
50
+ });
51
+ socket.destroy();
52
+ });
53
+ socket.on("error", reject);
54
+ setTimeout(() => {
55
+ socket.destroy();
56
+ reject(new Error("TLS probe timeout"));
57
+ }, 5e3);
58
+ });
59
+ }
60
+ const chromeTlsAgent = createChromeTlsAgent();
61
+ const CHROME_TLS_HEADERS = {
62
+ "Accept-Encoding": "gzip, deflate, br, zstd",
63
+ "Accept-Language": "en-US,en;q=0.9",
64
+ "sec-ch-ua": '"Google Chrome";v="120", "Chromium";v="120", "Not-A.Brand";v="99"',
65
+ "sec-ch-ua-mobile": "?0",
66
+ "sec-ch-ua-platform": '"Windows"',
67
+ "Sec-Fetch-Dest": "document",
68
+ "Sec-Fetch-Mode": "navigate",
69
+ "Sec-Fetch-Site": "none",
70
+ "Sec-Fetch-User": "?1",
71
+ "Upgrade-Insecure-Requests": "1"
72
+ };
73
+ export {
74
+ CHROME_CIPHERS,
75
+ CHROME_CURVES,
76
+ CHROME_TLS_HEADERS,
77
+ chromeTlsAgent,
78
+ createChromeTlsAgent,
79
+ probeTlsFingerprint
80
+ };
81
+ //# sourceMappingURL=tls-fingerprint.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tls-fingerprint.ts"],
4
+ "sourcesContent": ["/**\n * TLS fingerprint spoofing \u2014 Chrome JA3 cipher suite ordering.\n *\n * Node.js TLS doesn't expose JA3 hash control directly, but ordering\n * cipher suites to match Chrome's TLS ClientHello reduces fingerprint\n * detectability vs the default OpenSSL order.\n *\n * For full JA3 bypass, pair with the browser-stealth engine (Camoufox).\n */\n\nimport { Agent as HttpsAgent } from 'node:https';\nimport { connect as tlsConnect } from 'node:tls';\n\n// Chrome 120+ TLS 1.3 + 1.2 cipher suite order\nexport const CHROME_CIPHERS = [\n 'TLS_AES_128_GCM_SHA256',\n 'TLS_AES_256_GCM_SHA384',\n 'TLS_CHACHA20_POLY1305_SHA256',\n 'ECDHE-ECDSA-AES128-GCM-SHA256',\n 'ECDHE-RSA-AES128-GCM-SHA256',\n 'ECDHE-ECDSA-AES256-GCM-SHA384',\n 'ECDHE-RSA-AES256-GCM-SHA384',\n 'ECDHE-ECDSA-CHACHA20-POLY1305',\n 'ECDHE-RSA-CHACHA20-POLY1305',\n 'ECDHE-RSA-AES128-SHA',\n 'ECDHE-RSA-AES256-SHA',\n 'AES128-GCM-SHA256',\n 'AES256-GCM-SHA384',\n 'AES128-SHA',\n 'AES256-SHA',\n].join(':');\n\n// Chrome's ECDH curve preference order\nexport const CHROME_CURVES = 'X25519:P-256:P-384';\n\nexport interface TlsFingerprintOptions {\n rejectUnauthorized?: boolean;\n keepAlive?: boolean;\n keepAliveMsecs?: number;\n maxSockets?: number;\n}\n\n/**\n * Returns an HTTPS agent configured with Chrome-like TLS settings.\n * Drop-in replacement for the default agent in fetch/got/axios.\n */\nexport function createChromeTlsAgent(opts: TlsFingerprintOptions = {}): HttpsAgent {\n return new HttpsAgent({\n ciphers: CHROME_CIPHERS,\n ecdhCurve: CHROME_CURVES,\n minVersion: 'TLSv1.2',\n maxVersion: 'TLSv1.3',\n rejectUnauthorized: opts.rejectUnauthorized ?? true,\n keepAlive: opts.keepAlive ?? true,\n keepAliveMsecs: opts.keepAliveMsecs ?? 10_000,\n maxSockets: opts.maxSockets ?? 50,\n });\n}\n\n/**\n * Probe a host and return its JA3-relevant TLS parameters.\n * Useful for verifying that the fingerprint changed vs default.\n */\nexport async function probeTlsFingerprint(host: string, port = 443): Promise<{\n protocol: string;\n cipher: string;\n authorized: boolean;\n subjectCN: string | null;\n}> {\n return new Promise((resolve, reject) => {\n const socket = tlsConnect({\n host,\n port,\n servername: host,\n ciphers: CHROME_CIPHERS,\n ecdhCurve: CHROME_CURVES,\n minVersion: 'TLSv1.2',\n rejectUnauthorized: false,\n }, () => {\n const cert = socket.getPeerCertificate();\n resolve({\n protocol: socket.getProtocol() ?? 'unknown',\n cipher: socket.getCipher()?.name ?? 'unknown',\n authorized: socket.authorized,\n subjectCN: cert?.subject?.CN?.toString() ?? null,\n });\n socket.destroy();\n });\n socket.on('error', reject);\n setTimeout(() => { socket.destroy(); reject(new Error('TLS probe timeout')); }, 5_000);\n });\n}\n\n/**\n * Node.js fetch dispatcher options with Chrome TLS agent.\n * Pass as the `dispatcher` option to undici or as `agent` to node-fetch.\n */\nexport const chromeTlsAgent = createChromeTlsAgent();\n\nexport const CHROME_TLS_HEADERS = {\n 'Accept-Encoding': 'gzip, deflate, br, zstd',\n 'Accept-Language': 'en-US,en;q=0.9',\n 'sec-ch-ua': '\"Google Chrome\";v=\"120\", \"Chromium\";v=\"120\", \"Not-A.Brand\";v=\"99\"',\n 'sec-ch-ua-mobile': '?0',\n 'sec-ch-ua-platform': '\"Windows\"',\n 'Sec-Fetch-Dest': 'document',\n 'Sec-Fetch-Mode': 'navigate',\n 'Sec-Fetch-Site': 'none',\n 'Sec-Fetch-User': '?1',\n 'Upgrade-Insecure-Requests': '1',\n};\n"],
5
+ "mappings": "AAUA,SAAS,SAAS,kBAAkB;AACpC,SAAS,WAAW,kBAAkB;AAG/B,MAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAGH,MAAM,gBAAgB;AAatB,SAAS,qBAAqB,OAA8B,CAAC,GAAe;AACjF,SAAO,IAAI,WAAW;AAAA,IACpB,SAAS;AAAA,IACT,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,oBAAoB,KAAK,sBAAsB;AAAA,IAC/C,WAAW,KAAK,aAAa;AAAA,IAC7B,gBAAgB,KAAK,kBAAkB;AAAA,IACvC,YAAY,KAAK,cAAc;AAAA,EACjC,CAAC;AACH;AAMA,eAAsB,oBAAoB,MAAc,OAAO,KAK5D;AACD,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,WAAW;AAAA,MACxB;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,oBAAoB;AAAA,IACtB,GAAG,MAAM;AACP,YAAM,OAAO,OAAO,mBAAmB;AACvC,cAAQ;AAAA,QACN,UAAU,OAAO,YAAY,KAAK;AAAA,QAClC,QAAQ,OAAO,UAAU,GAAG,QAAQ;AAAA,QACpC,YAAY,OAAO;AAAA,QACnB,WAAW,MAAM,SAAS,IAAI,SAAS,KAAK;AAAA,MAC9C,CAAC;AACD,aAAO,QAAQ;AAAA,IACjB,CAAC;AACD,WAAO,GAAG,SAAS,MAAM;AACzB,eAAW,MAAM;AAAE,aAAO,QAAQ;AAAG,aAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,IAAG,GAAG,GAAK;AAAA,EACvF,CAAC;AACH;AAMO,MAAM,iBAAiB,qBAAqB;AAE5C,MAAM,qBAAqB;AAAA,EAChC,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,6BAA6B;AAC/B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * P1: URL Pattern LLM Completion
3
+ *
4
+ * After BFS crawl completes, runs a single LLM call to predict URLs that exist
5
+ * but were never linked from within the app. Validates each candidate with
6
+ * HTTP HEAD before emitting as crawl evidence (URL_CANDIDATES).
7
+ *
8
+ * Cost: ~1 LLM call + N HEAD requests. Takes ~5-30 seconds depending on
9
+ * candidate count. Does NOT launch a browser.
10
+ */
11
+ export interface UrlCandidate {
12
+ url: string;
13
+ confidence: number;
14
+ reason: string;
15
+ status: 'hit' | 'miss' | 'error';
16
+ }
17
+ interface LlmCfg {
18
+ provider: string;
19
+ model: string;
20
+ apiKey?: string;
21
+ baseUrl?: string;
22
+ }
23
+ /**
24
+ * Returns validated URL candidates (HEAD=200) predicted by LLM from
25
+ * the pattern of already-discovered URLs.
26
+ */
27
+ export declare function runUrlPatternCompletion(discoveredUrls: string[], appUrl: string, llmCfg: LlmCfg | null | undefined): Promise<UrlCandidate[]>;
28
+ export {};
@@ -0,0 +1,122 @@
1
+ import { logger as rootLogger } from "./logger.js";
2
+ const logger = rootLogger.child({ module: "url-pattern-completer" });
3
+ const SUPPORTED = /* @__PURE__ */ new Set(["openai", "anthropic"]);
4
+ function resolveTextLLM(llmCfg) {
5
+ if (llmCfg?.provider === "openai" && llmCfg.apiKey) {
6
+ return { provider: "openai", apiKey: llmCfg.apiKey, model: llmCfg.model, baseUrl: llmCfg.baseUrl };
7
+ }
8
+ if (llmCfg?.provider === "anthropic" && llmCfg.apiKey) {
9
+ return { provider: "anthropic", apiKey: llmCfg.apiKey, model: llmCfg.model };
10
+ }
11
+ if (process.env.OPENAI_API_KEY) {
12
+ return { provider: "openai", apiKey: process.env.OPENAI_API_KEY, model: "gpt-4o-mini" };
13
+ }
14
+ if (process.env.ANTHROPIC_API_KEY) {
15
+ return { provider: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY, model: "claude-haiku-4-5-20251001" };
16
+ }
17
+ return null;
18
+ }
19
+ async function runUrlPatternCompletion(discoveredUrls, appUrl, llmCfg) {
20
+ const effective = resolveTextLLM(llmCfg);
21
+ if (!effective || discoveredUrls.length < 2) return [];
22
+ const origin = (() => {
23
+ try {
24
+ return new URL(appUrl).origin;
25
+ } catch {
26
+ return "";
27
+ }
28
+ })();
29
+ if (!origin) return [];
30
+ const ASSET_EXT = /\.(png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf|eot|css|js|map|json|xml|txt|pdf)$/i;
31
+ const internalUrls = discoveredUrls.filter((u) => {
32
+ try {
33
+ return new URL(u).origin === origin && !ASSET_EXT.test(u);
34
+ } catch {
35
+ return false;
36
+ }
37
+ }).slice(0, 60);
38
+ if (internalUrls.length < 2) return [];
39
+ const systemPrompt = "You are a URL pattern analyst. Always respond with valid JSON only.";
40
+ const userPrompt = `You are analyzing a web application's URL structure to find screens that exist but weren't discovered during automated crawling.
41
+
42
+ Discovered URLs:
43
+ ${internalUrls.map((u) => {
44
+ try {
45
+ return new URL(u).pathname + new URL(u).search;
46
+ } catch {
47
+ return u;
48
+ }
49
+ }).join("\n")}
50
+
51
+ Based on these URL patterns, suggest up to 15 additional URLs that likely exist in this application but weren't found \u2014 typically settings subpages, admin panels, profile variants, paginated views, or feature-gated routes.
52
+
53
+ Rules:
54
+ - Only suggest URLs on the same domain
55
+ - Use relative paths (starting with /)
56
+ - Skip generic guesses (/login, /register, /home) unless the pattern strongly implies them
57
+ - Focus on patterns YOU SEE in the discovered URLs (e.g. if /settings/profile exists, try /settings/security, /settings/billing)
58
+ - Confidence 0.0\u20131.0: how certain you are the page exists
59
+
60
+ Respond with ONLY valid JSON, no explanation:
61
+ {"candidates":[{"path":"/settings/billing","confidence":0.85,"reason":"settings subpage pattern from /settings/profile"},{"path":"/admin/users","confidence":0.6,"reason":"admin prefix implied by /admin/dashboard"}]}`;
62
+ try {
63
+ let text = "";
64
+ if (effective.provider === "openai") {
65
+ const { OpenAI } = await import("openai");
66
+ const client = new OpenAI({ apiKey: effective.apiKey, ...effective.baseUrl ? { baseURL: effective.baseUrl } : {} });
67
+ const response = await client.chat.completions.create({
68
+ model: effective.model ?? "gpt-4o-mini",
69
+ max_tokens: 1024,
70
+ temperature: 0,
71
+ messages: [
72
+ { role: "system", content: systemPrompt },
73
+ { role: "user", content: userPrompt }
74
+ ]
75
+ });
76
+ text = response.choices[0]?.message?.content ?? "";
77
+ } else {
78
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
79
+ const client = new Anthropic({ apiKey: effective.apiKey });
80
+ const response = await client.messages.create({
81
+ model: effective.model ?? "claude-haiku-4-5-20251001",
82
+ max_tokens: 1024,
83
+ temperature: 0,
84
+ // @ts-ignore: cache_control supported in beta
85
+ system: [{ type: "text", text: systemPrompt, cache_control: { type: "ephemeral" } }],
86
+ messages: [{ role: "user", content: userPrompt }]
87
+ });
88
+ text = response.content[0]?.text ?? "";
89
+ }
90
+ const jsonMatch = text.match(/\{[\s\S]*\}/);
91
+ if (!jsonMatch) return [];
92
+ const parsed = JSON.parse(jsonMatch[0]);
93
+ const rawCandidates = parsed.candidates ?? [];
94
+ if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) return [];
95
+ const results = [];
96
+ for (const c of rawCandidates.slice(0, 20)) {
97
+ if (!c.path || typeof c.path !== "string") continue;
98
+ const fullUrl = c.path.startsWith("http") ? c.path : `${origin}${c.path.startsWith("/") ? "" : "/"}${c.path}`;
99
+ try {
100
+ const controller = new AbortController();
101
+ const timeout = setTimeout(() => controller.abort(), 6e3);
102
+ const res = await fetch(fullUrl, { method: "HEAD", signal: controller.signal, redirect: "follow" }).catch(() => null);
103
+ clearTimeout(timeout);
104
+ const status = res?.ok ? "hit" : "miss";
105
+ results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? "", status });
106
+ logger.info({ url: fullUrl, httpStatus: res?.status ?? "timeout", status }, "[url-completer] candidate checked");
107
+ } catch {
108
+ results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? "", status: "error" });
109
+ }
110
+ }
111
+ const hits = results.filter((r) => r.status === "hit");
112
+ logger.info({ total: results.length, hits: hits.length, provider: effective.provider }, "[url-completer] pattern completion done");
113
+ return results;
114
+ } catch (err) {
115
+ logger.warn({ err: String(err) }, "[url-completer] LLM call failed \u2014 skipping pattern completion");
116
+ return [];
117
+ }
118
+ }
119
+ export {
120
+ runUrlPatternCompletion
121
+ };
122
+ //# sourceMappingURL=url-pattern-completer.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/url-pattern-completer.ts"],
4
+ "sourcesContent": ["/**\n * P1: URL Pattern LLM Completion\n *\n * After BFS crawl completes, runs a single LLM call to predict URLs that exist\n * but were never linked from within the app. Validates each candidate with\n * HTTP HEAD before emitting as crawl evidence (URL_CANDIDATES).\n *\n * Cost: ~1 LLM call + N HEAD requests. Takes ~5-30 seconds depending on\n * candidate count. Does NOT launch a browser.\n */\n\nimport { logger as rootLogger } from './logger.js';\n\nconst logger = rootLogger.child({ module: 'url-pattern-completer' });\n\nexport interface UrlCandidate {\n url: string;\n confidence: number;\n reason: string;\n status: 'hit' | 'miss' | 'error';\n}\n\ninterface LlmCfg {\n provider: string;\n model: string;\n apiKey?: string;\n baseUrl?: string;\n}\n\n// Only these two providers are supported by this module (they use standard SDK calls).\n// NVIDIA, groq, ollama, openrouter, etc. are skipped \u2014 fall through to env fallback.\nconst SUPPORTED = new Set(['openai', 'anthropic']);\n\n/**\n * Resolve the effective LLM to use. Priority: openai \u2192 anthropic \u2192 skip others.\n * Falls back to env vars when the platform-configured provider is unsupported.\n */\nfunction resolveTextLLM(llmCfg: LlmCfg | null | undefined): { provider: 'openai' | 'anthropic'; apiKey: string; model: string; baseUrl?: string } | null {\n // Use platform config if it's a supported provider with a key\n if (llmCfg?.provider === 'openai' && llmCfg.apiKey) {\n return { provider: 'openai', apiKey: llmCfg.apiKey, model: llmCfg.model, baseUrl: llmCfg.baseUrl };\n }\n if (llmCfg?.provider === 'anthropic' && llmCfg.apiKey) {\n return { provider: 'anthropic', apiKey: llmCfg.apiKey, model: llmCfg.model };\n }\n // Platform provider is unsupported (nvidia, groq, etc.) or no key \u2014 fall back to env vars.\n // OpenAI takes priority over Anthropic.\n if (process.env.OPENAI_API_KEY) {\n return { provider: 'openai', apiKey: process.env.OPENAI_API_KEY, model: 'gpt-4o-mini' };\n }\n if (process.env.ANTHROPIC_API_KEY) {\n return { provider: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-haiku-4-5-20251001' };\n }\n return null;\n}\n\n/**\n * Returns validated URL candidates (HEAD=200) predicted by LLM from\n * the pattern of already-discovered URLs.\n */\nexport async function runUrlPatternCompletion(\n discoveredUrls: string[],\n appUrl: string,\n llmCfg: LlmCfg | null | undefined,\n): Promise<UrlCandidate[]> {\n const effective = resolveTextLLM(llmCfg);\n if (!effective || discoveredUrls.length < 2) return [];\n\n const origin = (() => { try { return new URL(appUrl).origin; } catch { return ''; } })();\n if (!origin) return [];\n\n // Only send internal URLs (same origin, no assets)\n const ASSET_EXT = /\\.(png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf|eot|css|js|map|json|xml|txt|pdf)$/i;\n const internalUrls = discoveredUrls\n .filter(u => { try { return new URL(u).origin === origin && !ASSET_EXT.test(u); } catch { return false; } })\n .slice(0, 60); // cap context size\n\n if (internalUrls.length < 2) return [];\n\n const systemPrompt = 'You are a URL pattern analyst. Always respond with valid JSON only.';\n const userPrompt = `You are analyzing a web application's URL structure to find screens that exist but weren't discovered during automated crawling.\n\nDiscovered URLs:\n${internalUrls.map(u => { try { return new URL(u).pathname + new URL(u).search; } catch { return u; } }).join('\\n')}\n\nBased on these URL patterns, suggest up to 15 additional URLs that likely exist in this application but weren't found \u2014 typically settings subpages, admin panels, profile variants, paginated views, or feature-gated routes.\n\nRules:\n- Only suggest URLs on the same domain\n- Use relative paths (starting with /)\n- Skip generic guesses (/login, /register, /home) unless the pattern strongly implies them\n- Focus on patterns YOU SEE in the discovered URLs (e.g. if /settings/profile exists, try /settings/security, /settings/billing)\n- Confidence 0.0\u20131.0: how certain you are the page exists\n\nRespond with ONLY valid JSON, no explanation:\n{\"candidates\":[{\"path\":\"/settings/billing\",\"confidence\":0.85,\"reason\":\"settings subpage pattern from /settings/profile\"},{\"path\":\"/admin/users\",\"confidence\":0.6,\"reason\":\"admin prefix implied by /admin/dashboard\"}]}`;\n\n try {\n let text = '';\n\n if (effective.provider === 'openai') {\n const { OpenAI } = await import('openai');\n const client = new OpenAI({ apiKey: effective.apiKey, ...(effective.baseUrl ? { baseURL: effective.baseUrl } : {}) });\n const response = await client.chat.completions.create({\n model: effective.model ?? 'gpt-4o-mini',\n max_tokens: 1024,\n temperature: 0,\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt },\n ],\n });\n text = response.choices[0]?.message?.content ?? '';\n } else {\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n const client = new Anthropic({ apiKey: effective.apiKey });\n const response = await client.messages.create({\n model: effective.model ?? 'claude-haiku-4-5-20251001',\n max_tokens: 1024,\n temperature: 0,\n // @ts-ignore: cache_control supported in beta\n system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],\n messages: [{ role: 'user', content: userPrompt }],\n });\n text = (response.content[0] as any)?.text ?? '';\n }\n\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (!jsonMatch) return [];\n const parsed = JSON.parse(jsonMatch[0]);\n const rawCandidates: Array<{ path: string; confidence: number; reason: string }> =\n parsed.candidates ?? [];\n\n if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) return [];\n\n // HEAD-validate each candidate\n const results: UrlCandidate[] = [];\n for (const c of rawCandidates.slice(0, 20)) {\n if (!c.path || typeof c.path !== 'string') continue;\n const fullUrl = c.path.startsWith('http') ? c.path : `${origin}${c.path.startsWith('/') ? '' : '/'}${c.path}`;\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 6_000);\n const res = await fetch(fullUrl, { method: 'HEAD', signal: controller.signal, redirect: 'follow' }).catch(() => null);\n clearTimeout(timeout);\n const status: UrlCandidate['status'] = res?.ok ? 'hit' : 'miss';\n results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? '', status });\n logger.info({ url: fullUrl, httpStatus: res?.status ?? 'timeout', status }, '[url-completer] candidate checked');\n } catch {\n results.push({ url: fullUrl, confidence: c.confidence ?? 0.5, reason: c.reason ?? '', status: 'error' });\n }\n }\n\n const hits = results.filter(r => r.status === 'hit');\n logger.info({ total: results.length, hits: hits.length, provider: effective.provider }, '[url-completer] pattern completion done');\n return results;\n } catch (err) {\n logger.warn({ err: String(err) }, '[url-completer] LLM call failed \u2014 skipping pattern completion');\n return [];\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pure visual-diff verdict logic, split out of crawler.ts so it can be unit-tested
3
+ * without pulling in crawler.ts's full import graph (Playwright, Stagehand, and
4
+ * transitively @taodigital/database, which instantiates a PrismaClient at module load and
5
+ * so requires a live DATABASE_URL just to import the module).
6
+ */
7
+ export interface VisualDiffVerdict {
8
+ changeType: 'NONE' | 'MINOR' | 'SIGNIFICANT' | 'CRITICAL';
9
+ recommendation: 'APPROVE' | 'REVIEW' | 'FLAG' | 'BLOCK';
10
+ changes: Array<{
11
+ location: string;
12
+ severity: string;
13
+ description: string;
14
+ }>;
15
+ bugReportDraft?: {
16
+ title: string;
17
+ severity: string;
18
+ steps: string[];
19
+ expected: string;
20
+ actual: string;
21
+ };
22
+ aiSummary?: string;
23
+ }
24
+ /** Cheap, always-on first-pass verdict from perceptual hash distance + layout/element diffs. */
25
+ export declare function pHashVerdict(pHashDistance: number, changeLevel: 'IDENTICAL' | 'MINOR' | 'MODERATE' | 'SIGNIFICANT', changes: Array<{
26
+ location: string;
27
+ severity: string;
28
+ description: string;
29
+ }>): VisualDiffVerdict;
30
+ /** Replaces a pHash-only verdict with the AI's semantic judgment once it's available,
31
+ * and drafts a bug report for anything still CRITICAL/SIGNIFICANT after AI review
32
+ * (pHash flags plenty of anti-aliasing/font-rendering noise the AI correctly discards
33
+ * as NONE/MINOR — only what survives AI review is worth a draft). */
34
+ export declare function mergeAiVisualVerdict(pHashResult: VisualDiffVerdict, aiResult: {
35
+ changeType: string;
36
+ changes: any[];
37
+ recommendation: string;
38
+ diffScore: number;
39
+ summary?: string;
40
+ }, screenName: string): VisualDiffVerdict;
@@ -0,0 +1,27 @@
1
+ function pHashVerdict(pHashDistance, changeLevel, changes) {
2
+ const changeType = changeLevel === "IDENTICAL" ? "NONE" : changeLevel === "MINOR" ? "MINOR" : changeLevel === "MODERATE" ? "SIGNIFICANT" : "CRITICAL";
3
+ const approveThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_APPROVE_THRESHOLD ?? "") || 5;
4
+ const reviewThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_REVIEW_THRESHOLD ?? "") || 20;
5
+ const recommendation = pHashDistance === 0 ? "APPROVE" : pHashDistance < approveThreshold ? "APPROVE" : pHashDistance < reviewThreshold ? "REVIEW" : "FLAG";
6
+ return { changeType, recommendation, changes };
7
+ }
8
+ function mergeAiVisualVerdict(pHashResult, aiResult, screenName) {
9
+ if (!aiResult.changeType) return pHashResult;
10
+ const changeType = aiResult.changeType;
11
+ const recommendation = aiResult.recommendation;
12
+ const changes = Array.isArray(aiResult.changes) && aiResult.changes.length > 0 ? aiResult.changes : pHashResult.changes;
13
+ const aiSummary = aiResult.summary ?? void 0;
14
+ const bugReportDraft = changeType === "CRITICAL" || changeType === "SIGNIFICANT" ? {
15
+ title: `Visual regression on "${screenName}"`,
16
+ severity: changeType === "CRITICAL" ? "High" : "Medium",
17
+ steps: ["Navigate to the screen", "Compare current view to baseline screenshot"],
18
+ expected: "Screen appearance should match the stored baseline.",
19
+ actual: aiSummary || `Screen has changed (diff score: ${aiResult.diffScore.toFixed(1)}%)`
20
+ } : void 0;
21
+ return { changeType, recommendation, changes, bugReportDraft, aiSummary };
22
+ }
23
+ export {
24
+ mergeAiVisualVerdict,
25
+ pHashVerdict
26
+ };
27
+ //# sourceMappingURL=visual-diff-verdict.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/visual-diff-verdict.ts"],
4
+ "sourcesContent": ["/**\n * Pure visual-diff verdict logic, split out of crawler.ts so it can be unit-tested\n * without pulling in crawler.ts's full import graph (Playwright, Stagehand, and\n * transitively @detiq/database, which instantiates a PrismaClient at module load and\n * so requires a live DATABASE_URL just to import the module).\n */\n\nexport interface VisualDiffVerdict {\n changeType: 'NONE' | 'MINOR' | 'SIGNIFICANT' | 'CRITICAL';\n recommendation: 'APPROVE' | 'REVIEW' | 'FLAG' | 'BLOCK';\n changes: Array<{ location: string; severity: string; description: string }>;\n bugReportDraft?: { title: string; severity: string; steps: string[]; expected: string; actual: string };\n aiSummary?: string;\n}\n\n/** Cheap, always-on first-pass verdict from perceptual hash distance + layout/element diffs. */\nexport function pHashVerdict(\n pHashDistance: number,\n changeLevel: 'IDENTICAL' | 'MINOR' | 'MODERATE' | 'SIGNIFICANT',\n changes: Array<{ location: string; severity: string; description: string }>,\n): VisualDiffVerdict {\n const changeType: VisualDiffVerdict['changeType'] = changeLevel === 'IDENTICAL' ? 'NONE'\n : changeLevel === 'MINOR' ? 'MINOR'\n : changeLevel === 'MODERATE' ? 'SIGNIFICANT'\n : 'CRITICAL';\n const approveThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_APPROVE_THRESHOLD ?? '') || 5;\n const reviewThreshold = parseInt(process.env.CRAWLER_VISUAL_DIFF_REVIEW_THRESHOLD ?? '') || 20;\n const recommendation: VisualDiffVerdict['recommendation'] = pHashDistance === 0 ? 'APPROVE'\n : pHashDistance < approveThreshold ? 'APPROVE'\n : pHashDistance < reviewThreshold ? 'REVIEW'\n : 'FLAG';\n return { changeType, recommendation, changes };\n}\n\n/** Replaces a pHash-only verdict with the AI's semantic judgment once it's available,\n * and drafts a bug report for anything still CRITICAL/SIGNIFICANT after AI review\n * (pHash flags plenty of anti-aliasing/font-rendering noise the AI correctly discards\n * as NONE/MINOR \u2014 only what survives AI review is worth a draft). */\nexport function mergeAiVisualVerdict(\n pHashResult: VisualDiffVerdict,\n aiResult: { changeType: string; changes: any[]; recommendation: string; diffScore: number; summary?: string },\n screenName: string,\n): VisualDiffVerdict {\n if (!aiResult.changeType) return pHashResult;\n const changeType = aiResult.changeType as VisualDiffVerdict['changeType'];\n const recommendation = aiResult.recommendation as VisualDiffVerdict['recommendation'];\n const changes = Array.isArray(aiResult.changes) && aiResult.changes.length > 0 ? aiResult.changes : pHashResult.changes;\n const aiSummary = aiResult.summary ?? undefined;\n const bugReportDraft = (changeType === 'CRITICAL' || changeType === 'SIGNIFICANT')\n ? {\n title: `Visual regression on \"${screenName}\"`,\n severity: changeType === 'CRITICAL' ? 'High' : 'Medium',\n steps: ['Navigate to the screen', 'Compare current view to baseline screenshot'],\n expected: 'Screen appearance should match the stored baseline.',\n actual: aiSummary || `Screen has changed (diff score: ${aiResult.diffScore.toFixed(1)}%)`,\n }\n : undefined;\n return { changeType, recommendation, changes, bugReportDraft, aiSummary };\n}\n"],
5
+ "mappings": "AAgBO,SAAS,aACd,eACA,aACA,SACmB;AACnB,QAAM,aAA8C,gBAAgB,cAAc,SAC9E,gBAAgB,UAAU,UACxB,gBAAgB,aAAa,gBAC3B;AACR,QAAM,mBAAmB,SAAS,QAAQ,IAAI,yCAAyC,EAAE,KAAK;AAC9F,QAAM,kBAAkB,SAAS,QAAQ,IAAI,wCAAwC,EAAE,KAAK;AAC5F,QAAM,iBAAsD,kBAAkB,IAAI,YAC9E,gBAAgB,mBAAmB,YACjC,gBAAgB,kBAAkB,WAChC;AACR,SAAO,EAAE,YAAY,gBAAgB,QAAQ;AAC/C;AAMO,SAAS,qBACd,aACA,UACA,YACmB;AACnB,MAAI,CAAC,SAAS,WAAY,QAAO;AACjC,QAAM,aAAa,SAAS;AAC5B,QAAM,iBAAiB,SAAS;AAChC,QAAM,UAAU,MAAM,QAAQ,SAAS,OAAO,KAAK,SAAS,QAAQ,SAAS,IAAI,SAAS,UAAU,YAAY;AAChH,QAAM,YAAY,SAAS,WAAW;AACtC,QAAM,iBAAkB,eAAe,cAAc,eAAe,gBAChE;AAAA,IACE,OAAO,yBAAyB,UAAU;AAAA,IAC1C,UAAU,eAAe,aAAa,SAAS;AAAA,IAC/C,OAAO,CAAC,0BAA0B,6CAA6C;AAAA,IAC/E,UAAU;AAAA,IACV,QAAQ,aAAa,mCAAmC,SAAS,UAAU,QAAQ,CAAC,CAAC;AAAA,EACvF,IACA;AACJ,SAAO,EAAE,YAAY,gBAAgB,SAAS,gBAAgB,UAAU;AAC1E;",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Webhook notifier — fire HTTP POST callbacks on crawl lifecycle events.
3
+ *
4
+ * Supports: crawl.started, crawl.progress, crawl.completed, crawl.failed
5
+ * Payload format compatible with Slack, Discord, and generic REST webhooks.
6
+ * Retries 3x on failure with exponential backoff.
7
+ */
8
+ export type CrawlEventType = 'crawl.started' | 'crawl.progress' | 'crawl.completed' | 'crawl.failed';
9
+ export interface WebhookPayload {
10
+ event: CrawlEventType;
11
+ jobId: string;
12
+ projectId: string;
13
+ tenantId: string;
14
+ timestamp: string;
15
+ data: Record<string, unknown>;
16
+ }
17
+ export interface WebhookNotifierOpts {
18
+ url: string;
19
+ secret?: string;
20
+ headers?: Record<string, string>;
21
+ timeoutMs?: number;
22
+ maxRetries?: number;
23
+ }
24
+ export declare class WebhookNotifier {
25
+ private readonly opts;
26
+ constructor(opts: WebhookNotifierOpts);
27
+ notify(event: CrawlEventType, jobId: string, projectId: string, tenantId: string, data?: Record<string, unknown>): Promise<void>;
28
+ /** Slack-compatible message format */
29
+ static slackPayload(event: CrawlEventType, data: Record<string, unknown>): Record<string, unknown>;
30
+ }
@@ -0,0 +1,76 @@
1
+ async function hmacSign(secret, payload) {
2
+ const key = await crypto.subtle.importKey(
3
+ "raw",
4
+ new TextEncoder().encode(secret),
5
+ { name: "HMAC", hash: "SHA-256" },
6
+ false,
7
+ ["sign"]
8
+ );
9
+ const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
10
+ return Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
11
+ }
12
+ class WebhookNotifier {
13
+ opts;
14
+ constructor(opts) {
15
+ this.opts = {
16
+ url: opts.url,
17
+ secret: opts.secret,
18
+ headers: opts.headers ?? {},
19
+ timeoutMs: opts.timeoutMs ?? 1e4,
20
+ maxRetries: opts.maxRetries ?? 3
21
+ };
22
+ }
23
+ async notify(event, jobId, projectId, tenantId, data = {}) {
24
+ const payload = {
25
+ event,
26
+ jobId,
27
+ projectId,
28
+ tenantId,
29
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
30
+ data
31
+ };
32
+ const body = JSON.stringify(payload);
33
+ const headers = {
34
+ "Content-Type": "application/json",
35
+ "User-Agent": "ZeTa-Crawler/1.0",
36
+ ...this.opts.headers
37
+ };
38
+ if (this.opts.secret) {
39
+ headers["X-ZeTa-Signature"] = `sha256=${await hmacSign(this.opts.secret, body)}`;
40
+ }
41
+ let lastErr;
42
+ for (let attempt = 0; attempt <= this.opts.maxRetries; attempt++) {
43
+ try {
44
+ const res = await fetch(this.opts.url, {
45
+ method: "POST",
46
+ headers,
47
+ body,
48
+ signal: AbortSignal.timeout(this.opts.timeoutMs)
49
+ });
50
+ if (res.ok) return;
51
+ lastErr = new Error(`Webhook HTTP ${res.status}`);
52
+ } catch (e) {
53
+ lastErr = e;
54
+ }
55
+ if (attempt < this.opts.maxRetries) {
56
+ await new Promise((r) => setTimeout(r, 1e3 * Math.pow(2, attempt)));
57
+ }
58
+ }
59
+ console.error(`[webhook] Failed to deliver ${event} after ${this.opts.maxRetries + 1} attempts:`, lastErr?.message);
60
+ }
61
+ /** Slack-compatible message format */
62
+ static slackPayload(event, data) {
63
+ const emoji = event === "crawl.completed" ? "\u2705" : event === "crawl.failed" ? "\u274C" : "\u{1F504}";
64
+ return {
65
+ text: `${emoji} *ZeTa Crawl Event: ${event}*`,
66
+ blocks: [
67
+ { type: "section", text: { type: "mrkdwn", text: `${emoji} *${event}*
68
+ ${Object.entries(data).map(([k, v]) => `\u2022 *${k}*: ${v}`).join("\n")}` } }
69
+ ]
70
+ };
71
+ }
72
+ }
73
+ export {
74
+ WebhookNotifier
75
+ };
76
+ //# sourceMappingURL=webhook-notifier.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/webhook-notifier.ts"],
4
+ "sourcesContent": ["/**\n * Webhook notifier \u2014 fire HTTP POST callbacks on crawl lifecycle events.\n *\n * Supports: crawl.started, crawl.progress, crawl.completed, crawl.failed\n * Payload format compatible with Slack, Discord, and generic REST webhooks.\n * Retries 3x on failure with exponential backoff.\n */\n\nexport type CrawlEventType = 'crawl.started' | 'crawl.progress' | 'crawl.completed' | 'crawl.failed';\n\nexport interface WebhookPayload {\n event: CrawlEventType;\n jobId: string;\n projectId: string;\n tenantId: string;\n timestamp: string;\n data: Record<string, unknown>;\n}\n\nexport interface WebhookNotifierOpts {\n url: string;\n secret?: string; // HMAC-SHA256 signing secret \u2014 adds X-ZeTa-Signature header\n headers?: Record<string, string>;\n timeoutMs?: number; // default 10s\n maxRetries?: number; // default 3\n}\n\nasync function hmacSign(secret: string, payload: string): Promise<string> {\n const key = await crypto.subtle.importKey(\n 'raw',\n new TextEncoder().encode(secret),\n { name: 'HMAC', hash: 'SHA-256' },\n false,\n ['sign'],\n );\n const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));\n return Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join('');\n}\n\nexport class WebhookNotifier {\n private readonly opts: Required<Omit<WebhookNotifierOpts, 'secret'>> & { secret?: string };\n\n constructor(opts: WebhookNotifierOpts) {\n this.opts = {\n url: opts.url,\n secret: opts.secret,\n headers: opts.headers ?? {},\n timeoutMs: opts.timeoutMs ?? 10_000,\n maxRetries: opts.maxRetries ?? 3,\n };\n }\n\n async notify(\n event: CrawlEventType,\n jobId: string,\n projectId: string,\n tenantId: string,\n data: Record<string, unknown> = {},\n ): Promise<void> {\n const payload: WebhookPayload = {\n event,\n jobId,\n projectId,\n tenantId,\n timestamp: new Date().toISOString(),\n data,\n };\n const body = JSON.stringify(payload);\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'User-Agent': 'ZeTa-Crawler/1.0',\n ...this.opts.headers,\n };\n if (this.opts.secret) {\n headers['X-ZeTa-Signature'] = `sha256=${await hmacSign(this.opts.secret, body)}`;\n }\n\n let lastErr: any;\n for (let attempt = 0; attempt <= this.opts.maxRetries; attempt++) {\n try {\n const res = await fetch(this.opts.url, {\n method: 'POST',\n headers,\n body,\n signal: AbortSignal.timeout(this.opts.timeoutMs),\n });\n if (res.ok) return;\n lastErr = new Error(`Webhook HTTP ${res.status}`);\n } catch (e) {\n lastErr = e;\n }\n if (attempt < this.opts.maxRetries) {\n await new Promise(r => setTimeout(r, 1_000 * Math.pow(2, attempt)));\n }\n }\n console.error(`[webhook] Failed to deliver ${event} after ${this.opts.maxRetries + 1} attempts:`, lastErr?.message);\n }\n\n /** Slack-compatible message format */\n static slackPayload(event: CrawlEventType, data: Record<string, unknown>): Record<string, unknown> {\n const emoji = event === 'crawl.completed' ? '\u2705' : event === 'crawl.failed' ? '\u274C' : '\uD83D\uDD04';\n return {\n text: `${emoji} *ZeTa Crawl Event: ${event}*`,\n blocks: [\n { type: 'section', text: { type: 'mrkdwn', text: `${emoji} *${event}*\\n${Object.entries(data).map(([k, v]) => `\u2022 *${k}*: ${v}`).join('\\n')}` } },\n ],\n };\n }\n}\n"],
5
+ "mappings": "AA2BA,eAAe,SAAS,QAAgB,SAAkC;AACxE,QAAM,MAAM,MAAM,OAAO,OAAO;AAAA,IAC9B;AAAA,IACA,IAAI,YAAY,EAAE,OAAO,MAAM;AAAA,IAC/B,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,IAChC;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AACA,QAAM,MAAM,MAAM,OAAO,OAAO,KAAK,QAAQ,KAAK,IAAI,YAAY,EAAE,OAAO,OAAO,CAAC;AACnF,SAAO,MAAM,KAAK,IAAI,WAAW,GAAG,CAAC,EAAE,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC1F;AAEO,MAAM,gBAAgB;AAAA,EACV;AAAA,EAEjB,YAAY,MAA2B;AACrC,SAAK,OAAO;AAAA,MACV,KAAK,KAAK;AAAA,MACV,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,WAAW,CAAC;AAAA,MAC1B,WAAW,KAAK,aAAa;AAAA,MAC7B,YAAY,KAAK,cAAc;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,OACJ,OACA,OACA,WACA,UACA,OAAgC,CAAC,GAClB;AACf,UAAM,UAA0B;AAAA,MAC9B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC;AAAA,IACF;AACA,UAAM,OAAO,KAAK,UAAU,OAAO;AACnC,UAAM,UAAkC;AAAA,MACtC,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,GAAG,KAAK,KAAK;AAAA,IACf;AACA,QAAI,KAAK,KAAK,QAAQ;AACpB,cAAQ,kBAAkB,IAAI,UAAU,MAAM,SAAS,KAAK,KAAK,QAAQ,IAAI,CAAC;AAAA,IAChF;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,WAAW,KAAK,KAAK,YAAY,WAAW;AAChE,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,KAAK,KAAK,KAAK;AAAA,UACrC,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA,QAAQ,YAAY,QAAQ,KAAK,KAAK,SAAS;AAAA,QACjD,CAAC;AACD,YAAI,IAAI,GAAI;AACZ,kBAAU,IAAI,MAAM,gBAAgB,IAAI,MAAM,EAAE;AAAA,MAClD,SAAS,GAAG;AACV,kBAAU;AAAA,MACZ;AACA,UAAI,UAAU,KAAK,KAAK,YAAY;AAClC,cAAM,IAAI,QAAQ,OAAK,WAAW,GAAG,MAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACpE;AAAA,IACF;AACA,YAAQ,MAAM,+BAA+B,KAAK,UAAU,KAAK,KAAK,aAAa,CAAC,cAAc,SAAS,OAAO;AAAA,EACpH;AAAA;AAAA,EAGA,OAAO,aAAa,OAAuB,MAAwD;AACjG,UAAM,QAAQ,UAAU,oBAAoB,WAAM,UAAU,iBAAiB,WAAM;AACnF,WAAO;AAAA,MACL,MAAM,GAAG,KAAK,uBAAuB,KAAK;AAAA,MAC1C,QAAQ;AAAA,QACN,EAAE,MAAM,WAAW,MAAM,EAAE,MAAM,UAAU,MAAM,GAAG,KAAK,KAAK,KAAK;AAAA,EAAM,OAAO,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,WAAM,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE;AAAA,MACjJ;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * WebSocket detection — intercepts WS connections opened during page crawl.
3
+ * Records connection URLs, message schemas, and message counts.
4
+ * This lets ZeTa discover real-time API contracts (chat, notifications, presence)
5
+ * that would be invisible to HTTP-only crawlers.
6
+ */
7
+ export interface WsConnection {
8
+ url: string;
9
+ framesSent: number;
10
+ framesReceived: number;
11
+ /** Sampled message bodies (first 10 per connection, truncated to 500 chars) */
12
+ samples: Array<{
13
+ direction: 'sent' | 'received';
14
+ payload: string;
15
+ ts: string;
16
+ }>;
17
+ /** Inferred protocol: 'json-rpc' | 'socket.io' | 'graphql-ws' | 'raw' */
18
+ protocol: string;
19
+ openedAt: string;
20
+ }
21
+ export declare function attachWebSocketDetector(cdpSession: any): {
22
+ getConnections(): WsConnection[];
23
+ detach(): void;
24
+ };
@@ -0,0 +1,60 @@
1
+ function inferProtocol(sample) {
2
+ try {
3
+ const obj = JSON.parse(sample);
4
+ if (obj?.jsonrpc) return "json-rpc";
5
+ if (obj?.type === "connection_init" || obj?.type === "subscribe") return "graphql-ws";
6
+ if (typeof obj?.sid === "string" || sample.startsWith("0{")) return "socket.io";
7
+ return "json";
8
+ } catch {
9
+ return "raw";
10
+ }
11
+ }
12
+ function attachWebSocketDetector(cdpSession) {
13
+ const connections = /* @__PURE__ */ new Map();
14
+ const onCreated = (evt) => {
15
+ const { requestId, url, timestamp } = evt ?? {};
16
+ if (!requestId || !url) return;
17
+ connections.set(requestId, {
18
+ url,
19
+ framesSent: 0,
20
+ framesReceived: 0,
21
+ samples: [],
22
+ protocol: "unknown",
23
+ openedAt: new Date(timestamp * 1e3).toISOString()
24
+ });
25
+ };
26
+ const onFrame = (evt, direction) => {
27
+ const { requestId, response } = evt ?? {};
28
+ if (!requestId) return;
29
+ const conn = connections.get(requestId);
30
+ if (!conn) return;
31
+ if (direction === "sent") conn.framesSent++;
32
+ else conn.framesReceived++;
33
+ if (conn.samples.length < 10 && response?.payloadData) {
34
+ const payload = String(response.payloadData).slice(0, 500);
35
+ conn.samples.push({ direction, payload, ts: (/* @__PURE__ */ new Date()).toISOString() });
36
+ if (conn.protocol === "unknown") conn.protocol = inferProtocol(payload);
37
+ }
38
+ };
39
+ const onSent = (evt) => onFrame(evt, "sent");
40
+ const onReceived = (evt) => onFrame(evt, "received");
41
+ if (cdpSession?.on) {
42
+ cdpSession.on("Network.webSocketCreated", onCreated);
43
+ cdpSession.on("Network.webSocketFrameSent", onSent);
44
+ cdpSession.on("Network.webSocketFrameReceived", onReceived);
45
+ }
46
+ return {
47
+ getConnections: () => [...connections.values()],
48
+ detach() {
49
+ if (cdpSession?.off) {
50
+ cdpSession.off("Network.webSocketCreated", onCreated);
51
+ cdpSession.off("Network.webSocketFrameSent", onSent);
52
+ cdpSession.off("Network.webSocketFrameReceived", onReceived);
53
+ }
54
+ }
55
+ };
56
+ }
57
+ export {
58
+ attachWebSocketDetector
59
+ };
60
+ //# sourceMappingURL=websocket-detector.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/websocket-detector.ts"],
4
+ "sourcesContent": ["/**\n * WebSocket detection \u2014 intercepts WS connections opened during page crawl.\n * Records connection URLs, message schemas, and message counts.\n * This lets ZeTa discover real-time API contracts (chat, notifications, presence)\n * that would be invisible to HTTP-only crawlers.\n */\n\nexport interface WsConnection {\n url: string;\n framesSent: number;\n framesReceived: number;\n /** Sampled message bodies (first 10 per connection, truncated to 500 chars) */\n samples: Array<{ direction: 'sent' | 'received'; payload: string; ts: string }>;\n /** Inferred protocol: 'json-rpc' | 'socket.io' | 'graphql-ws' | 'raw' */\n protocol: string;\n openedAt: string;\n}\n\nfunction inferProtocol(sample: string): string {\n try {\n const obj = JSON.parse(sample);\n if (obj?.jsonrpc) return 'json-rpc';\n if (obj?.type === 'connection_init' || obj?.type === 'subscribe') return 'graphql-ws';\n if (typeof obj?.sid === 'string' || sample.startsWith('0{')) return 'socket.io';\n return 'json';\n } catch {\n return 'raw';\n }\n}\n\nexport function attachWebSocketDetector(cdpSession: any): {\n getConnections(): WsConnection[];\n detach(): void;\n} {\n const connections = new Map<string, WsConnection>();\n\n const onCreated = (evt: any) => {\n const { requestId, url, timestamp } = evt ?? {};\n if (!requestId || !url) return;\n connections.set(requestId, {\n url,\n framesSent: 0,\n framesReceived: 0,\n samples: [],\n protocol: 'unknown',\n openedAt: new Date(timestamp * 1000).toISOString(),\n });\n };\n\n const onFrame = (evt: any, direction: 'sent' | 'received') => {\n const { requestId, response } = evt ?? {};\n if (!requestId) return;\n const conn = connections.get(requestId);\n if (!conn) return;\n if (direction === 'sent') conn.framesSent++;\n else conn.framesReceived++;\n if (conn.samples.length < 10 && response?.payloadData) {\n const payload = String(response.payloadData).slice(0, 500);\n conn.samples.push({ direction, payload, ts: new Date().toISOString() });\n if (conn.protocol === 'unknown') conn.protocol = inferProtocol(payload);\n }\n };\n\n const onSent = (evt: any) => onFrame(evt, 'sent');\n const onReceived = (evt: any) => onFrame(evt, 'received');\n\n if (cdpSession?.on) {\n cdpSession.on('Network.webSocketCreated', onCreated);\n cdpSession.on('Network.webSocketFrameSent', onSent);\n cdpSession.on('Network.webSocketFrameReceived', onReceived);\n }\n\n return {\n getConnections: () => [...connections.values()],\n detach() {\n if (cdpSession?.off) {\n cdpSession.off('Network.webSocketCreated', onCreated);\n cdpSession.off('Network.webSocketFrameSent', onSent);\n cdpSession.off('Network.webSocketFrameReceived', onReceived);\n }\n },\n };\n}\n"],
5
+ "mappings": "AAkBA,SAAS,cAAc,QAAwB;AAC7C,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,MAAM;AAC7B,QAAI,KAAK,QAAS,QAAO;AACzB,QAAI,KAAK,SAAS,qBAAqB,KAAK,SAAS,YAAa,QAAO;AACzE,QAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,WAAW,IAAI,EAAG,QAAO;AACpE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,wBAAwB,YAGtC;AACA,QAAM,cAAc,oBAAI,IAA0B;AAElD,QAAM,YAAY,CAAC,QAAa;AAC9B,UAAM,EAAE,WAAW,KAAK,UAAU,IAAI,OAAO,CAAC;AAC9C,QAAI,CAAC,aAAa,CAAC,IAAK;AACxB,gBAAY,IAAI,WAAW;AAAA,MACzB;AAAA,MACA,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,SAAS,CAAC;AAAA,MACV,UAAU;AAAA,MACV,UAAU,IAAI,KAAK,YAAY,GAAI,EAAE,YAAY;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,CAAC,KAAU,cAAmC;AAC5D,UAAM,EAAE,WAAW,SAAS,IAAI,OAAO,CAAC;AACxC,QAAI,CAAC,UAAW;AAChB,UAAM,OAAO,YAAY,IAAI,SAAS;AACtC,QAAI,CAAC,KAAM;AACX,QAAI,cAAc,OAAQ,MAAK;AAAA,QAC1B,MAAK;AACV,QAAI,KAAK,QAAQ,SAAS,MAAM,UAAU,aAAa;AACrD,YAAM,UAAU,OAAO,SAAS,WAAW,EAAE,MAAM,GAAG,GAAG;AACzD,WAAK,QAAQ,KAAK,EAAE,WAAW,SAAS,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AACtE,UAAI,KAAK,aAAa,UAAW,MAAK,WAAW,cAAc,OAAO;AAAA,IACxE;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,QAAa,QAAQ,KAAK,MAAM;AAChD,QAAM,aAAa,CAAC,QAAa,QAAQ,KAAK,UAAU;AAExD,MAAI,YAAY,IAAI;AAClB,eAAW,GAAG,4BAA4B,SAAS;AACnD,eAAW,GAAG,8BAA8B,MAAM;AAClD,eAAW,GAAG,kCAAkC,UAAU;AAAA,EAC5D;AAEA,SAAO;AAAA,IACL,gBAAgB,MAAM,CAAC,GAAG,YAAY,OAAO,CAAC;AAAA,IAC9C,SAAS;AACP,UAAI,YAAY,KAAK;AACnB,mBAAW,IAAI,4BAA4B,SAAS;AACpD,mBAAW,IAAI,8BAA8B,MAAM;AACnD,mBAAW,IAAI,kCAAkC,UAAU;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/worker.js ADDED
@@ -0,0 +1,33 @@
1
+ import { startJobWorkerLoop, processDataRetentionPurge } from "./job-queue.js";
2
+ import { logger } from "./logger.js";
3
+ async function main() {
4
+ logger.info("[worker] starting standalone crawler worker process");
5
+ if (process.env.JOB_QUEUE_BACKEND === "sqs") {
6
+ const { startSqsWorker } = await import("./sqs-queue.js");
7
+ startSqsWorker().catch((err) => {
8
+ logger.error({ err: String(err) }, "[worker] SQS worker crashed");
9
+ process.exit(1);
10
+ });
11
+ }
12
+ startJobWorkerLoop();
13
+ const runPurge = () => {
14
+ processDataRetentionPurge().catch((err) => {
15
+ logger.error({ err: String(err) }, "[worker] data-retention purge failed");
16
+ });
17
+ };
18
+ runPurge();
19
+ setInterval(runPurge, 24 * 60 * 60 * 1e3);
20
+ }
21
+ process.on("SIGTERM", () => {
22
+ logger.info("[worker] SIGTERM received, exiting");
23
+ process.exit(0);
24
+ });
25
+ process.on("SIGINT", () => {
26
+ logger.info("[worker] SIGINT received, exiting");
27
+ process.exit(0);
28
+ });
29
+ main().catch((err) => {
30
+ logger.error({ err: String(err) }, "[worker] fatal startup error");
31
+ process.exit(1);
32
+ });
33
+ //# sourceMappingURL=worker.js.map