sonance-brand-mcp 1.3.107 → 1.3.109
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.
|
@@ -2035,6 +2035,7 @@ export async function POST(request: Request) {
|
|
|
2035
2035
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
2036
2036
|
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
2037
2037
|
let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
|
|
2038
|
+
let smartSearchTopPath: string | null = null; // Top smart search result for fallback
|
|
2038
2039
|
|
|
2039
2040
|
// ========================================================================
|
|
2040
2041
|
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
@@ -2141,7 +2142,7 @@ export async function POST(request: Request) {
|
|
|
2141
2142
|
// IMPROVED: Relative scoring - compare focused element vs smart search
|
|
2142
2143
|
// This prevents false positives from generic element IDs in wrong files
|
|
2143
2144
|
const smartSearchTopScore = searchResults[0]?.score || 0;
|
|
2144
|
-
|
|
2145
|
+
smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
|
|
2145
2146
|
const focusedTopScore = focusedElementHints[0]?.score || 0;
|
|
2146
2147
|
const focusedTopPath = focusedElementHints[0]?.path || null;
|
|
2147
2148
|
|
|
@@ -2510,6 +2511,22 @@ export async function POST(request: Request) {
|
|
|
2510
2511
|
}
|
|
2511
2512
|
}
|
|
2512
2513
|
|
|
2514
|
+
// ========== SMART SEARCH FALLBACK ==========
|
|
2515
|
+
// If we still don't have a good target file (just the page wrapper),
|
|
2516
|
+
// use the smart search top result - it already found relevant files!
|
|
2517
|
+
if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
|
|
2518
|
+
const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
|
|
2519
|
+
if (fs.existsSync(smartSearchFullPath)) {
|
|
2520
|
+
const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
|
|
2521
|
+
actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
|
|
2522
|
+
debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
|
|
2523
|
+
originalFile: pageContext.pageFile,
|
|
2524
|
+
redirectTo: smartSearchTopPath,
|
|
2525
|
+
contentLength: smartSearchContent.length
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2513
2530
|
debugLog("File redirect complete", {
|
|
2514
2531
|
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2515
2532
|
actualTarget: actualTargetFile?.path || 'none',
|
|
@@ -2004,6 +2004,7 @@ export async function POST(request: Request) {
|
|
|
2004
2004
|
let recommendedFile: { path: string; reason: string } | null = null;
|
|
2005
2005
|
let deterministicMatch: { path: string; lineNumber: number } | null = null;
|
|
2006
2006
|
let phase1VisibleText: string[] = []; // Store Phase 1 visible text for use in file scoring
|
|
2007
|
+
let smartSearchTopPath: string | null = null; // Top smart search result for fallback
|
|
2007
2008
|
|
|
2008
2009
|
// ========================================================================
|
|
2009
2010
|
// PHASE 0: Deterministic Element ID Search (Cursor-style explicit selection)
|
|
@@ -2110,7 +2111,7 @@ export async function POST(request: Request) {
|
|
|
2110
2111
|
// IMPROVED: Relative scoring - compare focused element vs smart search
|
|
2111
2112
|
// This prevents false positives from generic element IDs in wrong files
|
|
2112
2113
|
const smartSearchTopScore = searchResults[0]?.score || 0;
|
|
2113
|
-
|
|
2114
|
+
smartSearchTopPath = searchResults[0]?.path || null; // Assign to outer scope variable
|
|
2114
2115
|
const focusedTopScore = focusedElementHints[0]?.score || 0;
|
|
2115
2116
|
const focusedTopPath = focusedElementHints[0]?.path || null;
|
|
2116
2117
|
|
|
@@ -2479,6 +2480,22 @@ export async function POST(request: Request) {
|
|
|
2479
2480
|
}
|
|
2480
2481
|
}
|
|
2481
2482
|
|
|
2483
|
+
// ========== SMART SEARCH FALLBACK ==========
|
|
2484
|
+
// If we still don't have a good target file (just the page wrapper),
|
|
2485
|
+
// use the smart search top result - it already found relevant files!
|
|
2486
|
+
if (actualTargetFile && actualTargetFile.path === pageContext.pageFile && smartSearchTopPath) {
|
|
2487
|
+
const smartSearchFullPath = path.join(projectRoot, smartSearchTopPath);
|
|
2488
|
+
if (fs.existsSync(smartSearchFullPath)) {
|
|
2489
|
+
const smartSearchContent = fs.readFileSync(smartSearchFullPath, 'utf-8');
|
|
2490
|
+
actualTargetFile = { path: smartSearchTopPath, content: smartSearchContent };
|
|
2491
|
+
debugLog("SMART SEARCH FALLBACK: Using top smart search result as target", {
|
|
2492
|
+
originalFile: pageContext.pageFile,
|
|
2493
|
+
redirectTo: smartSearchTopPath,
|
|
2494
|
+
contentLength: smartSearchContent.length
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2482
2499
|
debugLog("File redirect complete", {
|
|
2483
2500
|
originalRecommended: recommendedFileContent?.path || 'none',
|
|
2484
2501
|
actualTarget: actualTargetFile?.path || 'none',
|
package/dist/index.js
CHANGED
|
@@ -1397,33 +1397,145 @@ function getImageMimeType(filePath) {
|
|
|
1397
1397
|
default: return 'application/octet-stream';
|
|
1398
1398
|
}
|
|
1399
1399
|
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Centralized logo path mapping for all brands
|
|
1402
|
+
* Light = logo for light backgrounds (typically darker logo)
|
|
1403
|
+
* Dark = logo for dark backgrounds (typically lighter/reversed logo)
|
|
1404
|
+
*/
|
|
1405
|
+
const LOGO_MAP = {
|
|
1406
|
+
default: {
|
|
1407
|
+
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
1408
|
+
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
1409
|
+
},
|
|
1410
|
+
sonance: {
|
|
1411
|
+
light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
|
|
1412
|
+
dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
|
|
1413
|
+
},
|
|
1414
|
+
iport: {
|
|
1415
|
+
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
1416
|
+
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
1417
|
+
},
|
|
1418
|
+
blaze: {
|
|
1419
|
+
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
1420
|
+
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
1421
|
+
},
|
|
1422
|
+
james: {
|
|
1423
|
+
light: "/logos/james/James_Logo_Black_RGB.png",
|
|
1424
|
+
dark: "/logos/james/James_Logo_Reverse_RGB.png",
|
|
1425
|
+
},
|
|
1426
|
+
};
|
|
1427
|
+
// Validation state for logo map
|
|
1428
|
+
let logoMapValidated = false;
|
|
1429
|
+
let logoMapValidationErrors = [];
|
|
1430
|
+
/**
|
|
1431
|
+
* Validate that all logos in LOGO_MAP exist
|
|
1432
|
+
* Called once on first logo-using tool invocation
|
|
1433
|
+
*/
|
|
1434
|
+
function validateLogoMap() {
|
|
1435
|
+
if (logoMapValidated) {
|
|
1436
|
+
return { valid: logoMapValidationErrors.length === 0, errors: logoMapValidationErrors };
|
|
1437
|
+
}
|
|
1438
|
+
const errors = [];
|
|
1439
|
+
for (const [key, paths] of Object.entries(LOGO_MAP)) {
|
|
1440
|
+
for (const [theme, logoPath] of Object.entries(paths)) {
|
|
1441
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
1442
|
+
if (!resolvedPath || !fs.existsSync(resolvedPath)) {
|
|
1443
|
+
errors.push(`LOGO_MAP["${key}"].${theme}: "${logoPath}" not found`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
logoMapValidated = true;
|
|
1448
|
+
logoMapValidationErrors = errors;
|
|
1449
|
+
if (errors.length > 0) {
|
|
1450
|
+
console.error(`[Logo Validation] ${errors.length} logo(s) not found:`);
|
|
1451
|
+
errors.forEach(e => console.error(` - ${e}`));
|
|
1452
|
+
}
|
|
1453
|
+
return { valid: errors.length === 0, errors };
|
|
1454
|
+
}
|
|
1400
1455
|
/**
|
|
1401
1456
|
* Embed a logo as base64 image content for MCP responses
|
|
1402
|
-
* Returns
|
|
1457
|
+
* Returns either a success result with image data or an error result with details
|
|
1403
1458
|
*/
|
|
1404
1459
|
async function embedLogo(logoPath) {
|
|
1460
|
+
// Validate input
|
|
1461
|
+
if (!logoPath || typeof logoPath !== 'string') {
|
|
1462
|
+
return {
|
|
1463
|
+
success: false,
|
|
1464
|
+
error: "invalid_path",
|
|
1465
|
+
message: "Logo path is empty or invalid",
|
|
1466
|
+
requestedPath: String(logoPath),
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
1470
|
+
if (!resolvedPath) {
|
|
1471
|
+
return {
|
|
1472
|
+
success: false,
|
|
1473
|
+
error: "invalid_path",
|
|
1474
|
+
message: `Failed to resolve logo path: ${logoPath}`,
|
|
1475
|
+
requestedPath: logoPath,
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
// Check if file exists
|
|
1479
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
1480
|
+
return {
|
|
1481
|
+
success: false,
|
|
1482
|
+
error: "not_found",
|
|
1483
|
+
message: `Logo file does not exist`,
|
|
1484
|
+
requestedPath: logoPath,
|
|
1485
|
+
resolvedPath,
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1405
1488
|
try {
|
|
1406
|
-
const resolvedPath = resolveLogoPath(logoPath);
|
|
1407
|
-
if (!resolvedPath)
|
|
1408
|
-
return null;
|
|
1409
|
-
// Check if file exists
|
|
1410
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
1411
|
-
console.error(`Logo not found: ${resolvedPath}`);
|
|
1412
|
-
return null;
|
|
1413
|
-
}
|
|
1414
1489
|
// Read file as base64
|
|
1415
1490
|
const fileBuffer = fs.readFileSync(resolvedPath);
|
|
1416
1491
|
const base64Data = fileBuffer.toString('base64');
|
|
1417
1492
|
const mimeType = getImageMimeType(resolvedPath);
|
|
1418
1493
|
return {
|
|
1494
|
+
success: true,
|
|
1419
1495
|
type: "image",
|
|
1420
1496
|
data: base64Data,
|
|
1421
1497
|
mimeType,
|
|
1422
1498
|
};
|
|
1423
1499
|
}
|
|
1424
1500
|
catch (error) {
|
|
1425
|
-
|
|
1426
|
-
|
|
1501
|
+
return {
|
|
1502
|
+
success: false,
|
|
1503
|
+
error: "read_error",
|
|
1504
|
+
message: `Failed to read logo file: ${error instanceof Error ? error.message : String(error)}`,
|
|
1505
|
+
requestedPath: logoPath,
|
|
1506
|
+
resolvedPath,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Embed a logo and add appropriate content blocks with warnings if it fails
|
|
1512
|
+
* Returns true if logo was embedded successfully, false otherwise
|
|
1513
|
+
*/
|
|
1514
|
+
async function embedLogoWithFeedback(logoPath, label, contentBlocks, options = {}) {
|
|
1515
|
+
const result = await embedLogo(logoPath);
|
|
1516
|
+
if (result.success) {
|
|
1517
|
+
contentBlocks.push({
|
|
1518
|
+
type: "text",
|
|
1519
|
+
text: `## ${label}\n**Path:** \`${logoPath}\``
|
|
1520
|
+
});
|
|
1521
|
+
contentBlocks.push({
|
|
1522
|
+
type: "image",
|
|
1523
|
+
data: result.data,
|
|
1524
|
+
mimeType: result.mimeType,
|
|
1525
|
+
});
|
|
1526
|
+
return true;
|
|
1527
|
+
}
|
|
1528
|
+
else {
|
|
1529
|
+
// Add warning to response unless silent mode
|
|
1530
|
+
if (!options.silent) {
|
|
1531
|
+
contentBlocks.push({
|
|
1532
|
+
type: "text",
|
|
1533
|
+
text: `> **Warning:** Could not embed ${label.toLowerCase()}: ${result.message}\n> Requested path: \`${logoPath}\`${result.resolvedPath ? `\n> Resolved to: \`${result.resolvedPath}\`` : ''}\n> Use \`list_logos\` to see available logos.`,
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
// Always log to console for debugging
|
|
1537
|
+
console.error(`[Logo Embed Failed] ${label}: ${result.message} (path: ${logoPath})`);
|
|
1538
|
+
return false;
|
|
1427
1539
|
}
|
|
1428
1540
|
}
|
|
1429
1541
|
/**
|
|
@@ -2427,7 +2539,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2427
2539
|
},
|
|
2428
2540
|
{
|
|
2429
2541
|
name: "get_logo",
|
|
2430
|
-
description: "Returns a brand logo
|
|
2542
|
+
description: "Returns a brand logo with ready-to-use implementation code. USE THIS when user asks to 'add logo', 'put the logo', 'include the logo', or needs a specific logo for their code. Returns: the logo image, React/Next.js code snippets, sizing guidelines, and recommendations for light/dark mode variants. Use list_logos first to see available logos.",
|
|
2431
2543
|
inputSchema: {
|
|
2432
2544
|
type: "object",
|
|
2433
2545
|
properties: {
|
|
@@ -2439,6 +2551,15 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2439
2551
|
required: ["logo_path"],
|
|
2440
2552
|
},
|
|
2441
2553
|
},
|
|
2554
|
+
{
|
|
2555
|
+
name: "diagnose_logos",
|
|
2556
|
+
description: "Diagnose logo loading issues. Returns validation status, path resolution examples, and environment info. Use this when logos fail to embed or appear in responses.",
|
|
2557
|
+
inputSchema: {
|
|
2558
|
+
type: "object",
|
|
2559
|
+
properties: {},
|
|
2560
|
+
required: [],
|
|
2561
|
+
},
|
|
2562
|
+
},
|
|
2442
2563
|
{
|
|
2443
2564
|
name: "get_full_library",
|
|
2444
2565
|
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.",
|
|
@@ -2890,6 +3011,87 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2890
3011
|
const base64Data = imageBuffer.toString('base64');
|
|
2891
3012
|
const mimeType = getImageMimeType(resolvedPath);
|
|
2892
3013
|
const fileName = path.basename(resolvedPath);
|
|
3014
|
+
// Determine logo variant info for recommendations
|
|
3015
|
+
const isLightLogo = /light|reverse|white/i.test(fileName);
|
|
3016
|
+
const isDarkLogo = /dark|black/i.test(fileName);
|
|
3017
|
+
const backgroundGuidance = isLightLogo
|
|
3018
|
+
? "**Use on:** Dark backgrounds (charcoal, dark gray, black)"
|
|
3019
|
+
: isDarkLogo
|
|
3020
|
+
? "**Use on:** Light backgrounds (white, light gray)"
|
|
3021
|
+
: "**Use on:** Check filename for light/dark variant guidance";
|
|
3022
|
+
// Suggest complementary variant
|
|
3023
|
+
let variantSuggestion = "";
|
|
3024
|
+
if (isLightLogo) {
|
|
3025
|
+
const darkVariant = fileName.replace(/light/gi, "Dark").replace(/reverse/gi, "Dark").replace(/white/gi, "Black");
|
|
3026
|
+
variantSuggestion = `**For light backgrounds:** Look for \`${darkVariant}\` or a "Dark" variant`;
|
|
3027
|
+
}
|
|
3028
|
+
else if (isDarkLogo) {
|
|
3029
|
+
const lightVariant = fileName.replace(/dark/gi, "Light").replace(/black/gi, "Reverse");
|
|
3030
|
+
variantSuggestion = `**For dark backgrounds:** Look for \`${lightVariant}\` or a "Light/Reverse" variant`;
|
|
3031
|
+
}
|
|
3032
|
+
// Determine brand for alt text
|
|
3033
|
+
const brandMatch = logoPath.match(/\/(sonance|iport|blaze|james|trufig)/i);
|
|
3034
|
+
const brandName = brandMatch ? brandMatch[1].charAt(0).toUpperCase() + brandMatch[1].slice(1) : "Brand";
|
|
3035
|
+
// Normalize the path for code examples (ensure it starts with /logos/)
|
|
3036
|
+
const normalizedPath = logoPath.startsWith('/') ? logoPath : `/logos/${logoPath.replace(/^logos\//, '')}`;
|
|
3037
|
+
const implementationGuide = `## Logo: ${fileName}
|
|
3038
|
+
|
|
3039
|
+
${backgroundGuidance}
|
|
3040
|
+
${variantSuggestion}
|
|
3041
|
+
|
|
3042
|
+
---
|
|
3043
|
+
|
|
3044
|
+
### Implementation (Next.js with next/image)
|
|
3045
|
+
\`\`\`tsx
|
|
3046
|
+
import Image from 'next/image';
|
|
3047
|
+
|
|
3048
|
+
// In your component:
|
|
3049
|
+
<Image
|
|
3050
|
+
src="${normalizedPath}"
|
|
3051
|
+
alt="${brandName}"
|
|
3052
|
+
width={150}
|
|
3053
|
+
height={40}
|
|
3054
|
+
className="h-10 w-auto"
|
|
3055
|
+
priority // Add if logo is above the fold
|
|
3056
|
+
/>
|
|
3057
|
+
\`\`\`
|
|
3058
|
+
|
|
3059
|
+
### Implementation (React/HTML)
|
|
3060
|
+
\`\`\`tsx
|
|
3061
|
+
<img
|
|
3062
|
+
src="${normalizedPath}"
|
|
3063
|
+
alt="${brandName}"
|
|
3064
|
+
className="h-10 w-auto"
|
|
3065
|
+
/>
|
|
3066
|
+
\`\`\`
|
|
3067
|
+
|
|
3068
|
+
### With Dark Mode Support
|
|
3069
|
+
\`\`\`tsx
|
|
3070
|
+
{/* Use CSS to swap logos based on theme */}
|
|
3071
|
+
<img
|
|
3072
|
+
src="${normalizedPath}"
|
|
3073
|
+
alt="${brandName}"
|
|
3074
|
+
className="h-10 w-auto dark:hidden" {/* Light mode */}
|
|
3075
|
+
/>
|
|
3076
|
+
<img
|
|
3077
|
+
src="${normalizedPath.replace(/dark/gi, 'Light').replace(/black/gi, 'Reverse')}"
|
|
3078
|
+
alt="${brandName}"
|
|
3079
|
+
className="h-10 w-auto hidden dark:block" {/* Dark mode */}
|
|
3080
|
+
/>
|
|
3081
|
+
\`\`\`
|
|
3082
|
+
|
|
3083
|
+
### Sizing Guidelines
|
|
3084
|
+
| Location | Tailwind Class | Pixels |
|
|
3085
|
+
|----------|---------------|--------|
|
|
3086
|
+
| Header/Navbar | \`h-8\` to \`h-10\` | 32-40px |
|
|
3087
|
+
| Hero Section | \`h-12\` to \`h-16\` | 48-64px |
|
|
3088
|
+
| Footer | \`h-6\` to \`h-8\` | 24-32px |
|
|
3089
|
+
| Mobile Header | \`h-6\` to \`h-8\` | 24-32px |
|
|
3090
|
+
|
|
3091
|
+
---
|
|
3092
|
+
|
|
3093
|
+
**File Info:** ${imageBuffer.length} bytes, ${mimeType}
|
|
3094
|
+
**Full Path:** \`${normalizedPath}\``;
|
|
2893
3095
|
return {
|
|
2894
3096
|
content: [
|
|
2895
3097
|
{
|
|
@@ -2899,7 +3101,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2899
3101
|
},
|
|
2900
3102
|
{
|
|
2901
3103
|
type: "text",
|
|
2902
|
-
text:
|
|
3104
|
+
text: implementationGuide,
|
|
2903
3105
|
},
|
|
2904
3106
|
],
|
|
2905
3107
|
};
|
|
@@ -2911,6 +3113,88 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2911
3113
|
};
|
|
2912
3114
|
}
|
|
2913
3115
|
}
|
|
3116
|
+
case "diagnose_logos": {
|
|
3117
|
+
// Run validation and collect diagnostic info
|
|
3118
|
+
const validation = validateLogoMap();
|
|
3119
|
+
const samplePath = "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png";
|
|
3120
|
+
const resolvedSample = resolveLogoPath(samplePath);
|
|
3121
|
+
// Count logos in manifest or directory
|
|
3122
|
+
let logoCount = "Unknown";
|
|
3123
|
+
try {
|
|
3124
|
+
if (IS_BUNDLED) {
|
|
3125
|
+
const manifestPath = getAssetPath("logos");
|
|
3126
|
+
if (fs.existsSync(manifestPath)) {
|
|
3127
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
3128
|
+
logoCount = `${manifest.length} logos in manifest`;
|
|
3129
|
+
}
|
|
3130
|
+
else {
|
|
3131
|
+
logoCount = "Manifest file not found";
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
else {
|
|
3135
|
+
const logosDir = getAssetPath("logos");
|
|
3136
|
+
if (fs.existsSync(logosDir)) {
|
|
3137
|
+
// Recursive count
|
|
3138
|
+
const countFiles = (dir) => {
|
|
3139
|
+
let count = 0;
|
|
3140
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
3141
|
+
for (const entry of entries) {
|
|
3142
|
+
if (entry.isDirectory()) {
|
|
3143
|
+
count += countFiles(path.join(dir, entry.name));
|
|
3144
|
+
}
|
|
3145
|
+
else if (/\.(png|svg|jpg|jpeg)$/i.test(entry.name)) {
|
|
3146
|
+
count++;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
return count;
|
|
3150
|
+
};
|
|
3151
|
+
logoCount = `${countFiles(logosDir)} logos in directory`;
|
|
3152
|
+
}
|
|
3153
|
+
else {
|
|
3154
|
+
logoCount = "Logos directory not found";
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
catch (e) {
|
|
3159
|
+
logoCount = `Error counting: ${e}`;
|
|
3160
|
+
}
|
|
3161
|
+
const diagnostics = `# Logo System Diagnostics
|
|
3162
|
+
|
|
3163
|
+
## Environment
|
|
3164
|
+
- **Bundled Mode:** ${IS_BUNDLED}
|
|
3165
|
+
- **Assets Path:** ${BUNDLED_ASSETS}
|
|
3166
|
+
- **Assets Exist:** ${fs.existsSync(BUNDLED_ASSETS)}
|
|
3167
|
+
- **Dev Root:** ${DEV_PROJECT_ROOT}
|
|
3168
|
+
|
|
3169
|
+
## Logo Map Validation
|
|
3170
|
+
- **Status:** ${validation.valid ? "PASS - All logos found" : "FAIL - Some logos missing"}
|
|
3171
|
+
${validation.errors.length > 0 ? `- **Errors:**\n${validation.errors.map(e => ` - ${e}`).join('\n')}` : "- **Errors:** None"}
|
|
3172
|
+
|
|
3173
|
+
## Path Resolution Test
|
|
3174
|
+
- **Input:** \`${samplePath}\`
|
|
3175
|
+
- **Resolved:** \`${resolvedSample || "NULL - Resolution failed"}\`
|
|
3176
|
+
- **Exists:** ${resolvedSample ? (fs.existsSync(resolvedSample) ? "Yes" : "No - File not found") : "N/A"}
|
|
3177
|
+
|
|
3178
|
+
## Logo Inventory
|
|
3179
|
+
- **Count:** ${logoCount}
|
|
3180
|
+
- **Source:** ${IS_BUNDLED ? getAssetPath("logos") : getAssetPath("logos")}
|
|
3181
|
+
|
|
3182
|
+
## Centralized LOGO_MAP
|
|
3183
|
+
${Object.entries(LOGO_MAP).map(([key, paths]) => `- **${key}:**
|
|
3184
|
+
- light: \`${paths.light}\`
|
|
3185
|
+
- dark: \`${paths.dark}\``).join('\n')}
|
|
3186
|
+
|
|
3187
|
+
## Recommendations
|
|
3188
|
+
${!validation.valid ? `
|
|
3189
|
+
1. Check that logo files exist at the expected paths
|
|
3190
|
+
2. Run \`npm run mcp:build\` to rebundle assets if in bundled mode
|
|
3191
|
+
3. Verify \`public/logos/\` directory structure in development mode
|
|
3192
|
+
` : "Logo system is functioning correctly. If you still experience issues, check the specific logo path being requested."}
|
|
3193
|
+
`;
|
|
3194
|
+
return {
|
|
3195
|
+
content: [{ type: "text", text: diagnostics }],
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
2914
3198
|
case "get_full_library": {
|
|
2915
3199
|
try {
|
|
2916
3200
|
let fullLibrary = "# Sonance Component Library\n\n";
|
|
@@ -2957,8 +3241,60 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2957
3241
|
catch (e) {
|
|
2958
3242
|
fullLibrary += "*Components directory not found*\n";
|
|
2959
3243
|
}
|
|
3244
|
+
// Add logo section with implementation guidance
|
|
3245
|
+
fullLibrary += "\n\n---\n\n## 5. Brand Logos\n\n";
|
|
3246
|
+
fullLibrary += "Use these logos in your application. The Sonance+James+IPORT lockup is the default.\n\n";
|
|
3247
|
+
fullLibrary += `### Logo Paths
|
|
3248
|
+
| Variant | Path | Use On |
|
|
3249
|
+
|---------|------|--------|
|
|
3250
|
+
| **Light Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png\` | White, light gray backgrounds |
|
|
3251
|
+
| **Dark Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png\` | Charcoal, dark backgrounds |
|
|
3252
|
+
|
|
3253
|
+
### Implementation (Next.js)
|
|
3254
|
+
\`\`\`tsx
|
|
3255
|
+
import Image from 'next/image';
|
|
3256
|
+
|
|
3257
|
+
// Light mode logo (for light backgrounds)
|
|
3258
|
+
<Image
|
|
3259
|
+
src="/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png"
|
|
3260
|
+
alt="Sonance"
|
|
3261
|
+
width={150}
|
|
3262
|
+
height={40}
|
|
3263
|
+
className="h-10 w-auto dark:hidden"
|
|
3264
|
+
priority
|
|
3265
|
+
/>
|
|
3266
|
+
|
|
3267
|
+
// Dark mode logo (for dark backgrounds)
|
|
3268
|
+
<Image
|
|
3269
|
+
src="/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png"
|
|
3270
|
+
alt="Sonance"
|
|
3271
|
+
width={150}
|
|
3272
|
+
height={40}
|
|
3273
|
+
className="h-10 w-auto hidden dark:block"
|
|
3274
|
+
priority
|
|
3275
|
+
/>
|
|
3276
|
+
\`\`\`
|
|
3277
|
+
|
|
3278
|
+
### Sizing Guidelines
|
|
3279
|
+
- Header/Navbar: \`h-8\` to \`h-10\` (32-40px)
|
|
3280
|
+
- Hero Section: \`h-12\` to \`h-16\` (48-64px)
|
|
3281
|
+
- Footer: \`h-6\` to \`h-8\` (24-32px)
|
|
3282
|
+
|
|
3283
|
+
### Other Brand Logos
|
|
3284
|
+
Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code.
|
|
3285
|
+
`;
|
|
3286
|
+
// Build content blocks with embedded logos
|
|
3287
|
+
const contentBlocks = [];
|
|
3288
|
+
// Validate and embed logos
|
|
3289
|
+
validateLogoMap();
|
|
3290
|
+
const lightLogoPath = LOGO_MAP.default.light;
|
|
3291
|
+
const darkLogoPath = LOGO_MAP.default.dark;
|
|
3292
|
+
await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
|
|
3293
|
+
await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
|
|
3294
|
+
// Add the full library text
|
|
3295
|
+
contentBlocks.push({ type: "text", text: fullLibrary });
|
|
2960
3296
|
return {
|
|
2961
|
-
content:
|
|
3297
|
+
content: contentBlocks,
|
|
2962
3298
|
};
|
|
2963
3299
|
}
|
|
2964
3300
|
catch (e) {
|
|
@@ -3000,31 +3336,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3000
3336
|
else if (!rawArgs.theme) {
|
|
3001
3337
|
defaultsUsed.push("theme → Light");
|
|
3002
3338
|
}
|
|
3003
|
-
//
|
|
3004
|
-
|
|
3005
|
-
const logoMap = {
|
|
3006
|
-
default: {
|
|
3007
|
-
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
3008
|
-
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
3009
|
-
},
|
|
3010
|
-
sonance: {
|
|
3011
|
-
light: "/logos/sonance/Sonance_Logo_2C_Dark_RGB.png",
|
|
3012
|
-
dark: "/logos/sonance/Sonance_Logo_2C_Light_RGB.png",
|
|
3013
|
-
},
|
|
3014
|
-
iport: {
|
|
3015
|
-
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
3016
|
-
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
3017
|
-
},
|
|
3018
|
-
blaze: {
|
|
3019
|
-
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
3020
|
-
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
3021
|
-
},
|
|
3022
|
-
james: {
|
|
3023
|
-
light: "/logos/james/James_Logo_Black_RGB.png",
|
|
3024
|
-
dark: "/logos/james/James_Logo_Reverse_RGB.png",
|
|
3025
|
-
},
|
|
3026
|
-
};
|
|
3027
|
-
const logoPath = logoMap[logoPreference]?.[theme] || logoMap.default[theme];
|
|
3339
|
+
// Use centralized LOGO_MAP for logo paths
|
|
3340
|
+
const logoPath = LOGO_MAP[logoPreference]?.[theme] || LOGO_MAP.default[theme];
|
|
3028
3341
|
const logoPreferenceLabel = logoPreference === "default"
|
|
3029
3342
|
? "Sonance + James + IPORT Lockup"
|
|
3030
3343
|
: logoPreference === "james"
|
|
@@ -3366,8 +3679,8 @@ const example = "Sonance";
|
|
|
3366
3679
|
let response;
|
|
3367
3680
|
if (isDualThemeMode) {
|
|
3368
3681
|
// DUAL-THEME MODE: Provide both light and dark tokens for UI
|
|
3369
|
-
const lightLogoPath =
|
|
3370
|
-
const darkLogoPath =
|
|
3682
|
+
const lightLogoPath = LOGO_MAP[logoPreference]?.light || LOGO_MAP.default.light;
|
|
3683
|
+
const darkLogoPath = LOGO_MAP[logoPreference]?.dark || LOGO_MAP.default.dark;
|
|
3371
3684
|
response = `# Design Context for: ${component_description}
|
|
3372
3685
|
|
|
3373
3686
|
**Brand**: ${brand.toUpperCase()}
|
|
@@ -3525,30 +3838,26 @@ ${COMPONENT_CATEGORIES[detectedComponentType]?.slice(0, 8).join(", ") || "See co
|
|
|
3525
3838
|
|
|
3526
3839
|
Now design the **${component_description}** following these tokens and principles.`;
|
|
3527
3840
|
}
|
|
3528
|
-
// Embed logos in response
|
|
3841
|
+
// Embed logos in response with proper error feedback
|
|
3529
3842
|
const contentBlocks = [];
|
|
3843
|
+
// Validate logo map on first use
|
|
3844
|
+
validateLogoMap();
|
|
3530
3845
|
if (isDualThemeMode) {
|
|
3531
3846
|
// Dual-theme mode: embed both light and dark theme logos
|
|
3532
|
-
const lightLogoPath =
|
|
3533
|
-
const darkLogoPath =
|
|
3534
|
-
const
|
|
3535
|
-
const
|
|
3536
|
-
if (
|
|
3537
|
-
contentBlocks.push({
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
contentBlocks.push({ type: "text", text: `## Logo for Dark Backgrounds\n**Path:** \`${darkLogoPath}\`` });
|
|
3542
|
-
contentBlocks.push(darkLogo);
|
|
3847
|
+
const lightLogoPath = LOGO_MAP[logoPreference]?.light || LOGO_MAP.default.light;
|
|
3848
|
+
const darkLogoPath = LOGO_MAP[logoPreference]?.dark || LOGO_MAP.default.dark;
|
|
3849
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
3850
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
3851
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
3852
|
+
contentBlocks.push({
|
|
3853
|
+
type: "text",
|
|
3854
|
+
text: `> **Note:** No logos were embedded. The design guidance below remains valid, but you'll need to manually obtain the logo files. Use \`get_logo\` with paths from \`list_logos\`.`,
|
|
3855
|
+
});
|
|
3543
3856
|
}
|
|
3544
3857
|
}
|
|
3545
3858
|
else {
|
|
3546
3859
|
// Single-theme mode: embed the appropriate logo
|
|
3547
|
-
|
|
3548
|
-
if (embeddedLogo) {
|
|
3549
|
-
contentBlocks.push({ type: "text", text: `## Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})\n**Path:** \`${logoPath}\`` });
|
|
3550
|
-
contentBlocks.push(embeddedLogo);
|
|
3551
|
-
}
|
|
3860
|
+
await embedLogoWithFeedback(logoPath, `Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})`, contentBlocks);
|
|
3552
3861
|
}
|
|
3553
3862
|
// Add the main response text
|
|
3554
3863
|
contentBlocks.push({ type: "text", text: response });
|
|
@@ -4303,8 +4612,8 @@ h2_style.paragraph_format.space_after = Pt(8)
|
|
|
4303
4612
|
# ============================================
|
|
4304
4613
|
# ADD CONTENT
|
|
4305
4614
|
# ============================================
|
|
4306
|
-
# Add logo
|
|
4307
|
-
|
|
4615
|
+
# Add logo - save the embedded logo above to 'logo.png', then:
|
|
4616
|
+
doc.add_picture('logo.png', width=Inches(2))
|
|
4308
4617
|
|
|
4309
4618
|
# Title
|
|
4310
4619
|
doc.add_paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', style='${palette.name}Title')
|
|
@@ -4422,8 +4731,8 @@ doc = SimpleDocTemplate(
|
|
|
4422
4731
|
# Content
|
|
4423
4732
|
content = []
|
|
4424
4733
|
|
|
4425
|
-
# Logo
|
|
4426
|
-
|
|
4734
|
+
# Logo - save the embedded logo above to 'logo.png', then:
|
|
4735
|
+
content.append(Image('logo.png', width=2*inch, height=0.5*inch))
|
|
4427
4736
|
|
|
4428
4737
|
# Title
|
|
4429
4738
|
content.append(Paragraph('${templateType === 'proposal' ? 'Project Proposal' : templateType === 'spec-sheet' ? 'Product Specification' : templateType === 'invoice' ? 'Invoice' : 'Report Title'}', title_style))
|
|
@@ -4565,8 +4874,32 @@ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark
|
|
|
4565
4874
|
isError: true,
|
|
4566
4875
|
};
|
|
4567
4876
|
}
|
|
4877
|
+
// Embed logos for document templates using centralized LOGO_MAP
|
|
4878
|
+
const templateContentBlocks = [];
|
|
4879
|
+
// Validate logo map on first use
|
|
4880
|
+
validateLogoMap();
|
|
4881
|
+
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
4882
|
+
const brandLogoKey = targetBrand === "sonance" ? "default" : targetBrand;
|
|
4883
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
4884
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
4885
|
+
// Embed logos with guidance
|
|
4886
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, `## Logo for Light Backgrounds\n**Save this image as \`logo_dark.png\` for use in your document**\nUse on white/light backgrounds.`, templateContentBlocks);
|
|
4887
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, `## Logo for Dark Backgrounds\n**Save this image as \`logo_light.png\` for use in your document**\nUse on dark/colored backgrounds.`, templateContentBlocks);
|
|
4888
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
4889
|
+
templateContentBlocks.push({
|
|
4890
|
+
type: "text",
|
|
4891
|
+
text: `> **Logo Note:** Brand logos could not be embedded automatically. Use the \`get_logo\` tool to retrieve ${palette.name} logos.`,
|
|
4892
|
+
});
|
|
4893
|
+
}
|
|
4894
|
+
// Add logo usage instructions
|
|
4895
|
+
templateContentBlocks.push({
|
|
4896
|
+
type: "text",
|
|
4897
|
+
text: `## Logo Usage\n\nTo add the logo to your document:\n1. Save the appropriate logo image above (right-click → Save as)\n2. Use the saved path in your code:\n\n**Word (python-docx):**\n\`\`\`python\ndoc.add_picture('logo_dark.png', width=Inches(2)) # For light backgrounds\n\`\`\`\n\n**PDF (ReportLab):**\n\`\`\`python\ncontent.append(Image('logo_dark.png', width=2*inch, height=0.5*inch))\n\`\`\`\n`,
|
|
4898
|
+
});
|
|
4899
|
+
// Add the main template text
|
|
4900
|
+
templateContentBlocks.push({ type: "text", text: template });
|
|
4568
4901
|
return {
|
|
4569
|
-
content:
|
|
4902
|
+
content: templateContentBlocks,
|
|
4570
4903
|
};
|
|
4571
4904
|
}
|
|
4572
4905
|
case "rebrand_document": {
|
|
@@ -4837,35 +5170,22 @@ ${rebrandArgs.source_format === "word" ? `
|
|
|
4837
5170
|
|
|
4838
5171
|
**Remember:** The goal is to make the document unmistakably ${palette.name} while preserving its original structure and content.
|
|
4839
5172
|
`;
|
|
4840
|
-
// Embed logos for rebrand_document
|
|
4841
|
-
// Use the Sonance+James+IPORT lockup as default for Sonance brand
|
|
4842
|
-
const rebrandLogoMap = {
|
|
4843
|
-
sonance: {
|
|
4844
|
-
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
4845
|
-
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
4846
|
-
},
|
|
4847
|
-
iport: {
|
|
4848
|
-
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
4849
|
-
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
4850
|
-
},
|
|
4851
|
-
blaze: {
|
|
4852
|
-
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
4853
|
-
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
4854
|
-
},
|
|
4855
|
-
};
|
|
5173
|
+
// Embed logos for rebrand_document using centralized LOGO_MAP
|
|
4856
5174
|
const rebrandContentBlocks = [];
|
|
5175
|
+
// Validate logo map on first use
|
|
5176
|
+
validateLogoMap();
|
|
4857
5177
|
// For documents, provide both light and dark logo variants
|
|
4858
|
-
|
|
4859
|
-
const
|
|
4860
|
-
const
|
|
4861
|
-
const
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
5178
|
+
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5179
|
+
const brandLogoKey = targetBrand === "sonance" ? "default" : targetBrand;
|
|
5180
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5181
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5182
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds\n**Use on white/light document backgrounds**", rebrandContentBlocks);
|
|
5183
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds\n**Use on dark/colored document backgrounds**", rebrandContentBlocks);
|
|
5184
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
5185
|
+
rebrandContentBlocks.push({
|
|
5186
|
+
type: "text",
|
|
5187
|
+
text: `> **Logo Note:** Brand logos could not be embedded automatically. Download the appropriate ${palette.name} logo from your brand assets folder or use the \`get_logo\` tool.`,
|
|
5188
|
+
});
|
|
4869
5189
|
}
|
|
4870
5190
|
// Add the main guide text
|
|
4871
5191
|
rebrandContentBlocks.push({ type: "text", text: rebrandGuide });
|
|
@@ -5060,31 +5380,21 @@ ${layout.template}
|
|
|
5060
5380
|
|
|
5061
5381
|
Now you have everything needed to build a ${palette.name}-branded ${appType} application!
|
|
5062
5382
|
`;
|
|
5063
|
-
// Embed logos
|
|
5064
|
-
const logoMap = {
|
|
5065
|
-
sonance: {
|
|
5066
|
-
light: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png",
|
|
5067
|
-
dark: "/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png",
|
|
5068
|
-
},
|
|
5069
|
-
iport: {
|
|
5070
|
-
light: "/logos/iport/IPORT_Sonance_LockUp_2C_Dark_RGB.png",
|
|
5071
|
-
dark: "/logos/iport/IPORT_Sonance_LockUp_2C_Light_RGB.png",
|
|
5072
|
-
},
|
|
5073
|
-
blaze: {
|
|
5074
|
-
light: "/logos/blaze/BlazeBySonance_Logo_Lockup_3C_Dark_RGB_05162025.png",
|
|
5075
|
-
dark: "/logos/blaze/BlazeBySonance_Logo_Lockup_2C_Light_RGB_05162025.png",
|
|
5076
|
-
},
|
|
5077
|
-
};
|
|
5383
|
+
// Embed logos using centralized LOGO_MAP with proper error feedback
|
|
5078
5384
|
const contentBlocks = [];
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5385
|
+
// Validate logo map on first use
|
|
5386
|
+
validateLogoMap();
|
|
5387
|
+
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5388
|
+
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5389
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5390
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5391
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
5392
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
5393
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
5394
|
+
contentBlocks.push({
|
|
5395
|
+
type: "text",
|
|
5396
|
+
text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
|
|
5397
|
+
});
|
|
5088
5398
|
}
|
|
5089
5399
|
contentBlocks.push({ type: "text", text: response });
|
|
5090
5400
|
return {
|
|
@@ -5359,9 +5669,54 @@ className="hover:bg-primary/90 focus-visible:ring-2 focus-visible:ring-primary"
|
|
|
5359
5669
|
- [ ] Dark mode support added
|
|
5360
5670
|
- [ ] Hover states on interactive elements
|
|
5361
5671
|
- [ ] Focus states for accessibility
|
|
5672
|
+
|
|
5673
|
+
---
|
|
5674
|
+
|
|
5675
|
+
## Brand Logos
|
|
5676
|
+
|
|
5677
|
+
Use these logos when adding branding to your application:
|
|
5678
|
+
|
|
5679
|
+
### Logo Paths
|
|
5680
|
+
| Variant | Path | Use On |
|
|
5681
|
+
|---------|------|--------|
|
|
5682
|
+
| **Light Backgrounds** | \`${LOGO_MAP[brand === "sonance" ? "default" : brand]?.light || LOGO_MAP.default.light}\` | White, light gray |
|
|
5683
|
+
| **Dark Backgrounds** | \`${LOGO_MAP[brand === "sonance" ? "default" : brand]?.dark || LOGO_MAP.default.dark}\` | Charcoal, dark |
|
|
5684
|
+
|
|
5685
|
+
### Quick Implementation
|
|
5686
|
+
\`\`\`tsx
|
|
5687
|
+
import Image from 'next/image';
|
|
5688
|
+
|
|
5689
|
+
<Image
|
|
5690
|
+
src="${LOGO_MAP[brand === "sonance" ? "default" : brand]?.light || LOGO_MAP.default.light}"
|
|
5691
|
+
alt="${palette.name}"
|
|
5692
|
+
width={150}
|
|
5693
|
+
height={40}
|
|
5694
|
+
className="h-10 w-auto dark:hidden"
|
|
5695
|
+
priority
|
|
5696
|
+
/>
|
|
5697
|
+
<Image
|
|
5698
|
+
src="${LOGO_MAP[brand === "sonance" ? "default" : brand]?.dark || LOGO_MAP.default.dark}"
|
|
5699
|
+
alt="${palette.name}"
|
|
5700
|
+
width={150}
|
|
5701
|
+
height={40}
|
|
5702
|
+
className="h-10 w-auto hidden dark:block"
|
|
5703
|
+
priority
|
|
5704
|
+
/>
|
|
5705
|
+
\`\`\`
|
|
5362
5706
|
`;
|
|
5707
|
+
// Build content blocks with embedded logos
|
|
5708
|
+
const contentBlocks = [];
|
|
5709
|
+
// Validate and embed logos for the target brand
|
|
5710
|
+
validateLogoMap();
|
|
5711
|
+
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5712
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5713
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5714
|
+
await embedLogoWithFeedback(lightLogoPath, `${palette.name} Logo (for Light Backgrounds)`, contentBlocks);
|
|
5715
|
+
await embedLogoWithFeedback(darkLogoPath, `${palette.name} Logo (for Dark Backgrounds)`, contentBlocks);
|
|
5716
|
+
// Add the analysis text
|
|
5717
|
+
contentBlocks.push({ type: "text", text: response });
|
|
5363
5718
|
return {
|
|
5364
|
-
content:
|
|
5719
|
+
content: contentBlocks,
|
|
5365
5720
|
};
|
|
5366
5721
|
}
|
|
5367
5722
|
case "analyze_for_redesign": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.109",
|
|
4
4
|
"description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|