@su-record/vibe 2.6.4 → 2.6.5
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/README.md +41 -1
- package/dist/lib/gemini-api.js +5 -5
- package/hooks/scripts/gemini-ui-gen.js +63 -179
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -43,7 +43,9 @@ vibe init
|
|
|
43
43
|
| `/vibe.review` | 13+ agent parallel review |
|
|
44
44
|
| `/vibe.analyze` | Code analysis |
|
|
45
45
|
| `/vibe.reason "problem"` | Systematic reasoning |
|
|
46
|
-
| `/vibe.utils` |
|
|
46
|
+
| `/vibe.utils --ui` | UI preview (Gemini image / ASCII fallback) |
|
|
47
|
+
| `/vibe.utils --ui-sync` | Sync design files to code (v2.6.3) |
|
|
48
|
+
| `/vibe.utils --e2e` | E2E testing, diagrams, etc. |
|
|
47
49
|
|
|
48
50
|
## Workflow
|
|
49
51
|
|
|
@@ -211,6 +213,44 @@ await generateChangelog('feature-name');
|
|
|
211
213
|
- Automatic changelog generation
|
|
212
214
|
- Baseline tagging for releases
|
|
213
215
|
|
|
216
|
+
## UI Design Tools (v2.6.3)
|
|
217
|
+
|
|
218
|
+
### UI Preview (`--ui`)
|
|
219
|
+
|
|
220
|
+
Generate UI previews from text description or design folder:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
/vibe.utils --ui "login form with email/password"
|
|
224
|
+
/vibe.utils --ui ./design/dashboard/
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Features:**
|
|
228
|
+
|
|
229
|
+
- **Gemini enabled**: Generates actual UI mockup image
|
|
230
|
+
- **Gemini disabled**: ASCII art fallback
|
|
231
|
+
- Supports: HTML, PNG, JPG, CSS, JSON, SVG, MD
|
|
232
|
+
|
|
233
|
+
### Design-to-Code Sync (`--ui-sync`)
|
|
234
|
+
|
|
235
|
+
Sync design files to existing UI code:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
/vibe.utils --ui-sync ./design/ui/
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Workflow:**
|
|
242
|
+
|
|
243
|
+
1. Read all design files (HTML, images, CSS, tokens)
|
|
244
|
+
2. Extract design specs (colors, typography, spacing)
|
|
245
|
+
3. Compare with existing code
|
|
246
|
+
4. Generate update plan
|
|
247
|
+
5. Apply changes (with confirmation)
|
|
248
|
+
|
|
249
|
+
**Mode:**
|
|
250
|
+
|
|
251
|
+
- **Gemini enabled**: Gemini generates code from design
|
|
252
|
+
- **Gemini disabled**: Claude handles analysis and generation
|
|
253
|
+
|
|
214
254
|
## Multi-model Orchestration
|
|
215
255
|
|
|
216
256
|
**Hook-based automatic routing** - Keywords in your prompt trigger the right LLM automatically:
|
package/dist/lib/gemini-api.js
CHANGED
|
@@ -433,35 +433,35 @@ export async function vibeGeminiOrchestrate(prompt, systemPrompt, options = {})
|
|
|
433
433
|
* Vibe Spec 파싱 (Vibe Spec → 실행 계획)
|
|
434
434
|
*/
|
|
435
435
|
export async function vibeGeminiParseSpec(spec) {
|
|
436
|
-
return vibeGeminiOrchestrate(spec, `You are a Vibe Spec parser. Parse the given specification and output a structured execution plan.
|
|
436
|
+
return vibeGeminiOrchestrate(spec, `You are a Vibe Spec parser. Parse the given specification and output a structured execution plan.
|
|
437
437
|
Output format: { "phases": [...], "files": [...], "dependencies": [...] }`);
|
|
438
438
|
}
|
|
439
439
|
/**
|
|
440
440
|
* Vibe 실행 계획 수립 (Task → Steps)
|
|
441
441
|
*/
|
|
442
442
|
export async function vibeGeminiPlanExecution(task, context) {
|
|
443
|
-
return vibeGeminiOrchestrate(`Task: ${task}\n\nContext:\n${context}`, `You are a Vibe execution planner. Given a task and context, create a step-by-step execution plan.
|
|
443
|
+
return vibeGeminiOrchestrate(`Task: ${task}\n\nContext:\n${context}`, `You are a Vibe execution planner. Given a task and context, create a step-by-step execution plan.
|
|
444
444
|
Output format: { "steps": [{ "id": 1, "action": "...", "target": "...", "expected": "..." }], "estimatedComplexity": "low|medium|high" }`);
|
|
445
445
|
}
|
|
446
446
|
/**
|
|
447
447
|
* Vibe 코드 분석 (빠른 구조 분석)
|
|
448
448
|
*/
|
|
449
449
|
export async function vibeGeminiAnalyze(code, question) {
|
|
450
|
-
return vibeGeminiOrchestrate(`Code:\n\`\`\`\n${code}\n\`\`\`\n\nQuestion: ${question}`, `You are a code analyzer. Answer the question about the given code concisely.
|
|
450
|
+
return vibeGeminiOrchestrate(`Code:\n\`\`\`\n${code}\n\`\`\`\n\nQuestion: ${question}`, `You are a code analyzer. Answer the question about the given code concisely.
|
|
451
451
|
Output format: { "answer": "...", "confidence": 0.0-1.0, "relatedSymbols": [...] }`);
|
|
452
452
|
}
|
|
453
453
|
/**
|
|
454
454
|
* Vibe 다음 액션 결정 (상태 기반)
|
|
455
455
|
*/
|
|
456
456
|
export async function vibeGeminiDecideNextAction(currentState, availableActions, goal) {
|
|
457
|
-
return vibeGeminiOrchestrate(`Current State:\n${currentState}\n\nAvailable Actions:\n${availableActions.join('\n')}\n\nGoal: ${goal}`, `You are an action decider. Based on the current state and goal, select the best next action.
|
|
457
|
+
return vibeGeminiOrchestrate(`Current State:\n${currentState}\n\nAvailable Actions:\n${availableActions.join('\n')}\n\nGoal: ${goal}`, `You are an action decider. Based on the current state and goal, select the best next action.
|
|
458
458
|
Output format: { "selectedAction": "...", "reason": "...", "parameters": {} }`);
|
|
459
459
|
}
|
|
460
460
|
/**
|
|
461
461
|
* Vibe UI/UX 분석 (검색 없이 내부 지식으로)
|
|
462
462
|
*/
|
|
463
463
|
export async function vibeGeminiAnalyzeUX(description) {
|
|
464
|
-
return vibeGeminiOrchestrate(description, `You are a UI/UX expert. Analyze the given design description and provide structured feedback.
|
|
464
|
+
return vibeGeminiOrchestrate(description, `You are a UI/UX expert. Analyze the given design description and provide structured feedback.
|
|
465
465
|
Output format: { "issues": [...], "suggestions": [...], "accessibility": { "score": 0-100, "concerns": [...] } }`, { jsonMode: true });
|
|
466
466
|
}
|
|
467
467
|
//# sourceMappingURL=gemini-api.js.map
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Gemini UI Code Generator
|
|
5
5
|
*
|
|
6
6
|
* 디자인 파일(이미지, HTML 등)을 분석해서 UI 코드를 생성합니다.
|
|
7
|
+
* 기존 gemini-api 인프라 사용.
|
|
7
8
|
*
|
|
8
9
|
* Usage:
|
|
9
10
|
* node gemini-ui-gen.js --image ./design.png --framework react --output ./src/components
|
|
@@ -13,165 +14,31 @@
|
|
|
13
14
|
|
|
14
15
|
import fs from 'fs';
|
|
15
16
|
import path from 'path';
|
|
16
|
-
import
|
|
17
|
+
import { getLibBaseUrl } from './utils.js';
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
// Config
|
|
20
|
-
// ============================================
|
|
21
|
-
|
|
22
|
-
function getGlobalConfigDir() {
|
|
23
|
-
return process.platform === 'win32'
|
|
24
|
-
? path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'vibe')
|
|
25
|
-
: path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'vibe');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getGeminiCredentials() {
|
|
29
|
-
const configDir = getGlobalConfigDir();
|
|
30
|
-
|
|
31
|
-
// OAuth 토큰 확인
|
|
32
|
-
const tokenPath = path.join(configDir, 'gemini-token.json');
|
|
33
|
-
if (fs.existsSync(tokenPath)) {
|
|
34
|
-
const tokenData = JSON.parse(fs.readFileSync(tokenPath, 'utf-8'));
|
|
35
|
-
if (tokenData.access_token) {
|
|
36
|
-
return { type: 'oauth', accessToken: tokenData.access_token };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// API Key 확인
|
|
41
|
-
const keyPath = path.join(configDir, 'gemini-apikey.json');
|
|
42
|
-
if (fs.existsSync(keyPath)) {
|
|
43
|
-
const keyData = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));
|
|
44
|
-
if (keyData.apiKey) {
|
|
45
|
-
return { type: 'apikey', apiKey: keyData.apiKey };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
19
|
+
const LIB_URL = getLibBaseUrl();
|
|
51
20
|
|
|
52
21
|
// ============================================
|
|
53
|
-
// Gemini API
|
|
22
|
+
// Gemini API (기존 인프라 사용)
|
|
54
23
|
// ============================================
|
|
55
24
|
|
|
56
|
-
|
|
57
|
-
const model = 'gemini-2.0-flash';
|
|
58
|
-
|
|
59
|
-
const requestBody = {
|
|
60
|
-
contents: [
|
|
61
|
-
{
|
|
62
|
-
role: 'user',
|
|
63
|
-
parts: [
|
|
64
|
-
{
|
|
65
|
-
inlineData: {
|
|
66
|
-
mimeType,
|
|
67
|
-
data: imageBase64
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
text: prompt
|
|
72
|
-
}
|
|
73
|
-
]
|
|
74
|
-
}
|
|
75
|
-
],
|
|
76
|
-
generationConfig: {
|
|
77
|
-
maxOutputTokens: 8192,
|
|
78
|
-
temperature: 0.3,
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
let url;
|
|
83
|
-
let headers;
|
|
84
|
-
|
|
85
|
-
if (creds.type === 'apikey') {
|
|
86
|
-
url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${creds.apiKey}`;
|
|
87
|
-
headers = { 'Content-Type': 'application/json' };
|
|
88
|
-
} else {
|
|
89
|
-
// OAuth - Antigravity
|
|
90
|
-
url = 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent';
|
|
91
|
-
headers = {
|
|
92
|
-
'Authorization': `Bearer ${creds.accessToken}`,
|
|
93
|
-
'Content-Type': 'application/json',
|
|
94
|
-
'x-goog-api-client': 'vibe-ui-gen',
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Wrap for Antigravity
|
|
98
|
-
const wrappedBody = {
|
|
99
|
-
project: 'anthropic-api-proxy',
|
|
100
|
-
model: 'gemini-2.0-flash-001',
|
|
101
|
-
request: requestBody,
|
|
102
|
-
requestType: 'agent',
|
|
103
|
-
userAgent: 'antigravity',
|
|
104
|
-
requestId: `ui-gen-${Date.now()}`,
|
|
105
|
-
};
|
|
106
|
-
requestBody = wrappedBody;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const response = await fetch(url, {
|
|
110
|
-
method: 'POST',
|
|
111
|
-
headers,
|
|
112
|
-
body: JSON.stringify(creds.type === 'apikey' ? requestBody : requestBody),
|
|
113
|
-
});
|
|
25
|
+
let geminiApi = null;
|
|
114
26
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
27
|
+
async function getGeminiApi() {
|
|
28
|
+
if (!geminiApi) {
|
|
29
|
+
geminiApi = await import(`${LIB_URL}gemini-api.js`);
|
|
118
30
|
}
|
|
119
|
-
|
|
120
|
-
const result = await response.json();
|
|
121
|
-
const responseData = result.response || result;
|
|
122
|
-
|
|
123
|
-
if (!responseData.candidates || responseData.candidates.length === 0) {
|
|
124
|
-
throw new Error('Gemini returned empty response');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return responseData.candidates[0].content?.parts?.[0]?.text || '';
|
|
31
|
+
return geminiApi;
|
|
128
32
|
}
|
|
129
33
|
|
|
130
|
-
async function
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
contents: [
|
|
135
|
-
{
|
|
136
|
-
role: 'user',
|
|
137
|
-
parts: [{ text: prompt }]
|
|
138
|
-
}
|
|
139
|
-
],
|
|
140
|
-
generationConfig: {
|
|
141
|
-
maxOutputTokens: 8192,
|
|
142
|
-
temperature: 0.3,
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
let url;
|
|
147
|
-
let headers;
|
|
148
|
-
|
|
149
|
-
if (creds.type === 'apikey') {
|
|
150
|
-
url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${creds.apiKey}`;
|
|
151
|
-
headers = { 'Content-Type': 'application/json' };
|
|
152
|
-
} else {
|
|
153
|
-
url = 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:generateContent';
|
|
154
|
-
headers = {
|
|
155
|
-
'Authorization': `Bearer ${creds.accessToken}`,
|
|
156
|
-
'Content-Type': 'application/json',
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const response = await fetch(url, {
|
|
161
|
-
method: 'POST',
|
|
162
|
-
headers,
|
|
163
|
-
body: JSON.stringify(requestBody),
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
if (!response.ok) {
|
|
167
|
-
const errorText = await response.text();
|
|
168
|
-
throw new Error(`Gemini API error (${response.status}): ${errorText}`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const result = await response.json();
|
|
172
|
-
const responseData = result.response || result;
|
|
34
|
+
async function getGeminiStatus() {
|
|
35
|
+
const api = await getGeminiApi();
|
|
36
|
+
return api.vibeGeminiStatus ? await api.vibeGeminiStatus() : null;
|
|
37
|
+
}
|
|
173
38
|
|
|
174
|
-
|
|
39
|
+
async function askGemini(prompt) {
|
|
40
|
+
const api = await getGeminiApi();
|
|
41
|
+
return api.ask(prompt, { model: 'gemini-3-flash', maxTokens: 8192, temperature: 0.3 });
|
|
175
42
|
}
|
|
176
43
|
|
|
177
44
|
// ============================================
|
|
@@ -206,7 +73,7 @@ function getFrameworkPrompt(framework) {
|
|
|
206
73
|
return prompts[framework] || prompts.react;
|
|
207
74
|
}
|
|
208
75
|
|
|
209
|
-
async function generateUIFromImage(imagePath, framework
|
|
76
|
+
async function generateUIFromImage(imagePath, framework) {
|
|
210
77
|
const imageBuffer = fs.readFileSync(imagePath);
|
|
211
78
|
const imageBase64 = imageBuffer.toString('base64');
|
|
212
79
|
|
|
@@ -220,7 +87,10 @@ async function generateUIFromImage(imagePath, framework, creds) {
|
|
|
220
87
|
};
|
|
221
88
|
const mimeType = mimeTypes[ext] || 'image/png';
|
|
222
89
|
|
|
223
|
-
const prompt = `
|
|
90
|
+
const prompt = `[Image attached as base64: ${mimeType}]
|
|
91
|
+
data:${mimeType};base64,${imageBase64}
|
|
92
|
+
|
|
93
|
+
Analyze this UI design image and generate production-ready code.
|
|
224
94
|
|
|
225
95
|
${getFrameworkPrompt(framework)}
|
|
226
96
|
|
|
@@ -242,10 +112,10 @@ Also provide a summary of:
|
|
|
242
112
|
- Components identified
|
|
243
113
|
- Layout structure`;
|
|
244
114
|
|
|
245
|
-
return
|
|
115
|
+
return askGemini(prompt);
|
|
246
116
|
}
|
|
247
117
|
|
|
248
|
-
async function generateUIFromHTML(htmlPath, framework
|
|
118
|
+
async function generateUIFromHTML(htmlPath, framework) {
|
|
249
119
|
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
250
120
|
|
|
251
121
|
const prompt = `Convert this HTML mockup to production-ready ${framework} code.
|
|
@@ -266,38 +136,55 @@ Requirements:
|
|
|
266
136
|
|
|
267
137
|
Output the converted code in proper format.`;
|
|
268
138
|
|
|
269
|
-
return
|
|
139
|
+
return askGemini(prompt);
|
|
270
140
|
}
|
|
271
141
|
|
|
272
|
-
async function analyzeDesignFolder(folderPath, framework
|
|
142
|
+
async function analyzeDesignFolder(folderPath, framework) {
|
|
273
143
|
const files = fs.readdirSync(folderPath);
|
|
274
|
-
|
|
144
|
+
let combinedPrompt = `Analyze the following design files and generate production-ready ${framework} code.\n\n`;
|
|
275
145
|
|
|
276
|
-
// Read all files
|
|
277
146
|
for (const file of files) {
|
|
278
147
|
const filePath = path.join(folderPath, file);
|
|
279
148
|
const ext = path.extname(file).toLowerCase();
|
|
280
149
|
|
|
281
|
-
if (['.png', '.jpg', '.jpeg', '.webp'].includes(ext)) {
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
150
|
+
if (['.png', '.jpg', '.jpeg', '.webp', '.gif'].includes(ext)) {
|
|
151
|
+
const imageBuffer = fs.readFileSync(filePath);
|
|
152
|
+
const imageBase64 = imageBuffer.toString('base64');
|
|
153
|
+
const mimeType = ext === '.png' ? 'image/png' : ext === '.webp' ? 'image/webp' : 'image/jpeg';
|
|
154
|
+
combinedPrompt += `\n--- Image: ${file} ---\ndata:${mimeType};base64,${imageBase64}\n`;
|
|
155
|
+
console.log(`📷 ${file}`);
|
|
285
156
|
} else if (ext === '.html') {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
157
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
158
|
+
combinedPrompt += `\n--- HTML: ${file} ---\n${content}\n`;
|
|
159
|
+
console.log(`📄 ${file}`);
|
|
289
160
|
} else if (ext === '.json') {
|
|
290
|
-
console.log(`📋 Reading tokens: ${file}`);
|
|
291
161
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
292
|
-
|
|
162
|
+
combinedPrompt += `\n--- Design Tokens: ${file} ---\n${content}\n`;
|
|
163
|
+
console.log(`📋 ${file}`);
|
|
293
164
|
} else if (['.css', '.scss'].includes(ext)) {
|
|
294
|
-
console.log(`🎨 Reading styles: ${file}`);
|
|
295
165
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
296
|
-
|
|
166
|
+
combinedPrompt += `\n--- Styles: ${file} ---\n${content}\n`;
|
|
167
|
+
console.log(`🎨 ${file}`);
|
|
168
|
+
} else if (ext === '.md') {
|
|
169
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
170
|
+
combinedPrompt += `\n--- Guide: ${file} ---\n${content}\n`;
|
|
171
|
+
console.log(`📝 ${file}`);
|
|
297
172
|
}
|
|
298
173
|
}
|
|
299
174
|
|
|
300
|
-
|
|
175
|
+
combinedPrompt += `\n${getFrameworkPrompt(framework)}
|
|
176
|
+
|
|
177
|
+
Requirements:
|
|
178
|
+
1. Match the visual design exactly
|
|
179
|
+
2. Extract design tokens from JSON if provided
|
|
180
|
+
3. Use CSS variables from stylesheets if provided
|
|
181
|
+
4. Create separate component files for each major UI section
|
|
182
|
+
5. Make it responsive (mobile-first)
|
|
183
|
+
6. Include accessibility attributes
|
|
184
|
+
|
|
185
|
+
Output complete component code.`;
|
|
186
|
+
|
|
187
|
+
return askGemini(combinedPrompt);
|
|
301
188
|
}
|
|
302
189
|
|
|
303
190
|
// ============================================
|
|
@@ -307,7 +194,6 @@ async function analyzeDesignFolder(folderPath, framework, creds) {
|
|
|
307
194
|
async function main() {
|
|
308
195
|
const args = process.argv.slice(2);
|
|
309
196
|
|
|
310
|
-
// Parse arguments
|
|
311
197
|
const options = {
|
|
312
198
|
image: null,
|
|
313
199
|
html: null,
|
|
@@ -358,14 +244,14 @@ Options:
|
|
|
358
244
|
}
|
|
359
245
|
}
|
|
360
246
|
|
|
361
|
-
// Check
|
|
362
|
-
const
|
|
363
|
-
if (!
|
|
247
|
+
// Check Gemini status
|
|
248
|
+
const status = await getGeminiStatus();
|
|
249
|
+
if (!status) {
|
|
364
250
|
console.error('❌ Gemini credentials not found. Run: vibe gemini auth');
|
|
365
251
|
process.exit(1);
|
|
366
252
|
}
|
|
367
253
|
|
|
368
|
-
console.log(`🤖 Gemini UI Generator (${
|
|
254
|
+
console.log(`🤖 Gemini UI Generator (${status.type}${status.email ? `: ${status.email}` : ''})`);
|
|
369
255
|
console.log(`📦 Framework: ${options.framework}`);
|
|
370
256
|
|
|
371
257
|
try {
|
|
@@ -373,14 +259,13 @@ Options:
|
|
|
373
259
|
|
|
374
260
|
if (options.image) {
|
|
375
261
|
console.log(`\n📷 Analyzing: ${options.image}\n`);
|
|
376
|
-
result = await generateUIFromImage(options.image, options.framework
|
|
262
|
+
result = await generateUIFromImage(options.image, options.framework);
|
|
377
263
|
} else if (options.html) {
|
|
378
264
|
console.log(`\n📄 Converting: ${options.html}\n`);
|
|
379
|
-
result = await generateUIFromHTML(options.html, options.framework
|
|
265
|
+
result = await generateUIFromHTML(options.html, options.framework);
|
|
380
266
|
} else if (options.designFolder) {
|
|
381
267
|
console.log(`\n📂 Analyzing folder: ${options.designFolder}\n`);
|
|
382
|
-
|
|
383
|
-
result = JSON.stringify(results, null, 2);
|
|
268
|
+
result = await analyzeDesignFolder(options.designFolder, options.framework);
|
|
384
269
|
} else {
|
|
385
270
|
console.error('❌ No input specified. Use --image, --html, or --design-folder');
|
|
386
271
|
process.exit(1);
|
|
@@ -390,7 +275,6 @@ Options:
|
|
|
390
275
|
console.log(result);
|
|
391
276
|
console.log('\n' + '='.repeat(60));
|
|
392
277
|
|
|
393
|
-
// Output to file if specified
|
|
394
278
|
if (options.output && result) {
|
|
395
279
|
if (!fs.existsSync(options.output)) {
|
|
396
280
|
fs.mkdirSync(options.output, { recursive: true });
|