@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
package/dist/bin.js CHANGED
@@ -1,28 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __banner_createRequire } from 'module'; const require = __banner_createRequire(import.meta.url);
3
+ import {
4
+ createAIClient,
5
+ detectProvider,
6
+ getApiKey
7
+ } from "./chunk-SXTKFDCR.js";
8
+ import {
9
+ setup
10
+ } from "./chunk-T5OMVL7E.js";
3
11
  import {
4
12
  buildFragments,
5
13
  buildFragmentsDir,
6
14
  measureBundleSizes,
15
+ resolveComponentSourcePath,
7
16
  runAnalyzeCommand,
8
17
  runDiffCommand,
9
18
  runScreenshotCommand,
10
19
  toPerformanceData,
11
20
  validateAll,
12
21
  validateCoverage,
22
+ validateDrift,
13
23
  validateSchema,
14
24
  validateSnippets
15
- } from "./chunk-5G3VZH43.js";
25
+ } from "./chunk-GVDSFQ4E.js";
26
+ import {
27
+ createComponentExtractor
28
+ } from "./chunk-JJ2VRTBU.js";
16
29
  import {
17
30
  scan
18
- } from "./chunk-ZM4ZQZWZ.js";
31
+ } from "./chunk-TPWGL2XS.js";
19
32
  import {
20
33
  loadConfig,
21
- loadFragmentFile
22
- } from "./chunk-HRFUSSZI.js";
34
+ loadFragmentFile,
35
+ parseFragmentFile
36
+ } from "./chunk-AM4MRTMN.js";
23
37
  import {
24
38
  discoverFragmentFiles
25
- } from "./chunk-WXSR2II7.js";
39
+ } from "./chunk-OQKMEFOS.js";
26
40
  import {
27
41
  FigmaClient,
28
42
  StorageManager,
@@ -38,7 +52,7 @@ import {
38
52
  renderAllComponentVariants,
39
53
  scanCodebase,
40
54
  shutdownSharedPool
41
- } from "./chunk-D5PYOXEI.js";
55
+ } from "./chunk-LVWFOLUZ.js";
42
56
  import "./chunk-D2CDBRNU.js";
43
57
  import {
44
58
  BRAND,
@@ -46,7 +60,7 @@ import {
46
60
  formatBytes,
47
61
  generateContext,
48
62
  resolvePerformanceConfig
49
- } from "./chunk-OQO55NKV.js";
63
+ } from "./chunk-WFS63PCW.js";
50
64
  import "./chunk-Z7EY4VHE.js";
51
65
 
52
66
  // src/bin.ts
@@ -54,7 +68,7 @@ import { Command } from "commander";
54
68
  import pc24 from "picocolors";
55
69
  import { readFileSync } from "fs";
56
70
  import { fileURLToPath } from "url";
57
- import { dirname as dirname5, join as join12 } from "path";
71
+ import { dirname as dirname4, join as join11 } from "path";
58
72
 
59
73
  // src/commands/validate.ts
60
74
  import pc from "picocolors";
@@ -78,6 +92,11 @@ ${BRAND.name} Validator
78
92
  componentStart: options.componentStart,
79
93
  componentLimit
80
94
  });
95
+ } else if (options.drift) {
96
+ console.log(pc.dim("Running drift detection...\n"));
97
+ const driftResult = await validateDrift(config, configDir, { tsconfig: options.tsconfig });
98
+ result = driftResult;
99
+ printDriftReport(driftResult);
81
100
  } else {
82
101
  console.log(pc.dim("Running all validations...\n"));
83
102
  result = await validateAll(config, configDir, {
@@ -118,6 +137,30 @@ ${BRAND.name} Validator
118
137
  }
119
138
  return result;
120
139
  }
140
+ function printDriftReport(result) {
141
+ if (result.reports.length === 0) {
142
+ console.log(pc.green("No drift detected \u2014 fragments are in sync with source.\n"));
143
+ return;
144
+ }
145
+ console.log(pc.bold(`Drift detected in ${result.reports.length} component(s):
146
+ `));
147
+ for (const report of result.reports) {
148
+ console.log(` ${pc.bold(report.component)} ${pc.dim(`(${report.file})`)}`);
149
+ for (const drift of report.drifts) {
150
+ const icon = drift.kind === "removed" ? pc.red("\u2212") : drift.kind === "added" ? pc.green("+") : pc.yellow("~");
151
+ const label = drift.kind.replace("_", " ");
152
+ console.log(` ${icon} ${drift.prop}: ${label}`);
153
+ if (drift.kind !== "added" && drift.kind !== "removed") {
154
+ console.log(pc.dim(` fragment: ${drift.fragment}`));
155
+ console.log(pc.dim(` source: ${drift.source}`));
156
+ }
157
+ }
158
+ if (report.compositionDrift) {
159
+ console.log(` ${pc.yellow("~")} composition: ${report.compositionDrift}`);
160
+ }
161
+ console.log();
162
+ }
163
+ }
121
164
 
122
165
  // src/commands/build.ts
123
166
  import pc2 from "picocolors";
@@ -1858,7 +1901,7 @@ ${BRAND.name} Dev Server
1858
1901
  }
1859
1902
  }
1860
1903
  }
1861
- const { createDevServer } = await import("./viewer-DNMNC5VS.js");
1904
+ const { createDevServer } = await import("./viewer-7ZEAFBVN.js");
1862
1905
  console.log(pc7.dim("\nStarting dev server..."));
1863
1906
  const parsedPort = typeof port === "string" ? parseInt(port, 10) : port;
1864
1907
  try {
@@ -4453,8 +4496,8 @@ async function enhance(options = {}) {
4453
4496
  yes = false,
4454
4497
  dryRun = false,
4455
4498
  format = "interactive",
4456
- provider = detectProvider(options),
4457
- apiKey = getApiKey(provider, options.apiKey),
4499
+ provider = detectProvider2(options),
4500
+ apiKey = getApiKey2(provider, options.apiKey),
4458
4501
  model = options.model || DEFAULT_MODELS[provider],
4459
4502
  root = process.cwd(),
4460
4503
  contextOnly = false,
@@ -4817,33 +4860,12 @@ For each component, provide your response in JSON format:
4817
4860
  context: fullContext
4818
4861
  };
4819
4862
  }
4820
- function detectProvider(options) {
4863
+ function detectProvider2(options) {
4821
4864
  if (options.contextOnly) return "none";
4822
- if (options.provider) return options.provider;
4823
- if (options.apiKey) {
4824
- if (options.apiKey.startsWith("sk-ant-")) return "anthropic";
4825
- if (options.apiKey.startsWith("sk-")) return "openai";
4826
- }
4827
- if (process.env.ANTHROPIC_API_KEY) return "anthropic";
4828
- if (process.env.OPENAI_API_KEY) return "openai";
4829
- return "none";
4865
+ return detectProvider({ provider: options.provider, apiKey: options.apiKey });
4830
4866
  }
4831
- function getApiKey(provider, explicitKey) {
4832
- if (explicitKey) return explicitKey;
4833
- if (provider === "anthropic") return process.env.ANTHROPIC_API_KEY;
4834
- if (provider === "openai") return process.env.OPENAI_API_KEY;
4835
- return void 0;
4836
- }
4837
- async function createAIClient(provider, apiKey) {
4838
- if (provider === "anthropic") {
4839
- const Anthropic = (await import("@anthropic-ai/sdk")).default;
4840
- return new Anthropic({ apiKey });
4841
- }
4842
- if (provider === "openai") {
4843
- const OpenAI = (await import("openai")).default;
4844
- return new OpenAI({ apiKey });
4845
- }
4846
- throw new Error(`Unknown provider: ${provider}`);
4867
+ function getApiKey2(provider, explicitKey) {
4868
+ return getApiKey(provider, explicitKey);
4847
4869
  }
4848
4870
  async function generateEnhancement(client, provider, model, context2) {
4849
4871
  const systemPrompt = generateSystemPrompt();
@@ -5785,365 +5807,228 @@ ${BRAND.name} Doctor
5785
5807
  return result;
5786
5808
  }
5787
5809
 
5788
- // src/commands/setup.ts
5789
- import { readFile as readFile9, writeFile as writeFile9, access as access4, mkdir as mkdir7 } from "fs/promises";
5790
- import { join as join11, resolve as resolve9, dirname as dirname4 } from "path";
5810
+ // src/commands/sync.ts
5791
5811
  import pc23 from "picocolors";
5792
- async function fileExists(path) {
5793
- try {
5794
- await access4(path);
5795
- return true;
5796
- } catch {
5797
- return false;
5798
- }
5799
- }
5800
- async function detectFramework(root) {
5801
- if (await fileExists(join11(root, "app/layout.tsx")) || await fileExists(join11(root, "src/app/layout.tsx"))) {
5802
- return "nextjs-app";
5803
- }
5804
- if (await fileExists(join11(root, "pages/_app.tsx")) || await fileExists(join11(root, "pages/_app.ts"))) {
5805
- return "nextjs-pages";
5806
- }
5807
- if (await fileExists(join11(root, "next.config.ts")) || await fileExists(join11(root, "next.config.js")) || await fileExists(join11(root, "next.config.mjs"))) {
5808
- return "nextjs-app";
5809
- }
5810
- if (await fileExists(join11(root, "vite.config.ts")) || await fileExists(join11(root, "vite.config.js"))) {
5811
- return "vite";
5812
- }
5813
- return "unknown";
5814
- }
5815
- async function findEntryFile(root, framework) {
5816
- const candidates = [];
5817
- switch (framework) {
5818
- case "nextjs-app":
5819
- candidates.push(
5820
- "src/app/layout.tsx",
5821
- "app/layout.tsx",
5822
- "src/app/layout.ts",
5823
- "app/layout.ts"
5824
- );
5825
- break;
5826
- case "nextjs-pages":
5827
- candidates.push("pages/_app.tsx", "pages/_app.ts");
5828
- break;
5829
- case "vite":
5830
- candidates.push(
5831
- "src/main.tsx",
5832
- "src/main.ts",
5833
- "src/index.tsx",
5834
- "src/index.ts"
5835
- );
5836
- break;
5837
- default:
5838
- candidates.push(
5839
- "src/main.tsx",
5840
- "src/main.ts",
5841
- "src/index.tsx",
5842
- "src/index.ts",
5843
- "src/App.tsx",
5844
- "src/App.ts"
5845
- );
5846
- }
5847
- for (const candidate of candidates) {
5848
- if (await fileExists(join11(root, candidate))) {
5849
- return candidate;
5850
- }
5812
+ import { readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
5813
+ async function sync(options = {}) {
5814
+ const { config, configDir } = await loadConfig(options.config);
5815
+ console.log(pc23.cyan(`
5816
+ ${BRAND.name} Sync
5817
+ `));
5818
+ if (options.dryRun) {
5819
+ console.log(pc23.dim("Dry run \u2014 no files will be modified.\n"));
5851
5820
  }
5852
- return null;
5853
- }
5854
- async function findNextConfig(root) {
5855
- const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
5856
- for (const candidate of candidates) {
5857
- if (await fileExists(join11(root, candidate))) {
5858
- return candidate;
5821
+ const result = await runSync(config, configDir, options);
5822
+ if (result.updated.length > 0) {
5823
+ const verb = options.dryRun ? "Would update" : "Updated";
5824
+ console.log(pc23.bold(`${verb} ${result.updated.length} fragment(s):
5825
+ `));
5826
+ for (const comp of result.updated) {
5827
+ console.log(` ${pc23.green("\u2713")} ${pc23.bold(comp.name)} ${pc23.dim(`(${comp.file})`)}`);
5828
+ for (const change of comp.changes) {
5829
+ console.log(` ${pc23.dim("\u2022")} ${change}`);
5830
+ }
5859
5831
  }
5832
+ console.log();
5860
5833
  }
5861
- return null;
5862
- }
5863
- async function addStylesImport(root, entryFile) {
5864
- const fullPath = join11(root, entryFile);
5865
- const content = await readFile9(fullPath, "utf-8");
5866
- if (content.includes("@fragments-sdk/ui/styles")) {
5867
- return { modified: false, message: `Styles already imported in ${entryFile}` };
5868
- }
5869
- const stylesImport = "import '@fragments-sdk/ui/styles';";
5870
- let newContent;
5871
- if (content.startsWith("'use client'") || content.startsWith('"use client"')) {
5872
- const directiveEnd = content.indexOf("\n") + 1;
5873
- newContent = content.slice(0, directiveEnd) + stylesImport + "\n" + content.slice(directiveEnd);
5874
- } else {
5875
- newContent = stylesImport + "\n" + content;
5834
+ if (result.skipped.length > 0) {
5835
+ console.log(pc23.dim(`Skipped ${result.skipped.length}: ${result.skipped.map((s) => s.name).join(", ")}
5836
+ `));
5876
5837
  }
5877
- await writeFile9(fullPath, newContent, "utf-8");
5878
- return { modified: true, message: `Added styles import to ${entryFile}` };
5879
- }
5880
- async function addThemeProvider(root, entryFile, framework) {
5881
- const fullPath = join11(root, entryFile);
5882
- const content = await readFile9(fullPath, "utf-8");
5883
- if (content.includes("ThemeProvider")) {
5884
- return { modified: false, message: `ThemeProvider already present in ${entryFile}` };
5885
- }
5886
- if (framework === "nextjs-app") {
5887
- const providerImport = "import { ThemeProvider, TooltipProvider, ToastProvider } from '@fragments-sdk/ui';";
5888
- let newContent = content;
5889
- const importLines = content.split("\n");
5890
- let lastImportIdx = -1;
5891
- for (let i = 0; i < importLines.length; i++) {
5892
- if (importLines[i].startsWith("import ") || importLines[i].startsWith("import '") || importLines[i].startsWith('import "')) {
5893
- lastImportIdx = i;
5894
- }
5895
- }
5896
- if (lastImportIdx >= 0) {
5897
- importLines.splice(lastImportIdx + 1, 0, providerImport);
5898
- newContent = importLines.join("\n");
5899
- } else {
5900
- newContent = providerImport + "\n" + content;
5901
- }
5902
- if (!content.includes("suppressHydrationWarning")) {
5903
- await writeFile9(fullPath, newContent, "utf-8");
5904
- return {
5905
- modified: true,
5906
- message: `Added provider imports to ${entryFile}. Wrap your {children} with:
5907
- <ThemeProvider defaultMode="system"><TooltipProvider><ToastProvider>{children}</ToastProvider></TooltipProvider></ThemeProvider>
5908
- Add suppressHydrationWarning to your <html> tag`
5909
- };
5838
+ if (result.errors.length > 0) {
5839
+ console.log(pc23.red(pc23.bold("Errors:")));
5840
+ for (const err of result.errors) {
5841
+ console.log(` ${pc23.red("\u2717")} ${pc23.bold(err.file)}: ${err.message}`);
5910
5842
  }
5911
- await writeFile9(fullPath, newContent, "utf-8");
5912
- return { modified: true, message: `Added provider imports to ${entryFile}. Wrap {children} with ThemeProvider.` };
5913
- }
5914
- return { modified: false, message: "Manual ThemeProvider setup needed \u2014 see https://usefragments.com/getting-started#provider-setup" };
5915
- }
5916
- async function addTranspilePackages(root) {
5917
- const configFile = await findNextConfig(root);
5918
- if (!configFile) {
5919
- return { modified: false, message: "No next.config found" };
5843
+ console.log();
5920
5844
  }
5921
- const fullPath = join11(root, configFile);
5922
- const content = await readFile9(fullPath, "utf-8");
5923
- if (content.includes("transpilePackages") && content.includes("@fragments-sdk/ui")) {
5924
- return { modified: false, message: `transpilePackages already configured in ${configFile}` };
5845
+ if (result.updated.length === 0 && result.errors.length === 0) {
5846
+ console.log(pc23.green("All fragments are in sync \u2014 nothing to update.\n"));
5925
5847
  }
5926
- if (content.includes("transpilePackages")) {
5927
- return {
5928
- modified: false,
5929
- message: `transpilePackages found in ${configFile} but missing '@fragments-sdk/ui'. Please add it manually.`
5930
- };
5848
+ return result;
5849
+ }
5850
+ async function runSync(config, configDir, options) {
5851
+ const fragmentFiles = await discoverFragmentFiles(config, configDir);
5852
+ const updated = [];
5853
+ const skipped = [];
5854
+ const errors = [];
5855
+ if (fragmentFiles.length === 0) {
5856
+ return { success: true, updated, skipped, errors };
5931
5857
  }
5932
- const patterns = [
5933
- // const nextConfig = { ... }
5934
- { search: /const\s+\w+\s*=\s*\{/, replacement: (match) => `${match}
5935
- transpilePackages: ['@fragments-sdk/ui'],` },
5936
- // module.exports = { ... }
5937
- { search: /module\.exports\s*=\s*\{/, replacement: (match) => `${match}
5938
- transpilePackages: ['@fragments-sdk/ui'],` },
5939
- // export default { ... }
5940
- { search: /export\s+default\s*\{/, replacement: (match) => `${match}
5941
- transpilePackages: ['@fragments-sdk/ui'],` }
5942
- ];
5943
- for (const pattern of patterns) {
5944
- if (pattern.search.test(content)) {
5945
- const newContent = content.replace(pattern.search, pattern.replacement);
5946
- await writeFile9(fullPath, newContent, "utf-8");
5947
- return { modified: true, message: `Added transpilePackages to ${configFile}` };
5858
+ const extractor = createComponentExtractor(options.tsconfig);
5859
+ try {
5860
+ for (const file of fragmentFiles) {
5861
+ try {
5862
+ const fragment = await loadFragmentFile(file.absolutePath);
5863
+ if (!fragment?.meta?.name) continue;
5864
+ if (options.component && fragment.meta.name !== options.component) continue;
5865
+ const fileContent = await readFile9(file.absolutePath, "utf-8");
5866
+ const parsed = parseFragmentFile(fileContent, file.absolutePath);
5867
+ if (!parsed.componentImport) {
5868
+ skipped.push({ name: fragment.meta.name, reason: "No component import found" });
5869
+ continue;
5870
+ }
5871
+ const sourcePath = resolveComponentSourcePath(file.absolutePath, parsed.componentImport);
5872
+ if (!sourcePath) {
5873
+ skipped.push({ name: fragment.meta.name, reason: "Cannot resolve component source" });
5874
+ continue;
5875
+ }
5876
+ const meta = extractor.extract(sourcePath, fragment.meta.name);
5877
+ if (!meta) {
5878
+ skipped.push({ name: fragment.meta.name, reason: "Extraction returned null" });
5879
+ continue;
5880
+ }
5881
+ const patch = computePatch(fileContent, fragment, meta);
5882
+ if (patch.changes.length === 0) {
5883
+ skipped.push({ name: fragment.meta.name, reason: "Already in sync" });
5884
+ continue;
5885
+ }
5886
+ if (!options.dryRun) {
5887
+ await writeFile9(file.absolutePath, patch.updatedContent, "utf-8");
5888
+ }
5889
+ updated.push({
5890
+ name: fragment.meta.name,
5891
+ file: file.relativePath,
5892
+ changes: patch.changes
5893
+ });
5894
+ } catch (err) {
5895
+ errors.push({
5896
+ file: file.relativePath,
5897
+ message: err instanceof Error ? err.message : String(err)
5898
+ });
5899
+ }
5948
5900
  }
5901
+ } finally {
5902
+ extractor.dispose();
5949
5903
  }
5950
5904
  return {
5951
- modified: false,
5952
- message: `Could not auto-modify ${configFile}. Add transpilePackages: ['@fragments-sdk/ui'] manually.`
5905
+ success: errors.length === 0,
5906
+ updated,
5907
+ skipped,
5908
+ errors
5953
5909
  };
5954
5910
  }
5955
- async function createScssSeeds(root, brand) {
5956
- const scssLocations = [
5957
- "src/app/globals.scss",
5958
- "app/globals.scss",
5959
- "src/styles/globals.scss",
5960
- "src/globals.scss",
5961
- "styles/globals.scss"
5962
- ];
5963
- for (const loc of scssLocations) {
5964
- const fullPath2 = join11(root, loc);
5965
- if (await fileExists(fullPath2)) {
5966
- const content = await readFile9(fullPath2, "utf-8");
5967
- if (content.includes("@fragments-sdk/ui/styles")) {
5968
- return { modified: false, message: `SCSS seeds already configured in ${loc}` };
5969
- }
5970
- const seedContent = generateScssSeedImport(brand);
5971
- const newContent = seedContent + "\n" + content;
5972
- await writeFile9(fullPath2, newContent, "utf-8");
5973
- return { modified: true, message: `Added SCSS seed import to ${loc}` };
5974
- }
5975
- }
5976
- const targetDir = await fileExists(join11(root, "src/app")) ? "src/app" : await fileExists(join11(root, "src")) ? "src/styles" : "styles";
5977
- const targetPath = join11(targetDir, "globals.scss");
5978
- const fullPath = join11(root, targetPath);
5979
- await mkdir7(dirname4(fullPath), { recursive: true });
5980
- await writeFile9(fullPath, generateScssSeedImport(brand), "utf-8");
5981
- return { modified: true, message: `Created ${targetPath} with SCSS seed configuration` };
5982
- }
5983
- async function setupMcpConfig(root) {
5984
- const mcpConfigPaths = [
5985
- ".cursor/mcp.json",
5986
- ".vscode/mcp.json"
5987
- ];
5988
- for (const configPath of mcpConfigPaths) {
5989
- const fullPath = join11(root, configPath);
5990
- if (await fileExists(fullPath)) {
5991
- try {
5992
- const content = await readFile9(fullPath, "utf-8");
5993
- const config = JSON.parse(content);
5994
- const servers = config.mcpServers || {};
5995
- const hasFragments = Object.values(servers).some((server) => {
5996
- const s = server;
5997
- return s.args?.some((arg) => arg.includes("@fragments-sdk/mcp"));
5998
- });
5999
- if (hasFragments) {
6000
- return { modified: false, message: `MCP server already configured in ${configPath}` };
5911
+ function computePatch(fileContent, fragment, meta) {
5912
+ const changes = [];
5913
+ let content = fileContent;
5914
+ const localSourceProps = Object.fromEntries(
5915
+ Object.entries(meta.props).filter(([_, p]) => p.source === "local")
5916
+ );
5917
+ const addedProps = [];
5918
+ for (const name of Object.keys(localSourceProps)) {
5919
+ if (!(name in fragment.props)) {
5920
+ addedProps.push(name);
5921
+ }
5922
+ }
5923
+ const removedProps = [];
5924
+ for (const name of Object.keys(fragment.props)) {
5925
+ if (!(name in localSourceProps)) {
5926
+ removedProps.push(name);
5927
+ }
5928
+ }
5929
+ if (addedProps.length > 0) {
5930
+ const propsBlockEnd = findPropsBlockEnd(content);
5931
+ if (propsBlockEnd !== -1) {
5932
+ const newEntries = addedProps.map((name) => {
5933
+ const prop = localSourceProps[name];
5934
+ return formatPropEntry(name, prop);
5935
+ }).join("\n");
5936
+ content = content.slice(0, propsBlockEnd) + newEntries + "\n " + content.slice(propsBlockEnd);
5937
+ changes.push(`Added props: ${addedProps.join(", ")}`);
5938
+ }
5939
+ }
5940
+ if (removedProps.length > 0) {
5941
+ for (const name of removedProps) {
5942
+ const propRegex = new RegExp(`([ \\t]*)${escapeRegex(name)}:\\s*\\{`, "g");
5943
+ const match = propRegex.exec(content);
5944
+ if (match) {
5945
+ const indent = match[1];
5946
+ const startIdx = match.index;
5947
+ const endIdx = findMatchingBrace2(content, match.index + match[0].length - 1);
5948
+ if (endIdx !== -1) {
5949
+ let lineEnd = endIdx + 1;
5950
+ if (content[lineEnd] === ",") lineEnd++;
5951
+ if (content[lineEnd] === "\n") lineEnd++;
5952
+ const removedBlock = content.slice(startIdx, lineEnd);
5953
+ const commented = removedBlock.split("\n").map((line) => line ? `${indent}// [drift:removed] ${line.trimStart()}` : "").join("\n");
5954
+ content = content.slice(0, startIdx) + commented + content.slice(lineEnd);
6001
5955
  }
6002
- servers.fragments = {
6003
- command: "npx",
6004
- args: ["@fragments-sdk/mcp"]
6005
- };
6006
- config.mcpServers = servers;
6007
- await writeFile9(fullPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
6008
- return { modified: true, message: `Added Fragments MCP server to ${configPath}` };
6009
- } catch {
6010
- return { modified: false, message: `Could not parse ${configPath}` };
6011
5956
  }
6012
5957
  }
5958
+ changes.push(`Removed props: ${removedProps.join(", ")}`);
6013
5959
  }
6014
- const cursorDir = join11(root, ".cursor");
6015
- const cursorMcpPath = join11(cursorDir, "mcp.json");
6016
- await mkdir7(cursorDir, { recursive: true });
6017
- const mcpConfig = {
6018
- mcpServers: {
6019
- fragments: {
6020
- command: "npx",
6021
- args: ["@fragments-sdk/mcp"]
6022
- }
6023
- }
6024
- };
6025
- await writeFile9(cursorMcpPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
6026
- return { modified: true, message: "Created .cursor/mcp.json with Fragments MCP server" };
5960
+ if (meta.composition && !fragment.ai?.compositionPattern) {
5961
+ changes.push(`Composition: "${meta.composition.pattern}" pattern detected`);
5962
+ }
5963
+ return { updatedContent: content, changes };
6027
5964
  }
6028
- function generateScssSeedImport(brand) {
6029
- const brandColor = brand || "#0066ff";
6030
- return `@use '@fragments-sdk/ui/styles' with (
6031
- $fui-brand: ${brandColor},
6032
- $fui-neutral: "stone",
6033
- $fui-density: "default",
6034
- $fui-radius-style: "rounded"
6035
- );
6036
- `;
5965
+ function findPropsBlockEnd(content) {
5966
+ const propsStart = content.search(/\bprops:\s*\{/);
5967
+ if (propsStart === -1) return -1;
5968
+ const braceStart = content.indexOf("{", propsStart);
5969
+ return findMatchingBrace2(content, braceStart);
6037
5970
  }
6038
- async function setup(options = {}) {
6039
- const root = resolve9(options.root ?? process.cwd());
6040
- const actions = [];
6041
- const errors = [];
6042
- console.log(pc23.cyan(`
6043
- ${BRAND.name} Setup
6044
- `));
6045
- const framework = await detectFramework(root);
6046
- const frameworkLabel = framework === "nextjs-app" ? "Next.js (App Router)" : framework === "nextjs-pages" ? "Next.js (Pages Router)" : framework === "vite" ? "Vite" : "Unknown";
6047
- console.log(` ${pc23.dim("Framework:")} ${frameworkLabel}`);
6048
- const entryFile = await findEntryFile(root, framework);
6049
- if (entryFile) {
6050
- console.log(` ${pc23.dim("Entry:")} ${entryFile}`);
6051
- } else {
6052
- console.log(` ${pc23.yellow("!")} Could not detect entry file`);
6053
- }
6054
- console.log();
6055
- if (entryFile) {
6056
- try {
6057
- const result = await addStylesImport(root, entryFile);
6058
- const icon = result.modified ? pc23.green("+") : pc23.dim("\xB7");
6059
- console.log(` ${icon} ${result.message}`);
6060
- if (result.modified) actions.push(result.message);
6061
- } catch (error) {
6062
- const msg = `Failed to add styles import: ${error instanceof Error ? error.message : error}`;
6063
- console.log(` ${pc23.red("\u2717")} ${msg}`);
6064
- errors.push(msg);
5971
+ function findMatchingBrace2(content, start) {
5972
+ let depth = 0;
5973
+ let inString = null;
5974
+ let escaped = false;
5975
+ for (let i = start; i < content.length; i++) {
5976
+ const ch = content[i];
5977
+ if (escaped) {
5978
+ escaped = false;
5979
+ continue;
6065
5980
  }
6066
- }
6067
- if (entryFile) {
6068
- try {
6069
- const result = await addThemeProvider(root, entryFile, framework);
6070
- const icon = result.modified ? pc23.green("+") : pc23.dim("\xB7");
6071
- console.log(` ${icon} ${result.message}`);
6072
- if (result.modified) actions.push(result.message);
6073
- } catch (error) {
6074
- const msg = `Failed to add ThemeProvider: ${error instanceof Error ? error.message : error}`;
6075
- console.log(` ${pc23.red("\u2717")} ${msg}`);
6076
- errors.push(msg);
5981
+ if (ch === "\\") {
5982
+ escaped = true;
5983
+ continue;
6077
5984
  }
6078
- }
6079
- if (framework === "nextjs-app" || framework === "nextjs-pages") {
6080
- try {
6081
- const result = await addTranspilePackages(root);
6082
- const icon = result.modified ? pc23.green("+") : pc23.dim("\xB7");
6083
- console.log(` ${icon} ${result.message}`);
6084
- if (result.modified) actions.push(result.message);
6085
- } catch (error) {
6086
- const msg = `Failed to update next.config: ${error instanceof Error ? error.message : error}`;
6087
- console.log(` ${pc23.red("\u2717")} ${msg}`);
6088
- errors.push(msg);
5985
+ if (inString) {
5986
+ if (ch === inString) inString = null;
5987
+ continue;
6089
5988
  }
6090
- }
6091
- if (options.scss || options.brand) {
6092
- try {
6093
- const result = await createScssSeeds(root, options.brand);
6094
- const icon = result.modified ? pc23.green("+") : pc23.dim("\xB7");
6095
- console.log(` ${icon} ${result.message}`);
6096
- if (result.modified) actions.push(result.message);
6097
- } catch (error) {
6098
- const msg = `Failed to create SCSS seeds: ${error instanceof Error ? error.message : error}`;
6099
- console.log(` ${pc23.red("\u2717")} ${msg}`);
6100
- errors.push(msg);
5989
+ if (ch === "'" || ch === '"' || ch === "`") {
5990
+ inString = ch;
5991
+ continue;
6101
5992
  }
6102
- }
6103
- if (options.mcp) {
6104
- try {
6105
- const result = await setupMcpConfig(root);
6106
- const icon = result.modified ? pc23.green("+") : pc23.dim("\xB7");
6107
- console.log(` ${icon} ${result.message}`);
6108
- if (result.modified) actions.push(result.message);
6109
- } catch (error) {
6110
- const msg = `Failed to configure MCP: ${error instanceof Error ? error.message : error}`;
6111
- console.log(` ${pc23.red("\u2717")} ${msg}`);
6112
- errors.push(msg);
5993
+ if (ch === "{") depth++;
5994
+ else if (ch === "}") {
5995
+ depth--;
5996
+ if (depth === 0) return i;
6113
5997
  }
6114
5998
  }
6115
- console.log();
6116
- if (errors.length > 0) {
6117
- console.log(pc23.red(` ${errors.length} error(s) occurred during setup`));
6118
- } else if (actions.length > 0) {
6119
- console.log(pc23.green(` \u2713 Setup complete \u2014 ${actions.length} file(s) modified`));
5999
+ return -1;
6000
+ }
6001
+ function formatPropEntry(name, prop) {
6002
+ const lines = [];
6003
+ lines.push(` ${name}: {`);
6004
+ lines.push(` type: '${prop.typeKind}',`);
6005
+ if (prop.description) {
6006
+ lines.push(` description: ${JSON.stringify(prop.description)},`);
6120
6007
  } else {
6121
- console.log(pc23.green(" \u2713 Already configured \u2014 no changes needed"));
6008
+ lines.push(` description: '', // TODO: add description`);
6122
6009
  }
6123
- console.log();
6124
- console.log(pc23.dim(" Next steps:"));
6125
- if (!options.scss && !options.brand) {
6126
- console.log(pc23.dim(" \u2022 Run with --scss to add build-time theme seeds"));
6010
+ if (prop.required) {
6011
+ lines.push(` required: true,`);
6127
6012
  }
6128
- if (!options.mcp) {
6129
- console.log(pc23.dim(" \u2022 Run with --mcp to configure AI tooling (MCP server)"));
6013
+ if (prop.values && prop.values.length > 0) {
6014
+ lines.push(` values: [${prop.values.map((v) => `'${v}'`).join(", ")}],`);
6130
6015
  }
6131
- console.log(pc23.dim(" \u2022 Run `fragments doctor` to verify your setup"));
6132
- console.log(pc23.dim(" \u2022 Visit https://usefragments.com/getting-started"));
6133
- console.log();
6134
- return {
6135
- success: errors.length === 0,
6136
- actions,
6137
- errors
6138
- };
6016
+ if (prop.default !== void 0) {
6017
+ lines.push(` default: '${prop.default}',`);
6018
+ }
6019
+ lines.push(` },`);
6020
+ return lines.join("\n");
6021
+ }
6022
+ function escapeRegex(str) {
6023
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6139
6024
  }
6140
6025
 
6141
6026
  // src/bin.ts
6142
- var __dirname = dirname5(fileURLToPath(import.meta.url));
6143
- var pkg = JSON.parse(readFileSync(join12(__dirname, "../package.json"), "utf-8"));
6027
+ var __dirname = dirname4(fileURLToPath(import.meta.url));
6028
+ var pkg = JSON.parse(readFileSync(join11(__dirname, "../package.json"), "utf-8"));
6144
6029
  var program = new Command();
6145
6030
  program.name(BRAND.cliCommand).description(`${BRAND.name} - Design system documentation and compliance tool`).version(pkg.version);
6146
- program.command("validate").description("Validate fragment files").option("-c, --config <path>", "Path to config file").option("--schema", "Validate fragment schema only").option("--coverage", "Validate coverage only").option("--snippets", "Validate snippet/render policy only").option("--snippet-mode <mode>", "Override snippet policy mode (warn|error)").option("--component-start <name>", "Start component name for alphabetical snippet batch validation").option("--component-limit <n>", "Component count for alphabetical snippet batch validation", (value) => Number.parseInt(value, 10)).action(async (options) => {
6031
+ program.command("validate").description("Validate fragment files").option("-c, --config <path>", "Path to config file").option("--schema", "Validate fragment schema only").option("--coverage", "Validate coverage only").option("--snippets", "Validate snippet/render policy only").option("--drift", "Detect metadata drift between source and fragments").option("--tsconfig <path>", "Path to tsconfig.json (for drift detection)").option("--snippet-mode <mode>", "Override snippet policy mode (warn|error)").option("--component-start <name>", "Start component name for alphabetical snippet batch validation").option("--component-limit <n>", "Component count for alphabetical snippet batch validation", (value) => Number.parseInt(value, 10)).action(async (options) => {
6147
6032
  try {
6148
6033
  const result = await validate(options);
6149
6034
  if (!result.valid) {
@@ -6154,6 +6039,22 @@ program.command("validate").description("Validate fragment files").option("-c, -
6154
6039
  process.exit(1);
6155
6040
  }
6156
6041
  });
6042
+ program.command("sync").description("Auto-update fragment files from component source").option("-c, --config <path>", "Path to config file").option("--tsconfig <path>", "Path to tsconfig.json").option("--dry-run", "Preview changes without writing").option("--component <name>", "Sync specific component only").action(async (options) => {
6043
+ try {
6044
+ const result = await sync({
6045
+ config: options.config,
6046
+ tsconfig: options.tsconfig,
6047
+ dryRun: options.dryRun,
6048
+ component: options.component
6049
+ });
6050
+ if (!result.success) {
6051
+ process.exit(1);
6052
+ }
6053
+ } catch (error) {
6054
+ console.error(pc24.red("Error:"), error instanceof Error ? error.message : error);
6055
+ process.exit(1);
6056
+ }
6057
+ });
6157
6058
  program.command("build").description(`Build compiled ${BRAND.outFile} and ${BRAND.dataDir}/ directory`).option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output file path").option("--registry", `Also generate ${BRAND.dataDir}/${BRAND.registryFile} and ${BRAND.contextFile}`).option("--registry-only", `Only generate ${BRAND.dataDir}/ directory (skip ${BRAND.outFile})`).option("--from-source", "Build from source code (zero-config, no fragment files needed)").option("--skip-usage", "Skip usage analysis when building from source").option("--skip-storybook", "Skip Storybook parsing when building from source").option("-v, --verbose", "Verbose output").action(async (options) => {
6158
6059
  try {
6159
6060
  const result = await build({
@@ -6508,7 +6409,7 @@ Make sure the dev server is running: ${BRAND.cliCommand} dev`));
6508
6409
  });
6509
6410
  program.command("view").description(`Generate a static HTML viewer for ${BRAND.outFile}`).option("-i, --input <path>", `Path to ${BRAND.outFile}`, BRAND.outFile).option("-o, --output <path>", "Output HTML file path", BRAND.viewerHtmlFile).option("--open", "Open in browser after generation").action(async (options) => {
6510
6411
  try {
6511
- const { generateViewerFromJson } = await import("./static-viewer-NUBFPKWH.js");
6412
+ const { generateViewerFromJson } = await import("./static-viewer-5GXH2MGE.js");
6512
6413
  const fs2 = await import("fs/promises");
6513
6414
  const path = await import("path");
6514
6415
  const inputPath = path.resolve(process.cwd(), options.input);
@@ -6571,14 +6472,20 @@ program.command("setup").description("Configure @fragments-sdk/ui in a consumer
6571
6472
  process.exit(1);
6572
6473
  }
6573
6474
  });
6574
- program.command("init").description("Initialize fragments in a project (interactive by default)").option("--force", "Overwrite existing config").option("-y, --yes", "Non-interactive mode - auto-detect and use defaults").option("--scan <path>", "Scan a TypeScript component directory and generate fragment files").action(async (options) => {
6475
+ program.command("init").description("Initialize fragments in a project (zero-config by default)").option("--force", "Overwrite existing config").option("-y, --yes", "Non-interactive mode (now the default)").option("--configure", "Interactive mode for theme seeds, snapshots, etc.").option("--scan <path>", "Scan a TypeScript component directory and generate fragment files").option("--enrich", "Use AI to fill knowledge fields during --scan (requires API key)").option("--dry-run", "Show what --enrich would generate without calling API").option("--provider <provider>", "AI provider for enrichment: anthropic or openai").option("--api-key <key>", "API key for AI enrichment").option("--model <model>", "Override AI model for enrichment").action(async (options) => {
6575
6476
  try {
6576
- const { init } = await import("./init-NDQXUWDU.js");
6477
+ const { init } = await import("./init-ZSX3NRCZ.js");
6577
6478
  const result = await init({
6578
6479
  projectRoot: process.cwd(),
6579
6480
  force: options.force,
6580
6481
  yes: options.scan ? true : options.yes,
6581
- scan: options.scan
6482
+ scan: options.scan,
6483
+ configure: options.configure,
6484
+ enrich: options.enrich,
6485
+ dryRun: options.dryRun,
6486
+ provider: options.provider,
6487
+ apiKey: options.apiKey,
6488
+ model: options.model
6582
6489
  });
6583
6490
  if (!result.success) {
6584
6491
  console.error(pc24.red("\nInit failed with errors:"));
@@ -6592,9 +6499,27 @@ program.command("init").description("Initialize fragments in a project (interact
6592
6499
  process.exit(1);
6593
6500
  }
6594
6501
  });
6502
+ program.command("snapshot").description("Run visual snapshot tests per component variant").option("-p, --port <port>", "Port of running dev server (skips starting one)").option("--update", "Update existing snapshots instead of comparing").option("--component <name>", "Filter to a specific component").option("--spec <path>", "Path to snapshot spec file").option("--ci", "CI mode - exit 1 on mismatch").action(async (options) => {
6503
+ try {
6504
+ const { snapshot } = await import("./snapshot-XOISO2IS.js");
6505
+ const result = await snapshot({
6506
+ port: options.port,
6507
+ update: options.update,
6508
+ component: options.component,
6509
+ spec: options.spec,
6510
+ ci: options.ci
6511
+ });
6512
+ if (!result.success) {
6513
+ process.exit(1);
6514
+ }
6515
+ } catch (error) {
6516
+ console.error(pc24.red("Error:"), error instanceof Error ? error.message : error);
6517
+ process.exit(1);
6518
+ }
6519
+ });
6595
6520
  program.command("tokens").description("Discover and list design tokens from CSS/SCSS files").option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--categories", "Group tokens by category").option("--theme <theme>", "Filter by theme name").option("--category <category>", "Filter by category (color, spacing, typography, etc.)").option("--verbose", "Show all tokens (no truncation)").action(async (options) => {
6596
6521
  try {
6597
- const { tokens } = await import("./tokens-CE46OTMD.js");
6522
+ const { tokens } = await import("./tokens-T6SIVUT5.js");
6598
6523
  const result = await tokens({
6599
6524
  config: options.config,
6600
6525
  json: options.json,
@@ -6613,7 +6538,7 @@ program.command("tokens").description("Discover and list design tokens from CSS/
6613
6538
  });
6614
6539
  program.command("generate").description("Generate fragment files from component source code").argument("[component]", "Specific component name to generate (optional)").option("--force", "Overwrite existing fragment files").option("--pattern <glob>", "Pattern for component files", "src/components/**/*.tsx").action(async (component, options) => {
6615
6540
  try {
6616
- const { generate } = await import("./generate-FBHSXR3D.js");
6541
+ const { generate } = await import("./generate-RJFS2JWA.js");
6617
6542
  const result = await generate({
6618
6543
  projectRoot: process.cwd(),
6619
6544
  component,
@@ -6658,7 +6583,7 @@ program.command("perf").description("Profile component bundle sizes and performa
6658
6583
  program.command("test").description("Run interaction tests for fragments with play functions").option("-c, --config <path>", "Path to config file").option("--component <name>", "Filter by component name").option("--tags <tags>", "Filter by tags (comma-separated)").option("--grep <pattern>", "Filter by variant name pattern").option("--exclude <pattern>", "Exclude tests matching pattern").option("--parallel <count>", "Number of parallel browser contexts", parseInt, 4).option("--timeout <ms>", "Timeout per test in milliseconds", parseInt, 3e4).option("--retries <count>", "Number of retries for failed tests", parseInt, 0).option("--bail", "Stop on first failure").option("--browser <name>", "Browser to use (chromium, firefox, webkit)", "chromium").option("--headed", "Run in headed mode (show browser)").option("--a11y", "Run accessibility checks with axe-core").option("--visual", "Capture screenshots for visual regression").option("--update-snapshots", "Update visual snapshots").option("--watch", "Watch mode - re-run on file changes").option("--reporters <names>", "Reporters to use (console, junit, json)", "console").option("-o, --output <dir>", "Output directory for results", "./test-results").option("--server-url <url>", "URL of running dev server (skips starting server)").option("-p, --port <port>", "Port for dev server", parseInt, 6006).option("--ci", "CI mode - non-interactive, exit with code 1 on failure").option("--list", "List available tests without running them").action(async (options) => {
6659
6584
  try {
6660
6585
  const { config, configDir } = await loadConfig(options.config);
6661
- const { runTestCommand, listTests } = await import("./test-Z5LVO724.js");
6586
+ const { runTestCommand, listTests } = await import("./test-SI4NSHQX.js");
6662
6587
  if (options.list) {
6663
6588
  await listTests(config, configDir, {
6664
6589
  component: options.component,