better-design 0.31.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1232,6 +1232,7 @@ var init_tool_names = __esm({
1232
1232
  "get-design-config",
1233
1233
  "get-design-system-kit",
1234
1234
  "get-design-system-status",
1235
+ "get-inspiration",
1235
1236
  "get-jig-guide",
1236
1237
  "get-motion-guide",
1237
1238
  "get-react-native-guide",
@@ -1269,6 +1270,7 @@ var init_tool_names = __esm({
1269
1270
  "get-design-config",
1270
1271
  "get-design-system-kit",
1271
1272
  "get-design-system-status",
1273
+ "get-inspiration",
1272
1274
  "get-jig-guide",
1273
1275
  "get-motion-guide",
1274
1276
  "get-react-native-guide",
@@ -1374,6 +1376,7 @@ Better Design provides design systems, design principles and UI review checks fo
1374
1376
 
1375
1377
  - find-design-system searches the catalog. preview-design-system shows one system. get-design-system-kit returns its install command or CSS kit.
1376
1378
  - create-design-system generates a custom system. get-design-system-status reports its progress.
1379
+ - get-inspiration returns real App Store screenshot sets from shipped apps as images.
1377
1380
  - get-ui-principle, get-ux-principle and the platform guides return design principles and best practices on a topic.
1378
1381
  - get-review-rules, check-comprehension, inspect-spacing and review-dashboard-structure check finished interfaces.
1379
1382
  - find-icon-library, find-icons and install-icons return icon component files.`;
@@ -1396,7 +1399,8 @@ Better Design provides design systems, design principles and UI review checks fo
1396
1399
  "install-icons": "Fetches the named icons from the public Iconify API and returns one React component file per icon, plus an index.ts barrel, as text for the caller to save. It writes no files. Takes a `libraryId` and icon names from find-icons.",
1397
1400
  "detect-icon-library": "Reads the contents of the `.library.json` file that an earlier icon install left in a project and returns the icon library the project uses and the icons it has installed, or reports that none is recorded.",
1398
1401
  "find-design-system": "Searches Better Design's catalog of design systems by industry, personality or a description of the interface, and returns ranked matches with a preview link for each. Use it when the user wants a design system or a visual direction for an app, site, page, dashboard or presentation.",
1399
- "get-design-system-kit": "Returns the build kit for a catalog design system or a generated project. `target` picks the format: `react` returns one shadcn install command for every component and the tokens, `html` returns framework-free token and component CSS for inline canvases, `react-native` and `swiftui` return mobile kits, `shopify` returns Liquid theme files, and `dtcg` returns a W3C Design Tokens document. `component` narrows the React install to one component.",
1402
+ "get-design-system-kit": "Returns the build kit for a catalog design system or a generated project. `target` picks the format: `react` returns one shadcn install command for every component and the tokens, `html` returns framework-free token and component CSS for inline canvases, `react-native` and `swiftui` return mobile kits, `shopify` returns Liquid theme files, `dtcg` returns a W3C Design Tokens document, and `store-screenshots` returns slide files and a render script for App Store and Google Play screenshots. `component` narrows the React install to one component.",
1403
+ "get-inspiration": 'Returns real reference designs from Better Design\'s library as images. `type` picks the collection: today `app-store-screenshots`, the App Store screenshot sets of shipped apps, each with its title and category. `category`, such as "Productivity", narrows the sets, and `limit` sets how many come back, 1 to 6.',
1400
1404
  "create-design-system": "Starts generating a custom design system from a description of the product and its look, and returns the project id and project URL at once. Generation runs in the background and typically takes 1 to 3 minutes. get-design-system-status reports progress and returns the install command when it finishes. `baseDesignSystemId` starts from a catalog system, and `brandCss` builds from an existing brand token sheet, such as the output of extract-design-system. Each call uses one credit from the user's Better Design account. Anyone with the registry or preview URL can view the generated source and previews.",
1401
1405
  "get-design-system-status": "Reports the progress of a design system started with create-design-system. While generation runs it returns the current stage. When generation finishes it returns the live preview URL and the `npx shadcn add` install command.",
1402
1406
  "preview-design-system": "Returns a visual preview of a catalog design system: its palette, typography, buttons, card, input and badge, with a link to the full live showcase. Takes an id from find-design-system.",
@@ -1891,6 +1895,7 @@ function buildKitOutputSchema(enableShopifyKit = true) {
1891
1895
  componentExampleHtml: z5.string().min(1).optional().describe("HTML target only. Starter markup that demonstrates the required .ds wrapper and ds-* component classes."),
1892
1896
  tokensJson: z5.string().min(1).optional().describe("DTCG target only. The design tokens as a W3C Design Tokens (DTCG) document. Write it to tokens.json; every token tool reads this format, including Style Dictionary, Tokens Studio, Terrazzo, and the Figma plugins that create variables."),
1893
1897
  nativeFiles: z5.array(z5.object({ path: z5.string().min(1), content: z5.string().min(1) })).optional().describe("React Native and SwiftUI targets only. Write each file verbatim at its path: this system's stored theme and material, and the primitives that read it."),
1898
+ screenshotFiles: z5.array(z5.object({ path: z5.string().min(1), content: z5.string().min(1) })).optional().describe("Store screenshots target only. Write each file verbatim at its path, then follow buildInstructions to capture screens, write the slides and render the PNG files."),
1894
1899
  unsupportedTokens: z5.array(z5.object({
1895
1900
  name: z5.string().min(1),
1896
1901
  value: z5.string().min(1),
@@ -10867,6 +10872,184 @@ ${FAQ_CARD_ITEM}
10867
10872
  }
10868
10873
  });
10869
10874
 
10875
+ // ../shared/src/mcp/store-screenshots.ts
10876
+ function renderScript() {
10877
+ const sizes = JSON.stringify(STORE_SCREENSHOT_SIZES);
10878
+ const feature = JSON.stringify(PLAY_FEATURE_GRAPHIC);
10879
+ return `// Renders store screenshots from slides.json at the exact store sizes. [Better Design]
10880
+ // Run once per change: node store-screenshots/render.mjs
10881
+ import { chromium } from "playwright";
10882
+ import { mkdirSync, readFileSync } from "node:fs";
10883
+ import { dirname, join } from "node:path";
10884
+ import { fileURLToPath, pathToFileURL } from "node:url";
10885
+
10886
+ const here = dirname(fileURLToPath(import.meta.url));
10887
+ const SIZES = ${sizes};
10888
+ const FEATURE = ${feature};
10889
+ const RANGE = ${JSON.stringify(STORE_SLIDE_RANGE)};
10890
+ const deck = JSON.parse(readFileSync(join(here, "slides.json"), "utf8"));
10891
+
10892
+ if (deck.slides.length < RANGE.min || deck.slides.length > RANGE.max) {
10893
+ throw new Error(\`slides.json holds \${deck.slides.length} slides. Use \${RANGE.min} to \${RANGE.max}: Google Play shows at most \${RANGE.max}.\`);
10894
+ }
10895
+
10896
+ // App Store Connect rejects a PNG with an alpha channel, so check the header, not the look.
10897
+ function check(file, size) {
10898
+ const png = readFileSync(file);
10899
+ const width = png.readUInt32BE(16), height = png.readUInt32BE(20), colorType = png[25];
10900
+ if (width !== size.width || height !== size.height) throw new Error(\`\${file} is \${width} x \${height}, not \${size.width} x \${size.height}.\`);
10901
+ if (colorType !== 2) throw new Error(\`\${file} has a transparency layer. Give every slide an opaque background.\`);
10902
+ }
10903
+
10904
+ async function shoot(browser, size, data, file) {
10905
+ const page = await browser.newPage({ viewport: { width: size.width, height: size.height }, deviceScaleFactor: 1 });
10906
+ await page.addInitScript((slide) => { window.__SLIDE__ = slide; }, data);
10907
+ await page.goto(pathToFileURL(join(here, "slide.html")).href);
10908
+ await page.evaluate(async () => {
10909
+ await document.fonts.ready;
10910
+ await Promise.all([...document.images].map((img) => img.decode().catch(() => { throw new Error("Missing capture: " + img.src); })));
10911
+ });
10912
+ await page.screenshot({ path: file, type: "png" });
10913
+ await page.close();
10914
+ check(file, size);
10915
+ }
10916
+
10917
+ const browser = await chromium.launch();
10918
+ try {
10919
+ for (const size of SIZES) {
10920
+ mkdirSync(join(here, "out", size.id), { recursive: true });
10921
+ for (const [i, slide] of deck.slides.entries()) {
10922
+ const file = join(here, "out", size.id, String(i + 1).padStart(2, "0") + ".png");
10923
+ await shoot(browser, size, { kind: "slide", app: deck.app, slide, frame: size.frame }, file);
10924
+ console.log("wrote", file);
10925
+ }
10926
+ }
10927
+ mkdirSync(join(here, "out", FEATURE.id), { recursive: true });
10928
+ const feature = join(here, "out", FEATURE.id, "feature-graphic.png");
10929
+ await shoot(browser, FEATURE, { kind: "feature", app: deck.app, slide: deck.featureGraphic, frame: "none" }, feature);
10930
+ console.log("wrote", feature);
10931
+ } finally {
10932
+ await browser.close();
10933
+ }
10934
+ `;
10935
+ }
10936
+ function buildStoreScreenshotFiles(tokensCss) {
10937
+ return [
10938
+ { path: "store-screenshots/theme.css", content: tokensCss },
10939
+ { path: "store-screenshots/slides.json", content: `${JSON.stringify(EXAMPLE_SLIDES, null, 2)}
10940
+ ` },
10941
+ { path: "store-screenshots/slide.html", content: SLIDE_HTML },
10942
+ { path: "store-screenshots/render.mjs", content: renderScript() }
10943
+ ];
10944
+ }
10945
+ function fencedFile(file) {
10946
+ const longest = Math.max(0, ...[...file.content.matchAll(/`+/g)].map((run) => run[0].length));
10947
+ const fence3 = "`".repeat(Math.max(3, longest + 1));
10948
+ return `## ${file.path}
10949
+
10950
+ ${fence3}
10951
+ ${file.content.replace(/\n$/, "")}
10952
+ ${fence3}`;
10953
+ }
10954
+ function storeScreenshotText(name, files) {
10955
+ return [storeScreenshotInstructions(name), ...files.map(fencedFile)].join("\n\n");
10956
+ }
10957
+ function storeScreenshotInstructions(name) {
10958
+ const sizes = [...STORE_SCREENSHOT_SIZES, PLAY_FEATURE_GRAPHIC].map((size) => `- ${size.label}: ${size.width} x ${size.height}, in \`store-screenshots/out/${size.id}/\``).join("\n");
10959
+ return [
10960
+ `# Store screenshots in ${name}`,
10961
+ "",
10962
+ "1. Write each file in `screenshotFiles` verbatim at its path.",
10963
+ `2. Capture ${STORE_SLIDE_RANGE.min} to ${STORE_SLIDE_RANGE.max} app screens into \`store-screenshots/captures/\`. iOS Simulator: \`xcrun simctl io booted screenshot store-screenshots/captures/01.png\`. Android emulator: \`adb exec-out screencap -p > store-screenshots/captures/01.png\`.`,
10964
+ '3. Edit `slides.json`: one outcome per slide, a headline a person understands in one second, and no claims such as "Best", "#1" or "Free", which Google Play rejects. Check every headline with Better Design\'s `check-comprehension` tool before you render.',
10965
+ "4. Install Playwright once as a dev dependency with the project's package manager, then its browser: `npx playwright install chromium`.",
10966
+ "5. Google Play asks the feature graphic to avoid pure white, black and dark gray. When this system's primary color is one of those, set `--feature-background` and `--feature-foreground` to a brand color pair at the end of `theme.css`.",
10967
+ "6. Run `node store-screenshots/render.mjs`. It stops on any file with the wrong size or a transparency layer.",
10968
+ "",
10969
+ "It writes:",
10970
+ sizes,
10971
+ "",
10972
+ "Google Play screenshots carry no drawn phone, because Google asks for no device imagery. Show the user every PNG before they upload it."
10973
+ ].join("\n");
10974
+ }
10975
+ var STORE_SCREENSHOT_SIZES, PLAY_FEATURE_GRAPHIC, STORE_SLIDE_RANGE, EXAMPLE_SLIDES, SLIDE_HTML;
10976
+ var init_store_screenshots = __esm({
10977
+ "../shared/src/mcp/store-screenshots.ts"() {
10978
+ "use strict";
10979
+ STORE_SCREENSHOT_SIZES = [
10980
+ { id: "iphone-6-9", label: "iPhone 6.9 inch", width: 1320, height: 2868, frame: "phone" },
10981
+ { id: "play-phone", label: "Google Play phone", width: 1080, height: 1920, frame: "none" }
10982
+ ];
10983
+ PLAY_FEATURE_GRAPHIC = {
10984
+ id: "play-feature-graphic",
10985
+ label: "Google Play feature graphic",
10986
+ width: 1024,
10987
+ height: 500,
10988
+ frame: "none"
10989
+ };
10990
+ STORE_SLIDE_RANGE = { min: 3, max: 8 };
10991
+ EXAMPLE_SLIDES = {
10992
+ app: "Your app",
10993
+ slides: [
10994
+ { headline: "Say the first outcome in five words", subhead: "", capture: "captures/01.png" },
10995
+ { headline: "Show the second outcome next", subhead: "", capture: "captures/02.png" },
10996
+ { headline: "End on the reason to stay", subhead: "", capture: "captures/03.png" }
10997
+ ],
10998
+ featureGraphic: { headline: "Your app, in one line" }
10999
+ };
11000
+ SLIDE_HTML = `<!doctype html>
11001
+ <html lang="en">
11002
+ <head>
11003
+ <meta charset="utf-8">
11004
+ <link rel="stylesheet" href="theme.css">
11005
+ <style>
11006
+ html, body { margin: 0; width: 100vw; height: 100vh; overflow: hidden; }
11007
+ body { background: var(--background); color: var(--foreground); font-family: var(--font-sans, system-ui, sans-serif);
11008
+ display: flex; flex-direction: column; align-items: center; box-sizing: border-box; padding: 7vh 8vw 0; gap: 3vh; }
11009
+ h1 { margin: 0; font-size: 7.5vw; line-height: 1.12; font-weight: 700; text-align: center; text-wrap: balance; letter-spacing: -0.02em; }
11010
+ p { margin: 0; font-size: 4vw; line-height: 1.35; text-align: center; color: var(--muted-foreground); text-wrap: balance; }
11011
+ .shot { flex: 1; min-height: 0; width: 100%; display: flex; justify-content: center; align-items: flex-start; }
11012
+ .shot img { display: block; height: 100%; max-width: 100%; object-fit: cover; object-position: top; }
11013
+ .phone { height: 104%; aspect-ratio: 9 / 19.5; box-sizing: border-box; padding: 1.6vw; border-radius: 11vw 11vw 0 0;
11014
+ background: var(--foreground); overflow: hidden; }
11015
+ .phone img { width: 100%; height: 100%; border-radius: 9.4vw 9.4vw 0 0; }
11016
+ .bare img { border-radius: calc(var(--radius, 0.75rem) * 3) calc(var(--radius, 0.75rem) * 3) 0 0; }
11017
+ body.feature { background: var(--feature-background, var(--primary)); color: var(--feature-foreground, var(--primary-foreground)); justify-content: center; align-items: flex-start; padding: 0 10vw; gap: 4vh; }
11018
+ body.feature h1 { font-size: 7vw; text-align: left; }
11019
+ body.feature .app { font-size: 3.4vw; font-weight: 600; opacity: 0.85; }
11020
+ </style>
11021
+ </head>
11022
+ <body>
11023
+ <script>
11024
+ // The render script sets window.__SLIDE__ before this page loads.
11025
+ const { kind, app, slide, frame } = window.__SLIDE__;
11026
+ const add = (tag, className, text) => {
11027
+ const el = document.createElement(tag);
11028
+ if (className) el.className = className;
11029
+ if (text) el.textContent = text;
11030
+ return document.body.appendChild(el);
11031
+ };
11032
+ if (kind === "feature") {
11033
+ document.body.classList.add("feature");
11034
+ add("div", "app", app);
11035
+ add("h1", "", slide.headline);
11036
+ } else {
11037
+ add("h1", "", slide.headline);
11038
+ if (slide.subhead) add("p", "", slide.subhead);
11039
+ const shot = add("div", "shot " + (frame === "phone" ? "" : "bare"));
11040
+ const holder = frame === "phone" ? shot.appendChild(document.createElement("div")) : shot;
11041
+ if (frame === "phone") holder.className = "phone";
11042
+ const img = holder.appendChild(document.createElement("img"));
11043
+ img.src = slide.capture;
11044
+ img.alt = "";
11045
+ }
11046
+ </script>
11047
+ </body>
11048
+ </html>
11049
+ `;
11050
+ }
11051
+ });
11052
+
10870
11053
  // ../shared/src/registry-css.ts
10871
11054
  function parseCssVars(css) {
10872
11055
  const out = { theme: {}, light: {}, dark: {} };
@@ -13892,36 +14075,38 @@ ${fence3}` }],
13892
14075
  )
13893
14076
  }, icons);
13894
14077
  });
14078
+ const loadTokenSource = async ({ designSystemId, projectId }) => {
14079
+ if (projectId) {
14080
+ const result = await fetchProjectTokensCss(projectId, apiFetch, authContext);
14081
+ return {
14082
+ tokensCss: result?.tokensCss,
14083
+ name: result?.name ?? `Project ${projectId.slice(0, 8)}`,
14084
+ viewUrl: `${APP_BASE_URL}/projects/${projectId}`,
14085
+ icons: result?.iconLibrary
14086
+ };
14087
+ }
14088
+ if (!designSystemId) return null;
14089
+ const ds = await provider.getDesignSystemById(designSystemId, { includeComponents: false, includeHtml: false });
14090
+ return {
14091
+ tokensCss: await fetchCatalogTokensCss(designSystemId),
14092
+ name: ds?.metadata.title ?? designSystemId,
14093
+ viewUrl: `${APP_BASE_URL}/design-systems/${designSystemId}`,
14094
+ icons: ds ? await catalogKitIcons(ds.metadata, provider) : void 0
14095
+ };
14096
+ };
13895
14097
  const getDtcgKit = async ({
13896
14098
  designSystemId,
13897
14099
  projectId,
13898
14100
  context
13899
14101
  }) => tracked("get-design-system-kit", { target: "dtcg", designSystemId, projectId, context }, async () => {
13900
- let tokensCss;
13901
- let name;
13902
- let viewUrl;
13903
- let icons;
13904
- if (projectId) {
13905
- viewUrl = `${APP_BASE_URL}/projects/${projectId}`;
13906
- const result = await fetchProjectTokensCss(projectId, apiFetch, authContext);
13907
- tokensCss = result?.tokensCss;
13908
- icons = result?.iconLibrary;
13909
- name = result?.name ?? `Project ${projectId.slice(0, 8)}`;
13910
- } else if (designSystemId) {
13911
- viewUrl = `${APP_BASE_URL}/design-systems/${designSystemId}`;
13912
- const ds = await provider.getDesignSystemById(designSystemId, {
13913
- includeComponents: false,
13914
- includeHtml: false
13915
- });
13916
- tokensCss = await fetchCatalogTokensCss(designSystemId);
13917
- name = ds?.metadata.title ?? designSystemId;
13918
- icons = ds ? await catalogKitIcons(ds.metadata, provider) : void 0;
13919
- } else {
14102
+ const source = await loadTokenSource({ designSystemId, projectId });
14103
+ if (!source) {
13920
14104
  return {
13921
14105
  content: [{ type: "text", text: "For target dtcg, pass either a catalog designSystemId (from find-design-system) or a projectId (from create-design-system)." }],
13922
14106
  structuredContent: { status: "error", name: "Missing design system", tokensCss: "" }
13923
14107
  };
13924
14108
  }
14109
+ const { tokensCss, name, viewUrl, icons } = source;
13925
14110
  if (!tokensCss) {
13926
14111
  return {
13927
14112
  content: [{ type: "text", text: `Could not load tokens for "${projectId ?? designSystemId}". A generated project may still be building. Check ${viewUrl}.` }],
@@ -13947,6 +14132,33 @@ ${fence3}` }],
13947
14132
  structuredContent: { status: "completed", name, viewUrl, tokensCss, tokensJson }
13948
14133
  }, icons);
13949
14134
  });
14135
+ const getStoreScreenshots = async ({
14136
+ designSystemId,
14137
+ projectId,
14138
+ context
14139
+ }) => tracked("get-design-system-kit", { target: "store-screenshots", designSystemId, projectId, context }, async () => {
14140
+ const source = await loadTokenSource({ designSystemId, projectId });
14141
+ if (!source?.tokensCss) {
14142
+ const reason = source ? `Could not load tokens for "${projectId ?? designSystemId}". A generated project may still be building. Check ${source.viewUrl}.` : "For target store-screenshots, pass either a catalog designSystemId (from find-design-system) or a projectId (from create-design-system).";
14143
+ return {
14144
+ content: [{ type: "text", text: reason }],
14145
+ structuredContent: { status: "error", name: source?.name ?? "Missing design system", tokensCss: "" }
14146
+ };
14147
+ }
14148
+ const files = buildStoreScreenshotFiles(source.tokensCss);
14149
+ const instructions = storeScreenshotInstructions(source.name);
14150
+ return {
14151
+ content: [{ type: "text", text: storeScreenshotText(source.name, files) }],
14152
+ structuredContent: {
14153
+ status: "completed",
14154
+ name: source.name,
14155
+ viewUrl: source.viewUrl,
14156
+ tokensCss: source.tokensCss,
14157
+ buildInstructions: instructions,
14158
+ screenshotFiles: files
14159
+ }
14160
+ };
14161
+ });
13950
14162
  const fetchProjectMobilePrimitives = async (projectId) => {
13951
14163
  try {
13952
14164
  const res = await apiFetch(`${APP_BASE_URL}/api/projects/${projectId}/mobile/all.json`, { headers: authHeaders(authContext) });
@@ -14162,9 +14374,10 @@ ${BUILD_TARGET_RULE}`,
14162
14374
  "html",
14163
14375
  ...enableShopifyKit ? ["shopify"] : [],
14164
14376
  "dtcg",
14377
+ "store-screenshots",
14165
14378
  ...enableMobileKits ? ["react-native", "swiftui"] : []
14166
14379
  ]
14167
- ).describe(`Required build target. Use "react" for any host with filesystem + terminal; use "html" only for inline chat canvases/artifacts.${enableShopifyKit ? ' Use "shopify" for Shopify stores and themes.' : ""} Use "dtcg" to hand the tokens to a design tool: it returns a W3C Design Tokens document for Style Dictionary, Tokens Studio, Terrazzo, or a Figma plugin that creates variables. ${enableMobileKits ? ` Use "react-native" for a React Native or Expo app and "swiftui" for an iOS app: each returns this system's stored mobile kit, its own material on native primitives, plus the handoff to the matching guide.` : ""}`),
14380
+ ).describe(`Required build target. Use "react" for any host with filesystem + terminal; use "html" only for inline chat canvases/artifacts.${enableShopifyKit ? ' Use "shopify" for Shopify stores and themes.' : ""} Use "dtcg" to hand the tokens to a design tool: it returns a W3C Design Tokens document for Style Dictionary, Tokens Studio, Terrazzo, or a Figma plugin that creates variables. Use "store-screenshots" for App Store and Google Play screenshots in this system: it returns slide files and a render script that writes PNG files at the store sizes. ${enableMobileKits ? ` Use "react-native" for a React Native or Expo app and "swiftui" for an iOS app: each returns this system's stored mobile kit, its own material on native primitives, plus the handoff to the matching guide.` : ""}`),
14168
14381
  designSystemId: z14.string().min(1).optional().describe(`Catalog design system ID from find-design-system. Required for target react; for every other target pass this or projectId.`),
14169
14382
  projectId: z14.string().min(1).optional().describe(`Generated project ID from create-design-system / get-design-system-status. Supported with every target but react.`),
14170
14383
  component: z14.union([z14.string(), z14.array(z14.string())]).optional().describe("React target only. Pass only when the user asks to inspect specific component source code. Omit to scaffold the entire design system."),
@@ -14180,7 +14393,7 @@ ${BUILD_TARGET_RULE}`,
14180
14393
  // interactive preview path. [BD-240]
14181
14394
  },
14182
14395
  async ({ target, designSystemId, projectId, component, library, context }) => {
14183
- if (target !== "react" && target !== "html" && target !== "shopify" && target !== "dtcg" && target !== "react-native" && target !== "swiftui") {
14396
+ if (target !== "react" && target !== "html" && target !== "shopify" && target !== "dtcg" && target !== "store-screenshots" && target !== "react-native" && target !== "swiftui") {
14184
14397
  return {
14185
14398
  content: [{ type: "text", text: `Unknown target "${target}".` }],
14186
14399
  structuredContent: { status: "error", name: "Unknown target", tokensCss: "" }
@@ -14192,6 +14405,9 @@ ${BUILD_TARGET_RULE}`,
14192
14405
  if (target === "dtcg") {
14193
14406
  return withRedesignContract(await getDtcgKit({ designSystemId, projectId, context }));
14194
14407
  }
14408
+ if (target === "store-screenshots") {
14409
+ return getStoreScreenshots({ designSystemId, projectId, context });
14410
+ }
14195
14411
  if (target === "react-native" || target === "swiftui") {
14196
14412
  if (!enableMobileKits) {
14197
14413
  return {
@@ -14687,6 +14903,7 @@ var init_design_system_tools = __esm({
14687
14903
  init_shopify_kit();
14688
14904
  init_redesign_contract();
14689
14905
  init_selected_icon_handoff();
14906
+ init_store_screenshots();
14690
14907
  init_shopify_primitives();
14691
14908
  init_preview_html();
14692
14909
  init_shared();
@@ -22772,7 +22989,7 @@ function columnToSchema(column, factory) {
22772
22989
  }
22773
22990
  return schema;
22774
22991
  }
22775
- function numberColumnToSchema(column, z35, coerce) {
22992
+ function numberColumnToSchema(column, z37, coerce) {
22776
22993
  let unsigned = column.getSQLType().includes("unsigned");
22777
22994
  let min;
22778
22995
  let max;
@@ -22840,20 +23057,20 @@ function numberColumnToSchema(column, z35, coerce) {
22840
23057
  min = Number.MIN_SAFE_INTEGER;
22841
23058
  max = Number.MAX_SAFE_INTEGER;
22842
23059
  }
22843
- let schema = coerce === true || coerce?.number ? z35.coerce.number() : z35.number();
23060
+ let schema = coerce === true || coerce?.number ? z37.coerce.number() : z37.number();
22844
23061
  schema = schema.min(min).max(max);
22845
23062
  return integer2 ? schema.int() : schema;
22846
23063
  }
22847
- function bigintColumnToSchema(column, z35, coerce) {
23064
+ function bigintColumnToSchema(column, z37, coerce) {
22848
23065
  const unsigned = column.getSQLType().includes("unsigned");
22849
23066
  const min = unsigned ? 0n : CONSTANTS.INT64_MIN;
22850
23067
  const max = unsigned ? CONSTANTS.INT64_UNSIGNED_MAX : CONSTANTS.INT64_MAX;
22851
- const schema = coerce === true || coerce?.bigint ? z35.coerce.bigint() : z35.bigint();
23068
+ const schema = coerce === true || coerce?.bigint ? z37.coerce.bigint() : z37.bigint();
22852
23069
  return schema.min(min).max(max);
22853
23070
  }
22854
- function stringColumnToSchema(column, z35, coerce) {
23071
+ function stringColumnToSchema(column, z37, coerce) {
22855
23072
  if (isColumnType(column, ["PgUUID"])) {
22856
- return z35.string().uuid();
23073
+ return z37.string().uuid();
22857
23074
  }
22858
23075
  let max;
22859
23076
  let regex;
@@ -22885,7 +23102,7 @@ function stringColumnToSchema(column, z35, coerce) {
22885
23102
  regex = /^[01]+$/;
22886
23103
  max = column.dimensions;
22887
23104
  }
22888
- let schema = coerce === true || coerce?.string ? z35.coerce.string() : z35.string();
23105
+ let schema = coerce === true || coerce?.string ? z37.coerce.string() : z37.string();
22889
23106
  schema = regex ? schema.regex(regex) : schema;
22890
23107
  return max && fixed ? schema.length(max) : max ? schema.max(max) : schema;
22891
23108
  }
@@ -22983,7 +23200,7 @@ var init_drizzle_zod = __esm({
22983
23200
 
22984
23201
  // ../shared/src/schema.ts
22985
23202
  import { z as z16 } from "zod";
22986
- var vector2, messageRoleEnum, messageTypeEnum, designSystemStatusEnum, onboardingPathValues, onboardingPathSchema, onboardingPathSqlValues, mcpIssueCategoryValues, mcpIssueStatusValues, mcpIssueImprovementStatusValues, mcpIssueImprovementVerdictValues, mcpIssueImprovementStatusSchema, mcpIssueImprovementVerdictSchema, mcpIssueImprovementStatusSqlValues, mcpIssueImprovementVerdictSqlValues, designConfigSchema, projectMemoryDataSchema, projects, messages, projectMemory, fragments, usage, scrapeBlock, resourceAccess, user, signupAlertDeliveries, billingAlertDeliveries, TEAM_RULES_MAX_BYTES, teamRules, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, emailCampaignSends, emailCampaignSteps, projectEvents, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, projectsRelations, projectMemoryRelations, mcpIssuesRelations, mcpIssueImprovementsRelations, messagesRelations, fragmentsRelations, designSystemsRelations, designSystemComponentsRelations, insertProjectSchema, selectProjectSchema, insertMessageSchema, selectMessageSchema, insertFragmentSchema, selectFragmentSchema, insertProjectMemorySchema, selectProjectMemorySchema, insertDesignSystemSchema, selectDesignSystemSchema, insertDesignSystemComponentSchema, selectDesignSystemComponentSchema, insertFoundationalDocSchema, selectFoundationalDocSchema, insertIconLibrarySchema, selectIconLibrarySchema, insertApiKeySchema, selectApiKeySchema, insertUserSchema, selectUserSchema, insertSignupAlertDeliverySchema, selectSignupAlertDeliverySchema, insertEmailCampaignSendSchema, selectEmailCampaignSendSchema, insertEmailCampaignStepSchema, selectEmailCampaignStepSchema, insertMcpIssueImprovementSchema, selectMcpIssueImprovementSchema, pluginAuthNonces;
23203
+ var vector2, messageRoleEnum, messageTypeEnum, designSystemStatusEnum, onboardingPathValues, onboardingPathSchema, onboardingPathSqlValues, mcpIssueCategoryValues, mcpIssueStatusValues, mcpIssueImprovementStatusValues, mcpIssueImprovementVerdictValues, mcpIssueImprovementStatusSchema, mcpIssueImprovementVerdictSchema, mcpIssueImprovementStatusSqlValues, mcpIssueImprovementVerdictSqlValues, designConfigSchema, projectMemoryDataSchema, projects, messages, projectMemory, fragments, usage, scrapeBlock, resourceAccess, user, signupAlertDeliveries, billingAlertDeliveries, billingEmailDeliveries, TEAM_RULES_MAX_BYTES, teamRules, billingSubscription, githubInstallationClaim, githubInstallation, featureAccess, session, account, verification, jwks, oauthClient, oauthRefreshToken, oauthAccessToken, oauthConsent, designSystems, designSystemComponents, foundationalDocs, apiKeys, emailCampaignSends, emailCampaignSteps, projectEvents, iconLibraries, dsSearchQueries, mcpToolCalls, mcpClientConnections, evalVerdicts, generationInputs, mcpIssues, mcpIssueImprovements, projectsRelations, projectMemoryRelations, mcpIssuesRelations, mcpIssueImprovementsRelations, messagesRelations, fragmentsRelations, designSystemsRelations, designSystemComponentsRelations, insertProjectSchema, selectProjectSchema, insertMessageSchema, selectMessageSchema, insertFragmentSchema, selectFragmentSchema, insertProjectMemorySchema, selectProjectMemorySchema, insertDesignSystemSchema, selectDesignSystemSchema, insertDesignSystemComponentSchema, selectDesignSystemComponentSchema, insertFoundationalDocSchema, selectFoundationalDocSchema, insertIconLibrarySchema, selectIconLibrarySchema, insertApiKeySchema, selectApiKeySchema, insertUserSchema, selectUserSchema, insertSignupAlertDeliverySchema, selectSignupAlertDeliverySchema, insertEmailCampaignSendSchema, selectEmailCampaignSendSchema, insertEmailCampaignStepSchema, selectEmailCampaignStepSchema, insertMcpIssueImprovementSchema, selectMcpIssueImprovementSchema, pluginAuthNonces;
22987
23204
  var init_schema2 = __esm({
22988
23205
  "../shared/src/schema.ts"() {
22989
23206
  "use strict";
@@ -23314,6 +23531,40 @@ var init_schema2 = __esm({
23314
23531
  )
23315
23532
  ]
23316
23533
  );
23534
+ billingEmailDeliveries = pgTable(
23535
+ "billing_email_deliveries",
23536
+ {
23537
+ id: text("id").primaryKey(),
23538
+ userId: text("user_id").notNull().references(() => user.id, { onDelete: "cascade" }),
23539
+ toEmail: text("to_email").notNull(),
23540
+ /** The Stripe subscription the email is about, so the daily cap can count per subscription. */
23541
+ stripeSubscriptionId: text("stripe_subscription_id").notNull(),
23542
+ email: jsonb("email").$type().notNull(),
23543
+ occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
23544
+ status: text("status").$type().default("pending").notNull(),
23545
+ attemptCount: integer("attempt_count").default(0).notNull(),
23546
+ nextAttemptAt: timestamp("next_attempt_at", { withTimezone: true }).defaultNow().notNull(),
23547
+ leaseUntil: timestamp("lease_until", { withTimezone: true }),
23548
+ leaseToken: uuid("lease_token"),
23549
+ deliveredAt: timestamp("delivered_at", { withTimezone: true }),
23550
+ resendEmailId: text("resend_email_id"),
23551
+ lastError: text("last_error"),
23552
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
23553
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
23554
+ },
23555
+ (t) => [
23556
+ index("billing_email_deliveries_due_idx").on(t.status, t.nextAttemptAt, t.leaseUntil),
23557
+ index("billing_email_deliveries_subscription_idx").on(t.stripeSubscriptionId, t.createdAt),
23558
+ check(
23559
+ "billing_email_deliveries_status_valid",
23560
+ sql`${t.status} in ('pending', 'processing', 'delivered', 'failed')`
23561
+ ),
23562
+ check(
23563
+ "billing_email_deliveries_processing_lease_valid",
23564
+ sql`${t.status} <> 'processing' or (${t.leaseUntil} is not null and ${t.leaseToken} is not null)`
23565
+ )
23566
+ ]
23567
+ );
23317
23568
  TEAM_RULES_MAX_BYTES = 32 * 1024;
23318
23569
  teamRules = pgTable(
23319
23570
  "team_rules",
@@ -28887,8 +29138,131 @@ var init_feature_map_tools = __esm({
28887
29138
  }
28888
29139
  });
28889
29140
 
29141
+ // ../shared/src/mcp/inspiration.ts
29142
+ import { z as z28 } from "zod";
29143
+ function describeSet(set) {
29144
+ const heading = `## ${set.title}${set.category ? ` (${set.category})` : ""}`;
29145
+ if (!set.notes) return heading;
29146
+ const written = /* @__PURE__ */ new Date(`${set.notes.writtenAt}T00:00:00Z`);
29147
+ const date2 = Number.isNaN(written.getTime()) ? set.notes.writtenAt : NOTE_DATE.format(written);
29148
+ return [
29149
+ heading,
29150
+ `Headlines: ${set.notes.headline}`,
29151
+ `Slide order: ${set.notes.order}`,
29152
+ `Framing: ${set.notes.framing}`,
29153
+ `Color: ${set.notes.color}`,
29154
+ `Better Design's notes, written by a vision model from these screenshots on ${date2}. The listing may have changed since.`
29155
+ ].join("\n");
29156
+ }
29157
+ var INSPIRATION_TYPES, MAX_INSPIRATION_LIMIT, noteSchema, inspirationNotesFileSchema, indexFileSchema, indexEntrySchema, inspirationIndexSchema, NOTE_DATE;
29158
+ var init_inspiration = __esm({
29159
+ "../shared/src/mcp/inspiration.ts"() {
29160
+ "use strict";
29161
+ INSPIRATION_TYPES = ["app-store-screenshots"];
29162
+ MAX_INSPIRATION_LIMIT = 6;
29163
+ noteSchema = z28.object({
29164
+ slug: z28.string(),
29165
+ /** The day the note was written, as YYYY-MM-DD. A listing can change after it. */
29166
+ writtenAt: z28.string(),
29167
+ headline: z28.string(),
29168
+ order: z28.string(),
29169
+ framing: z28.string(),
29170
+ color: z28.string()
29171
+ });
29172
+ inspirationNotesFileSchema = z28.object({
29173
+ model: z28.string(),
29174
+ notes: z28.array(noteSchema)
29175
+ });
29176
+ indexFileSchema = z28.object({
29177
+ key: z28.string(),
29178
+ type: z28.string(),
29179
+ width: z28.number(),
29180
+ height: z28.number()
29181
+ });
29182
+ indexEntrySchema = z28.object({
29183
+ title: z28.string(),
29184
+ slug: z28.string(),
29185
+ category: z28.string().nullish(),
29186
+ files: z28.array(indexFileSchema)
29187
+ });
29188
+ inspirationIndexSchema = z28.object({
29189
+ entries: z28.array(indexEntrySchema)
29190
+ });
29191
+ NOTE_DATE = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "long", year: "numeric", timeZone: "UTC" });
29192
+ }
29193
+ });
29194
+
29195
+ // ../shared/src/mcp/tools/inspiration-tools.ts
29196
+ import { z as z29 } from "zod";
29197
+ function describeSets(result) {
29198
+ const lines = result.sets.map((set, i) => {
29199
+ const category = set.category ? `, ${set.category}` : "";
29200
+ return `${i + 1}. ${set.title}${category}: ${set.images.length} screenshot${set.images.length === 1 ? "" : "s"} below`;
29201
+ });
29202
+ return [
29203
+ `# App Store screenshot sets`,
29204
+ "",
29205
+ ...lines,
29206
+ "",
29207
+ "Each image follows the set it belongs to. Read each set for its headline style, slide order, device framing and color, then propose two or three directions for the user's own screenshots and let the user pick one."
29208
+ ].join("\n");
29209
+ }
29210
+ function noMatch(result, category) {
29211
+ const asked = category ? `No App Store screenshot sets match the category "${category}".` : "The library holds no App Store screenshot sets yet.";
29212
+ return `${asked} Categories in the library: ${result.categories.join(", ")}.`;
29213
+ }
29214
+ function registerInspirationTools({ server, tracked }, readInspiration2) {
29215
+ server.registerTool(
29216
+ "get-inspiration",
29217
+ {
29218
+ title: "Get Inspiration",
29219
+ description: `Send real reference designs from Better Design's library as images, so you can study how shipped products solved the same design problem before you design the user's own.
29220
+
29221
+ Use it before designing App Store or Google Play screenshots: pass \`type: "app-store-screenshots"\`, and a \`category\` such as "Health & Fitness" when the user's app has one. Each set arrives as its title and category, followed by its first screenshots.
29222
+
29223
+ Then propose two or three directions drawn from what the sets do (headline style, slide order, device framing, color), and let the user choose before you build anything.`,
29224
+ inputSchema: z29.object({
29225
+ type: z29.enum(INSPIRATION_TYPES).describe('The kind of reference to send. Today: "app-store-screenshots".'),
29226
+ category: z29.string().trim().min(1).max(60).optional().describe('Optional App Store category, for example "Productivity" or "Health & Fitness". Ignores case.'),
29227
+ limit: z29.number().int().min(1).max(MAX_INSPIRATION_LIMIT).optional().describe(`How many sets to send, 1 to ${MAX_INSPIRATION_LIMIT}. Defaults to 3.`),
29228
+ context: CONTEXT_ARG
29229
+ }),
29230
+ annotations: READ_ONLY_TOOL_ANNOTATIONS
29231
+ },
29232
+ async ({ type, category, limit, context }) => tracked("get-inspiration", { type, category: category ?? null, limit: limit ?? null, context }, async () => {
29233
+ if (!readInspiration2) return { content: [{ type: "text", text: NOT_AVAILABLE }] };
29234
+ const result = await readInspiration2({ type, category, limit });
29235
+ if (result.sets.length === 0) {
29236
+ return { content: [{ type: "text", text: noMatch(result, category) }] };
29237
+ }
29238
+ return {
29239
+ content: [
29240
+ { type: "text", text: describeSets(result) },
29241
+ ...result.sets.flatMap((set) => [
29242
+ { type: "text", text: describeSet(set) },
29243
+ ...set.images.map((image) => ({
29244
+ type: "image",
29245
+ data: image.data,
29246
+ mimeType: image.mimeType
29247
+ }))
29248
+ ])
29249
+ ]
29250
+ };
29251
+ })
29252
+ );
29253
+ }
29254
+ var NOT_AVAILABLE;
29255
+ var init_inspiration_tools = __esm({
29256
+ "../shared/src/mcp/tools/inspiration-tools.ts"() {
29257
+ "use strict";
29258
+ init_shared();
29259
+ init_inspiration();
29260
+ NOT_AVAILABLE = "The inspiration library is not available from this server. Try again with the hosted Better Design MCP server or the latest `better-design` npm package.";
29261
+ }
29262
+ });
29263
+
28890
29264
  // ../shared/src/mcp/tools.ts
28891
- function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2) {
29265
+ function registerAllTools(server, provider, onToolUsage, authContext, options = {}, apiFetch = globalThis.fetch, userId, captureDashboardBrowser2, readInspiration2) {
28892
29266
  const { track, tracked } = createTracking(server, onToolUsage);
28893
29267
  const deps = {
28894
29268
  server,
@@ -28914,6 +29288,7 @@ function registerAllTools(server, provider, onToolUsage, authContext, options =
28914
29288
  registerInspectionTools(deps);
28915
29289
  registerFeatureMapTools(deps);
28916
29290
  registerTeamRulesTools(deps);
29291
+ registerInspirationTools(deps, readInspiration2);
28917
29292
  registerPreviewResource(deps);
28918
29293
  registerInspectionResource(deps);
28919
29294
  }
@@ -28937,6 +29312,7 @@ var init_tools = __esm({
28937
29312
  init_widget_tools();
28938
29313
  init_feature_map_tools();
28939
29314
  init_team_rules_tools();
29315
+ init_inspiration_tools();
28940
29316
  init_shared();
28941
29317
  }
28942
29318
  });
@@ -28946,7 +29322,7 @@ var MCP_IMPLEMENTATION_VERSION;
28946
29322
  var init_version2 = __esm({
28947
29323
  "../shared/src/mcp/version.ts"() {
28948
29324
  "use strict";
28949
- MCP_IMPLEMENTATION_VERSION = "0.31.1";
29325
+ MCP_IMPLEMENTATION_VERSION = "0.33.0";
28950
29326
  }
28951
29327
  });
28952
29328
 
@@ -29011,7 +29387,8 @@ function createMcpServer(options) {
29011
29387
  apiFetch = globalThis.fetch,
29012
29388
  userId,
29013
29389
  captureDashboardBrowser: captureDashboardBrowser2,
29014
- directoryListing = false
29390
+ directoryListing = false,
29391
+ readInspiration: readInspiration2
29015
29392
  } = options;
29016
29393
  const server = new McpServer(
29017
29394
  { name, version: version2 },
@@ -29038,7 +29415,8 @@ function createMcpServer(options) {
29038
29415
  },
29039
29416
  apiFetch,
29040
29417
  userId,
29041
- captureDashboardBrowser2
29418
+ captureDashboardBrowser2,
29419
+ readInspiration2
29042
29420
  );
29043
29421
  return server;
29044
29422
  }
@@ -29078,6 +29456,7 @@ __export(api_client_exports, {
29078
29456
  getSessionGuidance: () => getSessionGuidance,
29079
29457
  getTeamRules: () => getTeamRules,
29080
29458
  listPrinciples: () => listPrinciples,
29459
+ readInspiration: () => readInspiration,
29081
29460
  searchDesignSystems: () => searchDesignSystems,
29082
29461
  searchIconLibraries: () => searchIconLibraries,
29083
29462
  searchIcons: () => searchIcons,
@@ -29115,6 +29494,12 @@ function captureDashboardBrowser(input) {
29115
29494
  timeoutMs: 9e4
29116
29495
  });
29117
29496
  }
29497
+ function readInspiration(input) {
29498
+ const params = new URLSearchParams({ type: input.type });
29499
+ if (input.category) params.set("category", input.category);
29500
+ if (input.limit !== void 0) params.set("limit", String(input.limit));
29501
+ return request(`/v1/inspiration?${params}`, { timeoutMs: 3e4 });
29502
+ }
29118
29503
  async function searchDesignSystems(query, limit = 5) {
29119
29504
  return request(
29120
29505
  `/v1/design-systems/search?q=${encodeURIComponent(query)}&limit=${limit}`
@@ -31091,7 +31476,7 @@ var init_engine = __esm({
31091
31476
  });
31092
31477
 
31093
31478
  // ../shared/src/lint/config.ts
31094
- import { z as z28 } from "zod";
31479
+ import { z as z30 } from "zod";
31095
31480
  function mergeReviewConfig(override) {
31096
31481
  if (!override) return DEFAULT_REVIEW_CONFIG;
31097
31482
  return {
@@ -31149,47 +31534,47 @@ var init_config = __esm({
31149
31534
  semanticTokens: { enabled: true }
31150
31535
  }
31151
31536
  };
31152
- reviewFlowStepSchema = z28.discriminatedUnion("action", [
31153
- z28.object({ action: z28.literal("click"), selector: z28.string() }),
31154
- z28.object({ action: z28.literal("type"), selector: z28.string(), value: z28.string() }),
31155
- z28.object({ action: z28.literal("press"), key: z28.string() }),
31156
- z28.object({ action: z28.literal("wait"), milliseconds: z28.number().finite().nonnegative() })
31537
+ reviewFlowStepSchema = z30.discriminatedUnion("action", [
31538
+ z30.object({ action: z30.literal("click"), selector: z30.string() }),
31539
+ z30.object({ action: z30.literal("type"), selector: z30.string(), value: z30.string() }),
31540
+ z30.object({ action: z30.literal("press"), key: z30.string() }),
31541
+ z30.object({ action: z30.literal("wait"), milliseconds: z30.number().finite().nonnegative() })
31157
31542
  ]);
31158
- visualReviewConfigSchema = z28.object({
31159
- enabled: z28.boolean().optional(),
31160
- model: z28.object({
31161
- enabled: z28.boolean().optional(),
31162
- maxScreenshots: z28.number().int().nonnegative().optional()
31543
+ visualReviewConfigSchema = z30.object({
31544
+ enabled: z30.boolean().optional(),
31545
+ model: z30.object({
31546
+ enabled: z30.boolean().optional(),
31547
+ maxScreenshots: z30.number().int().nonnegative().optional()
31163
31548
  }).optional(),
31164
- routes: z28.array(z28.string()).optional(),
31165
- viewports: z28.array(z28.enum(["desktop", "mobile", "reflow"])).optional(),
31166
- flows: z28.array(z28.object({
31167
- name: z28.string(),
31168
- persona: z28.string(),
31169
- path: z28.string().optional(),
31170
- steps: z28.array(reviewFlowStepSchema)
31549
+ routes: z30.array(z30.string()).optional(),
31550
+ viewports: z30.array(z30.enum(["desktop", "mobile", "reflow"])).optional(),
31551
+ flows: z30.array(z30.object({
31552
+ name: z30.string(),
31553
+ persona: z30.string(),
31554
+ path: z30.string().optional(),
31555
+ steps: z30.array(reviewFlowStepSchema)
31171
31556
  })).optional(),
31172
- componentGallery: z28.object({
31173
- enabled: z28.boolean().optional(),
31174
- componentSelector: z28.string().optional(),
31175
- canvasSelector: z28.string().optional(),
31176
- labelSelector: z28.string().optional(),
31177
- maxComponents: z28.number().int().nonnegative().optional()
31557
+ componentGallery: z30.object({
31558
+ enabled: z30.boolean().optional(),
31559
+ componentSelector: z30.string().optional(),
31560
+ canvasSelector: z30.string().optional(),
31561
+ labelSelector: z30.string().optional(),
31562
+ maxComponents: z30.number().int().nonnegative().optional()
31178
31563
  }).optional()
31179
31564
  });
31180
- reviewConfigOverrideSchema = z28.object({
31181
- designSystem: z28.string().optional(),
31565
+ reviewConfigOverrideSchema = z30.object({
31566
+ designSystem: z30.string().optional(),
31182
31567
  visual: visualReviewConfigSchema.optional(),
31183
- rules: z28.object({
31184
- elements: z28.record(z28.string(), z28.object({ use: z28.string(), from: z28.string() })).optional(),
31185
- tokens: z28.record(z28.string(), z28.string()).optional(),
31186
- wcag: z28.object({
31187
- enabled: z28.boolean().optional(),
31188
- severity: z28.enum(["all", "critical+serious"]).optional()
31568
+ rules: z30.object({
31569
+ elements: z30.record(z30.string(), z30.object({ use: z30.string(), from: z30.string() })).optional(),
31570
+ tokens: z30.record(z30.string(), z30.string()).optional(),
31571
+ wcag: z30.object({
31572
+ enabled: z30.boolean().optional(),
31573
+ severity: z30.enum(["all", "critical+serious"]).optional()
31189
31574
  }).optional(),
31190
- slop: z28.object({ enabled: z28.boolean().optional() }).optional(),
31191
- copy: z28.object({ enabled: z28.boolean().optional() }).optional(),
31192
- semanticTokens: z28.object({ enabled: z28.boolean().optional() }).optional()
31575
+ slop: z30.object({ enabled: z30.boolean().optional() }).optional(),
31576
+ copy: z30.object({ enabled: z30.boolean().optional() }).optional(),
31577
+ semanticTokens: z30.object({ enabled: z30.boolean().optional() }).optional()
31193
31578
  }).optional()
31194
31579
  });
31195
31580
  }
@@ -31565,7 +31950,7 @@ __export(review_tool_exports, {
31565
31950
  isReviewableSourcePath: () => isReviewableSourcePath,
31566
31951
  registerReviewTool: () => registerReviewTool
31567
31952
  });
31568
- import { z as z29 } from "zod";
31953
+ import { z as z31 } from "zod";
31569
31954
  function isReviewableSourcePath(path6) {
31570
31955
  return /\.(tsx|jsx|ts|js|css)$/i.test(path6);
31571
31956
  }
@@ -31589,11 +31974,11 @@ get-review-rules gives you the judgment rules to apply yourself. This tool is th
31589
31974
  This source reviewer cannot judge rendered hierarchy or semantic color roles. After it passes, inspect the rendered dashboard and confirm title \u2192 metrics \u2192 records \u2192 interpretation, distinct action and status colors, and that a stepper begins only after a task starts.
31590
31975
 
31591
31976
  Fix every critical and serious finding, then call it again on the fixed source. Findings feed the same loop as a human code review: read, fix, re-check.`,
31592
- inputSchema: z29.object({
31593
- files: z29.array(
31594
- z29.object({
31595
- path: z29.string().trim().min(1).max(400).describe("Repo-relative file path, e.g. components/signup-form.tsx."),
31596
- content: z29.string().max(MAX_FILE_CHARS).describe("The file's full source.")
31977
+ inputSchema: z31.object({
31978
+ files: z31.array(
31979
+ z31.object({
31980
+ path: z31.string().trim().min(1).max(400).describe("Repo-relative file path, e.g. components/signup-form.tsx."),
31981
+ content: z31.string().max(MAX_FILE_CHARS).describe("The file's full source.")
31597
31982
  })
31598
31983
  ).min(1).max(MAX_FILES).describe("The .tsx, .jsx, .ts, .js, and .css files to review, as written."),
31599
31984
  context: CONTEXT_ARG
@@ -31654,7 +32039,7 @@ review-ui-code({ files: [{ path: "components/signup-form.tsx", content: "..." },
31654
32039
  });
31655
32040
 
31656
32041
  // ../shared/src/mcp/figma-prototype.ts
31657
- import { z as z30 } from "zod";
32042
+ import { z as z32 } from "zod";
31658
32043
  function escapeHtml(value) {
31659
32044
  return value.replace(/[&<>"]/g, (char2) => ESCAPES.get(char2) ?? char2);
31660
32045
  }
@@ -31688,63 +32073,63 @@ var colorSchema, paintSchema, sizingSchema, baseSchema, prototypeNodeSchema, pro
31688
32073
  var init_figma_prototype = __esm({
31689
32074
  "../shared/src/mcp/figma-prototype.ts"() {
31690
32075
  "use strict";
31691
- colorSchema = z30.object({ r: z30.number(), g: z30.number(), b: z30.number(), a: z30.number() });
31692
- paintSchema = z30.object({ variable: z30.string().optional(), color: colorSchema.optional() });
31693
- sizingSchema = z30.enum(["FIXED", "HUG", "FILL"]);
31694
- baseSchema = z30.object({
31695
- name: z30.string(),
31696
- width: z30.number(),
31697
- height: z30.number(),
31698
- x: z30.number(),
31699
- y: z30.number(),
31700
- sizing: z30.object({ horizontal: sizingSchema, vertical: sizingSchema }).optional(),
31701
- absolute: z30.boolean().optional(),
31702
- opacity: z30.number().optional()
32076
+ colorSchema = z32.object({ r: z32.number(), g: z32.number(), b: z32.number(), a: z32.number() });
32077
+ paintSchema = z32.object({ variable: z32.string().optional(), color: colorSchema.optional() });
32078
+ sizingSchema = z32.enum(["FIXED", "HUG", "FILL"]);
32079
+ baseSchema = z32.object({
32080
+ name: z32.string(),
32081
+ width: z32.number(),
32082
+ height: z32.number(),
32083
+ x: z32.number(),
32084
+ y: z32.number(),
32085
+ sizing: z32.object({ horizontal: sizingSchema, vertical: sizingSchema }).optional(),
32086
+ absolute: z32.boolean().optional(),
32087
+ opacity: z32.number().optional()
31703
32088
  });
31704
- prototypeNodeSchema = z30.lazy(() => z30.discriminatedUnion("kind", [
32089
+ prototypeNodeSchema = z32.lazy(() => z32.discriminatedUnion("kind", [
31705
32090
  baseSchema.extend({
31706
- kind: z30.literal("instance"),
31707
- auditKey: z30.string(),
31708
- systemId: z30.string(),
31709
- texts: z30.array(z30.string()),
31710
- mainTexts: z30.array(z30.string())
32091
+ kind: z32.literal("instance"),
32092
+ auditKey: z32.string(),
32093
+ systemId: z32.string(),
32094
+ texts: z32.array(z32.string()),
32095
+ mainTexts: z32.array(z32.string())
31711
32096
  }),
31712
32097
  baseSchema.extend({
31713
- kind: z30.literal("frame"),
31714
- layout: z30.object({
31715
- direction: z30.enum(["HORIZONTAL", "VERTICAL"]),
31716
- gap: z30.number(),
31717
- padding: z30.tuple([z30.number(), z30.number(), z30.number(), z30.number()]),
31718
- primary: z30.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]),
31719
- counter: z30.enum(["MIN", "CENTER", "MAX", "BASELINE"]),
31720
- wrap: z30.boolean().optional()
32098
+ kind: z32.literal("frame"),
32099
+ layout: z32.object({
32100
+ direction: z32.enum(["HORIZONTAL", "VERTICAL"]),
32101
+ gap: z32.number(),
32102
+ padding: z32.tuple([z32.number(), z32.number(), z32.number(), z32.number()]),
32103
+ primary: z32.enum(["MIN", "CENTER", "MAX", "SPACE_BETWEEN"]),
32104
+ counter: z32.enum(["MIN", "CENTER", "MAX", "BASELINE"]),
32105
+ wrap: z32.boolean().optional()
31721
32106
  }).optional(),
31722
32107
  fill: paintSchema.optional(),
31723
- stroke: z30.object({ paint: paintSchema, weight: z30.number() }).optional(),
31724
- radius: z30.number().optional(),
31725
- clips: z30.boolean().optional(),
31726
- children: z30.array(prototypeNodeSchema)
32108
+ stroke: z32.object({ paint: paintSchema, weight: z32.number() }).optional(),
32109
+ radius: z32.number().optional(),
32110
+ clips: z32.boolean().optional(),
32111
+ children: z32.array(prototypeNodeSchema)
31727
32112
  }),
31728
32113
  baseSchema.extend({
31729
- kind: z30.literal("text"),
31730
- characters: z30.string(),
31731
- fontSize: z30.number(),
31732
- fontWeight: z30.number(),
31733
- lineHeight: z30.number().optional(),
31734
- letterSpacing: z30.number().optional(),
31735
- fontFamily: z30.string().optional(),
31736
- align: z30.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]),
32114
+ kind: z32.literal("text"),
32115
+ characters: z32.string(),
32116
+ fontSize: z32.number(),
32117
+ fontWeight: z32.number(),
32118
+ lineHeight: z32.number().optional(),
32119
+ letterSpacing: z32.number().optional(),
32120
+ fontFamily: z32.string().optional(),
32121
+ align: z32.enum(["LEFT", "CENTER", "RIGHT", "JUSTIFIED"]),
31737
32122
  fill: paintSchema.optional()
31738
32123
  }),
31739
32124
  baseSchema.extend({
31740
- kind: z30.literal("shape"),
31741
- svg: z30.string(),
31742
- reason: z30.string()
32125
+ kind: z32.literal("shape"),
32126
+ svg: z32.string(),
32127
+ reason: z32.string()
31743
32128
  })
31744
32129
  ]));
31745
- prototypeRequestSchema = z30.object({
31746
- type: z30.literal("better-design-figma-prototype"),
31747
- id: z30.string(),
32130
+ prototypeRequestSchema = z32.object({
32131
+ type: z32.literal("better-design-figma-prototype"),
32132
+ id: z32.string(),
31748
32133
  frame: prototypeNodeSchema
31749
32134
  });
31750
32135
  ESCAPES = /* @__PURE__ */ new Map([["&", "&amp;"], ["<", "&lt;"], [">", "&gt;"], ['"', "&quot;"]]);
@@ -31930,7 +32315,7 @@ var init_figma_model_prompt = __esm({
31930
32315
  });
31931
32316
 
31932
32317
  // ../shared/src/mcp/figma-model.ts
31933
- import { z as z31 } from "zod";
32318
+ import { z as z33 } from "zod";
31934
32319
  function fenced(text3, language) {
31935
32320
  const match = text3.match(new RegExp("```" + language + "[^\\n]*\\n([\\s\\S]*?)```", "i"));
31936
32321
  return match ? match[1] : null;
@@ -32136,8 +32521,8 @@ var init_figma_model = __esm({
32136
32521
  init_figma_prototype();
32137
32522
  init_html_kit();
32138
32523
  init_preview_html();
32139
- unplacedSchema = z31.object({
32140
- unplaced: z31.array(z31.object({ name: z31.string(), reason: z31.string() }))
32524
+ unplacedSchema = z33.object({
32525
+ unplaced: z33.array(z33.object({ name: z33.string(), reason: z33.string() }))
32141
32526
  });
32142
32527
  UNSAFE_BLOCKS = /<(script|iframe|object|embed|noscript)\b[\s\S]*?(<\/\1[^>]*>|$)/gi;
32143
32528
  UNSAFE_TAGS = /<\/?(meta|link|base|form|style)\b[^>]*(>|$)/gi;
@@ -32238,7 +32623,7 @@ var init_figma_model_handoff = __esm({
32238
32623
  });
32239
32624
 
32240
32625
  // ../shared/src/mcp/figma-model-providers.ts
32241
- import { z as z32 } from "zod";
32626
+ import { z as z34 } from "zod";
32242
32627
  function serverLabel(url) {
32243
32628
  try {
32244
32629
  const parsed = new URL(url);
@@ -32331,19 +32716,19 @@ var init_figma_model_providers = __esm({
32331
32716
  DEFAULT_LOCAL_MODEL = "gemma4:latest";
32332
32717
  DEFAULT_LOCAL_BASE_URL = "http://localhost:11434/v1";
32333
32718
  MAX_OUTPUT_TOKENS = 16e3;
32334
- chatReplySchema = z32.object({
32335
- choices: z32.array(z32.object({
32336
- message: z32.object({ content: z32.string().nullable().optional() }),
32337
- finish_reason: z32.string().nullable().optional()
32719
+ chatReplySchema = z34.object({
32720
+ choices: z34.array(z34.object({
32721
+ message: z34.object({ content: z34.string().nullable().optional() }),
32722
+ finish_reason: z34.string().nullable().optional()
32338
32723
  })).min(1),
32339
- usage: z32.object({ prompt_tokens: z32.number(), completion_tokens: z32.number() }).optional()
32724
+ usage: z34.object({ prompt_tokens: z34.number(), completion_tokens: z34.number() }).optional()
32340
32725
  });
32341
- errorCodeSchema = z32.object({ code: z32.string() });
32726
+ errorCodeSchema = z34.object({ code: z34.string() });
32342
32727
  }
32343
32728
  });
32344
32729
 
32345
32730
  // ../shared/src/mcp/figma-relay.ts
32346
- import { z as z33 } from "zod";
32731
+ import { z as z35 } from "zod";
32347
32732
  function noPluginMessage() {
32348
32733
  return "No Figma plugin is connected. Open the Figma file, run Plugins, Development, Better Design, and leave the panel open.";
32349
32734
  }
@@ -32357,35 +32742,35 @@ var init_figma_relay = __esm({
32357
32742
  "use strict";
32358
32743
  init_figma_prototype();
32359
32744
  FIGMA_RELAY_PORT = 4397;
32360
- relayRequestSchema = z33.object({
32361
- id: z33.string(),
32745
+ relayRequestSchema = z35.object({
32746
+ id: z35.string(),
32362
32747
  /** A frame name as it reads in Figma. Omitted means the current selection. */
32363
- frame: z33.string().optional(),
32748
+ frame: z35.string().optional(),
32364
32749
  /**
32365
32750
  * `model` asks for the serialized frame and its PNG instead of a rendered
32366
32751
  * page, so a model can rebuild a frame that holds no library component.
32367
32752
  * Omitted means the library path, unchanged. [BD-866]
32368
32753
  */
32369
- mode: z33.enum(["library", "model"]).optional()
32754
+ mode: z35.enum(["library", "model"]).optional()
32370
32755
  });
32371
- relayResultSchema = z33.object({
32372
- id: z33.string(),
32373
- html: z33.string().optional(),
32374
- error: z33.string().optional(),
32375
- components: z33.number().optional(),
32376
- report: z33.array(z33.object({ name: z33.string(), reason: z33.string() })).default([]),
32377
- frame: z33.string().optional(),
32756
+ relayResultSchema = z35.object({
32757
+ id: z35.string(),
32758
+ html: z35.string().optional(),
32759
+ error: z35.string().optional(),
32760
+ components: z35.number().optional(),
32761
+ report: z35.array(z35.object({ name: z35.string(), reason: z35.string() })).default([]),
32762
+ frame: z35.string().optional(),
32378
32763
  /** The frames the plugin found, so a missing name can say what is there. */
32379
- frames: z33.array(z33.string()).optional(),
32764
+ frames: z35.array(z35.string()).optional(),
32380
32765
  /** Model mode: the serialized frame, for the model to rebuild. */
32381
32766
  tree: prototypeNodeSchema.optional(),
32382
32767
  /** Model mode: the frame's PNG export, base64. */
32383
- png: z33.string().optional()
32768
+ png: z35.string().optional()
32384
32769
  });
32385
- relayHelloSchema = z33.object({
32386
- file: z33.string(),
32770
+ relayHelloSchema = z35.object({
32771
+ file: z35.string(),
32387
32772
  /** The design system the file holds, when the plugin knows it. */
32388
- system: z33.string().optional()
32773
+ system: z35.string().optional()
32389
32774
  });
32390
32775
  POLL_HOLD_MS = 25e3;
32391
32776
  REQUEST_TIMEOUT_MS = 3e5;
@@ -32553,7 +32938,7 @@ __export(figma_prototype_tool_exports, {
32553
32938
  });
32554
32939
  import { mkdir as mkdir2, readFile, stat, writeFile as writeFile2 } from "fs/promises";
32555
32940
  import { dirname as dirname3, resolve as resolve2 } from "path";
32556
- import { z as z34 } from "zod";
32941
+ import { z as z36 } from "zod";
32557
32942
  async function ensureRelay() {
32558
32943
  if (relay) return relay;
32559
32944
  starting ??= startFigmaRelay().then((started) => {
@@ -32613,30 +32998,30 @@ function registerFigmaPrototypeTool(server) {
32613
32998
  "yourself: the tool returns a prompt file and the frame's image, you write the answer to a file, and",
32614
32999
  "you call this tool again with frameId and answerPath so it checks the page and reports what is missing."
32615
33000
  ].join(" "),
32616
- inputSchema: z34.object({
32617
- frame: z34.string().optional().describe("The frame's name as it reads in Figma. Omit to use the current selection."),
32618
- outputPath: z34.string().optional().describe("Where to write the HTML. Relative paths resolve against the working directory."),
32619
- mode: z34.enum(["library", "model"]).optional().describe(
33001
+ inputSchema: z36.object({
33002
+ frame: z36.string().optional().describe("The frame's name as it reads in Figma. Omit to use the current selection."),
33003
+ outputPath: z36.string().optional().describe("Where to write the HTML. Relative paths resolve against the working directory."),
33004
+ mode: z36.enum(["library", "model"]).optional().describe(
32620
33005
  "library (the default) maps components from a Better Design library. model asks a model to rebuild a frame from any file."
32621
33006
  ),
32622
- system: z34.string().optional().describe("Model mode: the catalog design system to build with, as find-design-system names it."),
32623
- provider: z34.enum(["agent", "openai-compatible"]).optional().describe(
33007
+ system: z36.string().optional().describe("Model mode: the catalog design system to build with, as find-design-system names it."),
33008
+ provider: z36.enum(["agent", "openai-compatible"]).optional().describe(
32624
33009
  "Model mode: agent (the default) hands the frame to you to rebuild; openai-compatible calls a local server, Ollama by default."
32625
33010
  ),
32626
- model: z34.string().optional().describe(`Model mode with openai-compatible: the local model id. Defaults to ${DEFAULT_LOCAL_MODEL}.`),
32627
- frameId: z34.string().optional().describe("Model mode, second call: the frameId the first call returned."),
32628
- answerPath: z34.string().optional().describe("Model mode, second call: the file holding your answer, as the prompt file asks.")
33011
+ model: z36.string().optional().describe(`Model mode with openai-compatible: the local model id. Defaults to ${DEFAULT_LOCAL_MODEL}.`),
33012
+ frameId: z36.string().optional().describe("Model mode, second call: the frameId the first call returned."),
33013
+ answerPath: z36.string().optional().describe("Model mode, second call: the file holding your answer, as the prompt file asks.")
32629
33014
  }),
32630
- outputSchema: z34.object({
32631
- status: z34.enum(["completed", "failed", "needs-answer"]),
32632
- message: z34.string(),
32633
- frameId: z34.string().optional(),
32634
- promptPath: z34.string().optional(),
32635
- imagePath: z34.string().optional(),
32636
- path: z34.string().optional(),
32637
- components: z34.number().optional(),
32638
- report: z34.array(z34.object({ name: z34.string(), reason: z34.string() })).optional(),
32639
- model: z34.string().optional()
33015
+ outputSchema: z36.object({
33016
+ status: z36.enum(["completed", "failed", "needs-answer"]),
33017
+ message: z36.string(),
33018
+ frameId: z36.string().optional(),
33019
+ promptPath: z36.string().optional(),
33020
+ imagePath: z36.string().optional(),
33021
+ path: z36.string().optional(),
33022
+ components: z36.number().optional(),
33023
+ report: z36.array(z36.object({ name: z36.string(), reason: z36.string() })).optional(),
33024
+ model: z36.string().optional()
32640
33025
  }),
32641
33026
  annotations: { readOnlyHint: false, openWorldHint: true }
32642
33027
  },
@@ -32858,6 +33243,7 @@ async function startServer() {
32858
33243
  const { createHttpDataProvider: createHttpDataProvider2 } = await Promise.resolve().then(() => (init_http_data_provider(), http_data_provider_exports));
32859
33244
  const {
32860
33245
  captureDashboardBrowser: captureDashboardBrowser2,
33246
+ readInspiration: readInspiration2,
32861
33247
  trackToolUsage: trackToolUsage2,
32862
33248
  trackMcpInstalled: trackMcpInstalled2
32863
33249
  } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
@@ -32883,7 +33269,8 @@ async function startServer() {
32883
33269
  name: "DesignSystems",
32884
33270
  version: MCP_IMPLEMENTATION_VERSION2,
32885
33271
  onToolUsage,
32886
- captureDashboardBrowser: captureDashboardBrowser2
33272
+ captureDashboardBrowser: captureDashboardBrowser2,
33273
+ readInspiration: readInspiration2
32887
33274
  });
32888
33275
  registerReviewTool2(server, onToolUsage);
32889
33276
  registerFigmaPrototypeTool2(server);