@fragments-sdk/cli 0.15.3 → 0.15.5

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.
@@ -13,6 +13,8 @@ import { BRAND } from '../core/index.js';
13
13
  import { decompressTheme } from '../theme/serializer.js';
14
14
  import { generateCssTokens, generateScssTokens } from '../theme/generator.js';
15
15
  import type { ThemeConfig } from '../theme/types.js';
16
+ import { SEED_PRESETS, generateSeedScss } from '../theme/seed-presets.js';
17
+ import type { SeedConfig } from '../theme/seed-presets.js';
16
18
 
17
19
  // ============================================
18
20
  // Font Helpers
@@ -47,6 +49,7 @@ export interface CreateOptions {
47
49
  theme?: string;
48
50
  preset?: string;
49
51
  brand?: string;
52
+ seeds?: SeedConfig;
50
53
  scss?: boolean;
51
54
  mcp?: boolean;
52
55
  yes?: boolean;
@@ -150,7 +153,7 @@ async function resolveTheme(options: CreateOptions): Promise<ThemeConfig | null>
150
153
  // Templates
151
154
  // ============================================
152
155
 
153
- export function generateNextjsLayout(themePath: string, theme?: ThemeConfig): string {
156
+ export function generateNextjsLayout(themePath: string, theme?: ThemeConfig, useSeedPath = false): string {
154
157
  const fontName = theme?.typography?.fontSans ? extractFontFamily(theme.typography.fontSans) : null;
155
158
  const fontUrl = fontName ? googleFontsUrl(fontName) : null;
156
159
 
@@ -165,9 +168,14 @@ export function generateNextjsLayout(themePath: string, theme?: ThemeConfig): st
165
168
  : ` <html lang="en" suppressHydrationWarning>
166
169
  <body>`;
167
170
 
171
+ // When using seed path, theme.scss already @use's the styles entry point.
172
+ // A bare import would compile with default seeds, defeating customization.
173
+ const stylesImport = useSeedPath
174
+ ? `import '${themePath}';`
175
+ : `import '@fragments-sdk/ui/styles';\nimport '${themePath}';`;
176
+
168
177
  return `import type { Metadata } from 'next';
169
- import '@fragments-sdk/ui/styles';
170
- import '${themePath}';
178
+ ${stylesImport}
171
179
  import { Providers } from './providers';
172
180
 
173
181
  export const metadata: Metadata = {
@@ -211,38 +219,255 @@ export function Providers({ children }: { children: ReactNode }) {
211
219
  export function generateNextjsPage(): string {
212
220
  return `'use client';
213
221
 
214
- import { Button, Card, Stack, Text, Input } from '@fragments-sdk/ui';
222
+ import { useState } from 'react';
223
+ import {
224
+ Avatar,
225
+ Badge,
226
+ Button,
227
+ Card,
228
+ Checkbox,
229
+ Input,
230
+ Progress,
231
+ Separator,
232
+ Stack,
233
+ Switch,
234
+ Tabs,
235
+ Text,
236
+ Tooltip,
237
+ } from '@fragments-sdk/ui';
238
+ import styles from './page.module.css';
239
+
240
+ function StatCard({ label, value, change }: { label: string; value: string; change: string }) {
241
+ const isPositive = change.startsWith('+');
242
+ return (
243
+ <Card>
244
+ <Card.Body>
245
+ <Stack gap="xs">
246
+ <Text size="sm" color="secondary">{label}</Text>
247
+ <Text size="2xl" weight="semibold">{value}</Text>
248
+ <Badge variant={isPositive ? 'success' : 'error'} size="sm">{change}</Badge>
249
+ </Stack>
250
+ </Card.Body>
251
+ </Card>
252
+ );
253
+ }
254
+
255
+ function TaskItem({ title, done }: { title: string; done?: boolean }) {
256
+ const [checked, setChecked] = useState(done ?? false);
257
+ return (
258
+ <Stack direction="row" align="center" gap="sm">
259
+ <Checkbox checked={checked} onChange={() => setChecked(!checked)} />
260
+ <Text size="sm" className={checked ? styles.done : undefined}>{title}</Text>
261
+ </Stack>
262
+ );
263
+ }
215
264
 
216
265
  export default function Home() {
266
+ const [notifications, setNotifications] = useState(true);
267
+
217
268
  return (
218
- <main style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
219
- <Card style={{ maxWidth: 480, width: '100%' }}>
220
- <Card.Header>
221
- <Card.Title>Welcome to Fragments</Card.Title>
222
- <Card.Description>Your app is ready. Start building something great.</Card.Description>
223
- </Card.Header>
224
- <Card.Body>
225
- <Stack gap={3}>
226
- <Input placeholder="Enter something..." />
227
- <Stack direction="row" gap={2}>
228
- <Button>Get Started</Button>
229
- <Button variant="secondary">Learn More</Button>
269
+ <main className={styles.page}>
270
+ {/* Hero */}
271
+ <Stack align="center" gap="md" className={styles.hero}>
272
+ <Badge variant="info">Powered by Fragments UI</Badge>
273
+ <Text as="h1" size="2xl" weight="bold">Your app is ready</Text>
274
+ <Text size="lg" color="secondary" className={styles.heroDescription}>
275
+ This page showcases your custom theme and components.
276
+ Edit <code>src/app/page.tsx</code> to start building.
277
+ </Text>
278
+ <Stack direction="row" gap="sm">
279
+ <Button size="lg">Start building</Button>
280
+ <Button size="lg" variant="secondary">Read the docs</Button>
281
+ </Stack>
282
+ </Stack>
283
+
284
+ <Separator />
285
+
286
+ {/* Stats */}
287
+ <div className={styles.statsGrid}>
288
+ <StatCard label="Components" value="66" change="+12 this month" />
289
+ <StatCard label="Design tokens" value="80" change="+5 new" />
290
+ <StatCard label="Accessibility" value="98%" change="+2.4%" />
291
+ </div>
292
+
293
+ {/* Content */}
294
+ <div className={styles.contentGrid}>
295
+ <Card>
296
+ <Card.Header>
297
+ <Card.Title>Getting started</Card.Title>
298
+ <Card.Description>Pick up where you left off</Card.Description>
299
+ </Card.Header>
300
+ <Card.Body>
301
+ <Tabs defaultValue="tasks">
302
+ <Tabs.List>
303
+ <Tabs.Tab value="tasks">Tasks</Tabs.Tab>
304
+ <Tabs.Tab value="progress">Progress</Tabs.Tab>
305
+ </Tabs.List>
306
+ <Tabs.Panel value="tasks">
307
+ <Stack gap="sm" className={styles.tabContent}>
308
+ <TaskItem title="Install Fragments UI" done />
309
+ <TaskItem title="Configure your theme" done />
310
+ <TaskItem title="Build your first page" />
311
+ <TaskItem title="Set up MCP for AI tooling" />
312
+ <TaskItem title="Deploy to production" />
313
+ </Stack>
314
+ </Tabs.Panel>
315
+ <Tabs.Panel value="progress">
316
+ <Stack gap="md" className={styles.tabContent}>
317
+ <Stack gap="xs">
318
+ <Stack direction="row" justify="between">
319
+ <Text size="sm">Setup</Text>
320
+ <Text size="sm" color="secondary">2 of 5</Text>
321
+ </Stack>
322
+ <Progress value={40} />
323
+ </Stack>
324
+ <Text size="sm" color="secondary">
325
+ Complete the remaining tasks to finish setting up your project.
326
+ </Text>
327
+ </Stack>
328
+ </Tabs.Panel>
329
+ </Tabs>
330
+ </Card.Body>
331
+ </Card>
332
+
333
+ <Card>
334
+ <Card.Header>
335
+ <Card.Title>Settings</Card.Title>
336
+ <Card.Description>Manage your preferences</Card.Description>
337
+ </Card.Header>
338
+ <Card.Body>
339
+ <Stack gap="md">
340
+ <Input placeholder="Search settings..." />
341
+ <Stack direction="row" align="center" justify="between">
342
+ <Stack gap="xs">
343
+ <Text size="sm" weight="medium">Notifications</Text>
344
+ <Text size="xs" color="secondary">Receive alerts for updates</Text>
345
+ </Stack>
346
+ <Switch checked={notifications} onChange={() => setNotifications(!notifications)} />
347
+ </Stack>
348
+ <Separator />
349
+ <Stack direction="row" align="center" justify="between">
350
+ <Stack gap="xs">
351
+ <Text size="sm" weight="medium">Theme</Text>
352
+ <Text size="xs" color="secondary">System preference detected</Text>
353
+ </Stack>
354
+ <Badge variant="default" size="sm">Auto</Badge>
355
+ </Stack>
356
+ <Separator />
357
+ <Stack direction="row" gap="sm" align="center">
358
+ <Avatar name="You" size="sm" />
359
+ <Stack gap="xs" className={styles.flex1}>
360
+ <Text size="sm" weight="medium">Your account</Text>
361
+ <Text size="xs" color="secondary">Manage profile and billing</Text>
362
+ </Stack>
363
+ <Tooltip content="Go to account settings">
364
+ <Button variant="ghost" size="sm">Manage</Button>
365
+ </Tooltip>
366
+ </Stack>
230
367
  </Stack>
231
- </Stack>
232
- </Card.Body>
233
- </Card>
368
+ </Card.Body>
369
+ </Card>
370
+ </div>
371
+
372
+ {/* Footer */}
373
+ <Text size="sm" color="secondary" className={styles.footer}>
374
+ Built with{' '}
375
+ <a href="https://usefragments.com" className={styles.link}>Fragments UI</a>
376
+ {' '}&mdash; 66 components, 80 design tokens, accessible by default.
377
+ </Text>
234
378
  </main>
235
379
  );
236
380
  }
237
381
  `;
238
382
  }
239
383
 
240
- function generateViteMain(themePath: string): string {
384
+ export function generatePageCssModule(): string {
385
+ return `.page {
386
+ min-height: 100vh;
387
+ display: flex;
388
+ flex-direction: column;
389
+ align-items: center;
390
+ padding: var(--fui-space-6, 3rem) var(--fui-space-3, 1.5rem);
391
+ gap: var(--fui-space-4, 2rem);
392
+ max-width: 960px;
393
+ margin: 0 auto;
394
+ }
395
+
396
+ .hero {
397
+ text-align: center;
398
+ padding-top: var(--fui-space-4, 2rem);
399
+ padding-bottom: var(--fui-space-2, 1rem);
400
+ }
401
+
402
+ .heroDescription {
403
+ max-width: 480px;
404
+ }
405
+
406
+ .heroDescription code {
407
+ font-size: 0.875em;
408
+ font-family: var(--fui-font-mono, monospace);
409
+ background: var(--fui-bg-secondary);
410
+ padding: 0.125em 0.375em;
411
+ border-radius: var(--fui-radius-sm, 0.25rem);
412
+ }
413
+
414
+ .statsGrid {
415
+ display: grid;
416
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
417
+ gap: var(--fui-space-2, 1rem);
418
+ width: 100%;
419
+ }
420
+
421
+ .contentGrid {
422
+ display: grid;
423
+ grid-template-columns: 1fr 1fr;
424
+ gap: var(--fui-space-3, 1.5rem);
425
+ width: 100%;
426
+ }
427
+
428
+ @media (max-width: 768px) {
429
+ .contentGrid {
430
+ grid-template-columns: 1fr;
431
+ }
432
+ }
433
+
434
+ .tabContent {
435
+ padding-top: var(--fui-space-1, 0.75rem);
436
+ }
437
+
438
+ .done {
439
+ text-decoration: line-through;
440
+ opacity: 0.5;
441
+ }
442
+
443
+ .flex1 {
444
+ flex: 1;
445
+ }
446
+
447
+ .footer {
448
+ padding-top: var(--fui-space-2, 1rem);
449
+ }
450
+
451
+ .link {
452
+ color: inherit;
453
+ text-decoration: underline;
454
+ }
455
+
456
+ .link:hover {
457
+ color: var(--fui-color-accent);
458
+ }
459
+ `;
460
+ }
461
+
462
+ function generateViteMain(themePath: string, useSeedPath = false): string {
463
+ const stylesImport = useSeedPath
464
+ ? `import '${themePath}';`
465
+ : `import '@fragments-sdk/ui/styles';\nimport '${themePath}';`;
466
+
241
467
  return `import { StrictMode } from 'react';
242
468
  import { createRoot } from 'react-dom/client';
243
469
  import { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';
244
- import '@fragments-sdk/ui/styles';
245
- import '${themePath}';
470
+ ${stylesImport}
246
471
  import App from './App';
247
472
 
248
473
  createRoot(document.getElementById('root')!).render(
@@ -260,32 +485,13 @@ createRoot(document.getElementById('root')!).render(
260
485
  }
261
486
 
262
487
  function generateViteApp(): string {
263
- return `import { Button, Card, Stack, Text, Input } from '@fragments-sdk/ui';
264
-
265
- function App() {
266
- return (
267
- <main style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
268
- <Card style={{ maxWidth: 480, width: '100%' }}>
269
- <Card.Header>
270
- <Card.Title>Welcome to Fragments</Card.Title>
271
- <Card.Description>Your app is ready. Start building something great.</Card.Description>
272
- </Card.Header>
273
- <Card.Body>
274
- <Stack gap={3}>
275
- <Input placeholder="Enter something..." />
276
- <Stack direction="row" gap={2}>
277
- <Button>Get Started</Button>
278
- <Button variant="secondary">Learn More</Button>
279
- </Stack>
280
- </Stack>
281
- </Card.Body>
282
- </Card>
283
- </main>
284
- );
285
- }
286
-
287
- export default App;
288
- `;
488
+ // Same content as Next.js page but with App.module.css import
489
+ return generateNextjsPage()
490
+ .replace("'use client';\n\n", '')
491
+ .replace("import styles from './page.module.css';", "import styles from './App.module.css';")
492
+ .replace('src/app/page.tsx', 'src/App.tsx')
493
+ .replace('export default function Home()', 'function App()')
494
+ + '\nexport default App;\n';
289
495
  }
290
496
 
291
497
  function injectFontIntoViteHtml(projectDir: string, theme: ThemeConfig): void {
@@ -348,6 +554,59 @@ async function promptIfMissing(options: CreateOptions): Promise<CreateOptions> {
348
554
  resolved.template = resolved.template || 'nextjs';
349
555
  resolved.packageManager = resolved.packageManager || detectPackageManager();
350
556
 
557
+ // Theme selection — only when no explicit theme flags are passed
558
+ if (!resolved.preset && !resolved.theme && !resolved.brand && !resolved.yes) {
559
+ try {
560
+ const { select, input } = await import('@inquirer/prompts');
561
+
562
+ const choices = SEED_PRESETS.map((p) => ({
563
+ name: `${p.name} (${p.description})`,
564
+ value: p.name.toLowerCase(),
565
+ }));
566
+ choices.push({ name: 'Custom (pick color + palette)', value: 'custom' });
567
+
568
+ const selected = await select({
569
+ message: 'Theme:',
570
+ choices,
571
+ default: 'stone',
572
+ });
573
+
574
+ if (selected === 'custom') {
575
+ const brandColor = await input({
576
+ message: 'Brand color (hex):',
577
+ default: '#6366f1',
578
+ validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || 'Enter a valid hex color (e.g., #6366f1)',
579
+ });
580
+
581
+ const neutral = await select({
582
+ message: 'Neutral palette:',
583
+ choices: [
584
+ { name: 'Stone (warm gray)', value: 'stone' as const },
585
+ { name: 'Sand (warm beige)', value: 'sand' as const },
586
+ { name: 'Ice (cool blue)', value: 'ice' as const },
587
+ { name: 'Earth (olive green)', value: 'earth' as const },
588
+ { name: 'Fire (warm orange)', value: 'fire' as const },
589
+ ],
590
+ default: 'stone',
591
+ });
592
+
593
+ resolved.seeds = {
594
+ brand: brandColor,
595
+ neutral,
596
+ density: 'default',
597
+ radiusStyle: 'default',
598
+ };
599
+ } else {
600
+ const preset = SEED_PRESETS.find((p) => p.name.toLowerCase() === selected);
601
+ if (preset) {
602
+ resolved.seeds = { ...preset.seeds };
603
+ }
604
+ }
605
+ } catch {
606
+ // User cancelled or prompt failed — fall through to default theme
607
+ }
608
+ }
609
+
351
610
  return resolved;
352
611
  }
353
612
 
@@ -367,33 +626,48 @@ function scaffoldFramework(name: string, template: string, pm: string): void {
367
626
  }
368
627
  }
369
628
 
370
- function installDeps(projectDir: string, pm: string, scss: boolean): void {
629
+ function installDeps(projectDir: string, pm: string): void {
371
630
  console.log(pc.cyan('\nInstalling Fragments UI...\n'));
372
631
 
373
632
  const install = getInstallCommand(pm);
633
+ const devInstall = getDevInstallCommand(pm);
634
+ // sass is required — without it, @fragments-sdk/ui/styles resolves to the
635
+ // compiled CSS which is missing the :root token definitions and body resets.
636
+ // The SCSS entry (used when sass is present) includes the full token layer.
374
637
  execSync(`${install} @fragments-sdk/ui`, { cwd: projectDir, stdio: 'inherit' });
375
-
376
- if (scss) {
377
- const devInstall = getDevInstallCommand(pm);
378
- execSync(`${devInstall} sass`, { cwd: projectDir, stdio: 'inherit' });
379
- }
638
+ execSync(`${devInstall} sass`, { cwd: projectDir, stdio: 'inherit' });
380
639
  }
381
640
 
382
- function writeThemeFile(projectDir: string, theme: ThemeConfig, scss: boolean, template: string): string {
641
+ function writeThemeFile(
642
+ projectDir: string,
643
+ template: string,
644
+ source: { kind: 'seeds'; seeds: SeedConfig } | { kind: 'css'; theme: ThemeConfig },
645
+ ): string {
383
646
  const stylesDir = join(projectDir, 'src', 'styles');
384
647
  mkdirSync(stylesDir, { recursive: true });
385
648
 
386
- const ext = scss ? 'scss' : 'css';
387
- const content = scss ? generateScssTokens(theme) : generateCssTokens(theme);
388
- const filePath = join(stylesDir, `theme.${ext}`);
649
+ let content: string;
650
+
651
+ if (source.kind === 'seeds') {
652
+ // Seed path: clean @use ... with() — the SCSS derivation system does the rest
653
+ content = generateSeedScss(source.seeds);
654
+ } else {
655
+ // CSS custom property path: for themes from the website / API / --brand flag
656
+ const cssContent = generateCssTokens(source.theme);
657
+ content = `// Fragments UI — tokens, resets, dark mode
658
+ @use '@fragments-sdk/ui/styles';
659
+
660
+ // Theme overrides (generated from your preset)
661
+ ${cssContent}`;
662
+ }
663
+
664
+ const filePath = join(stylesDir, 'theme.scss');
389
665
  writeFileSync(filePath, content, 'utf-8');
390
666
 
391
- // Next.js layout is at src/app/layout.tsx → relative path is ../styles/theme.ext
392
- // Vite main is at src/main.tsx → relative path is ./styles/theme.ext
393
667
  if (template === 'nextjs') {
394
- return `../styles/theme.${ext}`;
668
+ return '../styles/theme.scss';
395
669
  }
396
- return `./styles/theme.${ext}`;
670
+ return './styles/theme.scss';
397
671
  }
398
672
 
399
673
  export function addNextTranspilePackages(projectDir: string): void {
@@ -443,24 +717,27 @@ export function addNextTranspilePackages(projectDir: string): void {
443
717
  }
444
718
  }
445
719
 
446
- function rewriteAppFiles(projectDir: string, template: string, themePath: string, theme: ThemeConfig): void {
720
+ function rewriteAppFiles(
721
+ projectDir: string,
722
+ template: string,
723
+ themePath: string,
724
+ theme: ThemeConfig | null,
725
+ useSeedPath: boolean,
726
+ ): void {
447
727
  if (template === 'nextjs') {
448
728
  // Rewrite layout.tsx
449
729
  const layoutPath = join(projectDir, 'src', 'app', 'layout.tsx');
450
- writeFileSync(layoutPath, generateNextjsLayout(themePath, theme), 'utf-8');
730
+ writeFileSync(layoutPath, generateNextjsLayout(themePath, theme ?? undefined, useSeedPath), 'utf-8');
451
731
 
452
732
  const providersPath = join(projectDir, 'src', 'app', 'providers.tsx');
453
733
  writeFileSync(providersPath, generateNextjsProviders(), 'utf-8');
454
734
 
455
- // Rewrite page.tsx
735
+ // Rewrite page.tsx + CSS module
456
736
  const pagePath = join(projectDir, 'src', 'app', 'page.tsx');
457
737
  writeFileSync(pagePath, generateNextjsPage(), 'utf-8');
458
738
 
459
- // Remove default page.module.css if it exists
460
- const moduleCssPath = join(projectDir, 'src', 'app', 'page.module.css');
461
- try {
462
- unlinkSync(moduleCssPath);
463
- } catch { /* doesn't exist, that's fine */ }
739
+ const pageCssPath = join(projectDir, 'src', 'app', 'page.module.css');
740
+ writeFileSync(pageCssPath, generatePageCssModule(), 'utf-8');
464
741
 
465
742
  // Remove globals.css if it exists (we use our own theme)
466
743
  const globalsCssPath = join(projectDir, 'src', 'app', 'globals.css');
@@ -472,11 +749,14 @@ function rewriteAppFiles(projectDir: string, template: string, themePath: string
472
749
  } else {
473
750
  // Vite: rewrite main.tsx and App.tsx
474
751
  const mainPath = join(projectDir, 'src', 'main.tsx');
475
- writeFileSync(mainPath, generateViteMain(themePath), 'utf-8');
752
+ writeFileSync(mainPath, generateViteMain(themePath, useSeedPath), 'utf-8');
476
753
 
477
754
  const appPath = join(projectDir, 'src', 'App.tsx');
478
755
  writeFileSync(appPath, generateViteApp(), 'utf-8');
479
756
 
757
+ const appCssModulePath = join(projectDir, 'src', 'App.module.css');
758
+ writeFileSync(appCssModulePath, generatePageCssModule(), 'utf-8');
759
+
480
760
  // Clean up default Vite files
481
761
  for (const file of ['src/App.css', 'src/index.css']) {
482
762
  try {
@@ -484,7 +764,10 @@ function rewriteAppFiles(projectDir: string, template: string, themePath: string
484
764
  } catch { /* doesn't exist */ }
485
765
  }
486
766
 
487
- injectFontIntoViteHtml(projectDir, theme);
767
+ // Font injection only applies to CSS path (seed path has no typography config)
768
+ if (theme) {
769
+ injectFontIntoViteHtml(projectDir, theme);
770
+ }
488
771
  }
489
772
  }
490
773
 
@@ -528,6 +811,7 @@ export async function create(options: CreateOptions): Promise<CreateResult> {
528
811
  const name = resolved.name!;
529
812
  const template = resolved.template!;
530
813
  const pm = resolved.packageManager!;
814
+ const hasSeedPath = !!resolved.seeds;
531
815
 
532
816
  // 2. Validate
533
817
  if (!isValidProjectName(name)) {
@@ -539,10 +823,13 @@ export async function create(options: CreateOptions): Promise<CreateResult> {
539
823
  return { success: false, error: `Directory "${name}" already exists.` };
540
824
  }
541
825
 
542
- // 3. Resolve theme
543
- const theme = await resolveTheme(resolved);
544
- if (!theme) {
545
- return { success: false, error: 'Invalid theme configuration.' };
826
+ // 3. Resolve theme (skip when using seed path — SCSS handles derivation)
827
+ let theme: ThemeConfig | null = null;
828
+ if (!hasSeedPath) {
829
+ theme = await resolveTheme(resolved);
830
+ if (!theme) {
831
+ return { success: false, error: 'Invalid theme configuration.' };
832
+ }
546
833
  }
547
834
 
548
835
  // 4. Scaffold framework
@@ -553,16 +840,18 @@ export async function create(options: CreateOptions): Promise<CreateResult> {
553
840
  }
554
841
 
555
842
  // 5. Install deps
556
- installDeps(projectDir, pm, !!resolved.scss);
843
+ installDeps(projectDir, pm);
557
844
 
558
845
  // 6. Write theme tokens
559
- const themePath = writeThemeFile(projectDir, theme, !!resolved.scss, template);
846
+ const themePath = hasSeedPath
847
+ ? writeThemeFile(projectDir, template, { kind: 'seeds', seeds: resolved.seeds! })
848
+ : writeThemeFile(projectDir, template, { kind: 'css', theme: theme! });
560
849
 
561
850
  // 7. Rewrite app files with providers + theme import
562
- rewriteAppFiles(projectDir, template, themePath, theme);
851
+ rewriteAppFiles(projectDir, template, themePath, theme, hasSeedPath);
563
852
 
564
- // 8. MCP config
565
- if (resolved.mcp) {
853
+ // 8. MCP config (default-on; pass --no-mcp to skip)
854
+ if (resolved.mcp !== false) {
566
855
  configureMcp(projectDir);
567
856
  console.log(pc.dim(' Configured MCP server for AI tooling'));
568
857
  }
@@ -581,9 +870,15 @@ export async function create(options: CreateOptions): Promise<CreateResult> {
581
870
  console.log(` ${pc.cyan(run)} dev`);
582
871
  console.log('');
583
872
 
584
- if (theme.name !== 'default') {
873
+ if (hasSeedPath) {
874
+ const seeds = resolved.seeds!;
875
+ const presetName = seeds.neutral.charAt(0).toUpperCase() + seeds.neutral.slice(1);
876
+ console.log(pc.dim(` Theme: ${presetName} palette with ${seeds.brand} brand`));
877
+ console.log(pc.dim(` Edit src/styles/theme.scss to customize seed values.`));
878
+ console.log(pc.dim(` Fine-tune: ${pc.cyan('fragments init --configure')}\n`));
879
+ } else if (theme && theme.name !== 'default') {
585
880
  console.log(pc.dim(` Theme "${theme.name}" applied with full token set.`));
586
- console.log(pc.dim(` Edit src/styles/theme.${resolved.scss ? 'scss' : 'css'} to customize.\n`));
881
+ console.log(pc.dim(` Edit src/styles/theme.scss to customize.\n`));
587
882
  }
588
883
 
589
884
  return { success: true, projectDir };