@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 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` | Utilities (--e2e, --diagram, etc.) |
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:
@@ -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 os from 'os';
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 with Vision
22
+ // Gemini API (기존 인프라 사용)
54
23
  // ============================================
55
24
 
56
- async function callGeminiWithImage(imageBase64, mimeType, prompt, creds) {
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
- if (!response.ok) {
116
- const errorText = await response.text();
117
- throw new Error(`Gemini API error (${response.status}): ${errorText}`);
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 callGeminiText(prompt, creds) {
131
- const model = 'gemini-2.0-flash';
132
-
133
- const requestBody = {
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
- return responseData.candidates?.[0]?.content?.parts?.[0]?.text || '';
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, creds) {
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 = `Analyze this UI design image and generate production-ready code.
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 callGeminiWithImage(imageBase64, mimeType, prompt, creds);
115
+ return askGemini(prompt);
246
116
  }
247
117
 
248
- async function generateUIFromHTML(htmlPath, framework, creds) {
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 callGeminiText(prompt, creds);
139
+ return askGemini(prompt);
270
140
  }
271
141
 
272
- async function analyzeDesignFolder(folderPath, framework, creds) {
142
+ async function analyzeDesignFolder(folderPath, framework) {
273
143
  const files = fs.readdirSync(folderPath);
274
- const results = [];
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
- console.log(`📷 Analyzing image: ${file}`);
283
- const result = await generateUIFromImage(filePath, framework, creds);
284
- results.push({ file, type: 'image', result });
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
- console.log(`📄 Analyzing HTML: ${file}`);
287
- const result = await generateUIFromHTML(filePath, framework, creds);
288
- results.push({ file, type: 'html', result });
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
- results.push({ file, type: 'tokens', content });
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
- results.push({ file, type: 'styles', content });
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
- return results;
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 credentials
362
- const creds = getGeminiCredentials();
363
- if (!creds) {
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 (${creds.type})`);
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, creds);
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, creds);
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
- const results = await analyzeDesignFolder(options.designFolder, options.framework, creds);
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 });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "2.6.4",
3
+ "version": "2.6.5",
4
4
  "description": "Vibe - Claude Code exclusive SPEC-driven AI coding framework with 35+ integrated tools",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",