@tangle-network/browser-agent-driver 0.33.2 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -1
- package/discovery/llms.txt +27 -0
- package/discovery/tangle-agent.json +36 -0
- package/dist/brain/action-parse.d.ts +16 -0
- package/dist/brain/action-parse.d.ts.map +1 -0
- package/dist/brain/action-parse.js +191 -0
- package/dist/brain/action-parse.js.map +1 -0
- package/dist/brain/decide.d.ts +58 -0
- package/dist/brain/decide.d.ts.map +1 -0
- package/dist/brain/decide.js +418 -0
- package/dist/brain/decide.js.map +1 -0
- package/dist/brain/history-compact.d.ts +19 -0
- package/dist/brain/history-compact.d.ts.map +1 -0
- package/dist/brain/history-compact.js +153 -0
- package/dist/brain/history-compact.js.map +1 -0
- package/dist/brain/index.d.ts +78 -152
- package/dist/brain/index.d.ts.map +1 -1
- package/dist/brain/index.js +62 -2268
- package/dist/brain/index.js.map +1 -1
- package/dist/brain/model-client.d.ts +101 -0
- package/dist/brain/model-client.d.ts.map +1 -0
- package/dist/brain/model-client.js +391 -0
- package/dist/brain/model-client.js.map +1 -0
- package/dist/brain/plan.d.ts +57 -0
- package/dist/brain/plan.d.ts.map +1 -0
- package/dist/brain/plan.js +194 -0
- package/dist/brain/plan.js.map +1 -0
- package/dist/brain/prompts.d.ts +37 -0
- package/dist/brain/prompts.d.ts.map +1 -0
- package/dist/brain/prompts.js +455 -0
- package/dist/brain/prompts.js.map +1 -0
- package/dist/brain/provider-fetch.d.ts +26 -0
- package/dist/brain/provider-fetch.d.ts.map +1 -0
- package/dist/brain/provider-fetch.js +45 -0
- package/dist/brain/provider-fetch.js.map +1 -0
- package/dist/brain/snapshot-budget.d.ts +20 -0
- package/dist/brain/snapshot-budget.d.ts.map +1 -0
- package/dist/brain/snapshot-budget.js +202 -0
- package/dist/brain/snapshot-budget.js.map +1 -0
- package/dist/brain/system-prompt.d.ts +67 -0
- package/dist/brain/system-prompt.d.ts.map +1 -0
- package/dist/brain/system-prompt.js +128 -0
- package/dist/brain/system-prompt.js.map +1 -0
- package/dist/brain/tasks/design-audit.d.ts +33 -0
- package/dist/brain/tasks/design-audit.d.ts.map +1 -0
- package/dist/brain/tasks/design-audit.js +100 -0
- package/dist/brain/tasks/design-audit.js.map +1 -0
- package/dist/brain/tasks/evaluate.d.ts +25 -0
- package/dist/brain/tasks/evaluate.d.ts.map +1 -0
- package/dist/brain/tasks/evaluate.js +55 -0
- package/dist/brain/tasks/evaluate.js.map +1 -0
- package/dist/brain/tasks/goal-verification.d.ts +32 -0
- package/dist/brain/tasks/goal-verification.d.ts.map +1 -0
- package/dist/brain/tasks/goal-verification.js +68 -0
- package/dist/brain/tasks/goal-verification.js.map +1 -0
- package/dist/brain/tasks/knowledge.d.ts +26 -0
- package/dist/brain/tasks/knowledge.d.ts.map +1 -0
- package/dist/brain/tasks/knowledge.js +56 -0
- package/dist/brain/tasks/knowledge.js.map +1 -0
- package/dist/brain/tasks/link-scout.d.ts +37 -0
- package/dist/brain/tasks/link-scout.d.ts.map +1 -0
- package/dist/brain/tasks/link-scout.js +69 -0
- package/dist/brain/tasks/link-scout.js.map +1 -0
- package/dist/brain/types.d.ts +55 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +7 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/browser/stealth-init-script.d.ts +9 -0
- package/dist/browser/stealth-init-script.d.ts.map +1 -0
- package/dist/browser/stealth-init-script.js +128 -0
- package/dist/browser/stealth-init-script.js.map +1 -0
- package/dist/browser/storage-state.d.ts +29 -0
- package/dist/browser/storage-state.d.ts.map +1 -0
- package/dist/browser/storage-state.js +47 -0
- package/dist/browser/storage-state.js.map +1 -0
- package/dist/cli/args.d.ts +141 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +167 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/benchmark-sync.d.ts +9 -0
- package/dist/cli/benchmark-sync.d.ts.map +1 -0
- package/dist/cli/benchmark-sync.js +43 -0
- package/dist/cli/benchmark-sync.js.map +1 -0
- package/dist/cli/build-config.d.ts +11 -0
- package/dist/cli/build-config.d.ts.map +1 -0
- package/dist/cli/build-config.js +355 -0
- package/dist/cli/build-config.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +3 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +36 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/chrome-debug.d.ts +7 -0
- package/dist/cli/commands/chrome-debug.d.ts.map +1 -0
- package/dist/cli/commands/chrome-debug.js +11 -0
- package/dist/cli/commands/chrome-debug.js.map +1 -0
- package/dist/cli/commands/design-audit.d.ts +3 -0
- package/dist/cli/commands/design-audit.d.ts.map +1 -0
- package/dist/cli/commands/design-audit.js +68 -0
- package/dist/cli/commands/design-audit.js.map +1 -0
- package/dist/cli/commands/preview.d.ts +14 -0
- package/dist/cli/commands/preview.d.ts.map +1 -0
- package/dist/cli/commands/preview.js +31 -0
- package/dist/cli/commands/preview.js.map +1 -0
- package/dist/cli/commands/run.d.ts +3 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +728 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/runs.d.ts +8 -0
- package/dist/cli/commands/runs.d.ts.map +1 -0
- package/dist/cli/commands/runs.js +35 -0
- package/dist/cli/commands/runs.js.map +1 -0
- package/dist/cli/commands/share.d.ts +10 -0
- package/dist/cli/commands/share.d.ts.map +1 -0
- package/dist/cli/commands/share.js +32 -0
- package/dist/cli/commands/share.js.map +1 -0
- package/dist/cli/commands/showcase.d.ts +17 -0
- package/dist/cli/commands/showcase.d.ts.map +1 -0
- package/dist/cli/commands/showcase.js +20 -0
- package/dist/cli/commands/showcase.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +12 -0
- package/dist/cli/commands/snapshot.d.ts.map +1 -0
- package/dist/cli/commands/snapshot.js +24 -0
- package/dist/cli/commands/snapshot.js.map +1 -0
- package/dist/cli/commands/view.d.ts +7 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +23 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/constants.d.ts +6 -0
- package/dist/cli/constants.d.ts.map +1 -0
- package/dist/cli/constants.js +3 -0
- package/dist/cli/constants.js.map +1 -0
- package/dist/cli/version.d.ts +7 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +16 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli-design-audit.d.ts +44 -36
- package/dist/cli-design-audit.d.ts.map +1 -1
- package/dist/cli-design-audit.js +137 -1646
- package/dist/cli-design-audit.js.map +1 -1
- package/dist/cli-jobs.js +2 -1
- package/dist/cli-jobs.js.map +1 -1
- package/dist/cli.js +66 -1697
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/design/audit/evolve/agent.d.ts +23 -0
- package/dist/design/audit/evolve/agent.d.ts.map +1 -0
- package/dist/design/audit/evolve/agent.js +272 -0
- package/dist/design/audit/evolve/agent.js.map +1 -0
- package/dist/design/audit/evolve/css.d.ts +10 -0
- package/dist/design/audit/evolve/css.d.ts.map +1 -0
- package/dist/design/audit/evolve/css.js +206 -0
- package/dist/design/audit/evolve/css.js.map +1 -0
- package/dist/design/audit/evolve/index.d.ts +13 -0
- package/dist/design/audit/evolve/index.d.ts.map +1 -0
- package/dist/design/audit/evolve/index.js +12 -0
- package/dist/design/audit/evolve/index.js.map +1 -0
- package/dist/design/audit/evolve/report.d.ts +3 -0
- package/dist/design/audit/evolve/report.d.ts.map +1 -0
- package/dist/design/audit/evolve/report.js +45 -0
- package/dist/design/audit/evolve/report.js.map +1 -0
- package/dist/design/audit/evolve/types.d.ts +14 -0
- package/dist/design/audit/evolve/types.d.ts.map +1 -0
- package/dist/design/audit/evolve/types.js +2 -0
- package/dist/design/audit/evolve/types.js.map +1 -0
- package/dist/design/audit/pipeline.d.ts +12 -0
- package/dist/design/audit/pipeline.d.ts.map +1 -1
- package/dist/design/audit/pipeline.js +43 -27
- package/dist/design/audit/pipeline.js.map +1 -1
- package/dist/design/audit/reference/artifact/build.d.ts +33 -0
- package/dist/design/audit/reference/artifact/build.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/build.js +64 -0
- package/dist/design/audit/reference/artifact/build.js.map +1 -0
- package/dist/design/audit/reference/artifact/render.d.ts +39 -0
- package/dist/design/audit/reference/artifact/render.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/render.js +213 -0
- package/dist/design/audit/reference/artifact/render.js.map +1 -0
- package/dist/design/audit/reference/artifact/text.d.ts +11 -0
- package/dist/design/audit/reference/artifact/text.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/text.js +20 -0
- package/dist/design/audit/reference/artifact/text.js.map +1 -0
- package/dist/design/audit/reference/artifact/to-findings.d.ts +57 -0
- package/dist/design/audit/reference/artifact/to-findings.d.ts.map +1 -0
- package/dist/design/audit/reference/artifact/to-findings.js +314 -0
- package/dist/design/audit/reference/artifact/to-findings.js.map +1 -0
- package/dist/design/audit/reference/config.d.ts +50 -0
- package/dist/design/audit/reference/config.d.ts.map +1 -0
- package/dist/design/audit/reference/config.js +108 -0
- package/dist/design/audit/reference/config.js.map +1 -0
- package/dist/design/audit/reference/contracts.d.ts +1030 -0
- package/dist/design/audit/reference/contracts.d.ts.map +1 -0
- package/dist/design/audit/reference/contracts.js +28 -0
- package/dist/design/audit/reference/contracts.js.map +1 -0
- package/dist/design/audit/reference/corpus/build.d.ts +115 -0
- package/dist/design/audit/reference/corpus/build.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/build.js +152 -0
- package/dist/design/audit/reference/corpus/build.js.map +1 -0
- package/dist/design/audit/reference/corpus/schema.d.ts +33 -0
- package/dist/design/audit/reference/corpus/schema.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/schema.js +249 -0
- package/dist/design/audit/reference/corpus/schema.js.map +1 -0
- package/dist/design/audit/reference/corpus/store.d.ts +26 -0
- package/dist/design/audit/reference/corpus/store.d.ts.map +1 -0
- package/dist/design/audit/reference/corpus/store.js +104 -0
- package/dist/design/audit/reference/corpus/store.js.map +1 -0
- package/dist/design/audit/reference/dna/delta.d.ts +21 -0
- package/dist/design/audit/reference/dna/delta.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/delta.js +126 -0
- package/dist/design/audit/reference/dna/delta.js.map +1 -0
- package/dist/design/audit/reference/dna/derive.d.ts +31 -0
- package/dist/design/audit/reference/dna/derive.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/derive.js +381 -0
- package/dist/design/audit/reference/dna/derive.js.map +1 -0
- package/dist/design/audit/reference/dna/descriptor.d.ts +36 -0
- package/dist/design/audit/reference/dna/descriptor.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/descriptor.js +209 -0
- package/dist/design/audit/reference/dna/descriptor.js.map +1 -0
- package/dist/design/audit/reference/dna/page-adapter.d.ts +17 -0
- package/dist/design/audit/reference/dna/page-adapter.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/page-adapter.js +35 -0
- package/dist/design/audit/reference/dna/page-adapter.js.map +1 -0
- package/dist/design/audit/reference/dna/scroll-capture.d.ts +93 -0
- package/dist/design/audit/reference/dna/scroll-capture.d.ts.map +1 -0
- package/dist/design/audit/reference/dna/scroll-capture.js +374 -0
- package/dist/design/audit/reference/dna/scroll-capture.js.map +1 -0
- package/dist/design/audit/reference/engine/budget.d.ts +63 -0
- package/dist/design/audit/reference/engine/budget.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/budget.js +110 -0
- package/dist/design/audit/reference/engine/budget.js.map +1 -0
- package/dist/design/audit/reference/engine/core.d.ts +32 -0
- package/dist/design/audit/reference/engine/core.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/core.js +261 -0
- package/dist/design/audit/reference/engine/core.js.map +1 -0
- package/dist/design/audit/reference/engine/guard.d.ts +43 -0
- package/dist/design/audit/reference/engine/guard.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/guard.js +43 -0
- package/dist/design/audit/reference/engine/guard.js.map +1 -0
- package/dist/design/audit/reference/engine/score-core.d.ts +48 -0
- package/dist/design/audit/reference/engine/score-core.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/score-core.js +134 -0
- package/dist/design/audit/reference/engine/score-core.js.map +1 -0
- package/dist/design/audit/reference/engine/wiring.d.ts +35 -0
- package/dist/design/audit/reference/engine/wiring.d.ts.map +1 -0
- package/dist/design/audit/reference/engine/wiring.js +80 -0
- package/dist/design/audit/reference/engine/wiring.js.map +1 -0
- package/dist/design/audit/reference/eval/taste-core.d.ts +77 -0
- package/dist/design/audit/reference/eval/taste-core.d.ts.map +1 -0
- package/dist/design/audit/reference/eval/taste-core.js +95 -0
- package/dist/design/audit/reference/eval/taste-core.js.map +1 -0
- package/dist/design/audit/reference/generate/generator.d.ts +49 -0
- package/dist/design/audit/reference/generate/generator.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/generator.js +94 -0
- package/dist/design/audit/reference/generate/generator.js.map +1 -0
- package/dist/design/audit/reference/generate/parse.d.ts +28 -0
- package/dist/design/audit/reference/generate/parse.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/parse.js +180 -0
- package/dist/design/audit/reference/generate/parse.js.map +1 -0
- package/dist/design/audit/reference/generate/prompt.d.ts +36 -0
- package/dist/design/audit/reference/generate/prompt.d.ts.map +1 -0
- package/dist/design/audit/reference/generate/prompt.js +124 -0
- package/dist/design/audit/reference/generate/prompt.js.map +1 -0
- package/dist/design/audit/reference/index.d.ts +26 -0
- package/dist/design/audit/reference/index.d.ts.map +1 -0
- package/dist/design/audit/reference/index.js +31 -0
- package/dist/design/audit/reference/index.js.map +1 -0
- package/dist/design/audit/reference/judge/image-clamp.d.ts +34 -0
- package/dist/design/audit/reference/judge/image-clamp.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/image-clamp.js +88 -0
- package/dist/design/audit/reference/judge/image-clamp.js.map +1 -0
- package/dist/design/audit/reference/judge/pairwise.d.ts +34 -0
- package/dist/design/audit/reference/judge/pairwise.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/pairwise.js +105 -0
- package/dist/design/audit/reference/judge/pairwise.js.map +1 -0
- package/dist/design/audit/reference/judge/parse.d.ts +31 -0
- package/dist/design/audit/reference/judge/parse.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/parse.js +113 -0
- package/dist/design/audit/reference/judge/parse.js.map +1 -0
- package/dist/design/audit/reference/judge/prompt.d.ts +38 -0
- package/dist/design/audit/reference/judge/prompt.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/prompt.js +111 -0
- package/dist/design/audit/reference/judge/prompt.js.map +1 -0
- package/dist/design/audit/reference/judge/quality.d.ts +28 -0
- package/dist/design/audit/reference/judge/quality.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/quality.js +62 -0
- package/dist/design/audit/reference/judge/quality.js.map +1 -0
- package/dist/design/audit/reference/judge/rank.d.ts +41 -0
- package/dist/design/audit/reference/judge/rank.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/rank.js +196 -0
- package/dist/design/audit/reference/judge/rank.js.map +1 -0
- package/dist/design/audit/reference/judge/text-judge.d.ts +31 -0
- package/dist/design/audit/reference/judge/text-judge.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/text-judge.js +43 -0
- package/dist/design/audit/reference/judge/text-judge.js.map +1 -0
- package/dist/design/audit/reference/judge/vision-judge.d.ts +59 -0
- package/dist/design/audit/reference/judge/vision-judge.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/vision-judge.js +150 -0
- package/dist/design/audit/reference/judge/vision-judge.js.map +1 -0
- package/dist/design/audit/reference/judge/vision-model.d.ts +65 -0
- package/dist/design/audit/reference/judge/vision-model.d.ts.map +1 -0
- package/dist/design/audit/reference/judge/vision-model.js +110 -0
- package/dist/design/audit/reference/judge/vision-model.js.map +1 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts +68 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.d.ts.map +1 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.js +52 -0
- package/dist/design/audit/reference/pipeline/evaluate-reference.js.map +1 -0
- package/dist/design/audit/reference/reference-context.d.ts +31 -0
- package/dist/design/audit/reference/reference-context.d.ts.map +1 -0
- package/dist/design/audit/reference/reference-context.js +73 -0
- package/dist/design/audit/reference/reference-context.js.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.d.ts +30 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.js +87 -0
- package/dist/design/audit/reference/retrieval/embedding-hash.js.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.d.ts +38 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.js +73 -0
- package/dist/design/audit/reference/retrieval/embedding-openai.js.map +1 -0
- package/dist/design/audit/reference/retrieval/matcher.d.ts +34 -0
- package/dist/design/audit/reference/retrieval/matcher.d.ts.map +1 -0
- package/dist/design/audit/reference/retrieval/matcher.js +107 -0
- package/dist/design/audit/reference/retrieval/matcher.js.map +1 -0
- package/dist/design/audit/reference/run.d.ts +59 -0
- package/dist/design/audit/reference/run.d.ts.map +1 -0
- package/dist/design/audit/reference/run.js +99 -0
- package/dist/design/audit/reference/run.js.map +1 -0
- package/dist/design/audit/report.d.ts +35 -0
- package/dist/design/audit/report.d.ts.map +1 -0
- package/dist/design/audit/report.js +195 -0
- package/dist/design/audit/report.js.map +1 -0
- package/dist/design/audit/tokens/extract.d.ts +71 -0
- package/dist/design/audit/tokens/extract.d.ts.map +1 -0
- package/dist/design/audit/tokens/extract.js +1034 -0
- package/dist/design/audit/tokens/extract.js.map +1 -0
- package/dist/design/audit/types.d.ts +8 -0
- package/dist/design/audit/types.d.ts.map +1 -1
- package/dist/design/compare.d.ts.map +1 -1
- package/dist/design/compare.js +2 -6
- package/dist/design/compare.js.map +1 -1
- package/dist/design/cookie-consent.d.ts +3 -0
- package/dist/design/cookie-consent.d.ts.map +1 -0
- package/dist/design/cookie-consent.js +26 -0
- package/dist/design/cookie-consent.js.map +1 -0
- package/dist/design/viewports.d.ts +18 -0
- package/dist/design/viewports.d.ts.map +1 -0
- package/dist/design/viewports.js +10 -0
- package/dist/design/viewports.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/memory/store.d.ts +19 -1
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +47 -0
- package/dist/memory/store.js.map +1 -1
- package/dist/provider-defaults.d.ts +18 -0
- package/dist/provider-defaults.d.ts.map +1 -1
- package/dist/provider-defaults.js +33 -0
- package/dist/provider-defaults.js.map +1 -1
- package/dist/runner/allowed-domains.d.ts +11 -0
- package/dist/runner/allowed-domains.d.ts.map +1 -0
- package/dist/runner/allowed-domains.js +14 -0
- package/dist/runner/allowed-domains.js.map +1 -0
- package/dist/runner/batch-fill.d.ts +28 -0
- package/dist/runner/batch-fill.d.ts.map +1 -0
- package/dist/runner/batch-fill.js +80 -0
- package/dist/runner/batch-fill.js.map +1 -0
- package/dist/runner/completion-policy.d.ts +26 -0
- package/dist/runner/completion-policy.d.ts.map +1 -0
- package/dist/runner/completion-policy.js +76 -0
- package/dist/runner/completion-policy.js.map +1 -0
- package/dist/runner/constants.d.ts +14 -0
- package/dist/runner/constants.d.ts.map +1 -0
- package/dist/runner/constants.js +23 -0
- package/dist/runner/constants.js.map +1 -0
- package/dist/runner/decision-screenshot.d.ts +24 -0
- package/dist/runner/decision-screenshot.d.ts.map +1 -0
- package/dist/runner/decision-screenshot.js +23 -0
- package/dist/runner/decision-screenshot.js.map +1 -0
- package/dist/runner/domain-boundary.d.ts +36 -0
- package/dist/runner/domain-boundary.d.ts.map +1 -0
- package/dist/runner/domain-boundary.js +81 -0
- package/dist/runner/domain-boundary.js.map +1 -0
- package/dist/runner/effect-verify.d.ts +28 -0
- package/dist/runner/effect-verify.d.ts.map +1 -0
- package/dist/runner/effect-verify.js +42 -0
- package/dist/runner/effect-verify.js.map +1 -0
- package/dist/runner/execute-plan.d.ts +58 -0
- package/dist/runner/execute-plan.d.ts.map +1 -0
- package/dist/runner/execute-plan.js +426 -0
- package/dist/runner/execute-plan.js.map +1 -0
- package/dist/runner/max-turns-extension.d.ts +19 -0
- package/dist/runner/max-turns-extension.d.ts.map +1 -0
- package/dist/runner/max-turns-extension.js +25 -0
- package/dist/runner/max-turns-extension.js.map +1 -0
- package/dist/runner/micro-plan.d.ts +11 -0
- package/dist/runner/micro-plan.d.ts.map +1 -0
- package/dist/runner/micro-plan.js +29 -0
- package/dist/runner/micro-plan.js.map +1 -0
- package/dist/runner/prompt-snippets.d.ts +5 -0
- package/dist/runner/prompt-snippets.d.ts.map +1 -0
- package/dist/runner/prompt-snippets.js +13 -0
- package/dist/runner/prompt-snippets.js.map +1 -0
- package/dist/runner/replay/contracts.d.ts +300 -0
- package/dist/runner/replay/contracts.d.ts.map +1 -0
- package/dist/runner/replay/contracts.js +42 -0
- package/dist/runner/replay/contracts.js.map +1 -0
- package/dist/runner/replay/controller.d.ts +27 -0
- package/dist/runner/replay/controller.d.ts.map +1 -0
- package/dist/runner/replay/controller.js +197 -0
- package/dist/runner/replay/controller.js.map +1 -0
- package/dist/runner/replay/guard.d.ts +28 -0
- package/dist/runner/replay/guard.d.ts.map +1 -0
- package/dist/runner/replay/guard.js +100 -0
- package/dist/runner/replay/guard.js.map +1 -0
- package/dist/runner/runner.d.ts +44 -51
- package/dist/runner/runner.d.ts.map +1 -1
- package/dist/runner/runner.js +155 -851
- package/dist/runner/runner.js.map +1 -1
- package/dist/runner/scout-feedback.d.ts +51 -0
- package/dist/runner/scout-feedback.d.ts.map +1 -0
- package/dist/runner/scout-feedback.js +149 -0
- package/dist/runner/scout-feedback.js.map +1 -0
- package/dist/skills/macro-loader.d.ts +1 -1
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +25 -0
- package/dist/test-runner.js.map +1 -1
- package/dist/types/actions.d.ts +241 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +5 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/config.d.ts +211 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/design-audit.d.ts +92 -0
- package/dist/types/design-audit.d.ts.map +1 -0
- package/dist/types/design-audit.js +5 -0
- package/dist/types/design-audit.js.map +1 -0
- package/dist/types/design-tokens.d.ts +138 -0
- package/dist/types/design-tokens.d.ts.map +1 -0
- package/dist/types/design-tokens.js +5 -0
- package/dist/types/design-tokens.js.map +1 -0
- package/dist/types/page.d.ts +16 -0
- package/dist/types/page.d.ts.map +1 -0
- package/dist/types/page.js +2 -0
- package/dist/types/page.js.map +1 -0
- package/dist/types/plan.d.ts +50 -0
- package/dist/types/plan.d.ts.map +1 -0
- package/dist/types/plan.js +2 -0
- package/dist/types/plan.js.map +1 -0
- package/dist/types/preview.d.ts +9 -0
- package/dist/types/preview.d.ts.map +1 -0
- package/dist/types/preview.js +5 -0
- package/dist/types/preview.js.map +1 -0
- package/dist/types/result.d.ts +62 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +2 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/scenario.d.ts +20 -0
- package/dist/types/scenario.d.ts.map +1 -0
- package/dist/types/scenario.js +5 -0
- package/dist/types/scenario.js.map +1 -0
- package/dist/types/test-runner.d.ts +145 -0
- package/dist/types/test-runner.d.ts.map +1 -0
- package/dist/types/test-runner.js +10 -0
- package/dist/types/test-runner.js.map +1 -0
- package/dist/types/trajectory.d.ts +38 -0
- package/dist/types/trajectory.d.ts.map +1 -0
- package/dist/types/trajectory.js +2 -0
- package/dist/types/trajectory.js.map +1 -0
- package/dist/types/turn.d.ts +50 -0
- package/dist/types/turn.d.ts.map +1 -0
- package/dist/types/turn.js +2 -0
- package/dist/types/turn.js.map +1 -0
- package/dist/types.d.ts +16 -1017
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +15 -8
- package/dist/types.js.map +1 -1
- package/dist/wallet/rpc-interception.d.ts +15 -0
- package/dist/wallet/rpc-interception.d.ts.map +1 -0
- package/dist/wallet/rpc-interception.js +95 -0
- package/dist/wallet/rpc-interception.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,1034 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design token extraction — pure DOM mining, no LLM calls.
|
|
3
|
+
*
|
|
4
|
+
* Leaf module: loads a page at multiple viewports, mines colours, typography,
|
|
5
|
+
* spacing, components, and brand/asset references inside `page.evaluate`,
|
|
6
|
+
* clusters them into a `DesignTokens` artifact, downloads referenced assets,
|
|
7
|
+
* and zips the result. Imported (often lazily) by the CLI design-audit handler,
|
|
8
|
+
* the design-compare diff, and the reference-DNA page adapter.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { chromium } from 'playwright';
|
|
13
|
+
import { createScrollCapturer } from '../reference/dna/scroll-capture.js';
|
|
14
|
+
import { VIEWPORTS } from '../../viewports.js';
|
|
15
|
+
import { dismissCookieBanners } from '../../cookie-consent.js';
|
|
16
|
+
// The in-page extraction function — runs inside page.evaluate()
|
|
17
|
+
function extractTokensFromDOM() {
|
|
18
|
+
const MAX_ELEMENTS = 50_000;
|
|
19
|
+
// --- CSS Custom Properties & Font Files ---
|
|
20
|
+
const customProperties = {};
|
|
21
|
+
const fontFiles = [];
|
|
22
|
+
const externalStylesheetUrls = [];
|
|
23
|
+
for (const sheet of document.styleSheets) {
|
|
24
|
+
if (sheet.href && (sheet.href.startsWith('http://') || sheet.href.startsWith('https://'))) {
|
|
25
|
+
externalStylesheetUrls.push(sheet.href);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
for (const rule of sheet.cssRules) {
|
|
29
|
+
if (rule instanceof CSSStyleRule &&
|
|
30
|
+
(rule.selectorText === ':root' || rule.selectorText?.includes('html') || rule.selectorText?.includes('body'))) {
|
|
31
|
+
for (let i = 0; i < rule.style.length; i++) {
|
|
32
|
+
const prop = rule.style[i];
|
|
33
|
+
if (prop.startsWith('--')) {
|
|
34
|
+
customProperties[prop] = rule.style.getPropertyValue(prop).trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (rule instanceof CSSFontFaceRule) {
|
|
39
|
+
const family = rule.style.getPropertyValue('font-family').replace(/['"]/g, '').trim();
|
|
40
|
+
const weight = rule.style.getPropertyValue('font-weight') || '400';
|
|
41
|
+
const style = rule.style.getPropertyValue('font-style') || 'normal';
|
|
42
|
+
const src = rule.style.getPropertyValue('src');
|
|
43
|
+
const urlMatch = src.match(/url\(["']?([^"')]+)["']?\)/);
|
|
44
|
+
const formatMatch = src.match(/format\(["']?([^"')]+)["']?\)/);
|
|
45
|
+
if (urlMatch) {
|
|
46
|
+
fontFiles.push({
|
|
47
|
+
family,
|
|
48
|
+
weight,
|
|
49
|
+
style,
|
|
50
|
+
src: urlMatch[1],
|
|
51
|
+
format: formatMatch?.[1] || 'unknown',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { /* cross-origin stylesheet */ }
|
|
58
|
+
}
|
|
59
|
+
// Supplement with document.fonts API (catches cross-origin @font-face)
|
|
60
|
+
const seenFontSrcs = new Set(fontFiles.map(f => f.src));
|
|
61
|
+
try {
|
|
62
|
+
for (const face of document.fonts) {
|
|
63
|
+
if (face.status === 'loaded') {
|
|
64
|
+
// face.family is the CSS font-family, face.weight/style are strings
|
|
65
|
+
// Check if we already captured this via @font-face rules
|
|
66
|
+
const family = face.family.replace(/['"]/g, '').trim();
|
|
67
|
+
const weight = face.weight || '400';
|
|
68
|
+
const style = face.style || 'normal';
|
|
69
|
+
// document.fonts doesn't expose the URL, but we can match against loaded resources
|
|
70
|
+
if (!fontFiles.some(f => f.family === family && f.weight === weight && f.style === style)) {
|
|
71
|
+
// Will be supplemented by Performance API below
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* older browser */ }
|
|
77
|
+
// Use Performance API to find all loaded font resources (works cross-origin)
|
|
78
|
+
try {
|
|
79
|
+
const resources = performance.getEntriesByType('resource');
|
|
80
|
+
for (const r of resources) {
|
|
81
|
+
if (/\.(woff2?|ttf|otf|eot)(\?|$)/i.test(r.name) && !seenFontSrcs.has(r.name)) {
|
|
82
|
+
seenFontSrcs.add(r.name);
|
|
83
|
+
const formatMatch = r.name.match(/\.(woff2?|ttf|otf|eot)/i);
|
|
84
|
+
fontFiles.push({
|
|
85
|
+
family: '', // can't determine from URL alone
|
|
86
|
+
weight: '',
|
|
87
|
+
style: '',
|
|
88
|
+
src: r.name,
|
|
89
|
+
format: formatMatch?.[1] || 'unknown',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { /* Performance API not available */ }
|
|
95
|
+
// --- Element traversal ---
|
|
96
|
+
const colorMap = new Map();
|
|
97
|
+
const spacingMap = new Map();
|
|
98
|
+
const borderRadiusMap = new Map();
|
|
99
|
+
const shadowMap = new Map();
|
|
100
|
+
const fontFamilyMap = new Map();
|
|
101
|
+
const typeScaleMap = new Map();
|
|
102
|
+
const animationMap = new Map();
|
|
103
|
+
const backgroundImageUrls = new Set();
|
|
104
|
+
function addColor(value, property) {
|
|
105
|
+
if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)' || value === 'inherit' || value === 'initial')
|
|
106
|
+
return;
|
|
107
|
+
const entry = colorMap.get(value);
|
|
108
|
+
if (entry) {
|
|
109
|
+
entry.count++;
|
|
110
|
+
entry.properties.add(property);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
colorMap.set(value, { count: 1, properties: new Set([property]) });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function addSpacing(value, property) {
|
|
117
|
+
if (!value || value === '0px' || value === 'auto' || value === 'normal')
|
|
118
|
+
return;
|
|
119
|
+
// Normalize multi-value shorthands — take individual values
|
|
120
|
+
const vals = value.split(/\s+/).filter(v => v !== '0px' && v !== 'auto');
|
|
121
|
+
for (const v of vals) {
|
|
122
|
+
if (!v.endsWith('px') && !v.endsWith('rem') && !v.endsWith('em'))
|
|
123
|
+
continue;
|
|
124
|
+
const entry = spacingMap.get(v);
|
|
125
|
+
if (entry) {
|
|
126
|
+
entry.count++;
|
|
127
|
+
entry.properties.add(property);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
spacingMap.set(v, { count: 1, properties: new Set([property]) });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function parseGradientColors(bg) {
|
|
135
|
+
const colorRegex = /(?:rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-fA-F]{3,8})/g;
|
|
136
|
+
const matches = bg.match(colorRegex) || [];
|
|
137
|
+
for (const c of matches)
|
|
138
|
+
addColor(c, 'gradient');
|
|
139
|
+
}
|
|
140
|
+
function parseShadowColors(shadow) {
|
|
141
|
+
if (shadow === 'none')
|
|
142
|
+
return;
|
|
143
|
+
const colorRegex = /(?:rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-fA-F]{3,8})/g;
|
|
144
|
+
const matches = shadow.match(colorRegex) || [];
|
|
145
|
+
for (const c of matches)
|
|
146
|
+
addColor(c, 'boxShadow');
|
|
147
|
+
}
|
|
148
|
+
const HEADING_TAGS = new Set(['H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
|
|
149
|
+
const MONO_FAMILIES = /mono|consolas|courier|fira\s*code|jetbrains|source\s*code/i;
|
|
150
|
+
let elementCount = 0;
|
|
151
|
+
// Per-element extraction — shared between light DOM walker and shadow DOM walker
|
|
152
|
+
function processElement(el) {
|
|
153
|
+
const htmlEl = el;
|
|
154
|
+
if (htmlEl.offsetWidth === 0 && htmlEl.offsetHeight === 0)
|
|
155
|
+
return;
|
|
156
|
+
const cs = getComputedStyle(el);
|
|
157
|
+
// Colors
|
|
158
|
+
addColor(cs.color, 'color');
|
|
159
|
+
addColor(cs.backgroundColor, 'backgroundColor');
|
|
160
|
+
addColor(cs.borderTopColor, 'borderColor');
|
|
161
|
+
addColor(cs.borderRightColor, 'borderColor');
|
|
162
|
+
addColor(cs.borderBottomColor, 'borderColor');
|
|
163
|
+
addColor(cs.borderLeftColor, 'borderColor');
|
|
164
|
+
addColor(cs.outlineColor, 'outlineColor');
|
|
165
|
+
if (cs.backgroundImage.includes('gradient'))
|
|
166
|
+
parseGradientColors(cs.backgroundImage);
|
|
167
|
+
if (cs.backgroundImage.includes('url(')) {
|
|
168
|
+
const urlMatches = cs.backgroundImage.matchAll(/url\(["']?([^"')]+)["']?\)/g);
|
|
169
|
+
for (const m of urlMatches) {
|
|
170
|
+
const u = m[1];
|
|
171
|
+
if (u && !u.startsWith('data:') && !u.startsWith('blob:'))
|
|
172
|
+
backgroundImageUrls.add(u);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (cs.boxShadow !== 'none')
|
|
176
|
+
parseShadowColors(cs.boxShadow);
|
|
177
|
+
// Spacing
|
|
178
|
+
addSpacing(cs.paddingTop, 'padding');
|
|
179
|
+
addSpacing(cs.paddingRight, 'padding');
|
|
180
|
+
addSpacing(cs.paddingBottom, 'padding');
|
|
181
|
+
addSpacing(cs.paddingLeft, 'padding');
|
|
182
|
+
addSpacing(cs.marginTop, 'margin');
|
|
183
|
+
addSpacing(cs.marginRight, 'margin');
|
|
184
|
+
addSpacing(cs.marginBottom, 'margin');
|
|
185
|
+
addSpacing(cs.marginLeft, 'margin');
|
|
186
|
+
addSpacing(cs.gap, 'gap');
|
|
187
|
+
addSpacing(cs.rowGap, 'gap');
|
|
188
|
+
addSpacing(cs.columnGap, 'gap');
|
|
189
|
+
// Border radius
|
|
190
|
+
const br = cs.borderRadius;
|
|
191
|
+
if (br && br !== '0px') {
|
|
192
|
+
borderRadiusMap.set(br, (borderRadiusMap.get(br) ?? 0) + 1);
|
|
193
|
+
}
|
|
194
|
+
// Shadows
|
|
195
|
+
if (cs.boxShadow !== 'none') {
|
|
196
|
+
shadowMap.set(cs.boxShadow, (shadowMap.get(cs.boxShadow) ?? 0) + 1);
|
|
197
|
+
}
|
|
198
|
+
// Typography — only for elements with text content
|
|
199
|
+
if (el.childNodes.length > 0 && Array.from(el.childNodes).some(n => n.nodeType === Node.TEXT_NODE && n.textContent?.trim())) {
|
|
200
|
+
const family = cs.fontFamily;
|
|
201
|
+
const weight = parseInt(cs.fontWeight) || 400;
|
|
202
|
+
const isHeading = HEADING_TAGS.has(el.tagName);
|
|
203
|
+
const isMono = MONO_FAMILIES.test(family);
|
|
204
|
+
const fEntry = fontFamilyMap.get(family);
|
|
205
|
+
if (fEntry) {
|
|
206
|
+
fEntry.weights.add(weight);
|
|
207
|
+
if (isHeading)
|
|
208
|
+
fEntry.headingUse = true;
|
|
209
|
+
if (isMono)
|
|
210
|
+
fEntry.monoUse = true;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
fontFamilyMap.set(family, { weights: new Set([weight]), headingUse: isHeading, monoUse: isMono });
|
|
214
|
+
}
|
|
215
|
+
const scaleKey = `${cs.fontSize}|${cs.fontWeight}|${cs.lineHeight}|${cs.letterSpacing}|${family}`;
|
|
216
|
+
const scaleEntry = typeScaleMap.get(scaleKey);
|
|
217
|
+
if (scaleEntry) {
|
|
218
|
+
scaleEntry.count++;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
typeScaleMap.set(scaleKey, {
|
|
222
|
+
fontSize: cs.fontSize,
|
|
223
|
+
fontWeight: cs.fontWeight,
|
|
224
|
+
lineHeight: cs.lineHeight,
|
|
225
|
+
letterSpacing: cs.letterSpacing,
|
|
226
|
+
fontFamily: family,
|
|
227
|
+
tag: el.tagName.toLowerCase(),
|
|
228
|
+
count: 1,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// Animations/transitions
|
|
233
|
+
if (cs.transition && cs.transition !== 'all 0s ease 0s' && cs.transition !== 'none') {
|
|
234
|
+
const key = cs.transition;
|
|
235
|
+
const aEntry = animationMap.get(key);
|
|
236
|
+
if (aEntry) {
|
|
237
|
+
aEntry.count++;
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
animationMap.set(key, { property: 'transition', value: cs.transition, count: 1 });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (cs.animationName && cs.animationName !== 'none') {
|
|
244
|
+
const key = `animation:${cs.animationName}`;
|
|
245
|
+
const aEntry = animationMap.get(key);
|
|
246
|
+
if (aEntry) {
|
|
247
|
+
aEntry.count++;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
animationMap.set(key, { property: 'animation', value: `${cs.animationName} ${cs.animationDuration} ${cs.animationTimingFunction}`, count: 1 });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// --- Light DOM walk ---
|
|
255
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
|
|
256
|
+
let el = walker.currentNode;
|
|
257
|
+
while (el && elementCount < MAX_ELEMENTS) {
|
|
258
|
+
elementCount++;
|
|
259
|
+
processElement(el);
|
|
260
|
+
el = walker.nextNode();
|
|
261
|
+
}
|
|
262
|
+
// --- Shadow DOM walk ---
|
|
263
|
+
function walkShadowRoots(root) {
|
|
264
|
+
const shadowWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
|
265
|
+
// currentNode starts at root (ShadowRoot, not Element) — advance to first element
|
|
266
|
+
let shadowEl = shadowWalker.nextNode();
|
|
267
|
+
while (shadowEl && elementCount < MAX_ELEMENTS) {
|
|
268
|
+
elementCount++;
|
|
269
|
+
processElement(shadowEl);
|
|
270
|
+
if (shadowEl.shadowRoot) {
|
|
271
|
+
walkShadowRoots(shadowEl.shadowRoot);
|
|
272
|
+
}
|
|
273
|
+
shadowEl = shadowWalker.nextNode();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const allElements = document.body.querySelectorAll('*');
|
|
277
|
+
for (const bodyEl of allElements) {
|
|
278
|
+
if (bodyEl.shadowRoot && elementCount < MAX_ELEMENTS) {
|
|
279
|
+
walkShadowRoots(bodyEl.shadowRoot);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// --- Components: Buttons ---
|
|
283
|
+
const buttonStyles = [];
|
|
284
|
+
const btnFingerprints = new Map();
|
|
285
|
+
const BUTTON_STYLE_KEYS = ['backgroundColor', 'color', 'borderRadius', 'padding', 'fontSize', 'fontWeight', 'border', 'boxShadow', 'textTransform', 'letterSpacing'];
|
|
286
|
+
for (const btn of document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]')) {
|
|
287
|
+
const htmlBtn = btn;
|
|
288
|
+
if (htmlBtn.offsetWidth === 0 && htmlBtn.offsetHeight === 0)
|
|
289
|
+
continue;
|
|
290
|
+
const cs = getComputedStyle(btn);
|
|
291
|
+
const styles = {};
|
|
292
|
+
for (const k of BUTTON_STYLE_KEYS)
|
|
293
|
+
styles[k] = cs[k];
|
|
294
|
+
const fp = JSON.stringify(styles);
|
|
295
|
+
const existing = btnFingerprints.get(fp);
|
|
296
|
+
if (existing) {
|
|
297
|
+
existing.count++;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
const entry = { fingerprint: fp, count: 1, exampleText: htmlBtn.textContent?.trim().slice(0, 60) || '', styles };
|
|
301
|
+
btnFingerprints.set(fp, entry);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
buttonStyles.push(...btnFingerprints.values());
|
|
305
|
+
// --- Components: Inputs ---
|
|
306
|
+
const inputStyles = [];
|
|
307
|
+
const inputFingerprints = new Map();
|
|
308
|
+
const INPUT_STYLE_KEYS = ['backgroundColor', 'color', 'borderRadius', 'padding', 'fontSize', 'border', 'boxShadow', 'outline'];
|
|
309
|
+
for (const inp of document.querySelectorAll('input[type="text"], input[type="email"], input[type="password"], input[type="search"], input[type="number"], input[type="tel"], input[type="url"], textarea, select, [role="textbox"], [role="combobox"]')) {
|
|
310
|
+
const htmlInp = inp;
|
|
311
|
+
if (htmlInp.offsetWidth === 0 && htmlInp.offsetHeight === 0)
|
|
312
|
+
continue;
|
|
313
|
+
const cs = getComputedStyle(inp);
|
|
314
|
+
const styles = {};
|
|
315
|
+
for (const k of INPUT_STYLE_KEYS)
|
|
316
|
+
styles[k] = cs[k];
|
|
317
|
+
const fp = JSON.stringify(styles);
|
|
318
|
+
const existing = inputFingerprints.get(fp);
|
|
319
|
+
if (existing) {
|
|
320
|
+
existing.count++;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const entry = { fingerprint: fp, count: 1, exampleText: inp.placeholder?.slice(0, 60) || '', styles };
|
|
324
|
+
inputFingerprints.set(fp, entry);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
inputStyles.push(...inputFingerprints.values());
|
|
328
|
+
// --- Components: Cards (elements with shadow or border-radius + multiple children) ---
|
|
329
|
+
const cardStyles = [];
|
|
330
|
+
const cardFingerprints = new Map();
|
|
331
|
+
const CARD_STYLE_KEYS = ['borderRadius', 'boxShadow', 'padding', 'backgroundColor', 'border'];
|
|
332
|
+
for (const el of document.querySelectorAll('div, section, article, li')) {
|
|
333
|
+
const htmlEl = el;
|
|
334
|
+
if (htmlEl.offsetWidth === 0 || htmlEl.children.length < 2)
|
|
335
|
+
continue;
|
|
336
|
+
const cs = getComputedStyle(el);
|
|
337
|
+
if (cs.boxShadow === 'none' && cs.borderRadius === '0px' && cs.border === '0px none rgb(0, 0, 0)')
|
|
338
|
+
continue;
|
|
339
|
+
const styles = {};
|
|
340
|
+
for (const k of CARD_STYLE_KEYS)
|
|
341
|
+
styles[k] = cs[k];
|
|
342
|
+
const fp = JSON.stringify(styles);
|
|
343
|
+
const existing = cardFingerprints.get(fp);
|
|
344
|
+
if (existing) {
|
|
345
|
+
existing.count++;
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const entry = { fingerprint: fp, count: 1, styles };
|
|
349
|
+
cardFingerprints.set(fp, entry);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Only keep card patterns with 2+ instances (skip one-off containers)
|
|
353
|
+
for (const c of cardFingerprints.values()) {
|
|
354
|
+
if (c.count >= 2)
|
|
355
|
+
cardStyles.push(c);
|
|
356
|
+
}
|
|
357
|
+
// --- Components: Navigation ---
|
|
358
|
+
const navPatterns = [];
|
|
359
|
+
for (const nav of document.querySelectorAll('nav, [role="navigation"], header')) {
|
|
360
|
+
const cs = getComputedStyle(nav);
|
|
361
|
+
const links = nav.querySelectorAll('a');
|
|
362
|
+
if (links.length === 0)
|
|
363
|
+
continue;
|
|
364
|
+
const firstLinkCs = getComputedStyle(links[0]);
|
|
365
|
+
navPatterns.push({
|
|
366
|
+
selector: nav.tagName.toLowerCase() + (nav.className ? '.' + nav.className.split(/\s+/)[0] : ''),
|
|
367
|
+
layout: {
|
|
368
|
+
display: cs.display,
|
|
369
|
+
flexDirection: cs.flexDirection,
|
|
370
|
+
gap: cs.gap,
|
|
371
|
+
alignItems: cs.alignItems,
|
|
372
|
+
justifyContent: cs.justifyContent,
|
|
373
|
+
backgroundColor: cs.backgroundColor,
|
|
374
|
+
padding: cs.padding,
|
|
375
|
+
},
|
|
376
|
+
linkCount: links.length,
|
|
377
|
+
linkStyles: {
|
|
378
|
+
color: firstLinkCs.color,
|
|
379
|
+
fontSize: firstLinkCs.fontSize,
|
|
380
|
+
fontWeight: firstLinkCs.fontWeight,
|
|
381
|
+
textDecoration: firstLinkCs.textDecoration,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
// --- Logos & Icons ---
|
|
386
|
+
const logos = [];
|
|
387
|
+
const icons = [];
|
|
388
|
+
// SVG logos (in header/nav or with logo-related attributes)
|
|
389
|
+
for (const svg of document.querySelectorAll('svg')) {
|
|
390
|
+
const parent = svg.closest('header, nav, [role="banner"]');
|
|
391
|
+
const classes = (svg.getAttribute('class') || '') + ' ' + (svg.parentElement?.getAttribute('class') || '');
|
|
392
|
+
const ariaLabel = svg.getAttribute('aria-label') || '';
|
|
393
|
+
const isLogo = parent !== null || /logo|brand|mark/i.test(classes) || /logo|brand/i.test(ariaLabel);
|
|
394
|
+
if (isLogo) {
|
|
395
|
+
const content = svg.outerHTML.slice(0, 4096);
|
|
396
|
+
logos.push({
|
|
397
|
+
type: 'svg',
|
|
398
|
+
width: svg.width?.baseVal?.value || undefined,
|
|
399
|
+
height: svg.height?.baseVal?.value || undefined,
|
|
400
|
+
svgContent: content,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
else if (svg.outerHTML.length < 2048) {
|
|
404
|
+
// Collect as icon (small SVGs)
|
|
405
|
+
icons.push({
|
|
406
|
+
selector: svg.tagName + (svg.getAttribute('class') ? '.' + svg.getAttribute('class').split(/\s+/)[0] : ''),
|
|
407
|
+
viewBox: svg.getAttribute('viewBox') || undefined,
|
|
408
|
+
width: svg.width?.baseVal?.value || undefined,
|
|
409
|
+
height: svg.height?.baseVal?.value || undefined,
|
|
410
|
+
content: svg.outerHTML,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// IMG logos
|
|
415
|
+
for (const img of document.querySelectorAll('img')) {
|
|
416
|
+
const parent = img.closest('header, nav, [role="banner"]');
|
|
417
|
+
const src = img.src || '';
|
|
418
|
+
const alt = img.alt || '';
|
|
419
|
+
const classes = img.className || '';
|
|
420
|
+
if (parent !== null || /logo|brand|mark/i.test(src) || /logo|brand/i.test(alt) || /logo|brand/i.test(classes)) {
|
|
421
|
+
logos.push({
|
|
422
|
+
type: 'img',
|
|
423
|
+
src: img.src,
|
|
424
|
+
alt: img.alt || undefined,
|
|
425
|
+
width: img.naturalWidth || undefined,
|
|
426
|
+
height: img.naturalHeight || undefined,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// Limit icons to top 50 unique by viewBox
|
|
431
|
+
const seenViewBoxes = new Set();
|
|
432
|
+
const dedupedIcons = icons.filter(icon => {
|
|
433
|
+
const key = icon.viewBox || icon.content.slice(0, 100);
|
|
434
|
+
if (seenViewBoxes.has(key))
|
|
435
|
+
return false;
|
|
436
|
+
seenViewBoxes.add(key);
|
|
437
|
+
return true;
|
|
438
|
+
}).slice(0, 50);
|
|
439
|
+
// --- Brand Meta ---
|
|
440
|
+
const getMeta = (sel) => document.querySelector(sel)?.getAttribute('content') || undefined;
|
|
441
|
+
const getLink = (sel) => {
|
|
442
|
+
const el = document.querySelector(sel);
|
|
443
|
+
return el ? el.href || el.getAttribute('href') || undefined : undefined;
|
|
444
|
+
};
|
|
445
|
+
const brand = {
|
|
446
|
+
title: getMeta('meta[property="og:title"]') || document.title || undefined,
|
|
447
|
+
description: getMeta('meta[property="og:description"]') || getMeta('meta[name="description"]'),
|
|
448
|
+
themeColor: getMeta('meta[name="theme-color"]'),
|
|
449
|
+
favicon: getLink('link[rel="icon"]') || getLink('link[rel="shortcut icon"]'),
|
|
450
|
+
ogImage: getMeta('meta[property="og:image"]'),
|
|
451
|
+
appleTouchIcon: getLink('link[rel="apple-touch-icon"]'),
|
|
452
|
+
manifestUrl: getLink('link[rel="manifest"]'),
|
|
453
|
+
};
|
|
454
|
+
// --- All Image URLs ---
|
|
455
|
+
const imageUrls = new Set();
|
|
456
|
+
for (const img of document.querySelectorAll('img')) {
|
|
457
|
+
const src = img.src;
|
|
458
|
+
if (src && !src.startsWith('data:') && !src.startsWith('blob:'))
|
|
459
|
+
imageUrls.add(src);
|
|
460
|
+
const srcset = img.srcset;
|
|
461
|
+
if (srcset) {
|
|
462
|
+
for (const part of srcset.split(',')) {
|
|
463
|
+
const url = part.trim().split(/\s+/)[0];
|
|
464
|
+
if (url && !url.startsWith('data:') && !url.startsWith('blob:'))
|
|
465
|
+
imageUrls.add(url);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// --- Video URLs ---
|
|
470
|
+
const videoUrls = new Set();
|
|
471
|
+
for (const video of document.querySelectorAll('video')) {
|
|
472
|
+
const src = video.src;
|
|
473
|
+
if (src && !src.startsWith('data:') && !src.startsWith('blob:'))
|
|
474
|
+
videoUrls.add(src);
|
|
475
|
+
const poster = video.poster;
|
|
476
|
+
if (poster && !poster.startsWith('data:') && !poster.startsWith('blob:'))
|
|
477
|
+
imageUrls.add(poster);
|
|
478
|
+
for (const source of video.querySelectorAll('source')) {
|
|
479
|
+
const ssrc = source.src;
|
|
480
|
+
if (ssrc && !ssrc.startsWith('data:') && !ssrc.startsWith('blob:'))
|
|
481
|
+
videoUrls.add(ssrc);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// Orphan <source> elements (audio, picture)
|
|
485
|
+
for (const source of document.querySelectorAll('source[src]')) {
|
|
486
|
+
const ssrc = source.src;
|
|
487
|
+
if (ssrc && /\.(mp4|webm|mov|ogg|avi|m3u8)/i.test(ssrc))
|
|
488
|
+
videoUrls.add(ssrc);
|
|
489
|
+
}
|
|
490
|
+
// <picture> <source> with srcset
|
|
491
|
+
for (const source of document.querySelectorAll('picture source[srcset]')) {
|
|
492
|
+
const srcset = source.srcset;
|
|
493
|
+
if (srcset) {
|
|
494
|
+
for (const part of srcset.split(',')) {
|
|
495
|
+
const url = part.trim().split(/\s+/)[0];
|
|
496
|
+
if (url && !url.startsWith('data:') && !url.startsWith('blob:'))
|
|
497
|
+
imageUrls.add(url);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// --- Lazy-load data attributes ---
|
|
502
|
+
for (const el of document.querySelectorAll('[data-src], [data-bg], [data-background-image], [data-poster]')) {
|
|
503
|
+
for (const attr of ['data-src', 'data-bg', 'data-background-image', 'data-poster']) {
|
|
504
|
+
const val = el.getAttribute(attr);
|
|
505
|
+
if (!val || val.startsWith('data:') || val.startsWith('blob:'))
|
|
506
|
+
continue;
|
|
507
|
+
try {
|
|
508
|
+
const resolved = new URL(val, document.location.href).href;
|
|
509
|
+
if (/\.(mp4|webm|mov|ogg|avi|m3u8)/i.test(val))
|
|
510
|
+
videoUrls.add(resolved);
|
|
511
|
+
else
|
|
512
|
+
imageUrls.add(resolved);
|
|
513
|
+
}
|
|
514
|
+
catch { /* bad URL */ }
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// --- Link Assets (favicons, icons, manifests) ---
|
|
518
|
+
const linkAssetUrls = [];
|
|
519
|
+
for (const link of document.querySelectorAll('link[href]')) {
|
|
520
|
+
const href = link.href;
|
|
521
|
+
const rel = link.rel || '';
|
|
522
|
+
if (href && !href.startsWith('data:') && !href.startsWith('blob:') &&
|
|
523
|
+
/^(icon|shortcut icon|apple-touch-icon|manifest|preload)$/i.test(rel)) {
|
|
524
|
+
linkAssetUrls.push({ url: href, rel });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// --- Inline script library detection ---
|
|
528
|
+
const detectedLibraries = [];
|
|
529
|
+
const seenLibs = new Set();
|
|
530
|
+
for (const script of document.querySelectorAll('script')) {
|
|
531
|
+
const src = script.src || '';
|
|
532
|
+
const text = script.textContent || '';
|
|
533
|
+
const content = src + ' ' + text.slice(0, 10_000);
|
|
534
|
+
const checks = [
|
|
535
|
+
[/gsap|ScrollTrigger|ScrollSmoother/i, 'gsap'],
|
|
536
|
+
[/new\s+p5\b|p5\.setup|p5\.draw|p5\.js/i, 'p5.js'],
|
|
537
|
+
[/THREE\.|new\s+THREE\.|from\s+['"]three/i, 'three.js'],
|
|
538
|
+
[/lottie|bodymovin/i, 'lottie'],
|
|
539
|
+
[/anime\s*\(|animejs/i, 'anime.js'],
|
|
540
|
+
[/framer-motion|motion\.div/i, 'framer-motion'],
|
|
541
|
+
[/webflow/i, 'webflow'],
|
|
542
|
+
[/swiper/i, 'swiper'],
|
|
543
|
+
[/locomotive.?scroll/i, 'locomotive-scroll'],
|
|
544
|
+
[/rive\.?app|@rive-app/i, 'rive'],
|
|
545
|
+
[/spline/i, 'spline'],
|
|
546
|
+
];
|
|
547
|
+
for (const [regex, name] of checks) {
|
|
548
|
+
if (!seenLibs.has(name) && regex.test(content)) {
|
|
549
|
+
seenLibs.add(name);
|
|
550
|
+
detectedLibraries.push(name);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// --- Assemble result ---
|
|
555
|
+
return {
|
|
556
|
+
customProperties,
|
|
557
|
+
colors: Array.from(colorMap.entries())
|
|
558
|
+
.map(([value, data]) => ({ value, count: data.count, properties: Array.from(data.properties) }))
|
|
559
|
+
.sort((a, b) => b.count - a.count),
|
|
560
|
+
typography: {
|
|
561
|
+
families: Array.from(fontFamilyMap.entries()).map(([family, data]) => ({
|
|
562
|
+
family,
|
|
563
|
+
weights: Array.from(data.weights).sort((a, b) => a - b),
|
|
564
|
+
headingUse: data.headingUse,
|
|
565
|
+
monoUse: data.monoUse,
|
|
566
|
+
})),
|
|
567
|
+
scale: Array.from(typeScaleMap.values()).sort((a, b) => b.count - a.count),
|
|
568
|
+
},
|
|
569
|
+
brand,
|
|
570
|
+
logos,
|
|
571
|
+
icons: dedupedIcons,
|
|
572
|
+
spacing: Array.from(spacingMap.entries())
|
|
573
|
+
.map(([value, data]) => ({ value, count: data.count, properties: Array.from(data.properties) }))
|
|
574
|
+
.sort((a, b) => b.count - a.count),
|
|
575
|
+
borders: Array.from(borderRadiusMap.entries())
|
|
576
|
+
.map(([borderRadius, count]) => ({ borderRadius, count }))
|
|
577
|
+
.sort((a, b) => b.count - a.count),
|
|
578
|
+
shadows: Array.from(shadowMap.entries())
|
|
579
|
+
.map(([value, count]) => ({ value, count }))
|
|
580
|
+
.sort((a, b) => b.count - a.count),
|
|
581
|
+
components: {
|
|
582
|
+
buttons: buttonStyles.sort((a, b) => b.count - a.count),
|
|
583
|
+
inputs: inputStyles.sort((a, b) => b.count - a.count),
|
|
584
|
+
cards: cardStyles.sort((a, b) => b.count - a.count),
|
|
585
|
+
nav: navPatterns,
|
|
586
|
+
},
|
|
587
|
+
animations: Array.from(animationMap.values()).sort((a, b) => b.count - a.count),
|
|
588
|
+
fontFiles,
|
|
589
|
+
imageUrls: Array.from(imageUrls),
|
|
590
|
+
backgroundImageUrls: Array.from(backgroundImageUrls),
|
|
591
|
+
videoUrls: Array.from(videoUrls),
|
|
592
|
+
externalStylesheetUrls,
|
|
593
|
+
linkAssetUrls,
|
|
594
|
+
detectedLibraries,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
// ---------------------------------------------------------------------------
|
|
598
|
+
// Color clustering — assign semantic roles to extracted colors
|
|
599
|
+
// ---------------------------------------------------------------------------
|
|
600
|
+
function rgbaToHsl(rgba) {
|
|
601
|
+
const match = rgba.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/);
|
|
602
|
+
if (!match)
|
|
603
|
+
return null;
|
|
604
|
+
const r = parseFloat(match[1]) / 255;
|
|
605
|
+
const g = parseFloat(match[2]) / 255;
|
|
606
|
+
const b = parseFloat(match[3]) / 255;
|
|
607
|
+
const a = match[4] !== undefined ? parseFloat(match[4]) : 1;
|
|
608
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
609
|
+
const l = (max + min) / 2;
|
|
610
|
+
if (max === min)
|
|
611
|
+
return { h: 0, s: 0, l, a };
|
|
612
|
+
const d = max - min;
|
|
613
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
614
|
+
let h = 0;
|
|
615
|
+
if (max === r)
|
|
616
|
+
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
617
|
+
else if (max === g)
|
|
618
|
+
h = ((b - r) / d + 2) / 6;
|
|
619
|
+
else
|
|
620
|
+
h = ((r - g) / d + 4) / 6;
|
|
621
|
+
return { h: h * 360, s: s * 100, l: l * 100, a };
|
|
622
|
+
}
|
|
623
|
+
function rgbaToHex(rgba) {
|
|
624
|
+
const match = rgba.match(/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/);
|
|
625
|
+
if (!match)
|
|
626
|
+
return rgba;
|
|
627
|
+
const r = Math.round(parseFloat(match[1]));
|
|
628
|
+
const g = Math.round(parseFloat(match[2]));
|
|
629
|
+
const b = Math.round(parseFloat(match[3]));
|
|
630
|
+
const a = match[4] !== undefined ? parseFloat(match[4]) : 1;
|
|
631
|
+
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
|
|
632
|
+
if (a < 1)
|
|
633
|
+
return `${hex}${Math.round(a * 255).toString(16).padStart(2, '0')}`;
|
|
634
|
+
return hex;
|
|
635
|
+
}
|
|
636
|
+
function clusterColors(colors) {
|
|
637
|
+
const result = [];
|
|
638
|
+
let primaryHue = -1;
|
|
639
|
+
let secondaryHue = -1;
|
|
640
|
+
for (const c of colors) {
|
|
641
|
+
const hsl = rgbaToHsl(c.value);
|
|
642
|
+
const hex = rgbaToHex(c.value);
|
|
643
|
+
let cluster;
|
|
644
|
+
if (!hsl || hsl.s < 10) {
|
|
645
|
+
// Low saturation = neutral (grays, black, white)
|
|
646
|
+
if (hsl && hsl.l > 90)
|
|
647
|
+
cluster = 'background';
|
|
648
|
+
else
|
|
649
|
+
cluster = 'neutral';
|
|
650
|
+
}
|
|
651
|
+
else if (c.properties.includes('borderColor') && !c.properties.includes('color') && !c.properties.includes('backgroundColor')) {
|
|
652
|
+
cluster = 'border';
|
|
653
|
+
}
|
|
654
|
+
else if (primaryHue < 0) {
|
|
655
|
+
primaryHue = hsl.h;
|
|
656
|
+
cluster = 'primary';
|
|
657
|
+
}
|
|
658
|
+
else if (Math.abs(hsl.h - primaryHue) < 30 || Math.abs(hsl.h - primaryHue) > 330) {
|
|
659
|
+
cluster = 'primary';
|
|
660
|
+
}
|
|
661
|
+
else if (secondaryHue < 0) {
|
|
662
|
+
secondaryHue = hsl.h;
|
|
663
|
+
cluster = 'secondary';
|
|
664
|
+
}
|
|
665
|
+
else if (Math.abs(hsl.h - secondaryHue) < 30 || Math.abs(hsl.h - secondaryHue) > 330) {
|
|
666
|
+
cluster = 'secondary';
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
cluster = 'accent';
|
|
670
|
+
}
|
|
671
|
+
result.push({ value: c.value, hex, count: c.count, properties: c.properties, cluster });
|
|
672
|
+
}
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
// ---------------------------------------------------------------------------
|
|
676
|
+
// Grid base unit detection
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
function detectGridUnit(spacingValues) {
|
|
679
|
+
// Build frequency-weighted pairs
|
|
680
|
+
const pairs = spacingValues
|
|
681
|
+
.map(s => ({ px: parseFloat(s.value), count: s.count }))
|
|
682
|
+
.filter(p => p.px > 0 && p.px <= 200 && Number.isInteger(p.px));
|
|
683
|
+
if (pairs.length < 3)
|
|
684
|
+
return undefined;
|
|
685
|
+
// Score common grid units by how much of the total usage they explain
|
|
686
|
+
const CANDIDATES = [4, 5, 6, 8, 10];
|
|
687
|
+
let bestUnit = 0;
|
|
688
|
+
let bestScore = 0;
|
|
689
|
+
for (const unit of CANDIDATES) {
|
|
690
|
+
let aligned = 0;
|
|
691
|
+
let total = 0;
|
|
692
|
+
for (const p of pairs) {
|
|
693
|
+
total += p.count;
|
|
694
|
+
if (p.px % unit === 0)
|
|
695
|
+
aligned += p.count;
|
|
696
|
+
}
|
|
697
|
+
const score = aligned / total;
|
|
698
|
+
if (score > bestScore) {
|
|
699
|
+
bestScore = score;
|
|
700
|
+
bestUnit = unit;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
// Require at least 60% of usage aligns with the grid
|
|
704
|
+
return bestScore >= 0.6 ? bestUnit : undefined;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Extract design tokens from a URL at multiple viewports.
|
|
708
|
+
* Pure DOM extraction — no LLM calls.
|
|
709
|
+
*
|
|
710
|
+
* ```typescript
|
|
711
|
+
* import { extractDesignTokens } from '@tangle-network/browser-agent-driver'
|
|
712
|
+
*
|
|
713
|
+
* const { tokens } = await extractDesignTokens({ url: 'https://stripe.com' })
|
|
714
|
+
* console.log(tokens.colors.filter(c => c.cluster === 'primary'))
|
|
715
|
+
* console.log(tokens.typography.families)
|
|
716
|
+
* console.log(tokens.brand)
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
/**
|
|
720
|
+
* Reduce a candidate asset filename to a safe, separator-free basename so a
|
|
721
|
+
* DOM-controlled value (e.g. a hostile `@font-face` family `../../../tmp/evil`)
|
|
722
|
+
* can never escape the output directory when joined to it. Strips any path
|
|
723
|
+
* components and leading dots (no traversal, no dotfiles); returns `fallback`
|
|
724
|
+
* when nothing usable remains.
|
|
725
|
+
*/
|
|
726
|
+
export function safeAssetFilename(raw, fallback) {
|
|
727
|
+
return path.basename(raw).replace(/^[.\s]+/, '') || fallback;
|
|
728
|
+
}
|
|
729
|
+
export async function extractDesignTokens(opts) {
|
|
730
|
+
const viewports = opts.viewports ?? [...VIEWPORTS];
|
|
731
|
+
const outputDir = opts.outputDir ?? `./audit-results/${new URL(opts.url).hostname}-tokens-${Date.now()}`;
|
|
732
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
733
|
+
const screenshotDir = path.join(outputDir, 'screenshots');
|
|
734
|
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
735
|
+
const browser = await chromium.launch({ headless: opts.headless ?? true });
|
|
736
|
+
const allCustomProps = {};
|
|
737
|
+
const allColors = new Map();
|
|
738
|
+
const allFamilies = new Map();
|
|
739
|
+
const allScaleEntries = [];
|
|
740
|
+
let brand = {};
|
|
741
|
+
const allLogos = [];
|
|
742
|
+
const allIcons = [];
|
|
743
|
+
const allFontFiles = new Map();
|
|
744
|
+
const allImageUrls = new Set();
|
|
745
|
+
const allBackgroundImageUrls = new Set();
|
|
746
|
+
const allVideoUrls = new Set();
|
|
747
|
+
const allExternalStylesheetUrls = new Set();
|
|
748
|
+
const allLinkAssetUrls = new Map(); // url -> rel
|
|
749
|
+
const allDetectedLibraries = new Set();
|
|
750
|
+
const responsiveTokens = {};
|
|
751
|
+
const screenshotPaths = {};
|
|
752
|
+
let scrollMotion;
|
|
753
|
+
for (const vp of viewports) {
|
|
754
|
+
const context = await browser.newContext({ viewport: { width: vp.width, height: vp.height } });
|
|
755
|
+
const page = await context.newPage();
|
|
756
|
+
try {
|
|
757
|
+
await page.goto(opts.url, { waitUntil: 'networkidle', timeout: 20_000 }).catch(() => page.goto(opts.url, { waitUntil: 'domcontentloaded', timeout: 15_000 }));
|
|
758
|
+
await page.waitForTimeout(2000);
|
|
759
|
+
await dismissCookieBanners(page);
|
|
760
|
+
await page.waitForTimeout(500);
|
|
761
|
+
// Opt-in live scroll-motion capture — runs FIRST on the freshly loaded
|
|
762
|
+
// page, BEFORE the fullPage screenshot scrolls it, so one-shot scroll
|
|
763
|
+
// reveals are observed as they actually fire. Desktop only (long-scroll is
|
|
764
|
+
// a desktop signal); the capturer restores scroll to the top when done.
|
|
765
|
+
if (opts.captureScrollMotion && vp.name === 'desktop') {
|
|
766
|
+
scrollMotion = await createScrollCapturer().capture(page).catch(() => undefined);
|
|
767
|
+
}
|
|
768
|
+
const screenshotPath = path.join(screenshotDir, `${vp.name}.png`);
|
|
769
|
+
await page.screenshot({ path: screenshotPath, fullPage: true }).catch(() => page.screenshot({ path: screenshotPath, fullPage: false }));
|
|
770
|
+
screenshotPaths[vp.name] = screenshotPath;
|
|
771
|
+
const raw = await page.evaluate(extractTokensFromDOM);
|
|
772
|
+
Object.assign(allCustomProps, raw.customProperties);
|
|
773
|
+
for (const c of raw.colors) {
|
|
774
|
+
const existing = allColors.get(c.value);
|
|
775
|
+
if (existing) {
|
|
776
|
+
existing.count += c.count;
|
|
777
|
+
for (const p of c.properties)
|
|
778
|
+
existing.properties.add(p);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
allColors.set(c.value, { count: c.count, properties: new Set(c.properties) });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
for (const f of raw.typography.families) {
|
|
785
|
+
const existing = allFamilies.get(f.family);
|
|
786
|
+
if (existing) {
|
|
787
|
+
for (const w of f.weights)
|
|
788
|
+
existing.weights.add(w);
|
|
789
|
+
if (f.headingUse)
|
|
790
|
+
existing.headingUse = true;
|
|
791
|
+
if (f.monoUse)
|
|
792
|
+
existing.monoUse = true;
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
allFamilies.set(f.family, { weights: new Set(f.weights), headingUse: f.headingUse, monoUse: f.monoUse });
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
allScaleEntries.push(...raw.typography.scale);
|
|
799
|
+
if (vp.name === 'desktop' || !brand.title) {
|
|
800
|
+
brand = raw.brand;
|
|
801
|
+
}
|
|
802
|
+
for (const logo of raw.logos) {
|
|
803
|
+
if (!allLogos.some(l => l.src === logo.src && l.svgContent === logo.svgContent)) {
|
|
804
|
+
allLogos.push(logo);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
for (const icon of raw.icons) {
|
|
808
|
+
if (!allIcons.some(i => i.content === icon.content)) {
|
|
809
|
+
allIcons.push(icon);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
for (const ff of raw.fontFiles) {
|
|
813
|
+
if (!allFontFiles.has(ff.src)) {
|
|
814
|
+
allFontFiles.set(ff.src, ff);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
for (const u of raw.imageUrls)
|
|
818
|
+
allImageUrls.add(u);
|
|
819
|
+
for (const u of raw.backgroundImageUrls)
|
|
820
|
+
allBackgroundImageUrls.add(u);
|
|
821
|
+
for (const u of raw.videoUrls)
|
|
822
|
+
allVideoUrls.add(u);
|
|
823
|
+
for (const u of raw.externalStylesheetUrls)
|
|
824
|
+
allExternalStylesheetUrls.add(u);
|
|
825
|
+
for (const la of raw.linkAssetUrls)
|
|
826
|
+
allLinkAssetUrls.set(la.url, la.rel);
|
|
827
|
+
for (const lib of raw.detectedLibraries)
|
|
828
|
+
allDetectedLibraries.add(lib);
|
|
829
|
+
responsiveTokens[vp.name] = {
|
|
830
|
+
width: vp.width,
|
|
831
|
+
height: vp.height,
|
|
832
|
+
spacing: raw.spacing,
|
|
833
|
+
gridBaseUnit: detectGridUnit(raw.spacing),
|
|
834
|
+
borders: raw.borders,
|
|
835
|
+
shadows: raw.shadows,
|
|
836
|
+
components: raw.components,
|
|
837
|
+
animations: raw.animations,
|
|
838
|
+
screenshotPath,
|
|
839
|
+
};
|
|
840
|
+
opts.onProgress?.(vp.name, vp.width, vp.height, {
|
|
841
|
+
colors: raw.colors.length,
|
|
842
|
+
fonts: raw.typography.families.length,
|
|
843
|
+
buttons: raw.components.buttons.length,
|
|
844
|
+
inputs: raw.components.inputs.length,
|
|
845
|
+
cards: raw.components.cards.length,
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
finally {
|
|
849
|
+
await context.close();
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
await browser.close();
|
|
853
|
+
// Deduplicate type scale
|
|
854
|
+
const scaleMap = new Map();
|
|
855
|
+
for (const entry of allScaleEntries) {
|
|
856
|
+
const key = `${entry.fontSize}|${entry.fontWeight}|${entry.lineHeight}|${entry.fontFamily}`;
|
|
857
|
+
const existing = scaleMap.get(key);
|
|
858
|
+
if (existing) {
|
|
859
|
+
existing.count += entry.count;
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
const isHeading = /^h[1-6]$/.test(entry.tag);
|
|
863
|
+
const fontSize = parseFloat(entry.fontSize);
|
|
864
|
+
let usage = 'body';
|
|
865
|
+
if (isHeading)
|
|
866
|
+
usage = 'heading';
|
|
867
|
+
else if (fontSize <= 12)
|
|
868
|
+
usage = 'caption';
|
|
869
|
+
else if (fontSize <= 14 && entry.fontWeight >= '500')
|
|
870
|
+
usage = 'label';
|
|
871
|
+
scaleMap.set(key, { fontSize: entry.fontSize, fontWeight: entry.fontWeight, lineHeight: entry.lineHeight, letterSpacing: entry.letterSpacing, fontFamily: entry.fontFamily, usage, tag: entry.tag, count: entry.count });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const mergedColors = Array.from(allColors.entries())
|
|
875
|
+
.map(([value, data]) => ({ value, count: data.count, properties: Array.from(data.properties) }))
|
|
876
|
+
.sort((a, b) => b.count - a.count);
|
|
877
|
+
const clusteredColors = clusterColors(mergedColors);
|
|
878
|
+
const families = Array.from(allFamilies.entries()).map(([family, data]) => ({
|
|
879
|
+
family,
|
|
880
|
+
weights: Array.from(data.weights).sort((a, b) => a - b),
|
|
881
|
+
classification: data.monoUse ? 'mono' :
|
|
882
|
+
(data.headingUse && allFamilies.size > 1) ? 'heading' :
|
|
883
|
+
data.headingUse ? 'display' : 'body',
|
|
884
|
+
}));
|
|
885
|
+
// Download font files
|
|
886
|
+
const downloadedFontFiles = [];
|
|
887
|
+
if (allFontFiles.size > 0) {
|
|
888
|
+
const fontDir = path.join(outputDir, 'fonts');
|
|
889
|
+
fs.mkdirSync(fontDir, { recursive: true });
|
|
890
|
+
for (const ff of allFontFiles.values()) {
|
|
891
|
+
try {
|
|
892
|
+
const res = await fetch(ff.src);
|
|
893
|
+
if (!res.ok)
|
|
894
|
+
continue;
|
|
895
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
896
|
+
const urlPath = decodeURIComponent(new URL(ff.src).pathname);
|
|
897
|
+
const rawName = path.basename(urlPath) || `${ff.family.replace(/\s+/g, '-')}-${ff.weight}-${ff.style}.${ff.format === 'unknown' ? 'bin' : ff.format}`;
|
|
898
|
+
const localPath = path.join(fontDir, safeAssetFilename(rawName, 'font.bin'));
|
|
899
|
+
fs.writeFileSync(localPath, buffer);
|
|
900
|
+
downloadedFontFiles.push({ ...ff, localPath });
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
downloadedFontFiles.push({ ...ff });
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// Download images
|
|
908
|
+
const downloadedImages = [];
|
|
909
|
+
const allAssetUrls = new Map();
|
|
910
|
+
for (const u of allImageUrls)
|
|
911
|
+
allAssetUrls.set(u, 'img');
|
|
912
|
+
for (const u of allBackgroundImageUrls)
|
|
913
|
+
allAssetUrls.set(u, 'background');
|
|
914
|
+
// Brand assets
|
|
915
|
+
if (brand.favicon)
|
|
916
|
+
allAssetUrls.set(brand.favicon, 'favicon');
|
|
917
|
+
if (brand.ogImage)
|
|
918
|
+
allAssetUrls.set(brand.ogImage, 'og-image');
|
|
919
|
+
if (brand.appleTouchIcon)
|
|
920
|
+
allAssetUrls.set(brand.appleTouchIcon, 'icon');
|
|
921
|
+
for (const [url, rel] of allLinkAssetUrls) {
|
|
922
|
+
if (/icon/i.test(rel))
|
|
923
|
+
allAssetUrls.set(url, 'favicon');
|
|
924
|
+
}
|
|
925
|
+
if (allAssetUrls.size > 0) {
|
|
926
|
+
const imageDir = path.join(outputDir, 'images');
|
|
927
|
+
fs.mkdirSync(imageDir, { recursive: true });
|
|
928
|
+
for (const [url, type] of allAssetUrls) {
|
|
929
|
+
try {
|
|
930
|
+
const res = await fetch(url);
|
|
931
|
+
if (!res.ok) {
|
|
932
|
+
downloadedImages.push({ url, type });
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
936
|
+
const urlPath = decodeURIComponent(new URL(url).pathname);
|
|
937
|
+
const filename = path.basename(urlPath) || `asset-${downloadedImages.length}.bin`;
|
|
938
|
+
const localPath = path.join(imageDir, filename);
|
|
939
|
+
fs.writeFileSync(localPath, buffer);
|
|
940
|
+
downloadedImages.push({
|
|
941
|
+
url, type, localPath,
|
|
942
|
+
mimeType: res.headers.get('content-type') || undefined,
|
|
943
|
+
sizeBytes: buffer.length,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
downloadedImages.push({ url, type });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// Download external stylesheets
|
|
952
|
+
const downloadedStylesheets = [];
|
|
953
|
+
if (allExternalStylesheetUrls.size > 0) {
|
|
954
|
+
const cssDir = path.join(outputDir, 'stylesheets');
|
|
955
|
+
fs.mkdirSync(cssDir, { recursive: true });
|
|
956
|
+
for (const url of allExternalStylesheetUrls) {
|
|
957
|
+
try {
|
|
958
|
+
const res = await fetch(url);
|
|
959
|
+
if (!res.ok) {
|
|
960
|
+
downloadedStylesheets.push({ url });
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
const text = await res.text();
|
|
964
|
+
const urlPath = decodeURIComponent(new URL(url).pathname);
|
|
965
|
+
const filename = path.basename(urlPath) || `style-${downloadedStylesheets.length}.css`;
|
|
966
|
+
const localPath = path.join(cssDir, filename);
|
|
967
|
+
fs.writeFileSync(localPath, text);
|
|
968
|
+
downloadedStylesheets.push({ url, localPath });
|
|
969
|
+
}
|
|
970
|
+
catch {
|
|
971
|
+
downloadedStylesheets.push({ url });
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Download videos
|
|
976
|
+
const downloadedVideos = [];
|
|
977
|
+
if (allVideoUrls.size > 0) {
|
|
978
|
+
const videoDir = path.join(outputDir, 'videos');
|
|
979
|
+
fs.mkdirSync(videoDir, { recursive: true });
|
|
980
|
+
for (const url of allVideoUrls) {
|
|
981
|
+
try {
|
|
982
|
+
const res = await fetch(url);
|
|
983
|
+
if (!res.ok) {
|
|
984
|
+
downloadedVideos.push({ url, type: 'video' });
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
988
|
+
const urlPath = decodeURIComponent(new URL(url).pathname);
|
|
989
|
+
const filename = path.basename(urlPath) || `video-${downloadedVideos.length}.mp4`;
|
|
990
|
+
const localPath = path.join(videoDir, filename);
|
|
991
|
+
fs.writeFileSync(localPath, buffer);
|
|
992
|
+
downloadedVideos.push({
|
|
993
|
+
url, type: 'video', localPath,
|
|
994
|
+
mimeType: res.headers.get('content-type') || undefined,
|
|
995
|
+
sizeBytes: buffer.length,
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
catch {
|
|
999
|
+
downloadedVideos.push({ url, type: 'video' });
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const tokens = {
|
|
1004
|
+
url: opts.url,
|
|
1005
|
+
extractedAt: new Date().toISOString(),
|
|
1006
|
+
viewportsAudited: viewports.map(v => v.name),
|
|
1007
|
+
customProperties: allCustomProps,
|
|
1008
|
+
colors: clusteredColors,
|
|
1009
|
+
typography: { families, scale: Array.from(scaleMap.values()).sort((a, b) => b.count - a.count) },
|
|
1010
|
+
brand,
|
|
1011
|
+
logos: allLogos,
|
|
1012
|
+
icons: allIcons.slice(0, 50),
|
|
1013
|
+
fontFiles: downloadedFontFiles,
|
|
1014
|
+
images: downloadedImages,
|
|
1015
|
+
videos: downloadedVideos,
|
|
1016
|
+
stylesheets: downloadedStylesheets,
|
|
1017
|
+
responsive: responsiveTokens,
|
|
1018
|
+
detectedLibraries: Array.from(allDetectedLibraries),
|
|
1019
|
+
};
|
|
1020
|
+
const tokenPath = path.join(outputDir, 'tokens.json');
|
|
1021
|
+
fs.writeFileSync(tokenPath, JSON.stringify(tokens, null, 2));
|
|
1022
|
+
return { tokens, outputDir, screenshotPaths, scrollMotion };
|
|
1023
|
+
}
|
|
1024
|
+
// ---------------------------------------------------------------------------
|
|
1025
|
+
// Zip packaging
|
|
1026
|
+
// ---------------------------------------------------------------------------
|
|
1027
|
+
export async function createZipBundle(outputDir, tokens) {
|
|
1028
|
+
const { execSync } = await import('node:child_process');
|
|
1029
|
+
const zipPath = `${outputDir}.zip`;
|
|
1030
|
+
// Use system zip (available on macOS/Linux)
|
|
1031
|
+
execSync(`cd "${path.dirname(outputDir)}" && zip -r "${path.basename(zipPath)}" "${path.basename(outputDir)}"`, { stdio: 'pipe' });
|
|
1032
|
+
return zipPath;
|
|
1033
|
+
}
|
|
1034
|
+
//# sourceMappingURL=extract.js.map
|