@palate/cli 0.0.0-stage → 0.0.2
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 +153 -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 +37 -4
- package/scripts/pack-assets.mjs +58 -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
|
@@ -0,0 +1,1246 @@
|
|
|
1
|
+
// palate's MCP server: the design loop as tools any agent can call (Claude Code, Codex, Cursor, …), plus
|
|
2
|
+
// prompts that show up as slash commands. Start it with `palate mcp`; it speaks MCP over stdio.
|
|
3
|
+
//
|
|
4
|
+
// The default: palate_plan (triage) → palate_build with the job (one brief for the real route) → (one builder)
|
|
5
|
+
// → the finishing pass (palate_shoot, palate_states, palate_details, palate_critique on request) → palate_point_start (the
|
|
6
|
+
// user points at what's wrong). A job-level question first goes through palate_direction (3 ideas, the user
|
|
7
|
+
// picks one). Variants only when the user asks to explore or for a new kind of screen: palate_variants
|
|
8
|
+
// (sketches) → palate_pick_start with ready (with more than 4: palate_judge_render → a fresh judge scores →
|
|
9
|
+
// palate_judge_score first) → palate_pick_result → palate_build (the winner, built out) → palate_polish.
|
|
10
|
+
// Specific jobs on an existing page: palate_critique → (agent writes findings) → palate_critique_review_start →
|
|
11
|
+
// (the user agrees or disagrees) → palate_critique_review_result → palate_fix (verify) ; palate_vary → pick ;
|
|
12
|
+
// palate_details ; palate_animate. Rules palate proposes (palate_taste learn, palate_fail): palate_rule review_start →
|
|
13
|
+
// (the user keeps or discards each, with its scope) → palate_rule review_result.
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
// stdout carries the protocol; anything palate prints goes to stderr instead.
|
|
21
|
+
console.log = (...a) => console.error(...a);
|
|
22
|
+
|
|
23
|
+
const DEFAULT_DIR = process.env.PALATE_DIR ?? process.cwd();
|
|
24
|
+
const projectDir = (dir) => path.resolve(dir ?? DEFAULT_DIR);
|
|
25
|
+
const text = (t) => ({ content: [{ type: "text", text: String(t) }] });
|
|
26
|
+
const image = (file) => ({ type: "image", data: fs.readFileSync(file).toString("base64"), mimeType: /\.jpe?g$/i.test(file) ? "image/jpeg" : "image/png" });
|
|
27
|
+
const fail = (e) => ({ isError: true, content: [{ type: "text", text: String(e?.message ?? e) }] });
|
|
28
|
+
const dirArg = { dir: z.string().optional().describe("The project folder (defaults to the folder the agent runs in).") };
|
|
29
|
+
const pending = new Map();
|
|
30
|
+
|
|
31
|
+
const INSTRUCTIONS = `palate does the last 20%: AI gets a screen most of the way; palate builds it from the user's references, notes and rules, finishes it (shoot, details, critique), and lets the user point at what's wrong. Always start with palate_plan: it decides what the request deserves.
|
|
32
|
+
- direct (a fix, tweak, copy change, or a critique/details finding): change it in place, one palate_shoot with fold: true at 1440, palate_check. No loop.
|
|
33
|
+
- build (the default): build → finish → point.
|
|
34
|
+
1. palate_build with job (the user's words) and route. A new page gets a blueprint first: palate_build answers with palate_blueprint's planner brief (the spec with mood, category truths and clichés; the system; imagery per section; one archetype per section from palate's library). Plan it (yourself or a fresh subagent), palate_blueprint with file until it passes, then palate_build again: one brief for the real route plus one brief per section. Build the foundation and the hero yourself, then every other section in parallel subagents (one each, each owning its component file; each checks only its own section with palate_section, not a full shoot, and ends with early: true), then stitch. Judge each section as it lands: give its early packet to a fresh subagent at once, palate_section with verdict, and send a section below the bar back to its builder once while the others still build; the final gate still judges the whole page. An existing page gets the existing-page blueprint (palate captures it as it is; the plan keeps the brand and marks each section keep, upgrade, remove or add), and only the changed sections are rebuilt. Without subagents (Codex, Cursor), palate_gate with judge "api" when a model API key is set, else "self" (tell the user it is self-judged).
|
|
35
|
+
2. The finishing pass on that page: palate_finish runs the shoot, states, details and the motion check in one call (or separately: palate_shoot (both widths; a failing rule is fixed first, and so is a failing similarity gate: you fix it by changing what it names, you never ask the user), palate_states (build the missing states, then verify: true), palate_details, palate_animate with check if it moves, then always palate_gate: the quality floor, judged independently. It runs palate's floor checks and writes judge packets (a yes/no checklist per section, and the page beside strong real sites in both orders); run each packet with its own fresh subagent in parallel (never judge your own build, never tell the judges which page is ours), then palate_gate with answers. Below the bar: palate writes one repair brief per failing section (up to three); run them in parallel, one fresh subagent each, then palate_gate again; two repair rounds at most, and a round that got worse is rolled back (restore). Then tell the user the verdict in one line, honestly. Don't ask the user anything in the finishing pass. palate_critique only if the user asks for a review.
|
|
36
|
+
3. palate_point_start: the user points at what's wrong in the running app. Then keep listening without being asked: loop palate_point_next (it waits up to 55 s and answers "Still listening" when nothing came; call it again at once), fix each pin, palate_point_crop (before and after at the width they pinned at and the other one, phone or desktop; if the other width broke, fix that too), palate_point_done (it refuses while the other width is broken), and back to palate_point_next. A pin inside a state that needs a click first (an open modal, a dropdown, a tab, a hover menu) carries the steps that opened it, and palate reopens it before every crop; when it couldn't, the check says so, and that never counts as broken. Don't end your turn while point is running. If the user writes something else in chat, answer it, then go back to listening. Stop only when they say they're done or palate_point_next says they pressed Done. Then palate_taste with action "learn"; if it proposed rules, palate_rule with action "review_start" so the user keeps or discards them. To have pins fixed with no chat at all, the user runs palate point --auto: palate runs their agent headless on each pin.
|
|
37
|
+
- research (a new screen with no strong taste history, or the user wants something new or creative): research → direction → build, driven by you with no human step unless palate asks for one. 1. palate_research with the job: run the searches it returns with your own web search, then palate_research again with urls (4–8 official competitor homepages; [] to skip). 2. Open the contact sheet and screenshots, refine the drafted notes in research.md. 3. palate_direction with problem (the job) and from_research; answer with 3 art directions (metaphor, one signature move adapted to the product, the brand kept or a full starter system when the project has none, sections, provenance, wireframe), then palate_direction with the ideas: palate picks from taste when it is confident (or choose "auto" when the user said "just build it") and opens the picker only for a real judgement call, saying why in one line. 4. palate_blueprint with the job (the design decided before code, from the research and the chosen direction), palate_blueprint with file until it passes, then palate_build with the job: it carries the blueprint, the direction, the signature move's recipe and the abstract research notes, and writes one brief per section; deliver the signature moment. Imagery: palate_imagery for CC0 museum photos, palate_texture for materials drawn in code. Use the ideas, never the expression: no copied code, copy, images, illustrations, logos or paid fonts. If the user reacts to a reference, palate_research with action "verdict".
|
|
38
|
+
- match (the user gave their own design to build: a screenshot, a mockup, a Figma frame, "make it look exactly like this"): the design is the spec, not palate's taste. palate_match with source (an image path, a page URL, or a Figma frame link; Figma needs FIGMA_TOKEN, else tell the user to export the frame as PNG) and route: it saves the design, measures it and writes a fidelity brief. Follow it: spec.json first (every value observed, inferred or substituted), then the build in the project's own stack, with no blueprint, archetypes or house-look check and no improvements. Then palate_match with check: true, run its one judge packet with a fresh subagent (never yourself), then palate_match with answers: READY, READY WITH SUBSTITUTIONS, NOT READY (fix what it lists, most visible first; two repair rounds at most) or UNVERIFIED. Tell the user the verdict, every substitution and what was inferred (states, the phone layout). palate_gate and the similarity gate don't apply to a matched route; contrast and clipping only warn, and you never change the design for them.
|
|
39
|
+
- direction (a job-level question or problem: "users wait 20 seconds here, what should they see?"): palate_direction with the problem; answer its brief with 3 ideas (a name, 2–3 sentences, a rough ASCII or tiny HTML wireframe each; no code, no renders), then palate_direction with the ideas: a picker opens in the user's browser. palate_direction with action "result" once they've chosen, then build mode: palate_build carries the chosen idea.
|
|
40
|
+
- quick or full (variants, opt-in: only when the user asks to explore, or a new kind of screen with no history). The triage shows their token cost next to build mode's; offer build mode as the cheaper choice. Then: palate_variants (sketches; build every brief in parallel subagents, each shoots once at 1440 with fold: true) → palate_pick_start with ready right away (more than 4 variants: palate_judge_render, a fresh judge scores, palate_judge_score first) → palate_pick_result → palate_build with name (the winner built out) → palate_polish → palate_taste learn.
|
|
41
|
+
References: when the user gives a screenshot or URL, palate_refs_add it with like and dislike in their words; if they gave none, ask them what they like and dislike about each one, then add the notes (they become rules). palate_stats shows how long loops take and their tokens per mode.
|
|
42
|
+
The user's rules (~/.palate/taste/rules.json) are enforced, not advice: every brief opens with "Your rules (must pass)" and the failures for that kind of screen; palate_shoot reports "Rules: n pass, n fail" and a failing rule is fixed before anything else; the judge ranks down variants that fail one. A rule can hold everywhere, for kinds of screen, on one route, for one component, or only in one project. When they state a rule ("only agent logos before the demo"), palate_rule with action "add": it applies at once. One pin is a one-off fix. What palate infers (the same ask twice, an agreed critique finding, palate_fail with the user's words when they call a screen bad) is only a proposed rule with a suggested scope: nothing carries into later builds until the user keeps it. When palate_taste learn or palate_fail proposes rules, palate_rule with action "review_start" opens a board where the user edits, scopes, keeps or discards each one (then "review_result"); or ask them in chat and call approve / reject. Never approve one they haven't agreed to. Never call a page done while a rule, the similarity gate or the quality gate fails (palate_check says "Not done"). Before you tell the user UI work is finished, palate_guard must pass (every changed screen looked at after its last edit, copy and code tells clean, gates passing); in a project with palate's Stop hook it runs on its own when you stop and sends you back with what is open. A finding that is wrong here: palate_waive with the file and a reason, never to get past a real problem. After writing or changing page copy: palate_copy. A page may not look like the user's other projects (over 50% similar) or copy a captured research reference: taste is how the user judges, not another product's look. Only when the user asked to match another project, pass like with its name.
|
|
43
|
+
For small changes: build, palate_shoot to look at the result, palate_polish, palate_check.
|
|
44
|
+
The last 20% of every screen is its states: after building or changing a screen with data, forms or lists, run palate_states (empty, loading, error, long text, many items, failing images, 320 wide, keyboard, forms pressed twice / failing / offline), build what it says is missing, and verify with verify: true. palate_fix includes its findings.
|
|
45
|
+
For specific jobs on an existing page: palate_critique (a designer's review; the user agrees or disagrees on a board via palate_critique_review_start / _result), palate_fix (fix the agreed findings, then verify), palate_vary (new versions of one section, then palate_pick_start), palate_details (micro-components: avatars, brand marks, inputs, states, favicon), palate_animate (motion that serves the content, with a frame-strip check).
|
|
46
|
+
Every fact must come from the project; never copy other brands' text, logos or imagery.
|
|
47
|
+
Activation: the checks and the finishing pass are free (palate_guard, palate_copy, palate_check, palate_shoot, palate_finish, palate_states, palate_details, palate_animate, palate_gate, palate_imagery, palate_texture, palate_icons). The rest is invite-only: if a tool answers "This part of palate is invite-only", tell the user in one line, ask for their invite code (palate-XXXX-XXXX-XXXX) and call palate_activate with it; without one, build the screen yourself and still run the free finishing pass and palate_guard.`;
|
|
48
|
+
|
|
49
|
+
const server = new McpServer({ name: "palate", version: "0.1.0" }, { instructions: INSTRUCTIONS });
|
|
50
|
+
|
|
51
|
+
// Invite-only: the server always starts, but until palate is activated every tool outside auth.FREE_TOOLS
|
|
52
|
+
// answers with the invite message instead of running, so the agent tells the user. See src/auth.mjs.
|
|
53
|
+
const auth = await import("../auth.mjs");
|
|
54
|
+
auth.maybeRecheck({ inProcess: true });
|
|
55
|
+
const registerTool = server.registerTool.bind(server);
|
|
56
|
+
// A long-lived server keeps the code it started with. Run from palate's own checkout, a change to palate since then
|
|
57
|
+
// isn't in this server: say so on every answer (a Moveday gate kept failing on a bug fixed an hour earlier).
|
|
58
|
+
const STARTED = Date.now();
|
|
59
|
+
const SRC = path.resolve(path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")), "..");
|
|
60
|
+
let staleAt = 0, stale = false;
|
|
61
|
+
const staleNote = () => {
|
|
62
|
+
if (!auth.devCheckout?.()) return null;
|
|
63
|
+
if (Date.now() - staleAt > 30_000) {
|
|
64
|
+
staleAt = Date.now();
|
|
65
|
+
const walk = (d) => fs.readdirSync(d, { withFileTypes: true }).some((e) => (e.isDirectory() ? walk(path.join(d, e.name)) : /\.(mjs|js)$/.test(e.name) && fs.statSync(path.join(d, e.name)).mtimeMs > STARTED + 1000));
|
|
66
|
+
try {
|
|
67
|
+
stale = walk(SRC);
|
|
68
|
+
} catch {}
|
|
69
|
+
}
|
|
70
|
+
return stale ? "Note: palate's code changed after this palate server started, so these answers come from the older code. Restart the palate MCP server (or start a new session) to use the new code." : null;
|
|
71
|
+
};
|
|
72
|
+
const withNote = (res) => {
|
|
73
|
+
const n = staleNote();
|
|
74
|
+
return n && Array.isArray(res?.content) ? { ...res, content: [...res.content, { type: "text", text: n }] } : res;
|
|
75
|
+
};
|
|
76
|
+
server.registerTool = (name, config, handler) =>
|
|
77
|
+
registerTool(name, config, async (...a) => {
|
|
78
|
+
if (auth.FREE_TOOLS.has(name)) return withNote(await handler(...a)); // the free tools need no code
|
|
79
|
+
const g = await auth.ensureGate();
|
|
80
|
+
if (!g.ok) return { isError: true, content: [{ type: "text", text: g.message }] };
|
|
81
|
+
auth.maybeRecheck({ inProcess: true }); // a long-lived server renews its licence too
|
|
82
|
+
const res = await handler(...a);
|
|
83
|
+
return withNote(g.note && Array.isArray(res?.content) ? { ...res, content: [...res.content, { type: "text", text: g.note }] } : res);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
registerTool(
|
|
87
|
+
"palate_activate",
|
|
88
|
+
{
|
|
89
|
+
title: "Activate palate with an invite code",
|
|
90
|
+
description: "palate is invite-only. When a tool answers \"palate is invite-only\", ask the user for their invite code and call this with it; without a code it reports whether palate is activated. Sends only the code and a random machine id to palate's site, once.",
|
|
91
|
+
inputSchema: { code: z.string().max(64).optional().describe("The user's invite code, exactly as they gave it: palate-XXXX-XXXX-XXXX") },
|
|
92
|
+
},
|
|
93
|
+
async ({ code }) => {
|
|
94
|
+
try {
|
|
95
|
+
if (code === undefined) return text(auth.status());
|
|
96
|
+
if (!auth.CODE_RE.test(code.trim())) return fail("That isn't a palate invite code: codes look like palate-XXXX-XXXX-XXXX. Ask the user to copy it from their invite email.");
|
|
97
|
+
return text(await auth.activate(code, { mcp: true }));
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return fail(e);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// One browser and one dev server for the whole session; closed after 10 idle minutes.
|
|
105
|
+
(await import("../pool.mjs")).keepAlive({ idle: 10 * 60_000 });
|
|
106
|
+
|
|
107
|
+
server.registerTool(
|
|
108
|
+
"palate_plan",
|
|
109
|
+
{
|
|
110
|
+
title: "Triage the request",
|
|
111
|
+
description: "Run first for any design request. Decides what it deserves, with the reason and the estimated token cost: direct (a fix or tweak: change in place), build (the default: one build, the finishing pass, then the user points), direction (a job-level question or problem: 3 ideas first), research (a new screen with no strong taste history, or an ask for something new: competitors and craft looked at live, 3 art directions, then one build; its auto flag says palate may pick the direction itself), or variants only when the user asks to explore or for a new kind of screen (quick: 2–3 sketches; full: sketches, judge, pick at 4).",
|
|
112
|
+
inputSchema: { ...dirArg, request: z.string().describe("The request, in the user's words"), route: z.string().optional().describe("The route it is about, if any"), mode: z.enum(["direct", "build", "direction", "research", "quick", "full", "match"]).optional().describe("Only when the user asked for a mode") },
|
|
113
|
+
},
|
|
114
|
+
async ({ dir, request, route, mode }) => {
|
|
115
|
+
try {
|
|
116
|
+
const { plan, planText } = await import("../loop/plan.mjs");
|
|
117
|
+
return text(planText(plan(projectDir(dir), request, { route, mode })));
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return fail(e);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
server.registerTool(
|
|
125
|
+
"palate_context",
|
|
126
|
+
{ title: "Read the project", description: "What this project uses: stack, tokens, fonts, components, screens. Run first.", inputSchema: { ...dirArg } },
|
|
127
|
+
async ({ dir }) => {
|
|
128
|
+
try {
|
|
129
|
+
const { context } = await import("../context.mjs");
|
|
130
|
+
return text(context(projectDir(dir)));
|
|
131
|
+
} catch (e) {
|
|
132
|
+
return fail(e);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
server.registerTool(
|
|
138
|
+
"palate_taste",
|
|
139
|
+
{
|
|
140
|
+
title: "The user's taste profile",
|
|
141
|
+
description: 'action "show" (default): what palate has learned about the user (rules by kind, the newest rules, proposals waiting for their OK, how often its top-ranked variant was their pick) and the taste profile; "learn": turns patterns in new evidence (agreed critique findings, failures, the same point request twice) into proposed rules, then returns the rest so you can update the profile file it names. When it proposed rules, show the user each one with its scope: palate_rule action "review_start" (a board), or ask in chat and call palate_rule approve / reject. Proposals apply nowhere until kept.',
|
|
142
|
+
inputSchema: { ...dirArg, action: z.enum(["show", "learn"]).optional() },
|
|
143
|
+
},
|
|
144
|
+
async ({ dir, action }) => {
|
|
145
|
+
try {
|
|
146
|
+
const { taste } = await import("../loop/taste.mjs");
|
|
147
|
+
return text(taste(action === "learn" ? "learn" : undefined, { dir: projectDir(dir) }));
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return fail(e);
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
server.registerTool(
|
|
155
|
+
"palate_refs_add",
|
|
156
|
+
{
|
|
157
|
+
title: "Save the user's references",
|
|
158
|
+
description: "Save screenshots, image files or URLs the user gives into their personal library (URLs are captured and measured), with what they like and dislike about each. The notes become rules tied to the reference; references with their notes come first in future briefs. Without notes, it tells you to ask the user for them; call it again with the same source and like / dislike.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
sources: z.array(z.string()).min(1).describe("Image file paths or URLs"),
|
|
161
|
+
like: z.array(z.string()).optional().describe("What the user likes about them, in their words, one item each (e.g. \"the calm spacing\")"),
|
|
162
|
+
dislike: z.array(z.string()).optional().describe("What the user doesn't like, in their words, one item each (e.g. \"gradient buttons\")"),
|
|
163
|
+
kind: z.string().optional().describe("Kinds of screen the notes' rules apply to, comma separated (landing, pricing, app, all…); default: from the job"),
|
|
164
|
+
note: z.string().optional().describe("Any other note"),
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
async ({ sources, like, dislike, kind, note }) => {
|
|
168
|
+
try {
|
|
169
|
+
const { addRefs, refsAddText } = await import("../loop/myrefs.mjs");
|
|
170
|
+
const saved = await addRefs(sources, { note: note ?? "", like, dislike, kinds: kind ? kind.split(",") : undefined });
|
|
171
|
+
return text(refsAddText(saved, { mcp: true }));
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return fail(e);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
server.registerTool(
|
|
179
|
+
"palate_variants",
|
|
180
|
+
{
|
|
181
|
+
title: "Plan the variants",
|
|
182
|
+
description: "Opt-in: only when the user asks to explore (options, directions, variants) or the triage says quick/full for a new kind of screen. Chooses directions from the user's history plus one to explore, finds references, and writes one brief per direction, with the loop's estimated token cost. How many comes from the triage (palate_plan); a request the triage calls direct, build or direction gets no loop. Briefs are sketches (first screen + centrepiece) unless full is true. Build every brief afterwards.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
...dirArg,
|
|
185
|
+
job: z.string().describe("What to build, in the user's words"),
|
|
186
|
+
name: z.string().describe("Short name for this screen, e.g. pricing; variants become routes like pricing-a1"),
|
|
187
|
+
quick: z.boolean().optional().describe("One variant per direction and no judge step; for small or everyday screens"),
|
|
188
|
+
refs: z.array(z.string()).optional().describe("The user's own references for this screen: image paths or URLs"),
|
|
189
|
+
mode: z.enum(["quick", "full"]).optional().describe("Override the triage, only when the user asked to explore"),
|
|
190
|
+
full: z.boolean().optional().describe("Build whole pages instead of sketches (slower)"),
|
|
191
|
+
route: z.string().optional().describe("The route this replaces, if it exists"),
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
async ({ dir, job, name, quick, refs, mode, full, route }) => {
|
|
195
|
+
try {
|
|
196
|
+
const { variants } = await import("../loop/variants.mjs");
|
|
197
|
+
return text(await variants(projectDir(dir), job, { name, quick: Boolean(quick), refs: refs ?? [], mode, full: Boolean(full), route }));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return fail(e);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
server.registerTool(
|
|
205
|
+
"palate_judge_render",
|
|
206
|
+
{
|
|
207
|
+
title: "Render variants for judging",
|
|
208
|
+
description: "Renders every variant the same way and returns the rubric and the screenshots. A fresh judge (not the builder) should score them, then call palate_judge_score. ready: first wait until that many variants are built (can take minutes).",
|
|
209
|
+
inputSchema: { ...dirArg, name: z.string(), ready: z.number().int().min(2).max(8).optional() },
|
|
210
|
+
},
|
|
211
|
+
async ({ dir, name, ready }) => {
|
|
212
|
+
try {
|
|
213
|
+
const { judge } = await import("../loop/judge.mjs");
|
|
214
|
+
const d = projectDir(dir);
|
|
215
|
+
const out = await judge(d, name, { ready });
|
|
216
|
+
const session = path.join(d, ".palate", "variants", name);
|
|
217
|
+
const key = JSON.parse(fs.readFileSync(path.join(session, "judge-key.json"), "utf8"));
|
|
218
|
+
const rubric = fs.readFileSync(path.join(session, "rubric.md"), "utf8");
|
|
219
|
+
return { content: [{ type: "text", text: `${out}\n\n${rubric}` }, ...key.flatMap((k) => [{ type: "text", text: k.label }, image(k.file)])] };
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return fail(e);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
server.registerTool(
|
|
227
|
+
"palate_judge_score",
|
|
228
|
+
{
|
|
229
|
+
title: "Keep the best variants",
|
|
230
|
+
description: "Takes the judge's scores (1–10 per quality, plus true/false for each of the user's rules the rubric asks about) and keeps the best variants for the user's pick. Variants that fail a measured rule rank below every variant that passes.",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
...dirArg,
|
|
233
|
+
name: z.string(),
|
|
234
|
+
scores: z.array(z.object({ label: z.string(), clarity: z.number(), content_at_glance: z.number(), main_object_dominance: z.number(), chrome_clutter: z.number(), rules: z.record(z.string(), z.boolean()).optional().describe("The rubric's rule questions: rule id → true if the design follows it") })),
|
|
235
|
+
keep: z.number().int().min(2).max(6).optional(),
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
async ({ dir, name, scores, keep }) => {
|
|
239
|
+
try {
|
|
240
|
+
const { judge } = await import("../loop/judge.mjs");
|
|
241
|
+
const d = projectDir(dir);
|
|
242
|
+
const file = path.join(d, ".palate", "variants", name, "scores.json");
|
|
243
|
+
fs.writeFileSync(file, JSON.stringify({ scores }, null, 2));
|
|
244
|
+
return text(await judge(d, name, { scores: file, keep: keep ?? 4 }));
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return fail(e);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
server.registerTool(
|
|
252
|
+
"palate_pick_start",
|
|
253
|
+
{
|
|
254
|
+
title: "Let the user choose",
|
|
255
|
+
description: "Opens the variants side by side in the user's browser, blind; the user clicks the best one. Returns the link at once: give it to the user, then call palate_pick_result. ready: call it right after the briefs go out; it returns at once and opens the picker by itself as soon as that many variants are built.",
|
|
256
|
+
inputSchema: { ...dirArg, name: z.string(), pairs: z.boolean().optional().describe("Ask every A/B pair instead of one click (for close calls)"), ready: z.number().int().min(2).max(8).optional().describe("Open as soon as this many variants are built") },
|
|
257
|
+
},
|
|
258
|
+
async ({ dir, name, pairs, ready }) => {
|
|
259
|
+
try {
|
|
260
|
+
const { startPick } = await import("../loop/pick.mjs");
|
|
261
|
+
const d = projectDir(dir);
|
|
262
|
+
if (ready) {
|
|
263
|
+
// Waiting for builders can take minutes: return now, open the picker when they're ready.
|
|
264
|
+
const entry = { link: null, report: null, waiting: ready };
|
|
265
|
+
pending.set(`${d}|${name}`, entry);
|
|
266
|
+
startPick(d, name, { open: true, pairsMode: Boolean(pairs), ready })
|
|
267
|
+
.then((s) => {
|
|
268
|
+
entry.link = s.link;
|
|
269
|
+
return s.done.then((r) => (entry.report = r));
|
|
270
|
+
})
|
|
271
|
+
.catch((e) => (entry.report = `Picker failed: ${e.message}`));
|
|
272
|
+
return text(`The picker will open in the user's browser as soon as ${ready} variants are built. Tell the user it's coming; then call palate_pick_result (it gives the link once it's open).`);
|
|
273
|
+
}
|
|
274
|
+
const s = await startPick(d, name, { open: true, pairsMode: Boolean(pairs) });
|
|
275
|
+
const entry = { link: s.link, report: null };
|
|
276
|
+
s.done.then((r) => (entry.report = r)).catch((e) => (entry.report = `Picker failed: ${e.message}`));
|
|
277
|
+
pending.set(`${d}|${name}`, entry);
|
|
278
|
+
return text(`The picker is open: ${s.link} (${s.summary}). Tell the user to open it if it didn't open by itself and click the design they want. Then call palate_pick_result.`);
|
|
279
|
+
} catch (e) {
|
|
280
|
+
return fail(e);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
server.registerTool(
|
|
286
|
+
"palate_pick_result",
|
|
287
|
+
{
|
|
288
|
+
title: "The user's choice",
|
|
289
|
+
description: "Returns the winner and the user's reasons once they have picked; otherwise says it is still waiting. Wait for the user before calling again.",
|
|
290
|
+
inputSchema: { ...dirArg, name: z.string() },
|
|
291
|
+
},
|
|
292
|
+
async ({ dir, name }) => {
|
|
293
|
+
const entry = pending.get(`${projectDir(dir)}|${name}`);
|
|
294
|
+
if (!entry) return fail(`No picker running for "${name}". Call palate_pick_start first.`);
|
|
295
|
+
return text(entry.report ?? (entry.link ? `Still waiting for the user to choose at ${entry.link}.` : `Still waiting for ${entry.waiting} variants to be built; the picker opens by itself then.`));
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
server.registerTool(
|
|
300
|
+
"palate_build",
|
|
301
|
+
{
|
|
302
|
+
title: "Build it (build mode), or build out a variants winner",
|
|
303
|
+
description: "Build mode, the default (give job): one brief for the real route with the user's rules, references with their likes and dislikes, taste and the direction they chose; one builder follows it, then the finishing pass (shoot, details, critique on request), then palate_point_start. Logs the brief size and estimated token cost. After a variants pick (give name, no job): the brief that completes the winning sketch into the full page.",
|
|
304
|
+
inputSchema: {
|
|
305
|
+
...dirArg,
|
|
306
|
+
job: z.string().optional().describe("Build mode: what to build, in the user's words"),
|
|
307
|
+
route: z.string().optional().describe("Build mode: the real route (e.g. / or /pricing). After a pick: the winning route, if not the recorded winner"),
|
|
308
|
+
direction: z.string().optional().describe('Build mode: the chosen direction\'s name ("latest" for the newest); default: one chosen for this route today'),
|
|
309
|
+
refs: z.array(z.string()).optional().describe("Build mode: the user's references for this screen (image paths or URLs); add their notes with palate_refs_add"),
|
|
310
|
+
name: z.string().optional().describe("After a pick: the loop's name. Build mode: a short name for this build (default from the route)"),
|
|
311
|
+
like: z.array(z.string()).optional().describe("Build mode, only when the user asked to match another of their projects: its name (the similarity gate then lets its look through)"),
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
async ({ dir, job, route, direction, refs, name, like }) => {
|
|
315
|
+
try {
|
|
316
|
+
const { build, buildBrief } = await import("../loop/build.mjs");
|
|
317
|
+
const d = projectDir(dir);
|
|
318
|
+
const out = job ? await buildBrief(d, job, { route, name, direction, refs: refs ?? [], like: like ?? [] }) : build(d, { name, route });
|
|
319
|
+
const file = out.match(/Brief: (\S+)/)?.[1];
|
|
320
|
+
return text(file && fs.existsSync(file) ? `${out}\n\n${fs.readFileSync(file, "utf8")}` : out);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
return fail(e);
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// ── the quality floor: blueprint before code, the independent gate after it (src/loop/blueprint.mjs, gate.mjs) ──
|
|
328
|
+
server.registerTool(
|
|
329
|
+
"palate_blueprint",
|
|
330
|
+
{
|
|
331
|
+
title: "Decide the design before code",
|
|
332
|
+
description: "A new page's first build step (palate_build asks for it). With job: the planner brief: expand the job into a spec (mood from the audience, category truths to keep, clichés to avoid), the system (colours by role including a material tone, type roles), an imagery decision per section, the page's button language (look.buttons, from a library of languages) and one archetype per section from palate's library. Plan it yourself or with a fresh subagent and write the JSON it names. With file: palate checks it (neighbours differ in shape, surfaces vary, material present, AA contrast, a display face with character) and, when it passes, saves it with tokens (layered shadows, the buttons in its language). Then palate_build builds it section by section. No question to the user.",
|
|
333
|
+
inputSchema: { ...dirArg, job: z.string().optional().describe("What to build, in the user's words (for the planner brief)"), route: z.string().optional(), file: z.string().optional().describe("The blueprint JSON to check and save"), name: z.string().optional(), direction: z.string().optional() },
|
|
334
|
+
},
|
|
335
|
+
async ({ dir, job, route, file, name, direction }) => {
|
|
336
|
+
try {
|
|
337
|
+
const B = await import("../loop/blueprint.mjs");
|
|
338
|
+
const d = projectDir(dir);
|
|
339
|
+
if (file) return text(B.saveBlueprint(d, file, { name }));
|
|
340
|
+
const out = await B.blueprintBrief(d, job ?? "", { route, name, direction });
|
|
341
|
+
const brief = out.match(/Brief: (\S+)/)?.[1];
|
|
342
|
+
return text(brief && fs.existsSync(brief) ? `${out}\n\n${fs.readFileSync(brief, "utf8")}` : out);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
return fail(e);
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
server.registerTool(
|
|
350
|
+
"palate_finish",
|
|
351
|
+
{
|
|
352
|
+
title: "The finishing pass, in one call",
|
|
353
|
+
description: "After building a page: the shoot at both widths (your rules, the similarity gate, the look), every state, the small details and the motion check, in one process sharing one browser and dev server (about a minute, instead of four separate calls). Fix what it names, then palate_states with verify: true for the states you built, then palate_gate.",
|
|
354
|
+
inputSchema: { ...dirArg, route: z.string(), url: z.string().optional() },
|
|
355
|
+
},
|
|
356
|
+
async ({ dir, route, url }) => {
|
|
357
|
+
try {
|
|
358
|
+
const d = projectDir(dir);
|
|
359
|
+
const { shoot } = await import("../shoot.mjs");
|
|
360
|
+
const { states } = await import("../loop/states.mjs");
|
|
361
|
+
const { details } = await import("../loop/details.mjs");
|
|
362
|
+
const { animate } = await import("../loop/animate.mjs");
|
|
363
|
+
const run = async (name, fn) => {
|
|
364
|
+
try {
|
|
365
|
+
const out = await fn();
|
|
366
|
+
return `## ${name}
|
|
367
|
+
${(typeof out === "string" ? out : out?.text ?? out?.report ?? "").trim()}`;
|
|
368
|
+
} catch (e) {
|
|
369
|
+
return `## ${name}
|
|
370
|
+
${name} failed: ${e.message}`;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
const first = await run("shoot", async () => (await shoot(d, route, { url })).report);
|
|
374
|
+
const rest = await Promise.all([run("states", () => states(d, route, { url })), run("details", () => details(d, route, { url })), run("animate", () => animate(d, route, { url, checkOnly: true }))]);
|
|
375
|
+
return text([`# Finishing pass ${route}`, first, ...rest].join("\n\n"));
|
|
376
|
+
} catch (e) {
|
|
377
|
+
return fail(e);
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
server.registerTool(
|
|
383
|
+
"palate_gate",
|
|
384
|
+
{
|
|
385
|
+
title: "The quality floor, judged independently",
|
|
386
|
+
description: "Run after building a page (part of the finishing pass, always). Without answers: palate renders the page, runs its floor checks (dead space, half-empty sections, one template repeated, one flat colour with no material, monospace as decoration, placeholder text, clipped text, endless phone stacks) and writes judge packets: a yes/no checklist per section and the page beside strong real sites in both orders. Run each packet with its own fresh subagent in parallel; never judge your own build. With answers (the round's folder): the verdict and, below the bar, one repair brief for one section. Two repair rounds at most; restore: <round> rolls a worse round back. Tell the user the verdict honestly.",
|
|
387
|
+
inputSchema: { ...dirArg, route: z.string(), url: z.string().optional(), answers: z.string().optional().describe("The round folder palate named (or comma-separated answer files)"), restore: z.number().optional().describe("Roll the source back to this round's snapshot"), judge: z.enum(["subagent", "api", "self"]).optional().describe("subagent (default: you run each packet with a fresh subagent); api when you can't start subagents (Codex, Cursor) and a model API key is set: palate judges itself; self only when neither is possible: the verdict is marked self-judged") },
|
|
388
|
+
},
|
|
389
|
+
async ({ dir, route, url, answers, restore, judge }) => {
|
|
390
|
+
try {
|
|
391
|
+
const { gate } = await import("../loop/gate.mjs");
|
|
392
|
+
return text(await gate(projectDir(dir), route, { url, answers: answers ? answers.split(",") : undefined, restore, judge }));
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return fail(e);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
server.registerTool(
|
|
400
|
+
"palate_match",
|
|
401
|
+
{
|
|
402
|
+
title: "Build from the user's own design",
|
|
403
|
+
description: "When the user gives their own design to build (a screenshot or mockup PNG/JPG, a page URL, a Figma frame link) and wants it faithfully, not palate's look. With source: saves it into .palate/match/<route>/, measures it (colours by share, surfaces, content blocks, the content's span) and writes the fidelity brief (spec.json with every value observed, inferred or substituted; then the build; states and the phone layout are inferred and listed). A Figma link needs FIGMA_TOKEN; without it palate says to export the frame as PNG. With check: renders the build at the design's width, writes the two side by side and a difference image, measures palette distance, text sizes against the spec, motion, contrast and clipping (warnings only), and writes one judge packet for a fresh subagent. With answers (the check's folder): the verdict, READY, READY WITH SUBSTITUTIONS, NOT READY (what's off, most visible first, with the fix; two repair rounds) or UNVERIFIED. restore: roll a worse round back.",
|
|
404
|
+
inputSchema: { ...dirArg, source: z.string().optional().describe("An image path, a page URL or a Figma frame link (with node-id)"), route: z.string().optional().describe("The route to build it as; default /"), width: z.number().optional().describe("The design's width in CSS px (default: the image's width; a 2400 px+ image is read as @2x; a URL is captured at 1440)"), check: z.boolean().optional().describe("Check the build against the design and write the judge packet"), answers: z.string().optional().describe("The check's folder, after the judge wrote its answers"), restore: z.number().optional().describe("Roll the source back to this match round's snapshot"), url: z.string().optional().describe("Dev server URL if the project isn't Vite") },
|
|
405
|
+
},
|
|
406
|
+
async ({ dir, source, route, width, check, answers, restore, url }) => {
|
|
407
|
+
try {
|
|
408
|
+
const { match } = await import("../loop/match.mjs");
|
|
409
|
+
const res = await match(projectDir(dir), source, { route: route ?? "/", width, check, answers, restore, url });
|
|
410
|
+
return text(res.text);
|
|
411
|
+
} catch (e) {
|
|
412
|
+
return fail(e);
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
server.registerTool(
|
|
418
|
+
"palate_sections",
|
|
419
|
+
{
|
|
420
|
+
title: "palate's section archetypes",
|
|
421
|
+
description: "The library of section archetypes (kb/sections): structures with their finish, phone layout and the checks the judge uses. kind + tags: ranked matches; show: archetypes in full. The blueprint picks from these; use it when rebuilding a section.",
|
|
422
|
+
inputSchema: { kind: z.string().optional(), tags: z.array(z.string()).optional(), show: z.array(z.string()).optional(), limit: z.number().optional() },
|
|
423
|
+
},
|
|
424
|
+
async ({ kind, tags, show, limit }) => {
|
|
425
|
+
try {
|
|
426
|
+
const S = await import("../sections.mjs");
|
|
427
|
+
if (show?.length) return text(S.sectionBrief(show));
|
|
428
|
+
const list = kind ? S.matchSections({ kind, tags: tags ?? [], limit: limit ?? 8 }) : S.loadSections();
|
|
429
|
+
return text(list.map((s) => `${s.id} (${s.kind}, ${s.shape}): ${s.layout}`).join("\n"));
|
|
430
|
+
} catch (e) {
|
|
431
|
+
return fail(e);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
server.registerTool(
|
|
437
|
+
"palate_imagery",
|
|
438
|
+
{
|
|
439
|
+
title: "Imagery: open-licence photos",
|
|
440
|
+
description: "Search for every photo slot on the page in ONE call (queries: 6 to 12 searches, run side by side, cached for a week across projects), pick across all the contact sheets, then one pick call; a second batch only for a slot still empty. Never one search per call. For a section that needs a photo: query searches The Met and Cleveland open access (CC0 only) by default; sources [\"commons\"] searches Wikimedia Commons for everyday photographs (people at work, a shop, a street, a tool), keeping only CC0, public domain and CC BY files with no restrictions (never share-alike, non-commercial or no-derivatives), the licence read from each file. A CC BY photo must be credited on the page (palate writes the line in credits.json; palate_guard checks it is there). It returns contact sheets (12 to a sheet, with ids and pixel sizes) to look at. type narrows to one kind: Photograph for real photographs (also Painting, Print, Drawing, Textile, Ceramic, Sculpture, Glass); artist finds one maker's work (a photographer's name). A source that fails or is rate-limited is named in a note and the other's results still come back; answers are cached for 2 hours in .palate (refresh skips the cache). pick downloads the chosen ids into the project (self-hosted) as web-sized JPEGs (longest side 2400 px; a museum mount trimmed unless trim is false) with credits.json and a credits line to show on the page. Use one treatment for every photo on the page (duotone, tone).",
|
|
441
|
+
inputSchema: { ...dirArg, query: z.string().optional(), queries: z.array(z.string()).optional().describe("Every search the page needs at once (6 to 12), run side by side: much faster than one call per query"), type: z.string().optional().describe("Photograph, Painting, Print, Drawing, Textile, Ceramic, Sculpture or Glass"), artist: z.string().optional().describe("One maker, e.g. a photographer's name"), pick: z.array(z.string()).optional().describe("Candidate ids to download (met-…, cma-…, wc-…)"), from: z.string().optional().describe("The search's slug, for pick"), trim: z.boolean().optional().describe("For pick: trim a museum mount (default true)"), sources: z.array(z.string()).optional().describe("met, cleveland (the default), commons"), count: z.number().optional(), refresh: z.boolean().optional() },
|
|
442
|
+
},
|
|
443
|
+
async ({ dir, query, queries, type, artist, pick, from, trim, sources, count, refresh }) => {
|
|
444
|
+
try {
|
|
445
|
+
const I = await import("../loop/imagery.mjs");
|
|
446
|
+
const d = projectDir(dir);
|
|
447
|
+
if (pick?.length) return text(await I.pickImages(d, pick, { slug: from, trim: trim !== false }));
|
|
448
|
+
if (queries?.length > 1 || /\|/.test(query ?? "")) {
|
|
449
|
+
const out = await I.imageryMany(d, queries?.length ? queries : query, { sources, count, type, refresh });
|
|
450
|
+
const sheets = [...out.matchAll(/(\S+contact\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f)).slice(0, 6);
|
|
451
|
+
return sheets.length ? { content: [{ type: "text", text: out }, ...sheets.map(image)] } : text(out);
|
|
452
|
+
}
|
|
453
|
+
const out = await I.imagery(d, query ?? "", { sources, count, type, artist, refresh });
|
|
454
|
+
// Every sheet up to three (36 candidates); any more are named in the text.
|
|
455
|
+
const sheets = [...out.matchAll(/(\S+contact(?:-\d+)?\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f)).slice(0, 3);
|
|
456
|
+
return sheets.length ? { content: [{ type: "text", text: out }, ...sheets.map(image)] } : text(out);
|
|
457
|
+
} catch (e) {
|
|
458
|
+
return fail(e);
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
server.registerTool(
|
|
464
|
+
"palate_icons",
|
|
465
|
+
{
|
|
466
|
+
title: "Brand icons",
|
|
467
|
+
description:
|
|
468
|
+
"Real brand marks for a page that names other products (a works-with row, an integrations grid, import sources, a stack list): writes each as a local SVG in the brand's own colour (mono: currentColor) from Simple Icons at a pinned release, with its source and brand guidelines in brands.json. Use it instead of drawing a logo or hotlinking one. A brand the set doesn't carry is reported with near names; then use the brand's press-kit file or its name in text. Each mark is its owner's trademark: only to name that product, never as the page's own logo.",
|
|
469
|
+
inputSchema: { ...dirArg, names: z.array(z.string()).min(1).describe("Brand names, e.g. Figma, GitHub, Google Drive"), mono: z.boolean().optional() },
|
|
470
|
+
},
|
|
471
|
+
async ({ dir, names, mono }) => {
|
|
472
|
+
try {
|
|
473
|
+
const { icons } = await import("../loop/icons.mjs");
|
|
474
|
+
return text(await icons(projectDir(dir), names, { mono }));
|
|
475
|
+
} catch (e) {
|
|
476
|
+
return fail(e);
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
server.registerTool(
|
|
482
|
+
"palate_texture",
|
|
483
|
+
{
|
|
484
|
+
title: "A material drawn in code",
|
|
485
|
+
description: "Writes a seeded, seamless texture into the project (grain, paper, speckle, halftone, linen) in the given colour, with the CSS to use it as a surface or an overlay. For the blueprint's texture slots and material bands.",
|
|
486
|
+
inputSchema: { ...dirArg, name: z.enum(["grain", "paper", "speckle", "halftone", "linen"]), colour: z.string().optional(), opacity: z.number().optional(), seed: z.string().optional() },
|
|
487
|
+
},
|
|
488
|
+
async ({ dir, name, colour, opacity, seed }) => {
|
|
489
|
+
try {
|
|
490
|
+
const I = await import("../loop/imagery.mjs");
|
|
491
|
+
return text(I.writeTexture(projectDir(dir), name, { colour, opacity, seed }));
|
|
492
|
+
} catch (e) {
|
|
493
|
+
return fail(e);
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
server.registerTool(
|
|
499
|
+
"palate_bench",
|
|
500
|
+
{
|
|
501
|
+
title: "The benchmark",
|
|
502
|
+
description: "30 fictional briefs in 6 categories, each with real reference sites, to measure palate's from-scratch quality honestly. list; new (id, into: scaffold a project); record (id: the latest gate verdict of the project in dir becomes a result row); report (pass rate, tie-or-win rate against the references, minutes).",
|
|
503
|
+
inputSchema: { ...dirArg, action: z.enum(["list", "new", "record", "report"]), id: z.string().optional(), into: z.string().optional(), minutes: z.number().optional(), note: z.string().optional() },
|
|
504
|
+
},
|
|
505
|
+
async ({ dir, action, id, into, minutes, note }) => {
|
|
506
|
+
try {
|
|
507
|
+
const Bn = await import("../loop/bench.mjs");
|
|
508
|
+
if (action === "list") return text(Bn.listBriefs({}).map((b) => `${b.id} (${b.category}): ${b.job}`).join("\n"));
|
|
509
|
+
if (action === "new") return text(Bn.scaffold(id, path.resolve(into)));
|
|
510
|
+
if (action === "record") {
|
|
511
|
+
const { benchResultFrom } = await import("../loop/gate.mjs");
|
|
512
|
+
return text(JSON.stringify(Bn.recordResult(id, { ...benchResultFrom(projectDir(dir)), minutes, notes: note }), null, 1));
|
|
513
|
+
}
|
|
514
|
+
return text(Bn.benchReport({}));
|
|
515
|
+
} catch (e) {
|
|
516
|
+
return fail(e);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
server.registerTool(
|
|
522
|
+
"palate_direction",
|
|
523
|
+
{
|
|
524
|
+
title: "Ideas or art directions before a build",
|
|
525
|
+
description: 'Call it yourself when palate_plan says direction or research; never ask the user to run anything. Two uses. (1) A job-level ask ("users wait ~20 seconds here, what should they see?"): action "brief" with problem; answer with 3 ideas (name, 2–3 sentences, rough ASCII or tiny HTML wireframe; no code). (2) After palate_research: action "brief" with problem (the job) and from_research; answer with 3 art directions (name, metaphor, signature { move, moment }, brand or a starter system, sections, provenance, summary, wireframe). Then action "pick" with the ideas: palate validates them (AA contrast, licence-safe fonts and imagery, the brand kept), and either picks one itself from the user\'s taste (strong history and a clear lead, or choose "auto" when the user said "just build it") and returns the choice, or opens the picker in the user\'s browser for a real judgement call and says why in one line. action "result": the user\'s choice once made. Then palate_build carries it.',
|
|
526
|
+
inputSchema: {
|
|
527
|
+
...dirArg,
|
|
528
|
+
action: z.enum(["brief", "pick", "result"]).optional(),
|
|
529
|
+
problem: z.string().optional().describe("brief: the problem or the job, in the user's words"),
|
|
530
|
+
route: z.string().optional().describe("brief: the route it is about, if any"),
|
|
531
|
+
from_research: z.string().optional().describe('brief: the research name from palate_research ("latest" for the newest): asks for 3 art directions'),
|
|
532
|
+
ideas: z
|
|
533
|
+
.array(z.object({ name: z.string(), summary: z.string().optional().describe("2–3 sentences"), wireframe: z.string().describe("Rough ASCII (≤16 lines) or a tiny HTML snippet"), format: z.enum(["ascii", "html"]).optional() }).catchall(z.any()))
|
|
534
|
+
.optional()
|
|
535
|
+
.describe("pick: the 3 ideas, or the 3 art directions with metaphor, signature, brand or starter, sections, provenance"),
|
|
536
|
+
choose: z.enum(["auto", "user"]).optional().describe('pick: "auto" when the user said "just build it" (palate picks from taste, no picker); "user" when they asked to choose. Omit: palate decides.'),
|
|
537
|
+
name: z.string().optional().describe("The direction's name (default: the newest)"),
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
async ({ dir, action, problem, route, from_research, ideas, choose, name }) => {
|
|
541
|
+
try {
|
|
542
|
+
const D = await import("../loop/direction.mjs");
|
|
543
|
+
const d = projectDir(dir);
|
|
544
|
+
const a = action ?? (ideas?.length ? "pick" : "brief");
|
|
545
|
+
if (a === "result") {
|
|
546
|
+
const entry = pending.get(`direction|${d}`);
|
|
547
|
+
if (!entry) return fail("No direction picker running. Call palate_direction with the ideas first.");
|
|
548
|
+
return text(entry.report ?? `Still waiting for the user to choose at ${entry.link}.`);
|
|
549
|
+
}
|
|
550
|
+
if (a === "brief") return text(D.direction(d, problem ?? "", { route, name, fromResearch: from_research }));
|
|
551
|
+
const s = await D.startDirectionPick(d, { ideas, name, open: true, choose });
|
|
552
|
+
const warn = s.warnings?.length ? `\n\n${s.warnings.map((w) => `⚠ ${w}`).join("\n")}` : "";
|
|
553
|
+
if (s.auto) return text(`${await s.done}${warn}`);
|
|
554
|
+
const entry = { link: s.link, report: null };
|
|
555
|
+
s.done.then((r) => (entry.report = r)).catch((e) => (entry.report = `Picker failed: ${e.message}`));
|
|
556
|
+
pending.set(`direction|${d}`, entry);
|
|
557
|
+
return text(`${s.decision?.why ? `${s.decision.why}\n` : ""}The direction picker is open in the user's browser: ${s.link} (${s.count} ${s.decision ? "directions" : "ideas"}). Tell the user in one line why (above) and that it's open; they choose one (and may like or dislike the references, and add a note). Then call palate_direction with action "result".${warn}`);
|
|
558
|
+
} catch (e) {
|
|
559
|
+
return fail(e);
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
server.registerTool(
|
|
565
|
+
"palate_stats",
|
|
566
|
+
{ title: "How long loops take", description: "Recent loops stage by stage (setup, build, judge, the user's pick), loops under way, estimated tokens per mode (build, direction, variants), and the triage's recent decisions.", inputSchema: { ...dirArg } },
|
|
567
|
+
async ({ dir }) => {
|
|
568
|
+
try {
|
|
569
|
+
const { stats } = await import("../loop/stats.mjs");
|
|
570
|
+
return text(stats(projectDir(dir)));
|
|
571
|
+
} catch (e) {
|
|
572
|
+
return fail(e);
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
server.registerTool(
|
|
578
|
+
"palate_shoot",
|
|
579
|
+
{
|
|
580
|
+
title: "Look at a screen",
|
|
581
|
+
description: "Renders a route at desktop and phone width, returns the screenshots and measured problems (contrast, overflow, small text, generic AI patterns, originality) and the similarity gate: it fails when the page is over 50% like another of the user's projects or too close to a captured reference, naming what is too close and what to change. Fix that yourself and shoot again; don't ask the user. While building variants: widths [1440] and fold: true (first screen only, no principles pass).",
|
|
582
|
+
inputSchema: { ...dirArg, route: z.string().describe("e.g. /pricing"), url: z.string().optional().describe("Dev server URL if the project isn't Vite"), widths: z.array(z.number().int()).optional().describe("Default [1440, 390]"), tablet: z.boolean().optional().describe("Also 1024 and 768 (tablet is where hero photos blow up behind text); palate_gate always checks them"), fold: z.boolean().optional().describe("First screen only, no full page or principles pass"), like: z.array(z.string()).optional().describe("Only when the user asked this page to match another of their projects: that project's name") },
|
|
583
|
+
},
|
|
584
|
+
async ({ dir, route, url, widths, tablet, fold, like }) => {
|
|
585
|
+
try {
|
|
586
|
+
const { shoot } = await import("../shoot.mjs");
|
|
587
|
+
const r = await shoot(projectDir(dir), route, { url, widths, tablet: Boolean(tablet), fold: Boolean(fold), like: like ?? [] });
|
|
588
|
+
return { content: [{ type: "text", text: r.report }, ...r.files.filter((f) => f.endsWith("-fold.png")).map(image)] };
|
|
589
|
+
} catch (e) {
|
|
590
|
+
return fail(e);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
server.registerTool(
|
|
596
|
+
"palate_polish",
|
|
597
|
+
{
|
|
598
|
+
title: "Polish the chosen design",
|
|
599
|
+
description: "Shoots the before state and returns the polish steps (Jakub Krehel's better-interface/better-ui, Emil Kowalski's emil-design-eng) with the skill files to apply.",
|
|
600
|
+
inputSchema: { ...dirArg, route: z.string() },
|
|
601
|
+
},
|
|
602
|
+
async ({ dir, route }) => {
|
|
603
|
+
try {
|
|
604
|
+
const { polish } = await import("../loop/polish.mjs");
|
|
605
|
+
return text(await polish(projectDir(dir), route, {}));
|
|
606
|
+
} catch (e) {
|
|
607
|
+
return fail(e);
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
server.registerTool(
|
|
613
|
+
"palate_check",
|
|
614
|
+
{ title: "Check changed code", description: "Scans the changed lines for generated-UI tells and token drift, and the changed copy (palate copy).", inputSchema: { ...dirArg } },
|
|
615
|
+
async ({ dir }) => {
|
|
616
|
+
try {
|
|
617
|
+
const { check } = await import("../check.mjs");
|
|
618
|
+
return text(check(projectDir(dir)));
|
|
619
|
+
} catch (e) {
|
|
620
|
+
return fail(e);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
server.registerTool(
|
|
626
|
+
"palate_section",
|
|
627
|
+
{
|
|
628
|
+
title: "Check one section",
|
|
629
|
+
description:
|
|
630
|
+
"A section builder's own check, in seconds: the section cropped at 1440, 768 and 390 plus palate's floor checks for it (not a full-page shoot). early: true also writes a packet for a fresh judge with the exact questions the final gate will ask; the builder puts its path in its report and the main build runs the judge at once (a fresh subagent, never the builder), then verdict: true reads the answers. A section below the bar goes back to its builder once, while the other sections still build, so the final gate finds less to repair.",
|
|
631
|
+
inputSchema: { ...dirArg, route: z.string(), section: z.string().describe("The section's blueprint id (its root element's id), name or a selector"), early: z.boolean().optional(), verdict: z.boolean().optional() },
|
|
632
|
+
},
|
|
633
|
+
async ({ dir, route, section, early, verdict }) => {
|
|
634
|
+
try {
|
|
635
|
+
const { sectionCheck, sectionVerdict } = await import("../loop/gate.mjs");
|
|
636
|
+
const d = projectDir(dir);
|
|
637
|
+
return text(verdict ? sectionVerdict(d, route, section) : await sectionCheck(d, route, section, { judge: Boolean(early) }));
|
|
638
|
+
} catch (e) {
|
|
639
|
+
return fail(e);
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
server.registerTool(
|
|
645
|
+
"palate_copy",
|
|
646
|
+
{
|
|
647
|
+
title: "Check the copy",
|
|
648
|
+
description:
|
|
649
|
+
"Reads the words a page shows, from source with no browser, and flags copy that reads as generated: hype verbs, inflated adjectives, stock filler, placeholder text, proof with no number or source, the \"not just X, it's Y\" turn; habits past their limit (a third intensifier, a second fragment slogan, the same vague button twice). Each finding says what to write instead. Call it after writing or changing page copy, before palate_shoot. Defaults to the changed files; all: true reads every file.",
|
|
650
|
+
inputSchema: { ...dirArg, files: z.array(z.string()).optional().describe("Files to read (relative to the project)."), all: z.boolean().optional() },
|
|
651
|
+
},
|
|
652
|
+
async ({ dir, files, all }) => {
|
|
653
|
+
try {
|
|
654
|
+
const d = projectDir(dir);
|
|
655
|
+
const { lintFiles, copyReport } = await import("../copy.mjs");
|
|
656
|
+
const { changedFiles } = await import("../guard.mjs");
|
|
657
|
+
const { execFileSync } = await import("node:child_process");
|
|
658
|
+
const list = files?.length ? files : all ? execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard"], { cwd: d, encoding: "utf8" }).split("\n").filter(Boolean) : (changedFiles(d) ?? []);
|
|
659
|
+
return text(copyReport(lintFiles(d, list), { files: list.length }));
|
|
660
|
+
} catch (e) {
|
|
661
|
+
return fail(e);
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
server.registerTool(
|
|
667
|
+
"palate_guard",
|
|
668
|
+
{
|
|
669
|
+
title: "The finish guard",
|
|
670
|
+
description:
|
|
671
|
+
"palate's guard decides whether UI work is done: every changed screen looked at by palate after its last edit, the changed copy and code tells clean, no gate failing. report (the default): what is open now; call it before telling the user UI work is finished. install: add it as a stop hook for the agents this project uses (Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot; agents picks them, \"git\" adds a pre-commit check for any agent; global: every project), so the agent is sent back with what is open when it stops (3 rounds at most). Install only when the user asked for it or agreed; uninstall removes it.",
|
|
672
|
+
inputSchema: { ...dirArg, action: z.enum(["report", "install", "uninstall"]).optional(), agents: z.array(z.string()).optional().describe("claude, codex, cursor, gemini, copilot, git (default: the ones this project uses)"), global: z.boolean().optional() },
|
|
673
|
+
},
|
|
674
|
+
async ({ dir, action = "report", agents, global }) => {
|
|
675
|
+
try {
|
|
676
|
+
const G = await import("../guard.mjs");
|
|
677
|
+
const d = projectDir(dir);
|
|
678
|
+
if (action === "report") return text(G.report(d));
|
|
679
|
+
return text(G.install(d, { agents, global, remove: action === "uninstall" }));
|
|
680
|
+
} catch (e) {
|
|
681
|
+
return fail(e);
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
server.registerTool(
|
|
687
|
+
"palate_waive",
|
|
688
|
+
{
|
|
689
|
+
title: "Waive a finding",
|
|
690
|
+
description:
|
|
691
|
+
"When a guard, copy or check finding is wrong here (the client's own brand word, a screen no dev server can render), waive it by id with the file and a reason a reviewer would accept, in a sentence. Never waive to get past a real problem; fix it instead. An \"unshot\" waiver lasts until that file changes again. Waivers are listed in every guard report, at most 12 per project. action list shows them; remove deletes one.",
|
|
692
|
+
inputSchema: { ...dirArg, action: z.enum(["add", "list", "remove"]).optional(), id: z.string().optional(), file: z.string().optional(), reason: z.string().optional() },
|
|
693
|
+
},
|
|
694
|
+
async ({ dir, action = "add", id, file, reason }) => {
|
|
695
|
+
try {
|
|
696
|
+
const G = await import("../guard.mjs");
|
|
697
|
+
const d = projectDir(dir);
|
|
698
|
+
if (action === "list") {
|
|
699
|
+
const w = G.readWaivers(d);
|
|
700
|
+
return text(w.length ? w.map((x) => `${x.id}${x.file ? ` · ${x.file}` : ""} · ${x.reason} (${x.by})`).join("\n") : "No waivers in this project.");
|
|
701
|
+
}
|
|
702
|
+
if (action === "remove") return text(G.removeWaiver(d, id, file));
|
|
703
|
+
const r = G.addWaiver(d, { id, file, reason, by: "agent" });
|
|
704
|
+
return r.ok ? text(r.message) : fail(r.message);
|
|
705
|
+
} catch (e) {
|
|
706
|
+
return fail(e);
|
|
707
|
+
}
|
|
708
|
+
},
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
server.registerTool(
|
|
712
|
+
"palate_search",
|
|
713
|
+
{ title: "Component anatomy", description: "Finds knowledge entries (component anatomy, screen patterns, details) for the parts a screen needs. Returns short cards; read full entries with ids.", inputSchema: { query: z.string(), ids: z.array(z.string()).optional().describe("Read these entries in full instead of searching") } },
|
|
714
|
+
async ({ query, ids }) => {
|
|
715
|
+
try {
|
|
716
|
+
const { card, search, show } = await import("../kb.mjs");
|
|
717
|
+
if (ids?.length) return text(show(ids).join("\n\n---\n\n"));
|
|
718
|
+
const r = search(query);
|
|
719
|
+
return text(r.length ? r.map(card).join("\n\n") : "No matches.");
|
|
720
|
+
} catch (e) {
|
|
721
|
+
return fail(e);
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
// ── palate_research: competitors and craft, looked at live, before ideation (src/loop/research.mjs) ────────────
|
|
727
|
+
server.registerTool(
|
|
728
|
+
"palate_research",
|
|
729
|
+
{
|
|
730
|
+
title: "Research before a new screen",
|
|
731
|
+
description:
|
|
732
|
+
'Call it yourself, without asking, when palate_plan says research (a new screen with no strong taste history, or the user wants something new). Step 1 (job, no urls): palate reads the job and the project (product, audience, mood, a working metaphor, brand or scratch) and returns web searches for competitors and category leaders: run them with your own web search tool, take 4–8 official product homepages, then step 2 (same job, urls): palate looks at them plus its own picks live on this machine (robots.txt respected, a few seconds apart per site, at most 12 pages, never logs in or submits anything), ranks them by craft, the user\'s taste and diversity, and writes research.md and a contact sheet (returned as an image). urls: [] skips the search (palate\'s own picks only). Refine the drafted notes from the screenshots, then palate_direction with from_research. action "verdict" with verdicts records what the user said about a reference ("love R2", "not R4"): palate ranks with it next time and learns rules from repeats.',
|
|
733
|
+
inputSchema: {
|
|
734
|
+
...dirArg,
|
|
735
|
+
action: z.enum(["research", "verdict"]).optional().describe('Default "research"'),
|
|
736
|
+
job: z.string().optional().describe("The job, in the user's words"),
|
|
737
|
+
route: z.string().optional(),
|
|
738
|
+
urls: z.array(z.string()).optional().describe("Step 2: competitor and category-leader homepages from your searches ([] for palate's own picks only)"),
|
|
739
|
+
inspire: z.array(z.string()).optional().describe("Well-crafted pages outside the category the user or you want looked at"),
|
|
740
|
+
name: z.string().optional().describe("The research name (default from the product and screen; verdict: default the newest)"),
|
|
741
|
+
keep: z.number().optional().describe("How many references to keep (4–6 by default)"),
|
|
742
|
+
verdicts: z.array(z.object({ id: z.string().describe("R1, R2 … or the site's name"), verdict: z.enum(["like", "dislike"]), note: z.string().optional() })).optional().describe("verdict: what the user said about references"),
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
async ({ dir, action, job, route, urls, inspire, name, keep, verdicts }) => {
|
|
746
|
+
try {
|
|
747
|
+
const R = await import("../loop/research.mjs");
|
|
748
|
+
const d = projectDir(dir);
|
|
749
|
+
if (action === "verdict") {
|
|
750
|
+
const saved = R.recordVerdicts(d, name, verdicts ?? []);
|
|
751
|
+
return text(saved.length ? `Noted: ${saved.map((s) => `${s.verdict} ${s.ref} ${s.name}`).join(", ")}. palate ranks with it next time; the same trait in two references becomes a proposed rule at palate_taste learn.` : "No matching references: use their ids (R1, R2 …).");
|
|
752
|
+
}
|
|
753
|
+
const res = await R.research(d, job ?? "", { route, urls: urls ?? null, inspire: inspire ?? [], name, keep });
|
|
754
|
+
const sheet = res.data?.contact;
|
|
755
|
+
return { content: [{ type: "text", text: res.text }, ...(sheet && fs.existsSync(sheet) ? [image(sheet)] : [])] };
|
|
756
|
+
} catch (e) {
|
|
757
|
+
return fail(e);
|
|
758
|
+
}
|
|
759
|
+
},
|
|
760
|
+
);
|
|
761
|
+
// ── end palate_research ──────────────────────────────────────────────────────────────────────────────────────────
|
|
762
|
+
|
|
763
|
+
// ── palate_moves: the moves library (src/moves.mjs, kb/moves/*.md) ─────────────────────────────────────────────
|
|
764
|
+
server.registerTool(
|
|
765
|
+
"palate_moves",
|
|
766
|
+
{
|
|
767
|
+
title: "Creative moves",
|
|
768
|
+
description:
|
|
769
|
+
'The moves library: named creative techniques (a hero that paints itself in, an engraved footer with a colour lens, a self-playing product demo, a designed dark mode, a console easter egg…) with palate\'s own recipes (perf, a11y, reduced motion, no-JS fallback, pitfalls, how to verify). You call this yourself, without being asked, whenever you are ideating a screen or upgrading one that is correct but basic: pick the one signature move and one or two supporting moves that fit the brand, then build them from the brief. The user never needs to name a move. action "search" (default) ranks moves by the brand/mood tags (fits minus avoid); pass history (the user\'s kept/reverted moves from ~/.palate) and screen so moves they kept on this kind of screen rise and reverted ones sink. action "show" returns moves in full; action "brief" returns their recipes as compact Markdown to paste into a build brief.',
|
|
770
|
+
inputSchema: {
|
|
771
|
+
action: z.enum(["search", "show", "brief"]).optional().describe('Default "search"'),
|
|
772
|
+
tags: z.array(z.string()).optional().describe("Brand and mood tags, e.g. calm, crafted, editorial, developer, playful (search)"),
|
|
773
|
+
kind: z.string().optional().describe("Only moves of this kind: hero, texture, motion, interaction, type, layout, imagery, footer, easter-egg, navigation, data, sound-free-feedback (comma separated for several)"),
|
|
774
|
+
signature: z.boolean().optional().describe("true: only moves that can be the page's one memorable moment"),
|
|
775
|
+
exclude: z.array(z.string()).optional().describe("Move ids to leave out (e.g. used recently in this project)"),
|
|
776
|
+
limit: z.number().optional().describe("How many to return (default 8)"),
|
|
777
|
+
history: z
|
|
778
|
+
.array(z.object({ id: z.string(), outcome: z.enum(["kept", "reverted"]), kind: z.string().optional().describe("The kind of screen it was used on") }))
|
|
779
|
+
.optional()
|
|
780
|
+
.describe("The user's past outcomes with moves: kept ones rise, reverted ones sink, weighted to screen"),
|
|
781
|
+
screen: z.string().optional().describe("The kind of screen being built (landing, pricing, app…), to weight history"),
|
|
782
|
+
ids: z.array(z.string()).optional().describe("Move ids (show, brief)"),
|
|
783
|
+
},
|
|
784
|
+
},
|
|
785
|
+
async ({ action = "search", tags = [], kind, signature, exclude = [], limit = 8, history = [], screen, ids = [] }) => {
|
|
786
|
+
try {
|
|
787
|
+
const M = await import("../moves.mjs");
|
|
788
|
+
if (action === "show") return text(ids.length ? ids.map(M.showMove).join("\n\n---\n\n") : "Pass ids to show.");
|
|
789
|
+
if (action === "brief") return text(ids.length ? M.moveBrief(ids) : "Pass ids for the brief.");
|
|
790
|
+
const rows = M.matchMoves({ tags, kind, signature, exclude, limit, history, screen });
|
|
791
|
+
return text(M.movesReport(rows, { tags, kind, signature }) + '\n\nThen palate_moves with action "brief" and the chosen ids, and build from it.');
|
|
792
|
+
} catch (e) {
|
|
793
|
+
return fail(e);
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
);
|
|
797
|
+
// ── end palate_moves ─────────────────────────────────────────────────────────────────────────────────────────────
|
|
798
|
+
|
|
799
|
+
server.registerTool(
|
|
800
|
+
"palate_fail",
|
|
801
|
+
{
|
|
802
|
+
title: "The user called a screen bad",
|
|
803
|
+
description: 'Records a failure: shoots the route at 1440 (or keeps the screenshots given), saves it in ~/.palate/failures with the user\'s words, and proposes rules from them (a measured check when palate knows one: "logos too small" becomes logo marks of at least 24px), for the user\'s OK and scope before they apply anywhere. Briefs for that kind of screen show the failure itself under "Don\'t do this". Call it whenever the user rejects something.',
|
|
804
|
+
inputSchema: { ...dirArg, route: z.string().describe("e.g. /"), why: z.string().describe("Why it is bad, in the user's words"), kind: z.string().optional().describe("Kinds of screen it applies to, comma separated (landing, hero, logo cloud, pricing, app…); default: from the route"), shots: z.array(z.string()).optional().describe("Screenshot files to keep instead of shooting"), url: z.string().optional() },
|
|
805
|
+
},
|
|
806
|
+
async ({ dir, route, why, kind, shots, url }) => {
|
|
807
|
+
try {
|
|
808
|
+
const m = await import("../loop/fail.mjs");
|
|
809
|
+
return text(await m.fail(projectDir(dir), route, why, { kind, shots, url }));
|
|
810
|
+
} catch (e) {
|
|
811
|
+
return fail(e);
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
server.registerTool(
|
|
817
|
+
"palate_rule",
|
|
818
|
+
{
|
|
819
|
+
title: "The user's rules",
|
|
820
|
+
description:
|
|
821
|
+
'The user\'s rules. Active rules are enforced; what palate infers (repeated asks, agreed critique findings, failures) is only proposed, with the scopes its evidence supports, and applies nowhere until the user keeps it. list (default): active rules, then the proposals "waiting for your OK"; add: a rule the user stated (active at once; only/route/project narrow it); edit: new words, kinds or project for a rule; remove; learn: turn new evidence into proposals; proposed: the proposals with their evidence and scope options; review_start: opens a board in the user\'s browser where they edit, scope, keep or discard each proposal (returns the link at once; then review_result); review_result: waits up to 50 s for their answers (applied as soon as they press Apply); approve: keep one proposal (id; scope, only, route or kind; rule for new words), only after the user said so in chat; reject: discard one (remembered, so the same evidence won\'t propose it again).',
|
|
822
|
+
inputSchema: {
|
|
823
|
+
action: z.enum(["list", "add", "edit", "remove", "learn", "proposed", "review_start", "review_result", "approve", "reject"]).optional(),
|
|
824
|
+
rule: z.string().optional().describe("add/edit: the rule; approve: new words for it, if the user changed them"),
|
|
825
|
+
id: z.string().optional().describe("edit/remove/approve/reject: the rule id, e.g. R3"),
|
|
826
|
+
kind: z.string().optional().describe("Kinds of screen, comma separated (landing, hero, logo cloud, team, app, all…)"),
|
|
827
|
+
scope: z.string().optional().describe('approve: "suggested" (default), "everywhere", or an option id from action proposed (e.g. "component:RoleBadge", "route:/team", "kinds:team", "project:palate-site")'),
|
|
828
|
+
only: z.string().optional().describe("add/approve: only for this component, e.g. RoleBadge"),
|
|
829
|
+
route: z.string().optional().describe("add/approve: only on this route, e.g. /team"),
|
|
830
|
+
project: z.string().optional().describe('add/edit/approve: only in this project (its folder name, e.g. palate-site); edit with "all" makes it hold in every project again'),
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
async ({ action, rule: words, id, kind, scope, only, route, project }) => {
|
|
834
|
+
try {
|
|
835
|
+
const a = action ?? "list";
|
|
836
|
+
if (a === "review_start") {
|
|
837
|
+
const { startRuleBoard } = await import("../loop/rulereview.mjs");
|
|
838
|
+
const b = await startRuleBoard({ open: true });
|
|
839
|
+
if (!b) return text("Nothing waiting for the user's OK.");
|
|
840
|
+
const entry = { link: b.link, report: null };
|
|
841
|
+
b.done.then((r) => (entry.report = r)).catch((e) => (entry.report = `Rule review failed: ${e.message}`));
|
|
842
|
+
pending.set("rules", entry);
|
|
843
|
+
return text(`The rule review board is open: ${b.link} (${b.count} proposed rule${b.count > 1 ? "s" : ""}). Tell the user to open it if it didn't open by itself: for each rule they can edit the words, choose where it applies, and keep or discard it, then press Apply. Then call palate_rule with action "review_result".`);
|
|
844
|
+
}
|
|
845
|
+
if (a === "review_result") {
|
|
846
|
+
const entry = pending.get("rules");
|
|
847
|
+
if (!entry) return fail('No rule review running. Call palate_rule with action "review_start" first.');
|
|
848
|
+
for (let i = 0; i < 50 && !entry.report; i++) await new Promise((r) => setTimeout(r, 1000));
|
|
849
|
+
return text(entry.report ?? `Still waiting for the user at ${entry.link}. Call review_result again when they say they're done.`);
|
|
850
|
+
}
|
|
851
|
+
const { rule } = await import("../loop/fail.mjs");
|
|
852
|
+
const sub = { remove: "rm", approve: "ok", reject: "no" }[a] ?? a;
|
|
853
|
+
const args = a === "add" ? [words ?? ""] : ["edit", "remove", "approve", "reject"].includes(a) ? [id ?? "", ...(a === "edit" && words ? [words] : [])] : [];
|
|
854
|
+
return text(rule(sub, args, { kind, only, route, scope, project, dir: DEFAULT_DIR, text: a === "approve" ? words : undefined }));
|
|
855
|
+
} catch (e) {
|
|
856
|
+
return fail(e);
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
);
|
|
860
|
+
|
|
861
|
+
// Commands for specific jobs: critique (with the user's review board), fix, vary, details, animate.
|
|
862
|
+
const routeArg = { route: z.string().describe("e.g. / or /pricing"), url: z.string().optional().describe("Dev server URL, if palate shouldn't start or find one") };
|
|
863
|
+
const finding = z.object({
|
|
864
|
+
id: z.string().optional(),
|
|
865
|
+
section: z.union([z.string(), z.number()]).describe('The section number from the critique sheet, or "page"'),
|
|
866
|
+
severity: z.enum(["high", "medium", "low", "idea"]),
|
|
867
|
+
what: z.string().describe("What is wrong, naming the element"),
|
|
868
|
+
why: z.string().optional().describe("Why it matters"),
|
|
869
|
+
fix: z.string().describe("The concrete change"),
|
|
870
|
+
rubric: z.string().optional().describe("The rubric item, e.g. page.2, section.5, M16"),
|
|
871
|
+
width: z.number().optional().describe("1440 or 390 when it only shows at one width"),
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
server.registerTool(
|
|
875
|
+
"palate_critique",
|
|
876
|
+
{
|
|
877
|
+
title: "A designer's review of a page",
|
|
878
|
+
description: "Renders the route at 1440 and 390, crops every section, measures the whole page (repeated devices, screenshots vs live product, logo rows, motion) and returns a critique sheet with the user's taste and a rubric. Write findings in its schema, then call palate_critique_review_start.",
|
|
879
|
+
inputSchema: { ...dirArg, ...routeArg },
|
|
880
|
+
},
|
|
881
|
+
async ({ dir, route, url }) => {
|
|
882
|
+
try {
|
|
883
|
+
const { critique } = await import("../loop/critique.mjs");
|
|
884
|
+
const r = await critique(projectDir(dir), route, { url });
|
|
885
|
+
return { content: [{ type: "text", text: r.sheet }, ...r.images.filter((f) => fs.existsSync(f)).map(image)] };
|
|
886
|
+
} catch (e) {
|
|
887
|
+
return fail(e);
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
server.registerTool(
|
|
893
|
+
"palate_critique_review_start",
|
|
894
|
+
{
|
|
895
|
+
title: "Let the user review the critique",
|
|
896
|
+
description: "Saves the findings and opens a review board in the user's browser, where they agree or disagree with each one (disagreements teach their taste). Returns the link at once; then call palate_critique_review_result.",
|
|
897
|
+
inputSchema: { ...dirArg, route: z.string().optional().describe("The route the critique was for (defaults to the latest critique)"), findings: z.array(finding).optional(), file: z.string().optional().describe("A findings JSON file instead of the findings array") },
|
|
898
|
+
},
|
|
899
|
+
async ({ dir, route, findings, file }) => {
|
|
900
|
+
try {
|
|
901
|
+
const { latestCritique, startBoard } = await import("../loop/critique.mjs");
|
|
902
|
+
const d = projectDir(dir);
|
|
903
|
+
let target = file;
|
|
904
|
+
if (findings?.length) {
|
|
905
|
+
const session = latestCritique(d, route);
|
|
906
|
+
if (!session) throw new Error("No critique yet. Call palate_critique first.");
|
|
907
|
+
target = path.join(session, "findings.json");
|
|
908
|
+
fs.writeFileSync(target, JSON.stringify({ route, findings }, null, 2));
|
|
909
|
+
}
|
|
910
|
+
if (!target) throw new Error("Pass findings (or a findings file).");
|
|
911
|
+
const b = await startBoard(d, target, { open: true });
|
|
912
|
+
const entry = { link: b.link, report: null };
|
|
913
|
+
b.done.then((r) => (entry.report = r)).catch((e) => (entry.report = `Review board failed: ${e.message}`));
|
|
914
|
+
pending.set(`critique|${d}`, entry);
|
|
915
|
+
return text(`The review board is open: ${b.link} (${b.count} findings). Tell the user to open it if it didn't open by itself, mark each finding, and press "Send to the agent". Then call palate_critique_review_result.`);
|
|
916
|
+
} catch (e) {
|
|
917
|
+
return fail(e);
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
server.registerTool(
|
|
923
|
+
"palate_critique_review_result",
|
|
924
|
+
{
|
|
925
|
+
title: "The user's verdicts on the critique",
|
|
926
|
+
description: "Returns which findings the user agreed and disagreed with (and their notes) once they have sent them; otherwise says it is still waiting. Wait for the user before calling again.",
|
|
927
|
+
inputSchema: { ...dirArg },
|
|
928
|
+
},
|
|
929
|
+
async ({ dir }) => {
|
|
930
|
+
const entry = pending.get(`critique|${projectDir(dir)}`);
|
|
931
|
+
if (!entry) return fail("No review board running. Call palate_critique_review_start first.");
|
|
932
|
+
return text(entry.report ?? `Still waiting for the user at ${entry.link}.`);
|
|
933
|
+
},
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
server.registerTool(
|
|
937
|
+
"palate_fix",
|
|
938
|
+
{
|
|
939
|
+
title: "Fix what the critique found",
|
|
940
|
+
description: "A brief from the findings the user agreed with in the latest critique, most severe first, with each section's crops (with no critique: what palate shoot measures as wrong). verify: re-renders each finding's section and returns before | after.",
|
|
941
|
+
inputSchema: { ...dirArg, ...routeArg, verify: z.boolean().optional() },
|
|
942
|
+
},
|
|
943
|
+
async ({ dir, route, url, verify }) => {
|
|
944
|
+
try {
|
|
945
|
+
const { fix } = await import("../loop/fix.mjs");
|
|
946
|
+
const out = await fix(projectDir(dir), route, { url, verify: Boolean(verify) });
|
|
947
|
+
const imgs = verify ? [...out.matchAll(/(\S+(?:before-after|page-before-after)\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f)) : [];
|
|
948
|
+
return { content: [{ type: "text", text: out }, ...imgs.map(image)] };
|
|
949
|
+
} catch (e) {
|
|
950
|
+
return fail(e);
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
server.registerTool(
|
|
956
|
+
"palate_vary",
|
|
957
|
+
{
|
|
958
|
+
title: "Vary one section",
|
|
959
|
+
description: "Fresh variations of one section of a page: copies the page into variant routes and writes one brief per variant (a different structural idea and references each). Build every brief, then palate_pick_start with the name it gives.",
|
|
960
|
+
inputSchema: { ...dirArg, ...routeArg, section: z.string().describe("The section's name, heading, id or a CSS selector"), count: z.number().int().min(2).max(6).optional() },
|
|
961
|
+
},
|
|
962
|
+
async ({ dir, route, url, section, count }) => {
|
|
963
|
+
try {
|
|
964
|
+
const { vary } = await import("../loop/vary.mjs");
|
|
965
|
+
return text(await vary(projectDir(dir), route, { section, count: count ?? 3, url }));
|
|
966
|
+
} catch (e) {
|
|
967
|
+
return fail(e);
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
// ── palate ambition: the remarkable-not-only-correct pass (src/loop/ambition.mjs) ──
|
|
973
|
+
server.registerTool(
|
|
974
|
+
"palate_ambition",
|
|
975
|
+
{
|
|
976
|
+
title: "Make it remarkable, not only correct",
|
|
977
|
+
description:
|
|
978
|
+
"Call it yourself as the last step of every finishing pass (after palate_shoot, palate_states, palate_details, palate_animate), with no question to the user. 1) palate_ambition with route: renders the page at 1440, puts the first screen and every section beside the strongest references (the research step's captures, else the user's references, else palate's library) and the signature move, measures what it can, and returns the sheets plus a strict rubric. 2) Look at the sheets, score every section 0–10 on the rubric (screenshot-worthy, memorable moment, signature delivered, type with character, point of view in copy, hierarchy and rhythm against the references, nothing generic), and call palate_ambition with scores: it picks the weakest section and returns its upgrade brief (what's generic, moves from the moves library with recipes, led by what this user kept before, and what to keep). 3) Upgrade that one section once, then palate_ambition with verify: true and moves (the move ids you used): before | after with new scores, and the outcome is logged so palate learns which upgrades this user keeps. If the similarity gate fails, fix that first.",
|
|
979
|
+
inputSchema: {
|
|
980
|
+
...dirArg,
|
|
981
|
+
...routeArg,
|
|
982
|
+
scores: z.union([z.string(), z.record(z.string(), z.any())]).optional().describe('Step 2: your rubric scores, as a file path or inline: {"sections": [{"index": 2, "scores": {"screenshot": 3, "moment": 2, "signature": null, "type": 4, "copy": 5, "hierarchy": 6, "generic": 3}, "generic": "what reads as template", "keep": "what works"}]}'),
|
|
983
|
+
verify: z.boolean().optional().describe("Step 3: after the one upgrade, re-render that section before | after"),
|
|
984
|
+
moves: z.array(z.string()).optional().describe("With verify: the move ids you used for the upgrade"),
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
async ({ dir, route, url, scores, verify, moves }) => {
|
|
988
|
+
try {
|
|
989
|
+
const { ambition } = await import("../loop/ambition.mjs");
|
|
990
|
+
const out = await ambition(projectDir(dir), route, { url, scores, verify: Boolean(verify), moves });
|
|
991
|
+
const imgs = [...out.matchAll(/(\S+(?:first-screen|sections-side-by-side|before-after)\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f));
|
|
992
|
+
return { content: [{ type: "text", text: out }, ...[...new Set(imgs)].map(image)] };
|
|
993
|
+
} catch (e) {
|
|
994
|
+
return fail(e);
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
);
|
|
998
|
+
// ── end palate ambition ──
|
|
999
|
+
|
|
1000
|
+
server.registerTool(
|
|
1001
|
+
"palate_details",
|
|
1002
|
+
{
|
|
1003
|
+
title: "Upgrade the small parts",
|
|
1004
|
+
description: "Finds generic micro-components (initial avatars, monochrome or missing brand marks, default inputs, bare empty states, raw numbers, emoji, mixed icons, placeholder text, badge soup, missing hover/focus/pressed states, favicon and social image) and returns a brief with each one's upgrade and crops.",
|
|
1005
|
+
inputSchema: { ...dirArg, ...routeArg },
|
|
1006
|
+
},
|
|
1007
|
+
async ({ dir, route, url }) => {
|
|
1008
|
+
try {
|
|
1009
|
+
const { details } = await import("../loop/details.mjs");
|
|
1010
|
+
const out = await details(projectDir(dir), route, { url });
|
|
1011
|
+
const imgs = [...out.matchAll(/(\S+(?:details|logo-cloud-refs)\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f));
|
|
1012
|
+
return { content: [{ type: "text", text: out }, ...imgs.map(image)] };
|
|
1013
|
+
} catch (e) {
|
|
1014
|
+
return fail(e);
|
|
1015
|
+
}
|
|
1016
|
+
},
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
server.registerTool(
|
|
1020
|
+
"palate_states",
|
|
1021
|
+
{
|
|
1022
|
+
title: "Every state of a screen",
|
|
1023
|
+
description: "The last 20%: finds the screen's stateful parts (lists, tables, cards, forms, numbers, images, text from data, buttons that act) from the DOM and its source, renders each state without touching the code (empty, loading, error 500, network down, no answer, long text, 50 items, one item, images failing, 320 wide, 200% zoom with a keyboard pass, forms pressed twice / failing / offline / empty), measures each one, and returns a brief of the missing states with a contact sheet (a row per part, a column per state). Data written in the code is said to be static, and only DOM-level states run. verify: renders them again and returns each earlier finding before | after.",
|
|
1024
|
+
inputSchema: { ...dirArg, ...routeArg, verify: z.boolean().optional() },
|
|
1025
|
+
},
|
|
1026
|
+
async ({ dir, route, url, verify }) => {
|
|
1027
|
+
try {
|
|
1028
|
+
const { states } = await import("../loop/states.mjs");
|
|
1029
|
+
const out = await states(projectDir(dir), route, { url, verify: Boolean(verify) });
|
|
1030
|
+
const imgs = [out.sheet].filter((f) => f && fs.existsSync(f));
|
|
1031
|
+
return { content: [{ type: "text", text: out.text }, ...imgs.map(image)] };
|
|
1032
|
+
} catch (e) {
|
|
1033
|
+
return fail(e);
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
);
|
|
1037
|
+
|
|
1038
|
+
server.registerTool(
|
|
1039
|
+
"palate_animate",
|
|
1040
|
+
{
|
|
1041
|
+
title: "A motion pass",
|
|
1042
|
+
description: "Runs the motion check (frame strips on load and per section, content left invisible, layout shift, reduced motion, JavaScript off) and returns a brief for motion that serves the content, with the motion skills installed. check: only the check, to verify the pass.",
|
|
1043
|
+
inputSchema: { ...dirArg, ...routeArg, check: z.boolean().optional() },
|
|
1044
|
+
},
|
|
1045
|
+
async ({ dir, route, url, check }) => {
|
|
1046
|
+
try {
|
|
1047
|
+
const { animate } = await import("../loop/animate.mjs");
|
|
1048
|
+
const out = await animate(projectDir(dir), route, { url, checkOnly: Boolean(check) });
|
|
1049
|
+
const imgs = [...out.matchAll(/(\S+-strip\.png)/g)].map((m) => m[1]).filter((f) => fs.existsSync(f));
|
|
1050
|
+
return { content: [{ type: "text", text: out }, ...imgs.map(image)] };
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
return fail(e);
|
|
1053
|
+
}
|
|
1054
|
+
},
|
|
1055
|
+
);
|
|
1056
|
+
|
|
1057
|
+
// palate point: the user points at elements in their running app and says what should change; the agent
|
|
1058
|
+
// takes the requests one at a time (next → fix in the source → crop to verify → done).
|
|
1059
|
+
server.registerTool(
|
|
1060
|
+
"palate_point_start",
|
|
1061
|
+
{
|
|
1062
|
+
title: "Let the user point at what to fix",
|
|
1063
|
+
description: "Starts (or reuses) the project's dev server behind palate's overlay and opens it in the user's browser. The user clicks any element, says what should change and sends it; requests queue up as numbered pins. Returns the link; then keep calling palate_point_next for as long as point runs, without waiting to be asked.",
|
|
1064
|
+
inputSchema: { ...dirArg, route: z.string().optional().describe("The page to open, e.g. / or /pricing"), url: z.string().optional().describe("Dev server URL, if palate shouldn't start or find one") },
|
|
1065
|
+
},
|
|
1066
|
+
async ({ dir, route, url }) => {
|
|
1067
|
+
try {
|
|
1068
|
+
const { startPoint } = await import("../point/server.mjs");
|
|
1069
|
+
const d = projectDir(dir);
|
|
1070
|
+
const s = await startPoint(d, { route: route ?? "/", url, open: true });
|
|
1071
|
+
const { pointState } = await import("../point/server.mjs");
|
|
1072
|
+
const agent = (await pointState(d).catch(() => null))?.agent;
|
|
1073
|
+
if (agent?.auto)
|
|
1074
|
+
return text(`palate point is running: ${s.link} (in front of ${s.base}), and palate point --auto is already taking the pins with a headless agent. Give the user the link; you don't need to call palate_point_next.`);
|
|
1075
|
+
return text(`${s.reused ? "palate point is already running" : "palate point is running"}: ${s.link} (in front of ${s.base}). Give the user this link: they turn on point mode with the palate button or Alt+P, click an element, type what should change (or tap a chip) and send. For something inside a modal, a dropdown or a tab, they open it first, then press Alt+P: palate reopens it for every check. The overlay shows them whether you are listening.
|
|
1076
|
+
|
|
1077
|
+
Now listen, without waiting to be asked:
|
|
1078
|
+
- Call palate_point_next right away, and again every time it returns, for as long as point is running. It waits up to 55 s; "Still listening" means nothing came yet: call it again at once.
|
|
1079
|
+
- For each pin: fix it, palate_point_crop to check (it shows both widths; a "broke" at the other width needs fixing too), palate_point_done, then straight back to palate_point_next.
|
|
1080
|
+
- Don't end your turn while point is running. If the user writes something else in chat, answer it, then go back to palate_point_next.
|
|
1081
|
+
- Stop only when the user says they're done, or palate_point_next says they pressed Done.`);
|
|
1082
|
+
} catch (e) {
|
|
1083
|
+
return fail(e);
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
server.registerTool(
|
|
1089
|
+
"palate_point_next",
|
|
1090
|
+
{
|
|
1091
|
+
title: "The next thing the user pointed at",
|
|
1092
|
+
description: "Waits up to 55 s for the next pin and returns it, marked as being worked on: the user's words and chips, the element (selector, DOM path, component and owners, trimmed HTML, key styles), a crop, and the likely source lines. A pin inside an opened state (a modal, a dropdown, a tab) says which steps open it; palate replays them before every crop. \"Still listening\" means nothing came yet: call it again at once, and keep calling it while point runs (each call tells the overlay you're listening). Stop when it says the user pressed Done.",
|
|
1093
|
+
inputSchema: { ...dirArg, wait: z.number().int().min(1).max(55).optional().describe("Seconds to wait (default 55)") },
|
|
1094
|
+
},
|
|
1095
|
+
async ({ dir, wait }) => {
|
|
1096
|
+
try {
|
|
1097
|
+
const { nextRequest, formatRequest } = await import("../point/server.mjs");
|
|
1098
|
+
const d = projectDir(dir);
|
|
1099
|
+
const r = await nextRequest(d, { wait: wait ?? 55, agent: "mcp" });
|
|
1100
|
+
// Cheap on purpose: idle polling is one short line, no images.
|
|
1101
|
+
if (!r) return text("Still listening. Nothing new; call palate_point_next again.");
|
|
1102
|
+
if (r.stopped) return text("The user stopped point mode (Done in the overlay). Stop calling palate_point_next; tell them what you fixed. If they send more pins later, they can ask you to listen again.");
|
|
1103
|
+
const asked = r.other?.asked && r.other.before?.crop && fs.existsSync(r.other.before.crop) ? [{ type: "text", text: `Before at ${r.other.width} (where the user says the problem is):` }, image(r.other.before.crop)] : [];
|
|
1104
|
+
return { content: [{ type: "text", text: `${formatRequest(r, d)}\n- When it's done, call palate_point_next again right away: the user may have queued more.` }, ...(r.crop && fs.existsSync(r.crop) ? [{ type: "text", text: `Before at ${r.viewport?.width ?? "the pinned width"} (when the user pointed):` }, image(r.crop)] : []), ...asked] };
|
|
1105
|
+
} catch (e) {
|
|
1106
|
+
return fail(e);
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
server.registerTool(
|
|
1112
|
+
"palate_point_crop",
|
|
1113
|
+
{
|
|
1114
|
+
title: "Look at the element now",
|
|
1115
|
+
description: "Re-shoots a request's element after your edit at both widths: the one the user pinned at (their viewport and scroll) and the other one (390 for a desktop pin, 1440 for a phone pin, or the width the user's words name: \"on mobile\", \"at 768px\"). Returns before and now at each, and a verdict for the other width: unchanged, changed (shown; often expected for shared CSS) or broke (the page now scrolls sideways, the element disappeared, or text in it is cut off). Fix a broke before palate_point_done: done refuses while it stands. A pin inside an opened state (a modal, a dropdown, a tab) is shot in that state: palate replays the steps that opened it first, and says when it couldn't reopen it (not checked there, never a break).",
|
|
1116
|
+
inputSchema: { ...dirArg, id: z.string() },
|
|
1117
|
+
},
|
|
1118
|
+
async ({ dir, id }) => {
|
|
1119
|
+
try {
|
|
1120
|
+
const { cropRequest } = await import("../point/server.mjs");
|
|
1121
|
+
const c = await cropRequest(projectDir(dir), id);
|
|
1122
|
+
const has = (f) => f && fs.existsSync(f);
|
|
1123
|
+
const w = c.viewport?.width ?? "the pinned width";
|
|
1124
|
+
const main = c.removed ? `At ${w} (pinned): removed, the element is gone (as asked), so there's no after crop. Before:` : `At ${w} (pinned): before | now${c.trail ? ` (in the state opened by: ${c.trail})` : ""}${c.found === false ? " (the selector no longer matches; this is the region where the element was)" : ""}`;
|
|
1125
|
+
const o = c.other;
|
|
1126
|
+
const state = (s) => (s === "hidden" ? "hidden at this width, so the screen around where it was" : s === "missing" ? "not on the page at this width, so the screen around where it was" : null);
|
|
1127
|
+
const other = o
|
|
1128
|
+
? [
|
|
1129
|
+
{ type: "text", text: `${o.note} Before | now${[state(o.beforeState) && `before: ${state(o.beforeState)}`, state(o.afterState) && `now: ${state(o.afterState)}`].filter(Boolean).map((x) => ` (${x})`).join("")}:` },
|
|
1130
|
+
...(has(o.before) ? [image(o.before)] : []),
|
|
1131
|
+
...(has(o.after) ? [image(o.after)] : []),
|
|
1132
|
+
]
|
|
1133
|
+
: [];
|
|
1134
|
+
const stuck = c.reopen?.state === "couldn't reopen" ? [{ type: "text", text: `At ${w}: palate couldn't reopen the state this element sits in (${c.reopen.why}), so "now" is the page as it loads. That isn't a break; check the change in the code.` }] : [];
|
|
1135
|
+
const fix = o?.verdict === "broke" ? [{ type: "text", text: `Fix ${o.width} before palate_point_done: it refuses while ${o.width} is broken. Then palate_point_crop again.` }] : [];
|
|
1136
|
+
return { content: [{ type: "text", text: main }, ...(has(c.before) ? [image(c.before)] : []), ...(c.removed ? [] : [image(c.file)]), ...stuck, ...other, ...fix] };
|
|
1137
|
+
} catch (e) {
|
|
1138
|
+
return fail(e);
|
|
1139
|
+
}
|
|
1140
|
+
},
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1143
|
+
server.registerTool(
|
|
1144
|
+
"palate_point_done",
|
|
1145
|
+
{
|
|
1146
|
+
title: "Mark a pointed request done",
|
|
1147
|
+
description: "Marks the request done with a one-line summary, re-shoots the element at both widths (the pinned one and the other one) and saves the after crops. The user's pin turns done and shows before and after at both. Refuses while the other width is broken (the page scrolls sideways, the element disappeared, or text in it is cut off): fix it, or pass override only when the user accepts it. Then call palate_point_next for the next one.",
|
|
1148
|
+
inputSchema: { ...dirArg, id: z.string(), summary: z.string().describe("One line: what changed"), removed: z.boolean().optional().describe("You removed the element (as asked): the after view says Removed instead of cropping what sits there now"), override: z.boolean().optional().describe("Mark it done even though the other width broke. Only when the user accepts that; the pin keeps the warning") },
|
|
1149
|
+
},
|
|
1150
|
+
async ({ dir, id, summary, removed, override }) => {
|
|
1151
|
+
try {
|
|
1152
|
+
const { doneRequest } = await import("../point/server.mjs");
|
|
1153
|
+
const r = await doneRequest(projectDir(dir), id, summary, { removed: Boolean(removed), override: Boolean(override) });
|
|
1154
|
+
const o = r.other?.width ? r.other : null;
|
|
1155
|
+
const imgs = [r.crop, r.after, o?.before?.crop, o?.after?.crop].filter((f) => f && fs.existsSync(f));
|
|
1156
|
+
return { content: [{ type: "text", text: `Pin ${r.n} is done${r.removed ? " (removed: the pin shows Removed as the after)" : ""}. Before | after at ${r.viewport?.width ?? "the pinned width"} below${o ? `, then at ${o.width}${o.after?.removed ? " (removed there)" : ""}. ${o.note ?? ""}${o.overridden ? " Done anyway (override): the pin shows the warning to the user." : ""}` : "."} Now call palate_point_next (keep listening; don't end your turn).` }, ...imgs.map(image)] };
|
|
1157
|
+
} catch (e) {
|
|
1158
|
+
return fail(e);
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
);
|
|
1162
|
+
|
|
1163
|
+
const refsStep = (refs) => (refs ? ` My references: ${refs}. palate_refs_add each one with what I like and dislike about it; if I haven't said, ask me first.` : "");
|
|
1164
|
+
const finishStep = "Then the finishing pass on that page: palate_shoot at both widths (a failing rule or similarity gate is fixed first, by you), palate_states (build the missing states it lists, then verify: true), palate_details, palate_animate with check if it moves, then palate_ambition (score the sections, upgrade the weakest once, verify). Then palate_point_start and give me the link: I point at what's wrong and you fix exactly that (palate_point_next, palate_point_crop, palate_point_done). Keep calling palate_point_next without waiting for me to tell you, until I say I'm done or press Done in the overlay.";
|
|
1165
|
+
const loopPrompt = (screen, refs) =>
|
|
1166
|
+
`Design ${screen} with palate: build, finish, then I point at what's wrong.${refsStep(refs)}
|
|
1167
|
+
1. palate_plan first. direct: change it in place, one palate_shoot (widths [1440], fold: true), palate_check, and stop.
|
|
1168
|
+
2r. research (a new screen, or I want something new): palate_research with my words, run its searches yourself, palate_research again with the homepages you found, refine its notes from the screenshots, then palate_direction with from_research and 3 art directions; palate picks one or opens the picker if it is a real judgement call. Then step 3. Don't ask me to run anything.
|
|
1169
|
+
2. direction (a question or a problem): palate_direction with my words, answer its brief with 3 ideas and rough wireframes (no code), then palate_direction with the ideas and tell me the picker is open; palate_direction with action "result" after I've chosen. Then step 3.
|
|
1170
|
+
3. build (the default): palate_context, then palate_build with the job in my words and the route; one builder follows its brief. ${finishStep}
|
|
1171
|
+
4. quick or full (variants): only when I asked to explore or it's a new kind of screen. Tell me the token cost the triage shows and offer build mode first; if I still want variants, run the explore flow (palate_variants, palate_pick_start with ready, palate_build with the loop's name).
|
|
1172
|
+
Throughout: every brief opens with my rules; if I reject something, palate_fail with my words. At the end, palate_taste with action learn, and if it proposes rules, show them to me (palate_rule review_start).`;
|
|
1173
|
+
|
|
1174
|
+
server.registerPrompt(
|
|
1175
|
+
"design",
|
|
1176
|
+
{ title: "Design a screen", description: "Triage, then the default: one build from your references and rules, the finishing pass, then you point at what's wrong. Ideas first for a job-level question.", argsSchema: { screen: z.string().describe("What to build"), refs: z.string().optional().describe("Your references: image paths or URLs, comma separated") } },
|
|
1177
|
+
({ screen, refs }) => ({ messages: [{ role: "user", content: { type: "text", text: loopPrompt(screen, refs) } }] }),
|
|
1178
|
+
);
|
|
1179
|
+
server.registerPrompt(
|
|
1180
|
+
"explore",
|
|
1181
|
+
{ title: "Explore variants", description: "Opt-in: sketches in different directions, your blind pick as soon as they're ready, the winner built out. Costs more tokens than a build.", argsSchema: { screen: z.string(), refs: z.string().optional() } },
|
|
1182
|
+
({ screen, refs }) =>
|
|
1183
|
+
say(`Explore ${screen} with palate: I want options.${refsStep(refs)}
|
|
1184
|
+
1. palate_plan with mode "quick" (one section) or "full" (a page); tell me its token estimate next to build mode's.
|
|
1185
|
+
2. palate_context and palate_taste; palate_variants with the job in my words (briefs are sketches: first screen and centrepiece). Build every brief in parallel subagents if you can; each shoots once at 1440 with fold: true.
|
|
1186
|
+
3. Right away, palate_pick_start with the ready number the variants result gives (more than 4 variants: palate_judge_render with ready, a fresh judge scores, palate_judge_score first); tell me the picker is coming. palate_pick_result after I've picked.
|
|
1187
|
+
4. palate_build with the loop's name; one builder completes the winner, moves it to the real route and deletes the other variants. ${finishStep}
|
|
1188
|
+
5. palate_polish; palate_taste with action learn (show me any proposed rules: palate_rule review_start).`),
|
|
1189
|
+
);
|
|
1190
|
+
server.registerPrompt(
|
|
1191
|
+
"direction",
|
|
1192
|
+
{ title: "Ideas for a problem", description: "Describe the problem (\"users wait 20 seconds here\"); get 3 short ideas with rough wireframes, pick one, then it's built.", argsSchema: { problem: z.string().describe("The problem, in your words"), route: z.string().optional() } },
|
|
1193
|
+
({ problem, route }) =>
|
|
1194
|
+
say(`Help me with this, with palate: "${problem}"${route ? ` (on ${route})` : ""}. palate_direction with the problem${route ? " and route" : ""}; read its brief and answer with 3 different ideas (a name, 2–3 sentences, a rough ASCII or tiny HTML wireframe each; no code). Then palate_direction with the ideas, tell me the picker is open, and palate_direction with action "result" after I've chosen. Then palate_build with the job and route (it carries the idea I chose), one builder. ${finishStep}`),
|
|
1195
|
+
);
|
|
1196
|
+
server.registerPrompt(
|
|
1197
|
+
"polish",
|
|
1198
|
+
{ title: "Polish a screen", description: "Final polish pass on an existing screen.", argsSchema: { route: z.string() } },
|
|
1199
|
+
({ route }) => ({ messages: [{ role: "user", content: { type: "text", text: `Polish ${route} with palate: palate_taste, palate_shoot to see it, then palate_polish and apply its steps; shoot again and keep only changes that look better; palate_check at the end.` } }] }),
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
const say = (t) => ({ messages: [{ role: "user", content: { type: "text", text: t } }] });
|
|
1203
|
+
server.registerPrompt(
|
|
1204
|
+
"critique",
|
|
1205
|
+
{ title: "Critique a page", description: "A designer's review, section by section; you agree or disagree with each finding.", argsSchema: { route: z.string() } },
|
|
1206
|
+
({ route }) => say(`Critique ${route} with palate: palate_critique, then open every section crop and write specific findings in its schema (whole-page findings for repeated devices, static screenshots and missing motion; at least one idea for each generic section). Then palate_critique_review_start with the findings and give me the link; palate_critique_review_result after I've sent my verdicts. Don't change any code yet.`),
|
|
1207
|
+
);
|
|
1208
|
+
server.registerPrompt(
|
|
1209
|
+
"fix",
|
|
1210
|
+
{ title: "Fix a page", description: "Fixes the critique findings you agreed with (or what is measurably wrong), then verifies each one.", argsSchema: { route: z.string() } },
|
|
1211
|
+
({ route }) => say(`Fix ${route} with palate: palate_fix and follow its brief, most severe first, keeping the design's idea. Then palate_fix with verify: true, open each before | after and tell me per finding: fixed, partly or not. Finish with palate_check and the project's typecheck.`),
|
|
1212
|
+
);
|
|
1213
|
+
server.registerPrompt(
|
|
1214
|
+
"vary",
|
|
1215
|
+
{ title: "Vary one section", description: "Fresh variations of one section, shown in the page; you pick.", argsSchema: { route: z.string(), section: z.string().describe("The section's name, heading or selector") } },
|
|
1216
|
+
({ route, section }) => say(`Vary the "${section}" section of ${route} with palate: palate_vary, build every brief (parallel subagents if you can, each changing only the section in its route), then palate_pick_start with the name it gave and give me the link; palate_pick_result after I've picked. Then do what the result says: move the winner into the page and delete every variant route.`),
|
|
1217
|
+
);
|
|
1218
|
+
server.registerPrompt(
|
|
1219
|
+
"details",
|
|
1220
|
+
{ title: "Upgrade the small parts", description: "Avatars, brand marks, inputs, empty states, numbers, icons, states, favicon: the micro-details pass.", argsSchema: { route: z.string() } },
|
|
1221
|
+
({ route }) => say(`Run palate's details pass on ${route}: palate_details, look at the crops, and upgrade each part as its brief says, with real content only (real people's photos or generated avatars, official brand marks). Then palate_details again to confirm they're gone, and palate_shoot to look at the result.`),
|
|
1222
|
+
);
|
|
1223
|
+
server.registerPrompt(
|
|
1224
|
+
"states",
|
|
1225
|
+
{ title: "Every state of a screen", description: "Empty, loading, error, long text, many items, failing images, narrow, keyboard, forms pressed twice: rendered, measured, and the missing ones built.", argsSchema: { route: z.string() } },
|
|
1226
|
+
({ route }) => say(`Run palate's states pass on ${route}: palate_states, open the contact sheet and the crops of each finding, then build the missing states its brief lists, most important first, with the project's own components and the copy guidance (empty states say what to do next; errors say what happened and what to do). Don't change the design otherwise. Then palate_states with verify: true, open the before | after and tell me per finding: fixed or still failing. If palate says the data is static, tell me which states it couldn't simulate.`),
|
|
1227
|
+
);
|
|
1228
|
+
server.registerPrompt(
|
|
1229
|
+
"animate",
|
|
1230
|
+
{ title: "Add motion that serves the content", description: "A motion pass with a frame-strip check, reduced motion and no-JS verified.", argsSchema: { route: z.string() } },
|
|
1231
|
+
({ route }) => say(`Run palate's motion pass on ${route}: palate_animate, read the strips and the brief, read the motion skills it names, and add motion where it explains something (never hiding content). Then palate_animate with check: true and look at the strips: no hidden content (normal, reduced motion, JavaScript off), no layout shift.`),
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
server.registerPrompt(
|
|
1235
|
+
"point",
|
|
1236
|
+
{ title: "Fix what I point at", description: "Opens your app with palate's overlay: click any element, say what should change, and the agent fixes exactly that, live.", argsSchema: { route: z.string().optional().describe("The page to open, e.g. / or /pricing") } },
|
|
1237
|
+
({ route }) =>
|
|
1238
|
+
say(`Run palate point${route ? ` on ${route}` : ""}: palate_point_start${route ? ` with route "${route}"` : ""} and give me the link. Then keep listening for my pins without waiting for me to say anything, and don't end your turn while point is running:
|
|
1239
|
+
- Call palate_point_next, and call it again every time it returns. "Still listening" means nothing came yet: call it again at once.
|
|
1240
|
+
- For each pin: fix exactly that element or section in the source it points to (confirm the file by the text and classes; change nothing around it; keep the project's tokens and components); palate_point_crop to verify it looks better at both widths, fixing once more if not (and fixing the other width if it broke); palate_point_done with the id and a one-line summary; then straight back to palate_point_next.
|
|
1241
|
+
- For a pin with "Variants of this", run palate_vary with that route and the pin's selector as the section, build the briefs, and palate_pick_start instead of editing in place; mark it done once the pick is made real.
|
|
1242
|
+
- If I write something else in chat, answer it, then go back to palate_point_next.
|
|
1243
|
+
- Stop only when I say I'm done, or palate_point_next says I pressed Done in the overlay.`),
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
await server.connect(new StdioServerTransport());
|