@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/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "@fragments-sdk/cli",
3
+ "version": "0.2.2",
4
+ "description": "CLI, MCP server, and dev tools for Fragments design system",
5
+ "type": "module",
6
+ "bin": {
7
+ "fragments": "./dist/bin.js",
8
+ "fragments-mcp": "./dist/mcp-bin.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./mcp": {
19
+ "types": "./dist/mcp/index.d.ts",
20
+ "import": "./dist/mcp/index.js"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src"
29
+ ],
30
+ "dependencies": {
31
+ "@anthropic-ai/sdk": "^0.71.2",
32
+ "@babel/generator": "^7.23.6",
33
+ "@babel/parser": "^7.23.6",
34
+ "@babel/traverse": "^7.23.6",
35
+ "@babel/types": "^7.23.6",
36
+ "@figma/rest-api-spec": "^0.35.0",
37
+ "@hookform/resolvers": "^5.2.2",
38
+ "@inquirer/prompts": "^7.2.1",
39
+ "@modelcontextprotocol/sdk": "^1.0.0",
40
+ "@monaco-editor/react": "^4.7.0",
41
+ "@storybook/csf": "^0.1.12",
42
+ "@tanstack/react-virtual": "^3.13.18",
43
+ "@vitejs/plugin-react": "^4.3.0",
44
+ "autoprefixer": "^10.4.20",
45
+ "axe-core": "^4.11.1",
46
+ "chokidar": "^4.0.3",
47
+ "clsx": "^2.1.1",
48
+ "commander": "^12.1.0",
49
+ "esbuild": "^0.24.0",
50
+ "fast-glob": "^3.3.3",
51
+ "html2canvas": "^1.4.1",
52
+ "jiti": "^2.6.1",
53
+ "monaco-editor": "^0.55.1",
54
+ "openai": "^6.16.0",
55
+ "picocolors": "^1.1.1",
56
+ "pixelmatch": "^5.3.0",
57
+ "pngjs": "^7.0.0",
58
+ "postcss": "^8.4.49",
59
+ "react-colorful": "^5.6.1",
60
+ "react-hook-form": "^7.71.0",
61
+ "react-live": "^4.1.6",
62
+ "shiki": "^3.21.0",
63
+ "tailwindcss": "^3.4.17",
64
+ "vite": "^6.0.0",
65
+ "vite-plugin-svgr": "^4.5.0",
66
+ "zod": "^3.24.1"
67
+ },
68
+ "devDependencies": {
69
+ "@types/babel__generator": "^7.6.8",
70
+ "@types/babel__traverse": "^7.20.5",
71
+ "@types/node": "^22.0.0",
72
+ "@types/pixelmatch": "^5.2.6",
73
+ "@types/pngjs": "^6.0.5",
74
+ "@types/react": "^18.3.0",
75
+ "@types/react-dom": "^18.3.0",
76
+ "react": "^18.3.0",
77
+ "react-dom": "^18.3.0",
78
+ "sass": "^1.83.0",
79
+ "tsup": "^8.3.5",
80
+ "typescript": "^5.7.2",
81
+ "vitest": "^2.1.8"
82
+ },
83
+ "peerDependencies": {
84
+ "playwright": "^1.40.0",
85
+ "react": ">=18",
86
+ "react-dom": ">=18",
87
+ "typescript": "^5.0.0"
88
+ },
89
+ "peerDependenciesMeta": {
90
+ "playwright": {
91
+ "optional": true
92
+ },
93
+ "react": {
94
+ "optional": true
95
+ },
96
+ "react-dom": {
97
+ "optional": true
98
+ }
99
+ },
100
+ "scripts": {
101
+ "build": "tsup",
102
+ "dev": "tsup --watch",
103
+ "test": "vitest run",
104
+ "typecheck": "tsc --noEmit",
105
+ "clean": "rm -rf dist"
106
+ }
107
+ }
package/src/ai.ts ADDED
@@ -0,0 +1,266 @@
1
+ import type { CompiledSegment, CompiledSegmentsFile } from "./core/index.js";
2
+ import { generateContext } from "./core/index.js";
3
+
4
+ export interface AiSuggestOptions {
5
+ /** The user's prompt describing what they want to build */
6
+ prompt: string;
7
+ /** Optional additional context */
8
+ context?: string;
9
+ /** Compiled segments to use */
10
+ segments: CompiledSegment[];
11
+ /** Whether to stream output */
12
+ stream?: boolean;
13
+ }
14
+
15
+ export interface AiSuggestResult {
16
+ success: boolean;
17
+ response?: string;
18
+ error?: string;
19
+ tokensUsed?: number;
20
+ }
21
+
22
+ /**
23
+ * Call AI API to get component suggestions based on design system context.
24
+ * Supports both Anthropic and OpenAI APIs via environment variables.
25
+ */
26
+ export async function aiSuggest(options: AiSuggestOptions): Promise<AiSuggestResult> {
27
+ const { prompt, context, segments, stream = true } = options;
28
+
29
+ // Check for API keys
30
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
31
+ const openaiKey = process.env.OPENAI_API_KEY;
32
+
33
+ if (!anthropicKey && !openaiKey) {
34
+ return {
35
+ success: false,
36
+ error: "No API key found. Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable.",
37
+ };
38
+ }
39
+
40
+ // Generate design system context
41
+ const { content: systemContext } = generateContext(segments, {
42
+ format: "markdown",
43
+ compact: false,
44
+ include: {
45
+ props: true,
46
+ variants: true,
47
+ usage: true,
48
+ code: true,
49
+ },
50
+ });
51
+
52
+ // Build the system prompt
53
+ const systemPrompt = `You are a UI developer assistant helping to build interfaces using a specific design system.
54
+
55
+ You have access to the following design system components:
56
+
57
+ ${systemContext}
58
+
59
+ When suggesting components:
60
+ 1. ONLY use components from this design system - never suggest generic HTML or components not listed above
61
+ 2. Explain WHY each component is appropriate for the use case
62
+ 3. Show complete, ready-to-use code examples with correct props
63
+ 4. Follow the "when to use" and "when NOT to use" guidelines for each component
64
+ 5. Consider accessibility and best practices
65
+ 6. If multiple approaches are valid, recommend the simplest one first
66
+
67
+ Format your response as:
68
+ 1. A brief explanation of the approach
69
+ 2. Code example(s) with comments
70
+ 3. Any important notes about props or customization`;
71
+
72
+ // Build the user prompt
73
+ const userPrompt = context
74
+ ? `${prompt}\n\nAdditional context: ${context}`
75
+ : prompt;
76
+
77
+ // Prefer Anthropic, fallback to OpenAI
78
+ if (anthropicKey) {
79
+ return callAnthropic(anthropicKey, systemPrompt, userPrompt, stream);
80
+ } else {
81
+ return callOpenAI(openaiKey!, systemPrompt, userPrompt, stream);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Call Anthropic's Claude API
87
+ */
88
+ async function callAnthropic(
89
+ apiKey: string,
90
+ systemPrompt: string,
91
+ userPrompt: string,
92
+ stream: boolean
93
+ ): Promise<AiSuggestResult> {
94
+ try {
95
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
96
+ method: "POST",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ "x-api-key": apiKey,
100
+ "anthropic-version": "2023-06-01",
101
+ },
102
+ body: JSON.stringify({
103
+ model: "claude-sonnet-4-20250514",
104
+ max_tokens: 4096,
105
+ system: systemPrompt,
106
+ messages: [
107
+ {
108
+ role: "user",
109
+ content: userPrompt,
110
+ },
111
+ ],
112
+ stream: stream,
113
+ }),
114
+ });
115
+
116
+ if (!response.ok) {
117
+ const error = await response.text();
118
+ return {
119
+ success: false,
120
+ error: `Anthropic API error: ${response.status} - ${error}`,
121
+ };
122
+ }
123
+
124
+ if (stream && response.body) {
125
+ // Stream the response
126
+ const reader = response.body.getReader();
127
+ const decoder = new TextDecoder();
128
+ let fullResponse = "";
129
+
130
+ while (true) {
131
+ const { done, value } = await reader.read();
132
+ if (done) break;
133
+
134
+ const chunk = decoder.decode(value, { stream: true });
135
+ const lines = chunk.split("\n");
136
+
137
+ for (const line of lines) {
138
+ if (line.startsWith("data: ")) {
139
+ const data = line.slice(6);
140
+ if (data === "[DONE]") continue;
141
+
142
+ try {
143
+ const parsed = JSON.parse(data);
144
+ if (parsed.type === "content_block_delta" && parsed.delta?.text) {
145
+ process.stdout.write(parsed.delta.text);
146
+ fullResponse += parsed.delta.text;
147
+ }
148
+ } catch {
149
+ // Skip malformed JSON
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ console.log(); // Final newline
156
+ return {
157
+ success: true,
158
+ response: fullResponse,
159
+ };
160
+ } else {
161
+ const data = await response.json();
162
+ const text = data.content?.[0]?.text ?? "";
163
+
164
+ return {
165
+ success: true,
166
+ response: text,
167
+ tokensUsed: data.usage?.input_tokens + data.usage?.output_tokens,
168
+ };
169
+ }
170
+ } catch (error) {
171
+ return {
172
+ success: false,
173
+ error: `Failed to call Anthropic API: ${error instanceof Error ? error.message : error}`,
174
+ };
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Call OpenAI's API
180
+ */
181
+ async function callOpenAI(
182
+ apiKey: string,
183
+ systemPrompt: string,
184
+ userPrompt: string,
185
+ stream: boolean
186
+ ): Promise<AiSuggestResult> {
187
+ try {
188
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
189
+ method: "POST",
190
+ headers: {
191
+ "Content-Type": "application/json",
192
+ Authorization: `Bearer ${apiKey}`,
193
+ },
194
+ body: JSON.stringify({
195
+ model: "gpt-4o",
196
+ messages: [
197
+ { role: "system", content: systemPrompt },
198
+ { role: "user", content: userPrompt },
199
+ ],
200
+ max_tokens: 4096,
201
+ stream: stream,
202
+ }),
203
+ });
204
+
205
+ if (!response.ok) {
206
+ const error = await response.text();
207
+ return {
208
+ success: false,
209
+ error: `OpenAI API error: ${response.status} - ${error}`,
210
+ };
211
+ }
212
+
213
+ if (stream && response.body) {
214
+ // Stream the response
215
+ const reader = response.body.getReader();
216
+ const decoder = new TextDecoder();
217
+ let fullResponse = "";
218
+
219
+ while (true) {
220
+ const { done, value } = await reader.read();
221
+ if (done) break;
222
+
223
+ const chunk = decoder.decode(value, { stream: true });
224
+ const lines = chunk.split("\n");
225
+
226
+ for (const line of lines) {
227
+ if (line.startsWith("data: ")) {
228
+ const data = line.slice(6);
229
+ if (data === "[DONE]") continue;
230
+
231
+ try {
232
+ const parsed = JSON.parse(data);
233
+ const content = parsed.choices?.[0]?.delta?.content;
234
+ if (content) {
235
+ process.stdout.write(content);
236
+ fullResponse += content;
237
+ }
238
+ } catch {
239
+ // Skip malformed JSON
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ console.log(); // Final newline
246
+ return {
247
+ success: true,
248
+ response: fullResponse,
249
+ };
250
+ } else {
251
+ const data = await response.json();
252
+ const text = data.choices?.[0]?.message?.content ?? "";
253
+
254
+ return {
255
+ success: true,
256
+ response: text,
257
+ tokensUsed: data.usage?.total_tokens,
258
+ };
259
+ }
260
+ } catch (error) {
261
+ return {
262
+ success: false,
263
+ error: `Failed to call OpenAI API: ${error instanceof Error ? error.message : error}`,
264
+ };
265
+ }
266
+ }
package/src/analyze.ts ADDED
@@ -0,0 +1,265 @@
1
+ /**
2
+ * CLI command: segments analyze
3
+ *
4
+ * Analyzes the design system and generates an HTML report with:
5
+ * - Component inventory
6
+ * - Documentation coverage
7
+ * - Quality insights
8
+ * - Actionable recommendations
9
+ */
10
+
11
+ import { existsSync } from "node:fs";
12
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
13
+ import { join, dirname } from "node:path";
14
+ import pc from "picocolors";
15
+ import type { SegmentsConfig, CompiledSegmentsFile } from "./core/index.js";
16
+ import { BRAND, DEFAULTS } from "./core/index.js";
17
+ import {
18
+ analyzeDesignSystem,
19
+ generateHtmlReport,
20
+ getGrade,
21
+ getScoreColor,
22
+ type DesignSystemAnalytics,
23
+ } from "./service/index.js";
24
+
25
+ export interface AnalyzeOptions {
26
+ /** Output format */
27
+ format?: "html" | "json" | "console";
28
+ /** Output file path (default: segments-report.html) */
29
+ output?: string;
30
+ /** Open report in browser after generation */
31
+ open?: boolean;
32
+ /** CI mode - exit with appropriate code */
33
+ ci?: boolean;
34
+ /** Minimum score to pass in CI mode */
35
+ minScore?: number;
36
+ }
37
+
38
+ export interface AnalyzeResult {
39
+ success: boolean;
40
+ analytics: DesignSystemAnalytics;
41
+ outputPath?: string;
42
+ }
43
+
44
+ /**
45
+ * Run the analyze command
46
+ */
47
+ export async function runAnalyzeCommand(
48
+ config: SegmentsConfig,
49
+ configDir: string,
50
+ options: AnalyzeOptions = {}
51
+ ): Promise<AnalyzeResult> {
52
+ const format = options.format ?? "html";
53
+ const minScore = options.minScore ?? 0;
54
+
55
+ console.log(pc.cyan(`\n${BRAND.name} Analyzer\n`));
56
+
57
+ // Load compiled segments
58
+ const segmentsPath = join(configDir, config.outFile ?? "segments.json");
59
+
60
+ if (!existsSync(segmentsPath)) {
61
+ console.log(pc.red(`✗ No segments.json found. Run \`${BRAND.cliCommand} build\` first.\n`));
62
+ return {
63
+ success: false,
64
+ analytics: createEmptyAnalytics(),
65
+ };
66
+ }
67
+
68
+ console.log(pc.dim("Analyzing design system...\n"));
69
+
70
+ const content = await readFile(segmentsPath, "utf-8");
71
+ const data: CompiledSegmentsFile = JSON.parse(content);
72
+
73
+ // Run analysis
74
+ const analytics = analyzeDesignSystem(data);
75
+
76
+ // Print summary to console
77
+ printConsoleSummary(analytics);
78
+
79
+ // Generate output based on format
80
+ let outputPath: string | undefined;
81
+
82
+ if (format === "html" || format === "json") {
83
+ outputPath = options.output ?? getDefaultOutputPath(format, configDir);
84
+
85
+ // Ensure output directory exists
86
+ await mkdir(dirname(outputPath), { recursive: true });
87
+
88
+ if (format === "html") {
89
+ const html = generateHtmlReport(analytics);
90
+ await writeFile(outputPath, html);
91
+ console.log(pc.green(`✓ Report generated: ${outputPath}\n`));
92
+ } else {
93
+ await writeFile(outputPath, JSON.stringify(analytics, null, 2));
94
+ console.log(pc.green(`✓ JSON report generated: ${outputPath}\n`));
95
+ }
96
+
97
+ // Open in browser if requested
98
+ if (options.open && format === "html") {
99
+ await openInBrowser(outputPath);
100
+ }
101
+ }
102
+
103
+ // CI mode - check score
104
+ const passedCi = analytics.summary.overallScore >= minScore;
105
+
106
+ if (options.ci) {
107
+ if (passedCi) {
108
+ console.log(
109
+ pc.green(`✓ Score ${analytics.summary.overallScore} meets minimum threshold ${minScore}\n`)
110
+ );
111
+ } else {
112
+ console.log(
113
+ pc.red(
114
+ `✗ Score ${analytics.summary.overallScore} below minimum threshold ${minScore}\n`
115
+ )
116
+ );
117
+ }
118
+ }
119
+
120
+ return {
121
+ success: !options.ci || passedCi,
122
+ analytics,
123
+ outputPath,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Print a summary to the console
129
+ */
130
+ function printConsoleSummary(analytics: DesignSystemAnalytics): void {
131
+ const { summary, coverage, recommendations } = analytics;
132
+ const grade = getGrade(summary.overallScore);
133
+
134
+ // Score header
135
+ console.log(
136
+ pc.bold(
137
+ `Overall Score: ${colorizeScore(summary.overallScore)} (${grade})\n`
138
+ )
139
+ );
140
+
141
+ // Summary stats
142
+ console.log(pc.dim("Summary"));
143
+ console.log(` Components: ${pc.white(summary.totalComponents.toString())}`);
144
+ console.log(` Variants: ${pc.white(summary.totalVariants.toString())}`);
145
+ console.log(` Props: ${pc.white(summary.totalProps.toString())}`);
146
+ console.log(` Categories: ${pc.white(summary.categories.join(", "))}`);
147
+ console.log();
148
+
149
+ // Coverage
150
+ console.log(pc.dim("Coverage"));
151
+ console.log(` Description: ${formatCoverage(coverage.fields.description)}`);
152
+ console.log(` Usage when: ${formatCoverage(coverage.fields.usageWhen)}`);
153
+ console.log(` Usage whenNot:${formatCoverage(coverage.fields.usageWhenNot)}`);
154
+ console.log(` Guidelines: ${formatCoverage(coverage.fields.guidelines)}`);
155
+ console.log(` Relations: ${formatCoverage(coverage.fields.relations)}`);
156
+ console.log();
157
+
158
+ // Top recommendations
159
+ if (recommendations.length > 0) {
160
+ console.log(pc.dim("Top Recommendations"));
161
+ for (const rec of recommendations.slice(0, 3)) {
162
+ const priority = rec.priority === "high"
163
+ ? pc.red(`[${rec.priority}]`)
164
+ : rec.priority === "medium"
165
+ ? pc.yellow(`[${rec.priority}]`)
166
+ : pc.dim(`[${rec.priority}]`);
167
+ console.log(` ${priority} ${rec.title}`);
168
+ }
169
+ console.log();
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Format coverage percentage with color
175
+ */
176
+ function formatCoverage(field: { percentage: number; covered: number; total: number }): string {
177
+ const pct = colorizeScore(field.percentage);
178
+ return `${pct} (${field.covered}/${field.total})`;
179
+ }
180
+
181
+ /**
182
+ * Colorize a score
183
+ */
184
+ function colorizeScore(score: number): string {
185
+ if (score >= 80) return pc.green(`${score}%`);
186
+ if (score >= 60) return pc.yellow(`${score}%`);
187
+ return pc.red(`${score}%`);
188
+ }
189
+
190
+ /**
191
+ * Get default output path
192
+ */
193
+ function getDefaultOutputPath(format: "html" | "json", configDir: string): string {
194
+ const filename = format === "html" ? "segments-report.html" : "segments-report.json";
195
+ return join(configDir, filename);
196
+ }
197
+
198
+ /**
199
+ * Open file in default browser
200
+ */
201
+ async function openInBrowser(path: string): Promise<void> {
202
+ const { platform } = await import("node:os");
203
+ const { exec } = await import("node:child_process");
204
+
205
+ const os = platform();
206
+ const cmd = os === "darwin"
207
+ ? `open "${path}"`
208
+ : os === "win32"
209
+ ? `start "" "${path}"`
210
+ : `xdg-open "${path}"`;
211
+
212
+ exec(cmd);
213
+ }
214
+
215
+ /**
216
+ * Create empty analytics for error cases
217
+ */
218
+ function createEmptyAnalytics(): DesignSystemAnalytics {
219
+ return {
220
+ analyzedAt: new Date(),
221
+ summary: {
222
+ totalComponents: 0,
223
+ totalVariants: 0,
224
+ totalProps: 0,
225
+ categories: [],
226
+ overallScore: 0,
227
+ },
228
+ inventory: {
229
+ byCategory: {},
230
+ byStatus: {},
231
+ byVariantCount: [],
232
+ byPropCount: [],
233
+ },
234
+ coverage: {
235
+ overall: 0,
236
+ fields: {
237
+ description: { covered: 0, total: 0, percentage: 0 },
238
+ usageWhen: { covered: 0, total: 0, percentage: 0 },
239
+ usageWhenNot: { covered: 0, total: 0, percentage: 0 },
240
+ guidelines: { covered: 0, total: 0, percentage: 0 },
241
+ accessibility: { covered: 0, total: 0, percentage: 0 },
242
+ relations: { covered: 0, total: 0, percentage: 0 },
243
+ propDescriptions: { covered: 0, total: 0, percentage: 0 },
244
+ propConstraints: { covered: 0, total: 0, percentage: 0 },
245
+ },
246
+ incomplete: [],
247
+ },
248
+ quality: {
249
+ missingWhenNot: [],
250
+ isolated: [],
251
+ deprecated: [],
252
+ fewVariants: [],
253
+ undocumentedProps: [],
254
+ unconstrainedProps: [],
255
+ },
256
+ distribution: {
257
+ variantsPerComponent: [],
258
+ propsPerComponent: [],
259
+ componentsPerCategory: [],
260
+ statusDistribution: [],
261
+ tagFrequency: [],
262
+ },
263
+ recommendations: [],
264
+ };
265
+ }