@palate/cli 0.0.0-stage → 0.0.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/LICENSE +20 -0
- package/README.md +151 -2
- package/bin/palate.mjs +894 -0
- package/kb/anatomy/data-table.md +24 -0
- package/kb/anatomy/empty-state.md +16 -0
- package/kb/anatomy/filter-toolbar.md +19 -0
- package/kb/anatomy/meter.md +16 -0
- package/kb/anatomy/product-window.md +18 -0
- package/kb/anatomy/settings-page.md +18 -0
- package/kb/anatomy/status-indicator.md +17 -0
- package/kb/anatomy/toast-undo.md +16 -0
- package/kb/bench/briefs.json +618 -0
- package/kb/craft/SOURCES.md +48 -0
- package/kb/craft/creativity.md +55 -0
- package/kb/craft/details.md +75 -0
- package/kb/craft/landing-demos.md +46 -0
- package/kb/craft/mistakes.md +73 -0
- package/kb/craft/motion.md +53 -0
- package/kb/craft/principles.md +68 -0
- package/kb/index/discovery.json +1 -0
- package/kb/inspiration/index.json +79 -0
- package/kb/moves/animated-comparison-table.md +58 -0
- package/kb/moves/before-after-slider.md +58 -0
- package/kb/moves/bento-live-tiles.md +57 -0
- package/kb/moves/calm-motion-system.md +63 -0
- package/kb/moves/canvas-grain.md +58 -0
- package/kb/moves/changelog-as-magazine.md +56 -0
- package/kb/moves/code-drawn-diagram.md +56 -0
- package/kb/moves/colophon.md +54 -0
- package/kb/moves/command-palette-site-nav.md +58 -0
- package/kb/moves/confetti-free-celebration.md +58 -0
- package/kb/moves/console-easter-egg.md +57 -0
- package/kb/moves/copy-confirmation.md +57 -0
- package/kb/moves/cursor-reactive-light.md +57 -0
- package/kb/moves/dark-band-contrast-section.md +55 -0
- package/kb/moves/designed-dark-mode.md +58 -0
- package/kb/moves/duotone-photography.md +55 -0
- package/kb/moves/editorial-drop-cap.md +58 -0
- package/kb/moves/editorial-grid-pull-quotes.md +55 -0
- package/kb/moves/engraving-colour-lens.md +64 -0
- package/kb/moves/explorable-control.md +57 -0
- package/kb/moves/feature-films-on-painted-stages.md +58 -0
- package/kb/moves/first-win-onboarding.md +59 -0
- package/kb/moves/footer-live-status.md +56 -0
- package/kb/moves/footer-rewards-the-end.md +58 -0
- package/kb/moves/generative-brand-pattern.md +55 -0
- package/kb/moves/gentle-error-nudge.md +59 -0
- package/kb/moves/halftone-dot-imagery.md +55 -0
- package/kb/moves/hand-drawn-annotations.md +55 -0
- package/kb/moves/hero-paints-itself-in.md +65 -0
- package/kb/moves/honest-progress-narration.md +58 -0
- package/kb/moves/honest-testimonial-wall.md +57 -0
- package/kb/moves/inline-sparklines-in-copy.md +57 -0
- package/kb/moves/inspect-our-taste.md +58 -0
- package/kb/moves/interactive-pricing-calculator.md +59 -0
- package/kb/moves/isometric-illustration-system.md +56 -0
- package/kb/moves/kinetic-variable-type.md +57 -0
- package/kb/moves/konami-secret.md +59 -0
- package/kb/moves/layered-paper-depth.md +56 -0
- package/kb/moves/lightweight-3d-object-hero.md +59 -0
- package/kb/moves/line-engraving-from-image.md +63 -0
- package/kb/moves/link-preview-peek.md +57 -0
- package/kb/moves/live-data-ticker.md +61 -0
- package/kb/moves/made-with-proof.md +51 -0
- package/kb/moves/magnetic-buttons.md +55 -0
- package/kb/moves/marquee-done-right.md +58 -0
- package/kb/moves/moves.yaml +450 -0
- package/kb/moves/museum-painting-backdrop.md +59 -0
- package/kb/moves/one-colour-brand-world.md +56 -0
- package/kb/moves/playful-404.md +58 -0
- package/kb/moves/pointer-highlight-word.md +56 -0
- package/kb/moves/product-in-context.md +56 -0
- package/kb/moves/real-world-data.md +58 -0
- package/kb/moves/referral-reward-reveal.md +59 -0
- package/kb/moves/riso-print-texture.md +57 -0
- package/kb/moves/screenshot-detail-crops.md +56 -0
- package/kb/moves/scroll-progress-chapters.md +57 -0
- package/kb/moves/scroll-scrubbed-product-film.md +62 -0
- package/kb/moves/self-playing-product-demo.md +61 -0
- package/kb/moves/shape-continuity-morph.md +58 -0
- package/kb/moves/sliding-nav-indicator.md +58 -0
- package/kb/moves/sticky-scroll-story.md +56 -0
- package/kb/moves/story-led-long-scroll.md +56 -0
- package/kb/moves/tactile-button.md +63 -0
- package/kb/moves/technical-drawing-lines.md +57 -0
- package/kb/moves/time-of-day-theming.md +56 -0
- package/kb/moves/tiny-game-easter-egg.md +60 -0
- package/kb/moves/type-decode-reveal.md +57 -0
- package/kb/moves/typographic-numeral-hero.md +58 -0
- package/kb/moves/view-transitions-between-pages.md +57 -0
- package/kb/moves/waitlist-position-live.md +60 -0
- package/kb/patterns/app-team-members.md +27 -0
- package/kb/patterns/chat-agent-approvals.md +31 -0
- package/kb/patterns/commerce-product-page.md +34 -0
- package/kb/patterns/dashboard-entity-table.md +37 -0
- package/kb/patterns/dashboard-home-today.md +24 -0
- package/kb/patterns/docs-api-reference.md +31 -0
- package/kb/patterns/marketing-product-led-landing.md +30 -0
- package/kb/patterns/mobile-daily-checklist.md +26 -0
- package/kb/references/index.json +1 -0
- package/kb/references/thumbs/004c5e9072.jpg +0 -0
- package/kb/references/thumbs/008915eb21.jpg +0 -0
- package/kb/references/thumbs/0098cf66c2.jpg +0 -0
- package/kb/references/thumbs/012e61b31f.jpg +0 -0
- package/kb/references/thumbs/014d4e46fc.jpg +0 -0
- package/kb/references/thumbs/01e54f394a.jpg +0 -0
- package/kb/references/thumbs/01e99a579c.jpg +0 -0
- package/kb/references/thumbs/01fd014575.jpg +0 -0
- package/kb/references/thumbs/022778ca72.jpg +0 -0
- package/kb/references/thumbs/02717bf1e1.jpg +0 -0
- package/kb/references/thumbs/02a5cfdbe9.jpg +0 -0
- package/kb/references/thumbs/02c9823f8d.jpg +0 -0
- package/kb/references/thumbs/02f3974613.jpg +0 -0
- package/kb/references/thumbs/032427f1ce.jpg +0 -0
- package/kb/references/thumbs/03ba677d07.jpg +0 -0
- package/kb/references/thumbs/04f2476cfe.jpg +0 -0
- package/kb/references/thumbs/0563e7c52e.jpg +0 -0
- package/kb/references/thumbs/05fa6b8eef.jpg +0 -0
- package/kb/references/thumbs/065bce1517.jpg +0 -0
- package/kb/references/thumbs/06c017f113.jpg +0 -0
- package/kb/references/thumbs/0770e2d8d3.jpg +0 -0
- package/kb/references/thumbs/078a26c4cb.jpg +0 -0
- package/kb/references/thumbs/08679b6e84.jpg +0 -0
- package/kb/references/thumbs/088c45a34c.jpg +0 -0
- package/kb/references/thumbs/089e9fd4e4.jpg +0 -0
- package/kb/references/thumbs/08f773231e.jpg +0 -0
- package/kb/references/thumbs/093ca3e2e3.jpg +0 -0
- package/kb/references/thumbs/0963320f74.jpg +0 -0
- package/kb/references/thumbs/09fc1ded85.jpg +0 -0
- package/kb/references/thumbs/0a368f3963.jpg +0 -0
- package/kb/references/thumbs/0a9e0e5201.jpg +0 -0
- package/kb/references/thumbs/0aace67ee7.jpg +0 -0
- package/kb/references/thumbs/0b61ecf793.jpg +0 -0
- package/kb/references/thumbs/0bde18ae09.jpg +0 -0
- package/kb/references/thumbs/0bf7ee8143.jpg +0 -0
- package/kb/references/thumbs/0ca20a13e0.jpg +0 -0
- package/kb/references/thumbs/0cdc0198c4.jpg +0 -0
- package/kb/references/thumbs/0cf9790b28.jpg +0 -0
- package/kb/references/thumbs/0d0202904b.jpg +0 -0
- package/kb/references/thumbs/0d053aba18.jpg +0 -0
- package/kb/references/thumbs/0d0c2c13d2.jpg +0 -0
- package/kb/references/thumbs/0d30be99b0.jpg +0 -0
- package/kb/references/thumbs/0d503da1b7.jpg +0 -0
- package/kb/references/thumbs/0ddad7b740.jpg +0 -0
- package/kb/references/thumbs/0e0a148101.jpg +0 -0
- package/kb/references/thumbs/0e401b6b75.jpg +0 -0
- package/kb/references/thumbs/0e8f32ba0f.jpg +0 -0
- package/kb/references/thumbs/0ef1966a7d.jpg +0 -0
- package/kb/references/thumbs/0efd416d11.jpg +0 -0
- package/kb/references/thumbs/0f3af263ae.jpg +0 -0
- package/kb/references/thumbs/0f9644c3a5.jpg +0 -0
- package/kb/references/thumbs/1020552737.jpg +0 -0
- package/kb/references/thumbs/1037626587.jpg +0 -0
- package/kb/references/thumbs/10512b7e94.jpg +0 -0
- package/kb/references/thumbs/106dc4a7b5.jpg +0 -0
- package/kb/references/thumbs/108943f6f7.jpg +0 -0
- package/kb/references/thumbs/10a5551d51.jpg +0 -0
- package/kb/references/thumbs/10dd01076c.jpg +0 -0
- package/kb/references/thumbs/11b06e39da.jpg +0 -0
- package/kb/references/thumbs/11baa1ec83.jpg +0 -0
- package/kb/references/thumbs/11bbe12167.jpg +0 -0
- package/kb/references/thumbs/11ca37feb8.jpg +0 -0
- package/kb/references/thumbs/11f14d3f12.jpg +0 -0
- package/kb/references/thumbs/124530ceb5.jpg +0 -0
- package/kb/references/thumbs/1276402336.jpg +0 -0
- package/kb/references/thumbs/1280e5c9d6.jpg +0 -0
- package/kb/references/thumbs/12cac0d172.jpg +0 -0
- package/kb/references/thumbs/138be6623a.jpg +0 -0
- package/kb/references/thumbs/13a0c544d1.jpg +0 -0
- package/kb/references/thumbs/13a32cb94c.jpg +0 -0
- package/kb/references/thumbs/144035d641.jpg +0 -0
- package/kb/references/thumbs/1457e41924.jpg +0 -0
- package/kb/references/thumbs/14972e3a86.jpg +0 -0
- package/kb/references/thumbs/15a47f2e4c.jpg +0 -0
- package/kb/references/thumbs/15aa15d22e.jpg +0 -0
- package/kb/references/thumbs/160dcd24b7.jpg +0 -0
- package/kb/references/thumbs/1610978529.jpg +0 -0
- package/kb/references/thumbs/16db30ba37.jpg +0 -0
- package/kb/references/thumbs/16f19ae4f3.jpg +0 -0
- package/kb/references/thumbs/16f758b03f.jpg +0 -0
- package/kb/references/thumbs/1748e5b751.jpg +0 -0
- package/kb/references/thumbs/175d0b0e69.jpg +0 -0
- package/kb/references/thumbs/178f571a36.jpg +0 -0
- package/kb/references/thumbs/17c43dc025.jpg +0 -0
- package/kb/references/thumbs/17c7f05b50.jpg +0 -0
- package/kb/references/thumbs/17f97e61dd.jpg +0 -0
- package/kb/references/thumbs/1804e17365.jpg +0 -0
- package/kb/references/thumbs/195c162ed1.jpg +0 -0
- package/kb/references/thumbs/19e3699c29.jpg +0 -0
- package/kb/references/thumbs/1a48e0a4d5.jpg +0 -0
- package/kb/references/thumbs/1a6cda265e.jpg +0 -0
- package/kb/references/thumbs/1a7e012091.jpg +0 -0
- package/kb/references/thumbs/1a9173a172.jpg +0 -0
- package/kb/references/thumbs/1b34a6b4d2.jpg +0 -0
- package/kb/references/thumbs/1b5284f3b2.jpg +0 -0
- package/kb/references/thumbs/1b93fbd6d3.jpg +0 -0
- package/kb/references/thumbs/1c6728fef6.jpg +0 -0
- package/kb/references/thumbs/1c6f101618.jpg +0 -0
- package/kb/references/thumbs/1cf7011a94.jpg +0 -0
- package/kb/references/thumbs/1d1769e539.jpg +0 -0
- package/kb/references/thumbs/1d1fd23199.jpg +0 -0
- package/kb/references/thumbs/1d7b6602ac.jpg +0 -0
- package/kb/references/thumbs/1dbbc61b9e.jpg +0 -0
- package/kb/references/thumbs/1dbdd6d21d.jpg +0 -0
- package/kb/references/thumbs/1dd873744b.jpg +0 -0
- package/kb/references/thumbs/1df4dd7cf9.jpg +0 -0
- package/kb/references/thumbs/1ece8c35b8.jpg +0 -0
- package/kb/references/thumbs/1f0e5b9aa4.jpg +0 -0
- package/kb/references/thumbs/1f1419651b.jpg +0 -0
- package/kb/references/thumbs/1f2a6da92d.jpg +0 -0
- package/kb/references/thumbs/1f651f33c0.jpg +0 -0
- package/kb/references/thumbs/1f8b697d18.jpg +0 -0
- package/kb/references/thumbs/1fee3824cd.jpg +0 -0
- package/kb/references/thumbs/1ff7757853.jpg +0 -0
- package/kb/references/thumbs/2043182f01.jpg +0 -0
- package/kb/references/thumbs/2059dabcd5.jpg +0 -0
- package/kb/references/thumbs/209d9dbb95.jpg +0 -0
- package/kb/references/thumbs/20d8923554.jpg +0 -0
- package/kb/references/thumbs/210dd3b671.jpg +0 -0
- package/kb/references/thumbs/2120719116.jpg +0 -0
- package/kb/references/thumbs/21a7384429.jpg +0 -0
- package/kb/references/thumbs/225f187391.jpg +0 -0
- package/kb/references/thumbs/230caf7dd2.jpg +0 -0
- package/kb/references/thumbs/235afad355.jpg +0 -0
- package/kb/references/thumbs/2378066687.jpg +0 -0
- package/kb/references/thumbs/241faf68a1.jpg +0 -0
- package/kb/references/thumbs/24d9f1a9b9.jpg +0 -0
- package/kb/references/thumbs/2505c8b9a0.jpg +0 -0
- package/kb/references/thumbs/25593638ca.jpg +0 -0
- package/kb/references/thumbs/25dd454f18.jpg +0 -0
- package/kb/references/thumbs/2602e123e2.jpg +0 -0
- package/kb/references/thumbs/26095c0b11.jpg +0 -0
- package/kb/references/thumbs/26260d6238.jpg +0 -0
- package/kb/references/thumbs/26bba21836.jpg +0 -0
- package/kb/references/thumbs/26bde3520b.jpg +0 -0
- package/kb/references/thumbs/272e22067c.jpg +0 -0
- package/kb/references/thumbs/27421b3226.jpg +0 -0
- package/kb/references/thumbs/2769ebca41.jpg +0 -0
- package/kb/references/thumbs/27e2f6e8b0.jpg +0 -0
- package/kb/references/thumbs/28432ba373.jpg +0 -0
- package/kb/references/thumbs/2846626f19.jpg +0 -0
- package/kb/references/thumbs/28750e821e.jpg +0 -0
- package/kb/references/thumbs/289481230f.jpg +0 -0
- package/kb/references/thumbs/2897967408.jpg +0 -0
- package/kb/references/thumbs/28c0a5fa17.jpg +0 -0
- package/kb/references/thumbs/28c181fba5.jpg +0 -0
- package/kb/references/thumbs/29806f539f.jpg +0 -0
- package/kb/references/thumbs/2997f56ee7.jpg +0 -0
- package/kb/references/thumbs/29b34a10fb.jpg +0 -0
- package/kb/references/thumbs/29bd78cdb4.jpg +0 -0
- package/kb/references/thumbs/29c8c6c22a.jpg +0 -0
- package/kb/references/thumbs/2ac23f7fae.jpg +0 -0
- package/kb/references/thumbs/2b76e13f79.jpg +0 -0
- package/kb/references/thumbs/2b84226479.jpg +0 -0
- package/kb/references/thumbs/2bf2dbf737.jpg +0 -0
- package/kb/references/thumbs/2bf89abbf6.jpg +0 -0
- package/kb/references/thumbs/2c562cd534.jpg +0 -0
- package/kb/references/thumbs/2cfe9d48c4.jpg +0 -0
- package/kb/references/thumbs/2d49f19b27.jpg +0 -0
- package/kb/references/thumbs/2dc8670da7.jpg +0 -0
- package/kb/references/thumbs/2de947c469.jpg +0 -0
- package/kb/references/thumbs/2e075ca79f.jpg +0 -0
- package/kb/references/thumbs/2eb0820e6b.jpg +0 -0
- package/kb/references/thumbs/2ec56a9dc0.jpg +0 -0
- package/kb/references/thumbs/30bb4ad607.jpg +0 -0
- package/kb/references/thumbs/30d19b8444.jpg +0 -0
- package/kb/references/thumbs/3173c71d73.jpg +0 -0
- package/kb/references/thumbs/31e59e4518.jpg +0 -0
- package/kb/references/thumbs/322b8960e8.jpg +0 -0
- package/kb/references/thumbs/325c9be6f4.jpg +0 -0
- package/kb/references/thumbs/3273338982.jpg +0 -0
- package/kb/references/thumbs/32f8387f38.jpg +0 -0
- package/kb/references/thumbs/332f6b74c3.jpg +0 -0
- package/kb/references/thumbs/334b146b6a.jpg +0 -0
- package/kb/references/thumbs/339abd60e4.jpg +0 -0
- package/kb/references/thumbs/33e88ec295.jpg +0 -0
- package/kb/references/thumbs/340931b5d4.jpg +0 -0
- package/kb/references/thumbs/34165a93bd.jpg +0 -0
- package/kb/references/thumbs/34d0575dd7.jpg +0 -0
- package/kb/references/thumbs/3549e75f0a.jpg +0 -0
- package/kb/references/thumbs/35827f1561.jpg +0 -0
- package/kb/references/thumbs/37c50280ec.jpg +0 -0
- package/kb/references/thumbs/37e66ecf11.jpg +0 -0
- package/kb/references/thumbs/3841a04fdc.jpg +0 -0
- package/kb/references/thumbs/38490c088f.jpg +0 -0
- package/kb/references/thumbs/3850319e9f.jpg +0 -0
- package/kb/references/thumbs/3856a64166.jpg +0 -0
- package/kb/references/thumbs/3881f211c2.jpg +0 -0
- package/kb/references/thumbs/38c7dde2f3.jpg +0 -0
- package/kb/references/thumbs/38ce9e486c.jpg +0 -0
- package/kb/references/thumbs/390ea8e01b.jpg +0 -0
- package/kb/references/thumbs/391b7fc27c.jpg +0 -0
- package/kb/references/thumbs/395f7214dc.jpg +0 -0
- package/kb/references/thumbs/39e81a5db2.jpg +0 -0
- package/kb/references/thumbs/3a67c8d655.jpg +0 -0
- package/kb/references/thumbs/3a89c25e3b.jpg +0 -0
- package/kb/references/thumbs/3aa0837542.jpg +0 -0
- package/kb/references/thumbs/3aae18aa8a.jpg +0 -0
- package/kb/references/thumbs/3bdbe767db.jpg +0 -0
- package/kb/references/thumbs/3c0616fc7f.jpg +0 -0
- package/kb/references/thumbs/3c18e9db1d.jpg +0 -0
- package/kb/references/thumbs/3c5a14076c.jpg +0 -0
- package/kb/references/thumbs/3ca9ec15c6.jpg +0 -0
- package/kb/references/thumbs/3d7756fd35.jpg +0 -0
- package/kb/references/thumbs/3dd45bd33d.jpg +0 -0
- package/kb/references/thumbs/3de72d5414.jpg +0 -0
- package/kb/references/thumbs/3dede597af.jpg +0 -0
- package/kb/references/thumbs/3ea948619e.jpg +0 -0
- package/kb/references/thumbs/402c12054c.jpg +0 -0
- package/kb/references/thumbs/403ab4af90.jpg +0 -0
- package/kb/references/thumbs/40773a8c63.jpg +0 -0
- package/kb/references/thumbs/407d9583db.jpg +0 -0
- package/kb/references/thumbs/415dc1d84c.jpg +0 -0
- package/kb/references/thumbs/420b676a4f.jpg +0 -0
- package/kb/references/thumbs/42a1ee85eb.jpg +0 -0
- package/kb/references/thumbs/43059221aa.jpg +0 -0
- package/kb/references/thumbs/432fa0d22c.jpg +0 -0
- package/kb/references/thumbs/43826f20e3.jpg +0 -0
- package/kb/references/thumbs/43ac55c98a.jpg +0 -0
- package/kb/references/thumbs/43d098f3c3.jpg +0 -0
- package/kb/references/thumbs/4449d80c2c.jpg +0 -0
- package/kb/references/thumbs/44625727dc.jpg +0 -0
- package/kb/references/thumbs/4482bb87a1.jpg +0 -0
- package/kb/references/thumbs/44b078acdc.jpg +0 -0
- package/kb/references/thumbs/44d0305850.jpg +0 -0
- package/kb/references/thumbs/456bb6b59e.jpg +0 -0
- package/kb/references/thumbs/45df73ce48.jpg +0 -0
- package/kb/references/thumbs/45edb45d87.jpg +0 -0
- package/kb/references/thumbs/4671d57cd5.jpg +0 -0
- package/kb/references/thumbs/46cc108518.jpg +0 -0
- package/kb/references/thumbs/470022e614.jpg +0 -0
- package/kb/references/thumbs/4719a669ce.jpg +0 -0
- package/kb/references/thumbs/47404f5ad4.jpg +0 -0
- package/kb/references/thumbs/479d3eb977.jpg +0 -0
- package/kb/references/thumbs/479ed92ceb.jpg +0 -0
- package/kb/references/thumbs/47baed50c7.jpg +0 -0
- package/kb/references/thumbs/47fdcfd7b0.jpg +0 -0
- package/kb/references/thumbs/48fd5e94c5.jpg +0 -0
- package/kb/references/thumbs/493807917b.jpg +0 -0
- package/kb/references/thumbs/49a1c67293.jpg +0 -0
- package/kb/references/thumbs/49e2d7fafe.jpg +0 -0
- package/kb/references/thumbs/49f540fcb6.jpg +0 -0
- package/kb/references/thumbs/4a67ec4028.jpg +0 -0
- package/kb/references/thumbs/4a93275b04.jpg +0 -0
- package/kb/references/thumbs/4aae6f52da.jpg +0 -0
- package/kb/references/thumbs/4b1bae66bd.jpg +0 -0
- package/kb/references/thumbs/4b75da15a9.jpg +0 -0
- package/kb/references/thumbs/4b77789b88.jpg +0 -0
- package/kb/references/thumbs/4bc2f3f3a4.jpg +0 -0
- package/kb/references/thumbs/4c48f27b88.jpg +0 -0
- package/kb/references/thumbs/4c52bd8bfb.jpg +0 -0
- package/kb/references/thumbs/4c9b4acc60.jpg +0 -0
- package/kb/references/thumbs/4cbf0752d4.jpg +0 -0
- package/kb/references/thumbs/4cd2672517.jpg +0 -0
- package/kb/references/thumbs/4dd22a8181.jpg +0 -0
- package/kb/references/thumbs/4ee3447314.jpg +0 -0
- package/kb/references/thumbs/4ef29c8144.jpg +0 -0
- package/kb/references/thumbs/4f82fadfd6.jpg +0 -0
- package/kb/references/thumbs/4fc9b643d8.jpg +0 -0
- package/kb/references/thumbs/50549524b0.jpg +0 -0
- package/kb/references/thumbs/509a615883.jpg +0 -0
- package/kb/references/thumbs/50c03ef855.jpg +0 -0
- package/kb/references/thumbs/50cf9acad6.jpg +0 -0
- package/kb/references/thumbs/51131b025c.jpg +0 -0
- package/kb/references/thumbs/51da1124f0.jpg +0 -0
- package/kb/references/thumbs/524d5e7288.jpg +0 -0
- package/kb/references/thumbs/5251247c81.jpg +0 -0
- package/kb/references/thumbs/526b91b7ec.jpg +0 -0
- package/kb/references/thumbs/52f02f6c2e.jpg +0 -0
- package/kb/references/thumbs/530ab530dc.jpg +0 -0
- package/kb/references/thumbs/5335ee4404.jpg +0 -0
- package/kb/references/thumbs/5385b90939.jpg +0 -0
- package/kb/references/thumbs/53c54654cc.jpg +0 -0
- package/kb/references/thumbs/5420794c6c.jpg +0 -0
- package/kb/references/thumbs/542a9dce8b.jpg +0 -0
- package/kb/references/thumbs/5571530a5f.jpg +0 -0
- package/kb/references/thumbs/56cc68d804.jpg +0 -0
- package/kb/references/thumbs/577b7f7c44.jpg +0 -0
- package/kb/references/thumbs/57adf8f1bb.jpg +0 -0
- package/kb/references/thumbs/57c5f5e3a6.jpg +0 -0
- package/kb/references/thumbs/57ffe4e839.jpg +0 -0
- package/kb/references/thumbs/5817dfc7d6.jpg +0 -0
- package/kb/references/thumbs/581a182a7a.jpg +0 -0
- package/kb/references/thumbs/58807679c8.jpg +0 -0
- package/kb/references/thumbs/589a1d9111.jpg +0 -0
- package/kb/references/thumbs/59167391ab.jpg +0 -0
- package/kb/references/thumbs/5a70420166.jpg +0 -0
- package/kb/references/thumbs/5b10df1b8d.jpg +0 -0
- package/kb/references/thumbs/5b7a712fdb.jpg +0 -0
- package/kb/references/thumbs/5bc46b92ad.jpg +0 -0
- package/kb/references/thumbs/5c0546a120.jpg +0 -0
- package/kb/references/thumbs/5c2b6125a8.jpg +0 -0
- package/kb/references/thumbs/5c536b9e42.jpg +0 -0
- package/kb/references/thumbs/5c8e441d30.jpg +0 -0
- package/kb/references/thumbs/5cb6f1baa5.jpg +0 -0
- package/kb/references/thumbs/5ce6c1f21e.jpg +0 -0
- package/kb/references/thumbs/5cef052c0a.jpg +0 -0
- package/kb/references/thumbs/5d08438209.jpg +0 -0
- package/kb/references/thumbs/5d0f9af9b0.jpg +0 -0
- package/kb/references/thumbs/5d300ed332.jpg +0 -0
- package/kb/references/thumbs/5d708a4794.jpg +0 -0
- package/kb/references/thumbs/5de8826a98.jpg +0 -0
- package/kb/references/thumbs/5df7bff22c.jpg +0 -0
- package/kb/references/thumbs/5e087d0f00.jpg +0 -0
- package/kb/references/thumbs/5e44e70ca0.jpg +0 -0
- package/kb/references/thumbs/5eff5d80ce.jpg +0 -0
- package/kb/references/thumbs/5f015c96b1.jpg +0 -0
- package/kb/references/thumbs/5f3289b7c4.jpg +0 -0
- package/kb/references/thumbs/5f830c40eb.jpg +0 -0
- package/kb/references/thumbs/60c4c3527b.jpg +0 -0
- package/kb/references/thumbs/60e9e594dd.jpg +0 -0
- package/kb/references/thumbs/615421d7be.jpg +0 -0
- package/kb/references/thumbs/6176e4004f.jpg +0 -0
- package/kb/references/thumbs/61f0fe5e06.jpg +0 -0
- package/kb/references/thumbs/6213785db4.jpg +0 -0
- package/kb/references/thumbs/62a670ae09.jpg +0 -0
- package/kb/references/thumbs/63cc12981b.jpg +0 -0
- package/kb/references/thumbs/641f15cc71.jpg +0 -0
- package/kb/references/thumbs/642ab2081d.jpg +0 -0
- package/kb/references/thumbs/644a59bf6e.jpg +0 -0
- package/kb/references/thumbs/644da8a8e3.jpg +0 -0
- package/kb/references/thumbs/6458986897.jpg +0 -0
- package/kb/references/thumbs/64cfbd7b93.jpg +0 -0
- package/kb/references/thumbs/661a57686a.jpg +0 -0
- package/kb/references/thumbs/66355a813a.jpg +0 -0
- package/kb/references/thumbs/664396dd44.jpg +0 -0
- package/kb/references/thumbs/66d832d65f.jpg +0 -0
- package/kb/references/thumbs/66de9e9833.jpg +0 -0
- package/kb/references/thumbs/6750964fd9.jpg +0 -0
- package/kb/references/thumbs/67c7164832.jpg +0 -0
- package/kb/references/thumbs/681600b6cd.jpg +0 -0
- package/kb/references/thumbs/682e75afe4.jpg +0 -0
- package/kb/references/thumbs/69274bc952.jpg +0 -0
- package/kb/references/thumbs/696ec08c52.jpg +0 -0
- package/kb/references/thumbs/69760111cc.jpg +0 -0
- package/kb/references/thumbs/697e32664f.jpg +0 -0
- package/kb/references/thumbs/69df92544b.jpg +0 -0
- package/kb/references/thumbs/69e48c5229.jpg +0 -0
- package/kb/references/thumbs/6a5fb75de1.jpg +0 -0
- package/kb/references/thumbs/6a7389e472.jpg +0 -0
- package/kb/references/thumbs/6a9b041e02.jpg +0 -0
- package/kb/references/thumbs/6ab2fa93ee.jpg +0 -0
- package/kb/references/thumbs/6ae3400319.jpg +0 -0
- package/kb/references/thumbs/6b21344bf3.jpg +0 -0
- package/kb/references/thumbs/6bd1b67a8a.jpg +0 -0
- package/kb/references/thumbs/6c99156c4f.jpg +0 -0
- package/kb/references/thumbs/6cc9b65384.jpg +0 -0
- package/kb/references/thumbs/6d15830a73.jpg +0 -0
- package/kb/references/thumbs/6d4c33d180.jpg +0 -0
- package/kb/references/thumbs/6e36815799.jpg +0 -0
- package/kb/references/thumbs/6eddb9cbfc.jpg +0 -0
- package/kb/references/thumbs/6f97968a7c.jpg +0 -0
- package/kb/references/thumbs/6fa0716a7d.jpg +0 -0
- package/kb/references/thumbs/6facd57184.jpg +0 -0
- package/kb/references/thumbs/6fd57b785a.jpg +0 -0
- package/kb/references/thumbs/700b3cb185.jpg +0 -0
- package/kb/references/thumbs/71197443fa.jpg +0 -0
- package/kb/references/thumbs/72082bc9ee.jpg +0 -0
- package/kb/references/thumbs/72453db479.jpg +0 -0
- package/kb/references/thumbs/72cd64d500.jpg +0 -0
- package/kb/references/thumbs/72d1e75283.jpg +0 -0
- package/kb/references/thumbs/72e13f7f5b.jpg +0 -0
- package/kb/references/thumbs/7459f8e644.jpg +0 -0
- package/kb/references/thumbs/7470d55c79.jpg +0 -0
- package/kb/references/thumbs/74c468f02a.jpg +0 -0
- package/kb/references/thumbs/74fed6eb77.jpg +0 -0
- package/kb/references/thumbs/756e05080d.jpg +0 -0
- package/kb/references/thumbs/75df7e5f49.jpg +0 -0
- package/kb/references/thumbs/76226da6ef.jpg +0 -0
- package/kb/references/thumbs/76a34aa056.jpg +0 -0
- package/kb/references/thumbs/76c7076589.jpg +0 -0
- package/kb/references/thumbs/76cccc396c.jpg +0 -0
- package/kb/references/thumbs/774654789f.jpg +0 -0
- package/kb/references/thumbs/7746cc494d.jpg +0 -0
- package/kb/references/thumbs/7766e36aa3.jpg +0 -0
- package/kb/references/thumbs/78049331ef.jpg +0 -0
- package/kb/references/thumbs/78808a0ca5.jpg +0 -0
- package/kb/references/thumbs/7890b0a310.jpg +0 -0
- package/kb/references/thumbs/78911975e6.jpg +0 -0
- package/kb/references/thumbs/78d289d5a0.jpg +0 -0
- package/kb/references/thumbs/794126267e.jpg +0 -0
- package/kb/references/thumbs/7976eb9025.jpg +0 -0
- package/kb/references/thumbs/798519ec7c.jpg +0 -0
- package/kb/references/thumbs/7a6cc5eecc.jpg +0 -0
- package/kb/references/thumbs/7ac8a7cd4a.jpg +0 -0
- package/kb/references/thumbs/7b1341d61d.jpg +0 -0
- package/kb/references/thumbs/7b9c4031e3.jpg +0 -0
- package/kb/references/thumbs/7ba5ad3779.jpg +0 -0
- package/kb/references/thumbs/7be6eb2bd0.jpg +0 -0
- package/kb/references/thumbs/7c30864f5d.jpg +0 -0
- package/kb/references/thumbs/7c379b5e17.jpg +0 -0
- package/kb/references/thumbs/7c4050eb02.jpg +0 -0
- package/kb/references/thumbs/7ca47b3221.jpg +0 -0
- package/kb/references/thumbs/7d43516bd7.jpg +0 -0
- package/kb/references/thumbs/7da9dbb98a.jpg +0 -0
- package/kb/references/thumbs/7ddda8f57c.jpg +0 -0
- package/kb/references/thumbs/7ece6d215c.jpg +0 -0
- package/kb/references/thumbs/7eebf029de.jpg +0 -0
- package/kb/references/thumbs/7f45483c15.jpg +0 -0
- package/kb/references/thumbs/7fde06eaa5.jpg +0 -0
- package/kb/references/thumbs/80407b97e9.jpg +0 -0
- package/kb/references/thumbs/80f0a6cf4b.jpg +0 -0
- package/kb/references/thumbs/8118b451b8.jpg +0 -0
- package/kb/references/thumbs/816baed4a8.jpg +0 -0
- package/kb/references/thumbs/817d2d15d9.jpg +0 -0
- package/kb/references/thumbs/8260cb430d.jpg +0 -0
- package/kb/references/thumbs/827b562899.jpg +0 -0
- package/kb/references/thumbs/8293cf1e92.jpg +0 -0
- package/kb/references/thumbs/8294653169.jpg +0 -0
- package/kb/references/thumbs/83061f871f.jpg +0 -0
- package/kb/references/thumbs/833df778ca.jpg +0 -0
- package/kb/references/thumbs/83d3acaafc.jpg +0 -0
- package/kb/references/thumbs/844c6efeff.jpg +0 -0
- package/kb/references/thumbs/84563d482a.jpg +0 -0
- package/kb/references/thumbs/848082dd94.jpg +0 -0
- package/kb/references/thumbs/8494b1da94.jpg +0 -0
- package/kb/references/thumbs/84b71014be.jpg +0 -0
- package/kb/references/thumbs/84be4a5f3c.jpg +0 -0
- package/kb/references/thumbs/84fd0fd25a.jpg +0 -0
- package/kb/references/thumbs/853e488e22.jpg +0 -0
- package/kb/references/thumbs/858c91585d.jpg +0 -0
- package/kb/references/thumbs/85903e8fe9.jpg +0 -0
- package/kb/references/thumbs/86597d43fd.jpg +0 -0
- package/kb/references/thumbs/866eb99500.jpg +0 -0
- package/kb/references/thumbs/86ac050340.jpg +0 -0
- package/kb/references/thumbs/86afec7b64.jpg +0 -0
- package/kb/references/thumbs/87238127f5.jpg +0 -0
- package/kb/references/thumbs/87321df792.jpg +0 -0
- package/kb/references/thumbs/87460f70cd.jpg +0 -0
- package/kb/references/thumbs/8770dfeaec.jpg +0 -0
- package/kb/references/thumbs/8798451a8c.jpg +0 -0
- package/kb/references/thumbs/87da1f34a3.jpg +0 -0
- package/kb/references/thumbs/882cdf8c05.jpg +0 -0
- package/kb/references/thumbs/8839f4106f.jpg +0 -0
- package/kb/references/thumbs/8844455212.jpg +0 -0
- package/kb/references/thumbs/88abba0497.jpg +0 -0
- package/kb/references/thumbs/88b372435d.jpg +0 -0
- package/kb/references/thumbs/88bd4f35fa.jpg +0 -0
- package/kb/references/thumbs/88c71a075e.jpg +0 -0
- package/kb/references/thumbs/892e5f048e.jpg +0 -0
- package/kb/references/thumbs/893cc8e920.jpg +0 -0
- package/kb/references/thumbs/896c198814.jpg +0 -0
- package/kb/references/thumbs/8a8e22f756.jpg +0 -0
- package/kb/references/thumbs/8a9d8a35cb.jpg +0 -0
- package/kb/references/thumbs/8ab1d67fec.jpg +0 -0
- package/kb/references/thumbs/8ab201fe12.jpg +0 -0
- package/kb/references/thumbs/8ac372c102.jpg +0 -0
- package/kb/references/thumbs/8b15736d62.jpg +0 -0
- package/kb/references/thumbs/8b1d8d6a05.jpg +0 -0
- package/kb/references/thumbs/8bb6050e20.jpg +0 -0
- package/kb/references/thumbs/8c6c9d1577.jpg +0 -0
- package/kb/references/thumbs/8d337851e0.jpg +0 -0
- package/kb/references/thumbs/8d44b33cc5.jpg +0 -0
- package/kb/references/thumbs/8d4536ff3b.jpg +0 -0
- package/kb/references/thumbs/8d9b99454a.jpg +0 -0
- package/kb/references/thumbs/8dda2303ff.jpg +0 -0
- package/kb/references/thumbs/8dec722d62.jpg +0 -0
- package/kb/references/thumbs/8e0ea4ca74.jpg +0 -0
- package/kb/references/thumbs/8ebba84d69.jpg +0 -0
- package/kb/references/thumbs/8ef09140c0.jpg +0 -0
- package/kb/references/thumbs/8f2928a1b0.jpg +0 -0
- package/kb/references/thumbs/8f88f9ddba.jpg +0 -0
- package/kb/references/thumbs/8fa4451009.jpg +0 -0
- package/kb/references/thumbs/90109b0e02.jpg +0 -0
- package/kb/references/thumbs/90e854e5c9.jpg +0 -0
- package/kb/references/thumbs/914e79f893.jpg +0 -0
- package/kb/references/thumbs/9161369197.jpg +0 -0
- package/kb/references/thumbs/9165c87d6f.jpg +0 -0
- package/kb/references/thumbs/9193f6558a.jpg +0 -0
- package/kb/references/thumbs/91ac7df877.jpg +0 -0
- package/kb/references/thumbs/9216bd4931.jpg +0 -0
- package/kb/references/thumbs/922ae8a26a.jpg +0 -0
- package/kb/references/thumbs/92a70e6867.jpg +0 -0
- package/kb/references/thumbs/92c77ece22.jpg +0 -0
- package/kb/references/thumbs/92e5914fbf.jpg +0 -0
- package/kb/references/thumbs/92ee36419a.jpg +0 -0
- package/kb/references/thumbs/94153a60b0.jpg +0 -0
- package/kb/references/thumbs/9449666640.jpg +0 -0
- package/kb/references/thumbs/944be6a646.jpg +0 -0
- package/kb/references/thumbs/945831476e.jpg +0 -0
- package/kb/references/thumbs/94c1af82f4.jpg +0 -0
- package/kb/references/thumbs/9599d6d589.jpg +0 -0
- package/kb/references/thumbs/960710f4cf.jpg +0 -0
- package/kb/references/thumbs/9608f12d64.jpg +0 -0
- package/kb/references/thumbs/962dabfaa0.jpg +0 -0
- package/kb/references/thumbs/965a3a95c6.jpg +0 -0
- package/kb/references/thumbs/9672ed5ae4.jpg +0 -0
- package/kb/references/thumbs/96891ae9c1.jpg +0 -0
- package/kb/references/thumbs/96e1907c99.jpg +0 -0
- package/kb/references/thumbs/96f25b6578.jpg +0 -0
- package/kb/references/thumbs/97211df9ae.jpg +0 -0
- package/kb/references/thumbs/9769d763a5.jpg +0 -0
- package/kb/references/thumbs/9792bd6d05.jpg +0 -0
- package/kb/references/thumbs/97d2837e3e.jpg +0 -0
- package/kb/references/thumbs/97d90d1d85.jpg +0 -0
- package/kb/references/thumbs/97fcd5774a.jpg +0 -0
- package/kb/references/thumbs/980f68564e.jpg +0 -0
- package/kb/references/thumbs/98511fb393.jpg +0 -0
- package/kb/references/thumbs/98f8fa694d.jpg +0 -0
- package/kb/references/thumbs/9a42c6bb97.jpg +0 -0
- package/kb/references/thumbs/9ab3600a06.jpg +0 -0
- package/kb/references/thumbs/9ab48d9130.jpg +0 -0
- package/kb/references/thumbs/9ad519197d.jpg +0 -0
- package/kb/references/thumbs/9b1382c657.jpg +0 -0
- package/kb/references/thumbs/9b6cc1e077.jpg +0 -0
- package/kb/references/thumbs/9beede8480.jpg +0 -0
- package/kb/references/thumbs/9bf8976952.jpg +0 -0
- package/kb/references/thumbs/9cace7b75a.jpg +0 -0
- package/kb/references/thumbs/9cdcc5745e.jpg +0 -0
- package/kb/references/thumbs/9d0b269074.jpg +0 -0
- package/kb/references/thumbs/9d41788b48.jpg +0 -0
- package/kb/references/thumbs/9d475bd6af.jpg +0 -0
- package/kb/references/thumbs/9e6bc543d0.jpg +0 -0
- package/kb/references/thumbs/9ed0e0a206.jpg +0 -0
- package/kb/references/thumbs/9ef538de13.jpg +0 -0
- package/kb/references/thumbs/9efc373ab4.jpg +0 -0
- package/kb/references/thumbs/9f4a939fa5.jpg +0 -0
- package/kb/references/thumbs/9f8d12d7b9.jpg +0 -0
- package/kb/references/thumbs/9fd5e3efa7.jpg +0 -0
- package/kb/references/thumbs/a0a9b9c572.jpg +0 -0
- package/kb/references/thumbs/a29ca9b185.jpg +0 -0
- package/kb/references/thumbs/a2a42884af.jpg +0 -0
- package/kb/references/thumbs/a2f5064e34.jpg +0 -0
- package/kb/references/thumbs/a3112ce672.jpg +0 -0
- package/kb/references/thumbs/a3437736d1.jpg +0 -0
- package/kb/references/thumbs/a372667e76.jpg +0 -0
- package/kb/references/thumbs/a39bae94e9.jpg +0 -0
- package/kb/references/thumbs/a3fc75d94c.jpg +0 -0
- package/kb/references/thumbs/a440487631.jpg +0 -0
- package/kb/references/thumbs/a4993d5fb5.jpg +0 -0
- package/kb/references/thumbs/a500476a86.jpg +0 -0
- package/kb/references/thumbs/a55506bf46.jpg +0 -0
- package/kb/references/thumbs/a59a5f9843.jpg +0 -0
- package/kb/references/thumbs/a5bce0eede.jpg +0 -0
- package/kb/references/thumbs/a5d921e1f0.jpg +0 -0
- package/kb/references/thumbs/a5e19d4637.jpg +0 -0
- package/kb/references/thumbs/a5f92b97d5.jpg +0 -0
- package/kb/references/thumbs/a6562c210a.jpg +0 -0
- package/kb/references/thumbs/a6f61a8d09.jpg +0 -0
- package/kb/references/thumbs/a719fdf663.jpg +0 -0
- package/kb/references/thumbs/a724e5442f.jpg +0 -0
- package/kb/references/thumbs/a852b4319f.jpg +0 -0
- package/kb/references/thumbs/a896c08448.jpg +0 -0
- package/kb/references/thumbs/a8d190a4b8.jpg +0 -0
- package/kb/references/thumbs/a8dcdd57d7.jpg +0 -0
- package/kb/references/thumbs/a951347e18.jpg +0 -0
- package/kb/references/thumbs/a9a1a0bf7d.jpg +0 -0
- package/kb/references/thumbs/a9b72986a8.jpg +0 -0
- package/kb/references/thumbs/aa11555ba8.jpg +0 -0
- package/kb/references/thumbs/aa4aab9093.jpg +0 -0
- package/kb/references/thumbs/aa89b96658.jpg +0 -0
- package/kb/references/thumbs/aaeb3739b6.jpg +0 -0
- package/kb/references/thumbs/ab25a608ec.jpg +0 -0
- package/kb/references/thumbs/ab34b62268.jpg +0 -0
- package/kb/references/thumbs/ab3b0217bb.jpg +0 -0
- package/kb/references/thumbs/ac70c04afb.jpg +0 -0
- package/kb/references/thumbs/ace0879117.jpg +0 -0
- package/kb/references/thumbs/ad0515ae55.jpg +0 -0
- package/kb/references/thumbs/ad66b25b47.jpg +0 -0
- package/kb/references/thumbs/ad68229d2f.jpg +0 -0
- package/kb/references/thumbs/add6480693.jpg +0 -0
- package/kb/references/thumbs/ae5b8c3b8c.jpg +0 -0
- package/kb/references/thumbs/ae81247b47.jpg +0 -0
- package/kb/references/thumbs/af8aa54379.jpg +0 -0
- package/kb/references/thumbs/afa80dedd4.jpg +0 -0
- package/kb/references/thumbs/afe04d730f.jpg +0 -0
- package/kb/references/thumbs/b09a7dc315.jpg +0 -0
- package/kb/references/thumbs/b0c02629f6.jpg +0 -0
- package/kb/references/thumbs/b1cb4f91de.jpg +0 -0
- package/kb/references/thumbs/b1d4c13c92.jpg +0 -0
- package/kb/references/thumbs/b20eef86db.jpg +0 -0
- package/kb/references/thumbs/b3136ccdde.jpg +0 -0
- package/kb/references/thumbs/b3f5e8d7dd.jpg +0 -0
- package/kb/references/thumbs/b44adb81c9.jpg +0 -0
- package/kb/references/thumbs/b457a5894b.jpg +0 -0
- package/kb/references/thumbs/b461281e0f.jpg +0 -0
- package/kb/references/thumbs/b4942644f0.jpg +0 -0
- package/kb/references/thumbs/b49f8eccad.jpg +0 -0
- package/kb/references/thumbs/b56c69be23.jpg +0 -0
- package/kb/references/thumbs/b59786b33c.jpg +0 -0
- package/kb/references/thumbs/b5d368ce5a.jpg +0 -0
- package/kb/references/thumbs/b5eb442406.jpg +0 -0
- package/kb/references/thumbs/b7fdabf4ab.jpg +0 -0
- package/kb/references/thumbs/b88ee8d235.jpg +0 -0
- package/kb/references/thumbs/b8a4e5be8a.jpg +0 -0
- package/kb/references/thumbs/b8c85b661c.jpg +0 -0
- package/kb/references/thumbs/ba1b8e7435.jpg +0 -0
- package/kb/references/thumbs/ba9a593eed.jpg +0 -0
- package/kb/references/thumbs/baa7afc201.jpg +0 -0
- package/kb/references/thumbs/baf74398f7.jpg +0 -0
- package/kb/references/thumbs/bb3e947c10.jpg +0 -0
- package/kb/references/thumbs/bb4c1bb7c1.jpg +0 -0
- package/kb/references/thumbs/bbaa25cc8a.jpg +0 -0
- package/kb/references/thumbs/bc7e7f88cc.jpg +0 -0
- package/kb/references/thumbs/bc9d5b58f5.jpg +0 -0
- package/kb/references/thumbs/bcc99a51f7.jpg +0 -0
- package/kb/references/thumbs/bce13dd1c1.jpg +0 -0
- package/kb/references/thumbs/bd079a6130.jpg +0 -0
- package/kb/references/thumbs/bd2d70dda7.jpg +0 -0
- package/kb/references/thumbs/bd5895c5f2.jpg +0 -0
- package/kb/references/thumbs/bde8e31d62.jpg +0 -0
- package/kb/references/thumbs/be084e78f3.jpg +0 -0
- package/kb/references/thumbs/be94f170c8.jpg +0 -0
- package/kb/references/thumbs/bed33654f7.jpg +0 -0
- package/kb/references/thumbs/beeb9a0c18.jpg +0 -0
- package/kb/references/thumbs/bef42094dc.jpg +0 -0
- package/kb/references/thumbs/bf625496fa.jpg +0 -0
- package/kb/references/thumbs/bf771f1398.jpg +0 -0
- package/kb/references/thumbs/c0ddefa3d9.jpg +0 -0
- package/kb/references/thumbs/c0f01e536e.jpg +0 -0
- package/kb/references/thumbs/c1d592b678.jpg +0 -0
- package/kb/references/thumbs/c1e83e1b3e.jpg +0 -0
- package/kb/references/thumbs/c2168f8096.jpg +0 -0
- package/kb/references/thumbs/c249cd6b17.jpg +0 -0
- package/kb/references/thumbs/c2e3d4feed.jpg +0 -0
- package/kb/references/thumbs/c3cec01df1.jpg +0 -0
- package/kb/references/thumbs/c47bedb42d.jpg +0 -0
- package/kb/references/thumbs/c4b03b4680.jpg +0 -0
- package/kb/references/thumbs/c4e6d18ec3.jpg +0 -0
- package/kb/references/thumbs/c5477e2877.jpg +0 -0
- package/kb/references/thumbs/c566794674.jpg +0 -0
- package/kb/references/thumbs/c5b945afbb.jpg +0 -0
- package/kb/references/thumbs/c5e4659497.jpg +0 -0
- package/kb/references/thumbs/c622786702.jpg +0 -0
- package/kb/references/thumbs/c6250649ca.jpg +0 -0
- package/kb/references/thumbs/c6261a9526.jpg +0 -0
- package/kb/references/thumbs/c62e609076.jpg +0 -0
- package/kb/references/thumbs/c6602d3e85.jpg +0 -0
- package/kb/references/thumbs/c6c1a16e40.jpg +0 -0
- package/kb/references/thumbs/c77f932157.jpg +0 -0
- package/kb/references/thumbs/c79788f01e.jpg +0 -0
- package/kb/references/thumbs/c7a511ad1e.jpg +0 -0
- package/kb/references/thumbs/c7acfbc3c8.jpg +0 -0
- package/kb/references/thumbs/c7bb6d1bb8.jpg +0 -0
- package/kb/references/thumbs/c829eadcb9.jpg +0 -0
- package/kb/references/thumbs/c891fd9d85.jpg +0 -0
- package/kb/references/thumbs/c89f812ee3.jpg +0 -0
- package/kb/references/thumbs/c8a85b9f61.jpg +0 -0
- package/kb/references/thumbs/c8fdf85c9a.jpg +0 -0
- package/kb/references/thumbs/c961ac1031.jpg +0 -0
- package/kb/references/thumbs/c9fd3f88d8.jpg +0 -0
- package/kb/references/thumbs/ca0d6399c3.jpg +0 -0
- package/kb/references/thumbs/ca316c83e4.jpg +0 -0
- package/kb/references/thumbs/ca3b93aa0d.jpg +0 -0
- package/kb/references/thumbs/cb0596a85b.jpg +0 -0
- package/kb/references/thumbs/cb95a5c3ef.jpg +0 -0
- package/kb/references/thumbs/cbe238b656.jpg +0 -0
- package/kb/references/thumbs/cbf530e608.jpg +0 -0
- package/kb/references/thumbs/cc173494ec.jpg +0 -0
- package/kb/references/thumbs/ccb664bddf.jpg +0 -0
- package/kb/references/thumbs/ccf69d36cd.jpg +0 -0
- package/kb/references/thumbs/ccf759db42.jpg +0 -0
- package/kb/references/thumbs/cd2a285807.jpg +0 -0
- package/kb/references/thumbs/cd4a24a8ca.jpg +0 -0
- package/kb/references/thumbs/cdb3fe8938.jpg +0 -0
- package/kb/references/thumbs/cdce1b9bc6.jpg +0 -0
- package/kb/references/thumbs/cde3aa761a.jpg +0 -0
- package/kb/references/thumbs/ce9d5cd9c2.jpg +0 -0
- package/kb/references/thumbs/ceb01f8a34.jpg +0 -0
- package/kb/references/thumbs/cee15c98d0.jpg +0 -0
- package/kb/references/thumbs/cf6944ddae.jpg +0 -0
- package/kb/references/thumbs/d15dd21cd8.jpg +0 -0
- package/kb/references/thumbs/d1633bf2ed.jpg +0 -0
- package/kb/references/thumbs/d16a838000.jpg +0 -0
- package/kb/references/thumbs/d1cf87e9f0.jpg +0 -0
- package/kb/references/thumbs/d32511d913.jpg +0 -0
- package/kb/references/thumbs/d327e9dedd.jpg +0 -0
- package/kb/references/thumbs/d35bf6c85e.jpg +0 -0
- package/kb/references/thumbs/d37afc9d61.jpg +0 -0
- package/kb/references/thumbs/d3b66655c7.jpg +0 -0
- package/kb/references/thumbs/d40b4172b6.jpg +0 -0
- package/kb/references/thumbs/d4c40d1824.jpg +0 -0
- package/kb/references/thumbs/d4cb55a4b6.jpg +0 -0
- package/kb/references/thumbs/d4f0c30f7e.jpg +0 -0
- package/kb/references/thumbs/d51cce3ae5.jpg +0 -0
- package/kb/references/thumbs/d52948f9ba.jpg +0 -0
- package/kb/references/thumbs/d62bd3cba4.jpg +0 -0
- package/kb/references/thumbs/d668a21c92.jpg +0 -0
- package/kb/references/thumbs/d685072c52.jpg +0 -0
- package/kb/references/thumbs/d6d7b96093.jpg +0 -0
- package/kb/references/thumbs/d7cd874b21.jpg +0 -0
- package/kb/references/thumbs/d7e111736a.jpg +0 -0
- package/kb/references/thumbs/d88325f832.jpg +0 -0
- package/kb/references/thumbs/d9272ded76.jpg +0 -0
- package/kb/references/thumbs/d962e7c654.jpg +0 -0
- package/kb/references/thumbs/d97f000ee2.jpg +0 -0
- package/kb/references/thumbs/d9c1ae2543.jpg +0 -0
- package/kb/references/thumbs/d9ccb3cac8.jpg +0 -0
- package/kb/references/thumbs/da99019680.jpg +0 -0
- package/kb/references/thumbs/da9b8bc615.jpg +0 -0
- package/kb/references/thumbs/daafbee2c1.jpg +0 -0
- package/kb/references/thumbs/db0f11f143.jpg +0 -0
- package/kb/references/thumbs/db5dd367f5.jpg +0 -0
- package/kb/references/thumbs/db69677866.jpg +0 -0
- package/kb/references/thumbs/db87c1956e.jpg +0 -0
- package/kb/references/thumbs/dbb86d8bda.jpg +0 -0
- package/kb/references/thumbs/dbd3a8ff2a.jpg +0 -0
- package/kb/references/thumbs/dbdf4b2794.jpg +0 -0
- package/kb/references/thumbs/dd4c19b4b1.jpg +0 -0
- package/kb/references/thumbs/dd98d9f6fa.jpg +0 -0
- package/kb/references/thumbs/ddb00e3b6a.jpg +0 -0
- package/kb/references/thumbs/dde3541a5c.jpg +0 -0
- package/kb/references/thumbs/de2a4da502.jpg +0 -0
- package/kb/references/thumbs/de4c387a66.jpg +0 -0
- package/kb/references/thumbs/de5618fa82.jpg +0 -0
- package/kb/references/thumbs/de6bec2538.jpg +0 -0
- package/kb/references/thumbs/dea13cbb1f.jpg +0 -0
- package/kb/references/thumbs/deff9202aa.jpg +0 -0
- package/kb/references/thumbs/df12835f8d.jpg +0 -0
- package/kb/references/thumbs/df57acbb3d.jpg +0 -0
- package/kb/references/thumbs/df8265009e.jpg +0 -0
- package/kb/references/thumbs/dfc99a1e8e.jpg +0 -0
- package/kb/references/thumbs/dfc9fde460.jpg +0 -0
- package/kb/references/thumbs/e038348eee.jpg +0 -0
- package/kb/references/thumbs/e04c2ae1d5.jpg +0 -0
- package/kb/references/thumbs/e070345c43.jpg +0 -0
- package/kb/references/thumbs/e0a0c84d47.jpg +0 -0
- package/kb/references/thumbs/e0af1e6b9e.jpg +0 -0
- package/kb/references/thumbs/e1025942c4.jpg +0 -0
- package/kb/references/thumbs/e11b6f5b82.jpg +0 -0
- package/kb/references/thumbs/e1553de3fc.jpg +0 -0
- package/kb/references/thumbs/e162ebe7cd.jpg +0 -0
- package/kb/references/thumbs/e1f1e531d8.jpg +0 -0
- package/kb/references/thumbs/e20842e5c9.jpg +0 -0
- package/kb/references/thumbs/e24e7d949b.jpg +0 -0
- package/kb/references/thumbs/e2a3e5d10a.jpg +0 -0
- package/kb/references/thumbs/e2de02615a.jpg +0 -0
- package/kb/references/thumbs/e305dad7dd.jpg +0 -0
- package/kb/references/thumbs/e52e3ef97f.jpg +0 -0
- package/kb/references/thumbs/e570e92d06.jpg +0 -0
- package/kb/references/thumbs/e583932fb9.jpg +0 -0
- package/kb/references/thumbs/e5da3c038f.jpg +0 -0
- package/kb/references/thumbs/e657634af3.jpg +0 -0
- package/kb/references/thumbs/e67946e3e1.jpg +0 -0
- package/kb/references/thumbs/e69e0f3639.jpg +0 -0
- package/kb/references/thumbs/e6ebe66031.jpg +0 -0
- package/kb/references/thumbs/e7db9563c9.jpg +0 -0
- package/kb/references/thumbs/e84a4c3b7b.jpg +0 -0
- package/kb/references/thumbs/e84cf52c27.jpg +0 -0
- package/kb/references/thumbs/e8707cf6e8.jpg +0 -0
- package/kb/references/thumbs/e888890b40.jpg +0 -0
- package/kb/references/thumbs/e8a502b418.jpg +0 -0
- package/kb/references/thumbs/e8ba68371d.jpg +0 -0
- package/kb/references/thumbs/e9098f54cc.jpg +0 -0
- package/kb/references/thumbs/e9144deae9.jpg +0 -0
- package/kb/references/thumbs/e93e98d757.jpg +0 -0
- package/kb/references/thumbs/e9762dd435.jpg +0 -0
- package/kb/references/thumbs/e9db4544c8.jpg +0 -0
- package/kb/references/thumbs/e9ddffeacd.jpg +0 -0
- package/kb/references/thumbs/e9e2cf1d7e.jpg +0 -0
- package/kb/references/thumbs/ea1e23e623.jpg +0 -0
- package/kb/references/thumbs/ea41200f5e.jpg +0 -0
- package/kb/references/thumbs/eaf4916e1a.jpg +0 -0
- package/kb/references/thumbs/eb2c505fc8.jpg +0 -0
- package/kb/references/thumbs/eb47943693.jpg +0 -0
- package/kb/references/thumbs/eb4ffd6625.jpg +0 -0
- package/kb/references/thumbs/eb82ac0a4d.jpg +0 -0
- package/kb/references/thumbs/ebeb345ade.jpg +0 -0
- package/kb/references/thumbs/ec4e2aba0a.jpg +0 -0
- package/kb/references/thumbs/ed683bc9f7.jpg +0 -0
- package/kb/references/thumbs/ed9e236a84.jpg +0 -0
- package/kb/references/thumbs/ee45e1caea.jpg +0 -0
- package/kb/references/thumbs/ee50bbf2e7.jpg +0 -0
- package/kb/references/thumbs/eed6ad3b7b.jpg +0 -0
- package/kb/references/thumbs/eee247fd70.jpg +0 -0
- package/kb/references/thumbs/ef87b6cb99.jpg +0 -0
- package/kb/references/thumbs/ef87dccf04.jpg +0 -0
- package/kb/references/thumbs/ef941bba1e.jpg +0 -0
- package/kb/references/thumbs/effc14fb2c.jpg +0 -0
- package/kb/references/thumbs/f04aa5a191.jpg +0 -0
- package/kb/references/thumbs/f13f12485f.jpg +0 -0
- package/kb/references/thumbs/f16a5c0fd4.jpg +0 -0
- package/kb/references/thumbs/f1ba7a7a8f.jpg +0 -0
- package/kb/references/thumbs/f1bc4c9c19.jpg +0 -0
- package/kb/references/thumbs/f1ee827878.jpg +0 -0
- package/kb/references/thumbs/f21736aeff.jpg +0 -0
- package/kb/references/thumbs/f22bc41213.jpg +0 -0
- package/kb/references/thumbs/f266aaffcc.jpg +0 -0
- package/kb/references/thumbs/f2c0ed0d65.jpg +0 -0
- package/kb/references/thumbs/f32ac710fd.jpg +0 -0
- package/kb/references/thumbs/f32b697ff6.jpg +0 -0
- package/kb/references/thumbs/f39c883904.jpg +0 -0
- package/kb/references/thumbs/f3b9d55e6e.jpg +0 -0
- package/kb/references/thumbs/f3c1763e14.jpg +0 -0
- package/kb/references/thumbs/f40853fd43.jpg +0 -0
- package/kb/references/thumbs/f4fd07dc4d.jpg +0 -0
- package/kb/references/thumbs/f50b756ee5.jpg +0 -0
- package/kb/references/thumbs/f51272e9b3.jpg +0 -0
- package/kb/references/thumbs/f538895eee.jpg +0 -0
- package/kb/references/thumbs/f5d28081b5.jpg +0 -0
- package/kb/references/thumbs/f6046c9501.jpg +0 -0
- package/kb/references/thumbs/f62e4af316.jpg +0 -0
- package/kb/references/thumbs/f6f6bffeb8.jpg +0 -0
- package/kb/references/thumbs/f736e00af6.jpg +0 -0
- package/kb/references/thumbs/f7932da47d.jpg +0 -0
- package/kb/references/thumbs/f879e118ca.jpg +0 -0
- package/kb/references/thumbs/f88257215d.jpg +0 -0
- package/kb/references/thumbs/f88c45a6d7.jpg +0 -0
- package/kb/references/thumbs/f89a7b646e.jpg +0 -0
- package/kb/references/thumbs/f8a1e7a274.jpg +0 -0
- package/kb/references/thumbs/f9183cf2b6.jpg +0 -0
- package/kb/references/thumbs/f995de5ce9.jpg +0 -0
- package/kb/references/thumbs/f9ada2c3e1.jpg +0 -0
- package/kb/references/thumbs/f9d4616f89.jpg +0 -0
- package/kb/references/thumbs/f9f4307bb0.jpg +0 -0
- package/kb/references/thumbs/fa070122c7.jpg +0 -0
- package/kb/references/thumbs/fa29cc1369.jpg +0 -0
- package/kb/references/thumbs/fa791a4dab.jpg +0 -0
- package/kb/references/thumbs/fa8060509e.jpg +0 -0
- package/kb/references/thumbs/fafeed4003.jpg +0 -0
- package/kb/references/thumbs/fb1c024f38.jpg +0 -0
- package/kb/references/thumbs/fb77aa28b3.jpg +0 -0
- package/kb/references/thumbs/fb9f7f1966.jpg +0 -0
- package/kb/references/thumbs/fba8a8c2da.jpg +0 -0
- package/kb/references/thumbs/fce2754c36.jpg +0 -0
- package/kb/references/thumbs/fce2fb83df.jpg +0 -0
- package/kb/references/thumbs/fd4c51bb47.jpg +0 -0
- package/kb/references/thumbs/fd8c269817.jpg +0 -0
- package/kb/references/thumbs/fdb7b250ca.jpg +0 -0
- package/kb/references/thumbs/fe18088de8.jpg +0 -0
- package/kb/references/thumbs/fe4f742ba7.jpg +0 -0
- package/kb/references/thumbs/fe64742ce7.jpg +0 -0
- package/kb/references/thumbs/feb5d62161.jpg +0 -0
- package/kb/references/thumbs/fed21b3e98.jpg +0 -0
- package/kb/references/thumbs/fed2205951.jpg +0 -0
- package/kb/references/thumbs/ffdacf1715.jpg +0 -0
- package/kb/sections/_buttons.md +93 -0
- package/kb/sections/_finish.md +325 -0
- package/kb/sections/about-founder-note.md +85 -0
- package/kb/sections/about-team-roster.md +86 -0
- package/kb/sections/alternating-rows-live-tiles.md +110 -0
- package/kb/sections/annotated-product-shot.md +105 -0
- package/kb/sections/asymmetric-bento-dominant.md +114 -0
- package/kb/sections/before-after-features.md +104 -0
- package/kb/sections/before-after-repair.md +107 -0
- package/kb/sections/bento-lead-tile.md +112 -0
- package/kb/sections/book-a-slot-honest.md +116 -0
- package/kb/sections/capability-prompt-ledger.md +68 -0
- package/kb/sections/case-study-band.md +113 -0
- package/kb/sections/centred-stack-hero.md +107 -0
- package/kb/sections/closing-demo-final-state.md +81 -0
- package/kb/sections/closing-full-bleed-line-and-form.md +86 -0
- package/kb/sections/closing-result-previewed.md +88 -0
- package/kb/sections/closing-three-real-steps.md +87 -0
- package/kb/sections/command-reference-table.md +70 -0
- package/kb/sections/comparison-old-way-bars.md +88 -0
- package/kb/sections/comparison-same-task-two-ways.md +88 -0
- package/kb/sections/day-of-the-place.md +102 -0
- package/kb/sections/docs-reading-rail.md +88 -0
- package/kb/sections/editorial-columns-hero.md +107 -0
- package/kb/sections/editorial-essay-pull-quote.md +112 -0
- package/kb/sections/events-calendar-list.md +107 -0
- package/kb/sections/explorable-control-demo.md +102 -0
- package/kb/sections/faq-open-answers-editorial.md +85 -0
- package/kb/sections/faq-ruled-accordion-aside.md +87 -0
- package/kb/sections/footer-closing-scene.md +84 -0
- package/kb/sections/footer-editorial-sitemap.md +85 -0
- package/kb/sections/footer-minimal-credits.md +80 -0
- package/kb/sections/full-bleed-image-band.md +103 -0
- package/kb/sections/full-bleed-image-hero.md +105 -0
- package/kb/sections/gallery-first-hero.md +109 -0
- package/kb/sections/horizontal-scroll-strip.md +101 -0
- package/kb/sections/how-i-work-own-voice.md +111 -0
- package/kb/sections/how-it-works-in-place.md +109 -0
- package/kb/sections/impact-numbers-sourced.md +109 -0
- package/kb/sections/install-steps-per-agent.md +77 -0
- package/kb/sections/masonry-real-work.md +109 -0
- package/kb/sections/numbered-editorial-columns.md +105 -0
- package/kb/sections/object-first-hero.md +106 -0
- package/kb/sections/object-timeline.md +105 -0
- package/kb/sections/one-huge-number.md +100 -0
- package/kb/sections/one-person-story.md +103 -0
- package/kb/sections/order-ahead-ticket.md +119 -0
- package/kb/sections/press-strip-quoted.md +106 -0
- package/kb/sections/pricing-comparison-table-sticky.md +94 -0
- package/kb/sections/pricing-free-pro-upgrade-moment.md +85 -0
- package/kb/sections/pricing-plan-beside-product.md +88 -0
- package/kb/sections/pricing-plans-ledger.md +94 -0
- package/kb/sections/pricing-usage-calculator.md +93 -0
- package/kb/sections/printed-menu-card.md +110 -0
- package/kb/sections/process-happening.md +102 -0
- package/kb/sections/project-index-ruled.md +106 -0
- package/kb/sections/proof-logo-cloud-with-reasons.md +82 -0
- package/kb/sections/proof-measured-numbers.md +88 -0
- package/kb/sections/proof-output-wall.md +84 -0
- package/kb/sections/proof-single-honest-quote.md +81 -0
- package/kb/sections/resources-searchable-list.md +110 -0
- package/kb/sections/rooms-and-rates.md +107 -0
- package/kb/sections/ruled-facts-table.md +108 -0
- package/kb/sections/ruled-list-pinned-visual.md +107 -0
- package/kb/sections/scroll-scrubbed-product-demo.md +103 -0
- package/kb/sections/self-playing-run-demo.md +104 -0
- package/kb/sections/services-price-ledger.md +106 -0
- package/kb/sections/signup-form-with-next-steps.md +86 -0
- package/kb/sections/signup-waitlist-place-in-line.md +90 -0
- package/kb/sections/spec-sheet-features.md +108 -0
- package/kb/sections/split-hero-live-object.md +108 -0
- package/kb/sections/sticky-feature-narrative.md +104 -0
- package/kb/sections/todays-board-hero.md +112 -0
- package/kb/sections/touchable-tool-hero.md +111 -0
- package/kb/sections/typographic-statement-hero.md +99 -0
- package/kb/sections/visit-hours-map.md +110 -0
- package/kb/sections/ways-to-help-honest.md +115 -0
- package/kb/sources/design-reference.md +991 -0
- package/kb/sources/product-and-docs.md +37 -0
- package/kb/systems/1win/DESIGN.md +463 -0
- package/kb/systems/LICENSE-awesome-design-md +21 -0
- package/kb/systems/README.md +7 -0
- package/kb/systems/airbnb/DESIGN.md +545 -0
- package/kb/systems/airtable/DESIGN.md +554 -0
- package/kb/systems/aljazeera/DESIGN.md +594 -0
- package/kb/systems/apple/DESIGN.md +562 -0
- package/kb/systems/binance/DESIGN.md +634 -0
- package/kb/systems/bmw/DESIGN.md +544 -0
- package/kb/systems/bmw-m/DESIGN.md +503 -0
- package/kb/systems/bugatti/DESIGN.md +454 -0
- package/kb/systems/cal/DESIGN.md +542 -0
- package/kb/systems/categories.json +87 -0
- package/kb/systems/claude/DESIGN.md +589 -0
- package/kb/systems/clay/DESIGN.md +541 -0
- package/kb/systems/clickhouse/DESIGN.md +544 -0
- package/kb/systems/cohere/DESIGN.md +451 -0
- package/kb/systems/coinbase/DESIGN.md +570 -0
- package/kb/systems/composio/DESIGN.md +506 -0
- package/kb/systems/cursor/DESIGN.md +537 -0
- package/kb/systems/dell-1996/DESIGN.md +632 -0
- package/kb/systems/dji/DESIGN.md +882 -0
- package/kb/systems/elevenlabs/DESIGN.md +504 -0
- package/kb/systems/expo/DESIGN.md +526 -0
- package/kb/systems/ferrari/DESIGN.md +531 -0
- package/kb/systems/figma/DESIGN.md +578 -0
- package/kb/systems/framer/DESIGN.md +544 -0
- package/kb/systems/hashicorp/DESIGN.md +575 -0
- package/kb/systems/hp/DESIGN.md +670 -0
- package/kb/systems/ibm/DESIGN.md +550 -0
- package/kb/systems/intercom/DESIGN.md +546 -0
- package/kb/systems/jobright/DESIGN.md +525 -0
- package/kb/systems/kraken/DESIGN.md +125 -0
- package/kb/systems/lamborghini/DESIGN.md +288 -0
- package/kb/systems/linear.app/DESIGN.md +548 -0
- package/kb/systems/lovable/DESIGN.md +298 -0
- package/kb/systems/mastercard/DESIGN.md +365 -0
- package/kb/systems/mercury/DESIGN.md +568 -0
- package/kb/systems/meta/DESIGN.md +683 -0
- package/kb/systems/minimax/DESIGN.md +746 -0
- package/kb/systems/mintlify/DESIGN.md +852 -0
- package/kb/systems/miro/DESIGN.md +825 -0
- package/kb/systems/mistral.ai/DESIGN.md +773 -0
- package/kb/systems/mongodb/DESIGN.md +767 -0
- package/kb/systems/mydealz/DESIGN.md +551 -0
- package/kb/systems/nerdwallet/DESIGN.md +722 -0
- package/kb/systems/nike/DESIGN.md +575 -0
- package/kb/systems/nintendo-2001/DESIGN.md +649 -0
- package/kb/systems/notion/DESIGN.md +821 -0
- package/kb/systems/nvidia/DESIGN.md +640 -0
- package/kb/systems/ollama/DESIGN.md +539 -0
- package/kb/systems/opencode.ai/DESIGN.md +521 -0
- package/kb/systems/oracle/DESIGN.md +778 -0
- package/kb/systems/oura/DESIGN.md +616 -0
- package/kb/systems/pinterest/DESIGN.md +597 -0
- package/kb/systems/playstation/DESIGN.md +661 -0
- package/kb/systems/posthog/DESIGN.md +690 -0
- package/kb/systems/raycast/DESIGN.md +669 -0
- package/kb/systems/renault/DESIGN.md +589 -0
- package/kb/systems/replicate/DESIGN.md +616 -0
- package/kb/systems/resend/DESIGN.md +585 -0
- package/kb/systems/revolut/DESIGN.md +636 -0
- package/kb/systems/runwayml/DESIGN.md +244 -0
- package/kb/systems/sanity/DESIGN.md +357 -0
- package/kb/systems/sap/DESIGN.md +639 -0
- package/kb/systems/sentry/DESIGN.md +551 -0
- package/kb/systems/shopify/DESIGN.md +516 -0
- package/kb/systems/slack/DESIGN.md +482 -0
- package/kb/systems/spacex/DESIGN.md +363 -0
- package/kb/systems/spotify/DESIGN.md +246 -0
- package/kb/systems/starbucks/DESIGN.md +580 -0
- package/kb/systems/stripe/DESIGN.md +487 -0
- package/kb/systems/supabase/DESIGN.md +462 -0
- package/kb/systems/superhuman/DESIGN.md +448 -0
- package/kb/systems/tesla/DESIGN.md +286 -0
- package/kb/systems/theverge/DESIGN.md +339 -0
- package/kb/systems/together.ai/DESIGN.md +633 -0
- package/kb/systems/uber/DESIGN.md +636 -0
- package/kb/systems/vercel/DESIGN.md +736 -0
- package/kb/systems/vodafone/DESIGN.md +538 -0
- package/kb/systems/voltagent/DESIGN.md +521 -0
- package/kb/systems/warp/DESIGN.md +526 -0
- package/kb/systems/webflow/DESIGN.md +588 -0
- package/kb/systems/wired/DESIGN.md +497 -0
- package/kb/systems/wise/DESIGN.md +544 -0
- package/kb/systems/x.ai/DESIGN.md +465 -0
- package/kb/systems/zapier/DESIGN.md +537 -0
- package/kb/taste/garvit-by-job.md +46 -0
- package/kb/taste/garvit-portfolio.md +94 -0
- package/kb/universal/color-roles.md +26 -0
- package/kb/universal/copy.md +16 -0
- package/kb/universal/details.md +31 -0
- package/kb/universal/first-screen.md +30 -0
- package/kb/universal/fit-the-codebase.md +29 -0
- package/kb/universal/layout-width.md +28 -0
- package/kb/universal/motion-feedback.md +23 -0
- package/kb/universal/signature.md +29 -0
- package/kb/universal/states.md +17 -0
- package/kb/universal/type-app.md +33 -0
- package/kb/universal/type-marketing.md +28 -0
- package/package.json +36 -4
- package/scripts/pack-assets.mjs +48 -0
- package/skill/palate/SKILL.md +261 -0
- package/src/auth-recheck.mjs +4 -0
- package/src/auth.mjs +416 -0
- package/src/capture.mjs +182 -0
- package/src/catalog/classify.mjs +96 -0
- package/src/catalog/curated.mjs +128 -0
- package/src/catalog/index-build.mjs +67 -0
- package/src/catalog/literal.mjs +146 -0
- package/src/catalog/store.mjs +40 -0
- package/src/catalog/vibrant.mjs +126 -0
- package/src/check.mjs +93 -0
- package/src/concept.mjs +157 -0
- package/src/context.mjs +226 -0
- package/src/copy.mjs +188 -0
- package/src/devserver.mjs +135 -0
- package/src/fontgenre.mjs +44 -0
- package/src/guard-agents.mjs +213 -0
- package/src/guard.mjs +377 -0
- package/src/kb.mjs +91 -0
- package/src/look.mjs +312 -0
- package/src/loop/ambition.mjs +620 -0
- package/src/loop/animate.mjs +70 -0
- package/src/loop/artdirection.mjs +264 -0
- package/src/loop/bench.mjs +184 -0
- package/src/loop/benchpick.mjs +155 -0
- package/src/loop/blueprint.mjs +620 -0
- package/src/loop/board.html +180 -0
- package/src/loop/build.mjs +393 -0
- package/src/loop/buttons.mjs +413 -0
- package/src/loop/common.mjs +206 -0
- package/src/loop/cost.mjs +63 -0
- package/src/loop/craft.mjs +152 -0
- package/src/loop/craftchecks.mjs +148 -0
- package/src/loop/critique.mjs +393 -0
- package/src/loop/details.mjs +225 -0
- package/src/loop/direction.html +168 -0
- package/src/loop/direction.mjs +256 -0
- package/src/loop/directions.json +8 -0
- package/src/loop/explore.mjs +219 -0
- package/src/loop/fail.mjs +110 -0
- package/src/loop/fix.mjs +194 -0
- package/src/loop/floor.mjs +677 -0
- package/src/loop/forget.mjs +52 -0
- package/src/loop/gate.mjs +1053 -0
- package/src/loop/grid.html +123 -0
- package/src/loop/growth.mjs +118 -0
- package/src/loop/icons.mjs +109 -0
- package/src/loop/imagery.mjs +1280 -0
- package/src/loop/judge-api.mjs +245 -0
- package/src/loop/judge.mjs +167 -0
- package/src/loop/layouts.mjs +492 -0
- package/src/loop/library.mjs +82 -0
- package/src/loop/lookplan.mjs +182 -0
- package/src/loop/lookvec.mjs +645 -0
- package/src/loop/match.mjs +923 -0
- package/src/loop/matched.mjs +26 -0
- package/src/loop/myrefs.mjs +151 -0
- package/src/loop/pick.mjs +213 -0
- package/src/loop/picker.html +118 -0
- package/src/loop/plan.mjs +289 -0
- package/src/loop/polish.mjs +39 -0
- package/src/loop/research-learn.mjs +41 -0
- package/src/loop/research.mjs +1130 -0
- package/src/loop/rulereview.mjs +146 -0
- package/src/loop/rules-board.html +199 -0
- package/src/loop/rules.mjs +939 -0
- package/src/loop/similar.mjs +276 -0
- package/src/loop/states-source.mjs +258 -0
- package/src/loop/states.mjs +1547 -0
- package/src/loop/stats.mjs +78 -0
- package/src/loop/taste.mjs +81 -0
- package/src/loop/timeline.mjs +149 -0
- package/src/loop/variants.mjs +228 -0
- package/src/loop/vary.mjs +249 -0
- package/src/mcp/server.mjs +1246 -0
- package/src/motion.mjs +207 -0
- package/src/moves.mjs +242 -0
- package/src/novelty.mjs +116 -0
- package/src/page-ambition.js +181 -0
- package/src/page-craft.js +724 -0
- package/src/page-details.js +415 -0
- package/src/page-devices.js +365 -0
- package/src/page-fingerprint.js +281 -0
- package/src/page-gate.js +1307 -0
- package/src/page-look.js +698 -0
- package/src/page-metrics.js +78 -0
- package/src/page-principles.js +1167 -0
- package/src/page-rules.js +308 -0
- package/src/page-sections.js +211 -0
- package/src/page-states.js +703 -0
- package/src/paths.mjs +13 -0
- package/src/png.mjs +107 -0
- package/src/point/auto.mjs +224 -0
- package/src/point/locate.mjs +114 -0
- package/src/point/overlay.js +1516 -0
- package/src/point/server.mjs +1122 -0
- package/src/point/trail.mjs +219 -0
- package/src/pool.mjs +80 -0
- package/src/principles.mjs +279 -0
- package/src/refs.mjs +299 -0
- package/src/robots.mjs +145 -0
- package/src/sections.mjs +210 -0
- package/src/shoot.mjs +235 -0
- package/src/systems/apply.mjs +355 -0
- package/src/systems/load.mjs +251 -0
- package/src/systems/pick.mjs +213 -0
- package/templates/vite-react-tw/index.html +12 -0
- package/templates/vite-react-tw/package.json +25 -0
- package/templates/vite-react-tw/src/App.tsx +3 -0
- package/templates/vite-react-tw/src/index.css +1 -0
- package/templates/vite-react-tw/src/main.tsx +10 -0
- package/templates/vite-react-tw/tsconfig.app.json +26 -0
- package/templates/vite-react-tw/tsconfig.json +7 -0
- package/templates/vite-react-tw/tsconfig.node.json +23 -0
- package/templates/vite-react-tw/vite.config.ts +8 -0
package/src/copy.mjs
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// `palate copy`: reads the words a page shows (from its source, no browser) and flags phrasing that reads as
|
|
2
|
+
// written by a model rather than by the company. Each finding says why it reads that way and what to write
|
|
3
|
+
// instead. Three weights:
|
|
4
|
+
// stop one is enough: hype, filler, placeholder copy, proof with no source. The guard won't let a page finish on one.
|
|
5
|
+
// habit fine once, a habit when it comes back (two fragment slogans, a third "powerful"). Flagged past its limit.
|
|
6
|
+
// hint a soft sign, only listed. Four or more on one page read as generated together, and the report says so.
|
|
7
|
+
// Words inside the project's own name or a waiver (.palate/waivers.json) are skipped.
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
const word = (list) => new RegExp(`\\b(?:${list})\\b`, "i");
|
|
12
|
+
|
|
13
|
+
export const TELLS = [
|
|
14
|
+
// stop
|
|
15
|
+
{ id: "copy-hype", weight: "stop", re: word("unlock(?:s|ing)?|unleash(?:es|ing)?|supercharg(?:e|es|ing)|turbocharg(?:e|es|ing)|elevat(?:e|es|ing) your|empower(?:s|ing)?|revolutioni[sz](?:e|es|ing)|reimagin(?:e|ed|es|ing)|redefin(?:e|es|ing)|transform(?:s)? (?:the way|how) you|streamlin(?:e|es|ing)|leverag(?:e|es|ing)"), why: "A hype verb promises a feeling, not a result.", fix: "Say what changes for the reader, with a number or an example." },
|
|
16
|
+
{ id: "copy-inflated", weight: "stop", re: word("seamless(?:ly)?|effortless(?:ly)?|cutting[- ]edge|next[- ]level|world[- ]class|best[- ]in[- ]class|state[- ]of[- ]the[- ]art|game[- ]chang(?:er|ing)|groundbreaking|innovative|blazing(?:ly)?[- ]fast|lightning[- ]fast|unparalleled|unrivall?ed"), why: "Inflated adjectives are what every page says, so they say nothing.", fix: "Replace it with the fact behind it (how fast, how many, compared with what)." },
|
|
17
|
+
{ id: "copy-filler", weight: "stop", re: word("delve|tapestry|a testament to|embark on|in today'?s (?:fast[- ]paced|digital|modern) (?:world|landscape|age)|ever[- ]evolving|navigat(?:e|ing) the (?:complexities|world) of|look no further|we'?ve got you covered|say goodbye to|harness(?:ing)? the power|the power of|take (?:your|it) to the next level|at the intersection of|in the realm of"), why: "Stock phrasing that models reach for and people rarely write.", fix: "Cut it and start with the specific thing." },
|
|
18
|
+
{ id: "copy-placeholder", weight: "stop", re: /lorem ipsum|dolor sit amet|your (?:tagline|headline|company|logo) here|\b(?:john|jane) doe\b|\bacme (?:inc|corp|co)\b|\bexample company\b|\bTBD\b|\bcoming soon\b(?![^.]{0,40}\d)/i, why: "Placeholder copy left on the page.", fix: "Use the project's real words, or remove the block until they exist." },
|
|
19
|
+
{ id: "copy-vague-proof", weight: "stop", re: /\b(?:thousands|millions|hundreds|tons) of (?:happy |satisfied |delighted )?(?:users|customers|teams|companies|developers|creators|businesses)\b|\btrusted by (?:thousands|millions|teams worldwide|the best|leading)\b|\bloved by (?:thousands|millions|teams|developers|everyone)\b|\bindustry[- ]leading\b/i, why: "Proof with no number or source reads as invented.", fix: "Give the real count and where it comes from, or name the customers, or cut it." },
|
|
20
|
+
{ id: "copy-not-just", weight: "stop", re: /\bnot just (?:an?|another|the)?\s*[\w-]+(?:\s[\w-]+)?\s*[,.;:—–-]+\s*(?:it'?s|but|we'?re|this is)\b|\bmore than just (?:an?|another)\b|\bisn'?t just (?:an?|another)\b/i, why: "The \"not just X, it's Y\" turn is a model habit; it argues with a claim nobody made.", fix: "State what it is, plainly, once." },
|
|
21
|
+
{ id: "copy-click-here", weight: "stop", re: /^\s*(?:click here|tap here)\s*$/i, short: true, why: "\"Click here\" names the gesture, not the destination, and reads badly to screen readers.", fix: "Name what the link opens: \"Read the pricing\", \"Download the report\"." },
|
|
22
|
+
// habit
|
|
23
|
+
{ id: "copy-intensifier", weight: "habit", limit: 2, re: word("powerful|robust|simple|simply|easy|easily|instantly|truly|incredibly|amazing|beautiful(?:ly)?|stunning|intuitive"), why: "Intensifiers stand in for evidence; past two on a page they read as filler.", fix: "Keep the one you can show; show the rest (a timing, a screenshot, a step count)." },
|
|
24
|
+
{ id: "copy-everything", weight: "habit", limit: 1, re: word("everything you need|all[- ]in[- ]one|in one place|one platform|one tool for|a single source of truth"), why: "\"Everything in one place\" is the default pitch of every tool.", fix: "Name the two or three things it actually does." },
|
|
25
|
+
{ id: "copy-slogan-trio", weight: "habit", limit: 1, re: /^\s*(?:[\p{Lu}][\p{L}'’]*(?:\s[\p{L}'’]+){0,2}[.!]\s*){3}$/u, short: true, why: "Three clipped fragments (\"Plan. Build. Ship.\") are fine once; twice is a template.", fix: "Write one of them as a sentence." },
|
|
26
|
+
{ id: "copy-adjective-trio", weight: "habit", limit: 1, re: /^\s*[\p{L}-]+,\s+[\p{L}-]+,?\s+and\s+[\p{L}-]+[.!]?\s*$/iu, short: true, why: "\"Fast, simple, and secure\" lists virtues no one would argue against.", fix: "Keep the one that's true and different, and prove it." },
|
|
27
|
+
{ id: "copy-exclaim", weight: "habit", limit: 1, re: /\w!(?:\s|$)/, why: "Exclamation marks in interface copy read as forced enthusiasm.", fix: "End on a full stop; let the content carry it." },
|
|
28
|
+
{ id: "copy-vague-cta", weight: "habit", limit: 1, re: /^\s*(?:learn more|read more|discover more|find out more|explore|see more|get started now)\s*[→›>]?\s*$/i, short: true, why: "A vague button text repeated across sections; nobody knows where each one goes.", fix: "Verb plus object: \"See the pricing\", \"Read the API docs\"." },
|
|
29
|
+
// hint
|
|
30
|
+
{ id: "copy-question-hook", weight: "hint", re: /\b(?:ready to|want to|looking to|tired of|struggling (?:to|with)|ever wish(?:ed)?)\b[^?]{0,80}\?/i, why: "A rhetorical question as a hook is a stock opener.", fix: "Answer it instead: say what the reader gets." },
|
|
31
|
+
{ id: "copy-announce", weight: "hint", re: /^\s*(?:introducing|meet|say hello to)\b|\bwe'?re (?:excited|thrilled|proud) to\b/i, why: "Announcing the product instead of describing it.", fix: "Lead with what it does." },
|
|
32
|
+
{ id: "copy-future-of", weight: "hint", re: /\bthe future of\b|\bbuilt for (?:the )?(?:modern|next generation|tomorrow)\b|\bfor the (?:modern|AI) era\b/i, why: "\"The future of X\" claims a category instead of describing a product.", fix: "Describe what someone can do with it today." },
|
|
33
|
+
{ id: "copy-solution", weight: "hint", re: word("solutions?|a suite of|end[- ]to[- ]end|at scale|unlock(?:s)? value|actionable insights|drive (?:growth|results|engagement)"), why: "Consultant nouns hide what the thing is.", fix: "Use the plain noun: an app, a report, a checker." },
|
|
34
|
+
{ id: "copy-title-case", weight: "hint", re: null, why: "Every Word Capitalised reads as a template heading.", fix: "Sentence case: capitalise the first word and names only." },
|
|
35
|
+
{ id: "copy-dash-aside", weight: "hint", re: /\s[—–]\s.+\s[—–]\s|\w—\w.*\w—\w/, why: "A pair of dashes around an aside is a strong model habit in prose.", fix: "Use commas, brackets or two sentences." },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const UI = /\.(tsx|jsx|vue|svelte|astro|html|mdx)$/i; // .md is notes and docs, not page copy
|
|
39
|
+
const CONTENT = /\.(ts|js|mjs|json)$/i; // copy kept in content files (content.ts, strings.json)
|
|
40
|
+
const SKIP = /(^|\/)(node_modules|\.next|dist|out|build|\.palate|\.git|coverage|public)\//;
|
|
41
|
+
const NOT_COPY = /\.(test|spec|stories|d)\.[a-z]+$/i;
|
|
42
|
+
|
|
43
|
+
// Text a reader sees, with its line: JSX/HTML text between tags, readable attributes, and prose string literals.
|
|
44
|
+
export function extractCopy(source, file = "") {
|
|
45
|
+
const out = [];
|
|
46
|
+
const lines = source.split("\n");
|
|
47
|
+
const at = (index) => source.slice(0, index).split("\n").length;
|
|
48
|
+
const push = (text, index, tag) => {
|
|
49
|
+
const t = text.replace(/ | /g, " ").replace(/&/g, "&").replace(/’|'|'/g, "'").replace(/\{\s*["'`]\s*["'`]\s*\}/g, " ").replace(/\s+/g, " ").trim();
|
|
50
|
+
if (t && /\p{L}{2}/u.test(t) && !/^[{}()[\];,.:<>=/\\|&*+-]+$/.test(t)) out.push({ text: t, line: at(index), at: index, ...(tag ? { tag } : {}) });
|
|
51
|
+
};
|
|
52
|
+
let body = source;
|
|
53
|
+
// Comments never count.
|
|
54
|
+
body = body.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:"'`\\])\/\/[^\n]*/g, (m, p) => p + " ".repeat(m.length - p.length)).replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " ")).replace(/\{\/\*[\s\S]*?\*\/\}/g, (m) => m.replace(/[^\n]/g, " "));
|
|
55
|
+
if (UI.test(file)) {
|
|
56
|
+
// Code blocks and style/script bodies aren't copy.
|
|
57
|
+
const masked = body.replace(/<(script|style|code|pre)\b[\s\S]*?<\/\1>/gi, (m) => m.replace(/[^\n]/g, " "));
|
|
58
|
+
if (/\.mdx$/i.test(file)) {
|
|
59
|
+
masked.split("\n").forEach((l, i) => {
|
|
60
|
+
if (!/^\s*(```|import |export |<|\|)/.test(l) && /\p{L}{2}/u.test(l)) out.push({ text: l.replace(/^#+\s*|[*_`]/g, "").trim(), line: i + 1, ...(/^\s*#/.test(l) ? { tag: "h" } : {}) });
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
for (const m of masked.matchAll(/>([^<>{}]*(?:\{["'`][^"'`{}]*["'`]\}[^<>{}]*)*)</g)) {
|
|
64
|
+
if (!/\p{L}{2}/u.test(m[1])) continue;
|
|
65
|
+
// The element this text sits in: a heading gets the heading-only checks.
|
|
66
|
+
const open = masked.lastIndexOf("<", m.index);
|
|
67
|
+
const tag = /^<\s*([A-Za-z][\w.-]*)/.exec(masked.slice(open, m.index + 1))?.[1]?.toLowerCase();
|
|
68
|
+
push(m[1], m.index + 1, tag && /^h[1-6]$/.test(tag) ? "h" : undefined);
|
|
69
|
+
}
|
|
70
|
+
for (const m of masked.matchAll(/\b(alt|title|aria-label|placeholder|label|content)=(["'])([^"'\n]{3,})\2/g)) push(m[3], m.index);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (UI.test(file) || CONTENT.test(file)) {
|
|
74
|
+
for (const m of body.matchAll(/(["'`])((?:(?!\1)[^\\\n]|\\.){12,})\1/g)) {
|
|
75
|
+
const s = m[2];
|
|
76
|
+
if (/\$\{|^\s*[./@#]|https?:|\.(png|jpe?g|svg|webp|mp4|css|tsx?|mjs)\b/i.test(s)) continue;
|
|
77
|
+
if (!proseLike(s)) continue;
|
|
78
|
+
const text = s.replace(/\\(["'`n])/g, (_, c) => (c === "n" ? " " : c)).replace(/\s+/g, " ").trim();
|
|
79
|
+
// The tag pass already read this one (an attribute value or a quoted child).
|
|
80
|
+
if (out.some((c) => c.line === at(m.index) && c.text === text)) continue;
|
|
81
|
+
push(text, m.index);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return out.filter((c) => c.line <= lines.length + 1).map(({ at: _, ...c }) => c);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// A string a person reads: three or more words, mostly words, not a class list, a key path or a regex.
|
|
88
|
+
export function proseLike(s) {
|
|
89
|
+
const words = s.trim().split(/\s+/);
|
|
90
|
+
if (words.length < 3) return false;
|
|
91
|
+
const codey = words.filter((w) => /[-:[\]/=_]{1}/.test(w) && /^[a-z0-9:[\]/#%.()!_=-]+$/.test(w)).length;
|
|
92
|
+
if (codey / words.length > 0.25) return false;
|
|
93
|
+
if (/[{};=<>]|=>|\b(function|return|const|import)\b/.test(s)) return false;
|
|
94
|
+
const real = words.filter((w) => /^[\p{L}'’,.!?:;"()-]+$/u.test(w) && /\p{L}{2}/u.test(w)).length;
|
|
95
|
+
return real / words.length >= 0.6;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const TITLE_SMALL = new Set(["a", "an", "and", "as", "at", "but", "by", "for", "in", "nor", "of", "on", "or", "the", "to", "vs", "via", "with", "from", "into"]);
|
|
99
|
+
export function titleCased(text) {
|
|
100
|
+
const words = text.replace(/[.!?:]+$/, "").split(/\s+/).filter(Boolean);
|
|
101
|
+
if (words.length < 4 || words.length > 12 || /[.!?]\s/.test(text)) return false;
|
|
102
|
+
const big = words.slice(1).filter((w) => /^\p{L}/u.test(w) && !TITLE_SMALL.has(w.toLowerCase()));
|
|
103
|
+
if (big.length < 3) return false;
|
|
104
|
+
return big.every((w) => /^\p{Lu}\p{Ll}/u.test(w)) && !big.every((w) => /^\p{Lu}+$/u.test(w));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Lint a list of copy entries ({ text, line, file }). Returns findings with weight; habits only past their limit.
|
|
108
|
+
export function lintCopy(entries, { name = "" } = {}) {
|
|
109
|
+
const own = name ? new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "gi") : null;
|
|
110
|
+
const hits = [];
|
|
111
|
+
for (const e of entries) {
|
|
112
|
+
const text = own ? e.text.replace(own, " ") : e.text;
|
|
113
|
+
const short = text.length <= 60;
|
|
114
|
+
for (const t of TELLS) {
|
|
115
|
+
if (t.id === "copy-title-case") {
|
|
116
|
+
if (e.tag === "h" && short && titleCased(text)) hits.push({ ...pick(t), file: e.file, line: e.line, text: e.text });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (t.short && !short) continue;
|
|
120
|
+
// A habit counts every use; the others one finding per line.
|
|
121
|
+
const ms = t.weight === "habit" ? [...text.matchAll(new RegExp(t.re.source, t.re.flags.includes("g") ? t.re.flags : t.re.flags + "g"))] : [t.re.exec(text)].filter(Boolean);
|
|
122
|
+
for (const m of ms) {
|
|
123
|
+
// A capitalised word mid-sentence is a name ("Simple Icons", "Robust Labs"), not the adjective.
|
|
124
|
+
if (t.id === "copy-intensifier" && m.index > 0 && /^\p{Lu}/u.test(m[0]) && !/[.!?:]\s*$/.test(text.slice(0, m.index))) continue;
|
|
125
|
+
hits.push({ ...pick(t), file: e.file, line: e.line, text: e.text, match: m[0].trim() });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Habits: the first `limit` uses are fine; the rest are findings (each listed with the earlier ones as context).
|
|
130
|
+
const out = [];
|
|
131
|
+
const byId = new Map();
|
|
132
|
+
for (const h of hits) (byId.get(h.id) ?? byId.set(h.id, []).get(h.id)).push(h);
|
|
133
|
+
for (const [, list] of byId) {
|
|
134
|
+
const t = TELLS.find((x) => x.id === list[0].id);
|
|
135
|
+
if (t.weight === "habit") { if (list.length > t.limit) out.push(...list.map((h, i) => ({ ...h, over: i >= t.limit }))); }
|
|
136
|
+
else out.push(...list);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
const pick = ({ id, weight, why, fix, limit }) => ({ id, weight, why, fix, ...(limit ? { limit } : {}) });
|
|
141
|
+
|
|
142
|
+
// The project's name, so its own words ("Seamless Inc.") aren't flagged: package.json name, else the folder.
|
|
143
|
+
export function projectName(dir) {
|
|
144
|
+
try {
|
|
145
|
+
const n = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).name;
|
|
146
|
+
if (n && !/^@?[\w-]+\/(app|web|site)$/.test(n)) return String(n).replace(/^@[\w-]+\//, "").replace(/[-_]+/g, " ");
|
|
147
|
+
} catch {}
|
|
148
|
+
return path.basename(dir).replace(/[-_]+/g, " ");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const isCopyFile = (f) => (UI.test(f) || CONTENT.test(f)) && !SKIP.test(`/${f.replace(/\\/g, "/")}`) && !NOT_COPY.test(f) && !/(^|\/)(package|tsconfig|components)\.json$|lock|config\.|\.config\./i.test(f);
|
|
152
|
+
|
|
153
|
+
// Lint files (relative to dir). Content files only count their prose strings.
|
|
154
|
+
export function lintFiles(dir, files, opts = {}) {
|
|
155
|
+
const entries = [];
|
|
156
|
+
for (const f of files.filter(isCopyFile)) {
|
|
157
|
+
let src;
|
|
158
|
+
try { src = fs.readFileSync(path.join(dir, f), "utf8"); } catch { continue; }
|
|
159
|
+
if (src.length > 400_000) continue;
|
|
160
|
+
for (const c of extractCopy(src, f)) entries.push({ ...c, file: f.replace(/\\/g, "/") });
|
|
161
|
+
}
|
|
162
|
+
return { entries: entries.length, findings: lintCopy(entries, { name: opts.name ?? projectName(dir) }) };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The report palate prints.
|
|
166
|
+
export function copyReport({ entries, findings }, { files = 0 } = {}) {
|
|
167
|
+
const real = findings.filter((f) => f.weight !== "habit" || f.over);
|
|
168
|
+
if (!real.length) return `palate copy: ${entries} line${entries === 1 ? "" : "s"} of copy in ${files} file${files === 1 ? "" : "s"}, nothing reads as generated.`;
|
|
169
|
+
const stops = real.filter((f) => f.weight === "stop").length;
|
|
170
|
+
const habits = new Set(real.filter((f) => f.weight === "habit").map((f) => f.id)).size;
|
|
171
|
+
const hints = real.filter((f) => f.weight === "hint").length;
|
|
172
|
+
const out = [`palate copy: ${[stops && `${stops} to fix`, habits && `${habits} repeated habit${habits > 1 ? "s" : ""}`, hints && `${hints} hint${hints > 1 ? "s" : ""}`].filter(Boolean).join(", ")} in ${entries} lines of copy`];
|
|
173
|
+
if (hints >= 4) out.push("Four or more soft signs together: this copy reads as generated. Rewrite it in the project's own voice, not just the flagged words.");
|
|
174
|
+
out.push("");
|
|
175
|
+
const byId = new Map();
|
|
176
|
+
for (const f of findings) (byId.get(f.id) ?? byId.set(f.id, []).get(f.id)).push(f);
|
|
177
|
+
for (const w of ["stop", "habit", "hint"])
|
|
178
|
+
for (const [id, list] of byId) {
|
|
179
|
+
if (list[0].weight !== w) continue;
|
|
180
|
+
const head = w === "habit" ? `${list.length} uses, ${list[0].limit} is the limit` : `${list.length}`;
|
|
181
|
+
out.push(`## ${id} (${w}, ${head}): ${list[0].why} ${list[0].fix}`);
|
|
182
|
+
for (const f of list.slice(0, 6)) out.push(`- ${f.file}:${f.line} "${f.text.slice(0, 90)}"${f.match && f.match.length < f.text.length ? ` (${f.match})` : ""}`);
|
|
183
|
+
if (list.length > 6) out.push(`- … ${list.length - 6} more`);
|
|
184
|
+
out.push("");
|
|
185
|
+
}
|
|
186
|
+
out.push("Your own brand words are fine: waive one with a reason (palate waive <id> --file <file> --reason \"…\").");
|
|
187
|
+
return out.join("\n").trimEnd();
|
|
188
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Starts the project's own dev server on a free port, whatever the stack: palate needs a live page to
|
|
2
|
+
// screenshot. Known frameworks are started directly from node_modules with an explicit port; anything else
|
|
3
|
+
// runs the project's "dev" (or "start") script with PORT set, and palate reads the URL it prints.
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import net from "node:net";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
export const freePort = () =>
|
|
10
|
+
new Promise((resolve, reject) => {
|
|
11
|
+
const s = net.createServer();
|
|
12
|
+
s.on("error", reject);
|
|
13
|
+
s.listen(0, "127.0.0.1", () => {
|
|
14
|
+
const p = s.address().port;
|
|
15
|
+
s.close(() => resolve(p));
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export async function waitFor(url, ms) {
|
|
20
|
+
const until = Date.now() + ms;
|
|
21
|
+
while (Date.now() < until) {
|
|
22
|
+
try {
|
|
23
|
+
if ((await fetch(url)).status < 500) return true;
|
|
24
|
+
} catch {}
|
|
25
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function kill(child) {
|
|
31
|
+
if (!child || child.exitCode !== null) return;
|
|
32
|
+
if (process.platform === "win32") spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore" });
|
|
33
|
+
else child.kill("SIGTERM");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const bin = (dir, ...p) => path.join(dir, "node_modules", ...p);
|
|
37
|
+
|
|
38
|
+
// The first framework whose package is installed wins. Each entry: the script to run with node, and its args.
|
|
39
|
+
const FRAMEWORKS = [
|
|
40
|
+
{ name: "Next.js", pkg: "next", script: ["next", "dist", "bin", "next"], args: (port) => ["dev", "-p", String(port), "-H", "127.0.0.1"] },
|
|
41
|
+
{ name: "Nuxt", pkg: "nuxt", script: ["nuxi", "bin", "nuxi.mjs"], args: (port) => ["dev", "--port", String(port), "--host", "127.0.0.1"] },
|
|
42
|
+
{ name: "Astro", pkg: "astro", script: ["astro", "astro.js"], args: (port) => ["dev", "--port", String(port), "--host", "127.0.0.1"] },
|
|
43
|
+
{ name: "React Router", pkg: "@react-router/dev", script: ["@react-router", "dev", "bin.js"], args: (port) => ["dev", "--port", String(port), "--host", "127.0.0.1"] },
|
|
44
|
+
{ name: "Remix", pkg: "@remix-run/dev", script: ["@remix-run", "dev", "dist", "cli.js"], args: (port) => ["vite:dev", "--port", String(port), "--host", "127.0.0.1"] },
|
|
45
|
+
{ name: "Angular", pkg: "@angular/cli", script: ["@angular", "cli", "bin", "ng.js"], args: (port) => ["serve", "--port", String(port), "--host", "127.0.0.1"] },
|
|
46
|
+
{ name: "Vite", pkg: "vite", script: ["vite", "bin", "vite.js"], args: (port) => ["--port", String(port), "--strictPort", "--host", "127.0.0.1"] },
|
|
47
|
+
{ name: "Create React App", pkg: "react-scripts", script: ["react-scripts", "bin", "react-scripts.js"], args: () => ["start"], env: (port) => ({ PORT: String(port), BROWSER: "none", HOST: "127.0.0.1" }) },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export function detectStack(dir) {
|
|
51
|
+
let pkg = {};
|
|
52
|
+
try {
|
|
53
|
+
pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
|
|
54
|
+
} catch {}
|
|
55
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
56
|
+
const fw = FRAMEWORKS.find((f) => f.pkg in deps && fs.existsSync(bin(dir, ...f.script)));
|
|
57
|
+
return { framework: fw, scripts: pkg.scripts ?? {} };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A dev server that was already running (the user's, or another agent's) is remembered in
|
|
61
|
+
// .palate/devserver.json, so the next command uses it at once instead of spawning a server to find it.
|
|
62
|
+
const cacheFile = (dir) => path.join(dir, ".palate", "devserver.json");
|
|
63
|
+
function rememberRunning(dir, base, name) {
|
|
64
|
+
try {
|
|
65
|
+
fs.mkdirSync(path.join(dir, ".palate"), { recursive: true });
|
|
66
|
+
fs.writeFileSync(cacheFile(dir), JSON.stringify({ base, name, at: new Date().toISOString() }));
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
async function cachedRunning(dir) {
|
|
70
|
+
try {
|
|
71
|
+
const c = JSON.parse(fs.readFileSync(cacheFile(dir), "utf8"));
|
|
72
|
+
if (Date.now() - Date.parse(c.at) > 12 * 3600_000) return null;
|
|
73
|
+
const r = await fetch(c.base, { signal: AbortSignal.timeout(4000) });
|
|
74
|
+
return r.status < 500 ? c : null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Returns { base, stop, name }. With `url`, uses an already-running server instead.
|
|
81
|
+
export async function startDevServer(dir, { url, timeout = 120_000 } = {}) {
|
|
82
|
+
if (url) return { base: url.replace(/\/$/, ""), stop: () => {}, name: "your running server" };
|
|
83
|
+
const cached = await cachedRunning(dir);
|
|
84
|
+
if (cached) return { base: cached.base, stop: () => {}, name: `${cached.name} (already running)` };
|
|
85
|
+
const { framework, scripts } = detectStack(dir);
|
|
86
|
+
const port = await freePort();
|
|
87
|
+
const log = [];
|
|
88
|
+
let child;
|
|
89
|
+
let base = `http://127.0.0.1:${port}`;
|
|
90
|
+
if (framework) {
|
|
91
|
+
child = spawn(process.execPath, [bin(dir, ...framework.script), ...framework.args(port)], { cwd: dir, windowsHide: true, env: { ...process.env, ...(framework.env?.(port) ?? {}) } });
|
|
92
|
+
} else {
|
|
93
|
+
const script = scripts.dev ? "dev" : scripts.start ? "start" : null;
|
|
94
|
+
if (!script) throw new Error("palate can't tell how to start this project's dev server. Start it yourself and pass --url http://localhost:PORT");
|
|
95
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
96
|
+
child = spawn(npm, ["run", script], { cwd: dir, windowsHide: true, shell: process.platform === "win32", env: { ...process.env, PORT: String(port), BROWSER: "none" } });
|
|
97
|
+
base = null;
|
|
98
|
+
}
|
|
99
|
+
child.stdout.on("data", (d) => log.push(String(d)));
|
|
100
|
+
child.stderr.on("data", (d) => log.push(String(d)));
|
|
101
|
+
let exited = false;
|
|
102
|
+
child.on("exit", () => (exited = true));
|
|
103
|
+
// Next.js 16 allows one dev server per project; when one is running (another agent's, or the user's), use it.
|
|
104
|
+
const running = () => {
|
|
105
|
+
const t = log.join("");
|
|
106
|
+
const i = t.search(/already running/i);
|
|
107
|
+
return i < 0 ? null : (t.slice(i).match(/Local:\s*(https?:\/\/[^\s]+)/)?.[1].replace(/\/$/, "") ?? null);
|
|
108
|
+
};
|
|
109
|
+
const until = Date.now() + timeout;
|
|
110
|
+
let up = false;
|
|
111
|
+
while (!up && Date.now() < until) {
|
|
112
|
+
const other = running();
|
|
113
|
+
if (other) {
|
|
114
|
+
kill(child);
|
|
115
|
+
rememberRunning(dir, other, framework?.name ?? "dev server");
|
|
116
|
+
return { base: other, stop: () => {}, name: `${framework?.name ?? "dev server"} (already running)` };
|
|
117
|
+
}
|
|
118
|
+
// Unknown stacks print where they listen; known ones listen where we told them.
|
|
119
|
+
const m = !base && log.join("").match(/https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0):(\d+)/);
|
|
120
|
+
if (m) base = `http://127.0.0.1:${m[1]}`;
|
|
121
|
+
if (base) up = await waitFor(base, 1000);
|
|
122
|
+
else await new Promise((r) => setTimeout(r, 250));
|
|
123
|
+
// Next answers briefly before it notices the other server and exits, so let it settle once.
|
|
124
|
+
if (up) {
|
|
125
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
126
|
+
up = !exited && !running();
|
|
127
|
+
}
|
|
128
|
+
if (exited && !up && !running()) break;
|
|
129
|
+
}
|
|
130
|
+
if (!up) {
|
|
131
|
+
kill(child);
|
|
132
|
+
throw new Error(`The dev server did not start (${framework?.name ?? "npm run dev"}):\n${log.join("").slice(-1500)}\nStart it yourself and pass --url http://localhost:PORT`);
|
|
133
|
+
}
|
|
134
|
+
return { base, stop: () => kill(child), name: framework?.name ?? "npm run dev" };
|
|
135
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// What kind of typeface a family is: serif, sans, slab, mono, display or script. palate's look check uses it to
|
|
2
|
+
// tell when pages share a visual language (every page in a serif headline over a grotesque body reads as one
|
|
3
|
+
// house style, whatever the exact faces). Known Google Fonts / OFL families first, then the name's own words.
|
|
4
|
+
|
|
5
|
+
const LISTS = {
|
|
6
|
+
serif: ["fraunces", "young serif", "gloock", "instrument serif", "playfair display", "playfair", "eb garamond", "cormorant", "cormorant garamond", "libre baskerville", "libre caslon text", "libre caslon display", "lora", "merriweather", "newsreader", "spectral", "crimson pro", "crimson text", "source serif 4", "source serif pro", "pt serif", "noto serif", "dm serif display", "dm serif text", "bodoni moda", "literata", "petrona", "gelasio", "domine", "vollkorn", "alegreya", "cardo", "old standard tt", "rufina", "marcellus", "cinzel", "prata", "roboto serif", "ibm plex serif", "besley", "brygada 1918", "castoro", "fanwood text", "gilda display", "italiana", "junicode", "linden hill", "ovo", "piazzolla", "radley", "sorts mill goudy", "tenor sans", "trirong", "unna", "yrsa", "rasa", "georgia", "times new roman", "tiempos", "recoleta", "canela", "editorial new", "ui-serif"],
|
|
7
|
+
slab: ["roboto slab", "zilla slab", "arvo", "bitter", "josefin slab", "rokkitt", "aleo", "crete round", "kameron", "noticia text", "podkova", "slabo 27px", "patua one", "ultra", "alfa slab one", "ibm plex mono slab"],
|
|
8
|
+
mono: ["jetbrains mono", "ibm plex mono", "space mono", "dm mono", "fira code", "fira mono", "source code pro", "roboto mono", "inconsolata", "martian mono", "commit mono", "geist mono", "ubuntu mono", "courier prime", "courier new", "overpass mono", "red hat mono", "azeret mono", "major mono display", "xanh mono", "ui-monospace", "menlo", "consolas"],
|
|
9
|
+
script: ["caveat", "dancing script", "pacifico", "great vibes", "satisfy", "homemade apple", "kalam", "shadows into light", "indie flower", "permanent marker", "sacramento", "allura", "parisienne", "nothing you could do", "reenie beanie", "la belle aurore", "covered by your grace", "nanum pen script", "gochi hand", "patrick hand", "architects daughter"],
|
|
10
|
+
display: ["bricolage grotesque", "syne", "unbounded", "bebas neue", "anton", "archivo black", "big shoulders display", "climate crisis", "dela gothic one", "abril fatface", "rubik mono one", "monoton", "bungee", "righteous", "fredoka", "lobster", "chivo mono", "darker grotesque", "familjen grotesk", "hanken grotesk display", "migra", "clash display", "cabinet grotesk", "gambetta", "bowlby one", "titan one", "staatliches", "oswald", "league gothic", "six caps", "londrina solid", "shrikhand", "yeseva one", "bagel fat one", "rammetto one", "averia sans libre", "averia serif libre"],
|
|
11
|
+
sans: ["inter", "geist", "roboto", "open sans", "lato", "montserrat", "poppins", "nunito", "nunito sans", "work sans", "public sans", "ibm plex sans", "hanken grotesk", "instrument sans", "dm sans", "manrope", "plus jakarta sans", "outfit", "sora", "space grotesk", "karla", "rubik", "figtree", "onest", "archivo", "be vietnam pro", "albert sans", "schibsted grotesk", "red hat display", "red hat text", "source sans 3", "source sans pro", "pt sans", "noto sans", "atkinson hyperlegible", "lexend", "mulish", "urbanist", "epilogue", "chivo", "barlow", "fira sans", "heebo", "jost", "kumbh sans", "overpass", "raleway", "ubuntu", "work sans", "satoshi", "general sans", "switzer", "helvetica", "helvetica neue", "arial", "system-ui", "ui-sans-serif", "segoe ui", "sf pro", "-apple-system"],
|
|
12
|
+
};
|
|
13
|
+
const INDEX = new Map();
|
|
14
|
+
for (const [genre, names] of Object.entries(LISTS)) for (const n of names) if (!INDEX.has(n)) INDEX.set(n, genre);
|
|
15
|
+
|
|
16
|
+
const clean = (f) => String(f ?? "").split(",")[0].replace(/["']/g, "").trim().toLowerCase();
|
|
17
|
+
|
|
18
|
+
// genre of one family name (the first of a font-family list), or "unknown".
|
|
19
|
+
export function fontGenre(family) {
|
|
20
|
+
const f = clean(family);
|
|
21
|
+
if (!f) return "unknown";
|
|
22
|
+
if (INDEX.has(f)) return INDEX.get(f);
|
|
23
|
+
if (/\bmono|code\b/.test(f)) return "mono";
|
|
24
|
+
if (/\bslab\b/.test(f)) return "slab";
|
|
25
|
+
if (/script|hand|brush|marker/.test(f)) return "script";
|
|
26
|
+
if (/serif/.test(f) && !/sans/.test(f)) return "serif";
|
|
27
|
+
if (/display|poster|black|fatface|grotesque display/.test(f)) return "display";
|
|
28
|
+
if (/sans|grotesk|grotesque|gothic/.test(f)) return "sans";
|
|
29
|
+
return "unknown";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The family's genre from a CSS font-family stack: the first known family wins, else the generic family.
|
|
33
|
+
export function stackGenre(stack) {
|
|
34
|
+
const parts = String(stack ?? "").split(",").map((x) => x.replace(/["']/g, "").trim()).filter(Boolean);
|
|
35
|
+
for (const p of parts) {
|
|
36
|
+
const g = fontGenre(p);
|
|
37
|
+
if (g !== "unknown") return g;
|
|
38
|
+
}
|
|
39
|
+
const generic = parts.find((p) => /^(serif|sans-serif|monospace|cursive|fantasy)$/i.test(p));
|
|
40
|
+
return generic ? { serif: "serif", "sans-serif": "sans", monospace: "mono", cursive: "script", fantasy: "display" }[generic.toLowerCase()] : "unknown";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For the in-page scripts: the lists as plain data (they can't import modules).
|
|
44
|
+
export const FONT_GENRES = LISTS;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// The guard for every agent that can hold its own stop: where each one keeps its hooks, what it sends the hook,
|
|
2
|
+
// and how the hook says "not done yet". One guard (guard.mjs decide), one adapter per agent.
|
|
3
|
+
//
|
|
4
|
+
// agent file (project · global) event not done yet
|
|
5
|
+
// claude .claude/settings.json · ~/.claude/settings.json Stop {"decision":"block","reason"}
|
|
6
|
+
// codex .codex/hooks.json · ~/.codex/hooks.json Stop {"decision":"block","reason"}
|
|
7
|
+
// cursor .cursor/hooks.json · ~/.cursor/hooks.json stop {"followup_message"}
|
|
8
|
+
// gemini .gemini/settings.json · ~/.gemini/settings.json AfterAgent {"decision":"deny","reason"}
|
|
9
|
+
// copilot .github/hooks/palate-guard.json · ~/.copilot/hooks/… Stop {"decision":"block","reason"} (+ VS Code's hookSpecificOutput)
|
|
10
|
+
// git .git/hooks/pre-commit (any agent, or a person) commit exit 1 with the report
|
|
11
|
+
//
|
|
12
|
+
// Copilot (CLI and VS Code) also reads the project's .claude/settings.json, so with the Claude hook in place it is
|
|
13
|
+
// covered already and palate doesn't add a second one. Windsurf's after-response hook runs in the background and
|
|
14
|
+
// Cline's task-complete hook can't continue a task, so neither can hold a stop: those agents get the guard through
|
|
15
|
+
// palate's MCP instructions (palate_guard before saying done) and, if wanted, the git hook.
|
|
16
|
+
// Each agent's own loop cap is set where it has one (Cursor loop_limit 3); palate's own cap (3 per session) holds
|
|
17
|
+
// for all of them.
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import os from "node:os";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
|
|
23
|
+
export const AGENTS = ["claude", "codex", "cursor", "gemini", "copilot", "git"];
|
|
24
|
+
export const NO_STOP_HOOK = { windsurf: "Windsurf's after-response hook runs in the background, so it can't hold a stop", cline: "Cline's task-complete hook can't send a task back" };
|
|
25
|
+
|
|
26
|
+
const readJson = (f, d) => {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(fs.readFileSync(f, "utf8"));
|
|
29
|
+
} catch {
|
|
30
|
+
return d;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const home = (h) => h ?? os.homedir();
|
|
34
|
+
|
|
35
|
+
// The command a hook runs: this checkout's bin when palate runs from its repo, else npx.
|
|
36
|
+
export function hookCommand(agent = "claude") {
|
|
37
|
+
const bin = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "bin", "palate.mjs");
|
|
38
|
+
const dev = /[\\/]packages[\\/]palate[\\/]bin[\\/]palate\.mjs$/.test(bin) && fs.existsSync(bin);
|
|
39
|
+
const base = dev ? `node "${bin.replace(/\\/g, "/")}"` : "npx @palate/cli";
|
|
40
|
+
return `${base} guard --hook${agent === "claude" ? "" : ` --agent ${agent}`}`;
|
|
41
|
+
}
|
|
42
|
+
const ours = (cmd) => typeof cmd === "string" && /palate(\.mjs"?)?\s+guard\s+--hook/.test(cmd);
|
|
43
|
+
|
|
44
|
+
// ---------- what each agent sends ----------
|
|
45
|
+
// Normalised to { session_id, cwd, transcript_path, skip? }.
|
|
46
|
+
export function parseInput(agent, raw = {}) {
|
|
47
|
+
switch (agent) {
|
|
48
|
+
case "cursor":
|
|
49
|
+
return {
|
|
50
|
+
session_id: raw.conversation_id ?? raw.session_id,
|
|
51
|
+
cwd: raw.workspace_roots?.[0] ?? raw.cwd,
|
|
52
|
+
transcript_path: raw.transcript_path ?? undefined,
|
|
53
|
+
// Only a finished turn is checked; an aborted or failed one is the person's to handle.
|
|
54
|
+
...(raw.status && raw.status !== "completed" ? { skip: `turn ${raw.status}` } : {}),
|
|
55
|
+
};
|
|
56
|
+
case "copilot":
|
|
57
|
+
return { session_id: raw.session_id ?? raw.sessionId, cwd: raw.cwd, transcript_path: raw.transcript_path ?? raw.transcriptPath };
|
|
58
|
+
default:
|
|
59
|
+
return { session_id: raw.session_id, cwd: raw.cwd, transcript_path: raw.transcript_path ?? undefined };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------- how each agent is told ----------
|
|
64
|
+
// The stdout text for a decision ({ decision: "block"|"allow", reason?, message? }); "" when there's nothing to say.
|
|
65
|
+
export function formatOutput(agent, d) {
|
|
66
|
+
const block = d.decision === "block";
|
|
67
|
+
switch (agent) {
|
|
68
|
+
case "cursor":
|
|
69
|
+
return JSON.stringify(block ? { followup_message: d.reason } : {});
|
|
70
|
+
case "gemini":
|
|
71
|
+
return block ? JSON.stringify({ decision: "deny", reason: d.reason }) : d.message ? JSON.stringify({ systemMessage: d.message }) : "";
|
|
72
|
+
case "copilot":
|
|
73
|
+
// The Copilot CLI reads decision/reason; VS Code's local harness reads hookSpecificOutput.
|
|
74
|
+
return block ? JSON.stringify({ decision: "block", reason: d.reason, hookSpecificOutput: { hookEventName: "Stop", decision: "block", reason: d.reason } }) : "";
|
|
75
|
+
case "codex":
|
|
76
|
+
case "claude":
|
|
77
|
+
default:
|
|
78
|
+
return block ? JSON.stringify({ decision: "block", reason: d.reason }) : d.message ? JSON.stringify({ systemMessage: d.message }) : "";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------- install ----------
|
|
83
|
+
const FILES = {
|
|
84
|
+
claude: (dir, g, h) => (g ? path.join(home(h), ".claude", "settings.json") : path.join(dir, ".claude", "settings.json")),
|
|
85
|
+
codex: (dir, g, h) => (g ? path.join(home(h), ".codex", "hooks.json") : path.join(dir, ".codex", "hooks.json")),
|
|
86
|
+
cursor: (dir, g, h) => (g ? path.join(home(h), ".cursor", "hooks.json") : path.join(dir, ".cursor", "hooks.json")),
|
|
87
|
+
gemini: (dir, g, h) => (g ? path.join(home(h), ".gemini", "settings.json") : path.join(dir, ".gemini", "settings.json")),
|
|
88
|
+
copilot: (dir, g, h) => (g ? path.join(home(h), ".copilot", "hooks", "palate-guard.json") : path.join(dir, ".github", "hooks", "palate-guard.json")),
|
|
89
|
+
git: (dir) => path.join(dir, ".git", "hooks", "pre-commit"),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Claude-style nesting (event → groups → hooks), used by Claude Code, Codex and Gemini.
|
|
93
|
+
function nested(settings, event, entry, remove) {
|
|
94
|
+
const hooks = settings.hooks ?? {};
|
|
95
|
+
const groups = (hooks[event] ?? []).map((g) => ({ ...g, hooks: (g.hooks ?? []).filter((h) => !ours(h.command)) })).filter((g) => g.hooks.length);
|
|
96
|
+
if (!remove) groups.push({ hooks: [entry] });
|
|
97
|
+
const next = { ...settings, hooks: { ...hooks, [event]: groups } };
|
|
98
|
+
if (!groups.length) delete next.hooks[event];
|
|
99
|
+
if (!Object.keys(next.hooks).length) delete next.hooks;
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const GIT_MARK = "# palate guard (palate guard --uninstall --agent git removes it)";
|
|
104
|
+
|
|
105
|
+
function installOne(agent, dir, { global = false, home: h, remove = false } = {}) {
|
|
106
|
+
const file = FILES[agent](dir, global, h);
|
|
107
|
+
const cmd = hookCommand(agent);
|
|
108
|
+
if (agent === "git") {
|
|
109
|
+
if (global) return { agent, file, note: "The git hook is per project; run it without --global." };
|
|
110
|
+
if (!fs.existsSync(path.join(dir, ".git"))) return { agent, file, note: "Not a git repository here." };
|
|
111
|
+
const had = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
112
|
+
const stripped = had.split(GIT_MARK)[0].trimEnd();
|
|
113
|
+
if (remove) {
|
|
114
|
+
if (stripped && stripped !== "#!/bin/sh") fs.writeFileSync(file, stripped + "\n");
|
|
115
|
+
else if (had.includes(GIT_MARK)) fs.rmSync(file);
|
|
116
|
+
return { agent, file, done: true };
|
|
117
|
+
}
|
|
118
|
+
const body = `${stripped || "#!/bin/sh"}\n${GIT_MARK}\n${cmd.replace(" --hook --agent git", " --commit")} || exit 1\n`;
|
|
119
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
120
|
+
fs.writeFileSync(file, body, { mode: 0o755 });
|
|
121
|
+
return { agent, file, done: true };
|
|
122
|
+
}
|
|
123
|
+
if (fs.existsSync(file) && fs.readFileSync(file, "utf8").trim() && readJson(file, null) === null) return { agent, file, note: `${file} isn't valid JSON, so palate left it alone.` };
|
|
124
|
+
const settings = readJson(file, {});
|
|
125
|
+
let next;
|
|
126
|
+
if (agent === "cursor") {
|
|
127
|
+
const stop = (settings.hooks?.stop ?? []).filter((h) => !ours(h.command));
|
|
128
|
+
if (!remove) stop.push({ command: cmd, timeout: 60, loop_limit: 3 });
|
|
129
|
+
next = { version: settings.version ?? 1, ...settings, hooks: { ...(settings.hooks ?? {}), stop } };
|
|
130
|
+
if (!stop.length) delete next.hooks.stop;
|
|
131
|
+
} else if (agent === "copilot") {
|
|
132
|
+
if (remove) {
|
|
133
|
+
fs.rmSync(file, { force: true });
|
|
134
|
+
return { agent, file, done: true };
|
|
135
|
+
}
|
|
136
|
+
if (!global && hasOurs("claude", dir)) return { agent, file, note: "Copilot reads this project's .claude/settings.json, where the guard already is." };
|
|
137
|
+
next = { version: 1, hooks: { Stop: [{ type: "command", command: cmd, bash: cmd, powershell: cmd, timeout: 60, timeoutSec: 60 }] } };
|
|
138
|
+
} else if (agent === "codex") {
|
|
139
|
+
// A layer uses one format: with [hooks] already in config.toml, hooks.json would draw a warning.
|
|
140
|
+
const toml = path.join(path.dirname(file), "config.toml");
|
|
141
|
+
if (!remove && fs.existsSync(toml) && /^\s*\[\[?hooks[.\]]/m.test(fs.readFileSync(toml, "utf8"))) return { agent, file, note: `${toml} already defines hooks; add palate there: [[hooks.Stop]] with [[hooks.Stop.hooks]] type = "command", command = '${cmd}', timeout = 60.` };
|
|
142
|
+
next = nested(settings, "Stop", { type: "command", command: cmd, timeout: 60 }, remove);
|
|
143
|
+
} else if (agent === "gemini") {
|
|
144
|
+
next = nested(settings, "AfterAgent", { type: "command", command: cmd, name: "palate-guard", description: "palate: UI work isn't done until palate has looked at it", timeout: 60_000 }, remove);
|
|
145
|
+
} else {
|
|
146
|
+
next = nested(settings, "Stop", { type: "command", command: cmd, timeout: 60 }, remove);
|
|
147
|
+
}
|
|
148
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
149
|
+
fs.writeFileSync(file, JSON.stringify(next, null, 2) + "\n");
|
|
150
|
+
return { agent, file, done: true };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function hasOurs(agent, dir, { global = false, home: h } = {}) {
|
|
154
|
+
const file = FILES[agent](dir, global, h);
|
|
155
|
+
if (!fs.existsSync(file)) return false;
|
|
156
|
+
if (agent === "git") return fs.readFileSync(file, "utf8").includes(GIT_MARK);
|
|
157
|
+
const s = readJson(file, {});
|
|
158
|
+
const all = JSON.stringify(s.hooks ?? {});
|
|
159
|
+
return ours(all) || /palate(\.mjs\\?"?)?\s+guard\s+--hook/.test(all);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// The agents this project (or machine) uses: their folder in the project, or in the home folder.
|
|
163
|
+
export function detectAgents(dir, { home: h } = {}) {
|
|
164
|
+
const found = new Set(["claude"]);
|
|
165
|
+
const at = (p) => fs.existsSync(p);
|
|
166
|
+
if (at(path.join(dir, ".codex")) || at(path.join(home(h), ".codex"))) found.add("codex");
|
|
167
|
+
if (at(path.join(dir, ".cursor")) || at(path.join(home(h), ".cursor"))) found.add("cursor");
|
|
168
|
+
if (at(path.join(dir, ".gemini")) || at(path.join(home(h), ".gemini"))) found.add("gemini");
|
|
169
|
+
return [...found];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const AFTER = {
|
|
173
|
+
codex: "Codex runs a new project hook only after you trust it: open /hooks in Codex once (it also needs the project's .codex folder trusted).",
|
|
174
|
+
gemini: "Gemini CLI asks before it runs a new hook the first time.",
|
|
175
|
+
cursor: "Cursor picks it up on its next agent turn (Settings → Hooks shows it).",
|
|
176
|
+
copilot: "Copilot CLI and VS Code load .github/hooks on their next session.",
|
|
177
|
+
git: "A commit with open items stops with the report; git commit --no-verify skips it once.",
|
|
178
|
+
};
|
|
179
|
+
const LABEL = { claude: "Claude Code", codex: "Codex", cursor: "Cursor", gemini: "Gemini CLI", copilot: "GitHub Copilot", git: "git pre-commit" };
|
|
180
|
+
|
|
181
|
+
// install / uninstall for a list of agents (or every one this project uses). Returns the lines to print.
|
|
182
|
+
export function install(dir, { agents, global = false, home: h, remove = false } = {}) {
|
|
183
|
+
const asked = (agents?.length ? agents : remove ? AGENTS : detectAgents(dir, { home: h })).map((a) => String(a).toLowerCase().trim());
|
|
184
|
+
const lines = [];
|
|
185
|
+
for (const a of asked) {
|
|
186
|
+
if (NO_STOP_HOOK[a]) {
|
|
187
|
+
lines.push(`- ${a}: ${NO_STOP_HOOK[a]}. It still gets the guard through palate's MCP tools (palate_guard before saying done); add --agent git for a commit check.`);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!FILES[a]) {
|
|
191
|
+
lines.push(`- ${a}: not an agent palate knows (${AGENTS.join(", ")}).`);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (remove && !hasOurs(a, dir, { global, home: h })) continue;
|
|
195
|
+
const r = installOne(a, dir, { global, home: h, remove });
|
|
196
|
+
if (r.note) lines.push(`- ${LABEL[a]}: ${r.note}`);
|
|
197
|
+
else lines.push(`- ${LABEL[a]}: ${remove ? "removed from" : "on, in"} ${r.file}${!remove && AFTER[a] ? `. ${AFTER[a]}` : ""}`);
|
|
198
|
+
}
|
|
199
|
+
if (remove) return lines.length ? [`palate guard is off for:`, ...lines].join("\n") : "palate guard wasn't on here.";
|
|
200
|
+
const head = `palate guard is on: when the agent stops, palate checks the UI it changed (looked at, copy, code tells, credits, gates) and sends it back with what is open, three rounds a session at most.${asked.includes("claude") && !asked.includes("copilot") ? " Copilot reads the Claude hook too." : ""}`;
|
|
201
|
+
return [head, ...lines, `Off: palate guard --uninstall${global ? " --global" : ""} (--agent to pick).`].join("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Where the guard is on for this project: [{ agent, file }].
|
|
205
|
+
export function installedAgents(dir, { home: h } = {}) {
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const a of AGENTS)
|
|
208
|
+
for (const g of a === "git" ? [false] : [false, true]) if (hasOurs(a, dir, { global: g, home: h })) out.push({ agent: a, file: FILES[a](dir, g, h) });
|
|
209
|
+
// Claude Code's local settings file counts too.
|
|
210
|
+
if (!out.some((o) => o.agent === "claude") && JSON.stringify(readJson(path.join(dir, ".claude", "settings.local.json"), {}).hooks ?? {}).match(/palate[^"]*guard\s+--hook/)) out.push({ agent: "claude", file: path.join(dir, ".claude", "settings.local.json") });
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
export const agentLabel = (a) => LABEL[a] ?? a;
|