sonance-brand-mcp 1.3.116 → 1.3.118
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/index.js +377 -118
- package/dist/utils/response-formatter.d.ts +71 -0
- package/dist/utils/response-formatter.js +152 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import * as os from "os";
|
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
10
|
import sharp from "sharp";
|
|
11
|
+
import { formatCodeBlock, } from "./utils/response-formatter.js";
|
|
11
12
|
// ============================================
|
|
12
13
|
// INSTALLER (--init flag)
|
|
13
14
|
// ============================================
|
|
@@ -2634,6 +2635,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2634
2635
|
required: ["logo_path"],
|
|
2635
2636
|
},
|
|
2636
2637
|
},
|
|
2638
|
+
{
|
|
2639
|
+
name: "get_logo_base64",
|
|
2640
|
+
description: "Returns a logo as base64-encoded data URL ready for embedding in React artifacts, HTML, or web applications. USE THIS for artifacts instead of drawing/approximating logos. Returns a data URL that works directly in <img src='...'> tags. CRITICAL: NEVER draw, recreate, or approximate logos with SVG, CSS, or styled text - always use this tool to get the actual image data.",
|
|
2641
|
+
inputSchema: {
|
|
2642
|
+
type: "object",
|
|
2643
|
+
properties: {
|
|
2644
|
+
logo_path: {
|
|
2645
|
+
type: "string",
|
|
2646
|
+
description: "Path to the logo from list_logos output (e.g., 'sonance/Sonance_Logo_2C_Dark_RGB.png')",
|
|
2647
|
+
},
|
|
2648
|
+
format: {
|
|
2649
|
+
type: "string",
|
|
2650
|
+
enum: ["dataUrl", "base64", "both"],
|
|
2651
|
+
description: "Output format. 'dataUrl' for img src (default), 'base64' for raw data, 'both' for both formats",
|
|
2652
|
+
},
|
|
2653
|
+
},
|
|
2654
|
+
required: ["logo_path"],
|
|
2655
|
+
},
|
|
2656
|
+
},
|
|
2637
2657
|
{
|
|
2638
2658
|
name: "diagnose_logos",
|
|
2639
2659
|
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.",
|
|
@@ -2915,7 +2935,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2915
2935
|
try {
|
|
2916
2936
|
const content = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
2917
2937
|
return {
|
|
2918
|
-
content: [{
|
|
2938
|
+
content: [{
|
|
2939
|
+
type: "text",
|
|
2940
|
+
text: `# Sonance Brand Theme
|
|
2941
|
+
|
|
2942
|
+
${formatCodeBlock(content, { language: "css", filename: "globals.css" })}`
|
|
2943
|
+
}],
|
|
2919
2944
|
};
|
|
2920
2945
|
}
|
|
2921
2946
|
catch (e) {
|
|
@@ -2964,10 +2989,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2964
2989
|
const match = files.find(f => f.toLowerCase() === `${componentName}.tsx`);
|
|
2965
2990
|
if (match) {
|
|
2966
2991
|
const content = fs.readFileSync(path.join(componentsDir, match), "utf-8");
|
|
2992
|
+
const matchedName = match.replace('.tsx', '');
|
|
2967
2993
|
return {
|
|
2968
2994
|
content: [{
|
|
2969
2995
|
type: "text",
|
|
2970
|
-
text:
|
|
2996
|
+
text: `# ${matchedName} Component
|
|
2997
|
+
|
|
2998
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${match}` })}`
|
|
2971
2999
|
}],
|
|
2972
3000
|
};
|
|
2973
3001
|
}
|
|
@@ -2980,7 +3008,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2980
3008
|
return {
|
|
2981
3009
|
content: [{
|
|
2982
3010
|
type: "text",
|
|
2983
|
-
text:
|
|
3011
|
+
text: `# ${componentName} Component
|
|
3012
|
+
|
|
3013
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${componentName}.tsx` })}`
|
|
2984
3014
|
}],
|
|
2985
3015
|
};
|
|
2986
3016
|
}
|
|
@@ -2997,7 +3027,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2997
3027
|
return {
|
|
2998
3028
|
content: [{
|
|
2999
3029
|
type: "text",
|
|
3000
|
-
text:
|
|
3030
|
+
text: `# Sonance UI Utilities
|
|
3031
|
+
|
|
3032
|
+
These utilities are used by Sonance UI components.
|
|
3033
|
+
|
|
3034
|
+
${formatCodeBlock(content, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
3001
3035
|
}],
|
|
3002
3036
|
};
|
|
3003
3037
|
}
|
|
@@ -3094,6 +3128,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3094
3128
|
const base64Data = imageBuffer.toString('base64');
|
|
3095
3129
|
const mimeType = getImageMimeType(resolvedPath);
|
|
3096
3130
|
const fileName = path.basename(resolvedPath);
|
|
3131
|
+
// Create data URL for artifact use
|
|
3132
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
3097
3133
|
// Determine logo variant info for recommendations
|
|
3098
3134
|
const isLightLogo = /light|reverse|white/i.test(fileName);
|
|
3099
3135
|
const isDarkLogo = /dark|black/i.test(fileName);
|
|
@@ -3124,28 +3160,45 @@ ${variantSuggestion}
|
|
|
3124
3160
|
|
|
3125
3161
|
---
|
|
3126
3162
|
|
|
3127
|
-
|
|
3163
|
+
## CRITICAL: Logo Usage Rules
|
|
3164
|
+
|
|
3165
|
+
> **NEVER draw, recreate, or approximate this logo.** This includes:
|
|
3166
|
+
> - SVG drawings or paths
|
|
3167
|
+
> - Styled text (e.g., colored letters spelling "SONANCE")
|
|
3168
|
+
> - CSS recreations or shapes
|
|
3169
|
+
> - Unicode character approximations
|
|
3170
|
+
>
|
|
3171
|
+
> **ALWAYS use the actual image data provided below.**
|
|
3172
|
+
|
|
3173
|
+
---
|
|
3174
|
+
|
|
3175
|
+
### For React Artifacts (USE THIS - includes actual image data)
|
|
3176
|
+
\`\`\`tsx
|
|
3177
|
+
<img
|
|
3178
|
+
src="${dataUrl}"
|
|
3179
|
+
alt="${brandName}"
|
|
3180
|
+
className="h-10 w-auto"
|
|
3181
|
+
/>
|
|
3182
|
+
\`\`\`
|
|
3183
|
+
|
|
3184
|
+
### For Next.js Projects (with file system access)
|
|
3128
3185
|
\`\`\`tsx
|
|
3129
3186
|
import Image from 'next/image';
|
|
3130
3187
|
|
|
3131
|
-
// In your component:
|
|
3132
3188
|
<Image
|
|
3133
3189
|
src="${normalizedPath}"
|
|
3134
3190
|
alt="${brandName}"
|
|
3135
3191
|
width={150}
|
|
3136
3192
|
height={40}
|
|
3137
3193
|
className="h-10 w-auto"
|
|
3138
|
-
priority
|
|
3194
|
+
priority
|
|
3139
3195
|
/>
|
|
3140
3196
|
\`\`\`
|
|
3141
3197
|
|
|
3142
|
-
###
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
alt="${brandName}"
|
|
3147
|
-
className="h-10 w-auto"
|
|
3148
|
-
/>
|
|
3198
|
+
### Base64 Data URL (copy for artifacts)
|
|
3199
|
+
Use this data URL directly in img src for artifacts:
|
|
3200
|
+
\`\`\`
|
|
3201
|
+
${dataUrl}
|
|
3149
3202
|
\`\`\`
|
|
3150
3203
|
|
|
3151
3204
|
### With Dark Mode Support
|
|
@@ -3154,12 +3207,12 @@ import Image from 'next/image';
|
|
|
3154
3207
|
<img
|
|
3155
3208
|
src="${normalizedPath}"
|
|
3156
3209
|
alt="${brandName}"
|
|
3157
|
-
className="h-10 w-auto dark:hidden"
|
|
3210
|
+
className="h-10 w-auto dark:hidden"
|
|
3158
3211
|
/>
|
|
3159
3212
|
<img
|
|
3160
3213
|
src="${normalizedPath.replace(/dark/gi, 'Light').replace(/black/gi, 'Reverse')}"
|
|
3161
3214
|
alt="${brandName}"
|
|
3162
|
-
className="h-10 w-auto hidden dark:block"
|
|
3215
|
+
className="h-10 w-auto hidden dark:block"
|
|
3163
3216
|
/>
|
|
3164
3217
|
\`\`\`
|
|
3165
3218
|
|
|
@@ -3196,6 +3249,98 @@ import Image from 'next/image';
|
|
|
3196
3249
|
};
|
|
3197
3250
|
}
|
|
3198
3251
|
}
|
|
3252
|
+
case "get_logo_base64": {
|
|
3253
|
+
const logoArgs = args;
|
|
3254
|
+
const logoPath = logoArgs.logo_path;
|
|
3255
|
+
const format = logoArgs.format || "dataUrl";
|
|
3256
|
+
if (!logoPath) {
|
|
3257
|
+
return {
|
|
3258
|
+
content: [{ type: "text", text: "Error: logo_path is required. Use list_logos to see available logos." }],
|
|
3259
|
+
isError: true,
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
try {
|
|
3263
|
+
const resolvedPath = resolveLogoPath(logoPath);
|
|
3264
|
+
if (!resolvedPath) {
|
|
3265
|
+
return {
|
|
3266
|
+
content: [{ type: "text", text: `Error: Invalid logo path: ${logoPath}` }],
|
|
3267
|
+
isError: true,
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
3271
|
+
return {
|
|
3272
|
+
content: [{ type: "text", text: `Error: Logo not found: ${logoPath}. Use list_logos to see available logos.` }],
|
|
3273
|
+
isError: true,
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
3276
|
+
// Read and encode the image
|
|
3277
|
+
const imageBuffer = fs.readFileSync(resolvedPath);
|
|
3278
|
+
const base64Data = imageBuffer.toString('base64');
|
|
3279
|
+
const mimeType = getImageMimeType(resolvedPath);
|
|
3280
|
+
const fileName = path.basename(resolvedPath);
|
|
3281
|
+
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
|
3282
|
+
// Determine brand for alt text
|
|
3283
|
+
const brandMatch = logoPath.match(/\/(sonance|iport|blaze|james|trufig)/i);
|
|
3284
|
+
const brandName = brandMatch ? brandMatch[1].charAt(0).toUpperCase() + brandMatch[1].slice(1) : "Brand";
|
|
3285
|
+
// Build response based on format
|
|
3286
|
+
let responseText = `## Logo for Artifacts: ${fileName}
|
|
3287
|
+
|
|
3288
|
+
> **CRITICAL:** Use ONLY this image data. NEVER draw, recreate, or approximate this logo with SVG, CSS, styled text, or any other method.
|
|
3289
|
+
|
|
3290
|
+
### Ready-to-Use Code (copy this directly)
|
|
3291
|
+
\`\`\`tsx
|
|
3292
|
+
<img
|
|
3293
|
+
src="${dataUrl}"
|
|
3294
|
+
alt="${brandName}"
|
|
3295
|
+
className="h-10 w-auto"
|
|
3296
|
+
/>
|
|
3297
|
+
\`\`\`
|
|
3298
|
+
`;
|
|
3299
|
+
if (format === "dataUrl" || format === "both") {
|
|
3300
|
+
responseText += `
|
|
3301
|
+
### Data URL (use in img src)
|
|
3302
|
+
\`\`\`
|
|
3303
|
+
${dataUrl}
|
|
3304
|
+
\`\`\`
|
|
3305
|
+
`;
|
|
3306
|
+
}
|
|
3307
|
+
if (format === "base64" || format === "both") {
|
|
3308
|
+
responseText += `
|
|
3309
|
+
### Raw Base64 Data
|
|
3310
|
+
\`\`\`
|
|
3311
|
+
${base64Data}
|
|
3312
|
+
\`\`\`
|
|
3313
|
+
|
|
3314
|
+
**MIME Type:** ${mimeType}
|
|
3315
|
+
`;
|
|
3316
|
+
}
|
|
3317
|
+
responseText += `
|
|
3318
|
+
---
|
|
3319
|
+
|
|
3320
|
+
**File:** ${fileName}
|
|
3321
|
+
**Size:** ${imageBuffer.length} bytes
|
|
3322
|
+
`;
|
|
3323
|
+
return {
|
|
3324
|
+
content: [
|
|
3325
|
+
{
|
|
3326
|
+
type: "image",
|
|
3327
|
+
data: base64Data,
|
|
3328
|
+
mimeType: mimeType,
|
|
3329
|
+
},
|
|
3330
|
+
{
|
|
3331
|
+
type: "text",
|
|
3332
|
+
text: responseText,
|
|
3333
|
+
},
|
|
3334
|
+
],
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
catch (e) {
|
|
3338
|
+
return {
|
|
3339
|
+
content: [{ type: "text", text: `Error reading logo: ${e}` }],
|
|
3340
|
+
isError: true,
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3199
3344
|
case "diagnose_logos": {
|
|
3200
3345
|
// Run validation and collect diagnostic info
|
|
3201
3346
|
const validation = validateLogoMap();
|
|
@@ -3280,36 +3425,73 @@ ${!validation.valid ? `
|
|
|
3280
3425
|
}
|
|
3281
3426
|
case "get_full_library": {
|
|
3282
3427
|
try {
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
//
|
|
3286
|
-
|
|
3428
|
+
// Build structured response with multiple content blocks for artifact rendering
|
|
3429
|
+
const contentBlocks = [];
|
|
3430
|
+
// Validate and embed logos first
|
|
3431
|
+
validateLogoMap();
|
|
3432
|
+
const lightLogoPath = LOGO_MAP.default.light;
|
|
3433
|
+
const darkLogoPath = LOGO_MAP.default.dark;
|
|
3434
|
+
await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
|
|
3435
|
+
await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
|
|
3436
|
+
// Section 1: Overview
|
|
3437
|
+
contentBlocks.push({
|
|
3438
|
+
type: "text",
|
|
3439
|
+
text: `# Sonance Component Library
|
|
3440
|
+
|
|
3441
|
+
This is the complete Sonance UI component library. Copy these files into your project.
|
|
3442
|
+
|
|
3443
|
+
## Table of Contents
|
|
3444
|
+
1. [Brand Guidelines](#brand-guidelines)
|
|
3445
|
+
2. [CSS Theme](#css-theme)
|
|
3446
|
+
3. [Utilities](#utilities)
|
|
3447
|
+
4. [UI Components](#ui-components)
|
|
3448
|
+
5. [Brand Logos](#brand-logos)`
|
|
3449
|
+
});
|
|
3450
|
+
// Section 2: Brand Guidelines
|
|
3451
|
+
let guidelines = "*Brand guidelines not found*";
|
|
3287
3452
|
try {
|
|
3288
|
-
|
|
3453
|
+
guidelines = fs.readFileSync(getAssetPath("guidelines"), "utf-8");
|
|
3289
3454
|
}
|
|
3290
3455
|
catch (e) {
|
|
3291
|
-
|
|
3456
|
+
// Use fallback
|
|
3292
3457
|
}
|
|
3293
|
-
|
|
3294
|
-
|
|
3458
|
+
contentBlocks.push({
|
|
3459
|
+
type: "text",
|
|
3460
|
+
text: `## 1. Brand Guidelines\n\n${guidelines}`
|
|
3461
|
+
});
|
|
3462
|
+
// Section 3: CSS Theme (with filename hint)
|
|
3463
|
+
let cssTheme = "/* CSS theme not found */";
|
|
3295
3464
|
try {
|
|
3296
|
-
|
|
3465
|
+
cssTheme = fs.readFileSync(getAssetPath("css"), "utf-8");
|
|
3297
3466
|
}
|
|
3298
3467
|
catch (e) {
|
|
3299
|
-
|
|
3468
|
+
// Use fallback
|
|
3300
3469
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3470
|
+
contentBlocks.push({
|
|
3471
|
+
type: "text",
|
|
3472
|
+
text: `## 2. CSS Theme
|
|
3473
|
+
|
|
3474
|
+
${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
|
|
3475
|
+
});
|
|
3476
|
+
// Section 4: Utilities (with filename hint)
|
|
3477
|
+
let utils = "// Utils not found";
|
|
3304
3478
|
try {
|
|
3305
|
-
|
|
3479
|
+
utils = fs.readFileSync(getAssetPath("utils"), "utf-8");
|
|
3306
3480
|
}
|
|
3307
3481
|
catch (e) {
|
|
3308
|
-
|
|
3482
|
+
// Use fallback
|
|
3309
3483
|
}
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3484
|
+
contentBlocks.push({
|
|
3485
|
+
type: "text",
|
|
3486
|
+
text: `## 3. Utilities
|
|
3487
|
+
|
|
3488
|
+
${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
3489
|
+
});
|
|
3490
|
+
// Section 5: Components - each as a separate block
|
|
3491
|
+
contentBlocks.push({
|
|
3492
|
+
type: "text",
|
|
3493
|
+
text: `## 4. UI Components`
|
|
3494
|
+
});
|
|
3313
3495
|
try {
|
|
3314
3496
|
const componentsDir = getAssetPath("components");
|
|
3315
3497
|
const files = fs.readdirSync(componentsDir)
|
|
@@ -3318,24 +3500,35 @@ ${!validation.valid ? `
|
|
|
3318
3500
|
for (const file of files) {
|
|
3319
3501
|
const componentName = file.replace('.tsx', '');
|
|
3320
3502
|
const content = fs.readFileSync(path.join(componentsDir, file), "utf-8");
|
|
3321
|
-
|
|
3503
|
+
contentBlocks.push({
|
|
3504
|
+
type: "text",
|
|
3505
|
+
text: `### ${componentName}
|
|
3506
|
+
|
|
3507
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${file}` })}`
|
|
3508
|
+
});
|
|
3322
3509
|
}
|
|
3323
3510
|
}
|
|
3324
3511
|
catch (e) {
|
|
3325
|
-
|
|
3512
|
+
contentBlocks.push({
|
|
3513
|
+
type: "text",
|
|
3514
|
+
text: "*Components directory not found*"
|
|
3515
|
+
});
|
|
3326
3516
|
}
|
|
3327
|
-
//
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3517
|
+
// Section 6: Logo guidance
|
|
3518
|
+
contentBlocks.push({
|
|
3519
|
+
type: "text",
|
|
3520
|
+
text: `## 5. Brand Logos
|
|
3521
|
+
|
|
3522
|
+
Use these logos in your application. The Sonance+James+IPORT lockup is the default.
|
|
3523
|
+
|
|
3524
|
+
### Logo Paths
|
|
3331
3525
|
| Variant | Path | Use On |
|
|
3332
3526
|
|---------|------|--------|
|
|
3333
3527
|
| **Light Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png\` | White, light gray backgrounds |
|
|
3334
3528
|
| **Dark Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png\` | Charcoal, dark backgrounds |
|
|
3335
3529
|
|
|
3336
3530
|
### Implementation (Next.js)
|
|
3337
|
-
|
|
3338
|
-
import Image from 'next/image';
|
|
3531
|
+
${formatCodeBlock(`import Image from 'next/image';
|
|
3339
3532
|
|
|
3340
3533
|
// Light mode logo (for light backgrounds)
|
|
3341
3534
|
<Image
|
|
@@ -3355,8 +3548,7 @@ import Image from 'next/image';
|
|
|
3355
3548
|
height={40}
|
|
3356
3549
|
className="h-10 w-auto hidden dark:block"
|
|
3357
3550
|
priority
|
|
3358
|
-
|
|
3359
|
-
\`\`\`
|
|
3551
|
+
/>`, { language: "tsx", filename: "components/Logo.tsx" })}
|
|
3360
3552
|
|
|
3361
3553
|
### Sizing Guidelines
|
|
3362
3554
|
- Header/Navbar: \`h-8\` to \`h-10\` (32-40px)
|
|
@@ -3364,18 +3556,8 @@ import Image from 'next/image';
|
|
|
3364
3556
|
- Footer: \`h-6\` to \`h-8\` (24-32px)
|
|
3365
3557
|
|
|
3366
3558
|
### Other Brand Logos
|
|
3367
|
-
Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code
|
|
3368
|
-
|
|
3369
|
-
// Build content blocks with embedded logos
|
|
3370
|
-
const contentBlocks = [];
|
|
3371
|
-
// Validate and embed logos
|
|
3372
|
-
validateLogoMap();
|
|
3373
|
-
const lightLogoPath = LOGO_MAP.default.light;
|
|
3374
|
-
const darkLogoPath = LOGO_MAP.default.dark;
|
|
3375
|
-
await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
|
|
3376
|
-
await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
|
|
3377
|
-
// Add the full library text
|
|
3378
|
-
contentBlocks.push({ type: "text", text: fullLibrary });
|
|
3559
|
+
Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code.`
|
|
3560
|
+
});
|
|
3379
3561
|
return {
|
|
3380
3562
|
content: contentBlocks,
|
|
3381
3563
|
};
|
|
@@ -3942,6 +4124,28 @@ Now design the **${component_description}** following these tokens and principle
|
|
|
3942
4124
|
// Single-theme mode: embed the appropriate logo
|
|
3943
4125
|
await embedLogoWithFeedback(logoPath, `Brand Logo (${theme === "light" ? "for Light Backgrounds" : "for Dark Backgrounds"})`, contentBlocks);
|
|
3944
4126
|
}
|
|
4127
|
+
// Add critical logo usage warning
|
|
4128
|
+
contentBlocks.push({
|
|
4129
|
+
type: "text",
|
|
4130
|
+
text: `## Logo Usage Warning
|
|
4131
|
+
|
|
4132
|
+
> **CRITICAL:** The logo image(s) shown above are the ONLY acceptable representations.
|
|
4133
|
+
>
|
|
4134
|
+
> **NEVER:**
|
|
4135
|
+
> - Draw the logo with SVG paths or shapes
|
|
4136
|
+
> - Recreate it with styled text or colored letters
|
|
4137
|
+
> - Approximate it with CSS or Unicode characters
|
|
4138
|
+
>
|
|
4139
|
+
> **ALWAYS:**
|
|
4140
|
+
> - Use the embedded image directly in your artifact
|
|
4141
|
+
> - Or use \`get_logo_base64\` for a ready-to-use data URL
|
|
4142
|
+
> - If you cannot embed the image, use placeholder text: "[Logo: ${brand.charAt(0).toUpperCase() + brand.slice(1)}]"
|
|
4143
|
+
|
|
4144
|
+
To get the logo as a base64 data URL for artifacts, use:
|
|
4145
|
+
\`\`\`
|
|
4146
|
+
get_logo_base64({ logo_path: "${logoPath}" })
|
|
4147
|
+
\`\`\``,
|
|
4148
|
+
});
|
|
3945
4149
|
// Add the main response text
|
|
3946
4150
|
contentBlocks.push({ type: "text", text: response });
|
|
3947
4151
|
return {
|
|
@@ -4979,6 +5183,23 @@ ${palette.preferredTheme === "dark" ? `- **Note**: ${palette.name} prefers dark
|
|
|
4979
5183
|
type: "text",
|
|
4980
5184
|
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`,
|
|
4981
5185
|
});
|
|
5186
|
+
// Add critical logo usage warning
|
|
5187
|
+
templateContentBlocks.push({
|
|
5188
|
+
type: "text",
|
|
5189
|
+
text: `## Logo Warning
|
|
5190
|
+
|
|
5191
|
+
> **CRITICAL:** Use ONLY the actual logo image files shown above.
|
|
5192
|
+
>
|
|
5193
|
+
> **NEVER:**
|
|
5194
|
+
> - Draw the logo with shapes or vector tools in your document
|
|
5195
|
+
> - Recreate it with styled text or colored letters
|
|
5196
|
+
> - Use any visual approximation or placeholder
|
|
5197
|
+
>
|
|
5198
|
+
> **ALWAYS:**
|
|
5199
|
+
> - Save and embed the actual PNG image file
|
|
5200
|
+
> - Use the code snippets above that reference the saved image file
|
|
5201
|
+
> - The logo images are embedded above - save them directly`,
|
|
5202
|
+
});
|
|
4982
5203
|
// Add the main template text
|
|
4983
5204
|
templateContentBlocks.push({ type: "text", text: template });
|
|
4984
5205
|
return {
|
|
@@ -5270,6 +5491,23 @@ ${rebrandArgs.source_format === "word" ? `
|
|
|
5270
5491
|
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.`,
|
|
5271
5492
|
});
|
|
5272
5493
|
}
|
|
5494
|
+
// Add critical logo usage warning
|
|
5495
|
+
rebrandContentBlocks.push({
|
|
5496
|
+
type: "text",
|
|
5497
|
+
text: `## Logo Usage Warning
|
|
5498
|
+
|
|
5499
|
+
> **CRITICAL:** When adding the logo to your document, use the ACTUAL image files shown above.
|
|
5500
|
+
>
|
|
5501
|
+
> **NEVER:**
|
|
5502
|
+
> - Draw the logo with shapes or vector tools
|
|
5503
|
+
> - Recreate it with styled text or colored letters
|
|
5504
|
+
> - Use any visual approximation
|
|
5505
|
+
>
|
|
5506
|
+
> **ALWAYS:**
|
|
5507
|
+
> - Download and embed the actual logo image
|
|
5508
|
+
> - Or use \`get_logo_base64\` for a data URL for web/HTML
|
|
5509
|
+
> - For Word/PDF: Save the image above and use it directly`,
|
|
5510
|
+
});
|
|
5273
5511
|
// Add the main guide text
|
|
5274
5512
|
rebrandContentBlocks.push({ type: "text", text: rebrandGuide });
|
|
5275
5513
|
return {
|
|
@@ -5380,7 +5618,26 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
|
|
|
5380
5618
|
|
|
5381
5619
|
4. Copy the CSS and component files as described below
|
|
5382
5620
|
`;
|
|
5383
|
-
|
|
5621
|
+
// Build structured response with multiple content blocks for artifact rendering
|
|
5622
|
+
const contentBlocks = [];
|
|
5623
|
+
// Validate logo map on first use
|
|
5624
|
+
validateLogoMap();
|
|
5625
|
+
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5626
|
+
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5627
|
+
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5628
|
+
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5629
|
+
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
5630
|
+
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
5631
|
+
if (!lightEmbedded && !darkEmbedded) {
|
|
5632
|
+
contentBlocks.push({
|
|
5633
|
+
type: "text",
|
|
5634
|
+
text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
// Section 1: Overview and Setup Instructions
|
|
5638
|
+
contentBlocks.push({
|
|
5639
|
+
type: "text",
|
|
5640
|
+
text: `# ${palette.name} App Starter Kit
|
|
5384
5641
|
|
|
5385
5642
|
**App Type:** ${appType}
|
|
5386
5643
|
**Framework:** ${framework}
|
|
@@ -5390,37 +5647,39 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
|
|
|
5390
5647
|
|
|
5391
5648
|
---
|
|
5392
5649
|
|
|
5393
|
-
${setupInstructions}
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
\`\`\`css
|
|
5400
|
-
${cssTheme}
|
|
5401
|
-
\`\`\`
|
|
5402
|
-
|
|
5403
|
-
---
|
|
5404
|
-
|
|
5405
|
-
## Utility Functions (copy to lib/utils.ts)
|
|
5406
|
-
|
|
5407
|
-
\`\`\`typescript
|
|
5408
|
-
${utils}
|
|
5409
|
-
\`\`\`
|
|
5410
|
-
|
|
5411
|
-
---
|
|
5412
|
-
|
|
5413
|
-
## Required Components (${componentSources.length} total)
|
|
5414
|
-
|
|
5415
|
-
${componentSources.map(c => `### ${c.name}.tsx
|
|
5650
|
+
${setupInstructions}`
|
|
5651
|
+
});
|
|
5652
|
+
// Section 2: Global Styles (with filename hint for artifact rendering)
|
|
5653
|
+
contentBlocks.push({
|
|
5654
|
+
type: "text",
|
|
5655
|
+
text: `## Global Styles
|
|
5416
5656
|
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5657
|
+
${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
|
|
5658
|
+
});
|
|
5659
|
+
// Section 3: Utility Functions (with filename hint)
|
|
5660
|
+
contentBlocks.push({
|
|
5661
|
+
type: "text",
|
|
5662
|
+
text: `## Utility Functions
|
|
5420
5663
|
|
|
5421
|
-
|
|
5664
|
+
${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
|
|
5665
|
+
});
|
|
5666
|
+
// Section 4: Each component as a separate content block
|
|
5667
|
+
contentBlocks.push({
|
|
5668
|
+
type: "text",
|
|
5669
|
+
text: `## Required Components (${componentSources.length} total)`
|
|
5670
|
+
});
|
|
5671
|
+
for (const comp of componentSources) {
|
|
5672
|
+
contentBlocks.push({
|
|
5673
|
+
type: "text",
|
|
5674
|
+
text: `### ${comp.name}.tsx
|
|
5422
5675
|
|
|
5423
|
-
|
|
5676
|
+
${formatCodeBlock(comp.source, { language: "tsx", filename: `components/ui/${comp.name}.tsx` })}`
|
|
5677
|
+
});
|
|
5678
|
+
}
|
|
5679
|
+
// Section 5: Page Template
|
|
5680
|
+
contentBlocks.push({
|
|
5681
|
+
type: "text",
|
|
5682
|
+
text: `## Page Template: ${appType}
|
|
5424
5683
|
|
|
5425
5684
|
${layout.description}
|
|
5426
5685
|
|
|
@@ -5430,13 +5689,12 @@ ${layout.structure}
|
|
|
5430
5689
|
- Grid: \`${layout.gridLayout}\`
|
|
5431
5690
|
- Spacing: \`${layout.spacing}\`
|
|
5432
5691
|
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
## Quick Reference
|
|
5692
|
+
${formatCodeBlock(layout.template, { language: "tsx", filename: "app/page.tsx" })}`
|
|
5693
|
+
});
|
|
5694
|
+
// Section 6: Quick Reference
|
|
5695
|
+
contentBlocks.push({
|
|
5696
|
+
type: "text",
|
|
5697
|
+
text: `## Quick Reference
|
|
5440
5698
|
|
|
5441
5699
|
### ${palette.name} Brand Colors
|
|
5442
5700
|
- **Primary:** ${palette.primary} (bg-primary, text-primary)
|
|
@@ -5461,25 +5719,8 @@ ${layout.template}
|
|
|
5461
5719
|
|
|
5462
5720
|
---
|
|
5463
5721
|
|
|
5464
|
-
Now you have everything needed to build a ${palette.name}-branded ${appType} application
|
|
5465
|
-
|
|
5466
|
-
// Embed logos using centralized LOGO_MAP with proper error feedback
|
|
5467
|
-
const contentBlocks = [];
|
|
5468
|
-
// Validate logo map on first use
|
|
5469
|
-
validateLogoMap();
|
|
5470
|
-
// Map brand to centralized LOGO_MAP (sonance uses default lockup)
|
|
5471
|
-
const brandLogoKey = brand === "sonance" ? "default" : brand;
|
|
5472
|
-
const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
|
|
5473
|
-
const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
|
|
5474
|
-
const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
|
|
5475
|
-
const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
|
|
5476
|
-
if (!lightEmbedded && !darkEmbedded) {
|
|
5477
|
-
contentBlocks.push({
|
|
5478
|
-
type: "text",
|
|
5479
|
-
text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
|
|
5480
|
-
});
|
|
5481
|
-
}
|
|
5482
|
-
contentBlocks.push({ type: "text", text: response });
|
|
5722
|
+
Now you have everything needed to build a ${palette.name}-branded ${appType} application!`
|
|
5723
|
+
});
|
|
5483
5724
|
return {
|
|
5484
5725
|
content: contentBlocks,
|
|
5485
5726
|
};
|
|
@@ -5957,31 +6198,49 @@ Use \`redesign_app\` tool on individual files for detailed transformation instru
|
|
|
5957
6198
|
}
|
|
5958
6199
|
const components = COMPONENT_CATEGORIES[category];
|
|
5959
6200
|
const componentsDir = getAssetPath("components");
|
|
5960
|
-
|
|
5961
|
-
|
|
6201
|
+
// Build structured response with multiple content blocks
|
|
6202
|
+
const contentBlocks = [];
|
|
6203
|
+
const categoryDescription = category === "forms" ? "building forms and capturing user input" :
|
|
5962
6204
|
category === "navigation" ? "navigation and menus" :
|
|
5963
6205
|
category === "feedback" ? "user feedback and notifications" :
|
|
5964
6206
|
category === "overlays" ? "modals, dialogs, and overlays" :
|
|
5965
6207
|
category === "data-display" ? "displaying data and content" :
|
|
5966
6208
|
category === "layout" ? "page structure and layouts" :
|
|
5967
|
-
"utility purposes"
|
|
6209
|
+
"utility purposes";
|
|
6210
|
+
contentBlocks.push({
|
|
6211
|
+
type: "text",
|
|
6212
|
+
text: `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length} components)
|
|
6213
|
+
|
|
6214
|
+
Use these components for ${categoryDescription}.`
|
|
6215
|
+
});
|
|
5968
6216
|
for (const compName of components) {
|
|
5969
6217
|
const filePath = path.join(componentsDir, `${compName}.tsx`);
|
|
5970
6218
|
if (fs.existsSync(filePath)) {
|
|
5971
6219
|
try {
|
|
5972
6220
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
5973
|
-
|
|
6221
|
+
contentBlocks.push({
|
|
6222
|
+
type: "text",
|
|
6223
|
+
text: `## ${compName}
|
|
6224
|
+
|
|
6225
|
+
${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${compName}.tsx` })}`
|
|
6226
|
+
});
|
|
5974
6227
|
}
|
|
5975
6228
|
catch {
|
|
5976
|
-
|
|
6229
|
+
contentBlocks.push({
|
|
6230
|
+
type: "text",
|
|
6231
|
+
text: `## ${compName}\n\n*Could not read component file*`
|
|
6232
|
+
});
|
|
5977
6233
|
}
|
|
5978
6234
|
}
|
|
5979
6235
|
else {
|
|
5980
|
-
|
|
6236
|
+
contentBlocks.push({
|
|
6237
|
+
type: "text",
|
|
6238
|
+
text: `## ${compName}\n\n*Component file not found*`
|
|
6239
|
+
});
|
|
5981
6240
|
}
|
|
5982
6241
|
}
|
|
5983
6242
|
return {
|
|
5984
|
-
content:
|
|
6243
|
+
content: contentBlocks,
|
|
5985
6244
|
};
|
|
5986
6245
|
}
|
|
5987
6246
|
default:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response formatter utilities for artifact-friendly MCP outputs
|
|
3
|
+
*
|
|
4
|
+
* These utilities help format code blocks with proper language identifiers
|
|
5
|
+
* and filename hints that Claude Desktop can render as artifacts.
|
|
6
|
+
*/
|
|
7
|
+
export interface CodeBlockOptions {
|
|
8
|
+
language: string;
|
|
9
|
+
filename?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface MultiFileSection {
|
|
12
|
+
filename: string;
|
|
13
|
+
content: string;
|
|
14
|
+
language: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format a code block with proper language identifier and optional filename hint
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* formatCodeBlock(cssContent, { language: 'css', filename: 'globals.css' })
|
|
22
|
+
* // Returns: ```css:globals.css\n{content}\n```
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatCodeBlock(code: string, options: CodeBlockOptions): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format multiple files as separate code blocks with headers
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* formatMultiFile([
|
|
30
|
+
* { filename: 'button.tsx', content: '...', language: 'tsx' },
|
|
31
|
+
* { filename: 'card.tsx', content: '...', language: 'tsx' }
|
|
32
|
+
* ])
|
|
33
|
+
*/
|
|
34
|
+
export declare function formatMultiFile(sections: MultiFileSection[]): string;
|
|
35
|
+
/**
|
|
36
|
+
* Auto-detect the language of code content
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* detectLanguage('import React from "react"') // returns 'tsx'
|
|
40
|
+
* detectLanguage(':root { --color: red; }') // returns 'css'
|
|
41
|
+
*/
|
|
42
|
+
export declare function detectLanguage(content: string): string;
|
|
43
|
+
/**
|
|
44
|
+
* Split large content into manageable chunks based on natural boundaries
|
|
45
|
+
*
|
|
46
|
+
* @param content - The content to split
|
|
47
|
+
* @param options - Split options
|
|
48
|
+
* @returns Array of content chunks
|
|
49
|
+
*/
|
|
50
|
+
export declare function splitContent(content: string, options?: {
|
|
51
|
+
maxLines?: number;
|
|
52
|
+
splitOn?: RegExp;
|
|
53
|
+
}): string[];
|
|
54
|
+
/**
|
|
55
|
+
* Generate a table of contents from markdown content
|
|
56
|
+
*
|
|
57
|
+
* @param content - Markdown content with headings
|
|
58
|
+
* @returns Markdown-formatted table of contents
|
|
59
|
+
*/
|
|
60
|
+
export declare function generateTOC(content: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Create a section with header and formatted code block
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* createCodeSection('Global Styles', cssContent, { language: 'css', filename: 'globals.css' })
|
|
66
|
+
*/
|
|
67
|
+
export declare function createCodeSection(title: string, code: string, options: CodeBlockOptions): string;
|
|
68
|
+
/**
|
|
69
|
+
* Get file extension to language mapping
|
|
70
|
+
*/
|
|
71
|
+
export declare function extensionToLanguage(ext: string): string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response formatter utilities for artifact-friendly MCP outputs
|
|
3
|
+
*
|
|
4
|
+
* These utilities help format code blocks with proper language identifiers
|
|
5
|
+
* and filename hints that Claude Desktop can render as artifacts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Format a code block with proper language identifier and optional filename hint
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* formatCodeBlock(cssContent, { language: 'css', filename: 'globals.css' })
|
|
12
|
+
* // Returns: ```css:globals.css\n{content}\n```
|
|
13
|
+
*/
|
|
14
|
+
export function formatCodeBlock(code, options) {
|
|
15
|
+
const { language, filename } = options;
|
|
16
|
+
const header = filename ? `${language}:${filename}` : language;
|
|
17
|
+
return `\`\`\`${header}\n${code}\n\`\`\``;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format multiple files as separate code blocks with headers
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* formatMultiFile([
|
|
24
|
+
* { filename: 'button.tsx', content: '...', language: 'tsx' },
|
|
25
|
+
* { filename: 'card.tsx', content: '...', language: 'tsx' }
|
|
26
|
+
* ])
|
|
27
|
+
*/
|
|
28
|
+
export function formatMultiFile(sections) {
|
|
29
|
+
return sections.map(section => {
|
|
30
|
+
const header = section.description
|
|
31
|
+
? `### ${section.filename}\n\n${section.description}\n\n`
|
|
32
|
+
: `### ${section.filename}\n\n`;
|
|
33
|
+
return header + formatCodeBlock(section.content, {
|
|
34
|
+
language: section.language,
|
|
35
|
+
filename: section.filename
|
|
36
|
+
});
|
|
37
|
+
}).join('\n\n---\n\n');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Auto-detect the language of code content
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* detectLanguage('import React from "react"') // returns 'tsx'
|
|
44
|
+
* detectLanguage(':root { --color: red; }') // returns 'css'
|
|
45
|
+
*/
|
|
46
|
+
export function detectLanguage(content) {
|
|
47
|
+
// React/TSX patterns (check first - most specific)
|
|
48
|
+
if (/^['"]use client['"]|import.*from\s+['"]react['"]|<[A-Z][a-zA-Z]*[\s/>]/m.test(content)) {
|
|
49
|
+
return 'tsx';
|
|
50
|
+
}
|
|
51
|
+
// CSS patterns
|
|
52
|
+
if (/^:root\s*\{|@tailwind|@layer|--[a-z-]+:|@import\s+['"]|@media\s*\(/m.test(content)) {
|
|
53
|
+
return 'css';
|
|
54
|
+
}
|
|
55
|
+
// TypeScript patterns (non-React)
|
|
56
|
+
if (/^interface\s+\w+|^type\s+\w+\s*=|^export\s+type\s+|:\s*(string|number|boolean|void)\s*[;,)]/m.test(content)) {
|
|
57
|
+
return 'typescript';
|
|
58
|
+
}
|
|
59
|
+
// JSON patterns
|
|
60
|
+
if (/^\s*\{[\s\S]*"[^"]+"\s*:/m.test(content)) {
|
|
61
|
+
return 'json';
|
|
62
|
+
}
|
|
63
|
+
// Python patterns
|
|
64
|
+
if (/^from\s+\w+\s+import|^import\s+\w+|^def\s+\w+\s*\(|^class\s+\w+(\s*\(|:)/m.test(content)) {
|
|
65
|
+
return 'python';
|
|
66
|
+
}
|
|
67
|
+
// Bash/Shell patterns
|
|
68
|
+
if (/^#!/m.test(content) || /^\s*(npm|npx|yarn|pnpm|git|cd|mkdir|curl|wget)\s/m.test(content)) {
|
|
69
|
+
return 'bash';
|
|
70
|
+
}
|
|
71
|
+
// HTML patterns
|
|
72
|
+
if (/^<!DOCTYPE|^<html|^<head|^<body/im.test(content)) {
|
|
73
|
+
return 'html';
|
|
74
|
+
}
|
|
75
|
+
// Markdown patterns
|
|
76
|
+
if (/^#{1,6}\s+|^\*{1,2}[^*]|^\[.*\]\(.*\)|^>\s+/m.test(content)) {
|
|
77
|
+
return 'markdown';
|
|
78
|
+
}
|
|
79
|
+
return 'text';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Split large content into manageable chunks based on natural boundaries
|
|
83
|
+
*
|
|
84
|
+
* @param content - The content to split
|
|
85
|
+
* @param options - Split options
|
|
86
|
+
* @returns Array of content chunks
|
|
87
|
+
*/
|
|
88
|
+
export function splitContent(content, options = {}) {
|
|
89
|
+
const { maxLines = 200, splitOn = /^(?=#{1,3}\s)/m } = options;
|
|
90
|
+
const lines = content.split('\n');
|
|
91
|
+
if (lines.length <= maxLines) {
|
|
92
|
+
return [content];
|
|
93
|
+
}
|
|
94
|
+
// Try to split on natural boundaries (headers)
|
|
95
|
+
const sections = content.split(splitOn).filter(Boolean);
|
|
96
|
+
if (sections.length > 1) {
|
|
97
|
+
return sections;
|
|
98
|
+
}
|
|
99
|
+
// Fall back to line-based splitting
|
|
100
|
+
const chunks = [];
|
|
101
|
+
for (let i = 0; i < lines.length; i += maxLines) {
|
|
102
|
+
chunks.push(lines.slice(i, i + maxLines).join('\n'));
|
|
103
|
+
}
|
|
104
|
+
return chunks;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate a table of contents from markdown content
|
|
108
|
+
*
|
|
109
|
+
* @param content - Markdown content with headings
|
|
110
|
+
* @returns Markdown-formatted table of contents
|
|
111
|
+
*/
|
|
112
|
+
export function generateTOC(content) {
|
|
113
|
+
const headings = content.match(/^#{1,3}\s+.+$/gm) || [];
|
|
114
|
+
return headings.map(heading => {
|
|
115
|
+
const level = (heading.match(/^#+/) || [''])[0].length;
|
|
116
|
+
const text = heading.replace(/^#+\s+/, '');
|
|
117
|
+
const anchor = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '');
|
|
118
|
+
const indent = ' '.repeat(level - 1);
|
|
119
|
+
return `${indent}- [${text}](#${anchor})`;
|
|
120
|
+
}).join('\n');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a section with header and formatted code block
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* createCodeSection('Global Styles', cssContent, { language: 'css', filename: 'globals.css' })
|
|
127
|
+
*/
|
|
128
|
+
export function createCodeSection(title, code, options) {
|
|
129
|
+
return `## ${title}\n\n${formatCodeBlock(code, options)}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get file extension to language mapping
|
|
133
|
+
*/
|
|
134
|
+
export function extensionToLanguage(ext) {
|
|
135
|
+
const map = {
|
|
136
|
+
'.tsx': 'tsx',
|
|
137
|
+
'.ts': 'typescript',
|
|
138
|
+
'.jsx': 'tsx',
|
|
139
|
+
'.js': 'javascript',
|
|
140
|
+
'.css': 'css',
|
|
141
|
+
'.scss': 'scss',
|
|
142
|
+
'.json': 'json',
|
|
143
|
+
'.md': 'markdown',
|
|
144
|
+
'.py': 'python',
|
|
145
|
+
'.html': 'html',
|
|
146
|
+
'.sh': 'bash',
|
|
147
|
+
'.bash': 'bash',
|
|
148
|
+
'.yaml': 'yaml',
|
|
149
|
+
'.yml': 'yaml',
|
|
150
|
+
};
|
|
151
|
+
return map[ext.toLowerCase()] || 'text';
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.118",
|
|
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",
|