@fragments-sdk/cli 0.2.2

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 (259) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/dist/bin.d.ts +1 -0
  4. package/dist/bin.js +4783 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/chunk-4FDQSGKX.js +786 -0
  7. package/dist/chunk-4FDQSGKX.js.map +1 -0
  8. package/dist/chunk-7H2MMGYG.js +369 -0
  9. package/dist/chunk-7H2MMGYG.js.map +1 -0
  10. package/dist/chunk-BSCG3IP7.js +619 -0
  11. package/dist/chunk-BSCG3IP7.js.map +1 -0
  12. package/dist/chunk-LY2CFFPY.js +898 -0
  13. package/dist/chunk-LY2CFFPY.js.map +1 -0
  14. package/dist/chunk-MUZ6CM66.js +6636 -0
  15. package/dist/chunk-MUZ6CM66.js.map +1 -0
  16. package/dist/chunk-OAENNG3G.js +1489 -0
  17. package/dist/chunk-OAENNG3G.js.map +1 -0
  18. package/dist/chunk-XHNKNI6J.js +235 -0
  19. package/dist/chunk-XHNKNI6J.js.map +1 -0
  20. package/dist/core-DWKLGY4N.js +68 -0
  21. package/dist/core-DWKLGY4N.js.map +1 -0
  22. package/dist/generate-4LQNJ7SX.js +249 -0
  23. package/dist/generate-4LQNJ7SX.js.map +1 -0
  24. package/dist/index.d.ts +775 -0
  25. package/dist/index.js +41 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/init-EMVI47QG.js +416 -0
  28. package/dist/init-EMVI47QG.js.map +1 -0
  29. package/dist/mcp-bin.d.ts +1 -0
  30. package/dist/mcp-bin.js +1117 -0
  31. package/dist/mcp-bin.js.map +1 -0
  32. package/dist/scan-4YPRF7FV.js +12 -0
  33. package/dist/scan-4YPRF7FV.js.map +1 -0
  34. package/dist/service-QSZMZJBJ.js +208 -0
  35. package/dist/service-QSZMZJBJ.js.map +1 -0
  36. package/dist/static-viewer-MIPGZ4Z7.js +12 -0
  37. package/dist/static-viewer-MIPGZ4Z7.js.map +1 -0
  38. package/dist/test-SQ5ZHXWU.js +1067 -0
  39. package/dist/test-SQ5ZHXWU.js.map +1 -0
  40. package/dist/tokens-HSGMYK64.js +173 -0
  41. package/dist/tokens-HSGMYK64.js.map +1 -0
  42. package/dist/viewer-YRF4SQE4.js +11101 -0
  43. package/dist/viewer-YRF4SQE4.js.map +1 -0
  44. package/package.json +107 -0
  45. package/src/ai.ts +266 -0
  46. package/src/analyze.ts +265 -0
  47. package/src/bin.ts +916 -0
  48. package/src/build.ts +248 -0
  49. package/src/commands/a11y.ts +302 -0
  50. package/src/commands/add.ts +313 -0
  51. package/src/commands/audit.ts +195 -0
  52. package/src/commands/baseline.ts +221 -0
  53. package/src/commands/build.ts +144 -0
  54. package/src/commands/compare.ts +337 -0
  55. package/src/commands/context.ts +107 -0
  56. package/src/commands/dev.ts +107 -0
  57. package/src/commands/enhance.ts +858 -0
  58. package/src/commands/generate.ts +391 -0
  59. package/src/commands/init.ts +531 -0
  60. package/src/commands/link/figma.ts +645 -0
  61. package/src/commands/link/index.ts +10 -0
  62. package/src/commands/link/storybook.ts +267 -0
  63. package/src/commands/list.ts +49 -0
  64. package/src/commands/metrics.ts +114 -0
  65. package/src/commands/reset.ts +242 -0
  66. package/src/commands/scan.ts +537 -0
  67. package/src/commands/storygen.ts +207 -0
  68. package/src/commands/tokens.ts +251 -0
  69. package/src/commands/validate.ts +93 -0
  70. package/src/commands/verify.ts +215 -0
  71. package/src/core/composition.test.ts +262 -0
  72. package/src/core/composition.ts +255 -0
  73. package/src/core/config.ts +84 -0
  74. package/src/core/constants.ts +111 -0
  75. package/src/core/context.ts +380 -0
  76. package/src/core/defineSegment.ts +137 -0
  77. package/src/core/discovery.ts +337 -0
  78. package/src/core/figma.ts +263 -0
  79. package/src/core/fragment-types.ts +214 -0
  80. package/src/core/generators/context.ts +389 -0
  81. package/src/core/generators/index.ts +23 -0
  82. package/src/core/generators/registry.ts +364 -0
  83. package/src/core/generators/typescript-extractor.ts +374 -0
  84. package/src/core/importAnalyzer.ts +217 -0
  85. package/src/core/index.ts +149 -0
  86. package/src/core/loader.ts +155 -0
  87. package/src/core/node.ts +63 -0
  88. package/src/core/parser.ts +551 -0
  89. package/src/core/previewLoader.ts +172 -0
  90. package/src/core/schema/fragment.schema.json +189 -0
  91. package/src/core/schema/registry.schema.json +137 -0
  92. package/src/core/schema.ts +182 -0
  93. package/src/core/storyAdapter.test.ts +571 -0
  94. package/src/core/storyAdapter.ts +761 -0
  95. package/src/core/token-types.ts +287 -0
  96. package/src/core/types.ts +754 -0
  97. package/src/diff.ts +323 -0
  98. package/src/index.ts +43 -0
  99. package/src/mcp/__tests__/projectFields.test.ts +130 -0
  100. package/src/mcp/bin.ts +36 -0
  101. package/src/mcp/index.ts +8 -0
  102. package/src/mcp/server.ts +1310 -0
  103. package/src/mcp/utils.ts +54 -0
  104. package/src/mcp-bin.ts +36 -0
  105. package/src/migrate/__tests__/argTypes/argTypes.test.ts +189 -0
  106. package/src/migrate/__tests__/args/args.test.ts +452 -0
  107. package/src/migrate/__tests__/meta/meta.test.ts +198 -0
  108. package/src/migrate/__tests__/stories/stories.test.ts +278 -0
  109. package/src/migrate/__tests__/utils/utils.test.ts +371 -0
  110. package/src/migrate/__tests__/values/values.test.ts +303 -0
  111. package/src/migrate/bin.ts +108 -0
  112. package/src/migrate/converter.ts +658 -0
  113. package/src/migrate/detect.ts +196 -0
  114. package/src/migrate/index.ts +45 -0
  115. package/src/migrate/migrate.ts +163 -0
  116. package/src/migrate/parser.ts +1136 -0
  117. package/src/migrate/report.ts +624 -0
  118. package/src/migrate/types.ts +169 -0
  119. package/src/screenshot.ts +249 -0
  120. package/src/service/__tests__/ast-utils.test.ts +426 -0
  121. package/src/service/__tests__/enhance-scanner.test.ts +200 -0
  122. package/src/service/__tests__/figma/figma.test.ts +652 -0
  123. package/src/service/__tests__/metrics-store.test.ts +409 -0
  124. package/src/service/__tests__/patch-generator.test.ts +186 -0
  125. package/src/service/__tests__/props-extractor.test.ts +365 -0
  126. package/src/service/__tests__/token-registry.test.ts +267 -0
  127. package/src/service/analytics.ts +659 -0
  128. package/src/service/ast-utils.ts +444 -0
  129. package/src/service/browser-pool.ts +339 -0
  130. package/src/service/capture.ts +267 -0
  131. package/src/service/diff.ts +279 -0
  132. package/src/service/enhance/aggregator.ts +489 -0
  133. package/src/service/enhance/cache.ts +275 -0
  134. package/src/service/enhance/codebase-scanner.ts +357 -0
  135. package/src/service/enhance/context-generator.ts +529 -0
  136. package/src/service/enhance/doc-extractor.ts +523 -0
  137. package/src/service/enhance/index.ts +131 -0
  138. package/src/service/enhance/props-extractor.ts +665 -0
  139. package/src/service/enhance/scanner.ts +445 -0
  140. package/src/service/enhance/storybook-parser.ts +552 -0
  141. package/src/service/enhance/types.ts +346 -0
  142. package/src/service/enhance/variant-renderer.ts +479 -0
  143. package/src/service/figma.ts +1008 -0
  144. package/src/service/index.ts +249 -0
  145. package/src/service/metrics-store.ts +333 -0
  146. package/src/service/patch-generator.ts +349 -0
  147. package/src/service/report.ts +854 -0
  148. package/src/service/storage.ts +401 -0
  149. package/src/service/token-fixes.ts +281 -0
  150. package/src/service/token-parser.ts +504 -0
  151. package/src/service/token-registry.ts +721 -0
  152. package/src/service/utils.ts +172 -0
  153. package/src/setup.ts +241 -0
  154. package/src/shared/command-wrapper.ts +81 -0
  155. package/src/shared/dev-server-client.ts +199 -0
  156. package/src/shared/index.ts +8 -0
  157. package/src/shared/segment-loader.ts +59 -0
  158. package/src/shared/types.ts +147 -0
  159. package/src/static-viewer.ts +715 -0
  160. package/src/test/discovery.ts +172 -0
  161. package/src/test/index.ts +281 -0
  162. package/src/test/reporters/console.ts +194 -0
  163. package/src/test/reporters/json.ts +190 -0
  164. package/src/test/reporters/junit.ts +186 -0
  165. package/src/test/runner.ts +598 -0
  166. package/src/test/types.ts +245 -0
  167. package/src/test/watch.ts +200 -0
  168. package/src/validators.ts +152 -0
  169. package/src/viewer/__tests__/jsx-parser.test.ts +502 -0
  170. package/src/viewer/__tests__/render-utils.test.ts +232 -0
  171. package/src/viewer/__tests__/style-utils.test.ts +404 -0
  172. package/src/viewer/bin.ts +86 -0
  173. package/src/viewer/cli/health.ts +256 -0
  174. package/src/viewer/cli/index.ts +33 -0
  175. package/src/viewer/cli/scan.ts +124 -0
  176. package/src/viewer/cli/utils.ts +174 -0
  177. package/src/viewer/components/AccessibilityPanel.tsx +1404 -0
  178. package/src/viewer/components/ActionCapture.tsx +172 -0
  179. package/src/viewer/components/ActionsPanel.tsx +371 -0
  180. package/src/viewer/components/App.tsx +638 -0
  181. package/src/viewer/components/BottomPanel.tsx +224 -0
  182. package/src/viewer/components/CodePanel.tsx +589 -0
  183. package/src/viewer/components/CommandPalette.tsx +336 -0
  184. package/src/viewer/components/ComponentGraph.tsx +394 -0
  185. package/src/viewer/components/ComponentHeader.tsx +85 -0
  186. package/src/viewer/components/ContractPanel.tsx +234 -0
  187. package/src/viewer/components/ErrorBoundary.tsx +85 -0
  188. package/src/viewer/components/FigmaEmbed.tsx +231 -0
  189. package/src/viewer/components/FragmentEditor.tsx +485 -0
  190. package/src/viewer/components/HealthDashboard.tsx +452 -0
  191. package/src/viewer/components/HmrStatusIndicator.tsx +71 -0
  192. package/src/viewer/components/Icons.tsx +417 -0
  193. package/src/viewer/components/InteractionsPanel.tsx +720 -0
  194. package/src/viewer/components/IsolatedPreviewFrame.tsx +321 -0
  195. package/src/viewer/components/IsolatedRender.tsx +111 -0
  196. package/src/viewer/components/KeyboardShortcutsHelp.tsx +89 -0
  197. package/src/viewer/components/LandingPage.tsx +441 -0
  198. package/src/viewer/components/Layout.tsx +22 -0
  199. package/src/viewer/components/LeftSidebar.tsx +391 -0
  200. package/src/viewer/components/MultiViewportPreview.tsx +429 -0
  201. package/src/viewer/components/PreviewArea.tsx +404 -0
  202. package/src/viewer/components/PreviewFrameHost.tsx +310 -0
  203. package/src/viewer/components/PreviewPane.tsx +150 -0
  204. package/src/viewer/components/PreviewToolbar.tsx +176 -0
  205. package/src/viewer/components/PropsEditor.tsx +512 -0
  206. package/src/viewer/components/PropsTable.tsx +98 -0
  207. package/src/viewer/components/RelationsSection.tsx +57 -0
  208. package/src/viewer/components/ResizablePanel.tsx +328 -0
  209. package/src/viewer/components/RightSidebar.tsx +118 -0
  210. package/src/viewer/components/ScreenshotButton.tsx +90 -0
  211. package/src/viewer/components/Sidebar.tsx +169 -0
  212. package/src/viewer/components/SkeletonLoader.tsx +156 -0
  213. package/src/viewer/components/StoryRenderer.tsx +128 -0
  214. package/src/viewer/components/ThemeProvider.tsx +96 -0
  215. package/src/viewer/components/Toast.tsx +67 -0
  216. package/src/viewer/components/TokenStylePanel.tsx +708 -0
  217. package/src/viewer/components/UsageSection.tsx +95 -0
  218. package/src/viewer/components/VariantMatrix.tsx +350 -0
  219. package/src/viewer/components/VariantRenderer.tsx +131 -0
  220. package/src/viewer/components/VariantTabs.tsx +84 -0
  221. package/src/viewer/components/ViewportSelector.tsx +165 -0
  222. package/src/viewer/components/_future/CreatePage.tsx +836 -0
  223. package/src/viewer/composition-renderer.ts +381 -0
  224. package/src/viewer/constants/index.ts +1 -0
  225. package/src/viewer/constants/ui.ts +185 -0
  226. package/src/viewer/entry.tsx +299 -0
  227. package/src/viewer/hooks/index.ts +2 -0
  228. package/src/viewer/hooks/useA11yCache.ts +383 -0
  229. package/src/viewer/hooks/useA11yService.ts +498 -0
  230. package/src/viewer/hooks/useActions.ts +138 -0
  231. package/src/viewer/hooks/useAppState.ts +124 -0
  232. package/src/viewer/hooks/useFigmaIntegration.ts +132 -0
  233. package/src/viewer/hooks/useHmrStatus.ts +109 -0
  234. package/src/viewer/hooks/useKeyboardShortcuts.ts +222 -0
  235. package/src/viewer/hooks/usePreviewBridge.ts +347 -0
  236. package/src/viewer/hooks/useScrollSpy.ts +78 -0
  237. package/src/viewer/hooks/useUrlState.ts +330 -0
  238. package/src/viewer/hooks/useViewSettings.ts +125 -0
  239. package/src/viewer/index.html +28 -0
  240. package/src/viewer/index.ts +14 -0
  241. package/src/viewer/intelligence/healthReport.ts +505 -0
  242. package/src/viewer/intelligence/styleDrift.ts +340 -0
  243. package/src/viewer/intelligence/usageScanner.ts +309 -0
  244. package/src/viewer/jsx-parser.ts +485 -0
  245. package/src/viewer/postcss.config.js +6 -0
  246. package/src/viewer/preview-frame-entry.tsx +25 -0
  247. package/src/viewer/preview-frame.html +109 -0
  248. package/src/viewer/render-template.html +68 -0
  249. package/src/viewer/render-utils.ts +170 -0
  250. package/src/viewer/server.ts +276 -0
  251. package/src/viewer/style-utils.ts +414 -0
  252. package/src/viewer/styles/globals.css +355 -0
  253. package/src/viewer/tailwind.config.js +37 -0
  254. package/src/viewer/types/a11y.ts +197 -0
  255. package/src/viewer/utils/a11y-fixes.ts +471 -0
  256. package/src/viewer/utils/actionExport.ts +372 -0
  257. package/src/viewer/utils/colorSchemes.ts +201 -0
  258. package/src/viewer/utils/detectRelationships.ts +256 -0
  259. package/src/viewer/vite-plugin.ts +2143 -0
package/dist/bin.js ADDED
@@ -0,0 +1,4783 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
+ import {
4
+ buildFragmentsDir,
5
+ buildSegments,
6
+ runAnalyzeCommand,
7
+ runDiffCommand,
8
+ runScreenshotCommand,
9
+ validateAll,
10
+ validateCoverage,
11
+ validateSchema
12
+ } from "./chunk-4FDQSGKX.js";
13
+ import {
14
+ scan
15
+ } from "./chunk-7H2MMGYG.js";
16
+ import {
17
+ FigmaClient,
18
+ StorageManager,
19
+ checkStorybookRunning,
20
+ createMetricsStore,
21
+ extractPropsFromFile,
22
+ generateComponentContext,
23
+ generatePromptContext,
24
+ generateSystemPrompt,
25
+ generateUserPrompt,
26
+ getScanStats,
27
+ parseAllStories,
28
+ renderAllComponentVariants,
29
+ scanCodebase,
30
+ shutdownSharedPool
31
+ } from "./chunk-MUZ6CM66.js";
32
+ import {
33
+ discoverSegmentFiles,
34
+ loadConfig,
35
+ loadSegmentFile
36
+ } from "./chunk-OAENNG3G.js";
37
+ import {
38
+ generateContext
39
+ } from "./chunk-LY2CFFPY.js";
40
+ import {
41
+ BRAND
42
+ } from "./chunk-XHNKNI6J.js";
43
+
44
+ // src/bin.ts
45
+ import { Command } from "commander";
46
+ import pc20 from "picocolors";
47
+
48
+ // src/commands/validate.ts
49
+ import pc from "picocolors";
50
+ async function validate(options = {}) {
51
+ const { config, configDir } = await loadConfig(options.config);
52
+ console.log(pc.cyan(`
53
+ ${BRAND.name} Validator
54
+ `));
55
+ let result;
56
+ if (options.schema) {
57
+ console.log(pc.dim("Running schema validation...\n"));
58
+ result = await validateSchema(config, configDir);
59
+ } else if (options.coverage) {
60
+ console.log(pc.dim("Running coverage validation...\n"));
61
+ result = await validateCoverage(config, configDir);
62
+ } else {
63
+ console.log(pc.dim("Running all validations...\n"));
64
+ result = await validateAll(config, configDir);
65
+ }
66
+ if (result.errors.length > 0) {
67
+ console.log(pc.red(pc.bold("Errors:")));
68
+ for (const error of result.errors) {
69
+ console.log(` ${pc.red("\u2717")} ${pc.bold(error.file)}`);
70
+ console.log(` ${error.message}`);
71
+ if (error.details) {
72
+ console.log(pc.dim(` ${error.details}`));
73
+ }
74
+ }
75
+ console.log();
76
+ }
77
+ if (result.warnings.length > 0) {
78
+ console.log(pc.yellow(pc.bold("Warnings:")));
79
+ for (const warning of result.warnings) {
80
+ console.log(` ${pc.yellow("\u26A0")} ${pc.bold(warning.file)}`);
81
+ console.log(` ${warning.message}`);
82
+ }
83
+ console.log();
84
+ }
85
+ if (result.valid && result.warnings.length === 0) {
86
+ console.log(pc.green("\u2713 All validations passed\n"));
87
+ } else if (result.valid) {
88
+ console.log(
89
+ pc.yellow(`\u26A0 Passed with ${result.warnings.length} warning(s)
90
+ `)
91
+ );
92
+ } else {
93
+ console.log(pc.red(`\u2717 Failed with ${result.errors.length} error(s)
94
+ `));
95
+ }
96
+ return result;
97
+ }
98
+
99
+ // src/commands/build.ts
100
+ import pc2 from "picocolors";
101
+ async function build(options = {}) {
102
+ if (options.fromSource) {
103
+ console.log(pc2.cyan(`
104
+ ${BRAND.name} Build (from source)
105
+ `));
106
+ console.log(pc2.dim("Using zero-config source extraction pipeline\n"));
107
+ const scanResult = await scan({
108
+ config: options.config,
109
+ output: options.output,
110
+ skipUsage: options.skipUsage,
111
+ skipStorybook: options.skipStorybook,
112
+ verbose: options.verbose
113
+ });
114
+ return {
115
+ success: scanResult.success,
116
+ segmentCount: scanResult.componentCount,
117
+ outputPath: scanResult.outputPath,
118
+ errors: scanResult.errors.map((e) => ({ file: e.component, error: e.error }))
119
+ };
120
+ }
121
+ const { config, configDir } = await loadConfig(options.config);
122
+ if (options.output) {
123
+ config.outFile = options.output;
124
+ }
125
+ console.log(pc2.cyan(`
126
+ ${BRAND.name} Build
127
+ `));
128
+ const errors = [];
129
+ let segmentCount;
130
+ let outputPath;
131
+ let componentCount;
132
+ let registryPath;
133
+ let contextPath;
134
+ if (!options.registryOnly) {
135
+ console.log(pc2.dim("Compiling fragments...\n"));
136
+ const result = await buildSegments(config, configDir);
137
+ if (result.errors.length > 0) {
138
+ console.log(pc2.yellow("Build completed with errors:\n"));
139
+ for (const error of result.errors) {
140
+ console.log(` ${pc2.red("\u2717")} ${error.file}: ${error.error}`);
141
+ errors.push(error);
142
+ }
143
+ console.log();
144
+ }
145
+ segmentCount = result.segmentCount;
146
+ outputPath = result.outputPath;
147
+ console.log(pc2.green(`\u2713 Built ${result.segmentCount} fragment(s)`));
148
+ console.log(pc2.dim(` Output: ${result.outputPath}
149
+ `));
150
+ }
151
+ if (options.registry || options.registryOnly) {
152
+ console.log(pc2.dim("Generating registry and context...\n"));
153
+ const fragmentsResult = await buildFragmentsDir(config, configDir);
154
+ if (fragmentsResult.errors.length > 0) {
155
+ console.log(pc2.yellow("Registry build completed with errors:\n"));
156
+ for (const error of fragmentsResult.errors) {
157
+ console.log(` ${pc2.red("\u2717")} ${error.file}: ${error.error}`);
158
+ errors.push(error);
159
+ }
160
+ console.log();
161
+ }
162
+ componentCount = fragmentsResult.componentCount;
163
+ registryPath = fragmentsResult.registryPath;
164
+ contextPath = fragmentsResult.contextPath;
165
+ console.log(pc2.green(`\u2713 Generated registry with ${fragmentsResult.componentCount} component(s)`));
166
+ console.log(pc2.dim(` Registry: ${fragmentsResult.registryPath}`));
167
+ console.log(pc2.dim(` Context: ${fragmentsResult.contextPath}
168
+ `));
169
+ }
170
+ return {
171
+ success: errors.length === 0,
172
+ segmentCount,
173
+ outputPath,
174
+ componentCount,
175
+ registryPath,
176
+ contextPath,
177
+ errors
178
+ };
179
+ }
180
+
181
+ // src/commands/context.ts
182
+ import { readFile } from "fs/promises";
183
+ import { resolve } from "path";
184
+ import pc3 from "picocolors";
185
+ async function context(options = {}) {
186
+ const {
187
+ format = "markdown",
188
+ compact = false,
189
+ includeCode = false,
190
+ includeRelations = false,
191
+ tokensOnly = false
192
+ } = options;
193
+ let segments;
194
+ if (options.input) {
195
+ const inputPath = resolve(process.cwd(), options.input);
196
+ const content2 = await readFile(inputPath, "utf-8");
197
+ const data = JSON.parse(content2);
198
+ segments = Object.values(data.segments);
199
+ } else {
200
+ const { config, configDir } = await loadConfig(options.config);
201
+ const result = await buildSegments(config, configDir);
202
+ if (result.errors.length > 0 && result.segmentCount === 0) {
203
+ console.error(pc3.red("Error: No segments found. Run `segments build` first or fix errors."));
204
+ return { success: false, tokenEstimate: 0 };
205
+ }
206
+ const content2 = await readFile(result.outputPath, "utf-8");
207
+ const data = JSON.parse(content2);
208
+ segments = Object.values(data.segments);
209
+ }
210
+ if (segments.length === 0) {
211
+ console.error(pc3.red("No segments found."));
212
+ return { success: false, tokenEstimate: 0 };
213
+ }
214
+ const { content, tokenEstimate } = generateContext(segments, {
215
+ format,
216
+ compact,
217
+ include: {
218
+ code: includeCode,
219
+ relations: includeRelations
220
+ }
221
+ });
222
+ if (tokensOnly) {
223
+ console.log(`Estimated tokens: ${tokenEstimate}`);
224
+ } else {
225
+ console.log(content);
226
+ console.error(pc3.dim(`
227
+ [${tokenEstimate} tokens estimated]`));
228
+ }
229
+ return {
230
+ success: true,
231
+ content,
232
+ tokenEstimate
233
+ };
234
+ }
235
+
236
+ // src/commands/list.ts
237
+ import pc4 from "picocolors";
238
+ async function list(options = {}) {
239
+ const { config, configDir } = await loadConfig(options.config);
240
+ const files = await discoverSegmentFiles(config, configDir);
241
+ console.log(pc4.cyan(`
242
+ ${BRAND.name} - Discovered Fragments
243
+ `));
244
+ if (files.length === 0) {
245
+ console.log(pc4.yellow("No fragment files found.\n"));
246
+ console.log(pc4.dim(`Looking for: ${config.include.join(", ")}`));
247
+ return { success: true, files: [] };
248
+ }
249
+ for (const file of files) {
250
+ console.log(` ${pc4.dim("\u2022")} ${file.relativePath}`);
251
+ }
252
+ console.log(pc4.dim(`
253
+ ${files.length} fragment(s) found
254
+ `));
255
+ return { success: true, files };
256
+ }
257
+
258
+ // src/commands/reset.ts
259
+ import { stat, rm, unlink } from "fs/promises";
260
+ import { join, relative } from "path";
261
+ import pc5 from "picocolors";
262
+ import fg from "fast-glob";
263
+ async function reset(options = {}) {
264
+ const { yes = false, dryRun = false } = options;
265
+ console.log(pc5.cyan(`
266
+ ${BRAND.name} Reset
267
+ `));
268
+ const projectRoot = process.cwd();
269
+ const filesToDelete = [];
270
+ const dirsToDelete = [];
271
+ const dataDir = join(projectRoot, BRAND.dataDir);
272
+ try {
273
+ const dataDirStat = await stat(dataDir);
274
+ if (dataDirStat.isDirectory()) {
275
+ dirsToDelete.push(dataDir);
276
+ }
277
+ } catch {
278
+ }
279
+ const defaultOutFile = join(projectRoot, "segments.json");
280
+ try {
281
+ const fileStat = await stat(defaultOutFile);
282
+ if (fileStat.isFile()) {
283
+ filesToDelete.push(defaultOutFile);
284
+ }
285
+ } catch {
286
+ }
287
+ let segmentPatterns = [`**/*${BRAND.fileExtension}`];
288
+ try {
289
+ const { config } = await loadConfig();
290
+ if (config.outFile && config.outFile !== "segments.json") {
291
+ const customOutFile = join(projectRoot, config.outFile);
292
+ try {
293
+ const fileStat = await stat(customOutFile);
294
+ if (fileStat.isFile()) {
295
+ filesToDelete.push(customOutFile);
296
+ }
297
+ } catch {
298
+ }
299
+ }
300
+ if (config.include && config.include.length > 0) {
301
+ segmentPatterns = config.include;
302
+ }
303
+ } catch {
304
+ }
305
+ console.log(pc5.dim("Scanning for generated files...\n"));
306
+ for (const pattern of segmentPatterns) {
307
+ const matches = await fg(pattern, {
308
+ cwd: projectRoot,
309
+ ignore: ["**/node_modules/**"],
310
+ absolute: true
311
+ });
312
+ for (const match of matches) {
313
+ if (!filesToDelete.includes(match)) {
314
+ filesToDelete.push(match);
315
+ }
316
+ }
317
+ }
318
+ const mdxFiles = await fg("**/Documentation.mdx", {
319
+ cwd: projectRoot,
320
+ ignore: ["**/node_modules/**"],
321
+ absolute: true
322
+ });
323
+ for (const mdxFile of mdxFiles) {
324
+ if (!filesToDelete.includes(mdxFile)) {
325
+ filesToDelete.push(mdxFile);
326
+ }
327
+ }
328
+ if (filesToDelete.length === 0 && dirsToDelete.length === 0) {
329
+ console.log(pc5.yellow("Nothing to reset. No generated files found.\n"));
330
+ return { success: true, deletedFiles: 0, deletedDirs: 0 };
331
+ }
332
+ console.log(pc5.dim("The following will be deleted:\n"));
333
+ for (const dir of dirsToDelete) {
334
+ const relativePath = relative(projectRoot, dir);
335
+ console.log(` \u{1F4C1} ${relativePath}/`);
336
+ }
337
+ const segmentFiles = filesToDelete.filter((f) => f.endsWith(BRAND.fileExtension));
338
+ const mdxFilesFound = filesToDelete.filter((f) => f.endsWith(".mdx"));
339
+ const otherFiles = filesToDelete.filter(
340
+ (f) => !f.endsWith(BRAND.fileExtension) && !f.endsWith(".mdx")
341
+ );
342
+ if (segmentFiles.length > 0) {
343
+ console.log(` \u{1F4C4} ${segmentFiles.length} segment file(s) (*${BRAND.fileExtension})`);
344
+ if (segmentFiles.length <= 5) {
345
+ for (const f of segmentFiles) {
346
+ console.log(pc5.dim(` ${relative(projectRoot, f)}`));
347
+ }
348
+ } else {
349
+ for (const f of segmentFiles.slice(0, 3)) {
350
+ console.log(pc5.dim(` ${relative(projectRoot, f)}`));
351
+ }
352
+ console.log(pc5.dim(` ... and ${segmentFiles.length - 3} more`));
353
+ }
354
+ }
355
+ if (mdxFilesFound.length > 0) {
356
+ console.log(` \u{1F4C4} ${mdxFilesFound.length} documentation file(s) (*.mdx)`);
357
+ if (mdxFilesFound.length <= 5) {
358
+ for (const f of mdxFilesFound) {
359
+ console.log(pc5.dim(` ${relative(projectRoot, f)}`));
360
+ }
361
+ } else {
362
+ for (const f of mdxFilesFound.slice(0, 3)) {
363
+ console.log(pc5.dim(` ${relative(projectRoot, f)}`));
364
+ }
365
+ console.log(pc5.dim(` ... and ${mdxFilesFound.length - 3} more`));
366
+ }
367
+ }
368
+ for (const f of otherFiles) {
369
+ console.log(` \u{1F4C4} ${relative(projectRoot, f)}`);
370
+ }
371
+ const totalCount = filesToDelete.length + dirsToDelete.length;
372
+ console.log(pc5.dim(`
373
+ Total: ${totalCount} item(s)
374
+ `));
375
+ if (dryRun) {
376
+ console.log(pc5.yellow("[Dry run - no files were deleted]\n"));
377
+ return { success: true, deletedFiles: 0, deletedDirs: 0 };
378
+ }
379
+ let proceed = yes;
380
+ if (!proceed) {
381
+ const { confirm } = await import("@inquirer/prompts");
382
+ try {
383
+ proceed = await confirm({
384
+ message: `Delete ${totalCount} item(s)?`,
385
+ default: false
386
+ });
387
+ } catch {
388
+ proceed = false;
389
+ }
390
+ }
391
+ if (!proceed) {
392
+ console.log(pc5.dim("\nNo changes made.\n"));
393
+ return { success: true, deletedFiles: 0, deletedDirs: 0 };
394
+ }
395
+ console.log();
396
+ let deletedDirs = 0;
397
+ for (const dir of dirsToDelete) {
398
+ try {
399
+ const relativePath = relative(projectRoot, dir);
400
+ await rm(dir, { recursive: true, force: true });
401
+ console.log(` ${pc5.green("\u2713")} Deleted ${relativePath}/`);
402
+ deletedDirs++;
403
+ } catch {
404
+ console.log(` ${pc5.red("\u2717")} Failed: ${relative(projectRoot, dir)}`);
405
+ }
406
+ }
407
+ let deletedCount = 0;
408
+ let failedCount = 0;
409
+ for (const file of filesToDelete) {
410
+ try {
411
+ await unlink(file);
412
+ deletedCount++;
413
+ } catch {
414
+ failedCount++;
415
+ }
416
+ }
417
+ if (deletedCount > 0) {
418
+ console.log(` ${pc5.green("\u2713")} Deleted ${deletedCount} file(s)`);
419
+ }
420
+ if (failedCount > 0) {
421
+ console.log(` ${pc5.red("\u2717")} Failed to delete ${failedCount} file(s)`);
422
+ }
423
+ console.log(pc5.green(`
424
+ \u2713 Reset complete
425
+ `));
426
+ console.log(pc5.dim(`Config file retained: ${BRAND.configFile}`));
427
+ console.log(pc5.dim(`Run ${pc5.cyan(`${BRAND.cliCommand} init`)} to start fresh
428
+ `));
429
+ return {
430
+ success: true,
431
+ deletedFiles: deletedCount,
432
+ deletedDirs
433
+ };
434
+ }
435
+
436
+ // src/commands/dev.ts
437
+ import pc7 from "picocolors";
438
+
439
+ // src/setup.ts
440
+ import pc6 from "picocolors";
441
+
442
+ // src/migrate/migrate.ts
443
+ import { writeFile, mkdir, access, stat as stat2 } from "fs/promises";
444
+ import { join as join2, dirname, relative as relative2 } from "path";
445
+ import fg2 from "fast-glob";
446
+
447
+ // src/migrate/parser.ts
448
+ import { readFile as readFile2 } from "fs/promises";
449
+ async function parseStoryFile(filePath) {
450
+ const content = await readFile2(filePath, "utf-8");
451
+ return parseStoryContent(content, filePath);
452
+ }
453
+ function parseStoryContent(content, filePath) {
454
+ const warnings = [];
455
+ const meta = parseMeta(content, filePath, warnings);
456
+ const argTypes = parseArgTypes(content, warnings);
457
+ const constDeclarations = extractConstDeclarations(content);
458
+ const stories = parseStories(content, meta.componentName, warnings, constDeclarations);
459
+ const confidence = calculateConfidence(meta, argTypes, stories, warnings);
460
+ return {
461
+ filePath,
462
+ meta,
463
+ argTypes,
464
+ stories,
465
+ warnings,
466
+ confidence
467
+ };
468
+ }
469
+ function calculateConfidence(meta, argTypes, stories, warnings) {
470
+ let score = 1;
471
+ score -= warnings.length * 0.1;
472
+ if (meta.componentName === "Unknown") {
473
+ score -= 0.3;
474
+ }
475
+ if (!meta.componentImport) {
476
+ score -= 0.1;
477
+ }
478
+ if (Object.keys(argTypes).length === 0) {
479
+ score -= 0.1;
480
+ }
481
+ if (stories.length === 0) {
482
+ score -= 0.3;
483
+ }
484
+ const customRenderCount = stories.filter((s) => s.hasCustomRender).length;
485
+ if (customRenderCount > 0) {
486
+ score -= customRenderCount / Math.max(stories.length, 1) * 0.2;
487
+ }
488
+ return Math.max(0, Math.min(1, score));
489
+ }
490
+ function extractConstDeclarations(content) {
491
+ const declarations = /* @__PURE__ */ new Map();
492
+ const constPattern = /const\s+(\w+)(?:\s*:\s*[^=]+)?\s*=\s*\{/g;
493
+ let match;
494
+ while ((match = constPattern.exec(content)) !== null) {
495
+ const varName = match[1];
496
+ if (varName === "meta" || varName === "default" || varName === "Template") {
497
+ continue;
498
+ }
499
+ const braceStart = match.index + match[0].length - 1;
500
+ const braceEnd = findMatchingBraceInContent(content, braceStart, "{", "}");
501
+ if (braceEnd === -1) {
502
+ continue;
503
+ }
504
+ const objectContent = content.slice(braceStart + 1, braceEnd);
505
+ const parsed = parseArgsSimple(objectContent);
506
+ if (Object.keys(parsed).length > 0) {
507
+ declarations.set(varName, parsed);
508
+ }
509
+ }
510
+ return declarations;
511
+ }
512
+ function parseArgsSimple(content) {
513
+ const args = {};
514
+ const pairs = splitAtTopLevelCommas(content);
515
+ for (const pair of pairs) {
516
+ const trimmed = pair.trim();
517
+ if (!trimmed) continue;
518
+ if (trimmed.startsWith("...")) {
519
+ continue;
520
+ }
521
+ if (/^\w+$/.test(trimmed)) {
522
+ args[trimmed] = `__REF__${trimmed}`;
523
+ continue;
524
+ }
525
+ const colonIndex = trimmed.indexOf(":");
526
+ if (colonIndex > 0) {
527
+ const key = trimmed.slice(0, colonIndex).trim();
528
+ const valueStr = trimmed.slice(colonIndex + 1).trim();
529
+ if (/^\w+$/.test(key)) {
530
+ args[key] = parseArgValue(valueStr);
531
+ }
532
+ }
533
+ }
534
+ return args;
535
+ }
536
+ function parseMeta(content, filePath, warnings) {
537
+ const result = {
538
+ title: "",
539
+ componentName: ""
540
+ };
541
+ const titleRegex = /title:\s*['"`]([^'"`]+)['"`]/g;
542
+ const titleMatches = [];
543
+ let match;
544
+ while ((match = titleRegex.exec(content)) !== null) {
545
+ titleMatches.push(match[1]);
546
+ }
547
+ const componentPathTitle = titleMatches.find((t) => t.includes("/"));
548
+ const selectedTitle = componentPathTitle ?? titleMatches[0];
549
+ if (selectedTitle) {
550
+ result.title = selectedTitle;
551
+ const segments = result.title.split("/");
552
+ result.componentName = segments[segments.length - 1];
553
+ }
554
+ const componentMatch = content.match(/component:\s*(\w+)/);
555
+ if (componentMatch) {
556
+ if (!result.componentName) {
557
+ result.componentName = componentMatch[1];
558
+ }
559
+ const importMatch = content.match(
560
+ new RegExp(
561
+ `import\\s*{[^}]*\\b${componentMatch[1]}\\b[^}]*}\\s*from\\s*['"\`]([^'"\`]+)['"\`]`
562
+ )
563
+ );
564
+ if (importMatch) {
565
+ result.componentImport = importMatch[1];
566
+ } else {
567
+ const defaultImportMatch = content.match(
568
+ new RegExp(
569
+ `import\\s+${componentMatch[1]}\\s+from\\s*['"\`]([^'"\`]+)['"\`]`
570
+ )
571
+ );
572
+ if (defaultImportMatch) {
573
+ result.componentImport = defaultImportMatch[1];
574
+ }
575
+ }
576
+ }
577
+ const tagsMatch = content.match(/tags:\s*\[([^\]]+)\]/);
578
+ if (tagsMatch) {
579
+ const tagsContent = tagsMatch[1];
580
+ result.tags = tagsContent.split(",").map((t) => t.trim().replace(/['"`]/g, "")).filter(Boolean);
581
+ }
582
+ const descMatch = content.match(
583
+ /description:\s*\{[^}]*component:\s*['"`]([^'"`]+)['"`]/
584
+ );
585
+ if (descMatch) {
586
+ result.description = descMatch[1];
587
+ }
588
+ if (!result.componentName) {
589
+ const match2 = filePath.match(/([^/\\]+)\.stories\.(tsx?|jsx?|mdx)$/);
590
+ if (match2) {
591
+ result.componentName = match2[1];
592
+ } else {
593
+ result.componentName = "Unknown";
594
+ warnings.push("Could not determine component name");
595
+ }
596
+ }
597
+ if (!result.title) {
598
+ result.title = `Components/${result.componentName}`;
599
+ warnings.push(`No title found, using default: ${result.title}`);
600
+ }
601
+ return result;
602
+ }
603
+ function parseArgTypes(content, warnings) {
604
+ const result = {};
605
+ const argTypesStart = content.indexOf("argTypes:");
606
+ if (argTypesStart === -1) {
607
+ return result;
608
+ }
609
+ const braceStart = content.indexOf("{", argTypesStart);
610
+ if (braceStart === -1) {
611
+ return result;
612
+ }
613
+ let depth = 1;
614
+ let braceEnd = braceStart + 1;
615
+ while (depth > 0 && braceEnd < content.length) {
616
+ if (content[braceEnd] === "{") depth++;
617
+ if (content[braceEnd] === "}") depth--;
618
+ braceEnd++;
619
+ }
620
+ const argTypesContent = content.slice(braceStart + 1, braceEnd - 1);
621
+ let pos = 0;
622
+ while (pos < argTypesContent.length) {
623
+ while (pos < argTypesContent.length && /[\s,]/.test(argTypesContent[pos])) {
624
+ pos++;
625
+ }
626
+ const nameMatch = argTypesContent.slice(pos).match(/^(\w+)\s*:\s*\{/);
627
+ if (!nameMatch) break;
628
+ const propName = nameMatch[1];
629
+ pos += nameMatch[0].length - 1;
630
+ let propDepth = 1;
631
+ const propStart = pos + 1;
632
+ pos++;
633
+ while (propDepth > 0 && pos < argTypesContent.length) {
634
+ if (argTypesContent[pos] === "{") propDepth++;
635
+ if (argTypesContent[pos] === "}") propDepth--;
636
+ pos++;
637
+ }
638
+ const propContent = argTypesContent.slice(propStart, pos - 1);
639
+ result[propName] = parseArgTypeContent(propContent, warnings);
640
+ }
641
+ return result;
642
+ }
643
+ function parseArgTypeContent(content, warnings) {
644
+ const result = {};
645
+ const controlMatch = content.match(/control:\s*['"`](\w+)['"`]/);
646
+ if (controlMatch) {
647
+ result.control = controlMatch[1];
648
+ } else {
649
+ const controlTypeMatch = content.match(/control:\s*\{[^}]*type:\s*['"`](\w+)['"`]/);
650
+ if (controlTypeMatch) {
651
+ result.control = controlTypeMatch[1];
652
+ }
653
+ }
654
+ const optionsMatch = content.match(/options:\s*\[([^\]]+)\]/);
655
+ if (optionsMatch) {
656
+ result.options = optionsMatch[1].split(",").map((o) => o.trim().replace(/['"`]/g, "")).filter(Boolean);
657
+ }
658
+ const descMatch = content.match(/description:\s*['"`]([^'"`]+)['"`]/);
659
+ if (descMatch) {
660
+ result.description = descMatch[1];
661
+ }
662
+ const defaultMatch = content.match(/defaultValue:\s*\{[^}]*summary:\s*['"`]([^'"`]+)['"`]/);
663
+ if (defaultMatch) {
664
+ result.defaultValue = defaultMatch[1];
665
+ } else {
666
+ const simpleDefaultMatch = content.match(/defaultValue:\s*['"`]?([^,\s'"`]+)['"`]?/);
667
+ if (simpleDefaultMatch && simpleDefaultMatch[1] !== "{") {
668
+ result.defaultValue = simpleDefaultMatch[1];
669
+ }
670
+ }
671
+ return result;
672
+ }
673
+ function parseStories(content, componentName, warnings, constDeclarations) {
674
+ const stories = [];
675
+ const storyNames = /* @__PURE__ */ new Set();
676
+ const csf3Pattern = /export\s+const\s+(\w+)(?::\s*\w+)?\s*=\s*\{([^;]*(?:\{[^}]*\}[^;]*)*)\}/g;
677
+ let match;
678
+ while ((match = csf3Pattern.exec(content)) !== null) {
679
+ const storyName = match[1];
680
+ const storyContent = match[2];
681
+ if (storyName === "default" || storyName === "meta") {
682
+ continue;
683
+ }
684
+ if (storyContent.includes("typeof")) {
685
+ continue;
686
+ }
687
+ const story = parseStoryContent2(storyName, storyContent, componentName, warnings, constDeclarations);
688
+ if (story) {
689
+ stories.push(story);
690
+ storyNames.add(storyName);
691
+ }
692
+ }
693
+ const csf2Pattern = /export\s+const\s+(\w+)\s*=\s*(\w+)\.bind\s*\(\s*\{\s*\}\s*\)/g;
694
+ while ((match = csf2Pattern.exec(content)) !== null) {
695
+ const storyName = match[1];
696
+ const templateName = match[2];
697
+ if (storyNames.has(storyName)) {
698
+ continue;
699
+ }
700
+ if (storyName === "default" || storyName === "meta") {
701
+ continue;
702
+ }
703
+ const argsContent = extractArgsContent(content, storyName);
704
+ const story = {
705
+ name: storyName,
706
+ args: argsContent ? parseArgs(argsContent, constDeclarations) : {},
707
+ hasCustomRender: isCustomTemplate(content, templateName)
708
+ };
709
+ const descPattern = new RegExp(
710
+ `${storyName}\\.parameters\\s*=\\s*\\{[^}]*docs:\\s*\\{[^}]*description:\\s*\\{[^}]*story:\\s*['"\`]([^'"\`]+)['"\`]`
711
+ );
712
+ const descMatch = content.match(descPattern);
713
+ if (descMatch) {
714
+ story.description = descMatch[1];
715
+ }
716
+ stories.push(story);
717
+ storyNames.add(storyName);
718
+ }
719
+ return stories;
720
+ }
721
+ function extractArgsContent(content, storyName) {
722
+ const argsAssignPattern = new RegExp(`${storyName}\\.args\\s*=\\s*\\{`);
723
+ const argsMatch = content.match(argsAssignPattern);
724
+ if (!argsMatch || argsMatch.index === void 0) {
725
+ return null;
726
+ }
727
+ const startPos = argsMatch.index + argsMatch[0].length - 1;
728
+ const closingIndex = findMatchingBraceInContent(content, startPos, "{", "}");
729
+ if (closingIndex === -1) {
730
+ return null;
731
+ }
732
+ return content.slice(startPos + 1, closingIndex);
733
+ }
734
+ function findMatchingBraceInContent(content, startIndex, openChar, closeChar) {
735
+ let depth = 0;
736
+ let inString = null;
737
+ for (let i = startIndex; i < content.length; i++) {
738
+ const char = content[i];
739
+ const prevChar = i > 0 ? content[i - 1] : "";
740
+ if ((char === '"' || char === "'" || char === "`") && prevChar !== "\\") {
741
+ if (inString === char) {
742
+ inString = null;
743
+ } else if (inString === null) {
744
+ inString = char;
745
+ }
746
+ }
747
+ if (inString === null) {
748
+ if (char === openChar) {
749
+ depth++;
750
+ } else if (char === closeChar) {
751
+ depth--;
752
+ if (depth === 0) {
753
+ return i;
754
+ }
755
+ }
756
+ }
757
+ }
758
+ return -1;
759
+ }
760
+ function isCustomTemplate(content, templateName) {
761
+ const templatePattern = new RegExp(
762
+ `const\\s+${templateName}[^=]*=\\s*\\([^)]*\\)\\s*=>\\s*([\\s\\S]*?)(?=\\n(?:export|const\\s+\\w+\\s*=)|$)`,
763
+ "m"
764
+ );
765
+ const match = content.match(templatePattern);
766
+ if (!match) {
767
+ return false;
768
+ }
769
+ const templateBody = match[1].trim();
770
+ const simplePatterns = [
771
+ /^<\w+\s+\{\.\.\.args\}\s*\/?>/,
772
+ // <Comp {...args} /> or <Comp {...args}>
773
+ /^\(\s*<\w+\s+\{\.\.\.args\}\s*\/?>\s*\)/
774
+ // (<Comp {...args} />)
775
+ ];
776
+ for (const pattern of simplePatterns) {
777
+ if (pattern.test(templateBody)) {
778
+ return false;
779
+ }
780
+ }
781
+ const customIndicators = [
782
+ "useState",
783
+ "useEffect",
784
+ "useRef",
785
+ "useCallback",
786
+ "useMemo",
787
+ "useContext",
788
+ "return ("
789
+ // Multi-line return with logic
790
+ ];
791
+ for (const indicator of customIndicators) {
792
+ if (templateBody.includes(indicator)) {
793
+ return true;
794
+ }
795
+ }
796
+ const bodyWithoutFirstLine = templateBody.split("\n").slice(1).join("\n");
797
+ if (/const\s+\w+\s*=/.test(bodyWithoutFirstLine)) {
798
+ return true;
799
+ }
800
+ if (templateBody.includes("{...args}") && !templateBody.includes("return")) {
801
+ return false;
802
+ }
803
+ return false;
804
+ }
805
+ function parseStoryContent2(name, content, componentName, warnings, constDeclarations) {
806
+ const result = {
807
+ name,
808
+ args: {}
809
+ };
810
+ const argsMatch = content.match(/args:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/);
811
+ if (argsMatch) {
812
+ result.args = parseArgs(argsMatch[1], constDeclarations);
813
+ }
814
+ if (content.includes("render:") || content.includes("render(")) {
815
+ result.hasCustomRender = true;
816
+ }
817
+ const descMatch = content.match(/parameters:\s*\{[^}]*docs:\s*\{[^}]*description:\s*\{[^}]*story:\s*['"`]([^'"`]+)['"`]/);
818
+ if (descMatch) {
819
+ result.description = descMatch[1];
820
+ }
821
+ return result;
822
+ }
823
+ function parseArgs(content, constDeclarations) {
824
+ const args = {};
825
+ const pairs = splitAtTopLevelCommas(content);
826
+ for (const pair of pairs) {
827
+ const trimmed = pair.trim();
828
+ if (!trimmed) continue;
829
+ if (trimmed.startsWith("...")) {
830
+ const spreadValue = trimmed.slice(3).trim();
831
+ if (constDeclarations && /^\w+$/.test(spreadValue)) {
832
+ const resolved = constDeclarations.get(spreadValue);
833
+ if (resolved) {
834
+ for (const [key, value] of Object.entries(resolved)) {
835
+ if (!(key in args)) {
836
+ args[key] = value;
837
+ }
838
+ }
839
+ continue;
840
+ }
841
+ }
842
+ args["__SPREAD__"] = `__REF__${spreadValue}`;
843
+ continue;
844
+ }
845
+ if (/^\w+$/.test(trimmed)) {
846
+ args[trimmed] = `__REF__${trimmed}`;
847
+ continue;
848
+ }
849
+ const colonIndex = trimmed.indexOf(":");
850
+ if (colonIndex > 0) {
851
+ const key = trimmed.slice(0, colonIndex).trim();
852
+ const valueStr = trimmed.slice(colonIndex + 1).trim();
853
+ if (/^\w+$/.test(key)) {
854
+ args[key] = parseArgValue(valueStr, constDeclarations);
855
+ }
856
+ }
857
+ }
858
+ return args;
859
+ }
860
+ function splitAtTopLevelCommas(content) {
861
+ const parts = [];
862
+ let current = "";
863
+ let depth = 0;
864
+ let inString = null;
865
+ for (let i = 0; i < content.length; i++) {
866
+ const char = content[i];
867
+ const prevChar = i > 0 ? content[i - 1] : "";
868
+ if ((char === '"' || char === "'" || char === "`") && prevChar !== "\\") {
869
+ if (inString === char) {
870
+ inString = null;
871
+ } else if (inString === null) {
872
+ inString = char;
873
+ }
874
+ }
875
+ if (inString === null) {
876
+ if (char === "{" || char === "[" || char === "(") {
877
+ depth++;
878
+ } else if (char === "}" || char === "]" || char === ")") {
879
+ depth--;
880
+ } else if (char === "," && depth === 0) {
881
+ parts.push(current);
882
+ current = "";
883
+ continue;
884
+ }
885
+ }
886
+ current += char;
887
+ }
888
+ if (current.trim()) {
889
+ parts.push(current);
890
+ }
891
+ return parts;
892
+ }
893
+ function resolveEscapeSequences(str) {
894
+ let result = "";
895
+ let i = 0;
896
+ while (i < str.length) {
897
+ if (str[i] === "\\" && i + 1 < str.length) {
898
+ const next = str[i + 1];
899
+ switch (next) {
900
+ case "n":
901
+ result += "\n";
902
+ i += 2;
903
+ break;
904
+ case "t":
905
+ result += " ";
906
+ i += 2;
907
+ break;
908
+ case "r":
909
+ result += "\r";
910
+ i += 2;
911
+ break;
912
+ case "\\":
913
+ result += "\\";
914
+ i += 2;
915
+ break;
916
+ case '"':
917
+ result += '"';
918
+ i += 2;
919
+ break;
920
+ case "'":
921
+ result += "'";
922
+ i += 2;
923
+ break;
924
+ case "`":
925
+ result += "`";
926
+ i += 2;
927
+ break;
928
+ default:
929
+ result += str[i];
930
+ i += 1;
931
+ }
932
+ } else {
933
+ result += str[i];
934
+ i += 1;
935
+ }
936
+ }
937
+ return result;
938
+ }
939
+ function tryParseStringConcatenation(value) {
940
+ if (!value.includes("+")) {
941
+ return null;
942
+ }
943
+ const parts = [];
944
+ let current = "";
945
+ let inString = null;
946
+ for (let i = 0; i < value.length; i++) {
947
+ const char = value[i];
948
+ const prevChar = i > 0 ? value[i - 1] : "";
949
+ if ((char === '"' || char === "'" || char === "`") && prevChar !== "\\") {
950
+ if (inString === char) {
951
+ inString = null;
952
+ } else if (inString === null) {
953
+ inString = char;
954
+ }
955
+ }
956
+ if (char === "+" && inString === null) {
957
+ const trimmed = current.trim();
958
+ if (trimmed) {
959
+ parts.push(trimmed);
960
+ }
961
+ current = "";
962
+ continue;
963
+ }
964
+ current += char;
965
+ }
966
+ const lastTrimmed = current.trim();
967
+ if (lastTrimmed) {
968
+ parts.push(lastTrimmed);
969
+ }
970
+ if (parts.length < 2) {
971
+ return null;
972
+ }
973
+ const stringParts = [];
974
+ for (const part of parts) {
975
+ if (part.startsWith('"') && part.endsWith('"') || part.startsWith("'") && part.endsWith("'")) {
976
+ stringParts.push(resolveEscapeSequences(part.slice(1, -1)));
977
+ } else if (part.startsWith("`") && part.endsWith("`")) {
978
+ if (!part.includes("${")) {
979
+ stringParts.push(resolveEscapeSequences(part.slice(1, -1)));
980
+ } else {
981
+ return null;
982
+ }
983
+ } else {
984
+ return null;
985
+ }
986
+ }
987
+ return stringParts.join("");
988
+ }
989
+ function parseArgValue(value, constDeclarations) {
990
+ value = value.trim();
991
+ value = value.replace(/,\s*$/, "");
992
+ if (!value) return void 0;
993
+ const concatenationResult = tryParseStringConcatenation(value);
994
+ if (concatenationResult !== null) {
995
+ return concatenationResult;
996
+ }
997
+ const asConstMatch = value.match(/^(['"`])(.+?)\1\s+as\s+const$/);
998
+ if (asConstMatch) {
999
+ return resolveEscapeSequences(asConstMatch[2]);
1000
+ }
1001
+ if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) {
1002
+ return resolveEscapeSequences(value.slice(1, -1));
1003
+ }
1004
+ if (value.startsWith("`") && value.endsWith("`")) {
1005
+ return resolveEscapeSequences(value.slice(1, -1));
1006
+ }
1007
+ if (value === "true") return true;
1008
+ if (value === "false") return false;
1009
+ if (/^-?\d+(\.\d+)?$/.test(value)) {
1010
+ return parseFloat(value);
1011
+ }
1012
+ if (value === "null") return null;
1013
+ if (value === "undefined") return void 0;
1014
+ if (value.startsWith("{")) {
1015
+ const closingIndex = findMatchingBrace(value, 0, "{", "}");
1016
+ if (closingIndex !== -1) {
1017
+ const inner = value.slice(1, closingIndex).trim();
1018
+ if (inner) {
1019
+ return parseArgs(inner, constDeclarations);
1020
+ }
1021
+ return {};
1022
+ }
1023
+ }
1024
+ if (value.startsWith("[")) {
1025
+ const closingIndex = findMatchingBrace(value, 0, "[", "]");
1026
+ if (closingIndex !== -1) {
1027
+ const inner = value.slice(1, closingIndex).trim();
1028
+ if (!inner) return [];
1029
+ const items = splitAtTopLevelCommas(inner);
1030
+ return items.map((item) => parseArgValue(item.trim(), constDeclarations)).filter((v) => v !== void 0);
1031
+ }
1032
+ }
1033
+ if (value.startsWith("<")) {
1034
+ const trimmed = value.trimEnd();
1035
+ if (trimmed.endsWith("/>") || trimmed.endsWith(">")) {
1036
+ return `__JSX__`;
1037
+ }
1038
+ }
1039
+ if (value.includes("(") || value.includes("=>")) {
1040
+ return `__EXPR__`;
1041
+ }
1042
+ if (/\s+as\s+[A-Z]/.test(value)) {
1043
+ return `__EXPR__`;
1044
+ }
1045
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(value)) {
1046
+ if (constDeclarations) {
1047
+ const resolved = constDeclarations.get(value);
1048
+ if (resolved !== void 0) {
1049
+ return resolved;
1050
+ }
1051
+ }
1052
+ return `__REF__${value}`;
1053
+ }
1054
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)+$/.test(value)) {
1055
+ if (constDeclarations) {
1056
+ const parts = value.replace(/\?/g, "").split(".");
1057
+ const baseVar = parts[0];
1058
+ const resolved = constDeclarations.get(baseVar);
1059
+ if (resolved !== void 0) {
1060
+ let current = resolved;
1061
+ for (let i = 1; i < parts.length && current !== void 0; i++) {
1062
+ if (typeof current === "object" && current !== null) {
1063
+ current = current[parts[i]];
1064
+ } else {
1065
+ current = void 0;
1066
+ }
1067
+ }
1068
+ if (current !== void 0) {
1069
+ return current;
1070
+ }
1071
+ }
1072
+ }
1073
+ return `__REF__${value}`;
1074
+ }
1075
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*(\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)*\[\d+\]!?(\??\.[a-zA-Z_$][a-zA-Z0-9_$]*)*$/.test(value)) {
1076
+ return `__REF__${value}`;
1077
+ }
1078
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*!$/.test(value)) {
1079
+ return `__REF__${value}`;
1080
+ }
1081
+ return value;
1082
+ }
1083
+ function findMatchingBrace(content, startIndex, openChar, closeChar) {
1084
+ let depth = 0;
1085
+ let inString = null;
1086
+ for (let i = startIndex; i < content.length; i++) {
1087
+ const char = content[i];
1088
+ const prevChar = i > 0 ? content[i - 1] : "";
1089
+ if ((char === '"' || char === "'" || char === "`") && prevChar !== "\\") {
1090
+ if (inString === char) {
1091
+ inString = null;
1092
+ } else if (inString === null) {
1093
+ inString = char;
1094
+ }
1095
+ }
1096
+ if (inString === null) {
1097
+ if (char === openChar) {
1098
+ depth++;
1099
+ } else if (char === closeChar) {
1100
+ depth--;
1101
+ if (depth === 0) {
1102
+ return i;
1103
+ }
1104
+ }
1105
+ }
1106
+ }
1107
+ return -1;
1108
+ }
1109
+ function storyNameToTitle(name) {
1110
+ return name.replace(/([A-Z])/g, " $1").trim().replace(/\s+/g, " ");
1111
+ }
1112
+ function extractCategory(title) {
1113
+ const segments = title.split("/");
1114
+ if (segments.length >= 2) {
1115
+ const category = segments[segments.length - 2];
1116
+ return category.toLowerCase();
1117
+ }
1118
+ return "components";
1119
+ }
1120
+
1121
+ // src/migrate/converter.ts
1122
+ function sanitizeComponentName(name) {
1123
+ return name.split(/[\s-_]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("").replace(/[^a-zA-Z0-9]/g, "");
1124
+ }
1125
+ function convertToSegment(parsed) {
1126
+ const warnings = [...parsed.warnings];
1127
+ const todos = [];
1128
+ const category = extractCategory(parsed.meta.title);
1129
+ const componentName = sanitizeComponentName(parsed.meta.componentName);
1130
+ if (!parsed.meta.componentImport) {
1131
+ warnings.push(`No importable component found - story may define component locally`);
1132
+ const outputFile2 = parsed.filePath.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".segment.tsx");
1133
+ return {
1134
+ sourceFile: parsed.filePath,
1135
+ outputFile: outputFile2,
1136
+ code: "",
1137
+ componentName,
1138
+ category,
1139
+ variantCount: 0,
1140
+ propCount: 0,
1141
+ confidence: 0,
1142
+ todos: [],
1143
+ warnings,
1144
+ success: false
1145
+ };
1146
+ }
1147
+ const props = convertArgTypesToProps(parsed.argTypes);
1148
+ const variants = convertStoriesToVariants(parsed, componentName);
1149
+ todos.push("Add usage.when - scenarios where this component is appropriate");
1150
+ todos.push("Add usage.whenNot - scenarios where alternatives should be used");
1151
+ todos.push("Add usage.guidelines - best practices");
1152
+ todos.push("Add relations - related components");
1153
+ if (Object.keys(props).length > 0) {
1154
+ const propsWithoutConstraints = Object.entries(props).filter(([, p]) => !p.constraints?.length).map(([name]) => name);
1155
+ if (propsWithoutConstraints.length > 0) {
1156
+ todos.push(`Add constraints for props: ${propsWithoutConstraints.slice(0, 3).join(", ")}${propsWithoutConstraints.length > 3 ? "..." : ""}`);
1157
+ }
1158
+ }
1159
+ const skippedVariants = variants.filter((v) => v.needsManualReview && v.skipReason).map((v) => ({ name: v.name, reason: v.skipReason }));
1160
+ const code = generateSegmentCode({
1161
+ componentName,
1162
+ componentImport: parsed.meta.componentImport,
1163
+ description: parsed.meta.description,
1164
+ category,
1165
+ tags: parsed.meta.tags,
1166
+ props,
1167
+ variants,
1168
+ todos,
1169
+ generated: {
1170
+ source: "storybook",
1171
+ sourceFile: parsed.filePath,
1172
+ confidence: parsed.confidence,
1173
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1174
+ skippedVariants: skippedVariants.length > 0 ? skippedVariants : void 0
1175
+ }
1176
+ });
1177
+ const outputFile = parsed.filePath.replace(/\.stories\.(tsx?|jsx?|mdx)$/, ".segment.tsx");
1178
+ return {
1179
+ sourceFile: parsed.filePath,
1180
+ outputFile,
1181
+ code,
1182
+ componentName,
1183
+ category,
1184
+ variantCount: variants.length,
1185
+ propCount: Object.keys(props).length,
1186
+ confidence: parsed.confidence,
1187
+ todos,
1188
+ warnings,
1189
+ success: true
1190
+ };
1191
+ }
1192
+ function convertArgTypesToProps(argTypes) {
1193
+ const props = {};
1194
+ for (const [name, argType] of Object.entries(argTypes)) {
1195
+ props[name] = convertArgType(name, argType);
1196
+ }
1197
+ return props;
1198
+ }
1199
+ function convertArgType(name, argType) {
1200
+ const prop = {
1201
+ type: inferPropType(argType)
1202
+ };
1203
+ if (argType.description) {
1204
+ prop.description = argType.description;
1205
+ }
1206
+ if (argType.defaultValue !== void 0) {
1207
+ prop.default = argType.defaultValue;
1208
+ }
1209
+ if (argType.required) {
1210
+ prop.required = true;
1211
+ }
1212
+ if (argType.options && argType.options.length > 0) {
1213
+ prop.type = "enum";
1214
+ prop.values = argType.options;
1215
+ }
1216
+ return prop;
1217
+ }
1218
+ function inferPropType(argType) {
1219
+ if (argType.type) {
1220
+ const typeMap = {
1221
+ string: "string",
1222
+ number: "number",
1223
+ boolean: "boolean",
1224
+ object: "object",
1225
+ array: "array",
1226
+ function: "function"
1227
+ };
1228
+ return typeMap[argType.type.toLowerCase()] ?? "custom";
1229
+ }
1230
+ if (argType.control) {
1231
+ const controlMap = {
1232
+ text: "string",
1233
+ number: "number",
1234
+ boolean: "boolean",
1235
+ select: "enum",
1236
+ radio: "enum",
1237
+ "inline-radio": "enum",
1238
+ check: "boolean",
1239
+ "inline-check": "boolean",
1240
+ range: "number",
1241
+ object: "object",
1242
+ array: "array",
1243
+ date: "string",
1244
+ color: "string"
1245
+ };
1246
+ return controlMap[argType.control] ?? "custom";
1247
+ }
1248
+ if (argType.options && argType.options.length > 0) {
1249
+ return "enum";
1250
+ }
1251
+ return "custom";
1252
+ }
1253
+ function convertStoriesToVariants(parsed, componentName) {
1254
+ return parsed.stories.map((story) => {
1255
+ const hasCustomRender = story.hasCustomRender === true;
1256
+ const unrenderableReason = getUnrenderableReason(story.args);
1257
+ const needsManualReview = hasCustomRender || unrenderableReason !== null;
1258
+ let skipReason;
1259
+ if (hasCustomRender) {
1260
+ skipReason = "uses custom render function";
1261
+ } else if (unrenderableReason) {
1262
+ skipReason = unrenderableReason;
1263
+ }
1264
+ const renderCode = hasCustomRender ? `<${componentName} />` : generateRenderCode(componentName, story.args);
1265
+ const description = story.description ?? `${storyNameToTitle(story.name)} variant`;
1266
+ return {
1267
+ name: storyNameToTitle(story.name),
1268
+ description,
1269
+ renderCode,
1270
+ needsManualReview,
1271
+ skipReason
1272
+ };
1273
+ });
1274
+ }
1275
+ function getUnrenderableReason(args, path = "") {
1276
+ for (const [key, value] of Object.entries(args)) {
1277
+ const currentPath = path ? `${path}.${key}` : key;
1278
+ if (typeof value === "string") {
1279
+ if (value === "__JSX__") {
1280
+ return `JSX element in prop "${currentPath}"`;
1281
+ }
1282
+ if (value === "__EXPR__") {
1283
+ return `expression in prop "${currentPath}"`;
1284
+ }
1285
+ if (value.startsWith("__REF__")) {
1286
+ const ref = value.slice(7);
1287
+ return `variable reference "${ref}" in prop "${currentPath}"`;
1288
+ }
1289
+ if (value === "__SPREAD__" || key === "__SPREAD__") {
1290
+ return `spread syntax in args`;
1291
+ }
1292
+ }
1293
+ if (typeof value === "object" && value !== null) {
1294
+ const nestedReason = getUnrenderableReason(value, currentPath);
1295
+ if (nestedReason) {
1296
+ return nestedReason;
1297
+ }
1298
+ }
1299
+ }
1300
+ return null;
1301
+ }
1302
+ function generateRenderCode(componentName, args) {
1303
+ const entries = Object.entries(args);
1304
+ if (entries.length === 0) {
1305
+ return `<${componentName} />`;
1306
+ }
1307
+ const children = args.children;
1308
+ const otherArgs = Object.entries(args).filter(([k]) => k !== "children");
1309
+ const propsString = otherArgs.map(([key, value]) => formatPropValue(key, value)).filter(Boolean).join(" ");
1310
+ if (children !== void 0) {
1311
+ const childrenStr = formatChildrenValue(children);
1312
+ return propsString ? `<${componentName} ${propsString}>${childrenStr}</${componentName}>` : `<${componentName}>${childrenStr}</${componentName}>`;
1313
+ }
1314
+ return propsString ? `<${componentName} ${propsString} />` : `<${componentName} />`;
1315
+ }
1316
+ function formatChildrenValue(value) {
1317
+ if (typeof value === "string") {
1318
+ if (value === "__JSX__") return "{null /* JSX children */}";
1319
+ if (value === "__EXPR__") return "{null /* expression */}";
1320
+ if (value.startsWith("__REF__")) return `{${value.slice(7)}}`;
1321
+ return value;
1322
+ }
1323
+ return String(value);
1324
+ }
1325
+ function formatPropValue(key, value) {
1326
+ if (value === void 0 || value === null) {
1327
+ return "";
1328
+ }
1329
+ if (typeof value === "string") {
1330
+ if (value === "__JSX__") {
1331
+ return `${key}={undefined /* JSX */}`;
1332
+ }
1333
+ if (value === "__EXPR__") {
1334
+ return `${key}={undefined /* expression */}`;
1335
+ }
1336
+ if (value.startsWith("__REF__")) {
1337
+ return `${key}={${value.slice(7)}}`;
1338
+ }
1339
+ if (value.includes('"') || value.includes("\\") || value.includes("\n")) {
1340
+ return `${key}={"${escapeString(value)}"}`;
1341
+ }
1342
+ return `${key}="${value}"`;
1343
+ }
1344
+ if (typeof value === "boolean") {
1345
+ return value ? key : `${key}={false}`;
1346
+ }
1347
+ if (typeof value === "number") {
1348
+ return `${key}={${value}}`;
1349
+ }
1350
+ if (Array.isArray(value)) {
1351
+ const formatted = formatArrayValue(value);
1352
+ return `${key}={${formatted}}`;
1353
+ }
1354
+ if (typeof value === "object" && value !== null) {
1355
+ const formatted = formatObjectValue(value);
1356
+ return `${key}={${formatted}}`;
1357
+ }
1358
+ return `${key}={${JSON.stringify(value)}}`;
1359
+ }
1360
+ function formatObjectValue(obj) {
1361
+ const entries = Object.entries(obj);
1362
+ if (entries.length === 0) return "{}";
1363
+ const props = entries.map(([k, v]) => {
1364
+ const formatted = formatValueForObject(v);
1365
+ return `${k}: ${formatted}`;
1366
+ }).join(", ");
1367
+ return `{ ${props} }`;
1368
+ }
1369
+ function formatArrayValue(arr) {
1370
+ if (arr.length === 0) return "[]";
1371
+ const items = arr.map((item) => formatValueForObject(item));
1372
+ return `[${items.join(", ")}]`;
1373
+ }
1374
+ function formatValueForObject(value) {
1375
+ if (value === void 0) return "undefined";
1376
+ if (value === null) return "null";
1377
+ if (typeof value === "string") {
1378
+ if (value === "__JSX__") return "undefined /* JSX */";
1379
+ if (value === "__EXPR__") return "undefined /* expression */";
1380
+ if (value.startsWith("__REF__")) return value.slice(7);
1381
+ return `"${escapeString(value)}"`;
1382
+ }
1383
+ if (typeof value === "boolean" || typeof value === "number") {
1384
+ return String(value);
1385
+ }
1386
+ if (Array.isArray(value)) {
1387
+ return formatArrayValue(value);
1388
+ }
1389
+ if (typeof value === "object") {
1390
+ return formatObjectValue(value);
1391
+ }
1392
+ return JSON.stringify(value);
1393
+ }
1394
+ function generateSegmentCode(options) {
1395
+ const {
1396
+ componentName,
1397
+ componentImport,
1398
+ description,
1399
+ category,
1400
+ tags,
1401
+ props,
1402
+ variants,
1403
+ todos,
1404
+ generated
1405
+ } = options;
1406
+ const propsCode = formatPropsCode(props);
1407
+ const variantsCode = formatVariantsCode(componentName, variants);
1408
+ const tagsCode = tags && tags.length > 0 ? `tags: [${tags.map((t) => `"${t}"`).join(", ")}],` : "";
1409
+ const todosComments = todos.length > 0 ? todos.map((t) => ` // TODO: ${t}`).join("\n") + "\n" : "";
1410
+ let generatedCode = "";
1411
+ if (generated) {
1412
+ const skippedCode = generated.skippedVariants && generated.skippedVariants.length > 0 ? `
1413
+ skippedVariants: [
1414
+ ${generated.skippedVariants.map((sv) => ` { name: "${escapeString(sv.name)}", reason: "${escapeString(sv.reason)}" },`).join("\n")}
1415
+ ],` : "";
1416
+ generatedCode = `
1417
+ _generated: {
1418
+ source: "${generated.source}",
1419
+ sourceFile: "${escapeString(generated.sourceFile)}",
1420
+ confidence: ${generated.confidence.toFixed(2)},
1421
+ timestamp: "${generated.timestamp}",${skippedCode}
1422
+ },
1423
+ `;
1424
+ }
1425
+ return `import { defineSegment } from "@fragments/core";
1426
+ import { ${componentName} } from "${componentImport}";
1427
+
1428
+ export default defineSegment({
1429
+ component: ${componentName},
1430
+
1431
+ meta: {
1432
+ name: "${componentName}",
1433
+ description: "${escapeString(description ?? `${componentName} component`)}",
1434
+ category: "${category}",
1435
+ ${tagsCode}
1436
+ // status: undefined, // TODO: Set to stable/beta/deprecated/experimental
1437
+ },
1438
+
1439
+ usage: {
1440
+ // TODO: Add specific use cases - when should developers use this component?
1441
+ when: [
1442
+ ${todosComments} ],
1443
+ // TODO: Add anti-patterns - when should developers NOT use this component?
1444
+ whenNot: [],
1445
+ },
1446
+
1447
+ ${propsCode}
1448
+
1449
+ relations: [
1450
+ // TODO: Add related components
1451
+ ],
1452
+
1453
+ ${variantsCode}
1454
+ ${generatedCode}});
1455
+ `;
1456
+ }
1457
+ function formatPropsCode(props) {
1458
+ if (Object.keys(props).length === 0) {
1459
+ return " props: {},";
1460
+ }
1461
+ const entries = Object.entries(props).map(([name, prop]) => {
1462
+ const lines = [];
1463
+ lines.push(` ${name}: {`);
1464
+ lines.push(` type: "${prop.type}",`);
1465
+ if (prop.values && prop.values.length > 0) {
1466
+ lines.push(` values: [${prop.values.map((v) => `"${v}"`).join(", ")}],`);
1467
+ }
1468
+ if (prop.default !== void 0) {
1469
+ const defaultVal = typeof prop.default === "string" ? `"${prop.default}"` : String(prop.default);
1470
+ lines.push(` default: ${defaultVal},`);
1471
+ }
1472
+ if (prop.required) {
1473
+ lines.push(` required: true,`);
1474
+ }
1475
+ if (prop.description) {
1476
+ lines.push(` description: "${escapeString(prop.description)}",`);
1477
+ }
1478
+ lines.push(` },`);
1479
+ return lines.join("\n");
1480
+ });
1481
+ return ` props: {
1482
+ ${entries.join("\n")}
1483
+ },`;
1484
+ }
1485
+ function formatVariantsCode(componentName, variants) {
1486
+ const renderableVariants = variants.filter((v) => !v.needsManualReview);
1487
+ if (renderableVariants.length === 0) {
1488
+ return " variants: [],";
1489
+ }
1490
+ const entries = renderableVariants.map((variant) => {
1491
+ return ` {
1492
+ name: "${variant.name}",
1493
+ description: "${escapeString(variant.description)}",
1494
+ render: () => ${variant.renderCode},
1495
+ },`;
1496
+ });
1497
+ return ` variants: [
1498
+ ${entries.join("\n")}
1499
+ ],`;
1500
+ }
1501
+ function escapeString(str) {
1502
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
1503
+ }
1504
+
1505
+ // src/migrate/detect.ts
1506
+ import { existsSync } from "fs";
1507
+ import { readFile as readFile3 } from "fs/promises";
1508
+ import { join as join3 } from "path";
1509
+ import fg3 from "fast-glob";
1510
+ var CONFIG_FILES = [
1511
+ ".storybook/main.ts",
1512
+ ".storybook/main.mts",
1513
+ ".storybook/main.js",
1514
+ ".storybook/main.mjs",
1515
+ ".storybook/main.cjs"
1516
+ ];
1517
+ var DEFAULT_PATTERNS = [
1518
+ "**/*.stories.@(ts|tsx|js|jsx|mdx)",
1519
+ "**/*.story.@(ts|tsx|js|jsx|mdx)"
1520
+ ];
1521
+ async function detectStorybookConfig(projectRoot) {
1522
+ for (const configFile of CONFIG_FILES) {
1523
+ const configPath = join3(projectRoot, configFile);
1524
+ if (existsSync(configPath)) {
1525
+ return parseStorybookConfig(configPath);
1526
+ }
1527
+ }
1528
+ return null;
1529
+ }
1530
+ async function parseStorybookConfig(configPath) {
1531
+ const errors = [];
1532
+ let storyPatterns = [];
1533
+ let framework;
1534
+ let builder;
1535
+ try {
1536
+ const content = await readFile3(configPath, "utf-8");
1537
+ const storiesMatch = content.match(
1538
+ /stories:\s*\[([^\]]+)\]/s
1539
+ );
1540
+ if (storiesMatch) {
1541
+ const storiesContent = storiesMatch[1];
1542
+ const patterns = storiesContent.split(",").map((p) => p.trim()).map((p) => {
1543
+ const cleaned = p.replace(/^['"`]|['"`]$/g, "").trim();
1544
+ return cleaned;
1545
+ }).filter((p) => p && !p.startsWith("//") && !p.startsWith("{"));
1546
+ if (patterns.length > 0) {
1547
+ storyPatterns = patterns;
1548
+ }
1549
+ }
1550
+ const frameworkMatch = content.match(
1551
+ /framework:\s*['"`]([^'"`]+)['"`]/
1552
+ );
1553
+ if (frameworkMatch) {
1554
+ framework = frameworkMatch[1];
1555
+ } else {
1556
+ const frameworkNameMatch = content.match(
1557
+ /framework:\s*\{[^}]*name:\s*['"`]([^'"`]+)['"`]/
1558
+ );
1559
+ if (frameworkNameMatch) {
1560
+ framework = frameworkNameMatch[1];
1561
+ }
1562
+ }
1563
+ const builderMatch = content.match(
1564
+ /builder:\s*['"`]([^'"`]+)['"`]/
1565
+ );
1566
+ if (builderMatch) {
1567
+ builder = builderMatch[1];
1568
+ }
1569
+ } catch (error) {
1570
+ errors.push(
1571
+ `Failed to parse config: ${error instanceof Error ? error.message : String(error)}`
1572
+ );
1573
+ }
1574
+ if (storyPatterns.length === 0) {
1575
+ storyPatterns = DEFAULT_PATTERNS;
1576
+ errors.push("No story patterns found in config, using defaults");
1577
+ }
1578
+ return {
1579
+ configPath,
1580
+ storyPatterns,
1581
+ framework,
1582
+ builder,
1583
+ valid: errors.length === 0 || storyPatterns.length > 0,
1584
+ errors: errors.length > 0 ? errors : void 0
1585
+ };
1586
+ }
1587
+ async function discoverStoryFiles(projectRoot, patterns) {
1588
+ if (!patterns || patterns.length === 0) {
1589
+ const config = await detectStorybookConfig(projectRoot);
1590
+ patterns = config?.storyPatterns ?? DEFAULT_PATTERNS;
1591
+ }
1592
+ const configDir = join3(projectRoot, ".storybook");
1593
+ const resolvedPatterns = patterns.map((p) => {
1594
+ if (p.startsWith("../")) {
1595
+ return join3(configDir, p);
1596
+ }
1597
+ return join3(projectRoot, p);
1598
+ });
1599
+ const files = await fg3(resolvedPatterns, {
1600
+ cwd: projectRoot,
1601
+ absolute: true,
1602
+ ignore: [
1603
+ "**/node_modules/**",
1604
+ "**/dist/**",
1605
+ "**/build/**",
1606
+ "**/.storybook/**"
1607
+ ]
1608
+ });
1609
+ return files.sort();
1610
+ }
1611
+
1612
+ // src/setup.ts
1613
+ async function isSegmentsJsonStale(configDir, outFile) {
1614
+ const fs = await import("fs/promises");
1615
+ const path = await import("path");
1616
+ const fg4 = await import("fast-glob");
1617
+ const segmentsJsonPath = path.join(configDir, outFile);
1618
+ try {
1619
+ const segmentsJsonStat = await fs.stat(segmentsJsonPath);
1620
+ const segmentFiles = await fg4.default(`**/*${BRAND.fileExtension}`, {
1621
+ cwd: configDir,
1622
+ ignore: ["**/node_modules/**"],
1623
+ absolute: true
1624
+ });
1625
+ for (const file of segmentFiles) {
1626
+ const stat3 = await fs.stat(file);
1627
+ if (stat3.mtimeMs > segmentsJsonStat.mtimeMs) {
1628
+ return { stale: true, missing: false };
1629
+ }
1630
+ }
1631
+ return { stale: false, missing: false };
1632
+ } catch {
1633
+ return { stale: false, missing: true };
1634
+ }
1635
+ }
1636
+ async function loadSegmentInfo(segmentFiles) {
1637
+ const fs = await import("fs/promises");
1638
+ const segments = [];
1639
+ for (const file of segmentFiles) {
1640
+ try {
1641
+ const content = await fs.readFile(file.absolutePath, "utf-8");
1642
+ const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
1643
+ const hasFigma = /meta:\s*\{[^}]*figma:\s*['"]https?:/.test(content);
1644
+ if (nameMatch) {
1645
+ segments.push({
1646
+ name: nameMatch[1],
1647
+ filePath: file.absolutePath,
1648
+ hasFigma
1649
+ });
1650
+ }
1651
+ } catch {
1652
+ }
1653
+ }
1654
+ return segments;
1655
+ }
1656
+ async function runSetup(options = {}) {
1657
+ const fs = await import("fs/promises");
1658
+ const path = await import("path");
1659
+ const result = {
1660
+ segmentFilesCreated: 0,
1661
+ segmentsBuilt: 0,
1662
+ figmaLinked: 0,
1663
+ errors: []
1664
+ };
1665
+ const log = (msg) => {
1666
+ if (!options.silent) console.log(msg);
1667
+ };
1668
+ try {
1669
+ const { config, configDir } = await loadConfig(options.configPath);
1670
+ log(pc6.dim("Checking for fragment files..."));
1671
+ let segmentFiles = await discoverSegmentFiles(config, configDir);
1672
+ if (segmentFiles.length === 0 && !options.skipStorybook) {
1673
+ log(pc6.yellow("\n No fragment files found"));
1674
+ const sbConfig = await detectStorybookConfig(configDir);
1675
+ if (sbConfig) {
1676
+ log(pc6.dim(` Found Storybook at ${sbConfig.configPath}`));
1677
+ log(pc6.dim(" Converting stories to fragments...\n"));
1678
+ const storyFiles = await discoverStoryFiles(configDir, sbConfig.storyPatterns);
1679
+ if (storyFiles.length > 0) {
1680
+ let converted = 0;
1681
+ for (const storyFile of storyFiles) {
1682
+ try {
1683
+ const parsed = await parseStoryFile(storyFile);
1684
+ const segmentResult = convertToSegment(parsed);
1685
+ await fs.mkdir(path.dirname(segmentResult.outputFile), { recursive: true });
1686
+ await fs.writeFile(segmentResult.outputFile, segmentResult.code);
1687
+ converted++;
1688
+ } catch {
1689
+ }
1690
+ }
1691
+ result.segmentFilesCreated = converted;
1692
+ log(pc6.green(` Generated ${converted} fragment file(s)`));
1693
+ segmentFiles = await discoverSegmentFiles(config, configDir);
1694
+ }
1695
+ } else {
1696
+ log(pc6.dim(" No Storybook config found"));
1697
+ log(pc6.dim(` Run ${pc6.cyan(`${BRAND.cliCommand} add <ComponentName>`)} to create your first fragment`));
1698
+ }
1699
+ } else if (segmentFiles.length > 0) {
1700
+ log(pc6.green(` Found ${segmentFiles.length} fragment file(s)`));
1701
+ }
1702
+ if (segmentFiles.length > 0 && !options.skipBuild) {
1703
+ const outFile = config.outFile || BRAND.outFile;
1704
+ const { stale, missing } = await isSegmentsJsonStale(configDir, outFile);
1705
+ if (missing || stale) {
1706
+ const reason = missing ? "Building" : "Rebuilding";
1707
+ log(pc6.dim(`
1708
+ ${reason} ${BRAND.outFile}...`));
1709
+ try {
1710
+ const buildResult = await buildSegments(config, configDir);
1711
+ result.segmentsBuilt = buildResult.segmentCount;
1712
+ if (buildResult.errors.length > 0) {
1713
+ for (const err of buildResult.errors) {
1714
+ result.errors.push(`${err.file}: ${err.error}`);
1715
+ }
1716
+ }
1717
+ log(pc6.green(` Built ${buildResult.segmentCount} fragment(s)`));
1718
+ } catch (error) {
1719
+ result.errors.push(`Build failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1720
+ }
1721
+ } else {
1722
+ log(pc6.dim(`
1723
+ ${BRAND.outFile} is up to date`));
1724
+ }
1725
+ }
1726
+ if (!options.skipFigma && config.figmaFile && process.env.FIGMA_ACCESS_TOKEN) {
1727
+ const segments = await loadSegmentInfo(segmentFiles);
1728
+ const linkedCount = segments.filter((s) => s.hasFigma).length;
1729
+ if (linkedCount === 0 && segments.length > 0) {
1730
+ log(pc6.dim("\n Figma configured but no fragments linked"));
1731
+ log(pc6.dim(` Run ${pc6.cyan(`${BRAND.cliCommand} link figma --auto`)} to auto-link components`));
1732
+ } else if (linkedCount > 0) {
1733
+ log(pc6.dim(`
1734
+ ${linkedCount}/${segments.length} fragment(s) linked to Figma`));
1735
+ }
1736
+ } else if (!options.skipFigma && config.figmaFile && !process.env.FIGMA_ACCESS_TOKEN) {
1737
+ log(pc6.dim("\n Figma file configured but FIGMA_ACCESS_TOKEN not set"));
1738
+ }
1739
+ } catch (error) {
1740
+ result.errors.push(error instanceof Error ? error.message : "Unknown error");
1741
+ }
1742
+ return result;
1743
+ }
1744
+
1745
+ // src/commands/dev.ts
1746
+ async function dev(options = {}) {
1747
+ const {
1748
+ port = 6006,
1749
+ open = true,
1750
+ skipSetup = false,
1751
+ skipStorybook = false,
1752
+ skipFigma = false,
1753
+ skipBuild = false
1754
+ } = options;
1755
+ console.log(pc7.cyan(`
1756
+ ${BRAND.name} Dev Server
1757
+ `));
1758
+ if (!skipSetup) {
1759
+ const setupResult = await runSetup({
1760
+ skipStorybook,
1761
+ skipFigma,
1762
+ skipBuild
1763
+ });
1764
+ if (setupResult.errors.length > 0) {
1765
+ console.log(pc7.yellow("\n Setup completed with warnings:"));
1766
+ for (const error of setupResult.errors) {
1767
+ console.log(pc7.dim(` ${error}`));
1768
+ }
1769
+ }
1770
+ }
1771
+ const { createDevServer } = await import("./viewer-YRF4SQE4.js");
1772
+ console.log(pc7.dim("\nStarting dev server..."));
1773
+ const parsedPort = typeof port === "string" ? parseInt(port, 10) : port;
1774
+ try {
1775
+ const server = await createDevServer({
1776
+ port: parsedPort,
1777
+ open,
1778
+ projectRoot: process.cwd()
1779
+ });
1780
+ const address = server.httpServer?.address();
1781
+ const actualPort = typeof address === "object" && address ? address.port : parsedPort;
1782
+ console.log(pc7.green(`
1783
+ Viewer running at http://localhost:${actualPort}/fragments/
1784
+ `));
1785
+ console.log(pc7.dim("Press Ctrl+C to stop\n"));
1786
+ return {
1787
+ success: true,
1788
+ port: typeof actualPort === "number" ? actualPort : parseInt(String(actualPort), 10)
1789
+ };
1790
+ } catch (error) {
1791
+ const errMsg = error instanceof Error ? error.message : String(error);
1792
+ if (errMsg.includes("EADDRINUSE") || errMsg.includes("address already in use")) {
1793
+ console.error(pc7.red(`
1794
+ Port ${parsedPort} is already in use.`));
1795
+ console.error(pc7.dim(` Try a different port: ${BRAND.cliCommand} dev --port ${parsedPort + 1}
1796
+ `));
1797
+ } else if (errMsg.includes("EACCES")) {
1798
+ console.error(pc7.red(`
1799
+ Permission denied for port ${parsedPort}.`));
1800
+ console.error(pc7.dim(` Try a port above 1024: ${BRAND.cliCommand} dev --port 6006
1801
+ `));
1802
+ } else {
1803
+ console.error(pc7.red(`
1804
+ Failed to start dev server: ${errMsg}
1805
+ `));
1806
+ }
1807
+ return { success: false };
1808
+ }
1809
+ }
1810
+
1811
+ // src/commands/compare.ts
1812
+ import pc8 from "picocolors";
1813
+ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1814
+ import { resolve as resolve2, join as join4 } from "path";
1815
+ async function compare(component, options = {}) {
1816
+ const {
1817
+ variant,
1818
+ figma: figmaUrl,
1819
+ threshold = 1,
1820
+ all = false,
1821
+ output,
1822
+ port = 6006
1823
+ } = options;
1824
+ if (!process.env.FIGMA_ACCESS_TOKEN) {
1825
+ console.error(pc8.red("\nFIGMA_ACCESS_TOKEN environment variable required."));
1826
+ console.log(pc8.dim("Generate at: https://www.figma.com/developers/api#access-tokens"));
1827
+ console.log(pc8.dim(" export FIGMA_ACCESS_TOKEN=figd_xxx"));
1828
+ process.exit(1);
1829
+ }
1830
+ const baseUrl = `http://localhost:${port}`;
1831
+ console.log(pc8.cyan(`
1832
+ ${BRAND.name} Design Verification
1833
+ `));
1834
+ if (all) {
1835
+ return compareAll(baseUrl, threshold, output);
1836
+ }
1837
+ let componentsToCompare = [];
1838
+ if (component) {
1839
+ componentsToCompare = [component];
1840
+ } else {
1841
+ componentsToCompare = await selectComponents(baseUrl);
1842
+ }
1843
+ if (componentsToCompare.length === 0) {
1844
+ console.log(pc8.dim("\nNo components selected."));
1845
+ return { success: true, passed: 0, failed: 0, skipped: 0 };
1846
+ }
1847
+ if (componentsToCompare.length === 1) {
1848
+ console.log(pc8.dim(`Comparing ${componentsToCompare[0]} to Figma design...
1849
+ `));
1850
+ } else {
1851
+ console.log(pc8.dim(`Comparing ${componentsToCompare.length} components to Figma designs...
1852
+ `));
1853
+ }
1854
+ let passed = 0;
1855
+ let failed = 0;
1856
+ for (const comp of componentsToCompare) {
1857
+ const response = await fetch(`${baseUrl}/segments/compare`, {
1858
+ method: "POST",
1859
+ headers: { "Content-Type": "application/json" },
1860
+ body: JSON.stringify({
1861
+ component: comp,
1862
+ variant,
1863
+ figmaUrl,
1864
+ threshold,
1865
+ figmaToken: process.env.FIGMA_ACCESS_TOKEN
1866
+ })
1867
+ });
1868
+ const result = await response.json();
1869
+ if (result.error) {
1870
+ failed++;
1871
+ console.log(`${pc8.red("\u2717")} ${pc8.bold(comp)} - ${result.error}`);
1872
+ continue;
1873
+ }
1874
+ if (result.match) {
1875
+ passed++;
1876
+ console.log(`${pc8.green("\u2713")} ${pc8.bold(comp)} ${pc8.dim(`${result.diffPercentage}%`)}`);
1877
+ } else {
1878
+ failed++;
1879
+ console.log(`${pc8.red("\u2717")} ${pc8.bold(comp)} ${pc8.yellow(`${result.diffPercentage}%`)} ${pc8.dim(`(threshold: ${threshold}%)`)}`);
1880
+ }
1881
+ if (output && result.rendered && result.figma && result.diff) {
1882
+ await saveImages(output, comp, result);
1883
+ }
1884
+ }
1885
+ if (componentsToCompare.length > 1) {
1886
+ console.log();
1887
+ console.log(pc8.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1888
+ console.log(`
1889
+ ${pc8.green(`${passed} passed`)}, ${pc8.red(`${failed} failed`)}
1890
+ `);
1891
+ } else {
1892
+ console.log();
1893
+ }
1894
+ if (output && componentsToCompare.length > 0) {
1895
+ console.log(pc8.dim(`Images saved to: ${output}/
1896
+ `));
1897
+ }
1898
+ return {
1899
+ success: failed === 0,
1900
+ passed,
1901
+ failed,
1902
+ skipped: 0
1903
+ };
1904
+ }
1905
+ async function compareAll(baseUrl, threshold, output) {
1906
+ console.log(pc8.dim("Comparing all components with Figma links...\n"));
1907
+ const contextResp = await fetch(`${baseUrl}/segments/context?format=json`);
1908
+ if (!contextResp.ok) {
1909
+ throw new Error("Failed to fetch segments. Make sure dev server is running.");
1910
+ }
1911
+ const contextText = await contextResp.text();
1912
+ let segments = [];
1913
+ try {
1914
+ const contextData = JSON.parse(contextText);
1915
+ segments = contextData.components || [];
1916
+ } catch {
1917
+ segments = [];
1918
+ }
1919
+ if (segments.length === 0) {
1920
+ console.log(pc8.yellow("No components found with Figma links."));
1921
+ console.log(pc8.dim("Add figma field to your segment definitions:"));
1922
+ console.log(pc8.dim(' meta: { figma: "https://figma.com/file/..." }'));
1923
+ return { success: true, passed: 0, failed: 0, skipped: 0 };
1924
+ }
1925
+ let passed = 0;
1926
+ let failed = 0;
1927
+ let skipped = 0;
1928
+ for (const seg of segments) {
1929
+ if (!seg.figma) {
1930
+ skipped++;
1931
+ console.log(`${pc8.dim("\u23ED\uFE0F")} ${pc8.dim(seg.name)} ${pc8.dim("(no figma link)")}`);
1932
+ continue;
1933
+ }
1934
+ try {
1935
+ const response = await fetch(`${baseUrl}/segments/compare`, {
1936
+ method: "POST",
1937
+ headers: { "Content-Type": "application/json" },
1938
+ body: JSON.stringify({
1939
+ component: seg.name,
1940
+ threshold,
1941
+ figmaToken: process.env.FIGMA_ACCESS_TOKEN
1942
+ })
1943
+ });
1944
+ const result = await response.json();
1945
+ if (result.error) {
1946
+ failed++;
1947
+ console.log(`${pc8.red("\u2717")} ${pc8.bold(seg.name)} - ${result.error}`);
1948
+ } else if (result.match) {
1949
+ passed++;
1950
+ console.log(`${pc8.green("\u2713")} ${pc8.bold(seg.name)} ${pc8.dim(`${result.diffPercentage}%`)}`);
1951
+ } else {
1952
+ failed++;
1953
+ console.log(`${pc8.red("\u2717")} ${pc8.bold(seg.name)} ${pc8.yellow(`${result.diffPercentage}%`)} ${pc8.dim(`(threshold: ${threshold}%)`)}`);
1954
+ }
1955
+ if (output && result.rendered && result.figma && result.diff) {
1956
+ await saveImages(output, seg.name, result);
1957
+ }
1958
+ } catch (error) {
1959
+ failed++;
1960
+ console.log(`${pc8.red("\u2717")} ${pc8.bold(seg.name)} - ${error instanceof Error ? error.message : "Unknown error"}`);
1961
+ }
1962
+ }
1963
+ console.log();
1964
+ console.log(pc8.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1965
+ console.log(`
1966
+ ${pc8.green(`${passed} passed`)}, ${pc8.red(`${failed} failed`)}, ${pc8.dim(`${skipped} skipped`)}
1967
+ `);
1968
+ if (output) {
1969
+ console.log(pc8.dim(`Images saved to: ${output}/
1970
+ `));
1971
+ }
1972
+ return {
1973
+ success: failed === 0,
1974
+ passed,
1975
+ failed,
1976
+ skipped
1977
+ };
1978
+ }
1979
+ async function selectComponents(baseUrl) {
1980
+ console.log(pc8.dim("Fetching components with Figma links...\n"));
1981
+ const contextResp = await fetch(`${baseUrl}/segments/context?format=json`);
1982
+ if (!contextResp.ok) {
1983
+ throw new Error("Failed to fetch segments. Make sure dev server is running.");
1984
+ }
1985
+ const contextText = await contextResp.text();
1986
+ let segments = [];
1987
+ try {
1988
+ const contextData = JSON.parse(contextText);
1989
+ segments = (contextData.components || []).filter((s) => s.figma);
1990
+ } catch {
1991
+ segments = [];
1992
+ }
1993
+ if (segments.length === 0) {
1994
+ console.log(pc8.yellow("No components found with Figma links."));
1995
+ console.log(pc8.dim("Add figma field to your segment definitions:"));
1996
+ console.log(pc8.dim(' meta: { figma: "https://figma.com/file/..." }'));
1997
+ return [];
1998
+ }
1999
+ const { checkbox } = await import("@inquirer/prompts");
2000
+ try {
2001
+ const choices = segments.map((seg) => ({
2002
+ name: seg.name,
2003
+ value: seg.name,
2004
+ checked: true
2005
+ }));
2006
+ return await checkbox({
2007
+ message: "Select components to compare:",
2008
+ choices,
2009
+ pageSize: 15
2010
+ });
2011
+ } catch {
2012
+ console.log(pc8.dim("\nNo changes made."));
2013
+ return [];
2014
+ }
2015
+ }
2016
+ async function saveImages(outputDir, component, result) {
2017
+ const dir = resolve2(process.cwd(), outputDir);
2018
+ await mkdir2(dir, { recursive: true });
2019
+ const saveImage = async (data, filename) => {
2020
+ const base64 = data.replace("data:image/png;base64,", "");
2021
+ await writeFile2(join4(dir, filename), Buffer.from(base64, "base64"));
2022
+ };
2023
+ if (result.rendered) {
2024
+ await saveImage(result.rendered, `${component}-rendered.png`);
2025
+ }
2026
+ if (result.figma) {
2027
+ await saveImage(result.figma, `${component}-figma.png`);
2028
+ }
2029
+ if (result.diff) {
2030
+ await saveImage(result.diff, `${component}-diff.png`);
2031
+ }
2032
+ }
2033
+
2034
+ // src/commands/verify.ts
2035
+ import pc10 from "picocolors";
2036
+
2037
+ // src/shared/dev-server-client.ts
2038
+ var DevServerClient = class {
2039
+ baseUrl;
2040
+ timeout;
2041
+ constructor(options) {
2042
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
2043
+ this.timeout = options.timeout ?? 3e4;
2044
+ }
2045
+ /**
2046
+ * Check if the dev server is reachable
2047
+ */
2048
+ async ping() {
2049
+ try {
2050
+ const controller = new AbortController();
2051
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
2052
+ const response = await fetch(`${this.baseUrl}/segments/context?format=json`, {
2053
+ signal: controller.signal
2054
+ });
2055
+ clearTimeout(timeoutId);
2056
+ return response.ok;
2057
+ } catch {
2058
+ return false;
2059
+ }
2060
+ }
2061
+ /**
2062
+ * Get all segments from the context endpoint
2063
+ */
2064
+ async getSegments() {
2065
+ const response = await this.fetch("/segments/context?format=json");
2066
+ const data = await response.json();
2067
+ const components = data.components || {};
2068
+ return Object.entries(components).map(([name, info]) => ({
2069
+ name,
2070
+ category: info.category || "components",
2071
+ description: info.description,
2072
+ status: info.status,
2073
+ figma: info.figma
2074
+ }));
2075
+ }
2076
+ /**
2077
+ * Get compliance data for a component
2078
+ */
2079
+ async getCompliance(request) {
2080
+ const response = await this.fetch("/segments/compliance", {
2081
+ method: "POST",
2082
+ headers: { "Content-Type": "application/json" },
2083
+ body: JSON.stringify(request)
2084
+ });
2085
+ const data = await response.json();
2086
+ return data;
2087
+ }
2088
+ /**
2089
+ * Get accessibility results for a component
2090
+ */
2091
+ async getA11y(component, variant) {
2092
+ const response = await this.fetch("/fragments/a11y", {
2093
+ method: "POST",
2094
+ headers: { "Content-Type": "application/json" },
2095
+ body: JSON.stringify({ component, variant })
2096
+ });
2097
+ const data = await response.json();
2098
+ return data;
2099
+ }
2100
+ /**
2101
+ * Internal fetch wrapper with error handling
2102
+ */
2103
+ async fetch(path, options) {
2104
+ const url = `${this.baseUrl}${path}`;
2105
+ try {
2106
+ const controller = new AbortController();
2107
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2108
+ const response = await fetch(url, {
2109
+ ...options,
2110
+ signal: controller.signal
2111
+ });
2112
+ clearTimeout(timeoutId);
2113
+ if (!response.ok) {
2114
+ const errorBody = await response.text().catch(() => "");
2115
+ throw new DevServerError(
2116
+ `Server returned ${response.status}: ${response.statusText}`,
2117
+ response.status,
2118
+ errorBody
2119
+ );
2120
+ }
2121
+ return response;
2122
+ } catch (error) {
2123
+ if (error instanceof DevServerError) {
2124
+ throw error;
2125
+ }
2126
+ const errMsg = error instanceof Error && error.cause ? String(error.cause.code || error.message) : error instanceof Error ? error.message : "Unknown error";
2127
+ if (errMsg.includes("ECONNREFUSED") || errMsg.includes("fetch failed")) {
2128
+ throw new DevServerConnectionError(
2129
+ `Cannot connect to dev server at ${this.baseUrl}`,
2130
+ this.baseUrl
2131
+ );
2132
+ }
2133
+ throw error;
2134
+ }
2135
+ }
2136
+ };
2137
+ var DevServerError = class extends Error {
2138
+ constructor(message, statusCode, body) {
2139
+ super(message);
2140
+ this.statusCode = statusCode;
2141
+ this.body = body;
2142
+ this.name = "DevServerError";
2143
+ }
2144
+ };
2145
+ var DevServerConnectionError = class extends Error {
2146
+ constructor(message, serverUrl) {
2147
+ super(message);
2148
+ this.serverUrl = serverUrl;
2149
+ this.name = "DevServerConnectionError";
2150
+ }
2151
+ };
2152
+ function createDevServerClient(port = 6006) {
2153
+ return new DevServerClient({
2154
+ baseUrl: `http://localhost:${port}`
2155
+ });
2156
+ }
2157
+
2158
+ // src/shared/command-wrapper.ts
2159
+ import pc9 from "picocolors";
2160
+
2161
+ // src/commands/verify.ts
2162
+ async function verify(component, options = {}) {
2163
+ const { config: configPath, ci = false, port = 6006 } = options;
2164
+ const { config } = await loadConfig(configPath);
2165
+ const minCompliance = options.minCompliance ?? config.ci?.minCompliance ?? 80;
2166
+ const client = createDevServerClient(port);
2167
+ const results = [];
2168
+ let totalCompliance = 0;
2169
+ let componentCount = 0;
2170
+ if (!ci) {
2171
+ console.log(pc10.cyan(`
2172
+ ${BRAND.name} Compliance Verification
2173
+ `));
2174
+ console.log(pc10.dim(`Minimum compliance: ${minCompliance}%
2175
+ `));
2176
+ }
2177
+ const isReachable = await client.ping();
2178
+ if (!isReachable) {
2179
+ throw new DevServerConnectionError(
2180
+ `Cannot connect to dev server at http://localhost:${port}`,
2181
+ `http://localhost:${port}`
2182
+ );
2183
+ }
2184
+ let segments = await client.getSegments();
2185
+ if (component) {
2186
+ segments = segments.filter(
2187
+ (s) => s.name.toLowerCase() === component.toLowerCase()
2188
+ );
2189
+ if (segments.length === 0) {
2190
+ const error = { error: `Component "${component}" not found` };
2191
+ if (ci) {
2192
+ console.log(JSON.stringify(error));
2193
+ } else {
2194
+ console.log(pc10.red(error.error));
2195
+ }
2196
+ process.exit(1);
2197
+ }
2198
+ }
2199
+ for (const seg of segments) {
2200
+ try {
2201
+ const complianceResult = await client.getCompliance({
2202
+ component: seg.name
2203
+ });
2204
+ const passed = complianceResult.compliance >= minCompliance;
2205
+ results.push({
2206
+ component: seg.name,
2207
+ compliance: complianceResult.compliance,
2208
+ passed,
2209
+ violations: complianceResult.violations,
2210
+ totalProperties: complianceResult.totalProperties,
2211
+ hardcoded: complianceResult.hardcoded,
2212
+ usingTokens: complianceResult.usingTokens
2213
+ });
2214
+ totalCompliance += complianceResult.compliance;
2215
+ componentCount++;
2216
+ if (!ci) {
2217
+ const icon = passed ? pc10.green("\u2713") : pc10.red("\u2717");
2218
+ const complianceStr = passed ? pc10.green(`${complianceResult.compliance}%`) : pc10.red(`${complianceResult.compliance}%`);
2219
+ console.log(` ${icon} ${seg.name} ${complianceStr}`);
2220
+ if (!passed && complianceResult.violations.length > 0) {
2221
+ const violationsToShow = complianceResult.violations.slice(0, 3);
2222
+ for (const v of violationsToShow) {
2223
+ console.log(pc10.dim(` - ${v.property}: ${v.issue}`));
2224
+ if (v.suggestion) {
2225
+ console.log(pc10.dim(` ${pc10.cyan("\u2192")} ${v.suggestion}`));
2226
+ }
2227
+ }
2228
+ if (complianceResult.violations.length > 3) {
2229
+ console.log(pc10.dim(` ... and ${complianceResult.violations.length - 3} more`));
2230
+ }
2231
+ }
2232
+ }
2233
+ } catch (error) {
2234
+ results.push({
2235
+ component: seg.name,
2236
+ compliance: 0,
2237
+ passed: false,
2238
+ violations: [{
2239
+ property: "unknown",
2240
+ issue: error instanceof Error ? error.message : "Unknown error",
2241
+ severity: "error"
2242
+ }],
2243
+ totalProperties: 0,
2244
+ hardcoded: 0,
2245
+ usingTokens: 0
2246
+ });
2247
+ if (!ci) {
2248
+ console.log(` ${pc10.red("\u2717")} ${seg.name} ${pc10.red("error")}`);
2249
+ }
2250
+ }
2251
+ }
2252
+ const averageCompliance = componentCount > 0 ? totalCompliance / componentCount : 100;
2253
+ const allPassed = results.every((r) => r.passed);
2254
+ const summary = {
2255
+ passed: allPassed,
2256
+ compliance: Math.round(averageCompliance * 100) / 100,
2257
+ threshold: minCompliance,
2258
+ totalComponents: componentCount,
2259
+ passedComponents: results.filter((r) => r.passed).length,
2260
+ failedComponents: results.filter((r) => !r.passed).length,
2261
+ results,
2262
+ violations: results.flatMap((r) => r.violations.map((v) => ({
2263
+ component: r.component,
2264
+ ...v
2265
+ })))
2266
+ };
2267
+ if (ci) {
2268
+ console.log(JSON.stringify(summary, null, 2));
2269
+ } else {
2270
+ console.log();
2271
+ if (allPassed) {
2272
+ console.log(pc10.green(`\u2713 All ${componentCount} component(s) meet ${minCompliance}% compliance threshold`));
2273
+ } else {
2274
+ const failedCount = results.filter((r) => !r.passed).length;
2275
+ console.log(pc10.red(`\u2717 ${failedCount} component(s) below ${minCompliance}% compliance threshold`));
2276
+ }
2277
+ console.log(pc10.dim(` Average compliance: ${summary.compliance}%
2278
+ `));
2279
+ }
2280
+ return summary;
2281
+ }
2282
+
2283
+ // src/commands/audit.ts
2284
+ import pc11 from "picocolors";
2285
+ async function audit(options = {}) {
2286
+ const { config: configPath, json = false, sort = "compliance", port = 6006 } = options;
2287
+ await loadConfig(configPath);
2288
+ const client = createDevServerClient(port);
2289
+ const audits = [];
2290
+ if (!json) {
2291
+ console.log(pc11.cyan(`
2292
+ ${BRAND.name} Design System Audit
2293
+ `));
2294
+ }
2295
+ const isReachable = await client.ping();
2296
+ if (!isReachable) {
2297
+ throw new DevServerConnectionError(
2298
+ `Cannot connect to dev server at http://localhost:${port}`,
2299
+ `http://localhost:${port}`
2300
+ );
2301
+ }
2302
+ const segments = await client.getSegments();
2303
+ if (segments.length === 0) {
2304
+ if (json) {
2305
+ console.log(JSON.stringify({ error: "No fragments found", components: [] }));
2306
+ } else {
2307
+ console.log(pc11.yellow("No fragments found.\n"));
2308
+ }
2309
+ return {
2310
+ totalComponents: 0,
2311
+ averageCompliance: 100,
2312
+ worstOffenders: [],
2313
+ components: [],
2314
+ stats: {
2315
+ totalHardcoded: 0,
2316
+ totalTokenMismatches: 0,
2317
+ totalProperties: 0
2318
+ }
2319
+ };
2320
+ }
2321
+ if (!json) {
2322
+ console.log(pc11.dim(`Auditing ${segments.length} component(s)...
2323
+ `));
2324
+ }
2325
+ for (const seg of segments) {
2326
+ try {
2327
+ const complianceResult = await client.getCompliance({
2328
+ component: seg.name
2329
+ });
2330
+ audits.push({
2331
+ name: seg.name,
2332
+ category: seg.category || "uncategorized",
2333
+ compliance: complianceResult.compliance,
2334
+ hardcoded: complianceResult.hardcoded,
2335
+ tokenMismatches: complianceResult.violations.filter(
2336
+ (v) => v.issue.includes("mismatch")
2337
+ ).length,
2338
+ totalProperties: complianceResult.totalProperties
2339
+ });
2340
+ } catch (error) {
2341
+ audits.push({
2342
+ name: seg.name,
2343
+ category: seg.category || "uncategorized",
2344
+ compliance: 0,
2345
+ hardcoded: 0,
2346
+ tokenMismatches: 0,
2347
+ totalProperties: 0
2348
+ });
2349
+ }
2350
+ }
2351
+ audits.sort((a, b) => {
2352
+ switch (sort) {
2353
+ case "name":
2354
+ return a.name.localeCompare(b.name);
2355
+ case "hardcoded":
2356
+ return b.hardcoded - a.hardcoded;
2357
+ // Most hardcoded first
2358
+ case "compliance":
2359
+ default:
2360
+ return a.compliance - b.compliance;
2361
+ }
2362
+ });
2363
+ const summary = {
2364
+ totalComponents: audits.length,
2365
+ averageCompliance: audits.length > 0 ? Math.round(audits.reduce((sum, a) => sum + a.compliance, 0) / audits.length * 100) / 100 : 100,
2366
+ worstOffenders: audits.slice(0, 5),
2367
+ components: audits,
2368
+ stats: {
2369
+ totalHardcoded: audits.reduce((sum, a) => sum + a.hardcoded, 0),
2370
+ totalTokenMismatches: audits.reduce((sum, a) => sum + a.tokenMismatches, 0),
2371
+ totalProperties: audits.reduce((sum, a) => sum + a.totalProperties, 0)
2372
+ }
2373
+ };
2374
+ if (json) {
2375
+ console.log(JSON.stringify(summary, null, 2));
2376
+ } else {
2377
+ console.log(pc11.bold("Component".padEnd(30) + "Compliance".padEnd(12) + "Hardcoded".padEnd(12) + "Properties"));
2378
+ console.log(pc11.dim("\u2500".repeat(66)));
2379
+ for (const auditItem of audits) {
2380
+ const complianceColor = auditItem.compliance >= 90 ? pc11.green : auditItem.compliance >= 70 ? pc11.yellow : pc11.red;
2381
+ console.log(
2382
+ auditItem.name.padEnd(30) + complianceColor(`${auditItem.compliance}%`.padEnd(12)) + String(auditItem.hardcoded).padEnd(12) + String(auditItem.totalProperties)
2383
+ );
2384
+ }
2385
+ console.log(pc11.dim("\u2500".repeat(66)));
2386
+ console.log();
2387
+ console.log(pc11.bold("Summary:"));
2388
+ console.log(` Total components: ${audits.length}`);
2389
+ console.log(` Average compliance: ${summary.averageCompliance}%`);
2390
+ console.log(` Total hardcoded values: ${summary.stats.totalHardcoded}`);
2391
+ console.log(` Total properties checked: ${summary.stats.totalProperties}`);
2392
+ if (audits.some((a) => a.compliance < 100)) {
2393
+ console.log();
2394
+ console.log(pc11.dim(`Run ${pc11.cyan(`${BRAND.cliCommand} verify <component>`)} for detailed compliance info.`));
2395
+ }
2396
+ console.log();
2397
+ }
2398
+ return summary;
2399
+ }
2400
+
2401
+ // src/commands/a11y.ts
2402
+ import pc12 from "picocolors";
2403
+ async function a11y(options = {}) {
2404
+ const { config: configPath, json = false, ci = false, component, port = 6006 } = options;
2405
+ await loadConfig(configPath);
2406
+ const client = createDevServerClient(port);
2407
+ const componentResults = [];
2408
+ if (!json) {
2409
+ console.log(pc12.cyan(`
2410
+ ${BRAND.name} Accessibility Report
2411
+ `));
2412
+ }
2413
+ const isReachable = await client.ping();
2414
+ if (!isReachable) {
2415
+ throw new DevServerConnectionError(
2416
+ `Cannot connect to dev server at http://localhost:${port}`,
2417
+ `http://localhost:${port}`
2418
+ );
2419
+ }
2420
+ const segments = await client.getSegments();
2421
+ if (segments.length === 0) {
2422
+ if (json) {
2423
+ console.log(JSON.stringify({ error: "No fragments found", components: [] }));
2424
+ } else {
2425
+ console.log(pc12.yellow("No fragments found.\n"));
2426
+ }
2427
+ return {
2428
+ totalComponents: 0,
2429
+ accessibleComponents: 0,
2430
+ accessiblePercent: 100,
2431
+ components: [],
2432
+ totalViolations: 0,
2433
+ totalCritical: 0,
2434
+ totalSerious: 0,
2435
+ totalModerate: 0,
2436
+ totalMinor: 0,
2437
+ passed: true
2438
+ };
2439
+ }
2440
+ const componentsToCheck = component ? segments.filter((s) => s.name.toLowerCase() === component.toLowerCase()) : segments;
2441
+ if (component && componentsToCheck.length === 0) {
2442
+ const error = `Component '${component}' not found. Available: ${segments.map((s) => s.name).join(", ")}`;
2443
+ if (json) {
2444
+ console.log(JSON.stringify({ error }));
2445
+ } else {
2446
+ console.log(pc12.red(error));
2447
+ }
2448
+ throw new Error(error);
2449
+ }
2450
+ if (!json) {
2451
+ console.log(pc12.dim(`Checking ${componentsToCheck.length} component(s) for accessibility issues...
2452
+ `));
2453
+ }
2454
+ for (const seg of componentsToCheck) {
2455
+ try {
2456
+ const a11yResult = await client.getA11y(seg.name);
2457
+ let totalViolations2 = 0;
2458
+ let totalCritical2 = 0;
2459
+ let totalSerious2 = 0;
2460
+ for (const result of a11yResult.results) {
2461
+ totalViolations2 += result.summary.total;
2462
+ totalCritical2 += result.summary.critical;
2463
+ totalSerious2 += result.summary.serious;
2464
+ }
2465
+ let status = "PASS";
2466
+ if (totalCritical2 > 0 || totalSerious2 > 0) {
2467
+ status = "FAIL";
2468
+ } else if (totalViolations2 > 0) {
2469
+ status = "WARN";
2470
+ }
2471
+ componentResults.push({
2472
+ component: seg.name,
2473
+ results: a11yResult.results,
2474
+ status,
2475
+ totalViolations: totalViolations2,
2476
+ totalCritical: totalCritical2,
2477
+ totalSerious: totalSerious2
2478
+ });
2479
+ } catch (error) {
2480
+ componentResults.push({
2481
+ component: seg.name,
2482
+ results: [],
2483
+ status: "FAIL",
2484
+ totalViolations: 0,
2485
+ totalCritical: 0,
2486
+ totalSerious: 0
2487
+ });
2488
+ }
2489
+ }
2490
+ const accessibleComponents = componentResults.filter((c) => c.status !== "FAIL").length;
2491
+ const totalViolations = componentResults.reduce((sum, c) => sum + c.totalViolations, 0);
2492
+ const totalCritical = componentResults.reduce((sum, c) => sum + c.totalCritical, 0);
2493
+ const totalSerious = componentResults.reduce((sum, c) => sum + c.totalSerious, 0);
2494
+ let totalModerate = 0;
2495
+ let totalMinor = 0;
2496
+ for (const comp of componentResults) {
2497
+ for (const result of comp.results) {
2498
+ totalModerate += result.summary.moderate;
2499
+ totalMinor += result.summary.minor;
2500
+ }
2501
+ }
2502
+ const summary = {
2503
+ totalComponents: componentResults.length,
2504
+ accessibleComponents,
2505
+ accessiblePercent: componentResults.length > 0 ? Math.round(accessibleComponents / componentResults.length * 100) : 100,
2506
+ components: componentResults,
2507
+ totalViolations,
2508
+ totalCritical,
2509
+ totalSerious,
2510
+ totalModerate,
2511
+ totalMinor,
2512
+ passed: totalCritical === 0 && totalSerious === 0
2513
+ };
2514
+ if (json) {
2515
+ console.log(JSON.stringify(summary, null, 2));
2516
+ } else {
2517
+ console.log(pc12.bold(
2518
+ "Component".padEnd(20) + "Variants".padEnd(10) + "Violations".padEnd(12) + "Critical".padEnd(10) + "Serious".padEnd(10) + "Status"
2519
+ ));
2520
+ console.log(pc12.dim("\u2500".repeat(72)));
2521
+ for (const result of componentResults) {
2522
+ const statusColor = result.status === "PASS" ? pc12.green : result.status === "WARN" ? pc12.yellow : pc12.red;
2523
+ const variantCount = result.results.length || 1;
2524
+ console.log(
2525
+ result.component.padEnd(20) + String(variantCount).padEnd(10) + String(result.totalViolations).padEnd(12) + String(result.totalCritical).padEnd(10) + String(result.totalSerious).padEnd(10) + statusColor(result.status)
2526
+ );
2527
+ }
2528
+ console.log(pc12.dim("\u2500".repeat(72)));
2529
+ console.log();
2530
+ console.log(pc12.bold("Summary:"));
2531
+ console.log(` ${accessibleComponents}/${componentResults.length} components accessible (${summary.accessiblePercent}%)`);
2532
+ console.log(` Total violations: ${totalViolations} (${totalCritical} critical, ${totalSerious} serious, ${totalModerate} moderate, ${totalMinor} minor)`);
2533
+ if (!summary.passed) {
2534
+ console.log();
2535
+ console.log(pc12.red("\u2717 Accessibility check failed - critical/serious violations found"));
2536
+ } else if (totalViolations > 0) {
2537
+ console.log();
2538
+ console.log(pc12.yellow("\u26A0 Minor/moderate violations found - consider fixing for better accessibility"));
2539
+ } else {
2540
+ console.log();
2541
+ console.log(pc12.green("\u2713 All components pass accessibility checks"));
2542
+ }
2543
+ console.log();
2544
+ }
2545
+ if (ci && !summary.passed) {
2546
+ throw new Error(`Accessibility check failed: ${totalCritical} critical, ${totalSerious} serious violations found`);
2547
+ }
2548
+ return summary;
2549
+ }
2550
+
2551
+ // src/commands/storygen.ts
2552
+ import { writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
2553
+ import { resolve as resolve3, join as join5, relative as relative3 } from "path";
2554
+ import pc13 from "picocolors";
2555
+ async function storygen(options = {}) {
2556
+ const { config: configPath, output = ".storybook/generated", watch = false } = options;
2557
+ const { config, configDir } = await loadConfig(configPath);
2558
+ console.log(pc13.cyan(`
2559
+ ${BRAND.name} Story Generator
2560
+ `));
2561
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
2562
+ if (segmentFiles.length === 0) {
2563
+ console.log(pc13.yellow("No segment files found.\n"));
2564
+ return { success: true, generated: 0, outputDir: output };
2565
+ }
2566
+ const outputDir = resolve3(configDir, output);
2567
+ await mkdir3(outputDir, { recursive: true });
2568
+ let generated = 0;
2569
+ const generateStory = async (file) => {
2570
+ try {
2571
+ const segment = await loadSegmentFile(file.absolutePath);
2572
+ if (!segment) return false;
2573
+ const storyContent = generateCSF3Story(segment, file.relativePath);
2574
+ const storyName = `${segment.meta.name}.stories.tsx`;
2575
+ const storyPath = join5(outputDir, storyName);
2576
+ await writeFile3(storyPath, storyContent);
2577
+ console.log(`${pc13.green("\u2713")} Generated ${storyName}`);
2578
+ return true;
2579
+ } catch (error) {
2580
+ console.log(`${pc13.red("\u2717")} Failed: ${file.relativePath} - ${error instanceof Error ? error.message : error}`);
2581
+ return false;
2582
+ }
2583
+ };
2584
+ console.log(pc13.dim(`Generating stories to ${relative3(process.cwd(), outputDir)}/
2585
+ `));
2586
+ for (const file of segmentFiles) {
2587
+ if (await generateStory(file)) {
2588
+ generated++;
2589
+ }
2590
+ }
2591
+ console.log();
2592
+ console.log(pc13.green(`\u2713 Generated ${generated} story file(s)
2593
+ `));
2594
+ if (watch) {
2595
+ console.log(pc13.dim("Watching for segment changes... (Ctrl+C to stop)\n"));
2596
+ const chokidar = await import("chokidar");
2597
+ const patterns = segmentFiles.map((f) => f.absolutePath);
2598
+ const watcher = chokidar.watch(patterns, {
2599
+ ignoreInitial: true,
2600
+ awaitWriteFinish: { stabilityThreshold: 100 }
2601
+ });
2602
+ watcher.on("change", async (changedPath) => {
2603
+ const file = segmentFiles.find((f) => f.absolutePath === changedPath);
2604
+ if (file) {
2605
+ console.log(pc13.dim(`
2606
+ Changed: ${relative3(process.cwd(), changedPath)}`));
2607
+ await generateStory(file);
2608
+ }
2609
+ });
2610
+ watcher.on("add", async (addedPath) => {
2611
+ console.log(pc13.dim(`
2612
+ Added: ${relative3(process.cwd(), addedPath)}`));
2613
+ const newFiles = await discoverSegmentFiles(config, configDir);
2614
+ const file = newFiles.find((f) => f.absolutePath === addedPath);
2615
+ if (file) {
2616
+ segmentFiles.push(file);
2617
+ await generateStory(file);
2618
+ }
2619
+ });
2620
+ await new Promise(() => {
2621
+ });
2622
+ }
2623
+ return { success: true, generated, outputDir };
2624
+ }
2625
+ function generateCSF3Story(segment, relativePath) {
2626
+ const { meta, variants, props, usage } = segment;
2627
+ const componentName = meta.name;
2628
+ const argTypes = [];
2629
+ if (props) {
2630
+ for (const [propName, propDef] of Object.entries(props)) {
2631
+ let controlType = "text";
2632
+ let controlOptions = "";
2633
+ if (propDef.type === "enum" && propDef.values) {
2634
+ controlType = "select";
2635
+ controlOptions = `,
2636
+ options: ${JSON.stringify(propDef.values)}`;
2637
+ } else if (propDef.type === "boolean") {
2638
+ controlType = "boolean";
2639
+ } else if (propDef.type === "number") {
2640
+ controlType = "number";
2641
+ }
2642
+ argTypes.push(` ${propName}: {
2643
+ control: '${controlType}'${controlOptions},
2644
+ description: ${JSON.stringify(propDef.description || "")},
2645
+ ${propDef.default !== void 0 ? `defaultValue: ${JSON.stringify(propDef.default)},` : ""}
2646
+ }`);
2647
+ }
2648
+ }
2649
+ const storyExports = [];
2650
+ for (const variant of variants) {
2651
+ const storyName = variant.name.replace(/[^a-zA-Z0-9]/g, "");
2652
+ storyExports.push(`
2653
+ export const ${storyName}: Story = {
2654
+ name: ${JSON.stringify(variant.name)},
2655
+ ${variant.description ? `parameters: { docs: { description: { story: ${JSON.stringify(variant.description)} } } },` : ""}
2656
+ };`);
2657
+ }
2658
+ const usageDoc = usage ? `When to use:
2659
+ ${usage.when?.map((w) => `- ${w}`).join("\n") || ""}
2660
+
2661
+ When not to use:
2662
+ ${usage.whenNot?.map((w) => `- ${w}`).join("\n") || ""}` : "";
2663
+ return `/**
2664
+ * Auto-generated Storybook stories from ${relativePath}
2665
+ *
2666
+ * DO NOT EDIT - regenerate with: segments storygen
2667
+ */
2668
+
2669
+ import type { Meta, StoryObj } from '@storybook/react';
2670
+ import { ${componentName} } from '${relativePath.replace(/\.segment\.tsx$/, "/index.js")}';
2671
+
2672
+ const meta: Meta<typeof ${componentName}> = {
2673
+ title: '${meta.category ? `${meta.category.charAt(0).toUpperCase() + meta.category.slice(1)}/` : ""}${componentName}',
2674
+ component: ${componentName},
2675
+ tags: ['autodocs'],
2676
+ parameters: {
2677
+ docs: {
2678
+ description: {
2679
+ component: ${JSON.stringify(meta.description || "")},
2680
+ },
2681
+ },
2682
+ },
2683
+ argTypes: {
2684
+ ${argTypes.join(",\n")}
2685
+ },
2686
+ };
2687
+
2688
+ export default meta;
2689
+ type Story = StoryObj<typeof meta>;
2690
+ ${storyExports.join("\n")}
2691
+ `;
2692
+ }
2693
+
2694
+ // src/commands/metrics.ts
2695
+ import pc14 from "picocolors";
2696
+ async function metrics(component, options = {}) {
2697
+ const { config: configPath, days = 30, json = false } = options;
2698
+ const { configDir } = await loadConfig(configPath);
2699
+ const store = createMetricsStore(configDir);
2700
+ console.log(pc14.cyan(`
2701
+ ${BRAND.name} Compliance Metrics
2702
+ `));
2703
+ const trend = await store.getTrend(component || "all", {
2704
+ days,
2705
+ groupBy: days > 14 ? "week" : "day"
2706
+ });
2707
+ if (json) {
2708
+ console.log(JSON.stringify(trend, null, 2));
2709
+ return { success: true, trend };
2710
+ }
2711
+ const title = component ? `Component: ${component}` : "System-wide";
2712
+ console.log(pc14.bold(title));
2713
+ console.log(pc14.dim(`Last ${days} days
2714
+ `));
2715
+ if (trend.dataPoints.length === 0) {
2716
+ console.log(pc14.yellow("No metrics data found.\n"));
2717
+ console.log(pc14.dim("Metrics are recorded automatically when running verification commands."));
2718
+ console.log(pc14.dim(`Try running: ${pc14.cyan(`${BRAND.cliCommand} verify --ci`)}
2719
+ `));
2720
+ return { success: true, trend };
2721
+ }
2722
+ const sparkline = store.generateSparkline(trend.dataPoints);
2723
+ console.log(pc14.bold("Trend: ") + sparkline);
2724
+ console.log();
2725
+ const trendColor = trend.trend === "improving" ? pc14.green : trend.trend === "declining" ? pc14.red : pc14.dim;
2726
+ const trendIcon = trend.trend === "improving" ? "\u2191" : trend.trend === "declining" ? "\u2193" : "\u2192";
2727
+ console.log(` Average compliance: ${pc14.bold(`${trend.averageCompliance}%`)}`);
2728
+ console.log(` Direction: ${trendColor(`${trendIcon} ${trend.trend}`)}`);
2729
+ console.log(` Data points: ${trend.dataPoints.length}`);
2730
+ console.log();
2731
+ const recent = trend.dataPoints.slice(-5);
2732
+ if (recent.length > 0) {
2733
+ console.log(pc14.dim("Recent data:"));
2734
+ for (const point of recent) {
2735
+ const complianceColor = point.compliance >= 90 ? pc14.green : point.compliance >= 70 ? pc14.yellow : pc14.red;
2736
+ console.log(` ${pc14.dim(point.date)} ${complianceColor(`${point.compliance}%`)} ${pc14.dim(`(${point.violations} violations)`)}`);
2737
+ }
2738
+ console.log();
2739
+ }
2740
+ return { success: true, trend };
2741
+ }
2742
+
2743
+ // src/commands/baseline.ts
2744
+ import { readdir as readdir3, rm as rm2 } from "fs/promises";
2745
+ import { join as join6, relative as relative4 } from "path";
2746
+ import pc15 from "picocolors";
2747
+ async function baseline(action, component, options = {}) {
2748
+ const { config: configPath, variant, all = false, theme = "light", port = 6006 } = options;
2749
+ const { config, configDir } = await loadConfig(configPath);
2750
+ const storage = new StorageManager({
2751
+ projectRoot: configDir,
2752
+ viewport: config.screenshots?.viewport
2753
+ });
2754
+ await storage.initialize();
2755
+ console.log(pc15.cyan(`
2756
+ ${BRAND.name} Baseline Manager
2757
+ `));
2758
+ const baseUrl = `http://localhost:${port}`;
2759
+ switch (action) {
2760
+ case "update":
2761
+ return updateBaseline(component, options, config, configDir, baseUrl);
2762
+ case "list":
2763
+ return listBaselines(configDir);
2764
+ case "delete":
2765
+ return deleteBaseline(component, options, configDir);
2766
+ default:
2767
+ console.log(pc15.red(`Unknown action: ${action}`));
2768
+ console.log(pc15.dim("Available actions: update, list, delete\n"));
2769
+ process.exit(1);
2770
+ }
2771
+ }
2772
+ async function updateBaseline(component, options, config, configDir, baseUrl) {
2773
+ const { variant, all = false, theme = "light" } = options;
2774
+ if (!component && !all) {
2775
+ const { select } = await import("@inquirer/prompts");
2776
+ const contextResp = await fetch(`${baseUrl}/fragments/context?format=json`);
2777
+ if (!contextResp.ok) {
2778
+ throw new Error("Failed to fetch fragments. Make sure dev server is running.");
2779
+ }
2780
+ const contextData = JSON.parse(await contextResp.text());
2781
+ const segments = contextData.components || [];
2782
+ if (segments.length === 0) {
2783
+ console.log(pc15.yellow("No components found.\n"));
2784
+ return { success: true, action: "update", count: 0 };
2785
+ }
2786
+ component = await select({
2787
+ message: "Select component to update:",
2788
+ choices: segments.map((s) => ({
2789
+ name: s.name,
2790
+ value: s.name
2791
+ }))
2792
+ });
2793
+ }
2794
+ if (all) {
2795
+ console.log(pc15.dim("Updating all baselines...\n"));
2796
+ await runScreenshotCommand(config, configDir, {
2797
+ theme
2798
+ });
2799
+ console.log(pc15.green("\n\u2713 All baselines updated\n"));
2800
+ } else if (component) {
2801
+ console.log(pc15.dim(`Updating baselines for ${component}...
2802
+ `));
2803
+ await runScreenshotCommand(config, configDir, {
2804
+ component,
2805
+ variant,
2806
+ theme
2807
+ });
2808
+ console.log(pc15.green(`
2809
+ \u2713 Baselines updated for ${component}
2810
+ `));
2811
+ }
2812
+ const baselinesDir = join6(configDir, BRAND.dataDir, "baselines");
2813
+ console.log(pc15.dim(`Baselines directory: ${relative4(process.cwd(), baselinesDir)}
2814
+ `));
2815
+ return { success: true, action: "update" };
2816
+ }
2817
+ async function listBaselines(configDir) {
2818
+ const baselinesDir = join6(configDir, BRAND.dataDir, "baselines");
2819
+ try {
2820
+ const files = await readdir3(baselinesDir, { recursive: true });
2821
+ const pngFiles = files.filter((f) => f.endsWith(".png"));
2822
+ if (pngFiles.length === 0) {
2823
+ console.log(pc15.yellow("No baselines found.\n"));
2824
+ console.log(pc15.dim(`Run ${pc15.cyan(`${BRAND.cliCommand} screenshot`)} to capture baselines.
2825
+ `));
2826
+ return { success: true, action: "list", count: 0 };
2827
+ }
2828
+ console.log(pc15.bold("Baselines:\n"));
2829
+ for (const file of pngFiles) {
2830
+ console.log(` ${file}`);
2831
+ }
2832
+ console.log();
2833
+ console.log(pc15.dim(`Total: ${pngFiles.length} baseline(s)
2834
+ `));
2835
+ return { success: true, action: "list", count: pngFiles.length };
2836
+ } catch {
2837
+ console.log(pc15.yellow("No baselines directory found.\n"));
2838
+ console.log(pc15.dim(`Run ${pc15.cyan(`${BRAND.cliCommand} screenshot`)} to capture baselines.
2839
+ `));
2840
+ return { success: true, action: "list", count: 0 };
2841
+ }
2842
+ }
2843
+ async function deleteBaseline(component, options, configDir) {
2844
+ const { all = false } = options;
2845
+ const baselinesDir = join6(configDir, BRAND.dataDir, "baselines");
2846
+ if (all) {
2847
+ const { confirm } = await import("@inquirer/prompts");
2848
+ const confirmed = await confirm({
2849
+ message: "Delete ALL baselines? This cannot be undone.",
2850
+ default: false
2851
+ });
2852
+ if (!confirmed) {
2853
+ console.log(pc15.dim("\nNo changes made.\n"));
2854
+ return { success: true, action: "delete", count: 0 };
2855
+ }
2856
+ await rm2(baselinesDir, { recursive: true, force: true });
2857
+ console.log(pc15.green("\n\u2713 All baselines deleted\n"));
2858
+ return { success: true, action: "delete" };
2859
+ } else if (component) {
2860
+ const componentDir = join6(baselinesDir, component);
2861
+ try {
2862
+ await rm2(componentDir, { recursive: true, force: true });
2863
+ console.log(pc15.green(`\u2713 Baselines deleted for ${component}
2864
+ `));
2865
+ return { success: true, action: "delete" };
2866
+ } catch {
2867
+ console.log(pc15.yellow(`No baselines found for ${component}.
2868
+ `));
2869
+ return { success: true, action: "delete", count: 0 };
2870
+ }
2871
+ } else {
2872
+ console.log(pc15.yellow("Specify a component name or use --all flag.\n"));
2873
+ return { success: false, action: "delete" };
2874
+ }
2875
+ }
2876
+
2877
+ // src/commands/add.ts
2878
+ import { writeFile as writeFile4, mkdir as mkdir5, access as access2 } from "fs/promises";
2879
+ import { resolve as resolve4, join as join7, relative as relative5 } from "path";
2880
+ import pc16 from "picocolors";
2881
+ async function add(name, options = {}) {
2882
+ console.log(pc16.cyan(`
2883
+ ${BRAND.name} Component Scaffold
2884
+ `));
2885
+ let componentName = name;
2886
+ let category = options.category;
2887
+ let template = options.template;
2888
+ let dir = options.dir;
2889
+ const generateComponent = options.component !== false;
2890
+ if (!componentName || !category || !template || !dir) {
2891
+ const { input, select } = await import("@inquirer/prompts");
2892
+ try {
2893
+ if (!componentName) {
2894
+ componentName = await input({
2895
+ message: "Component name:",
2896
+ validate: (value) => {
2897
+ if (!value.trim()) return "Component name is required";
2898
+ if (!/^[A-Za-z][A-Za-z0-9]*$/.test(value)) return "Use PascalCase (e.g., Button, TextField)";
2899
+ return true;
2900
+ }
2901
+ });
2902
+ }
2903
+ if (!template) {
2904
+ template = await select({
2905
+ message: "What type of component is this?",
2906
+ choices: [
2907
+ { name: "Display - Shows information or status", value: "display" },
2908
+ { name: "Action - Triggers an action (button, link)", value: "action" },
2909
+ { name: "Form Input - Accepts user input", value: "form-input" },
2910
+ { name: "Layout - Organizes content structure", value: "layout" }
2911
+ ],
2912
+ default: "display"
2913
+ });
2914
+ }
2915
+ if (!category) {
2916
+ category = await input({
2917
+ message: "Category:",
2918
+ default: template === "form-input" ? "forms" : template === "layout" ? "layout" : "components"
2919
+ });
2920
+ }
2921
+ if (!dir) {
2922
+ dir = await input({
2923
+ message: "Output directory:",
2924
+ default: "src/components"
2925
+ });
2926
+ }
2927
+ } catch {
2928
+ console.log(pc16.dim("\nNo changes made."));
2929
+ process.exit(0);
2930
+ }
2931
+ }
2932
+ componentName = componentName.charAt(0).toUpperCase() + componentName.slice(1);
2933
+ category = category || "components";
2934
+ template = template || "display";
2935
+ dir = dir || "src/components";
2936
+ const componentDir = resolve4(process.cwd(), dir, componentName);
2937
+ const componentFile = join7(componentDir, `${componentName}.tsx`);
2938
+ const segmentFile = join7(componentDir, `${componentName}${BRAND.fileExtension}`);
2939
+ const indexFile = join7(componentDir, "index.ts");
2940
+ try {
2941
+ await access2(componentDir);
2942
+ console.log(pc16.yellow(`Directory already exists: ${relative5(process.cwd(), componentDir)}`));
2943
+ console.log(pc16.dim("Use a different name or remove the existing directory."));
2944
+ process.exit(1);
2945
+ } catch {
2946
+ }
2947
+ await mkdir5(componentDir, { recursive: true });
2948
+ if (generateComponent) {
2949
+ const componentCode = generateComponentStub(componentName, template);
2950
+ await writeFile4(componentFile, componentCode);
2951
+ console.log(`${pc16.green("\u2713")} Created ${relative5(process.cwd(), componentFile)}`);
2952
+ }
2953
+ const segmentCode = generateSegmentStub(componentName, category, template);
2954
+ await writeFile4(segmentFile, segmentCode);
2955
+ console.log(`${pc16.green("\u2713")} Created ${relative5(process.cwd(), segmentFile)}`);
2956
+ const indexCode = `export { ${componentName} } from './${componentName}.js';
2957
+ `;
2958
+ await writeFile4(indexFile, indexCode);
2959
+ console.log(`${pc16.green("\u2713")} Created ${relative5(process.cwd(), indexFile)}`);
2960
+ console.log(pc16.green(`
2961
+ \u2713 Scaffolded ${componentName}
2962
+ `));
2963
+ console.log(pc16.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2964
+ console.log(pc16.bold("\nNext steps:"));
2965
+ if (generateComponent) {
2966
+ console.log(` 1. Implement ${componentName}.tsx`);
2967
+ }
2968
+ console.log(` 2. Fill in usage.when and usage.whenNot in the fragment file`);
2969
+ console.log(` 3. Add variants with different prop combinations`);
2970
+ console.log(` 4. Run ${pc16.cyan(`${BRAND.cliCommand} dev`)} to preview`);
2971
+ console.log();
2972
+ return {
2973
+ success: true,
2974
+ componentPath: generateComponent ? componentFile : void 0,
2975
+ segmentPath: segmentFile,
2976
+ indexPath: indexFile
2977
+ };
2978
+ }
2979
+ function generateComponentStub(name, _template) {
2980
+ const propsInterface = `export interface ${name}Props {
2981
+ /** Content to display */
2982
+ children?: React.ReactNode;
2983
+ /** Additional CSS classes */
2984
+ className?: string;
2985
+ }`;
2986
+ return `import React from 'react';
2987
+
2988
+ ${propsInterface}
2989
+
2990
+ /**
2991
+ * ${name} component
2992
+ *
2993
+ * TODO: Implement this component
2994
+ */
2995
+ export function ${name}({ children, className }: ${name}Props) {
2996
+ return (
2997
+ <div className={className}>
2998
+ {children}
2999
+ </div>
3000
+ );
3001
+ }
3002
+ `;
3003
+ }
3004
+ function generateSegmentStub(name, category, template) {
3005
+ const usageHints = {
3006
+ action: {
3007
+ when: ["User needs to trigger an action", "Form submission is required"],
3008
+ whenNot: ["Navigation is needed (use Link)", "Action is destructive without confirmation"]
3009
+ },
3010
+ "form-input": {
3011
+ when: ["User needs to enter data", "Form field is required"],
3012
+ whenNot: ["Display-only data is shown", "Rich text editing is needed"]
3013
+ },
3014
+ layout: {
3015
+ when: ["Content needs to be organized", "Responsive layout is required"],
3016
+ whenNot: ["Simple inline content is displayed", "Scroll container is needed"]
3017
+ },
3018
+ display: {
3019
+ when: ["Information needs to be presented", "Status or feedback is shown"],
3020
+ whenNot: ["User interaction is required", "Data input is needed"]
3021
+ }
3022
+ };
3023
+ const hints = usageHints[template] || usageHints.display;
3024
+ const scenarioTagHints = {
3025
+ action: ["action.primary", "action.secondary", "form.submit"],
3026
+ "form-input": ["form.input", "form.field", "form.text"],
3027
+ layout: ["layout.container", "layout.section", "content.group"],
3028
+ display: ["display.info", "display.status", "content.text"]
3029
+ };
3030
+ const scenarioTags = scenarioTagHints[template] || scenarioTagHints.display;
3031
+ return `import React from 'react';
3032
+ import { defineSegment } from '@fragments/core';
3033
+ import { ${name} } from './index.js';
3034
+
3035
+ export default defineSegment({
3036
+ component: ${name},
3037
+
3038
+ meta: {
3039
+ name: '${name}',
3040
+ description: 'TODO: Add description',
3041
+ category: '${category}',
3042
+ status: 'experimental',
3043
+ tags: ['${category}'],
3044
+ },
3045
+
3046
+ usage: {
3047
+ when: [
3048
+ '${hints.when[0]}',
3049
+ '${hints.when[1]}',
3050
+ ],
3051
+ whenNot: [
3052
+ '${hints.whenNot[0]}',
3053
+ '${hints.whenNot[1]}',
3054
+ ],
3055
+ guidelines: [
3056
+ // TODO: Add best practices
3057
+ ],
3058
+ accessibility: [
3059
+ // TODO: Add accessibility guidelines
3060
+ ],
3061
+ },
3062
+
3063
+ props: {
3064
+ children: {
3065
+ type: 'node',
3066
+ description: 'Content to display',
3067
+ },
3068
+ className: {
3069
+ type: 'string',
3070
+ description: 'Additional CSS classes',
3071
+ },
3072
+ // TODO: Add more props
3073
+ },
3074
+
3075
+ relations: [
3076
+ // TODO: Add related components
3077
+ // { component: 'RelatedComponent', relationship: 'alternative', note: 'Use for...' },
3078
+ ],
3079
+
3080
+ contract: {
3081
+ propsSummary: [
3082
+ 'children: ReactNode - content to display',
3083
+ 'className: string - additional CSS classes',
3084
+ // TODO: Add prop summaries
3085
+ ],
3086
+ scenarioTags: [
3087
+ '${scenarioTags[0]}',
3088
+ '${scenarioTags[1]}',
3089
+ // TODO: Add scenario tags for AI agent queries
3090
+ ],
3091
+ a11yRules: [
3092
+ // TODO: Add accessibility rule IDs
3093
+ ],
3094
+ },
3095
+
3096
+ variants: [
3097
+ {
3098
+ name: 'Default',
3099
+ description: 'Default ${name} appearance',
3100
+ render: () => <${name}>Example content</${name}>,
3101
+ },
3102
+ // TODO: Add more variants
3103
+ // {
3104
+ // name: 'WithProps',
3105
+ // description: '${name} with additional props',
3106
+ // render: () => <${name} someProp="value">Content</${name}>,
3107
+ // },
3108
+ ],
3109
+ });
3110
+ `;
3111
+ }
3112
+
3113
+ // src/commands/link/figma.ts
3114
+ import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
3115
+ import pc17 from "picocolors";
3116
+ async function linkFigma(figmaUrl, options = {}) {
3117
+ const { config: configPath, auto = false, dryRun = false, variants = true } = options;
3118
+ if (!process.env.FIGMA_ACCESS_TOKEN) {
3119
+ console.error(pc17.red("\nFIGMA_ACCESS_TOKEN environment variable required."));
3120
+ console.log(pc17.dim("Generate at: https://www.figma.com/developers/api#access-tokens"));
3121
+ console.log(pc17.dim(" export FIGMA_ACCESS_TOKEN=figd_xxx"));
3122
+ process.exit(1);
3123
+ }
3124
+ console.log(pc17.cyan(`
3125
+ ${BRAND.name} Link Wizard
3126
+ `));
3127
+ const { config, configDir } = await loadConfig(configPath);
3128
+ let fileUrl = figmaUrl || config.figmaFile;
3129
+ if (!fileUrl) {
3130
+ const { input } = await import("@inquirer/prompts");
3131
+ console.log(pc17.dim("Tip: Add `figmaFile` to fragments.config.ts to skip this prompt\n"));
3132
+ try {
3133
+ fileUrl = await input({
3134
+ message: "Enter Figma file URL:",
3135
+ validate: (value) => {
3136
+ if (!value.trim()) return "URL is required";
3137
+ if (!value.includes("figma.com")) return "Please enter a valid Figma URL";
3138
+ return true;
3139
+ }
3140
+ });
3141
+ } catch {
3142
+ console.log(pc17.dim("\nNo changes made."));
3143
+ process.exit(0);
3144
+ }
3145
+ } else if (config.figmaFile && !figmaUrl) {
3146
+ console.log(pc17.dim(`Using Figma file from config: ${config.figmaFile}
3147
+ `));
3148
+ }
3149
+ const figmaClient = new FigmaClient({ accessToken: process.env.FIGMA_ACCESS_TOKEN });
3150
+ console.log(pc17.dim("Parsing Figma URL..."));
3151
+ const { fileKey } = figmaClient.parseFileUrl(fileUrl);
3152
+ console.log(pc17.dim("Fetching components from Figma..."));
3153
+ const figmaData = await figmaClient.getFileComponents(fileKey);
3154
+ const allFigmaComponents = figmaData.componentSets.length > 0 ? figmaData.componentSets : figmaData.components;
3155
+ if (allFigmaComponents.length === 0) {
3156
+ console.log(pc17.yellow("\nNo components found in Figma file."));
3157
+ console.log(pc17.dim("Make sure the file contains published components."));
3158
+ process.exit(0);
3159
+ }
3160
+ const componentType = figmaData.componentSets.length > 0 ? "component set" : "component";
3161
+ console.log(pc17.green(`\u2713 Found ${allFigmaComponents.length} Figma ${componentType}(s) in "${figmaData.fileName}"`));
3162
+ if (figmaData.components.length > 0 && figmaData.componentSets.length > 0) {
3163
+ console.log(pc17.dim(` (${figmaData.components.length} individual components also available)
3164
+ `));
3165
+ } else {
3166
+ console.log();
3167
+ }
3168
+ const segmentFiles = await discoverSegmentFiles(config, configDir);
3169
+ if (segmentFiles.length === 0) {
3170
+ console.log(pc17.yellow("No segment files found in codebase."));
3171
+ console.log(pc17.dim(`Looking for: ${config.include.join(", ")}`));
3172
+ process.exit(0);
3173
+ }
3174
+ console.log(pc17.dim(`Found ${segmentFiles.length} segment file(s)
3175
+ `));
3176
+ const segments = [];
3177
+ for (const file of segmentFiles) {
3178
+ try {
3179
+ const content = await readFile4(file.absolutePath, "utf-8");
3180
+ const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
3181
+ const hasFigma = /meta:\s*\{[^}]*figma:\s*['"]https?:/.test(content);
3182
+ const segmentVariants = extractVariants(content, nameMatch?.[1]);
3183
+ if (nameMatch) {
3184
+ segments.push({
3185
+ name: nameMatch[1],
3186
+ filePath: file.absolutePath,
3187
+ relativePath: file.relativePath,
3188
+ hasFigma,
3189
+ variants: segmentVariants
3190
+ });
3191
+ }
3192
+ } catch {
3193
+ }
3194
+ }
3195
+ const matches = [];
3196
+ const unmatchedSegments = [];
3197
+ for (const segment of segments) {
3198
+ let bestMatch = null;
3199
+ let bestScore = 0;
3200
+ for (const figmaComp of allFigmaComponents) {
3201
+ const score = calculateMatchScore(segment.name, figmaComp.name);
3202
+ if (score > bestScore) {
3203
+ bestMatch = figmaComp;
3204
+ bestScore = score;
3205
+ if (score === 100) break;
3206
+ }
3207
+ }
3208
+ if (bestMatch && bestScore >= 65) {
3209
+ const alreadyLinked = segment.hasFigma;
3210
+ if (alreadyLinked && !auto) {
3211
+ console.log(pc17.dim(`\u23ED\uFE0F ${segment.name} (already linked)`));
3212
+ }
3213
+ matches.push({ segment, figmaComponent: bestMatch, score: bestScore, alreadyLinked });
3214
+ } else {
3215
+ unmatchedSegments.push(segment);
3216
+ }
3217
+ }
3218
+ if (unmatchedSegments.length > 0) {
3219
+ console.log(pc17.dim("Unmatched segments:"));
3220
+ for (const seg of unmatchedSegments) {
3221
+ console.log(` ${pc17.dim("\u2022")} ${seg.name}`);
3222
+ }
3223
+ console.log();
3224
+ }
3225
+ const newMatches = matches.filter((m) => !m.alreadyLinked);
3226
+ const alreadyLinkedMatches = matches.filter((m) => m.alreadyLinked);
3227
+ if (matches.length === 0) {
3228
+ console.log(pc17.yellow("\nNo automatic matches found."));
3229
+ console.log(pc17.dim("You can manually add figma URLs to your segment definitions."));
3230
+ process.exit(0);
3231
+ }
3232
+ if (dryRun) {
3233
+ if (newMatches.length > 0) {
3234
+ console.log(pc17.bold("\nMatched Components:\n"));
3235
+ for (const match of newMatches) {
3236
+ const scoreColor = match.score === 100 ? pc17.green : pc17.yellow;
3237
+ console.log(
3238
+ ` ${pc17.green("\u2713")} ${pc17.bold(match.segment.name)} \u2192 ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
3239
+ );
3240
+ }
3241
+ }
3242
+ console.log(pc17.yellow("\n[Dry run - no files were updated]"));
3243
+ return { success: true, updated: 0, variantUpdates: 0 };
3244
+ }
3245
+ let selectedMatches = newMatches;
3246
+ if (newMatches.length > 0) {
3247
+ if (auto) {
3248
+ console.log(pc17.bold("\nMatched Components:\n"));
3249
+ for (const match of newMatches) {
3250
+ const scoreColor = match.score === 100 ? pc17.green : pc17.yellow;
3251
+ console.log(
3252
+ ` ${pc17.green("\u2713")} ${pc17.bold(match.segment.name)} \u2192 ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`
3253
+ );
3254
+ }
3255
+ } else {
3256
+ const { checkbox } = await import("@inquirer/prompts");
3257
+ console.log(pc17.bold("\nMatched Components:\n"));
3258
+ console.log(pc17.dim("Use \u2191/\u2193 to navigate, Space to toggle, Enter to confirm\n"));
3259
+ const choices = newMatches.map((match) => {
3260
+ const scoreColor = match.score === 100 ? pc17.green : pc17.yellow;
3261
+ return {
3262
+ name: `${pc17.bold(match.segment.name)} \u2192 ${match.figmaComponent.name} ${scoreColor(`(${Math.round(match.score)}%)`)}`,
3263
+ value: match,
3264
+ checked: true
3265
+ };
3266
+ });
3267
+ try {
3268
+ selectedMatches = await checkbox({
3269
+ message: "Select components to link:",
3270
+ choices,
3271
+ pageSize: 20
3272
+ });
3273
+ } catch {
3274
+ console.log(pc17.dim("\nNo changes made."));
3275
+ return { success: true, updated: 0, variantUpdates: 0 };
3276
+ }
3277
+ }
3278
+ }
3279
+ const allSelectedMatches = [...selectedMatches, ...alreadyLinkedMatches];
3280
+ let updated = 0;
3281
+ for (const match of selectedMatches) {
3282
+ if (match.alreadyLinked) continue;
3283
+ try {
3284
+ let content = await readFile4(match.segment.filePath, "utf-8");
3285
+ const figmaUrlToInsert = figmaClient.buildNodeUrl(
3286
+ match.figmaComponent.file_key,
3287
+ match.figmaComponent.node_id,
3288
+ figmaData.fileName
3289
+ );
3290
+ if (/meta:\s*\{[^}]*figma:/.test(content)) {
3291
+ content = content.replace(
3292
+ /(meta:\s*\{[^}]*figma:\s*['"])([^'"]*)['"]/,
3293
+ `$1${figmaUrlToInsert}'`
3294
+ );
3295
+ } else {
3296
+ content = content.replace(
3297
+ /(meta:\s*\{[^}]*category:\s*['"][^'"]*['"],?)/,
3298
+ `$1
3299
+ figma: '${figmaUrlToInsert}',`
3300
+ );
3301
+ }
3302
+ await writeFile5(match.segment.filePath, content);
3303
+ updated++;
3304
+ console.log(` ${pc17.green("\u2713")} Updated ${match.segment.relativePath}`);
3305
+ } catch (error) {
3306
+ console.log(` ${pc17.red("\u2717")} Failed to update ${match.segment.relativePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
3307
+ }
3308
+ }
3309
+ if (updated > 0) {
3310
+ console.log(pc17.green(`
3311
+ \u2713 Updated ${updated} segment file(s)
3312
+ `));
3313
+ }
3314
+ let variantUpdates = 0;
3315
+ if (variants && figmaData.componentSets.length > 0) {
3316
+ variantUpdates = await linkVariants(
3317
+ allSelectedMatches,
3318
+ figmaData,
3319
+ figmaClient,
3320
+ fileKey
3321
+ );
3322
+ }
3323
+ console.log(pc17.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
3324
+ console.log(pc17.bold("\nNext steps:"));
3325
+ console.log(` 1. Run ${pc17.cyan(`${BRAND.cliCommand} compare --all`)} to verify designs`);
3326
+ console.log(` 2. Add figmaProps mappings for prop-level linking`);
3327
+ console.log();
3328
+ return { success: true, updated, variantUpdates };
3329
+ }
3330
+ function extractVariants(content, componentName) {
3331
+ const variants = [];
3332
+ const variantsArrayMatch = content.match(/variants:\s*\[/);
3333
+ if (variantsArrayMatch && variantsArrayMatch.index !== void 0) {
3334
+ const variantsStart = variantsArrayMatch.index + variantsArrayMatch[0].length;
3335
+ let bracketCount = 1;
3336
+ let variantsEnd = variantsStart;
3337
+ while (bracketCount > 0 && variantsEnd < content.length) {
3338
+ if (content[variantsEnd] === "[") bracketCount++;
3339
+ if (content[variantsEnd] === "]") bracketCount--;
3340
+ variantsEnd++;
3341
+ }
3342
+ const variantsSection = content.slice(variantsStart, variantsEnd - 1);
3343
+ const variantNameRegex = /\{\s*\n?\s*name:\s*['"]([^'"]+)['"]/g;
3344
+ let variantMatch;
3345
+ while ((variantMatch = variantNameRegex.exec(variantsSection)) !== null) {
3346
+ const variantName = variantMatch[1];
3347
+ if (componentName && variantName === componentName) continue;
3348
+ const objectStart = variantMatch.index;
3349
+ let braceCount = 1;
3350
+ let objectEnd = objectStart + 1;
3351
+ while (braceCount > 0 && objectEnd < variantsSection.length) {
3352
+ if (variantsSection[objectEnd] === "{") braceCount++;
3353
+ if (variantsSection[objectEnd] === "}") braceCount--;
3354
+ objectEnd++;
3355
+ }
3356
+ const objectContent = variantsSection.slice(objectStart, objectEnd);
3357
+ const variantHasFigma = /figma:\s*['"]https?:/.test(objectContent);
3358
+ variants.push({
3359
+ name: variantName,
3360
+ hasFigma: variantHasFigma
3361
+ });
3362
+ }
3363
+ }
3364
+ return variants;
3365
+ }
3366
+ function calculateMatchScore(segmentName, figmaName) {
3367
+ const normalizeForMatch = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
3368
+ const normalizedSegment = normalizeForMatch(segmentName);
3369
+ const normalizedFigma = normalizeForMatch(figmaName);
3370
+ if (normalizedSegment === normalizedFigma) {
3371
+ return 100;
3372
+ }
3373
+ if (normalizedFigma.startsWith(normalizedSegment)) {
3374
+ const coverage = normalizedSegment.length / normalizedFigma.length;
3375
+ return Math.max(85, coverage * 100);
3376
+ }
3377
+ if (normalizedSegment.startsWith(normalizedFigma)) {
3378
+ const coverage = normalizedFigma.length / normalizedSegment.length;
3379
+ return Math.max(80, coverage * 100);
3380
+ }
3381
+ const getWords = (s) => {
3382
+ return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]+/g, " ").toLowerCase().split(/\s+/).filter((w) => w.length > 0);
3383
+ };
3384
+ const segmentWords = getWords(segmentName);
3385
+ const figmaWords = getWords(figmaName);
3386
+ const allSegmentWordsInFigma = segmentWords.every(
3387
+ (sw) => figmaWords.some((fw) => fw === sw || fw.startsWith(sw) || sw.startsWith(fw))
3388
+ );
3389
+ if (allSegmentWordsInFigma && segmentWords.length > 0) {
3390
+ const wordOverlap = segmentWords.length / Math.max(segmentWords.length, figmaWords.length);
3391
+ return Math.max(75, wordOverlap * 95);
3392
+ }
3393
+ if (normalizedFigma.includes(normalizedSegment)) {
3394
+ return 70;
3395
+ }
3396
+ if (normalizedSegment.includes(normalizedFigma)) {
3397
+ return 65;
3398
+ }
3399
+ return 0;
3400
+ }
3401
+ async function linkVariants(matches, figmaData, figmaClient, fileKey) {
3402
+ console.log(pc17.bold("\nLinking variants...\n"));
3403
+ const matchedComponentSets = matches.map((m) => m.figmaComponent).filter((c) => figmaData.componentSets.some((cs) => cs.key === c.key));
3404
+ if (matchedComponentSets.length === 0) {
3405
+ return 0;
3406
+ }
3407
+ console.log(pc17.dim("Fetching Figma variants..."));
3408
+ const componentSetVariants = await figmaClient.getComponentSetVariants(fileKey, matchedComponentSets);
3409
+ let variantUpdates = 0;
3410
+ const { select } = await import("@inquirer/prompts");
3411
+ const normalizeForMatch = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
3412
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3413
+ for (const match of matches) {
3414
+ const csWithVariants = componentSetVariants.find(
3415
+ (cs) => cs.componentSet.key === match.figmaComponent.key
3416
+ );
3417
+ if (!csWithVariants || csWithVariants.variants.length === 0) {
3418
+ continue;
3419
+ }
3420
+ const segmentVariants = match.segment.variants.filter((v) => !v.hasFigma);
3421
+ if (segmentVariants.length === 0) {
3422
+ console.log(pc17.dim(` \u23ED\uFE0F ${match.segment.name}: all variants already linked`));
3423
+ continue;
3424
+ }
3425
+ console.log(pc17.dim(` ${match.segment.name}: ${csWithVariants.variants.length} Figma variants`));
3426
+ for (const segmentVariant of segmentVariants) {
3427
+ const variantMatches = [];
3428
+ for (const fv of csWithVariants.variants) {
3429
+ const normalizedSegment = normalizeForMatch(segmentVariant.name);
3430
+ for (const value of fv.values) {
3431
+ const normalizedValue = normalizeForMatch(value);
3432
+ if (normalizedSegment === normalizedValue) {
3433
+ variantMatches.push({ figmaVariant: fv, score: 100 });
3434
+ break;
3435
+ } else if (normalizedValue.includes(normalizedSegment)) {
3436
+ variantMatches.push({ figmaVariant: fv, score: 85 });
3437
+ } else if (normalizedSegment.includes(normalizedValue)) {
3438
+ variantMatches.push({ figmaVariant: fv, score: 75 });
3439
+ }
3440
+ }
3441
+ }
3442
+ variantMatches.sort((a, b) => b.score - a.score);
3443
+ if (variantMatches.length > 0 && variantMatches[0].score === 100) {
3444
+ const bestMatch = variantMatches[0];
3445
+ const variantUrl = figmaClient.buildNodeUrl(
3446
+ match.figmaComponent.file_key,
3447
+ bestMatch.figmaVariant.node_id,
3448
+ figmaData.fileName
3449
+ );
3450
+ try {
3451
+ let content = await readFile4(match.segment.filePath, "utf-8");
3452
+ const namePattern = new RegExp(
3453
+ `(name:\\s*['"]${escapeRegExp(segmentVariant.name)}['"],?)`,
3454
+ "g"
3455
+ );
3456
+ let replaced = false;
3457
+ content = content.replace(namePattern, (matchedStr) => {
3458
+ if (replaced) return matchedStr;
3459
+ replaced = true;
3460
+ return `${matchedStr}
3461
+ figma: '${variantUrl}',`;
3462
+ });
3463
+ await writeFile5(match.segment.filePath, content);
3464
+ variantUpdates++;
3465
+ console.log(
3466
+ ` ${pc17.green("\u2713")} ${segmentVariant.name} \u2192 ${bestMatch.figmaVariant.name}`
3467
+ );
3468
+ } catch (error) {
3469
+ console.log(
3470
+ ` ${pc17.red("\u2717")} ${segmentVariant.name}: ${error instanceof Error ? error.message : "Unknown error"}`
3471
+ );
3472
+ }
3473
+ } else if (variantMatches.length > 0) {
3474
+ const choices = [
3475
+ ...variantMatches.slice(0, 4).map((m) => ({
3476
+ name: `${m.figmaVariant.name} (${m.score}%)`,
3477
+ value: m.figmaVariant
3478
+ })),
3479
+ { name: "Skip this variant", value: null }
3480
+ ];
3481
+ try {
3482
+ const selectedVariant = await select({
3483
+ message: ` Match for "${segmentVariant.name}":`,
3484
+ choices
3485
+ });
3486
+ if (selectedVariant) {
3487
+ const variantUrl = figmaClient.buildNodeUrl(
3488
+ match.figmaComponent.file_key,
3489
+ selectedVariant.node_id,
3490
+ figmaData.fileName
3491
+ );
3492
+ let content = await readFile4(match.segment.filePath, "utf-8");
3493
+ const namePattern = new RegExp(
3494
+ `(name:\\s*['"]${escapeRegExp(segmentVariant.name)}['"],?)`,
3495
+ "g"
3496
+ );
3497
+ let replaced = false;
3498
+ content = content.replace(namePattern, (matchedStr) => {
3499
+ if (replaced) return matchedStr;
3500
+ replaced = true;
3501
+ return `${matchedStr}
3502
+ figma: '${variantUrl}',`;
3503
+ });
3504
+ await writeFile5(match.segment.filePath, content);
3505
+ variantUpdates++;
3506
+ console.log(
3507
+ ` ${pc17.green("\u2713")} ${segmentVariant.name} \u2192 ${selectedVariant.name}`
3508
+ );
3509
+ } else {
3510
+ console.log(` ${pc17.dim("\u23ED\uFE0F")} ${segmentVariant.name} (skipped)`);
3511
+ }
3512
+ } catch {
3513
+ console.log(` ${pc17.dim("\u23ED\uFE0F")} ${segmentVariant.name} (cancelled)`);
3514
+ }
3515
+ } else {
3516
+ console.log(` ${pc17.yellow("?")} ${segmentVariant.name}: no matching Figma variant`);
3517
+ }
3518
+ }
3519
+ }
3520
+ if (variantUpdates > 0) {
3521
+ console.log(pc17.green(`
3522
+ \u2713 Linked ${variantUpdates} variant(s)
3523
+ `));
3524
+ } else {
3525
+ console.log(pc17.dim("\nNo variant updates made.\n"));
3526
+ }
3527
+ return variantUpdates;
3528
+ }
3529
+
3530
+ // src/commands/link/storybook.ts
3531
+ import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
3532
+ import { join as join8, dirname as dirname3, relative as relative6 } from "path";
3533
+ import pc18 from "picocolors";
3534
+ async function linkStorybook(options = {}) {
3535
+ const { out, yes = false, dryRun = false, verbose = false } = options;
3536
+ console.log(pc18.cyan(`
3537
+ ${BRAND.name} Storybook Link
3538
+ `));
3539
+ const projectRoot = process.cwd();
3540
+ console.log(pc18.dim("Detecting Storybook configuration..."));
3541
+ const sbConfig = await detectStorybookConfig(projectRoot);
3542
+ if (!sbConfig) {
3543
+ console.log(pc18.yellow("\nNo Storybook configuration found."));
3544
+ console.log(pc18.dim("Looking for: .storybook/main.ts, .storybook/main.js, etc."));
3545
+ console.log(pc18.dim("\nUse --config to specify a custom path."));
3546
+ process.exit(1);
3547
+ }
3548
+ console.log(pc18.green(`\u2713 Found: ${sbConfig.configPath}`));
3549
+ if (sbConfig.framework) {
3550
+ console.log(pc18.dim(` Framework: ${sbConfig.framework}`));
3551
+ }
3552
+ if (sbConfig.errors?.length) {
3553
+ for (const err of sbConfig.errors) {
3554
+ console.log(pc18.yellow(` Warning: ${err}`));
3555
+ }
3556
+ }
3557
+ console.log(pc18.dim("\nDiscovering story files..."));
3558
+ const storyFiles = await discoverStoryFiles(projectRoot, sbConfig.storyPatterns);
3559
+ if (storyFiles.length === 0) {
3560
+ console.log(pc18.yellow("\nNo story files found."));
3561
+ console.log(pc18.dim(`Patterns: ${sbConfig.storyPatterns.join(", ")}`));
3562
+ process.exit(1);
3563
+ }
3564
+ console.log(pc18.green(`\u2713 Found ${storyFiles.length} story file(s)
3565
+ `));
3566
+ const previews = [];
3567
+ let parseErrors = 0;
3568
+ const total = storyFiles.length;
3569
+ console.log(pc18.dim(`Analyzing ${total} stories...
3570
+ `));
3571
+ for (let i = 0; i < storyFiles.length; i++) {
3572
+ const storyFile = storyFiles[i];
3573
+ const relativePath = relative6(projectRoot, storyFile);
3574
+ process.stdout.write(`\r ${pc18.dim(`[${i + 1}/${total}]`)} ${relativePath.slice(0, 60).padEnd(60)}`);
3575
+ try {
3576
+ const parsed = await parseStoryFile(storyFile);
3577
+ const result = convertToSegment(parsed);
3578
+ const outputFile = out ? join8(out, relativePath.replace(/\.stories\.(tsx?|jsx?)$/, BRAND.fileExtension)) : result.outputFile;
3579
+ previews.push({
3580
+ componentName: result.componentName,
3581
+ sourceFile: relativePath,
3582
+ outputFile: relative6(projectRoot, outputFile),
3583
+ variantCount: result.variantCount,
3584
+ propCount: result.propCount,
3585
+ confidence: result.confidence,
3586
+ warnings: result.warnings
3587
+ });
3588
+ } catch (error) {
3589
+ parseErrors++;
3590
+ if (verbose) {
3591
+ process.stdout.write("\n");
3592
+ console.log(pc18.red(`\u2717 ${relativePath}: ${error instanceof Error ? error.message : "Parse error"}`));
3593
+ }
3594
+ }
3595
+ }
3596
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
3597
+ console.log(pc18.bold("Preview:\n"));
3598
+ console.log(pc18.dim("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
3599
+ console.log(pc18.dim("\u2502") + " Component".padEnd(20) + pc18.dim("\u2502") + " Stories".padEnd(10) + pc18.dim("\u2502") + " Props".padEnd(8) + pc18.dim("\u2502") + " Confidence".padEnd(13) + pc18.dim("\u2502"));
3600
+ console.log(pc18.dim("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
3601
+ for (const item of previews) {
3602
+ const confColor = item.confidence >= 0.8 ? pc18.green : item.confidence >= 0.5 ? pc18.yellow : pc18.red;
3603
+ const confLabel = item.confidence >= 0.8 ? "high" : item.confidence >= 0.5 ? "medium" : "low";
3604
+ console.log(
3605
+ pc18.dim("\u2502") + ` ${item.componentName}`.padEnd(20).slice(0, 20) + pc18.dim("\u2502") + ` ${item.variantCount}`.padEnd(10) + pc18.dim("\u2502") + ` ${item.propCount}`.padEnd(8) + pc18.dim("\u2502") + ` ${confColor(confLabel)}`.padEnd(13 + (confColor === pc18.green ? 10 : confColor === pc18.yellow ? 11 : 9)) + pc18.dim("\u2502")
3606
+ );
3607
+ }
3608
+ console.log(pc18.dim("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
3609
+ if (parseErrors > 0) {
3610
+ console.log(pc18.yellow(`
3611
+ ${parseErrors} file(s) could not be parsed (use --verbose for details)`));
3612
+ }
3613
+ if (previews.length === 0) {
3614
+ console.log(pc18.yellow("\nNo stories could be parsed successfully."));
3615
+ return { success: true, generated: 0 };
3616
+ }
3617
+ if (dryRun) {
3618
+ console.log(pc18.dim(`
3619
+ ${previews.length} segment file(s) would be created`));
3620
+ console.log(pc18.yellow("\n[Dry run - no files were written]"));
3621
+ return { success: true, generated: 0 };
3622
+ }
3623
+ let selectedPreviews = previews;
3624
+ if (yes) {
3625
+ console.log(pc18.dim(`
3626
+ ${previews.length} segment file(s) will be created`));
3627
+ } else {
3628
+ const { checkbox } = await import("@inquirer/prompts");
3629
+ console.log(pc18.dim("\nUse \u2191/\u2193 to navigate, Space to toggle, Enter to confirm\n"));
3630
+ const choices = previews.map((item) => {
3631
+ const confColor = item.confidence >= 0.8 ? pc18.green : item.confidence >= 0.5 ? pc18.yellow : pc18.red;
3632
+ const confLabel = item.confidence >= 0.8 ? "high" : item.confidence >= 0.5 ? "medium" : "low";
3633
+ return {
3634
+ name: `${pc18.bold(item.componentName)} ${pc18.dim(`(${item.variantCount} stories, ${confLabel} confidence)`)}`,
3635
+ value: item,
3636
+ checked: true
3637
+ };
3638
+ });
3639
+ try {
3640
+ selectedPreviews = await checkbox({
3641
+ message: "Select stories to convert:",
3642
+ choices,
3643
+ pageSize: 15
3644
+ });
3645
+ } catch {
3646
+ console.log(pc18.dim("\nNo changes made."));
3647
+ return { success: true, generated: 0 };
3648
+ }
3649
+ if (selectedPreviews.length === 0) {
3650
+ console.log(pc18.dim("\nNo stories selected. No changes made."));
3651
+ return { success: true, generated: 0 };
3652
+ }
3653
+ }
3654
+ const genTotal = selectedPreviews.length;
3655
+ console.log(pc18.dim(`
3656
+ Generating ${genTotal} segment file(s)...
3657
+ `));
3658
+ let generated = 0;
3659
+ let genErrors = 0;
3660
+ for (let i = 0; i < selectedPreviews.length; i++) {
3661
+ const preview = selectedPreviews[i];
3662
+ const storyFile = join8(projectRoot, preview.sourceFile);
3663
+ try {
3664
+ const parsed = await parseStoryFile(storyFile);
3665
+ const result = convertToSegment(parsed);
3666
+ const outputFile = out ? join8(projectRoot, out, preview.sourceFile.replace(/\.stories\.(tsx?|jsx?)$/, BRAND.fileExtension)) : result.outputFile;
3667
+ await mkdir6(dirname3(outputFile), { recursive: true });
3668
+ await writeFile6(outputFile, result.code);
3669
+ generated++;
3670
+ console.log(`${pc18.dim(`[${i + 1}/${genTotal}]`)} ${pc18.green("\u2713")} ${result.componentName}`);
3671
+ } catch (error) {
3672
+ genErrors++;
3673
+ console.log(`${pc18.dim(`[${i + 1}/${genTotal}]`)} ${pc18.red("\u2717")} ${preview.componentName}`);
3674
+ }
3675
+ }
3676
+ console.log(pc18.green(`
3677
+ \u2713 Generated ${generated} segment file(s)
3678
+ `));
3679
+ console.log(pc18.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
3680
+ console.log(pc18.bold("\nNext steps:"));
3681
+ console.log(` 1. Review generated ${BRAND.fileExtension} files`);
3682
+ console.log(` 2. Fill in usage.when and usage.whenNot fields`);
3683
+ console.log(` 3. Run ${pc18.cyan(`${BRAND.cliCommand} build`)} to compile`);
3684
+ console.log(` 4. Run ${pc18.cyan(`${BRAND.cliCommand} dev`)} to view your design system`);
3685
+ console.log();
3686
+ return { success: true, generated };
3687
+ }
3688
+
3689
+ // src/commands/enhance.ts
3690
+ import pc19 from "picocolors";
3691
+ import { readFile as readFile5, writeFile as writeFile7 } from "fs/promises";
3692
+ import { resolve as resolve5, relative as relative7, join as join9 } from "path";
3693
+ var DEFAULT_MODELS = {
3694
+ anthropic: "claude-sonnet-4-20250514",
3695
+ openai: "gpt-4o",
3696
+ none: ""
3697
+ };
3698
+ async function enhance(options = {}) {
3699
+ const {
3700
+ config: configPath,
3701
+ component,
3702
+ yes = false,
3703
+ dryRun = false,
3704
+ format = "interactive",
3705
+ provider = detectProvider(options),
3706
+ apiKey = getApiKey(provider, options.apiKey),
3707
+ model = options.model || DEFAULT_MODELS[provider],
3708
+ root = process.cwd(),
3709
+ contextOnly = false,
3710
+ renderVariants = false,
3711
+ storybookUrl = "http://localhost:6006"
3712
+ } = options;
3713
+ const isInteractive = format === "interactive";
3714
+ const isQuiet = format === "quiet";
3715
+ const isContextMode = contextOnly || format === "context" || provider === "none";
3716
+ const config = await loadConfig(configPath);
3717
+ const rootDir = resolve5(root);
3718
+ if (isInteractive) {
3719
+ console.log(pc19.cyan(`
3720
+ ${BRAND.name} AI Enhancement
3721
+ `));
3722
+ if (isContextMode) {
3723
+ console.log(pc19.dim("Running in context-only mode (for use with IDE AI like Cursor)\n"));
3724
+ } else {
3725
+ console.log(pc19.dim(`Using ${provider} API with model: ${model}
3726
+ `));
3727
+ }
3728
+ }
3729
+ if (!isContextMode && !apiKey) {
3730
+ const envVar = provider === "openai" ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
3731
+ const error = `API key required. Set ${envVar} or use --api-key, or use --context-only for IDE AI mode`;
3732
+ if (format === "json") {
3733
+ console.log(JSON.stringify({ success: false, error }));
3734
+ } else if (!isQuiet) {
3735
+ console.error(pc19.red("Error:"), error);
3736
+ console.log(pc19.dim("\nTip: Use --context-only to generate prompts for Cursor/Copilot/etc."));
3737
+ }
3738
+ return { success: false, enhanced: [], totalTokens: 0, estimatedCost: 0 };
3739
+ }
3740
+ if (isInteractive) {
3741
+ console.log(pc19.dim("Phase 1: Scanning codebase for usage patterns..."));
3742
+ }
3743
+ let usageAnalysis;
3744
+ try {
3745
+ usageAnalysis = await scanCodebase({
3746
+ rootDir,
3747
+ useCache: true,
3748
+ onProgress: (progress) => {
3749
+ if (isInteractive && progress.phase === "scanning") {
3750
+ const pct = Math.round(progress.current / progress.total * 100);
3751
+ process.stdout.write(`\r Scanning: ${pct}% (${progress.current}/${progress.total})`);
3752
+ }
3753
+ }
3754
+ });
3755
+ if (isInteractive) {
3756
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
3757
+ const stats = getScanStats(usageAnalysis);
3758
+ console.log(pc19.green(` Found ${stats.totalUsages} usages across ${stats.totalFiles} files`));
3759
+ }
3760
+ } catch (error) {
3761
+ const msg = error instanceof Error ? error.message : String(error);
3762
+ if (format === "json") {
3763
+ console.log(JSON.stringify({ success: false, error: `Scan failed: ${msg}` }));
3764
+ } else if (!isQuiet) {
3765
+ console.error(pc19.red("Scan failed:"), msg);
3766
+ }
3767
+ return { success: false, enhanced: [], totalTokens: 0, estimatedCost: 0 };
3768
+ }
3769
+ if (isInteractive) {
3770
+ console.log(pc19.dim("Phase 2: Parsing Storybook stories..."));
3771
+ }
3772
+ let storyFiles;
3773
+ try {
3774
+ storyFiles = await parseAllStories(rootDir);
3775
+ if (isInteractive) {
3776
+ console.log(pc19.green(` Found ${storyFiles.size} story files`));
3777
+ }
3778
+ } catch {
3779
+ storyFiles = /* @__PURE__ */ new Map();
3780
+ if (isInteractive) {
3781
+ console.log(pc19.yellow(" No Storybook stories found"));
3782
+ }
3783
+ }
3784
+ if (isInteractive) {
3785
+ console.log(pc19.dim("Phase 3: Loading fragment files..."));
3786
+ }
3787
+ const segmentFiles = await findSegmentFiles(rootDir);
3788
+ if (segmentFiles.length === 0) {
3789
+ const msg = "No fragment files found";
3790
+ if (format === "json") {
3791
+ console.log(JSON.stringify({ success: false, error: msg }));
3792
+ } else if (!isQuiet) {
3793
+ console.log(pc19.yellow(msg));
3794
+ }
3795
+ return { success: false, enhanced: [], totalTokens: 0, estimatedCost: 0 };
3796
+ }
3797
+ if (isInteractive) {
3798
+ console.log(pc19.green(` Found ${segmentFiles.length} fragment files`));
3799
+ }
3800
+ let componentsToEnhance;
3801
+ if (component && component !== "all") {
3802
+ componentsToEnhance = [component];
3803
+ } else {
3804
+ componentsToEnhance = segmentFiles.map((f) => extractComponentName(f));
3805
+ }
3806
+ if (isInteractive) {
3807
+ console.log(pc19.dim("Phase 4: Extracting props from TypeScript interfaces..."));
3808
+ }
3809
+ const propsExtractions = /* @__PURE__ */ new Map();
3810
+ for (const compName of componentsToEnhance) {
3811
+ const segmentFile = segmentFiles.find((f) => extractComponentName(f) === compName);
3812
+ if (!segmentFile) continue;
3813
+ const segmentDir = segmentFile.replace(/\.segment\.(tsx?|jsx?)$/, "");
3814
+ const possiblePaths = [
3815
+ `${segmentDir}.tsx`,
3816
+ `${segmentDir}.ts`,
3817
+ `${segmentDir}/index.tsx`,
3818
+ `${segmentDir}/index.ts`,
3819
+ join9(rootDir, "src", "components", `${compName}.tsx`),
3820
+ join9(rootDir, "src", "components", compName, `${compName}.tsx`),
3821
+ join9(rootDir, "src", "components", compName, "index.tsx")
3822
+ ];
3823
+ for (const srcPath of possiblePaths) {
3824
+ try {
3825
+ const fs = await import("fs");
3826
+ if (fs.existsSync(srcPath)) {
3827
+ const extraction = await extractPropsFromFile(srcPath, {
3828
+ propsTypeName: `${compName}Props`
3829
+ });
3830
+ if (extraction.success) {
3831
+ propsExtractions.set(compName, extraction);
3832
+ break;
3833
+ }
3834
+ }
3835
+ } catch {
3836
+ }
3837
+ }
3838
+ }
3839
+ if (isInteractive) {
3840
+ const propsCount = Array.from(propsExtractions.values()).reduce((sum, ext) => sum + ext.props.length, 0);
3841
+ console.log(pc19.green(` Extracted ${propsCount} props from ${propsExtractions.size} component files`));
3842
+ }
3843
+ const renderedVariants = /* @__PURE__ */ new Map();
3844
+ if (renderVariants) {
3845
+ if (isInteractive) {
3846
+ console.log(pc19.dim("Phase 5: Rendering variants from Storybook..."));
3847
+ }
3848
+ const storybookRunning = await checkStorybookRunning(storybookUrl);
3849
+ if (!storybookRunning) {
3850
+ if (isInteractive) {
3851
+ console.log(pc19.yellow(` Storybook not running at ${storybookUrl}. Skipping variant rendering.`));
3852
+ console.log(pc19.dim(` Start Storybook with: npm run storybook (or yarn storybook)`));
3853
+ }
3854
+ } else {
3855
+ let totalVariants = 0;
3856
+ let failedVariants = 0;
3857
+ for (const compName of componentsToEnhance) {
3858
+ try {
3859
+ const result = await renderAllComponentVariants(storybookUrl, compName);
3860
+ if (result.variants.length > 0) {
3861
+ renderedVariants.set(compName, result.variants);
3862
+ totalVariants += result.variants.length;
3863
+ }
3864
+ failedVariants += result.failed.length;
3865
+ } catch (err) {
3866
+ if (isInteractive) {
3867
+ console.log(pc19.dim(` ${compName}: Failed to render (${err.message})`));
3868
+ }
3869
+ }
3870
+ }
3871
+ if (isInteractive) {
3872
+ if (totalVariants > 0) {
3873
+ console.log(pc19.green(` Rendered ${totalVariants} variants from ${renderedVariants.size} components`));
3874
+ }
3875
+ if (failedVariants > 0) {
3876
+ console.log(pc19.yellow(` ${failedVariants} variant(s) failed to render`));
3877
+ }
3878
+ }
3879
+ await shutdownSharedPool();
3880
+ }
3881
+ }
3882
+ const contexts = [];
3883
+ for (const compName of componentsToEnhance) {
3884
+ const analysis = usageAnalysis.components[compName];
3885
+ const stories = storyFiles.get(compName);
3886
+ const segmentFile = segmentFiles.find((f) => extractComponentName(f) === compName);
3887
+ const propsExtraction = propsExtractions.get(compName);
3888
+ if (!segmentFile) continue;
3889
+ const context2 = generateComponentContext(
3890
+ compName,
3891
+ analysis,
3892
+ void 0,
3893
+ stories,
3894
+ propsExtraction
3895
+ );
3896
+ contexts.push({ name: compName, context: context2, segmentFile });
3897
+ }
3898
+ if (isContextMode) {
3899
+ return handleContextOnlyMode(contexts, format, isInteractive);
3900
+ }
3901
+ if (isInteractive) {
3902
+ console.log(pc19.dim(`
3903
+ Phase 6: Generating AI enhancements for ${componentsToEnhance.length} component(s)...
3904
+ `));
3905
+ }
3906
+ const enhanced = [];
3907
+ let totalTokens = 0;
3908
+ const aiClient = await createAIClient(provider, apiKey);
3909
+ for (const { name: compName, context: context2, segmentFile } of contexts) {
3910
+ if (!context2.usageAnalysis || context2.usageAnalysis.totalUsages < 2) {
3911
+ enhanced.push({
3912
+ componentName: compName,
3913
+ added: { when: [], whenNot: [] },
3914
+ confidence: 0,
3915
+ tokensUsed: 0,
3916
+ skipped: true,
3917
+ reason: context2.usageAnalysis?.totalUsages === 1 ? "Only 1 usage found" : "No usage data found"
3918
+ });
3919
+ if (isInteractive) {
3920
+ console.log(pc19.dim(` ${compName}: Skipped (insufficient data)`));
3921
+ }
3922
+ continue;
3923
+ }
3924
+ if (isInteractive) {
3925
+ process.stdout.write(` ${compName}: Generating...`);
3926
+ }
3927
+ try {
3928
+ const result = await generateEnhancement(aiClient, provider, model, context2);
3929
+ enhanced.push({
3930
+ componentName: compName,
3931
+ added: result.suggestions,
3932
+ confidence: result.confidence,
3933
+ tokensUsed: result.tokensUsed,
3934
+ skipped: false
3935
+ });
3936
+ totalTokens += result.tokensUsed;
3937
+ if (isInteractive) {
3938
+ process.stdout.write(`\r ${compName}: ${pc19.green("Done")} (${result.suggestions.when.length} when, ${result.suggestions.whenNot.length} whenNot)
3939
+ `);
3940
+ }
3941
+ } catch (error) {
3942
+ const msg = error instanceof Error ? error.message : String(error);
3943
+ enhanced.push({
3944
+ componentName: compName,
3945
+ added: { when: [], whenNot: [] },
3946
+ confidence: 0,
3947
+ tokensUsed: 0,
3948
+ skipped: true,
3949
+ reason: `AI error: ${msg}`
3950
+ });
3951
+ if (isInteractive) {
3952
+ process.stdout.write(`\r ${compName}: ${pc19.red("Failed")} - ${msg}
3953
+ `);
3954
+ }
3955
+ }
3956
+ }
3957
+ const estimatedCost = calculateCost(provider, totalTokens);
3958
+ if (!dryRun) {
3959
+ if (isInteractive) {
3960
+ console.log(pc19.dim("\nPhase 7: Updating segment files..."));
3961
+ }
3962
+ for (const result of enhanced) {
3963
+ if (result.skipped || result.added.when.length === 0 && result.added.whenNot.length === 0) {
3964
+ continue;
3965
+ }
3966
+ const segmentFile = segmentFiles.find((f) => extractComponentName(f) === result.componentName);
3967
+ if (!segmentFile) continue;
3968
+ try {
3969
+ await updateSegmentFile(segmentFile, result.added);
3970
+ if (isInteractive) {
3971
+ console.log(pc19.green(` Updated: ${relative7(rootDir, segmentFile)}`));
3972
+ }
3973
+ } catch {
3974
+ if (isInteractive) {
3975
+ console.log(pc19.red(` Failed to update: ${relative7(rootDir, segmentFile)}`));
3976
+ }
3977
+ }
3978
+ }
3979
+ }
3980
+ const successCount = enhanced.filter((e) => !e.skipped).length;
3981
+ if (format === "json") {
3982
+ console.log(JSON.stringify({
3983
+ success: true,
3984
+ enhanced,
3985
+ totalTokens,
3986
+ estimatedCost
3987
+ }, null, 2));
3988
+ } else if (isInteractive) {
3989
+ console.log();
3990
+ console.log(pc19.bold("Summary:"));
3991
+ console.log(` Components processed: ${componentsToEnhance.length}`);
3992
+ console.log(` Successfully enhanced: ${successCount}`);
3993
+ console.log(` Skipped: ${enhanced.filter((e) => e.skipped).length}`);
3994
+ console.log(` Total tokens used: ${totalTokens}`);
3995
+ console.log(` Estimated cost: $${estimatedCost.toFixed(4)}`);
3996
+ if (dryRun) {
3997
+ console.log();
3998
+ console.log(pc19.yellow("Dry run - no files were modified"));
3999
+ }
4000
+ console.log();
4001
+ }
4002
+ return {
4003
+ success: true,
4004
+ enhanced,
4005
+ totalTokens,
4006
+ estimatedCost
4007
+ };
4008
+ }
4009
+ function handleContextOnlyMode(contexts, format, isInteractive) {
4010
+ const systemPrompt = generateSystemPrompt();
4011
+ const componentContexts = [];
4012
+ for (const { name, context: context2 } of contexts) {
4013
+ if (!context2.usageAnalysis || context2.usageAnalysis.totalUsages < 2) {
4014
+ continue;
4015
+ }
4016
+ componentContexts.push(generatePromptContext(context2));
4017
+ }
4018
+ if (componentContexts.length === 0) {
4019
+ if (format === "json") {
4020
+ console.log(JSON.stringify({ success: false, error: "No components with sufficient usage data" }));
4021
+ } else if (isInteractive) {
4022
+ console.log(pc19.yellow("\nNo components with sufficient usage data to analyze."));
4023
+ }
4024
+ return { success: false, enhanced: [], totalTokens: 0, estimatedCost: 0 };
4025
+ }
4026
+ const fullContext = `${systemPrompt}
4027
+
4028
+ ---
4029
+
4030
+ ${componentContexts.join("\n\n---\n\n")}
4031
+
4032
+ ---
4033
+
4034
+ Based on the usage analysis above, generate "when" and "whenNot" documentation for each component.
4035
+
4036
+ For each component, provide your response in JSON format:
4037
+ \`\`\`json
4038
+ {
4039
+ "componentName": "...",
4040
+ "when": ["scenario 1", "scenario 2", ...],
4041
+ "whenNot": ["anti-pattern 1", ...]
4042
+ }
4043
+ \`\`\``;
4044
+ if (format === "json") {
4045
+ console.log(JSON.stringify({
4046
+ success: true,
4047
+ context: fullContext,
4048
+ instructions: "Copy this prompt into your IDE AI (Cursor, Copilot, etc.) to generate suggestions"
4049
+ }, null, 2));
4050
+ } else {
4051
+ console.log(pc19.bold("\n\u{1F4CB} AI Context Generated\n"));
4052
+ console.log(pc19.dim("Copy the following prompt into your IDE AI (Cursor, Copilot, etc.):\n"));
4053
+ console.log(pc19.dim("\u2500".repeat(60)));
4054
+ console.log(fullContext);
4055
+ console.log(pc19.dim("\u2500".repeat(60)));
4056
+ console.log();
4057
+ console.log(pc19.green("Tip: In Cursor, press Cmd+L to open chat and paste this prompt."));
4058
+ console.log(pc19.dim("After getting suggestions, manually update your segment files."));
4059
+ console.log();
4060
+ }
4061
+ return {
4062
+ success: true,
4063
+ enhanced: [],
4064
+ totalTokens: 0,
4065
+ estimatedCost: 0,
4066
+ context: fullContext
4067
+ };
4068
+ }
4069
+ function detectProvider(options) {
4070
+ if (options.contextOnly) return "none";
4071
+ if (options.provider) return options.provider;
4072
+ if (options.apiKey) {
4073
+ if (options.apiKey.startsWith("sk-ant-")) return "anthropic";
4074
+ if (options.apiKey.startsWith("sk-")) return "openai";
4075
+ }
4076
+ if (process.env.ANTHROPIC_API_KEY) return "anthropic";
4077
+ if (process.env.OPENAI_API_KEY) return "openai";
4078
+ return "none";
4079
+ }
4080
+ function getApiKey(provider, explicitKey) {
4081
+ if (explicitKey) return explicitKey;
4082
+ if (provider === "anthropic") return process.env.ANTHROPIC_API_KEY;
4083
+ if (provider === "openai") return process.env.OPENAI_API_KEY;
4084
+ return void 0;
4085
+ }
4086
+ async function createAIClient(provider, apiKey) {
4087
+ if (provider === "anthropic") {
4088
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
4089
+ return new Anthropic({ apiKey });
4090
+ }
4091
+ if (provider === "openai") {
4092
+ const OpenAI = (await import("openai")).default;
4093
+ return new OpenAI({ apiKey });
4094
+ }
4095
+ throw new Error(`Unknown provider: ${provider}`);
4096
+ }
4097
+ async function generateEnhancement(client, provider, model, context2) {
4098
+ const systemPrompt = generateSystemPrompt();
4099
+ const userPrompt = generateUserPrompt(context2);
4100
+ if (provider === "anthropic") {
4101
+ return generateWithAnthropic(client, model, systemPrompt, userPrompt, context2);
4102
+ }
4103
+ if (provider === "openai") {
4104
+ return generateWithOpenAI(client, model, systemPrompt, userPrompt, context2);
4105
+ }
4106
+ throw new Error(`Unknown provider: ${provider}`);
4107
+ }
4108
+ async function generateWithAnthropic(client, model, systemPrompt, userPrompt, context2) {
4109
+ const anthropic = client;
4110
+ const response = await anthropic.messages.create({
4111
+ model,
4112
+ max_tokens: 1024,
4113
+ system: systemPrompt,
4114
+ messages: [{ role: "user", content: userPrompt }]
4115
+ });
4116
+ const content = response.content[0];
4117
+ if (content.type !== "text") {
4118
+ throw new Error("Unexpected response type");
4119
+ }
4120
+ const suggestions = parseAIResponse(content.text);
4121
+ const tokensUsed = (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0);
4122
+ return {
4123
+ suggestions,
4124
+ confidence: calculateConfidence2(context2, suggestions),
4125
+ tokensUsed
4126
+ };
4127
+ }
4128
+ async function generateWithOpenAI(client, model, systemPrompt, userPrompt, context2) {
4129
+ const openai = client;
4130
+ const response = await openai.chat.completions.create({
4131
+ model,
4132
+ max_tokens: 1024,
4133
+ messages: [
4134
+ { role: "system", content: systemPrompt },
4135
+ { role: "user", content: userPrompt }
4136
+ ]
4137
+ });
4138
+ const content = response.choices[0]?.message?.content;
4139
+ if (!content) {
4140
+ throw new Error("No response from OpenAI");
4141
+ }
4142
+ const suggestions = parseAIResponse(content);
4143
+ const tokensUsed = (response.usage?.prompt_tokens || 0) + (response.usage?.completion_tokens || 0);
4144
+ return {
4145
+ suggestions,
4146
+ confidence: calculateConfidence2(context2, suggestions),
4147
+ tokensUsed
4148
+ };
4149
+ }
4150
+ function parseAIResponse(text) {
4151
+ try {
4152
+ const jsonMatch = text.match(/```json\n?([\s\S]*?)\n?```/) || text.match(/\{[\s\S]*\}/);
4153
+ const jsonStr = jsonMatch ? jsonMatch[1] || jsonMatch[0] : text;
4154
+ const parsed = JSON.parse(jsonStr);
4155
+ return {
4156
+ when: Array.isArray(parsed.when) ? parsed.when : [],
4157
+ whenNot: Array.isArray(parsed.whenNot) ? parsed.whenNot : []
4158
+ };
4159
+ } catch {
4160
+ return extractSuggestionsFromText(text);
4161
+ }
4162
+ }
4163
+ function extractSuggestionsFromText(text) {
4164
+ const when = [];
4165
+ const whenNot = [];
4166
+ const lines = text.split("\n");
4167
+ let currentSection = null;
4168
+ for (const line of lines) {
4169
+ const trimmed = line.trim();
4170
+ if (/^(when|use when|when to use)/i.test(trimmed)) {
4171
+ currentSection = "when";
4172
+ continue;
4173
+ }
4174
+ if (/^(when not|do not use|avoid|whenNot)/i.test(trimmed)) {
4175
+ currentSection = "whenNot";
4176
+ continue;
4177
+ }
4178
+ if (currentSection && /^[-*]\s+/.test(trimmed)) {
4179
+ const item = trimmed.replace(/^[-*]\s+/, "").trim();
4180
+ if (item && item.length > 10) {
4181
+ if (currentSection === "when") {
4182
+ when.push(item);
4183
+ } else {
4184
+ whenNot.push(item);
4185
+ }
4186
+ }
4187
+ }
4188
+ }
4189
+ return { when, whenNot };
4190
+ }
4191
+ function calculateConfidence2(context2, suggestions) {
4192
+ let confidence = 50;
4193
+ if (context2.usageAnalysis.totalUsages > 10) confidence += 20;
4194
+ if (context2.storybook && context2.storybook.stories.length > 0) confidence += 15;
4195
+ if (suggestions.when.length >= 2) confidence += 10;
4196
+ if (suggestions.whenNot.length >= 1) confidence += 5;
4197
+ return Math.min(confidence, 100);
4198
+ }
4199
+ function calculateCost(provider, tokens) {
4200
+ const costsPer1M = {
4201
+ anthropic: 3,
4202
+ // Claude Sonnet
4203
+ openai: 5,
4204
+ // GPT-4o
4205
+ none: 0
4206
+ };
4207
+ return tokens / 1e6 * costsPer1M[provider];
4208
+ }
4209
+ async function findSegmentFiles(dir) {
4210
+ const fg4 = await import("fast-glob");
4211
+ return fg4.default(["**/*.segment.tsx", "**/*.segment.ts"], {
4212
+ cwd: dir,
4213
+ absolute: true,
4214
+ ignore: ["**/node_modules/**", "**/dist/**"]
4215
+ });
4216
+ }
4217
+ function extractComponentName(filePath) {
4218
+ const match = filePath.match(/([^/\\]+)\.segment\.(tsx?|jsx?)$/);
4219
+ return match ? match[1] : "";
4220
+ }
4221
+ async function updateSegmentFile(filePath, suggestions) {
4222
+ const content = await readFile5(filePath, "utf-8");
4223
+ let updated = content;
4224
+ if (suggestions.when.length > 0) {
4225
+ const whenItems = suggestions.when.map((s) => ` "${s}"`).join(",\n");
4226
+ const whenMatch = updated.match(/when:\s*\[\s*([^\]]*)\]/);
4227
+ if (whenMatch) {
4228
+ const existingContent = whenMatch[1].trim();
4229
+ if (existingContent) {
4230
+ updated = updated.replace(
4231
+ /when:\s*\[\s*([^\]]*)\]/,
4232
+ `when: [
4233
+ ${existingContent},
4234
+ ${whenItems}
4235
+ ]`
4236
+ );
4237
+ } else {
4238
+ updated = updated.replace(
4239
+ /when:\s*\[\s*\]/,
4240
+ `when: [
4241
+ ${whenItems}
4242
+ ]`
4243
+ );
4244
+ }
4245
+ }
4246
+ }
4247
+ if (suggestions.whenNot.length > 0) {
4248
+ const whenNotItems = suggestions.whenNot.map((s) => ` "${s}"`).join(",\n");
4249
+ const whenNotMatch = updated.match(/whenNot:\s*\[\s*([^\]]*)\]/);
4250
+ if (whenNotMatch) {
4251
+ const existingContent = whenNotMatch[1].trim();
4252
+ if (existingContent) {
4253
+ updated = updated.replace(
4254
+ /whenNot:\s*\[\s*([^\]]*)\]/,
4255
+ `whenNot: [
4256
+ ${existingContent},
4257
+ ${whenNotItems}
4258
+ ]`
4259
+ );
4260
+ } else {
4261
+ updated = updated.replace(
4262
+ /whenNot:\s*\[\s*\]/,
4263
+ `whenNot: [
4264
+ ${whenNotItems}
4265
+ ]`
4266
+ );
4267
+ }
4268
+ }
4269
+ }
4270
+ if (updated !== content) {
4271
+ await writeFile7(filePath, updated, "utf-8");
4272
+ }
4273
+ }
4274
+
4275
+ // src/bin.ts
4276
+ var program = new Command();
4277
+ program.name(BRAND.cliCommand).description(`${BRAND.name} - Design system documentation and compliance tool`).version("0.1.0");
4278
+ 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").action(async (options) => {
4279
+ try {
4280
+ const result = await validate(options);
4281
+ if (!result.valid) {
4282
+ process.exit(1);
4283
+ }
4284
+ } catch (error) {
4285
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4286
+ process.exit(1);
4287
+ }
4288
+ });
4289
+ 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) => {
4290
+ try {
4291
+ const result = await build({
4292
+ config: options.config,
4293
+ output: options.output,
4294
+ registry: options.registry,
4295
+ registryOnly: options.registryOnly,
4296
+ fromSource: options.fromSource,
4297
+ skipUsage: options.skipUsage,
4298
+ skipStorybook: options.skipStorybook,
4299
+ verbose: options.verbose
4300
+ });
4301
+ if (!result.success) {
4302
+ process.exit(1);
4303
+ }
4304
+ } catch (error) {
4305
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4306
+ process.exit(1);
4307
+ }
4308
+ });
4309
+ program.command("context").description("Generate AI-ready context for your design system").option("-c, --config <path>", "Path to config file").option("-i, --input <path>", `Path to ${BRAND.outFile} (builds if not provided)`).option("-f, --format <format>", "Output format (markdown/json)", "markdown").option("--compact", "Minimal output for token efficiency").option("--code", "Include code examples").option("--relations", "Include component relationships").option("--tokens", "Only output token estimate").action(async (options) => {
4310
+ try {
4311
+ const result = await context({
4312
+ config: options.config,
4313
+ input: options.input,
4314
+ format: options.format,
4315
+ compact: options.compact,
4316
+ includeCode: options.code,
4317
+ includeRelations: options.relations,
4318
+ tokensOnly: options.tokens
4319
+ });
4320
+ if (!result.success) {
4321
+ process.exit(1);
4322
+ }
4323
+ } catch (error) {
4324
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4325
+ process.exit(1);
4326
+ }
4327
+ });
4328
+ program.command("ai").description("Generate context optimized for AI assistants (Claude Desktop)").option("-c, --config <path>", "Path to config file").option("--live", "Connect to running dev server for live data").option("-p, --port <port>", "Dev server port", "6006").action(async (options) => {
4329
+ try {
4330
+ if (options.live) {
4331
+ const baseUrl = `http://localhost:${options.port}`;
4332
+ const response = await fetch(`${baseUrl}/${BRAND.nameLower}/context?format=markdown`);
4333
+ if (!response.ok) {
4334
+ throw new Error("Failed to fetch context. Make sure dev server is running.");
4335
+ }
4336
+ const contextContent = await response.text();
4337
+ console.log(contextContent);
4338
+ } else {
4339
+ const result = await context({
4340
+ config: options.config,
4341
+ format: "markdown",
4342
+ compact: false,
4343
+ includeCode: true,
4344
+ includeRelations: true
4345
+ });
4346
+ if (!result.success) {
4347
+ process.exit(1);
4348
+ }
4349
+ }
4350
+ } catch (error) {
4351
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4352
+ process.exit(1);
4353
+ }
4354
+ });
4355
+ program.command("list").description("List all discovered fragment files").option("-c, --config <path>", "Path to config file").action(async (options) => {
4356
+ try {
4357
+ await list(options);
4358
+ } catch (error) {
4359
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4360
+ process.exit(1);
4361
+ }
4362
+ });
4363
+ program.command("reset").description("Reset to initial state (delete all generated files)").option("-y, --yes", "Skip confirmation prompt").option("--dry-run", "Show what would be deleted without deleting").action(async (options) => {
4364
+ try {
4365
+ await reset(options);
4366
+ } catch (error) {
4367
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4368
+ process.exit(1);
4369
+ }
4370
+ });
4371
+ var linkCommand = program.command("link").description("Link external resources (Figma designs, Storybook stories) to fragments");
4372
+ linkCommand.command("figma").argument("[figma-url]", "Figma file URL to link components from").description("Interactive wizard to link Figma components to code").option("-c, --config <path>", "Path to config file").option("--auto", "Auto-link matching components without prompts").option("--dry-run", "Show matches without updating files").option("--no-variants", "Skip linking individual variants to their Figma frames").action(async (figmaUrl, options) => {
4373
+ try {
4374
+ await linkFigma(figmaUrl, {
4375
+ config: options.config,
4376
+ auto: options.auto,
4377
+ dryRun: options.dryRun,
4378
+ variants: options.variants
4379
+ });
4380
+ } catch (error) {
4381
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4382
+ process.exit(1);
4383
+ }
4384
+ });
4385
+ linkCommand.command("storybook").description("Bootstrap fragments from existing Storybook stories").option("-c, --config <path>", "Path to .storybook/main.* config").option("-o, --out <dir>", "Output directory for fragment files").option("--yes", "Skip confirmation prompts").option("--dry-run", "Preview what would be generated without writing files").option("--include <glob>", "Only process stories matching glob").option("--exclude <glob>", "Skip stories matching glob").action(async (options) => {
4386
+ try {
4387
+ await linkStorybook({
4388
+ config: options.config,
4389
+ out: options.out,
4390
+ yes: options.yes,
4391
+ dryRun: options.dryRun,
4392
+ include: options.include,
4393
+ exclude: options.exclude
4394
+ });
4395
+ } catch (error) {
4396
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4397
+ process.exit(1);
4398
+ }
4399
+ });
4400
+ program.command("dev").description("Start the development server with live component rendering").option("-p, --port <port>", "Port to run on", "6006").option("--no-open", "Do not open browser").option("--skip-setup", "Skip auto-setup (Storybook import, build, Figma link)").option("--skip-storybook", "Skip auto-importing from Storybook").option("--skip-figma", "Skip Figma link check").option("--skip-build", `Skip auto-building ${BRAND.outFile}`).action(async (options) => {
4401
+ try {
4402
+ await dev({
4403
+ port: options.port,
4404
+ open: options.open,
4405
+ skipSetup: options.skipSetup,
4406
+ skipStorybook: options.skipStorybook,
4407
+ skipFigma: options.skipFigma,
4408
+ skipBuild: options.skipBuild
4409
+ });
4410
+ } catch (error) {
4411
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4412
+ if (error instanceof Error && error.stack) {
4413
+ console.error(pc20.dim(error.stack));
4414
+ }
4415
+ process.exit(1);
4416
+ }
4417
+ });
4418
+ program.command("screenshot").description("Capture screenshots of component variants").option("-c, --config <path>", "Path to config file").option("--component <name>", "Capture specific component only").option("--variant <name>", "Capture specific variant only").option("--theme <theme>", "Theme to capture (light/dark)", "light").option("--update", "Update existing baselines").option("--width <pixels>", "Viewport width", parseInt).option("--height <pixels>", "Viewport height", parseInt).option("--ci", "CI mode - no interactive prompts").action(async (options) => {
4419
+ try {
4420
+ const { config, configDir } = await loadConfig(options.config);
4421
+ const result = await runScreenshotCommand(config, configDir, {
4422
+ component: options.component,
4423
+ variant: options.variant,
4424
+ theme: options.theme,
4425
+ update: options.update,
4426
+ ci: options.ci,
4427
+ width: options.width,
4428
+ height: options.height
4429
+ });
4430
+ if (!result.success) {
4431
+ process.exit(1);
4432
+ }
4433
+ } catch (error) {
4434
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4435
+ process.exit(1);
4436
+ }
4437
+ });
4438
+ program.command("diff").argument("[component]", "Component name to diff (optional)").description("Compare current renders against baselines").option("-c, --config <path>", "Path to config file").option("--variant <name>", "Compare specific variant only").option("--theme <theme>", "Theme to compare (light/dark)", "light").option("--threshold <percent>", "Diff threshold percentage", parseFloat).option("--ci", "CI mode - exit 1 on differences").option("--open", "Open diff images").action(async (component, options) => {
4439
+ try {
4440
+ const { config, configDir } = await loadConfig(options.config);
4441
+ const result = await runDiffCommand(config, configDir, {
4442
+ component,
4443
+ variant: options.variant,
4444
+ theme: options.theme,
4445
+ threshold: options.threshold,
4446
+ ci: options.ci,
4447
+ open: options.open
4448
+ });
4449
+ if (!result.success && options.ci) {
4450
+ process.exit(1);
4451
+ }
4452
+ } catch (error) {
4453
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4454
+ process.exit(1);
4455
+ }
4456
+ });
4457
+ program.command("compare").argument("[component]", "Component name to compare").description("Compare component renders against Figma designs").option("-c, --config <path>", "Path to config file").option("--variant <name>", "Compare specific variant").option("--figma <url>", "Figma frame URL (uses fragment figma link if not provided)").option("--threshold <percent>", "Diff threshold percentage", parseFloat, 1).option("--all", "Compare all components with Figma links").option("--output <dir>", "Save diff images to directory").option("-p, --port <port>", "Dev server port", "6006").action(async (component, options) => {
4458
+ try {
4459
+ const result = await compare(component, {
4460
+ config: options.config,
4461
+ variant: options.variant,
4462
+ figma: options.figma,
4463
+ threshold: options.threshold,
4464
+ all: options.all,
4465
+ output: options.output,
4466
+ port: options.port
4467
+ });
4468
+ if (!result.success) {
4469
+ process.exit(1);
4470
+ }
4471
+ } catch (error) {
4472
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4473
+ console.log(pc20.dim(`
4474
+ Make sure the dev server is running: ${BRAND.cliCommand} dev`));
4475
+ process.exit(1);
4476
+ }
4477
+ });
4478
+ program.command("analyze").description("Analyze design system and generate report").option("-c, --config <path>", "Path to config file").option("-f, --format <format>", "Output format (html/json/console)", "html").option("-o, --output <path>", "Output file path").option("--open", "Open report in browser after generation").option("--ci", "CI mode - exit with code based on score").option("--min-score <score>", "Minimum score to pass in CI mode", parseFloat).action(async (options) => {
4479
+ try {
4480
+ const { config, configDir } = await loadConfig(options.config);
4481
+ const result = await runAnalyzeCommand(config, configDir, {
4482
+ format: options.format,
4483
+ output: options.output,
4484
+ open: options.open,
4485
+ ci: options.ci,
4486
+ minScore: options.minScore
4487
+ });
4488
+ if (!result.success) {
4489
+ process.exit(1);
4490
+ }
4491
+ } catch (error) {
4492
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4493
+ process.exit(1);
4494
+ }
4495
+ });
4496
+ program.command("verify").argument("[component]", "Component name to verify (optional, verifies all if omitted)").description("Verify component compliance for CI pipelines").option("-c, --config <path>", "Path to config file").option("--ci", "CI mode - output JSON and exit non-zero on failure").option("--min-compliance <percent>", "Minimum compliance percentage (default: 80)", parseFloat, 80).option("-p, --port <port>", "Dev server port", "6006").action(async (component, options) => {
4497
+ try {
4498
+ const summary = await verify(component, {
4499
+ config: options.config,
4500
+ ci: options.ci,
4501
+ minCompliance: options.minCompliance,
4502
+ port: options.port
4503
+ });
4504
+ if (!summary.passed && options.ci) {
4505
+ process.exit(1);
4506
+ }
4507
+ } catch (error) {
4508
+ if (options.ci) {
4509
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : "Verification failed" }));
4510
+ } else {
4511
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4512
+ }
4513
+ process.exit(1);
4514
+ }
4515
+ });
4516
+ program.command("audit").description("Scan all fragments and show compliance metrics across the design system").option("-c, --config <path>", "Path to config file").option("--json", "Output machine-readable JSON format").option("--sort <field>", "Sort by field: compliance, name, hardcoded (default: compliance)", "compliance").option("-p, --port <port>", "Dev server port", "6006").action(async (options) => {
4517
+ try {
4518
+ await audit({
4519
+ config: options.config,
4520
+ json: options.json,
4521
+ sort: options.sort,
4522
+ port: options.port
4523
+ });
4524
+ } catch (error) {
4525
+ if (options.json) {
4526
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : "Audit failed" }));
4527
+ } else {
4528
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4529
+ }
4530
+ process.exit(1);
4531
+ }
4532
+ });
4533
+ program.command("a11y").description("Run accessibility checks on all component variants").option("-c, --config <path>", "Path to config file").option("--json", "Output results as JSON").option("--ci", "CI mode (exit code 1 if any critical/serious violations)").option("--component <name>", "Check specific component only").option("-p, --port <port>", "Dev server port", "6006").action(async (options) => {
4534
+ try {
4535
+ await a11y({
4536
+ config: options.config,
4537
+ json: options.json,
4538
+ ci: options.ci,
4539
+ component: options.component,
4540
+ port: options.port
4541
+ });
4542
+ } catch (error) {
4543
+ if (options.json) {
4544
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : "A11y check failed" }));
4545
+ } else {
4546
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4547
+ }
4548
+ process.exit(1);
4549
+ }
4550
+ });
4551
+ program.command("enhance").description("AI-powered documentation generation from codebase analysis").option("-c, --config <path>", "Path to config file").option("--component <name>", 'Enhance specific component (or "all")').option("--yes", "Skip confirmation prompts").option("--dry-run", "Only analyze, do not modify files").option("--format <format>", "Output format: interactive, json, quiet, context", "interactive").option("--provider <provider>", "AI provider: anthropic, openai (auto-detected from API key)").option("--api-key <key>", "API key (or use ANTHROPIC_API_KEY/OPENAI_API_KEY env)").option("--model <model>", "AI model to use (default: claude-sonnet-4 for anthropic, gpt-4o for openai)").option("--root <dir>", "Root directory to scan", process.cwd()).option("--context-only", "Output context for IDE AI (Cursor, Copilot) without calling API").action(async (options) => {
4552
+ try {
4553
+ await enhance({
4554
+ config: options.config,
4555
+ component: options.component,
4556
+ yes: options.yes,
4557
+ dryRun: options.dryRun,
4558
+ format: options.format,
4559
+ provider: options.provider,
4560
+ apiKey: options.apiKey,
4561
+ model: options.model,
4562
+ root: options.root,
4563
+ contextOnly: options.contextOnly
4564
+ });
4565
+ } catch (error) {
4566
+ if (options.format === "json") {
4567
+ console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : "Enhance failed" }));
4568
+ } else {
4569
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4570
+ }
4571
+ process.exit(1);
4572
+ }
4573
+ });
4574
+ program.command("scan").description(`Zero-config ${BRAND.outFile} generation from source code`).option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output file path", BRAND.outFile).option("--patterns <patterns...>", "Component file patterns to scan").option("--barrel <files...>", "Barrel export files to parse").option("--usage-dir <dir>", "Directory to scan for usage patterns").option("--skip-usage", "Skip usage pattern analysis").option("--skip-storybook", "Skip Storybook story parsing").option("-v, --verbose", "Verbose output").action(async (options) => {
4575
+ try {
4576
+ const result = await scan({
4577
+ config: options.config,
4578
+ output: options.output,
4579
+ componentPatterns: options.patterns,
4580
+ barrelFiles: options.barrel,
4581
+ usageDir: options.usageDir,
4582
+ skipUsage: options.skipUsage,
4583
+ skipStorybook: options.skipStorybook,
4584
+ verbose: options.verbose
4585
+ });
4586
+ if (!result.success) {
4587
+ process.exit(1);
4588
+ }
4589
+ } catch (error) {
4590
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4591
+ process.exit(1);
4592
+ }
4593
+ });
4594
+ program.command("storygen").description("Generate Storybook stories from fragment definitions").option("-c, --config <path>", "Path to config file").option("-o, --output <dir>", "Output directory", ".storybook/generated").option("--watch", "Watch for segment changes and regenerate").option("--format <format>", "Story format (csf3)", "csf3").action(async (options) => {
4595
+ try {
4596
+ await storygen({
4597
+ config: options.config,
4598
+ output: options.output,
4599
+ watch: options.watch,
4600
+ format: options.format
4601
+ });
4602
+ } catch (error) {
4603
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4604
+ process.exit(1);
4605
+ }
4606
+ });
4607
+ program.command("metrics").argument("[component]", "Component name (optional, shows system-wide if omitted)").description("View compliance trends over time").option("-c, --config <path>", "Path to config file").option("--days <number>", "Number of days to look back", parseInt, 30).option("--json", "Output JSON format").action(async (component, options) => {
4608
+ try {
4609
+ await metrics(component, {
4610
+ config: options.config,
4611
+ days: options.days,
4612
+ json: options.json
4613
+ });
4614
+ } catch (error) {
4615
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4616
+ process.exit(1);
4617
+ }
4618
+ });
4619
+ program.command("baseline").description("Manage visual regression baselines").argument("<action>", "Action to perform: update, list, delete").argument("[component]", "Component name (optional for update/delete)").option("-c, --config <path>", "Path to config file").option("--variant <name>", "Specific variant to update").option("--all", "Update/delete all baselines").option("--theme <theme>", "Theme for baseline (light/dark)", "light").option("-p, --port <port>", "Dev server port", "6006").action(async (action, component, options) => {
4620
+ try {
4621
+ await baseline(action, component, {
4622
+ config: options.config,
4623
+ variant: options.variant,
4624
+ all: options.all,
4625
+ theme: options.theme,
4626
+ port: options.port
4627
+ });
4628
+ } catch (error) {
4629
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4630
+ if (action === "update") {
4631
+ console.log(pc20.dim(`
4632
+ Make sure the dev server is running: ${BRAND.cliCommand} dev`));
4633
+ }
4634
+ process.exit(1);
4635
+ }
4636
+ });
4637
+ 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) => {
4638
+ try {
4639
+ const { generateViewerFromJson } = await import("./static-viewer-MIPGZ4Z7.js");
4640
+ const fs = await import("fs/promises");
4641
+ const path = await import("path");
4642
+ const inputPath = path.resolve(process.cwd(), options.input);
4643
+ const outputPath = path.resolve(process.cwd(), options.output);
4644
+ console.log(pc20.cyan(`
4645
+ ${BRAND.name} Viewer Generator
4646
+ `));
4647
+ try {
4648
+ await fs.access(inputPath);
4649
+ } catch {
4650
+ console.log(pc20.red(`Error: ${options.input} not found.`));
4651
+ console.log(pc20.dim(`
4652
+ Run ${pc20.cyan(`${BRAND.cliCommand} build`)} first to generate ${BRAND.outFile}
4653
+ `));
4654
+ process.exit(1);
4655
+ }
4656
+ console.log(pc20.dim(`Reading: ${options.input}`));
4657
+ const html = await generateViewerFromJson(inputPath);
4658
+ await fs.writeFile(outputPath, html);
4659
+ console.log(pc20.green(`
4660
+ \u2713 Generated: ${options.output}
4661
+ `));
4662
+ if (options.open) {
4663
+ const { exec } = await import("child_process");
4664
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
4665
+ exec(`${openCmd} "${outputPath}"`);
4666
+ }
4667
+ } catch (error) {
4668
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4669
+ process.exit(1);
4670
+ }
4671
+ });
4672
+ program.command("add").argument("[name]", 'Component name (e.g., "Button", "TextField")').description("Scaffold a new component with fragment file").option("-c, --category <category>", "Component category").option("-d, --dir <directory>", "Output directory").option("-t, --template <template>", "Template to use (action, form-input, layout, display)").option("--no-component", "Only generate fragment file, skip component stub").action(async (name, options) => {
4673
+ try {
4674
+ await add(name, {
4675
+ category: options.category,
4676
+ dir: options.dir,
4677
+ template: options.template,
4678
+ component: options.component
4679
+ });
4680
+ } catch (error) {
4681
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4682
+ process.exit(1);
4683
+ }
4684
+ });
4685
+ 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").action(async (options) => {
4686
+ try {
4687
+ const { init } = await import("./init-EMVI47QG.js");
4688
+ const result = await init({
4689
+ projectRoot: process.cwd(),
4690
+ force: options.force,
4691
+ yes: options.yes
4692
+ });
4693
+ if (!result.success) {
4694
+ console.error(pc20.red("\nInit failed with errors:"));
4695
+ for (const error of result.errors) {
4696
+ console.error(pc20.red(` - ${error}`));
4697
+ }
4698
+ process.exit(1);
4699
+ }
4700
+ } catch (error) {
4701
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4702
+ process.exit(1);
4703
+ }
4704
+ });
4705
+ 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) => {
4706
+ try {
4707
+ const { tokens } = await import("./tokens-HSGMYK64.js");
4708
+ const result = await tokens({
4709
+ config: options.config,
4710
+ json: options.json,
4711
+ categories: options.categories,
4712
+ theme: options.theme,
4713
+ category: options.category,
4714
+ verbose: options.verbose
4715
+ });
4716
+ if (!result.success) {
4717
+ process.exit(1);
4718
+ }
4719
+ } catch (error) {
4720
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4721
+ process.exit(1);
4722
+ }
4723
+ });
4724
+ 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) => {
4725
+ try {
4726
+ const { generate } = await import("./generate-4LQNJ7SX.js");
4727
+ const result = await generate({
4728
+ projectRoot: process.cwd(),
4729
+ component,
4730
+ force: options.force,
4731
+ componentPattern: options.pattern
4732
+ });
4733
+ if (!result.success) {
4734
+ console.error(pc20.red("\nGenerate completed with errors"));
4735
+ process.exit(1);
4736
+ }
4737
+ } catch (error) {
4738
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4739
+ process.exit(1);
4740
+ }
4741
+ });
4742
+ 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) => {
4743
+ try {
4744
+ const { config, configDir } = await loadConfig(options.config);
4745
+ const { runTestCommand, listTests } = await import("./test-SQ5ZHXWU.js");
4746
+ if (options.list) {
4747
+ await listTests(config, configDir, {
4748
+ component: options.component,
4749
+ tags: options.tags,
4750
+ grep: options.grep,
4751
+ exclude: options.exclude
4752
+ });
4753
+ return;
4754
+ }
4755
+ const exitCode = await runTestCommand(config, configDir, {
4756
+ component: options.component,
4757
+ tags: options.tags,
4758
+ grep: options.grep,
4759
+ exclude: options.exclude,
4760
+ parallel: options.parallel,
4761
+ timeout: options.timeout,
4762
+ retries: options.retries,
4763
+ bail: options.bail,
4764
+ browser: options.browser,
4765
+ headless: !options.headed,
4766
+ a11y: options.a11y,
4767
+ visual: options.visual,
4768
+ updateSnapshots: options.updateSnapshots,
4769
+ watch: options.watch,
4770
+ reporters: options.reporters,
4771
+ output: options.output,
4772
+ serverUrl: options.serverUrl,
4773
+ port: options.port,
4774
+ ci: options.ci
4775
+ });
4776
+ process.exit(exitCode);
4777
+ } catch (error) {
4778
+ console.error(pc20.red("Error:"), error instanceof Error ? error.message : error);
4779
+ process.exit(1);
4780
+ }
4781
+ });
4782
+ program.parse();
4783
+ //# sourceMappingURL=bin.js.map