popeye-cli 1.0.1 → 1.2.0

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 (216) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +832 -123
  5. package/dist/adapters/claude.d.ts +19 -4
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +908 -42
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/gemini.d.ts +55 -0
  10. package/dist/adapters/gemini.d.ts.map +1 -0
  11. package/dist/adapters/gemini.js +318 -0
  12. package/dist/adapters/gemini.js.map +1 -0
  13. package/dist/adapters/grok.d.ts +73 -0
  14. package/dist/adapters/grok.d.ts.map +1 -0
  15. package/dist/adapters/grok.js +430 -0
  16. package/dist/adapters/grok.js.map +1 -0
  17. package/dist/adapters/openai.d.ts +1 -1
  18. package/dist/adapters/openai.d.ts.map +1 -1
  19. package/dist/adapters/openai.js +47 -8
  20. package/dist/adapters/openai.js.map +1 -1
  21. package/dist/auth/claude.d.ts +11 -9
  22. package/dist/auth/claude.d.ts.map +1 -1
  23. package/dist/auth/claude.js +107 -71
  24. package/dist/auth/claude.js.map +1 -1
  25. package/dist/auth/gemini.d.ts +58 -0
  26. package/dist/auth/gemini.d.ts.map +1 -0
  27. package/dist/auth/gemini.js +172 -0
  28. package/dist/auth/gemini.js.map +1 -0
  29. package/dist/auth/grok.d.ts +73 -0
  30. package/dist/auth/grok.d.ts.map +1 -0
  31. package/dist/auth/grok.js +211 -0
  32. package/dist/auth/grok.js.map +1 -0
  33. package/dist/auth/index.d.ts +14 -7
  34. package/dist/auth/index.d.ts.map +1 -1
  35. package/dist/auth/index.js +41 -6
  36. package/dist/auth/index.js.map +1 -1
  37. package/dist/auth/keychain.d.ts +20 -7
  38. package/dist/auth/keychain.d.ts.map +1 -1
  39. package/dist/auth/keychain.js +85 -29
  40. package/dist/auth/keychain.js.map +1 -1
  41. package/dist/auth/openai.d.ts +2 -2
  42. package/dist/auth/openai.d.ts.map +1 -1
  43. package/dist/auth/openai.js +30 -32
  44. package/dist/auth/openai.js.map +1 -1
  45. package/dist/cli/commands/auth.d.ts +1 -1
  46. package/dist/cli/commands/auth.d.ts.map +1 -1
  47. package/dist/cli/commands/auth.js +79 -8
  48. package/dist/cli/commands/auth.js.map +1 -1
  49. package/dist/cli/commands/create.d.ts.map +1 -1
  50. package/dist/cli/commands/create.js +15 -4
  51. package/dist/cli/commands/create.js.map +1 -1
  52. package/dist/cli/interactive.d.ts.map +1 -1
  53. package/dist/cli/interactive.js +1494 -114
  54. package/dist/cli/interactive.js.map +1 -1
  55. package/dist/config/defaults.d.ts +9 -1
  56. package/dist/config/defaults.d.ts.map +1 -1
  57. package/dist/config/defaults.js +19 -2
  58. package/dist/config/defaults.js.map +1 -1
  59. package/dist/config/index.d.ts +19 -0
  60. package/dist/config/index.d.ts.map +1 -1
  61. package/dist/config/index.js +33 -1
  62. package/dist/config/index.js.map +1 -1
  63. package/dist/config/schema.d.ts +47 -0
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +29 -1
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/generators/fullstack.d.ts +32 -0
  68. package/dist/generators/fullstack.d.ts.map +1 -0
  69. package/dist/generators/fullstack.js +497 -0
  70. package/dist/generators/fullstack.js.map +1 -0
  71. package/dist/generators/index.d.ts +4 -3
  72. package/dist/generators/index.d.ts.map +1 -1
  73. package/dist/generators/index.js +15 -1
  74. package/dist/generators/index.js.map +1 -1
  75. package/dist/generators/python.d.ts +17 -1
  76. package/dist/generators/python.d.ts.map +1 -1
  77. package/dist/generators/python.js +34 -20
  78. package/dist/generators/python.js.map +1 -1
  79. package/dist/generators/templates/fullstack.d.ts +113 -0
  80. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  81. package/dist/generators/templates/fullstack.js +1004 -0
  82. package/dist/generators/templates/fullstack.js.map +1 -0
  83. package/dist/generators/typescript.d.ts +19 -1
  84. package/dist/generators/typescript.d.ts.map +1 -1
  85. package/dist/generators/typescript.js +37 -20
  86. package/dist/generators/typescript.js.map +1 -1
  87. package/dist/state/index.d.ts +108 -0
  88. package/dist/state/index.d.ts.map +1 -1
  89. package/dist/state/index.js +551 -4
  90. package/dist/state/index.js.map +1 -1
  91. package/dist/state/registry.d.ts +52 -0
  92. package/dist/state/registry.d.ts.map +1 -0
  93. package/dist/state/registry.js +215 -0
  94. package/dist/state/registry.js.map +1 -0
  95. package/dist/types/cli.d.ts +8 -0
  96. package/dist/types/cli.d.ts.map +1 -1
  97. package/dist/types/cli.js.map +1 -1
  98. package/dist/types/consensus.d.ts +186 -4
  99. package/dist/types/consensus.d.ts.map +1 -1
  100. package/dist/types/consensus.js +35 -3
  101. package/dist/types/consensus.js.map +1 -1
  102. package/dist/types/project.d.ts +76 -0
  103. package/dist/types/project.d.ts.map +1 -1
  104. package/dist/types/project.js +1 -1
  105. package/dist/types/project.js.map +1 -1
  106. package/dist/types/workflow.d.ts +217 -16
  107. package/dist/types/workflow.d.ts.map +1 -1
  108. package/dist/types/workflow.js +40 -1
  109. package/dist/types/workflow.js.map +1 -1
  110. package/dist/workflow/auto-fix.d.ts +45 -0
  111. package/dist/workflow/auto-fix.d.ts.map +1 -0
  112. package/dist/workflow/auto-fix.js +274 -0
  113. package/dist/workflow/auto-fix.js.map +1 -0
  114. package/dist/workflow/consensus.d.ts +70 -2
  115. package/dist/workflow/consensus.d.ts.map +1 -1
  116. package/dist/workflow/consensus.js +872 -17
  117. package/dist/workflow/consensus.js.map +1 -1
  118. package/dist/workflow/execution-mode.d.ts +10 -4
  119. package/dist/workflow/execution-mode.d.ts.map +1 -1
  120. package/dist/workflow/execution-mode.js +547 -58
  121. package/dist/workflow/execution-mode.js.map +1 -1
  122. package/dist/workflow/index.d.ts +14 -2
  123. package/dist/workflow/index.d.ts.map +1 -1
  124. package/dist/workflow/index.js +69 -6
  125. package/dist/workflow/index.js.map +1 -1
  126. package/dist/workflow/milestone-workflow.d.ts +34 -0
  127. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  128. package/dist/workflow/milestone-workflow.js +414 -0
  129. package/dist/workflow/milestone-workflow.js.map +1 -0
  130. package/dist/workflow/plan-mode.d.ts +80 -3
  131. package/dist/workflow/plan-mode.d.ts.map +1 -1
  132. package/dist/workflow/plan-mode.js +767 -49
  133. package/dist/workflow/plan-mode.js.map +1 -1
  134. package/dist/workflow/plan-storage.d.ts +386 -0
  135. package/dist/workflow/plan-storage.d.ts.map +1 -0
  136. package/dist/workflow/plan-storage.js +878 -0
  137. package/dist/workflow/plan-storage.js.map +1 -0
  138. package/dist/workflow/project-verification.d.ts +37 -0
  139. package/dist/workflow/project-verification.d.ts.map +1 -0
  140. package/dist/workflow/project-verification.js +381 -0
  141. package/dist/workflow/project-verification.js.map +1 -0
  142. package/dist/workflow/task-workflow.d.ts +37 -0
  143. package/dist/workflow/task-workflow.d.ts.map +1 -0
  144. package/dist/workflow/task-workflow.js +386 -0
  145. package/dist/workflow/task-workflow.js.map +1 -0
  146. package/dist/workflow/test-runner.d.ts +9 -0
  147. package/dist/workflow/test-runner.d.ts.map +1 -1
  148. package/dist/workflow/test-runner.js +101 -5
  149. package/dist/workflow/test-runner.js.map +1 -1
  150. package/dist/workflow/ui-designer.d.ts +82 -0
  151. package/dist/workflow/ui-designer.d.ts.map +1 -0
  152. package/dist/workflow/ui-designer.js +234 -0
  153. package/dist/workflow/ui-designer.js.map +1 -0
  154. package/dist/workflow/ui-setup.d.ts +58 -0
  155. package/dist/workflow/ui-setup.d.ts.map +1 -0
  156. package/dist/workflow/ui-setup.js +685 -0
  157. package/dist/workflow/ui-setup.js.map +1 -0
  158. package/dist/workflow/ui-verification.d.ts +114 -0
  159. package/dist/workflow/ui-verification.d.ts.map +1 -0
  160. package/dist/workflow/ui-verification.js +258 -0
  161. package/dist/workflow/ui-verification.js.map +1 -0
  162. package/dist/workflow/workflow-logger.d.ts +110 -0
  163. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  164. package/dist/workflow/workflow-logger.js +267 -0
  165. package/dist/workflow/workflow-logger.js.map +1 -0
  166. package/dist/workflow/workspace-manager.d.ts +342 -0
  167. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  168. package/dist/workflow/workspace-manager.js +733 -0
  169. package/dist/workflow/workspace-manager.js.map +1 -0
  170. package/package.json +2 -2
  171. package/src/adapters/claude.ts +1067 -47
  172. package/src/adapters/gemini.ts +373 -0
  173. package/src/adapters/grok.ts +492 -0
  174. package/src/adapters/openai.ts +48 -9
  175. package/src/auth/claude.ts +120 -78
  176. package/src/auth/gemini.ts +207 -0
  177. package/src/auth/grok.ts +255 -0
  178. package/src/auth/index.ts +47 -9
  179. package/src/auth/keychain.ts +95 -28
  180. package/src/auth/openai.ts +29 -36
  181. package/src/cli/commands/auth.ts +89 -10
  182. package/src/cli/commands/create.ts +13 -4
  183. package/src/cli/interactive.ts +1774 -142
  184. package/src/config/defaults.ts +19 -2
  185. package/src/config/index.ts +36 -1
  186. package/src/config/schema.ts +30 -1
  187. package/src/generators/fullstack.ts +551 -0
  188. package/src/generators/index.ts +25 -1
  189. package/src/generators/python.ts +65 -20
  190. package/src/generators/templates/fullstack.ts +1047 -0
  191. package/src/generators/typescript.ts +69 -20
  192. package/src/state/index.ts +713 -4
  193. package/src/state/registry.ts +278 -0
  194. package/src/types/cli.ts +8 -0
  195. package/src/types/consensus.ts +197 -6
  196. package/src/types/project.ts +82 -1
  197. package/src/types/workflow.ts +90 -1
  198. package/src/workflow/auto-fix.ts +340 -0
  199. package/src/workflow/consensus.ts +1180 -16
  200. package/src/workflow/execution-mode.ts +673 -74
  201. package/src/workflow/index.ts +95 -6
  202. package/src/workflow/milestone-workflow.ts +576 -0
  203. package/src/workflow/plan-mode.ts +924 -50
  204. package/src/workflow/plan-storage.ts +1282 -0
  205. package/src/workflow/project-verification.ts +471 -0
  206. package/src/workflow/task-workflow.ts +528 -0
  207. package/src/workflow/test-runner.ts +120 -5
  208. package/src/workflow/ui-designer.ts +337 -0
  209. package/src/workflow/ui-setup.ts +797 -0
  210. package/src/workflow/ui-verification.ts +357 -0
  211. package/src/workflow/workflow-logger.ts +353 -0
  212. package/src/workflow/workspace-manager.ts +912 -0
  213. package/tests/config/config.test.ts +1 -1
  214. package/tests/types/consensus.test.ts +3 -3
  215. package/tests/workflow/plan-mode.test.ts +213 -0
  216. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,337 @@
1
+ /**
2
+ * UI Designer Module
3
+ * Generates UI design specifications from natural language project ideas
4
+ * Ensures Popeye handles all UI/UX automatically without user intervention
5
+ */
6
+
7
+ import { generateCode } from '../adapters/claude.js';
8
+ import { THEMES, detectProjectType, type DesignTheme } from './ui-setup.js';
9
+
10
+ /**
11
+ * UI Design Intent - what the user wants their app to look like
12
+ */
13
+ export interface UIDesignIntent {
14
+ /** Overall style preference */
15
+ style: 'modern' | 'elegant' | 'minimal' | 'vibrant' | 'professional' | 'playful';
16
+
17
+ /** Target audience */
18
+ audience: 'business' | 'consumer' | 'developer' | 'creative' | 'general';
19
+
20
+ /** Industry/domain */
21
+ industry?: string;
22
+
23
+ /** Color preferences */
24
+ colorPreference?: 'warm' | 'cool' | 'neutral' | 'colorful' | 'monochrome';
25
+
26
+ /** Key UI features needed */
27
+ features: string[];
28
+
29
+ /** Accessibility requirements */
30
+ accessibilityLevel: 'A' | 'AA' | 'AAA';
31
+
32
+ /** Dark mode support */
33
+ darkMode: boolean;
34
+
35
+ /** Mobile-first design */
36
+ mobileFirst: boolean;
37
+ }
38
+
39
+ /**
40
+ * Complete UI specification generated by the designer
41
+ */
42
+ export interface UISpecification {
43
+ /** The design intent derived from the idea */
44
+ intent: UIDesignIntent;
45
+
46
+ /** Selected theme */
47
+ theme: DesignTheme;
48
+
49
+ /** Theme name for reference */
50
+ themeName: string;
51
+
52
+ /** Project type detection */
53
+ projectType: string;
54
+
55
+ /** Components recommended for this project */
56
+ recommendedComponents: string[];
57
+
58
+ /** Layout patterns to use */
59
+ layoutPatterns: string[];
60
+
61
+ /** Navigation style */
62
+ navigationStyle: 'sidebar' | 'topbar' | 'bottom-tabs' | 'hybrid';
63
+
64
+ /** Key pages to create */
65
+ keyPages: Array<{
66
+ name: string;
67
+ route: string;
68
+ description: string;
69
+ layout: string;
70
+ }>;
71
+
72
+ /** Design system notes for code generation */
73
+ designNotes: string;
74
+ }
75
+
76
+ /**
77
+ * Analyze the project idea and generate UI design intent
78
+ */
79
+ export async function analyzeUIIntent(
80
+ idea: string,
81
+ onProgress?: (message: string) => void
82
+ ): Promise<UIDesignIntent> {
83
+ onProgress?.('Analyzing project idea for UI intent...');
84
+
85
+ const prompt = `Analyze this project idea and determine the UI design intent. Return ONLY a JSON object (no markdown, no explanation).
86
+
87
+ Project Idea: "${idea}"
88
+
89
+ Return JSON with this exact structure:
90
+ {
91
+ "style": "modern" | "elegant" | "minimal" | "vibrant" | "professional" | "playful",
92
+ "audience": "business" | "consumer" | "developer" | "creative" | "general",
93
+ "industry": "string or null",
94
+ "colorPreference": "warm" | "cool" | "neutral" | "colorful" | "monochrome",
95
+ "features": ["list of UI features needed like forms, tables, charts, cards, etc"],
96
+ "accessibilityLevel": "AA",
97
+ "darkMode": true or false,
98
+ "mobileFirst": true or false
99
+ }
100
+
101
+ Consider:
102
+ - Who will use this app (business users need clean/professional, consumers want modern/friendly)
103
+ - What type of data/content will be displayed
104
+ - What interactions are needed
105
+ - The overall mood/tone of the application`;
106
+
107
+ try {
108
+ const result = await generateCode(prompt, '');
109
+
110
+ if (result.success && result.response) {
111
+ // Parse the JSON response
112
+ const jsonMatch = result.response.match(/\{[\s\S]*\}/);
113
+ if (jsonMatch) {
114
+ const intent = JSON.parse(jsonMatch[0]) as UIDesignIntent;
115
+ onProgress?.(`UI Intent: ${intent.style} style for ${intent.audience} audience`);
116
+ return intent;
117
+ }
118
+ }
119
+ } catch {
120
+ onProgress?.('Using default UI intent (analysis failed)');
121
+ }
122
+
123
+ // Default intent if analysis fails
124
+ return {
125
+ style: 'modern',
126
+ audience: 'general',
127
+ colorPreference: 'cool',
128
+ features: ['cards', 'forms', 'buttons', 'navigation'],
129
+ accessibilityLevel: 'AA',
130
+ darkMode: true,
131
+ mobileFirst: true,
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Generate complete UI specification from intent
137
+ */
138
+ export async function generateUISpecification(
139
+ idea: string,
140
+ intent: UIDesignIntent,
141
+ onProgress?: (message: string) => void
142
+ ): Promise<UISpecification> {
143
+ onProgress?.('Generating UI specification...');
144
+
145
+ // Select theme based on intent
146
+ let themeName: string;
147
+ switch (intent.style) {
148
+ case 'elegant':
149
+ case 'professional':
150
+ themeName = 'elegant';
151
+ break;
152
+ case 'minimal':
153
+ themeName = 'minimal';
154
+ break;
155
+ case 'vibrant':
156
+ case 'playful':
157
+ themeName = 'vibrant';
158
+ break;
159
+ default:
160
+ themeName = 'modern';
161
+ }
162
+
163
+ // Override based on audience if style is generic
164
+ if (intent.style === 'professional' && intent.audience === 'business') {
165
+ themeName = 'minimal';
166
+ }
167
+
168
+ const theme = THEMES[themeName];
169
+ const projectType = detectProjectType(idea);
170
+
171
+ onProgress?.(`Selected theme: ${theme.name}`);
172
+ onProgress?.(`Project type: ${projectType}`);
173
+
174
+ // Generate detailed specification using Claude
175
+ const prompt = `Generate a UI specification for this project. Return ONLY a JSON object (no markdown).
176
+
177
+ Project Idea: "${idea}"
178
+ Style: ${intent.style}
179
+ Audience: ${intent.audience}
180
+ Project Type: ${projectType}
181
+
182
+ Return JSON:
183
+ {
184
+ "recommendedComponents": ["list of shadcn/ui components needed"],
185
+ "layoutPatterns": ["grid", "flex", "etc"],
186
+ "navigationStyle": "sidebar" | "topbar" | "bottom-tabs" | "hybrid",
187
+ "keyPages": [
188
+ {
189
+ "name": "Page Name",
190
+ "route": "/route",
191
+ "description": "What this page does",
192
+ "layout": "dashboard" | "form" | "list" | "detail" | "kanban" | "landing"
193
+ }
194
+ ],
195
+ "designNotes": "Brief notes for consistent design (colors, spacing, component usage)"
196
+ }`;
197
+
198
+ try {
199
+ const result = await generateCode(prompt, '');
200
+
201
+ if (result.success && result.response) {
202
+ const jsonMatch = result.response.match(/\{[\s\S]*\}/);
203
+ if (jsonMatch) {
204
+ const spec = JSON.parse(jsonMatch[0]);
205
+
206
+ return {
207
+ intent,
208
+ theme,
209
+ themeName,
210
+ projectType,
211
+ recommendedComponents: spec.recommendedComponents || [],
212
+ layoutPatterns: spec.layoutPatterns || ['flex', 'grid'],
213
+ navigationStyle: spec.navigationStyle || 'sidebar',
214
+ keyPages: spec.keyPages || [],
215
+ designNotes: spec.designNotes || '',
216
+ };
217
+ }
218
+ }
219
+ } catch {
220
+ onProgress?.('Using default specification (generation failed)');
221
+ }
222
+
223
+ // Default specification
224
+ return {
225
+ intent,
226
+ theme,
227
+ themeName,
228
+ projectType,
229
+ recommendedComponents: ['button', 'card', 'input', 'dialog', 'dropdown-menu', 'avatar', 'badge'],
230
+ layoutPatterns: ['flex', 'grid'],
231
+ navigationStyle: 'sidebar',
232
+ keyPages: [
233
+ { name: 'Dashboard', route: '/', description: 'Main dashboard', layout: 'dashboard' },
234
+ { name: 'Settings', route: '/settings', description: 'User settings', layout: 'form' },
235
+ ],
236
+ designNotes: `Use ${theme.name} theme consistently. Maintain ${intent.style} aesthetic throughout.`,
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Generate design system prompt for code generation
242
+ * This is injected into Claude's context when generating UI code
243
+ */
244
+ export function generateDesignSystemPrompt(spec: UISpecification): string {
245
+ return `
246
+ ## UI Design System
247
+
248
+ You are generating code for a ${spec.intent.style} ${spec.projectType} application.
249
+
250
+ ### Theme: ${spec.theme.name}
251
+ - Primary color: hsl(${spec.theme.colors.primary})
252
+ - Border radius: ${spec.theme.borderRadius}
253
+ - Font: ${spec.theme.fontFamily}
254
+
255
+ ### Components to Use
256
+ Always use these shadcn/ui components (from @/components/ui/):
257
+ ${spec.recommendedComponents.map(c => `- ${c}`).join('\n')}
258
+
259
+ ### Layout Patterns
260
+ ${spec.layoutPatterns.map(p => `- ${p}`).join('\n')}
261
+
262
+ ### Navigation
263
+ Use ${spec.navigationStyle} navigation pattern.
264
+
265
+ ### Accessibility
266
+ - Target WCAG ${spec.intent.accessibilityLevel} compliance
267
+ - All interactive elements must be keyboard accessible
268
+ - Use semantic HTML elements
269
+ - Include proper ARIA labels
270
+
271
+ ### Mobile Responsiveness
272
+ ${spec.intent.mobileFirst ? 'Design mobile-first, then scale up for larger screens.' : 'Design for desktop, with responsive adjustments for mobile.'}
273
+
274
+ ### Dark Mode
275
+ ${spec.intent.darkMode ? 'Support dark mode with class-based toggling.' : 'Light mode only.'}
276
+
277
+ ### Design Notes
278
+ ${spec.designNotes}
279
+
280
+ ### Key Pages
281
+ ${spec.keyPages.map(p => `- ${p.name} (${p.route}): ${p.description} - Use ${p.layout} layout`).join('\n')}
282
+ `;
283
+ }
284
+
285
+ /**
286
+ * Main function: Design UI from idea
287
+ * Call this early in the Popeye workflow to establish UI direction
288
+ */
289
+ export async function designUI(
290
+ idea: string,
291
+ onProgress?: (message: string) => void
292
+ ): Promise<UISpecification> {
293
+ onProgress?.('Starting UI design process...');
294
+
295
+ // Step 1: Analyze the idea to understand intent
296
+ const intent = await analyzeUIIntent(idea, onProgress);
297
+
298
+ // Step 2: Generate complete specification
299
+ const spec = await generateUISpecification(idea, intent, onProgress);
300
+
301
+ onProgress?.(`UI design complete: ${spec.theme.name} theme with ${spec.recommendedComponents.length} components`);
302
+
303
+ return spec;
304
+ }
305
+
306
+ /**
307
+ * Save UI specification to project
308
+ */
309
+ export async function saveUISpecification(
310
+ projectDir: string,
311
+ spec: UISpecification
312
+ ): Promise<void> {
313
+ const { promises: fs } = await import('node:fs');
314
+ const path = await import('node:path');
315
+
316
+ const specPath = path.join(projectDir, '.popeye', 'ui-spec.json');
317
+ await fs.mkdir(path.dirname(specPath), { recursive: true });
318
+ await fs.writeFile(specPath, JSON.stringify(spec, null, 2));
319
+ }
320
+
321
+ /**
322
+ * Load UI specification from project
323
+ */
324
+ export async function loadUISpecification(
325
+ projectDir: string
326
+ ): Promise<UISpecification | null> {
327
+ const { promises: fs } = await import('node:fs');
328
+ const path = await import('node:path');
329
+
330
+ try {
331
+ const specPath = path.join(projectDir, '.popeye', 'ui-spec.json');
332
+ const content = await fs.readFile(specPath, 'utf-8');
333
+ return JSON.parse(content) as UISpecification;
334
+ } catch {
335
+ return null;
336
+ }
337
+ }