@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,140 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ class ResponseCache {
5
+ dir;
6
+ totalHits = 0;
7
+ totalRequests = 0;
8
+ constructor(cacheDir) {
9
+ this.dir = cacheDir;
10
+ if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });
11
+ }
12
+ key(url) {
13
+ return createHash("sha256").update(url).digest("hex").slice(0, 16);
14
+ }
15
+ path(url) {
16
+ return join(this.dir, `${this.key(url)}.json`);
17
+ }
18
+ get(url) {
19
+ this.totalRequests++;
20
+ try {
21
+ const p = this.path(url);
22
+ if (!existsSync(p)) return null;
23
+ const entry = JSON.parse(readFileSync(p, "utf-8"));
24
+ this.totalHits++;
25
+ return entry;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ read(url) {
31
+ try {
32
+ const p = this.path(url);
33
+ if (!existsSync(p)) return null;
34
+ return JSON.parse(readFileSync(p, "utf-8"));
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+ isFresh(url) {
40
+ const entry = this.read(url);
41
+ if (!entry?.cacheControl) return false;
42
+ const m = /max-age=(\d+)/i.exec(entry.cacheControl);
43
+ if (!m) return false;
44
+ const maxAgeMs = parseInt(m[1], 10) * 1e3;
45
+ return entry.cachedAt + maxAgeMs > Date.now();
46
+ }
47
+ staleIfError(url) {
48
+ const entry = this.read(url);
49
+ if (!entry?.cacheControl) return false;
50
+ const m = /stale-if-error=(\d+)/i.exec(entry.cacheControl);
51
+ if (!m) return false;
52
+ const staleIfErrorMs = parseInt(m[1], 10) * 1e3;
53
+ return entry.cachedAt + staleIfErrorMs >= Date.now();
54
+ }
55
+ set(url, entry) {
56
+ try {
57
+ const full = { ...entry, cachedAt: Date.now(), hitCount: 0 };
58
+ writeFileSync(this.path(url), JSON.stringify(full), "utf-8");
59
+ } catch {
60
+ }
61
+ }
62
+ getValidationHeaders(url) {
63
+ const entry = this.get(url);
64
+ if (!entry) return {};
65
+ const headers = {};
66
+ if (entry.etag) headers["If-None-Match"] = entry.etag;
67
+ if (entry.lastModified) headers["If-Modified-Since"] = entry.lastModified;
68
+ return headers;
69
+ }
70
+ markHit(url) {
71
+ try {
72
+ const p = this.path(url);
73
+ if (!existsSync(p)) return;
74
+ const entry = JSON.parse(readFileSync(p, "utf-8"));
75
+ entry.hitCount++;
76
+ writeFileSync(p, JSON.stringify(entry), "utf-8");
77
+ } catch {
78
+ }
79
+ }
80
+ invalidate(url) {
81
+ try {
82
+ const p = this.path(url);
83
+ if (existsSync(p)) unlinkSync(p);
84
+ } catch {
85
+ }
86
+ }
87
+ clear() {
88
+ try {
89
+ for (const f of readdirSync(this.dir)) {
90
+ if (f.endsWith(".json")) unlinkSync(join(this.dir, f));
91
+ }
92
+ } catch {
93
+ }
94
+ }
95
+ stats() {
96
+ let entries = 0;
97
+ let totalSizeBytes = 0;
98
+ try {
99
+ for (const f of readdirSync(this.dir)) {
100
+ if (!f.endsWith(".json")) continue;
101
+ entries++;
102
+ try {
103
+ totalSizeBytes += statSync(join(this.dir, f)).size;
104
+ } catch {
105
+ }
106
+ }
107
+ } catch {
108
+ }
109
+ return {
110
+ entries,
111
+ totalSizeBytes,
112
+ hitRate: this.totalRequests > 0 ? this.totalHits / this.totalRequests : 0,
113
+ totalHits: this.totalHits
114
+ };
115
+ }
116
+ prune(maxAgeMs) {
117
+ let deleted = 0;
118
+ const cutoff = Date.now() - maxAgeMs;
119
+ try {
120
+ for (const f of readdirSync(this.dir)) {
121
+ if (!f.endsWith(".json")) continue;
122
+ try {
123
+ const p = join(this.dir, f);
124
+ const entry = JSON.parse(readFileSync(p, "utf-8"));
125
+ if (entry.cachedAt < cutoff) {
126
+ unlinkSync(p);
127
+ deleted++;
128
+ }
129
+ } catch {
130
+ }
131
+ }
132
+ } catch {
133
+ }
134
+ return deleted;
135
+ }
136
+ }
137
+ export {
138
+ ResponseCache
139
+ };
140
+ //# sourceMappingURL=response-cache.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/response-cache.ts"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto';\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface ResponseCacheEntry {\n url: string;\n etag?: string;\n lastModified?: string;\n cacheControl?: string;\n body: string;\n contentType: string;\n statusCode: number;\n cachedAt: number;\n hitCount: number;\n}\n\nexport class ResponseCache {\n private readonly dir: string;\n private totalHits = 0;\n private totalRequests = 0;\n\n constructor(cacheDir: string) {\n this.dir = cacheDir;\n if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });\n }\n\n private key(url: string): string {\n return createHash('sha256').update(url).digest('hex').slice(0, 16);\n }\n\n private path(url: string): string {\n return join(this.dir, `${this.key(url)}.json`);\n }\n\n get(url: string): ResponseCacheEntry | null {\n this.totalRequests++;\n try {\n const p = this.path(url);\n if (!existsSync(p)) return null;\n const entry: ResponseCacheEntry = JSON.parse(readFileSync(p, 'utf-8'));\n this.totalHits++;\n return entry;\n } catch {\n return null;\n }\n }\n\n private read(url: string): ResponseCacheEntry | null {\n try {\n const p = this.path(url);\n if (!existsSync(p)) return null;\n return JSON.parse(readFileSync(p, 'utf-8')) as ResponseCacheEntry;\n } catch {\n return null;\n }\n }\n\n isFresh(url: string): boolean {\n const entry = this.read(url);\n if (!entry?.cacheControl) return false;\n const m = /max-age=(\\d+)/i.exec(entry.cacheControl);\n if (!m) return false;\n const maxAgeMs = parseInt(m[1], 10) * 1000;\n return entry.cachedAt + maxAgeMs > Date.now();\n }\n\n staleIfError(url: string): boolean {\n const entry = this.read(url);\n if (!entry?.cacheControl) return false;\n const m = /stale-if-error=(\\d+)/i.exec(entry.cacheControl);\n if (!m) return false;\n const staleIfErrorMs = parseInt(m[1], 10) * 1000;\n return entry.cachedAt + staleIfErrorMs >= Date.now();\n }\n\n set(url: string, entry: Omit<ResponseCacheEntry, 'cachedAt' | 'hitCount'>): void {\n try {\n const full: ResponseCacheEntry = { ...entry, cachedAt: Date.now(), hitCount: 0 };\n writeFileSync(this.path(url), JSON.stringify(full), 'utf-8');\n } catch { /* non-fatal */ }\n }\n\n getValidationHeaders(url: string): { 'If-None-Match'?: string; 'If-Modified-Since'?: string } {\n const entry = this.get(url);\n if (!entry) return {};\n const headers: { 'If-None-Match'?: string; 'If-Modified-Since'?: string } = {};\n if (entry.etag) headers['If-None-Match'] = entry.etag;\n if (entry.lastModified) headers['If-Modified-Since'] = entry.lastModified;\n return headers;\n }\n\n markHit(url: string): void {\n try {\n const p = this.path(url);\n if (!existsSync(p)) return;\n const entry: ResponseCacheEntry = JSON.parse(readFileSync(p, 'utf-8'));\n entry.hitCount++;\n writeFileSync(p, JSON.stringify(entry), 'utf-8');\n } catch { /* non-fatal */ }\n }\n\n invalidate(url: string): void {\n try {\n const p = this.path(url);\n if (existsSync(p)) unlinkSync(p);\n } catch { /* non-fatal */ }\n }\n\n clear(): void {\n try {\n for (const f of readdirSync(this.dir)) {\n if (f.endsWith('.json')) unlinkSync(join(this.dir, f));\n }\n } catch { /* non-fatal */ }\n }\n\n stats(): { entries: number; totalSizeBytes: number; hitRate: number; totalHits: number } {\n let entries = 0;\n let totalSizeBytes = 0;\n try {\n for (const f of readdirSync(this.dir)) {\n if (!f.endsWith('.json')) continue;\n entries++;\n try { totalSizeBytes += statSync(join(this.dir, f)).size; } catch { /* skip */ }\n }\n } catch { /* non-fatal */ }\n return {\n entries,\n totalSizeBytes,\n hitRate: this.totalRequests > 0 ? this.totalHits / this.totalRequests : 0,\n totalHits: this.totalHits,\n };\n }\n\n prune(maxAgeMs: number): number {\n let deleted = 0;\n const cutoff = Date.now() - maxAgeMs;\n try {\n for (const f of readdirSync(this.dir)) {\n if (!f.endsWith('.json')) continue;\n try {\n const p = join(this.dir, f);\n const entry: ResponseCacheEntry = JSON.parse(readFileSync(p, 'utf-8'));\n if (entry.cachedAt < cutoff) { unlinkSync(p); deleted++; }\n } catch { /* skip */ }\n }\n } catch { /* non-fatal */ }\n return deleted;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Responsive design detector — captures page at multiple breakpoints
3
+ * and detects layout differences between desktop and mobile viewports.
4
+ *
5
+ * Breakpoints: mobile(375), tablet(768), desktop(1280), wide(1920)
6
+ * Detects: viewport-specific content visibility, menu collapse state,
7
+ * font size changes, hidden/shown elements.
8
+ */
9
+ import type { Page } from 'playwright';
10
+ export interface ViewportCapture {
11
+ width: number;
12
+ height: number;
13
+ label: 'mobile' | 'tablet' | 'desktop' | 'wide';
14
+ screenshotBuffer?: Buffer;
15
+ visibleTextLength: number;
16
+ hasHamburgerMenu: boolean;
17
+ hiddenElements: number;
18
+ scrollHeight: number;
19
+ }
20
+ export interface ResponsiveReport {
21
+ url: string;
22
+ captures: ViewportCapture[];
23
+ isResponsive: boolean;
24
+ hasMobileMenu: boolean;
25
+ issues: string[];
26
+ }
27
+ export declare function detectResponsive(page: Page, url: string, opts?: {
28
+ captureScreenshots?: boolean;
29
+ }): Promise<ResponsiveReport>;
@@ -0,0 +1,46 @@
1
+ const BREAKPOINTS = [
2
+ { width: 375, height: 812, label: "mobile" },
3
+ { width: 768, height: 1024, label: "tablet" },
4
+ { width: 1280, height: 800, label: "desktop" }
5
+ ];
6
+ async function detectResponsive(page, url, opts = {}) {
7
+ const captures = [];
8
+ const issues = [];
9
+ for (const bp of BREAKPOINTS) {
10
+ try {
11
+ await page.setViewportSize({ width: bp.width, height: bp.height });
12
+ await page.waitForTimeout(500);
13
+ const data = await page.evaluate(() => ({
14
+ visibleTextLength: (document.body.innerText ?? "").length,
15
+ hasHamburgerMenu: !!document.querySelector('[class*="hamburger"],[class*="menu-toggle"],[class*="nav-toggle"],[aria-label*="menu"],[aria-label*="Menu"]'),
16
+ hiddenElements: Array.from(document.querySelectorAll("*")).filter(
17
+ (el) => window.getComputedStyle(el).display === "none"
18
+ ).length,
19
+ scrollHeight: document.body.scrollHeight
20
+ })).catch(() => ({ visibleTextLength: 0, hasHamburgerMenu: false, hiddenElements: 0, scrollHeight: 0 }));
21
+ let screenshotBuffer;
22
+ if (opts.captureScreenshots) {
23
+ screenshotBuffer = await page.screenshot({ type: "jpeg", quality: 60 }).catch(() => void 0);
24
+ }
25
+ captures.push({ ...bp, ...data, screenshotBuffer });
26
+ } catch {
27
+ }
28
+ }
29
+ await page.setViewportSize({ width: 1280, height: 800 }).catch(() => {
30
+ });
31
+ const mobile = captures.find((c) => c.label === "mobile");
32
+ const desktop = captures.find((c) => c.label === "desktop");
33
+ const isResponsive = !!(mobile && desktop && (mobile.hiddenElements !== desktop.hiddenElements || mobile.scrollHeight !== desktop.scrollHeight || mobile.hasHamburgerMenu));
34
+ const hasMobileMenu = captures.some((c) => c.label === "mobile" && c.hasHamburgerMenu);
35
+ if (mobile && desktop && mobile.visibleTextLength > desktop.visibleTextLength * 1.3) {
36
+ issues.push("Mobile shows significantly more text than desktop \u2014 possible responsive issue");
37
+ }
38
+ if (!isResponsive && captures.length > 1) {
39
+ issues.push("Layout appears identical across breakpoints \u2014 may not be responsive");
40
+ }
41
+ return { url, captures, isResponsive, hasMobileMenu, issues };
42
+ }
43
+ export {
44
+ detectResponsive
45
+ };
46
+ //# sourceMappingURL=responsive-detector.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/responsive-detector.ts"],
4
+ "sourcesContent": ["/**\n * Responsive design detector \u2014 captures page at multiple breakpoints\n * and detects layout differences between desktop and mobile viewports.\n *\n * Breakpoints: mobile(375), tablet(768), desktop(1280), wide(1920)\n * Detects: viewport-specific content visibility, menu collapse state,\n * font size changes, hidden/shown elements.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface ViewportCapture {\n width: number;\n height: number;\n label: 'mobile' | 'tablet' | 'desktop' | 'wide';\n screenshotBuffer?: Buffer;\n visibleTextLength: number;\n hasHamburgerMenu: boolean;\n hiddenElements: number; // display:none element count\n scrollHeight: number;\n}\n\nexport interface ResponsiveReport {\n url: string;\n captures: ViewportCapture[];\n isResponsive: boolean; // true if layout changes between viewports\n hasMobileMenu: boolean;\n issues: string[];\n}\n\nconst BREAKPOINTS: Array<{ width: number; height: number; label: ViewportCapture['label'] }> = [\n { width: 375, height: 812, label: 'mobile' },\n { width: 768, height: 1024, label: 'tablet' },\n { width: 1280, height: 800, label: 'desktop' },\n];\n\nexport async function detectResponsive(\n page: Page,\n url: string,\n opts: { captureScreenshots?: boolean } = {},\n): Promise<ResponsiveReport> {\n const captures: ViewportCapture[] = [];\n const issues: string[] = [];\n\n for (const bp of BREAKPOINTS) {\n try {\n await page.setViewportSize({ width: bp.width, height: bp.height });\n await page.waitForTimeout(500); // allow CSS reflow\n\n const data = await page.evaluate(() => ({\n visibleTextLength: (document.body.innerText ?? '').length,\n hasHamburgerMenu: !!(\n document.querySelector('[class*=\"hamburger\"],[class*=\"menu-toggle\"],[class*=\"nav-toggle\"],[aria-label*=\"menu\"],[aria-label*=\"Menu\"]')\n ),\n hiddenElements: Array.from(document.querySelectorAll('*')).filter(\n (el: any) => window.getComputedStyle(el).display === 'none'\n ).length,\n scrollHeight: document.body.scrollHeight,\n })).catch(() => ({ visibleTextLength: 0, hasHamburgerMenu: false, hiddenElements: 0, scrollHeight: 0 }));\n\n let screenshotBuffer: Buffer | undefined;\n if (opts.captureScreenshots) {\n screenshotBuffer = await page.screenshot({ type: 'jpeg', quality: 60 }).catch(() => undefined);\n }\n\n captures.push({ ...bp, ...data, screenshotBuffer });\n } catch { /* viewport failed \u2014 skip */ }\n }\n\n // Restore to desktop\n await page.setViewportSize({ width: 1280, height: 800 }).catch(() => {});\n\n const mobile = captures.find(c => c.label === 'mobile');\n const desktop = captures.find(c => c.label === 'desktop');\n\n const isResponsive = !!(mobile && desktop && (\n mobile.hiddenElements !== desktop.hiddenElements ||\n mobile.scrollHeight !== desktop.scrollHeight ||\n mobile.hasHamburgerMenu\n ));\n\n const hasMobileMenu = captures.some(c => c.label === 'mobile' && c.hasHamburgerMenu);\n\n if (mobile && desktop && mobile.visibleTextLength > desktop.visibleTextLength * 1.3) {\n issues.push('Mobile shows significantly more text than desktop \u2014 possible responsive issue');\n }\n if (!isResponsive && captures.length > 1) {\n issues.push('Layout appears identical across breakpoints \u2014 may not be responsive');\n }\n\n return { url, captures, isResponsive, hasMobileMenu, issues };\n}\n"],
5
+ "mappings": "AA8BA,MAAM,cAAyF;AAAA,EAC7F,EAAE,OAAO,KAAK,QAAQ,KAAK,OAAO,SAAS;AAAA,EAC3C,EAAE,OAAO,KAAK,QAAQ,MAAM,OAAO,SAAS;AAAA,EAC5C,EAAE,OAAO,MAAM,QAAQ,KAAK,OAAO,UAAU;AAC/C;AAEA,eAAsB,iBACpB,MACA,KACA,OAAyC,CAAC,GACf;AAC3B,QAAM,WAA8B,CAAC;AACrC,QAAM,SAAmB,CAAC;AAE1B,aAAW,MAAM,aAAa;AAC5B,QAAI;AACF,YAAM,KAAK,gBAAgB,EAAE,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,CAAC;AACjE,YAAM,KAAK,eAAe,GAAG;AAE7B,YAAM,OAAO,MAAM,KAAK,SAAS,OAAO;AAAA,QACtC,oBAAoB,SAAS,KAAK,aAAa,IAAI;AAAA,QACnD,kBAAkB,CAAC,CACjB,SAAS,cAAc,6GAA6G;AAAA,QAEtI,gBAAgB,MAAM,KAAK,SAAS,iBAAiB,GAAG,CAAC,EAAE;AAAA,UACzD,CAAC,OAAY,OAAO,iBAAiB,EAAE,EAAE,YAAY;AAAA,QACvD,EAAE;AAAA,QACF,cAAc,SAAS,KAAK;AAAA,MAC9B,EAAE,EAAE,MAAM,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,OAAO,gBAAgB,GAAG,cAAc,EAAE,EAAE;AAEvG,UAAI;AACJ,UAAI,KAAK,oBAAoB;AAC3B,2BAAmB,MAAM,KAAK,WAAW,EAAE,MAAM,QAAQ,SAAS,GAAG,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,MAC/F;AAEA,eAAS,KAAK,EAAE,GAAG,IAAI,GAAG,MAAM,iBAAiB,CAAC;AAAA,IACpD,QAAQ;AAAA,IAA+B;AAAA,EACzC;AAGA,QAAM,KAAK,gBAAgB,EAAE,OAAO,MAAM,QAAQ,IAAI,CAAC,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAEvE,QAAM,SAAS,SAAS,KAAK,OAAK,EAAE,UAAU,QAAQ;AACtD,QAAM,UAAU,SAAS,KAAK,OAAK,EAAE,UAAU,SAAS;AAExD,QAAM,eAAe,CAAC,EAAE,UAAU,YAChC,OAAO,mBAAmB,QAAQ,kBAClC,OAAO,iBAAiB,QAAQ,gBAChC,OAAO;AAGT,QAAM,gBAAgB,SAAS,KAAK,OAAK,EAAE,UAAU,YAAY,EAAE,gBAAgB;AAEnF,MAAI,UAAU,WAAW,OAAO,oBAAoB,QAAQ,oBAAoB,KAAK;AACnF,WAAO,KAAK,oFAA+E;AAAA,EAC7F;AACA,MAAI,CAAC,gBAAgB,SAAS,SAAS,GAAG;AACxC,WAAO,KAAK,0EAAqE;AAAA,EACnF;AAEA,SAAO,EAAE,KAAK,UAAU,cAAc,eAAe,OAAO;AAC9D;",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Retry policy — configurable exponential backoff with jitter.
3
+ * Covers: 5xx server errors, network timeouts, ECONNREFUSED, ECONNRESET.
4
+ * NOT retried: 4xx client errors (except 429 — handled separately), redirects.
5
+ */
6
+ export interface RetryPolicyOpts {
7
+ maxRetries?: number;
8
+ baseDelayMs?: number;
9
+ maxDelayMs?: number;
10
+ jitterMs?: number;
11
+ retryOn?: number[];
12
+ }
13
+ export interface RetryResult<T> {
14
+ value: T;
15
+ attempts: number;
16
+ totalDelayMs: number;
17
+ }
18
+ export declare function withRetry<T>(fn: (attempt: number) => Promise<T>, opts?: RetryPolicyOpts): Promise<RetryResult<T>>;
19
+ /** Wraps fetch() with retry policy */
20
+ export declare function fetchWithRetry(url: string, init?: RequestInit, retryOpts?: RetryPolicyOpts): Promise<Response>;
@@ -0,0 +1,54 @@
1
+ const DEFAULT_RETRY_STATUSES = [429, 500, 502, 503, 504];
2
+ function shouldRetry(error, retryOn) {
3
+ if (error?.code && ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EPIPE"].includes(error.code)) return true;
4
+ if (error?.message && /timeout|net::ERR_/i.test(error.message)) return true;
5
+ if (typeof error?.status === "number") return retryOn.includes(error.status);
6
+ return false;
7
+ }
8
+ function delay(ms) {
9
+ return new Promise((r) => setTimeout(r, ms));
10
+ }
11
+ async function withRetry(fn, opts = {}) {
12
+ const {
13
+ maxRetries = 3,
14
+ baseDelayMs = 1e3,
15
+ maxDelayMs = 3e4,
16
+ jitterMs = 500,
17
+ retryOn = DEFAULT_RETRY_STATUSES
18
+ } = opts;
19
+ let lastError;
20
+ let totalDelayMs = 0;
21
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
22
+ try {
23
+ const value = await fn(attempt);
24
+ return { value, attempts: attempt + 1, totalDelayMs };
25
+ } catch (err) {
26
+ lastError = err;
27
+ if (attempt === maxRetries) break;
28
+ if (!shouldRetry(err, retryOn)) throw err;
29
+ const backoff = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt));
30
+ const jitter = Math.random() * jitterMs;
31
+ const waitMs = Math.round(backoff + jitter);
32
+ totalDelayMs += waitMs;
33
+ await delay(waitMs);
34
+ }
35
+ }
36
+ throw lastError;
37
+ }
38
+ async function fetchWithRetry(url, init = {}, retryOpts = {}) {
39
+ const result = await withRetry(async () => {
40
+ const res = await fetch(url, init);
41
+ if (retryOpts.retryOn?.includes(res.status) ?? DEFAULT_RETRY_STATUSES.includes(res.status)) {
42
+ const err = new Error(`HTTP ${res.status}`);
43
+ err.status = res.status;
44
+ throw err;
45
+ }
46
+ return res;
47
+ }, retryOpts);
48
+ return result.value;
49
+ }
50
+ export {
51
+ fetchWithRetry,
52
+ withRetry
53
+ };
54
+ //# sourceMappingURL=retry-policy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/retry-policy.ts"],
4
+ "sourcesContent": ["/**\n * Retry policy \u2014 configurable exponential backoff with jitter.\n * Covers: 5xx server errors, network timeouts, ECONNREFUSED, ECONNRESET.\n * NOT retried: 4xx client errors (except 429 \u2014 handled separately), redirects.\n */\n\nexport interface RetryPolicyOpts {\n maxRetries?: number; // default 3\n baseDelayMs?: number; // default 1000\n maxDelayMs?: number; // default 30000\n jitterMs?: number; // default 500\n retryOn?: number[]; // HTTP status codes to retry (default: [429, 500, 502, 503, 504])\n}\n\nexport interface RetryResult<T> {\n value: T;\n attempts: number;\n totalDelayMs: number;\n}\n\nconst DEFAULT_RETRY_STATUSES = [429, 500, 502, 503, 504];\n\nfunction shouldRetry(error: any, retryOn: number[]): boolean {\n // Network-level errors\n if (error?.code && ['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'EPIPE'].includes(error.code)) return true;\n // Playwright navigation timeout\n if (error?.message && /timeout|net::ERR_/i.test(error.message)) return true;\n // HTTP status errors\n if (typeof error?.status === 'number') return retryOn.includes(error.status);\n return false;\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise(r => setTimeout(r, ms));\n}\n\nexport async function withRetry<T>(\n fn: (attempt: number) => Promise<T>,\n opts: RetryPolicyOpts = {},\n): Promise<RetryResult<T>> {\n const {\n maxRetries = 3,\n baseDelayMs = 1_000,\n maxDelayMs = 30_000,\n jitterMs = 500,\n retryOn = DEFAULT_RETRY_STATUSES,\n } = opts;\n\n let lastError: any;\n let totalDelayMs = 0;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const value = await fn(attempt);\n return { value, attempts: attempt + 1, totalDelayMs };\n } catch (err) {\n lastError = err;\n if (attempt === maxRetries) break;\n if (!shouldRetry(err, retryOn)) throw err; // non-retryable \u2014 fail fast\n\n const backoff = Math.min(maxDelayMs, baseDelayMs * Math.pow(2, attempt));\n const jitter = Math.random() * jitterMs;\n const waitMs = Math.round(backoff + jitter);\n totalDelayMs += waitMs;\n await delay(waitMs);\n }\n }\n throw lastError;\n}\n\n/** Wraps fetch() with retry policy */\nexport async function fetchWithRetry(\n url: string,\n init: RequestInit = {},\n retryOpts: RetryPolicyOpts = {},\n): Promise<Response> {\n const result = await withRetry(async () => {\n const res = await fetch(url, init);\n if (retryOpts.retryOn?.includes(res.status) ?? DEFAULT_RETRY_STATUSES.includes(res.status)) {\n const err: any = new Error(`HTTP ${res.status}`);\n err.status = res.status;\n throw err;\n }\n return res;\n }, retryOpts);\n return result.value;\n}\n"],
5
+ "mappings": "AAoBA,MAAM,yBAAyB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAEvD,SAAS,YAAY,OAAY,SAA4B;AAE3D,MAAI,OAAO,QAAQ,CAAC,cAAc,gBAAgB,aAAa,aAAa,OAAO,EAAE,SAAS,MAAM,IAAI,EAAG,QAAO;AAElH,MAAI,OAAO,WAAW,qBAAqB,KAAK,MAAM,OAAO,EAAG,QAAO;AAEvE,MAAI,OAAO,OAAO,WAAW,SAAU,QAAO,QAAQ,SAAS,MAAM,MAAM;AAC3E,SAAO;AACT;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,OAAK,WAAW,GAAG,EAAE,CAAC;AAC3C;AAEA,eAAsB,UACpB,IACA,OAAwB,CAAC,GACA;AACzB,QAAM;AAAA,IACJ,aAAa;AAAA,IACb,cAAc;AAAA,IACd,aAAa;AAAA,IACb,WAAW;AAAA,IACX,UAAU;AAAA,EACZ,IAAI;AAEJ,MAAI;AACJ,MAAI,eAAe;AAEnB,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,QAAI;AACF,YAAM,QAAQ,MAAM,GAAG,OAAO;AAC9B,aAAO,EAAE,OAAO,UAAU,UAAU,GAAG,aAAa;AAAA,IACtD,SAAS,KAAK;AACZ,kBAAY;AACZ,UAAI,YAAY,WAAY;AAC5B,UAAI,CAAC,YAAY,KAAK,OAAO,EAAG,OAAM;AAEtC,YAAM,UAAU,KAAK,IAAI,YAAY,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC;AACvE,YAAM,SAAS,KAAK,OAAO,IAAI;AAC/B,YAAM,SAAS,KAAK,MAAM,UAAU,MAAM;AAC1C,sBAAgB;AAChB,YAAM,MAAM,MAAM;AAAA,IACpB;AAAA,EACF;AACA,QAAM;AACR;AAGA,eAAsB,eACpB,KACA,OAAoB,CAAC,GACrB,YAA6B,CAAC,GACX;AACnB,QAAM,SAAS,MAAM,UAAU,YAAY;AACzC,UAAM,MAAM,MAAM,MAAM,KAAK,IAAI;AACjC,QAAI,UAAU,SAAS,SAAS,IAAI,MAAM,KAAK,uBAAuB,SAAS,IAAI,MAAM,GAAG;AAC1F,YAAM,MAAW,IAAI,MAAM,QAAQ,IAAI,MAAM,EAAE;AAC/C,UAAI,SAAS,IAAI;AACjB,YAAM;AAAA,IACR;AACA,WAAO;AAAA,EACT,GAAG,SAAS;AACZ,SAAO,OAAO;AAChB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Minimal robots.txt fetcher + parser. No external deps. Caches per origin.
3
+ * Implements the subset of robots.txt spec used in practice:
4
+ * User-agent, Disallow, Allow — longest match wins (RFC 9309).
5
+ */
6
+ export declare class RobotsCache {
7
+ private readonly cache;
8
+ isAllowed(url: string, userAgent?: string): Promise<boolean>;
9
+ }
@@ -0,0 +1,77 @@
1
+ function parseRobots(text) {
2
+ const rules = [];
3
+ let current = null;
4
+ for (const rawLine of text.split("\n")) {
5
+ const line = rawLine.split("#")[0].trim();
6
+ if (!line) {
7
+ current = null;
8
+ continue;
9
+ }
10
+ const colonIdx = line.indexOf(":");
11
+ if (colonIdx === -1) continue;
12
+ const key = line.slice(0, colonIdx).trim().toLowerCase();
13
+ const value = line.slice(colonIdx + 1).trim();
14
+ if (key === "user-agent") {
15
+ if (!current || current.disallow.length > 0 || current.allow.length > 0) {
16
+ current = { userAgent: value.toLowerCase(), disallow: [], allow: [] };
17
+ rules.push(current);
18
+ } else {
19
+ current.userAgent = value.toLowerCase();
20
+ }
21
+ } else if (key === "disallow" && current && value) {
22
+ current.disallow.push(value);
23
+ } else if (key === "allow" && current && value) {
24
+ current.allow.push(value);
25
+ }
26
+ }
27
+ return rules;
28
+ }
29
+ function isAllowedByRules(rules, userAgent, pathname) {
30
+ const ua = userAgent.toLowerCase();
31
+ const exactRules = rules.filter((r) => r.userAgent === ua);
32
+ const wildcardRules = rules.filter((r) => r.userAgent === "*");
33
+ const applicable = exactRules.length > 0 ? exactRules : wildcardRules;
34
+ if (applicable.length === 0) return true;
35
+ const matched = [];
36
+ for (const rule of applicable) {
37
+ for (const p of rule.allow) {
38
+ if (pathname.startsWith(p)) matched.push({ path: p, allow: true });
39
+ }
40
+ for (const p of rule.disallow) {
41
+ if (pathname.startsWith(p)) matched.push({ path: p, allow: false });
42
+ }
43
+ }
44
+ if (matched.length === 0) return true;
45
+ matched.sort((a, b) => b.path.length - a.path.length);
46
+ return matched[0].allow;
47
+ }
48
+ class RobotsCache {
49
+ cache = /* @__PURE__ */ new Map();
50
+ async isAllowed(url, userAgent = "ZeTa-Crawler") {
51
+ let origin;
52
+ let pathname;
53
+ try {
54
+ const u = new URL(url);
55
+ origin = u.origin;
56
+ pathname = u.pathname || "/";
57
+ } catch {
58
+ return true;
59
+ }
60
+ if (!this.cache.has(origin)) {
61
+ try {
62
+ const res = await fetch(`${origin}/robots.txt`, {
63
+ headers: { "User-Agent": userAgent },
64
+ signal: AbortSignal.timeout(5e3)
65
+ });
66
+ this.cache.set(origin, res.ok ? parseRobots(await res.text()) : []);
67
+ } catch {
68
+ this.cache.set(origin, []);
69
+ }
70
+ }
71
+ return isAllowedByRules(this.cache.get(origin), userAgent, pathname);
72
+ }
73
+ }
74
+ export {
75
+ RobotsCache
76
+ };
77
+ //# sourceMappingURL=robots-txt.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/robots-txt.ts"],
4
+ "sourcesContent": ["/**\n * Minimal robots.txt fetcher + parser. No external deps. Caches per origin.\n * Implements the subset of robots.txt spec used in practice:\n * User-agent, Disallow, Allow \u2014 longest match wins (RFC 9309).\n */\n\ninterface RobotsRule {\n userAgent: string;\n disallow: string[];\n allow: string[];\n}\n\nfunction parseRobots(text: string): RobotsRule[] {\n const rules: RobotsRule[] = [];\n let current: RobotsRule | null = null;\n\n for (const rawLine of text.split('\\n')) {\n const line = rawLine.split('#')[0].trim();\n if (!line) { current = null; continue; }\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim().toLowerCase();\n const value = line.slice(colonIdx + 1).trim();\n\n if (key === 'user-agent') {\n // Start new block only when current block already has rules, otherwise\n // accumulate multiple User-agent lines into one block.\n if (!current || current.disallow.length > 0 || current.allow.length > 0) {\n current = { userAgent: value.toLowerCase(), disallow: [], allow: [] };\n rules.push(current);\n } else {\n current.userAgent = value.toLowerCase();\n }\n } else if (key === 'disallow' && current && value) {\n current.disallow.push(value);\n } else if (key === 'allow' && current && value) {\n current.allow.push(value);\n }\n }\n\n return rules;\n}\n\nfunction isAllowedByRules(rules: RobotsRule[], userAgent: string, pathname: string): boolean {\n const ua = userAgent.toLowerCase();\n const exactRules = rules.filter(r => r.userAgent === ua);\n const wildcardRules = rules.filter(r => r.userAgent === '*');\n const applicable = exactRules.length > 0 ? exactRules : wildcardRules;\n\n if (applicable.length === 0) return true;\n\n // Collect all matched allow/disallow directives; longest path wins.\n const matched: Array<{ path: string; allow: boolean }> = [];\n for (const rule of applicable) {\n for (const p of rule.allow) {\n if (pathname.startsWith(p)) matched.push({ path: p, allow: true });\n }\n for (const p of rule.disallow) {\n if (pathname.startsWith(p)) matched.push({ path: p, allow: false });\n }\n }\n\n if (matched.length === 0) return true;\n matched.sort((a, b) => b.path.length - a.path.length);\n return matched[0].allow;\n}\n\nexport class RobotsCache {\n private readonly cache = new Map<string, RobotsRule[]>();\n\n async isAllowed(url: string, userAgent = 'ZeTa-Crawler'): Promise<boolean> {\n let origin: string;\n let pathname: string;\n try {\n const u = new URL(url);\n origin = u.origin;\n pathname = u.pathname || '/';\n } catch {\n return true;\n }\n\n if (!this.cache.has(origin)) {\n try {\n const res = await fetch(`${origin}/robots.txt`, {\n headers: { 'User-Agent': userAgent },\n signal: AbortSignal.timeout(5_000),\n });\n this.cache.set(origin, res.ok ? parseRobots(await res.text()) : []);\n } catch {\n this.cache.set(origin, []); // network error or timeout = no restrictions\n }\n }\n\n return isAllowedByRules(this.cache.get(origin)!, userAgent, pathname);\n }\n}\n"],
5
+ "mappings": "AAYA,SAAS,YAAY,MAA4B;AAC/C,QAAM,QAAsB,CAAC;AAC7B,MAAI,UAA6B;AAEjC,aAAW,WAAW,KAAK,MAAM,IAAI,GAAG;AACtC,UAAM,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK;AACxC,QAAI,CAAC,MAAM;AAAE,gBAAU;AAAM;AAAA,IAAU;AACvC,UAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,QAAI,aAAa,GAAI;AACrB,UAAM,MAAM,KAAK,MAAM,GAAG,QAAQ,EAAE,KAAK,EAAE,YAAY;AACvD,UAAM,QAAQ,KAAK,MAAM,WAAW,CAAC,EAAE,KAAK;AAE5C,QAAI,QAAQ,cAAc;AAGxB,UAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,KAAK,QAAQ,MAAM,SAAS,GAAG;AACvE,kBAAU,EAAE,WAAW,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,EAAE;AACpE,cAAM,KAAK,OAAO;AAAA,MACpB,OAAO;AACL,gBAAQ,YAAY,MAAM,YAAY;AAAA,MACxC;AAAA,IACF,WAAW,QAAQ,cAAc,WAAW,OAAO;AACjD,cAAQ,SAAS,KAAK,KAAK;AAAA,IAC7B,WAAW,QAAQ,WAAW,WAAW,OAAO;AAC9C,cAAQ,MAAM,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAqB,WAAmB,UAA2B;AAC3F,QAAM,KAAK,UAAU,YAAY;AACjC,QAAM,aAAa,MAAM,OAAO,OAAK,EAAE,cAAc,EAAE;AACvD,QAAM,gBAAgB,MAAM,OAAO,OAAK,EAAE,cAAc,GAAG;AAC3D,QAAM,aAAa,WAAW,SAAS,IAAI,aAAa;AAExD,MAAI,WAAW,WAAW,EAAG,QAAO;AAGpC,QAAM,UAAmD,CAAC;AAC1D,aAAW,QAAQ,YAAY;AAC7B,eAAW,KAAK,KAAK,OAAO;AAC1B,UAAI,SAAS,WAAW,CAAC,EAAG,SAAQ,KAAK,EAAE,MAAM,GAAG,OAAO,KAAK,CAAC;AAAA,IACnE;AACA,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,SAAS,WAAW,CAAC,EAAG,SAAQ,KAAK,EAAE,MAAM,GAAG,OAAO,MAAM,CAAC;AAAA,IACpE;AAAA,EACF;AAEA,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AACpD,SAAO,QAAQ,CAAC,EAAE;AACpB;AAEO,MAAM,YAAY;AAAA,EACN,QAAQ,oBAAI,IAA0B;AAAA,EAEvD,MAAM,UAAU,KAAa,YAAY,gBAAkC;AACzE,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,YAAM,IAAI,IAAI,IAAI,GAAG;AACrB,eAAS,EAAE;AACX,iBAAW,EAAE,YAAY;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,KAAK,MAAM,IAAI,MAAM,GAAG;AAC3B,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,MAAM,eAAe;AAAA,UAC9C,SAAS,EAAE,cAAc,UAAU;AAAA,UACnC,QAAQ,YAAY,QAAQ,GAAK;AAAA,QACnC,CAAC;AACD,aAAK,MAAM,IAAI,QAAQ,IAAI,KAAK,YAAY,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;AAAA,MACpE,QAAQ;AACN,aAAK,MAAM,IAAI,QAAQ,CAAC,CAAC;AAAA,MAC3B;AAAA,IACF;AAEA,WAAO,iBAAiB,KAAK,MAAM,IAAI,MAAM,GAAI,WAAW,QAAQ;AAAA,EACtE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reads a page as a structured outline (cheap + reliable), the way
3
+ * Playwright MCP / the accessibility tree works — NOT by sending pixels to a
4
+ * vision model. Vision is a fallback for image-only pages (added later).
5
+ *
6
+ * Returns the interactive elements found, which the Screen Reader Agent then
7
+ * enriches with MEANING and saves to the Zeta Core.
8
+ */
9
+ export interface RawElement {
10
+ role: string;
11
+ label: string;
12
+ selectorHint: string;
13
+ expectedData?: string;
14
+ }
15
+ export declare function readScreen(page: any): Promise<RawElement[]>;
@@ -0,0 +1,48 @@
1
+ async function readScreen(page) {
2
+ return page.evaluate(() => {
3
+ if (typeof globalThis.__name === "undefined") globalThis.__name = (fn) => fn;
4
+ const out = [];
5
+ const seen = /* @__PURE__ */ new Set();
6
+ const noiseLabel = /^(cookie|consent|privacy policy|terms of use|copyright|©|\bv\d+\.\d+|got it|dismiss|skip to|powered by|copied|\s*$)/i;
7
+ const nodes = document.querySelectorAll(
8
+ 'input:not([type=hidden]), select, textarea, button, a[href], [role="button"], [role="combobox"], [role="slider"], [role="switch"], [role="tab"], [role="menuitem"], [role="searchbox"], [role="spinbutton"]'
9
+ );
10
+ nodes.forEach((el) => {
11
+ const htmlEl = el;
12
+ const style = window.getComputedStyle(htmlEl);
13
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return;
14
+ const rect = el.getBoundingClientRect();
15
+ const hasAccessibleLabel = !!(el.getAttribute("aria-label") || el.getAttribute("aria-labelledby"));
16
+ if (rect.width === 0 && rect.height === 0 && !hasAccessibleLabel) return;
17
+ const tag = el.tagName.toLowerCase();
18
+ const role = el.getAttribute("role") || (tag === "input" ? el.type || "text" : tag);
19
+ const raw = (el.getAttribute("aria-label") || el.getAttribute("placeholder") || (el.getAttribute("title") ?? "") || htmlEl.innerText?.replace(/\s+/g, " ").trim() || el.getAttribute("name") || "").substring(0, 80);
20
+ if (!raw || noiseLabel.test(raw) || raw.length > 70) return;
21
+ const key = `${role}:${raw.toLowerCase()}`;
22
+ if (seen.has(key)) return;
23
+ seen.add(key);
24
+ let expectedData;
25
+ if (tag === "input") {
26
+ const input = el;
27
+ if (input.type === "email" || /email/i.test(raw)) expectedData = "valid_email";
28
+ else if (input.type === "password" || /password/i.test(raw)) expectedData = "password";
29
+ else if (input.type === "tel" || /phone|mobile/i.test(raw)) expectedData = "phone";
30
+ else if (input.type === "date" || /\bdate\b/i.test(raw)) expectedData = "date";
31
+ else if (/\b(code|otp|token)\b/i.test(raw)) expectedData = "one_time_code";
32
+ else if (/\b(search|query)\b/i.test(raw)) expectedData = "search_term";
33
+ }
34
+ out.push({ role, label: raw, selectorHint: tag, expectedData });
35
+ });
36
+ const priority = (e) => {
37
+ if (["email", "password", "text", "tel", "date", "number", "select", "textarea"].includes(e.role)) return 0;
38
+ if (e.role === "button" || e.role === "submit") return 1;
39
+ return 2;
40
+ };
41
+ out.sort((a, b) => priority(a) - priority(b));
42
+ return out.slice(0, 25);
43
+ });
44
+ }
45
+ export {
46
+ readScreen
47
+ };
48
+ //# sourceMappingURL=screen-reader.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/screen-reader.ts"],
4
+ "sourcesContent": ["import type { Page } from 'playwright';\n\n/**\n * Reads a page as a structured outline (cheap + reliable), the way\n * Playwright MCP / the accessibility tree works \u2014 NOT by sending pixels to a\n * vision model. Vision is a fallback for image-only pages (added later).\n *\n * Returns the interactive elements found, which the Screen Reader Agent then\n * enriches with MEANING and saves to the Zeta Core.\n */\nexport interface RawElement {\n role: string;\n label: string;\n selectorHint: string;\n expectedData?: string;\n}\n\nexport async function readScreen(page: any): Promise<RawElement[]> {\n return page.evaluate(() => {\n // esbuild injects __name() for named arrow-function constants (e.g. `const priority = ...`).\n // Polyfill prevents ReferenceError when the serialized callback runs in the browser.\n if (typeof (globalThis as any).__name === 'undefined') (globalThis as any).__name = (fn: any) => fn;\n const out: { role: string; label: string; selectorHint: string; expectedData?: string }[] = [];\n const seen = new Set<string>();\n\n // Noise patterns to skip \u2014 cookie banners, footer chrome, version strings\n // Intentionally excludes standalone \"close\" and \"copy\" \u2014 too many false positives\n // (Close account, Close preview, Copy link, Copy to clipboard are all valid test targets)\n const noiseLabel =\n /^(cookie|consent|privacy policy|terms of use|copyright|\u00A9|\\bv\\d+\\.\\d+|got it|dismiss|skip to|powered by|copied|\\s*$)/i;\n\n // Include ARIA-role elements \u2014 Headless UI, Radix, Ant Design, Shadcn all use these instead of native tags\n const nodes = document.querySelectorAll(\n 'input:not([type=hidden]), select, textarea, button, a[href], ' +\n '[role=\"button\"], [role=\"combobox\"], [role=\"slider\"], [role=\"switch\"], ' +\n '[role=\"tab\"], [role=\"menuitem\"], [role=\"searchbox\"], [role=\"spinbutton\"]'\n );\n\n nodes.forEach((el) => {\n const htmlEl = el as HTMLElement;\n\n // Skip invisible elements\n const style = window.getComputedStyle(htmlEl);\n if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return;\n const rect = el.getBoundingClientRect();\n // Allow zero-size elements that carry an accessible label (sr-only icon buttons, skip-nav links)\n const hasAccessibleLabel = !!(el.getAttribute('aria-label') || el.getAttribute('aria-labelledby'));\n if (rect.width === 0 && rect.height === 0 && !hasAccessibleLabel) return;\n\n // Derive role\n const tag = el.tagName.toLowerCase();\n const role =\n el.getAttribute('role') ||\n (tag === 'input' ? ((el as HTMLInputElement).type || 'text') : tag);\n\n // Derive the best human-readable label (prefer aria > placeholder > inner text)\n const raw = (\n el.getAttribute('aria-label') ||\n el.getAttribute('placeholder') ||\n (el.getAttribute('title') ?? '') ||\n htmlEl.innerText?.replace(/\\s+/g, ' ').trim() ||\n el.getAttribute('name') ||\n ''\n ).substring(0, 80);\n\n // Skip empty, noise patterns, or very long labels (likely container text)\n if (!raw || noiseLabel.test(raw) || raw.length > 70) return;\n\n // Deduplicate by role+normalised label\n const key = `${role}:${raw.toLowerCase()}`;\n if (seen.has(key)) return;\n seen.add(key);\n\n let expectedData: string | undefined;\n if (tag === 'input') {\n const input = el as HTMLInputElement;\n if (input.type === 'email' || /email/i.test(raw)) expectedData = 'valid_email';\n else if (input.type === 'password' || /password/i.test(raw)) expectedData = 'password';\n else if (input.type === 'tel' || /phone|mobile/i.test(raw)) expectedData = 'phone';\n else if (input.type === 'date' || /\\bdate\\b/i.test(raw)) expectedData = 'date';\n else if (/\\b(code|otp|token)\\b/i.test(raw)) expectedData = 'one_time_code';\n else if (/\\b(search|query)\\b/i.test(raw)) expectedData = 'search_term';\n }\n\n out.push({ role, label: raw, selectorHint: tag, expectedData });\n });\n\n // Prioritise: form fields > buttons > links; within each group keep order\n const priority = (e: typeof out[0]) => {\n if (['email', 'password', 'text', 'tel', 'date', 'number', 'select', 'textarea'].includes(e.role)) return 0;\n if (e.role === 'button' || e.role === 'submit') return 1;\n return 2;\n };\n out.sort((a, b) => priority(a) - priority(b));\n\n return out.slice(0, 25); // cap at 25 \u2014 enough context, not overwhelming\n });\n}\n"],
5
+ "mappings": "AAiBA,eAAsB,WAAW,MAAkC;AACjE,SAAO,KAAK,SAAS,MAAM;AAGzB,QAAI,OAAQ,WAAmB,WAAW,YAAa,CAAC,WAAmB,SAAS,CAAC,OAAY;AACjG,UAAM,MAAsF,CAAC;AAC7F,UAAM,OAAO,oBAAI,IAAY;AAK7B,UAAM,aACJ;AAGF,UAAM,QAAQ,SAAS;AAAA,MACrB;AAAA,IAGF;AAEA,UAAM,QAAQ,CAAC,OAAO;AACpB,YAAM,SAAS;AAGf,YAAM,QAAQ,OAAO,iBAAiB,MAAM;AAC5C,UAAI,MAAM,YAAY,UAAU,MAAM,eAAe,YAAY,MAAM,YAAY,IAAK;AACxF,YAAM,OAAO,GAAG,sBAAsB;AAEtC,YAAM,qBAAqB,CAAC,EAAE,GAAG,aAAa,YAAY,KAAK,GAAG,aAAa,iBAAiB;AAChG,UAAI,KAAK,UAAU,KAAK,KAAK,WAAW,KAAK,CAAC,mBAAoB;AAGlE,YAAM,MAAM,GAAG,QAAQ,YAAY;AACnC,YAAM,OACJ,GAAG,aAAa,MAAM,MACrB,QAAQ,UAAY,GAAwB,QAAQ,SAAU;AAGjE,YAAM,OACJ,GAAG,aAAa,YAAY,KAC5B,GAAG,aAAa,aAAa,MAC5B,GAAG,aAAa,OAAO,KAAK,OAC7B,OAAO,WAAW,QAAQ,QAAQ,GAAG,EAAE,KAAK,KAC5C,GAAG,aAAa,MAAM,KACtB,IACA,UAAU,GAAG,EAAE;AAGjB,UAAI,CAAC,OAAO,WAAW,KAAK,GAAG,KAAK,IAAI,SAAS,GAAI;AAGrD,YAAM,MAAM,GAAG,IAAI,IAAI,IAAI,YAAY,CAAC;AACxC,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AAEZ,UAAI;AACJ,UAAI,QAAQ,SAAS;AACnB,cAAM,QAAQ;AACd,YAAI,MAAM,SAAS,WAAW,SAAS,KAAK,GAAG,EAAG,gBAAe;AAAA,iBACxD,MAAM,SAAS,cAAc,YAAY,KAAK,GAAG,EAAG,gBAAe;AAAA,iBACnE,MAAM,SAAS,SAAS,gBAAgB,KAAK,GAAG,EAAG,gBAAe;AAAA,iBAClE,MAAM,SAAS,UAAU,YAAY,KAAK,GAAG,EAAG,gBAAe;AAAA,iBAC/D,wBAAwB,KAAK,GAAG,EAAG,gBAAe;AAAA,iBAClD,sBAAsB,KAAK,GAAG,EAAG,gBAAe;AAAA,MAC3D;AAEA,UAAI,KAAK,EAAE,MAAM,OAAO,KAAK,cAAc,KAAK,aAAa,CAAC;AAAA,IAChE,CAAC;AAGD,UAAM,WAAW,CAAC,MAAqB;AACrC,UAAI,CAAC,SAAS,YAAY,QAAQ,OAAO,QAAQ,UAAU,UAAU,UAAU,EAAE,SAAS,EAAE,IAAI,EAAG,QAAO;AAC1G,UAAI,EAAE,SAAS,YAAY,EAAE,SAAS,SAAU,QAAO;AACvD,aAAO;AAAA,IACT;AACA,QAAI,KAAK,CAAC,GAAG,MAAM,SAAS,CAAC,IAAI,SAAS,CAAC,CAAC;AAE5C,WAAO,IAAI,MAAM,GAAG,EAAE;AAAA,EACxB,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Screenshot masker — blur or black-box PII regions in captured screenshots.
3
+ *
4
+ * Strategy: inject a CSS overlay into the page before screenshot capture that
5
+ * covers detected PII-likely elements (input[type=email], input[type=password],
6
+ * elements with PII-containing text) with a blur or solid black mask.
7
+ *
8
+ * This is a CSS-based approach (no image processing library needed):
9
+ * 1. Identify likely-PII DOM elements via selectors
10
+ * 2. Add an overlaid <div> with blur/black filter at each element's bounding box
11
+ * 3. Take screenshot
12
+ * 4. Remove overlays
13
+ *
14
+ * Falls back gracefully if elements are not found or overlay injection fails.
15
+ */
16
+ import type { Page } from 'playwright';
17
+ export interface MaskingOpts {
18
+ /** Additional CSS selectors to mask (in addition to defaults) */
19
+ extraSelectors?: string[];
20
+ /** Masking style: 'blur' or 'blackout' */
21
+ style?: 'blur' | 'blackout';
22
+ /** Blur strength in px (default 8) */
23
+ blurPx?: number;
24
+ }
25
+ export declare function maskScreenshot(page: Page, opts?: MaskingOpts): Promise<{
26
+ maskedCount: number;
27
+ screenshot: Buffer;
28
+ }>;
@@ -0,0 +1,62 @@
1
+ const DEFAULT_PII_SELECTORS = [
2
+ 'input[type="email"]',
3
+ 'input[type="password"]',
4
+ 'input[type="tel"]',
5
+ 'input[autocomplete="cc-number"]',
6
+ 'input[autocomplete="cc-csc"]',
7
+ 'input[autocomplete="ssn"]',
8
+ '[data-pii="true"]',
9
+ '[data-sensitive="true"]'
10
+ ];
11
+ async function maskScreenshot(page, opts = {}) {
12
+ const { extraSelectors = [], style = "blur", blurPx = 8 } = opts;
13
+ const selectors = [...DEFAULT_PII_SELECTORS, ...extraSelectors];
14
+ let maskedCount = 0;
15
+ const overlayIds = [];
16
+ try {
17
+ const result = await page.evaluate(({ selectors: selectors2, style: style2, blurPx: blurPx2 }) => {
18
+ const ids = [];
19
+ for (const sel of selectors2) {
20
+ try {
21
+ const elements = Array.from(document.querySelectorAll(sel));
22
+ for (const el of elements) {
23
+ const rect = el.getBoundingClientRect();
24
+ if (rect.width === 0 || rect.height === 0) continue;
25
+ const overlay = document.createElement("div");
26
+ const id = `__zeta_mask_${Math.random().toString(36).slice(2)}`;
27
+ overlay.id = id;
28
+ overlay.style.cssText = [
29
+ "position: fixed",
30
+ `top: ${rect.top + window.scrollY}px`,
31
+ `left: ${rect.left + window.scrollX}px`,
32
+ `width: ${rect.width}px`,
33
+ `height: ${rect.height}px`,
34
+ "z-index: 2147483647",
35
+ style2 === "blur" ? `backdrop-filter: blur(${blurPx2}px); -webkit-backdrop-filter: blur(${blurPx2}px); background: rgba(0,0,0,0.1);` : "background: #000000;",
36
+ "pointer-events: none"
37
+ ].join("; ");
38
+ document.body.appendChild(overlay);
39
+ ids.push(id);
40
+ }
41
+ } catch {
42
+ }
43
+ }
44
+ return ids;
45
+ }, { selectors, style, blurPx });
46
+ overlayIds.push(...result);
47
+ maskedCount = result.length;
48
+ } catch {
49
+ }
50
+ const screenshot = await page.screenshot({ type: "png", fullPage: false });
51
+ if (overlayIds.length > 0) {
52
+ await page.evaluate((ids) => {
53
+ for (const id of ids) document.getElementById(id)?.remove();
54
+ }, overlayIds).catch(() => {
55
+ });
56
+ }
57
+ return { maskedCount, screenshot };
58
+ }
59
+ export {
60
+ maskScreenshot
61
+ };
62
+ //# sourceMappingURL=screenshot-masker.js.map