@taodigital/crawler 0.0.0-stage → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (494) hide show
  1. package/Dockerfile +82 -0
  2. package/README.md +10 -2
  3. package/dist/accessibility-auditor.d.ts +29 -0
  4. package/dist/accessibility-auditor.js +57 -0
  5. package/dist/accessibility-auditor.js.map +7 -0
  6. package/dist/accessibility-tree-crawler.d.ts +44 -0
  7. package/dist/accessibility-tree-crawler.js +156 -0
  8. package/dist/accessibility-tree-crawler.js.map +7 -0
  9. package/dist/advanced-ui-probes.d.ts +47 -0
  10. package/dist/advanced-ui-probes.js +310 -0
  11. package/dist/advanced-ui-probes.js.map +7 -0
  12. package/dist/ai-login.d.ts +32 -0
  13. package/dist/ai-login.js +250 -0
  14. package/dist/ai-login.js.map +7 -0
  15. package/dist/auth-token-rotator.d.ts +47 -0
  16. package/dist/auth-token-rotator.js +58 -0
  17. package/dist/auth-token-rotator.js.map +7 -0
  18. package/dist/auth.d.ts +20 -0
  19. package/dist/auth.js +348 -0
  20. package/dist/auth.js.map +7 -0
  21. package/dist/browser-extension.d.ts +37 -0
  22. package/dist/browser-extension.js +113 -0
  23. package/dist/browser-extension.js.map +7 -0
  24. package/dist/captcha-solver.d.ts +16 -0
  25. package/dist/captcha-solver.js +105 -0
  26. package/dist/captcha-solver.js.map +7 -0
  27. package/dist/color-contrast.d.ts +30 -0
  28. package/dist/color-contrast.js +78 -0
  29. package/dist/color-contrast.js.map +7 -0
  30. package/dist/content-chunker.d.ts +18 -0
  31. package/dist/content-chunker.js +152 -0
  32. package/dist/content-chunker.js.map +7 -0
  33. package/dist/content-dedup.d.ts +23 -0
  34. package/dist/content-dedup.js +60 -0
  35. package/dist/content-dedup.js.map +7 -0
  36. package/dist/cookie-consent.d.ts +25 -0
  37. package/dist/cookie-consent.js +78 -0
  38. package/dist/cookie-consent.js.map +7 -0
  39. package/dist/cookie-jar.d.ts +20 -0
  40. package/dist/cookie-jar.js +61 -0
  41. package/dist/cookie-jar.js.map +7 -0
  42. package/dist/crawl-session-manager.d.ts +44 -0
  43. package/dist/crawl-session-manager.js +145 -0
  44. package/dist/crawl-session-manager.js.map +7 -0
  45. package/dist/crawler.d.ts +233 -0
  46. package/dist/crawler.js +6239 -0
  47. package/dist/crawler.js.map +7 -0
  48. package/dist/crud-interaction.d.ts +31 -0
  49. package/dist/crud-interaction.js +264 -0
  50. package/dist/crud-interaction.js.map +7 -0
  51. package/dist/custom-js-injector.d.ts +42 -0
  52. package/dist/custom-js-injector.js +50 -0
  53. package/dist/custom-js-injector.js.map +7 -0
  54. package/dist/dark-mode-capture.d.ts +23 -0
  55. package/dist/dark-mode-capture.js +75 -0
  56. package/dist/dark-mode-capture.js.map +7 -0
  57. package/dist/dead-link-detector.d.ts +37 -0
  58. package/dist/dead-link-detector.js +50 -0
  59. package/dist/dead-link-detector.js.map +7 -0
  60. package/dist/dropdown-enumerator.d.ts +20 -0
  61. package/dist/dropdown-enumerator.js +230 -0
  62. package/dist/dropdown-enumerator.js.map +7 -0
  63. package/dist/element-hash.d.ts +9 -0
  64. package/dist/element-hash.js +9 -0
  65. package/dist/element-hash.js.map +7 -0
  66. package/dist/email-otp.d.ts +42 -0
  67. package/dist/email-otp.js +177 -0
  68. package/dist/email-otp.js.map +7 -0
  69. package/dist/engine-router.d.ts +47 -0
  70. package/dist/engine-router.js +94 -0
  71. package/dist/engine-router.js.map +7 -0
  72. package/dist/export/bulk-export.d.ts +26 -0
  73. package/dist/export/bulk-export.js +76 -0
  74. package/dist/export/bulk-export.js.map +7 -0
  75. package/dist/export/vector-export.d.ts +76 -0
  76. package/dist/export/vector-export.js +85 -0
  77. package/dist/export/vector-export.js.map +7 -0
  78. package/dist/extractors/advanced.d.ts +64 -0
  79. package/dist/extractors/advanced.js +265 -0
  80. package/dist/extractors/advanced.js.map +7 -0
  81. package/dist/extractors/artifact-envelope.d.ts +63 -0
  82. package/dist/extractors/artifact-envelope.js +63 -0
  83. package/dist/extractors/artifact-envelope.js.map +7 -0
  84. package/dist/extractors/branding-extractor.d.ts +46 -0
  85. package/dist/extractors/branding-extractor.js +104 -0
  86. package/dist/extractors/branding-extractor.js.map +7 -0
  87. package/dist/extractors/summary-extractor.d.ts +10 -0
  88. package/dist/extractors/summary-extractor.js +36 -0
  89. package/dist/extractors/summary-extractor.js.map +7 -0
  90. package/dist/failure-analysis-agent.d.ts +16 -0
  91. package/dist/failure-analysis-agent.js +42 -0
  92. package/dist/failure-analysis-agent.js.map +7 -0
  93. package/dist/feature-flag-detector.d.ts +30 -0
  94. package/dist/feature-flag-detector.js +55 -0
  95. package/dist/feature-flag-detector.js.map +7 -0
  96. package/dist/filter-pagination-crawler.d.ts +52 -0
  97. package/dist/filter-pagination-crawler.js +299 -0
  98. package/dist/filter-pagination-crawler.js.map +7 -0
  99. package/dist/form-detector.d.ts +34 -0
  100. package/dist/form-detector.js +58 -0
  101. package/dist/form-detector.js.map +7 -0
  102. package/dist/frontier.d.ts +40 -0
  103. package/dist/frontier.js +103 -0
  104. package/dist/frontier.js.map +7 -0
  105. package/dist/gap-pass.d.ts +36 -0
  106. package/dist/gap-pass.js +115 -0
  107. package/dist/gap-pass.js.map +7 -0
  108. package/dist/graphql-detector.d.ts +39 -0
  109. package/dist/graphql-detector.js +86 -0
  110. package/dist/graphql-detector.js.map +7 -0
  111. package/dist/http-engine.d.ts +80 -0
  112. package/dist/http-engine.js +318 -0
  113. package/dist/http-engine.js.map +7 -0
  114. package/dist/incremental-diff.d.ts +116 -0
  115. package/dist/incremental-diff.js +137 -0
  116. package/dist/incremental-diff.js.map +7 -0
  117. package/dist/index.d.ts +52 -0
  118. package/dist/index.js +83 -0
  119. package/dist/index.js.map +7 -0
  120. package/dist/input-fuzzer.d.ts +30 -0
  121. package/dist/input-fuzzer.js +105 -0
  122. package/dist/input-fuzzer.js.map +7 -0
  123. package/dist/interactive-probe.d.ts +144 -0
  124. package/dist/interactive-probe.js +865 -0
  125. package/dist/interactive-probe.js.map +7 -0
  126. package/dist/job-queue.d.ts +297 -0
  127. package/dist/job-queue.js +3812 -0
  128. package/dist/job-queue.js.map +7 -0
  129. package/dist/js-coverage.d.ts +29 -0
  130. package/dist/js-coverage.js +56 -0
  131. package/dist/js-coverage.js.map +7 -0
  132. package/dist/js-router-detector.d.ts +17 -0
  133. package/dist/js-router-detector.js +90 -0
  134. package/dist/js-router-detector.js.map +7 -0
  135. package/dist/language-detector.d.ts +22 -0
  136. package/dist/language-detector.js +57 -0
  137. package/dist/language-detector.js.map +7 -0
  138. package/dist/link-classifier.d.ts +23 -0
  139. package/dist/link-classifier.js +40 -0
  140. package/dist/link-classifier.js.map +7 -0
  141. package/dist/live-view.d.ts +36 -0
  142. package/dist/live-view.js +105 -0
  143. package/dist/live-view.js.map +7 -0
  144. package/dist/locator-extractor.d.ts +46 -0
  145. package/dist/locator-extractor.js +220 -0
  146. package/dist/locator-extractor.js.map +7 -0
  147. package/dist/logger.d.ts +2 -0
  148. package/dist/logger.js +10 -0
  149. package/dist/logger.js.map +7 -0
  150. package/dist/markdown-extract.d.ts +8 -0
  151. package/dist/markdown-extract.js +54 -0
  152. package/dist/markdown-extract.js.map +7 -0
  153. package/dist/multi-context-handler.d.ts +55 -0
  154. package/dist/multi-context-handler.js +182 -0
  155. package/dist/multi-context-handler.js.map +7 -0
  156. package/dist/native-crawler.d.ts +69 -0
  157. package/dist/native-crawler.js +839 -0
  158. package/dist/native-crawler.js.map +7 -0
  159. package/dist/nested-window-crawler.d.ts +41 -0
  160. package/dist/nested-window-crawler.js +172 -0
  161. package/dist/nested-window-crawler.js.map +7 -0
  162. package/dist/network-action-correlator.d.ts +47 -0
  163. package/dist/network-action-correlator.js +209 -0
  164. package/dist/network-action-correlator.js.map +7 -0
  165. package/dist/network-observer.d.ts +49 -0
  166. package/dist/network-observer.js +149 -0
  167. package/dist/network-observer.js.map +7 -0
  168. package/dist/network-throttle.d.ts +25 -0
  169. package/dist/network-throttle.js +34 -0
  170. package/dist/network-throttle.js.map +7 -0
  171. package/dist/openapi-generator.d.ts +49 -0
  172. package/dist/openapi-generator.js +89 -0
  173. package/dist/openapi-generator.js.map +7 -0
  174. package/dist/pdf-extractor.d.ts +28 -0
  175. package/dist/pdf-extractor.js +81 -0
  176. package/dist/pdf-extractor.js.map +7 -0
  177. package/dist/performance-metrics-collector.d.ts +50 -0
  178. package/dist/performance-metrics-collector.js +155 -0
  179. package/dist/performance-metrics-collector.js.map +7 -0
  180. package/dist/performance-metrics.d.ts +52 -0
  181. package/dist/performance-metrics.js +83 -0
  182. package/dist/performance-metrics.js.map +7 -0
  183. package/dist/phash.d.ts +50 -0
  184. package/dist/phash.js +136 -0
  185. package/dist/phash.js.map +7 -0
  186. package/dist/pii-scrubber.d.ts +21 -0
  187. package/dist/pii-scrubber.js +75 -0
  188. package/dist/pii-scrubber.js.map +7 -0
  189. package/dist/playwright-recorder.d.ts +39 -0
  190. package/dist/playwright-recorder.js +123 -0
  191. package/dist/playwright-recorder.js.map +7 -0
  192. package/dist/policies/crawl-safety.d.ts +9 -0
  193. package/dist/policies/crawl-safety.js +41 -0
  194. package/dist/policies/crawl-safety.js.map +7 -0
  195. package/dist/profiles/domain-profile.d.ts +19 -0
  196. package/dist/profiles/domain-profile.js +160 -0
  197. package/dist/profiles/domain-profile.js.map +7 -0
  198. package/dist/proxy.d.ts +50 -0
  199. package/dist/proxy.js +165 -0
  200. package/dist/proxy.js.map +7 -0
  201. package/dist/rate-limiter.d.ts +31 -0
  202. package/dist/rate-limiter.js +64 -0
  203. package/dist/rate-limiter.js.map +7 -0
  204. package/dist/redirect-tracker.d.ts +37 -0
  205. package/dist/redirect-tracker.js +65 -0
  206. package/dist/redirect-tracker.js.map +7 -0
  207. package/dist/redis-frontier.d.ts +48 -0
  208. package/dist/redis-frontier.js +126 -0
  209. package/dist/redis-frontier.js.map +7 -0
  210. package/dist/redis-queue.d.ts +48 -0
  211. package/dist/redis-queue.js +165 -0
  212. package/dist/redis-queue.js.map +7 -0
  213. package/dist/response-cache.d.ts +38 -0
  214. package/dist/response-cache.js +140 -0
  215. package/dist/response-cache.js.map +7 -0
  216. package/dist/responsive-detector.d.ts +29 -0
  217. package/dist/responsive-detector.js +46 -0
  218. package/dist/responsive-detector.js.map +7 -0
  219. package/dist/retry-policy.d.ts +20 -0
  220. package/dist/retry-policy.js +54 -0
  221. package/dist/retry-policy.js.map +7 -0
  222. package/dist/robots-txt.d.ts +9 -0
  223. package/dist/robots-txt.js +77 -0
  224. package/dist/robots-txt.js.map +7 -0
  225. package/dist/screen-reader.d.ts +15 -0
  226. package/dist/screen-reader.js +48 -0
  227. package/dist/screen-reader.js.map +7 -0
  228. package/dist/screenshot-masker.d.ts +28 -0
  229. package/dist/screenshot-masker.js +62 -0
  230. package/dist/screenshot-masker.js.map +7 -0
  231. package/dist/seo-checker.d.ts +33 -0
  232. package/dist/seo-checker.js +91 -0
  233. package/dist/seo-checker.js.map +7 -0
  234. package/dist/service-worker-interceptor.d.ts +36 -0
  235. package/dist/service-worker-interceptor.js +136 -0
  236. package/dist/service-worker-interceptor.js.map +7 -0
  237. package/dist/session-capture.d.ts +39 -0
  238. package/dist/session-capture.js +274 -0
  239. package/dist/session-capture.js.map +7 -0
  240. package/dist/session-export.d.ts +49 -0
  241. package/dist/session-export.js +117 -0
  242. package/dist/session-export.js.map +7 -0
  243. package/dist/shadow-dom-crawler.d.ts +35 -0
  244. package/dist/shadow-dom-crawler.js +132 -0
  245. package/dist/shadow-dom-crawler.js.map +7 -0
  246. package/dist/sitemap-writer.d.ts +21 -0
  247. package/dist/sitemap-writer.js +47 -0
  248. package/dist/sitemap-writer.js.map +7 -0
  249. package/dist/sitemap.d.ts +51 -0
  250. package/dist/sitemap.js +160 -0
  251. package/dist/sitemap.js.map +7 -0
  252. package/dist/skyvern.d.ts +56 -0
  253. package/dist/skyvern.js +129 -0
  254. package/dist/skyvern.js.map +7 -0
  255. package/dist/sqs-queue.d.ts +13 -0
  256. package/dist/sqs-queue.js +115 -0
  257. package/dist/sqs-queue.js.map +7 -0
  258. package/dist/sso-token.d.ts +29 -0
  259. package/dist/sso-token.js +76 -0
  260. package/dist/sso-token.js.map +7 -0
  261. package/dist/state-graph.d.ts +59 -0
  262. package/dist/state-graph.js +193 -0
  263. package/dist/state-graph.js.map +7 -0
  264. package/dist/stealth/human-behavior.d.ts +20 -0
  265. package/dist/stealth/human-behavior.js +202 -0
  266. package/dist/stealth/human-behavior.js.map +7 -0
  267. package/dist/telemetry.d.ts +34 -0
  268. package/dist/telemetry.js +77 -0
  269. package/dist/telemetry.js.map +7 -0
  270. package/dist/test-executor.d.ts +81 -0
  271. package/dist/test-executor.js +443 -0
  272. package/dist/test-executor.js.map +7 -0
  273. package/dist/tls-fingerprint.d.ts +50 -0
  274. package/dist/tls-fingerprint.js +81 -0
  275. package/dist/tls-fingerprint.js.map +7 -0
  276. package/dist/url-pattern-completer.d.ts +28 -0
  277. package/dist/url-pattern-completer.js +122 -0
  278. package/dist/url-pattern-completer.js.map +7 -0
  279. package/dist/visual-diff-verdict.d.ts +40 -0
  280. package/dist/visual-diff-verdict.js +27 -0
  281. package/dist/visual-diff-verdict.js.map +7 -0
  282. package/dist/webhook-notifier.d.ts +30 -0
  283. package/dist/webhook-notifier.js +76 -0
  284. package/dist/webhook-notifier.js.map +7 -0
  285. package/dist/websocket-detector.d.ts +24 -0
  286. package/dist/websocket-detector.js +60 -0
  287. package/dist/websocket-detector.js.map +7 -0
  288. package/dist/worker.d.ts +1 -0
  289. package/dist/worker.js +33 -0
  290. package/dist/worker.js.map +7 -0
  291. package/package.json +49 -4
  292. package/src/accessibility-auditor.ts +102 -0
  293. package/src/accessibility-tree-crawler.ts +246 -0
  294. package/src/advanced-ui-probes.ts +470 -0
  295. package/src/ai-login.ts +298 -0
  296. package/src/auth-token-rotator.ts +89 -0
  297. package/src/auth.ts +418 -0
  298. package/src/browser-extension.ts +186 -0
  299. package/src/captcha-solver.ts +143 -0
  300. package/src/color-contrast.ts +116 -0
  301. package/src/content-chunker.ts +170 -0
  302. package/src/content-dedup.ts +99 -0
  303. package/src/cookie-consent.ts +106 -0
  304. package/src/cookie-jar.ts +91 -0
  305. package/src/crawl-session-manager.ts +231 -0
  306. package/src/crawler.ts +7123 -0
  307. package/src/crawler.ts.rej +12 -0
  308. package/src/crud-interaction.ts +386 -0
  309. package/src/custom-js-injector.ts +93 -0
  310. package/src/dark-mode-capture.ts +110 -0
  311. package/src/dead-link-detector.ts +76 -0
  312. package/src/dropdown-enumerator.ts +312 -0
  313. package/src/element-hash.ts +16 -0
  314. package/src/email-otp.ts +252 -0
  315. package/src/engine-router.ts +141 -0
  316. package/src/export/bulk-export.ts +85 -0
  317. package/src/export/vector-export.ts +160 -0
  318. package/src/extractors/advanced.ts +309 -0
  319. package/src/extractors/artifact-envelope.ts +111 -0
  320. package/src/extractors/branding-extractor.ts +159 -0
  321. package/src/extractors/summary-extractor.ts +49 -0
  322. package/src/failure-analysis-agent.ts +51 -0
  323. package/src/feature-flag-detector.ts +97 -0
  324. package/src/filter-pagination-crawler.ts +500 -0
  325. package/src/form-detector.ts +103 -0
  326. package/src/frontier.test.ts +64 -0
  327. package/src/frontier.ts +144 -0
  328. package/src/gap-pass.ts +165 -0
  329. package/src/graphql-detector.ts +129 -0
  330. package/src/http-engine.ts +453 -0
  331. package/src/incremental-diff.ts +259 -0
  332. package/src/index.ts +52 -0
  333. package/src/input-fuzzer.ts +129 -0
  334. package/src/interactive-probe.ts +1136 -0
  335. package/src/job-queue.test.ts +187 -0
  336. package/src/job-queue.ts +4504 -0
  337. package/src/js-coverage.ts +87 -0
  338. package/src/js-router-detector.ts +112 -0
  339. package/src/language-detector.ts +82 -0
  340. package/src/link-classifier.ts +65 -0
  341. package/src/live-view.ts +146 -0
  342. package/src/locator-extractor.ts +309 -0
  343. package/src/logger.ts +9 -0
  344. package/src/markdown-extract.test.ts +73 -0
  345. package/src/markdown-extract.ts +76 -0
  346. package/src/multi-context-handler.ts +296 -0
  347. package/src/native-crawler.ts +1108 -0
  348. package/src/nested-window-crawler.ts +310 -0
  349. package/src/network-action-correlator.ts +320 -0
  350. package/src/network-observer.ts +204 -0
  351. package/src/network-throttle.ts +69 -0
  352. package/src/openapi-generator.ts +135 -0
  353. package/src/pdf-extractor.ts +123 -0
  354. package/src/performance-metrics-collector.ts +243 -0
  355. package/src/performance-metrics.ts +134 -0
  356. package/src/phash.ts +184 -0
  357. package/src/pii-scrubber.ts +99 -0
  358. package/src/playwright-recorder.ts +170 -0
  359. package/src/policies/crawl-safety.ts +52 -0
  360. package/src/profiles/domain-profile.ts +190 -0
  361. package/src/proxy.ts +238 -0
  362. package/src/rate-limiter.ts +87 -0
  363. package/src/redirect-tracker.ts +97 -0
  364. package/src/redis-frontier.ts +143 -0
  365. package/src/redis-queue.ts +208 -0
  366. package/src/response-cache.ts +150 -0
  367. package/src/responsive-detector.ts +92 -0
  368. package/src/retry-policy.ts +87 -0
  369. package/src/robots-txt.ts +96 -0
  370. package/src/screen-reader.ts +98 -0
  371. package/src/screenshot-masker.ts +98 -0
  372. package/src/seo-checker.ts +134 -0
  373. package/src/service-worker-interceptor.ts +185 -0
  374. package/src/session-capture.ts +335 -0
  375. package/src/session-export.test.ts +75 -0
  376. package/src/session-export.ts +172 -0
  377. package/src/shadow-dom-crawler.ts +198 -0
  378. package/src/sitemap-writer.ts +65 -0
  379. package/src/sitemap.test.ts +127 -0
  380. package/src/sitemap.ts +233 -0
  381. package/src/skyvern.ts +199 -0
  382. package/src/sqs-queue.ts +129 -0
  383. package/src/sso-token.ts +120 -0
  384. package/src/state-graph.ts +280 -0
  385. package/src/stealth/human-behavior.ts +227 -0
  386. package/src/telemetry.ts +122 -0
  387. package/src/test-executor.test.ts +72 -0
  388. package/src/test-executor.ts +578 -0
  389. package/src/tls-fingerprint.ts +111 -0
  390. package/src/url-pattern-completer.ts +161 -0
  391. package/src/vendor.d.ts +20 -0
  392. package/src/visual-diff-verdict.test.ts +80 -0
  393. package/src/visual-diff-verdict.ts +59 -0
  394. package/src/webhook-notifier.ts +109 -0
  395. package/src/websocket-detector.ts +83 -0
  396. package/src/worker.ts +50 -0
  397. package/tsconfig.json +13 -0
  398. package/types/accessibility-auditor.d.ts +29 -0
  399. package/types/accessibility-tree-crawler.d.ts +44 -0
  400. package/types/advanced-ui-probes.d.ts +47 -0
  401. package/types/ai-login.d.ts +32 -0
  402. package/types/auth-token-rotator.d.ts +47 -0
  403. package/types/auth.d.ts +20 -0
  404. package/types/browser-extension.d.ts +37 -0
  405. package/types/bull-worker.d.ts +6 -0
  406. package/types/captcha-solver.d.ts +16 -0
  407. package/types/color-contrast.d.ts +30 -0
  408. package/types/content-chunker.d.ts +18 -0
  409. package/types/content-dedup.d.ts +23 -0
  410. package/types/cookie-consent.d.ts +25 -0
  411. package/types/cookie-jar.d.ts +20 -0
  412. package/types/crawl-session-manager.d.ts +44 -0
  413. package/types/crawler.d.ts +233 -0
  414. package/types/crud-interaction.d.ts +31 -0
  415. package/types/custom-js-injector.d.ts +42 -0
  416. package/types/dark-mode-capture.d.ts +23 -0
  417. package/types/dead-link-detector.d.ts +37 -0
  418. package/types/dropdown-enumerator.d.ts +20 -0
  419. package/types/element-hash.d.ts +9 -0
  420. package/types/email-otp.d.ts +42 -0
  421. package/types/engine-router.d.ts +47 -0
  422. package/types/export/bulk-export.d.ts +26 -0
  423. package/types/export/vector-export.d.ts +76 -0
  424. package/types/extractors/advanced.d.ts +64 -0
  425. package/types/extractors/artifact-envelope.d.ts +63 -0
  426. package/types/extractors/branding-extractor.d.ts +46 -0
  427. package/types/extractors/summary-extractor.d.ts +10 -0
  428. package/types/failure-analysis-agent.d.ts +16 -0
  429. package/types/feature-flag-detector.d.ts +30 -0
  430. package/types/filter-pagination-crawler.d.ts +52 -0
  431. package/types/form-detector.d.ts +34 -0
  432. package/types/frontier.d.ts +40 -0
  433. package/types/gap-pass.d.ts +36 -0
  434. package/types/graphql-detector.d.ts +39 -0
  435. package/types/http-engine.d.ts +80 -0
  436. package/types/incremental-diff.d.ts +116 -0
  437. package/types/index.d.ts +52 -0
  438. package/types/input-fuzzer.d.ts +30 -0
  439. package/types/interactive-probe.d.ts +144 -0
  440. package/types/job-queue.d.ts +308 -0
  441. package/types/js-coverage.d.ts +29 -0
  442. package/types/js-router-detector.d.ts +17 -0
  443. package/types/language-detector.d.ts +22 -0
  444. package/types/link-classifier.d.ts +23 -0
  445. package/types/live-view.d.ts +36 -0
  446. package/types/locator-extractor.d.ts +46 -0
  447. package/types/logger.d.ts +2 -0
  448. package/types/markdown-extract.d.ts +8 -0
  449. package/types/multi-context-handler.d.ts +55 -0
  450. package/types/native-crawler.d.ts +69 -0
  451. package/types/nested-window-crawler.d.ts +41 -0
  452. package/types/network-action-correlator.d.ts +47 -0
  453. package/types/network-observer.d.ts +49 -0
  454. package/types/network-throttle.d.ts +25 -0
  455. package/types/openapi-generator.d.ts +49 -0
  456. package/types/pdf-extractor.d.ts +28 -0
  457. package/types/performance-metrics-collector.d.ts +50 -0
  458. package/types/performance-metrics.d.ts +52 -0
  459. package/types/phash.d.ts +50 -0
  460. package/types/pii-scrubber.d.ts +21 -0
  461. package/types/playwright-recorder.d.ts +39 -0
  462. package/types/policies/crawl-safety.d.ts +9 -0
  463. package/types/profiles/domain-profile.d.ts +19 -0
  464. package/types/proxy.d.ts +50 -0
  465. package/types/rate-limiter.d.ts +31 -0
  466. package/types/redirect-tracker.d.ts +37 -0
  467. package/types/redis-frontier.d.ts +48 -0
  468. package/types/redis-queue.d.ts +48 -0
  469. package/types/response-cache.d.ts +38 -0
  470. package/types/responsive-detector.d.ts +29 -0
  471. package/types/retry-policy.d.ts +20 -0
  472. package/types/robots-txt.d.ts +9 -0
  473. package/types/screen-reader.d.ts +15 -0
  474. package/types/screenshot-masker.d.ts +28 -0
  475. package/types/seo-checker.d.ts +33 -0
  476. package/types/service-worker-interceptor.d.ts +36 -0
  477. package/types/session-capture.d.ts +39 -0
  478. package/types/session-export.d.ts +49 -0
  479. package/types/shadow-dom-crawler.d.ts +35 -0
  480. package/types/sitemap-writer.d.ts +21 -0
  481. package/types/sitemap.d.ts +51 -0
  482. package/types/skyvern.d.ts +56 -0
  483. package/types/sqs-queue.d.ts +13 -0
  484. package/types/sso-token.d.ts +29 -0
  485. package/types/state-graph.d.ts +59 -0
  486. package/types/stealth/human-behavior.d.ts +20 -0
  487. package/types/telemetry.d.ts +34 -0
  488. package/types/test-executor.d.ts +84 -0
  489. package/types/tls-fingerprint.d.ts +50 -0
  490. package/types/url-pattern-completer.d.ts +28 -0
  491. package/types/visual-diff-verdict.d.ts +40 -0
  492. package/types/webhook-notifier.d.ts +30 -0
  493. package/types/websocket-detector.d.ts +24 -0
  494. package/types/worker.d.ts +1 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Composites neutral grey (128,128,128) over each ignoredRegion (fractional
3
+ * 0-1 coordinates) so masked content — timestamps, live counters, ads —
4
+ * contributes nothing to any downstream pixel comparison. Shared by every
5
+ * comparison path (whole-image hash, per-cell grid hash, AI vision) so a
6
+ * region masked once is actually ignored everywhere, not just in the
7
+ * cheapest check.
8
+ */
9
+ export declare function applyMask(imagePath: string, ignoredRegions: Array<{
10
+ x: number;
11
+ y: number;
12
+ width: number;
13
+ height: number;
14
+ }>): Promise<Buffer>;
15
+ /**
16
+ * Generates a pixelmatch diff image highlighting changed pixels in red.
17
+ * Applies the same ignoredRegions mask as screenshotDiffPercent so masked
18
+ * zones don't show as "changed" in the visual output.
19
+ * Returns a PNG Buffer suitable for upload to S3.
20
+ */
21
+ export declare function generatePixelmatchDiff(pathA: string, pathB: string, ignoredRegions?: Array<{
22
+ x: number;
23
+ y: number;
24
+ width: number;
25
+ height: number;
26
+ }>): Promise<Buffer>;
27
+ export declare function screenshotDiffPercent(pathA: string, pathB: string, ignoredRegions?: Array<{
28
+ x: number;
29
+ y: number;
30
+ width: number;
31
+ height: number;
32
+ }>): Promise<{
33
+ pHashDistance: number;
34
+ changeLevel: 'IDENTICAL' | 'MINOR' | 'MODERATE' | 'SIGNIFICANT';
35
+ }>;
36
+ /**
37
+ * Grid-based layout analysis: divides screenshots into a 3×3 grid and computes
38
+ * pHash per cell. Returns which regions changed and where, for the VisualDiff.changes field.
39
+ * Uses sharp's `extract` to crop each cell before hashing.
40
+ */
41
+ export declare function gridLayoutDiff(pathA: string, pathB: string, ignoredRegions?: Array<{
42
+ x: number;
43
+ y: number;
44
+ width: number;
45
+ height: number;
46
+ }>): Promise<Array<{
47
+ location: string;
48
+ severity: string;
49
+ description: string;
50
+ }>>;
package/dist/phash.js ADDED
@@ -0,0 +1,136 @@
1
+ import sharp from "sharp";
2
+ async function applyMask(imagePath, ignoredRegions) {
3
+ const meta = await sharp(imagePath).metadata();
4
+ const W = meta.width ?? 1200;
5
+ const H = meta.height ?? 800;
6
+ const overlays = await Promise.all(
7
+ ignoredRegions.map(async (r) => {
8
+ const rW = Math.max(1, Math.round(r.width * W));
9
+ const rH = Math.max(1, Math.round(r.height * H));
10
+ const buf = await sharp({
11
+ create: { width: rW, height: rH, channels: 3, background: { r: 128, g: 128, b: 128 } }
12
+ }).png().toBuffer();
13
+ return { input: buf, top: Math.round(r.y * H), left: Math.round(r.x * W) };
14
+ })
15
+ );
16
+ return sharp(imagePath).composite(overlays).png().toBuffer();
17
+ }
18
+ async function hashWithMasks(imagePath, ignoredRegions = []) {
19
+ const pipeline = ignoredRegions.length > 0 ? sharp(await applyMask(imagePath, ignoredRegions)) : sharp(imagePath);
20
+ const buf = await pipeline.resize(8, 8, { fit: "fill" }).greyscale().raw().toBuffer();
21
+ const pixels = Array.from(buf);
22
+ const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;
23
+ return pixels.reduce((h, p, i) => p >= mean ? h | 1n << BigInt(63 - i) : h, 0n);
24
+ }
25
+ function hammingDistance(a, b) {
26
+ let xor = a ^ b;
27
+ let count = 0;
28
+ while (xor !== 0n) {
29
+ count += Number(xor & 1n);
30
+ xor >>= 1n;
31
+ }
32
+ return count;
33
+ }
34
+ async function generatePixelmatchDiff(pathA, pathB, ignoredRegions = []) {
35
+ const metaA = await sharp(pathA).metadata();
36
+ const W = metaA.width ?? 1200;
37
+ const H = metaA.height ?? 800;
38
+ const toRawRGBA = async (path) => {
39
+ const base = ignoredRegions.length > 0 ? sharp(await applyMask(path, ignoredRegions)) : sharp(path);
40
+ const { data } = await base.resize(W, H, { fit: "fill" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
41
+ return data;
42
+ };
43
+ const [rawA, rawB] = await Promise.all([toRawRGBA(pathA), toRawRGBA(pathB)]);
44
+ const { default: pixelmatch } = await import("pixelmatch");
45
+ const diffData = Buffer.alloc(W * H * 4);
46
+ pixelmatch(rawA, rawB, diffData, W, H, {
47
+ threshold: 0.1,
48
+ alpha: 0.3,
49
+ diffColor: [255, 0, 0],
50
+ aaColor: [255, 255, 0]
51
+ });
52
+ return sharp(diffData, { raw: { width: W, height: H, channels: 4 } }).png().toBuffer();
53
+ }
54
+ async function screenshotDiffPercent(pathA, pathB, ignoredRegions = []) {
55
+ const [hashA, hashB] = await Promise.all([
56
+ hashWithMasks(pathA, ignoredRegions),
57
+ hashWithMasks(pathB, ignoredRegions)
58
+ ]);
59
+ let xor = hashA ^ hashB;
60
+ let dist = 0;
61
+ while (xor !== 0n) {
62
+ dist += Number(xor & 1n);
63
+ xor >>= 1n;
64
+ }
65
+ const pct = parseFloat((dist / 64 * 100).toFixed(1));
66
+ const changeLevel = pct === 0 ? "IDENTICAL" : pct < 10 ? "MINOR" : pct < 30 ? "MODERATE" : "SIGNIFICANT";
67
+ return { pHashDistance: pct, changeLevel };
68
+ }
69
+ async function gridLayoutDiff(pathA, pathB, ignoredRegions = []) {
70
+ const GRID_LABELS = [
71
+ "top-left",
72
+ "top-center",
73
+ "top-right",
74
+ "mid-left",
75
+ "mid-center",
76
+ "mid-right",
77
+ "bottom-left",
78
+ "bottom-center",
79
+ "bottom-right"
80
+ ];
81
+ try {
82
+ const [sourceA, sourceB] = ignoredRegions.length > 0 ? await Promise.all([applyMask(pathA, ignoredRegions), applyMask(pathB, ignoredRegions)]) : [pathA, pathB];
83
+ const [metaA, metaB] = await Promise.all([
84
+ sharp(sourceA).metadata(),
85
+ sharp(sourceB).metadata()
86
+ ]);
87
+ const W = Math.min(metaA.width ?? 1200, metaB.width ?? 1200);
88
+ const H = Math.min(metaA.height ?? 800, metaB.height ?? 800);
89
+ const cW = Math.floor(W / 3);
90
+ const cH = Math.floor(H / 3);
91
+ const changes = [];
92
+ for (let row = 0; row < 3; row++) {
93
+ for (let col = 0; col < 3; col++) {
94
+ const region = { left: col * cW, top: row * cH, width: cW, height: cH };
95
+ const label = GRID_LABELS[row * 3 + col];
96
+ const [hashA, hashB] = await Promise.all([
97
+ sharp(sourceA).extract(region).resize(8, 8, { fit: "fill" }).greyscale().raw().toBuffer().then((buf) => {
98
+ const pixels = Array.from(buf);
99
+ const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;
100
+ return pixels.reduce((h, p, i) => p >= mean ? h | 1n << BigInt(63 - i) : h, 0n);
101
+ }),
102
+ sharp(sourceB).extract(region).resize(8, 8, { fit: "fill" }).greyscale().raw().toBuffer().then((buf) => {
103
+ const pixels = Array.from(buf);
104
+ const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;
105
+ return pixels.reduce((h, p, i) => p >= mean ? h | 1n << BigInt(63 - i) : h, 0n);
106
+ })
107
+ ]);
108
+ let xor = hashA ^ hashB;
109
+ let dist = 0;
110
+ while (xor !== 0n) {
111
+ dist += Number(xor & 1n);
112
+ xor >>= 1n;
113
+ }
114
+ const pct = dist / 64 * 100;
115
+ if (pct >= 5) {
116
+ const severity = pct >= 20 ? "HIGH" : pct >= 10 ? "MEDIUM" : "LOW";
117
+ changes.push({
118
+ location: label,
119
+ severity,
120
+ description: `${Math.round(pct)}% visual change in ${label.replace("-", " ")} region`
121
+ });
122
+ }
123
+ }
124
+ }
125
+ return changes;
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+ export {
131
+ applyMask,
132
+ generatePixelmatchDiff,
133
+ gridLayoutDiff,
134
+ screenshotDiffPercent
135
+ };
136
+ //# sourceMappingURL=phash.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/phash.ts"],
4
+ "sourcesContent": ["import sharp from 'sharp';\nimport type { OverlayOptions } from 'sharp';\n\n/**\n * Composites neutral grey (128,128,128) over each ignoredRegion (fractional\n * 0-1 coordinates) so masked content \u2014 timestamps, live counters, ads \u2014\n * contributes nothing to any downstream pixel comparison. Shared by every\n * comparison path (whole-image hash, per-cell grid hash, AI vision) so a\n * region masked once is actually ignored everywhere, not just in the\n * cheapest check.\n */\nexport async function applyMask(\n imagePath: string,\n ignoredRegions: Array<{ x: number; y: number; width: number; height: number }>,\n): Promise<Buffer> {\n const meta = await sharp(imagePath).metadata();\n const W = meta.width ?? 1200;\n const H = meta.height ?? 800;\n\n const overlays: OverlayOptions[] = await Promise.all(\n ignoredRegions.map(async (r) => {\n const rW = Math.max(1, Math.round(r.width * W));\n const rH = Math.max(1, Math.round(r.height * H));\n const buf = await sharp({\n create: { width: rW, height: rH, channels: 3, background: { r: 128, g: 128, b: 128 } },\n }).png().toBuffer();\n return { input: buf, top: Math.round(r.y * H), left: Math.round(r.x * W) } as OverlayOptions;\n }),\n );\n\n return sharp(imagePath).composite(overlays).png().toBuffer();\n}\n\n/**\n * Compute an 8x8 average hash (aHash) of an image file, with optional mask overlays.\n * ignoredRegions are fractional (0-1 range), converted to pixels here.\n * Masked regions are filled with neutral grey (128,128,128) before hashing.\n */\nasync function hashWithMasks(\n imagePath: string,\n ignoredRegions: Array<{ x: number; y: number; width: number; height: number }> = [],\n): Promise<bigint> {\n const pipeline = ignoredRegions.length > 0\n ? sharp(await applyMask(imagePath, ignoredRegions))\n : sharp(imagePath);\n\n const buf = await pipeline.resize(8, 8, { fit: 'fill' }).greyscale().raw().toBuffer();\n const pixels = Array.from(buf);\n const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;\n return pixels.reduce((h, p, i) => (p >= mean ? h | (1n << BigInt(63 - i)) : h), 0n);\n}\n\nfunction hammingDistance(a: bigint, b: bigint): number {\n let xor = a ^ b;\n let count = 0;\n while (xor !== 0n) { count += Number(xor & 1n); xor >>= 1n; }\n return count;\n}\n\n/**\n * Generates a pixelmatch diff image highlighting changed pixels in red.\n * Applies the same ignoredRegions mask as screenshotDiffPercent so masked\n * zones don't show as \"changed\" in the visual output.\n * Returns a PNG Buffer suitable for upload to S3.\n */\nexport async function generatePixelmatchDiff(\n pathA: string,\n pathB: string,\n ignoredRegions: Array<{ x: number; y: number; width: number; height: number }> = [],\n): Promise<Buffer> {\n const metaA = await sharp(pathA).metadata();\n const W = metaA.width ?? 1200;\n const H = metaA.height ?? 800;\n\n const toRawRGBA = async (path: string): Promise<Buffer> => {\n const base = ignoredRegions.length > 0 ? sharp(await applyMask(path, ignoredRegions)) : sharp(path);\n const { data } = await base.resize(W, H, { fit: 'fill' }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });\n return data;\n };\n\n const [rawA, rawB] = await Promise.all([toRawRGBA(pathA), toRawRGBA(pathB)]);\n const { default: pixelmatch } = await import('pixelmatch');\n const diffData = Buffer.alloc(W * H * 4);\n pixelmatch(rawA, rawB, diffData, W, H, {\n threshold: 0.1,\n alpha: 0.3,\n diffColor: [255, 0, 0],\n aaColor: [255, 255, 0],\n });\n return sharp(diffData, { raw: { width: W, height: H, channels: 4 } }).png().toBuffer();\n}\n\nexport async function screenshotDiffPercent(\n pathA: string,\n pathB: string,\n ignoredRegions: Array<{ x: number; y: number; width: number; height: number }> = [],\n): Promise<{ pHashDistance: number; changeLevel: 'IDENTICAL' | 'MINOR' | 'MODERATE' | 'SIGNIFICANT' }> {\n const [hashA, hashB] = await Promise.all([\n hashWithMasks(pathA, ignoredRegions),\n hashWithMasks(pathB, ignoredRegions),\n ]);\n let xor = hashA ^ hashB;\n let dist = 0;\n while (xor !== 0n) { dist += Number(xor & 1n); xor >>= 1n; }\n const pct = parseFloat(((dist / 64) * 100).toFixed(1));\n const changeLevel = pct === 0 ? 'IDENTICAL' : pct < 10 ? 'MINOR' : pct < 30 ? 'MODERATE' : 'SIGNIFICANT';\n return { pHashDistance: pct, changeLevel };\n}\n\n/**\n * Grid-based layout analysis: divides screenshots into a 3\u00D73 grid and computes\n * pHash per cell. Returns which regions changed and where, for the VisualDiff.changes field.\n * Uses sharp's `extract` to crop each cell before hashing.\n */\nexport async function gridLayoutDiff(\n pathA: string,\n pathB: string,\n ignoredRegions: Array<{ x: number; y: number; width: number; height: number }> = [],\n): Promise<Array<{ location: string; severity: string; description: string }>> {\n const GRID_LABELS = [\n 'top-left', 'top-center', 'top-right',\n 'mid-left', 'mid-center', 'mid-right',\n 'bottom-left', 'bottom-center', 'bottom-right',\n ];\n\n try {\n // Mask before cropping into cells \u2014 a masked region (e.g. a timestamp)\n // then contributes nothing to any cell's hash, instead of only being\n // ignored by the separate whole-image hash in screenshotDiffPercent().\n const [sourceA, sourceB] = ignoredRegions.length > 0\n ? await Promise.all([applyMask(pathA, ignoredRegions), applyMask(pathB, ignoredRegions)])\n : [pathA, pathB];\n\n const [metaA, metaB] = await Promise.all([\n sharp(sourceA).metadata(),\n sharp(sourceB).metadata(),\n ]);\n const W = Math.min(metaA.width ?? 1200, metaB.width ?? 1200);\n const H = Math.min(metaA.height ?? 800, metaB.height ?? 800);\n const cW = Math.floor(W / 3);\n const cH = Math.floor(H / 3);\n\n const changes: Array<{ location: string; severity: string; description: string }> = [];\n\n for (let row = 0; row < 3; row++) {\n for (let col = 0; col < 3; col++) {\n const region = { left: col * cW, top: row * cH, width: cW, height: cH };\n const label = GRID_LABELS[row * 3 + col];\n\n const [hashA, hashB] = await Promise.all([\n sharp(sourceA).extract(region).resize(8, 8, { fit: 'fill' }).greyscale().raw().toBuffer()\n .then((buf) => {\n const pixels = Array.from(buf);\n const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;\n return pixels.reduce((h, p, i) => p >= mean ? h | (1n << BigInt(63 - i)) : h, 0n);\n }),\n sharp(sourceB).extract(region).resize(8, 8, { fit: 'fill' }).greyscale().raw().toBuffer()\n .then((buf) => {\n const pixels = Array.from(buf);\n const mean = pixels.reduce((s, p) => s + p, 0) / pixels.length;\n return pixels.reduce((h, p, i) => p >= mean ? h | (1n << BigInt(63 - i)) : h, 0n);\n }),\n ]);\n\n let xor = hashA ^ hashB;\n let dist = 0;\n while (xor !== 0n) { dist += Number(xor & 1n); xor >>= 1n; }\n const pct = (dist / 64) * 100;\n\n if (pct >= 5) {\n const severity = pct >= 20 ? 'HIGH' : pct >= 10 ? 'MEDIUM' : 'LOW';\n changes.push({\n location: label,\n severity,\n description: `${Math.round(pct)}% visual change in ${label.replace('-', ' ')} region`,\n });\n }\n }\n }\n return changes;\n } catch {\n return [];\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * PII scrubber — detects and redacts Personally Identifiable Information
3
+ * from crawled markdown, HTML, and extracted text before storage.
4
+ *
5
+ * GDPR Article 4(1): "any information relating to an identified or identifiable natural person"
6
+ */
7
+ export interface PiiMatch {
8
+ type: 'email' | 'phone' | 'ssn' | 'credit-card' | 'ip-address' | 'passport' | 'iban' | 'dob';
9
+ value: string;
10
+ start: number;
11
+ end: number;
12
+ }
13
+ export interface ScrubResult {
14
+ text: string;
15
+ matches: PiiMatch[];
16
+ scrubbed: boolean;
17
+ }
18
+ export declare function detectPii(text: string): PiiMatch[];
19
+ export declare function scrubPii(text: string): ScrubResult;
20
+ export declare function hasPii(text: string): boolean;
21
+ export declare function scrubObject<T extends Record<string, unknown>>(obj: T, fields: (keyof T)[]): T;
@@ -0,0 +1,75 @@
1
+ const PATTERNS = [
2
+ // Email — RFC 5321 simplified
3
+ { type: "email", re: /\b[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}\b/g },
4
+ // US SSN — 9 digits with dashes or spaces
5
+ { type: "ssn", re: /\b(?!000|666|9\d\d)\d{3}[-\s](?!00)\d{2}[-\s](?!0000)\d{4}\b/g },
6
+ // Credit card — Visa/MC/Amex/Discover with optional spaces/dashes
7
+ { type: "credit-card", re: /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{3,4}\b/g },
8
+ // IBAN — international bank account (15-34 chars)
9
+ { type: "iban", re: /\b[A-Z]{2}\d{2}[A-Z0-9]{1,30}\b/g },
10
+ // Phone — international or US formats
11
+ { type: "phone", re: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]\d{3}[-.\s]\d{4}\b|\+\d{1,3}[-.\s]\d{2,4}[-.\s]\d{3,4}[-.\s]\d{3,4}/g },
12
+ // Date of birth — common formats
13
+ { type: "dob", re: /\b(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[12]\d|3[01])\/(?:19|20)\d{2}\b/g },
14
+ // IPv4 private/internal addresses — only flag private-looking ones in content
15
+ { type: "ip-address", re: /\b(?:192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b/g }
16
+ ];
17
+ const REDACT_MAP = {
18
+ email: "[EMAIL]",
19
+ phone: "[PHONE]",
20
+ ssn: "[SSN]",
21
+ "credit-card": "[CARD]",
22
+ "ip-address": "[IP]",
23
+ passport: "[PASSPORT]",
24
+ iban: "[IBAN]",
25
+ dob: "[DOB]"
26
+ };
27
+ function detectPii(text) {
28
+ const matches = [];
29
+ const seen = /* @__PURE__ */ new Set();
30
+ for (const { type, re } of PATTERNS) {
31
+ const r = new RegExp(re.source, re.flags.includes("g") ? re.flags : re.flags + "g");
32
+ let m;
33
+ while ((m = r.exec(text)) !== null) {
34
+ const key = `${type}:${m.index}`;
35
+ if (!seen.has(key)) {
36
+ seen.add(key);
37
+ matches.push({ type, value: m[0], start: m.index, end: m.index + m[0].length });
38
+ }
39
+ }
40
+ }
41
+ return matches.sort((a, b) => a.start - b.start);
42
+ }
43
+ function scrubPii(text) {
44
+ const matches = detectPii(text);
45
+ if (matches.length === 0) return { text, matches: [], scrubbed: false };
46
+ let scrubbed = text;
47
+ for (let i = matches.length - 1; i >= 0; i--) {
48
+ const { type, start, end } = matches[i];
49
+ scrubbed = scrubbed.slice(0, start) + REDACT_MAP[type] + scrubbed.slice(end);
50
+ }
51
+ return { text: scrubbed, matches, scrubbed: true };
52
+ }
53
+ function hasPii(text) {
54
+ for (const { re } of PATTERNS) {
55
+ const r = new RegExp(re.source, re.flags);
56
+ if (r.test(text)) return true;
57
+ }
58
+ return false;
59
+ }
60
+ function scrubObject(obj, fields) {
61
+ const result = { ...obj };
62
+ for (const field of fields) {
63
+ if (typeof result[field] === "string") {
64
+ result[field] = scrubPii(result[field]).text;
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+ export {
70
+ detectPii,
71
+ hasPii,
72
+ scrubObject,
73
+ scrubPii
74
+ };
75
+ //# sourceMappingURL=pii-scrubber.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/pii-scrubber.ts"],
4
+ "sourcesContent": ["/**\n * PII scrubber \u2014 detects and redacts Personally Identifiable Information\n * from crawled markdown, HTML, and extracted text before storage.\n *\n * GDPR Article 4(1): \"any information relating to an identified or identifiable natural person\"\n */\n\nexport interface PiiMatch {\n type: 'email' | 'phone' | 'ssn' | 'credit-card' | 'ip-address' | 'passport' | 'iban' | 'dob';\n value: string;\n start: number;\n end: number;\n}\n\nexport interface ScrubResult {\n text: string;\n matches: PiiMatch[];\n scrubbed: boolean;\n}\n\n// Patterns \u2014 ordered from most-specific to least-specific\nconst PATTERNS: Array<{ type: PiiMatch['type']; re: RegExp }> = [\n // Email \u2014 RFC 5321 simplified\n { type: 'email', re: /\\b[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}\\b/g },\n // US SSN \u2014 9 digits with dashes or spaces\n { type: 'ssn', re: /\\b(?!000|666|9\\d\\d)\\d{3}[-\\s](?!00)\\d{2}[-\\s](?!0000)\\d{4}\\b/g },\n // Credit card \u2014 Visa/MC/Amex/Discover with optional spaces/dashes\n { type: 'credit-card', re: /\\b(?:4\\d{3}|5[1-5]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{3,4}\\b/g },\n // IBAN \u2014 international bank account (15-34 chars)\n { type: 'iban', re: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{1,30}\\b/g },\n // Phone \u2014 international or US formats\n { type: 'phone', re: /(?:\\+?1[-.\\s]?)?\\(?\\d{3}\\)?[-.\\s]\\d{3}[-.\\s]\\d{4}\\b|\\+\\d{1,3}[-.\\s]\\d{2,4}[-.\\s]\\d{3,4}[-.\\s]\\d{3,4}/g },\n // Date of birth \u2014 common formats\n { type: 'dob', re: /\\b(?:0?[1-9]|1[0-2])\\/(?:0?[1-9]|[12]\\d|3[01])\\/(?:19|20)\\d{2}\\b/g },\n // IPv4 private/internal addresses \u2014 only flag private-looking ones in content\n { type: 'ip-address', re: /\\b(?:192\\.168\\.\\d{1,3}\\.\\d{1,3}|10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.(?:1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3})\\b/g },\n];\n\nconst REDACT_MAP: Record<PiiMatch['type'], string> = {\n email: '[EMAIL]',\n phone: '[PHONE]',\n ssn: '[SSN]',\n 'credit-card': '[CARD]',\n 'ip-address': '[IP]',\n passport: '[PASSPORT]',\n iban: '[IBAN]',\n dob: '[DOB]',\n};\n\nexport function detectPii(text: string): PiiMatch[] {\n const matches: PiiMatch[] = [];\n const seen = new Set<string>();\n\n for (const { type, re } of PATTERNS) {\n const r = new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g');\n let m: RegExpExecArray | null;\n while ((m = r.exec(text)) !== null) {\n const key = `${type}:${m.index}`;\n if (!seen.has(key)) {\n seen.add(key);\n matches.push({ type, value: m[0], start: m.index, end: m.index + m[0].length });\n }\n }\n }\n\n return matches.sort((a, b) => a.start - b.start);\n}\n\nexport function scrubPii(text: string): ScrubResult {\n const matches = detectPii(text);\n if (matches.length === 0) return { text, matches: [], scrubbed: false };\n\n // Replace from end to start so indices stay valid\n let scrubbed = text;\n for (let i = matches.length - 1; i >= 0; i--) {\n const { type, start, end } = matches[i];\n scrubbed = scrubbed.slice(0, start) + REDACT_MAP[type] + scrubbed.slice(end);\n }\n\n return { text: scrubbed, matches, scrubbed: true };\n}\n\nexport function hasPii(text: string): boolean {\n for (const { re } of PATTERNS) {\n const r = new RegExp(re.source, re.flags);\n if (r.test(text)) return true;\n }\n return false;\n}\n\nexport function scrubObject<T extends Record<string, unknown>>(obj: T, fields: (keyof T)[]): T {\n const result = { ...obj };\n for (const field of fields) {\n if (typeof result[field] === 'string') {\n (result as any)[field] = scrubPii(result[field] as string).text;\n }\n }\n return result;\n}\n"],
5
+ "mappings": "AAqBA,MAAM,WAA0D;AAAA;AAAA,EAE9D,EAAE,MAAM,SAAS,IAAI,wDAAwD;AAAA;AAAA,EAE7E,EAAE,MAAM,OAAO,IAAI,gEAAgE;AAAA;AAAA,EAEnF,EAAE,MAAM,eAAe,IAAI,4FAA4F;AAAA;AAAA,EAEvH,EAAE,MAAM,QAAQ,IAAI,mCAAmC;AAAA;AAAA,EAEvD,EAAE,MAAM,SAAS,IAAI,wGAAwG;AAAA;AAAA,EAE7H,EAAE,MAAM,OAAO,IAAI,oEAAoE;AAAA;AAAA,EAEvF,EAAE,MAAM,cAAc,IAAI,gHAAgH;AAC5I;AAEA,MAAM,aAA+C;AAAA,EACnD,OAAO;AAAA,EACP,OAAO;AAAA,EACP,KAAK;AAAA,EACL,eAAe;AAAA,EACf,cAAc;AAAA,EACd,UAAU;AAAA,EACV,MAAM;AAAA,EACN,KAAK;AACP;AAEO,SAAS,UAAU,MAA0B;AAClD,QAAM,UAAsB,CAAC;AAC7B,QAAM,OAAO,oBAAI,IAAY;AAE7B,aAAW,EAAE,MAAM,GAAG,KAAK,UAAU;AACnC,UAAM,IAAI,IAAI,OAAO,GAAG,QAAQ,GAAG,MAAM,SAAS,GAAG,IAAI,GAAG,QAAQ,GAAG,QAAQ,GAAG;AAClF,QAAI;AACJ,YAAQ,IAAI,EAAE,KAAK,IAAI,OAAO,MAAM;AAClC,YAAM,MAAM,GAAG,IAAI,IAAI,EAAE,KAAK;AAC9B,UAAI,CAAC,KAAK,IAAI,GAAG,GAAG;AAClB,aAAK,IAAI,GAAG;AACZ,gBAAQ,KAAK,EAAE,MAAM,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,OAAO,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,CAAC;AAAA,MAChF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACjD;AAEO,SAAS,SAAS,MAA2B;AAClD,QAAM,UAAU,UAAU,IAAI;AAC9B,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,SAAS,CAAC,GAAG,UAAU,MAAM;AAGtE,MAAI,WAAW;AACf,WAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,UAAM,EAAE,MAAM,OAAO,IAAI,IAAI,QAAQ,CAAC;AACtC,eAAW,SAAS,MAAM,GAAG,KAAK,IAAI,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG;AAAA,EAC7E;AAEA,SAAO,EAAE,MAAM,UAAU,SAAS,UAAU,KAAK;AACnD;AAEO,SAAS,OAAO,MAAuB;AAC5C,aAAW,EAAE,GAAG,KAAK,UAAU;AAC7B,UAAM,IAAI,IAAI,OAAO,GAAG,QAAQ,GAAG,KAAK;AACxC,QAAI,EAAE,KAAK,IAAI,EAAG,QAAO;AAAA,EAC3B;AACA,SAAO;AACT;AAEO,SAAS,YAA+C,KAAQ,QAAwB;AAC7F,QAAM,SAAS,EAAE,GAAG,IAAI;AACxB,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,OAAO,KAAK,MAAM,UAAU;AACrC,MAAC,OAAe,KAAK,IAAI,SAAS,OAAO,KAAK,CAAW,EAAE;AAAA,IAC7D;AAAA,EACF;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Playwright test recorder — generate Playwright test code from crawl data.
3
+ *
4
+ * Given a list of crawled screens, generates a minimal Playwright test file that:
5
+ * 1. Navigates to each screen URL
6
+ * 2. Verifies the page title
7
+ * 3. Checks key elements visible on the page
8
+ * 4. Verifies no console errors (optional)
9
+ *
10
+ * This is a "smoke test" generator — not a full interaction recorder.
11
+ * Output: valid TypeScript Playwright test file ready to run with npx playwright test.
12
+ */
13
+ export interface RecorderScreen {
14
+ id: string;
15
+ name: string;
16
+ url: string;
17
+ title?: string;
18
+ heading?: string;
19
+ markdownWordCount?: number;
20
+ smartLocator?: {
21
+ tag: string;
22
+ text?: string;
23
+ role?: string;
24
+ placeholder?: string;
25
+ nearText?: string;
26
+ };
27
+ }
28
+ export interface RecorderOpts {
29
+ baseUrl?: string;
30
+ includeConsoleCheck?: boolean;
31
+ testTimeout?: number;
32
+ projectName?: string;
33
+ useSmartLocators?: boolean;
34
+ }
35
+ export declare function generatePlaywrightTest(screens: RecorderScreen[], opts?: RecorderOpts): string;
36
+ export declare function generatePlaywrightConfig(opts?: {
37
+ baseUrl?: string;
38
+ reportDir?: string;
39
+ }): string;
@@ -0,0 +1,123 @@
1
+ function buildSmartLocatorHelper() {
2
+ return `
3
+ // smartLocator \u2014 falls back to similarity search when primary selector fails.
4
+ // Generated by ZeTa Crawler. Do not edit manually.
5
+ async function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {
6
+ try {
7
+ const el = page.locator(primary);
8
+ if (await el.count() > 0) return el;
9
+ } catch { /* fall through */ }
10
+ // Fallback: find by ARIA role + text
11
+ if (ref.role && ref.text) {
12
+ const byRole = page.getByRole(ref.role as any, { name: ref.text, exact: false });
13
+ if (await byRole.count() > 0) return byRole;
14
+ }
15
+ // Fallback: find by text content
16
+ if (ref.text) {
17
+ const byText = page.getByText(ref.text, { exact: false });
18
+ if (await byText.count() > 0) return byText;
19
+ }
20
+ // Fallback: find by placeholder
21
+ if (ref.placeholder) {
22
+ const byPlaceholder = page.getByPlaceholder(ref.placeholder, { exact: false });
23
+ if (await byPlaceholder.count() > 0) return byPlaceholder;
24
+ }
25
+ // Last resort: return the primary (will fail with original error)
26
+ return page.locator(primary);
27
+ }`.trim();
28
+ }
29
+ function sanitizeTestName(name) {
30
+ return name.replace(/[^a-zA-Z0-9 _-]/g, "").trim().slice(0, 80) || "Screen";
31
+ }
32
+ function escapeString(s) {
33
+ return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/`/g, "\\`");
34
+ }
35
+ function generatePlaywrightTest(screens, opts = {}) {
36
+ const {
37
+ includeConsoleCheck = false,
38
+ testTimeout = 3e4,
39
+ projectName = "ZeTa Crawl",
40
+ useSmartLocators = true
41
+ } = opts;
42
+ const needsSmartLocator = useSmartLocators && screens.some((s) => s.smartLocator != null);
43
+ const lines = [
44
+ `// Generated by ZeTa Crawler \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`,
45
+ `// Project: ${escapeString(projectName)}`,
46
+ `// Screens: ${screens.length}`,
47
+ `// Run: npx playwright test`,
48
+ "",
49
+ "import { test, expect } from '@playwright/test';",
50
+ ""
51
+ ];
52
+ if (needsSmartLocator) {
53
+ lines.push(buildSmartLocatorHelper(), "");
54
+ }
55
+ lines.push(`test.setTimeout(${testTimeout});`, "");
56
+ if (includeConsoleCheck) {
57
+ lines.push(
58
+ `test.beforeEach(async ({ page }) => {`,
59
+ ` const errors: string[] = [];`,
60
+ ` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,
61
+ ` page.on('pageerror', err => errors.push(err.message));`,
62
+ ` (page as any).__consoleErrors = errors;`,
63
+ `});`,
64
+ ""
65
+ );
66
+ }
67
+ for (const screen of screens) {
68
+ const testName = sanitizeTestName(screen.name);
69
+ lines.push(
70
+ `test('${escapeString(testName)}', async ({ page }) => {`,
71
+ ` await page.goto('${escapeString(screen.url)}');`,
72
+ ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/);`
73
+ );
74
+ if (screen.title) {
75
+ lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))}/i);`);
76
+ }
77
+ if (screen.heading) {
78
+ const h = escapeString(screen.heading.slice(0, 60));
79
+ if (needsSmartLocator && screen.smartLocator) {
80
+ const tag = escapeString(screen.smartLocator.tag);
81
+ const text = escapeString(screen.smartLocator.text ?? "");
82
+ lines.push(
83
+ ` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,
84
+ ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`
85
+ );
86
+ } else {
87
+ lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);
88
+ }
89
+ }
90
+ if (includeConsoleCheck) {
91
+ lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);
92
+ }
93
+ lines.push(`});`, "");
94
+ }
95
+ return lines.join("\n");
96
+ }
97
+ function generatePlaywrightConfig(opts = {}) {
98
+ const { baseUrl = "http://localhost:3000", reportDir = "playwright-report" } = opts;
99
+ return `// playwright.config.ts \u2014 generated by ZeTa Crawler
100
+ import { defineConfig } from '@playwright/test';
101
+
102
+ export default defineConfig({
103
+ testDir: './tests',
104
+ reporter: [['html', { outputFolder: '${reportDir}' }]],
105
+ use: {
106
+ baseURL: '${baseUrl}',
107
+ headless: true,
108
+ screenshot: 'only-on-failure',
109
+ video: 'retain-on-failure',
110
+ },
111
+ projects: [
112
+ { name: 'chromium', use: { browserName: 'chromium' } },
113
+ { name: 'firefox', use: { browserName: 'firefox' } },
114
+ { name: 'webkit', use: { browserName: 'webkit' } },
115
+ ],
116
+ });
117
+ `;
118
+ }
119
+ export {
120
+ generatePlaywrightConfig,
121
+ generatePlaywrightTest
122
+ };
123
+ //# sourceMappingURL=playwright-recorder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/playwright-recorder.ts"],
4
+ "sourcesContent": ["/**\n * Playwright test recorder \u2014 generate Playwright test code from crawl data.\n *\n * Given a list of crawled screens, generates a minimal Playwright test file that:\n * 1. Navigates to each screen URL\n * 2. Verifies the page title\n * 3. Checks key elements visible on the page\n * 4. Verifies no console errors (optional)\n *\n * This is a \"smoke test\" generator \u2014 not a full interaction recorder.\n * Output: valid TypeScript Playwright test file ready to run with npx playwright test.\n */\n\nexport interface RecorderScreen {\n id: string;\n name: string;\n url: string;\n title?: string;\n heading?: string;\n markdownWordCount?: number;\n smartLocator?: {\n tag: string; // e.g. 'button', 'a', 'input'\n text?: string; // visible text content\n role?: string; // ARIA role\n placeholder?: string; // for inputs\n nearText?: string; // nearby label text\n };\n}\n\nexport interface RecorderOpts {\n baseUrl?: string;\n includeConsoleCheck?: boolean;\n testTimeout?: number; // ms, default 30000\n projectName?: string;\n useSmartLocators?: boolean; // default true\n}\n\nfunction buildSmartLocatorHelper(): string {\n return `\n// smartLocator \u2014 falls back to similarity search when primary selector fails.\n// Generated by ZeTa Crawler. Do not edit manually.\nasync function smartLocator(page: any, primary: string, ref: { tag?: string; text?: string; role?: string; placeholder?: string }) {\n try {\n const el = page.locator(primary);\n if (await el.count() > 0) return el;\n } catch { /* fall through */ }\n // Fallback: find by ARIA role + text\n if (ref.role && ref.text) {\n const byRole = page.getByRole(ref.role as any, { name: ref.text, exact: false });\n if (await byRole.count() > 0) return byRole;\n }\n // Fallback: find by text content\n if (ref.text) {\n const byText = page.getByText(ref.text, { exact: false });\n if (await byText.count() > 0) return byText;\n }\n // Fallback: find by placeholder\n if (ref.placeholder) {\n const byPlaceholder = page.getByPlaceholder(ref.placeholder, { exact: false });\n if (await byPlaceholder.count() > 0) return byPlaceholder;\n }\n // Last resort: return the primary (will fail with original error)\n return page.locator(primary);\n}`.trim();\n}\n\nfunction sanitizeTestName(name: string): string {\n return name.replace(/[^a-zA-Z0-9 _-]/g, '').trim().slice(0, 80) || 'Screen';\n}\n\nfunction escapeString(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\").replace(/`/g, '\\\\`');\n}\n\nexport function generatePlaywrightTest(screens: RecorderScreen[], opts: RecorderOpts = {}): string {\n const {\n includeConsoleCheck = false,\n testTimeout = 30_000,\n projectName = 'ZeTa Crawl',\n useSmartLocators = true,\n } = opts;\n\n const needsSmartLocator = useSmartLocators && screens.some(s => s.smartLocator != null);\n\n const lines: string[] = [\n `// Generated by ZeTa Crawler \u2014 ${new Date().toISOString()}`,\n `// Project: ${escapeString(projectName)}`,\n `// Screens: ${screens.length}`,\n `// Run: npx playwright test`,\n '',\n \"import { test, expect } from '@playwright/test';\",\n '',\n ];\n\n if (needsSmartLocator) {\n lines.push(buildSmartLocatorHelper(), '');\n }\n\n lines.push(`test.setTimeout(${testTimeout});`, '');\n\n if (includeConsoleCheck) {\n lines.push(\n `test.beforeEach(async ({ page }) => {`,\n ` const errors: string[] = [];`,\n ` page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); });`,\n ` page.on('pageerror', err => errors.push(err.message));`,\n ` (page as any).__consoleErrors = errors;`,\n `});`,\n '',\n );\n }\n\n for (const screen of screens) {\n const testName = sanitizeTestName(screen.name);\n lines.push(\n `test('${escapeString(testName)}', async ({ page }) => {`,\n ` await page.goto('${escapeString(screen.url)}');`,\n ` await expect(page).toHaveURL(/${escapeString(screen.url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))}/);`,\n );\n\n if (screen.title) {\n lines.push(` await expect(page).toHaveTitle(/${escapeString(screen.title.slice(0, 40).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))}/i);`);\n }\n\n if (screen.heading) {\n const h = escapeString(screen.heading.slice(0, 60));\n if (needsSmartLocator && screen.smartLocator) {\n const tag = escapeString(screen.smartLocator.tag);\n const text = escapeString(screen.smartLocator.text ?? '');\n lines.push(\n ` const headingEl = await smartLocator(page, 'h1, h2, h3', { tag: '${tag}', text: '${text}' });`,\n ` await expect(headingEl.first()).toContainText('${h}', { ignoreCase: true });`,\n );\n } else {\n lines.push(` await expect(page.locator('h1, h2, h3').first()).toContainText('${h}', { ignoreCase: true });`);\n }\n }\n\n if (includeConsoleCheck) {\n lines.push(` expect((page as any).__consoleErrors ?? []).toHaveLength(0);`);\n }\n\n lines.push(`});`, '');\n }\n\n return lines.join('\\n');\n}\n\nexport function generatePlaywrightConfig(opts: { baseUrl?: string; reportDir?: string } = {}): string {\n const { baseUrl = 'http://localhost:3000', reportDir = 'playwright-report' } = opts;\n return `// playwright.config.ts \u2014 generated by ZeTa Crawler\nimport { defineConfig } from '@playwright/test';\n\nexport default defineConfig({\n testDir: './tests',\n reporter: [['html', { outputFolder: '${reportDir}' }]],\n use: {\n baseURL: '${baseUrl}',\n headless: true,\n screenshot: 'only-on-failure',\n video: 'retain-on-failure',\n },\n projects: [\n { name: 'chromium', use: { browserName: 'chromium' } },\n { name: 'firefox', use: { browserName: 'firefox' } },\n { name: 'webkit', use: { browserName: 'webkit' } },\n ],\n});\n`;\n}\n"],
5
+ "mappings": "AAqCA,SAAS,0BAAkC;AACzC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAyBN,KAAK;AACR;AAEA,SAAS,iBAAiB,MAAsB;AAC9C,SAAO,KAAK,QAAQ,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,EAAE,KAAK;AACrE;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM,KAAK;AAC1E;AAEO,SAAS,uBAAuB,SAA2B,OAAqB,CAAC,GAAW;AACjG,QAAM;AAAA,IACJ,sBAAsB;AAAA,IACtB,cAAc;AAAA,IACd,cAAc;AAAA,IACd,mBAAmB;AAAA,EACrB,IAAI;AAEJ,QAAM,oBAAoB,oBAAoB,QAAQ,KAAK,OAAK,EAAE,gBAAgB,IAAI;AAEtF,QAAM,QAAkB;AAAA,IACtB,wCAAkC,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IAC1D,eAAe,aAAa,WAAW,CAAC;AAAA,IACxC,eAAe,QAAQ,MAAM;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,mBAAmB;AACrB,UAAM,KAAK,wBAAwB,GAAG,EAAE;AAAA,EAC1C;AAEA,QAAM,KAAK,mBAAmB,WAAW,MAAM,EAAE;AAEjD,MAAI,qBAAqB;AACvB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,iBAAiB,OAAO,IAAI;AAC7C,UAAM;AAAA,MACJ,SAAS,aAAa,QAAQ,CAAC;AAAA,MAC/B,sBAAsB,aAAa,OAAO,GAAG,CAAC;AAAA,MAC9C,mCAAmC,aAAa,OAAO,IAAI,QAAQ,uBAAuB,MAAM,CAAC,CAAC;AAAA,IACpG;AAEA,QAAI,OAAO,OAAO;AAChB,YAAM,KAAK,qCAAqC,aAAa,OAAO,MAAM,MAAM,GAAG,EAAE,EAAE,QAAQ,uBAAuB,MAAM,CAAC,CAAC,MAAM;AAAA,IACtI;AAEA,QAAI,OAAO,SAAS;AAClB,YAAM,IAAI,aAAa,OAAO,QAAQ,MAAM,GAAG,EAAE,CAAC;AAClD,UAAI,qBAAqB,OAAO,cAAc;AAC5C,cAAM,MAAM,aAAa,OAAO,aAAa,GAAG;AAChD,cAAM,OAAO,aAAa,OAAO,aAAa,QAAQ,EAAE;AACxD,cAAM;AAAA,UACJ,sEAAsE,GAAG,aAAa,IAAI;AAAA,UAC1F,oDAAoD,CAAC;AAAA,QACvD;AAAA,MACF,OAAO;AACL,cAAM,KAAK,qEAAqE,CAAC,2BAA2B;AAAA,MAC9G;AAAA,IACF;AAEA,QAAI,qBAAqB;AACvB,YAAM,KAAK,gEAAgE;AAAA,IAC7E;AAEA,UAAM,KAAK,OAAO,EAAE;AAAA,EACtB;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,yBAAyB,OAAiD,CAAC,GAAW;AACpG,QAAM,EAAE,UAAU,yBAAyB,YAAY,oBAAoB,IAAI;AAC/E,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,yCAKgC,SAAS;AAAA;AAAA,gBAElC,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYvB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { DomainProfile } from '../profiles/domain-profile.js';
2
+ export interface CrawlSafetyDecision {
3
+ allowed: boolean;
4
+ reason?: string;
5
+ severity: 'info' | 'warn' | 'block';
6
+ }
7
+ export declare function assessActionSafety(label: string, profile: DomainProfile): CrawlSafetyDecision;
8
+ export declare function redactSensitiveValue(value: string): string;
9
+ export declare function redactJson(value: unknown): unknown;
@@ -0,0 +1,41 @@
1
+ const _extraDestructive = process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS ? process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS.split(",").map((p) => p.trim()).filter(Boolean) : [];
2
+ const DESTRUCTIVE_ACTION = new RegExp(
3
+ `\\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop${_extraDestructive.length ? `|${_extraDestructive.join("|")}` : ""})\\b`,
4
+ "i"
5
+ );
6
+ const SECRET_LIKE = /\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\b/i;
7
+ function assessActionSafety(label, profile) {
8
+ const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));
9
+ if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {
10
+ return {
11
+ allowed: false,
12
+ severity: "block",
13
+ reason: `Blocked risky action "${label}" for ${profile.industry} profile.`
14
+ };
15
+ }
16
+ return { allowed: true, severity: "info" };
17
+ }
18
+ function redactSensitiveValue(value) {
19
+ let redacted = value.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[email]").replace(/\b(?:\+?\d[\d -]{8,}\d)\b/g, "[phone-or-id]").replace(/\b(?:\d[ -]*?){13,19}\b/g, "[card-like-number]").replace(/\b[A-Za-z0-9_-]{24,}\b/g, "[token-like-value]");
20
+ if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\s*[^,\s}]+/g, ": [redacted]");
21
+ return redacted;
22
+ }
23
+ function redactJson(value) {
24
+ if (typeof value === "string") return redactSensitiveValue(value);
25
+ if (Array.isArray(value)) return value.map(redactJson);
26
+ if (value && typeof value === "object") {
27
+ const out = {};
28
+ for (const [key, item] of Object.entries(value)) {
29
+ if (SECRET_LIKE.test(key)) out[key] = "[redacted]";
30
+ else out[key] = redactJson(item);
31
+ }
32
+ return out;
33
+ }
34
+ return value;
35
+ }
36
+ export {
37
+ assessActionSafety,
38
+ redactJson,
39
+ redactSensitiveValue
40
+ };
41
+ //# sourceMappingURL=crawl-safety.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/policies/crawl-safety.ts"],
4
+ "sourcesContent": ["import type { DomainProfile } from '../profiles/domain-profile.js';\n\nexport interface CrawlSafetyDecision {\n allowed: boolean;\n reason?: string;\n severity: 'info' | 'warn' | 'block';\n}\n\nconst _extraDestructive = process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS\n ? process.env.CRAWL_EXTRA_DESTRUCTIVE_PATTERNS.split(',').map((p) => p.trim()).filter(Boolean)\n : [];\nconst DESTRUCTIVE_ACTION = new RegExp(\n `\\\\b(delete|remove|destroy|deactivate|disable|cancel|refund|void|transfer|withdraw|pay now|place order|submit order|confirm payment|send money|publish|approve|reject|bulk update|drop${_extraDestructive.length ? `|${_extraDestructive.join('|')}` : ''})\\\\b`,\n 'i',\n);\nconst SECRET_LIKE = /\\b(api[_ -]?key|secret|token|password|authorization|cookie|set-cookie)\\b/i;\n\nexport function assessActionSafety(label: string, profile: DomainProfile): CrawlSafetyDecision {\n const riskyByProfile = profile.riskyActions.some((risk) => label.toLowerCase().includes(risk.toLowerCase()));\n if (DESTRUCTIVE_ACTION.test(label) || riskyByProfile) {\n return {\n allowed: false,\n severity: 'block',\n reason: `Blocked risky action \"${label}\" for ${profile.industry} profile.`,\n };\n }\n return { allowed: true, severity: 'info' };\n}\n\nexport function redactSensitiveValue(value: string): string {\n let redacted = value\n .replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi, '[email]')\n .replace(/\\b(?:\\+?\\d[\\d -]{8,}\\d)\\b/g, '[phone-or-id]')\n .replace(/\\b(?:\\d[ -]*?){13,19}\\b/g, '[card-like-number]')\n .replace(/\\b[A-Za-z0-9_-]{24,}\\b/g, '[token-like-value]');\n if (SECRET_LIKE.test(redacted)) redacted = redacted.replace(/:\\s*[^,\\s}]+/g, ': [redacted]');\n return redacted;\n}\n\nexport function redactJson(value: unknown): unknown {\n if (typeof value === 'string') return redactSensitiveValue(value);\n if (Array.isArray(value)) return value.map(redactJson);\n if (value && typeof value === 'object') {\n const out: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value)) {\n if (SECRET_LIKE.test(key)) out[key] = '[redacted]';\n else out[key] = redactJson(item);\n }\n return out;\n }\n return value;\n}\n"],
5
+ "mappings": "AAQA,MAAM,oBAAoB,QAAQ,IAAI,mCAClC,QAAQ,IAAI,iCAAiC,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAC3F,CAAC;AACL,MAAM,qBAAqB,IAAI;AAAA,EAC7B,wLAAwL,kBAAkB,SAAS,IAAI,kBAAkB,KAAK,GAAG,CAAC,KAAK,EAAE;AAAA,EACzP;AACF;AACA,MAAM,cAAc;AAEb,SAAS,mBAAmB,OAAe,SAA6C;AAC7F,QAAM,iBAAiB,QAAQ,aAAa,KAAK,CAAC,SAAS,MAAM,YAAY,EAAE,SAAS,KAAK,YAAY,CAAC,CAAC;AAC3G,MAAI,mBAAmB,KAAK,KAAK,KAAK,gBAAgB;AACpD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,UAAU;AAAA,MACV,QAAQ,yBAAyB,KAAK,SAAS,QAAQ,QAAQ;AAAA,IACjE;AAAA,EACF;AACA,SAAO,EAAE,SAAS,MAAM,UAAU,OAAO;AAC3C;AAEO,SAAS,qBAAqB,OAAuB;AAC1D,MAAI,WAAW,MACZ,QAAQ,2CAA2C,SAAS,EAC5D,QAAQ,8BAA8B,eAAe,EACrD,QAAQ,4BAA4B,oBAAoB,EACxD,QAAQ,2BAA2B,oBAAoB;AAC1D,MAAI,YAAY,KAAK,QAAQ,EAAG,YAAW,SAAS,QAAQ,iBAAiB,cAAc;AAC3F,SAAO;AACT;AAEO,SAAS,WAAW,OAAyB;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,qBAAqB,KAAK;AAChE,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,UAAU;AACrD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC/C,UAAI,YAAY,KAAK,GAAG,EAAG,KAAI,GAAG,IAAI;AAAA,UACjC,KAAI,GAAG,IAAI,WAAW,IAAI;AAAA,IACjC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ export type Industry = 'ecommerce' | 'banking' | 'fintech' | 'healthcare' | 'pharma' | 'agriculture' | 'legacy-enterprise' | 'saas' | 'unknown';
2
+ export interface DomainProfile {
3
+ industry: Industry;
4
+ confidence: number;
5
+ importantScreenPatterns: string[];
6
+ workflowGoals: string[];
7
+ piiRules: string[];
8
+ riskyActions: string[];
9
+ safeProbeRules: string[];
10
+ requiredArtifacts: string[];
11
+ evidence: string[];
12
+ }
13
+ export declare function classifyDomainProfile(input: {
14
+ appUrl: string;
15
+ projectName?: string | null;
16
+ appType?: string | null;
17
+ seedText?: string;
18
+ }): DomainProfile;
19
+ export declare function getProfile(industry: Industry): DomainProfile;