@fragments-sdk/cli 0.10.1 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/dist/ai-client-I6MDWNYA.js +21 -0
  2. package/dist/bin.js +292 -367
  3. package/dist/bin.js.map +1 -1
  4. package/dist/{chunk-PW7QTQA6.js → chunk-4OC7FTJB.js} +2 -2
  5. package/dist/{chunk-HRFUSSZI.js → chunk-AM4MRTMN.js} +2 -2
  6. package/dist/{chunk-5G3VZH43.js → chunk-GVDSFQ4E.js} +281 -351
  7. package/dist/chunk-GVDSFQ4E.js.map +1 -0
  8. package/dist/chunk-JJ2VRTBU.js +626 -0
  9. package/dist/chunk-JJ2VRTBU.js.map +1 -0
  10. package/dist/{chunk-D5PYOXEI.js → chunk-LVWFOLUZ.js} +148 -13
  11. package/dist/{chunk-D5PYOXEI.js.map → chunk-LVWFOLUZ.js.map} +1 -1
  12. package/dist/{chunk-WXSR2II7.js → chunk-OQKMEFOS.js} +58 -6
  13. package/dist/chunk-OQKMEFOS.js.map +1 -0
  14. package/dist/chunk-SXTKFDCR.js +104 -0
  15. package/dist/chunk-SXTKFDCR.js.map +1 -0
  16. package/dist/chunk-T5OMVL7E.js +443 -0
  17. package/dist/chunk-T5OMVL7E.js.map +1 -0
  18. package/dist/{chunk-ZM4ZQZWZ.js → chunk-TPWGL2XS.js} +39 -37
  19. package/dist/chunk-TPWGL2XS.js.map +1 -0
  20. package/dist/{chunk-OQO55NKV.js → chunk-WFS63PCW.js} +85 -11
  21. package/dist/chunk-WFS63PCW.js.map +1 -0
  22. package/dist/core/index.js +9 -1
  23. package/dist/{discovery-NEOY4MPN.js → discovery-ZJQSXF56.js} +3 -3
  24. package/dist/{generate-FBHSXR3D.js → generate-RJFS2JWA.js} +4 -4
  25. package/dist/index.js +7 -6
  26. package/dist/index.js.map +1 -1
  27. package/dist/init-ZSX3NRCZ.js +636 -0
  28. package/dist/init-ZSX3NRCZ.js.map +1 -0
  29. package/dist/mcp-bin.js +2 -2
  30. package/dist/{scan-CJF2DOQW.js → scan-3PMCJ4RB.js} +6 -6
  31. package/dist/scan-generate-SYU4PYZD.js +1115 -0
  32. package/dist/scan-generate-SYU4PYZD.js.map +1 -0
  33. package/dist/{service-TQYWY65E.js → service-VMGNJZ42.js} +3 -3
  34. package/dist/snapshot-XOISO2IS.js +139 -0
  35. package/dist/snapshot-XOISO2IS.js.map +1 -0
  36. package/dist/{static-viewer-NUBFPKWH.js → static-viewer-5GXH2MGE.js} +3 -3
  37. package/dist/static-viewer-5GXH2MGE.js.map +1 -0
  38. package/dist/{test-Z5LVO724.js → test-SI4NSHQX.js} +4 -4
  39. package/dist/{tokens-CE46OTMD.js → tokens-T6SIVUT5.js} +5 -5
  40. package/dist/{viewer-DNMNC5VS.js → viewer-7ZEAFBVN.js} +80 -58
  41. package/dist/viewer-7ZEAFBVN.js.map +1 -0
  42. package/package.json +6 -14
  43. package/src/ai-client.ts +156 -0
  44. package/src/bin.ts +74 -2
  45. package/src/build.ts +95 -33
  46. package/src/commands/__tests__/drift-sync.test.ts +252 -0
  47. package/src/commands/__tests__/scan-generate.test.ts +497 -45
  48. package/src/commands/enhance.ts +11 -35
  49. package/src/commands/init.ts +296 -193
  50. package/src/commands/scan-generate.ts +740 -139
  51. package/src/commands/scan.ts +37 -32
  52. package/src/commands/setup.ts +143 -52
  53. package/src/commands/snapshot.ts +197 -0
  54. package/src/commands/sync.ts +357 -0
  55. package/src/commands/validate.ts +43 -1
  56. package/src/core/component-extractor.test.ts +282 -0
  57. package/src/core/component-extractor.ts +1030 -0
  58. package/src/core/discovery.ts +93 -7
  59. package/src/service/enhance/props-extractor.ts +235 -13
  60. package/src/validators.ts +236 -0
  61. package/src/viewer/__tests__/viewer-integration.test.ts +85 -74
  62. package/src/viewer/server.ts +37 -22
  63. package/src/viewer/vite-plugin.ts +25 -9
  64. package/dist/chunk-5G3VZH43.js.map +0 -1
  65. package/dist/chunk-OQO55NKV.js.map +0 -1
  66. package/dist/chunk-WXSR2II7.js.map +0 -1
  67. package/dist/chunk-ZM4ZQZWZ.js.map +0 -1
  68. package/dist/init-NDQXUWDU.js +0 -796
  69. package/dist/init-NDQXUWDU.js.map +0 -1
  70. package/dist/scan-generate-SJAN5MVI.js +0 -691
  71. package/dist/scan-generate-SJAN5MVI.js.map +0 -1
  72. package/dist/viewer-DNMNC5VS.js.map +0 -1
  73. package/src/ai.ts +0 -266
  74. package/src/commands/init-framework.ts +0 -414
  75. package/src/mcp/bin.ts +0 -36
  76. package/src/migrate/bin.ts +0 -114
  77. package/src/theme/index.ts +0 -77
  78. package/src/viewer/__tests__/a11y-fixes.test.ts +0 -358
  79. package/src/viewer/__tests__/jsx-parser.test.ts +0 -502
  80. package/src/viewer/__tests__/render-utils.test.ts +0 -232
  81. package/src/viewer/__tests__/style-utils.test.ts +0 -404
  82. package/src/viewer/assets/fragments-logo.ts +0 -4
  83. package/src/viewer/assets/fragments_logo.png +0 -0
  84. package/src/viewer/bin.ts +0 -86
  85. package/src/viewer/cli/health.ts +0 -256
  86. package/src/viewer/cli/index.ts +0 -33
  87. package/src/viewer/cli/scan.ts +0 -124
  88. package/src/viewer/cli/utils.ts +0 -174
  89. package/src/viewer/components/AccessibilityPanel.tsx +0 -1457
  90. package/src/viewer/components/ActionCapture.tsx +0 -172
  91. package/src/viewer/components/ActionsPanel.tsx +0 -332
  92. package/src/viewer/components/AllVariantsPreview.tsx +0 -78
  93. package/src/viewer/components/App.tsx +0 -582
  94. package/src/viewer/components/BottomPanel.tsx +0 -288
  95. package/src/viewer/components/CodePanel.naming.test.tsx +0 -59
  96. package/src/viewer/components/CodePanel.tsx +0 -118
  97. package/src/viewer/components/CommandPalette.tsx +0 -392
  98. package/src/viewer/components/ComponentDocView.tsx +0 -164
  99. package/src/viewer/components/ComponentGraph.tsx +0 -380
  100. package/src/viewer/components/ComponentHeader.tsx +0 -88
  101. package/src/viewer/components/ContractPanel.tsx +0 -241
  102. package/src/viewer/components/EmptyVariantMessage.tsx +0 -54
  103. package/src/viewer/components/ErrorBoundary.tsx +0 -97
  104. package/src/viewer/components/FigmaEmbed.tsx +0 -238
  105. package/src/viewer/components/FragmentEditor.tsx +0 -525
  106. package/src/viewer/components/FragmentRenderer.tsx +0 -61
  107. package/src/viewer/components/HeaderSearch.tsx +0 -24
  108. package/src/viewer/components/HealthDashboard.tsx +0 -441
  109. package/src/viewer/components/HmrStatusIndicator.tsx +0 -61
  110. package/src/viewer/components/Icons.tsx +0 -479
  111. package/src/viewer/components/InteractionsPanel.tsx +0 -757
  112. package/src/viewer/components/IsolatedPreviewFrame.tsx +0 -346
  113. package/src/viewer/components/IsolatedRender.tsx +0 -113
  114. package/src/viewer/components/KeyboardShortcutsHelp.tsx +0 -53
  115. package/src/viewer/components/LandingPage.tsx +0 -421
  116. package/src/viewer/components/Layout.tsx +0 -27
  117. package/src/viewer/components/LeftSidebar.tsx +0 -472
  118. package/src/viewer/components/LoadErrorMessage.tsx +0 -102
  119. package/src/viewer/components/MultiViewportPreview.tsx +0 -522
  120. package/src/viewer/components/NoVariantsMessage.tsx +0 -59
  121. package/src/viewer/components/PanelShell.tsx +0 -161
  122. package/src/viewer/components/PerformancePanel.tsx +0 -304
  123. package/src/viewer/components/PreviewArea.tsx +0 -472
  124. package/src/viewer/components/PreviewAside.tsx +0 -168
  125. package/src/viewer/components/PreviewFrameHost.tsx +0 -303
  126. package/src/viewer/components/PreviewPane.tsx +0 -149
  127. package/src/viewer/components/PreviewToolbar.tsx +0 -80
  128. package/src/viewer/components/PropsEditor.tsx +0 -506
  129. package/src/viewer/components/PropsTable.tsx +0 -111
  130. package/src/viewer/components/RelationsSection.tsx +0 -88
  131. package/src/viewer/components/ResizablePanel.tsx +0 -271
  132. package/src/viewer/components/RightSidebar.tsx +0 -102
  133. package/src/viewer/components/RuntimeToolsRegistrar.tsx +0 -17
  134. package/src/viewer/components/ScreenshotButton.tsx +0 -90
  135. package/src/viewer/components/Sidebar.tsx +0 -169
  136. package/src/viewer/components/SkeletonLoader.tsx +0 -161
  137. package/src/viewer/components/ThemeProvider.tsx +0 -42
  138. package/src/viewer/components/Toast.tsx +0 -3
  139. package/src/viewer/components/TokenStylePanel.tsx +0 -699
  140. package/src/viewer/components/TopToolbar.tsx +0 -159
  141. package/src/viewer/components/UsageSection.tsx +0 -95
  142. package/src/viewer/components/VariantMatrix.tsx +0 -388
  143. package/src/viewer/components/VariantRenderer.tsx +0 -131
  144. package/src/viewer/components/VariantTabs.tsx +0 -40
  145. package/src/viewer/components/ViewerHeader.tsx +0 -69
  146. package/src/viewer/components/ViewerStateSync.tsx +0 -52
  147. package/src/viewer/components/ViewportSelector.tsx +0 -172
  148. package/src/viewer/components/WebMCPDevTools.tsx +0 -503
  149. package/src/viewer/components/WebMCPIntegration.tsx +0 -47
  150. package/src/viewer/components/WebMCPStatusIndicator.tsx +0 -60
  151. package/src/viewer/components/_future/CreatePage.tsx +0 -836
  152. package/src/viewer/components/viewer-utils.ts +0 -16
  153. package/src/viewer/composition-renderer.ts +0 -381
  154. package/src/viewer/constants/index.ts +0 -1
  155. package/src/viewer/constants/ui.ts +0 -166
  156. package/src/viewer/entry.tsx +0 -335
  157. package/src/viewer/hooks/index.ts +0 -2
  158. package/src/viewer/hooks/useA11yCache.ts +0 -383
  159. package/src/viewer/hooks/useA11yService.ts +0 -364
  160. package/src/viewer/hooks/useActions.ts +0 -138
  161. package/src/viewer/hooks/useAppState.ts +0 -147
  162. package/src/viewer/hooks/useCompiledFragments.ts +0 -42
  163. package/src/viewer/hooks/useFigmaIntegration.ts +0 -132
  164. package/src/viewer/hooks/useHmrStatus.ts +0 -109
  165. package/src/viewer/hooks/useKeyboardShortcuts.ts +0 -270
  166. package/src/viewer/hooks/usePreviewBridge.ts +0 -347
  167. package/src/viewer/hooks/useScrollSpy.ts +0 -78
  168. package/src/viewer/hooks/useUrlState.ts +0 -318
  169. package/src/viewer/hooks/useViewSettings.ts +0 -111
  170. package/src/viewer/index.html +0 -28
  171. package/src/viewer/intelligence/healthReport.ts +0 -505
  172. package/src/viewer/intelligence/styleDrift.ts +0 -340
  173. package/src/viewer/intelligence/usageScanner.ts +0 -309
  174. package/src/viewer/jsx-parser.ts +0 -486
  175. package/src/viewer/preview-frame-entry.tsx +0 -25
  176. package/src/viewer/preview-frame.html +0 -125
  177. package/src/viewer/public/favicon.ico +0 -0
  178. package/src/viewer/render-template.html +0 -68
  179. package/src/viewer/styles/globals.css +0 -278
  180. package/src/viewer/types/a11y.ts +0 -197
  181. package/src/viewer/utils/a11y-fixes.ts +0 -509
  182. package/src/viewer/utils/actionExport.ts +0 -372
  183. package/src/viewer/utils/colorSchemes.ts +0 -201
  184. package/src/viewer/utils/detectRelationships.ts +0 -256
  185. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss +0 -10
  186. package/src/viewer/vendor/shared/src/ComponentDocContent.module.scss.d.ts +0 -2
  187. package/src/viewer/vendor/shared/src/ComponentDocContent.tsx +0 -274
  188. package/src/viewer/vendor/shared/src/DocsHeaderBar.tsx +0 -129
  189. package/src/viewer/vendor/shared/src/DocsPageAsideHost.tsx +0 -89
  190. package/src/viewer/vendor/shared/src/DocsPageShell.tsx +0 -124
  191. package/src/viewer/vendor/shared/src/DocsSearchCommand.tsx +0 -99
  192. package/src/viewer/vendor/shared/src/DocsSidebarNav.tsx +0 -66
  193. package/src/viewer/vendor/shared/src/PropsTable.module.scss +0 -68
  194. package/src/viewer/vendor/shared/src/PropsTable.module.scss.d.ts +0 -2
  195. package/src/viewer/vendor/shared/src/PropsTable.tsx +0 -76
  196. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss +0 -114
  197. package/src/viewer/vendor/shared/src/VariantPreviewCard.module.scss.d.ts +0 -2
  198. package/src/viewer/vendor/shared/src/VariantPreviewCard.tsx +0 -137
  199. package/src/viewer/vendor/shared/src/docs-data/index.ts +0 -32
  200. package/src/viewer/vendor/shared/src/docs-data/mcp-configs.ts +0 -72
  201. package/src/viewer/vendor/shared/src/docs-data/palettes.ts +0 -75
  202. package/src/viewer/vendor/shared/src/docs-data/setup-examples.ts +0 -55
  203. package/src/viewer/vendor/shared/src/docs-layout.scss +0 -28
  204. package/src/viewer/vendor/shared/src/docs-layout.scss.d.ts +0 -2
  205. package/src/viewer/vendor/shared/src/index.ts +0 -34
  206. package/src/viewer/vendor/shared/src/types.ts +0 -53
  207. package/src/viewer/webmcp/__tests__/analytics.test.ts +0 -108
  208. package/src/viewer/webmcp/analytics.ts +0 -165
  209. package/src/viewer/webmcp/index.ts +0 -3
  210. package/src/viewer/webmcp/posthog-bridge.ts +0 -39
  211. package/src/viewer/webmcp/runtime-tools.ts +0 -152
  212. package/src/viewer/webmcp/scan-utils.ts +0 -135
  213. package/src/viewer/webmcp/use-tool-analytics.ts +0 -69
  214. package/src/viewer/webmcp/viewer-state.ts +0 -45
  215. /package/dist/{discovery-NEOY4MPN.js.map → ai-client-I6MDWNYA.js.map} +0 -0
  216. /package/dist/{chunk-PW7QTQA6.js.map → chunk-4OC7FTJB.js.map} +0 -0
  217. /package/dist/{chunk-HRFUSSZI.js.map → chunk-AM4MRTMN.js.map} +0 -0
  218. /package/dist/{scan-CJF2DOQW.js.map → discovery-ZJQSXF56.js.map} +0 -0
  219. /package/dist/{generate-FBHSXR3D.js.map → generate-RJFS2JWA.js.map} +0 -0
  220. /package/dist/{service-TQYWY65E.js.map → scan-3PMCJ4RB.js.map} +0 -0
  221. /package/dist/{static-viewer-NUBFPKWH.js.map → service-VMGNJZ42.js.map} +0 -0
  222. /package/dist/{test-Z5LVO724.js.map → test-SI4NSHQX.js.map} +0 -0
  223. /package/dist/{tokens-CE46OTMD.js.map → tokens-T6SIVUT5.js.map} +0 -0
@@ -1,37 +1,48 @@
1
1
  /**
2
- * fragments init - Smart interactive initialization
2
+ * fragments init - Zero-config project initialization
3
3
  *
4
- * Handles four scenarios:
5
- * 1. --scan <path> Scan external component library, generate fragment files
6
- * 2. Stories found Configure and load existing stories
7
- * 3. Components found (no stories) → Auto-generate documentation
8
- * 4. Fresh project → Guided setup with example component
4
+ * Default: auto-detect everything, zero prompts, instant setup.
5
+ * --configure: interactive mode for theme seeds, snapshots, etc.
6
+ * --scan <path>: scan external component library, generate fragment files.
9
7
  */
10
8
 
11
9
  import { readFile, writeFile, mkdir, access } from "node:fs/promises";
12
- import { resolve, join, relative, dirname, basename } from "node:path";
10
+ import { resolve, join, relative } from "node:path";
13
11
  import { spawn } from "node:child_process";
14
12
  import pc from "picocolors";
15
13
  import { BRAND } from "../core/index.js";
16
14
  import fg from "fast-glob";
17
- import { input, confirm, select } from "@inquirer/prompts";
18
15
  import {
19
- setupFramework,
20
- detectFramework,
21
- type Framework,
22
- } from "./init-framework.js";
16
+ detectSetupFramework,
17
+ findEntryFile,
18
+ addStylesImport,
19
+ addThemeProvider,
20
+ addTranspilePackages,
21
+ } from "./setup.js";
23
22
 
24
23
  export interface InitOptions {
25
24
  /** Project root directory */
26
25
  projectRoot?: string;
27
26
  /** Force overwrite existing config */
28
27
  force?: boolean;
29
- /** Non-interactive mode - auto-detect and use defaults */
28
+ /** Non-interactive mode - auto-detect and use defaults (legacy, now the default behavior) */
30
29
  yes?: boolean;
31
30
  /** Explicit framework override */
32
31
  framework?: string;
33
32
  /** Path to scan for components (enables scan mode) */
34
33
  scan?: string;
34
+ /** Enable interactive configuration (theme seeds, snapshots, etc.) */
35
+ configure?: boolean;
36
+ /** Use AI to fill knowledge fields during --scan */
37
+ enrich?: boolean;
38
+ /** Show what --enrich would generate without calling API */
39
+ dryRun?: boolean;
40
+ /** AI provider for enrichment: anthropic or openai */
41
+ provider?: 'anthropic' | 'openai';
42
+ /** API key for AI enrichment */
43
+ apiKey?: string;
44
+ /** Override AI model for enrichment */
45
+ model?: string;
35
46
  }
36
47
 
37
48
  export interface InitResult {
@@ -49,14 +60,13 @@ interface DetectionResult {
49
60
  hasConfig: boolean;
50
61
  configPath: string | null;
51
62
  suggestedComponentPath: string;
63
+ scenario: "stories" | "components" | "fresh";
52
64
  }
53
65
 
54
66
  /**
55
67
  * Detect what exists in the project
56
68
  */
57
69
  async function detectProject(projectRoot: string): Promise<DetectionResult> {
58
- console.log(pc.dim("\nScanning project...\n"));
59
-
60
70
  // Check for existing config
61
71
  const configPath = join(projectRoot, BRAND.configFile);
62
72
  const legacyConfigPath = join(projectRoot, BRAND.legacyConfigFile);
@@ -127,12 +137,16 @@ async function detectProject(projectRoot: string): Promise<DetectionResult> {
127
137
  }
128
138
  }
129
139
 
140
+ const scenario: DetectionResult["scenario"] =
141
+ storyFiles.length > 0 ? "stories" : likelyComponents.length > 0 ? "components" : "fresh";
142
+
130
143
  return {
131
144
  storyFiles,
132
145
  componentFiles: likelyComponents,
133
146
  hasConfig,
134
147
  configPath: foundConfigPath,
135
148
  suggestedComponentPath,
149
+ scenario,
136
150
  };
137
151
  }
138
152
 
@@ -143,6 +157,8 @@ function generateConfig(options: {
143
157
  includePaths: string[];
144
158
  componentPaths: string[];
145
159
  framework: string;
160
+ themeBlock?: string;
161
+ snapshotsBlock?: string;
146
162
  }): string {
147
163
  const includeStr = options.includePaths.map((p) => ` '${p}'`).join(",\n");
148
164
  const componentStr = options.componentPaths.map((p) => ` '${p}'`).join(",\n");
@@ -165,7 +181,7 @@ ${componentStr}
165
181
 
166
182
  // Framework (react, vue, svelte)
167
183
  framework: '${options.framework}',
168
- };
184
+ ${options.themeBlock || ""}${options.snapshotsBlock || ""}};
169
185
 
170
186
  export default config;
171
187
  `;
@@ -326,52 +342,6 @@ export default defineFragment({
326
342
  `;
327
343
  }
328
344
 
329
- /**
330
- * Convert a filename to PascalCase component name
331
- */
332
- function toPascalCase(str: string): string {
333
- return str
334
- .replace(/[-_.](\w)/g, (_, c) => c.toUpperCase())
335
- .replace(/^\w/, (c) => c.toUpperCase());
336
- }
337
-
338
- /**
339
- * Generate a minimal fragment stub for a discovered component
340
- */
341
- function generateFragmentStub(componentName: string, importPath: string): string {
342
- return `import React from 'react';
343
- import { defineFragment } from '@fragments-sdk/cli/core';
344
- import { ${componentName} } from '${importPath}';
345
-
346
- export default defineFragment({
347
- component: ${componentName},
348
-
349
- meta: {
350
- name: '${componentName}',
351
- description: '${componentName} component',
352
- category: 'general',
353
- status: 'beta',
354
- },
355
-
356
- usage: {
357
- when: ['TODO: describe when to use ${componentName}'],
358
- whenNot: ['TODO: describe when not to use ${componentName}'],
359
- },
360
-
361
- props: {},
362
-
363
- variants: [
364
- {
365
- name: 'Default',
366
- description: 'Default ${componentName}',
367
- code: \`<${componentName} />\`,
368
- render: () => <${componentName} />,
369
- },
370
- ],
371
- });
372
- `;
373
- }
374
-
375
345
  /**
376
346
  * Start the dev server
377
347
  */
@@ -393,7 +363,142 @@ function startDevServer(projectRoot: string): void {
393
363
  }
394
364
 
395
365
  /**
396
- * Main init function - smart and interactive by default
366
+ * Map init-framework's Framework type to setup's Framework type
367
+ */
368
+ function mapFrameworkLabel(framework: string): string {
369
+ const labels: Record<string, string> = {
370
+ "nextjs-app": "Next.js (App Router)",
371
+ "nextjs-pages": "Next.js (Pages Router)",
372
+ "vite": "Vite",
373
+ "remix": "Remix",
374
+ "astro": "Astro",
375
+ };
376
+ return labels[framework] || "Unknown";
377
+ }
378
+
379
+ // ============================================
380
+ // Interactive configuration (--configure)
381
+ // ============================================
382
+
383
+ interface ConfigureResult {
384
+ componentPath: string;
385
+ createExample: boolean;
386
+ startServer: boolean;
387
+ themeBlock: string;
388
+ snapshotsBlock: string;
389
+ }
390
+
391
+ async function runInteractiveConfigure(
392
+ detection: DetectionResult,
393
+ scenario: "stories" | "components" | "fresh"
394
+ ): Promise<ConfigureResult> {
395
+ const { input, confirm, select } = await import("@inquirer/prompts");
396
+
397
+ const componentPath = await input({
398
+ message: "Where are your components located?",
399
+ default: detection.suggestedComponentPath,
400
+ });
401
+
402
+ let createExample = scenario === "fresh";
403
+ if (scenario === "fresh") {
404
+ createExample = await confirm({
405
+ message: "Create an example Button component to get started?",
406
+ default: true,
407
+ });
408
+ }
409
+
410
+ // Theme seed configuration
411
+ let themeBlock = "";
412
+ const configureTheme = await confirm({
413
+ message: "Configure theme seeds? (brand color, density, radius)",
414
+ default: false,
415
+ });
416
+
417
+ if (configureTheme) {
418
+ const brand = await input({
419
+ message: "Brand color (hex)",
420
+ default: "#18181b",
421
+ validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) || "Enter a valid hex color (e.g., #6366f1)",
422
+ });
423
+
424
+ const neutral = await select({
425
+ message: "Neutral palette",
426
+ choices: [
427
+ { value: "stone", name: "Stone (warm gray)" },
428
+ { value: "ice", name: "Ice (cool blue-gray)" },
429
+ { value: "earth", name: "Earth (olive/khaki)" },
430
+ { value: "sand", name: "Sand (warm beige)" },
431
+ { value: "fire", name: "Fire (warm red-gray)" },
432
+ ],
433
+ default: "stone",
434
+ });
435
+
436
+ const density = await select({
437
+ message: "Spacing density",
438
+ choices: [
439
+ { value: "compact", name: "Compact (tighter spacing)" },
440
+ { value: "default", name: "Default" },
441
+ { value: "relaxed", name: "Relaxed (more breathing room)" },
442
+ ],
443
+ default: "default",
444
+ });
445
+
446
+ const radiusStyle = await select({
447
+ message: "Border radius style",
448
+ choices: [
449
+ { value: "sharp", name: "Sharp (0px)" },
450
+ { value: "subtle", name: "Subtle (2px)" },
451
+ { value: "default", name: "Default (6px)" },
452
+ { value: "rounded", name: "Rounded (10px)" },
453
+ { value: "pill", name: "Pill (999px)" },
454
+ ],
455
+ default: "default",
456
+ });
457
+
458
+ // Build theme config block — only include non-default values
459
+ const themeEntries: string[] = [];
460
+ if (brand !== "#18181b") themeEntries.push(` brand: '${brand}'`);
461
+ if (neutral !== "stone") themeEntries.push(` neutral: '${neutral}'`);
462
+ if (density !== "default") themeEntries.push(` density: '${density}'`);
463
+ if (radiusStyle !== "default") themeEntries.push(` radiusStyle: '${radiusStyle}'`);
464
+
465
+ if (themeEntries.length > 0) {
466
+ themeBlock = `\n // Theme seed values (derives 120+ CSS custom properties)\n theme: {\n${themeEntries.join(",\n")},\n },\n`;
467
+ }
468
+ }
469
+
470
+ // Snapshot toggle
471
+ let snapshotsBlock = "";
472
+ const enableSnapshots = await confirm({
473
+ message: "Enable visual snapshot tests per component variant?",
474
+ default: false,
475
+ });
476
+
477
+ if (enableSnapshots) {
478
+ snapshotsBlock = `\n // Visual snapshot testing\n snapshots: {\n enabled: true,\n },\n`;
479
+ }
480
+
481
+ // Ask about starting the server
482
+ const startServer = await confirm({
483
+ message: "Start the viewer now?",
484
+ default: true,
485
+ });
486
+
487
+ return {
488
+ componentPath,
489
+ createExample,
490
+ startServer,
491
+ themeBlock,
492
+ snapshotsBlock,
493
+ };
494
+ }
495
+
496
+ // ============================================
497
+ // Main init function
498
+ // ============================================
499
+
500
+ /**
501
+ * Main init function - zero-config by default, interactive with --configure
397
502
  */
398
503
  export async function init(options: InitOptions = {}): Promise<InitResult> {
399
504
  const projectRoot = resolve(options.projectRoot || process.cwd());
@@ -423,6 +528,11 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
423
528
  scanPath,
424
529
  force: options.force,
425
530
  verbose: true,
531
+ enrich: options.enrich,
532
+ dryRun: options.dryRun,
533
+ provider: options.provider,
534
+ apiKey: options.apiKey,
535
+ model: options.model,
426
536
  });
427
537
 
428
538
  // Create config pointing at the scanned path
@@ -444,9 +554,16 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
444
554
  // Next steps
445
555
  if (scanResult.success) {
446
556
  console.log(pc.cyan("Next steps:"));
447
- console.log(` 1. Search generated files for ${pc.bold("TODO:")} markers and fill in human knowledge`);
448
- console.log(` 2. Run ${pc.bold(`${BRAND.cliCommand} dev`)} to preview your components`);
449
- console.log(` 3. Run ${pc.bold(`${BRAND.cliCommand} build`)} to compile fragments.json`);
557
+ if (options.enrich && !options.dryRun) {
558
+ console.log(` 1. Review AI-enriched fields (usage.when, a11yRules, scenarioTags) in generated files`);
559
+ if (scanResult.generated.some(g => g.todoCount > 0)) {
560
+ console.log(` 2. Search remaining ${pc.bold("TODO:")} markers and fill in human knowledge`);
561
+ }
562
+ } else {
563
+ console.log(` 1. Search generated files for ${pc.bold("TODO:")} markers and fill in human knowledge`);
564
+ }
565
+ console.log(` ${options.enrich ? '3' : '2'}. Run ${pc.bold(`${BRAND.cliCommand} dev`)} to preview your components`);
566
+ console.log(` ${options.enrich ? '4' : '3'}. Run ${pc.bold(`${BRAND.cliCommand} build`)} to compile fragments.json`);
450
567
  console.log();
451
568
  }
452
569
 
@@ -463,146 +580,152 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
463
580
  };
464
581
  }
465
582
 
466
- console.log(pc.cyan(`\n✨ Welcome to ${BRAND.name}!\n`));
583
+ console.log(pc.cyan(`\n${BRAND.name} init\n`));
467
584
 
468
585
  // Step 1: Detect what exists
469
586
  const detection = await detectProject(projectRoot);
470
587
 
471
588
  // Check for existing config
472
589
  if (detection.hasConfig && !options.force) {
473
- console.log(pc.yellow(`⚠ Config already exists: ${BRAND.configFile}`));
474
-
475
- if (!options.yes) {
476
- const overwrite = await confirm({
477
- message: "Do you want to reinitialize? (This will overwrite your config)",
478
- default: false,
479
- });
480
-
481
- if (!overwrite) {
482
- console.log(pc.dim(`\nKeeping existing configuration. Run \`${BRAND.cliCommand} dev\` to start.\n`));
483
- return {
484
- success: true,
485
- scenario: "stories",
486
- storiesFound: detection.storyFiles.length,
487
- componentsFound: detection.componentFiles.length,
488
- errors: [],
489
- };
490
- }
590
+ if (options.configure) {
591
+ // In configure mode, warn but continue
592
+ console.log(pc.yellow(` ! Config exists: ${BRAND.configFile} (will overwrite)`));
593
+ } else {
594
+ console.log(pc.green(` ✓ Already initialized`) + pc.dim(` (${BRAND.configFile} exists)`));
595
+ console.log();
596
+ console.log(pc.dim(` Run ${pc.bold(`${BRAND.cliCommand} init --force`)} to reinitialize`));
597
+ console.log(pc.dim(` Run ${pc.bold(`${BRAND.cliCommand} init --configure`)} to customize theme, snapshots, etc.`));
598
+ console.log();
599
+ return {
600
+ success: true,
601
+ scenario: detection.scenario,
602
+ storiesFound: detection.storyFiles.length,
603
+ componentsFound: detection.componentFiles.length,
604
+ errors: [],
605
+ };
491
606
  }
492
607
  }
493
608
 
494
- // Step 2: Determine scenario and show what we found
609
+ // Step 2: Determine scenario
495
610
  let scenario: "stories" | "components" | "fresh";
496
611
 
497
612
  if (detection.storyFiles.length > 0) {
498
613
  scenario = "stories";
499
- console.log(pc.green(`✓ Found ${detection.storyFiles.length} Storybook story file(s)`));
500
- console.log(pc.dim(` ${detection.storyFiles.slice(0, 3).join("\n ")}`));
501
- if (detection.storyFiles.length > 3) {
502
- console.log(pc.dim(` ... and ${detection.storyFiles.length - 3} more`));
503
- }
504
- console.log();
505
- console.log(
506
- pc.cyan("Great news! ") +
507
- "Fragments can load your existing stories automatically."
508
- );
614
+ console.log(pc.green(` ✓ Found ${detection.storyFiles.length} Storybook stories`));
509
615
  } else if (detection.componentFiles.length > 0) {
510
616
  scenario = "components";
511
- console.log(pc.green(`✓ Found ${detection.componentFiles.length} component file(s)`));
512
- console.log(pc.dim(` ${detection.componentFiles.slice(0, 3).join("\n ")}`));
513
- if (detection.componentFiles.length > 3) {
514
- console.log(pc.dim(` ... and ${detection.componentFiles.length - 3} more`));
515
- }
516
- console.log();
517
- console.log(
518
- pc.cyan("No stories found, but that's fine! ") +
519
- "Fragments can auto-generate documentation from your TypeScript."
520
- );
617
+ console.log(pc.green(` ✓ Found ${detection.componentFiles.length} components`) + pc.dim(` in ${detection.suggestedComponentPath}`));
521
618
  } else {
522
619
  scenario = "fresh";
523
- console.log(pc.yellow("No components or stories found."));
524
- console.log();
525
- console.log(pc.cyan("Let's create your first fragment!"));
620
+ console.log(pc.dim(` · No existing components found`));
526
621
  }
527
622
 
528
- console.log();
623
+ // Step 3: Detect framework
624
+ const framework = await detectSetupFramework(projectRoot);
625
+ console.log(pc.green(` ✓ Detected ${mapFrameworkLabel(framework)}`));
529
626
 
530
- // Step 3: Gather configuration (interactive unless --yes)
627
+ // Step 4: Branch interactive configure or fast path
531
628
  let componentPath = detection.suggestedComponentPath;
532
- let runScan = scenario === "components" || scenario === "stories";
533
629
  let createExample = scenario === "fresh";
534
630
  let startServer = false;
535
-
536
- if (!options.yes) {
537
- // Ask about component location
538
- componentPath = await input({
539
- message: "Where are your components located?",
540
- default: detection.suggestedComponentPath,
541
- });
542
-
543
- if (scenario === "fresh") {
544
- // Fresh project - ask about example
545
- createExample = await confirm({
546
- message: "Create an example Button component to get started?",
547
- default: true,
548
- });
549
- }
550
-
551
- // Ask about starting the server
552
- startServer = await confirm({
553
- message: "Start the viewer now?",
554
- default: true,
555
- });
631
+ let themeBlock = "";
632
+ let snapshotsBlock = "";
633
+
634
+ if (options.configure) {
635
+ const config = await runInteractiveConfigure(detection, scenario);
636
+ componentPath = config.componentPath;
637
+ createExample = config.createExample;
638
+ startServer = config.startServer;
639
+ themeBlock = config.themeBlock;
640
+ snapshotsBlock = config.snapshotsBlock;
556
641
  }
557
642
 
558
- // Step 4: Create configuration
559
- console.log(pc.dim("\nCreating configuration...\n"));
560
-
561
- // Build include patterns
643
+ // Step 5: Create configuration file
562
644
  const includePaths: string[] = [
563
645
  `${componentPath}/**/*.fragment.tsx`,
564
646
  ];
565
647
 
566
- // If Storybook stories detected, also include them for direct rendering
567
648
  if (scenario === 'stories') {
568
649
  includePaths.push(`${componentPath}/**/*.stories.tsx`);
569
650
  includePaths.push(`${componentPath}/**/*.stories.ts`);
570
651
  }
571
652
 
572
- // Create config file
573
653
  const configPath = join(projectRoot, BRAND.configFile);
574
654
  const configContent = generateConfig({
575
655
  includePaths,
576
656
  componentPaths: [`${componentPath}/**/*.tsx`],
577
657
  framework: "react",
658
+ themeBlock,
659
+ snapshotsBlock,
578
660
  });
579
661
 
580
662
  try {
581
663
  await writeFile(configPath, configContent, "utf-8");
582
- console.log(pc.green(`✓ Created ${BRAND.configFile}`));
664
+ console.log(pc.green(` ✓ Created ${BRAND.configFile}`));
583
665
  } catch (e) {
584
666
  errors.push(`Failed to create config: ${e}`);
585
667
  }
586
668
 
587
- // Step 5: Handle scenario-specific setup
669
+ // Step 6: Auto-inject styles + framework config
670
+ const entryFile = await findEntryFile(projectRoot, framework);
671
+
672
+ if (entryFile) {
673
+ try {
674
+ const stylesResult = await addStylesImport(projectRoot, entryFile);
675
+ if (stylesResult.modified) {
676
+ console.log(pc.green(` ✓ Added styles import to ${entryFile}`));
677
+ } else {
678
+ console.log(pc.dim(` · ${stylesResult.message}`));
679
+ }
680
+ } catch (e) {
681
+ errors.push(`Failed to add styles import: ${e instanceof Error ? e.message : e}`);
682
+ }
683
+
684
+ try {
685
+ const providerResult = await addThemeProvider(projectRoot, entryFile, framework);
686
+ if (providerResult.modified) {
687
+ console.log(pc.green(` ✓ Added ThemeProvider to ${entryFile}`));
688
+ } else {
689
+ console.log(pc.dim(` · ${providerResult.message}`));
690
+ }
691
+ } catch (e) {
692
+ errors.push(`Failed to add ThemeProvider: ${e instanceof Error ? e.message : e}`);
693
+ }
694
+ } else {
695
+ console.log(pc.yellow(` ! Could not detect entry file — add styles import manually`));
696
+ console.log(pc.dim(` import '@fragments-sdk/ui/styles'`));
697
+ }
698
+
699
+ // Next.js: add transpilePackages
700
+ if (framework === 'nextjs-app' || framework === 'nextjs-pages') {
701
+ try {
702
+ const transpileResult = await addTranspilePackages(projectRoot);
703
+ if (transpileResult.modified) {
704
+ console.log(pc.green(` ✓ ${transpileResult.message}`));
705
+ } else {
706
+ console.log(pc.dim(` · ${transpileResult.message}`));
707
+ }
708
+ } catch (e) {
709
+ errors.push(`Failed to update next.config: ${e instanceof Error ? e.message : e}`);
710
+ }
711
+ }
712
+
713
+ // Step 7: Handle scenario-specific setup
588
714
  if (scenario === "fresh" && createExample) {
589
- // Create example component
590
715
  const exampleDir = join(projectRoot, componentPath, "Button");
591
716
 
592
717
  try {
593
718
  await mkdir(exampleDir, { recursive: true });
594
719
 
595
- // Write Button.tsx
596
720
  await writeFile(
597
721
  join(exampleDir, "Button.tsx"),
598
722
  generateExampleComponent(),
599
723
  "utf-8"
600
724
  );
601
725
  console.log(
602
- pc.green(`✓ Created ${relative(projectRoot, join(exampleDir, "Button.tsx"))}`)
726
+ pc.green(` ✓ Created ${relative(projectRoot, join(exampleDir, "Button.tsx"))}`)
603
727
  );
604
728
 
605
- // Write Button.fragment.tsx
606
729
  await writeFile(
607
730
  join(exampleDir, "Button.fragment.tsx"),
608
731
  generateExampleFragment(),
@@ -610,7 +733,7 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
610
733
  );
611
734
  console.log(
612
735
  pc.green(
613
- `✓ Created ${relative(projectRoot, join(exampleDir, "Button.fragment.tsx"))}`
736
+ ` ✓ Created ${relative(projectRoot, join(exampleDir, "Button.fragment.tsx"))}`
614
737
  )
615
738
  );
616
739
  } catch (e) {
@@ -618,70 +741,50 @@ export async function init(options: InitOptions = {}): Promise<InitResult> {
618
741
  }
619
742
  }
620
743
 
621
- if (runScan) {
622
- // Run scan to generate fragments.json from source code
623
- console.log(pc.dim("\nScanning source code for documentation...\n"));
744
+ if (scenario === "components" || scenario === "stories") {
624
745
  try {
625
746
  const { scan } = await import("./scan.js");
626
- await scan({
747
+ const scanResult = await scan({
627
748
  config: configPath,
628
749
  verbose: false,
750
+ quiet: true,
629
751
  });
630
- } catch (e) {
752
+ if (scanResult.success) {
753
+ console.log(pc.green(` ✓ Generated fragments.json`) + pc.dim(` (${scanResult.componentCount} components)`));
754
+ } else {
755
+ console.log(pc.dim(` · Auto-documentation will run when you start the dev server`));
756
+ }
757
+ } catch {
631
758
  console.log(
632
- pc.yellow(`Note: Auto-documentation will run when you start the dev server.`)
759
+ pc.dim(` · Auto-documentation will run when you start the dev server`)
633
760
  );
634
761
  }
635
762
  }
636
763
 
637
- // Step 6: Framework-specific configuration
638
- console.log(pc.dim("\nConfiguring framework integration...\n"));
639
-
640
- const frameworkOverride = options.framework as Framework | undefined;
641
- const frameworkResult = await setupFramework({
642
- projectRoot,
643
- framework: frameworkOverride,
644
- });
645
-
646
- if (frameworkResult.filesCreated.length > 0) {
647
- for (const file of frameworkResult.filesCreated) {
648
- console.log(pc.green(`✓ Created ${file}`));
649
- }
650
- }
764
+ // Step 8: Summary
765
+ console.log();
651
766
 
652
- if (frameworkResult.configModified.length > 0) {
653
- for (const file of frameworkResult.configModified) {
654
- console.log(pc.green(`✓ Updated ${file}`));
767
+ if (errors.length > 0) {
768
+ console.log(pc.red(` ${errors.length} error(s) occurred:`));
769
+ for (const error of errors) {
770
+ console.log(pc.red(` - ${error}`));
655
771
  }
656
- }
657
-
658
- if (frameworkResult.packagesToInstall.length > 0) {
659
- const pkgs = frameworkResult.packagesToInstall.join(" ");
660
- console.log(
661
- pc.yellow(`\n⚠ Install required dependencies: `) +
662
- pc.bold(`pnpm add -D ${pkgs}`)
663
- );
664
- }
665
-
666
- for (const warning of frameworkResult.warnings) {
667
- console.log(pc.yellow(` Note: ${warning}`));
668
- }
669
-
670
- // Step 7: Show next steps or start server
671
- if (errors.length === 0) {
672
- console.log(pc.green("\n✓ Setup complete!\n"));
772
+ console.log();
773
+ } else {
774
+ console.log(pc.green(` Done!`) + pc.dim(` Setup complete.\n`));
673
775
 
674
776
  if (startServer) {
675
- console.log(pc.cyan("Starting viewer...\n"));
676
777
  startDevServer(projectRoot);
677
778
  } else {
678
- console.log(pc.cyan("Next steps:"));
679
- console.log(` 1. Run ${pc.bold(`${BRAND.cliCommand} dev`)} to start the viewer`);
680
- if (scenario === "fresh") {
681
- console.log(` 2. Edit ${pc.bold(`${componentPath}/Button/Button.fragment.tsx`)}`);
682
- }
683
- console.log(` 3. Run ${pc.bold(`${BRAND.cliCommand} generate`)} to create fragment files for your components`);
779
+ console.log(` ${pc.bold("Get started:")}`);
780
+ console.log(` ${pc.dim("$")} ${BRAND.cliCommand} dev`);
684
781
  console.log();
782
+
783
+ if (!options.configure) {
784
+ console.log(` ${pc.bold("Customize:")} theme seeds, snapshots, and more`);
785
+ console.log(` ${pc.dim("$")} ${BRAND.cliCommand} init --configure`);
786
+ console.log();
787
+ }
685
788
  }
686
789
  }
687
790