@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.
Files changed (129) hide show
  1. package/dist/components/catalog.d.ts.map +1 -1
  2. package/dist/components/catalog.js +10 -4
  3. package/dist/components/catalog.js.map +1 -1
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/index.js +11 -6
  6. package/dist/config/index.js.map +1 -1
  7. package/dist/context/builder.d.ts.map +1 -1
  8. package/dist/context/builder.js.map +1 -1
  9. package/dist/context/filter.d.ts.map +1 -1
  10. package/dist/context/filter.js +5 -1
  11. package/dist/context/filter.js.map +1 -1
  12. package/dist/context/grounding.d.ts.map +1 -1
  13. package/dist/context/grounding.js +7 -3
  14. package/dist/context/grounding.js.map +1 -1
  15. package/dist/context/guards.d.ts.map +1 -1
  16. package/dist/context/guards.js +53 -0
  17. package/dist/context/guards.js.map +1 -1
  18. package/dist/context/repo-hints.js.map +1 -1
  19. package/dist/context/styling-detector.d.ts +24 -0
  20. package/dist/context/styling-detector.d.ts.map +1 -0
  21. package/dist/context/styling-detector.js +337 -0
  22. package/dist/context/styling-detector.js.map +1 -0
  23. package/dist/design/principles.js.map +1 -1
  24. package/dist/generation/gemini-client.d.ts.map +1 -1
  25. package/dist/generation/gemini-client.js.map +1 -1
  26. package/dist/generation/litellm-client.d.ts.map +1 -1
  27. package/dist/generation/litellm-client.js +14 -7
  28. package/dist/generation/litellm-client.js.map +1 -1
  29. package/dist/generation/remote-client.d.ts +10 -5
  30. package/dist/generation/remote-client.d.ts.map +1 -1
  31. package/dist/generation/remote-client.js +13 -2
  32. package/dist/generation/remote-client.js.map +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/output/file-writer.d.ts.map +1 -1
  35. package/dist/output/file-writer.js +4 -4
  36. package/dist/output/file-writer.js.map +1 -1
  37. package/dist/output/formatter.d.ts.map +1 -1
  38. package/dist/output/formatter.js +5 -2
  39. package/dist/output/formatter.js.map +1 -1
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +2 -1
  42. package/dist/server.js.map +1 -1
  43. package/dist/stack/detect.d.ts.map +1 -1
  44. package/dist/stack/detect.js +42 -9
  45. package/dist/stack/detect.js.map +1 -1
  46. package/dist/tokens/sync.d.ts.map +1 -1
  47. package/dist/tokens/sync.js +22 -5
  48. package/dist/tokens/sync.js.map +1 -1
  49. package/dist/tools/analyze-screenshot-ui.d.ts.map +1 -1
  50. package/dist/tools/analyze-screenshot-ui.js +5 -5
  51. package/dist/tools/analyze-screenshot-ui.js.map +1 -1
  52. package/dist/tools/analyze-tokens.d.ts.map +1 -1
  53. package/dist/tools/analyze-tokens.js +3 -1
  54. package/dist/tools/analyze-tokens.js.map +1 -1
  55. package/dist/tools/catalog-components.d.ts.map +1 -1
  56. package/dist/tools/catalog-components.js +1 -4
  57. package/dist/tools/catalog-components.js.map +1 -1
  58. package/dist/tools/create-ui.d.ts +3 -0
  59. package/dist/tools/create-ui.d.ts.map +1 -1
  60. package/dist/tools/create-ui.js +203 -75
  61. package/dist/tools/create-ui.js.map +1 -1
  62. package/dist/tools/detect-ui-stack.js.map +1 -1
  63. package/dist/tools/generate-component-variants.d.ts.map +1 -1
  64. package/dist/tools/generate-component-variants.js +15 -4
  65. package/dist/tools/generate-component-variants.js.map +1 -1
  66. package/dist/tools/generate-vibes.d.ts.map +1 -1
  67. package/dist/tools/generate-vibes.js +7 -3
  68. package/dist/tools/generate-vibes.js.map +1 -1
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/tools/modify-ui.d.ts.map +1 -1
  71. package/dist/tools/modify-ui.js +7 -2
  72. package/dist/tools/modify-ui.js.map +1 -1
  73. package/dist/tools/scaffold-project.d.ts.map +1 -1
  74. package/dist/tools/scaffold-project.js +3 -1
  75. package/dist/tools/scaffold-project.js.map +1 -1
  76. package/dist/tools/snippet-ui.d.ts +3 -1
  77. package/dist/tools/snippet-ui.d.ts.map +1 -1
  78. package/dist/tools/snippet-ui.js +219 -88
  79. package/dist/tools/snippet-ui.js.map +1 -1
  80. package/dist/tools/sync-design-tokens.d.ts.map +1 -1
  81. package/dist/tools/sync-design-tokens.js +26 -11
  82. package/dist/tools/sync-design-tokens.js.map +1 -1
  83. package/dist/utils/walk.d.ts.map +1 -1
  84. package/dist/utils/walk.js.map +1 -1
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.d.ts.map +1 -0
  87. package/dist/version.js +5 -0
  88. package/dist/version.js.map +1 -0
  89. package/package.json +55 -55
  90. package/src/__tests__/builder.test.ts +19 -19
  91. package/src/__tests__/config.test.ts +63 -31
  92. package/src/__tests__/filter.test.ts +98 -92
  93. package/src/__tests__/remote-client.test.ts +179 -0
  94. package/src/components/catalog.ts +170 -166
  95. package/src/config/index.ts +185 -177
  96. package/src/context/builder.ts +157 -157
  97. package/src/context/filter.ts +110 -104
  98. package/src/context/grounding.ts +143 -129
  99. package/src/context/guards.ts +97 -38
  100. package/src/context/repo-hints.ts +24 -24
  101. package/src/context/styling-detector.ts +460 -0
  102. package/src/design/principles.ts +14 -14
  103. package/src/generation/gemini-client.ts +53 -56
  104. package/src/generation/litellm-client.ts +102 -86
  105. package/src/generation/remote-client.ts +100 -77
  106. package/src/index.ts +16 -16
  107. package/src/output/file-writer.ts +123 -123
  108. package/src/output/formatter.ts +139 -132
  109. package/src/server.ts +12 -11
  110. package/src/stack/detect.ts +226 -175
  111. package/src/tokens/sync.ts +189 -155
  112. package/src/tools/analyze-screenshot-ui.ts +89 -88
  113. package/src/tools/analyze-tokens.ts +80 -78
  114. package/src/tools/catalog-components.ts +68 -68
  115. package/src/tools/create-ui.ts +295 -142
  116. package/src/tools/detect-ui-stack.ts +36 -36
  117. package/src/tools/generate-component-variants.ts +155 -135
  118. package/src/tools/generate-vibes.ts +121 -117
  119. package/src/tools/index.ts +14 -14
  120. package/src/tools/modify-ui.ts +170 -165
  121. package/src/tools/scaffold-project.ts +68 -66
  122. package/src/tools/snippet-ui.ts +323 -172
  123. package/src/tools/sync-design-tokens.ts +217 -195
  124. package/src/utils/walk.ts +47 -45
  125. package/src/version.ts +6 -0
  126. package/tsconfig.json +23 -33
  127. package/vitest.config.ts +10 -10
  128. package/.prettierrc +0 -9
  129. package/eslint.config.js +0 -37
@@ -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
- 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.boolean().default(true).describe('If true (default), create a .bak backup before modifying'),
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
- const base = `You are a senior frontend engineer performing surgical code modifications that are VISUALLY DISTINCTIVE.
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
- if (returnDiff) {
62
- return `${base}\n\nOutput a UNIFIED DIFF only.`;
63
- }
64
+ if (returnDiff) {
65
+ return `${base}\n\nOutput a UNIFIED DIFF only.`;
66
+ }
64
67
 
65
- return `${base}\n\nOutput the COMPLETE modified file only (raw code).`;
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
- let out = response.trim();
73
- const codeBlockMatch = out.match(/```(?:\w+)?\n([\s\S]*?)```/);
74
- if (codeBlockMatch) {
75
- out = codeBlockMatch[1].trim();
76
- }
77
- return out;
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
- server.registerTool(
82
- 'modify_ui',
83
- {
84
- title: 'Modify UI Component',
85
- description:
86
- 'Make surgical edits to existing UI components. Preserves logic and only modifies UI-related code. Set applyChanges=true to write directly.',
87
- inputSchema,
88
- },
89
- async (args) => {
90
- const targetFile = args.targetFile as string;
91
- const instruction = args.instruction as string;
92
- const contextPaths = args.context as string[] | undefined;
93
- const preserveLogic = args.preserveLogic !== false;
94
- const returnDiff = args.returnDiff === true;
95
- const shouldApply = args.applyChanges === true;
96
- const shouldBackup = args.backup !== false;
97
-
98
- if (shouldApply && returnDiff) {
99
- return {
100
- content: [
101
- {
102
- type: 'text' as const,
103
- text: 'Error: applyChanges=true is not supported when returnDiff=true. Set returnDiff=false to apply changes.',
104
- },
105
- ],
106
- isError: true,
107
- };
108
- }
109
-
110
- // Resolve and validate the target path
111
- let safeReadPath: string;
112
- try {
113
- safeReadPath = assertReadablePath(targetFile, config);
114
- } catch (error) {
115
- const message = error instanceof Error ? error.message : 'Invalid targetFile';
116
- return {
117
- content: [{ type: 'text' as const, text: `Error: ${message}` }],
118
- isError: true,
119
- };
120
- }
121
-
122
- // Read the target file
123
- let fileContent: string;
124
- try {
125
- fileContent = fs.readFileSync(safeReadPath, 'utf-8');
126
- } catch {
127
- return {
128
- content: [
129
- {
130
- type: 'text' as const,
131
- text: `Error: Could not read file at ${targetFile}`,
132
- },
133
- ],
134
- isError: true,
135
- };
136
- }
137
-
138
- // Build context from related files
139
- let contextContent = '';
140
- if (contextPaths && contextPaths.length > 0) {
141
- contextContent = await buildContext(contextPaths, config);
142
- }
143
-
144
- // Auto-inject deterministic project grounding (stack + reusable components)
145
- const grounding = await buildRepoGrounding(config, {
146
- focusFileAbs: safeReadPath,
147
- instruction,
148
- });
149
-
150
- const systemPrompt = getSystemPrompt(returnDiff);
151
-
152
- const userPrompt = `Modify this file according to the instruction.
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
- try {
170
- const response = await generateWithGemini(config, systemPrompt, userPrompt, { toolName: 'modify_ui' });
171
- const cleaned = cleanResponse(response);
172
-
173
- // Apply changes if requested (and not returning diff)
174
- if (shouldApply) {
175
- let safeWritePath: string;
176
- try {
177
- safeWritePath = assertWritablePath(targetFile, config);
178
- } catch (error) {
179
- const message = error instanceof Error ? error.message : 'Invalid targetFile';
180
- return {
181
- content: [{ type: 'text' as const, text: `Error: ${message}` }],
182
- isError: true,
183
- };
184
- }
185
-
186
- const result = await writeFile(safeWritePath, cleaned, {
187
- format: true,
188
- backup: shouldBackup,
189
- });
190
-
191
- if (!result.success) {
192
- return {
193
- content: [
194
- {
195
- type: 'text' as const,
196
- text: `❌ Failed to apply changes: ${result.error || 'Unknown error'}`,
197
- },
198
- ],
199
- isError: true,
200
- };
201
- }
202
-
203
- const msg = [
204
- `✅ Applied changes successfully.`,
205
- `File: ${result.filePath}`,
206
- result.backupPath ? `Backup: ${result.backupPath}` : undefined,
207
- ]
208
- .filter(Boolean)
209
- .join('\n');
210
-
211
- return {
212
- content: [{ type: 'text' as const, text: msg }],
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
- 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'),
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
- 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;
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
- let userPrompt = `Create a UI scaffold for a ${projectType} project called "${name}".
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
- if (features && features.length > 0) {
103
- userPrompt += `\nFeatures: ${features.join(', ')}`;
104
- }
102
+ if (features && features.length > 0) {
103
+ userPrompt += `\nFeatures: ${features.join(', ')}`;
104
+ }
105
105
 
106
- if (vibe) {
107
- userPrompt += `\n\nDesign vibe/tokens:\n${vibe}`;
108
- }
106
+ if (vibe) {
107
+ userPrompt += `\n\nDesign vibe/tokens:\n${vibe}`;
108
+ }
109
109
 
110
- userPrompt += '\n\nReturn valid JSON only.';
110
+ userPrompt += '\n\nReturn valid JSON only.';
111
111
 
112
- try {
113
- const response = await generateWithGemini(config, SYSTEM_PROMPT, userPrompt, { toolName: 'scaffold_project' });
112
+ try {
113
+ const response = await generateWithGemini(config, SYSTEM_PROMPT, userPrompt, {
114
+ toolName: 'scaffold_project',
115
+ });
114
116
 
115
- // IMPORTANT: Return raw JSON ONLY (no wrappers) so agents can parse it reliably.
116
- return {
117
- content: [
118
- {
119
- type: 'text' as const,
120
- text: response.trim(),
121
- },
122
- ],
123
- };
124
- } catch (error) {
125
- const message = error instanceof Error ? error.message : 'Unknown error';
126
- return {
127
- content: [
128
- {
129
- type: 'text' as const,
130
- text: `Error scaffolding project: ${message}`,
131
- },
132
- ],
133
- isError: true,
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
  }