sonance-brand-mcp 1.2.5 → 1.3.2
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/assets/api/sonance-analyze/route.ts +1116 -0
- package/dist/assets/api/sonance-assets/route.ts +113 -0
- package/dist/assets/api/sonance-components/route.ts +41 -0
- package/dist/assets/api/sonance-inject-id/route.ts +363 -0
- package/dist/assets/api/sonance-save-logo/route.ts +426 -0
- package/dist/assets/api/sonance-theme/route.ts +106 -0
- package/dist/assets/brand-system.ts +1265 -0
- package/dist/assets/components/accordion.stories.tsx +26 -26
- package/dist/assets/components/accordion.tsx +3 -3
- package/dist/assets/components/alert-dialog.stories.tsx +142 -0
- package/dist/assets/components/alert-dialog.tsx +143 -0
- package/dist/assets/components/alert.stories.tsx +3 -3
- package/dist/assets/components/alert.tsx +4 -3
- package/dist/assets/components/aspect-ratio.stories.tsx +70 -0
- package/dist/assets/components/aspect-ratio.tsx +8 -0
- package/dist/assets/components/autocomplete.stories.tsx +9 -9
- package/dist/assets/components/autocomplete.tsx +3 -3
- package/dist/assets/components/avatar.stories.tsx +5 -5
- package/dist/assets/components/avatar.tsx +67 -23
- package/dist/assets/components/badge.stories.tsx +10 -10
- package/dist/assets/components/badge.tsx +3 -3
- package/dist/assets/components/breadcrumbs.stories.tsx +7 -7
- package/dist/assets/components/breadcrumbs.tsx +13 -8
- package/dist/assets/components/button.stories.tsx +74 -74
- package/dist/assets/components/button.tsx +2 -0
- package/dist/assets/components/calendar.stories.tsx +11 -11
- package/dist/assets/components/calendar.tsx +4 -4
- package/dist/assets/components/card.stories.tsx +22 -22
- package/dist/assets/components/card.tsx +7 -3
- package/dist/assets/components/carousel.stories.tsx +158 -0
- package/dist/assets/components/carousel.tsx +264 -0
- package/dist/assets/components/chart.stories.tsx +376 -0
- package/dist/assets/components/chart.tsx +384 -0
- package/dist/assets/components/checkbox-group.stories.tsx +6 -6
- package/dist/assets/components/checkbox-group.tsx +3 -3
- package/dist/assets/components/checkbox.stories.tsx +23 -20
- package/dist/assets/components/checkbox.tsx +13 -6
- package/dist/assets/components/code.stories.tsx +24 -24
- package/dist/assets/components/code.tsx +22 -27
- package/dist/assets/components/collapsible.stories.tsx +128 -0
- package/dist/assets/components/collapsible.tsx +10 -0
- package/dist/assets/components/command.stories.tsx +183 -0
- package/dist/assets/components/command.tsx +171 -0
- package/dist/assets/components/context-menu.stories.tsx +159 -0
- package/dist/assets/components/context-menu.tsx +214 -0
- package/dist/assets/components/date-input.stories.tsx +9 -9
- package/dist/assets/components/date-input.tsx +2 -2
- package/dist/assets/components/date-picker.stories.tsx +9 -9
- package/dist/assets/components/date-picker.tsx +3 -3
- package/dist/assets/components/date-range-picker.stories.tsx +12 -12
- package/dist/assets/components/date-range-picker.tsx +3 -3
- package/dist/assets/components/dialog.stories.tsx +40 -40
- package/dist/assets/components/dialog.tsx +8 -12
- package/dist/assets/components/divider.stories.tsx +30 -30
- package/dist/assets/components/divider.tsx +34 -35
- package/dist/assets/components/drawer.stories.tsx +32 -31
- package/dist/assets/components/drawer.tsx +7 -6
- package/dist/assets/components/dropdown-menu.tsx +213 -0
- package/dist/assets/components/dropdown.stories.tsx +12 -12
- package/dist/assets/components/dropdown.tsx +5 -5
- package/dist/assets/components/form.stories.tsx +30 -29
- package/dist/assets/components/form.tsx +5 -5
- package/dist/assets/components/hover-card.stories.tsx +115 -0
- package/dist/assets/components/hover-card.tsx +35 -0
- package/dist/assets/components/image.stories.tsx +48 -25
- package/dist/assets/components/image.tsx +8 -5
- package/dist/assets/components/input-otp.stories.tsx +15 -15
- package/dist/assets/components/input-otp.tsx +5 -5
- package/dist/assets/components/input.stories.tsx +30 -25
- package/dist/assets/components/input.tsx +7 -4
- package/dist/assets/components/kbd.stories.tsx +34 -34
- package/dist/assets/components/kbd.tsx +9 -9
- package/dist/assets/components/link.stories.tsx +36 -36
- package/dist/assets/components/link.tsx +4 -0
- package/dist/assets/components/listbox.stories.tsx +5 -5
- package/dist/assets/components/listbox.tsx +4 -4
- package/dist/assets/components/menubar.stories.tsx +208 -0
- package/dist/assets/components/menubar.tsx +247 -0
- package/dist/assets/components/navbar.stories.tsx +24 -24
- package/dist/assets/components/navbar.tsx +8 -14
- package/dist/assets/components/navigation-menu.stories.tsx +239 -0
- package/dist/assets/components/navigation-menu.tsx +135 -0
- package/dist/assets/components/number-input.stories.tsx +11 -11
- package/dist/assets/components/number-input.tsx +3 -3
- package/dist/assets/components/pagination.stories.tsx +13 -13
- package/dist/assets/components/pagination.tsx +6 -6
- package/dist/assets/components/popover.stories.tsx +35 -35
- package/dist/assets/components/popover.tsx +98 -15
- package/dist/assets/components/progress.stories.tsx +5 -5
- package/dist/assets/components/progress.tsx +5 -5
- package/dist/assets/components/radio-group.stories.tsx +7 -7
- package/dist/assets/components/radio-group.tsx +3 -3
- package/dist/assets/components/range-calendar.stories.tsx +18 -18
- package/dist/assets/components/range-calendar.tsx +3 -3
- package/dist/assets/components/resizable.stories.tsx +197 -0
- package/dist/assets/components/resizable.tsx +47 -0
- package/dist/assets/components/scroll-area.stories.tsx +123 -0
- package/dist/assets/components/scroll-area.tsx +48 -0
- package/dist/assets/components/scroll-shadow.stories.tsx +17 -17
- package/dist/assets/components/scroll-shadow.tsx +31 -9
- package/dist/assets/components/select.stories.tsx +20 -19
- package/dist/assets/components/select.tsx +10 -6
- package/dist/assets/components/separator.tsx +32 -0
- package/dist/assets/components/sheet.tsx +137 -0
- package/dist/assets/components/sidebar.stories.tsx +351 -0
- package/dist/assets/components/sidebar.tsx +757 -0
- package/dist/assets/components/skeleton.stories.tsx +3 -3
- package/dist/assets/components/skeleton.tsx +2 -2
- package/dist/assets/components/slider.stories.tsx +6 -6
- package/dist/assets/components/slider.tsx +3 -3
- package/dist/assets/components/spacer.stories.tsx +11 -11
- package/dist/assets/components/spacer.tsx +2 -2
- package/dist/assets/components/spinner.stories.tsx +8 -8
- package/dist/assets/components/spinner.tsx +5 -5
- package/dist/assets/components/switch.stories.tsx +24 -20
- package/dist/assets/components/switch.tsx +14 -6
- package/dist/assets/components/table.stories.tsx +7 -7
- package/dist/assets/components/table.tsx +8 -8
- package/dist/assets/components/tabs.stories.tsx +37 -37
- package/dist/assets/components/tabs.tsx +3 -3
- package/dist/assets/components/textarea.stories.tsx +13 -12
- package/dist/assets/components/textarea.tsx +3 -3
- package/dist/assets/components/theme-toggle.stories.tsx +31 -30
- package/dist/assets/components/theme-toggle.tsx +2 -2
- package/dist/assets/components/time-input.stories.tsx +16 -16
- package/dist/assets/components/time-input.tsx +2 -2
- package/dist/assets/components/toast.stories.tsx +8 -5
- package/dist/assets/components/toast.tsx +6 -6
- package/dist/assets/components/toggle-group.stories.tsx +153 -0
- package/dist/assets/components/toggle-group.tsx +61 -0
- package/dist/assets/components/toggle.stories.tsx +77 -0
- package/dist/assets/components/toggle.tsx +46 -0
- package/dist/assets/components/tooltip.stories.tsx +49 -27
- package/dist/assets/components/tooltip.tsx +23 -90
- package/dist/assets/components/user.stories.tsx +23 -23
- package/dist/assets/components/user.tsx +7 -4
- package/dist/assets/dev-tools/SonanceDevTools.tsx +4201 -0
- package/dist/assets/dev-tools/index.ts +10 -0
- package/dist/assets/globals.css +39 -0
- package/dist/assets/logos/40th-anniversary/Sonance_40_Logo_CMYK_BEAM_BLUE_40_AND_BEAM_DARK.png +0 -0
- package/dist/assets/logos/Sonance logo dark mode.png +0 -0
- package/dist/assets/logos/Sonance logo light mode.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png +0 -0
- package/dist/assets/logos/blaze/BlazeBySonance_Logo_Lockup_White_RGB_05162025.png +0 -0
- package/dist/assets/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Black_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_LtGray_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_LtGray_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Polished_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_Reverse_CMYK.png +0 -0
- package/dist/assets/logos/james/James_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/james/James_Logo_White_CMYK.png +0 -0
- package/dist/assets/logos/life-is-better/Sonance_LifeisBetter_Dark_RGB.png +0 -0
- package/dist/assets/logos/life-is-better/Sonance_LifeisBetter_Light_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_2C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/my-sonance/My.Sonance_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Dark_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Light_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_2C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Black_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Grayscale_RGB.png +0 -0
- package/dist/assets/logos/sonance/Sonance_Logo_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-academy/SonanceAcademy_Logo_Dark_CMYK.png +0 -0
- package/dist/assets/logos/sonance-academy/SonanceAcademy_Logo_Light_CMYK.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Dark_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Light_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_3C_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Black_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Grayscale_RGB.png +0 -0
- package/dist/assets/logos/sonance-iport/Sonance_IPORT_LockUp_Reverse_RGB.png +0 -0
- package/dist/assets/logos/sonance-james/Sonance_James_Lockup_Dark.png +0 -0
- package/dist/assets/logos/sonance-james/Sonance_James_Lockup_Light.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Dark.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_LockupStacked_Light.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png +0 -0
- package/dist/assets/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png +0 -0
- package/dist/assets/logos/trufig/TrufigLogo_Black.png +0 -0
- package/dist/assets/logos/trufig/TrufigLogo_Light.png +0 -0
- package/dist/assets/logos/trufig/TrufigWatermark_Black.png +0 -0
- package/dist/assets/logos/trufig/TrufigWatermark_Light.png +0 -0
- package/dist/assets/styles/brand-overrides.css +37 -0
- package/dist/index.js +2055 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -274,6 +274,222 @@ const BUNDLED_ASSETS = path.join(__dirname, "assets");
|
|
|
274
274
|
const IS_BUNDLED = fs.existsSync(BUNDLED_ASSETS);
|
|
275
275
|
// Development paths (when running from source)
|
|
276
276
|
const DEV_PROJECT_ROOT = path.resolve(__dirname, "../..");
|
|
277
|
+
/**
|
|
278
|
+
* Run the installer for DevTools Plugin (copies files to user project)
|
|
279
|
+
*/
|
|
280
|
+
function runDevToolsInstaller() {
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log(" ┌─────────────────────────────────────────────────┐");
|
|
283
|
+
console.log(" │ │");
|
|
284
|
+
console.log(" │ 🎨 Sonance DevTools Plugin - Installer │");
|
|
285
|
+
console.log(" │ │");
|
|
286
|
+
console.log(" └─────────────────────────────────────────────────┘");
|
|
287
|
+
console.log("");
|
|
288
|
+
const targetDir = process.cwd();
|
|
289
|
+
// Paths
|
|
290
|
+
const libDir = path.join(targetDir, "src/lib");
|
|
291
|
+
const devToolsDir = path.join(targetDir, "src/components/dev-tools");
|
|
292
|
+
const stylesDir = path.join(targetDir, "src/styles");
|
|
293
|
+
const apiThemeDir = path.join(targetDir, "src/app/api/sonance-theme");
|
|
294
|
+
const apiComponentsDir = path.join(targetDir, "src/app/api/sonance-components");
|
|
295
|
+
const apiSaveLogoDir = path.join(targetDir, "src/app/api/sonance-save-logo");
|
|
296
|
+
const apiAssetsDir = path.join(targetDir, "src/app/api/sonance-assets");
|
|
297
|
+
const apiInjectIdDir = path.join(targetDir, "src/app/api/sonance-inject-id");
|
|
298
|
+
const apiAnalyzeDir = path.join(targetDir, "src/app/api/sonance-analyze");
|
|
299
|
+
const themeDir = path.join(targetDir, "src/theme");
|
|
300
|
+
// Source resolution
|
|
301
|
+
let sourceBrandSystem;
|
|
302
|
+
let sourceDevTools;
|
|
303
|
+
let sourceBrandOverridesCss;
|
|
304
|
+
let sourceApiTheme;
|
|
305
|
+
let sourceApiComponents;
|
|
306
|
+
let sourceApiSaveLogo;
|
|
307
|
+
let sourceApiAssets;
|
|
308
|
+
let sourceApiInjectId;
|
|
309
|
+
let sourceApiAnalyze;
|
|
310
|
+
if (IS_BUNDLED) {
|
|
311
|
+
sourceBrandSystem = path.join(BUNDLED_ASSETS, "brand-system.ts");
|
|
312
|
+
sourceDevTools = path.join(BUNDLED_ASSETS, "dev-tools");
|
|
313
|
+
sourceBrandOverridesCss = path.join(BUNDLED_ASSETS, "styles/brand-overrides.css");
|
|
314
|
+
sourceApiTheme = path.join(BUNDLED_ASSETS, "api/sonance-theme/route.ts");
|
|
315
|
+
sourceApiComponents = path.join(BUNDLED_ASSETS, "api/sonance-components/route.ts");
|
|
316
|
+
sourceApiSaveLogo = path.join(BUNDLED_ASSETS, "api/sonance-save-logo/route.ts");
|
|
317
|
+
sourceApiAssets = path.join(BUNDLED_ASSETS, "api/sonance-assets/route.ts");
|
|
318
|
+
sourceApiInjectId = path.join(BUNDLED_ASSETS, "api/sonance-inject-id/route.ts");
|
|
319
|
+
sourceApiAnalyze = path.join(BUNDLED_ASSETS, "api/sonance-analyze/route.ts");
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
sourceBrandSystem = path.join(DEV_PROJECT_ROOT, "src/lib/brand-system.ts");
|
|
323
|
+
sourceDevTools = path.join(DEV_PROJECT_ROOT, "src/components/dev-tools");
|
|
324
|
+
sourceBrandOverridesCss = path.join(DEV_PROJECT_ROOT, "src/styles/brand-overrides.css");
|
|
325
|
+
sourceApiTheme = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-theme/route.ts");
|
|
326
|
+
sourceApiComponents = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-components/route.ts");
|
|
327
|
+
sourceApiSaveLogo = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-save-logo/route.ts");
|
|
328
|
+
sourceApiAssets = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-assets/route.ts");
|
|
329
|
+
sourceApiInjectId = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-inject-id/route.ts");
|
|
330
|
+
sourceApiAnalyze = path.join(DEV_PROJECT_ROOT, "src/app/api/sonance-analyze/route.ts");
|
|
331
|
+
}
|
|
332
|
+
// Verify sources exist
|
|
333
|
+
if (!fs.existsSync(sourceBrandSystem)) {
|
|
334
|
+
console.error(" ❌ Error: Could not find brand-system.ts source file.");
|
|
335
|
+
console.error(` Path: ${sourceBrandSystem}`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
if (!fs.existsSync(sourceDevTools)) {
|
|
339
|
+
console.error(" ❌ Error: Could not find dev-tools source directory.");
|
|
340
|
+
console.error(` Path: ${sourceDevTools}`);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
if (!fs.existsSync(sourceApiTheme)) {
|
|
344
|
+
console.error(" ❌ Error: Could not find sonance-theme API route source file.");
|
|
345
|
+
console.error(` Path: ${sourceApiTheme}`);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
if (!fs.existsSync(sourceApiComponents)) {
|
|
349
|
+
console.error(" ❌ Error: Could not find sonance-components API route source file.");
|
|
350
|
+
console.error(` Path: ${sourceApiComponents}`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
if (!fs.existsSync(sourceApiSaveLogo)) {
|
|
354
|
+
console.error(" ❌ Error: Could not find sonance-save-logo API route source file.");
|
|
355
|
+
console.error(` Path: ${sourceApiSaveLogo}`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
if (!fs.existsSync(sourceApiAssets)) {
|
|
359
|
+
console.error(" ❌ Error: Could not find sonance-assets API route source file.");
|
|
360
|
+
console.error(` Path: ${sourceApiAssets}`);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
if (!fs.existsSync(sourceApiInjectId)) {
|
|
364
|
+
console.error(" ❌ Error: Could not find sonance-inject-id API route source file.");
|
|
365
|
+
console.error(` Path: ${sourceApiInjectId}`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
if (!fs.existsSync(sourceApiAnalyze)) {
|
|
369
|
+
console.error(" ❌ Error: Could not find sonance-analyze API route source file.");
|
|
370
|
+
console.error(` Path: ${sourceApiAnalyze}`);
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
if (!fs.existsSync(sourceBrandOverridesCss)) {
|
|
374
|
+
console.error(" ❌ Error: Could not find brand-overrides.css source file.");
|
|
375
|
+
console.error(` Path: ${sourceBrandOverridesCss}`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
console.log(" 📂 Installing files...");
|
|
379
|
+
// 1. Install brand-system.ts
|
|
380
|
+
if (!fs.existsSync(libDir)) {
|
|
381
|
+
fs.mkdirSync(libDir, { recursive: true });
|
|
382
|
+
}
|
|
383
|
+
fs.copyFileSync(sourceBrandSystem, path.join(libDir, "brand-system.ts"));
|
|
384
|
+
console.log(" ✓ Created src/lib/brand-system.ts");
|
|
385
|
+
// 2. Install DevTools components
|
|
386
|
+
if (!fs.existsSync(devToolsDir)) {
|
|
387
|
+
fs.mkdirSync(devToolsDir, { recursive: true });
|
|
388
|
+
}
|
|
389
|
+
// Copy directory contents
|
|
390
|
+
const entries = fs.readdirSync(sourceDevTools, { withFileTypes: true });
|
|
391
|
+
for (const entry of entries) {
|
|
392
|
+
if (entry.isFile()) {
|
|
393
|
+
fs.copyFileSync(path.join(sourceDevTools, entry.name), path.join(devToolsDir, entry.name));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
console.log(" ✓ Created src/components/dev-tools/");
|
|
397
|
+
// 3. Install API route for saving theme
|
|
398
|
+
if (!fs.existsSync(apiThemeDir)) {
|
|
399
|
+
fs.mkdirSync(apiThemeDir, { recursive: true });
|
|
400
|
+
}
|
|
401
|
+
fs.copyFileSync(sourceApiTheme, path.join(apiThemeDir, "route.ts"));
|
|
402
|
+
console.log(" ✓ Created src/app/api/sonance-theme/route.ts");
|
|
403
|
+
// 4. Install API route for component detection
|
|
404
|
+
if (!fs.existsSync(apiComponentsDir)) {
|
|
405
|
+
fs.mkdirSync(apiComponentsDir, { recursive: true });
|
|
406
|
+
}
|
|
407
|
+
fs.copyFileSync(sourceApiComponents, path.join(apiComponentsDir, "route.ts"));
|
|
408
|
+
console.log(" ✓ Created src/app/api/sonance-components/route.ts");
|
|
409
|
+
// 5. Install API route for saving logo configuration
|
|
410
|
+
if (!fs.existsSync(apiSaveLogoDir)) {
|
|
411
|
+
fs.mkdirSync(apiSaveLogoDir, { recursive: true });
|
|
412
|
+
}
|
|
413
|
+
fs.copyFileSync(sourceApiSaveLogo, path.join(apiSaveLogoDir, "route.ts"));
|
|
414
|
+
console.log(" ✓ Created src/app/api/sonance-save-logo/route.ts");
|
|
415
|
+
// 7. Install API route for listing logo assets
|
|
416
|
+
if (!fs.existsSync(apiAssetsDir)) {
|
|
417
|
+
fs.mkdirSync(apiAssetsDir, { recursive: true });
|
|
418
|
+
}
|
|
419
|
+
fs.copyFileSync(sourceApiAssets, path.join(apiAssetsDir, "route.ts"));
|
|
420
|
+
console.log(" ✓ Created src/app/api/sonance-assets/route.ts");
|
|
421
|
+
// 8. Install API route for auto-fixing logo IDs
|
|
422
|
+
if (!fs.existsSync(apiInjectIdDir)) {
|
|
423
|
+
fs.mkdirSync(apiInjectIdDir, { recursive: true });
|
|
424
|
+
}
|
|
425
|
+
fs.copyFileSync(sourceApiInjectId, path.join(apiInjectIdDir, "route.ts"));
|
|
426
|
+
console.log(" ✓ Created src/app/api/sonance-inject-id/route.ts");
|
|
427
|
+
// 9. Install API route for project analysis
|
|
428
|
+
if (!fs.existsSync(apiAnalyzeDir)) {
|
|
429
|
+
fs.mkdirSync(apiAnalyzeDir, { recursive: true });
|
|
430
|
+
}
|
|
431
|
+
fs.copyFileSync(sourceApiAnalyze, path.join(apiAnalyzeDir, "route.ts"));
|
|
432
|
+
console.log(" ✓ Created src/app/api/sonance-analyze/route.ts");
|
|
433
|
+
// 11. Install brand-overrides.css for production logo sizing
|
|
434
|
+
if (!fs.existsSync(stylesDir)) {
|
|
435
|
+
fs.mkdirSync(stylesDir, { recursive: true });
|
|
436
|
+
}
|
|
437
|
+
fs.copyFileSync(sourceBrandOverridesCss, path.join(stylesDir, "brand-overrides.css"));
|
|
438
|
+
console.log(" ✓ Created src/styles/brand-overrides.css");
|
|
439
|
+
// 12. Create theme directory with initial files
|
|
440
|
+
if (!fs.existsSync(themeDir)) {
|
|
441
|
+
fs.mkdirSync(themeDir, { recursive: true });
|
|
442
|
+
// Create initial theme CSS
|
|
443
|
+
const initialCss = `/**
|
|
444
|
+
* Sonance Theme - Auto-generated by DevTools
|
|
445
|
+
* Do not edit manually - use the DevTools plugin to make changes.
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
:root {
|
|
449
|
+
/* Use DevTools to customize and apply your theme */
|
|
450
|
+
}
|
|
451
|
+
`;
|
|
452
|
+
fs.writeFileSync(path.join(themeDir, "sonance-theme.css"), initialCss, "utf-8");
|
|
453
|
+
// Create initial config
|
|
454
|
+
const initialConfig = {
|
|
455
|
+
baseColor: "#333F48",
|
|
456
|
+
accentColor: "#00A3E1",
|
|
457
|
+
radius: "sm",
|
|
458
|
+
headingWeight: 600,
|
|
459
|
+
bodyWeight: 400,
|
|
460
|
+
typographyScale: "default",
|
|
461
|
+
spacing: "default"
|
|
462
|
+
};
|
|
463
|
+
fs.writeFileSync(path.join(themeDir, "sonance-config.json"), JSON.stringify(initialConfig, null, 2), "utf-8");
|
|
464
|
+
console.log(" ✓ Created src/theme/ with initial files");
|
|
465
|
+
}
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log(" ✅ Sonance DevTools installed successfully!");
|
|
468
|
+
console.log("");
|
|
469
|
+
console.log(" ┌──────────────────────────────────────────────────────────────────┐");
|
|
470
|
+
console.log(" │ Next steps: │");
|
|
471
|
+
console.log(" │ │");
|
|
472
|
+
console.log(" │ 1. Add to your globals.css: │");
|
|
473
|
+
console.log(" │ @import \"../styles/brand-overrides.css\"; │");
|
|
474
|
+
console.log(" │ @import \"../theme/sonance-theme.css\"; │");
|
|
475
|
+
console.log(" │ │");
|
|
476
|
+
console.log(" │ 2. Add to your layout.tsx: │");
|
|
477
|
+
console.log(" │ import { SonanceDevTools } from '@/components/dev-tools'; │");
|
|
478
|
+
console.log(" │ │");
|
|
479
|
+
console.log(" │ 3. Add to your render function: │");
|
|
480
|
+
console.log(" │ {process.env.NODE_ENV === 'development' && <SonanceDevTools />}│");
|
|
481
|
+
console.log(" │ │");
|
|
482
|
+
console.log(" │ 4. Apply CSS variables to your logo components: │");
|
|
483
|
+
console.log(" │ style={{ transform: `scale(var(--sonance-logo-scale, 1))` }} │");
|
|
484
|
+
console.log(" │ │");
|
|
485
|
+
console.log(" └──────────────────────────────────────────────────────────────────┘");
|
|
486
|
+
console.log("");
|
|
487
|
+
}
|
|
488
|
+
// Check for install-devtools command
|
|
489
|
+
if (process.argv.includes("install-devtools") || process.argv.includes("--install-devtools")) {
|
|
490
|
+
runDevToolsInstaller();
|
|
491
|
+
process.exit(0);
|
|
492
|
+
}
|
|
277
493
|
// Resolve paths based on environment
|
|
278
494
|
function getAssetPath(assetType) {
|
|
279
495
|
if (IS_BUNDLED) {
|
|
@@ -306,6 +522,76 @@ function getAssetPath(assetType) {
|
|
|
306
522
|
}
|
|
307
523
|
}
|
|
308
524
|
}
|
|
525
|
+
// ============================================
|
|
526
|
+
// LOGO PATH RESOLUTION
|
|
527
|
+
// ============================================
|
|
528
|
+
/**
|
|
529
|
+
* Resolve a logo path to an absolute file path
|
|
530
|
+
* Handles both bundled and dev mode, with path sanitization
|
|
531
|
+
*/
|
|
532
|
+
function resolveLogoPath(logoPath) {
|
|
533
|
+
// Sanitize path to prevent directory traversal
|
|
534
|
+
const sanitized = logoPath
|
|
535
|
+
.replace(/^\/+/, '') // Remove leading slashes
|
|
536
|
+
.replace(/\.{2,}/g, '') // Remove .. sequences
|
|
537
|
+
.replace(/\/+/g, '/'); // Normalize multiple slashes
|
|
538
|
+
// Remove "logos/" prefix if present (manifest has /logos/xxx format)
|
|
539
|
+
const normalizedPath = sanitized.replace(/^logos\//, '');
|
|
540
|
+
if (IS_BUNDLED) {
|
|
541
|
+
// In bundled mode, logos are in dist/assets/logos/
|
|
542
|
+
const logosDir = path.join(BUNDLED_ASSETS, "logos");
|
|
543
|
+
return path.join(logosDir, normalizedPath);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// In dev mode, logos are in public/logos/
|
|
547
|
+
const logosDir = path.join(DEV_PROJECT_ROOT, "public/logos");
|
|
548
|
+
return path.join(logosDir, normalizedPath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Get MIME type for image file based on extension
|
|
553
|
+
*/
|
|
554
|
+
function getImageMimeType(filePath) {
|
|
555
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
556
|
+
switch (ext) {
|
|
557
|
+
case '.png': return 'image/png';
|
|
558
|
+
case '.jpg':
|
|
559
|
+
case '.jpeg': return 'image/jpeg';
|
|
560
|
+
case '.svg': return 'image/svg+xml';
|
|
561
|
+
case '.gif': return 'image/gif';
|
|
562
|
+
case '.webp': return 'image/webp';
|
|
563
|
+
default: return 'application/octet-stream';
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Embed a logo as base64 image content for MCP responses
|
|
568
|
+
* Returns an MCP-compatible image content block or null if logo not found
|
|
569
|
+
*/
|
|
570
|
+
async function embedLogo(logoPath) {
|
|
571
|
+
try {
|
|
572
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
573
|
+
if (!resolvedPath)
|
|
574
|
+
return null;
|
|
575
|
+
// Check if file exists
|
|
576
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
577
|
+
console.error(`Logo not found: ${resolvedPath}`);
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
// Read file as base64
|
|
581
|
+
const fileBuffer = fs.readFileSync(resolvedPath);
|
|
582
|
+
const base64Data = fileBuffer.toString('base64');
|
|
583
|
+
const mimeType = getImageMimeType(resolvedPath);
|
|
584
|
+
return {
|
|
585
|
+
type: "image",
|
|
586
|
+
data: base64Data,
|
|
587
|
+
mimeType,
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
console.error(`Error embedding logo: ${error}`);
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
309
595
|
/**
|
|
310
596
|
* Detect output type from user's request/prompt
|
|
311
597
|
*/
|
|
@@ -466,6 +752,610 @@ function getBrandColorsForDocsMulti(brand = "sonance") {
|
|
|
466
752
|
black: hexToFormats("#000000"),
|
|
467
753
|
};
|
|
468
754
|
}
|
|
755
|
+
// ============================================
|
|
756
|
+
// COMPONENT CATEGORIES
|
|
757
|
+
// ============================================
|
|
758
|
+
/**
|
|
759
|
+
* Component categories matching the sidebar navigation
|
|
760
|
+
* Used for category-based retrieval and feature-to-component mapping
|
|
761
|
+
*/
|
|
762
|
+
const COMPONENT_CATEGORIES = {
|
|
763
|
+
forms: [
|
|
764
|
+
"button", "input", "textarea", "checkbox", "checkbox-group", "radio-group",
|
|
765
|
+
"switch", "select", "slider", "number-input", "autocomplete", "listbox",
|
|
766
|
+
"form", "input-otp", "calendar", "date-input", "date-picker",
|
|
767
|
+
"date-range-picker", "time-input", "range-calendar", "command", "toggle", "toggle-group"
|
|
768
|
+
],
|
|
769
|
+
navigation: [
|
|
770
|
+
"breadcrumbs", "dropdown", "link", "navbar", "pagination", "tabs",
|
|
771
|
+
"context-menu", "dropdown-menu", "menubar", "navigation-menu"
|
|
772
|
+
],
|
|
773
|
+
feedback: ["alert", "badge", "toast", "progress", "spinner", "skeleton"],
|
|
774
|
+
overlays: ["dialog", "drawer", "tooltip", "popover", "alert-dialog", "hover-card"],
|
|
775
|
+
"data-display": [
|
|
776
|
+
"avatar", "user", "accordion", "table", "card", "image",
|
|
777
|
+
"aspect-ratio", "carousel", "chart", "collapsible", "scroll-area"
|
|
778
|
+
],
|
|
779
|
+
layout: ["resizable", "sidebar"],
|
|
780
|
+
utilities: ["code", "kbd", "divider", "spacer", "scroll-shadow"]
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* Maps app features to required component categories/components
|
|
784
|
+
*/
|
|
785
|
+
const FEATURE_TO_COMPONENTS = {
|
|
786
|
+
auth: ["button", "input", "form", "card", "alert"],
|
|
787
|
+
sidebar: ["sidebar", "button", "tooltip", "dropdown-menu", "separator"],
|
|
788
|
+
"dark-mode": ["switch", "dropdown-menu"], // Theme toggle components
|
|
789
|
+
tables: ["table", "pagination", "button", "badge", "dropdown-menu"],
|
|
790
|
+
forms: ["input", "textarea", "checkbox", "radio-group", "select", "button", "form", "alert"],
|
|
791
|
+
charts: ["chart", "card", "badge"],
|
|
792
|
+
dashboard: ["card", "chart", "table", "badge", "button", "tabs"],
|
|
793
|
+
navigation: ["navbar", "breadcrumbs", "tabs", "dropdown-menu", "link"]
|
|
794
|
+
};
|
|
795
|
+
/**
|
|
796
|
+
* Page layout templates for different app types
|
|
797
|
+
*/
|
|
798
|
+
const PAGE_LAYOUTS = {
|
|
799
|
+
dashboard: {
|
|
800
|
+
description: "Data overview with metrics, charts, and quick actions",
|
|
801
|
+
structure: `## Page Structure
|
|
802
|
+
1. **Header** - Page title, breadcrumbs, primary action button
|
|
803
|
+
2. **Metrics Row** - 3-4 stat cards showing KPIs
|
|
804
|
+
3. **Main Content** - Split into primary and secondary columns
|
|
805
|
+
- Primary (2/3): Main chart or data table
|
|
806
|
+
- Secondary (1/3): Activity feed, quick links
|
|
807
|
+
4. **Footer** (optional) - Pagination or additional actions`,
|
|
808
|
+
components: ["card", "chart", "table", "badge", "button", "dropdown-menu", "tabs"],
|
|
809
|
+
gridLayout: "grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6",
|
|
810
|
+
spacing: "py-8 px-6 lg:py-12 lg:px-8",
|
|
811
|
+
template: `// Dashboard page template
|
|
812
|
+
export default function DashboardPage() {
|
|
813
|
+
return (
|
|
814
|
+
<div className="min-h-screen bg-background">
|
|
815
|
+
{/* Header */}
|
|
816
|
+
<header className="border-b border-border px-6 py-4">
|
|
817
|
+
<div className="flex items-center justify-between">
|
|
818
|
+
<div>
|
|
819
|
+
<h1 className="text-2xl font-light text-foreground">Dashboard</h1>
|
|
820
|
+
<p className="text-sm text-muted-foreground">Overview of your activity</p>
|
|
821
|
+
</div>
|
|
822
|
+
<Button>New Report</Button>
|
|
823
|
+
</div>
|
|
824
|
+
</header>
|
|
825
|
+
|
|
826
|
+
{/* Main content */}
|
|
827
|
+
<main className="p-6 lg:p-8 space-y-8">
|
|
828
|
+
{/* Metrics row */}
|
|
829
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
830
|
+
<Card className="p-6">
|
|
831
|
+
<p className="text-sm text-muted-foreground">Total Revenue</p>
|
|
832
|
+
<p className="text-3xl font-light">$45,231</p>
|
|
833
|
+
</Card>
|
|
834
|
+
{/* Add more metric cards */}
|
|
835
|
+
</div>
|
|
836
|
+
|
|
837
|
+
{/* Main content grid */}
|
|
838
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
839
|
+
<div className="lg:col-span-2">
|
|
840
|
+
<Card className="p-6">
|
|
841
|
+
<h2 className="text-lg font-medium mb-4">Analytics</h2>
|
|
842
|
+
{/* Chart component here */}
|
|
843
|
+
</Card>
|
|
844
|
+
</div>
|
|
845
|
+
<div>
|
|
846
|
+
<Card className="p-6">
|
|
847
|
+
<h2 className="text-lg font-medium mb-4">Recent Activity</h2>
|
|
848
|
+
{/* Activity list here */}
|
|
849
|
+
</Card>
|
|
850
|
+
</div>
|
|
851
|
+
</div>
|
|
852
|
+
</main>
|
|
853
|
+
</div>
|
|
854
|
+
);
|
|
855
|
+
}`
|
|
856
|
+
},
|
|
857
|
+
settings: {
|
|
858
|
+
description: "Configuration page with grouped settings sections",
|
|
859
|
+
structure: `## Page Structure
|
|
860
|
+
1. **Header** - Page title, save button
|
|
861
|
+
2. **Navigation** - Tabs or sidebar for setting categories
|
|
862
|
+
3. **Content Area** - Form sections with dividers
|
|
863
|
+
4. **Footer** - Save/Cancel buttons (sticky on mobile)`,
|
|
864
|
+
components: ["tabs", "input", "switch", "select", "button", "card", "divider", "form"],
|
|
865
|
+
gridLayout: "max-w-4xl mx-auto",
|
|
866
|
+
spacing: "py-8 px-6",
|
|
867
|
+
template: `// Settings page template
|
|
868
|
+
export default function SettingsPage() {
|
|
869
|
+
return (
|
|
870
|
+
<div className="min-h-screen bg-background">
|
|
871
|
+
<div className="max-w-4xl mx-auto py-8 px-6">
|
|
872
|
+
{/* Header */}
|
|
873
|
+
<div className="mb-8">
|
|
874
|
+
<h1 className="text-2xl font-light text-foreground">Settings</h1>
|
|
875
|
+
<p className="text-sm text-muted-foreground">Manage your preferences</p>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
878
|
+
{/* Settings tabs */}
|
|
879
|
+
<Tabs defaultValue="general" className="space-y-8">
|
|
880
|
+
<TabsList>
|
|
881
|
+
<TabsTrigger value="general">General</TabsTrigger>
|
|
882
|
+
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
|
883
|
+
<TabsTrigger value="security">Security</TabsTrigger>
|
|
884
|
+
</TabsList>
|
|
885
|
+
|
|
886
|
+
<TabsContent value="general" className="space-y-6">
|
|
887
|
+
<Card className="p-6">
|
|
888
|
+
<h2 className="text-lg font-medium mb-4">Profile</h2>
|
|
889
|
+
<div className="space-y-4">
|
|
890
|
+
<div>
|
|
891
|
+
<Label htmlFor="name">Name</Label>
|
|
892
|
+
<Input id="name" placeholder="Enter your name" />
|
|
893
|
+
</div>
|
|
894
|
+
<div>
|
|
895
|
+
<Label htmlFor="email">Email</Label>
|
|
896
|
+
<Input id="email" type="email" placeholder="Enter your email" />
|
|
897
|
+
</div>
|
|
898
|
+
</div>
|
|
899
|
+
</Card>
|
|
900
|
+
</TabsContent>
|
|
901
|
+
</Tabs>
|
|
902
|
+
|
|
903
|
+
{/* Footer actions */}
|
|
904
|
+
<div className="mt-8 flex justify-end gap-4">
|
|
905
|
+
<Button variant="outline">Cancel</Button>
|
|
906
|
+
<Button>Save Changes</Button>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
);
|
|
911
|
+
}`
|
|
912
|
+
},
|
|
913
|
+
landing: {
|
|
914
|
+
description: "Marketing landing page with hero, features, and CTA sections",
|
|
915
|
+
structure: `## Page Structure
|
|
916
|
+
1. **Hero Section** - Large headline, subtext, CTA buttons
|
|
917
|
+
2. **Features Section** - 3-4 feature cards or grid
|
|
918
|
+
3. **Social Proof** - Testimonials or logos
|
|
919
|
+
4. **CTA Section** - Final call to action
|
|
920
|
+
5. **Footer** - Links, legal`,
|
|
921
|
+
components: ["button", "card", "badge", "image", "divider"],
|
|
922
|
+
gridLayout: "max-w-7xl mx-auto",
|
|
923
|
+
spacing: "py-20 px-6 lg:py-24 lg:px-8",
|
|
924
|
+
template: `// Landing page template
|
|
925
|
+
export default function LandingPage() {
|
|
926
|
+
return (
|
|
927
|
+
<div className="min-h-screen bg-background">
|
|
928
|
+
{/* Hero */}
|
|
929
|
+
<section className="py-20 px-6 lg:py-32 lg:px-8 text-center">
|
|
930
|
+
<div className="max-w-4xl mx-auto">
|
|
931
|
+
<Badge className="mb-4">New Release</Badge>
|
|
932
|
+
<h1 className="text-4xl lg:text-6xl font-light text-foreground mb-6">
|
|
933
|
+
Designed to Disappear
|
|
934
|
+
</h1>
|
|
935
|
+
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
|
|
936
|
+
Premium audio solutions that blend seamlessly into your architecture.
|
|
937
|
+
</p>
|
|
938
|
+
<div className="flex gap-4 justify-center">
|
|
939
|
+
<Button size="lg">Get Started</Button>
|
|
940
|
+
<Button size="lg" variant="outline">Learn More</Button>
|
|
941
|
+
</div>
|
|
942
|
+
</div>
|
|
943
|
+
</section>
|
|
944
|
+
|
|
945
|
+
{/* Features */}
|
|
946
|
+
<section className="py-20 px-6 lg:py-24 lg:px-8 bg-muted/50">
|
|
947
|
+
<div className="max-w-7xl mx-auto">
|
|
948
|
+
<h2 className="text-3xl font-light text-center mb-12">Features</h2>
|
|
949
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
950
|
+
<Card className="p-6">
|
|
951
|
+
<h3 className="text-lg font-medium mb-2">Feature One</h3>
|
|
952
|
+
<p className="text-muted-foreground">Description of the feature.</p>
|
|
953
|
+
</Card>
|
|
954
|
+
{/* More feature cards */}
|
|
955
|
+
</div>
|
|
956
|
+
</div>
|
|
957
|
+
</section>
|
|
958
|
+
|
|
959
|
+
{/* CTA */}
|
|
960
|
+
<section className="py-20 px-6 lg:py-24 lg:px-8 text-center">
|
|
961
|
+
<div className="max-w-2xl mx-auto">
|
|
962
|
+
<h2 className="text-3xl font-light mb-4">Ready to get started?</h2>
|
|
963
|
+
<p className="text-muted-foreground mb-8">Join thousands of satisfied customers.</p>
|
|
964
|
+
<Button size="lg">Start Free Trial</Button>
|
|
965
|
+
</div>
|
|
966
|
+
</section>
|
|
967
|
+
</div>
|
|
968
|
+
);
|
|
969
|
+
}`
|
|
970
|
+
},
|
|
971
|
+
auth: {
|
|
972
|
+
description: "Authentication page with centered form card",
|
|
973
|
+
structure: `## Page Structure
|
|
974
|
+
1. **Centered Container** - Max-width card
|
|
975
|
+
2. **Logo** - Brand logo at top
|
|
976
|
+
3. **Form** - Email, password, submit
|
|
977
|
+
4. **Links** - Forgot password, sign up`,
|
|
978
|
+
components: ["card", "input", "button", "form", "alert", "divider"],
|
|
979
|
+
gridLayout: "min-h-screen flex items-center justify-center",
|
|
980
|
+
spacing: "p-6",
|
|
981
|
+
template: `// Auth page template
|
|
982
|
+
export default function AuthPage() {
|
|
983
|
+
return (
|
|
984
|
+
<div className="min-h-screen flex items-center justify-center bg-background p-6">
|
|
985
|
+
<Card className="w-full max-w-md p-8">
|
|
986
|
+
{/* Logo */}
|
|
987
|
+
<div className="text-center mb-8">
|
|
988
|
+
<Image src="/logo.svg" alt="Logo" width={120} height={40} className="mx-auto" />
|
|
989
|
+
</div>
|
|
990
|
+
|
|
991
|
+
<h1 className="text-2xl font-light text-center mb-2">Welcome back</h1>
|
|
992
|
+
<p className="text-sm text-muted-foreground text-center mb-8">
|
|
993
|
+
Sign in to your account
|
|
994
|
+
</p>
|
|
995
|
+
|
|
996
|
+
<form className="space-y-4">
|
|
997
|
+
<div>
|
|
998
|
+
<Label htmlFor="email">Email</Label>
|
|
999
|
+
<Input id="email" type="email" placeholder="name@example.com" />
|
|
1000
|
+
</div>
|
|
1001
|
+
<div>
|
|
1002
|
+
<Label htmlFor="password">Password</Label>
|
|
1003
|
+
<Input id="password" type="password" placeholder="Enter your password" />
|
|
1004
|
+
</div>
|
|
1005
|
+
<Button className="w-full">Sign In</Button>
|
|
1006
|
+
</form>
|
|
1007
|
+
|
|
1008
|
+
<div className="mt-6 text-center text-sm">
|
|
1009
|
+
<a href="#" className="text-primary hover:underline">Forgot password?</a>
|
|
1010
|
+
</div>
|
|
1011
|
+
|
|
1012
|
+
<Divider className="my-6" />
|
|
1013
|
+
|
|
1014
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
1015
|
+
Don't have an account?{" "}
|
|
1016
|
+
<a href="#" className="text-primary hover:underline">Sign up</a>
|
|
1017
|
+
</p>
|
|
1018
|
+
</Card>
|
|
1019
|
+
</div>
|
|
1020
|
+
);
|
|
1021
|
+
}`
|
|
1022
|
+
},
|
|
1023
|
+
list: {
|
|
1024
|
+
description: "List/table page with filters and pagination",
|
|
1025
|
+
structure: `## Page Structure
|
|
1026
|
+
1. **Header** - Title, primary action
|
|
1027
|
+
2. **Filters** - Search, dropdown filters
|
|
1028
|
+
3. **Table/List** - Data display
|
|
1029
|
+
4. **Pagination** - Page controls`,
|
|
1030
|
+
components: ["table", "input", "select", "button", "badge", "pagination", "dropdown-menu"],
|
|
1031
|
+
gridLayout: "max-w-7xl mx-auto",
|
|
1032
|
+
spacing: "py-8 px-6",
|
|
1033
|
+
template: `// List page template
|
|
1034
|
+
export default function ListPage() {
|
|
1035
|
+
return (
|
|
1036
|
+
<div className="min-h-screen bg-background">
|
|
1037
|
+
<div className="max-w-7xl mx-auto py-8 px-6">
|
|
1038
|
+
{/* Header */}
|
|
1039
|
+
<div className="flex items-center justify-between mb-8">
|
|
1040
|
+
<div>
|
|
1041
|
+
<h1 className="text-2xl font-light text-foreground">Users</h1>
|
|
1042
|
+
<p className="text-sm text-muted-foreground">Manage your team members</p>
|
|
1043
|
+
</div>
|
|
1044
|
+
<Button>Add User</Button>
|
|
1045
|
+
</div>
|
|
1046
|
+
|
|
1047
|
+
{/* Filters */}
|
|
1048
|
+
<div className="flex gap-4 mb-6">
|
|
1049
|
+
<Input placeholder="Search users..." className="max-w-sm" />
|
|
1050
|
+
<Select>
|
|
1051
|
+
<SelectTrigger className="w-40">
|
|
1052
|
+
<SelectValue placeholder="Role" />
|
|
1053
|
+
</SelectTrigger>
|
|
1054
|
+
<SelectContent>
|
|
1055
|
+
<SelectItem value="all">All Roles</SelectItem>
|
|
1056
|
+
<SelectItem value="admin">Admin</SelectItem>
|
|
1057
|
+
<SelectItem value="user">User</SelectItem>
|
|
1058
|
+
</SelectContent>
|
|
1059
|
+
</Select>
|
|
1060
|
+
</div>
|
|
1061
|
+
|
|
1062
|
+
{/* Table */}
|
|
1063
|
+
<Card>
|
|
1064
|
+
<Table>
|
|
1065
|
+
<TableHeader>
|
|
1066
|
+
<TableRow>
|
|
1067
|
+
<TableHead>Name</TableHead>
|
|
1068
|
+
<TableHead>Email</TableHead>
|
|
1069
|
+
<TableHead>Role</TableHead>
|
|
1070
|
+
<TableHead>Status</TableHead>
|
|
1071
|
+
<TableHead></TableHead>
|
|
1072
|
+
</TableRow>
|
|
1073
|
+
</TableHeader>
|
|
1074
|
+
<TableBody>
|
|
1075
|
+
{/* Table rows */}
|
|
1076
|
+
</TableBody>
|
|
1077
|
+
</Table>
|
|
1078
|
+
</Card>
|
|
1079
|
+
|
|
1080
|
+
{/* Pagination */}
|
|
1081
|
+
<div className="mt-6 flex justify-center">
|
|
1082
|
+
<Pagination>
|
|
1083
|
+
{/* Pagination controls */}
|
|
1084
|
+
</Pagination>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
</div>
|
|
1088
|
+
);
|
|
1089
|
+
}`
|
|
1090
|
+
},
|
|
1091
|
+
detail: {
|
|
1092
|
+
description: "Detail/profile page with header and content sections",
|
|
1093
|
+
structure: `## Page Structure
|
|
1094
|
+
1. **Header** - Back button, title, actions
|
|
1095
|
+
2. **Main Content** - Primary information
|
|
1096
|
+
3. **Sidebar** - Related info, metadata`,
|
|
1097
|
+
components: ["card", "button", "badge", "tabs", "avatar", "divider"],
|
|
1098
|
+
gridLayout: "max-w-7xl mx-auto",
|
|
1099
|
+
spacing: "py-8 px-6",
|
|
1100
|
+
template: `// Detail page template
|
|
1101
|
+
export default function DetailPage() {
|
|
1102
|
+
return (
|
|
1103
|
+
<div className="min-h-screen bg-background">
|
|
1104
|
+
<div className="max-w-7xl mx-auto py-8 px-6">
|
|
1105
|
+
{/* Header */}
|
|
1106
|
+
<div className="mb-8">
|
|
1107
|
+
<Button variant="ghost" size="sm" className="mb-4">
|
|
1108
|
+
← Back
|
|
1109
|
+
</Button>
|
|
1110
|
+
<div className="flex items-start justify-between">
|
|
1111
|
+
<div className="flex items-center gap-4">
|
|
1112
|
+
<Avatar className="h-16 w-16" />
|
|
1113
|
+
<div>
|
|
1114
|
+
<h1 className="text-2xl font-light text-foreground">Item Name</h1>
|
|
1115
|
+
<p className="text-sm text-muted-foreground">Description or subtitle</p>
|
|
1116
|
+
</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
<div className="flex gap-2">
|
|
1119
|
+
<Button variant="outline">Edit</Button>
|
|
1120
|
+
<Button>Action</Button>
|
|
1121
|
+
</div>
|
|
1122
|
+
</div>
|
|
1123
|
+
</div>
|
|
1124
|
+
|
|
1125
|
+
{/* Content grid */}
|
|
1126
|
+
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
1127
|
+
{/* Main content */}
|
|
1128
|
+
<div className="lg:col-span-2 space-y-6">
|
|
1129
|
+
<Card className="p-6">
|
|
1130
|
+
<h2 className="text-lg font-medium mb-4">Details</h2>
|
|
1131
|
+
{/* Detail content */}
|
|
1132
|
+
</Card>
|
|
1133
|
+
</div>
|
|
1134
|
+
|
|
1135
|
+
{/* Sidebar */}
|
|
1136
|
+
<div className="space-y-6">
|
|
1137
|
+
<Card className="p-6">
|
|
1138
|
+
<h2 className="text-lg font-medium mb-4">Metadata</h2>
|
|
1139
|
+
{/* Sidebar content */}
|
|
1140
|
+
</Card>
|
|
1141
|
+
</div>
|
|
1142
|
+
</div>
|
|
1143
|
+
</div>
|
|
1144
|
+
</div>
|
|
1145
|
+
);
|
|
1146
|
+
}`
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
/**
|
|
1150
|
+
* Detect the component type from a description
|
|
1151
|
+
*/
|
|
1152
|
+
function detectComponentType(description) {
|
|
1153
|
+
const lower = description.toLowerCase();
|
|
1154
|
+
if (/button|input|form|checkbox|radio|switch|select|slider|date|time|calendar|toggle|textarea|autocomplete/i.test(lower))
|
|
1155
|
+
return "forms";
|
|
1156
|
+
if (/nav|menu|tab|breadcrumb|pagination|link|dropdown/i.test(lower))
|
|
1157
|
+
return "navigation";
|
|
1158
|
+
if (/alert|badge|toast|notification|progress|spinner|loading|skeleton/i.test(lower))
|
|
1159
|
+
return "feedback";
|
|
1160
|
+
if (/modal|dialog|drawer|tooltip|popover|overlay|sheet/i.test(lower))
|
|
1161
|
+
return "overlays";
|
|
1162
|
+
if (/card|table|avatar|user|accordion|list|chart|carousel|image|data/i.test(lower))
|
|
1163
|
+
return "data-display";
|
|
1164
|
+
if (/sidebar|layout|grid|container|resizable|panel|split/i.test(lower))
|
|
1165
|
+
return "layout";
|
|
1166
|
+
if (/code|kbd|divider|spacer|utility/i.test(lower))
|
|
1167
|
+
return "utilities";
|
|
1168
|
+
return "general";
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Get component-type-specific implementation guidance
|
|
1172
|
+
*/
|
|
1173
|
+
const COMPONENT_TYPE_GUIDANCE = {
|
|
1174
|
+
forms: `## Forms Component Implementation
|
|
1175
|
+
|
|
1176
|
+
### Key Rules
|
|
1177
|
+
- Always include label associations (htmlFor/id)
|
|
1178
|
+
- Support disabled, error, and loading states
|
|
1179
|
+
- Use ring-based focus indicators: \`focus-visible:ring-2 focus-visible:ring-primary\`
|
|
1180
|
+
- Maintain consistent sizing: h-10 for inputs, px-6 py-3 for buttons
|
|
1181
|
+
- Include aria-invalid and aria-describedby for error states
|
|
1182
|
+
|
|
1183
|
+
### Required States
|
|
1184
|
+
- Default, Hover, Focus, Disabled, Error, Loading
|
|
1185
|
+
|
|
1186
|
+
### Common Patterns
|
|
1187
|
+
\`\`\`tsx
|
|
1188
|
+
// Button with proper states
|
|
1189
|
+
<button className="bg-primary text-primary-foreground px-6 py-3 rounded-sm font-medium
|
|
1190
|
+
hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
|
|
1191
|
+
disabled:opacity-50 disabled:cursor-not-allowed transition-colors">
|
|
1192
|
+
|
|
1193
|
+
// Input with error state
|
|
1194
|
+
<input className="h-10 px-4 border border-input rounded-sm bg-background
|
|
1195
|
+
focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2
|
|
1196
|
+
aria-[invalid=true]:border-destructive" />
|
|
1197
|
+
\`\`\`
|
|
1198
|
+
|
|
1199
|
+
### Anti-Patterns
|
|
1200
|
+
- Missing focus states (CRITICAL)
|
|
1201
|
+
- No error state styling
|
|
1202
|
+
- Inconsistent heights across form elements
|
|
1203
|
+
- Using font-bold (use font-medium)`,
|
|
1204
|
+
navigation: `## Navigation Component Implementation
|
|
1205
|
+
|
|
1206
|
+
### Key Rules
|
|
1207
|
+
- Clear active state indication: bg-primary or border-b-2
|
|
1208
|
+
- Keyboard navigation support: arrow keys, Home/End
|
|
1209
|
+
- ARIA roles: navigation, menubar, menu, menuitem
|
|
1210
|
+
- Consistent icon sizing: h-4 w-4 with text
|
|
1211
|
+
|
|
1212
|
+
### Required States
|
|
1213
|
+
- Default, Hover, Active, Disabled
|
|
1214
|
+
|
|
1215
|
+
### Common Patterns
|
|
1216
|
+
\`\`\`tsx
|
|
1217
|
+
// Nav item with active state
|
|
1218
|
+
<a className="px-4 py-2 text-sm font-medium text-muted-foreground
|
|
1219
|
+
hover:text-foreground hover:bg-muted rounded-sm transition-colors
|
|
1220
|
+
data-[active=true]:text-foreground data-[active=true]:bg-muted">
|
|
1221
|
+
|
|
1222
|
+
// Tab with indicator
|
|
1223
|
+
<button className="px-4 py-2 text-sm font-medium border-b-2 border-transparent
|
|
1224
|
+
hover:border-muted data-[state=active]:border-primary transition-colors">
|
|
1225
|
+
\`\`\`
|
|
1226
|
+
|
|
1227
|
+
### Anti-Patterns
|
|
1228
|
+
- Missing aria-current for active items
|
|
1229
|
+
- No keyboard navigation
|
|
1230
|
+
- Inconsistent spacing between items`,
|
|
1231
|
+
feedback: `## Feedback Component Implementation
|
|
1232
|
+
|
|
1233
|
+
### Key Rules
|
|
1234
|
+
- Use semantic colors: destructive for errors, success for positive
|
|
1235
|
+
- Include appropriate icons (checkmark, X, info, warning)
|
|
1236
|
+
- Support dismissible variants where appropriate
|
|
1237
|
+
- Consider auto-dismiss for toasts
|
|
1238
|
+
|
|
1239
|
+
### Common Patterns
|
|
1240
|
+
\`\`\`tsx
|
|
1241
|
+
// Alert with variants
|
|
1242
|
+
<div role="alert" className="p-4 rounded-sm border
|
|
1243
|
+
data-[variant=error]:bg-destructive/10 data-[variant=error]:border-destructive
|
|
1244
|
+
data-[variant=success]:bg-green-500/10 data-[variant=success]:border-green-500">
|
|
1245
|
+
|
|
1246
|
+
// Badge
|
|
1247
|
+
<span className="px-2 py-0.5 text-xs font-medium rounded-sm
|
|
1248
|
+
bg-primary text-primary-foreground">
|
|
1249
|
+
\`\`\`
|
|
1250
|
+
|
|
1251
|
+
### Anti-Patterns
|
|
1252
|
+
- Missing role="alert" for alerts
|
|
1253
|
+
- No icon to reinforce message type
|
|
1254
|
+
- Overly large badges`,
|
|
1255
|
+
overlays: `## Overlay Component Implementation
|
|
1256
|
+
|
|
1257
|
+
### Key Rules
|
|
1258
|
+
- Focus trap within modals
|
|
1259
|
+
- Close on Escape key
|
|
1260
|
+
- Backdrop click to close (optional)
|
|
1261
|
+
- Proper z-index management
|
|
1262
|
+
- Animate in/out smoothly
|
|
1263
|
+
|
|
1264
|
+
### Common Patterns
|
|
1265
|
+
\`\`\`tsx
|
|
1266
|
+
// Dialog backdrop
|
|
1267
|
+
<div className="fixed inset-0 bg-black/50 z-50" />
|
|
1268
|
+
|
|
1269
|
+
// Dialog content
|
|
1270
|
+
<div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2
|
|
1271
|
+
bg-background border border-border rounded-sm shadow-lg p-6 z-50
|
|
1272
|
+
animate-in fade-in zoom-in-95">
|
|
1273
|
+
\`\`\`
|
|
1274
|
+
|
|
1275
|
+
### Anti-Patterns
|
|
1276
|
+
- Missing focus trap
|
|
1277
|
+
- No Escape key handler
|
|
1278
|
+
- Forgetting to portal the overlay`,
|
|
1279
|
+
"data-display": `## Data Display Component Implementation
|
|
1280
|
+
|
|
1281
|
+
### Key Rules
|
|
1282
|
+
- Handle empty states gracefully
|
|
1283
|
+
- Support loading skeletons
|
|
1284
|
+
- Consider responsive behavior
|
|
1285
|
+
- Maintain data hierarchy through typography
|
|
1286
|
+
|
|
1287
|
+
### Common Patterns
|
|
1288
|
+
\`\`\`tsx
|
|
1289
|
+
// Card
|
|
1290
|
+
<div className="bg-card border border-border rounded-sm p-6 shadow-sm">
|
|
1291
|
+
|
|
1292
|
+
// Table
|
|
1293
|
+
<table className="w-full">
|
|
1294
|
+
<thead className="border-b border-border">
|
|
1295
|
+
<tr>
|
|
1296
|
+
<th className="text-left py-3 px-4 text-sm font-medium text-muted-foreground">
|
|
1297
|
+
\`\`\`
|
|
1298
|
+
|
|
1299
|
+
### Anti-Patterns
|
|
1300
|
+
- No empty state handling
|
|
1301
|
+
- Missing loading states
|
|
1302
|
+
- Cramped spacing in tables`,
|
|
1303
|
+
layout: `## Layout Component Implementation
|
|
1304
|
+
|
|
1305
|
+
### Key Rules
|
|
1306
|
+
- Use CSS Grid or Flexbox appropriately
|
|
1307
|
+
- Support responsive breakpoints
|
|
1308
|
+
- Consider collapsible/expandable states
|
|
1309
|
+
- Use semantic HTML (aside, main, nav)
|
|
1310
|
+
|
|
1311
|
+
### Common Patterns
|
|
1312
|
+
\`\`\`tsx
|
|
1313
|
+
// Sidebar layout
|
|
1314
|
+
<div className="flex min-h-screen">
|
|
1315
|
+
<aside className="w-64 border-r border-border bg-background">
|
|
1316
|
+
<main className="flex-1 p-6">
|
|
1317
|
+
</div>
|
|
1318
|
+
|
|
1319
|
+
// Resizable panel
|
|
1320
|
+
<div className="flex" style={{ resize: 'horizontal' }}>
|
|
1321
|
+
\`\`\`
|
|
1322
|
+
|
|
1323
|
+
### Anti-Patterns
|
|
1324
|
+
- Fixed widths without responsiveness
|
|
1325
|
+
- Missing semantic landmarks
|
|
1326
|
+
- No mobile consideration`,
|
|
1327
|
+
utilities: `## Utility Component Implementation
|
|
1328
|
+
|
|
1329
|
+
### Key Rules
|
|
1330
|
+
- Keep utilities simple and focused
|
|
1331
|
+
- Provide sensible defaults
|
|
1332
|
+
- Support customization via props/className
|
|
1333
|
+
|
|
1334
|
+
### Common Patterns
|
|
1335
|
+
\`\`\`tsx
|
|
1336
|
+
// Divider
|
|
1337
|
+
<hr className="border-t border-border my-6" />
|
|
1338
|
+
|
|
1339
|
+
// Spacer
|
|
1340
|
+
<div className="h-8" aria-hidden="true" />
|
|
1341
|
+
|
|
1342
|
+
// Kbd
|
|
1343
|
+
<kbd className="px-2 py-1 text-xs font-mono bg-muted rounded-sm border border-border">
|
|
1344
|
+
\`\`\``,
|
|
1345
|
+
general: `## General Component Implementation
|
|
1346
|
+
|
|
1347
|
+
### Key Rules
|
|
1348
|
+
- Follow brand guidelines for colors and typography
|
|
1349
|
+
- Use semantic CSS variables
|
|
1350
|
+
- Support light and dark mode
|
|
1351
|
+
- Include hover and focus states
|
|
1352
|
+
|
|
1353
|
+
### Quick Reference
|
|
1354
|
+
- Primary: bg-primary text-primary-foreground
|
|
1355
|
+
- Border radius: rounded-sm (2px)
|
|
1356
|
+
- Font weights: font-light (300), font-medium (500)
|
|
1357
|
+
- Spacing: generous (py-20 for sections)`
|
|
1358
|
+
};
|
|
469
1359
|
/**
|
|
470
1360
|
* Get regex patterns to detect brand colors in code
|
|
471
1361
|
*/
|
|
@@ -545,6 +1435,50 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
|
545
1435
|
catch (e) {
|
|
546
1436
|
// Components directory might not exist
|
|
547
1437
|
}
|
|
1438
|
+
// Add logo resources
|
|
1439
|
+
try {
|
|
1440
|
+
let logoList;
|
|
1441
|
+
if (IS_BUNDLED) {
|
|
1442
|
+
const manifestPath = getAssetPath("logos");
|
|
1443
|
+
logoList = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
// Scan directory in development
|
|
1447
|
+
const logosDir = getAssetPath("logos");
|
|
1448
|
+
function listLogoFiles(dir, prefix = "") {
|
|
1449
|
+
let results = [];
|
|
1450
|
+
if (!fs.existsSync(dir))
|
|
1451
|
+
return results;
|
|
1452
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1453
|
+
for (const entry of entries) {
|
|
1454
|
+
if (entry.isDirectory()) {
|
|
1455
|
+
results = [...results, ...listLogoFiles(path.join(dir, entry.name), `${prefix}${entry.name}/`)];
|
|
1456
|
+
}
|
|
1457
|
+
else if (/\.(png|svg|jpg|jpeg)$/i.test(entry.name)) {
|
|
1458
|
+
results.push(`/logos/${prefix}${entry.name}`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
return results;
|
|
1462
|
+
}
|
|
1463
|
+
logoList = listLogoFiles(logosDir);
|
|
1464
|
+
}
|
|
1465
|
+
for (const logoPath of logoList) {
|
|
1466
|
+
// Convert /logos/sonance/file.png to sonance/file.png for URI
|
|
1467
|
+
const uriPath = logoPath.replace(/^\/logos\//, '');
|
|
1468
|
+
const fileName = path.basename(logoPath, path.extname(logoPath));
|
|
1469
|
+
const folder = path.dirname(uriPath);
|
|
1470
|
+
const mimeType = getImageMimeType(logoPath);
|
|
1471
|
+
resources.push({
|
|
1472
|
+
uri: `sonance://logo/${uriPath}`,
|
|
1473
|
+
name: `${fileName} Logo`,
|
|
1474
|
+
description: `${folder !== '.' ? folder + ' - ' : ''}Brand logo image file`,
|
|
1475
|
+
mimeType: mimeType,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
catch (e) {
|
|
1480
|
+
// Logo resources might not be available
|
|
1481
|
+
}
|
|
548
1482
|
return { resources };
|
|
549
1483
|
});
|
|
550
1484
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
@@ -572,6 +1506,24 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
572
1506
|
};
|
|
573
1507
|
}
|
|
574
1508
|
}
|
|
1509
|
+
// Handle logo resources
|
|
1510
|
+
if (uri.startsWith("sonance://logo/")) {
|
|
1511
|
+
const logoPath = uri.replace("sonance://logo/", "");
|
|
1512
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
1513
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
1514
|
+
throw new Error(`Logo not found: ${logoPath}`);
|
|
1515
|
+
}
|
|
1516
|
+
const imageBuffer = fs.readFileSync(resolvedPath);
|
|
1517
|
+
const base64Data = imageBuffer.toString('base64');
|
|
1518
|
+
const mimeType = getImageMimeType(resolvedPath);
|
|
1519
|
+
return {
|
|
1520
|
+
contents: [{
|
|
1521
|
+
uri,
|
|
1522
|
+
mimeType,
|
|
1523
|
+
blob: base64Data, // Use blob for binary resources
|
|
1524
|
+
}],
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
575
1527
|
throw new Error(`Resource not found: ${uri}`);
|
|
576
1528
|
});
|
|
577
1529
|
// ============================================
|
|
@@ -639,6 +1591,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
639
1591
|
required: [],
|
|
640
1592
|
},
|
|
641
1593
|
},
|
|
1594
|
+
{
|
|
1595
|
+
name: "get_logo",
|
|
1596
|
+
description: "Returns a brand logo image as base64-encoded data that AI can view and analyze. Use list_logos first to see available logos, then use this tool to retrieve the actual image.",
|
|
1597
|
+
inputSchema: {
|
|
1598
|
+
type: "object",
|
|
1599
|
+
properties: {
|
|
1600
|
+
logo_path: {
|
|
1601
|
+
type: "string",
|
|
1602
|
+
description: "The path to the logo from list_logos output (e.g., '/logos/sonance/Sonance_Logo_2C_Dark_RGB.png' or 'sonance/Sonance_Logo_2C_Dark_RGB.png')",
|
|
1603
|
+
},
|
|
1604
|
+
},
|
|
1605
|
+
required: ["logo_path"],
|
|
1606
|
+
},
|
|
1607
|
+
},
|
|
642
1608
|
{
|
|
643
1609
|
name: "get_full_library",
|
|
644
1610
|
description: "RECOMMENDED FOR APP REDESIGNS & FULL APPLICATIONS: Returns the complete component library including brand guidelines, CSS theme, utilities, and all component source code. USE THIS TOOL when the user asks to: redesign an application, rebrand an app, build a full application, update an entire codebase to use Sonance/IPORT/Blaze brand, or any multi-file/multi-component project. This is the go-to tool for comprehensive brand implementation across an entire project.",
|
|
@@ -650,7 +1616,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
650
1616
|
},
|
|
651
1617
|
{
|
|
652
1618
|
name: "design_component",
|
|
653
|
-
description: "Returns brand-specific design tokens, colors, implementation rules, and logo
|
|
1619
|
+
description: "Returns brand-specific design tokens, colors, implementation rules, and AUTO-EMBEDS the correct logo image(s) for designing a component. For UI outputs, embeds both light and dark theme logos. Smart defaults: Sonance+James+IPORT lockup as default logo. Auto-detects output type (ui/doc/marketing/tech).",
|
|
654
1620
|
inputSchema: {
|
|
655
1621
|
type: "object",
|
|
656
1622
|
properties: {
|
|
@@ -666,8 +1632,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
666
1632
|
},
|
|
667
1633
|
logo_preference: {
|
|
668
1634
|
type: "string",
|
|
669
|
-
enum: ["default", "sonance", "iport", "blaze"],
|
|
670
|
-
description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', or '
|
|
1635
|
+
enum: ["default", "sonance", "iport", "blaze", "james"],
|
|
1636
|
+
description: "Which logo to use. 'default' = Sonance+James+IPORT lockup. Or specify 'sonance', 'iport', 'blaze', or 'james' for individual brand logos. Defaults to 'default' if not specified.",
|
|
671
1637
|
},
|
|
672
1638
|
output_type: {
|
|
673
1639
|
type: "string",
|
|
@@ -678,6 +1644,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
678
1644
|
type: "string",
|
|
679
1645
|
description: "What the user wants to design (e.g., 'hero section', 'pricing card', 'navigation bar', 'proposal PDF', 'email template')",
|
|
680
1646
|
},
|
|
1647
|
+
component_type: {
|
|
1648
|
+
type: "string",
|
|
1649
|
+
enum: ["forms", "navigation", "feedback", "overlays", "data-display", "layout", "utilities", "general"],
|
|
1650
|
+
description: "Type of component for specialized guidance. Auto-detected from description if not specified. 'forms' = buttons/inputs/selects, 'navigation' = tabs/menus/breadcrumbs, 'feedback' = alerts/badges/toasts, 'overlays' = dialogs/modals/popovers, 'data-display' = cards/tables/avatars, 'layout' = sidebar/resizable/grid, 'utilities' = dividers/spacers.",
|
|
1651
|
+
},
|
|
681
1652
|
},
|
|
682
1653
|
required: ["component_description"],
|
|
683
1654
|
},
|
|
@@ -755,7 +1726,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
755
1726
|
},
|
|
756
1727
|
{
|
|
757
1728
|
name: "rebrand_document",
|
|
758
|
-
description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze)
|
|
1729
|
+
description: "Returns universal rebranding instructions for converting ANY existing document to a specific brand (Sonance, IPORT, or Blaze) and AUTO-EMBEDS both light and dark logo variants for immediate use. Provides color replacements, font substitutions, and logo placement rules. Default: Sonance+James+IPORT lockup for Sonance brand.",
|
|
759
1730
|
inputSchema: {
|
|
760
1731
|
type: "object",
|
|
761
1732
|
properties: {
|
|
@@ -777,6 +1748,111 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
777
1748
|
required: ["source_format"],
|
|
778
1749
|
},
|
|
779
1750
|
},
|
|
1751
|
+
// ============================================
|
|
1752
|
+
// NEW APP DEVELOPMENT TOOLS
|
|
1753
|
+
// ============================================
|
|
1754
|
+
{
|
|
1755
|
+
name: "design_app",
|
|
1756
|
+
description: "USE FOR NEW APP DEVELOPMENT: Returns a complete starter kit for building a new application with Sonance, IPORT, or Blaze branding. Includes: globals.css with brand CSS variables, component library source code filtered by features needed, page layout templates, and step-by-step build instructions. Use when user asks to 'create', 'build', or 'start' a new branded application.",
|
|
1757
|
+
inputSchema: {
|
|
1758
|
+
type: "object",
|
|
1759
|
+
properties: {
|
|
1760
|
+
brand: {
|
|
1761
|
+
type: "string",
|
|
1762
|
+
enum: ["sonance", "iport", "blaze"],
|
|
1763
|
+
description: "Target brand for the app. Defaults to 'sonance'.",
|
|
1764
|
+
},
|
|
1765
|
+
app_type: {
|
|
1766
|
+
type: "string",
|
|
1767
|
+
enum: ["dashboard", "marketing", "saas", "internal-tool", "general"],
|
|
1768
|
+
description: "Type of application - affects page layout recommendations. Defaults to 'general'.",
|
|
1769
|
+
},
|
|
1770
|
+
features: {
|
|
1771
|
+
type: "array",
|
|
1772
|
+
items: {
|
|
1773
|
+
type: "string",
|
|
1774
|
+
enum: ["auth", "sidebar", "dark-mode", "tables", "forms", "charts", "navigation", "dashboard"],
|
|
1775
|
+
},
|
|
1776
|
+
description: "Features the app will have - returns only relevant components. If omitted, returns core components.",
|
|
1777
|
+
},
|
|
1778
|
+
framework: {
|
|
1779
|
+
type: "string",
|
|
1780
|
+
enum: ["nextjs", "react"],
|
|
1781
|
+
description: "Target framework. Defaults to 'nextjs'.",
|
|
1782
|
+
},
|
|
1783
|
+
},
|
|
1784
|
+
},
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
name: "redesign_app",
|
|
1788
|
+
description: "USE FOR REDESIGNING EXISTING APPS: Analyzes code patterns and returns specific transformation instructions to rebrand an application to Sonance, IPORT, or Blaze. Provide existing code and get: identified brand violations with line numbers, specific fixes with before/after code, step-by-step transformation plan. Use when user asks to 'redesign', 'rebrand', 'restyle', or 'update' an existing application.",
|
|
1789
|
+
inputSchema: {
|
|
1790
|
+
type: "object",
|
|
1791
|
+
properties: {
|
|
1792
|
+
brand: {
|
|
1793
|
+
type: "string",
|
|
1794
|
+
enum: ["sonance", "iport", "blaze"],
|
|
1795
|
+
description: "Target brand for redesign. Defaults to 'sonance'.",
|
|
1796
|
+
},
|
|
1797
|
+
existing_code: {
|
|
1798
|
+
type: "string",
|
|
1799
|
+
description: "The existing component/page code to analyze. Tool will identify what needs changing.",
|
|
1800
|
+
},
|
|
1801
|
+
file_type: {
|
|
1802
|
+
type: "string",
|
|
1803
|
+
enum: ["globals.css", "tailwind.config", "component", "page", "layout", "unknown"],
|
|
1804
|
+
description: "Type of file being analyzed - helps provide targeted fixes.",
|
|
1805
|
+
},
|
|
1806
|
+
file_path: {
|
|
1807
|
+
type: "string",
|
|
1808
|
+
description: "Optional file path for context (e.g., 'src/components/Button.tsx').",
|
|
1809
|
+
},
|
|
1810
|
+
},
|
|
1811
|
+
required: ["existing_code"],
|
|
1812
|
+
},
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
name: "analyze_for_redesign",
|
|
1816
|
+
description: "BATCH CODEBASE ANALYSIS: Accepts multiple file contents and returns a prioritized transformation plan for rebranding. Returns which files need changes, in what order, and what changes each needs. Use when redesigning multiple files or an entire codebase.",
|
|
1817
|
+
inputSchema: {
|
|
1818
|
+
type: "object",
|
|
1819
|
+
properties: {
|
|
1820
|
+
brand: {
|
|
1821
|
+
type: "string",
|
|
1822
|
+
enum: ["sonance", "iport", "blaze"],
|
|
1823
|
+
description: "Target brand for redesign. Defaults to 'sonance'.",
|
|
1824
|
+
},
|
|
1825
|
+
files: {
|
|
1826
|
+
type: "array",
|
|
1827
|
+
items: {
|
|
1828
|
+
type: "object",
|
|
1829
|
+
properties: {
|
|
1830
|
+
path: { type: "string", description: "File path" },
|
|
1831
|
+
content: { type: "string", description: "File content" },
|
|
1832
|
+
},
|
|
1833
|
+
required: ["path", "content"],
|
|
1834
|
+
},
|
|
1835
|
+
description: "Array of files to analyze",
|
|
1836
|
+
},
|
|
1837
|
+
},
|
|
1838
|
+
required: ["files"],
|
|
1839
|
+
},
|
|
1840
|
+
},
|
|
1841
|
+
{
|
|
1842
|
+
name: "get_components_by_category",
|
|
1843
|
+
description: "Returns all components in a specific category with their full source code. More efficient than get_full_library when you only need certain types of components. Categories: forms, navigation, feedback, overlays, data-display, layout, utilities.",
|
|
1844
|
+
inputSchema: {
|
|
1845
|
+
type: "object",
|
|
1846
|
+
properties: {
|
|
1847
|
+
category: {
|
|
1848
|
+
type: "string",
|
|
1849
|
+
enum: ["forms", "navigation", "feedback", "overlays", "data-display", "layout", "utilities"],
|
|
1850
|
+
description: "The component category to retrieve",
|
|
1851
|
+
},
|
|
1852
|
+
},
|
|
1853
|
+
required: ["category"],
|
|
1854
|
+
},
|
|
1855
|
+
},
|
|
780
1856
|
],
|
|
781
1857
|
};
|
|
782
1858
|
});
|
|
@@ -941,6 +2017,66 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
941
2017
|
};
|
|
942
2018
|
}
|
|
943
2019
|
}
|
|
2020
|
+
case "get_logo": {
|
|
2021
|
+
const logoPath = args.logo_path;
|
|
2022
|
+
if (!logoPath) {
|
|
2023
|
+
return {
|
|
2024
|
+
content: [{ type: "text", text: "Error: logo_path is required. Use list_logos to see available logos." }],
|
|
2025
|
+
isError: true,
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
try {
|
|
2029
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
2030
|
+
if (!resolvedPath) {
|
|
2031
|
+
return {
|
|
2032
|
+
content: [{ type: "text", text: `Error: Invalid logo path: ${logoPath}` }],
|
|
2033
|
+
isError: true,
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
2037
|
+
// Try to help user find the right logo
|
|
2038
|
+
let suggestion = '\n\nUse list_logos to see available logos.';
|
|
2039
|
+
if (IS_BUNDLED) {
|
|
2040
|
+
try {
|
|
2041
|
+
const manifestPath = getAssetPath("logos");
|
|
2042
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
2043
|
+
suggestion = `\n\nAvailable logos include:\n${manifest.slice(0, 5).map((p) => ` - ${p}`).join('\n')}\n ... and ${manifest.length - 5} more. Use list_logos to see all.`;
|
|
2044
|
+
}
|
|
2045
|
+
catch {
|
|
2046
|
+
// Keep default suggestion
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return {
|
|
2050
|
+
content: [{ type: "text", text: `Error: Logo not found: ${logoPath}${suggestion}` }],
|
|
2051
|
+
isError: true,
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
// Read and encode the image
|
|
2055
|
+
const imageBuffer = fs.readFileSync(resolvedPath);
|
|
2056
|
+
const base64Data = imageBuffer.toString('base64');
|
|
2057
|
+
const mimeType = getImageMimeType(resolvedPath);
|
|
2058
|
+
const fileName = path.basename(resolvedPath);
|
|
2059
|
+
return {
|
|
2060
|
+
content: [
|
|
2061
|
+
{
|
|
2062
|
+
type: "image",
|
|
2063
|
+
data: base64Data,
|
|
2064
|
+
mimeType: mimeType,
|
|
2065
|
+
},
|
|
2066
|
+
{
|
|
2067
|
+
type: "text",
|
|
2068
|
+
text: `**Logo:** ${fileName}\n**Path:** ${logoPath}\n**Size:** ${imageBuffer.length} bytes\n**Format:** ${mimeType}`,
|
|
2069
|
+
},
|
|
2070
|
+
],
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
catch (e) {
|
|
2074
|
+
return {
|
|
2075
|
+
content: [{ type: "text", text: `Error reading logo: ${e}` }],
|
|
2076
|
+
isError: true,
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
944
2080
|
case "get_full_library": {
|
|
945
2081
|
try {
|
|
946
2082
|
let fullLibrary = "# Sonance Component Library\n\n";
|
|
@@ -1016,6 +2152,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1016
2152
|
if (!rawArgs.output_type) {
|
|
1017
2153
|
defaultsUsed.push(`output type → ${detectedOutputType} (auto-detected)`);
|
|
1018
2154
|
}
|
|
2155
|
+
// Smart component type detection (only for UI outputs)
|
|
2156
|
+
const detectedComponentType = rawArgs.component_type || (detectedOutputType === "ui" ? detectComponentType(component_description) : "general");
|
|
2157
|
+
if (!rawArgs.component_type && detectedOutputType === "ui") {
|
|
2158
|
+
defaultsUsed.push(`component type → ${detectedComponentType} (auto-detected)`);
|
|
2159
|
+
}
|
|
1019
2160
|
// Dual-theme mode: For UI outputs without explicit theme, provide BOTH themes by default
|
|
1020
2161
|
const isDualThemeMode = detectedOutputType === "ui" && !rawArgs.theme;
|
|
1021
2162
|
const theme = rawArgs.theme || "light";
|
|
@@ -1044,11 +2185,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1044
2185
|
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
1045
2186
|
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
1046
2187
|
},
|
|
2188
|
+
james: {
|
|
2189
|
+
light: "/logos/james/James_Logo_Black_RGB.png",
|
|
2190
|
+
dark: "/logos/james/James_Logo_Reverse_RGB.png",
|
|
2191
|
+
},
|
|
1047
2192
|
};
|
|
1048
2193
|
const logoPath = logoMap[logoPreference]?.[theme] || logoMap.default[theme];
|
|
1049
2194
|
const logoPreferenceLabel = logoPreference === "default"
|
|
1050
2195
|
? "Sonance + James + IPORT Lockup"
|
|
1051
|
-
:
|
|
2196
|
+
: logoPreference === "james"
|
|
2197
|
+
? "James Only"
|
|
2198
|
+
: `${logoPreference.charAt(0).toUpperCase() + logoPreference.slice(1)} Only`;
|
|
1052
2199
|
// Brand-specific design tokens
|
|
1053
2200
|
const brandTokens = {
|
|
1054
2201
|
sonance: {
|
|
@@ -1391,7 +2538,8 @@ const example = "Sonance";
|
|
|
1391
2538
|
|
|
1392
2539
|
**Brand**: ${brand.toUpperCase()}
|
|
1393
2540
|
**Theme**: Light AND Dark Mode (automatically implemented)
|
|
1394
|
-
**Output Type**: ${detectedOutputType.toUpperCase()}
|
|
2541
|
+
**Output Type**: ${detectedOutputType.toUpperCase()}${detectedComponentType !== "general" ? `
|
|
2542
|
+
**Component Type**: ${detectedComponentType.charAt(0).toUpperCase() + detectedComponentType.slice(1)}` : ""}
|
|
1395
2543
|
**Logo**: ${logoPreferenceLabel}
|
|
1396
2544
|
${defaultsNotice}
|
|
1397
2545
|
|
|
@@ -1479,7 +2627,16 @@ function Logo() {
|
|
|
1479
2627
|
|
|
1480
2628
|
${outputTypeGuidance[detectedOutputType]}
|
|
1481
2629
|
|
|
1482
|
-
|
|
2630
|
+
${detectedComponentType !== "general" ? `---
|
|
2631
|
+
|
|
2632
|
+
## Component Type: ${detectedComponentType.charAt(0).toUpperCase() + detectedComponentType.slice(1)}
|
|
2633
|
+
|
|
2634
|
+
${COMPONENT_TYPE_GUIDANCE[detectedComponentType]}
|
|
2635
|
+
|
|
2636
|
+
### Related Components in This Category
|
|
2637
|
+
${COMPONENT_CATEGORIES[detectedComponentType]?.slice(0, 8).join(", ") || "See component library"}` : ""}
|
|
2638
|
+
|
|
2639
|
+
## Typography (All Brands)
|
|
1483
2640
|
- **Font Family**: Montserrat
|
|
1484
2641
|
- **Headlines**: font-weight 300 (Light) or 500 (Medium)
|
|
1485
2642
|
- **Body**: font-weight 400 (Regular), line-height 1.6
|
|
@@ -1501,13 +2658,23 @@ Now design the **${component_description}** with **BOTH light and dark mode supp
|
|
|
1501
2658
|
|
|
1502
2659
|
**Brand**: ${brand.toUpperCase()}
|
|
1503
2660
|
**Theme**: ${theme.charAt(0).toUpperCase() + theme.slice(1)}
|
|
1504
|
-
**Output Type**: ${detectedOutputType.toUpperCase()}
|
|
2661
|
+
**Output Type**: ${detectedOutputType.toUpperCase()}${detectedComponentType !== "general" && detectedOutputType === "ui" ? `
|
|
2662
|
+
**Component Type**: ${detectedComponentType.charAt(0).toUpperCase() + detectedComponentType.slice(1)}` : ""}
|
|
1505
2663
|
**Logo**: ${logoPreferenceLabel}
|
|
1506
2664
|
${defaultsNotice}
|
|
1507
2665
|
${singleThemeTokens}
|
|
1508
2666
|
|
|
1509
2667
|
${outputTypeGuidance[detectedOutputType]}
|
|
1510
2668
|
|
|
2669
|
+
${detectedComponentType !== "general" && detectedOutputType === "ui" ? `---
|
|
2670
|
+
|
|
2671
|
+
## Component Type: ${detectedComponentType.charAt(0).toUpperCase() + detectedComponentType.slice(1)}
|
|
2672
|
+
|
|
2673
|
+
${COMPONENT_TYPE_GUIDANCE[detectedComponentType]}
|
|
2674
|
+
|
|
2675
|
+
### Related Components in This Category
|
|
2676
|
+
${COMPONENT_CATEGORIES[detectedComponentType]?.slice(0, 8).join(", ") || "See component library"}` : ""}
|
|
2677
|
+
|
|
1511
2678
|
## Typography (All Brands)
|
|
1512
2679
|
- **Font Family**: Montserrat
|
|
1513
2680
|
- **Headlines**: font-weight 300 (Light) or 500 (Medium)
|
|
@@ -1524,8 +2691,35 @@ ${outputTypeGuidance[detectedOutputType]}
|
|
|
1524
2691
|
|
|
1525
2692
|
Now design the **${component_description}** following these tokens and principles.`;
|
|
1526
2693
|
}
|
|
2694
|
+
// Embed logos in response
|
|
2695
|
+
const contentBlocks = [];
|
|
2696
|
+
if (isDualThemeMode) {
|
|
2697
|
+
// Dual-theme mode: embed both light and dark theme logos
|
|
2698
|
+
const lightLogoPath = logoMap[logoPreference]?.light || logoMap.default.light;
|
|
2699
|
+
const darkLogoPath = logoMap[logoPreference]?.dark || logoMap.default.dark;
|
|
2700
|
+
const lightLogo = await embedLogo(lightLogoPath);
|
|
2701
|
+
const darkLogo = await embedLogo(darkLogoPath);
|
|
2702
|
+
if (lightLogo) {
|
|
2703
|
+
contentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Path:** \`${lightLogoPath}\`` });
|
|
2704
|
+
contentBlocks.push(lightLogo);
|
|
2705
|
+
}
|
|
2706
|
+
if (darkLogo) {
|
|
2707
|
+
contentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Path:** \`${darkLogoPath}\`` });
|
|
2708
|
+
contentBlocks.push(darkLogo);
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
// Single-theme mode: embed the appropriate logo
|
|
2713
|
+
const embeddedLogo = await embedLogo(logoPath);
|
|
2714
|
+
if (embeddedLogo) {
|
|
2715
|
+
contentBlocks.push({ type: "text", text: `## Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})\n**Path:** \`${logoPath}\`` });
|
|
2716
|
+
contentBlocks.push(embeddedLogo);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
// Add the main response text
|
|
2720
|
+
contentBlocks.push({ type: "text", text: response });
|
|
1527
2721
|
return {
|
|
1528
|
-
content:
|
|
2722
|
+
content: contentBlocks,
|
|
1529
2723
|
};
|
|
1530
2724
|
}
|
|
1531
2725
|
case "evaluate_design": {
|
|
@@ -1800,10 +2994,148 @@ Now design the **${component_description}** following these tokens and principle
|
|
|
1800
2994
|
tierLabel = "Rebuild Required";
|
|
1801
2995
|
deliverable = false;
|
|
1802
2996
|
}
|
|
1803
|
-
//
|
|
1804
|
-
const
|
|
2997
|
+
// Fix suggestions for common issues by check key
|
|
2998
|
+
const fixSuggestions = {
|
|
2999
|
+
// UI fixes
|
|
3000
|
+
usesSemantic: `**Fix: Use Semantic Colors**
|
|
3001
|
+
\`\`\`jsx
|
|
3002
|
+
// Instead of: bg-[#343d46] or bg-blue-500
|
|
3003
|
+
// Use: bg-primary or bg-background
|
|
3004
|
+
<div className="bg-primary text-primary-foreground">
|
|
3005
|
+
<div className="bg-background text-foreground">
|
|
3006
|
+
\`\`\``,
|
|
3007
|
+
noHardcodedColors: `**Fix: Replace Hardcoded Hex Colors**
|
|
3008
|
+
\`\`\`jsx
|
|
3009
|
+
// Instead of: className="bg-[#343d46] text-[#ffffff]"
|
|
3010
|
+
// Use CSS variables: className="bg-primary text-primary-foreground"
|
|
3011
|
+
// Or in CSS: var(--primary), var(--foreground)
|
|
3012
|
+
\`\`\``,
|
|
3013
|
+
usesCorrectRadius: `**Fix: Use Sharp Corners**
|
|
3014
|
+
\`\`\`jsx
|
|
3015
|
+
// Instead of: rounded-lg, rounded-xl, rounded-full
|
|
3016
|
+
// Use: rounded-sm (2px) or rounded-none
|
|
3017
|
+
<button className="rounded-sm bg-primary px-6 py-3">
|
|
3018
|
+
\`\`\``,
|
|
3019
|
+
usesMontserrat: `**Fix: Configure Montserrat Font**
|
|
3020
|
+
\`\`\`jsx
|
|
3021
|
+
// In globals.css or layout:
|
|
3022
|
+
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;700&display=swap');
|
|
3023
|
+
|
|
3024
|
+
// In Tailwind config:
|
|
3025
|
+
fontFamily: { sans: ['Montserrat', 'sans-serif'] }
|
|
3026
|
+
\`\`\``,
|
|
3027
|
+
noFontBold: `**Fix: Use Lighter Font Weights**
|
|
3028
|
+
\`\`\`jsx
|
|
3029
|
+
// Instead of: font-bold (700)
|
|
3030
|
+
// Use: font-light (300), font-normal (400), or font-medium (500)
|
|
3031
|
+
<h1 className="font-light text-4xl">Heading</h1>
|
|
3032
|
+
<h2 className="font-medium text-2xl">Subheading</h2>
|
|
3033
|
+
\`\`\``,
|
|
3034
|
+
hasDarkMode: `**Fix: Add Dark Mode Support**
|
|
3035
|
+
\`\`\`jsx
|
|
3036
|
+
// Use Tailwind dark: prefix
|
|
3037
|
+
<div className="bg-white dark:bg-sonance-charcoal text-gray-900 dark:text-white">
|
|
3038
|
+
|
|
3039
|
+
// Or use CSS variables that change with theme
|
|
3040
|
+
<div className="bg-background text-foreground">
|
|
3041
|
+
\`\`\``,
|
|
3042
|
+
hasGenerousSpacing: `**Fix: Add Generous Spacing**
|
|
3043
|
+
\`\`\`jsx
|
|
3044
|
+
// For sections: py-20 or py-24 (80-96px)
|
|
3045
|
+
<section className="py-20 px-6 lg:py-24 lg:px-8">
|
|
3046
|
+
|
|
3047
|
+
// For buttons: px-6 py-3
|
|
3048
|
+
<button className="px-6 py-3">
|
|
3049
|
+
|
|
3050
|
+
// For cards: p-6 or p-8
|
|
3051
|
+
<div className="p-6 lg:p-8">
|
|
3052
|
+
\`\`\``,
|
|
3053
|
+
hasHoverStates: `**Fix: Add Hover States**
|
|
3054
|
+
\`\`\`jsx
|
|
3055
|
+
<button className="bg-primary hover:bg-primary/90 transition-colors">
|
|
3056
|
+
<a className="text-primary hover:text-primary/80 hover:underline">
|
|
3057
|
+
\`\`\``,
|
|
3058
|
+
hasFocusStates: `**Fix: Add Focus States**
|
|
3059
|
+
\`\`\`jsx
|
|
3060
|
+
<button className="focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2">
|
|
3061
|
+
<input className="focus-visible:ring-2 focus-visible:ring-primary focus-visible:border-primary">
|
|
3062
|
+
\`\`\``,
|
|
3063
|
+
hasAccessibility: `**Fix: Add Accessibility Attributes**
|
|
3064
|
+
\`\`\`jsx
|
|
3065
|
+
<button aria-label="Close dialog">
|
|
3066
|
+
<img alt="Product image description" />
|
|
3067
|
+
<nav role="navigation" aria-label="Main menu">
|
|
3068
|
+
\`\`\``,
|
|
3069
|
+
hasTransitions: `**Fix: Add Smooth Transitions**
|
|
3070
|
+
\`\`\`jsx
|
|
3071
|
+
// For color changes
|
|
3072
|
+
<button className="transition-colors duration-200">
|
|
3073
|
+
|
|
3074
|
+
// For all properties
|
|
3075
|
+
<div className="transition-all duration-300 ease-out">
|
|
3076
|
+
\`\`\``,
|
|
3077
|
+
// Doc fixes
|
|
3078
|
+
usesPrimaryColor: `**Fix: Use ${evalPalette.name} Primary Color**
|
|
3079
|
+
\`\`\`python
|
|
3080
|
+
# python-docx
|
|
3081
|
+
from docx.shared import RGBColor
|
|
3082
|
+
CHARCOAL = RGBColor(0x34, 0x3D, 0x46)
|
|
3083
|
+
run.font.color.rgb = CHARCOAL
|
|
3084
|
+
|
|
3085
|
+
# ReportLab
|
|
3086
|
+
from reportlab.lib.colors import HexColor
|
|
3087
|
+
CHARCOAL = HexColor('#343d46')
|
|
3088
|
+
\`\`\``,
|
|
3089
|
+
hasFontConfig: `**Fix: Configure Montserrat Font**
|
|
3090
|
+
\`\`\`python
|
|
3091
|
+
# python-docx
|
|
3092
|
+
run.font.name = 'Montserrat'
|
|
3093
|
+
# Or fallback
|
|
3094
|
+
run.font.name = 'Arial'
|
|
3095
|
+
|
|
3096
|
+
# ReportLab
|
|
3097
|
+
from reportlab.pdfbase.ttfonts import TTFont
|
|
3098
|
+
pdfmetrics.registerFont(TTFont('Montserrat', 'Montserrat-Regular.ttf'))
|
|
3099
|
+
\`\`\``,
|
|
3100
|
+
usesProperWeights: `**Fix: Use Correct Font Weights**
|
|
3101
|
+
\`\`\`python
|
|
3102
|
+
# DON'T use bold=True for headings
|
|
3103
|
+
# Instead, use medium weight or increase font size
|
|
3104
|
+
|
|
3105
|
+
# python-docx
|
|
3106
|
+
run.font.bold = False # Use font size for hierarchy instead
|
|
3107
|
+
heading_run.font.size = Pt(24) # Large size = visual weight
|
|
3108
|
+
\`\`\``,
|
|
3109
|
+
// Marketing fixes
|
|
3110
|
+
usesInlineColors: `**Fix: Use Inline Brand Colors**
|
|
3111
|
+
\`\`\`html
|
|
3112
|
+
<td style="background-color: #343d46; color: #ffffff;">
|
|
3113
|
+
<a style="color: #00A3E1;">
|
|
3114
|
+
\`\`\``,
|
|
3115
|
+
hasFallbackFonts: `**Fix: Add Font Fallbacks**
|
|
3116
|
+
\`\`\`html
|
|
3117
|
+
style="font-family: 'Montserrat', Arial, Helvetica, sans-serif;"
|
|
3118
|
+
\`\`\``,
|
|
3119
|
+
usesTableLayout: `**Fix: Use Table-Based Layout**
|
|
3120
|
+
\`\`\`html
|
|
3121
|
+
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600">
|
|
3122
|
+
<tr>
|
|
3123
|
+
<td align="center" style="padding: 20px;">
|
|
3124
|
+
Content here
|
|
3125
|
+
</td>
|
|
3126
|
+
</tr>
|
|
3127
|
+
</table>
|
|
3128
|
+
\`\`\``,
|
|
3129
|
+
};
|
|
3130
|
+
// Build gaps list from failed checks with fix suggestions
|
|
3131
|
+
const gapsWithFixes = checkResults
|
|
1805
3132
|
.filter(c => !c.passed)
|
|
1806
|
-
.map(c =>
|
|
3133
|
+
.map(c => ({
|
|
3134
|
+
description: c.description,
|
|
3135
|
+
key: c.check,
|
|
3136
|
+
fix: fixSuggestions[c.check] || null
|
|
3137
|
+
}));
|
|
3138
|
+
const gaps = gapsWithFixes.map(g => g.description);
|
|
1807
3139
|
// Group results by category
|
|
1808
3140
|
const byCategory = {};
|
|
1809
3141
|
for (const result of checkResults) {
|
|
@@ -1846,7 +3178,8 @@ ${categoryScores.map(c => `| ${c.category} | ${c.earned} | ${c.max} | ${c.checkM
|
|
|
1846
3178
|
|
|
1847
3179
|
${gaps.length > 0 ? `## Gaps to Address
|
|
1848
3180
|
|
|
1849
|
-
${
|
|
3181
|
+
${gapsWithFixes.map((gap, i) => `### ${i + 1}. ${gap.description}
|
|
3182
|
+
${gap.fix ? `\n${gap.fix}\n` : ""}`).join('\n')}
|
|
1850
3183
|
|
|
1851
3184
|
---` : "## No major gaps identified — excellent work!"}
|
|
1852
3185
|
|
|
@@ -1854,7 +3187,9 @@ ${gaps.map((gap, i) => `${i + 1}. ${gap}`).join('\n')}
|
|
|
1854
3187
|
|
|
1855
3188
|
${deliverable
|
|
1856
3189
|
? "This output meets Sonance brand standards. Consider polish improvements for Tier 5."
|
|
1857
|
-
: `Iterate on the gaps above to reach Tier 4 (85+). Current gap: ${85 - totalScore} points
|
|
3190
|
+
: `Iterate on the gaps above to reach Tier 4 (85+). Current gap: ${85 - totalScore} points.
|
|
3191
|
+
|
|
3192
|
+
> **Tip**: Use \`design_component\` or \`redesign_app\` tools for comprehensive guidance on implementing these fixes.`}
|
|
1858
3193
|
`;
|
|
1859
3194
|
return {
|
|
1860
3195
|
content: [{ type: "text", text: evalResponse }],
|
|
@@ -2668,8 +4003,713 @@ ${rebrandArgs.source_format === "word" ? `
|
|
|
2668
4003
|
|
|
2669
4004
|
**Remember:** The goal is to make the document unmistakably ${palette.name} while preserving its original structure and content.
|
|
2670
4005
|
`;
|
|
4006
|
+
// Embed logos for rebrand_document
|
|
4007
|
+
// Use the Sonance+James+IPORT lockup as default for Sonance brand
|
|
4008
|
+
const rebrandLogoMap = {
|
|
4009
|
+
sonance: {
|
|
4010
|
+
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
4011
|
+
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
4012
|
+
},
|
|
4013
|
+
iport: {
|
|
4014
|
+
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
4015
|
+
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
4016
|
+
},
|
|
4017
|
+
blaze: {
|
|
4018
|
+
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
4019
|
+
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
4020
|
+
},
|
|
4021
|
+
};
|
|
4022
|
+
const rebrandContentBlocks = [];
|
|
4023
|
+
// For documents, provide both light and dark logo variants
|
|
4024
|
+
const lightLogoPath = rebrandLogoMap[targetBrand].light;
|
|
4025
|
+
const darkLogoPath = rebrandLogoMap[targetBrand].dark;
|
|
4026
|
+
const lightLogo = await embedLogo(lightLogoPath);
|
|
4027
|
+
const darkLogo = await embedLogo(darkLogoPath);
|
|
4028
|
+
if (lightLogo) {
|
|
4029
|
+
rebrandContentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Use on white/light document backgrounds**\n**Path:** \`${lightLogoPath}\`` });
|
|
4030
|
+
rebrandContentBlocks.push(lightLogo);
|
|
4031
|
+
}
|
|
4032
|
+
if (darkLogo) {
|
|
4033
|
+
rebrandContentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Use on dark/colored document backgrounds**\n**Path:** \`${darkLogoPath}\`` });
|
|
4034
|
+
rebrandContentBlocks.push(darkLogo);
|
|
4035
|
+
}
|
|
4036
|
+
// Add the main guide text
|
|
4037
|
+
rebrandContentBlocks.push({ type: "text", text: rebrandGuide });
|
|
2671
4038
|
return {
|
|
2672
|
-
content:
|
|
4039
|
+
content: rebrandContentBlocks,
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
4042
|
+
// ============================================
|
|
4043
|
+
// NEW APP DEVELOPMENT TOOLS
|
|
4044
|
+
// ============================================
|
|
4045
|
+
case "design_app": {
|
|
4046
|
+
const designAppArgs = args;
|
|
4047
|
+
const brand = designAppArgs.brand || "sonance";
|
|
4048
|
+
const appType = designAppArgs.app_type || "general";
|
|
4049
|
+
const features = designAppArgs.features || ["forms", "navigation"]; // Core features by default
|
|
4050
|
+
const framework = designAppArgs.framework || "nextjs";
|
|
4051
|
+
const palette = BRAND_PALETTES[brand];
|
|
4052
|
+
// Determine which components to include based on features
|
|
4053
|
+
const neededComponents = new Set();
|
|
4054
|
+
// Always include core components
|
|
4055
|
+
neededComponents.add("button");
|
|
4056
|
+
neededComponents.add("card");
|
|
4057
|
+
for (const feature of features) {
|
|
4058
|
+
const featureComponents = FEATURE_TO_COMPONENTS[feature] || [];
|
|
4059
|
+
for (const comp of featureComponents) {
|
|
4060
|
+
neededComponents.add(comp);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
// Add app-type specific components
|
|
4064
|
+
if (appType === "dashboard") {
|
|
4065
|
+
["card", "chart", "table", "badge", "tabs"].forEach(c => neededComponents.add(c));
|
|
4066
|
+
}
|
|
4067
|
+
else if (appType === "marketing") {
|
|
4068
|
+
["button", "card", "badge", "image"].forEach(c => neededComponents.add(c));
|
|
4069
|
+
}
|
|
4070
|
+
// Read the components
|
|
4071
|
+
const componentsDir = getAssetPath("components");
|
|
4072
|
+
const componentSources = [];
|
|
4073
|
+
for (const compName of neededComponents) {
|
|
4074
|
+
const filePath = path.join(componentsDir, `${compName}.tsx`);
|
|
4075
|
+
if (fs.existsSync(filePath)) {
|
|
4076
|
+
try {
|
|
4077
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
4078
|
+
componentSources.push({ name: compName, source });
|
|
4079
|
+
}
|
|
4080
|
+
catch {
|
|
4081
|
+
// Skip if can't read
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
// Read CSS theme
|
|
4086
|
+
let cssTheme = "";
|
|
4087
|
+
try {
|
|
4088
|
+
cssTheme = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
4089
|
+
}
|
|
4090
|
+
catch {
|
|
4091
|
+
cssTheme = "/* Could not load CSS theme */";
|
|
4092
|
+
}
|
|
4093
|
+
// Read utils
|
|
4094
|
+
let utils = "";
|
|
4095
|
+
try {
|
|
4096
|
+
utils = fs.readFileSync(getAssetPath("utils"), "utf-8");
|
|
4097
|
+
}
|
|
4098
|
+
catch {
|
|
4099
|
+
utils = "/* Could not load utils */";
|
|
4100
|
+
}
|
|
4101
|
+
// Get page layout template
|
|
4102
|
+
const layout = PAGE_LAYOUTS[appType] || PAGE_LAYOUTS.dashboard;
|
|
4103
|
+
// Build the response
|
|
4104
|
+
const setupInstructions = framework === "nextjs" ? `
|
|
4105
|
+
## Setup Instructions (Next.js)
|
|
4106
|
+
|
|
4107
|
+
1. Create a new Next.js project:
|
|
4108
|
+
\`\`\`bash
|
|
4109
|
+
npx create-next-app@latest my-${brand}-app --typescript --tailwind --eslint --app
|
|
4110
|
+
cd my-${brand}-app
|
|
4111
|
+
\`\`\`
|
|
4112
|
+
|
|
4113
|
+
2. Install dependencies:
|
|
4114
|
+
\`\`\`bash
|
|
4115
|
+
npm install clsx tailwind-merge class-variance-authority lucide-react
|
|
4116
|
+
npm install @radix-ui/react-slot @radix-ui/react-dialog @radix-ui/react-dropdown-menu
|
|
4117
|
+
\`\`\`
|
|
4118
|
+
|
|
4119
|
+
3. Copy the globals.css content below to \`src/app/globals.css\`
|
|
4120
|
+
4. Copy the utils to \`src/lib/utils.ts\`
|
|
4121
|
+
5. Create \`src/components/ui/\` and add the component files
|
|
4122
|
+
` : `
|
|
4123
|
+
## Setup Instructions (React + Vite)
|
|
4124
|
+
|
|
4125
|
+
1. Create a new React project:
|
|
4126
|
+
\`\`\`bash
|
|
4127
|
+
npm create vite@latest my-${brand}-app -- --template react-ts
|
|
4128
|
+
cd my-${brand}-app
|
|
4129
|
+
npm install
|
|
4130
|
+
\`\`\`
|
|
4131
|
+
|
|
4132
|
+
2. Install Tailwind CSS:
|
|
4133
|
+
\`\`\`bash
|
|
4134
|
+
npm install -D tailwindcss postcss autoprefixer
|
|
4135
|
+
npx tailwindcss init -p
|
|
4136
|
+
\`\`\`
|
|
4137
|
+
|
|
4138
|
+
3. Install dependencies:
|
|
4139
|
+
\`\`\`bash
|
|
4140
|
+
npm install clsx tailwind-merge class-variance-authority lucide-react
|
|
4141
|
+
npm install @radix-ui/react-slot @radix-ui/react-dialog
|
|
4142
|
+
\`\`\`
|
|
4143
|
+
|
|
4144
|
+
4. Copy the CSS and component files as described below
|
|
4145
|
+
`;
|
|
4146
|
+
const response = `# ${palette.name} App Starter Kit
|
|
4147
|
+
|
|
4148
|
+
**App Type:** ${appType}
|
|
4149
|
+
**Framework:** ${framework}
|
|
4150
|
+
**Brand:** ${palette.name}
|
|
4151
|
+
**Features:** ${features.join(", ")}
|
|
4152
|
+
**Components Included:** ${componentSources.length}
|
|
4153
|
+
|
|
4154
|
+
---
|
|
4155
|
+
|
|
4156
|
+
${setupInstructions}
|
|
4157
|
+
|
|
4158
|
+
---
|
|
4159
|
+
|
|
4160
|
+
## Global Styles (copy to globals.css)
|
|
4161
|
+
|
|
4162
|
+
\`\`\`css
|
|
4163
|
+
${cssTheme}
|
|
4164
|
+
\`\`\`
|
|
4165
|
+
|
|
4166
|
+
---
|
|
4167
|
+
|
|
4168
|
+
## Utility Functions (copy to lib/utils.ts)
|
|
4169
|
+
|
|
4170
|
+
\`\`\`typescript
|
|
4171
|
+
${utils}
|
|
4172
|
+
\`\`\`
|
|
4173
|
+
|
|
4174
|
+
---
|
|
4175
|
+
|
|
4176
|
+
## Required Components (${componentSources.length} total)
|
|
4177
|
+
|
|
4178
|
+
${componentSources.map(c => `### ${c.name}.tsx
|
|
4179
|
+
|
|
4180
|
+
\`\`\`tsx
|
|
4181
|
+
${c.source}
|
|
4182
|
+
\`\`\``).join("\n\n")}
|
|
4183
|
+
|
|
4184
|
+
---
|
|
4185
|
+
|
|
4186
|
+
## Page Template: ${appType}
|
|
4187
|
+
|
|
4188
|
+
${layout.description}
|
|
4189
|
+
|
|
4190
|
+
${layout.structure}
|
|
4191
|
+
|
|
4192
|
+
**Recommended Layout Classes:**
|
|
4193
|
+
- Grid: \`${layout.gridLayout}\`
|
|
4194
|
+
- Spacing: \`${layout.spacing}\`
|
|
4195
|
+
|
|
4196
|
+
\`\`\`tsx
|
|
4197
|
+
${layout.template}
|
|
4198
|
+
\`\`\`
|
|
4199
|
+
|
|
4200
|
+
---
|
|
4201
|
+
|
|
4202
|
+
## Quick Reference
|
|
4203
|
+
|
|
4204
|
+
### ${palette.name} Brand Colors
|
|
4205
|
+
- **Primary:** ${palette.primary} (bg-primary, text-primary)
|
|
4206
|
+
- **Secondary:** ${palette.secondary}
|
|
4207
|
+
- **Accent:** ${palette.accent}
|
|
4208
|
+
- **Preferred Theme:** ${palette.preferredTheme}
|
|
4209
|
+
|
|
4210
|
+
### Typography
|
|
4211
|
+
- Font: Montserrat
|
|
4212
|
+
- Headlines: font-light (300) or font-medium (500)
|
|
4213
|
+
- Body: font-normal (400)
|
|
4214
|
+
- **Never use font-bold (700) for headlines**
|
|
4215
|
+
|
|
4216
|
+
### Spacing
|
|
4217
|
+
- Section padding: py-20 or py-24
|
|
4218
|
+
- Component gaps: gap-4 to gap-8
|
|
4219
|
+
- Card padding: p-6
|
|
4220
|
+
|
|
4221
|
+
### Border Radius
|
|
4222
|
+
- Use rounded-sm (2px) or rounded-none
|
|
4223
|
+
- **Never use rounded-lg or rounded-xl**
|
|
4224
|
+
|
|
4225
|
+
---
|
|
4226
|
+
|
|
4227
|
+
Now you have everything needed to build a ${palette.name}-branded ${appType} application!
|
|
4228
|
+
`;
|
|
4229
|
+
// Embed logos
|
|
4230
|
+
const logoMap = {
|
|
4231
|
+
sonance: {
|
|
4232
|
+
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
4233
|
+
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
4234
|
+
},
|
|
4235
|
+
iport: {
|
|
4236
|
+
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
4237
|
+
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
4238
|
+
},
|
|
4239
|
+
blaze: {
|
|
4240
|
+
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
4241
|
+
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
4242
|
+
},
|
|
4243
|
+
};
|
|
4244
|
+
const contentBlocks = [];
|
|
4245
|
+
const lightLogo = await embedLogo(logoMap[brand].light);
|
|
4246
|
+
const darkLogo = await embedLogo(logoMap[brand].dark);
|
|
4247
|
+
if (lightLogo) {
|
|
4248
|
+
contentBlocks.push({ type: "text", text: `## Logo for Light Backgrounds\n**Path:** \`${logoMap[brand].light}\`` });
|
|
4249
|
+
contentBlocks.push(lightLogo);
|
|
4250
|
+
}
|
|
4251
|
+
if (darkLogo) {
|
|
4252
|
+
contentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Path:** \`${logoMap[brand].dark}\`` });
|
|
4253
|
+
contentBlocks.push(darkLogo);
|
|
4254
|
+
}
|
|
4255
|
+
contentBlocks.push({ type: "text", text: response });
|
|
4256
|
+
return {
|
|
4257
|
+
content: contentBlocks,
|
|
4258
|
+
};
|
|
4259
|
+
}
|
|
4260
|
+
case "redesign_app": {
|
|
4261
|
+
const redesignArgs = args;
|
|
4262
|
+
if (!redesignArgs.existing_code) {
|
|
4263
|
+
return {
|
|
4264
|
+
content: [{ type: "text", text: "Error: existing_code is required" }],
|
|
4265
|
+
isError: true,
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
const brand = redesignArgs.brand || "sonance";
|
|
4269
|
+
const code = redesignArgs.existing_code;
|
|
4270
|
+
const fileType = redesignArgs.file_type || "component";
|
|
4271
|
+
const filePath = redesignArgs.file_path || "unknown file";
|
|
4272
|
+
const palette = BRAND_PALETTES[brand];
|
|
4273
|
+
const violations = [];
|
|
4274
|
+
const lines = code.split("\n");
|
|
4275
|
+
// Check for hardcoded colors
|
|
4276
|
+
const hardcodedColorPatterns = [
|
|
4277
|
+
{ pattern: /bg-\[#[0-9a-fA-F]{6}\]/g, fix: "bg-primary or bg-secondary" },
|
|
4278
|
+
{ pattern: /text-\[#[0-9a-fA-F]{6}\]/g, fix: "text-foreground or text-primary" },
|
|
4279
|
+
{ pattern: /border-\[#[0-9a-fA-F]{6}\]/g, fix: "border-border or border-primary" },
|
|
4280
|
+
{ pattern: /#[0-9a-fA-F]{6}/g, fix: "Use CSS variable: var(--primary), var(--secondary), etc." },
|
|
4281
|
+
];
|
|
4282
|
+
// Check for non-brand Tailwind colors
|
|
4283
|
+
const nonBrandColors = [
|
|
4284
|
+
{ pattern: /bg-(blue|red|green|yellow|purple|pink|indigo|orange)-\d{2,3}/g, fix: "bg-primary or bg-accent" },
|
|
4285
|
+
{ pattern: /text-(blue|red|green|yellow|purple|pink|indigo|orange)-\d{2,3}/g, fix: "text-foreground or text-primary" },
|
|
4286
|
+
{ pattern: /border-(blue|red|green|yellow|purple|pink|indigo|orange)-\d{2,3}/g, fix: "border-border" },
|
|
4287
|
+
];
|
|
4288
|
+
// Check for wrong border radius
|
|
4289
|
+
const radiusPatterns = [
|
|
4290
|
+
{ pattern: /rounded-(lg|xl|2xl|3xl|full)/g, fix: "rounded-sm (2px) or rounded-none" },
|
|
4291
|
+
];
|
|
4292
|
+
// Check for wrong font weights
|
|
4293
|
+
const fontPatterns = [
|
|
4294
|
+
{ pattern: /font-bold/g, fix: "font-medium (500) or font-light (300) for headlines" },
|
|
4295
|
+
];
|
|
4296
|
+
// Check for missing dark mode
|
|
4297
|
+
const hasDarkMode = /dark:|\.dark\s|data-theme/i.test(code);
|
|
4298
|
+
// Check for missing hover states
|
|
4299
|
+
const hasHoverStates = /hover:/i.test(code);
|
|
4300
|
+
const hasFocusStates = /focus:|focus-visible:/i.test(code);
|
|
4301
|
+
// Scan each line
|
|
4302
|
+
lines.forEach((line, index) => {
|
|
4303
|
+
const lineNum = index + 1;
|
|
4304
|
+
for (const { pattern, fix } of hardcodedColorPatterns) {
|
|
4305
|
+
const matches = line.match(pattern);
|
|
4306
|
+
if (matches) {
|
|
4307
|
+
for (const match of matches) {
|
|
4308
|
+
violations.push({
|
|
4309
|
+
type: "hardcoded_color",
|
|
4310
|
+
severity: "critical",
|
|
4311
|
+
line: lineNum,
|
|
4312
|
+
match,
|
|
4313
|
+
fix,
|
|
4314
|
+
});
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
}
|
|
4318
|
+
for (const { pattern, fix } of nonBrandColors) {
|
|
4319
|
+
const matches = line.match(pattern);
|
|
4320
|
+
if (matches) {
|
|
4321
|
+
for (const match of matches) {
|
|
4322
|
+
violations.push({
|
|
4323
|
+
type: "non_brand_color",
|
|
4324
|
+
severity: "critical",
|
|
4325
|
+
line: lineNum,
|
|
4326
|
+
match,
|
|
4327
|
+
fix,
|
|
4328
|
+
});
|
|
4329
|
+
}
|
|
4330
|
+
}
|
|
4331
|
+
}
|
|
4332
|
+
for (const { pattern, fix } of radiusPatterns) {
|
|
4333
|
+
const matches = line.match(pattern);
|
|
4334
|
+
if (matches) {
|
|
4335
|
+
for (const match of matches) {
|
|
4336
|
+
violations.push({
|
|
4337
|
+
type: "wrong_radius",
|
|
4338
|
+
severity: "critical",
|
|
4339
|
+
line: lineNum,
|
|
4340
|
+
match,
|
|
4341
|
+
fix,
|
|
4342
|
+
});
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
}
|
|
4346
|
+
for (const { pattern, fix } of fontPatterns) {
|
|
4347
|
+
const matches = line.match(pattern);
|
|
4348
|
+
if (matches) {
|
|
4349
|
+
for (const match of matches) {
|
|
4350
|
+
violations.push({
|
|
4351
|
+
type: "wrong_font",
|
|
4352
|
+
severity: "critical",
|
|
4353
|
+
line: lineNum,
|
|
4354
|
+
match,
|
|
4355
|
+
fix,
|
|
4356
|
+
});
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
});
|
|
4361
|
+
// Add warnings
|
|
4362
|
+
if (!hasDarkMode) {
|
|
4363
|
+
violations.push({
|
|
4364
|
+
type: "missing_dark_mode",
|
|
4365
|
+
severity: "warning",
|
|
4366
|
+
match: "No dark mode support",
|
|
4367
|
+
fix: "Add dark: variants for theme switching",
|
|
4368
|
+
});
|
|
4369
|
+
}
|
|
4370
|
+
if (!hasHoverStates) {
|
|
4371
|
+
violations.push({
|
|
4372
|
+
type: "missing_hover",
|
|
4373
|
+
severity: "warning",
|
|
4374
|
+
match: "No hover states found",
|
|
4375
|
+
fix: "Add hover: variants to interactive elements",
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
if (!hasFocusStates) {
|
|
4379
|
+
violations.push({
|
|
4380
|
+
type: "missing_focus",
|
|
4381
|
+
severity: "warning",
|
|
4382
|
+
match: "No focus states found",
|
|
4383
|
+
fix: "Add focus-visible:ring-2 focus-visible:ring-primary to interactive elements",
|
|
4384
|
+
});
|
|
4385
|
+
}
|
|
4386
|
+
// Group violations
|
|
4387
|
+
const criticalViolations = violations.filter(v => v.severity === "critical");
|
|
4388
|
+
const warnings = violations.filter(v => v.severity === "warning");
|
|
4389
|
+
// Generate transformation code
|
|
4390
|
+
let transformedCode = code;
|
|
4391
|
+
// Apply fixes
|
|
4392
|
+
transformedCode = transformedCode
|
|
4393
|
+
.replace(/rounded-(lg|xl|2xl|3xl)/g, "rounded-sm")
|
|
4394
|
+
.replace(/rounded-full/g, "rounded-sm")
|
|
4395
|
+
.replace(/font-bold/g, "font-medium")
|
|
4396
|
+
.replace(/bg-blue-\d{2,3}/g, "bg-primary")
|
|
4397
|
+
.replace(/bg-red-\d{2,3}/g, "bg-destructive")
|
|
4398
|
+
.replace(/bg-green-\d{2,3}/g, "bg-primary")
|
|
4399
|
+
.replace(/text-blue-\d{2,3}/g, "text-primary")
|
|
4400
|
+
.replace(/text-gray-900/g, "text-foreground")
|
|
4401
|
+
.replace(/text-gray-500/g, "text-muted-foreground")
|
|
4402
|
+
.replace(/border-gray-\d{2,3}/g, "border-border");
|
|
4403
|
+
const response = `# Redesign Analysis: ${filePath}
|
|
4404
|
+
|
|
4405
|
+
**Target Brand:** ${palette.name}
|
|
4406
|
+
**File Type:** ${fileType}
|
|
4407
|
+
**Issues Found:** ${violations.length} (${criticalViolations.length} critical, ${warnings.length} warnings)
|
|
4408
|
+
|
|
4409
|
+
---
|
|
4410
|
+
|
|
4411
|
+
## Detected Issues
|
|
4412
|
+
|
|
4413
|
+
### Critical (Must Fix)
|
|
4414
|
+
|
|
4415
|
+
${criticalViolations.length > 0 ? criticalViolations.map(v => `
|
|
4416
|
+
**${v.type.replace(/_/g, " ")}**
|
|
4417
|
+
- Line ${v.line}: \`${v.match}\`
|
|
4418
|
+
- Fix: ${v.fix}
|
|
4419
|
+
`).join("\n") : "No critical issues found."}
|
|
4420
|
+
|
|
4421
|
+
### Warnings
|
|
4422
|
+
|
|
4423
|
+
${warnings.length > 0 ? warnings.map(v => `
|
|
4424
|
+
- **${v.type.replace(/_/g, " ")}**: ${v.match}
|
|
4425
|
+
- Fix: ${v.fix}
|
|
4426
|
+
`).join("\n") : "No warnings."}
|
|
4427
|
+
|
|
4428
|
+
---
|
|
4429
|
+
|
|
4430
|
+
## Transformation Code
|
|
4431
|
+
|
|
4432
|
+
### Before
|
|
4433
|
+
\`\`\`tsx
|
|
4434
|
+
${code.substring(0, 500)}${code.length > 500 ? "\n// ... (truncated)" : ""}
|
|
4435
|
+
\`\`\`
|
|
4436
|
+
|
|
4437
|
+
### After
|
|
4438
|
+
\`\`\`tsx
|
|
4439
|
+
${transformedCode.substring(0, 500)}${transformedCode.length > 500 ? "\n// ... (truncated)" : ""}
|
|
4440
|
+
\`\`\`
|
|
4441
|
+
|
|
4442
|
+
---
|
|
4443
|
+
|
|
4444
|
+
## Required CSS Variables
|
|
4445
|
+
|
|
4446
|
+
Add these to your globals.css if not present:
|
|
4447
|
+
|
|
4448
|
+
\`\`\`css
|
|
4449
|
+
:root {
|
|
4450
|
+
--background: #ffffff;
|
|
4451
|
+
--foreground: ${palette.primary};
|
|
4452
|
+
--primary: ${palette.primary};
|
|
4453
|
+
--primary-foreground: #ffffff;
|
|
4454
|
+
--secondary: ${palette.secondary};
|
|
4455
|
+
--secondary-foreground: ${palette.primary};
|
|
4456
|
+
--muted: ${palette.secondary};
|
|
4457
|
+
--muted-foreground: #64748b;
|
|
4458
|
+
--accent: ${palette.accent};
|
|
4459
|
+
--accent-foreground: #ffffff;
|
|
4460
|
+
--destructive: #ef4444;
|
|
4461
|
+
--border: ${palette.secondary};
|
|
4462
|
+
--input: ${palette.secondary};
|
|
4463
|
+
--ring: ${palette.accent};
|
|
4464
|
+
--radius: 0.125rem; /* 2px - sharp corners */
|
|
4465
|
+
}
|
|
4466
|
+
|
|
4467
|
+
.dark {
|
|
4468
|
+
--background: ${palette.preferredTheme === "dark" ? palette.primary : "#1a1a1a"};
|
|
4469
|
+
--foreground: #ffffff;
|
|
4470
|
+
--primary: ${palette.accent};
|
|
4471
|
+
--primary-foreground: ${palette.primary};
|
|
4472
|
+
--muted: #333333;
|
|
4473
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
4474
|
+
}
|
|
4475
|
+
\`\`\`
|
|
4476
|
+
|
|
4477
|
+
---
|
|
4478
|
+
|
|
4479
|
+
## Step-by-Step Transformation Plan
|
|
4480
|
+
|
|
4481
|
+
### Step 1: Update globals.css
|
|
4482
|
+
Copy the CSS variables above to your globals.css file.
|
|
4483
|
+
|
|
4484
|
+
### Step 2: Find and Replace Colors
|
|
4485
|
+
Use your editor's find/replace to apply these changes:
|
|
4486
|
+
|
|
4487
|
+
| Find | Replace |
|
|
4488
|
+
|------|---------|
|
|
4489
|
+
| \`bg-blue-*\` | \`bg-primary\` |
|
|
4490
|
+
| \`bg-gray-100\` | \`bg-muted\` |
|
|
4491
|
+
| \`text-gray-900\` | \`text-foreground\` |
|
|
4492
|
+
| \`text-gray-500\` | \`text-muted-foreground\` |
|
|
4493
|
+
| \`border-gray-*\` | \`border-border\` |
|
|
4494
|
+
|
|
4495
|
+
### Step 3: Fix Border Radius
|
|
4496
|
+
| Find | Replace |
|
|
4497
|
+
|------|---------|
|
|
4498
|
+
| \`rounded-lg\` | \`rounded-sm\` |
|
|
4499
|
+
| \`rounded-xl\` | \`rounded-sm\` |
|
|
4500
|
+
| \`rounded-2xl\` | \`rounded-sm\` |
|
|
4501
|
+
|
|
4502
|
+
### Step 4: Fix Font Weights
|
|
4503
|
+
| Find | Replace |
|
|
4504
|
+
|------|---------|
|
|
4505
|
+
| \`font-bold\` (in headlines) | \`font-medium\` or \`font-light\` |
|
|
4506
|
+
|
|
4507
|
+
### Step 5: Add Dark Mode
|
|
4508
|
+
Add \`dark:\` variants to your classes:
|
|
4509
|
+
\`\`\`tsx
|
|
4510
|
+
className="bg-white dark:bg-background text-foreground dark:text-white"
|
|
4511
|
+
\`\`\`
|
|
4512
|
+
|
|
4513
|
+
### Step 6: Add Hover/Focus States
|
|
4514
|
+
\`\`\`tsx
|
|
4515
|
+
className="hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-primary"
|
|
4516
|
+
\`\`\`
|
|
4517
|
+
|
|
4518
|
+
---
|
|
4519
|
+
|
|
4520
|
+
## Verification Checklist
|
|
4521
|
+
|
|
4522
|
+
- [ ] All hardcoded colors replaced with semantic variables
|
|
4523
|
+
- [ ] Border radius is rounded-sm or rounded-none
|
|
4524
|
+
- [ ] Font weights are 300, 400, or 500 (no 700/bold for headlines)
|
|
4525
|
+
- [ ] Dark mode support added
|
|
4526
|
+
- [ ] Hover states on interactive elements
|
|
4527
|
+
- [ ] Focus states for accessibility
|
|
4528
|
+
`;
|
|
4529
|
+
return {
|
|
4530
|
+
content: [{ type: "text", text: response }],
|
|
4531
|
+
};
|
|
4532
|
+
}
|
|
4533
|
+
case "analyze_for_redesign": {
|
|
4534
|
+
const analyzeArgs = args;
|
|
4535
|
+
if (!analyzeArgs.files || analyzeArgs.files.length === 0) {
|
|
4536
|
+
return {
|
|
4537
|
+
content: [{ type: "text", text: "Error: files array is required with at least one file" }],
|
|
4538
|
+
isError: true,
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
const brand = analyzeArgs.brand || "sonance";
|
|
4542
|
+
const palette = BRAND_PALETTES[brand];
|
|
4543
|
+
const files = analyzeArgs.files;
|
|
4544
|
+
const analyses = [];
|
|
4545
|
+
for (const file of files) {
|
|
4546
|
+
const issues = [];
|
|
4547
|
+
const content = file.content;
|
|
4548
|
+
// Check for various issues
|
|
4549
|
+
if (/#[0-9a-fA-F]{6}/.test(content)) {
|
|
4550
|
+
issues.push("hardcoded colors");
|
|
4551
|
+
}
|
|
4552
|
+
if (/bg-(blue|red|green|yellow|purple|pink|indigo|orange)-\d{2,3}/.test(content)) {
|
|
4553
|
+
issues.push("non-brand Tailwind colors");
|
|
4554
|
+
}
|
|
4555
|
+
if (/rounded-(lg|xl|2xl|3xl|full)/.test(content)) {
|
|
4556
|
+
issues.push("wrong border radius");
|
|
4557
|
+
}
|
|
4558
|
+
if (/font-bold/.test(content)) {
|
|
4559
|
+
issues.push("wrong font weight");
|
|
4560
|
+
}
|
|
4561
|
+
if (!/dark:/.test(content) && /className/.test(content)) {
|
|
4562
|
+
issues.push("missing dark mode");
|
|
4563
|
+
}
|
|
4564
|
+
// Determine priority based on file path and issues
|
|
4565
|
+
let priority = "low";
|
|
4566
|
+
if (file.path.includes("globals.css") || file.path.includes("tailwind.config")) {
|
|
4567
|
+
priority = "critical";
|
|
4568
|
+
}
|
|
4569
|
+
else if (issues.length >= 3) {
|
|
4570
|
+
priority = "high";
|
|
4571
|
+
}
|
|
4572
|
+
else if (issues.length >= 1) {
|
|
4573
|
+
priority = "medium";
|
|
4574
|
+
}
|
|
4575
|
+
analyses.push({
|
|
4576
|
+
path: file.path,
|
|
4577
|
+
priority,
|
|
4578
|
+
issues: issues.length,
|
|
4579
|
+
issueTypes: issues,
|
|
4580
|
+
});
|
|
4581
|
+
}
|
|
4582
|
+
// Sort by priority
|
|
4583
|
+
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
4584
|
+
analyses.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);
|
|
4585
|
+
const totalIssues = analyses.reduce((sum, a) => sum + a.issues, 0);
|
|
4586
|
+
const filesNeedingChanges = analyses.filter(a => a.issues > 0).length;
|
|
4587
|
+
const response = `# Codebase Redesign Plan: ${palette.name}
|
|
4588
|
+
|
|
4589
|
+
## Summary
|
|
4590
|
+
|
|
4591
|
+
- **Files Analyzed:** ${files.length}
|
|
4592
|
+
- **Files Needing Changes:** ${filesNeedingChanges}
|
|
4593
|
+
- **Total Issues:** ${totalIssues}
|
|
4594
|
+
- **Target Brand:** ${palette.name}
|
|
4595
|
+
|
|
4596
|
+
---
|
|
4597
|
+
|
|
4598
|
+
## Priority Order
|
|
4599
|
+
|
|
4600
|
+
${analyses.filter(a => a.issues > 0).map((a, i) => `
|
|
4601
|
+
### ${i + 1}. ${a.path} (${a.priority.toUpperCase()})
|
|
4602
|
+
|
|
4603
|
+
**Issues Found:** ${a.issues}
|
|
4604
|
+
${a.issueTypes.map(t => `- ${t}`).join("\n")}
|
|
4605
|
+
**Action:** ${a.priority === "critical" ? "Update first - affects entire app" : a.priority === "high" ? "Update after critical files" : "Update when convenient"}
|
|
4606
|
+
`).join("\n")}
|
|
4607
|
+
|
|
4608
|
+
---
|
|
4609
|
+
|
|
4610
|
+
## Recommended Workflow
|
|
4611
|
+
|
|
4612
|
+
### Phase 1: Foundation (Critical)
|
|
4613
|
+
${analyses.filter(a => a.priority === "critical").map(a => `1. Update \`${a.path}\``).join("\n") || "No critical files identified."}
|
|
4614
|
+
|
|
4615
|
+
### Phase 2: Components (High Priority)
|
|
4616
|
+
${analyses.filter(a => a.priority === "high").map(a => `- Update \`${a.path}\` (${a.issues} issues)`).join("\n") || "No high priority files."}
|
|
4617
|
+
|
|
4618
|
+
### Phase 3: Polish (Medium/Low)
|
|
4619
|
+
${analyses.filter(a => a.priority === "medium" || a.priority === "low").map(a => `- Update \`${a.path}\``).join("\n") || "No remaining files."}
|
|
4620
|
+
|
|
4621
|
+
---
|
|
4622
|
+
|
|
4623
|
+
## Global Search/Replace Commands
|
|
4624
|
+
|
|
4625
|
+
Run these across your codebase:
|
|
4626
|
+
|
|
4627
|
+
\`\`\`bash
|
|
4628
|
+
# Fix border radius
|
|
4629
|
+
find . -name "*.tsx" -exec sed -i 's/rounded-lg/rounded-sm/g' {} \\;
|
|
4630
|
+
find . -name "*.tsx" -exec sed -i 's/rounded-xl/rounded-sm/g' {} \\;
|
|
4631
|
+
|
|
4632
|
+
# Fix font weights
|
|
4633
|
+
find . -name "*.tsx" -exec sed -i 's/font-bold/font-medium/g' {} \\;
|
|
4634
|
+
|
|
4635
|
+
# Fix common color patterns
|
|
4636
|
+
find . -name "*.tsx" -exec sed -i 's/bg-blue-500/bg-primary/g' {} \\;
|
|
4637
|
+
find . -name "*.tsx" -exec sed -i 's/text-gray-900/text-foreground/g' {} \\;
|
|
4638
|
+
\`\`\`
|
|
4639
|
+
|
|
4640
|
+
---
|
|
4641
|
+
|
|
4642
|
+
## CSS Variables Needed
|
|
4643
|
+
|
|
4644
|
+
Ensure your globals.css includes:
|
|
4645
|
+
|
|
4646
|
+
\`\`\`css
|
|
4647
|
+
:root {
|
|
4648
|
+
--primary: ${palette.primary};
|
|
4649
|
+
--secondary: ${palette.secondary};
|
|
4650
|
+
--accent: ${palette.accent};
|
|
4651
|
+
--background: #ffffff;
|
|
4652
|
+
--foreground: ${palette.primary};
|
|
4653
|
+
--border: ${palette.secondary};
|
|
4654
|
+
--radius: 0.125rem;
|
|
4655
|
+
}
|
|
4656
|
+
\`\`\`
|
|
4657
|
+
|
|
4658
|
+
---
|
|
4659
|
+
|
|
4660
|
+
## Next Steps
|
|
4661
|
+
|
|
4662
|
+
1. Start with \`globals.css\` or \`tailwind.config\` to set up brand colors
|
|
4663
|
+
2. Work through high-priority component files
|
|
4664
|
+
3. Test each component after updating
|
|
4665
|
+
4. Verify dark mode works correctly
|
|
4666
|
+
5. Run \`evaluate_design\` on key components to verify brand compliance
|
|
4667
|
+
|
|
4668
|
+
Use \`redesign_app\` tool on individual files for detailed transformation instructions.
|
|
4669
|
+
`;
|
|
4670
|
+
return {
|
|
4671
|
+
content: [{ type: "text", text: response }],
|
|
4672
|
+
};
|
|
4673
|
+
}
|
|
4674
|
+
case "get_components_by_category": {
|
|
4675
|
+
const categoryArgs = args;
|
|
4676
|
+
const category = categoryArgs.category;
|
|
4677
|
+
if (!category || !COMPONENT_CATEGORIES[category]) {
|
|
4678
|
+
return {
|
|
4679
|
+
content: [{
|
|
4680
|
+
type: "text",
|
|
4681
|
+
text: `Error: Invalid category '${category}'. Available categories: ${Object.keys(COMPONENT_CATEGORIES).join(", ")}`,
|
|
4682
|
+
}],
|
|
4683
|
+
isError: true,
|
|
4684
|
+
};
|
|
4685
|
+
}
|
|
4686
|
+
const components = COMPONENT_CATEGORIES[category];
|
|
4687
|
+
const componentsDir = getAssetPath("components");
|
|
4688
|
+
let response = `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length} components)\n\n`;
|
|
4689
|
+
response += `Use these components for ${category === "forms" ? "building forms and capturing user input" :
|
|
4690
|
+
category === "navigation" ? "navigation and menus" :
|
|
4691
|
+
category === "feedback" ? "user feedback and notifications" :
|
|
4692
|
+
category === "overlays" ? "modals, dialogs, and overlays" :
|
|
4693
|
+
category === "data-display" ? "displaying data and content" :
|
|
4694
|
+
category === "layout" ? "page structure and layouts" :
|
|
4695
|
+
"utility purposes"}.\n\n---\n\n`;
|
|
4696
|
+
for (const compName of components) {
|
|
4697
|
+
const filePath = path.join(componentsDir, `${compName}.tsx`);
|
|
4698
|
+
if (fs.existsSync(filePath)) {
|
|
4699
|
+
try {
|
|
4700
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
4701
|
+
response += `## ${compName}\n\nFile: \`components/ui/${compName}.tsx\`\n\n\`\`\`tsx\n${content}\n\`\`\`\n\n---\n\n`;
|
|
4702
|
+
}
|
|
4703
|
+
catch {
|
|
4704
|
+
response += `## ${compName}\n\n*Could not read component file*\n\n---\n\n`;
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
else {
|
|
4708
|
+
response += `## ${compName}\n\n*Component file not found*\n\n---\n\n`;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return {
|
|
4712
|
+
content: [{ type: "text", text: response }],
|
|
2673
4713
|
};
|
|
2674
4714
|
}
|
|
2675
4715
|
default:
|