@gemini-designer/mcp-server 0.1.2 → 0.1.29
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/dist/components/catalog.d.ts.map +1 -1
- package/dist/components/catalog.js +10 -4
- package/dist/components/catalog.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +11 -6
- package/dist/config/index.js.map +1 -1
- package/dist/context/builder.d.ts.map +1 -1
- package/dist/context/builder.js.map +1 -1
- package/dist/context/filter.d.ts.map +1 -1
- package/dist/context/filter.js +5 -1
- package/dist/context/filter.js.map +1 -1
- package/dist/context/grounding.d.ts.map +1 -1
- package/dist/context/grounding.js +7 -3
- package/dist/context/grounding.js.map +1 -1
- package/dist/context/guards.d.ts.map +1 -1
- package/dist/context/guards.js +53 -0
- package/dist/context/guards.js.map +1 -1
- package/dist/context/repo-hints.js.map +1 -1
- package/dist/context/styling-detector.d.ts +24 -0
- package/dist/context/styling-detector.d.ts.map +1 -0
- package/dist/context/styling-detector.js +337 -0
- package/dist/context/styling-detector.js.map +1 -0
- package/dist/design/principles.js.map +1 -1
- package/dist/generation/gemini-client.d.ts.map +1 -1
- package/dist/generation/gemini-client.js.map +1 -1
- package/dist/generation/litellm-client.d.ts.map +1 -1
- package/dist/generation/litellm-client.js +14 -7
- package/dist/generation/litellm-client.js.map +1 -1
- package/dist/generation/remote-client.d.ts +10 -5
- package/dist/generation/remote-client.d.ts.map +1 -1
- package/dist/generation/remote-client.js +13 -2
- package/dist/generation/remote-client.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/file-writer.d.ts.map +1 -1
- package/dist/output/file-writer.js +4 -4
- package/dist/output/file-writer.js.map +1 -1
- package/dist/output/formatter.d.ts.map +1 -1
- package/dist/output/formatter.js +5 -2
- package/dist/output/formatter.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/dist/stack/detect.d.ts.map +1 -1
- package/dist/stack/detect.js +42 -9
- package/dist/stack/detect.js.map +1 -1
- package/dist/tokens/sync.d.ts.map +1 -1
- package/dist/tokens/sync.js +22 -5
- package/dist/tokens/sync.js.map +1 -1
- package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
- package/dist/tools/analyze-screenshot-ui.js +5 -5
- package/dist/tools/analyze-screenshot-ui.js.map +1 -1
- package/dist/tools/analyze-tokens.d.ts.map +1 -1
- package/dist/tools/analyze-tokens.js +3 -1
- package/dist/tools/analyze-tokens.js.map +1 -1
- package/dist/tools/catalog-components.d.ts.map +1 -1
- package/dist/tools/catalog-components.js +1 -4
- package/dist/tools/catalog-components.js.map +1 -1
- package/dist/tools/create-ui.d.ts +3 -0
- package/dist/tools/create-ui.d.ts.map +1 -1
- package/dist/tools/create-ui.js +203 -75
- package/dist/tools/create-ui.js.map +1 -1
- package/dist/tools/detect-ui-stack.js.map +1 -1
- package/dist/tools/generate-component-variants.d.ts.map +1 -1
- package/dist/tools/generate-component-variants.js +15 -4
- package/dist/tools/generate-component-variants.js.map +1 -1
- package/dist/tools/generate-vibes.d.ts.map +1 -1
- package/dist/tools/generate-vibes.js +7 -3
- package/dist/tools/generate-vibes.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/modify-ui.d.ts.map +1 -1
- package/dist/tools/modify-ui.js +7 -2
- package/dist/tools/modify-ui.js.map +1 -1
- package/dist/tools/scaffold-project.d.ts.map +1 -1
- package/dist/tools/scaffold-project.js +3 -1
- package/dist/tools/scaffold-project.js.map +1 -1
- package/dist/tools/snippet-ui.d.ts +3 -1
- package/dist/tools/snippet-ui.d.ts.map +1 -1
- package/dist/tools/snippet-ui.js +219 -88
- package/dist/tools/snippet-ui.js.map +1 -1
- package/dist/tools/sync-design-tokens.d.ts.map +1 -1
- package/dist/tools/sync-design-tokens.js +26 -11
- package/dist/tools/sync-design-tokens.js.map +1 -1
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +55 -55
- package/src/__tests__/builder.test.ts +19 -19
- package/src/__tests__/config.test.ts +63 -31
- package/src/__tests__/filter.test.ts +98 -92
- package/src/__tests__/remote-client.test.ts +179 -0
- package/src/components/catalog.ts +170 -166
- package/src/config/index.ts +185 -177
- package/src/context/builder.ts +157 -157
- package/src/context/filter.ts +110 -104
- package/src/context/grounding.ts +143 -129
- package/src/context/guards.ts +97 -38
- package/src/context/repo-hints.ts +24 -24
- package/src/context/styling-detector.ts +460 -0
- package/src/design/principles.ts +14 -14
- package/src/generation/gemini-client.ts +53 -56
- package/src/generation/litellm-client.ts +102 -86
- package/src/generation/remote-client.ts +100 -77
- package/src/index.ts +16 -16
- package/src/output/file-writer.ts +123 -123
- package/src/output/formatter.ts +139 -132
- package/src/server.ts +12 -11
- package/src/stack/detect.ts +226 -175
- package/src/tokens/sync.ts +189 -155
- package/src/tools/analyze-screenshot-ui.ts +89 -88
- package/src/tools/analyze-tokens.ts +80 -78
- package/src/tools/catalog-components.ts +68 -68
- package/src/tools/create-ui.ts +295 -142
- package/src/tools/detect-ui-stack.ts +36 -36
- package/src/tools/generate-component-variants.ts +155 -135
- package/src/tools/generate-vibes.ts +121 -117
- package/src/tools/index.ts +14 -14
- package/src/tools/modify-ui.ts +170 -165
- package/src/tools/scaffold-project.ts +68 -66
- package/src/tools/snippet-ui.ts +323 -172
- package/src/tools/sync-design-tokens.ts +217 -195
- package/src/utils/walk.ts +47 -45
- package/src/version.ts +6 -0
- package/tsconfig.json +23 -33
- package/vitest.config.ts +10 -10
- package/.prettierrc +0 -9
- package/eslint.config.js +0 -37
package/src/tools/modify-ui.ts
CHANGED
|
@@ -18,28 +18,31 @@ import { writeFile } from '../output/file-writer.js';
|
|
|
18
18
|
import { DESIGN_PRINCIPLES_COMPACT } from '../design/principles.js';
|
|
19
19
|
|
|
20
20
|
const inputSchema = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
21
|
+
targetFile: z.string().describe('Path to the file to modify'),
|
|
22
|
+
instruction: z
|
|
23
|
+
.string()
|
|
24
|
+
.describe('What to change (e.g., "Make the header sticky and add a subtle shadow")'),
|
|
25
|
+
context: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Related files for awareness (design tokens, parent components)'),
|
|
29
|
+
preserveLogic: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.default(true)
|
|
32
|
+
.describe('Never touch non-UI code like API calls, state logic, etc.'),
|
|
33
|
+
returnDiff: z.boolean().default(false).describe('Return unified diff instead of full file'),
|
|
34
|
+
applyChanges: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.default(false)
|
|
37
|
+
.describe('If true, apply the modifications directly to the file (only when returnDiff=false)'),
|
|
38
|
+
backup: z
|
|
39
|
+
.boolean()
|
|
40
|
+
.default(true)
|
|
41
|
+
.describe('If true (default), create a .bak backup before modifying'),
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
function getSystemPrompt(returnDiff: boolean): string {
|
|
42
|
-
|
|
45
|
+
const base = `You are a senior frontend engineer performing surgical code modifications that are VISUALLY DISTINCTIVE.
|
|
43
46
|
|
|
44
47
|
${DESIGN_PRINCIPLES_COMPACT}
|
|
45
48
|
|
|
@@ -58,98 +61,98 @@ CRITICAL RULES (must follow):
|
|
|
58
61
|
8. Output MUST NOT contain explanations, markdown, or code fences.
|
|
59
62
|
9. When modifying styles, apply design principles (depth, motion, typography).`;
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (returnDiff) {
|
|
65
|
+
return `${base}\n\nOutput a UNIFIED DIFF only.`;
|
|
66
|
+
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
return `${base}\n\nOutput the COMPLETE modified file only (raw code).`;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
72
|
* Extract code from a model response that might contain fences.
|
|
70
73
|
*/
|
|
71
74
|
function cleanResponse(response: string): string {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
let out = response.trim();
|
|
76
|
+
const codeBlockMatch = out.match(/```(?:\w+)?\n([\s\S]*?)```/);
|
|
77
|
+
if (codeBlockMatch) {
|
|
78
|
+
out = codeBlockMatch[1].trim();
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
export function registerModifyUI(server: McpServer, config: Config): void {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
84
|
+
server.registerTool(
|
|
85
|
+
'modify_ui',
|
|
86
|
+
{
|
|
87
|
+
title: 'Modify UI Component',
|
|
88
|
+
description:
|
|
89
|
+
'Make surgical edits to existing UI components. Preserves logic and only modifies UI-related code. Set applyChanges=true to write directly.',
|
|
90
|
+
inputSchema,
|
|
91
|
+
},
|
|
92
|
+
async (args) => {
|
|
93
|
+
const targetFile = args.targetFile as string;
|
|
94
|
+
const instruction = args.instruction as string;
|
|
95
|
+
const contextPaths = args.context as string[] | undefined;
|
|
96
|
+
const preserveLogic = args.preserveLogic !== false;
|
|
97
|
+
const returnDiff = args.returnDiff === true;
|
|
98
|
+
const shouldApply = args.applyChanges === true;
|
|
99
|
+
const shouldBackup = args.backup !== false;
|
|
100
|
+
|
|
101
|
+
if (shouldApply && returnDiff) {
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: 'text' as const,
|
|
106
|
+
text: 'Error: applyChanges=true is not supported when returnDiff=true. Set returnDiff=false to apply changes.',
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Resolve and validate the target path
|
|
114
|
+
let safeReadPath: string;
|
|
115
|
+
try {
|
|
116
|
+
safeReadPath = assertReadablePath(targetFile, config);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : 'Invalid targetFile';
|
|
119
|
+
return {
|
|
120
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Read the target file
|
|
126
|
+
let fileContent: string;
|
|
127
|
+
try {
|
|
128
|
+
fileContent = fs.readFileSync(safeReadPath, 'utf-8');
|
|
129
|
+
} catch {
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: 'text' as const,
|
|
134
|
+
text: `Error: Could not read file at ${targetFile}`,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Build context from related files
|
|
142
|
+
let contextContent = '';
|
|
143
|
+
if (contextPaths && contextPaths.length > 0) {
|
|
144
|
+
contextContent = await buildContext(contextPaths, config);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Auto-inject deterministic project grounding (stack + reusable components)
|
|
148
|
+
const grounding = await buildRepoGrounding(config, {
|
|
149
|
+
focusFileAbs: safeReadPath,
|
|
150
|
+
instruction,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const systemPrompt = getSystemPrompt(returnDiff);
|
|
154
|
+
|
|
155
|
+
const userPrompt = `Modify this file according to the instruction.
|
|
153
156
|
|
|
154
157
|
${grounding}
|
|
155
158
|
|
|
@@ -166,69 +169,71 @@ ${contextContent ? `\n\nRELATED FILES FOR CONTEXT:\n${contextContent}\n` : ''}
|
|
|
166
169
|
|
|
167
170
|
${returnDiff ? 'Return a unified diff only.' : 'Return the full modified file only.'}`;
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// Return diff or full file ONLY (no wrappers) for best agent compatibility
|
|
217
|
-
return {
|
|
218
|
-
content: [{ type: 'text' as const, text: cleaned }],
|
|
219
|
-
};
|
|
220
|
-
} catch (error) {
|
|
221
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
222
|
-
return {
|
|
223
|
-
content: [
|
|
224
|
-
{
|
|
225
|
-
type: 'text' as const,
|
|
226
|
-
text: `Error modifying UI: ${message}`,
|
|
227
|
-
},
|
|
228
|
-
],
|
|
229
|
-
isError: true,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
172
|
+
try {
|
|
173
|
+
const response = await generateWithGemini(config, systemPrompt, userPrompt, {
|
|
174
|
+
toolName: 'modify_ui',
|
|
175
|
+
});
|
|
176
|
+
const cleaned = cleanResponse(response);
|
|
177
|
+
|
|
178
|
+
// Apply changes if requested (and not returning diff)
|
|
179
|
+
if (shouldApply) {
|
|
180
|
+
let safeWritePath: string;
|
|
181
|
+
try {
|
|
182
|
+
safeWritePath = assertWritablePath(targetFile, config);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const message = error instanceof Error ? error.message : 'Invalid targetFile';
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: 'text' as const, text: `Error: ${message}` }],
|
|
187
|
+
isError: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await writeFile(safeWritePath, cleaned, {
|
|
192
|
+
format: true,
|
|
193
|
+
backup: shouldBackup,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!result.success) {
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{
|
|
200
|
+
type: 'text' as const,
|
|
201
|
+
text: `❌ Failed to apply changes: ${result.error || 'Unknown error'}`,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
isError: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const msg = [
|
|
209
|
+
`✅ Applied changes successfully.`,
|
|
210
|
+
`File: ${result.filePath}`,
|
|
211
|
+
result.backupPath ? `Backup: ${result.backupPath}` : undefined,
|
|
212
|
+
]
|
|
213
|
+
.filter(Boolean)
|
|
214
|
+
.join('\n');
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: 'text' as const, text: msg }],
|
|
218
|
+
};
|
|
232
219
|
}
|
|
233
|
-
|
|
220
|
+
|
|
221
|
+
// Return diff or full file ONLY (no wrappers) for best agent compatibility
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: 'text' as const, text: cleaned }],
|
|
224
|
+
};
|
|
225
|
+
} catch (error) {
|
|
226
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: 'text' as const,
|
|
231
|
+
text: `Error modifying UI: ${message}`,
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
isError: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
);
|
|
234
239
|
}
|
|
@@ -11,22 +11,22 @@ import { Config } from '../config/index.js';
|
|
|
11
11
|
import { generateWithGemini } from '../generation/gemini-client.js';
|
|
12
12
|
|
|
13
13
|
const inputSchema = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
projectType: z
|
|
15
|
+
.enum(['landing', 'dashboard', 'ecommerce', 'saas', 'blog', 'portfolio', 'docs', 'custom'])
|
|
16
|
+
.describe('Type of project to scaffold'),
|
|
17
|
+
name: z.string().describe('Project name'),
|
|
18
|
+
pages: z
|
|
19
|
+
.array(z.string())
|
|
20
|
+
.describe('List of pages to plan (e.g., ["Home", "Pricing", "About", "Contact"])'),
|
|
21
|
+
vibe: z.string().optional().describe('Selected design vibe from generate_vibes'),
|
|
22
|
+
features: z
|
|
23
|
+
.array(z.string())
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Special features (e.g., ["dark mode", "i18n", "auth", "animations"])'),
|
|
26
|
+
framework: z
|
|
27
|
+
.enum(['vanilla', 'react', 'vue', 'svelte', 'nextjs'])
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Target framework'),
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const SYSTEM_PROMPT = `You are a senior frontend architect creating a comprehensive UI plan for a new project.
|
|
@@ -76,63 +76,65 @@ Focus on:
|
|
|
76
76
|
- Accessibility defaults (WCAG AA) for interactive components`;
|
|
77
77
|
|
|
78
78
|
export function registerScaffoldProject(server: McpServer, config: Config): void {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
79
|
+
server.registerTool(
|
|
80
|
+
'scaffold_project',
|
|
81
|
+
{
|
|
82
|
+
title: 'Scaffold Project',
|
|
83
|
+
description:
|
|
84
|
+
'Create a complete UI architecture plan for a new project including component hierarchy, pages, and design tokens.',
|
|
85
|
+
inputSchema,
|
|
86
|
+
},
|
|
87
|
+
async (args) => {
|
|
88
|
+
const projectType = args.projectType as string;
|
|
89
|
+
const name = args.name as string;
|
|
90
|
+
const pages = args.pages as string[];
|
|
91
|
+
const vibe = args.vibe as string | undefined;
|
|
92
|
+
const features = args.features as string[] | undefined;
|
|
93
|
+
const framework =
|
|
94
|
+
(args.framework as 'vanilla' | 'react' | 'vue' | 'svelte' | 'nextjs') ||
|
|
95
|
+
config.defaultFramework;
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
let userPrompt = `Create a UI scaffold for a ${projectType} project called "${name}".
|
|
98
98
|
|
|
99
99
|
Framework: ${framework}
|
|
100
100
|
Pages: ${pages.join(', ')}`;
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
if (features && features.length > 0) {
|
|
103
|
+
userPrompt += `\nFeatures: ${features.join(', ')}`;
|
|
104
|
+
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
if (vibe) {
|
|
107
|
+
userPrompt += `\n\nDesign vibe/tokens:\n${vibe}`;
|
|
108
|
+
}
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
userPrompt += '\n\nReturn valid JSON only.';
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
try {
|
|
113
|
+
const response = await generateWithGemini(config, SYSTEM_PROMPT, userPrompt, {
|
|
114
|
+
toolName: 'scaffold_project',
|
|
115
|
+
});
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
117
|
+
// IMPORTANT: Return raw JSON ONLY (no wrappers) so agents can parse it reliably.
|
|
118
|
+
return {
|
|
119
|
+
content: [
|
|
120
|
+
{
|
|
121
|
+
type: 'text' as const,
|
|
122
|
+
text: response.trim(),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: 'text' as const,
|
|
132
|
+
text: `Error scaffolding project: ${message}`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
138
140
|
}
|