@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/src/bin.ts ADDED
@@ -0,0 +1,916 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * fragments CLI - Design system documentation and compliance tool
4
+ *
5
+ * This file registers all commands with Commander. Command implementations
6
+ * are in the commands/ directory.
7
+ */
8
+
9
+ import { Command } from 'commander';
10
+ import pc from 'picocolors';
11
+ import { BRAND } from './core/index.js';
12
+ import { loadConfig } from './core/node.js';
13
+
14
+ // Import command implementations
15
+ import { validate } from './commands/validate.js';
16
+ import { build } from './commands/build.js';
17
+ import { context } from './commands/context.js';
18
+ import { list } from './commands/list.js';
19
+ import { reset } from './commands/reset.js';
20
+ import { dev } from './commands/dev.js';
21
+ import { compare } from './commands/compare.js';
22
+ import { verify } from './commands/verify.js';
23
+ import { audit } from './commands/audit.js';
24
+ import { a11y } from './commands/a11y.js';
25
+ import { storygen } from './commands/storygen.js';
26
+ import { metrics } from './commands/metrics.js';
27
+ import { baseline } from './commands/baseline.js';
28
+ import { add } from './commands/add.js';
29
+ import { linkFigma, linkStorybook } from './commands/link/index.js';
30
+ import { enhance } from './commands/enhance.js';
31
+ import { scan } from './commands/scan.js';
32
+
33
+ // Import existing commands that were already extracted
34
+ import { runScreenshotCommand } from './screenshot.js';
35
+ import { runDiffCommand } from './diff.js';
36
+ import { runAnalyzeCommand } from './analyze.js';
37
+
38
+ const program = new Command();
39
+
40
+ program
41
+ .name(BRAND.cliCommand)
42
+ .description(`${BRAND.name} - Design system documentation and compliance tool`)
43
+ .version('0.1.0');
44
+
45
+ // ============================================================================
46
+ // VALIDATE COMMAND
47
+ // ============================================================================
48
+ program
49
+ .command('validate')
50
+ .description('Validate fragment files')
51
+ .option('-c, --config <path>', 'Path to config file')
52
+ .option('--schema', 'Validate fragment schema only')
53
+ .option('--coverage', 'Validate coverage only')
54
+ .action(async (options) => {
55
+ try {
56
+ const result = await validate(options);
57
+ if (!result.valid) {
58
+ process.exit(1);
59
+ }
60
+ } catch (error) {
61
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
62
+ process.exit(1);
63
+ }
64
+ });
65
+
66
+ // ============================================================================
67
+ // BUILD COMMAND
68
+ // ============================================================================
69
+ program
70
+ .command('build')
71
+ .description(`Build compiled ${BRAND.outFile} and ${BRAND.dataDir}/ directory`)
72
+ .option('-c, --config <path>', 'Path to config file')
73
+ .option('-o, --output <path>', 'Output file path')
74
+ .option('--registry', `Also generate ${BRAND.dataDir}/${BRAND.registryFile} and ${BRAND.contextFile}`)
75
+ .option('--registry-only', `Only generate ${BRAND.dataDir}/ directory (skip ${BRAND.outFile})`)
76
+ .option('--from-source', 'Build from source code (zero-config, no fragment files needed)')
77
+ .option('--skip-usage', 'Skip usage analysis when building from source')
78
+ .option('--skip-storybook', 'Skip Storybook parsing when building from source')
79
+ .option('-v, --verbose', 'Verbose output')
80
+ .action(async (options) => {
81
+ try {
82
+ const result = await build({
83
+ config: options.config,
84
+ output: options.output,
85
+ registry: options.registry,
86
+ registryOnly: options.registryOnly,
87
+ fromSource: options.fromSource,
88
+ skipUsage: options.skipUsage,
89
+ skipStorybook: options.skipStorybook,
90
+ verbose: options.verbose,
91
+ });
92
+ if (!result.success) {
93
+ process.exit(1);
94
+ }
95
+ } catch (error) {
96
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ // ============================================================================
102
+ // CONTEXT COMMAND
103
+ // ============================================================================
104
+ program
105
+ .command('context')
106
+ .description('Generate AI-ready context for your design system')
107
+ .option('-c, --config <path>', 'Path to config file')
108
+ .option('-i, --input <path>', `Path to ${BRAND.outFile} (builds if not provided)`)
109
+ .option('-f, --format <format>', 'Output format (markdown/json)', 'markdown')
110
+ .option('--compact', 'Minimal output for token efficiency')
111
+ .option('--code', 'Include code examples')
112
+ .option('--relations', 'Include component relationships')
113
+ .option('--tokens', 'Only output token estimate')
114
+ .action(async (options) => {
115
+ try {
116
+ const result = await context({
117
+ config: options.config,
118
+ input: options.input,
119
+ format: options.format,
120
+ compact: options.compact,
121
+ includeCode: options.code,
122
+ includeRelations: options.relations,
123
+ tokensOnly: options.tokens,
124
+ });
125
+ if (!result.success) {
126
+ process.exit(1);
127
+ }
128
+ } catch (error) {
129
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
130
+ process.exit(1);
131
+ }
132
+ });
133
+
134
+ // ============================================================================
135
+ // AI COMMAND (for Claude Desktop integration)
136
+ // ============================================================================
137
+ program
138
+ .command('ai')
139
+ .description('Generate context optimized for AI assistants (Claude Desktop)')
140
+ .option('-c, --config <path>', 'Path to config file')
141
+ .option('--live', 'Connect to running dev server for live data')
142
+ .option('-p, --port <port>', 'Dev server port', '6006')
143
+ .action(async (options) => {
144
+ try {
145
+ // If live mode, fetch from dev server
146
+ if (options.live) {
147
+ const baseUrl = `http://localhost:${options.port}`;
148
+ const response = await fetch(`${baseUrl}/${BRAND.nameLower}/context?format=markdown`);
149
+ if (!response.ok) {
150
+ throw new Error('Failed to fetch context. Make sure dev server is running.');
151
+ }
152
+ const contextContent = await response.text();
153
+ console.log(contextContent);
154
+ } else {
155
+ // Build and generate context
156
+ const result = await context({
157
+ config: options.config,
158
+ format: 'markdown',
159
+ compact: false,
160
+ includeCode: true,
161
+ includeRelations: true,
162
+ });
163
+ if (!result.success) {
164
+ process.exit(1);
165
+ }
166
+ }
167
+ } catch (error) {
168
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
169
+ process.exit(1);
170
+ }
171
+ });
172
+
173
+ // ============================================================================
174
+ // LIST COMMAND
175
+ // ============================================================================
176
+ program
177
+ .command('list')
178
+ .description('List all discovered fragment files')
179
+ .option('-c, --config <path>', 'Path to config file')
180
+ .action(async (options) => {
181
+ try {
182
+ await list(options);
183
+ } catch (error) {
184
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
185
+ process.exit(1);
186
+ }
187
+ });
188
+
189
+ // ============================================================================
190
+ // RESET COMMAND
191
+ // ============================================================================
192
+ program
193
+ .command('reset')
194
+ .description('Reset to initial state (delete all generated files)')
195
+ .option('-y, --yes', 'Skip confirmation prompt')
196
+ .option('--dry-run', 'Show what would be deleted without deleting')
197
+ .action(async (options) => {
198
+ try {
199
+ await reset(options);
200
+ } catch (error) {
201
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
202
+ process.exit(1);
203
+ }
204
+ });
205
+
206
+ // ============================================================================
207
+ // LINK COMMAND (parent for subcommands)
208
+ // ============================================================================
209
+ const linkCommand = program
210
+ .command('link')
211
+ .description('Link external resources (Figma designs, Storybook stories) to fragments');
212
+
213
+ // Link Figma subcommand
214
+ linkCommand
215
+ .command('figma')
216
+ .argument('[figma-url]', 'Figma file URL to link components from')
217
+ .description('Interactive wizard to link Figma components to code')
218
+ .option('-c, --config <path>', 'Path to config file')
219
+ .option('--auto', 'Auto-link matching components without prompts')
220
+ .option('--dry-run', 'Show matches without updating files')
221
+ .option('--no-variants', 'Skip linking individual variants to their Figma frames')
222
+ .action(async (figmaUrl, options) => {
223
+ try {
224
+ await linkFigma(figmaUrl, {
225
+ config: options.config,
226
+ auto: options.auto,
227
+ dryRun: options.dryRun,
228
+ variants: options.variants,
229
+ });
230
+ } catch (error) {
231
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
232
+ process.exit(1);
233
+ }
234
+ });
235
+
236
+ // Link Storybook subcommand
237
+ linkCommand
238
+ .command('storybook')
239
+ .description('Bootstrap fragments from existing Storybook stories')
240
+ .option('-c, --config <path>', 'Path to .storybook/main.* config')
241
+ .option('-o, --out <dir>', 'Output directory for fragment files')
242
+ .option('--yes', 'Skip confirmation prompts')
243
+ .option('--dry-run', 'Preview what would be generated without writing files')
244
+ .option('--include <glob>', 'Only process stories matching glob')
245
+ .option('--exclude <glob>', 'Skip stories matching glob')
246
+ .action(async (options) => {
247
+ try {
248
+ await linkStorybook({
249
+ config: options.config,
250
+ out: options.out,
251
+ yes: options.yes,
252
+ dryRun: options.dryRun,
253
+ include: options.include,
254
+ exclude: options.exclude,
255
+ });
256
+ } catch (error) {
257
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
258
+ process.exit(1);
259
+ }
260
+ });
261
+
262
+ // ============================================================================
263
+ // DEV COMMAND
264
+ // ============================================================================
265
+ program
266
+ .command('dev')
267
+ .description('Start the development server with live component rendering')
268
+ .option('-p, --port <port>', 'Port to run on', '6006')
269
+ .option('--no-open', 'Do not open browser')
270
+ .option('--skip-setup', 'Skip auto-setup (Storybook import, build, Figma link)')
271
+ .option('--skip-storybook', 'Skip auto-importing from Storybook')
272
+ .option('--skip-figma', 'Skip Figma link check')
273
+ .option('--skip-build', `Skip auto-building ${BRAND.outFile}`)
274
+ .action(async (options) => {
275
+ try {
276
+ await dev({
277
+ port: options.port,
278
+ open: options.open,
279
+ skipSetup: options.skipSetup,
280
+ skipStorybook: options.skipStorybook,
281
+ skipFigma: options.skipFigma,
282
+ skipBuild: options.skipBuild,
283
+ });
284
+ } catch (error) {
285
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
286
+ if (error instanceof Error && error.stack) {
287
+ console.error(pc.dim(error.stack));
288
+ }
289
+ process.exit(1);
290
+ }
291
+ });
292
+
293
+ // ============================================================================
294
+ // SCREENSHOT COMMAND
295
+ // ============================================================================
296
+ program
297
+ .command('screenshot')
298
+ .description('Capture screenshots of component variants')
299
+ .option('-c, --config <path>', 'Path to config file')
300
+ .option('--component <name>', 'Capture specific component only')
301
+ .option('--variant <name>', 'Capture specific variant only')
302
+ .option('--theme <theme>', 'Theme to capture (light/dark)', 'light')
303
+ .option('--update', 'Update existing baselines')
304
+ .option('--width <pixels>', 'Viewport width', parseInt)
305
+ .option('--height <pixels>', 'Viewport height', parseInt)
306
+ .option('--ci', 'CI mode - no interactive prompts')
307
+ .action(async (options) => {
308
+ try {
309
+ const { config, configDir } = await loadConfig(options.config);
310
+ const result = await runScreenshotCommand(config, configDir, {
311
+ component: options.component,
312
+ variant: options.variant,
313
+ theme: options.theme,
314
+ update: options.update,
315
+ ci: options.ci,
316
+ width: options.width,
317
+ height: options.height,
318
+ });
319
+ if (!result.success) {
320
+ process.exit(1);
321
+ }
322
+ } catch (error) {
323
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
324
+ process.exit(1);
325
+ }
326
+ });
327
+
328
+ // ============================================================================
329
+ // DIFF COMMAND
330
+ // ============================================================================
331
+ program
332
+ .command('diff')
333
+ .argument('[component]', 'Component name to diff (optional)')
334
+ .description('Compare current renders against baselines')
335
+ .option('-c, --config <path>', 'Path to config file')
336
+ .option('--variant <name>', 'Compare specific variant only')
337
+ .option('--theme <theme>', 'Theme to compare (light/dark)', 'light')
338
+ .option('--threshold <percent>', 'Diff threshold percentage', parseFloat)
339
+ .option('--ci', 'CI mode - exit 1 on differences')
340
+ .option('--open', 'Open diff images')
341
+ .action(async (component, options) => {
342
+ try {
343
+ const { config, configDir } = await loadConfig(options.config);
344
+ const result = await runDiffCommand(config, configDir, {
345
+ component,
346
+ variant: options.variant,
347
+ theme: options.theme,
348
+ threshold: options.threshold,
349
+ ci: options.ci,
350
+ open: options.open,
351
+ });
352
+ if (!result.success && options.ci) {
353
+ process.exit(1);
354
+ }
355
+ } catch (error) {
356
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
357
+ process.exit(1);
358
+ }
359
+ });
360
+
361
+ // ============================================================================
362
+ // COMPARE COMMAND
363
+ // ============================================================================
364
+ program
365
+ .command('compare')
366
+ .argument('[component]', 'Component name to compare')
367
+ .description('Compare component renders against Figma designs')
368
+ .option('-c, --config <path>', 'Path to config file')
369
+ .option('--variant <name>', 'Compare specific variant')
370
+ .option('--figma <url>', 'Figma frame URL (uses fragment figma link if not provided)')
371
+ .option('--threshold <percent>', 'Diff threshold percentage', parseFloat, 1.0)
372
+ .option('--all', 'Compare all components with Figma links')
373
+ .option('--output <dir>', 'Save diff images to directory')
374
+ .option('-p, --port <port>', 'Dev server port', '6006')
375
+ .action(async (component, options) => {
376
+ try {
377
+ const result = await compare(component, {
378
+ config: options.config,
379
+ variant: options.variant,
380
+ figma: options.figma,
381
+ threshold: options.threshold,
382
+ all: options.all,
383
+ output: options.output,
384
+ port: options.port,
385
+ });
386
+ if (!result.success) {
387
+ process.exit(1);
388
+ }
389
+ } catch (error) {
390
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
391
+ console.log(pc.dim(`\nMake sure the dev server is running: ${BRAND.cliCommand} dev`));
392
+ process.exit(1);
393
+ }
394
+ });
395
+
396
+ // ============================================================================
397
+ // ANALYZE COMMAND
398
+ // ============================================================================
399
+ program
400
+ .command('analyze')
401
+ .description('Analyze design system and generate report')
402
+ .option('-c, --config <path>', 'Path to config file')
403
+ .option('-f, --format <format>', 'Output format (html/json/console)', 'html')
404
+ .option('-o, --output <path>', 'Output file path')
405
+ .option('--open', 'Open report in browser after generation')
406
+ .option('--ci', 'CI mode - exit with code based on score')
407
+ .option('--min-score <score>', 'Minimum score to pass in CI mode', parseFloat)
408
+ .action(async (options) => {
409
+ try {
410
+ const { config, configDir } = await loadConfig(options.config);
411
+ const result = await runAnalyzeCommand(config, configDir, {
412
+ format: options.format,
413
+ output: options.output,
414
+ open: options.open,
415
+ ci: options.ci,
416
+ minScore: options.minScore,
417
+ });
418
+ if (!result.success) {
419
+ process.exit(1);
420
+ }
421
+ } catch (error) {
422
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
423
+ process.exit(1);
424
+ }
425
+ });
426
+
427
+ // ============================================================================
428
+ // VERIFY COMMAND
429
+ // ============================================================================
430
+ program
431
+ .command('verify')
432
+ .argument('[component]', 'Component name to verify (optional, verifies all if omitted)')
433
+ .description('Verify component compliance for CI pipelines')
434
+ .option('-c, --config <path>', 'Path to config file')
435
+ .option('--ci', 'CI mode - output JSON and exit non-zero on failure')
436
+ .option('--min-compliance <percent>', 'Minimum compliance percentage (default: 80)', parseFloat, 80)
437
+ .option('-p, --port <port>', 'Dev server port', '6006')
438
+ .action(async (component, options) => {
439
+ try {
440
+ const summary = await verify(component, {
441
+ config: options.config,
442
+ ci: options.ci,
443
+ minCompliance: options.minCompliance,
444
+ port: options.port,
445
+ });
446
+ if (!summary.passed && options.ci) {
447
+ process.exit(1);
448
+ }
449
+ } catch (error) {
450
+ if (options.ci) {
451
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : 'Verification failed' }));
452
+ } else {
453
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
454
+ }
455
+ process.exit(1);
456
+ }
457
+ });
458
+
459
+ // ============================================================================
460
+ // AUDIT COMMAND
461
+ // ============================================================================
462
+ program
463
+ .command('audit')
464
+ .description('Scan all fragments and show compliance metrics across the design system')
465
+ .option('-c, --config <path>', 'Path to config file')
466
+ .option('--json', 'Output machine-readable JSON format')
467
+ .option('--sort <field>', 'Sort by field: compliance, name, hardcoded (default: compliance)', 'compliance')
468
+ .option('-p, --port <port>', 'Dev server port', '6006')
469
+ .action(async (options) => {
470
+ try {
471
+ await audit({
472
+ config: options.config,
473
+ json: options.json,
474
+ sort: options.sort,
475
+ port: options.port,
476
+ });
477
+ } catch (error) {
478
+ if (options.json) {
479
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : 'Audit failed' }));
480
+ } else {
481
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
482
+ }
483
+ process.exit(1);
484
+ }
485
+ });
486
+
487
+ // ============================================================================
488
+ // A11Y COMMAND
489
+ // ============================================================================
490
+ program
491
+ .command('a11y')
492
+ .description('Run accessibility checks on all component variants')
493
+ .option('-c, --config <path>', 'Path to config file')
494
+ .option('--json', 'Output results as JSON')
495
+ .option('--ci', 'CI mode (exit code 1 if any critical/serious violations)')
496
+ .option('--component <name>', 'Check specific component only')
497
+ .option('-p, --port <port>', 'Dev server port', '6006')
498
+ .action(async (options) => {
499
+ try {
500
+ await a11y({
501
+ config: options.config,
502
+ json: options.json,
503
+ ci: options.ci,
504
+ component: options.component,
505
+ port: options.port,
506
+ });
507
+ } catch (error) {
508
+ if (options.json) {
509
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : 'A11y check failed' }));
510
+ } else {
511
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
512
+ }
513
+ process.exit(1);
514
+ }
515
+ });
516
+
517
+ // ============================================================================
518
+ // ENHANCE COMMAND
519
+ // ============================================================================
520
+ program
521
+ .command('enhance')
522
+ .description('AI-powered documentation generation from codebase analysis')
523
+ .option('-c, --config <path>', 'Path to config file')
524
+ .option('--component <name>', 'Enhance specific component (or "all")')
525
+ .option('--yes', 'Skip confirmation prompts')
526
+ .option('--dry-run', 'Only analyze, do not modify files')
527
+ .option('--format <format>', 'Output format: interactive, json, quiet, context', 'interactive')
528
+ .option('--provider <provider>', 'AI provider: anthropic, openai (auto-detected from API key)')
529
+ .option('--api-key <key>', 'API key (or use ANTHROPIC_API_KEY/OPENAI_API_KEY env)')
530
+ .option('--model <model>', 'AI model to use (default: claude-sonnet-4 for anthropic, gpt-4o for openai)')
531
+ .option('--root <dir>', 'Root directory to scan', process.cwd())
532
+ .option('--context-only', 'Output context for IDE AI (Cursor, Copilot) without calling API')
533
+ .action(async (options) => {
534
+ try {
535
+ await enhance({
536
+ config: options.config,
537
+ component: options.component,
538
+ yes: options.yes,
539
+ dryRun: options.dryRun,
540
+ format: options.format,
541
+ provider: options.provider,
542
+ apiKey: options.apiKey,
543
+ model: options.model,
544
+ root: options.root,
545
+ contextOnly: options.contextOnly,
546
+ });
547
+ } catch (error) {
548
+ if (options.format === 'json') {
549
+ console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Enhance failed' }));
550
+ } else {
551
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
552
+ }
553
+ process.exit(1);
554
+ }
555
+ });
556
+
557
+ // ============================================================================
558
+ // SCAN COMMAND
559
+ // ============================================================================
560
+ program
561
+ .command('scan')
562
+ .description(`Zero-config ${BRAND.outFile} generation from source code`)
563
+ .option('-c, --config <path>', 'Path to config file')
564
+ .option('-o, --output <path>', 'Output file path', BRAND.outFile)
565
+ .option('--patterns <patterns...>', 'Component file patterns to scan')
566
+ .option('--barrel <files...>', 'Barrel export files to parse')
567
+ .option('--usage-dir <dir>', 'Directory to scan for usage patterns')
568
+ .option('--skip-usage', 'Skip usage pattern analysis')
569
+ .option('--skip-storybook', 'Skip Storybook story parsing')
570
+ .option('-v, --verbose', 'Verbose output')
571
+ .action(async (options) => {
572
+ try {
573
+ const result = await scan({
574
+ config: options.config,
575
+ output: options.output,
576
+ componentPatterns: options.patterns,
577
+ barrelFiles: options.barrel,
578
+ usageDir: options.usageDir,
579
+ skipUsage: options.skipUsage,
580
+ skipStorybook: options.skipStorybook,
581
+ verbose: options.verbose,
582
+ });
583
+ if (!result.success) {
584
+ process.exit(1);
585
+ }
586
+ } catch (error) {
587
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
588
+ process.exit(1);
589
+ }
590
+ });
591
+
592
+ // ============================================================================
593
+ // STORYGEN COMMAND
594
+ // ============================================================================
595
+ program
596
+ .command('storygen')
597
+ .description('Generate Storybook stories from fragment definitions')
598
+ .option('-c, --config <path>', 'Path to config file')
599
+ .option('-o, --output <dir>', 'Output directory', '.storybook/generated')
600
+ .option('--watch', 'Watch for segment changes and regenerate')
601
+ .option('--format <format>', 'Story format (csf3)', 'csf3')
602
+ .action(async (options) => {
603
+ try {
604
+ await storygen({
605
+ config: options.config,
606
+ output: options.output,
607
+ watch: options.watch,
608
+ format: options.format,
609
+ });
610
+ } catch (error) {
611
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
612
+ process.exit(1);
613
+ }
614
+ });
615
+
616
+ // ============================================================================
617
+ // METRICS COMMAND
618
+ // ============================================================================
619
+ program
620
+ .command('metrics')
621
+ .argument('[component]', 'Component name (optional, shows system-wide if omitted)')
622
+ .description('View compliance trends over time')
623
+ .option('-c, --config <path>', 'Path to config file')
624
+ .option('--days <number>', 'Number of days to look back', parseInt, 30)
625
+ .option('--json', 'Output JSON format')
626
+ .action(async (component, options) => {
627
+ try {
628
+ await metrics(component, {
629
+ config: options.config,
630
+ days: options.days,
631
+ json: options.json,
632
+ });
633
+ } catch (error) {
634
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
635
+ process.exit(1);
636
+ }
637
+ });
638
+
639
+ // ============================================================================
640
+ // BASELINE COMMAND
641
+ // ============================================================================
642
+ program
643
+ .command('baseline')
644
+ .description('Manage visual regression baselines')
645
+ .argument('<action>', 'Action to perform: update, list, delete')
646
+ .argument('[component]', 'Component name (optional for update/delete)')
647
+ .option('-c, --config <path>', 'Path to config file')
648
+ .option('--variant <name>', 'Specific variant to update')
649
+ .option('--all', 'Update/delete all baselines')
650
+ .option('--theme <theme>', 'Theme for baseline (light/dark)', 'light')
651
+ .option('-p, --port <port>', 'Dev server port', '6006')
652
+ .action(async (action, component, options) => {
653
+ try {
654
+ await baseline(action, component, {
655
+ config: options.config,
656
+ variant: options.variant,
657
+ all: options.all,
658
+ theme: options.theme as 'light' | 'dark' | undefined,
659
+ port: options.port,
660
+ });
661
+ } catch (error) {
662
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
663
+ if (action === 'update') {
664
+ console.log(pc.dim(`\nMake sure the dev server is running: ${BRAND.cliCommand} dev`));
665
+ }
666
+ process.exit(1);
667
+ }
668
+ });
669
+
670
+ // ============================================================================
671
+ // VIEW COMMAND
672
+ // ============================================================================
673
+ program
674
+ .command('view')
675
+ .description(`Generate a static HTML viewer for ${BRAND.outFile}`)
676
+ .option('-i, --input <path>', `Path to ${BRAND.outFile}`, BRAND.outFile)
677
+ .option('-o, --output <path>', 'Output HTML file path', BRAND.viewerHtmlFile)
678
+ .option('--open', 'Open in browser after generation')
679
+ .action(async (options) => {
680
+ try {
681
+ const { generateViewerFromJson } = await import('./static-viewer.js');
682
+ const fs = await import('node:fs/promises');
683
+ const path = await import('node:path');
684
+
685
+ const inputPath = path.resolve(process.cwd(), options.input);
686
+ const outputPath = path.resolve(process.cwd(), options.output);
687
+
688
+ console.log(pc.cyan(`\n${BRAND.name} Viewer Generator\n`));
689
+
690
+ try {
691
+ await fs.access(inputPath);
692
+ } catch {
693
+ console.log(pc.red(`Error: ${options.input} not found.`));
694
+ console.log(pc.dim(`\nRun ${pc.cyan(`${BRAND.cliCommand} build`)} first to generate ${BRAND.outFile}\n`));
695
+ process.exit(1);
696
+ }
697
+
698
+ console.log(pc.dim(`Reading: ${options.input}`));
699
+ const html = await generateViewerFromJson(inputPath);
700
+
701
+ await fs.writeFile(outputPath, html);
702
+ console.log(pc.green(`\n✓ Generated: ${options.output}\n`));
703
+
704
+ if (options.open) {
705
+ const { exec } = await import('node:child_process');
706
+ const openCmd = process.platform === 'darwin' ? 'open' :
707
+ process.platform === 'win32' ? 'start' : 'xdg-open';
708
+ exec(`${openCmd} "${outputPath}"`);
709
+ }
710
+ } catch (error) {
711
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
712
+ process.exit(1);
713
+ }
714
+ });
715
+
716
+ // ============================================================================
717
+ // ADD COMMAND
718
+ // ============================================================================
719
+ program
720
+ .command('add')
721
+ .argument('[name]', 'Component name (e.g., "Button", "TextField")')
722
+ .description('Scaffold a new component with fragment file')
723
+ .option('-c, --category <category>', 'Component category')
724
+ .option('-d, --dir <directory>', 'Output directory')
725
+ .option('-t, --template <template>', 'Template to use (action, form-input, layout, display)')
726
+ .option('--no-component', 'Only generate fragment file, skip component stub')
727
+ .action(async (name, options) => {
728
+ try {
729
+ await add(name, {
730
+ category: options.category,
731
+ dir: options.dir,
732
+ template: options.template,
733
+ component: options.component,
734
+ });
735
+ } catch (error) {
736
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
737
+ process.exit(1);
738
+ }
739
+ });
740
+
741
+ // ============================================================================
742
+ // INIT COMMAND
743
+ // ============================================================================
744
+ program
745
+ .command('init')
746
+ .description('Initialize fragments in a project (interactive by default)')
747
+ .option('--force', 'Overwrite existing config')
748
+ .option('-y, --yes', 'Non-interactive mode - auto-detect and use defaults')
749
+ .action(async (options) => {
750
+ try {
751
+ const { init } = await import('./commands/init.js');
752
+ const result = await init({
753
+ projectRoot: process.cwd(),
754
+ force: options.force,
755
+ yes: options.yes,
756
+ });
757
+
758
+ if (!result.success) {
759
+ console.error(pc.red('\nInit failed with errors:'));
760
+ for (const error of result.errors) {
761
+ console.error(pc.red(` - ${error}`));
762
+ }
763
+ process.exit(1);
764
+ }
765
+ } catch (error) {
766
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
767
+ process.exit(1);
768
+ }
769
+ });
770
+
771
+ // ============================================================================
772
+ // TOKENS COMMAND
773
+ // ============================================================================
774
+ program
775
+ .command('tokens')
776
+ .description('Discover and list design tokens from CSS/SCSS files')
777
+ .option('-c, --config <path>', 'Path to config file')
778
+ .option('--json', 'Output as JSON')
779
+ .option('--categories', 'Group tokens by category')
780
+ .option('--theme <theme>', 'Filter by theme name')
781
+ .option('--category <category>', 'Filter by category (color, spacing, typography, etc.)')
782
+ .option('--verbose', 'Show all tokens (no truncation)')
783
+ .action(async (options) => {
784
+ try {
785
+ const { tokens } = await import('./commands/tokens.js');
786
+ const result = await tokens({
787
+ config: options.config,
788
+ json: options.json,
789
+ categories: options.categories,
790
+ theme: options.theme,
791
+ category: options.category,
792
+ verbose: options.verbose,
793
+ });
794
+
795
+ if (!result.success) {
796
+ process.exit(1);
797
+ }
798
+ } catch (error) {
799
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
800
+ process.exit(1);
801
+ }
802
+ });
803
+
804
+ // ============================================================================
805
+ // GENERATE COMMAND
806
+ // ============================================================================
807
+ program
808
+ .command('generate')
809
+ .description('Generate fragment files from component source code')
810
+ .argument('[component]', 'Specific component name to generate (optional)')
811
+ .option('--force', 'Overwrite existing fragment files')
812
+ .option('--pattern <glob>', 'Pattern for component files', 'src/components/**/*.tsx')
813
+ .action(async (component, options) => {
814
+ try {
815
+ const { generate } = await import('./commands/generate.js');
816
+ const result = await generate({
817
+ projectRoot: process.cwd(),
818
+ component,
819
+ force: options.force,
820
+ componentPattern: options.pattern,
821
+ });
822
+
823
+ if (!result.success) {
824
+ console.error(pc.red('\nGenerate completed with errors'));
825
+ process.exit(1);
826
+ }
827
+ } catch (error) {
828
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
829
+ process.exit(1);
830
+ }
831
+ });
832
+
833
+ // ============================================================================
834
+ // TEST COMMAND
835
+ // ============================================================================
836
+ program
837
+ .command('test')
838
+ .description('Run interaction tests for fragments with play functions')
839
+ .option('-c, --config <path>', 'Path to config file')
840
+ // Discovery options
841
+ .option('--component <name>', 'Filter by component name')
842
+ .option('--tags <tags>', 'Filter by tags (comma-separated)')
843
+ .option('--grep <pattern>', 'Filter by variant name pattern')
844
+ .option('--exclude <pattern>', 'Exclude tests matching pattern')
845
+ // Execution options
846
+ .option('--parallel <count>', 'Number of parallel browser contexts', parseInt, 4)
847
+ .option('--timeout <ms>', 'Timeout per test in milliseconds', parseInt, 30000)
848
+ .option('--retries <count>', 'Number of retries for failed tests', parseInt, 0)
849
+ .option('--bail', 'Stop on first failure')
850
+ .option('--browser <name>', 'Browser to use (chromium, firefox, webkit)', 'chromium')
851
+ .option('--headed', 'Run in headed mode (show browser)')
852
+ // Feature flags
853
+ .option('--a11y', 'Run accessibility checks with axe-core')
854
+ .option('--visual', 'Capture screenshots for visual regression')
855
+ .option('--update-snapshots', 'Update visual snapshots')
856
+ .option('--watch', 'Watch mode - re-run on file changes')
857
+ // Output options
858
+ .option('--reporters <names>', 'Reporters to use (console, junit, json)', 'console')
859
+ .option('-o, --output <dir>', 'Output directory for results', './test-results')
860
+ // Server options
861
+ .option('--server-url <url>', 'URL of running dev server (skips starting server)')
862
+ .option('-p, --port <port>', 'Port for dev server', parseInt, 6006)
863
+ // CI mode
864
+ .option('--ci', 'CI mode - non-interactive, exit with code 1 on failure')
865
+ // List only
866
+ .option('--list', 'List available tests without running them')
867
+ .action(async (options) => {
868
+ try {
869
+ const { config, configDir } = await loadConfig(options.config);
870
+
871
+ // Import test module
872
+ const { runTestCommand, listTests } = await import('./test/index.js');
873
+
874
+ // List mode
875
+ if (options.list) {
876
+ await listTests(config, configDir, {
877
+ component: options.component,
878
+ tags: options.tags,
879
+ grep: options.grep,
880
+ exclude: options.exclude,
881
+ });
882
+ return;
883
+ }
884
+
885
+ // Run tests
886
+ const exitCode = await runTestCommand(config, configDir, {
887
+ component: options.component,
888
+ tags: options.tags,
889
+ grep: options.grep,
890
+ exclude: options.exclude,
891
+ parallel: options.parallel,
892
+ timeout: options.timeout,
893
+ retries: options.retries,
894
+ bail: options.bail,
895
+ browser: options.browser as 'chromium' | 'firefox' | 'webkit',
896
+ headless: !options.headed,
897
+ a11y: options.a11y,
898
+ visual: options.visual,
899
+ updateSnapshots: options.updateSnapshots,
900
+ watch: options.watch,
901
+ reporters: options.reporters,
902
+ output: options.output,
903
+ serverUrl: options.serverUrl,
904
+ port: options.port,
905
+ ci: options.ci,
906
+ });
907
+
908
+ process.exit(exitCode);
909
+ } catch (error) {
910
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
911
+ process.exit(1);
912
+ }
913
+ });
914
+
915
+ // Parse command line arguments
916
+ program.parse();