@deneb-ui/cli 2.0.40 → 2.0.42

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.
package/bin/index.js CHANGED
@@ -1021,6 +1021,8 @@ if (command === 'init') {
1021
1021
  let explain = false;
1022
1022
  let legacy = false;
1023
1023
  let telemetry = 'off';
1024
+ let aiEnabled = false;
1025
+ let aiDryRun = false;
1024
1026
  for (let i = 0; i < commandArgs.length; i++) {
1025
1027
  const arg = commandArgs[i];
1026
1028
  if (arg === '--recipe' || arg === '-r') {
@@ -1033,6 +1035,11 @@ if (command === 'init') {
1033
1035
  explain = true;
1034
1036
  } else if (arg === '--legacy') {
1035
1037
  legacy = true;
1038
+ } else if (arg === '--ai') {
1039
+ aiEnabled = true;
1040
+ } else if (arg === '--ai-dry-run') {
1041
+ aiEnabled = true;
1042
+ aiDryRun = true;
1036
1043
  } else if (arg === '--telemetry' && commandArgs[i + 1]) {
1037
1044
  telemetry = commandArgs[++i];
1038
1045
  } else if (arg.startsWith('--telemetry=')) {
@@ -1041,7 +1048,7 @@ if (command === 'init') {
1041
1048
  targetInput = arg;
1042
1049
  }
1043
1050
  }
1044
- initProject(targetInput, { recipeName, dryRun, explain, legacy, telemetry });
1051
+ initProject(targetInput, { recipeName, dryRun, explain, legacy, telemetry, aiEnabled, aiDryRun });
1045
1052
  } else if (command === 'create') {
1046
1053
  createTemplate(commandArgs[0]);
1047
1054
  } else if (command === 'add') {
@@ -1108,7 +1115,7 @@ if (command === 'init') {
1108
1115
 
1109
1116
  Core Commands:
1110
1117
  init Deneb ARC: convert an existing React/Next.js app into a Fivora-editable storefront
1111
- flags: --dry-run --explain --recipe <name> --legacy --telemetry off|anonymous|enhanced
1118
+ flags: --dry-run --explain --recipe <name> --legacy --ai --ai-dry-run --telemetry off|anonymous|enhanced
1112
1119
  doctor Run comprehensive environment, manifest, visual editing AST & asset diagnostic checks (flags: --fix, --json)
1113
1120
  save-recipe Learn and save calibrated fixes & schemas into reusable recipe bank (e.g. deneb save-recipe . shoes-store)
1114
1121
  learn Alias for save-recipe
@@ -1136,6 +1143,8 @@ Examples:
1136
1143
  deneb init --recipe electronics
1137
1144
  deneb init --recipe cosmetics
1138
1145
  deneb init --legacy
1146
+ deneb init --ai
1147
+ deneb init --ai-dry-run
1139
1148
  deneb fonts list
1140
1149
  deneb fonts install .
1141
1150
  deneb fonts install . --font inter --font playfair-display
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deneb-ui/cli",
3
- "version": "2.0.40",
3
+ "version": "2.0.42",
4
4
  "description": "Official DENEB CLI — scaffold, convert, validate, and package Fivora-ready storefront templates.",
5
5
  "bin": {
6
6
  "deneb": "bin/index.js",
@@ -49,8 +49,10 @@
49
49
  "license": "MIT",
50
50
  "dependencies": {
51
51
  "@babel/parser": "^7.28.0",
52
- "@deneb-ui/core": "^2.0.40",
52
+ "@deneb-ui/core": "^2.0.42",
53
+ "@octokit/rest": "^22.0.1",
53
54
  "adm-zip": "^0.6.0",
55
+ "dotenv": "^17.4.2",
54
56
  "recast": "^0.23.11"
55
57
  },
56
58
  "devDependencies": {
@@ -0,0 +1,328 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — AI Agent
5
+ *
6
+ * OpenAI API integration with self-healing validation loop.
7
+ * Generates editable wrapper components for unknown UI components
8
+ * and validates them through ARC's strict pipeline before accepting.
9
+ *
10
+ * Default engine: GPT-4o-mini (configurable via OPENAI_CONTENT_MODEL).
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { parseSource } = require('./ast.cjs');
16
+ const { buildComponentPrompt, buildFixPrompt, buildDocsPrompt } = require('./ai-prompts.cjs');
17
+
18
+ const MAX_RETRIES = 3;
19
+
20
+ /**
21
+ * Load AI configuration from environment variables.
22
+ * Call loadEnv() before using this if .env is not loaded yet.
23
+ */
24
+ function getAiConfig() {
25
+ return {
26
+ apiKey: process.env.OPENAI_API_KEY || '',
27
+ model: process.env.OPENAI_CONTENT_MODEL || 'gpt-4o-mini',
28
+ maxTokens: parseInt(process.env.OPENAI_MAX_OUTPUT_TOKENS || '4000', 10),
29
+ timeoutMs: parseInt(process.env.OPENAI_CONTENT_TIMEOUT_MS || '120000', 10),
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Load .env file purely locally.
35
+ * Searches upward from targetDir or process.cwd(), supporting local .env and .env.local files.
36
+ * Eliminates the need for any global system configuration.
37
+ *
38
+ * @param {string} [startDir] - Starting directory (defaults to process.cwd())
39
+ */
40
+ function loadEnv(startDir) {
41
+ const checked = new Set();
42
+ const candidates = [];
43
+
44
+ let curr = path.resolve(startDir || process.cwd());
45
+ for (let i = 0; i < 5; i++) {
46
+ candidates.push(path.join(curr, '.env'));
47
+ candidates.push(path.join(curr, '.env.local'));
48
+ candidates.push(path.join(curr, 'core', '.env'));
49
+ const parent = path.dirname(curr);
50
+ if (parent === curr) break;
51
+ curr = parent;
52
+ }
53
+
54
+ candidates.push(path.resolve(__dirname, '..', '..', '..', '..', '.env'));
55
+ candidates.push(path.resolve(__dirname, '..', '..', '.env'));
56
+
57
+ for (const envPath of candidates) {
58
+ if (!checked.has(envPath) && fs.existsSync(envPath)) {
59
+ try {
60
+ require('dotenv').config({ path: envPath });
61
+ } catch {
62
+ // dotenv not available — env vars must be set manually
63
+ }
64
+ return;
65
+ }
66
+ checked.add(envPath);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Check if the AI agent is properly configured and ready to use.
72
+ * @returns {{ ready: boolean, reason?: string }}
73
+ */
74
+ function checkAiReady() {
75
+ loadEnv();
76
+ const config = getAiConfig();
77
+ if (!config.apiKey) {
78
+ return { ready: false, reason: 'OPENAI_API_KEY not set. Create a .env file (see .env.example) or set the environment variable.' };
79
+ }
80
+ if (!config.apiKey.startsWith('sk-')) {
81
+ return { ready: false, reason: 'OPENAI_API_KEY does not look valid (should start with sk-).' };
82
+ }
83
+ return { ready: true };
84
+ }
85
+
86
+ /**
87
+ * Call the OpenAI Chat Completions API.
88
+ *
89
+ * Uses native fetch (Node 18+) — no external HTTP library needed.
90
+ *
91
+ * @param {string} prompt - The user prompt
92
+ * @param {object} [opts] - Optional overrides
93
+ * @returns {Promise<{ content: string, inputTokens: number, outputTokens: number }>}
94
+ */
95
+ async function callOpenAI(prompt, opts = {}) {
96
+ const config = getAiConfig();
97
+ const model = opts.model || config.model;
98
+ const maxTokens = opts.maxTokens || config.maxTokens;
99
+ const timeoutMs = opts.timeoutMs || config.timeoutMs;
100
+
101
+ const controller = new AbortController();
102
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
103
+
104
+ try {
105
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ 'Authorization': `Bearer ${config.apiKey}`,
110
+ },
111
+ body: JSON.stringify({
112
+ model,
113
+ messages: [
114
+ { role: 'system', content: 'You are a senior React/TypeScript developer specializing in the DENEB UI framework. Output only raw code — no markdown, no explanations.' },
115
+ { role: 'user', content: prompt },
116
+ ],
117
+ max_tokens: maxTokens,
118
+ temperature: 0.2,
119
+ }),
120
+ signal: controller.signal,
121
+ });
122
+
123
+ if (!response.ok) {
124
+ const errorBody = await response.text().catch(() => '');
125
+ throw new Error(`OpenAI API error ${response.status}: ${errorBody.slice(0, 200)}`);
126
+ }
127
+
128
+ const data = await response.json();
129
+ const choice = data.choices?.[0];
130
+ if (!choice || !choice.message?.content) {
131
+ throw new Error('OpenAI returned empty response — no content in choices[0].message');
132
+ }
133
+
134
+ let content = choice.message.content.trim();
135
+ // Strip markdown fences if the model wraps output despite instructions
136
+ content = content.replace(/^```(?:tsx?|jsx?|typescript|javascript)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
137
+
138
+ return {
139
+ content,
140
+ inputTokens: data.usage?.prompt_tokens || 0,
141
+ outputTokens: data.usage?.completion_tokens || 0,
142
+ };
143
+ } finally {
144
+ clearTimeout(timer);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Validate generated component code using ARC's validators.
150
+ *
151
+ * @param {string} code - The generated TypeScript/JSX code
152
+ * @param {string} filename - Virtual filename for error messages
153
+ * @returns {{ passed: boolean, errors: string[] }}
154
+ */
155
+ function validateGeneratedCode(code, filename) {
156
+ const errors = [];
157
+
158
+ // 1. AST syntax check
159
+ try {
160
+ parseSource(code, filename);
161
+ } catch (err) {
162
+ errors.push(`AST syntax error: ${err.message}`);
163
+ }
164
+
165
+ // 2. Must have data-preview-field-path markers
166
+ if (!code.includes('data-preview-field-path')) {
167
+ errors.push('Missing data-preview-field-path markers — the component has no editable fields.');
168
+ }
169
+
170
+ // 3. Must have data-preview-item-path on the root wrapper
171
+ if (!code.includes('data-preview-item-path')) {
172
+ errors.push('Missing data-preview-item-path on root wrapper element.');
173
+ }
174
+
175
+ // 4. Must have proper TypeScript exports
176
+ if (!code.includes('export function') && !code.includes('export const')) {
177
+ errors.push('Missing named export — component must use `export function Editable...()`.');
178
+ }
179
+
180
+ // 5. Must have TypeScript interface
181
+ if (!code.includes('export interface')) {
182
+ errors.push('Missing exported TypeScript interface for component props.');
183
+ }
184
+
185
+ // 6. Must import EditableText or EditableImage (at least one)
186
+ if (!code.includes("from './EditableText'") && !code.includes("from './EditableImage'")) {
187
+ errors.push('Must import at least EditableText or EditableImage from relative path.');
188
+ }
189
+
190
+ // 7. Must NOT have default export (Deneb convention)
191
+ if (/export\s+default\s+/m.test(code)) {
192
+ errors.push('Must NOT use default export — use named export only (Deneb convention).');
193
+ }
194
+
195
+ // 8. Check for forbidden patterns
196
+ if (code.includes('useState') || code.includes('useEffect') || code.includes('useContext')) {
197
+ errors.push('Must NOT import React hooks (useState, useEffect, useContext). Component must be stateless.');
198
+ }
199
+
200
+ if (code.includes('useSiteData')) {
201
+ errors.push('Must NOT import useSiteData directly. The wrapper uses EditableText/EditableImage primitives instead.');
202
+ }
203
+
204
+ return {
205
+ passed: errors.length === 0,
206
+ errors,
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Extract the list of editable field paths from generated component code.
212
+ *
213
+ * @param {string} code - The generated component code
214
+ * @returns {string[]} Array of field path patterns found
215
+ */
216
+ function extractFieldPaths(code) {
217
+ const matches = code.matchAll(/data-preview-field-path=\{[`"']([^`"']+)[`"']\}/g);
218
+ const paths = new Set();
219
+ for (const m of matches) {
220
+ // Normalize template literal patterns like `${itemPath}.name`
221
+ const path = m[1].replace(/\$\{[^}]+\}/g, '*');
222
+ paths.add(path);
223
+ }
224
+ return [...paths];
225
+ }
226
+
227
+ /**
228
+ * Main AI adaptation function with self-healing validation loop.
229
+ *
230
+ * @param {string} sourceCode - The unknown component's source code
231
+ * @param {string} componentName - The component name (e.g. "Carousel")
232
+ * @param {object} profile - ARC project profile
233
+ * @param {object} [callbacks] - Optional progress callbacks
234
+ * @param {function} [callbacks.onAttempt] - Called at start of each attempt: (attemptNum, maxRetries)
235
+ * @param {function} [callbacks.onValidationFail] - Called when validation fails: (errors, attemptNum)
236
+ * @param {function} [callbacks.onSuccess] - Called on success: (attemptNum)
237
+ * @returns {Promise<{ success: boolean, code?: string, fields?: string[], attempts: number, totalInputTokens: number, totalOutputTokens: number, error?: string }>}
238
+ */
239
+ async function adaptComponent(sourceCode, componentName, profile, callbacks = {}) {
240
+ let totalInputTokens = 0;
241
+ let totalOutputTokens = 0;
242
+
243
+ // Generate initial code
244
+ const prompt = buildComponentPrompt(sourceCode, componentName, profile);
245
+ let lastCode = '';
246
+ let lastErrors = [];
247
+
248
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
249
+ if (callbacks.onAttempt) callbacks.onAttempt(attempt, MAX_RETRIES);
250
+
251
+ try {
252
+ let result;
253
+ if (attempt === 1) {
254
+ result = await callOpenAI(prompt);
255
+ } else {
256
+ const fixPrompt = buildFixPrompt(lastCode, lastErrors.join('\n'), attempt);
257
+ result = await callOpenAI(fixPrompt);
258
+ }
259
+
260
+ totalInputTokens += result.inputTokens;
261
+ totalOutputTokens += result.outputTokens;
262
+ lastCode = result.content;
263
+
264
+ // Validate the generated code
265
+ const filename = `Editable${componentName}.tsx`;
266
+ const validation = validateGeneratedCode(lastCode, filename);
267
+
268
+ if (validation.passed) {
269
+ if (callbacks.onSuccess) callbacks.onSuccess(attempt);
270
+ return {
271
+ success: true,
272
+ code: lastCode,
273
+ fields: extractFieldPaths(lastCode),
274
+ attempts: attempt,
275
+ totalInputTokens,
276
+ totalOutputTokens,
277
+ };
278
+ }
279
+
280
+ // Validation failed — prepare for retry
281
+ lastErrors = validation.errors;
282
+ if (callbacks.onValidationFail) callbacks.onValidationFail(validation.errors, attempt);
283
+ } catch (err) {
284
+ lastErrors = [err.message];
285
+ if (callbacks.onValidationFail) callbacks.onValidationFail([err.message], attempt);
286
+ }
287
+ }
288
+
289
+ // All retries exhausted
290
+ return {
291
+ success: false,
292
+ attempts: MAX_RETRIES,
293
+ totalInputTokens,
294
+ totalOutputTokens,
295
+ error: `Failed after ${MAX_RETRIES} attempts. Last errors:\n${lastErrors.join('\n')}`,
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Generate documentation page for a successfully adapted component.
301
+ *
302
+ * @param {string} componentName - e.g. "Carousel"
303
+ * @param {string} componentCode - The validated Editable*.tsx source
304
+ * @param {string[]} editableFields - List of field paths
305
+ * @returns {Promise<{ success: boolean, code?: string, error?: string }>}
306
+ */
307
+ async function generateDocsPage(componentName, componentCode, editableFields) {
308
+ const prompt = buildDocsPrompt(componentName, componentCode, editableFields);
309
+
310
+ try {
311
+ const result = await callOpenAI(prompt);
312
+ return { success: true, code: result.content };
313
+ } catch (err) {
314
+ return { success: false, error: err.message };
315
+ }
316
+ }
317
+
318
+ module.exports = {
319
+ loadEnv,
320
+ checkAiReady,
321
+ getAiConfig,
322
+ callOpenAI,
323
+ validateGeneratedCode,
324
+ extractFieldPaths,
325
+ adaptComponent,
326
+ generateDocsPage,
327
+ MAX_RETRIES,
328
+ };
@@ -0,0 +1,214 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deneb ARC — AI Prompt Templates
5
+ *
6
+ * Carefully engineered prompt templates that teach GPT-4o-mini the exact
7
+ * Deneb editable component conventions. Uses a real EditableServiceCard
8
+ * as a reference pattern so the AI learns the correct structure.
9
+ */
10
+
11
+ // ─── Reference component (shortened for token efficiency) ────────
12
+ const REFERENCE_COMPONENT = `
13
+ import React from 'react';
14
+ import { EditableText } from './EditableText';
15
+ import { EditableImage } from './EditableImage';
16
+
17
+ export interface ServiceItem {
18
+ id?: string | number;
19
+ name?: string;
20
+ title?: string;
21
+ price?: string | number;
22
+ description?: string;
23
+ imageUrl?: string;
24
+ image?: string;
25
+ features?: unknown[];
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export interface EditableServiceCardProps extends React.HTMLAttributes<HTMLElement> {
30
+ itemPath: string;
31
+ service: ServiceItem;
32
+ imageFallback?: string;
33
+ as?: React.ElementType;
34
+ align?: 'left' | 'center' | 'right';
35
+ showPrice?: boolean;
36
+ }
37
+
38
+ export function EditableServiceCard({
39
+ itemPath,
40
+ service,
41
+ imageFallback = '/placeholder.svg',
42
+ as: Component = 'article',
43
+ align = 'left',
44
+ showPrice = true,
45
+ className = '',
46
+ style,
47
+ children,
48
+ ...props
49
+ }: EditableServiceCardProps) {
50
+ const name = String(service?.name || service?.title || '');
51
+ const description = String(service?.description || '');
52
+ const imageUrl = String(service?.imageUrl || service?.image || '');
53
+
54
+ return (
55
+ <Component
56
+ data-preview-item-path={itemPath}
57
+ className={\`editable-service-card \${className}\`.trim()}
58
+ style={{ textAlign: align, ...style }}
59
+ {...(props as any)}
60
+ >
61
+ <div className="service-card-image-wrap">
62
+ <EditableImage
63
+ id={\`\${itemPath}.imageUrl\`}
64
+ data-preview-field-path={\`\${itemPath}.imageUrl\`}
65
+ src={imageUrl}
66
+ fallbackSrc={imageFallback}
67
+ alt={name}
68
+ className="service-card-image"
69
+ />
70
+ </div>
71
+ <div className="service-card-body">
72
+ <EditableText
73
+ as="h2"
74
+ id={\`\${itemPath}.name\`}
75
+ data-preview-field-path={\`\${itemPath}.name\`}
76
+ defaultValue={name}
77
+ className="service-card-title"
78
+ />
79
+ <EditableText
80
+ as="p"
81
+ id={\`\${itemPath}.description\`}
82
+ data-preview-field-path={\`\${itemPath}.description\`}
83
+ defaultValue={description}
84
+ className="service-card-description"
85
+ />
86
+ {children}
87
+ </div>
88
+ </Component>
89
+ );
90
+ }
91
+ `.trim();
92
+
93
+ /**
94
+ * Build the main component generation prompt.
95
+ *
96
+ * @param {string} sourceCode - The unknown component's source code
97
+ * @param {string} componentName - The component's name (e.g. "Carousel")
98
+ * @param {object} profile - ARC project profile (framework, libraries, etc.)
99
+ * @returns {string} The complete prompt for OpenAI
100
+ */
101
+ function buildComponentPrompt(sourceCode, componentName, profile) {
102
+ return `You are an expert React/TypeScript developer working on the DENEB UI framework.
103
+ Your task is to create an editable wrapper component for an existing component.
104
+
105
+ ## RULES (MANDATORY — violating any rule means the output is rejected):
106
+
107
+ 1. **Import only from relative paths**: Use \`./EditableText\` and \`./EditableImage\` (these are the ONLY two Deneb primitives you may use).
108
+ 2. **Every user-visible text** must be wrapped in \`<EditableText>\` with:
109
+ - \`as\` prop matching the original HTML tag (h1, h2, p, span, etc.)
110
+ - \`id={\`\${itemPath}.fieldName\`}\`
111
+ - \`data-preview-field-path={\`\${itemPath}.fieldName\`}\`
112
+ - \`defaultValue={value}\`
113
+ 3. **Every user-visible image** must use \`<EditableImage>\` with:
114
+ - \`id={\`\${itemPath}.fieldName\`}\`
115
+ - \`data-preview-field-path={\`\${itemPath}.fieldName\`}\`
116
+ - \`src={value}\`
117
+ - \`fallbackSrc={imageFallback}\`
118
+ 4. **Lists/arrays** must have \`data-preview-list-path\` on the container and \`data-preview-item-path\` on each item.
119
+ 5. **Root wrapper** must have \`data-preview-item-path={itemPath}\`.
120
+ 6. **Export a TypeScript interface** named \`Editable${componentName}Props\` extending \`React.HTMLAttributes<HTMLElement>\`.
121
+ 7. **Export a named function** (not default export) named \`Editable${componentName}\`.
122
+ 8. **Preserve all original CSS classes and styling** — do NOT remove or change className values.
123
+ 9. **Props must include**: \`itemPath: string\` (required), data object, \`as?: React.ElementType\`, \`className?\`, \`style?\`, \`children?\`.
124
+ 10. **Use \`'use strict'\` is NOT needed** — this is a .tsx file.
125
+ 11. **Do NOT import React hooks** like useState, useEffect, useContext. The component must be stateless.
126
+ 12. **Do NOT import useSiteData** — the wrapper component does not need it directly.
127
+
128
+ ## REFERENCE PATTERN (follow this structure exactly):
129
+
130
+ \`\`\`tsx
131
+ ${REFERENCE_COMPONENT}
132
+ \`\`\`
133
+
134
+ ## SOURCE COMPONENT TO ADAPT:
135
+
136
+ Component name: ${componentName}
137
+ Framework: ${profile.framework || 'nextjs'}
138
+ Libraries: ${(profile.componentLibraries || []).join(', ') || 'none detected'}
139
+
140
+ \`\`\`tsx
141
+ ${sourceCode}
142
+ \`\`\`
143
+
144
+ ## YOUR OUTPUT:
145
+
146
+ Generate ONLY the complete TypeScript (.tsx) file content for \`Editable${componentName}.tsx\`.
147
+ Do NOT include markdown fences, explanations, or comments outside the code.
148
+ Output the raw TypeScript code directly.`;
149
+ }
150
+
151
+ /**
152
+ * Build the error-fix prompt when the validator rejects AI-generated code.
153
+ *
154
+ * @param {string} previousCode - The code that failed validation
155
+ * @param {string} errorMessage - The exact validator error
156
+ * @param {number} attempt - Current attempt number
157
+ * @returns {string} The fix prompt
158
+ */
159
+ function buildFixPrompt(previousCode, errorMessage, attempt) {
160
+ return `The DENEB ARC validator REJECTED your generated component (attempt ${attempt}/3).
161
+
162
+ ## EXACT ERROR:
163
+ ${errorMessage}
164
+
165
+ ## YOUR PREVIOUS CODE:
166
+ \`\`\`tsx
167
+ ${previousCode}
168
+ \`\`\`
169
+
170
+ ## INSTRUCTIONS:
171
+ 1. Fix ONLY the specific error described above.
172
+ 2. Do NOT change anything else — preserve all existing field paths, markers, and class names.
173
+ 3. Output the COMPLETE corrected .tsx file (not a diff, not a snippet — the full file).
174
+ 4. Do NOT include markdown fences or explanations. Output raw TypeScript code only.`;
175
+ }
176
+
177
+ /**
178
+ * Build the docs page generation prompt.
179
+ *
180
+ * @param {string} componentName - e.g. "Carousel"
181
+ * @param {string} componentCode - The validated EditableCarousel.tsx source
182
+ * @param {string[]} editableFields - List of field paths discovered
183
+ * @returns {string} The docs prompt
184
+ */
185
+ function buildDocsPrompt(componentName, componentCode, editableFields) {
186
+ return `Generate a Next.js documentation page for the DENEB UI component "Editable${componentName}".
187
+
188
+ ## COMPONENT CODE:
189
+ \`\`\`tsx
190
+ ${componentCode}
191
+ \`\`\`
192
+
193
+ ## EDITABLE FIELDS:
194
+ ${editableFields.map((f) => `- ${f}`).join('\n')}
195
+
196
+ ## OUTPUT FORMAT:
197
+ Generate a React component that exports a default function named \`Editable${componentName}DocsPage\`.
198
+ It should render:
199
+ 1. A title section with the component name
200
+ 2. A description paragraph explaining what the component does
201
+ 3. A props table showing all available props with their types and defaults
202
+ 4. A usage example code block
203
+ 5. A list of editable field paths
204
+
205
+ Use plain HTML/JSX with className for styling. Do NOT import any external UI libraries.
206
+ Output raw TSX code only — no markdown fences or explanations.`;
207
+ }
208
+
209
+ module.exports = {
210
+ REFERENCE_COMPONENT,
211
+ buildComponentPrompt,
212
+ buildFixPrompt,
213
+ buildDocsPrompt,
214
+ };