@su-record/vibe 2.6.2 → 2.6.4
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/agents/ui-syncer.md +359 -0
- package/commands/vibe.utils.md +41 -0
- package/hooks/scripts/gemini-ui-gen.js +409 -0
- package/package.json +1 -1
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Sync UI code with design files (Gemini or Claude)
|
|
3
|
+
argument-hint: "design folder path"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /vibe.ui-sync
|
|
7
|
+
|
|
8
|
+
Analyze design files and update existing UI code to match.
|
|
9
|
+
|
|
10
|
+
- **Gemini enabled**: Gemini analyzes designs and generates code
|
|
11
|
+
- **Gemini disabled**: Claude handles directly
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/vibe.utils --ui-sync ./design/ui/
|
|
17
|
+
/vibe.utils --ui-sync ./mockups/
|
|
18
|
+
/vibe.utils --ui-sync ./design/homepage.html
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Process
|
|
22
|
+
|
|
23
|
+
### 0. Check Gemini Status
|
|
24
|
+
|
|
25
|
+
**FIRST: Check if Gemini is available.**
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
vibe gemini status
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or check: `~/.config/vibe/gemini.json` or `~/.config/vibe/gemini-apikey.json`
|
|
32
|
+
|
|
33
|
+
- **Gemini available** → Use Gemini for code generation (Step 1A)
|
|
34
|
+
- **Gemini NOT available** → Claude handles directly (Step 1B)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Path A: Gemini Enabled
|
|
39
|
+
|
|
40
|
+
### 1A. Collect ALL Design Files
|
|
41
|
+
|
|
42
|
+
Scan folder and collect all files to send to Gemini:
|
|
43
|
+
|
|
44
|
+
| Format | What to Collect |
|
|
45
|
+
| ------ | --------------- |
|
|
46
|
+
| `*.html` | Full HTML content |
|
|
47
|
+
| `*.png` / `*.jpg` / `*.webp` | Image as base64 |
|
|
48
|
+
| `*.json` | Design tokens, theme config |
|
|
49
|
+
| `*.css` / `*.scss` | Style variables |
|
|
50
|
+
| `*.md` | Design guidelines |
|
|
51
|
+
| `*.svg` | Vector content |
|
|
52
|
+
|
|
53
|
+
### 2A. Send to Gemini
|
|
54
|
+
|
|
55
|
+
Use the Gemini UI Generator script:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
node hooks/scripts/gemini-ui-gen.js \
|
|
59
|
+
--design-folder ./design/ui/ \
|
|
60
|
+
--framework react \
|
|
61
|
+
--output ./src/components
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Or call Gemini API directly with all files:**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Prepare multipart content
|
|
68
|
+
const parts = [
|
|
69
|
+
// Images as inline data
|
|
70
|
+
{ inlineData: { mimeType: "image/png", data: imageBase64 } },
|
|
71
|
+
// HTML/CSS/JSON as text
|
|
72
|
+
{ text: `HTML Mockup:\n${htmlContent}` },
|
|
73
|
+
{ text: `CSS Styles:\n${cssContent}` },
|
|
74
|
+
{ text: `Design Tokens:\n${jsonContent}` },
|
|
75
|
+
// Prompt
|
|
76
|
+
{ text: `
|
|
77
|
+
Analyze these design files and generate production-ready React components.
|
|
78
|
+
|
|
79
|
+
Requirements:
|
|
80
|
+
1. Match the visual design exactly
|
|
81
|
+
2. Use Tailwind CSS
|
|
82
|
+
3. TypeScript with proper types
|
|
83
|
+
4. Responsive and accessible
|
|
84
|
+
|
|
85
|
+
Output complete component code.
|
|
86
|
+
` }
|
|
87
|
+
];
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 3A. Apply Gemini's Output
|
|
91
|
+
|
|
92
|
+
1. Parse Gemini's code output
|
|
93
|
+
2. Extract component files
|
|
94
|
+
3. Compare with existing code
|
|
95
|
+
4. Show diff to user
|
|
96
|
+
5. Apply with confirmation
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Path B: Gemini NOT Available (Claude Fallback)
|
|
101
|
+
|
|
102
|
+
### 1B. Read ALL Design Files
|
|
103
|
+
|
|
104
|
+
**MANDATORY: Read every file in the design folder.**
|
|
105
|
+
|
|
106
|
+
| Format | What to Extract |
|
|
107
|
+
| ------ | --------------- |
|
|
108
|
+
| `*.html` | Structure, components, layout, classes |
|
|
109
|
+
| `*.png` / `*.jpg` / `*.webp` | Visual layout, colors, spacing, typography |
|
|
110
|
+
| `*.json` | Design tokens, theme config, component props |
|
|
111
|
+
| `*.css` / `*.scss` | Variables, colors, spacing, typography |
|
|
112
|
+
| `*.md` | Design guidelines, component specs |
|
|
113
|
+
| `*.svg` | Icons, vector assets |
|
|
114
|
+
|
|
115
|
+
**Reading order:**
|
|
116
|
+
|
|
117
|
+
1. `*.md` files first (design specs/guidelines)
|
|
118
|
+
2. `*.html` files (structure reference)
|
|
119
|
+
3. `*.png` / `*.jpg` images (visual reference - use Read tool)
|
|
120
|
+
4. `*.json` (design tokens)
|
|
121
|
+
5. `*.css` / `*.scss` (styles)
|
|
122
|
+
|
|
123
|
+
**CRITICAL:** Do NOT skip any file. Read images with the Read tool.
|
|
124
|
+
|
|
125
|
+
### 2B. Analyze Design Intent (Claude)
|
|
126
|
+
|
|
127
|
+
From all design files, extract:
|
|
128
|
+
|
|
129
|
+
**Layout:**
|
|
130
|
+
- Page structure (header, sidebar, main, footer)
|
|
131
|
+
- Grid system (columns, gaps)
|
|
132
|
+
- Responsive breakpoints
|
|
133
|
+
|
|
134
|
+
**Components:**
|
|
135
|
+
- Component hierarchy
|
|
136
|
+
- Component names and types
|
|
137
|
+
- Props and variants
|
|
138
|
+
|
|
139
|
+
**Styling:**
|
|
140
|
+
- Color palette (primary, secondary, accent, background, text)
|
|
141
|
+
- Typography (font family, sizes, weights)
|
|
142
|
+
- Spacing system (padding, margin, gaps)
|
|
143
|
+
- Border radius, shadows
|
|
144
|
+
- Dark/light theme support
|
|
145
|
+
|
|
146
|
+
### 3B. Generate Code (Claude)
|
|
147
|
+
|
|
148
|
+
Claude generates the code directly based on analysis.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Common Steps (Both Paths)
|
|
153
|
+
|
|
154
|
+
### 4. Find Existing UI Code
|
|
155
|
+
|
|
156
|
+
Search for existing UI implementation:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
src/components/
|
|
160
|
+
src/pages/
|
|
161
|
+
src/views/
|
|
162
|
+
src/ui/
|
|
163
|
+
app/components/
|
|
164
|
+
app/(routes)/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Detect framework:
|
|
168
|
+
- React: `*.tsx`, `*.jsx`
|
|
169
|
+
- Vue: `*.vue`
|
|
170
|
+
- Svelte: `*.svelte`
|
|
171
|
+
- Angular: `*.component.ts`
|
|
172
|
+
|
|
173
|
+
### 5. Compare Design vs Code
|
|
174
|
+
|
|
175
|
+
Create a diff report:
|
|
176
|
+
|
|
177
|
+
| Aspect | Design | Current Code | Action |
|
|
178
|
+
| ------ | ------ | ------------ | ------ |
|
|
179
|
+
| Colors | #2F6BFF | #3B82F6 | Update |
|
|
180
|
+
| Font | Inter | system-ui | Update |
|
|
181
|
+
| Border radius | 12px | 8px | Update |
|
|
182
|
+
| Component X | Exists | Missing | Create |
|
|
183
|
+
|
|
184
|
+
### 6. Generate Update Plan
|
|
185
|
+
|
|
186
|
+
List all changes needed:
|
|
187
|
+
|
|
188
|
+
**Style Updates:**
|
|
189
|
+
```css
|
|
190
|
+
/* Before */
|
|
191
|
+
--primary: #3B82F6;
|
|
192
|
+
--radius: 8px;
|
|
193
|
+
|
|
194
|
+
/* After */
|
|
195
|
+
--primary: #2F6BFF;
|
|
196
|
+
--radius: 12px;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Component Updates:**
|
|
200
|
+
```tsx
|
|
201
|
+
// Before: Missing hover state
|
|
202
|
+
<Button>Click</Button>
|
|
203
|
+
|
|
204
|
+
// After: Add hover state from design
|
|
205
|
+
<Button className="hover:bg-primary-600">Click</Button>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**New Components:**
|
|
209
|
+
- List components that exist in design but not in code
|
|
210
|
+
|
|
211
|
+
### 7. Execute Updates
|
|
212
|
+
|
|
213
|
+
**Ask user before making changes:**
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
Found 12 differences between design and code:
|
|
217
|
+
- 3 color updates
|
|
218
|
+
- 2 spacing changes
|
|
219
|
+
- 1 new component
|
|
220
|
+
- 6 style adjustments
|
|
221
|
+
|
|
222
|
+
Proceed with updates? [Y/n]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Apply changes:**
|
|
226
|
+
|
|
227
|
+
1. Update CSS/SCSS variables
|
|
228
|
+
2. Update Tailwind config (if applicable)
|
|
229
|
+
3. Update component styles
|
|
230
|
+
4. Create new components (if needed)
|
|
231
|
+
5. Update imports
|
|
232
|
+
|
|
233
|
+
### 8. Verify Changes
|
|
234
|
+
|
|
235
|
+
After updates:
|
|
236
|
+
|
|
237
|
+
1. Run build to check for errors
|
|
238
|
+
2. Show before/after comparison
|
|
239
|
+
3. List all files modified
|
|
240
|
+
|
|
241
|
+
## Output Format
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
📂 Design Analysis: ./design/ui/
|
|
245
|
+
🤖 Mode: Gemini (or Claude fallback)
|
|
246
|
+
|
|
247
|
+
Files Read:
|
|
248
|
+
✓ homepage.html (structure)
|
|
249
|
+
✓ homepage.png (visual)
|
|
250
|
+
✓ tokens.json (design tokens)
|
|
251
|
+
✓ styles.css (variables)
|
|
252
|
+
|
|
253
|
+
Design Specs Extracted:
|
|
254
|
+
Colors: #2F6BFF (primary), #1E293B (text), #F8FAFC (bg)
|
|
255
|
+
Typography: Inter, 14px base, 1.5 line-height
|
|
256
|
+
Spacing: 4px base unit
|
|
257
|
+
Border Radius: 12px (cards), 8px (buttons)
|
|
258
|
+
|
|
259
|
+
Differences Found: 8
|
|
260
|
+
|
|
261
|
+
| File | Change | Status |
|
|
262
|
+
|------|--------|--------|
|
|
263
|
+
| tailwind.config.js | Update primary color | ⏳ Pending |
|
|
264
|
+
| src/components/Button.tsx | Add hover state | ⏳ Pending |
|
|
265
|
+
| src/styles/globals.css | Update CSS variables | ⏳ Pending |
|
|
266
|
+
|
|
267
|
+
Apply changes? [Y/n]
|
|
268
|
+
|
|
269
|
+
✅ Updated 3 files
|
|
270
|
+
- tailwind.config.js
|
|
271
|
+
- src/components/Button.tsx
|
|
272
|
+
- src/styles/globals.css
|
|
273
|
+
|
|
274
|
+
Next: Run `npm run build` to verify
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Example (with Gemini)
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
User: /vibe.utils --ui-sync ./design/dashboard/
|
|
281
|
+
|
|
282
|
+
Claude: Checking Gemini status...
|
|
283
|
+
✅ Gemini: OAuth authenticated (user@example.com)
|
|
284
|
+
|
|
285
|
+
📂 Collecting design files...
|
|
286
|
+
- dashboard.html
|
|
287
|
+
- dashboard-dark.png
|
|
288
|
+
- dashboard-light.png
|
|
289
|
+
- tokens.json
|
|
290
|
+
- components.md
|
|
291
|
+
|
|
292
|
+
🤖 Sending to Gemini for analysis...
|
|
293
|
+
|
|
294
|
+
Gemini Response:
|
|
295
|
+
Layout: Header + Sidebar + 3-column main
|
|
296
|
+
Theme: Dark (#0F172A bg, #F8FAFC text)
|
|
297
|
+
Components: Header, Sidebar, StatCard, DataTable
|
|
298
|
+
|
|
299
|
+
Generated code:
|
|
300
|
+
- Header.tsx (64 lines)
|
|
301
|
+
- Sidebar.tsx (45 lines)
|
|
302
|
+
- StatCard.tsx (32 lines)
|
|
303
|
+
- DataTable.tsx (78 lines)
|
|
304
|
+
|
|
305
|
+
🔍 Comparing with existing code...
|
|
306
|
+
|
|
307
|
+
Found: src/pages/Dashboard.tsx
|
|
308
|
+
Found: src/components/StatCard.tsx
|
|
309
|
+
Missing: src/components/DataTable.tsx
|
|
310
|
+
|
|
311
|
+
Differences:
|
|
312
|
+
1. StatCard border-radius: design=16px, code=8px
|
|
313
|
+
2. Sidebar width: design=240px, code=200px
|
|
314
|
+
3. Primary color: design=#6366F1, code=#3B82F6
|
|
315
|
+
4. DataTable component: missing
|
|
316
|
+
|
|
317
|
+
Apply 3 updates + create 1 component? [Y/n]
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Example (Claude Fallback)
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
User: /vibe.utils --ui-sync ./design/dashboard/
|
|
324
|
+
|
|
325
|
+
Claude: Checking Gemini status...
|
|
326
|
+
⚠️ Gemini not configured. Using Claude fallback.
|
|
327
|
+
|
|
328
|
+
📂 Reading design files...
|
|
329
|
+
|
|
330
|
+
Reading dashboard.html...
|
|
331
|
+
[Extracts: header, sidebar, 3-column grid]
|
|
332
|
+
|
|
333
|
+
Reading dashboard-dark.png...
|
|
334
|
+
[Analyzes: dark theme colors, spacing]
|
|
335
|
+
|
|
336
|
+
Reading tokens.json...
|
|
337
|
+
[Extracts: color palette, spacing scale]
|
|
338
|
+
|
|
339
|
+
📊 Design Analysis Complete (Claude):
|
|
340
|
+
|
|
341
|
+
Layout: Header + Sidebar + 3-column main
|
|
342
|
+
Theme: Dark (#0F172A bg, #F8FAFC text)
|
|
343
|
+
|
|
344
|
+
🔍 Comparing with existing code...
|
|
345
|
+
...
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Notes
|
|
349
|
+
|
|
350
|
+
- Always check Gemini status first
|
|
351
|
+
- Gemini handles image analysis better than Claude for visual matching
|
|
352
|
+
- Claude fallback works but may be less accurate for complex visuals
|
|
353
|
+
- Always read ALL files including images
|
|
354
|
+
- Ask for confirmation before making changes
|
|
355
|
+
- Preserve existing functionality while updating styles
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
ARGUMENTS: $ARGUMENTS
|
package/commands/vibe.utils.md
CHANGED
|
@@ -11,6 +11,7 @@ Collection of utility tools. Use with options.
|
|
|
11
11
|
|
|
12
12
|
```
|
|
13
13
|
/vibe.utils --ui "description" # UI ASCII preview
|
|
14
|
+
/vibe.utils --ui-sync ./design/ # Sync UI code with design files
|
|
14
15
|
/vibe.utils --diagram # Architecture diagram
|
|
15
16
|
/vibe.utils --diagram --er # ERD diagram
|
|
16
17
|
/vibe.utils --diagram --flow # Flowchart
|
|
@@ -63,6 +64,46 @@ Generate UI preview from description or design folder.
|
|
|
63
64
|
|
|
64
65
|
---
|
|
65
66
|
|
|
67
|
+
## --ui-sync (Design to Code Sync)
|
|
68
|
+
|
|
69
|
+
Read and follow `agents/ui-syncer.md` for design-to-code synchronization.
|
|
70
|
+
|
|
71
|
+
Analyze design files (HTML, images, CSS, tokens) and update existing UI code to match.
|
|
72
|
+
|
|
73
|
+
**What it does:**
|
|
74
|
+
|
|
75
|
+
1. Read ALL design files (including images)
|
|
76
|
+
2. Extract design specs (colors, typography, spacing, components)
|
|
77
|
+
3. Compare with existing code
|
|
78
|
+
4. Generate update plan
|
|
79
|
+
5. Apply changes (with confirmation)
|
|
80
|
+
|
|
81
|
+
**Supported file formats:**
|
|
82
|
+
|
|
83
|
+
- `*.html` - Structure, layout, classes
|
|
84
|
+
- `*.png` / `*.jpg` / `*.webp` - Visual reference (Claude reads images)
|
|
85
|
+
- `*.json` - Design tokens, theme config
|
|
86
|
+
- `*.css` / `*.scss` - Variables, colors, spacing
|
|
87
|
+
- `*.md` - Design guidelines
|
|
88
|
+
- `*.svg` - Icons, vectors
|
|
89
|
+
|
|
90
|
+
**Example:**
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
/vibe.utils --ui-sync ./design/ui/
|
|
94
|
+
/vibe.utils --ui-sync ./mockups/dashboard/
|
|
95
|
+
/vibe.utils --ui-sync ./design/homepage.html
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Output:**
|
|
99
|
+
|
|
100
|
+
- Diff report (design vs code)
|
|
101
|
+
- List of changes to apply
|
|
102
|
+
- Confirmation prompt
|
|
103
|
+
- Files modified
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
66
107
|
## --diagram (Diagram Generation)
|
|
67
108
|
|
|
68
109
|
Read and follow `agents/diagrammer.md` for diagram generation.
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gemini UI Code Generator
|
|
5
|
+
*
|
|
6
|
+
* 디자인 파일(이미지, HTML 등)을 분석해서 UI 코드를 생성합니다.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node gemini-ui-gen.js --image ./design.png --framework react --output ./src/components
|
|
10
|
+
* node gemini-ui-gen.js --html ./mockup.html --framework vue --output ./src/components
|
|
11
|
+
* node gemini-ui-gen.js --design-folder ./design/ --framework react --output ./src
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from 'fs';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import os from 'os';
|
|
17
|
+
|
|
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
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================
|
|
53
|
+
// Gemini API with Vision
|
|
54
|
+
// ============================================
|
|
55
|
+
|
|
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
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
const errorText = await response.text();
|
|
117
|
+
throw new Error(`Gemini API error (${response.status}): ${errorText}`);
|
|
118
|
+
}
|
|
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 || '';
|
|
128
|
+
}
|
|
129
|
+
|
|
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;
|
|
173
|
+
|
|
174
|
+
return responseData.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// UI Code Generation
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
function getFrameworkPrompt(framework) {
|
|
182
|
+
const prompts = {
|
|
183
|
+
react: `Generate React TypeScript components using:
|
|
184
|
+
- Functional components with hooks
|
|
185
|
+
- Tailwind CSS for styling
|
|
186
|
+
- Proper TypeScript types/interfaces
|
|
187
|
+
- Export as default`,
|
|
188
|
+
|
|
189
|
+
vue: `Generate Vue 3 components using:
|
|
190
|
+
- Composition API with <script setup>
|
|
191
|
+
- Tailwind CSS for styling
|
|
192
|
+
- TypeScript support
|
|
193
|
+
- Single File Component format`,
|
|
194
|
+
|
|
195
|
+
svelte: `Generate Svelte components using:
|
|
196
|
+
- Svelte 5 runes syntax
|
|
197
|
+
- Tailwind CSS for styling
|
|
198
|
+
- TypeScript support`,
|
|
199
|
+
|
|
200
|
+
html: `Generate semantic HTML5 with:
|
|
201
|
+
- Tailwind CSS classes
|
|
202
|
+
- Accessible markup
|
|
203
|
+
- Responsive design`,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
return prompts[framework] || prompts.react;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function generateUIFromImage(imagePath, framework, creds) {
|
|
210
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
211
|
+
const imageBase64 = imageBuffer.toString('base64');
|
|
212
|
+
|
|
213
|
+
const ext = path.extname(imagePath).toLowerCase();
|
|
214
|
+
const mimeTypes = {
|
|
215
|
+
'.png': 'image/png',
|
|
216
|
+
'.jpg': 'image/jpeg',
|
|
217
|
+
'.jpeg': 'image/jpeg',
|
|
218
|
+
'.webp': 'image/webp',
|
|
219
|
+
'.gif': 'image/gif',
|
|
220
|
+
};
|
|
221
|
+
const mimeType = mimeTypes[ext] || 'image/png';
|
|
222
|
+
|
|
223
|
+
const prompt = `Analyze this UI design image and generate production-ready code.
|
|
224
|
+
|
|
225
|
+
${getFrameworkPrompt(framework)}
|
|
226
|
+
|
|
227
|
+
Requirements:
|
|
228
|
+
1. Match the visual design exactly (colors, spacing, typography, layout)
|
|
229
|
+
2. Extract exact colors as hex values
|
|
230
|
+
3. Use proper semantic HTML structure
|
|
231
|
+
4. Make it responsive (mobile-first)
|
|
232
|
+
5. Include hover/focus states where appropriate
|
|
233
|
+
6. Add appropriate accessibility attributes
|
|
234
|
+
|
|
235
|
+
Output format:
|
|
236
|
+
\`\`\`${framework === 'html' ? 'html' : 'tsx'}
|
|
237
|
+
// Component code here
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
Also provide a summary of:
|
|
241
|
+
- Colors extracted
|
|
242
|
+
- Components identified
|
|
243
|
+
- Layout structure`;
|
|
244
|
+
|
|
245
|
+
return callGeminiWithImage(imageBase64, mimeType, prompt, creds);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function generateUIFromHTML(htmlPath, framework, creds) {
|
|
249
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
250
|
+
|
|
251
|
+
const prompt = `Convert this HTML mockup to production-ready ${framework} code.
|
|
252
|
+
|
|
253
|
+
HTML Mockup:
|
|
254
|
+
\`\`\`html
|
|
255
|
+
${htmlContent}
|
|
256
|
+
\`\`\`
|
|
257
|
+
|
|
258
|
+
${getFrameworkPrompt(framework)}
|
|
259
|
+
|
|
260
|
+
Requirements:
|
|
261
|
+
1. Preserve the exact visual appearance
|
|
262
|
+
2. Extract inline styles to Tailwind classes
|
|
263
|
+
3. Create reusable components where appropriate
|
|
264
|
+
4. Add proper TypeScript types
|
|
265
|
+
5. Make it responsive
|
|
266
|
+
|
|
267
|
+
Output the converted code in proper format.`;
|
|
268
|
+
|
|
269
|
+
return callGeminiText(prompt, creds);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function analyzeDesignFolder(folderPath, framework, creds) {
|
|
273
|
+
const files = fs.readdirSync(folderPath);
|
|
274
|
+
const results = [];
|
|
275
|
+
|
|
276
|
+
// Read all files
|
|
277
|
+
for (const file of files) {
|
|
278
|
+
const filePath = path.join(folderPath, file);
|
|
279
|
+
const ext = path.extname(file).toLowerCase();
|
|
280
|
+
|
|
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 });
|
|
285
|
+
} 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 });
|
|
289
|
+
} else if (ext === '.json') {
|
|
290
|
+
console.log(`📋 Reading tokens: ${file}`);
|
|
291
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
292
|
+
results.push({ file, type: 'tokens', content });
|
|
293
|
+
} else if (['.css', '.scss'].includes(ext)) {
|
|
294
|
+
console.log(`🎨 Reading styles: ${file}`);
|
|
295
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
296
|
+
results.push({ file, type: 'styles', content });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================
|
|
304
|
+
// CLI
|
|
305
|
+
// ============================================
|
|
306
|
+
|
|
307
|
+
async function main() {
|
|
308
|
+
const args = process.argv.slice(2);
|
|
309
|
+
|
|
310
|
+
// Parse arguments
|
|
311
|
+
const options = {
|
|
312
|
+
image: null,
|
|
313
|
+
html: null,
|
|
314
|
+
designFolder: null,
|
|
315
|
+
framework: 'react',
|
|
316
|
+
output: './generated',
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
for (let i = 0; i < args.length; i++) {
|
|
320
|
+
switch (args[i]) {
|
|
321
|
+
case '--image':
|
|
322
|
+
options.image = args[++i];
|
|
323
|
+
break;
|
|
324
|
+
case '--html':
|
|
325
|
+
options.html = args[++i];
|
|
326
|
+
break;
|
|
327
|
+
case '--design-folder':
|
|
328
|
+
case '--folder':
|
|
329
|
+
options.designFolder = args[++i];
|
|
330
|
+
break;
|
|
331
|
+
case '--framework':
|
|
332
|
+
case '-f':
|
|
333
|
+
options.framework = args[++i];
|
|
334
|
+
break;
|
|
335
|
+
case '--output':
|
|
336
|
+
case '-o':
|
|
337
|
+
options.output = args[++i];
|
|
338
|
+
break;
|
|
339
|
+
case '--help':
|
|
340
|
+
case '-h':
|
|
341
|
+
console.log(`
|
|
342
|
+
Gemini UI Code Generator
|
|
343
|
+
|
|
344
|
+
Usage:
|
|
345
|
+
node gemini-ui-gen.js --image ./design.png --framework react
|
|
346
|
+
node gemini-ui-gen.js --html ./mockup.html --framework vue
|
|
347
|
+
node gemini-ui-gen.js --design-folder ./design/ --framework react
|
|
348
|
+
|
|
349
|
+
Options:
|
|
350
|
+
--image <path> Image file to analyze
|
|
351
|
+
--html <path> HTML mockup to convert
|
|
352
|
+
--design-folder <path> Folder with design files
|
|
353
|
+
--framework <name> Target framework (react, vue, svelte, html)
|
|
354
|
+
--output <path> Output directory
|
|
355
|
+
--help Show this help
|
|
356
|
+
`);
|
|
357
|
+
process.exit(0);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Check credentials
|
|
362
|
+
const creds = getGeminiCredentials();
|
|
363
|
+
if (!creds) {
|
|
364
|
+
console.error('❌ Gemini credentials not found. Run: vibe gemini auth');
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(`🤖 Gemini UI Generator (${creds.type})`);
|
|
369
|
+
console.log(`📦 Framework: ${options.framework}`);
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
let result;
|
|
373
|
+
|
|
374
|
+
if (options.image) {
|
|
375
|
+
console.log(`\n📷 Analyzing: ${options.image}\n`);
|
|
376
|
+
result = await generateUIFromImage(options.image, options.framework, creds);
|
|
377
|
+
} else if (options.html) {
|
|
378
|
+
console.log(`\n📄 Converting: ${options.html}\n`);
|
|
379
|
+
result = await generateUIFromHTML(options.html, options.framework, creds);
|
|
380
|
+
} else if (options.designFolder) {
|
|
381
|
+
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);
|
|
384
|
+
} else {
|
|
385
|
+
console.error('❌ No input specified. Use --image, --html, or --design-folder');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
console.log('\n' + '='.repeat(60) + '\n');
|
|
390
|
+
console.log(result);
|
|
391
|
+
console.log('\n' + '='.repeat(60));
|
|
392
|
+
|
|
393
|
+
// Output to file if specified
|
|
394
|
+
if (options.output && result) {
|
|
395
|
+
if (!fs.existsSync(options.output)) {
|
|
396
|
+
fs.mkdirSync(options.output, { recursive: true });
|
|
397
|
+
}
|
|
398
|
+
const outputFile = path.join(options.output, `generated-${Date.now()}.txt`);
|
|
399
|
+
fs.writeFileSync(outputFile, result);
|
|
400
|
+
console.log(`\n✅ Output saved to: ${outputFile}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error(`\n❌ Error: ${error.message}`);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
main();
|