@fragments-sdk/cli 0.15.4 → 0.15.7

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.
@@ -338,6 +338,66 @@ function generateCssTokens(config) {
338
338
  return lines.join("\n");
339
339
  }
340
340
 
341
+ // src/theme/seed-presets.ts
342
+ var SEED_PRESETS = [
343
+ {
344
+ name: "Stone",
345
+ description: "warm gray",
346
+ seeds: { brand: "#52525b", neutral: "stone", density: "default", radiusStyle: "default" }
347
+ },
348
+ {
349
+ name: "Sand",
350
+ description: "warm beige",
351
+ seeds: { brand: "#8b5e34", neutral: "sand", density: "default", radiusStyle: "default" }
352
+ },
353
+ {
354
+ name: "Ice",
355
+ description: "cool blue",
356
+ seeds: { brand: "#0284c7", neutral: "ice", density: "default", radiusStyle: "default" }
357
+ },
358
+ {
359
+ name: "Earth",
360
+ description: "olive green",
361
+ seeds: { brand: "#517035", neutral: "earth", density: "default", radiusStyle: "default" }
362
+ },
363
+ {
364
+ name: "Fire",
365
+ description: "warm orange",
366
+ seeds: { brand: "#ea580c", neutral: "fire", density: "default", radiusStyle: "default" }
367
+ }
368
+ ];
369
+ var SEED_DEFAULTS = {
370
+ brand: "#18181b",
371
+ neutral: "stone",
372
+ density: "default",
373
+ radiusStyle: "default"
374
+ };
375
+ function generateSeedScss(seeds) {
376
+ const overrides = [];
377
+ if (seeds.brand !== SEED_DEFAULTS.brand) {
378
+ overrides.push(` $fui-brand: ${seeds.brand}`);
379
+ }
380
+ if (seeds.neutral !== SEED_DEFAULTS.neutral) {
381
+ overrides.push(` $fui-neutral: "${seeds.neutral}"`);
382
+ }
383
+ if (seeds.density !== SEED_DEFAULTS.density) {
384
+ overrides.push(` $fui-density: "${seeds.density}"`);
385
+ }
386
+ if (seeds.radiusStyle !== SEED_DEFAULTS.radiusStyle) {
387
+ overrides.push(` $fui-radius-style: "${seeds.radiusStyle}"`);
388
+ }
389
+ if (overrides.length === 0) {
390
+ return `// Fragments UI \u2014 tokens, resets, dark mode
391
+ @use '@fragments-sdk/ui/styles';
392
+ `;
393
+ }
394
+ return `// Fragments UI \u2014 tokens, resets, dark mode
395
+ @use '@fragments-sdk/ui/styles' with (
396
+ ${overrides.join(",\n")}
397
+ );
398
+ `;
399
+ }
400
+
341
401
  // src/commands/create.ts
342
402
  function extractFontFamily(cssFontStack) {
343
403
  const match = cssFontStack.match(/^["']?([^"',]+)["']?/);
@@ -415,7 +475,7 @@ async function resolveTheme(options) {
415
475
  }
416
476
  };
417
477
  }
418
- function generateNextjsLayout(themePath, theme) {
478
+ function generateNextjsLayout(themePath, theme, useSeedPath = false) {
419
479
  const fontName = theme?.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;
420
480
  const fontUrl = fontName ? googleFontsUrl(fontName) : null;
421
481
  const htmlOpen = fontUrl ? ` <html lang="en" suppressHydrationWarning>
@@ -426,9 +486,10 @@ function generateNextjsLayout(themePath, theme) {
426
486
  </head>
427
487
  <body>` : ` <html lang="en" suppressHydrationWarning>
428
488
  <body>`;
489
+ const stylesImport = useSeedPath ? `import '${themePath}';` : `import '@fragments-sdk/ui/styles';
490
+ import '${themePath}';`;
429
491
  return `import type { Metadata } from 'next';
430
- import '@fragments-sdk/ui/styles';
431
- import '${themePath}';
492
+ ${stylesImport}
432
493
  import { Providers } from './providers';
433
494
 
434
495
  export const metadata: Metadata = {
@@ -708,12 +769,13 @@ function generatePageCssModule() {
708
769
  }
709
770
  `;
710
771
  }
711
- function generateViteMain(themePath) {
772
+ function generateViteMain(themePath, useSeedPath = false) {
773
+ const stylesImport = useSeedPath ? `import '${themePath}';` : `import '@fragments-sdk/ui/styles';
774
+ import '${themePath}';`;
712
775
  return `import { StrictMode } from 'react';
713
776
  import { createRoot } from 'react-dom/client';
714
777
  import { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';
715
- import '@fragments-sdk/ui/styles';
716
- import '${themePath}';
778
+ ${stylesImport}
717
779
  import App from './App';
718
780
 
719
781
  createRoot(document.getElementById('root')!).render(
@@ -782,6 +844,51 @@ async function promptIfMissing(options) {
782
844
  }
783
845
  resolved.template = resolved.template || "nextjs";
784
846
  resolved.packageManager = resolved.packageManager || detectPackageManager();
847
+ if (!resolved.preset && !resolved.theme && !resolved.brand && !resolved.yes) {
848
+ try {
849
+ const { select, input } = await import("@inquirer/prompts");
850
+ const choices = SEED_PRESETS.map((p) => ({
851
+ name: `${p.name} (${p.description})`,
852
+ value: p.name.toLowerCase()
853
+ }));
854
+ choices.push({ name: "Custom (pick color + palette)", value: "custom" });
855
+ const selected = await select({
856
+ message: "Theme:",
857
+ choices,
858
+ default: "stone"
859
+ });
860
+ if (selected === "custom") {
861
+ const brandColor = await input({
862
+ message: "Brand color (hex):",
863
+ default: "#6366f1",
864
+ validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || "Enter a valid hex color (e.g., #6366f1)"
865
+ });
866
+ const neutral = await select({
867
+ message: "Neutral palette:",
868
+ choices: [
869
+ { name: "Stone (warm gray)", value: "stone" },
870
+ { name: "Sand (warm beige)", value: "sand" },
871
+ { name: "Ice (cool blue)", value: "ice" },
872
+ { name: "Earth (olive green)", value: "earth" },
873
+ { name: "Fire (warm orange)", value: "fire" }
874
+ ],
875
+ default: "stone"
876
+ });
877
+ resolved.seeds = {
878
+ brand: brandColor,
879
+ neutral,
880
+ density: "default",
881
+ radiusStyle: "default"
882
+ };
883
+ } else {
884
+ const preset = SEED_PRESETS.find((p) => p.name.toLowerCase() === selected);
885
+ if (preset) {
886
+ resolved.seeds = { ...preset.seeds };
887
+ }
888
+ }
889
+ } catch {
890
+ }
891
+ }
785
892
  return resolved;
786
893
  }
787
894
  function scaffoldFramework(name, template, pm) {
@@ -806,15 +913,20 @@ function installDeps(projectDir, pm) {
806
913
  execSync(`${install} @fragments-sdk/ui`, { cwd: projectDir, stdio: "inherit" });
807
914
  execSync(`${devInstall} sass`, { cwd: projectDir, stdio: "inherit" });
808
915
  }
809
- function writeThemeFile(projectDir, theme, template) {
916
+ function writeThemeFile(projectDir, template, source) {
810
917
  const stylesDir = join2(projectDir, "src", "styles");
811
918
  mkdirSync(stylesDir, { recursive: true });
812
- const cssContent = generateCssTokens(theme);
813
- const content = `// Fragments UI \u2014 tokens, resets, dark mode
919
+ let content;
920
+ if (source.kind === "seeds") {
921
+ content = generateSeedScss(source.seeds);
922
+ } else {
923
+ const cssContent = generateCssTokens(source.theme);
924
+ content = `// Fragments UI \u2014 tokens, resets, dark mode
814
925
  @use '@fragments-sdk/ui/styles';
815
926
 
816
927
  // Theme overrides (generated from your preset)
817
928
  ${cssContent}`;
929
+ }
818
930
  const filePath = join2(stylesDir, "theme.scss");
819
931
  writeFileSync(filePath, content, "utf-8");
820
932
  if (template === "nextjs") {
@@ -863,10 +975,10 @@ function addNextTranspilePackages(projectDir) {
863
975
  return;
864
976
  }
865
977
  }
866
- function rewriteAppFiles(projectDir, template, themePath, theme) {
978
+ function rewriteAppFiles(projectDir, template, themePath, theme, useSeedPath) {
867
979
  if (template === "nextjs") {
868
980
  const layoutPath = join2(projectDir, "src", "app", "layout.tsx");
869
- writeFileSync(layoutPath, generateNextjsLayout(themePath, theme), "utf-8");
981
+ writeFileSync(layoutPath, generateNextjsLayout(themePath, theme ?? void 0, useSeedPath), "utf-8");
870
982
  const providersPath = join2(projectDir, "src", "app", "providers.tsx");
871
983
  writeFileSync(providersPath, generateNextjsProviders(), "utf-8");
872
984
  const pagePath = join2(projectDir, "src", "app", "page.tsx");
@@ -881,7 +993,7 @@ function rewriteAppFiles(projectDir, template, themePath, theme) {
881
993
  addNextTranspilePackages(projectDir);
882
994
  } else {
883
995
  const mainPath = join2(projectDir, "src", "main.tsx");
884
- writeFileSync(mainPath, generateViteMain(themePath), "utf-8");
996
+ writeFileSync(mainPath, generateViteMain(themePath, useSeedPath), "utf-8");
885
997
  const appPath = join2(projectDir, "src", "App.tsx");
886
998
  writeFileSync(appPath, generateViteApp(), "utf-8");
887
999
  const appCssModulePath = join2(projectDir, "src", "App.module.css");
@@ -892,7 +1004,9 @@ function rewriteAppFiles(projectDir, template, themePath, theme) {
892
1004
  } catch {
893
1005
  }
894
1006
  }
895
- injectFontIntoViteHtml(projectDir, theme);
1007
+ if (theme) {
1008
+ injectFontIntoViteHtml(projectDir, theme);
1009
+ }
896
1010
  }
897
1011
  }
898
1012
  function initGit(projectDir) {
@@ -926,6 +1040,7 @@ ${BRAND.name} Create
926
1040
  const name = resolved.name;
927
1041
  const template = resolved.template;
928
1042
  const pm = resolved.packageManager;
1043
+ const hasSeedPath = !!resolved.seeds;
929
1044
  if (!isValidProjectName(name)) {
930
1045
  return { success: false, error: `Invalid project name: ${name}. Use lowercase letters, numbers, hyphens, dots, underscores.` };
931
1046
  }
@@ -933,18 +1048,21 @@ ${BRAND.name} Create
933
1048
  if (existsSync(projectDir)) {
934
1049
  return { success: false, error: `Directory "${name}" already exists.` };
935
1050
  }
936
- const theme = await resolveTheme(resolved);
937
- if (!theme) {
938
- return { success: false, error: "Invalid theme configuration." };
1051
+ let theme = null;
1052
+ if (!hasSeedPath) {
1053
+ theme = await resolveTheme(resolved);
1054
+ if (!theme) {
1055
+ return { success: false, error: "Invalid theme configuration." };
1056
+ }
939
1057
  }
940
1058
  scaffoldFramework(name, template, pm);
941
1059
  if (!existsSync(projectDir)) {
942
1060
  return { success: false, error: "Framework scaffolding failed \u2014 project directory was not created." };
943
1061
  }
944
1062
  installDeps(projectDir, pm);
945
- const themePath = writeThemeFile(projectDir, theme, template);
946
- rewriteAppFiles(projectDir, template, themePath, theme);
947
- if (resolved.mcp) {
1063
+ const themePath = hasSeedPath ? writeThemeFile(projectDir, template, { kind: "seeds", seeds: resolved.seeds }) : writeThemeFile(projectDir, template, { kind: "css", theme });
1064
+ rewriteAppFiles(projectDir, template, themePath, theme, hasSeedPath);
1065
+ if (resolved.mcp !== false) {
948
1066
  configureMcp(projectDir);
949
1067
  console.log(pc.dim(" Configured MCP server for AI tooling"));
950
1068
  }
@@ -958,7 +1076,14 @@ ${BRAND.name} Create
958
1076
  console.log(` ${pc.cyan("cd")} ${name}`);
959
1077
  console.log(` ${pc.cyan(run)} dev`);
960
1078
  console.log("");
961
- if (theme.name !== "default") {
1079
+ if (hasSeedPath) {
1080
+ const seeds = resolved.seeds;
1081
+ const presetName = seeds.neutral.charAt(0).toUpperCase() + seeds.neutral.slice(1);
1082
+ console.log(pc.dim(` Theme: ${presetName} palette with ${seeds.brand} brand`));
1083
+ console.log(pc.dim(` Edit src/styles/theme.scss to customize seed values.`));
1084
+ console.log(pc.dim(` Fine-tune: ${pc.cyan("fragments init --configure")}
1085
+ `));
1086
+ } else if (theme && theme.name !== "default") {
962
1087
  console.log(pc.dim(` Theme "${theme.name}" applied with full token set.`));
963
1088
  console.log(pc.dim(` Edit src/styles/theme.scss to customize.
964
1089
  `));
@@ -973,4 +1098,4 @@ export {
973
1098
  generateNextjsProviders,
974
1099
  generatePageCssModule
975
1100
  };
976
- //# sourceMappingURL=create-LA4J2HHQ.js.map
1101
+ //# sourceMappingURL=create-AC2PMGBF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/create.ts","../src/theme/serializer.ts","../src/theme/schema.ts","../src/theme/generator.ts","../src/theme/seed-presets.ts"],"sourcesContent":["/**\n * fragments create - Scaffold a new project with Fragments UI and a custom theme\n *\n * Mirrors the shadcn model: configure on the web (usefragments.com/create),\n * then `npx @fragments-sdk/cli create` bootstraps a project with that config.\n */\n\nimport { execSync } from 'node:child_process';\nimport { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport pc from 'picocolors';\nimport { BRAND } from '../core/index.js';\nimport { decompressTheme } from '../theme/serializer.js';\nimport { generateCssTokens, generateScssTokens } from '../theme/generator.js';\nimport type { ThemeConfig } from '../theme/types.js';\nimport { SEED_PRESETS, generateSeedScss } from '../theme/seed-presets.js';\nimport type { SeedConfig } from '../theme/seed-presets.js';\n\n// ============================================\n// Font Helpers\n// ============================================\n\n/**\n * Extract the primary font family name from a CSS font-stack string.\n * e.g. '\"Space Grotesk\", sans-serif' → 'Space Grotesk'\n */\nfunction extractFontFamily(cssFontStack: string): string | null {\n const match = cssFontStack.match(/^[\"']?([^\"',]+)[\"']?/);\n if (!match) return null;\n const name = match[1].trim();\n const generics = [\"sans-serif\", \"serif\", \"monospace\", \"cursive\", \"fantasy\", \"system-ui\"];\n if (generics.includes(name.toLowerCase())) return null;\n if (name.toLowerCase() === \"inter\") return null; // Inter is the default, no need to load\n return name;\n}\n\nfunction googleFontsUrl(familyName: string): string {\n return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(familyName)}:wght@400;500;600;700&display=swap`;\n}\n\n// ============================================\n// Types\n// ============================================\n\nexport interface CreateOptions {\n name?: string;\n template?: 'nextjs' | 'vite';\n packageManager?: 'npm' | 'pnpm' | 'yarn' | 'bun';\n theme?: string;\n preset?: string;\n brand?: string;\n seeds?: SeedConfig;\n scss?: boolean;\n mcp?: boolean;\n yes?: boolean;\n noGit?: boolean;\n}\n\nexport interface CreateResult {\n success: boolean;\n projectDir?: string;\n error?: string;\n}\n\n// ============================================\n// Package Manager Detection\n// ============================================\n\nfunction detectPackageManager(): 'npm' | 'pnpm' | 'yarn' | 'bun' {\n const agent = process.env.npm_config_user_agent || '';\n if (agent.startsWith('pnpm')) return 'pnpm';\n if (agent.startsWith('yarn')) return 'yarn';\n if (agent.startsWith('bun')) return 'bun';\n return 'npm';\n}\n\nfunction getInstallCommand(pm: string): string {\n return pm === 'yarn' ? 'yarn add' : `${pm} add`;\n}\n\nfunction getDevInstallCommand(pm: string): string {\n return pm === 'yarn' ? 'yarn add -D' : `${pm} add -D`;\n}\n\nfunction getRunCommand(pm: string): string {\n return pm === 'npm' ? 'npm run' : pm;\n}\n\n// ============================================\n// Validation\n// ============================================\n\nfunction isValidProjectName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/.test(name);\n}\n\n// ============================================\n// Theme Resolution\n// ============================================\n\nconst PRESET_API_URL = 'https://canny-otter-874.convex.site/api/theme-presets';\n\nasync function fetchPreset(presetId: string): Promise<ThemeConfig | null> {\n try {\n const res = await fetch(`${PRESET_API_URL}?id=${encodeURIComponent(presetId)}`);\n if (!res.ok) return null;\n const theme = await res.json();\n if (!theme || !theme.name) return null;\n return theme as ThemeConfig;\n } catch {\n return null;\n }\n}\n\nasync function resolveTheme(options: CreateOptions): Promise<ThemeConfig | null> {\n if (options.preset) {\n console.log(pc.dim(` Fetching theme preset ${options.preset}...`));\n const theme = await fetchPreset(options.preset);\n if (!theme) {\n console.error(pc.red(`Error: Could not fetch preset \"${options.preset}\". It may have expired or the ID is invalid.`));\n return null;\n }\n return theme;\n }\n\n if (options.theme) {\n const decoded = decompressTheme(options.theme);\n if (!decoded) {\n console.error(pc.red('Error: Could not decode theme string. Make sure you copied it from usefragments.com/create'));\n return null;\n }\n return decoded;\n }\n\n if (options.brand) {\n return {\n name: 'custom',\n colors: {\n accent: options.brand,\n },\n };\n }\n\n return {\n name: 'default',\n colors: {\n accent: '#6366f1',\n },\n };\n}\n\n// ============================================\n// Templates\n// ============================================\n\nexport function generateNextjsLayout(themePath: string, theme?: ThemeConfig, useSeedPath = false): string {\n const fontName = theme?.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;\n const fontUrl = fontName ? googleFontsUrl(fontName) : null;\n\n const htmlOpen = fontUrl\n ? ` <html lang=\"en\" suppressHydrationWarning>\n <head>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossOrigin=\"\" />\n <link href=\"${fontUrl}\" rel=\"stylesheet\" />\n </head>\n <body>`\n : ` <html lang=\"en\" suppressHydrationWarning>\n <body>`;\n\n // When using seed path, theme.scss already @use's the styles entry point.\n // A bare import would compile with default seeds, defeating customization.\n const stylesImport = useSeedPath\n ? `import '${themePath}';`\n : `import '@fragments-sdk/ui/styles';\\nimport '${themePath}';`;\n\n return `import type { Metadata } from 'next';\n${stylesImport}\nimport { Providers } from './providers';\n\nexport const metadata: Metadata = {\n title: 'My App',\n description: 'Built with Fragments UI',\n};\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode;\n}) {\n return (\n${htmlOpen}\n <Providers>{children}</Providers>\n </body>\n </html>\n );\n}\n`;\n}\n\nexport function generateNextjsProviders(): string {\n return `'use client';\n\nimport type { ReactNode } from 'react';\nimport { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';\n\nexport function Providers({ children }: { children: ReactNode }) {\n return (\n <ThemeProvider defaultMode=\"system\">\n <TooltipProvider>\n <ToastProvider>{children}</ToastProvider>\n </TooltipProvider>\n </ThemeProvider>\n );\n}\n`;\n}\n\nexport function generateNextjsPage(): string {\n return `'use client';\n\nimport { useState } from 'react';\nimport {\n Avatar,\n Badge,\n Button,\n Card,\n Checkbox,\n Input,\n Progress,\n Separator,\n Stack,\n Switch,\n Tabs,\n Text,\n Tooltip,\n} from '@fragments-sdk/ui';\nimport styles from './page.module.css';\n\nfunction StatCard({ label, value, change }: { label: string; value: string; change: string }) {\n const isPositive = change.startsWith('+');\n return (\n <Card>\n <Card.Body>\n <Stack gap=\"xs\">\n <Text size=\"sm\" color=\"secondary\">{label}</Text>\n <Text size=\"2xl\" weight=\"semibold\">{value}</Text>\n <Badge variant={isPositive ? 'success' : 'error'} size=\"sm\">{change}</Badge>\n </Stack>\n </Card.Body>\n </Card>\n );\n}\n\nfunction TaskItem({ title, done }: { title: string; done?: boolean }) {\n const [checked, setChecked] = useState(done ?? false);\n return (\n <Stack direction=\"row\" align=\"center\" gap=\"sm\">\n <Checkbox checked={checked} onChange={() => setChecked(!checked)} />\n <Text size=\"sm\" className={checked ? styles.done : undefined}>{title}</Text>\n </Stack>\n );\n}\n\nexport default function Home() {\n const [notifications, setNotifications] = useState(true);\n\n return (\n <main className={styles.page}>\n {/* Hero */}\n <Stack align=\"center\" gap=\"md\" className={styles.hero}>\n <Badge variant=\"info\">Powered by Fragments UI</Badge>\n <Text as=\"h1\" size=\"2xl\" weight=\"bold\">Your app is ready</Text>\n <Text size=\"lg\" color=\"secondary\" className={styles.heroDescription}>\n This page showcases your custom theme and components.\n Edit <code>src/app/page.tsx</code> to start building.\n </Text>\n <Stack direction=\"row\" gap=\"sm\">\n <Button size=\"lg\">Start building</Button>\n <Button size=\"lg\" variant=\"secondary\">Read the docs</Button>\n </Stack>\n </Stack>\n\n <Separator />\n\n {/* Stats */}\n <div className={styles.statsGrid}>\n <StatCard label=\"Components\" value=\"66\" change=\"+12 this month\" />\n <StatCard label=\"Design tokens\" value=\"80\" change=\"+5 new\" />\n <StatCard label=\"Accessibility\" value=\"98%\" change=\"+2.4%\" />\n </div>\n\n {/* Content */}\n <div className={styles.contentGrid}>\n <Card>\n <Card.Header>\n <Card.Title>Getting started</Card.Title>\n <Card.Description>Pick up where you left off</Card.Description>\n </Card.Header>\n <Card.Body>\n <Tabs defaultValue=\"tasks\">\n <Tabs.List>\n <Tabs.Tab value=\"tasks\">Tasks</Tabs.Tab>\n <Tabs.Tab value=\"progress\">Progress</Tabs.Tab>\n </Tabs.List>\n <Tabs.Panel value=\"tasks\">\n <Stack gap=\"sm\" className={styles.tabContent}>\n <TaskItem title=\"Install Fragments UI\" done />\n <TaskItem title=\"Configure your theme\" done />\n <TaskItem title=\"Build your first page\" />\n <TaskItem title=\"Set up MCP for AI tooling\" />\n <TaskItem title=\"Deploy to production\" />\n </Stack>\n </Tabs.Panel>\n <Tabs.Panel value=\"progress\">\n <Stack gap=\"md\" className={styles.tabContent}>\n <Stack gap=\"xs\">\n <Stack direction=\"row\" justify=\"between\">\n <Text size=\"sm\">Setup</Text>\n <Text size=\"sm\" color=\"secondary\">2 of 5</Text>\n </Stack>\n <Progress value={40} />\n </Stack>\n <Text size=\"sm\" color=\"secondary\">\n Complete the remaining tasks to finish setting up your project.\n </Text>\n </Stack>\n </Tabs.Panel>\n </Tabs>\n </Card.Body>\n </Card>\n\n <Card>\n <Card.Header>\n <Card.Title>Settings</Card.Title>\n <Card.Description>Manage your preferences</Card.Description>\n </Card.Header>\n <Card.Body>\n <Stack gap=\"md\">\n <Input placeholder=\"Search settings...\" />\n <Stack direction=\"row\" align=\"center\" justify=\"between\">\n <Stack gap=\"xs\">\n <Text size=\"sm\" weight=\"medium\">Notifications</Text>\n <Text size=\"xs\" color=\"secondary\">Receive alerts for updates</Text>\n </Stack>\n <Switch checked={notifications} onChange={() => setNotifications(!notifications)} />\n </Stack>\n <Separator />\n <Stack direction=\"row\" align=\"center\" justify=\"between\">\n <Stack gap=\"xs\">\n <Text size=\"sm\" weight=\"medium\">Theme</Text>\n <Text size=\"xs\" color=\"secondary\">System preference detected</Text>\n </Stack>\n <Badge variant=\"default\" size=\"sm\">Auto</Badge>\n </Stack>\n <Separator />\n <Stack direction=\"row\" gap=\"sm\" align=\"center\">\n <Avatar name=\"You\" size=\"sm\" />\n <Stack gap=\"xs\" className={styles.flex1}>\n <Text size=\"sm\" weight=\"medium\">Your account</Text>\n <Text size=\"xs\" color=\"secondary\">Manage profile and billing</Text>\n </Stack>\n <Tooltip content=\"Go to account settings\">\n <Button variant=\"ghost\" size=\"sm\">Manage</Button>\n </Tooltip>\n </Stack>\n </Stack>\n </Card.Body>\n </Card>\n </div>\n\n {/* Footer */}\n <Text size=\"sm\" color=\"secondary\" className={styles.footer}>\n Built with{' '}\n <a href=\"https://usefragments.com\" className={styles.link}>Fragments UI</a>\n {' '}&mdash; 66 components, 80 design tokens, accessible by default.\n </Text>\n </main>\n );\n}\n`;\n}\n\nexport function generatePageCssModule(): string {\n return `.page {\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: var(--fui-space-6, 3rem) var(--fui-space-3, 1.5rem);\n gap: var(--fui-space-4, 2rem);\n max-width: 960px;\n margin: 0 auto;\n}\n\n.hero {\n text-align: center;\n padding-top: var(--fui-space-4, 2rem);\n padding-bottom: var(--fui-space-2, 1rem);\n}\n\n.heroDescription {\n max-width: 480px;\n}\n\n.heroDescription code {\n font-size: 0.875em;\n font-family: var(--fui-font-mono, monospace);\n background: var(--fui-bg-secondary);\n padding: 0.125em 0.375em;\n border-radius: var(--fui-radius-sm, 0.25rem);\n}\n\n.statsGrid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: var(--fui-space-2, 1rem);\n width: 100%;\n}\n\n.contentGrid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: var(--fui-space-3, 1.5rem);\n width: 100%;\n}\n\n@media (max-width: 768px) {\n .contentGrid {\n grid-template-columns: 1fr;\n }\n}\n\n.tabContent {\n padding-top: var(--fui-space-1, 0.75rem);\n}\n\n.done {\n text-decoration: line-through;\n opacity: 0.5;\n}\n\n.flex1 {\n flex: 1;\n}\n\n.footer {\n padding-top: var(--fui-space-2, 1rem);\n}\n\n.link {\n color: inherit;\n text-decoration: underline;\n}\n\n.link:hover {\n color: var(--fui-color-accent);\n}\n`;\n}\n\nfunction generateViteMain(themePath: string, useSeedPath = false): string {\n const stylesImport = useSeedPath\n ? `import '${themePath}';`\n : `import '@fragments-sdk/ui/styles';\\nimport '${themePath}';`;\n\n return `import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';\n${stylesImport}\nimport App from './App';\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <ThemeProvider defaultMode=\"system\">\n <TooltipProvider>\n <ToastProvider>\n <App />\n </ToastProvider>\n </TooltipProvider>\n </ThemeProvider>\n </StrictMode>,\n);\n`;\n}\n\nfunction generateViteApp(): string {\n // Same content as Next.js page but with App.module.css import\n return generateNextjsPage()\n .replace(\"'use client';\\n\\n\", '')\n .replace(\"import styles from './page.module.css';\", \"import styles from './App.module.css';\")\n .replace('src/app/page.tsx', 'src/App.tsx')\n .replace('export default function Home()', 'function App()')\n + '\\nexport default App;\\n';\n}\n\nfunction injectFontIntoViteHtml(projectDir: string, theme: ThemeConfig): void {\n const fontName = theme.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;\n if (!fontName) return;\n\n const indexPath = join(projectDir, \"index.html\");\n if (!existsSync(indexPath)) return;\n\n let html = readFileSync(indexPath, \"utf-8\");\n const fontLinks = [\n ' <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />',\n ' <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />',\n ` <link href=\"${googleFontsUrl(fontName)}\" rel=\"stylesheet\" />`,\n ].join(\"\\n\");\n html = html.replace(\"</head>\", `${fontLinks}\\n </head>`);\n writeFileSync(indexPath, html, \"utf-8\");\n}\n\n// ============================================\n// Scaffolding\n// ============================================\n\nasync function promptIfMissing(options: CreateOptions): Promise<CreateOptions> {\n const resolved = { ...options };\n\n if (!resolved.name) {\n if (resolved.yes) {\n resolved.name = 'my-app';\n } else {\n try {\n const { input } = await import('@inquirer/prompts');\n resolved.name = await input({\n message: 'Project name:',\n default: 'my-app',\n validate: (val) => isValidProjectName(val) || 'Use lowercase letters, numbers, hyphens, dots, underscores',\n });\n } catch {\n resolved.name = 'my-app';\n }\n }\n }\n\n if (!resolved.template && !resolved.yes) {\n try {\n const { select } = await import('@inquirer/prompts');\n resolved.template = await select({\n message: 'Framework:',\n choices: [\n { name: 'Next.js (App Router)', value: 'nextjs' as const },\n { name: 'Vite + React', value: 'vite' as const },\n ],\n default: 'nextjs',\n });\n } catch {\n resolved.template = 'nextjs';\n }\n }\n\n resolved.template = resolved.template || 'nextjs';\n resolved.packageManager = resolved.packageManager || detectPackageManager();\n\n // Theme selection — only when no explicit theme flags are passed\n if (!resolved.preset && !resolved.theme && !resolved.brand && !resolved.yes) {\n try {\n const { select, input } = await import('@inquirer/prompts');\n\n const choices = SEED_PRESETS.map((p) => ({\n name: `${p.name} (${p.description})`,\n value: p.name.toLowerCase(),\n }));\n choices.push({ name: 'Custom (pick color + palette)', value: 'custom' });\n\n const selected = await select({\n message: 'Theme:',\n choices,\n default: 'stone',\n });\n\n if (selected === 'custom') {\n const brandColor = await input({\n message: 'Brand color (hex):',\n default: '#6366f1',\n validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || 'Enter a valid hex color (e.g., #6366f1)',\n });\n\n const neutral = await select({\n message: 'Neutral palette:',\n choices: [\n { name: 'Stone (warm gray)', value: 'stone' as const },\n { name: 'Sand (warm beige)', value: 'sand' as const },\n { name: 'Ice (cool blue)', value: 'ice' as const },\n { name: 'Earth (olive green)', value: 'earth' as const },\n { name: 'Fire (warm orange)', value: 'fire' as const },\n ],\n default: 'stone',\n });\n\n resolved.seeds = {\n brand: brandColor,\n neutral,\n density: 'default',\n radiusStyle: 'default',\n };\n } else {\n const preset = SEED_PRESETS.find((p) => p.name.toLowerCase() === selected);\n if (preset) {\n resolved.seeds = { ...preset.seeds };\n }\n }\n } catch {\n // User cancelled or prompt failed — fall through to default theme\n }\n }\n\n return resolved;\n}\n\nfunction scaffoldFramework(name: string, template: string, pm: string): void {\n console.log(pc.cyan(`\\nScaffolding ${template === 'nextjs' ? 'Next.js' : 'Vite + React'} project...\\n`));\n\n if (template === 'nextjs') {\n const cmd = `npx create-next-app@latest ${name} --ts --app --src-dir --no-tailwind --no-eslint --import-alias \"@/*\" --yes`;\n execSync(cmd, { stdio: 'inherit' });\n } else {\n // Vite\n if (pm === 'bun') {\n execSync(`bun create vite ${name} --template react-ts`, { stdio: 'inherit' });\n } else {\n execSync(`npm create vite@latest ${name} -- --template react-ts`, { stdio: 'inherit' });\n }\n }\n}\n\nfunction installDeps(projectDir: string, pm: string): void {\n console.log(pc.cyan('\\nInstalling Fragments UI...\\n'));\n\n const install = getInstallCommand(pm);\n const devInstall = getDevInstallCommand(pm);\n // sass is required — without it, @fragments-sdk/ui/styles resolves to the\n // compiled CSS which is missing the :root token definitions and body resets.\n // The SCSS entry (used when sass is present) includes the full token layer.\n execSync(`${install} @fragments-sdk/ui`, { cwd: projectDir, stdio: 'inherit' });\n execSync(`${devInstall} sass`, { cwd: projectDir, stdio: 'inherit' });\n}\n\nfunction writeThemeFile(\n projectDir: string,\n template: string,\n source: { kind: 'seeds'; seeds: SeedConfig } | { kind: 'css'; theme: ThemeConfig },\n): string {\n const stylesDir = join(projectDir, 'src', 'styles');\n mkdirSync(stylesDir, { recursive: true });\n\n let content: string;\n\n if (source.kind === 'seeds') {\n // Seed path: clean @use ... with() — the SCSS derivation system does the rest\n content = generateSeedScss(source.seeds);\n } else {\n // CSS custom property path: for themes from the website / API / --brand flag\n const cssContent = generateCssTokens(source.theme);\n content = `// Fragments UI — tokens, resets, dark mode\n@use '@fragments-sdk/ui/styles';\n\n// Theme overrides (generated from your preset)\n${cssContent}`;\n }\n\n const filePath = join(stylesDir, 'theme.scss');\n writeFileSync(filePath, content, 'utf-8');\n\n if (template === 'nextjs') {\n return '../styles/theme.scss';\n }\n return './styles/theme.scss';\n}\n\nexport function addNextTranspilePackages(projectDir: string): void {\n const configCandidates = ['next.config.ts', 'next.config.mjs', 'next.config.js'];\n\n for (const configFile of configCandidates) {\n const fullPath = join(projectDir, configFile);\n if (!existsSync(fullPath)) continue;\n\n const content = readFileSync(fullPath, 'utf-8');\n\n if (content.includes('transpilePackages') && content.includes('@fragments-sdk/ui')) {\n return;\n }\n\n if (content.includes('transpilePackages')) {\n return;\n }\n\n const patterns = [\n {\n search: /const\\s+\\w+\\s*(?::\\s*\\w+)?\\s*=\\s*\\{/,\n replacement: (match: string) => `${match}\\n transpilePackages: ['@fragments-sdk/ui'],`,\n },\n {\n search: /module\\.exports\\s*=\\s*\\{/,\n replacement: (match: string) => `${match}\\n transpilePackages: ['@fragments-sdk/ui'],`,\n },\n {\n search: /export\\s+default\\s*\\{/,\n replacement: (match: string) => `${match}\\n transpilePackages: ['@fragments-sdk/ui'],`,\n },\n ];\n\n for (const pattern of patterns) {\n if (!pattern.search.test(content)) continue;\n\n writeFileSync(\n fullPath,\n content.replace(pattern.search, pattern.replacement),\n 'utf-8',\n );\n return;\n }\n\n return;\n }\n}\n\nfunction rewriteAppFiles(\n projectDir: string,\n template: string,\n themePath: string,\n theme: ThemeConfig | null,\n useSeedPath: boolean,\n): void {\n if (template === 'nextjs') {\n // Rewrite layout.tsx\n const layoutPath = join(projectDir, 'src', 'app', 'layout.tsx');\n writeFileSync(layoutPath, generateNextjsLayout(themePath, theme ?? undefined, useSeedPath), 'utf-8');\n\n const providersPath = join(projectDir, 'src', 'app', 'providers.tsx');\n writeFileSync(providersPath, generateNextjsProviders(), 'utf-8');\n\n // Rewrite page.tsx + CSS module\n const pagePath = join(projectDir, 'src', 'app', 'page.tsx');\n writeFileSync(pagePath, generateNextjsPage(), 'utf-8');\n\n const pageCssPath = join(projectDir, 'src', 'app', 'page.module.css');\n writeFileSync(pageCssPath, generatePageCssModule(), 'utf-8');\n\n // Remove globals.css if it exists (we use our own theme)\n const globalsCssPath = join(projectDir, 'src', 'app', 'globals.css');\n try {\n unlinkSync(globalsCssPath);\n } catch { /* doesn't exist */ }\n\n addNextTranspilePackages(projectDir);\n } else {\n // Vite: rewrite main.tsx and App.tsx\n const mainPath = join(projectDir, 'src', 'main.tsx');\n writeFileSync(mainPath, generateViteMain(themePath, useSeedPath), 'utf-8');\n\n const appPath = join(projectDir, 'src', 'App.tsx');\n writeFileSync(appPath, generateViteApp(), 'utf-8');\n\n const appCssModulePath = join(projectDir, 'src', 'App.module.css');\n writeFileSync(appCssModulePath, generatePageCssModule(), 'utf-8');\n\n // Clean up default Vite files\n for (const file of ['src/App.css', 'src/index.css']) {\n try {\n unlinkSync(join(projectDir, file));\n } catch { /* doesn't exist */ }\n }\n\n // Font injection only applies to CSS path (seed path has no typography config)\n if (theme) {\n injectFontIntoViteHtml(projectDir, theme);\n }\n }\n}\n\nfunction initGit(projectDir: string): void {\n try {\n // create-next-app already inits git, so check first\n if (!existsSync(join(projectDir, '.git'))) {\n execSync('git init', { cwd: projectDir, stdio: 'ignore' });\n execSync('git add -A', { cwd: projectDir, stdio: 'ignore' });\n execSync('git commit -m \"Initial commit with Fragments UI\"', { cwd: projectDir, stdio: 'ignore' });\n }\n } catch {\n // Git not available — skip silently\n }\n}\n\nfunction configureMcp(projectDir: string): void {\n const mcpConfig = {\n mcpServers: {\n fragments: {\n command: 'npx',\n args: ['-y', '@fragments-sdk/cli', 'mcp'],\n },\n },\n };\n\n const mcpDir = join(projectDir, '.mcp');\n mkdirSync(mcpDir, { recursive: true });\n writeFileSync(join(mcpDir, 'config.json'), JSON.stringify(mcpConfig, null, 2), 'utf-8');\n}\n\n// ============================================\n// Main\n// ============================================\n\nexport async function create(options: CreateOptions): Promise<CreateResult> {\n console.log(pc.cyan(`\\n${BRAND.name} Create\\n`));\n\n // 1. Gather inputs\n const resolved = await promptIfMissing(options);\n const name = resolved.name!;\n const template = resolved.template!;\n const pm = resolved.packageManager!;\n const hasSeedPath = !!resolved.seeds;\n\n // 2. Validate\n if (!isValidProjectName(name)) {\n return { success: false, error: `Invalid project name: ${name}. Use lowercase letters, numbers, hyphens, dots, underscores.` };\n }\n\n const projectDir = resolve(process.cwd(), name);\n if (existsSync(projectDir)) {\n return { success: false, error: `Directory \"${name}\" already exists.` };\n }\n\n // 3. Resolve theme (skip when using seed path — SCSS handles derivation)\n let theme: ThemeConfig | null = null;\n if (!hasSeedPath) {\n theme = await resolveTheme(resolved);\n if (!theme) {\n return { success: false, error: 'Invalid theme configuration.' };\n }\n }\n\n // 4. Scaffold framework\n scaffoldFramework(name, template, pm);\n\n if (!existsSync(projectDir)) {\n return { success: false, error: 'Framework scaffolding failed — project directory was not created.' };\n }\n\n // 5. Install deps\n installDeps(projectDir, pm);\n\n // 6. Write theme tokens\n const themePath = hasSeedPath\n ? writeThemeFile(projectDir, template, { kind: 'seeds', seeds: resolved.seeds! })\n : writeThemeFile(projectDir, template, { kind: 'css', theme: theme! });\n\n // 7. Rewrite app files with providers + theme import\n rewriteAppFiles(projectDir, template, themePath, theme, hasSeedPath);\n\n // 8. MCP config (default-on; pass --no-mcp to skip)\n if (resolved.mcp !== false) {\n configureMcp(projectDir);\n console.log(pc.dim(' Configured MCP server for AI tooling'));\n }\n\n // 9. Git init\n if (!resolved.noGit) {\n initGit(projectDir);\n }\n\n // 10. Success message\n const run = getRunCommand(pm);\n console.log('');\n console.log(pc.green(' Project created successfully!'));\n console.log('');\n console.log(` ${pc.cyan('cd')} ${name}`);\n console.log(` ${pc.cyan(run)} dev`);\n console.log('');\n\n if (hasSeedPath) {\n const seeds = resolved.seeds!;\n const presetName = seeds.neutral.charAt(0).toUpperCase() + seeds.neutral.slice(1);\n console.log(pc.dim(` Theme: ${presetName} palette with ${seeds.brand} brand`));\n console.log(pc.dim(` Edit src/styles/theme.scss to customize seed values.`));\n console.log(pc.dim(` Fine-tune: ${pc.cyan('fragments init --configure')}\\n`));\n } else if (theme && theme.name !== 'default') {\n console.log(pc.dim(` Theme \"${theme.name}\" applied with full token set.`));\n console.log(pc.dim(` Edit src/styles/theme.scss to customize.\\n`));\n }\n\n return { success: true, projectDir };\n}\n","/**\n * Theme Serialization\n *\n * Encodes/decodes theme configurations to/from URL-safe strings\n * using zlib compression and base64url encoding.\n */\n\nimport { deflateSync, inflateSync } from \"node:zlib\";\nimport { validateThemeConfig } from \"./schema.js\";\nimport type { ThemeConfig } from \"./types.js\";\n\nconst DEFAULT_BASE_URL = \"https://usefragments.com/create\";\n\n/**\n * Convert a Buffer to base64url encoding (URL-safe base64)\n */\nfunction toBase64Url(buffer: Buffer): string {\n return buffer\n .toString(\"base64\")\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=/g, \"\");\n}\n\n/**\n * Convert a base64url string to Buffer\n */\nfunction fromBase64Url(str: string): Buffer {\n // Restore standard base64 characters\n let base64 = str.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\n // Add padding if needed\n const pad = base64.length % 4;\n if (pad) {\n base64 += \"=\".repeat(4 - pad);\n }\n\n return Buffer.from(base64, \"base64\");\n}\n\n/**\n * Compress a theme configuration to a URL-safe string\n *\n * Process: JSON → deflate → base64url\n *\n * @param config - Theme configuration to compress\n * @returns URL-safe encoded string\n */\nexport function compressTheme(config: ThemeConfig): string {\n const json = JSON.stringify(config);\n const compressed = deflateSync(Buffer.from(json, \"utf-8\"), { level: 9 });\n return toBase64Url(compressed);\n}\n\n/**\n * Decompress a URL-safe string back to theme configuration\n *\n * Process: base64url → inflate → JSON\n *\n * @param encoded - URL-safe encoded string\n * @returns Theme configuration or null if invalid\n */\nexport function decompressTheme(encoded: string): ThemeConfig | null {\n // Try zlib-compressed first (CLI native encoding)\n try {\n const buffer = fromBase64Url(encoded);\n const decompressed = inflateSync(buffer);\n const json = decompressed.toString(\"utf-8\");\n const parsed = JSON.parse(json);\n const result = validateThemeConfig(parsed);\n if (result.success) return result.data;\n } catch { /* not zlib-compressed */ }\n\n // Fall back to plain base64url JSON (browser/docs encoding)\n try {\n const buffer = fromBase64Url(encoded);\n const json = buffer.toString(\"utf-8\");\n const parsed = JSON.parse(json);\n const result = validateThemeConfig(parsed);\n if (result.success) return result.data;\n } catch { /* invalid */ }\n\n return null;\n}\n\n/**\n * Encode a theme configuration to a full URL with ?preset= parameter\n *\n * @param config - Theme configuration to encode\n * @param baseUrl - Base URL (default: https://fragments.dev/init)\n * @returns Full URL with encoded theme as preset parameter\n */\nexport function encodeThemeToUrl(\n config: ThemeConfig,\n baseUrl: string = DEFAULT_BASE_URL\n): string {\n const encoded = compressTheme(config);\n const url = new URL(baseUrl);\n url.searchParams.set(\"preset\", encoded);\n return url.toString();\n}\n\n/**\n * Decode a theme from a URL or raw encoded string\n *\n * Handles:\n * - Full URLs with ?preset= parameter\n * - Raw encoded strings (compressed theme data)\n *\n * @param input - URL or encoded string\n * @returns Theme configuration or null if invalid\n */\nexport function decodeThemeFromUrl(input: string): ThemeConfig | null {\n // Try to parse as URL first\n try {\n const url = new URL(input);\n const preset = url.searchParams.get(\"preset\");\n if (preset) {\n return decompressTheme(preset);\n }\n // URL parsed but no preset param\n return null;\n } catch {\n // Not a valid URL, try as raw encoded string\n return decompressTheme(input);\n }\n}\n","/**\n * Theme Configuration Zod Schemas\n *\n * Provides validation for theme configuration objects\n */\n\nimport { z } from \"zod\";\nimport type { ThemeConfig } from \"./types.js\";\n\n/**\n * Regex patterns for color validation\n */\nconst HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst RGB_COLOR_REGEX = /^rgba?\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*,\\s*\\d{1,3}\\s*(,\\s*(0|1|0?\\.\\d+))?\\s*\\)$/;\nconst HSL_COLOR_REGEX = /^hsla?\\(\\s*\\d{1,3}\\s*,\\s*\\d{1,3}%\\s*,\\s*\\d{1,3}%\\s*(,\\s*(0|1|0?\\.\\d+))?\\s*\\)$/;\n\n/**\n * CSS color string validator\n */\nconst colorSchema = z.string().refine(\n (val) => {\n return (\n HEX_COLOR_REGEX.test(val) ||\n RGB_COLOR_REGEX.test(val) ||\n HSL_COLOR_REGEX.test(val)\n );\n },\n { message: \"Invalid color format. Use hex (#rrggbb), rgb(), rgba(), hsl(), or hsla()\" }\n);\n\n/**\n * CSS size value validator (px, rem, em, etc.)\n */\nconst sizeSchema = z.string().refine(\n (val) => {\n // Match number followed by unit, or just a number (for unitless values like 9999px)\n return /^-?\\d*\\.?\\d+(px|rem|em|%|vw|vh)?$/.test(val);\n },\n { message: \"Invalid size format. Use px, rem, em, %, vw, or vh\" }\n);\n\n/**\n * Shadow value validator (more permissive for complex shadow syntax)\n */\nconst shadowSchema = z.string();\n\n/**\n * Font stack validator\n */\nconst fontStackSchema = z.string();\n\n/**\n * Font weight validator (100-900 in increments of 100)\n */\nconst fontWeightSchema = z.number().int().min(100).max(900);\n\n/**\n * Theme colors schema\n */\nexport const themeColorsSchema = z.object({\n accent: colorSchema.optional(),\n accentHover: colorSchema.optional(),\n accentActive: colorSchema.optional(),\n danger: colorSchema.optional(),\n dangerHover: colorSchema.optional(),\n success: colorSchema.optional(),\n warning: colorSchema.optional(),\n info: colorSchema.optional(),\n dangerBg: colorSchema.optional(),\n successBg: colorSchema.optional(),\n warningBg: colorSchema.optional(),\n infoBg: colorSchema.optional(),\n}).strict();\n\n/**\n * Theme surfaces schema\n */\nexport const themeSurfacesSchema = z.object({\n bgPrimary: colorSchema.optional(),\n bgSecondary: colorSchema.optional(),\n bgTertiary: colorSchema.optional(),\n bgElevated: colorSchema.optional(),\n bgHover: colorSchema.optional(),\n bgActive: colorSchema.optional(),\n}).strict();\n\n/**\n * Theme text schema\n */\nexport const themeTextSchema = z.object({\n primary: colorSchema.optional(),\n secondary: colorSchema.optional(),\n tertiary: colorSchema.optional(),\n inverse: colorSchema.optional(),\n}).strict();\n\n/**\n * Theme borders schema\n */\nexport const themeBordersSchema = z.object({\n default: colorSchema.optional(),\n strong: colorSchema.optional(),\n}).strict();\n\n/**\n * Theme typography schema\n */\nexport const themeTypographySchema = z.object({\n fontSans: fontStackSchema.optional(),\n fontMono: fontStackSchema.optional(),\n fontWeightNormal: fontWeightSchema.optional(),\n fontWeightMedium: fontWeightSchema.optional(),\n fontWeightSemibold: fontWeightSchema.optional(),\n}).strict();\n\n/**\n * Theme radius schema\n */\nexport const themeRadiusSchema = z.object({\n sm: sizeSchema.optional(),\n md: sizeSchema.optional(),\n lg: sizeSchema.optional(),\n full: sizeSchema.optional(),\n}).strict();\n\n/**\n * Theme shadows schema\n */\nexport const themeShadowsSchema = z.object({\n sm: shadowSchema.optional(),\n md: shadowSchema.optional(),\n}).strict();\n\n/**\n * Theme dark mode schema\n */\nexport const themeDarkModeSchema = z.object({\n surfaces: themeSurfacesSchema.optional(),\n text: themeTextSchema.optional(),\n borders: themeBordersSchema.optional(),\n shadows: themeShadowsSchema.optional(),\n accent: colorSchema.optional(),\n accentHover: colorSchema.optional(),\n accentActive: colorSchema.optional(),\n dangerBg: colorSchema.optional(),\n successBg: colorSchema.optional(),\n warningBg: colorSchema.optional(),\n infoBg: colorSchema.optional(),\n backdrop: colorSchema.optional(),\n dangerText: colorSchema.optional(),\n successText: colorSchema.optional(),\n warningText: colorSchema.optional(),\n infoText: colorSchema.optional(),\n}).strict();\n\n/**\n * Complete theme configuration schema\n */\nexport const themeConfigSchema = z.object({\n name: z.string().min(1, \"Theme name is required\"),\n version: z.string().optional(),\n extends: z.string().optional(),\n colors: themeColorsSchema.optional(),\n surfaces: themeSurfacesSchema.optional(),\n text: themeTextSchema.optional(),\n borders: themeBordersSchema.optional(),\n typography: themeTypographySchema.optional(),\n radius: themeRadiusSchema.optional(),\n shadows: themeShadowsSchema.optional(),\n dark: themeDarkModeSchema.optional(),\n density: z.enum(['compact', 'default', 'relaxed']).optional(),\n}).strict();\n\n/**\n * Type for the inferred schema\n */\nexport type ThemeConfigInput = z.input<typeof themeConfigSchema>;\nexport type ThemeConfigOutput = z.output<typeof themeConfigSchema>;\n\n/**\n * Validation result type\n */\nexport type ThemeValidationResult =\n | { success: true; data: ThemeConfig }\n | { success: false; error: z.ZodError };\n\n/**\n * Validate a theme configuration object\n *\n * @param config - The theme configuration to validate\n * @returns Validation result with either the parsed data or error details\n */\nexport function validateThemeConfig(config: unknown): ThemeValidationResult {\n const result = themeConfigSchema.safeParse(config);\n\n if (result.success) {\n return { success: true, data: result.data as ThemeConfig };\n }\n\n return { success: false, error: result.error };\n}\n","/**\n * Theme Token Generator\n *\n * Generates SCSS and CSS token files from theme configurations\n */\n\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type {\n ThemeConfig,\n TokenGeneratorOptions,\n TokenGeneratorResult,\n ContrastPair,\n ContrastWarning,\n ContrastValidationResult,\n} from \"./types.js\";\nimport {\n parseColor,\n contrastRatio,\n meetsAA as checkAA,\n meetsAAA as checkAAA,\n} from \"./contrast.js\";\n\nconst DEFAULT_FILE_PREFIX = \"_theme-tokens\";\n\n/**\n * Token mapping from ThemeConfig structure to SCSS variable names\n */\nconst TOKEN_MAPPINGS = {\n colors: {\n accent: \"fui-color-accent\",\n accentHover: \"fui-color-accent-hover\",\n accentActive: \"fui-color-accent-active\",\n danger: \"fui-color-danger\",\n dangerHover: \"fui-color-danger-hover\",\n success: \"fui-color-success\",\n warning: \"fui-color-warning\",\n info: \"fui-color-info\",\n dangerBg: \"fui-color-danger-bg\",\n successBg: \"fui-color-success-bg\",\n warningBg: \"fui-color-warning-bg\",\n infoBg: \"fui-color-info-bg\",\n dangerText: \"fui-color-danger-text\",\n successText: \"fui-color-success-text\",\n warningText: \"fui-color-warning-text\",\n infoText: \"fui-color-info-text\",\n },\n surfaces: {\n bgPrimary: \"fui-bg-primary\",\n bgSecondary: \"fui-bg-secondary\",\n bgTertiary: \"fui-bg-tertiary\",\n bgElevated: \"fui-bg-elevated\",\n bgHover: \"fui-bg-hover\",\n bgActive: \"fui-bg-active\",\n },\n text: {\n primary: \"fui-text-primary\",\n secondary: \"fui-text-secondary\",\n tertiary: \"fui-text-tertiary\",\n inverse: \"fui-text-inverse\",\n },\n borders: {\n default: \"fui-border\",\n strong: \"fui-border-strong\",\n },\n typography: {\n fontSans: \"fui-font-sans\",\n fontMono: \"fui-font-mono\",\n fontWeightNormal: \"fui-font-weight-normal\",\n fontWeightMedium: \"fui-font-weight-medium\",\n fontWeightSemibold: \"fui-font-weight-semibold\",\n },\n radius: {\n sm: \"fui-radius-sm\",\n md: \"fui-radius-md\",\n lg: \"fui-radius-lg\",\n full: \"fui-radius-full\",\n },\n shadows: {\n sm: \"fui-shadow-sm\",\n md: \"fui-shadow-md\",\n },\n} as const;\n\n/**\n * Dark mode token mappings\n */\nconst DARK_TOKEN_MAPPINGS = {\n surfaces: {\n bgPrimary: \"fui-dark-bg-primary\",\n bgSecondary: \"fui-dark-bg-secondary\",\n bgTertiary: \"fui-dark-bg-tertiary\",\n bgElevated: \"fui-dark-bg-elevated\",\n bgHover: \"fui-dark-bg-hover\",\n bgActive: \"fui-dark-bg-active\",\n },\n text: {\n primary: \"fui-dark-text-primary\",\n secondary: \"fui-dark-text-secondary\",\n tertiary: \"fui-dark-text-tertiary\",\n inverse: \"fui-dark-text-inverse\",\n },\n borders: {\n default: \"fui-dark-border\",\n strong: \"fui-dark-border-strong\",\n },\n shadows: {\n sm: \"fui-dark-shadow-sm\",\n md: \"fui-dark-shadow-md\",\n },\n // Direct dark mode properties\n accent: \"fui-dark-color-accent\",\n accentHover: \"fui-dark-color-accent-hover\",\n accentActive: \"fui-dark-color-accent-active\",\n dangerBg: \"fui-dark-color-danger-bg\",\n successBg: \"fui-dark-color-success-bg\",\n warningBg: \"fui-dark-color-warning-bg\",\n infoBg: \"fui-dark-color-info-bg\",\n dangerText: \"fui-dark-color-danger-text\",\n successText: \"fui-dark-color-success-text\",\n warningText: \"fui-dark-color-warning-text\",\n infoText: \"fui-dark-color-info-text\",\n backdrop: \"fui-dark-backdrop\",\n} as const;\n\n/**\n * Generate tokens for a category\n */\nfunction generateCategoryTokens(\n config: ThemeConfig,\n categoryKey: keyof typeof TOKEN_MAPPINGS,\n format: \"scss\" | \"css\"\n): string[] {\n const category = config[categoryKey];\n if (!category) return [];\n\n const mappings = TOKEN_MAPPINGS[categoryKey];\n const tokens: string[] = [];\n\n for (const [key, varName] of Object.entries(mappings)) {\n const value = (category as Record<string, unknown>)[key];\n if (value !== undefined) {\n if (format === \"scss\") {\n tokens.push(`$${varName}: ${value} !default;`);\n } else {\n tokens.push(` --${varName}: ${value};`);\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Generate dark mode tokens\n */\nfunction generateDarkTokens(\n config: ThemeConfig,\n format: \"scss\" | \"css\"\n): string[] {\n if (!config.dark) return [];\n\n const tokens: string[] = [];\n\n // Handle nested categories\n const nestedCategories = [\"surfaces\", \"text\", \"borders\", \"shadows\"] as const;\n for (const category of nestedCategories) {\n const categoryData = config.dark[category];\n if (!categoryData) continue;\n\n const mappings = DARK_TOKEN_MAPPINGS[category];\n for (const [key, varName] of Object.entries(mappings)) {\n const value = (categoryData as Record<string, unknown>)[key];\n if (value !== undefined) {\n if (format === \"scss\") {\n tokens.push(`$${varName}: ${value} !default;`);\n } else {\n tokens.push(` --${varName.replace(\"fui-dark-\", \"fui-\")}: ${value};`);\n }\n }\n }\n }\n\n // Handle direct dark mode properties\n const directProps = [\"accent\", \"accentHover\", \"accentActive\", \"dangerBg\", \"successBg\", \"warningBg\", \"infoBg\", \"dangerText\", \"successText\", \"warningText\", \"infoText\", \"backdrop\"] as const;\n for (const prop of directProps) {\n const value = config.dark[prop];\n if (value !== undefined) {\n const varName = DARK_TOKEN_MAPPINGS[prop];\n if (format === \"scss\") {\n tokens.push(`$${varName}: ${value} !default;`);\n } else {\n // For CSS, use the light mode variable name in dark context\n const cssVarName = varName.replace(\"fui-dark-color-\", \"fui-color-\").replace(\"fui-dark-\", \"fui-\");\n tokens.push(` --${cssVarName}: ${value};`);\n }\n }\n }\n\n return tokens;\n}\n\n/**\n * Validate contrast across all text/surface token pairs in a theme.\n *\n * Returns `ContrastValidationResult` with every evaluated pair plus\n * errors (fails AA) and warnings (passes AA, fails AAA).\n */\nexport function validateContrast(config: ThemeConfig): ContrastValidationResult {\n const pairs: ContrastPair[] = [];\n const warnings: ContrastWarning[] = [];\n const errors: ContrastWarning[] = [];\n\n const textTokens = [\n { key: 'primary', token: 'text.primary' },\n { key: 'secondary', token: 'text.secondary' },\n { key: 'tertiary', token: 'text.tertiary' },\n ] as const;\n\n const surfaceTokens = [\n { key: 'bgPrimary', token: 'surfaces.bgPrimary' },\n { key: 'bgSecondary', token: 'surfaces.bgSecondary' },\n { key: 'bgTertiary', token: 'surfaces.bgTertiary' },\n { key: 'bgElevated', token: 'surfaces.bgElevated' },\n ] as const;\n\n // -- Light mode matrix --\n for (const text of textTokens) {\n const textColor = config.text?.[text.key];\n if (!textColor) continue;\n\n for (const surface of surfaceTokens) {\n const surfaceColor = config.surfaces?.[surface.key];\n if (!surfaceColor) continue;\n\n try {\n const fg = parseColor(textColor);\n const bg = parseColor(surfaceColor);\n const ratio = contrastRatio(fg, bg);\n const aa = checkAA(ratio);\n const aaa = checkAAA(ratio);\n\n const pair: ContrastPair = {\n textToken: text.token,\n surfaceToken: surface.token,\n textColor,\n surfaceColor,\n ratio: Math.round(ratio * 100) / 100,\n meetsAA: aa,\n meetsAAA: aaa,\n mode: 'light',\n };\n pairs.push(pair);\n\n if (!aa) {\n errors.push({\n message: `${text.token} (${textColor}) on ${surface.token} (${surfaceColor}) — ${pair.ratio}:1 — fails AA (need 4.5:1)`,\n pair,\n severity: 'error',\n });\n } else if (!aaa) {\n warnings.push({\n message: `${text.token} (${textColor}) on ${surface.token} (${surfaceColor}) — ${pair.ratio}:1 — passes AA but fails AAA (need 7:1)`,\n pair,\n severity: 'warning',\n });\n }\n } catch {\n // Skip unparseable colors (e.g. rgba with alpha)\n }\n }\n }\n\n // inverse text on accent\n if (config.text?.inverse && config.colors?.accent) {\n try {\n const fg = parseColor(config.text.inverse);\n const bg = parseColor(config.colors.accent);\n const ratio = contrastRatio(fg, bg);\n const aa = checkAA(ratio);\n const aaa = checkAAA(ratio);\n\n const pair: ContrastPair = {\n textToken: 'text.inverse',\n surfaceToken: 'colors.accent',\n textColor: config.text.inverse,\n surfaceColor: config.colors.accent,\n ratio: Math.round(ratio * 100) / 100,\n meetsAA: aa,\n meetsAAA: aaa,\n mode: 'light',\n };\n pairs.push(pair);\n\n if (!aa) {\n errors.push({\n message: `text.inverse (${config.text.inverse}) on colors.accent (${config.colors.accent}) — ${pair.ratio}:1 — fails AA`,\n pair,\n severity: 'error',\n });\n } else if (!aaa) {\n warnings.push({\n message: `text.inverse (${config.text.inverse}) on colors.accent (${config.colors.accent}) — ${pair.ratio}:1 — passes AA but fails AAA`,\n pair,\n severity: 'warning',\n });\n }\n } catch {\n // skip\n }\n }\n\n // -- Dark mode matrix --\n if (config.dark) {\n const darkTextTokens = [\n { key: 'primary' as const, token: 'dark.text.primary' },\n { key: 'secondary' as const, token: 'dark.text.secondary' },\n { key: 'tertiary' as const, token: 'dark.text.tertiary' },\n ];\n\n const darkSurfaceTokens = [\n { key: 'bgPrimary' as const, token: 'dark.surfaces.bgPrimary' },\n { key: 'bgSecondary' as const, token: 'dark.surfaces.bgSecondary' },\n { key: 'bgTertiary' as const, token: 'dark.surfaces.bgTertiary' },\n { key: 'bgElevated' as const, token: 'dark.surfaces.bgElevated' },\n ];\n\n for (const text of darkTextTokens) {\n const textColor = config.dark.text?.[text.key] ?? config.text?.[text.key];\n if (!textColor) continue;\n\n for (const surface of darkSurfaceTokens) {\n const surfaceColor = config.dark.surfaces?.[surface.key] ?? config.surfaces?.[surface.key];\n if (!surfaceColor) continue;\n\n try {\n const fg = parseColor(textColor);\n const bg = parseColor(surfaceColor);\n const ratio = contrastRatio(fg, bg);\n const aa = checkAA(ratio);\n const aaa = checkAAA(ratio);\n\n const pair: ContrastPair = {\n textToken: text.token,\n surfaceToken: surface.token,\n textColor,\n surfaceColor,\n ratio: Math.round(ratio * 100) / 100,\n meetsAA: aa,\n meetsAAA: aaa,\n mode: 'dark',\n };\n pairs.push(pair);\n\n if (!aa) {\n errors.push({\n message: `${text.token} (${textColor}) on ${surface.token} (${surfaceColor}) — ${pair.ratio}:1 — fails AA`,\n pair,\n severity: 'error',\n });\n } else if (!aaa) {\n warnings.push({\n message: `${text.token} (${textColor}) on ${surface.token} (${surfaceColor}) — ${pair.ratio}:1 — passes AA but fails AAA`,\n pair,\n severity: 'warning',\n });\n }\n } catch {\n // skip\n }\n }\n }\n }\n\n return {\n pairs,\n warnings,\n errors,\n passed: errors.length === 0,\n };\n}\n\n/**\n * Generate SCSS tokens from a theme configuration\n *\n * @param config - Theme configuration\n * @returns SCSS content string\n */\nexport function generateScssTokens(config: ThemeConfig): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"// Auto-generated by @fragments-sdk/cli\");\n lines.push(`// Theme: ${config.name}`);\n if (config.version) {\n lines.push(`// Version: ${config.version}`);\n }\n lines.push(\"\");\n\n // Colors\n const colorTokens = generateCategoryTokens(config, \"colors\", \"scss\");\n if (colorTokens.length > 0) {\n lines.push(\"// Colors\");\n lines.push(...colorTokens);\n lines.push(\"\");\n }\n\n // Surfaces\n const surfaceTokens = generateCategoryTokens(config, \"surfaces\", \"scss\");\n if (surfaceTokens.length > 0) {\n lines.push(\"// Surfaces\");\n lines.push(...surfaceTokens);\n lines.push(\"\");\n }\n\n // Text\n const textTokens = generateCategoryTokens(config, \"text\", \"scss\");\n if (textTokens.length > 0) {\n lines.push(\"// Text\");\n lines.push(...textTokens);\n lines.push(\"\");\n }\n\n // Borders\n const borderTokens = generateCategoryTokens(config, \"borders\", \"scss\");\n if (borderTokens.length > 0) {\n lines.push(\"// Borders\");\n lines.push(...borderTokens);\n if (config.borders?.default) {\n lines.push(`$fui-border-default: ${config.borders.default} !default;`);\n }\n lines.push(\"\");\n }\n\n // Typography\n const typographyTokens = generateCategoryTokens(config, \"typography\", \"scss\");\n if (typographyTokens.length > 0) {\n lines.push(\"// Typography\");\n lines.push(...typographyTokens);\n lines.push(\"\");\n }\n\n // Border Radius\n const radiusTokens = generateCategoryTokens(config, \"radius\", \"scss\");\n if (radiusTokens.length > 0) {\n lines.push(\"// Border Radius\");\n lines.push(...radiusTokens);\n lines.push(\"\");\n }\n\n // Shadows\n const shadowTokens = generateCategoryTokens(config, \"shadows\", \"scss\");\n if (shadowTokens.length > 0) {\n lines.push(\"// Shadows\");\n lines.push(...shadowTokens);\n lines.push(\"\");\n }\n\n // Density\n if (config.density) {\n lines.push(\"// Density\");\n lines.push(`$fui-density: \"${config.density}\" !default;`);\n lines.push(\"\");\n }\n\n // Dark mode\n const darkTokens = generateDarkTokens(config, \"scss\");\n if (darkTokens.length > 0) {\n lines.push(\"// Dark Mode\");\n lines.push(...darkTokens);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate CSS custom properties from a theme configuration\n *\n * @param config - Theme configuration\n * @returns CSS content string\n */\nexport function generateCssTokens(config: ThemeConfig): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"/* Auto-generated by @fragments-sdk/cli */\");\n lines.push(`/* Theme: ${config.name} */`);\n if (config.version) {\n lines.push(`/* Version: ${config.version} */`);\n }\n lines.push(\"\");\n\n // Collect all light mode tokens\n const lightTokens: string[] = [];\n\n const categories = [\"colors\", \"surfaces\", \"text\", \"borders\", \"typography\", \"radius\", \"shadows\"] as const;\n for (const category of categories) {\n const tokens = generateCategoryTokens(config, category, \"css\");\n lightTokens.push(...tokens);\n }\n\n // Emit --fui-border-default alias (components reference both names)\n if (config.borders?.default) {\n lightTokens.push(` --fui-border-default: ${config.borders.default};`);\n }\n\n // Density\n if (config.density) {\n lightTokens.push(` --fui-density: ${config.density};`);\n }\n\n if (lightTokens.length > 0) {\n lines.push(\":root {\");\n lines.push(...lightTokens);\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n // Dark mode\n const darkTokens = generateDarkTokens(config, \"css\");\n\n // Border-default alias for dark mode\n if (config.dark?.borders?.default) {\n darkTokens.push(` --fui-border-default: ${config.dark.borders.default};`);\n }\n\n if (darkTokens.length > 0) {\n lines.push(':root.dark,');\n lines.push(':root[data-theme=\"dark\"] {');\n lines.push(...darkTokens);\n lines.push(\"}\");\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Generate token files from a theme configuration\n *\n * @param config - Theme configuration\n * @param options - Generator options\n * @returns Result with file paths or error\n */\nexport async function generateTokenFiles(\n config: ThemeConfig,\n options: TokenGeneratorOptions\n): Promise<TokenGeneratorResult> {\n const { format, outputDir, filePrefix = DEFAULT_FILE_PREFIX } = options;\n\n try {\n // Ensure output directory exists\n await mkdir(outputDir, { recursive: true });\n\n const result: TokenGeneratorResult = { success: true };\n\n // Run contrast validation (non-blocking)\n const contrastResult = validateContrast(config);\n result.contrastValidation = contrastResult;\n\n if (contrastResult.errors.length > 0) {\n for (const err of contrastResult.errors) {\n console.warn(`[contrast] ERROR: ${err.message}`);\n }\n }\n if (contrastResult.warnings.length > 0) {\n for (const warn of contrastResult.warnings) {\n console.warn(`[contrast] WARNING: ${warn.message}`);\n }\n }\n\n // Generate SCSS if requested\n if (format === \"scss\" || format === \"both\") {\n const scssContent = generateScssTokens(config);\n const scssPath = join(outputDir, `${filePrefix}.scss`);\n await writeFile(scssPath, scssContent, \"utf-8\");\n result.scssPath = scssPath;\n }\n\n // Generate CSS if requested\n if (format === \"css\" || format === \"both\") {\n const cssContent = generateCssTokens(config);\n const cssPath = join(outputDir, `${filePrefix}.css`);\n await writeFile(cssPath, cssContent, \"utf-8\");\n result.cssPath = cssPath;\n }\n\n return result;\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n","/**\n * Seed-Based Presets for `fragments create`\n *\n * Each preset maps to SCSS seed variables consumed by\n * `@use '@fragments-sdk/ui/styles' with (...)`.\n * The SCSS derivation system expands 4 seeds into 120+ design tokens.\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface SeedConfig {\n brand: string;\n neutral: \"stone\" | \"ice\" | \"sand\" | \"earth\" | \"fire\";\n density: \"compact\" | \"default\" | \"relaxed\";\n radiusStyle: \"sharp\" | \"subtle\" | \"default\" | \"rounded\" | \"pill\";\n}\n\nexport interface SeedPreset {\n name: string;\n description: string;\n seeds: SeedConfig;\n}\n\n// ============================================\n// Presets\n// ============================================\n\n/** Brand colors chosen from each palette's 600 shade for visual cohesion */\nexport const SEED_PRESETS: SeedPreset[] = [\n {\n name: \"Stone\",\n description: \"warm gray\",\n seeds: { brand: \"#52525b\", neutral: \"stone\", density: \"default\", radiusStyle: \"default\" },\n },\n {\n name: \"Sand\",\n description: \"warm beige\",\n seeds: { brand: \"#8b5e34\", neutral: \"sand\", density: \"default\", radiusStyle: \"default\" },\n },\n {\n name: \"Ice\",\n description: \"cool blue\",\n seeds: { brand: \"#0284c7\", neutral: \"ice\", density: \"default\", radiusStyle: \"default\" },\n },\n {\n name: \"Earth\",\n description: \"olive green\",\n seeds: { brand: \"#517035\", neutral: \"earth\", density: \"default\", radiusStyle: \"default\" },\n },\n {\n name: \"Fire\",\n description: \"warm orange\",\n seeds: { brand: \"#ea580c\", neutral: \"fire\", density: \"default\", radiusStyle: \"default\" },\n },\n];\n\n// ============================================\n// SCSS Generator\n// ============================================\n\n// Default seed values — matches libs/ui/src/tokens/_seeds.scss\nconst SEED_DEFAULTS: SeedConfig = {\n brand: \"#18181b\",\n neutral: \"stone\",\n density: \"default\",\n radiusStyle: \"default\",\n};\n\n/**\n * Generate SCSS that configures the Fragments UI seed system.\n *\n * Only non-default values are emitted to keep output minimal.\n * If all seeds match defaults, returns a bare `@use` with no `with()`.\n */\nexport function generateSeedScss(seeds: SeedConfig): string {\n const overrides: string[] = [];\n\n if (seeds.brand !== SEED_DEFAULTS.brand) {\n overrides.push(` $fui-brand: ${seeds.brand}`);\n }\n if (seeds.neutral !== SEED_DEFAULTS.neutral) {\n overrides.push(` $fui-neutral: \"${seeds.neutral}\"`);\n }\n if (seeds.density !== SEED_DEFAULTS.density) {\n overrides.push(` $fui-density: \"${seeds.density}\"`);\n }\n if (seeds.radiusStyle !== SEED_DEFAULTS.radiusStyle) {\n overrides.push(` $fui-radius-style: \"${seeds.radiusStyle}\"`);\n }\n\n if (overrides.length === 0) {\n return `// Fragments UI — tokens, resets, dark mode\n@use '@fragments-sdk/ui/styles';\n`;\n }\n\n return `// Fragments UI — tokens, resets, dark mode\n@use '@fragments-sdk/ui/styles' with (\n${overrides.join(\",\\n\")}\n);\n`;\n}\n"],"mappings":"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","names":["join","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragments-sdk/cli",
3
- "version": "0.15.4",
3
+ "version": "0.15.7",
4
4
  "license": "FSL-1.1-MIT",
5
5
  "description": "CLI, MCP server, and dev tools for Fragments design system",
6
6
  "author": "Conan McNicholl",
@@ -82,11 +82,11 @@
82
82
  "vite-plugin-svgr": "^4.5.0",
83
83
  "zod": "^3.24.1",
84
84
  "@fragments-sdk/compiler": "0.2.0",
85
- "@fragments-sdk/extract": "0.1.0",
86
- "@fragments-sdk/context": "0.6.0",
87
85
  "@fragments-sdk/govern": "^0.3.1",
88
86
  "@fragments-sdk/core": "2.0.0",
87
+ "@fragments-sdk/extract": "0.1.0",
89
88
  "@fragments-sdk/webmcp": "3.0.0",
89
+ "@fragments-sdk/context": "0.6.0",
90
90
  "@fragments-sdk/viewer": "0.2.8"
91
91
  },
92
92
  "devDependencies": {
package/src/bin.ts CHANGED
@@ -810,7 +810,7 @@ program
810
810
  .option('--preset <id>', 'Theme preset ID from usefragments.com/create')
811
811
  .option('--brand <color>', 'Brand color hex (e.g., #6366f1)')
812
812
  .option('--scss', 'Use SCSS output (installs sass)')
813
- .option('--mcp', 'Configure MCP server for AI tooling')
813
+ .option('--no-mcp', 'Skip MCP server configuration')
814
814
  .option('-y, --yes', 'Skip interactive prompts')
815
815
  .option('--no-git', 'Skip git initialization')
816
816
  .action(async (name, options) => {
@@ -37,6 +37,6 @@
37
37
  "source": "extracted",
38
38
  "verified": false,
39
39
  "frameworkSupport": "native",
40
- "extractedAt": "2026-03-26T14:13:18.781Z"
40
+ "extractedAt": "2026-03-26T14:38:30.456Z"
41
41
  }
42
42
  }
@@ -15,6 +15,6 @@
15
15
  "source": "extracted",
16
16
  "verified": false,
17
17
  "frameworkSupport": "native",
18
- "extractedAt": "2026-03-26T14:13:18.783Z"
18
+ "extractedAt": "2026-03-26T14:38:30.457Z"
19
19
  }
20
20
  }