@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
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Styling Approach Detection
3
+ *
4
+ * Auto-detects the styling approach used in a project by scanning for
5
+ * config files, package.json dependencies, and file patterns.
6
+ */
7
+
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+
11
+ export type StylingApproach =
12
+ | 'tailwind'
13
+ | 'css-modules'
14
+ | 'styled-components'
15
+ | 'emotion'
16
+ | 'scss'
17
+ | 'vanilla-extract'
18
+ | 'panda-css'
19
+ | 'uno-css'
20
+ | 'stylex'
21
+ | 'css-in-js'
22
+ | 'vanilla-css';
23
+
24
+ export interface StylingInfo {
25
+ approach: StylingApproach;
26
+ confidence: 'high' | 'medium' | 'low';
27
+ detectedFrom: string;
28
+ fileExtension: string;
29
+ importStatement: string;
30
+ usage: string;
31
+ }
32
+
33
+ interface PackageJson {
34
+ dependencies?: Record<string, string>;
35
+ devDependencies?: Record<string, string>;
36
+ }
37
+
38
+ /**
39
+ * Default styling info when nothing is detected
40
+ */
41
+ const DEFAULT_STYLING: StylingInfo = {
42
+ approach: 'css-modules',
43
+ confidence: 'low',
44
+ detectedFrom: 'default fallback',
45
+ fileExtension: '.module.css',
46
+ importStatement: "import styles from './Component.module.css';",
47
+ usage: 'Use className={styles.className} pattern',
48
+ };
49
+
50
+ /**
51
+ * Styling approach configurations
52
+ */
53
+ const STYLING_CONFIGS: Record<StylingApproach, Omit<StylingInfo, 'detectedFrom' | 'confidence'>> = {
54
+ tailwind: {
55
+ approach: 'tailwind',
56
+ fileExtension: '',
57
+ importStatement: '',
58
+ usage: 'Use Tailwind utility classes directly in className',
59
+ },
60
+ 'css-modules': {
61
+ approach: 'css-modules',
62
+ fileExtension: '.module.css',
63
+ importStatement: "import styles from './Component.module.css';",
64
+ usage: 'Use className={styles.className} pattern',
65
+ },
66
+ 'styled-components': {
67
+ approach: 'styled-components',
68
+ fileExtension: '.tsx',
69
+ importStatement: "import styled from 'styled-components';",
70
+ usage: 'Use const StyledDiv = styled.div`...`; pattern',
71
+ },
72
+ emotion: {
73
+ approach: 'emotion',
74
+ fileExtension: '.tsx',
75
+ importStatement: "import { css } from '@emotion/react';\nimport styled from '@emotion/styled';",
76
+ usage: 'Use css prop or styled.div pattern',
77
+ },
78
+ scss: {
79
+ approach: 'scss',
80
+ fileExtension: '.module.scss',
81
+ importStatement: "import styles from './Component.module.scss';",
82
+ usage: 'Use className={styles.className} with SCSS features',
83
+ },
84
+ 'vanilla-extract': {
85
+ approach: 'vanilla-extract',
86
+ fileExtension: '.css.ts',
87
+ importStatement: "import { style } from '@vanilla-extract/css';",
88
+ usage: 'Define styles in .css.ts file, import and use in component',
89
+ },
90
+ 'panda-css': {
91
+ approach: 'panda-css',
92
+ fileExtension: '',
93
+ importStatement: "import { css } from '../styled-system/css';",
94
+ usage: 'Use css() function from generated styled-system',
95
+ },
96
+ 'uno-css': {
97
+ approach: 'uno-css',
98
+ fileExtension: '',
99
+ importStatement: "import 'virtual:uno.css';",
100
+ usage: 'Use utility classes similar to Tailwind',
101
+ },
102
+ stylex: {
103
+ approach: 'stylex',
104
+ fileExtension: '.stylex.ts',
105
+ importStatement: "import * as stylex from '@stylexjs/stylex';",
106
+ usage: 'Use stylex.create() and stylex.props()',
107
+ },
108
+ 'css-in-js': {
109
+ approach: 'css-in-js',
110
+ fileExtension: '.tsx',
111
+ importStatement: '',
112
+ usage: 'Define styles as objects in component file',
113
+ },
114
+ 'vanilla-css': {
115
+ approach: 'vanilla-css',
116
+ fileExtension: '.css',
117
+ importStatement: "import './Component.css';",
118
+ usage: 'Use className with plain CSS selectors',
119
+ },
120
+ };
121
+
122
+ /**
123
+ * Check if a file exists in the project root
124
+ */
125
+ function fileExists(projectRoot: string, ...filenames: string[]): string | null {
126
+ for (const filename of filenames) {
127
+ const fullPath = path.join(projectRoot, filename);
128
+ if (fs.existsSync(fullPath)) {
129
+ return filename;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * Read and parse package.json
137
+ */
138
+ function readPackageJson(projectRoot: string): PackageJson | null {
139
+ try {
140
+ const pkgPath = path.join(projectRoot, 'package.json');
141
+ if (!fs.existsSync(pkgPath)) return null;
142
+ const content = fs.readFileSync(pkgPath, 'utf-8');
143
+ return JSON.parse(content) as PackageJson;
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Check if a dependency exists in package.json
151
+ */
152
+ function hasDependency(pkg: PackageJson | null, ...deps: string[]): string | null {
153
+ if (!pkg) return null;
154
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
155
+ for (const dep of deps) {
156
+ if (allDeps[dep]) return dep;
157
+ }
158
+ return null;
159
+ }
160
+
161
+ /**
162
+ * Check for file patterns in the project
163
+ */
164
+ function hasFilePattern(projectRoot: string, pattern: RegExp, maxDepth = 3): boolean {
165
+ function scan(dir: string, depth: number): boolean {
166
+ if (depth > maxDepth) return false;
167
+ try {
168
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
169
+ for (const entry of entries) {
170
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
171
+
172
+ if (entry.isFile() && pattern.test(entry.name)) {
173
+ return true;
174
+ }
175
+ if (entry.isDirectory()) {
176
+ if (scan(path.join(dir, entry.name), depth + 1)) return true;
177
+ }
178
+ }
179
+ } catch {
180
+ // Ignore read errors
181
+ }
182
+ return false;
183
+ }
184
+ return scan(projectRoot, 0);
185
+ }
186
+
187
+ /**
188
+ * Detect styling approach from project files
189
+ */
190
+ export function detectStylingApproach(projectRoot: string): StylingInfo {
191
+ const pkg = readPackageJson(projectRoot);
192
+
193
+ // 1. Check for Tailwind (highest priority - very popular)
194
+ const tailwindConfig = fileExists(
195
+ projectRoot,
196
+ 'tailwind.config.js',
197
+ 'tailwind.config.ts',
198
+ 'tailwind.config.mjs',
199
+ 'tailwind.config.cjs'
200
+ );
201
+ if (tailwindConfig) {
202
+ return {
203
+ ...STYLING_CONFIGS['tailwind'],
204
+ confidence: 'high',
205
+ detectedFrom: tailwindConfig,
206
+ };
207
+ }
208
+ if (hasDependency(pkg, 'tailwindcss')) {
209
+ return {
210
+ ...STYLING_CONFIGS['tailwind'],
211
+ confidence: 'high',
212
+ detectedFrom: 'package.json: tailwindcss',
213
+ };
214
+ }
215
+
216
+ // 2. Check for Panda CSS
217
+ const pandaConfig = fileExists(
218
+ projectRoot,
219
+ 'panda.config.ts',
220
+ 'panda.config.js',
221
+ 'panda.config.mjs'
222
+ );
223
+ if (pandaConfig) {
224
+ return {
225
+ ...STYLING_CONFIGS['panda-css'],
226
+ confidence: 'high',
227
+ detectedFrom: pandaConfig,
228
+ };
229
+ }
230
+ if (hasDependency(pkg, '@pandacss/dev')) {
231
+ return {
232
+ ...STYLING_CONFIGS['panda-css'],
233
+ confidence: 'high',
234
+ detectedFrom: 'package.json: @pandacss/dev',
235
+ };
236
+ }
237
+
238
+ // 3. Check for UnoCSS
239
+ const unoConfig = fileExists(projectRoot, 'uno.config.ts', 'uno.config.js', 'unocss.config.ts');
240
+ if (unoConfig) {
241
+ return {
242
+ ...STYLING_CONFIGS['uno-css'],
243
+ confidence: 'high',
244
+ detectedFrom: unoConfig,
245
+ };
246
+ }
247
+ if (hasDependency(pkg, 'unocss')) {
248
+ return {
249
+ ...STYLING_CONFIGS['uno-css'],
250
+ confidence: 'high',
251
+ detectedFrom: 'package.json: unocss',
252
+ };
253
+ }
254
+
255
+ // 4. Check for Vanilla Extract
256
+ if (hasDependency(pkg, '@vanilla-extract/css')) {
257
+ return {
258
+ ...STYLING_CONFIGS['vanilla-extract'],
259
+ confidence: 'high',
260
+ detectedFrom: 'package.json: @vanilla-extract/css',
261
+ };
262
+ }
263
+ if (hasFilePattern(projectRoot, /\.css\.ts$/)) {
264
+ return {
265
+ ...STYLING_CONFIGS['vanilla-extract'],
266
+ confidence: 'medium',
267
+ detectedFrom: 'file pattern: *.css.ts',
268
+ };
269
+ }
270
+
271
+ // 5. Check for StyleX
272
+ if (hasDependency(pkg, '@stylexjs/stylex')) {
273
+ return {
274
+ ...STYLING_CONFIGS['stylex'],
275
+ confidence: 'high',
276
+ detectedFrom: 'package.json: @stylexjs/stylex',
277
+ };
278
+ }
279
+
280
+ // 6. Check for styled-components
281
+ if (hasDependency(pkg, 'styled-components')) {
282
+ return {
283
+ ...STYLING_CONFIGS['styled-components'],
284
+ confidence: 'high',
285
+ detectedFrom: 'package.json: styled-components',
286
+ };
287
+ }
288
+ if (hasFilePattern(projectRoot, /\.styled\.(ts|tsx)$/)) {
289
+ return {
290
+ ...STYLING_CONFIGS['styled-components'],
291
+ confidence: 'medium',
292
+ detectedFrom: 'file pattern: *.styled.tsx',
293
+ };
294
+ }
295
+
296
+ // 7. Check for Emotion
297
+ if (hasDependency(pkg, '@emotion/react', '@emotion/styled')) {
298
+ return {
299
+ ...STYLING_CONFIGS['emotion'],
300
+ confidence: 'high',
301
+ detectedFrom: 'package.json: @emotion/*',
302
+ };
303
+ }
304
+
305
+ // 8. Check for SCSS/SASS
306
+ if (hasDependency(pkg, 'sass', 'node-sass')) {
307
+ // Check for CSS Modules with SCSS
308
+ if (hasFilePattern(projectRoot, /\.module\.scss$/)) {
309
+ return {
310
+ ...STYLING_CONFIGS['scss'],
311
+ confidence: 'high',
312
+ detectedFrom: 'file pattern: *.module.scss',
313
+ };
314
+ }
315
+ return {
316
+ ...STYLING_CONFIGS['scss'],
317
+ confidence: 'medium',
318
+ detectedFrom: 'package.json: sass',
319
+ };
320
+ }
321
+
322
+ // 9. Check for CSS Modules (file pattern)
323
+ if (hasFilePattern(projectRoot, /\.module\.css$/)) {
324
+ return {
325
+ ...STYLING_CONFIGS['css-modules'],
326
+ confidence: 'high',
327
+ detectedFrom: 'file pattern: *.module.css',
328
+ };
329
+ }
330
+
331
+ // 10. Check for Next.js (often uses CSS Modules by default)
332
+ const nextConfig = fileExists(projectRoot, 'next.config.js', 'next.config.ts', 'next.config.mjs');
333
+ if (nextConfig) {
334
+ return {
335
+ ...STYLING_CONFIGS['css-modules'],
336
+ confidence: 'medium',
337
+ detectedFrom: `${nextConfig} (Next.js default)`,
338
+ };
339
+ }
340
+
341
+ // 11. Default to CSS Modules (modern safe default)
342
+ return DEFAULT_STYLING;
343
+ }
344
+
345
+ /**
346
+ * Get styling instructions for the prompt
347
+ */
348
+ export function getStylingInstructions(info: StylingInfo): string {
349
+ const lines = [
350
+ `## Detected Styling Approach: ${info.approach.toUpperCase()}`,
351
+ `Confidence: ${info.confidence}`,
352
+ `Detected from: ${info.detectedFrom}`,
353
+ '',
354
+ ];
355
+
356
+ switch (info.approach) {
357
+ case 'tailwind':
358
+ lines.push(
359
+ '### Tailwind CSS Instructions',
360
+ '- Use Tailwind utility classes directly in className',
361
+ '- NO separate CSS file needed',
362
+ '- Use @apply in global.css if needed for complex reusable styles',
363
+ '- Example: className="flex items-center justify-between p-4 bg-gradient-to-r from-purple-500 to-pink-500"',
364
+ '- Use tailwind.config variants for animations'
365
+ );
366
+ break;
367
+
368
+ case 'css-modules':
369
+ lines.push(
370
+ '### CSS Modules Instructions',
371
+ `- Create separate ${info.fileExtension} file`,
372
+ `- Import: ${info.importStatement}`,
373
+ '- Use: className={styles.container}',
374
+ '- CSS classes are locally scoped by default',
375
+ '- Use :global() for global styles if needed'
376
+ );
377
+ break;
378
+
379
+ case 'styled-components':
380
+ lines.push(
381
+ '### styled-components Instructions',
382
+ `- Import: ${info.importStatement}`,
383
+ '- Define styled components: const Button = styled.button`...`;',
384
+ '- Use props: ${props => props.primary && "background: blue;"}',
385
+ '- NO separate CSS file needed - styles in component file'
386
+ );
387
+ break;
388
+
389
+ case 'emotion':
390
+ lines.push(
391
+ '### Emotion Instructions',
392
+ `- Import: ${info.importStatement}`,
393
+ '- Use css prop: <div css={css`color: hotpink;`}>',
394
+ '- Or styled API: const Button = styled.button`...`;',
395
+ '- NO separate CSS file needed'
396
+ );
397
+ break;
398
+
399
+ case 'scss':
400
+ lines.push(
401
+ '### SCSS/SASS Instructions',
402
+ `- Create separate ${info.fileExtension} file`,
403
+ `- Import: ${info.importStatement}`,
404
+ '- Use: className={styles.container}',
405
+ '- Can use SCSS features: nesting, variables, mixins'
406
+ );
407
+ break;
408
+
409
+ case 'vanilla-extract':
410
+ lines.push(
411
+ '### Vanilla Extract Instructions',
412
+ `- Create ${info.fileExtension} file for styles`,
413
+ `- Import: ${info.importStatement}`,
414
+ '- Define: export const container = style({ display: "flex" });',
415
+ '- Use: className={container}',
416
+ '- Type-safe CSS-in-JS with zero runtime'
417
+ );
418
+ break;
419
+
420
+ case 'panda-css':
421
+ lines.push(
422
+ '### Panda CSS Instructions',
423
+ `- Import: ${info.importStatement}`,
424
+ '- Use: className={css({ display: "flex", padding: "4" })}',
425
+ '- Utility-first like Tailwind but with type safety',
426
+ '- NO separate CSS file needed'
427
+ );
428
+ break;
429
+
430
+ case 'uno-css':
431
+ lines.push(
432
+ '### UnoCSS Instructions',
433
+ '- Use utility classes similar to Tailwind',
434
+ '- className="flex items-center p-4"',
435
+ '- NO separate CSS file needed',
436
+ '- Supports attributify mode if configured'
437
+ );
438
+ break;
439
+
440
+ case 'stylex':
441
+ lines.push(
442
+ '### StyleX Instructions',
443
+ `- Import: ${info.importStatement}`,
444
+ '- Define: const styles = stylex.create({ container: { display: "flex" } });',
445
+ '- Use: {...stylex.props(styles.container)}',
446
+ '- Type-safe, optimized by Meta'
447
+ );
448
+ break;
449
+
450
+ default:
451
+ lines.push(
452
+ '### Vanilla CSS Instructions',
453
+ `- Create separate ${info.fileExtension} file`,
454
+ `- Import: ${info.importStatement}`,
455
+ '- Use className="container" with plain CSS selectors'
456
+ );
457
+ }
458
+
459
+ return lines.join('\n');
460
+ }
@@ -91,24 +91,24 @@ export const DESIGN_PRINCIPLES_COMPACT = `
91
91
  * Font blacklist for explicit filtering
92
92
  */
93
93
  export const BANNED_FONTS = [
94
- 'Inter',
95
- 'Roboto',
96
- 'Arial',
97
- 'Open Sans',
98
- 'Lato',
99
- 'Helvetica',
100
- 'system-ui',
101
- 'sans-serif',
102
- '-apple-system',
103
- 'BlinkMacSystemFont',
94
+ 'Inter',
95
+ 'Roboto',
96
+ 'Arial',
97
+ 'Open Sans',
98
+ 'Lato',
99
+ 'Helvetica',
100
+ 'system-ui',
101
+ 'sans-serif',
102
+ '-apple-system',
103
+ 'BlinkMacSystemFont',
104
104
  ];
105
105
 
106
106
  /**
107
107
  * Suggested distinctive fonts by category
108
108
  */
109
109
  export const SUGGESTED_FONTS = {
110
- display: ['Bricolage Grotesque', 'Playfair Display', 'Outfit', 'Space Grotesk'],
111
- body: ['Plus Jakarta Sans', 'Source Sans 3', 'IBM Plex Sans', 'Crimson Pro'],
112
- code: ['JetBrains Mono', 'Fira Code', 'IBM Plex Mono', 'Cascadia Code'],
113
- editorial: ['Newsreader', 'Playfair Display', 'Cormorant Garamond', 'Libre Baskerville'],
110
+ display: ['Bricolage Grotesque', 'Playfair Display', 'Outfit', 'Space Grotesk'],
111
+ body: ['Plus Jakarta Sans', 'Source Sans 3', 'IBM Plex Sans', 'Crimson Pro'],
112
+ code: ['JetBrains Mono', 'Fira Code', 'IBM Plex Mono', 'Cascadia Code'],
113
+ editorial: ['Newsreader', 'Playfair Display', 'Cormorant Garamond', 'Libre Baskerville'],
114
114
  };
@@ -9,86 +9,83 @@ import { GoogleGenerativeAI } from '@google/generative-ai';
9
9
  import { Config } from '../config/index.js';
10
10
 
11
11
  export type GeminiUserContent =
12
- | string
13
- | Array<
14
- | { text: string }
15
- | { inlineData: { mimeType: string; data: string } }
16
- >;
12
+ | string
13
+ | Array<{ text: string } | { inlineData: { mimeType: string; data: string } }>;
17
14
 
18
15
  // Cache the client instance
19
16
  let genAI: GoogleGenerativeAI | null = null;
20
17
 
21
18
  export interface GenerateOptions {
22
- toolName?: string;
19
+ toolName?: string;
23
20
  }
24
21
 
25
22
  /**
26
23
  * Get or create the Gemini client
27
24
  */
28
25
  function getClient(config: Config): GoogleGenerativeAI {
29
- if (!config.apiKey) {
30
- throw new Error('Gemini API key not configured. Set GEMINI_API_KEY environment variable.');
31
- }
26
+ if (!config.apiKey) {
27
+ throw new Error('Gemini API key not configured. Set GEMINI_API_KEY environment variable.');
28
+ }
32
29
 
33
- if (!genAI) {
34
- genAI = new GoogleGenerativeAI(config.apiKey);
35
- }
30
+ if (!genAI) {
31
+ genAI = new GoogleGenerativeAI(config.apiKey);
32
+ }
36
33
 
37
- return genAI;
34
+ return genAI;
38
35
  }
39
36
 
40
37
  /**
41
38
  * Generate content using Gemini
42
39
  */
43
40
  export async function generateWithGemini(
44
- config: Config,
45
- systemPrompt: string,
46
- userPrompt: GeminiUserContent,
47
- options?: GenerateOptions
41
+ config: Config,
42
+ systemPrompt: string,
43
+ userPrompt: GeminiUserContent,
44
+ options?: GenerateOptions
48
45
  ): Promise<string> {
49
- // If remote mode, delegate to remote client
50
- if (config.mode === 'remote') {
51
- const { generateWithRemote } = await import('./remote-client.js');
52
- return generateWithRemote(config, systemPrompt, userPrompt, options?.toolName);
53
- }
54
-
55
- // If local mode is configured to use an OpenAI-compatible proxy (e.g. LiteLLM)
56
- if (config.localProvider === 'litellm') {
57
- const { generateWithLiteLLM } = await import('./litellm-client.js');
58
- return generateWithLiteLLM(config, systemPrompt, userPrompt, options?.toolName);
59
- }
60
-
61
- const client = getClient(config);
62
-
63
- // Use model from config (default: gemini-2.5-flash-lite)
64
- const model = client.getGenerativeModel({
65
- model: config.model,
66
- systemInstruction: systemPrompt,
67
- });
68
-
69
- const result = await model.generateContent(userPrompt as any);
70
- const response = result.response;
71
- const text = response.text();
72
-
73
- if (config.debug) {
74
- console.error('[gemini] Tool:', options?.toolName || '(unknown)');
75
- console.error('[gemini] Response length:', text.length);
76
- console.error('[gemini] Usage:', response.usageMetadata);
77
- }
78
-
79
- return text;
46
+ // If remote mode, delegate to remote client
47
+ if (config.mode === 'remote') {
48
+ const { generateWithRemote } = await import('./remote-client.js');
49
+ return generateWithRemote(config, systemPrompt, userPrompt, options?.toolName);
50
+ }
51
+
52
+ // If local mode is configured to use an OpenAI-compatible proxy (e.g. LiteLLM)
53
+ if (config.localProvider === 'litellm') {
54
+ const { generateWithLiteLLM } = await import('./litellm-client.js');
55
+ return generateWithLiteLLM(config, systemPrompt, userPrompt, options?.toolName);
56
+ }
57
+
58
+ const client = getClient(config);
59
+
60
+ // Use model from config (default: gemini-2.5-flash-lite)
61
+ const model = client.getGenerativeModel({
62
+ model: config.model,
63
+ systemInstruction: systemPrompt,
64
+ });
65
+
66
+ const result = await model.generateContent(userPrompt);
67
+ const response = result.response;
68
+ const text = response.text();
69
+
70
+ if (config.debug) {
71
+ console.error('[gemini] Tool:', options?.toolName || '(unknown)');
72
+ console.error('[gemini] Response length:', text.length);
73
+ console.error('[gemini] Usage:', response.usageMetadata);
74
+ }
75
+
76
+ return text;
80
77
  }
81
78
 
82
79
  /**
83
80
  * Count tokens for a given text (useful for quota tracking)
84
81
  */
85
82
  export async function countTokens(config: Config, text: string): Promise<number> {
86
- if (config.localProvider === 'litellm') {
87
- throw new Error('countTokens is not supported when localProvider=litellm');
88
- }
89
- const client = getClient(config);
90
- const model = client.getGenerativeModel({ model: config.model });
91
-
92
- const result = await model.countTokens(text);
93
- return result.totalTokens;
83
+ if (config.localProvider === 'litellm') {
84
+ throw new Error('countTokens is not supported when localProvider=litellm');
85
+ }
86
+ const client = getClient(config);
87
+ const model = client.getGenerativeModel({ model: config.model });
88
+
89
+ const result = await model.countTokens(text);
90
+ return result.totalTokens;
94
91
  }