codymaster 4.1.4 → 4.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -2
- package/README.md +56 -86
- package/dist/index.js +130 -16
- package/dist/ui/box.js +2 -2
- package/dist/ui/onboarding.js +11 -5
- package/install.sh +317 -35
- package/package.json +8 -8
- package/public/dashboard/app.js +1270 -0
- package/public/dashboard/index.html +218 -0
- package/public/dashboard/style.css +440 -0
- package/skills/AGENTS.md +61 -0
- package/skills/CLAUDE.md +158 -0
- package/skills/boxme-git-config/SKILL.md +56 -0
- package/skills/boxme-local-dev/SKILL.md +66 -0
- package/skills/build.sh +30 -0
- package/skills/cf +314 -0
- package/skills/cf 2 +313 -0
- package/skills/cm-ads-tracker/SKILL.md +364 -69
- package/skills/cm-auto-publisher/SKILL.md +81 -0
- package/skills/cm-booking-calendar/SKILL.md +521 -0
- package/skills/cm-booking-calendar/references/industry-patterns.md +527 -0
- package/skills/cm-booking-calendar/templates/booking-form.css +626 -0
- package/skills/cm-booking-calendar/templates/booking-form.html +477 -0
- package/skills/cm-booking-calendar/templates/calendar-engine.js +419 -0
- package/skills/cm-booking-calendar/templates/calendar-export.js +395 -0
- package/skills/cm-booking-calendar/templates/reminder-config.js +629 -0
- package/skills/cm-brainstorm-idea/SKILL.md +15 -24
- package/skills/cm-clean-code/SKILL.md +300 -0
- package/skills/cm-code-review/SKILL.md +0 -27
- package/skills/cm-codeintell/SKILL.md +598 -0
- package/skills/cm-content-factory/.content-factory-state.json +132 -0
- package/skills/cm-content-factory/.git 2/logs/refs/heads/main +1 -0
- package/skills/cm-content-factory/.git 2/logs/refs/remotes/origin/main +1 -0
- package/skills/cm-content-factory/.git 2/objects/02/fb0956734b5f8ba3f918b7defd04a89cfe0076 +0 -0
- package/skills/cm-content-factory/.git 2/objects/08/1e129d75dc6feac6c02037272e6bd1a04e3324 +0 -0
- package/skills/cm-content-factory/.git 2/objects/0c/5393416f3c5e01c9a655a802bff0dd52f76f0a +0 -0
- package/skills/cm-content-factory/.git 2/objects/10/0b9be46978a946a77188f68be725098a122001 +0 -0
- package/skills/cm-content-factory/.git 2/objects/10/cf041167fc9843610eb3d90259ef3396315fdc +0 -0
- package/skills/cm-content-factory/.git 2/objects/12/5e19538dd6e1338ffe74f6c4c165b00435bf48 +0 -0
- package/skills/cm-content-factory/.git 2/objects/16/a9b9d0088d5c1347628b45a2620b479d8ad57c +0 -0
- package/skills/cm-content-factory/.git 2/objects/17/8c2a9ef93c33ae4eec9d58e82321f9229843a1 +0 -0
- package/skills/cm-content-factory/.git 2/objects/25/397ae41d09104d763bdcac2695209d85cdea89 +0 -0
- package/skills/cm-content-factory/.git 2/objects/2f/a836b7947f2d458e1f639788bf4bb0983a3305 +0 -0
- package/skills/cm-content-factory/.git 2/objects/3a/baaaf0a1c0909c0828335791557125fba911e0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/42/2924221b81f5ce3c4e4daac9a64a24f9b01f9a +0 -0
- package/skills/cm-content-factory/.git 2/objects/42/ec0ce707447dc11446a34c9995fb8533801731 +0 -0
- package/skills/cm-content-factory/.git 2/objects/46/e43ce92866d56ce74b1d750db307cfe6154a15 +0 -0
- package/skills/cm-content-factory/.git 2/objects/48/5e41b633c63f55b8277bcc59f44f67681f671a +0 -0
- package/skills/cm-content-factory/.git 2/objects/49/49c596a3a89fa240642acd95dd3258e261eb09 +0 -0
- package/skills/cm-content-factory/.git 2/objects/50/9d42d8412ef8eaf7f7e138476bac2e4d10ce60 +0 -0
- package/skills/cm-content-factory/.git 2/objects/55/0c8c389d981b463ef849aeb792d8be3ccb6ec8 +0 -0
- package/skills/cm-content-factory/.git 2/objects/5d/82d3b18410cdda3ace3677436f0cb599dbe2d2 +0 -0
- package/skills/cm-content-factory/.git 2/objects/60/0617c58e871a38b33bf29e282d132bb3c381ad +0 -0
- package/skills/cm-content-factory/.git 2/objects/6a/8369a99c687b7245c92ffaf0e0f0dab9014504 +0 -0
- package/skills/cm-content-factory/.git 2/objects/79/bea435d40ab531c1aaf6be0432c6a5b7aaed21 +0 -0
- package/skills/cm-content-factory/.git 2/objects/7e/5ebd79251c2f14e4aceb86c74b6b6daae6b500 +0 -0
- package/skills/cm-content-factory/.git 2/objects/81/98a822a60178d6d5023ddb3e222cddf048742e +0 -0
- package/skills/cm-content-factory/.git 2/objects/86/0a0e1943dfe53411d2e499a1f16f46a96ef758 +0 -0
- package/skills/cm-content-factory/.git 2/objects/86/971fb55fdc081fdbae52376f0f13e57a4e9b04 +0 -0
- package/skills/cm-content-factory/.git 2/objects/88/b89dd609a0a03f8d4fe8bfde20d5b8fc1d326d +0 -0
- package/skills/cm-content-factory/.git 2/objects/90/8737edb6b7809e32cc01590b4e08ba42a9d40d +0 -0
- package/skills/cm-content-factory/.git 2/objects/93/d5a8a9a7d4fb7f11491cb596a6880528725118 +0 -0
- package/skills/cm-content-factory/.git 2/objects/98/46a2ab81d0c3b3eb00ef88fc56989aa7e9f316 +0 -0
- package/skills/cm-content-factory/.git 2/objects/9b/d8dd1e49cf274eaf9c555f3ab39dce7af5715e +0 -0
- package/skills/cm-content-factory/.git 2/objects/a1/13329fb0cec96ae78b222d33a24c3b5bc7fa1f +0 -0
- package/skills/cm-content-factory/.git 2/objects/a9/e6effe626e8a3aea3a8fc3364b492191c6e7d0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ad/6de7e48d9782cca9353d1ff0aa1aab7fe1df85 +0 -0
- package/skills/cm-content-factory/.git 2/objects/af/54ae316f771ff692e299ffcd8bf2f06b413b59 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b0/4cb8b0b00dad633e731c1472161419e738d674 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b3/094abb0b9ed46419b269e4a4e36a459690e3b0 +0 -0
- package/skills/cm-content-factory/.git 2/objects/b9/435c5d4baac2cfc5c83009ddd27b46b60db5f1 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ba/5da17dbaec5ec2dcfdfd126aead518d1171d5c +0 -0
- package/skills/cm-content-factory/.git 2/objects/c0/bf58703aa258ba5dd63083bebaec8f223d844c +0 -0
- package/skills/cm-content-factory/.git 2/objects/c4/701a34edf1fc1bad58ccc57bd03f9426acb59a +0 -0
- package/skills/cm-content-factory/.git 2/objects/c7/5ccce9a4e5cc74d9b3174550cf6d993ca43638 +0 -0
- package/skills/cm-content-factory/.git 2/objects/c7/710d59b5a35b0f1f0a0399386643a0bd94c929 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d1/fe58237112e953e5fec52da22cf38e08be3df9 +5 -0
- package/skills/cm-content-factory/.git 2/objects/d2/2bbe9fd2f74c95bc5583e803f5e435f1e2cd86 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d7/e72852ea2bff74581dbf247d400120086229f4 +0 -0
- package/skills/cm-content-factory/.git 2/objects/d8/d4c3b5553e4fd72807e1d4b49ef07d9ef3ac35 +0 -0
- package/skills/cm-content-factory/.git 2/objects/dc/75050c2876f6a02ae2a53a3c886f395b622977 +0 -0
- package/skills/cm-content-factory/.git 2/objects/ee/e8546f95acec500187c08a28a8b9ee02db0dec +0 -0
- package/skills/cm-content-factory/.git 2/objects/ef/263c059208b416c2146434f10cb2b9fabcba16 +0 -0
- package/skills/cm-content-factory/.git 2/objects/f3/ae597e84d9a59b88acd21c99bde2eaf686d785 +0 -0
- package/skills/cm-content-factory/.git 2/objects/f3/f6f5673c821d3d8e76fa267a9e882e7a5387ea +0 -0
- package/skills/cm-content-factory/.git 2/objects/f9/6e6d0ad02624dd11d5848594d056caef7a5e8b +0 -0
- package/skills/cm-content-factory/.git 2/objects/ff/278988fc1edf0db3abcf18de795f4cc0b4f3e1 +0 -0
- package/skills/cm-content-factory/.git 2/refs/heads/main +1 -0
- package/skills/cm-content-factory/.git 2/refs/remotes/origin/main +1 -0
- package/skills/cm-content-factory/.pytest_cache 2/v/cache/nodeids +76 -0
- package/skills/cm-content-factory/.pytest_cache 2/v/cache/stepwise +1 -0
- package/skills/cm-content-factory/AGENTS.md +61 -0
- package/skills/cm-content-factory/CLAUDE.md +63 -0
- package/skills/cm-content-factory/CURSOR.md +43 -0
- package/skills/cm-content-factory/Content Factory.zip +0 -0
- package/skills/cm-content-factory/cf +313 -0
- package/skills/cm-content-factory/config.schema.json +397 -0
- package/skills/cm-content-factory/dashboard/app.js +556 -0
- package/skills/cm-content-factory/dashboard/index.html +397 -0
- package/skills/cm-content-factory/dashboard/style.css +1211 -0
- package/skills/cm-content-factory/examples/01-real-estate.config.json +146 -0
- package/skills/cm-content-factory/examples/02-personal-finance.config.json +146 -0
- package/skills/cm-content-factory/examples/03-health-wellness.config.json +147 -0
- package/skills/cm-content-factory/examples/04-saas-software.config.json +147 -0
- package/skills/cm-content-factory/examples/05-legal-services.config.json +147 -0
- package/skills/cm-content-factory/examples/06-insurance.config.json +146 -0
- package/skills/cm-content-factory/examples/07-ecommerce-dropship.config.json +146 -0
- package/skills/cm-content-factory/examples/08-online-education.config.json +147 -0
- package/skills/cm-content-factory/examples/09-crypto-defi.config.json +147 -0
- package/skills/cm-content-factory/examples/10-beauty-skincare.config.json +147 -0
- package/skills/cm-content-factory/examples/11-home-services.config.json +146 -0
- package/skills/cm-content-factory/examples/12-dental-clinic.config.json +147 -0
- package/skills/cm-content-factory/examples/13-pet-care.config.json +147 -0
- package/skills/cm-content-factory/examples/14-travel-hospitality.config.json +147 -0
- package/skills/cm-content-factory/examples/15-ai-automation.config.json +147 -0
- package/skills/cm-content-factory/examples/16-wedding-events.config.json +147 -0
- package/skills/cm-content-factory/examples/17-fitness-coaching.config.json +148 -0
- package/skills/cm-content-factory/examples/18-cybersecurity.config.json +147 -0
- package/skills/cm-content-factory/examples/19-food-restaurant.config.json +148 -0
- package/skills/cm-content-factory/examples/20-solar-energy.config.json +147 -0
- package/skills/cm-content-factory/examples/fitness-blog.config.json +116 -0
- package/skills/cm-content-factory/examples/tech-blog.config.json +107 -0
- package/skills/cm-content-factory/extensions/EXTENSION_GUIDE.md +72 -0
- package/skills/cm-content-factory/extensions/hooks.py +126 -0
- package/skills/cm-content-factory/extensions/openclaw_adapter.py +132 -0
- package/skills/cm-content-factory/landing/index.html +680 -0
- package/skills/cm-content-factory/landing/script.js +101 -0
- package/skills/cm-content-factory/landing/style.css +1216 -0
- package/skills/cm-content-factory/landing/translations.js +508 -0
- package/skills/cm-content-factory/logs/events.jsonl +11 -0
- package/skills/cm-content-factory/profiles/_template.profile.json +231 -0
- package/skills/cm-content-factory/profiles/finance.profile.json +278 -0
- package/skills/cm-content-factory/profiles/legal.profile.json +263 -0
- package/skills/cm-content-factory/profiles/medical-research.profile.json +321 -0
- package/skills/cm-content-factory/profiles/technology.profile.json +275 -0
- package/skills/cm-content-factory/scripts/agent_dispatcher.py +266 -0
- package/skills/cm-content-factory/scripts/audit.py +106 -0
- package/skills/cm-content-factory/scripts/dashboard_server.py +225 -0
- package/skills/cm-content-factory/scripts/deploy.py +146 -0
- package/skills/cm-content-factory/scripts/extract.py +132 -0
- package/skills/cm-content-factory/scripts/landing_generator.py +459 -0
- package/skills/cm-content-factory/scripts/memory.py +521 -0
- package/skills/cm-content-factory/scripts/monetize.py +239 -0
- package/skills/cm-content-factory/scripts/pipeline.py +357 -0
- package/skills/cm-content-factory/scripts/plan.py +163 -0
- package/skills/cm-content-factory/scripts/publish.py +145 -0
- package/skills/cm-content-factory/scripts/research.py +337 -0
- package/skills/cm-content-factory/scripts/scaffold.py +464 -0
- package/skills/cm-content-factory/scripts/scoreboard.py +336 -0
- package/skills/cm-content-factory/scripts/seo.py +90 -0
- package/skills/cm-content-factory/scripts/state_manager.py +320 -0
- package/skills/cm-content-factory/scripts/token_manager.py +268 -0
- package/skills/cm-content-factory/scripts/validate.py +221 -0
- package/skills/cm-content-factory/scripts/wizard.py +329 -0
- package/skills/cm-content-factory/scripts/write.py +93 -0
- package/skills/cm-content-factory/sites/docs-site/src/assets/houston.webp +0 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/architecture.md +90 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/data-flow.md +54 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/deployment.md +38 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/index.md +65 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/lc-content-lifecycle.md +48 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/seq-write-mode.md +39 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/uj-first-batch.md +42 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/wf-content-pipeline.md +51 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/flows/wf-learning-cycle.md +52 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/configuration.md +86 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/installation.md +80 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/getting-started/intro.md +58 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/index.md +102 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/index.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/optimize-seo.md +29 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/scale-content-production.md +55 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/jtbd/standardize-quality.md +29 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/buyer-cmo-huong.md +41 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/buyer-content-lead-khoa.md +40 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/index.md +56 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-content-manager-lan.md +46 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-seo-minh.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/personas/user-writer-tu.md +45 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/content-pipeline.md +108 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/index.md +22 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/memory-system.md +52 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/seo-optimization.md +58 -0
- package/skills/cm-content-factory/sites/docs-site/src/content/docs/sop/troubleshooting-guide.md +92 -0
- package/skills/cm-content-factory/sites/docs-site/src/styles/custom.css +575 -0
- package/skills/cm-content-factory/tests/conftest.py +66 -0
- package/skills/cm-content-factory/tests/test_agent_dispatcher.py +125 -0
- package/skills/cm-content-factory/tests/test_memory.py +128 -0
- package/skills/cm-content-factory/tests/test_pipeline.py +107 -0
- package/skills/cm-content-factory/tests/test_research.py +56 -0
- package/skills/cm-content-factory/tests/test_state_manager.py +131 -0
- package/skills/cm-content-factory/tests/test_token_manager.py +110 -0
- package/skills/cm-content-factory/tests/test_wizard.py +121 -0
- package/skills/cm-continuity/SKILL.md +7 -0
- package/skills/cm-cro-methodology/SKILL.md +290 -0
- package/skills/cm-dashboard/SKILL.md +7 -525
- package/skills/cm-debugging/SKILL.md +7 -116
- package/skills/cm-deep-search/SKILL.md +5 -1
- package/skills/cm-dockit/README.md +6 -15
- package/skills/cm-dockit/SKILL.md +20 -37
- package/skills/cm-execution/SKILL.md +6 -1
- package/skills/cm-frappe-agent/SKILL.md +134 -0
- package/skills/cm-frappe-agent/agents/doctype-architect.md +596 -0
- package/skills/cm-frappe-agent/agents/erpnext-customizer.md +643 -0
- package/skills/cm-frappe-agent/agents/frappe-backend.md +814 -0
- package/skills/cm-frappe-agent/agents/frappe-custom-frontend.md +557 -0
- package/skills/cm-frappe-agent/agents/frappe-debugger.md +625 -0
- package/skills/cm-frappe-agent/agents/frappe-fixer.md +275 -0
- package/skills/cm-frappe-agent/agents/frappe-frontend.md +660 -0
- package/skills/cm-frappe-agent/agents/frappe-installer.md +158 -0
- package/skills/cm-frappe-agent/agents/frappe-performance.md +307 -0
- package/skills/cm-frappe-agent/agents/frappe-planner.md +419 -0
- package/skills/cm-frappe-agent/agents/frappe-remote-ops.md +153 -0
- package/skills/cm-frappe-agent/agents/github-workflow.md +286 -0
- package/skills/cm-frappe-agent/commands/frappe-app.md +351 -0
- package/skills/cm-frappe-agent/commands/frappe-backend.md +162 -0
- package/skills/cm-frappe-agent/commands/frappe-bench.md +254 -0
- package/skills/cm-frappe-agent/commands/frappe-debug.md +263 -0
- package/skills/cm-frappe-agent/commands/frappe-doctype-create.md +272 -0
- package/skills/cm-frappe-agent/commands/frappe-doctype-field.md +310 -0
- package/skills/cm-frappe-agent/commands/frappe-erpnext.md +210 -0
- package/skills/cm-frappe-agent/commands/frappe-fix.md +59 -0
- package/skills/cm-frappe-agent/commands/frappe-frontend.md +210 -0
- package/skills/cm-frappe-agent/commands/frappe-fullstack.md +243 -0
- package/skills/cm-frappe-agent/commands/frappe-github.md +57 -0
- package/skills/cm-frappe-agent/commands/frappe-install.md +52 -0
- package/skills/cm-frappe-agent/commands/frappe-plan.md +442 -0
- package/skills/cm-frappe-agent/commands/frappe-remote.md +58 -0
- package/skills/cm-frappe-agent/commands/frappe-test.md +356 -0
- package/skills/cm-frappe-agent/docs/README.md +51 -0
- package/skills/cm-frappe-agent/docs/agents-catalog.md +113 -0
- package/skills/cm-frappe-agent/docs/architecture.md +149 -0
- package/skills/cm-frappe-agent/docs/commands-catalog.md +82 -0
- package/skills/cm-frappe-agent/docs/resources-catalog.md +66 -0
- package/skills/cm-frappe-agent/docs/sitemap-urls.txt +52 -0
- package/skills/cm-frappe-agent/docs/sitemap.md +81 -0
- package/skills/cm-frappe-agent/docs/sop/user-guide.md +178 -0
- package/skills/cm-frappe-agent/docs/sop/vibe-coding-guide.md +122 -0
- package/skills/cm-frappe-agent/resources/7-layer-architecture.md +985 -0
- package/skills/cm-frappe-agent/resources/bench_commands.md +73 -0
- package/skills/cm-frappe-agent/resources/code-patterns-guide.md +948 -0
- package/skills/cm-frappe-agent/resources/common_pitfalls.md +266 -0
- package/skills/cm-frappe-agent/resources/doctype-registry.md +158 -0
- package/skills/cm-frappe-agent/resources/installation-guide.md +289 -0
- package/skills/cm-frappe-agent/resources/rest-api-patterns.md +182 -0
- package/skills/cm-frappe-agent/resources/scaffold_checklist.md +82 -0
- package/skills/cm-frappe-agent/resources/upgrade_patterns.md +113 -0
- package/skills/cm-frappe-agent/resources/web-form-patterns.md +252 -0
- package/skills/cm-frappe-agent/skills/bench-commands/SKILL.md +621 -0
- package/skills/cm-frappe-agent/skills/client-scripts/SKILL.md +642 -0
- package/skills/cm-frappe-agent/skills/doctype-patterns/SKILL.md +576 -0
- package/skills/cm-frappe-agent/skills/frappe-api/SKILL.md +740 -0
- package/skills/cm-frappe-agent/skills/remote-operations/SKILL.md +47 -0
- package/skills/cm-frappe-agent/skills/server-scripts/SKILL.md +608 -0
- package/skills/cm-frappe-agent/skills/web-forms/SKILL.md +46 -0
- package/skills/cm-git-worktrees/SKILL.md +0 -7
- package/skills/cm-google-form/SKILL.md +266 -0
- package/skills/cm-google-form/templates/apps-script.js +55 -0
- package/skills/cm-google-form/templates/form-markup.html +110 -0
- package/skills/cm-google-form/templates/form-submit.js +201 -0
- package/skills/cm-google-form/templates/toast.css +152 -0
- package/skills/cm-growth-hacking/SKILL.md +293 -0
- package/skills/cm-growth-hacking/bottom-sheet-engine.md +261 -0
- package/skills/cm-growth-hacking/calendar-integration.md +264 -0
- package/skills/cm-growth-hacking/references/engagement-patterns.md +346 -0
- package/skills/cm-growth-hacking/templates/bottom-sheet.css +528 -0
- package/skills/cm-growth-hacking/templates/bottom-sheet.js +269 -0
- package/skills/cm-growth-hacking/templates/calendar-cta.js +213 -0
- package/skills/cm-growth-hacking/templates/tracking-events.js +211 -0
- package/skills/cm-growth-hacking/templates/trigger-manager.js +254 -0
- package/skills/cm-growth-hacking/tracking-events.md +246 -0
- package/skills/cm-growth-hacking/trigger-system.md +342 -0
- package/skills/cm-how-it-work/SKILL.md +20 -4
- package/skills/cm-identity-guard/SKILL.md +0 -11
- package/skills/cm-jtbd/SKILL.md +1 -1
- package/skills/cm-notebooklm/SKILL.md +172 -0
- package/skills/cm-notebooklm/references/command_reference.md +94 -0
- package/skills/cm-notebooklm/references/workflows.md +60 -0
- package/skills/cm-notebooklm/resources/knowledge_sources.md +106 -0
- package/skills/cm-notebooklm/scripts/brain-sync.sh +453 -0
- package/skills/cm-notebooklm/scripts/graduate_wisdom.py +101 -0
- package/skills/cm-planning/SKILL.md +39 -52
- package/skills/cm-project-bootstrap/SKILL.md +1308 -99
- package/skills/cm-quality-gate/SKILL.md +13 -106
- package/skills/cm-reactor/SKILL.md +274 -0
- package/skills/cm-safe-deploy/SKILL.md +415 -52
- package/skills/cm-safe-i18n/SKILL.md +1 -22
- package/skills/cm-secret-shield/SKILL.md +2 -2
- package/skills/cm-security-gate/SKILL.md +114 -0
- package/skills/cm-skill-chain/SKILL.md +2 -2
- package/skills/cm-skill-index/SKILL.md +9 -6
- package/skills/cm-skill-mastery/SKILL.md +2 -15
- package/skills/cm-start/SKILL.md +9 -0
- package/skills/cm-tdd/SKILL.md +16 -49
- package/skills/cm-ui-preview/SKILL.md +35 -173
- package/skills/cm-ux-master/FEATURES-v4.md +305 -0
- package/skills/cm-ux-master/README-ru.md +135 -0
- package/skills/cm-ux-master/README-vi.md +135 -0
- package/skills/cm-ux-master/README-zh.md +135 -0
- package/skills/cm-ux-master/README.md +489 -0
- package/skills/cm-ux-master/SKILL.md +773 -62
- package/skills/cm-ux-master/cli/README.md +180 -0
- package/skills/cm-ux-master/cli/pyproject.toml +106 -0
- package/skills/cm-ux-master/cli/requirements.txt +21 -0
- package/skills/cm-ux-master/cli/templates/base/skill-core.md +262 -0
- package/skills/cm-ux-master/cli/templates/platforms/claude.yaml +21 -0
- package/skills/cm-ux-master/cli/templates/platforms/cursor.yaml +21 -0
- package/skills/cm-ux-master/cli/templates/platforms/figma.yaml +24 -0
- package/skills/cm-ux-master/cli/templates/platforms/vscode-mcp.yaml +28 -0
- package/skills/cm-ux-master/cli/templates/platforms/windsurf.yaml +21 -0
- package/skills/cm-ux-master/cli/uxmaster/__init__.py +10 -0
- package/skills/cm-ux-master/cli/uxmaster/__main__.py +19 -0
- package/skills/cm-ux-master/cli/uxmaster/cli.py +349 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/__init__.py +8 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/extract.py +18 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/init.py +58 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/mcp.py +194 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/search.py +23 -0
- package/skills/cm-ux-master/cli/uxmaster/commands/validate.py +270 -0
- package/skills/cm-ux-master/cli/uxmaster/search_engine.py +532 -0
- package/skills/cm-ux-master/cli/uxmaster/template_engine.py +458 -0
- package/skills/cm-ux-master/cli/uxmaster/utils/__init__.py +9 -0
- package/skills/cm-ux-master/cli/uxmaster/utils/console.py +42 -0
- package/skills/cm-ux-master/cli/uxmaster/utils/detect.py +83 -0
- package/skills/cm-ux-master/data/accessibility-advanced.csv +26 -0
- package/skills/cm-ux-master/data/animation.csv +31 -0
- package/skills/cm-ux-master/data/charts.csv +26 -0
- package/skills/cm-ux-master/data/colors.csv +97 -0
- package/skills/cm-ux-master/data/design-tests.csv +37 -0
- package/skills/cm-ux-master/data/devices.csv +21 -0
- package/skills/cm-ux-master/data/icons.csv +101 -0
- package/skills/cm-ux-master/data/landing.csv +31 -0
- package/skills/cm-ux-master/data/products.csv +97 -0
- package/skills/cm-ux-master/data/react-performance.csv +45 -0
- package/skills/cm-ux-master/data/responsive.csv +26 -0
- package/skills/cm-ux-master/data/semi-tokens.csv +52 -0
- package/skills/cm-ux-master/data/stacks/angular.csv +34 -0
- package/skills/cm-ux-master/data/stacks/astro.csv +54 -0
- package/skills/cm-ux-master/data/stacks/electron.csv +32 -0
- package/skills/cm-ux-master/data/stacks/flutter.csv +53 -0
- package/skills/cm-ux-master/data/stacks/html-tailwind.csv +56 -0
- package/skills/cm-ux-master/data/stacks/htmx.csv +28 -0
- package/skills/cm-ux-master/data/stacks/jetpack-compose.csv +53 -0
- package/skills/cm-ux-master/data/stacks/nextjs.csv +53 -0
- package/skills/cm-ux-master/data/stacks/nuxt-ui.csv +51 -0
- package/skills/cm-ux-master/data/stacks/nuxtjs.csv +59 -0
- package/skills/cm-ux-master/data/stacks/react-native.csv +52 -0
- package/skills/cm-ux-master/data/stacks/react.csv +54 -0
- package/skills/cm-ux-master/data/stacks/shadcn.csv +61 -0
- package/skills/cm-ux-master/data/stacks/svelte.csv +54 -0
- package/skills/cm-ux-master/data/stacks/swiftui.csv +51 -0
- package/skills/cm-ux-master/data/stacks/tauri.csv +29 -0
- package/skills/cm-ux-master/data/stacks/vue.csv +50 -0
- package/skills/cm-ux-master/data/styles.csv +68 -0
- package/skills/cm-ux-master/data/typography.csv +58 -0
- package/skills/cm-ux-master/data/ui-reasoning.csv +101 -0
- package/skills/cm-ux-master/data/ux-guidelines.csv +100 -0
- package/skills/cm-ux-master/data/ux-laws.csv +49 -0
- package/skills/cm-ux-master/data/web-interface.csv +31 -0
- package/skills/cm-ux-master/docs/LANDING-PAGE.html +377 -0
- package/skills/cm-ux-master/docs/README.md +108 -0
- package/skills/cm-ux-master/docs/css/styles.css +573 -0
- package/skills/cm-ux-master/docs/examples/demo-script.md +319 -0
- package/skills/cm-ux-master/docs/guides/for-designers.md +692 -0
- package/skills/cm-ux-master/docs/guides/for-developers.md +778 -0
- package/skills/cm-ux-master/docs/guides/for-product-managers.md +693 -0
- package/skills/cm-ux-master/docs/guides/react-guide-vi.md +50 -0
- package/skills/cm-ux-master/docs/index.html +1062 -0
- package/skills/cm-ux-master/docs/js/i18n.js +84 -0
- package/skills/cm-ux-master/docs/js/lang/de.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/en.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/fr.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/hi.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/id.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/ja.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/ko.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/ru.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/vi.js +145 -0
- package/skills/cm-ux-master/docs/js/lang/zh.js +145 -0
- package/skills/cm-ux-master/docs/js/main.js +117 -0
- package/skills/cm-ux-master/docs/plan/PHASE1-COMPLETION.md +217 -0
- package/skills/cm-ux-master/docs/plan/PHASE2-COMPLETION.md +199 -0
- package/skills/cm-ux-master/docs/plan/PHASE2-ENHANCED-COMPLETION.md +352 -0
- package/skills/cm-ux-master/docs/plan/PHASE3-VALIDATION-COMPLETION.md +499 -0
- package/skills/cm-ux-master/docs/plan/PHASE4-TESTING-POLISH-COMPLETION.md +483 -0
- package/skills/cm-ux-master/docs/plan/UXM-2.0-ROADMAP.md +681 -0
- package/skills/cm-ux-master/docs/plan/WOW-PITCH.md +410 -0
- package/skills/cm-ux-master/docs/technical/api-reference.md +824 -0
- package/skills/cm-ux-master/docs/technical/harvester-v4.md +328 -0
- package/skills/cm-ux-master/docs/technical/how-it-works.md +1128 -0
- package/skills/cm-ux-master/docs/tutorials/quickstart.md +339 -0
- package/skills/cm-ux-master/docs/tutorials/tutorials.md +939 -0
- package/skills/cm-ux-master/docs/tutorials/user-guide.md +716 -0
- package/skills/cm-ux-master/examples/README.md +63 -0
- package/skills/cm-ux-master/mcp/__init__.py +3 -0
- package/skills/cm-ux-master/mcp/integrations/__init__.py +11 -0
- package/skills/cm-ux-master/mcp/integrations/figma/__init__.py +6 -0
- package/skills/cm-ux-master/mcp/integrations/figma/client.py +293 -0
- package/skills/cm-ux-master/mcp/integrations/figma/plugin/code.js +561 -0
- package/skills/cm-ux-master/mcp/integrations/figma/plugin/ui.html +334 -0
- package/skills/cm-ux-master/mcp/integrations/stitch/__init__.py +5 -0
- package/skills/cm-ux-master/mcp/integrations/stitch/client.py +410 -0
- package/skills/cm-ux-master/mcp/integrations/vscode/package.json +167 -0
- package/skills/cm-ux-master/mcp/integrations/vscode/src/extension.ts +81 -0
- package/skills/cm-ux-master/mcp/mcp-config.json +274 -0
- package/skills/cm-ux-master/mcp/server.py +771 -0
- package/skills/cm-ux-master/mcp/tools/__init__.py +13 -0
- package/skills/cm-ux-master/mcp-server/server.py +595 -0
- package/skills/cm-ux-master/output/fila/FilaDashboard.tsx +47 -0
- package/skills/cm-ux-master/output/fila/components/badge/component.tsx +35 -0
- package/skills/cm-ux-master/output/fila/components/badge/index.ts +1 -0
- package/skills/cm-ux-master/output/fila/components/button/component.tsx +53 -0
- package/skills/cm-ux-master/output/fila/components/button/index.ts +1 -0
- package/skills/cm-ux-master/output/fila/components/card/component.tsx +35 -0
- package/skills/cm-ux-master/output/fila/components/card/index.ts +1 -0
- package/skills/cm-ux-master/output/fila/components/input/component.tsx +41 -0
- package/skills/cm-ux-master/output/fila/components/input/index.ts +1 -0
- package/skills/cm-ux-master/output/fila/design-system.css +151 -0
- package/skills/cm-ux-master/output/fila/design-system.html +1596 -0
- package/skills/cm-ux-master/output/fila/design-system.json +168 -0
- package/skills/cm-ux-master/output/fila/figma-tokens.json +523 -0
- package/skills/cm-ux-master/output/fila/harvest-v4-raw.json +406 -0
- package/skills/cm-ux-master/output/fila/semi-theme-override.css +95 -0
- package/skills/cm-ux-master/output/haravan/HaravanDashboard.tsx +103 -0
- package/skills/cm-ux-master/output/haravan/design-system-v3-live.html +2716 -0
- package/skills/cm-ux-master/output/haravan/design-system-v3.html +1770 -0
- package/skills/cm-ux-master/output/haravan/design-system.html +914 -0
- package/skills/cm-ux-master/output/haravan/figma-tokens.json +84 -0
- package/skills/cm-ux-master/output/haravan/haravan-harvest.json +33 -0
- package/skills/cm-ux-master/output/haravan/harvest-v3-raw.json +167 -0
- package/skills/cm-ux-master/output/haravan/semi-theme-override.css +39 -0
- package/skills/cm-ux-master/references/audit-template.md +257 -0
- package/skills/cm-ux-master/references/cultural-ux.md +346 -0
- package/skills/cm-ux-master/references/dark-patterns.md +362 -0
- package/skills/cm-ux-master/references/heuristic-conflicts.md +296 -0
- package/skills/cm-ux-master/references/krug-principles.md +289 -0
- package/skills/cm-ux-master/references/nielsen-heuristics.md +360 -0
- package/skills/cm-ux-master/references/wcag-checklist.md +306 -0
- package/skills/cm-ux-master/scripts/component_generator.py +631 -0
- package/skills/cm-ux-master/scripts/core.py +305 -0
- package/skills/cm-ux-master/scripts/demo_validation.py +452 -0
- package/skills/cm-ux-master/scripts/design_doc_generator.py +1325 -0
- package/skills/cm-ux-master/scripts/design_system.py +1141 -0
- package/skills/cm-ux-master/scripts/design_system_indexer.py +889 -0
- package/skills/cm-ux-master/scripts/extract_i18n.py +251 -0
- package/skills/cm-ux-master/scripts/extractor.py +1437 -0
- package/skills/cm-ux-master/scripts/figma_bridge.py +406 -0
- package/skills/cm-ux-master/scripts/generate.py +147 -0
- package/skills/cm-ux-master/scripts/harvest_session.py +207 -0
- package/skills/cm-ux-master/scripts/harvester.js +240 -0
- package/skills/cm-ux-master/scripts/harvester_browser.py +717 -0
- package/skills/cm-ux-master/scripts/harvester_cli.py +431 -0
- package/skills/cm-ux-master/scripts/harvester_v1.js +275 -0
- package/skills/cm-ux-master/scripts/harvester_v3.js +620 -0
- package/skills/cm-ux-master/scripts/harvester_v4.js +1003 -0
- package/skills/cm-ux-master/scripts/install.py +528 -0
- package/skills/cm-ux-master/scripts/license.py +81 -0
- package/skills/cm-ux-master/scripts/media/qrpayment.png +0 -0
- package/skills/cm-ux-master/scripts/pro_stubs.py +120 -0
- package/skills/cm-ux-master/scripts/project_registry.py +217 -0
- package/skills/cm-ux-master/scripts/search.py +114 -0
- package/skills/cm-ux-master/scripts/semi_mcp_bridge.py +425 -0
- package/skills/cm-ux-master/scripts/stitch_integration.py +583 -0
- package/skills/cm-ux-master/scripts/test_harvester_v4.py +335 -0
- package/skills/cm-ux-master/scripts/token_mapper.py +626 -0
- package/skills/cm-ux-master/scripts/validation_engine.py +1571 -0
- package/skills/cm-ux-master/scripts/wizard.py +653 -0
- package/skills/cm-ux-master/setup.py +93 -0
- package/skills/cm-ux-master/templates/base/flutter-widget.dart +69 -0
- package/skills/cm-ux-master/templates/base/html-page.html +152 -0
- package/skills/cm-ux-master/templates/base/react-component.tsx +47 -0
- package/skills/cm-ux-master/templates/base/swiftui-view.swift +62 -0
- package/skills/cm-ux-master/templates/quick-start.sh +176 -0
- package/skills/cm-ux-master/tests/automation/batch-validate.sh +250 -0
- package/skills/cm-ux-master/tests/automation/generate-test-projects.sh +561 -0
- package/skills/cm-ux-master/tests/automation/run-all-tests.sh +315 -0
- package/skills/cm-ux-master/tests/test_design_doc.py +145 -0
- package/skills/cm-ux-master/tests/test_devices.py +74 -0
- package/skills/cm-ux-master/tests/test_generator.py +116 -0
- package/skills/cm-ux-master/tests/test_harvest_session.py +131 -0
- package/skills/cm-ux-master/tests/test_harvester.py +127 -0
- package/skills/cm-ux-master/tests/test_harvester_v3.py +324 -0
- package/skills/cm-ux-master/tests/test_mcp_server.py +496 -0
- package/skills/cm-ux-master/tests/test_new_domains.py +108 -0
- package/skills/cm-ux-master/tests/test_new_stacks.py +103 -0
- package/skills/cm-ux-master/tests/test_project_registry.py +146 -0
- package/skills/cm-ux-master/tests/test_semi_mcp_bridge.py +207 -0
- package/skills/cm-ux-master/tests/test_token_mapper.py +247 -0
- package/skills/cm-ux-master/tests/test_validation_engine.py +617 -0
- package/skills/config.schema.json +397 -0
- package/skills/frappe-app-builder.zip +0 -0
- package/skills/jobs-to-be-done/SKILL.md +266 -0
- package/skills/jobs-to-be-done/references/case-studies.md +154 -0
- package/skills/jobs-to-be-done/references/competitive-strategy.md +280 -0
- package/skills/jobs-to-be-done/references/diagnostics.md +158 -0
- package/skills/jobs-to-be-done/references/innovation-process.md +392 -0
- package/skills/jobs-to-be-done/references/organizational-change.md +328 -0
- package/skills/marketplace-report-crawler/SKILL.md +176 -0
- package/skills/marketplace-report-crawler/config/accounts.json +41 -0
- package/skills/marketplace-report-crawler/config/report-types.json +422 -0
- package/skills/marketplace-report-crawler/config/sessions.json +3 -0
- package/skills/marketplace-report-crawler/scripts/ab-wrapper.sh +102 -0
- package/skills/marketplace-report-crawler/scripts/browser-actions/lazada/lazada-actions.js +114 -0
- package/skills/marketplace-report-crawler/scripts/browser-actions/shopee/shopee-actions.js +94 -0
- package/skills/marketplace-report-crawler/scripts/browser-actions/tiktok/tiktok-actions.js +272 -0
- package/skills/marketplace-report-crawler/scripts/crawl-runner.js +281 -0
- package/skills/marketplace-report-crawler/scripts/session-check.sh +72 -0
- package/skills/marketplace-report-crawler/scripts/session-manager.sh +349 -0
- package/skills/marketplace-report-crawler/scripts/setup-folders.sh +83 -0
- package/skills/medical-research/SKILL.md +194 -0
- package/skills/medical-research/scripts/evidence_checker.py +288 -0
- package/skills/mom-test/SKILL.md +267 -0
- package/skills/mom-test/references/avoiding-bad-data.md +221 -0
- package/skills/mom-test/references/case-studies.md +306 -0
- package/skills/mom-test/references/commitment-advancement.md +219 -0
- package/skills/mom-test/references/finding-conversations.md +251 -0
- package/skills/mom-test/references/processing-learning.md +256 -0
- package/skills/mom-test/references/question-patterns.md +198 -0
- package/skills/pandasai-analytics/SKILL.md +251 -0
- package/skills/release-it/SKILL.md +235 -0
- package/skills/release-it/references/anti-patterns.md +279 -0
- package/skills/release-it/references/capacity-planning.md +285 -0
- package/skills/release-it/references/chaos-engineering.md +325 -0
- package/skills/release-it/references/deployment-strategies.md +331 -0
- package/skills/release-it/references/observability.md +301 -0
- package/skills/release-it/references/stability-patterns.md +355 -0
- package/skills/scripts/sync-ide-skills.sh +61 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-audit.md +37 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-compare.md +34 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-export.md +51 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-generate.md +39 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-scaffold.md +52 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-simulate.md +25 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-stats.md +31 -0
- package/skills/skill-creator-ultra/.agents/workflows/skill-validate.md +25 -0
- package/skills/skill-creator-ultra/README.md +1242 -0
- package/skills/skill-creator-ultra/SKILL.md +388 -0
- package/skills/skill-creator-ultra/agents/analyzer.md +274 -0
- package/skills/skill-creator-ultra/agents/comparator.md +202 -0
- package/skills/skill-creator-ultra/agents/grader.md +223 -0
- package/skills/skill-creator-ultra/assets/eval_review.html +146 -0
- package/skills/skill-creator-ultra/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator-ultra/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator-ultra/examples/example_anthropic_frontend.md +109 -0
- package/skills/skill-creator-ultra/examples/example_anthropic_pdf.md +116 -0
- package/skills/skill-creator-ultra/examples/example_api_docs.md +189 -0
- package/skills/skill-creator-ultra/examples/example_db_migration.md +253 -0
- package/skills/skill-creator-ultra/examples/example_git_commit.md +111 -0
- package/skills/skill-creator-ultra/install.ps1 +289 -0
- package/skills/skill-creator-ultra/install.sh +313 -0
- package/skills/skill-creator-ultra/phases/phase1_interview.md +202 -0
- package/skills/skill-creator-ultra/phases/phase2_extract.md +55 -0
- package/skills/skill-creator-ultra/phases/phase3_detect.md +57 -0
- package/skills/skill-creator-ultra/phases/phase4_generate.md +543 -0
- package/skills/skill-creator-ultra/phases/phase5_test.md +319 -0
- package/skills/skill-creator-ultra/phases/phase6_eval.md +301 -0
- package/skills/skill-creator-ultra/phases/phase7_iterate.md +103 -0
- package/skills/skill-creator-ultra/phases/phase8_optimize.md +113 -0
- package/skills/skill-creator-ultra/resources/advanced_patterns.md +499 -0
- package/skills/skill-creator-ultra/resources/anti_patterns.md +376 -0
- package/skills/skill-creator-ultra/resources/blueprints.md +498 -0
- package/skills/skill-creator-ultra/resources/checklist.md +243 -0
- package/skills/skill-creator-ultra/resources/composition_cookbook.md +291 -0
- package/skills/skill-creator-ultra/resources/description_optimization.md +90 -0
- package/skills/skill-creator-ultra/resources/eval_guide.md +133 -0
- package/skills/skill-creator-ultra/resources/industry_questions.md +189 -0
- package/skills/skill-creator-ultra/resources/interview_questions.md +200 -0
- package/skills/skill-creator-ultra/resources/pattern_detection.md +200 -0
- package/skills/skill-creator-ultra/resources/prompt_engineering.md +531 -0
- package/skills/skill-creator-ultra/resources/schemas.md +430 -0
- package/skills/skill-creator-ultra/resources/script_integration.md +593 -0
- package/skills/skill-creator-ultra/resources/scripts_guide.md +339 -0
- package/skills/skill-creator-ultra/resources/skill_template.md +124 -0
- package/skills/skill-creator-ultra/resources/skill_writing_guide.md +634 -0
- package/skills/skill-creator-ultra/resources/versioning_guide.md +193 -0
- package/skills/skill-creator-ultra/scripts/ci_eval.py +200 -0
- package/skills/skill-creator-ultra/scripts/package_skill.py +165 -0
- package/skills/skill-creator-ultra/scripts/simulate_skill.py +398 -0
- package/skills/skill-creator-ultra/scripts/skill_audit.py +611 -0
- package/skills/skill-creator-ultra/scripts/skill_compare.py +265 -0
- package/skills/skill-creator-ultra/scripts/skill_export.py +334 -0
- package/skills/skill-creator-ultra/scripts/skill_scaffold.py +403 -0
- package/skills/skill-creator-ultra/scripts/skill_stats.py +339 -0
- package/skills/skill-creator-ultra/scripts/validate_skill.py +411 -0
- package/skills/tailwind-mastery/SKILL.md +229 -0
- package/skills/vercel-react-best-practices/AGENTS.md +3373 -0
- package/skills/vercel-react-best-practices/README.md +123 -0
- package/skills/vercel-react-best-practices/SKILL.md +143 -0
- package/skills/vercel-react-best-practices/rules/_sections.md +46 -0
- package/skills/vercel-react-best-practices/rules/_template.md +28 -0
- package/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/skills/vercel-react-best-practices/rules/js-flatmap-filter.md +60 -0
- package/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/skills/vercel-react-best-practices/rules/rendering-resource-hints.md +85 -0
- package/skills/vercel-react-best-practices/rules/rendering-script-defer-async.md +68 -0
- package/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/skills/vercel-react-best-practices/rules/rerender-no-inline-components.md +82 -0
- package/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/skills/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/skills/vercel-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/skills/vercel-react-best-practices/rules/server-hoist-static-io.md +142 -0
- package/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/skills/web-design-guidelines/SKILL.md +39 -0
- package/skills/cro-methodology/SKILL.md +0 -98
- /package/skills/{cro-methodology → cm-cro-methodology}/references/COPYWRITING.md +0 -0
- /package/skills/{cro-methodology → cm-cro-methodology}/references/OBJECTIONS.md +0 -0
- /package/skills/{cro-methodology → cm-cro-methodology}/references/PERSUASION.md +0 -0
- /package/skills/{cro-methodology → cm-cro-methodology}/references/RESEARCH.md +0 -0
- /package/skills/{cro-methodology → cm-cro-methodology}/references/funnel-analysis.md +0 -0
- /package/skills/{cro-methodology → cm-cro-methodology}/references/testing-methodology.md +0 -0
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
Design System Extractor — Extract design tokens from existing websites/apps.
|
|
5
|
+
|
|
6
|
+
This is the CORE NEW CAPABILITY that existing AI tools and libraries lack:
|
|
7
|
+
the ability to analyze an existing product's design system and generate
|
|
8
|
+
a compatible skill set that inherits the brand identity.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
# Extract from URL
|
|
12
|
+
python3 extractor.py --url "https://example.com" --output design-system/EXTRACTED.md
|
|
13
|
+
|
|
14
|
+
# Extract from screenshots directory
|
|
15
|
+
python3 extractor.py --screenshots ./screenshots/ --output design-system/EXTRACTED.md
|
|
16
|
+
|
|
17
|
+
# Extract and generate custom skill
|
|
18
|
+
python3 extractor.py --url "https://example.com" --generate-skill --project "MyBrand"
|
|
19
|
+
|
|
20
|
+
# Extract with specific pages for deeper analysis
|
|
21
|
+
python3 extractor.py --url "https://example.com" --pages "home,pricing,dashboard"
|
|
22
|
+
|
|
23
|
+
Workflow:
|
|
24
|
+
1. ANALYZE — Crawl/parse existing product → extract visual tokens
|
|
25
|
+
2. GENERATE — Create custom skill set matching brand identity
|
|
26
|
+
3. EXTEND — Build new pages/features consistent with extracted system
|
|
27
|
+
|
|
28
|
+
Dependencies:
|
|
29
|
+
- Python 3.8+ (stdlib only for core; optional: requests for URL fetching)
|
|
30
|
+
- No external dependencies required for local/screenshot mode
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import json
|
|
35
|
+
import re
|
|
36
|
+
import sys
|
|
37
|
+
import io
|
|
38
|
+
import os
|
|
39
|
+
import csv
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from datetime import datetime
|
|
42
|
+
from dataclasses import dataclass, field, asdict
|
|
43
|
+
from typing import List, Dict, Optional, Tuple
|
|
44
|
+
from collections import Counter, defaultdict
|
|
45
|
+
from math import sqrt
|
|
46
|
+
|
|
47
|
+
# Force UTF-8
|
|
48
|
+
if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8':
|
|
49
|
+
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ============================================================================
|
|
53
|
+
# DATA MODELS
|
|
54
|
+
# ============================================================================
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ColorToken:
|
|
58
|
+
"""Represents an extracted color with context."""
|
|
59
|
+
hex: str
|
|
60
|
+
role: str = "unknown" # primary, secondary, accent, bg, text, border, etc.
|
|
61
|
+
frequency: int = 0 # how often it appears
|
|
62
|
+
contexts: List[str] = field(default_factory=list) # where it's used: ["button", "header", "link"]
|
|
63
|
+
contrast_white: float = 0.0 # contrast ratio against white
|
|
64
|
+
contrast_black: float = 0.0 # contrast ratio against black
|
|
65
|
+
wcag_aa: bool = False
|
|
66
|
+
wcag_aaa: bool = False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class TypographyToken:
|
|
71
|
+
"""Represents extracted typography information."""
|
|
72
|
+
font_family: str
|
|
73
|
+
role: str = "unknown" # heading, body, mono, accent
|
|
74
|
+
weight: str = "400"
|
|
75
|
+
size_px: int = 16
|
|
76
|
+
line_height: float = 1.5
|
|
77
|
+
letter_spacing: str = "normal"
|
|
78
|
+
frequency: int = 0
|
|
79
|
+
source: str = "system" # google-fonts, adobe-fonts, system, custom
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class SpacingToken:
|
|
84
|
+
"""Represents extracted spacing values."""
|
|
85
|
+
value_px: int
|
|
86
|
+
css_variable: str = ""
|
|
87
|
+
frequency: int = 0
|
|
88
|
+
contexts: List[str] = field(default_factory=list) # padding, margin, gap
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class ComponentPattern:
|
|
93
|
+
"""Represents an extracted UI component pattern."""
|
|
94
|
+
name: str # button, card, input, modal, nav, etc.
|
|
95
|
+
variants: List[str] = field(default_factory=list) # primary, secondary, ghost
|
|
96
|
+
border_radius: str = ""
|
|
97
|
+
shadow: str = ""
|
|
98
|
+
padding: str = ""
|
|
99
|
+
transition: str = ""
|
|
100
|
+
hover_state: str = ""
|
|
101
|
+
css_snippet: str = ""
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class LayoutPattern:
|
|
106
|
+
"""Represents extracted layout information."""
|
|
107
|
+
max_width: str = "1200px"
|
|
108
|
+
grid_system: str = "12-column"
|
|
109
|
+
breakpoints: Dict[str, int] = field(default_factory=lambda: {
|
|
110
|
+
"mobile": 375,
|
|
111
|
+
"tablet": 768,
|
|
112
|
+
"desktop": 1024,
|
|
113
|
+
"wide": 1440
|
|
114
|
+
})
|
|
115
|
+
container_padding: str = "16px"
|
|
116
|
+
section_gap: str = "64px"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass
|
|
120
|
+
class ExtractedDesignSystem:
|
|
121
|
+
"""Complete extracted design system."""
|
|
122
|
+
project_name: str
|
|
123
|
+
source_url: str = ""
|
|
124
|
+
extracted_at: str = ""
|
|
125
|
+
brand_voice: str = "" # formal, casual, playful, premium, technical
|
|
126
|
+
visual_density: str = "medium" # low, medium, high
|
|
127
|
+
|
|
128
|
+
colors: List[ColorToken] = field(default_factory=list)
|
|
129
|
+
typography: List[TypographyToken] = field(default_factory=list)
|
|
130
|
+
spacing: List[SpacingToken] = field(default_factory=list)
|
|
131
|
+
components: List[ComponentPattern] = field(default_factory=list)
|
|
132
|
+
layout: LayoutPattern = field(default_factory=LayoutPattern)
|
|
133
|
+
|
|
134
|
+
# Inferred properties
|
|
135
|
+
color_temperature: str = "" # warm, cool, neutral
|
|
136
|
+
design_era: str = "" # modern, classic, retro, futuristic
|
|
137
|
+
ui_style_match: str = "" # closest match to known UI styles
|
|
138
|
+
anti_patterns_detected: List[str] = field(default_factory=list)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ============================================================================
|
|
142
|
+
# COLOR UTILITIES
|
|
143
|
+
# ============================================================================
|
|
144
|
+
|
|
145
|
+
class ColorUtils:
|
|
146
|
+
"""Color analysis and contrast calculation utilities."""
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
|
|
150
|
+
"""Convert hex color to RGB tuple."""
|
|
151
|
+
hex_color = hex_color.lstrip('#')
|
|
152
|
+
if len(hex_color) == 3:
|
|
153
|
+
hex_color = ''.join(c * 2 for c in hex_color)
|
|
154
|
+
if len(hex_color) != 6:
|
|
155
|
+
return (0, 0, 0)
|
|
156
|
+
try:
|
|
157
|
+
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
|
158
|
+
except ValueError:
|
|
159
|
+
return (0, 0, 0)
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def rgb_to_hex(r: int, g: int, b: int) -> str:
|
|
163
|
+
"""Convert RGB to hex."""
|
|
164
|
+
return f"#{r:02x}{g:02x}{b:02x}"
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def relative_luminance(r: int, g: int, b: int) -> float:
|
|
168
|
+
"""Calculate relative luminance per WCAG 2.0."""
|
|
169
|
+
def linearize(c):
|
|
170
|
+
c = c / 255.0
|
|
171
|
+
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
|
|
172
|
+
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b)
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
def contrast_ratio(cls, hex1: str, hex2: str) -> float:
|
|
176
|
+
"""Calculate WCAG contrast ratio between two colors."""
|
|
177
|
+
r1, g1, b1 = cls.hex_to_rgb(hex1)
|
|
178
|
+
r2, g2, b2 = cls.hex_to_rgb(hex2)
|
|
179
|
+
l1 = cls.relative_luminance(r1, g1, b1)
|
|
180
|
+
l2 = cls.relative_luminance(r2, g2, b2)
|
|
181
|
+
lighter = max(l1, l2)
|
|
182
|
+
darker = min(l1, l2)
|
|
183
|
+
return (lighter + 0.05) / (darker + 0.05)
|
|
184
|
+
|
|
185
|
+
@classmethod
|
|
186
|
+
def is_wcag_aa(cls, fg_hex: str, bg_hex: str, large_text: bool = False) -> bool:
|
|
187
|
+
"""Check WCAG AA compliance."""
|
|
188
|
+
ratio = cls.contrast_ratio(fg_hex, bg_hex)
|
|
189
|
+
return ratio >= 3.0 if large_text else ratio >= 4.5
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def is_wcag_aaa(cls, fg_hex: str, bg_hex: str, large_text: bool = False) -> bool:
|
|
193
|
+
"""Check WCAG AAA compliance."""
|
|
194
|
+
ratio = cls.contrast_ratio(fg_hex, bg_hex)
|
|
195
|
+
return ratio >= 4.5 if large_text else ratio >= 7.0
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def color_temperature(hex_color: str) -> str:
|
|
199
|
+
"""Determine if a color is warm, cool, or neutral."""
|
|
200
|
+
r, g, b = ColorUtils.hex_to_rgb(hex_color)
|
|
201
|
+
if r > b + 30:
|
|
202
|
+
return "warm"
|
|
203
|
+
elif b > r + 30:
|
|
204
|
+
return "cool"
|
|
205
|
+
return "neutral"
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def color_category(hex_color: str) -> str:
|
|
209
|
+
"""Categorize color into broad groups."""
|
|
210
|
+
r, g, b = ColorUtils.hex_to_rgb(hex_color)
|
|
211
|
+
# Grayscale detection
|
|
212
|
+
if abs(r - g) < 15 and abs(g - b) < 15 and abs(r - b) < 15:
|
|
213
|
+
if r < 50:
|
|
214
|
+
return "black"
|
|
215
|
+
elif r > 200:
|
|
216
|
+
return "white"
|
|
217
|
+
return "gray"
|
|
218
|
+
# Dominant channel
|
|
219
|
+
if r > g and r > b:
|
|
220
|
+
return "warm-red" if g < 100 else "warm-orange"
|
|
221
|
+
elif g > r and g > b:
|
|
222
|
+
return "green"
|
|
223
|
+
elif b > r and b > g:
|
|
224
|
+
return "blue"
|
|
225
|
+
return "mixed"
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ============================================================================
|
|
229
|
+
# CSS PARSER — Extract design tokens from raw CSS
|
|
230
|
+
# ============================================================================
|
|
231
|
+
|
|
232
|
+
class CSSParser:
|
|
233
|
+
"""Parse CSS content and extract design tokens."""
|
|
234
|
+
|
|
235
|
+
# Regex patterns for CSS extraction
|
|
236
|
+
COLOR_PATTERNS = [
|
|
237
|
+
re.compile(r'#([0-9a-fA-F]{6})\b'),
|
|
238
|
+
re.compile(r'#([0-9a-fA-F]{3})\b'),
|
|
239
|
+
re.compile(r'rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)'),
|
|
240
|
+
re.compile(r'rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*[\d.]+\s*\)'),
|
|
241
|
+
re.compile(r'hsl\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)'),
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
FONT_PATTERN = re.compile(
|
|
245
|
+
r'font-family\s*:\s*([^;]+);', re.IGNORECASE
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
FONT_SIZE_PATTERN = re.compile(
|
|
249
|
+
r'font-size\s*:\s*(\d+(?:\.\d+)?)(px|rem|em)', re.IGNORECASE
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
FONT_WEIGHT_PATTERN = re.compile(
|
|
253
|
+
r'font-weight\s*:\s*(\d+|bold|normal|lighter|bolder)', re.IGNORECASE
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
SPACING_PATTERN = re.compile(
|
|
257
|
+
r'(?:padding|margin|gap)\s*:\s*([\d.]+)(px|rem|em)', re.IGNORECASE
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
BORDER_RADIUS_PATTERN = re.compile(
|
|
261
|
+
r'border-radius\s*:\s*([^;]+);', re.IGNORECASE
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
BOX_SHADOW_PATTERN = re.compile(
|
|
265
|
+
r'box-shadow\s*:\s*([^;]+);', re.IGNORECASE
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
TRANSITION_PATTERN = re.compile(
|
|
269
|
+
r'transition\s*:\s*([^;]+);', re.IGNORECASE
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
CSS_VAR_PATTERN = re.compile(
|
|
273
|
+
r'--([a-zA-Z0-9_-]+)\s*:\s*([^;]+);'
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
GOOGLE_FONTS_PATTERN = re.compile(
|
|
277
|
+
r'fonts\.googleapis\.com/css2?\?family=([^"\'&\s]+)', re.IGNORECASE
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
MAX_WIDTH_PATTERN = re.compile(
|
|
281
|
+
r'max-width\s*:\s*(\d+)(px|rem)', re.IGNORECASE
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
MEDIA_QUERY_PATTERN = re.compile(
|
|
285
|
+
r'@media[^{]*(?:min-width|max-width)\s*:\s*(\d+)px', re.IGNORECASE
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def __init__(self):
|
|
289
|
+
self.colors: Counter = Counter()
|
|
290
|
+
self.color_contexts: Dict[str, List[str]] = defaultdict(list)
|
|
291
|
+
self.fonts: Counter = Counter()
|
|
292
|
+
self.font_sizes: Counter = Counter()
|
|
293
|
+
self.font_weights: Counter = Counter()
|
|
294
|
+
self.spacings: Counter = Counter()
|
|
295
|
+
self.border_radii: Counter = Counter()
|
|
296
|
+
self.shadows: List[str] = []
|
|
297
|
+
self.transitions: List[str] = []
|
|
298
|
+
self.css_variables: Dict[str, str] = {}
|
|
299
|
+
self.google_fonts: List[str] = []
|
|
300
|
+
self.max_widths: List[int] = []
|
|
301
|
+
self.breakpoints: List[int] = []
|
|
302
|
+
|
|
303
|
+
def parse_css(self, css_content: str, context: str = "stylesheet"):
|
|
304
|
+
"""Parse CSS content and extract all tokens."""
|
|
305
|
+
self._extract_css_variables(css_content)
|
|
306
|
+
self._extract_colors(css_content, context)
|
|
307
|
+
self._extract_fonts(css_content)
|
|
308
|
+
self._extract_spacing(css_content)
|
|
309
|
+
self._extract_borders(css_content)
|
|
310
|
+
self._extract_shadows(css_content)
|
|
311
|
+
self._extract_transitions(css_content)
|
|
312
|
+
self._extract_layout(css_content)
|
|
313
|
+
|
|
314
|
+
def parse_html(self, html_content: str):
|
|
315
|
+
"""Extract design tokens from HTML (inline styles, class hints, font imports)."""
|
|
316
|
+
# Extract Google Fonts
|
|
317
|
+
for match in self.GOOGLE_FONTS_PATTERN.finditer(html_content):
|
|
318
|
+
font_names = match.group(1).replace('+', ' ').split('|')
|
|
319
|
+
for name in font_names:
|
|
320
|
+
clean_name = name.split(':')[0].strip()
|
|
321
|
+
if clean_name:
|
|
322
|
+
self.google_fonts.append(clean_name)
|
|
323
|
+
|
|
324
|
+
# Extract inline styles
|
|
325
|
+
inline_style_pattern = re.compile(r'style\s*=\s*"([^"]*)"', re.IGNORECASE)
|
|
326
|
+
for match in inline_style_pattern.finditer(html_content):
|
|
327
|
+
self.parse_css(match.group(1), context="inline")
|
|
328
|
+
|
|
329
|
+
# Extract <style> blocks
|
|
330
|
+
style_block_pattern = re.compile(r'<style[^>]*>(.*?)</style>', re.DOTALL | re.IGNORECASE)
|
|
331
|
+
for match in style_block_pattern.finditer(html_content):
|
|
332
|
+
self.parse_css(match.group(1), context="style-block")
|
|
333
|
+
|
|
334
|
+
# Detect Tailwind classes for color extraction
|
|
335
|
+
tailwind_color_pattern = re.compile(
|
|
336
|
+
r'(?:bg|text|border|ring|shadow)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(\d+)',
|
|
337
|
+
re.IGNORECASE
|
|
338
|
+
)
|
|
339
|
+
tailwind_colors = tailwind_color_pattern.findall(html_content)
|
|
340
|
+
# Note: we track Tailwind color usage patterns but can't resolve to hex without config
|
|
341
|
+
|
|
342
|
+
def _extract_css_variables(self, css: str):
|
|
343
|
+
for match in self.CSS_VAR_PATTERN.finditer(css):
|
|
344
|
+
var_name = match.group(1)
|
|
345
|
+
var_value = match.group(2).strip()
|
|
346
|
+
self.css_variables[var_name] = var_value
|
|
347
|
+
|
|
348
|
+
def _extract_colors(self, css: str, context: str):
|
|
349
|
+
# Hex colors
|
|
350
|
+
for pattern in self.COLOR_PATTERNS[:2]:
|
|
351
|
+
for match in pattern.finditer(css):
|
|
352
|
+
hex_val = match.group(1)
|
|
353
|
+
if len(hex_val) == 3:
|
|
354
|
+
hex_val = ''.join(c * 2 for c in hex_val)
|
|
355
|
+
hex_color = f"#{hex_val.lower()}"
|
|
356
|
+
self.colors[hex_color] += 1
|
|
357
|
+
self.color_contexts[hex_color].append(context)
|
|
358
|
+
|
|
359
|
+
# RGB colors → convert to hex
|
|
360
|
+
for match in self.COLOR_PATTERNS[2].finditer(css):
|
|
361
|
+
r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
|
362
|
+
hex_color = ColorUtils.rgb_to_hex(r, g, b)
|
|
363
|
+
self.colors[hex_color] += 1
|
|
364
|
+
self.color_contexts[hex_color].append(context)
|
|
365
|
+
|
|
366
|
+
# RGBA → convert to hex (ignore alpha for token extraction)
|
|
367
|
+
for match in self.COLOR_PATTERNS[3].finditer(css):
|
|
368
|
+
r, g, b = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
|
369
|
+
hex_color = ColorUtils.rgb_to_hex(r, g, b)
|
|
370
|
+
self.colors[hex_color] += 1
|
|
371
|
+
self.color_contexts[hex_color].append(context)
|
|
372
|
+
|
|
373
|
+
def _extract_fonts(self, css: str):
|
|
374
|
+
for match in self.FONT_PATTERN.finditer(css):
|
|
375
|
+
font_list = match.group(1).strip()
|
|
376
|
+
# Get first font in the stack
|
|
377
|
+
primary_font = font_list.split(',')[0].strip().strip("'\"")
|
|
378
|
+
if primary_font and primary_font.lower() not in ('inherit', 'initial', 'unset'):
|
|
379
|
+
self.fonts[primary_font] += 1
|
|
380
|
+
|
|
381
|
+
for match in self.FONT_SIZE_PATTERN.finditer(css):
|
|
382
|
+
size = float(match.group(1))
|
|
383
|
+
unit = match.group(2)
|
|
384
|
+
if unit == 'rem':
|
|
385
|
+
size = size * 16 # Convert to px (assuming 16px base)
|
|
386
|
+
elif unit == 'em':
|
|
387
|
+
size = size * 16
|
|
388
|
+
self.font_sizes[int(size)] += 1
|
|
389
|
+
|
|
390
|
+
for match in self.FONT_WEIGHT_PATTERN.finditer(css):
|
|
391
|
+
weight = match.group(1)
|
|
392
|
+
weight_map = {'normal': '400', 'bold': '700', 'lighter': '300', 'bolder': '800'}
|
|
393
|
+
weight = weight_map.get(weight.lower(), weight)
|
|
394
|
+
self.font_weights[weight] += 1
|
|
395
|
+
|
|
396
|
+
def _extract_spacing(self, css: str):
|
|
397
|
+
for match in self.SPACING_PATTERN.finditer(css):
|
|
398
|
+
size = float(match.group(1))
|
|
399
|
+
unit = match.group(2)
|
|
400
|
+
if unit == 'rem':
|
|
401
|
+
size = size * 16
|
|
402
|
+
elif unit == 'em':
|
|
403
|
+
size = size * 16
|
|
404
|
+
self.spacings[int(size)] += 1
|
|
405
|
+
|
|
406
|
+
def _extract_borders(self, css: str):
|
|
407
|
+
for match in self.BORDER_RADIUS_PATTERN.finditer(css):
|
|
408
|
+
radius = match.group(1).strip()
|
|
409
|
+
self.border_radii[radius] += 1
|
|
410
|
+
|
|
411
|
+
def _extract_shadows(self, css: str):
|
|
412
|
+
for match in self.BOX_SHADOW_PATTERN.finditer(css):
|
|
413
|
+
shadow = match.group(1).strip()
|
|
414
|
+
if shadow.lower() != 'none':
|
|
415
|
+
self.shadows.append(shadow)
|
|
416
|
+
|
|
417
|
+
def _extract_transitions(self, css: str):
|
|
418
|
+
for match in self.TRANSITION_PATTERN.finditer(css):
|
|
419
|
+
transition = match.group(1).strip()
|
|
420
|
+
if transition.lower() != 'none':
|
|
421
|
+
self.transitions.append(transition)
|
|
422
|
+
|
|
423
|
+
def _extract_layout(self, css: str):
|
|
424
|
+
for match in self.MAX_WIDTH_PATTERN.finditer(css):
|
|
425
|
+
width = int(match.group(1))
|
|
426
|
+
unit = match.group(2)
|
|
427
|
+
if unit == 'rem':
|
|
428
|
+
width = width * 16
|
|
429
|
+
self.max_widths.append(width)
|
|
430
|
+
|
|
431
|
+
for match in self.MEDIA_QUERY_PATTERN.finditer(css):
|
|
432
|
+
self.breakpoints.append(int(match.group(1)))
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# ============================================================================
|
|
436
|
+
# DESIGN SYSTEM ANALYZER — Infer roles and patterns from raw tokens
|
|
437
|
+
# ============================================================================
|
|
438
|
+
|
|
439
|
+
class DesignSystemAnalyzer:
|
|
440
|
+
"""Analyze extracted tokens and infer design system structure."""
|
|
441
|
+
|
|
442
|
+
def __init__(self, parser: CSSParser, project_name: str, source_url: str = ""):
|
|
443
|
+
self.parser = parser
|
|
444
|
+
self.project_name = project_name
|
|
445
|
+
self.source_url = source_url
|
|
446
|
+
|
|
447
|
+
def analyze(self) -> ExtractedDesignSystem:
|
|
448
|
+
"""Run full analysis and return structured design system."""
|
|
449
|
+
ds = ExtractedDesignSystem(
|
|
450
|
+
project_name=self.project_name,
|
|
451
|
+
source_url=self.source_url,
|
|
452
|
+
extracted_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
ds.colors = self._analyze_colors()
|
|
456
|
+
ds.typography = self._analyze_typography()
|
|
457
|
+
ds.spacing = self._analyze_spacing()
|
|
458
|
+
ds.components = self._analyze_components()
|
|
459
|
+
ds.layout = self._analyze_layout()
|
|
460
|
+
ds.color_temperature = self._infer_color_temperature(ds.colors)
|
|
461
|
+
ds.brand_voice = self._infer_brand_voice(ds)
|
|
462
|
+
ds.visual_density = self._infer_visual_density(ds)
|
|
463
|
+
ds.design_era = self._infer_design_era(ds)
|
|
464
|
+
ds.ui_style_match = self._match_ui_style(ds)
|
|
465
|
+
ds.anti_patterns_detected = self._detect_anti_patterns(ds)
|
|
466
|
+
|
|
467
|
+
return ds
|
|
468
|
+
|
|
469
|
+
def _analyze_colors(self) -> List[ColorToken]:
|
|
470
|
+
"""Analyze colors and infer their roles."""
|
|
471
|
+
tokens = []
|
|
472
|
+
# Sort by frequency
|
|
473
|
+
sorted_colors = self.parser.colors.most_common(20)
|
|
474
|
+
|
|
475
|
+
for i, (hex_color, freq) in enumerate(sorted_colors):
|
|
476
|
+
category = ColorUtils.color_category(hex_color)
|
|
477
|
+
contexts = self.parser.color_contexts.get(hex_color, [])
|
|
478
|
+
|
|
479
|
+
# Infer role based on frequency, category, and position
|
|
480
|
+
role = self._infer_color_role(hex_color, freq, category, i, sorted_colors)
|
|
481
|
+
|
|
482
|
+
contrast_w = ColorUtils.contrast_ratio(hex_color, "#ffffff")
|
|
483
|
+
contrast_b = ColorUtils.contrast_ratio(hex_color, "#000000")
|
|
484
|
+
|
|
485
|
+
token = ColorToken(
|
|
486
|
+
hex=hex_color,
|
|
487
|
+
role=role,
|
|
488
|
+
frequency=freq,
|
|
489
|
+
contexts=contexts[:5],
|
|
490
|
+
contrast_white=round(contrast_w, 2),
|
|
491
|
+
contrast_black=round(contrast_b, 2),
|
|
492
|
+
wcag_aa=ColorUtils.is_wcag_aa(hex_color, "#ffffff"),
|
|
493
|
+
wcag_aaa=ColorUtils.is_wcag_aaa(hex_color, "#ffffff")
|
|
494
|
+
)
|
|
495
|
+
tokens.append(token)
|
|
496
|
+
|
|
497
|
+
return tokens
|
|
498
|
+
|
|
499
|
+
def _infer_color_role(self, hex_color: str, freq: int, category: str,
|
|
500
|
+
position: int, all_colors: list) -> str:
|
|
501
|
+
"""Infer the role of a color in the design system."""
|
|
502
|
+
r, g, b = ColorUtils.hex_to_rgb(hex_color)
|
|
503
|
+
|
|
504
|
+
# White/near-white = background
|
|
505
|
+
if r > 240 and g > 240 and b > 240:
|
|
506
|
+
return "background"
|
|
507
|
+
|
|
508
|
+
# Black/near-black = text
|
|
509
|
+
if r < 50 and g < 50 and b < 50:
|
|
510
|
+
return "text-primary"
|
|
511
|
+
|
|
512
|
+
# Dark gray = text-secondary
|
|
513
|
+
if category == "gray" and r < 120:
|
|
514
|
+
return "text-secondary"
|
|
515
|
+
|
|
516
|
+
# Light gray = border or background-secondary
|
|
517
|
+
if category == "gray" and r > 180:
|
|
518
|
+
return "border" if freq < 10 else "background-secondary"
|
|
519
|
+
|
|
520
|
+
# Medium gray = text-tertiary
|
|
521
|
+
if category == "gray":
|
|
522
|
+
return "text-tertiary"
|
|
523
|
+
|
|
524
|
+
# First chromatic color = primary
|
|
525
|
+
chromatic_position = 0
|
|
526
|
+
for c_hex, c_freq in all_colors:
|
|
527
|
+
cat = ColorUtils.color_category(c_hex)
|
|
528
|
+
if cat not in ("black", "white", "gray"):
|
|
529
|
+
if c_hex == hex_color:
|
|
530
|
+
break
|
|
531
|
+
chromatic_position += 1
|
|
532
|
+
|
|
533
|
+
if chromatic_position == 0:
|
|
534
|
+
return "primary"
|
|
535
|
+
elif chromatic_position == 1:
|
|
536
|
+
return "secondary"
|
|
537
|
+
elif chromatic_position == 2:
|
|
538
|
+
return "accent"
|
|
539
|
+
elif chromatic_position == 3:
|
|
540
|
+
return "cta"
|
|
541
|
+
|
|
542
|
+
return "decorative"
|
|
543
|
+
|
|
544
|
+
def _analyze_typography(self) -> List[TypographyToken]:
|
|
545
|
+
"""Analyze typography and infer roles."""
|
|
546
|
+
tokens = []
|
|
547
|
+
sorted_fonts = self.parser.fonts.most_common(5)
|
|
548
|
+
sorted_sizes = self.parser.font_sizes.most_common(10)
|
|
549
|
+
|
|
550
|
+
for i, (font_name, freq) in enumerate(sorted_fonts):
|
|
551
|
+
# Determine if Google Font
|
|
552
|
+
source = "google-fonts" if font_name in self.parser.google_fonts else "system"
|
|
553
|
+
|
|
554
|
+
# Infer role
|
|
555
|
+
if i == 0 and len(sorted_fonts) > 1:
|
|
556
|
+
role = "body" # Most frequent = body
|
|
557
|
+
elif i == 1:
|
|
558
|
+
role = "heading" # Second = heading
|
|
559
|
+
elif "mono" in font_name.lower() or "code" in font_name.lower():
|
|
560
|
+
role = "mono"
|
|
561
|
+
else:
|
|
562
|
+
role = "accent"
|
|
563
|
+
|
|
564
|
+
# If only 1 font, it's both
|
|
565
|
+
if len(sorted_fonts) == 1:
|
|
566
|
+
role = "body+heading"
|
|
567
|
+
|
|
568
|
+
# Find most common size for this role context
|
|
569
|
+
size = sorted_sizes[0][0] if sorted_sizes else 16
|
|
570
|
+
|
|
571
|
+
token = TypographyToken(
|
|
572
|
+
font_family=font_name,
|
|
573
|
+
role=role,
|
|
574
|
+
weight=self.parser.font_weights.most_common(1)[0][0] if self.parser.font_weights else "400",
|
|
575
|
+
size_px=size,
|
|
576
|
+
frequency=freq,
|
|
577
|
+
source=source
|
|
578
|
+
)
|
|
579
|
+
tokens.append(token)
|
|
580
|
+
|
|
581
|
+
return tokens
|
|
582
|
+
|
|
583
|
+
def _analyze_spacing(self) -> List[SpacingToken]:
|
|
584
|
+
"""Analyze spacing values and detect scale."""
|
|
585
|
+
tokens = []
|
|
586
|
+
sorted_spacings = self.parser.spacings.most_common(15)
|
|
587
|
+
|
|
588
|
+
# Detect if 4px or 8px base
|
|
589
|
+
values = [s[0] for s in sorted_spacings]
|
|
590
|
+
base_4_count = sum(1 for v in values if v % 4 == 0)
|
|
591
|
+
base_8_count = sum(1 for v in values if v % 8 == 0)
|
|
592
|
+
|
|
593
|
+
base = 4 if base_4_count > base_8_count else 8
|
|
594
|
+
|
|
595
|
+
for value, freq in sorted_spacings:
|
|
596
|
+
# Generate CSS variable name
|
|
597
|
+
if value <= 4:
|
|
598
|
+
var_name = "--space-xs"
|
|
599
|
+
elif value <= 8:
|
|
600
|
+
var_name = "--space-sm"
|
|
601
|
+
elif value <= 16:
|
|
602
|
+
var_name = "--space-md"
|
|
603
|
+
elif value <= 24:
|
|
604
|
+
var_name = "--space-lg"
|
|
605
|
+
elif value <= 32:
|
|
606
|
+
var_name = "--space-xl"
|
|
607
|
+
elif value <= 48:
|
|
608
|
+
var_name = "--space-2xl"
|
|
609
|
+
else:
|
|
610
|
+
var_name = "--space-3xl"
|
|
611
|
+
|
|
612
|
+
token = SpacingToken(
|
|
613
|
+
value_px=value,
|
|
614
|
+
css_variable=var_name,
|
|
615
|
+
frequency=freq
|
|
616
|
+
)
|
|
617
|
+
tokens.append(token)
|
|
618
|
+
|
|
619
|
+
return tokens
|
|
620
|
+
|
|
621
|
+
def _analyze_components(self) -> List[ComponentPattern]:
|
|
622
|
+
"""Infer component patterns from extracted data."""
|
|
623
|
+
components = []
|
|
624
|
+
|
|
625
|
+
# Button pattern
|
|
626
|
+
most_common_radius = self.parser.border_radii.most_common(1)
|
|
627
|
+
radius = most_common_radius[0][0] if most_common_radius else "8px"
|
|
628
|
+
|
|
629
|
+
# Most common transition
|
|
630
|
+
common_transition = "all 200ms ease"
|
|
631
|
+
if self.parser.transitions:
|
|
632
|
+
common_transition = self.parser.transitions[0]
|
|
633
|
+
|
|
634
|
+
# Most common shadow
|
|
635
|
+
common_shadow = "0 1px 3px rgba(0,0,0,0.1)"
|
|
636
|
+
if self.parser.shadows:
|
|
637
|
+
common_shadow = self.parser.shadows[0]
|
|
638
|
+
|
|
639
|
+
# Find primary and secondary colors
|
|
640
|
+
primary_color = "#2563EB"
|
|
641
|
+
text_color = "#1E293B"
|
|
642
|
+
bg_color = "#FFFFFF"
|
|
643
|
+
for token_data in self.parser.colors.most_common(20):
|
|
644
|
+
hex_c = token_data[0]
|
|
645
|
+
cat = ColorUtils.color_category(hex_c)
|
|
646
|
+
if cat not in ("black", "white", "gray") and primary_color == "#2563EB":
|
|
647
|
+
primary_color = hex_c
|
|
648
|
+
r, g, b = ColorUtils.hex_to_rgb(hex_c)
|
|
649
|
+
if r > 240 and g > 240 and b > 240 and bg_color == "#FFFFFF":
|
|
650
|
+
bg_color = hex_c
|
|
651
|
+
if r < 50 and g < 50 and b < 50 and text_color == "#1E293B":
|
|
652
|
+
text_color = hex_c
|
|
653
|
+
|
|
654
|
+
components.append(ComponentPattern(
|
|
655
|
+
name="button",
|
|
656
|
+
variants=["primary", "secondary", "ghost"],
|
|
657
|
+
border_radius=radius,
|
|
658
|
+
shadow=common_shadow,
|
|
659
|
+
padding="12px 24px",
|
|
660
|
+
transition=common_transition,
|
|
661
|
+
hover_state="opacity: 0.9; transform: translateY(-1px)",
|
|
662
|
+
css_snippet=f""".btn-primary {{
|
|
663
|
+
background: {primary_color};
|
|
664
|
+
color: white;
|
|
665
|
+
padding: 12px 24px;
|
|
666
|
+
border-radius: {radius};
|
|
667
|
+
font-weight: 600;
|
|
668
|
+
transition: {common_transition};
|
|
669
|
+
cursor: pointer;
|
|
670
|
+
}}"""
|
|
671
|
+
))
|
|
672
|
+
|
|
673
|
+
components.append(ComponentPattern(
|
|
674
|
+
name="card",
|
|
675
|
+
variants=["default", "elevated", "outlined"],
|
|
676
|
+
border_radius=radius,
|
|
677
|
+
shadow=common_shadow,
|
|
678
|
+
padding="24px",
|
|
679
|
+
transition=common_transition,
|
|
680
|
+
hover_state="box-shadow: 0 10px 15px rgba(0,0,0,0.1); transform: translateY(-2px)",
|
|
681
|
+
css_snippet=f""".card {{
|
|
682
|
+
background: {bg_color};
|
|
683
|
+
border-radius: {radius};
|
|
684
|
+
padding: 24px;
|
|
685
|
+
box-shadow: {common_shadow};
|
|
686
|
+
transition: {common_transition};
|
|
687
|
+
}}"""
|
|
688
|
+
))
|
|
689
|
+
|
|
690
|
+
components.append(ComponentPattern(
|
|
691
|
+
name="input",
|
|
692
|
+
variants=["default", "error", "disabled"],
|
|
693
|
+
border_radius=radius,
|
|
694
|
+
padding="12px 16px",
|
|
695
|
+
transition=common_transition,
|
|
696
|
+
css_snippet=f""".input {{
|
|
697
|
+
padding: 12px 16px;
|
|
698
|
+
border: 1px solid #E2E8F0;
|
|
699
|
+
border-radius: {radius};
|
|
700
|
+
font-size: 16px;
|
|
701
|
+
transition: border-color 200ms ease;
|
|
702
|
+
}}
|
|
703
|
+
.input:focus {{
|
|
704
|
+
border-color: {primary_color};
|
|
705
|
+
outline: none;
|
|
706
|
+
box-shadow: 0 0 0 3px {primary_color}20;
|
|
707
|
+
}}"""
|
|
708
|
+
))
|
|
709
|
+
|
|
710
|
+
return components
|
|
711
|
+
|
|
712
|
+
def _analyze_layout(self) -> LayoutPattern:
|
|
713
|
+
"""Analyze layout patterns."""
|
|
714
|
+
layout = LayoutPattern()
|
|
715
|
+
|
|
716
|
+
if self.parser.max_widths:
|
|
717
|
+
# Use most common max-width
|
|
718
|
+
most_common = Counter(self.parser.max_widths).most_common(1)
|
|
719
|
+
if most_common:
|
|
720
|
+
layout.max_width = f"{most_common[0][0]}px"
|
|
721
|
+
|
|
722
|
+
if self.parser.breakpoints:
|
|
723
|
+
sorted_bp = sorted(set(self.parser.breakpoints))
|
|
724
|
+
if len(sorted_bp) >= 1:
|
|
725
|
+
layout.breakpoints["mobile"] = sorted_bp[0]
|
|
726
|
+
if len(sorted_bp) >= 2:
|
|
727
|
+
layout.breakpoints["tablet"] = sorted_bp[1]
|
|
728
|
+
if len(sorted_bp) >= 3:
|
|
729
|
+
layout.breakpoints["desktop"] = sorted_bp[2]
|
|
730
|
+
if len(sorted_bp) >= 4:
|
|
731
|
+
layout.breakpoints["wide"] = sorted_bp[3]
|
|
732
|
+
|
|
733
|
+
return layout
|
|
734
|
+
|
|
735
|
+
def _infer_color_temperature(self, colors: List[ColorToken]) -> str:
|
|
736
|
+
"""Infer overall color temperature."""
|
|
737
|
+
temps = {"warm": 0, "cool": 0, "neutral": 0}
|
|
738
|
+
for color in colors:
|
|
739
|
+
if color.role not in ("background", "text-primary", "text-secondary", "border"):
|
|
740
|
+
temp = ColorUtils.color_temperature(color.hex)
|
|
741
|
+
temps[temp] += color.frequency
|
|
742
|
+
return max(temps, key=temps.get)
|
|
743
|
+
|
|
744
|
+
def _infer_brand_voice(self, ds: ExtractedDesignSystem) -> str:
|
|
745
|
+
"""Infer brand voice from visual signals."""
|
|
746
|
+
signals = []
|
|
747
|
+
|
|
748
|
+
# Color-based signals
|
|
749
|
+
if ds.color_temperature == "warm":
|
|
750
|
+
signals.append("friendly")
|
|
751
|
+
elif ds.color_temperature == "cool":
|
|
752
|
+
signals.append("professional")
|
|
753
|
+
|
|
754
|
+
# Typography signals
|
|
755
|
+
for t in ds.typography:
|
|
756
|
+
name_lower = t.font_family.lower()
|
|
757
|
+
if any(kw in name_lower for kw in ["serif", "garamond", "georgia", "times", "playfair", "cormorant"]):
|
|
758
|
+
signals.append("premium")
|
|
759
|
+
elif any(kw in name_lower for kw in ["mono", "code", "source", "jetbrains", "fira"]):
|
|
760
|
+
signals.append("technical")
|
|
761
|
+
elif any(kw in name_lower for kw in ["comic", "fredoka", "bubblegum", "pacifico"]):
|
|
762
|
+
signals.append("playful")
|
|
763
|
+
|
|
764
|
+
# Shadow depth signals
|
|
765
|
+
if len(self.parser.shadows) > 5:
|
|
766
|
+
signals.append("layered")
|
|
767
|
+
elif len(self.parser.shadows) == 0:
|
|
768
|
+
signals.append("flat")
|
|
769
|
+
|
|
770
|
+
# Determine dominant voice
|
|
771
|
+
if "premium" in signals:
|
|
772
|
+
return "premium"
|
|
773
|
+
elif "technical" in signals:
|
|
774
|
+
return "technical"
|
|
775
|
+
elif "playful" in signals:
|
|
776
|
+
return "playful"
|
|
777
|
+
elif "professional" in signals:
|
|
778
|
+
return "formal"
|
|
779
|
+
return "casual"
|
|
780
|
+
|
|
781
|
+
def _infer_visual_density(self, ds: ExtractedDesignSystem) -> str:
|
|
782
|
+
"""Infer visual density from spacing."""
|
|
783
|
+
if not ds.spacing:
|
|
784
|
+
return "medium"
|
|
785
|
+
avg_spacing = sum(s.value_px for s in ds.spacing) / len(ds.spacing)
|
|
786
|
+
if avg_spacing < 12:
|
|
787
|
+
return "high"
|
|
788
|
+
elif avg_spacing > 24:
|
|
789
|
+
return "low"
|
|
790
|
+
return "medium"
|
|
791
|
+
|
|
792
|
+
def _infer_design_era(self, ds: ExtractedDesignSystem) -> str:
|
|
793
|
+
"""Infer design era/trend."""
|
|
794
|
+
radius_values = list(self.parser.border_radii.keys())
|
|
795
|
+
has_large_radius = any("16" in r or "20" in r or "24" in r or "full" in r or "9999" in r for r in radius_values)
|
|
796
|
+
has_shadows = len(self.parser.shadows) > 0
|
|
797
|
+
has_glassmorphism = any("backdrop" in str(s).lower() or "blur" in str(s).lower() for s in self.parser.shadows + list(self.parser.css_variables.values()))
|
|
798
|
+
|
|
799
|
+
if has_glassmorphism:
|
|
800
|
+
return "modern-glass"
|
|
801
|
+
elif has_large_radius and has_shadows:
|
|
802
|
+
return "modern-soft"
|
|
803
|
+
elif not has_shadows and not has_large_radius:
|
|
804
|
+
return "modern-flat"
|
|
805
|
+
return "modern"
|
|
806
|
+
|
|
807
|
+
def _match_ui_style(self, ds: ExtractedDesignSystem) -> str:
|
|
808
|
+
"""Match to closest known UI style from ui-ux-pro-max library."""
|
|
809
|
+
era = ds.design_era
|
|
810
|
+
voice = ds.brand_voice
|
|
811
|
+
density = ds.visual_density
|
|
812
|
+
|
|
813
|
+
style_map = {
|
|
814
|
+
("modern-glass", "premium", "low"): "Glassmorphism + Liquid Glass",
|
|
815
|
+
("modern-glass", "professional", "medium"): "Glassmorphism",
|
|
816
|
+
("modern-glass", "technical", "high"): "Glassmorphism + Dark Mode",
|
|
817
|
+
("modern-soft", "friendly", "low"): "Soft UI Evolution",
|
|
818
|
+
("modern-soft", "premium", "low"): "Neumorphism",
|
|
819
|
+
("modern-soft", "casual", "medium"): "Claymorphism",
|
|
820
|
+
("modern-flat", "professional", "medium"): "Minimalism",
|
|
821
|
+
("modern-flat", "technical", "high"): "Flat Design",
|
|
822
|
+
("modern-flat", "playful", "medium"): "Vibrant & Block-based",
|
|
823
|
+
("modern", "premium", "low"): "Minimalism + Glassmorphism",
|
|
824
|
+
("modern", "professional", "medium"): "Minimalism",
|
|
825
|
+
("modern", "casual", "medium"): "Flat Design",
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
key = (era, voice, density)
|
|
829
|
+
if key in style_map:
|
|
830
|
+
return style_map[key]
|
|
831
|
+
|
|
832
|
+
# Fallback: match by era
|
|
833
|
+
era_fallback = {
|
|
834
|
+
"modern-glass": "Glassmorphism",
|
|
835
|
+
"modern-soft": "Soft UI Evolution",
|
|
836
|
+
"modern-flat": "Minimalism",
|
|
837
|
+
"modern": "Flat Design"
|
|
838
|
+
}
|
|
839
|
+
return era_fallback.get(era, "Minimalism")
|
|
840
|
+
|
|
841
|
+
def _detect_anti_patterns(self, ds: ExtractedDesignSystem) -> List[str]:
|
|
842
|
+
"""Detect UX anti-patterns in the extracted system."""
|
|
843
|
+
issues = []
|
|
844
|
+
|
|
845
|
+
# Check color contrast
|
|
846
|
+
bg_colors = [c for c in ds.colors if c.role == "background"]
|
|
847
|
+
text_colors = [c for c in ds.colors if "text" in c.role]
|
|
848
|
+
if bg_colors and text_colors:
|
|
849
|
+
for text in text_colors:
|
|
850
|
+
for bg in bg_colors:
|
|
851
|
+
ratio = ColorUtils.contrast_ratio(text.hex, bg.hex)
|
|
852
|
+
if ratio < 4.5:
|
|
853
|
+
issues.append(f"Low contrast: {text.hex} on {bg.hex} (ratio: {ratio:.1f}, need >= 4.5)")
|
|
854
|
+
|
|
855
|
+
# Check too many colors
|
|
856
|
+
chromatic = [c for c in ds.colors if c.role not in ("background", "text-primary", "text-secondary", "border")]
|
|
857
|
+
if len(chromatic) > 6:
|
|
858
|
+
issues.append(f"Too many accent colors ({len(chromatic)}). Recommend <= 5 for consistency.")
|
|
859
|
+
|
|
860
|
+
# Check font count
|
|
861
|
+
if len(ds.typography) > 3:
|
|
862
|
+
issues.append(f"Too many fonts ({len(ds.typography)}). Recommend <= 3 (heading + body + optional mono).")
|
|
863
|
+
|
|
864
|
+
# Check spacing consistency
|
|
865
|
+
if ds.spacing:
|
|
866
|
+
values = [s.value_px for s in ds.spacing]
|
|
867
|
+
base_4 = sum(1 for v in values if v % 4 == 0)
|
|
868
|
+
ratio = base_4 / len(values) if values else 0
|
|
869
|
+
if ratio < 0.5:
|
|
870
|
+
issues.append("Inconsistent spacing scale. Recommend using 4px or 8px base unit.")
|
|
871
|
+
|
|
872
|
+
# Check missing transitions
|
|
873
|
+
if not self.parser.transitions:
|
|
874
|
+
issues.append("No transitions detected. Add 150-300ms transitions for hover/focus states.")
|
|
875
|
+
|
|
876
|
+
return issues
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
# ============================================================================
|
|
880
|
+
# OUTPUT FORMATTERS
|
|
881
|
+
# ============================================================================
|
|
882
|
+
|
|
883
|
+
class OutputFormatter:
|
|
884
|
+
"""Format extracted design system for various outputs."""
|
|
885
|
+
|
|
886
|
+
@staticmethod
|
|
887
|
+
def to_markdown(ds: ExtractedDesignSystem) -> str:
|
|
888
|
+
"""Generate comprehensive EXTRACTED.md file."""
|
|
889
|
+
lines = []
|
|
890
|
+
|
|
891
|
+
lines.append(f"# Extracted Design System: {ds.project_name}")
|
|
892
|
+
lines.append("")
|
|
893
|
+
lines.append(f"> **Source:** {ds.source_url or 'Local analysis'}")
|
|
894
|
+
lines.append(f"> **Extracted:** {ds.extracted_at}")
|
|
895
|
+
lines.append(f"> **Brand Voice:** {ds.brand_voice}")
|
|
896
|
+
lines.append(f"> **Visual Density:** {ds.visual_density}")
|
|
897
|
+
lines.append(f"> **Color Temperature:** {ds.color_temperature}")
|
|
898
|
+
lines.append(f"> **Design Era:** {ds.design_era}")
|
|
899
|
+
lines.append(f"> **Closest UI Style:** {ds.ui_style_match}")
|
|
900
|
+
lines.append("")
|
|
901
|
+
lines.append("---")
|
|
902
|
+
lines.append("")
|
|
903
|
+
|
|
904
|
+
# Colors
|
|
905
|
+
lines.append("## Color Palette")
|
|
906
|
+
lines.append("")
|
|
907
|
+
lines.append("| Hex | Role | Frequency | Contrast (White) | WCAG AA | WCAG AAA |")
|
|
908
|
+
lines.append("|-----|------|-----------|-------------------|---------|----------|")
|
|
909
|
+
for c in ds.colors[:12]:
|
|
910
|
+
lines.append(f"| `{c.hex}` | {c.role} | {c.frequency} | {c.contrast_white} | {'Pass' if c.wcag_aa else 'Fail'} | {'Pass' if c.wcag_aaa else 'Fail'} |")
|
|
911
|
+
lines.append("")
|
|
912
|
+
|
|
913
|
+
# CSS Variables for colors
|
|
914
|
+
lines.append("### Color CSS Variables")
|
|
915
|
+
lines.append("")
|
|
916
|
+
lines.append("```css")
|
|
917
|
+
lines.append(":root {")
|
|
918
|
+
for c in ds.colors[:8]:
|
|
919
|
+
var_name = f"--color-{c.role.replace(' ', '-')}"
|
|
920
|
+
lines.append(f" {var_name}: {c.hex};")
|
|
921
|
+
lines.append("}")
|
|
922
|
+
lines.append("```")
|
|
923
|
+
lines.append("")
|
|
924
|
+
|
|
925
|
+
# Typography
|
|
926
|
+
lines.append("## Typography")
|
|
927
|
+
lines.append("")
|
|
928
|
+
lines.append("| Font | Role | Weight | Source |")
|
|
929
|
+
lines.append("|------|------|--------|--------|")
|
|
930
|
+
for t in ds.typography:
|
|
931
|
+
lines.append(f"| {t.font_family} | {t.role} | {t.weight} | {t.source} |")
|
|
932
|
+
lines.append("")
|
|
933
|
+
|
|
934
|
+
if ds.typography:
|
|
935
|
+
lines.append("### Typography CSS")
|
|
936
|
+
lines.append("")
|
|
937
|
+
lines.append("```css")
|
|
938
|
+
for t in ds.typography:
|
|
939
|
+
role_class = t.role.replace("+", "-")
|
|
940
|
+
lines.append(f".text-{role_class} {{")
|
|
941
|
+
lines.append(f" font-family: '{t.font_family}', sans-serif;")
|
|
942
|
+
lines.append(f" font-weight: {t.weight};")
|
|
943
|
+
lines.append(f"}}")
|
|
944
|
+
lines.append("")
|
|
945
|
+
lines.append("```")
|
|
946
|
+
lines.append("")
|
|
947
|
+
|
|
948
|
+
# Spacing
|
|
949
|
+
lines.append("## Spacing Scale")
|
|
950
|
+
lines.append("")
|
|
951
|
+
lines.append("| Value (px) | CSS Variable | Frequency |")
|
|
952
|
+
lines.append("|------------|--------------|-----------|")
|
|
953
|
+
for s in ds.spacing[:10]:
|
|
954
|
+
lines.append(f"| {s.value_px}px | `{s.css_variable}` | {s.frequency} |")
|
|
955
|
+
lines.append("")
|
|
956
|
+
|
|
957
|
+
# Components
|
|
958
|
+
lines.append("## Component Patterns")
|
|
959
|
+
lines.append("")
|
|
960
|
+
for comp in ds.components:
|
|
961
|
+
lines.append(f"### {comp.name.title()}")
|
|
962
|
+
lines.append("")
|
|
963
|
+
lines.append(f"- **Variants:** {', '.join(comp.variants)}")
|
|
964
|
+
lines.append(f"- **Border Radius:** {comp.border_radius}")
|
|
965
|
+
if comp.shadow:
|
|
966
|
+
lines.append(f"- **Shadow:** {comp.shadow}")
|
|
967
|
+
if comp.transition:
|
|
968
|
+
lines.append(f"- **Transition:** {comp.transition}")
|
|
969
|
+
lines.append("")
|
|
970
|
+
lines.append("```css")
|
|
971
|
+
lines.append(comp.css_snippet)
|
|
972
|
+
lines.append("```")
|
|
973
|
+
lines.append("")
|
|
974
|
+
|
|
975
|
+
# Layout
|
|
976
|
+
lines.append("## Layout")
|
|
977
|
+
lines.append("")
|
|
978
|
+
lines.append(f"- **Max Width:** {ds.layout.max_width}")
|
|
979
|
+
lines.append(f"- **Grid System:** {ds.layout.grid_system}")
|
|
980
|
+
lines.append(f"- **Breakpoints:**")
|
|
981
|
+
for name, value in ds.layout.breakpoints.items():
|
|
982
|
+
lines.append(f" - {name}: {value}px")
|
|
983
|
+
lines.append("")
|
|
984
|
+
|
|
985
|
+
# Anti-patterns
|
|
986
|
+
if ds.anti_patterns_detected:
|
|
987
|
+
lines.append("## Anti-Patterns Detected")
|
|
988
|
+
lines.append("")
|
|
989
|
+
for issue in ds.anti_patterns_detected:
|
|
990
|
+
lines.append(f"- {issue}")
|
|
991
|
+
lines.append("")
|
|
992
|
+
|
|
993
|
+
# Recommendations
|
|
994
|
+
lines.append("## Recommendations")
|
|
995
|
+
lines.append("")
|
|
996
|
+
lines.append("Based on the analysis, here are recommendations to improve the design system:")
|
|
997
|
+
lines.append("")
|
|
998
|
+
lines.append(f"1. **UI Style Match:** Your design most closely matches **{ds.ui_style_match}**. "
|
|
999
|
+
f"Consider aligning fully with this style for consistency.")
|
|
1000
|
+
lines.append(f"2. **Brand Voice:** Detected **{ds.brand_voice}** voice. "
|
|
1001
|
+
f"Ensure all new components maintain this tone.")
|
|
1002
|
+
lines.append(f"3. **Color Temperature:** **{ds.color_temperature}**. "
|
|
1003
|
+
f"New accent colors should stay within this temperature range.")
|
|
1004
|
+
lines.append("")
|
|
1005
|
+
|
|
1006
|
+
return "\n".join(lines)
|
|
1007
|
+
|
|
1008
|
+
@staticmethod
|
|
1009
|
+
def to_skill(ds: ExtractedDesignSystem) -> str:
|
|
1010
|
+
"""Generate a BRAND-SKILL.md file for AI coding tools."""
|
|
1011
|
+
lines = []
|
|
1012
|
+
|
|
1013
|
+
lines.append(f"# Brand Skill: {ds.project_name}")
|
|
1014
|
+
lines.append("")
|
|
1015
|
+
lines.append("> **AUTO-GENERATED** from extracted design system.")
|
|
1016
|
+
lines.append("> When building UI for this project, ALWAYS follow these rules.")
|
|
1017
|
+
lines.append("> These rules OVERRIDE generic style recommendations.")
|
|
1018
|
+
lines.append("")
|
|
1019
|
+
lines.append("---")
|
|
1020
|
+
lines.append("")
|
|
1021
|
+
|
|
1022
|
+
# Brand Identity
|
|
1023
|
+
lines.append("## Brand Identity")
|
|
1024
|
+
lines.append("")
|
|
1025
|
+
lines.append(f"- **Voice:** {ds.brand_voice}")
|
|
1026
|
+
lines.append(f"- **Visual Density:** {ds.visual_density}")
|
|
1027
|
+
lines.append(f"- **Color Temperature:** {ds.color_temperature}")
|
|
1028
|
+
lines.append(f"- **Base Style:** {ds.ui_style_match}")
|
|
1029
|
+
lines.append("")
|
|
1030
|
+
|
|
1031
|
+
# Mandatory Colors
|
|
1032
|
+
lines.append("## Mandatory Color Palette")
|
|
1033
|
+
lines.append("")
|
|
1034
|
+
lines.append("ALWAYS use these exact colors. Do NOT substitute with generic palettes.")
|
|
1035
|
+
lines.append("")
|
|
1036
|
+
lines.append("```css")
|
|
1037
|
+
lines.append(":root {")
|
|
1038
|
+
for c in ds.colors[:8]:
|
|
1039
|
+
var_name = f"--brand-{c.role.replace(' ', '-')}"
|
|
1040
|
+
lines.append(f" {var_name}: {c.hex};")
|
|
1041
|
+
lines.append("}")
|
|
1042
|
+
lines.append("```")
|
|
1043
|
+
lines.append("")
|
|
1044
|
+
|
|
1045
|
+
# Mandatory Typography
|
|
1046
|
+
lines.append("## Mandatory Typography")
|
|
1047
|
+
lines.append("")
|
|
1048
|
+
lines.append("ALWAYS use these fonts. Do NOT fall back to Inter/system fonts.")
|
|
1049
|
+
lines.append("")
|
|
1050
|
+
for t in ds.typography:
|
|
1051
|
+
lines.append(f"- **{t.role}:** `{t.font_family}` (weight: {t.weight}, source: {t.source})")
|
|
1052
|
+
lines.append("")
|
|
1053
|
+
|
|
1054
|
+
# Component Rules
|
|
1055
|
+
lines.append("## Component Rules")
|
|
1056
|
+
lines.append("")
|
|
1057
|
+
lines.append("All components MUST follow these patterns:")
|
|
1058
|
+
lines.append("")
|
|
1059
|
+
for comp in ds.components:
|
|
1060
|
+
lines.append(f"### {comp.name.title()}")
|
|
1061
|
+
lines.append(f"- Border radius: `{comp.border_radius}`")
|
|
1062
|
+
if comp.shadow:
|
|
1063
|
+
lines.append(f"- Shadow: `{comp.shadow}`")
|
|
1064
|
+
if comp.transition:
|
|
1065
|
+
lines.append(f"- Transition: `{comp.transition}`")
|
|
1066
|
+
lines.append(f"- Variants: {', '.join(comp.variants)}")
|
|
1067
|
+
lines.append("")
|
|
1068
|
+
|
|
1069
|
+
# Deviation Rules
|
|
1070
|
+
lines.append("## Deviation Policy")
|
|
1071
|
+
lines.append("")
|
|
1072
|
+
lines.append("If you MUST deviate from the brand skill:")
|
|
1073
|
+
lines.append("")
|
|
1074
|
+
lines.append("1. Document the deviation in a comment: `/* DEVIATION: reason */`")
|
|
1075
|
+
lines.append("2. Ensure the deviation maintains brand voice ({})".format(ds.brand_voice))
|
|
1076
|
+
lines.append("3. Keep color temperature consistent ({})".format(ds.color_temperature))
|
|
1077
|
+
lines.append("4. Log the deviation for design review")
|
|
1078
|
+
lines.append("")
|
|
1079
|
+
|
|
1080
|
+
# Anti-patterns
|
|
1081
|
+
lines.append("## Brand Anti-Patterns (NEVER DO)")
|
|
1082
|
+
lines.append("")
|
|
1083
|
+
if ds.anti_patterns_detected:
|
|
1084
|
+
for issue in ds.anti_patterns_detected:
|
|
1085
|
+
lines.append(f"- {issue}")
|
|
1086
|
+
lines.append("- Do NOT use colors outside the brand palette without documentation")
|
|
1087
|
+
lines.append("- Do NOT mix icon libraries")
|
|
1088
|
+
lines.append("- Do NOT use different border-radius values than defined above")
|
|
1089
|
+
lines.append("")
|
|
1090
|
+
|
|
1091
|
+
return "\n".join(lines)
|
|
1092
|
+
|
|
1093
|
+
@staticmethod
|
|
1094
|
+
def to_tailwind_config(ds: ExtractedDesignSystem) -> str:
|
|
1095
|
+
"""Generate Tailwind CSS config from extracted tokens."""
|
|
1096
|
+
config = {
|
|
1097
|
+
"theme": {
|
|
1098
|
+
"extend": {
|
|
1099
|
+
"colors": {},
|
|
1100
|
+
"fontFamily": {},
|
|
1101
|
+
"borderRadius": {},
|
|
1102
|
+
"boxShadow": {},
|
|
1103
|
+
"spacing": {}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
# Colors
|
|
1109
|
+
for c in ds.colors[:8]:
|
|
1110
|
+
key = c.role.replace(" ", "-").replace("_", "-")
|
|
1111
|
+
config["theme"]["extend"]["colors"][key] = c.hex
|
|
1112
|
+
|
|
1113
|
+
# Typography
|
|
1114
|
+
for t in ds.typography:
|
|
1115
|
+
key = t.role.replace("+", "-")
|
|
1116
|
+
config["theme"]["extend"]["fontFamily"][key] = [t.font_family, "sans-serif"]
|
|
1117
|
+
|
|
1118
|
+
# Border radius
|
|
1119
|
+
if ds.components:
|
|
1120
|
+
for comp in ds.components:
|
|
1121
|
+
if comp.border_radius:
|
|
1122
|
+
config["theme"]["extend"]["borderRadius"]["brand"] = comp.border_radius
|
|
1123
|
+
break
|
|
1124
|
+
|
|
1125
|
+
# Spacing
|
|
1126
|
+
for s in ds.spacing[:8]:
|
|
1127
|
+
key = s.css_variable.replace("--space-", "")
|
|
1128
|
+
config["theme"]["extend"]["spacing"][key] = f"{s.value_px}px"
|
|
1129
|
+
|
|
1130
|
+
return f"""// tailwind.config.js — Auto-generated from {ds.project_name} design extraction
|
|
1131
|
+
// Generated: {ds.extracted_at}
|
|
1132
|
+
|
|
1133
|
+
/** @type {{import('tailwindcss').Config}} */
|
|
1134
|
+
module.exports = {json.dumps(config, indent=2)}
|
|
1135
|
+
"""
|
|
1136
|
+
|
|
1137
|
+
@staticmethod
|
|
1138
|
+
def to_css_variables(ds: ExtractedDesignSystem) -> str:
|
|
1139
|
+
"""Generate CSS custom properties file."""
|
|
1140
|
+
lines = []
|
|
1141
|
+
lines.append(f"/* {ds.project_name} — Design Tokens */")
|
|
1142
|
+
lines.append(f"/* Auto-extracted: {ds.extracted_at} */")
|
|
1143
|
+
lines.append(f"/* Source: {ds.source_url or 'local'} */")
|
|
1144
|
+
lines.append("")
|
|
1145
|
+
lines.append(":root {")
|
|
1146
|
+
lines.append(" /* Colors */")
|
|
1147
|
+
for c in ds.colors[:10]:
|
|
1148
|
+
var_name = f"--brand-{c.role.replace(' ', '-')}"
|
|
1149
|
+
lines.append(f" {var_name}: {c.hex};")
|
|
1150
|
+
|
|
1151
|
+
lines.append("")
|
|
1152
|
+
lines.append(" /* Typography */")
|
|
1153
|
+
for t in ds.typography:
|
|
1154
|
+
role = t.role.replace("+", "-")
|
|
1155
|
+
lines.append(f" --font-{role}: '{t.font_family}', sans-serif;")
|
|
1156
|
+
lines.append(f" --font-weight-{role}: {t.weight};")
|
|
1157
|
+
|
|
1158
|
+
lines.append("")
|
|
1159
|
+
lines.append(" /* Spacing */")
|
|
1160
|
+
for s in ds.spacing[:8]:
|
|
1161
|
+
lines.append(f" {s.css_variable}: {s.value_px}px;")
|
|
1162
|
+
|
|
1163
|
+
lines.append("")
|
|
1164
|
+
lines.append(" /* Shadows */")
|
|
1165
|
+
if ds.components:
|
|
1166
|
+
for comp in ds.components:
|
|
1167
|
+
if comp.shadow:
|
|
1168
|
+
lines.append(f" --shadow-{comp.name}: {comp.shadow};")
|
|
1169
|
+
|
|
1170
|
+
lines.append("")
|
|
1171
|
+
lines.append(" /* Border Radius */")
|
|
1172
|
+
if ds.components:
|
|
1173
|
+
for comp in ds.components:
|
|
1174
|
+
if comp.border_radius:
|
|
1175
|
+
lines.append(f" --radius-{comp.name}: {comp.border_radius};")
|
|
1176
|
+
break # Usually uniform
|
|
1177
|
+
|
|
1178
|
+
lines.append("")
|
|
1179
|
+
lines.append(" /* Transitions */")
|
|
1180
|
+
if ds.components:
|
|
1181
|
+
for comp in ds.components:
|
|
1182
|
+
if comp.transition:
|
|
1183
|
+
lines.append(f" --transition-default: {comp.transition};")
|
|
1184
|
+
break
|
|
1185
|
+
|
|
1186
|
+
lines.append("")
|
|
1187
|
+
lines.append(" /* Layout */")
|
|
1188
|
+
lines.append(f" --max-width: {ds.layout.max_width};")
|
|
1189
|
+
for name, value in ds.layout.breakpoints.items():
|
|
1190
|
+
lines.append(f" --breakpoint-{name}: {value}px;")
|
|
1191
|
+
|
|
1192
|
+
lines.append("}")
|
|
1193
|
+
lines.append("")
|
|
1194
|
+
|
|
1195
|
+
return "\n".join(lines)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
# ============================================================================
|
|
1199
|
+
# MAIN EXTRACTION PIPELINE
|
|
1200
|
+
# ============================================================================
|
|
1201
|
+
|
|
1202
|
+
class DesignSystemExtractor:
|
|
1203
|
+
"""Main extraction pipeline orchestrator."""
|
|
1204
|
+
|
|
1205
|
+
def __init__(self, project_name: str):
|
|
1206
|
+
self.project_name = project_name
|
|
1207
|
+
self.parser = CSSParser()
|
|
1208
|
+
|
|
1209
|
+
def extract_from_css_files(self, css_files: List[str]) -> ExtractedDesignSystem:
|
|
1210
|
+
"""Extract from local CSS files."""
|
|
1211
|
+
for filepath in css_files:
|
|
1212
|
+
path = Path(filepath)
|
|
1213
|
+
if path.exists() and path.suffix == '.css':
|
|
1214
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
1215
|
+
self.parser.parse_css(f.read(), context=path.name)
|
|
1216
|
+
|
|
1217
|
+
analyzer = DesignSystemAnalyzer(self.parser, self.project_name)
|
|
1218
|
+
return analyzer.analyze()
|
|
1219
|
+
|
|
1220
|
+
def extract_from_html_files(self, html_files: List[str]) -> ExtractedDesignSystem:
|
|
1221
|
+
"""Extract from local HTML files."""
|
|
1222
|
+
for filepath in html_files:
|
|
1223
|
+
path = Path(filepath)
|
|
1224
|
+
if path.exists() and path.suffix in ('.html', '.htm', '.vue', '.svelte', '.jsx', '.tsx'):
|
|
1225
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
1226
|
+
content = f.read()
|
|
1227
|
+
self.parser.parse_html(content)
|
|
1228
|
+
|
|
1229
|
+
analyzer = DesignSystemAnalyzer(self.parser, self.project_name)
|
|
1230
|
+
return analyzer.analyze()
|
|
1231
|
+
|
|
1232
|
+
def extract_from_directory(self, directory: str) -> ExtractedDesignSystem:
|
|
1233
|
+
"""Extract from all CSS/HTML files in a directory recursively."""
|
|
1234
|
+
dir_path = Path(directory)
|
|
1235
|
+
if not dir_path.exists():
|
|
1236
|
+
print(f"Error: Directory not found: {directory}", file=sys.stderr)
|
|
1237
|
+
sys.exit(1)
|
|
1238
|
+
|
|
1239
|
+
css_extensions = {'.css'}
|
|
1240
|
+
html_extensions = {'.html', '.htm', '.vue', '.svelte', '.jsx', '.tsx'}
|
|
1241
|
+
|
|
1242
|
+
file_count = 0
|
|
1243
|
+
for path in dir_path.rglob('*'):
|
|
1244
|
+
if path.suffix in css_extensions:
|
|
1245
|
+
try:
|
|
1246
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
1247
|
+
self.parser.parse_css(f.read(), context=path.name)
|
|
1248
|
+
file_count += 1
|
|
1249
|
+
except Exception as e:
|
|
1250
|
+
print(f"Warning: Could not parse {path}: {e}", file=sys.stderr)
|
|
1251
|
+
|
|
1252
|
+
elif path.suffix in html_extensions:
|
|
1253
|
+
try:
|
|
1254
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
1255
|
+
self.parser.parse_html(f.read())
|
|
1256
|
+
file_count += 1
|
|
1257
|
+
except Exception as e:
|
|
1258
|
+
print(f"Warning: Could not parse {path}: {e}", file=sys.stderr)
|
|
1259
|
+
|
|
1260
|
+
print(f"Analyzed {file_count} files from {directory}", file=sys.stderr)
|
|
1261
|
+
|
|
1262
|
+
analyzer = DesignSystemAnalyzer(self.parser, self.project_name, source_url=directory)
|
|
1263
|
+
return analyzer.analyze()
|
|
1264
|
+
|
|
1265
|
+
def extract_from_url(self, url: str) -> ExtractedDesignSystem:
|
|
1266
|
+
"""Extract from a live URL by fetching HTML and linked CSS."""
|
|
1267
|
+
try:
|
|
1268
|
+
import urllib.request
|
|
1269
|
+
import urllib.parse
|
|
1270
|
+
|
|
1271
|
+
print(f"Fetching {url}...", file=sys.stderr)
|
|
1272
|
+
|
|
1273
|
+
# Fetch HTML
|
|
1274
|
+
req = urllib.request.Request(url, headers={
|
|
1275
|
+
'User-Agent': 'Mozilla/5.0 (UI-UX-Pro-Max Extractor)'
|
|
1276
|
+
})
|
|
1277
|
+
with urllib.request.urlopen(req, timeout=15) as response:
|
|
1278
|
+
html_content = response.read().decode('utf-8', errors='ignore')
|
|
1279
|
+
|
|
1280
|
+
self.parser.parse_html(html_content)
|
|
1281
|
+
|
|
1282
|
+
# Find and fetch linked CSS files
|
|
1283
|
+
css_link_pattern = re.compile(
|
|
1284
|
+
r'<link[^>]*href=["\']([^"\']*\.css[^"\']*)["\']', re.IGNORECASE
|
|
1285
|
+
)
|
|
1286
|
+
for match in css_link_pattern.finditer(html_content):
|
|
1287
|
+
css_url = match.group(1)
|
|
1288
|
+
if not css_url.startswith('http'):
|
|
1289
|
+
css_url = urllib.parse.urljoin(url, css_url)
|
|
1290
|
+
|
|
1291
|
+
try:
|
|
1292
|
+
print(f" Fetching CSS: {css_url[:80]}...", file=sys.stderr)
|
|
1293
|
+
css_req = urllib.request.Request(css_url, headers={
|
|
1294
|
+
'User-Agent': 'Mozilla/5.0 (UI-UX-Pro-Max Extractor)'
|
|
1295
|
+
})
|
|
1296
|
+
with urllib.request.urlopen(css_req, timeout=10) as css_response:
|
|
1297
|
+
css_content = css_response.read().decode('utf-8', errors='ignore')
|
|
1298
|
+
self.parser.parse_css(css_content, context=css_url.split('/')[-1])
|
|
1299
|
+
except Exception as e:
|
|
1300
|
+
print(f" Warning: Could not fetch CSS {css_url}: {e}", file=sys.stderr)
|
|
1301
|
+
|
|
1302
|
+
analyzer = DesignSystemAnalyzer(self.parser, self.project_name, source_url=url)
|
|
1303
|
+
return analyzer.analyze()
|
|
1304
|
+
|
|
1305
|
+
except ImportError:
|
|
1306
|
+
print("Error: urllib not available. Use --directory mode instead.", file=sys.stderr)
|
|
1307
|
+
sys.exit(1)
|
|
1308
|
+
except Exception as e:
|
|
1309
|
+
print(f"Error fetching URL: {e}", file=sys.stderr)
|
|
1310
|
+
sys.exit(1)
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
# ============================================================================
|
|
1314
|
+
# CLI ENTRY POINT
|
|
1315
|
+
# ============================================================================
|
|
1316
|
+
|
|
1317
|
+
def main():
|
|
1318
|
+
parser = argparse.ArgumentParser(
|
|
1319
|
+
description="Extract design system from existing websites/apps",
|
|
1320
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
1321
|
+
epilog="""
|
|
1322
|
+
Examples:
|
|
1323
|
+
# Extract from URL
|
|
1324
|
+
python3 extractor.py --url https://example.com -p "MyBrand"
|
|
1325
|
+
|
|
1326
|
+
# Extract from local project directory
|
|
1327
|
+
python3 extractor.py --directory ./src -p "MyApp"
|
|
1328
|
+
|
|
1329
|
+
# Extract from specific CSS files
|
|
1330
|
+
python3 extractor.py --css style.css theme.css -p "MyProject"
|
|
1331
|
+
|
|
1332
|
+
# Generate complete skill set
|
|
1333
|
+
python3 extractor.py --url https://example.com --generate-skill -p "MyBrand"
|
|
1334
|
+
|
|
1335
|
+
# Export as Tailwind config
|
|
1336
|
+
python3 extractor.py --directory ./src -p "MyApp" --format tailwind
|
|
1337
|
+
"""
|
|
1338
|
+
)
|
|
1339
|
+
|
|
1340
|
+
# Input sources (mutually exclusive)
|
|
1341
|
+
input_group = parser.add_mutually_exclusive_group(required=True)
|
|
1342
|
+
input_group.add_argument("--url", "-u", help="URL to analyze")
|
|
1343
|
+
input_group.add_argument("--directory", "-d", help="Local directory to analyze")
|
|
1344
|
+
input_group.add_argument("--css", nargs="+", help="Specific CSS files to analyze")
|
|
1345
|
+
input_group.add_argument("--html", nargs="+", help="Specific HTML files to analyze")
|
|
1346
|
+
|
|
1347
|
+
# Options
|
|
1348
|
+
parser.add_argument("--project-name", "-p", required=True, help="Project/brand name")
|
|
1349
|
+
parser.add_argument("--output", "-o", default=None, help="Output file path (default: stdout)")
|
|
1350
|
+
parser.add_argument("--format", "-f",
|
|
1351
|
+
choices=["markdown", "skill", "tailwind", "css-vars", "json"],
|
|
1352
|
+
default="markdown",
|
|
1353
|
+
help="Output format (default: markdown)")
|
|
1354
|
+
parser.add_argument("--generate-skill", action="store_true",
|
|
1355
|
+
help="Also generate BRAND-SKILL.md file")
|
|
1356
|
+
parser.add_argument("--persist", action="store_true",
|
|
1357
|
+
help="Save to design-system/ folder structure")
|
|
1358
|
+
|
|
1359
|
+
args = parser.parse_args()
|
|
1360
|
+
|
|
1361
|
+
# Initialize extractor
|
|
1362
|
+
extractor = DesignSystemExtractor(args.project_name)
|
|
1363
|
+
|
|
1364
|
+
# Extract based on input source
|
|
1365
|
+
if args.url:
|
|
1366
|
+
ds = extractor.extract_from_url(args.url)
|
|
1367
|
+
elif args.directory:
|
|
1368
|
+
ds = extractor.extract_from_directory(args.directory)
|
|
1369
|
+
elif args.css:
|
|
1370
|
+
ds = extractor.extract_from_css_files(args.css)
|
|
1371
|
+
elif args.html:
|
|
1372
|
+
ds = extractor.extract_from_html_files(args.html)
|
|
1373
|
+
|
|
1374
|
+
# Format output
|
|
1375
|
+
formatter = OutputFormatter()
|
|
1376
|
+
format_map = {
|
|
1377
|
+
"markdown": formatter.to_markdown,
|
|
1378
|
+
"skill": formatter.to_skill,
|
|
1379
|
+
"tailwind": formatter.to_tailwind_config,
|
|
1380
|
+
"css-vars": formatter.to_css_variables,
|
|
1381
|
+
"json": lambda ds: json.dumps(asdict(ds), indent=2, ensure_ascii=False, default=str),
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
output = format_map[args.format](ds)
|
|
1385
|
+
|
|
1386
|
+
# Write output
|
|
1387
|
+
if args.output:
|
|
1388
|
+
output_path = Path(args.output)
|
|
1389
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1390
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
1391
|
+
f.write(output)
|
|
1392
|
+
print(f"Written to {output_path}", file=sys.stderr)
|
|
1393
|
+
else:
|
|
1394
|
+
print(output)
|
|
1395
|
+
|
|
1396
|
+
# Generate skill if requested
|
|
1397
|
+
if args.generate_skill and args.format != "skill":
|
|
1398
|
+
skill_output = formatter.to_skill(ds)
|
|
1399
|
+
if args.persist:
|
|
1400
|
+
project_slug = args.project_name.lower().replace(' ', '-')
|
|
1401
|
+
skill_dir = Path("design-system") / project_slug
|
|
1402
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
1403
|
+
|
|
1404
|
+
# Write EXTRACTED.md
|
|
1405
|
+
extracted_path = skill_dir / "EXTRACTED.md"
|
|
1406
|
+
with open(extracted_path, 'w', encoding='utf-8') as f:
|
|
1407
|
+
f.write(formatter.to_markdown(ds))
|
|
1408
|
+
print(f"Written to {extracted_path}", file=sys.stderr)
|
|
1409
|
+
|
|
1410
|
+
# Write BRAND-SKILL.md
|
|
1411
|
+
skill_path = skill_dir / "BRAND-SKILL.md"
|
|
1412
|
+
with open(skill_path, 'w', encoding='utf-8') as f:
|
|
1413
|
+
f.write(skill_output)
|
|
1414
|
+
print(f"Written to {skill_path}", file=sys.stderr)
|
|
1415
|
+
|
|
1416
|
+
# Write tailwind config
|
|
1417
|
+
tailwind_path = skill_dir / "tailwind.config.js"
|
|
1418
|
+
with open(tailwind_path, 'w', encoding='utf-8') as f:
|
|
1419
|
+
f.write(formatter.to_tailwind_config(ds))
|
|
1420
|
+
print(f"Written to {tailwind_path}", file=sys.stderr)
|
|
1421
|
+
|
|
1422
|
+
# Write CSS variables
|
|
1423
|
+
css_path = skill_dir / "design-tokens.css"
|
|
1424
|
+
with open(css_path, 'w', encoding='utf-8') as f:
|
|
1425
|
+
f.write(formatter.to_css_variables(ds))
|
|
1426
|
+
print(f"Written to {css_path}", file=sys.stderr)
|
|
1427
|
+
|
|
1428
|
+
print(f"\nComplete design system persisted to design-system/{project_slug}/", file=sys.stderr)
|
|
1429
|
+
else:
|
|
1430
|
+
print("\n" + "=" * 60, file=sys.stderr)
|
|
1431
|
+
print("BRAND SKILL (use --persist to save to file):", file=sys.stderr)
|
|
1432
|
+
print("=" * 60, file=sys.stderr)
|
|
1433
|
+
print(skill_output)
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
if __name__ == "__main__":
|
|
1437
|
+
main()
|