@fragments-sdk/cli 0.14.3 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +0 -3
  2. package/dist/{ai-client-I6MDWNYA.js → ai-client-LSLQGOMM.js} +1 -2
  3. package/dist/bin.js +4745 -3817
  4. package/dist/bin.js.map +1 -1
  5. package/dist/{chunk-TXFCEDOC.js → chunk-2WXKALIG.js} +2 -2
  6. package/dist/{chunk-I34BC3CU.js → chunk-32LIWN2P.js} +1006 -3
  7. package/dist/chunk-32LIWN2P.js.map +1 -0
  8. package/dist/chunk-5JF26E55.js +1255 -0
  9. package/dist/chunk-5JF26E55.js.map +1 -0
  10. package/dist/{chunk-APTQIBS5.js → chunk-6SQPP47U.js} +153 -1342
  11. package/dist/chunk-6SQPP47U.js.map +1 -0
  12. package/dist/chunk-7DZC4YEV.js +294 -0
  13. package/dist/chunk-7DZC4YEV.js.map +1 -0
  14. package/dist/{chunk-PJT5IZ37.js → chunk-BJE3425I.js} +19 -52
  15. package/dist/{chunk-PJT5IZ37.js.map → chunk-BJE3425I.js.map} +1 -1
  16. package/dist/{chunk-55KERLWL.js → chunk-HQ6A6DTV.js} +1587 -1073
  17. package/dist/chunk-HQ6A6DTV.js.map +1 -0
  18. package/dist/chunk-MHIBEEW4.js +511 -0
  19. package/dist/chunk-MHIBEEW4.js.map +1 -0
  20. package/dist/{chunk-5A6X2Y73.js → chunk-ONUP6Z4W.js} +25 -13
  21. package/dist/chunk-ONUP6Z4W.js.map +1 -0
  22. package/dist/chunk-QCN35LJU.js +630 -0
  23. package/dist/chunk-QCN35LJU.js.map +1 -0
  24. package/dist/chunk-T47OLCSF.js +36 -0
  25. package/dist/chunk-T47OLCSF.js.map +1 -0
  26. package/dist/codebase-scanner-MQHUZC2G.js +21 -0
  27. package/dist/converter-7XM3Y6NJ.js +33 -0
  28. package/dist/converter-7XM3Y6NJ.js.map +1 -0
  29. package/dist/core/index.js +43 -2
  30. package/dist/create-IH4R45GE.js +806 -0
  31. package/dist/create-IH4R45GE.js.map +1 -0
  32. package/dist/{generate-RYWIPDN2.js → generate-PVOLUAAC.js} +4 -6
  33. package/dist/{generate-RYWIPDN2.js.map → generate-PVOLUAAC.js.map} +1 -1
  34. package/dist/govern-scan-OYFZYOQW.js +413 -0
  35. package/dist/govern-scan-OYFZYOQW.js.map +1 -0
  36. package/dist/index.d.ts +4 -23
  37. package/dist/index.js +15 -14
  38. package/dist/index.js.map +1 -1
  39. package/dist/{init-WRUSW7R5.js → init-SSGUSP7Z.js} +131 -129
  40. package/dist/init-SSGUSP7Z.js.map +1 -0
  41. package/dist/{init-cloud-REQ3XLHO.js → init-cloud-3DNKPWFB.js} +30 -5
  42. package/dist/{init-cloud-REQ3XLHO.js.map → init-cloud-3DNKPWFB.js.map} +1 -1
  43. package/dist/mcp-bin.js +5 -37
  44. package/dist/mcp-bin.js.map +1 -1
  45. package/dist/node-37AUE74M.js +65 -0
  46. package/dist/push-contracts-WY32TFP6.js +84 -0
  47. package/dist/push-contracts-WY32TFP6.js.map +1 -0
  48. package/dist/scan-PKSYSTRR.js +15 -0
  49. package/dist/{scan-generate-TFZVL3BT.js → scan-generate-VY27PIOX.js} +340 -52
  50. package/dist/scan-generate-VY27PIOX.js.map +1 -0
  51. package/dist/scanner-4KZNOXAK.js +12 -0
  52. package/dist/{service-HKJ6B7P7.js → service-QJGWUIVL.js} +41 -30
  53. package/dist/{snapshot-C5DYIGIV.js → snapshot-WIJMEIFT.js} +2 -3
  54. package/dist/{snapshot-C5DYIGIV.js.map → snapshot-WIJMEIFT.js.map} +1 -1
  55. package/dist/{static-viewer-DUVC4UIM.js → static-viewer-7QIBQZRC.js} +3 -4
  56. package/dist/static-viewer-7QIBQZRC.js.map +1 -0
  57. package/dist/{test-JW7JIDFG.js → test-64Z5BKBA.js} +4 -7
  58. package/dist/{test-JW7JIDFG.js.map → test-64Z5BKBA.js.map} +1 -1
  59. package/dist/token-normalizer-TEPOVBPV.js +312 -0
  60. package/dist/token-normalizer-TEPOVBPV.js.map +1 -0
  61. package/dist/token-parser-32KOIOFN.js +22 -0
  62. package/dist/token-parser-32KOIOFN.js.map +1 -0
  63. package/dist/{tokens-KE73G5JC.js → tokens-NZWFQIAB.js} +10 -9
  64. package/dist/{tokens-KE73G5JC.js.map → tokens-NZWFQIAB.js.map} +1 -1
  65. package/dist/tokens-generate-5JQSJ27E.js +85 -0
  66. package/dist/tokens-generate-5JQSJ27E.js.map +1 -0
  67. package/dist/tokens-push-HY3KO36V.js +148 -0
  68. package/dist/tokens-push-HY3KO36V.js.map +1 -0
  69. package/package.json +8 -6
  70. package/src/bin.ts +300 -48
  71. package/src/commands/__fixtures__/shadcn-label-wrapper/package.json +7 -0
  72. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.contract.json +42 -0
  73. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/label.tsx +11 -0
  74. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.contract.json +20 -0
  75. package/src/commands/__fixtures__/shadcn-label-wrapper/src/components/ui/primitive.tsx +14 -0
  76. package/src/commands/__fixtures__/shadcn-label-wrapper/tsconfig.app.json +23 -0
  77. package/src/commands/__tests__/build-freshness.test.ts +231 -0
  78. package/src/commands/__tests__/create.test.ts +71 -0
  79. package/src/commands/__tests__/drift-sync.test.ts +1 -1
  80. package/src/commands/__tests__/govern.test.ts +258 -0
  81. package/src/commands/__tests__/init.test.ts +113 -0
  82. package/src/commands/__tests__/scan-generate.test.ts +189 -70
  83. package/src/commands/__tests__/verify.test.ts +91 -0
  84. package/src/commands/build.ts +54 -1
  85. package/src/commands/context.ts +1 -1
  86. package/src/commands/create.ts +536 -0
  87. package/src/commands/discover.ts +151 -0
  88. package/src/commands/doctor.ts +3 -2
  89. package/src/commands/enhance.ts +3 -1
  90. package/src/commands/govern-scan.ts +565 -0
  91. package/src/commands/govern.ts +67 -4
  92. package/src/commands/init-cloud.ts +32 -4
  93. package/src/commands/init.ts +152 -28
  94. package/src/commands/inspect.ts +290 -0
  95. package/src/commands/migrate-contract.ts +85 -0
  96. package/src/commands/push-contracts.ts +112 -0
  97. package/src/commands/scan-generate.ts +439 -51
  98. package/src/commands/scan.ts +14 -0
  99. package/src/commands/setup.ts +27 -50
  100. package/src/commands/sync.ts +2 -2
  101. package/src/commands/tokens-generate.ts +113 -0
  102. package/src/commands/tokens-push.ts +199 -0
  103. package/src/commands/verify.ts +195 -1
  104. package/src/core/__fixtures__/shadcn-input/input.tsx +7 -0
  105. package/src/core/__fixtures__/shadcn-input/tsconfig.json +14 -0
  106. package/src/core/__fixtures__/shadcn-label/label.tsx +11 -0
  107. package/src/core/__fixtures__/shadcn-label/primitive.tsx +14 -0
  108. package/src/core/__fixtures__/shadcn-label/tsconfig.json +14 -0
  109. package/src/core/__fixtures__/shadcn-radix-label/label.tsx +11 -0
  110. package/src/core/__fixtures__/shadcn-radix-label/node_modules/radix-ui/index.d.ts +12 -0
  111. package/src/core/__fixtures__/shadcn-radix-label/tsconfig.json +14 -0
  112. package/src/core/__tests__/contract-parity.test.ts +316 -0
  113. package/src/core/__tests__/token-resolver.test.ts +1 -1
  114. package/src/core/component-extractor.test.ts +40 -1
  115. package/src/core/config.ts +2 -1
  116. package/src/core/discovery.ts +13 -2
  117. package/src/core/drift-verifier.ts +123 -0
  118. package/src/core/extractor-adapter.ts +80 -0
  119. package/src/index.ts +3 -3
  120. package/src/mcp/__tests__/projectFields.test.ts +1 -1
  121. package/src/mcp/utils.ts +1 -50
  122. package/src/migrate/converter.ts +3 -3
  123. package/src/migrate/fragment-to-contract.ts +253 -0
  124. package/src/migrate/report.ts +1 -1
  125. package/src/scripts/token-benchmark.ts +121 -0
  126. package/src/service/__tests__/props-extractor.test.ts +94 -0
  127. package/src/service/__tests__/token-normalizer.test.ts +690 -0
  128. package/src/service/ast-utils.ts +4 -23
  129. package/src/service/babel-config.ts +23 -0
  130. package/src/service/enhance/converter.ts +61 -0
  131. package/src/service/enhance/props-extractor.ts +25 -8
  132. package/src/service/enhance/scanner.ts +5 -24
  133. package/src/service/index.ts +8 -0
  134. package/src/service/snippet-validation.ts +9 -3
  135. package/src/service/tailwind-v4-parser.ts +314 -0
  136. package/src/service/token-normalizer.ts +510 -0
  137. package/src/service/token-parser.ts +56 -0
  138. package/src/setup.ts +10 -39
  139. package/src/shared/index.ts +1 -0
  140. package/src/shared/project-fields.ts +46 -0
  141. package/src/theme/__tests__/component-contrast.test.ts +2 -2
  142. package/src/theme/__tests__/serializer.test.ts +1 -1
  143. package/src/theme/generator.ts +16 -1
  144. package/src/theme/schema.ts +8 -0
  145. package/src/theme/serializer.ts +13 -9
  146. package/src/theme/types.ts +8 -0
  147. package/src/validators.ts +1 -2
  148. package/src/viewer/__tests__/viewer-integration.test.ts +8 -8
  149. package/src/viewer/style-utils.ts +27 -412
  150. package/src/viewer/vite-plugin.ts +2 -2
  151. package/dist/chunk-55KERLWL.js.map +0 -1
  152. package/dist/chunk-5A6X2Y73.js.map +0 -1
  153. package/dist/chunk-APTQIBS5.js.map +0 -1
  154. package/dist/chunk-EYXVAMEX.js +0 -626
  155. package/dist/chunk-EYXVAMEX.js.map +0 -1
  156. package/dist/chunk-I34BC3CU.js.map +0 -1
  157. package/dist/chunk-LOYS64QS.js +0 -2453
  158. package/dist/chunk-LOYS64QS.js.map +0 -1
  159. package/dist/chunk-Z7EY4VHE.js +0 -50
  160. package/dist/chunk-ZKTFKHWN.js +0 -324
  161. package/dist/chunk-ZKTFKHWN.js.map +0 -1
  162. package/dist/discovery-VDANZAJ2.js +0 -28
  163. package/dist/init-WRUSW7R5.js.map +0 -1
  164. package/dist/sass.node-4XJK6YBF.js +0 -130708
  165. package/dist/sass.node-4XJK6YBF.js.map +0 -1
  166. package/dist/scan-YJHQIRKG.js +0 -14
  167. package/dist/scan-generate-TFZVL3BT.js.map +0 -1
  168. package/dist/viewer-2TZS3NDL.js +0 -2730
  169. package/dist/viewer-2TZS3NDL.js.map +0 -1
  170. package/src/build.ts +0 -612
  171. package/src/commands/dev.ts +0 -107
  172. package/src/core/auto-props.ts +0 -464
  173. package/src/core/component-extractor.ts +0 -1030
  174. package/src/core/token-resolver.ts +0 -155
  175. /package/dist/{ai-client-I6MDWNYA.js.map → ai-client-LSLQGOMM.js.map} +0 -0
  176. /package/dist/{chunk-TXFCEDOC.js.map → chunk-2WXKALIG.js.map} +0 -0
  177. /package/dist/{chunk-Z7EY4VHE.js.map → codebase-scanner-MQHUZC2G.js.map} +0 -0
  178. /package/dist/{discovery-VDANZAJ2.js.map → node-37AUE74M.js.map} +0 -0
  179. /package/dist/{scan-YJHQIRKG.js.map → scan-PKSYSTRR.js.map} +0 -0
  180. /package/dist/{service-HKJ6B7P7.js.map → scanner-4KZNOXAK.js.map} +0 -0
  181. /package/dist/{static-viewer-DUVC4UIM.js.map → service-QJGWUIVL.js.map} +0 -0
package/src/bin.ts CHANGED
@@ -17,13 +17,14 @@ import { loadConfig } from './core/node.js';
17
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
18
  const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8')) as { version: string };
19
19
 
20
+ const EXPERIMENTAL = process.env.FRAGMENTS_EXPERIMENTAL === '1';
21
+
20
22
  // Import command implementations
21
23
  import { validate } from './commands/validate.js';
22
24
  import { build } from './commands/build.js';
23
25
  import { context } from './commands/context.js';
24
26
  import { list } from './commands/list.js';
25
27
  import { reset } from './commands/reset.js';
26
- import { dev } from './commands/dev.js';
27
28
  import { compare } from './commands/compare.js';
28
29
  import { verify } from './commands/verify.js';
29
30
  import { audit } from './commands/audit.js';
@@ -36,11 +37,14 @@ import { linkFigma, linkStorybook } from './commands/link/index.js';
36
37
  import { enhance } from './commands/enhance.js';
37
38
  import { scan } from './commands/scan.js';
38
39
  import { graph } from './commands/graph.js';
40
+ import { inspect } from './commands/inspect.js';
41
+ import { discover } from './commands/discover.js';
39
42
  import { perf } from './commands/perf.js';
40
43
  import { doctor } from './commands/doctor.js';
41
44
  import { setup } from './commands/setup.js';
42
45
  import { sync } from './commands/sync.js';
43
46
  import { governCheck, governInit, governReport, governConnect } from './commands/govern.js';
47
+ import { migrateContract } from './commands/migrate-contract.js';
44
48
 
45
49
  // Import existing commands that were already extracted
46
50
  import { runScreenshotCommand } from './screenshot.js';
@@ -119,6 +123,8 @@ program
119
123
  .option('--registry', `Also generate ${BRAND.dataDir}/${BRAND.registryFile} and ${BRAND.contextFile}`)
120
124
  .option('--registry-only', `Only generate ${BRAND.dataDir}/ directory (skip ${BRAND.outFile})`)
121
125
  .option('--from-source', 'Build from source code (zero-config, no fragment files needed)')
126
+ .option('--if-needed', `Skip rebuilding when ${BRAND.outFile} is already fresh`)
127
+ .option('--check', `Check whether ${BRAND.outFile} is fresh and exit non-zero if it is stale`)
122
128
  .option('--skip-usage', 'Skip usage analysis when building from source')
123
129
  .option('--skip-storybook', 'Skip Storybook parsing when building from source')
124
130
  .option('-v, --verbose', 'Verbose output')
@@ -130,6 +136,8 @@ program
130
136
  registry: options.registry,
131
137
  registryOnly: options.registryOnly,
132
138
  fromSource: options.fromSource,
139
+ ifNeeded: options.ifNeeded,
140
+ check: options.check,
133
141
  skipUsage: options.skipUsage,
134
142
  skipStorybook: options.skipStorybook,
135
143
  verbose: options.verbose,
@@ -304,37 +312,6 @@ linkCommand
304
312
  }
305
313
  });
306
314
 
307
- // ============================================================================
308
- // DEV COMMAND
309
- // ============================================================================
310
- program
311
- .command('dev')
312
- .description('Start the development server with live component rendering')
313
- .option('-p, --port <port>', 'Port to run on', '6006')
314
- .option('--no-open', 'Do not open browser')
315
- .option('--skip-setup', 'Skip auto-setup (Storybook import, build, Figma link)')
316
- .option('--skip-storybook', 'Skip auto-importing from Storybook')
317
- .option('--skip-figma', 'Skip Figma link check')
318
- .option('--skip-build', `Skip auto-building ${BRAND.outFile}`)
319
- .action(async (options) => {
320
- try {
321
- await dev({
322
- port: options.port,
323
- open: options.open,
324
- skipSetup: options.skipSetup,
325
- skipStorybook: options.skipStorybook,
326
- skipFigma: options.skipFigma,
327
- skipBuild: options.skipBuild,
328
- });
329
- } catch (error) {
330
- console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
331
- if (error instanceof Error && error.stack) {
332
- console.error(pc.dim(error.stack));
333
- }
334
- process.exit(1);
335
- }
336
- });
337
-
338
315
  // ============================================================================
339
316
  // SCREENSHOT COMMAND
340
317
  // ============================================================================
@@ -433,7 +410,7 @@ program
433
410
  }
434
411
  } catch (error) {
435
412
  console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
436
- console.log(pc.dim(`\nMake sure the dev server is running: ${BRAND.cliCommand} dev`));
413
+ console.log(pc.dim(`\nMake sure a dev server is running on the expected port.`));
437
414
  process.exit(1);
438
415
  }
439
416
  });
@@ -642,6 +619,33 @@ program
642
619
  }
643
620
  });
644
621
 
622
+ // ============================================================================
623
+ // MIGRATE-CONTRACT COMMAND
624
+ // ============================================================================
625
+ program
626
+ .command('migrate-contract')
627
+ .description('Migrate .fragment.tsx files to .contract.json format')
628
+ .option('-c, --config <path>', 'Path to config file')
629
+ .option('--glob <pattern>', 'Glob pattern for fragment files', 'src/**/*.fragment.tsx')
630
+ .option('--dry-run', 'Preview migration without writing files')
631
+ .option('--tsconfig <path>', 'Path to tsconfig.json')
632
+ .action(async (options) => {
633
+ try {
634
+ const result = await migrateContract({
635
+ config: options.config,
636
+ glob: options.glob,
637
+ dryRun: options.dryRun,
638
+ tsconfig: options.tsconfig,
639
+ });
640
+ if (result.failed > 0) {
641
+ process.exit(1);
642
+ }
643
+ } catch (error) {
644
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
645
+ process.exit(1);
646
+ }
647
+ });
648
+
645
649
  // ============================================================================
646
650
  // STORYGEN COMMAND
647
651
  // ============================================================================
@@ -714,7 +718,7 @@ program
714
718
  } catch (error) {
715
719
  console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
716
720
  if (action === 'update') {
717
- console.log(pc.dim(`\nMake sure the dev server is running: ${BRAND.cliCommand} dev`));
721
+ console.log(pc.dim(`\nMake sure a dev server is running on the expected port.`));
718
722
  }
719
723
  process.exit(1);
720
724
  }
@@ -791,6 +795,47 @@ program
791
795
  }
792
796
  });
793
797
 
798
+ // ============================================================================
799
+ // CREATE COMMAND
800
+ // ============================================================================
801
+ program
802
+ .command('create')
803
+ .argument('[name]', 'Project name')
804
+ .description('Create a new project with Fragments UI and your custom theme')
805
+ .option('-t, --template <template>', 'Framework template (nextjs, vite)', 'nextjs')
806
+ .option('--pm <manager>', 'Package manager (npm, pnpm, yarn, bun)')
807
+ .option('--theme <encoded>', 'Encoded theme string')
808
+ .option('--preset <id>', 'Theme preset ID from usefragments.com/create')
809
+ .option('--brand <color>', 'Brand color hex (e.g., #6366f1)')
810
+ .option('--scss', 'Use SCSS output (installs sass)')
811
+ .option('--mcp', 'Configure MCP server for AI tooling')
812
+ .option('-y, --yes', 'Skip interactive prompts')
813
+ .option('--no-git', 'Skip git initialization')
814
+ .action(async (name, options) => {
815
+ try {
816
+ const { create } = await import('./commands/create.js');
817
+ const result = await create({
818
+ name,
819
+ template: options.template,
820
+ packageManager: options.pm,
821
+ theme: options.theme,
822
+ preset: options.preset,
823
+ brand: options.brand,
824
+ scss: options.scss,
825
+ mcp: options.mcp,
826
+ yes: options.yes,
827
+ noGit: !options.git,
828
+ });
829
+ if (!result.success) {
830
+ if (result.error) console.error(pc.red(`Error: ${result.error}`));
831
+ process.exit(1);
832
+ }
833
+ } catch (error) {
834
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
835
+ process.exit(1);
836
+ }
837
+ });
838
+
794
839
  // ============================================================================
795
840
  // SETUP COMMAND
796
841
  // ============================================================================
@@ -823,27 +868,40 @@ program
823
868
  // ============================================================================
824
869
  // INIT COMMAND
825
870
  // ============================================================================
826
- program
871
+ const initCmd = program
827
872
  .command('init')
828
873
  .description('Initialize fragments in a project (zero-config by default)')
829
874
  .option('--force', 'Overwrite existing config')
830
875
  .option('-y, --yes', 'Non-interactive mode (now the default)')
831
- .option('--cloud', 'Set up Fragments Cloud governance (zero-config browser auth)')
832
- .option('--cloud-url <url>', 'Cloud dashboard URL (default: https://app.usefragments.com)')
833
- .option('--port <port>', 'Localhost port for auth callback (default: 9876)')
834
- .option('--auth-only', 'Only authenticate, skip project setup')
835
- .option('--skip-check', 'Skip running the first governance check')
836
876
  .option('--configure', 'Interactive mode for theme seeds, snapshots, etc.')
877
+ .option('--metadata-only', 'Generate config and metadata without modifying runtime app setup')
878
+ .option('--govern', 'Alias for --metadata-only')
837
879
  .option('--scan <path>', 'Scan a TypeScript component directory and generate fragment files')
838
880
  .option('--enrich', 'Use AI to fill knowledge fields during --scan (requires API key)')
839
881
  .option('--dry-run', 'Show what --enrich would generate without calling API')
840
882
  .option('--provider <provider>', 'AI provider for enrichment: anthropic or openai')
841
883
  .option('--api-key <key>', 'API key for AI enrichment')
842
- .option('--model <model>', 'Override AI model for enrichment')
843
- .action(async (options) => {
884
+ .option('--model <model>', 'Override AI model for enrichment');
885
+
886
+ // Cloud governance flags — only visible with FRAGMENTS_EXPERIMENTAL=1
887
+ if (EXPERIMENTAL) {
888
+ initCmd
889
+ .option('--cloud', 'Set up Fragments Cloud governance (zero-config browser auth)')
890
+ .option('--cloud-url <url>', 'Cloud dashboard URL (default: https://app.usefragments.com)')
891
+ .option('--port <port>', 'Localhost port for auth callback (default: 9876)')
892
+ .option('--auth-only', 'Only authenticate, skip project setup')
893
+ .option('--skip-check', 'Skip running the first governance check');
894
+ }
895
+
896
+ initCmd.action(async (options) => {
844
897
  try {
845
- // Cloud init — separate flow
898
+ // Cloud init — experimental, requires FRAGMENTS_EXPERIMENTAL=1
846
899
  if (options.cloud) {
900
+ if (!EXPERIMENTAL) {
901
+ console.log(pc.yellow(`\n Fragments Cloud is not yet publicly available.`));
902
+ console.log(pc.dim(` Set FRAGMENTS_EXPERIMENTAL=1 to enable preview features.\n`));
903
+ process.exit(1);
904
+ }
847
905
  const { initCloud } = await import('./commands/init-cloud.js');
848
906
  await initCloud({
849
907
  url: options.cloudUrl,
@@ -866,6 +924,8 @@ program
866
924
  provider: options.provider,
867
925
  apiKey: options.apiKey,
868
926
  model: options.model,
927
+ metadataOnly: options.metadataOnly,
928
+ govern: options.govern,
869
929
  });
870
930
 
871
931
  if (!result.success) {
@@ -912,11 +972,15 @@ program
912
972
  });
913
973
 
914
974
  // ============================================================================
915
- // TOKENS COMMAND
975
+ // TOKENS COMMAND GROUP
916
976
  // ============================================================================
917
- program
977
+ const tokensCmd = program
918
978
  .command('tokens')
919
- .description('Discover and list design tokens from CSS/SCSS files')
979
+ .description('Design token discovery, listing, and generation');
980
+
981
+ tokensCmd
982
+ .command('list', { isDefault: true })
983
+ .description('Discover and list design tokens from CSS/SCSS/DTCG files')
920
984
  .option('-c, --config <path>', 'Path to config file')
921
985
  .option('--json', 'Output as JSON')
922
986
  .option('--categories', 'Group tokens by category')
@@ -944,6 +1008,54 @@ program
944
1008
  }
945
1009
  });
946
1010
 
1011
+ tokensCmd
1012
+ .command('generate')
1013
+ .description('Generate CSS, SCSS, Tailwind, or Figma output from a DTCG .tokens.json file')
1014
+ .requiredOption('--from <path>', 'Path to DTCG .tokens.json source file')
1015
+ .requiredOption('--format <formats>', 'Output formats (comma-separated: css, scss, tailwind, figma)')
1016
+ .option('--out <dir>', 'Output directory (default: same directory as source)')
1017
+ .option('--prefix <prefix>', 'Token name prefix')
1018
+ .option('--selector <selector>', 'CSS selector for custom properties (default: :root)')
1019
+ .option('--verbose', 'Verbose output')
1020
+ .action(async (options) => {
1021
+ try {
1022
+ const { tokensGenerate } = await import('./commands/tokens-generate.js');
1023
+ await tokensGenerate({
1024
+ from: options.from,
1025
+ format: options.format,
1026
+ out: options.out,
1027
+ prefix: options.prefix,
1028
+ selector: options.selector,
1029
+ verbose: options.verbose,
1030
+ });
1031
+ } catch (error) {
1032
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1033
+ process.exit(1);
1034
+ }
1035
+ });
1036
+
1037
+ tokensCmd
1038
+ .command('push')
1039
+ .description('Push code tokens to Fragments Cloud for drift comparison')
1040
+ .option('-c, --config <path>', 'Path to fragments config file')
1041
+ .option('--tailwind-v4 <path>', 'Path to Tailwind v4 CSS file with @theme block')
1042
+ .option('--dry-run', 'Parse and display tokens without pushing')
1043
+ .option('--verbose', 'Show detailed output')
1044
+ .action(async (options) => {
1045
+ try {
1046
+ const { tokensPush } = await import('./commands/tokens-push.js');
1047
+ await tokensPush({
1048
+ config: options.config,
1049
+ tailwindV4: options.tailwindV4,
1050
+ dryRun: options.dryRun,
1051
+ verbose: options.verbose,
1052
+ });
1053
+ } catch (error) {
1054
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1055
+ process.exit(1);
1056
+ }
1057
+ });
1058
+
947
1059
  // ============================================================================
948
1060
  // GENERATE COMMAND
949
1061
  // ============================================================================
@@ -995,6 +1107,71 @@ program
995
1107
  }
996
1108
  });
997
1109
 
1110
+ // ============================================================================
1111
+ // INSPECT COMMAND
1112
+ // ============================================================================
1113
+ program
1114
+ .command('inspect')
1115
+ .description('Inspect a single component from compiled fragments data')
1116
+ .argument('<component>', 'Component name to inspect')
1117
+ .option('-c, --config <path>', 'Path to config file')
1118
+ .option('--fields <fields>', 'Comma-separated fields to include (dot notation: meta, props, examples)')
1119
+ .option('--variant <name>', 'Filter to a specific variant')
1120
+ .option('--verbosity <level>', 'Output verbosity: compact, standard, full', 'standard')
1121
+ .option('--maxExamples <n>', 'Max variant examples to include', (v) => Number.parseInt(v, 10))
1122
+ .option('--json', 'Output as JSON')
1123
+ .action(async (component, options) => {
1124
+ try {
1125
+ await inspect(component, {
1126
+ config: options.config,
1127
+ fields: options.fields,
1128
+ variant: options.variant,
1129
+ verbosity: options.verbosity,
1130
+ maxExamples: options.maxExamples,
1131
+ json: options.json,
1132
+ });
1133
+ } catch (error) {
1134
+ if (options.json) {
1135
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : 'Inspect failed' }));
1136
+ } else {
1137
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1138
+ }
1139
+ process.exit(1);
1140
+ }
1141
+ });
1142
+
1143
+ // ============================================================================
1144
+ // DISCOVER COMMAND
1145
+ // ============================================================================
1146
+ program
1147
+ .command('discover')
1148
+ .description('List and filter components from compiled fragments data')
1149
+ .option('-c, --config <path>', 'Path to config file')
1150
+ .option('--search <term>', 'Search by name, description, or tags')
1151
+ .option('--category <category>', 'Filter by category')
1152
+ .option('--status <status>', 'Filter by status: stable, beta, deprecated, experimental')
1153
+ .option('--compact', 'Output component names only')
1154
+ .option('--json', 'Output as JSON')
1155
+ .action(async (options) => {
1156
+ try {
1157
+ await discover({
1158
+ config: options.config,
1159
+ search: options.search,
1160
+ category: options.category,
1161
+ status: options.status,
1162
+ compact: options.compact,
1163
+ json: options.json,
1164
+ });
1165
+ } catch (error) {
1166
+ if (options.json) {
1167
+ console.log(JSON.stringify({ error: error instanceof Error ? error.message : 'Discover failed' }));
1168
+ } else {
1169
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1170
+ }
1171
+ process.exit(1);
1172
+ }
1173
+ });
1174
+
998
1175
  // ============================================================================
999
1176
  // PERF COMMAND
1000
1177
  // ============================================================================
@@ -1134,11 +1311,18 @@ program
1134
1311
  });
1135
1312
 
1136
1313
  // ============================================================================
1137
- // GOVERN COMMAND
1314
+ // GOVERN COMMAND (experimental — FRAGMENTS_EXPERIMENTAL=1)
1138
1315
  // ============================================================================
1139
1316
  const governCmd = program
1140
1317
  .command('govern')
1141
- .description('AI UI governance checks');
1318
+ .description(EXPERIMENTAL ? 'AI UI governance checks' : 'AI UI governance checks (preview — set FRAGMENTS_EXPERIMENTAL=1)')
1319
+ .hook('preAction', () => {
1320
+ if (!EXPERIMENTAL) {
1321
+ console.log(pc.yellow(`\n Fragments governance is not yet publicly available.`));
1322
+ console.log(pc.dim(` Set FRAGMENTS_EXPERIMENTAL=1 to enable preview features.\n`));
1323
+ process.exit(1);
1324
+ }
1325
+ });
1142
1326
 
1143
1327
  governCmd
1144
1328
  .command('check')
@@ -1199,5 +1383,73 @@ governCmd
1199
1383
  }
1200
1384
  });
1201
1385
 
1386
+ governCmd
1387
+ .command('scan')
1388
+ .description('Scan JSX/TSX codebase for governance violations')
1389
+ .option('-d, --dir <path>', 'Root directory (default: auto-detect)')
1390
+ .option('-c, --config <path>', 'Path to govern.config.ts')
1391
+ .option('-f, --format <format>', 'Output format: summary, json, sarif', 'summary')
1392
+ .option('-q, --quiet', 'Suppress non-error output')
1393
+ .action(async (options) => {
1394
+ try {
1395
+ const { governScan } = await import('./commands/govern-scan.js');
1396
+ const { exitCode } = await governScan({
1397
+ dir: options.dir,
1398
+ config: options.config,
1399
+ format: options.format,
1400
+ quiet: options.quiet,
1401
+ });
1402
+ process.exit(exitCode);
1403
+ } catch (error) {
1404
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1405
+ process.exit(1);
1406
+ }
1407
+ });
1408
+
1409
+ governCmd
1410
+ .command('watch')
1411
+ .description('Watch JSX/TSX files and re-check on changes')
1412
+ .option('-d, --dir <path>', 'Root directory (default: auto-detect)')
1413
+ .option('-c, --config <path>', 'Path to govern.config.ts')
1414
+ .option('-q, --quiet', 'Suppress non-error output')
1415
+ .option('--debounce <ms>', 'Debounce interval in ms', '300')
1416
+ .action(async (options) => {
1417
+ try {
1418
+ const { governWatch } = await import('./commands/govern-scan.js');
1419
+ await governWatch({
1420
+ dir: options.dir,
1421
+ config: options.config,
1422
+ quiet: options.quiet,
1423
+ debounce: parseInt(options.debounce, 10),
1424
+ });
1425
+ } catch (error) {
1426
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1427
+ process.exit(1);
1428
+ }
1429
+ });
1430
+
1431
+ governCmd
1432
+ .command('push-contracts')
1433
+ .description('Push component contracts to Fragments Cloud')
1434
+ .option('-i, --input <path>', 'Path to fragments.json (default: ./fragments.json)')
1435
+ .option('--url <url>', 'Fragments Cloud URL')
1436
+ .option('--api-key <key>', 'API key (default: FRAGMENTS_API_KEY env var)')
1437
+ .option('-q, --quiet', 'Suppress non-error output')
1438
+ .action(async (options) => {
1439
+ try {
1440
+ const { pushContracts } = await import('./commands/push-contracts.js');
1441
+ const { exitCode } = await pushContracts({
1442
+ input: options.input,
1443
+ url: options.url,
1444
+ apiKey: options.apiKey,
1445
+ quiet: options.quiet,
1446
+ });
1447
+ process.exit(exitCode);
1448
+ } catch (error) {
1449
+ console.error(pc.red('Error:'), error instanceof Error ? error.message : error);
1450
+ process.exit(1);
1451
+ }
1452
+ });
1453
+
1202
1454
  // Parse command line arguments
1203
1455
  program.parse();
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "shadcn-label-wrapper",
3
+ "private": true,
4
+ "devDependencies": {
5
+ "@fragments-sdk/core": "^1.0.1"
6
+ }
7
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$schema": "https://usefragments.com/schemas/contract.v1.json",
3
+ "name": "Label",
4
+ "description": "Label component",
5
+ "category": "Typography",
6
+ "sourcePath": "label.tsx",
7
+ "exportName": "Label",
8
+ "propsSummary": [
9
+ "className: string",
10
+ "htmlFor: string",
11
+ "form: string",
12
+ "children: node"
13
+ ],
14
+ "props": {
15
+ "className": {
16
+ "type": "string",
17
+ "description": ""
18
+ },
19
+ "htmlFor": {
20
+ "type": "string",
21
+ "description": ""
22
+ },
23
+ "form": {
24
+ "type": "string",
25
+ "description": ""
26
+ },
27
+ "children": {
28
+ "type": "node",
29
+ "description": ""
30
+ }
31
+ },
32
+ "usage": {
33
+ "when": [],
34
+ "whenNot": []
35
+ },
36
+ "provenance": {
37
+ "source": "extracted",
38
+ "verified": false,
39
+ "frameworkSupport": "native",
40
+ "extractedAt": "2026-03-22T23:45:29.606Z"
41
+ }
42
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import { Primitive } from "./primitive";
3
+
4
+ function Label({
5
+ className,
6
+ ...props
7
+ }: React.ComponentProps<typeof Primitive.Root>) {
8
+ return <Primitive.Root data-class={className} {...props} />;
9
+ }
10
+
11
+ export { Label };
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://usefragments.com/schemas/contract.v1.json",
3
+ "name": "Primitive",
4
+ "description": "Primitive component",
5
+ "category": "Components",
6
+ "sourcePath": "primitive.tsx",
7
+ "exportName": "Primitive",
8
+ "propsSummary": [],
9
+ "props": {},
10
+ "usage": {
11
+ "when": [],
12
+ "whenNot": []
13
+ },
14
+ "provenance": {
15
+ "source": "extracted",
16
+ "verified": false,
17
+ "frameworkSupport": "native",
18
+ "extractedAt": "2026-03-22T23:45:29.607Z"
19
+ }
20
+ }
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+
3
+ export interface PrimitiveLabelProps {
4
+ className?: string;
5
+ htmlFor?: string;
6
+ form?: string;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ function Root(props: PrimitiveLabelProps) {
11
+ return <label {...props} />;
12
+ }
13
+
14
+ export const Primitive = { Root };
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "allowSyntheticDefaultImports": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": [
12
+ "react",
13
+ "react-dom"
14
+ ]
15
+ },
16
+ "include": [
17
+ "src/**/*.tsx"
18
+ ],
19
+ "exclude": [
20
+ "**/*.fragment.tsx",
21
+ "**/*.contract.json"
22
+ ]
23
+ }