@detiq/crawler 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +49 -0
- package/README.md +11 -0
- package/dist/accessibility-auditor.d.ts +29 -0
- package/dist/accessibility-auditor.js +57 -0
- package/dist/accessibility-auditor.js.map +7 -0
- package/dist/ai-login.d.ts +32 -0
- package/dist/ai-login.js +177 -0
- package/dist/ai-login.js.map +7 -0
- package/dist/auth-token-rotator.d.ts +47 -0
- package/dist/auth-token-rotator.js +58 -0
- package/dist/auth-token-rotator.js.map +7 -0
- package/dist/auth.d.ts +20 -0
- package/dist/auth.js +329 -0
- package/dist/auth.js.map +7 -0
- package/dist/browser-extension.d.ts +37 -0
- package/dist/browser-extension.js +113 -0
- package/dist/browser-extension.js.map +7 -0
- package/dist/captcha-solver.d.ts +16 -0
- package/dist/captcha-solver.js +105 -0
- package/dist/captcha-solver.js.map +7 -0
- package/dist/color-contrast.d.ts +30 -0
- package/dist/color-contrast.js +78 -0
- package/dist/color-contrast.js.map +7 -0
- package/dist/content-chunker.d.ts +18 -0
- package/dist/content-chunker.js +152 -0
- package/dist/content-chunker.js.map +7 -0
- package/dist/content-dedup.d.ts +23 -0
- package/dist/content-dedup.js +60 -0
- package/dist/content-dedup.js.map +7 -0
- package/dist/cookie-consent.d.ts +25 -0
- package/dist/cookie-consent.js +78 -0
- package/dist/cookie-consent.js.map +7 -0
- package/dist/cookie-jar.d.ts +20 -0
- package/dist/cookie-jar.js +61 -0
- package/dist/cookie-jar.js.map +7 -0
- package/dist/crawler.d.ts +179 -0
- package/dist/crawler.js +3672 -0
- package/dist/crawler.js.map +7 -0
- package/dist/custom-js-injector.d.ts +42 -0
- package/dist/custom-js-injector.js +50 -0
- package/dist/custom-js-injector.js.map +7 -0
- package/dist/dark-mode-capture.d.ts +23 -0
- package/dist/dark-mode-capture.js +75 -0
- package/dist/dark-mode-capture.js.map +7 -0
- package/dist/dead-link-detector.d.ts +37 -0
- package/dist/dead-link-detector.js +50 -0
- package/dist/dead-link-detector.js.map +7 -0
- package/dist/element-hash.d.ts +9 -0
- package/dist/element-hash.js +9 -0
- package/dist/element-hash.js.map +7 -0
- package/dist/email-otp.d.ts +42 -0
- package/dist/email-otp.js +177 -0
- package/dist/email-otp.js.map +7 -0
- package/dist/engine-router.d.ts +47 -0
- package/dist/engine-router.js +94 -0
- package/dist/engine-router.js.map +7 -0
- package/dist/export/bulk-export.d.ts +26 -0
- package/dist/export/bulk-export.js +76 -0
- package/dist/export/bulk-export.js.map +7 -0
- package/dist/export/vector-export.d.ts +76 -0
- package/dist/export/vector-export.js +85 -0
- package/dist/export/vector-export.js.map +7 -0
- package/dist/extractors/advanced.d.ts +54 -0
- package/dist/extractors/advanced.js +245 -0
- package/dist/extractors/advanced.js.map +7 -0
- package/dist/extractors/artifact-envelope.d.ts +63 -0
- package/dist/extractors/artifact-envelope.js +63 -0
- package/dist/extractors/artifact-envelope.js.map +7 -0
- package/dist/failure-analysis-agent.d.ts +16 -0
- package/dist/failure-analysis-agent.js +40 -0
- package/dist/failure-analysis-agent.js.map +7 -0
- package/dist/feature-flag-detector.d.ts +30 -0
- package/dist/feature-flag-detector.js +55 -0
- package/dist/feature-flag-detector.js.map +7 -0
- package/dist/form-detector.d.ts +34 -0
- package/dist/form-detector.js +58 -0
- package/dist/form-detector.js.map +7 -0
- package/dist/frontier.d.ts +32 -0
- package/dist/frontier.js +83 -0
- package/dist/frontier.js.map +7 -0
- package/dist/gap-pass.d.ts +35 -0
- package/dist/gap-pass.js +81 -0
- package/dist/gap-pass.js.map +7 -0
- package/dist/graphql-detector.d.ts +39 -0
- package/dist/graphql-detector.js +86 -0
- package/dist/graphql-detector.js.map +7 -0
- package/dist/http-engine.d.ts +80 -0
- package/dist/http-engine.js +304 -0
- package/dist/http-engine.js.map +7 -0
- package/dist/incremental-diff.d.ts +59 -0
- package/dist/incremental-diff.js +61 -0
- package/dist/incremental-diff.js.map +7 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +7 -0
- package/dist/input-fuzzer.d.ts +30 -0
- package/dist/input-fuzzer.js +105 -0
- package/dist/input-fuzzer.js.map +7 -0
- package/dist/interactive-probe.d.ts +104 -0
- package/dist/interactive-probe.js +442 -0
- package/dist/interactive-probe.js.map +7 -0
- package/dist/job-queue.d.ts +171 -0
- package/dist/job-queue.js +2256 -0
- package/dist/job-queue.js.map +7 -0
- package/dist/js-coverage.d.ts +29 -0
- package/dist/js-coverage.js +56 -0
- package/dist/js-coverage.js.map +7 -0
- package/dist/js-router-detector.d.ts +17 -0
- package/dist/js-router-detector.js +90 -0
- package/dist/js-router-detector.js.map +7 -0
- package/dist/language-detector.d.ts +22 -0
- package/dist/language-detector.js +57 -0
- package/dist/language-detector.js.map +7 -0
- package/dist/link-classifier.d.ts +23 -0
- package/dist/link-classifier.js +40 -0
- package/dist/link-classifier.js.map +7 -0
- package/dist/live-view.d.ts +36 -0
- package/dist/live-view.js +100 -0
- package/dist/live-view.js.map +7 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +7 -0
- package/dist/markdown-extract.d.ts +8 -0
- package/dist/markdown-extract.js +54 -0
- package/dist/markdown-extract.js.map +7 -0
- package/dist/native-crawler.d.ts +69 -0
- package/dist/native-crawler.js +839 -0
- package/dist/native-crawler.js.map +7 -0
- package/dist/network-observer.d.ts +49 -0
- package/dist/network-observer.js +149 -0
- package/dist/network-observer.js.map +7 -0
- package/dist/network-throttle.d.ts +25 -0
- package/dist/network-throttle.js +34 -0
- package/dist/network-throttle.js.map +7 -0
- package/dist/openapi-generator.d.ts +49 -0
- package/dist/openapi-generator.js +89 -0
- package/dist/openapi-generator.js.map +7 -0
- package/dist/pdf-extractor.d.ts +28 -0
- package/dist/pdf-extractor.js +81 -0
- package/dist/pdf-extractor.js.map +7 -0
- package/dist/performance-metrics.d.ts +53 -0
- package/dist/performance-metrics.js +83 -0
- package/dist/performance-metrics.js.map +7 -0
- package/dist/phash.d.ts +38 -0
- package/dist/phash.js +115 -0
- package/dist/phash.js.map +7 -0
- package/dist/pii-scrubber.d.ts +21 -0
- package/dist/pii-scrubber.js +75 -0
- package/dist/pii-scrubber.js.map +7 -0
- package/dist/playwright-recorder.d.ts +39 -0
- package/dist/playwright-recorder.js +123 -0
- package/dist/playwright-recorder.js.map +7 -0
- package/dist/policies/crawl-safety.d.ts +9 -0
- package/dist/policies/crawl-safety.js +37 -0
- package/dist/policies/crawl-safety.js.map +7 -0
- package/dist/profiles/domain-profile.d.ts +19 -0
- package/dist/profiles/domain-profile.js +140 -0
- package/dist/profiles/domain-profile.js.map +7 -0
- package/dist/proxy.d.ts +50 -0
- package/dist/proxy.js +165 -0
- package/dist/proxy.js.map +7 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.js +64 -0
- package/dist/rate-limiter.js.map +7 -0
- package/dist/redirect-tracker.d.ts +37 -0
- package/dist/redirect-tracker.js +65 -0
- package/dist/redirect-tracker.js.map +7 -0
- package/dist/redis-frontier.d.ts +48 -0
- package/dist/redis-frontier.js +126 -0
- package/dist/redis-frontier.js.map +7 -0
- package/dist/redis-queue.d.ts +48 -0
- package/dist/redis-queue.js +165 -0
- package/dist/redis-queue.js.map +7 -0
- package/dist/response-cache.d.ts +38 -0
- package/dist/response-cache.js +140 -0
- package/dist/response-cache.js.map +7 -0
- package/dist/responsive-detector.d.ts +29 -0
- package/dist/responsive-detector.js +46 -0
- package/dist/responsive-detector.js.map +7 -0
- package/dist/retry-policy.d.ts +20 -0
- package/dist/retry-policy.js +54 -0
- package/dist/retry-policy.js.map +7 -0
- package/dist/robots-txt.d.ts +9 -0
- package/dist/robots-txt.js +77 -0
- package/dist/robots-txt.js.map +7 -0
- package/dist/screen-reader.d.ts +16 -0
- package/dist/screen-reader.js +46 -0
- package/dist/screen-reader.js.map +7 -0
- package/dist/screenshot-masker.d.ts +28 -0
- package/dist/screenshot-masker.js +62 -0
- package/dist/screenshot-masker.js.map +7 -0
- package/dist/seo-checker.d.ts +33 -0
- package/dist/seo-checker.js +91 -0
- package/dist/seo-checker.js.map +7 -0
- package/dist/session-capture.d.ts +35 -0
- package/dist/session-capture.js +267 -0
- package/dist/session-capture.js.map +7 -0
- package/dist/session-export.d.ts +49 -0
- package/dist/session-export.js +117 -0
- package/dist/session-export.js.map +7 -0
- package/dist/sitemap-writer.d.ts +21 -0
- package/dist/sitemap-writer.js +47 -0
- package/dist/sitemap-writer.js.map +7 -0
- package/dist/sitemap.d.ts +51 -0
- package/dist/sitemap.js +152 -0
- package/dist/sitemap.js.map +7 -0
- package/dist/skyvern.d.ts +56 -0
- package/dist/skyvern.js +129 -0
- package/dist/skyvern.js.map +7 -0
- package/dist/sqs-queue.d.ts +2 -0
- package/dist/sqs-queue.js +47 -0
- package/dist/sqs-queue.js.map +7 -0
- package/dist/sso-token.d.ts +29 -0
- package/dist/sso-token.js +76 -0
- package/dist/sso-token.js.map +7 -0
- package/dist/stealth/human-behavior.d.ts +20 -0
- package/dist/stealth/human-behavior.js +168 -0
- package/dist/stealth/human-behavior.js.map +7 -0
- package/dist/telemetry.d.ts +34 -0
- package/dist/telemetry.js +77 -0
- package/dist/telemetry.js.map +7 -0
- package/dist/test-executor.d.ts +67 -0
- package/dist/test-executor.js +311 -0
- package/dist/test-executor.js.map +7 -0
- package/dist/tls-fingerprint.d.ts +50 -0
- package/dist/tls-fingerprint.js +81 -0
- package/dist/tls-fingerprint.js.map +7 -0
- package/dist/url-pattern-completer.d.ts +28 -0
- package/dist/url-pattern-completer.js +87 -0
- package/dist/url-pattern-completer.js.map +7 -0
- package/dist/visual-diff-verdict.d.ts +40 -0
- package/dist/visual-diff-verdict.js +25 -0
- package/dist/visual-diff-verdict.js.map +7 -0
- package/dist/webhook-notifier.d.ts +30 -0
- package/dist/webhook-notifier.js +76 -0
- package/dist/webhook-notifier.js.map +7 -0
- package/dist/websocket-detector.d.ts +24 -0
- package/dist/websocket-detector.js +60 -0
- package/dist/websocket-detector.js.map +7 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +33 -0
- package/dist/worker.js.map +7 -0
- package/package.json +45 -0
- package/src/accessibility-auditor.ts +102 -0
- package/src/ai-login.ts +218 -0
- package/src/auth-token-rotator.ts +89 -0
- package/src/auth.ts +396 -0
- package/src/browser-extension.ts +186 -0
- package/src/captcha-solver.ts +143 -0
- package/src/color-contrast.ts +116 -0
- package/src/content-chunker.ts +170 -0
- package/src/content-dedup.ts +99 -0
- package/src/cookie-consent.ts +106 -0
- package/src/cookie-jar.ts +91 -0
- package/src/crawler.ts +4305 -0
- package/src/custom-js-injector.ts +93 -0
- package/src/dark-mode-capture.ts +110 -0
- package/src/dead-link-detector.ts +76 -0
- package/src/element-hash.ts +16 -0
- package/src/email-otp.ts +252 -0
- package/src/engine-router.ts +141 -0
- package/src/export/bulk-export.ts +85 -0
- package/src/export/vector-export.ts +160 -0
- package/src/extractors/advanced.ts +283 -0
- package/src/extractors/artifact-envelope.ts +111 -0
- package/src/failure-analysis-agent.ts +49 -0
- package/src/feature-flag-detector.ts +97 -0
- package/src/form-detector.ts +103 -0
- package/src/frontier.test.ts +64 -0
- package/src/frontier.ts +112 -0
- package/src/gap-pass.ts +122 -0
- package/src/graphql-detector.ts +129 -0
- package/src/http-engine.ts +433 -0
- package/src/incremental-diff.ts +121 -0
- package/src/index.ts +35 -0
- package/src/input-fuzzer.ts +129 -0
- package/src/interactive-probe.ts +623 -0
- package/src/job-queue.test.ts +120 -0
- package/src/job-queue.ts +2701 -0
- package/src/js-coverage.ts +87 -0
- package/src/js-router-detector.ts +112 -0
- package/src/language-detector.ts +82 -0
- package/src/link-classifier.ts +65 -0
- package/src/live-view.ts +141 -0
- package/src/logger.ts +9 -0
- package/src/markdown-extract.test.ts +73 -0
- package/src/markdown-extract.ts +76 -0
- package/src/native-crawler.ts +1106 -0
- package/src/network-observer.ts +204 -0
- package/src/network-throttle.ts +69 -0
- package/src/openapi-generator.ts +135 -0
- package/src/pdf-extractor.ts +123 -0
- package/src/performance-metrics.ts +134 -0
- package/src/phash.ts +151 -0
- package/src/pii-scrubber.ts +99 -0
- package/src/playwright-recorder.ts +170 -0
- package/src/policies/crawl-safety.ts +46 -0
- package/src/profiles/domain-profile.ts +167 -0
- package/src/proxy.ts +238 -0
- package/src/rate-limiter.ts +87 -0
- package/src/redirect-tracker.ts +97 -0
- package/src/redis-frontier.ts +143 -0
- package/src/redis-queue.ts +208 -0
- package/src/response-cache.ts +150 -0
- package/src/responsive-detector.ts +92 -0
- package/src/retry-policy.ts +87 -0
- package/src/robots-txt.ts +96 -0
- package/src/screen-reader.ts +89 -0
- package/src/screenshot-masker.ts +98 -0
- package/src/seo-checker.ts +134 -0
- package/src/session-capture.ts +325 -0
- package/src/session-export.test.ts +75 -0
- package/src/session-export.ts +172 -0
- package/src/sitemap-writer.ts +65 -0
- package/src/sitemap.test.ts +131 -0
- package/src/sitemap.ts +224 -0
- package/src/skyvern.ts +199 -0
- package/src/sqs-queue.ts +49 -0
- package/src/sso-token.ts +120 -0
- package/src/stealth/human-behavior.ts +191 -0
- package/src/telemetry.ts +122 -0
- package/src/test-executor.test.ts +72 -0
- package/src/test-executor.ts +424 -0
- package/src/tls-fingerprint.ts +111 -0
- package/src/url-pattern-completer.ts +114 -0
- package/src/visual-diff-verdict.test.ts +80 -0
- package/src/visual-diff-verdict.ts +57 -0
- package/src/webhook-notifier.ts +109 -0
- package/src/websocket-detector.ts +83 -0
- package/src/worker.ts +48 -0
- package/tsconfig.json +13 -0
- package/types/accessibility-auditor.d.ts +29 -0
- package/types/ai-login.d.ts +32 -0
- package/types/auth-token-rotator.d.ts +47 -0
- package/types/auth.d.ts +20 -0
- package/types/browser-extension.d.ts +37 -0
- package/types/bull-worker.d.ts +6 -0
- package/types/captcha-solver.d.ts +16 -0
- package/types/color-contrast.d.ts +30 -0
- package/types/content-chunker.d.ts +18 -0
- package/types/content-dedup.d.ts +23 -0
- package/types/cookie-consent.d.ts +25 -0
- package/types/cookie-jar.d.ts +20 -0
- package/types/crawler.d.ts +179 -0
- package/types/custom-js-injector.d.ts +42 -0
- package/types/dark-mode-capture.d.ts +23 -0
- package/types/dead-link-detector.d.ts +37 -0
- package/types/element-hash.d.ts +9 -0
- package/types/email-otp.d.ts +42 -0
- package/types/engine-router.d.ts +47 -0
- package/types/export/bulk-export.d.ts +26 -0
- package/types/export/vector-export.d.ts +76 -0
- package/types/extractors/advanced.d.ts +54 -0
- package/types/extractors/artifact-envelope.d.ts +63 -0
- package/types/failure-analysis-agent.d.ts +16 -0
- package/types/feature-flag-detector.d.ts +30 -0
- package/types/form-detector.d.ts +34 -0
- package/types/frontier.d.ts +32 -0
- package/types/gap-pass.d.ts +35 -0
- package/types/graphql-detector.d.ts +39 -0
- package/types/http-engine.d.ts +80 -0
- package/types/incremental-diff.d.ts +59 -0
- package/types/index.d.ts +35 -0
- package/types/input-fuzzer.d.ts +30 -0
- package/types/interactive-probe.d.ts +104 -0
- package/types/job-queue.d.ts +171 -0
- package/types/js-coverage.d.ts +29 -0
- package/types/js-router-detector.d.ts +17 -0
- package/types/language-detector.d.ts +22 -0
- package/types/link-classifier.d.ts +23 -0
- package/types/live-view.d.ts +36 -0
- package/types/logger.d.ts +2 -0
- package/types/markdown-extract.d.ts +8 -0
- package/types/native-crawler.d.ts +69 -0
- package/types/network-observer.d.ts +49 -0
- package/types/network-throttle.d.ts +25 -0
- package/types/openapi-generator.d.ts +49 -0
- package/types/pdf-extractor.d.ts +28 -0
- package/types/performance-metrics.d.ts +53 -0
- package/types/phash.d.ts +38 -0
- package/types/pii-scrubber.d.ts +21 -0
- package/types/playwright-recorder.d.ts +39 -0
- package/types/policies/crawl-safety.d.ts +9 -0
- package/types/profiles/domain-profile.d.ts +19 -0
- package/types/proxy.d.ts +50 -0
- package/types/rate-limiter.d.ts +31 -0
- package/types/redirect-tracker.d.ts +37 -0
- package/types/redis-frontier.d.ts +48 -0
- package/types/redis-queue.d.ts +48 -0
- package/types/response-cache.d.ts +38 -0
- package/types/responsive-detector.d.ts +29 -0
- package/types/retry-policy.d.ts +20 -0
- package/types/robots-txt.d.ts +9 -0
- package/types/screen-reader.d.ts +16 -0
- package/types/screenshot-masker.d.ts +28 -0
- package/types/seo-checker.d.ts +33 -0
- package/types/session-capture.d.ts +38 -0
- package/types/session-export.d.ts +49 -0
- package/types/sitemap-writer.d.ts +21 -0
- package/types/sitemap.d.ts +51 -0
- package/types/skyvern.d.ts +56 -0
- package/types/sqs-queue.d.ts +2 -0
- package/types/sso-token.d.ts +29 -0
- package/types/stealth/human-behavior.d.ts +20 -0
- package/types/telemetry.d.ts +34 -0
- package/types/test-executor.d.ts +67 -0
- package/types/tls-fingerprint.d.ts +50 -0
- package/types/url-pattern-completer.d.ts +28 -0
- package/types/visual-diff-verdict.d.ts +40 -0
- package/types/webhook-notifier.d.ts +30 -0
- package/types/websocket-detector.d.ts +24 -0
- package/types/worker.d.ts +1 -0
|
@@ -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,16 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
/**
|
|
3
|
+
* Reads a page as a structured outline (cheap + reliable), the way
|
|
4
|
+
* Playwright MCP / the accessibility tree works — NOT by sending pixels to a
|
|
5
|
+
* vision model. Vision is a fallback for image-only pages (added later).
|
|
6
|
+
*
|
|
7
|
+
* Returns the interactive elements found, which the Screen Reader Agent then
|
|
8
|
+
* enriches with MEANING and saves to the Zeta Core.
|
|
9
|
+
*/
|
|
10
|
+
export interface RawElement {
|
|
11
|
+
role: string;
|
|
12
|
+
label: string;
|
|
13
|
+
selectorHint: string;
|
|
14
|
+
expectedData?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function readScreen(page: Page): Promise<RawElement[]>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
async function readScreen(page) {
|
|
2
|
+
return page.evaluate(() => {
|
|
3
|
+
const out = [];
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
const noiseLabel = /^(cookie|consent|privacy policy|terms of use|copyright|©|\bv\d+\.\d+|accept|decline|got it|dismiss|close|skip to|powered by|copy|copied|\s*$)/i;
|
|
6
|
+
const nodes = document.querySelectorAll(
|
|
7
|
+
"input:not([type=hidden]), select, textarea, button, a[href]"
|
|
8
|
+
);
|
|
9
|
+
nodes.forEach((el) => {
|
|
10
|
+
const htmlEl = el;
|
|
11
|
+
const style = window.getComputedStyle(htmlEl);
|
|
12
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return;
|
|
13
|
+
const rect = el.getBoundingClientRect();
|
|
14
|
+
if (rect.width === 0 && rect.height === 0) return;
|
|
15
|
+
const tag = el.tagName.toLowerCase();
|
|
16
|
+
const role = el.getAttribute("role") || (tag === "input" ? el.type || "text" : tag);
|
|
17
|
+
const raw = (el.getAttribute("aria-label") || el.getAttribute("placeholder") || (el.getAttribute("title") ?? "") || htmlEl.innerText?.replace(/\s+/g, " ").trim() || el.getAttribute("name") || "").substring(0, 80);
|
|
18
|
+
if (!raw || noiseLabel.test(raw) || raw.length > 70) return;
|
|
19
|
+
const key = `${role}:${raw.toLowerCase()}`;
|
|
20
|
+
if (seen.has(key)) return;
|
|
21
|
+
seen.add(key);
|
|
22
|
+
let expectedData;
|
|
23
|
+
if (tag === "input") {
|
|
24
|
+
const input = el;
|
|
25
|
+
if (input.type === "email" || /email/i.test(raw)) expectedData = "valid_email";
|
|
26
|
+
else if (input.type === "password" || /password/i.test(raw)) expectedData = "password";
|
|
27
|
+
else if (input.type === "tel" || /phone|mobile/i.test(raw)) expectedData = "phone";
|
|
28
|
+
else if (input.type === "date" || /\bdate\b/i.test(raw)) expectedData = "date";
|
|
29
|
+
else if (/\b(code|otp|token)\b/i.test(raw)) expectedData = "one_time_code";
|
|
30
|
+
else if (/\b(search|query)\b/i.test(raw)) expectedData = "search_term";
|
|
31
|
+
}
|
|
32
|
+
out.push({ role, label: raw, selectorHint: tag, expectedData });
|
|
33
|
+
});
|
|
34
|
+
const priority = (e) => {
|
|
35
|
+
if (["email", "password", "text", "tel", "date", "number", "select", "textarea"].includes(e.role)) return 0;
|
|
36
|
+
if (e.role === "button" || e.role === "submit") return 1;
|
|
37
|
+
return 2;
|
|
38
|
+
};
|
|
39
|
+
out.sort((a, b) => priority(a) - priority(b));
|
|
40
|
+
return out.slice(0, 25);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
readScreen
|
|
45
|
+
};
|
|
46
|
+
//# 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: Page): Promise<RawElement[]> {\n return page.evaluate(() => {\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 junk, marketing copy buttons, version strings\n const noiseLabel =\n /^(cookie|consent|privacy policy|terms of use|copyright|\u00A9|\\bv\\d+\\.\\d+|accept|decline|got it|dismiss|close|skip to|powered by|copy|copied|\\s*$)/i;\n\n // Select only truly interactive elements (not generic [role] containers)\n const nodes = document.querySelectorAll(\n 'input:not([type=hidden]), select, textarea, button, a[href]'\n );\n\n nodes.forEach((el) => {\n const htmlEl = el as HTMLElement;\n\n // Skip invisible or zero-size 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 if (rect.width === 0 && rect.height === 0) 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,MAAmC;AAClE,SAAO,KAAK,SAAS,MAAM;AACzB,UAAM,MAAsF,CAAC;AAC7F,UAAM,OAAO,oBAAI,IAAY;AAG7B,UAAM,aACJ;AAGF,UAAM,QAAQ,SAAS;AAAA,MACrB;AAAA,IACF;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;AACtC,UAAI,KAAK,UAAU,KAAK,KAAK,WAAW,EAAG;AAG3C,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
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/screenshot-masker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Screenshot masker \u2014 blur or black-box PII regions in captured screenshots.\n *\n * Strategy: inject a CSS overlay into the page before screenshot capture that\n * covers detected PII-likely elements (input[type=email], input[type=password],\n * elements with PII-containing text) with a blur or solid black mask.\n *\n * This is a CSS-based approach (no image processing library needed):\n * 1. Identify likely-PII DOM elements via selectors\n * 2. Add an overlaid <div> with blur/black filter at each element's bounding box\n * 3. Take screenshot\n * 4. Remove overlays\n *\n * Falls back gracefully if elements are not found or overlay injection fails.\n */\n\nimport type { Page } from 'playwright';\n\nexport interface MaskingOpts {\n /** Additional CSS selectors to mask (in addition to defaults) */\n extraSelectors?: string[];\n /** Masking style: 'blur' or 'blackout' */\n style?: 'blur' | 'blackout';\n /** Blur strength in px (default 8) */\n blurPx?: number;\n}\n\n// Default PII-sensitive selectors\nconst DEFAULT_PII_SELECTORS = [\n 'input[type=\"email\"]',\n 'input[type=\"password\"]',\n 'input[type=\"tel\"]',\n 'input[autocomplete=\"cc-number\"]',\n 'input[autocomplete=\"cc-csc\"]',\n 'input[autocomplete=\"ssn\"]',\n '[data-pii=\"true\"]',\n '[data-sensitive=\"true\"]',\n];\n\nexport async function maskScreenshot(\n page: Page,\n opts: MaskingOpts = {},\n): Promise<{ maskedCount: number; screenshot: Buffer }> {\n const { extraSelectors = [], style = 'blur', blurPx = 8 } = opts;\n const selectors = [...DEFAULT_PII_SELECTORS, ...extraSelectors];\n\n let maskedCount = 0;\n const overlayIds: string[] = [];\n\n // Inject CSS overlays\n try {\n const result = await page.evaluate(({ selectors, style, blurPx }) => {\n const ids: string[] = [];\n for (const sel of selectors) {\n try {\n const elements = Array.from(document.querySelectorAll(sel));\n for (const el of elements) {\n const rect = el.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) continue;\n const overlay = document.createElement('div');\n const id = `__zeta_mask_${Math.random().toString(36).slice(2)}`;\n overlay.id = id;\n overlay.style.cssText = [\n 'position: fixed',\n `top: ${rect.top + window.scrollY}px`,\n `left: ${rect.left + window.scrollX}px`,\n `width: ${rect.width}px`,\n `height: ${rect.height}px`,\n 'z-index: 2147483647',\n style === 'blur'\n ? `backdrop-filter: blur(${blurPx}px); -webkit-backdrop-filter: blur(${blurPx}px); background: rgba(0,0,0,0.1);`\n : 'background: #000000;',\n 'pointer-events: none',\n ].join('; ');\n document.body.appendChild(overlay);\n ids.push(id);\n }\n } catch { /* selector error */ }\n }\n return ids;\n }, { selectors, style, blurPx });\n\n overlayIds.push(...result);\n maskedCount = result.length;\n } catch { /* non-fatal */ }\n\n // Take screenshot with overlays active\n const screenshot = await page.screenshot({ type: 'png', fullPage: false });\n\n // Remove overlays\n if (overlayIds.length > 0) {\n await page.evaluate((ids) => {\n for (const id of ids) document.getElementById(id)?.remove();\n }, overlayIds).catch(() => {});\n }\n\n return { maskedCount, screenshot };\n}\n"],
|
|
5
|
+
"mappings": "AA4BA,MAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAsB,eACpB,MACA,OAAoB,CAAC,GACiC;AACtD,QAAM,EAAE,iBAAiB,CAAC,GAAG,QAAQ,QAAQ,SAAS,EAAE,IAAI;AAC5D,QAAM,YAAY,CAAC,GAAG,uBAAuB,GAAG,cAAc;AAE9D,MAAI,cAAc;AAClB,QAAM,aAAuB,CAAC;AAG9B,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,SAAS,CAAC,EAAE,WAAAA,YAAW,OAAAC,QAAO,QAAAC,QAAO,MAAM;AACnE,YAAM,MAAgB,CAAC;AACvB,iBAAW,OAAOF,YAAW;AAC3B,YAAI;AACF,gBAAM,WAAW,MAAM,KAAK,SAAS,iBAAiB,GAAG,CAAC;AAC1D,qBAAW,MAAM,UAAU;AACzB,kBAAM,OAAO,GAAG,sBAAsB;AACtC,gBAAI,KAAK,UAAU,KAAK,KAAK,WAAW,EAAG;AAC3C,kBAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,kBAAM,KAAK,eAAe,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAC7D,oBAAQ,KAAK;AACb,oBAAQ,MAAM,UAAU;AAAA,cACtB;AAAA,cACA,QAAQ,KAAK,MAAM,OAAO,OAAO;AAAA,cACjC,SAAS,KAAK,OAAO,OAAO,OAAO;AAAA,cACnC,UAAU,KAAK,KAAK;AAAA,cACpB,WAAW,KAAK,MAAM;AAAA,cACtB;AAAA,cACAC,WAAU,SACN,yBAAyBC,OAAM,sCAAsCA,OAAM,sCAC3E;AAAA,cACJ;AAAA,YACF,EAAE,KAAK,IAAI;AACX,qBAAS,KAAK,YAAY,OAAO;AACjC,gBAAI,KAAK,EAAE;AAAA,UACb;AAAA,QACF,QAAQ;AAAA,QAAuB;AAAA,MACjC;AACA,aAAO;AAAA,IACT,GAAG,EAAE,WAAW,OAAO,OAAO,CAAC;AAE/B,eAAW,KAAK,GAAG,MAAM;AACzB,kBAAc,OAAO;AAAA,EACvB,QAAQ;AAAA,EAAkB;AAG1B,QAAM,aAAa,MAAM,KAAK,WAAW,EAAE,MAAM,OAAO,UAAU,MAAM,CAAC;AAGzE,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,KAAK,SAAS,CAAC,QAAQ;AAC3B,iBAAW,MAAM,IAAK,UAAS,eAAe,EAAE,GAAG,OAAO;AAAA,IAC5D,GAAG,UAAU,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/B;AAEA,SAAO,EAAE,aAAa,WAAW;AACnC;",
|
|
6
|
+
"names": ["selectors", "style", "blurPx"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO indexability checker — determines if a page is crawlable and indexable.
|
|
3
|
+
*
|
|
4
|
+
* Checks:
|
|
5
|
+
* 1. X-Robots-Tag response header (noindex, nofollow, noarchive)
|
|
6
|
+
* 2. <meta name="robots"> tag (noindex, nofollow)
|
|
7
|
+
* 3. Canonical URL (self-canonical vs cross-canonical)
|
|
8
|
+
* 4. rel="nofollow" on outbound links
|
|
9
|
+
* 5. hreflang tags (multilingual)
|
|
10
|
+
* 6. Open Graph completeness (og:title, og:description, og:image)
|
|
11
|
+
* 7. Structured data presence (JSON-LD)
|
|
12
|
+
* 8. Meta description
|
|
13
|
+
* 9. Title tag length (< 60 chars recommended)
|
|
14
|
+
* 10. URL structure (query params, fragments, excessive depth)
|
|
15
|
+
*/
|
|
16
|
+
import type { Page } from 'playwright';
|
|
17
|
+
export interface SeoIssue {
|
|
18
|
+
rule: string;
|
|
19
|
+
severity: 'critical' | 'warning' | 'info';
|
|
20
|
+
message: string;
|
|
21
|
+
value?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SeoCheckResult {
|
|
24
|
+
url: string;
|
|
25
|
+
isIndexable: boolean;
|
|
26
|
+
canonicalUrl?: string;
|
|
27
|
+
isSelfCanonical?: boolean;
|
|
28
|
+
hreflang: Record<string, string>;
|
|
29
|
+
issues: SeoIssue[];
|
|
30
|
+
score: number;
|
|
31
|
+
checkedAt: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function checkSeo(page: Page, url: string, responseHeaders?: Record<string, string>): Promise<SeoCheckResult>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
async function checkSeo(page, url, responseHeaders = {}) {
|
|
2
|
+
const issues = [];
|
|
3
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4
|
+
let isIndexable = true;
|
|
5
|
+
const robotsHeader = responseHeaders["x-robots-tag"] ?? "";
|
|
6
|
+
if (/noindex/i.test(robotsHeader)) {
|
|
7
|
+
isIndexable = false;
|
|
8
|
+
issues.push({ rule: "x-robots-noindex", severity: "critical", message: "X-Robots-Tag: noindex found in response header", value: robotsHeader });
|
|
9
|
+
}
|
|
10
|
+
const pageData = await page.evaluate(() => {
|
|
11
|
+
const meta = (name) => document.querySelector(`meta[name="${name}"]`)?.getAttribute("content") ?? "";
|
|
12
|
+
const og = (prop) => document.querySelector(`meta[property="og:${prop}"]`)?.getAttribute("content") ?? "";
|
|
13
|
+
const canonical = document.querySelector('link[rel="canonical"]')?.getAttribute("href") ?? "";
|
|
14
|
+
const hreflang = {};
|
|
15
|
+
document.querySelectorAll('link[rel="alternate"][hreflang]').forEach((el) => {
|
|
16
|
+
hreflang[el.getAttribute("hreflang")] = el.getAttribute("href") ?? "";
|
|
17
|
+
});
|
|
18
|
+
const jsonLd = Array.from(document.querySelectorAll('script[type="application/ld+json"]')).map((s) => s.textContent ?? "");
|
|
19
|
+
return {
|
|
20
|
+
robotsMeta: meta("robots"),
|
|
21
|
+
description: meta("description"),
|
|
22
|
+
title: document.title ?? "",
|
|
23
|
+
canonical,
|
|
24
|
+
hreflang,
|
|
25
|
+
ogTitle: og("title"),
|
|
26
|
+
ogDescription: og("description"),
|
|
27
|
+
ogImage: og("image"),
|
|
28
|
+
hasJsonLd: jsonLd.length > 0,
|
|
29
|
+
jsonLd
|
|
30
|
+
};
|
|
31
|
+
}).catch(() => ({
|
|
32
|
+
robotsMeta: "",
|
|
33
|
+
description: "",
|
|
34
|
+
title: "",
|
|
35
|
+
canonical: "",
|
|
36
|
+
hreflang: {},
|
|
37
|
+
ogTitle: "",
|
|
38
|
+
ogDescription: "",
|
|
39
|
+
ogImage: "",
|
|
40
|
+
hasJsonLd: false,
|
|
41
|
+
jsonLd: []
|
|
42
|
+
}));
|
|
43
|
+
if (/noindex/i.test(pageData.robotsMeta)) {
|
|
44
|
+
isIndexable = false;
|
|
45
|
+
issues.push({ rule: "meta-robots-noindex", severity: "critical", message: "Meta robots noindex found", value: pageData.robotsMeta });
|
|
46
|
+
}
|
|
47
|
+
let isSelfCanonical;
|
|
48
|
+
if (pageData.canonical) {
|
|
49
|
+
isSelfCanonical = pageData.canonical === url || pageData.canonical.replace(/\/$/, "") === url.replace(/\/$/, "");
|
|
50
|
+
if (!isSelfCanonical) {
|
|
51
|
+
issues.push({ rule: "cross-canonical", severity: "warning", message: "Cross-canonical points elsewhere", value: pageData.canonical });
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
issues.push({ rule: "missing-canonical", severity: "warning", message: "No canonical link tag" });
|
|
55
|
+
}
|
|
56
|
+
if (!pageData.title) {
|
|
57
|
+
issues.push({ rule: "missing-title", severity: "critical", message: "Page has no title tag" });
|
|
58
|
+
} else if (pageData.title.length > 60) {
|
|
59
|
+
issues.push({ rule: "title-too-long", severity: "warning", message: `Title ${pageData.title.length} chars (>60)`, value: pageData.title });
|
|
60
|
+
}
|
|
61
|
+
if (!pageData.description) {
|
|
62
|
+
issues.push({ rule: "missing-description", severity: "warning", message: "Missing meta description" });
|
|
63
|
+
} else if (pageData.description.length > 160) {
|
|
64
|
+
issues.push({ rule: "description-too-long", severity: "info", message: `Description ${pageData.description.length} chars (>160)` });
|
|
65
|
+
}
|
|
66
|
+
if (!pageData.ogTitle) issues.push({ rule: "missing-og-title", severity: "info", message: "Missing og:title" });
|
|
67
|
+
if (!pageData.ogImage) issues.push({ rule: "missing-og-image", severity: "info", message: "Missing og:image" });
|
|
68
|
+
if (!pageData.hasJsonLd) issues.push({ rule: "no-structured-data", severity: "info", message: "No JSON-LD structured data found" });
|
|
69
|
+
try {
|
|
70
|
+
const depth = new URL(url).pathname.split("/").filter(Boolean).length;
|
|
71
|
+
if (depth > 5) issues.push({ rule: "deep-url", severity: "info", message: `URL depth ${depth} (>5 levels)`, value: url });
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
const criticals = issues.filter((i) => i.severity === "critical").length;
|
|
75
|
+
const warnings = issues.filter((i) => i.severity === "warning").length;
|
|
76
|
+
const score = Math.max(0, 100 - criticals * 20 - warnings * 5 - issues.filter((i) => i.severity === "info").length * 2);
|
|
77
|
+
return {
|
|
78
|
+
url,
|
|
79
|
+
isIndexable,
|
|
80
|
+
canonicalUrl: pageData.canonical || void 0,
|
|
81
|
+
isSelfCanonical,
|
|
82
|
+
hreflang: pageData.hreflang,
|
|
83
|
+
issues,
|
|
84
|
+
score,
|
|
85
|
+
checkedAt
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
checkSeo
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=seo-checker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/seo-checker.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * SEO indexability checker \u2014 determines if a page is crawlable and indexable.\n *\n * Checks:\n * 1. X-Robots-Tag response header (noindex, nofollow, noarchive)\n * 2. <meta name=\"robots\"> tag (noindex, nofollow)\n * 3. Canonical URL (self-canonical vs cross-canonical)\n * 4. rel=\"nofollow\" on outbound links\n * 5. hreflang tags (multilingual)\n * 6. Open Graph completeness (og:title, og:description, og:image)\n * 7. Structured data presence (JSON-LD)\n * 8. Meta description\n * 9. Title tag length (< 60 chars recommended)\n * 10. URL structure (query params, fragments, excessive depth)\n */\n\nimport type { Page } from 'playwright';\n\nexport interface SeoIssue {\n rule: string;\n severity: 'critical' | 'warning' | 'info';\n message: string;\n value?: string;\n}\n\nexport interface SeoCheckResult {\n url: string;\n isIndexable: boolean;\n canonicalUrl?: string;\n isSelfCanonical?: boolean;\n hreflang: Record<string, string>; // lang \u2192 url\n issues: SeoIssue[];\n score: number; // 0-100\n checkedAt: string;\n}\n\nexport async function checkSeo(page: Page, url: string, responseHeaders: Record<string, string> = {}): Promise<SeoCheckResult> {\n const issues: SeoIssue[] = [];\n const checkedAt = new Date().toISOString();\n let isIndexable = true;\n\n // 1. X-Robots-Tag\n const robotsHeader = responseHeaders['x-robots-tag'] ?? '';\n if (/noindex/i.test(robotsHeader)) {\n isIndexable = false;\n issues.push({ rule: 'x-robots-noindex', severity: 'critical', message: 'X-Robots-Tag: noindex found in response header', value: robotsHeader });\n }\n\n // 2-9. Page-level checks\n const pageData = await page.evaluate(() => {\n const meta = (name: string) => document.querySelector(`meta[name=\"${name}\"]`)?.getAttribute('content') ?? '';\n const og = (prop: string) => document.querySelector(`meta[property=\"og:${prop}\"]`)?.getAttribute('content') ?? '';\n const canonical = document.querySelector('link[rel=\"canonical\"]')?.getAttribute('href') ?? '';\n const hreflang: Record<string, string> = {};\n document.querySelectorAll('link[rel=\"alternate\"][hreflang]').forEach((el: any) => {\n hreflang[el.getAttribute('hreflang')] = el.getAttribute('href') ?? '';\n });\n const jsonLd = Array.from(document.querySelectorAll('script[type=\"application/ld+json\"]')).map((s: any) => s.textContent ?? '');\n return {\n robotsMeta: meta('robots'),\n description: meta('description'),\n title: document.title ?? '',\n canonical,\n hreflang,\n ogTitle: og('title'),\n ogDescription: og('description'),\n ogImage: og('image'),\n hasJsonLd: jsonLd.length > 0,\n jsonLd,\n };\n }).catch(() => ({\n robotsMeta: '', description: '', title: '', canonical: '', hreflang: {},\n ogTitle: '', ogDescription: '', ogImage: '', hasJsonLd: false, jsonLd: [],\n }));\n\n // 2. Meta robots\n if (/noindex/i.test(pageData.robotsMeta)) {\n isIndexable = false;\n issues.push({ rule: 'meta-robots-noindex', severity: 'critical', message: 'Meta robots noindex found', value: pageData.robotsMeta });\n }\n\n // 3. Canonical\n let isSelfCanonical: boolean | undefined;\n if (pageData.canonical) {\n isSelfCanonical = pageData.canonical === url || pageData.canonical.replace(/\\/$/, '') === url.replace(/\\/$/, '');\n if (!isSelfCanonical) {\n issues.push({ rule: 'cross-canonical', severity: 'warning', message: 'Cross-canonical points elsewhere', value: pageData.canonical });\n }\n } else {\n issues.push({ rule: 'missing-canonical', severity: 'warning', message: 'No canonical link tag' });\n }\n\n // 4. Title\n if (!pageData.title) {\n issues.push({ rule: 'missing-title', severity: 'critical', message: 'Page has no title tag' });\n } else if (pageData.title.length > 60) {\n issues.push({ rule: 'title-too-long', severity: 'warning', message: `Title ${pageData.title.length} chars (>60)`, value: pageData.title });\n }\n\n // 5. Meta description\n if (!pageData.description) {\n issues.push({ rule: 'missing-description', severity: 'warning', message: 'Missing meta description' });\n } else if (pageData.description.length > 160) {\n issues.push({ rule: 'description-too-long', severity: 'info', message: `Description ${pageData.description.length} chars (>160)` });\n }\n\n // 6. OG tags\n if (!pageData.ogTitle) issues.push({ rule: 'missing-og-title', severity: 'info', message: 'Missing og:title' });\n if (!pageData.ogImage) issues.push({ rule: 'missing-og-image', severity: 'info', message: 'Missing og:image' });\n\n // 7. Structured data\n if (!pageData.hasJsonLd) issues.push({ rule: 'no-structured-data', severity: 'info', message: 'No JSON-LD structured data found' });\n\n // 8. URL depth\n try {\n const depth = new URL(url).pathname.split('/').filter(Boolean).length;\n if (depth > 5) issues.push({ rule: 'deep-url', severity: 'info', message: `URL depth ${depth} (>5 levels)`, value: url });\n } catch {}\n\n const criticals = issues.filter(i => i.severity === 'critical').length;\n const warnings = issues.filter(i => i.severity === 'warning').length;\n const score = Math.max(0, 100 - criticals * 20 - warnings * 5 - issues.filter(i => i.severity === 'info').length * 2);\n\n return {\n url,\n isIndexable,\n canonicalUrl: pageData.canonical || undefined,\n isSelfCanonical,\n hreflang: pageData.hreflang,\n issues,\n score,\n checkedAt,\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opens a headed (visible) browser at appUrl and returns a captureId.
|
|
3
|
+
* The user logs in manually, then calls completeSessionCapture() to harvest storageState.
|
|
4
|
+
* Sessions expire after 10 minutes if not completed.
|
|
5
|
+
*
|
|
6
|
+
* Throws 'HEADLESS_ENV' if running in Docker/Linux without a display server.
|
|
7
|
+
* The API route surfaces this as a 503 so the frontend can switch to manual JSON paste.
|
|
8
|
+
*/
|
|
9
|
+
export declare function startSessionCapture(appUrl: string): Promise<string>;
|
|
10
|
+
/** Captures storageState from the open browser, closes it, and returns the JSON string. */
|
|
11
|
+
export declare function completeSessionCapture(captureId: string): Promise<string>;
|
|
12
|
+
/** Closes the browser without capturing — called on Cancel. */
|
|
13
|
+
export declare function cancelSessionCapture(captureId: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Headless login + session capture — works on Docker/cloud (no display needed).
|
|
16
|
+
* Tries DOM-based login first (fast, free). If it fails and llmConfig is provided,
|
|
17
|
+
* falls back to Stagehand AI-guided login (handles modals, multi-step, marketing pages).
|
|
18
|
+
*
|
|
19
|
+
* Returns { storageState, cookies, localStorage } on success.
|
|
20
|
+
* Throws a human-readable error on failure.
|
|
21
|
+
*/
|
|
22
|
+
export declare function validateAndCaptureSession(appUrl: string, creds: {
|
|
23
|
+
username: string;
|
|
24
|
+
password: string;
|
|
25
|
+
totpSecret?: string;
|
|
26
|
+
httpBasicUsername?: string;
|
|
27
|
+
httpBasicPassword?: string;
|
|
28
|
+
}, llmConfig?: {
|
|
29
|
+
modelName: string;
|
|
30
|
+
apiKey: string;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
storageState: string;
|
|
33
|
+
cookies: number;
|
|
34
|
+
localStorage: number;
|
|
35
|
+
}>;
|