sonance-brand-mcp 1.3.115 → 1.3.117

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.
@@ -26,7 +26,7 @@ const alertVariants = cva(
26
26
  },
27
27
  defaultVariants: {
28
28
  variant: "default",
29
- size: "sm",
29
+ size: "xs",
30
30
  },
31
31
  }
32
32
  );
@@ -64,7 +64,7 @@ interface AlertProps
64
64
  }
65
65
 
66
66
  export const Alert = forwardRef<HTMLDivElement, AlertProps>(
67
- ({ className, variant = "default", size = "sm", title, children, dismissible, onClose, state, ...props }, ref) => {
67
+ ({ className, variant = "default", size = "xs", title, children, dismissible, onClose, state, ...props }, ref) => {
68
68
  const Icon = iconMap[variant || "default"];
69
69
  const showCloseButton = dismissible || onClose;
70
70
 
@@ -23,7 +23,7 @@ const avatarVariants = cva(
23
23
  },
24
24
  },
25
25
  defaultVariants: {
26
- size: "md",
26
+ size: "sm",
27
27
  shape: "circle",
28
28
  },
29
29
  }
@@ -120,7 +120,7 @@ interface AvatarGroupProps {
120
120
  className?: string;
121
121
  }
122
122
 
123
- function AvatarGroup({ children, max, size = "md", className }: AvatarGroupProps) {
123
+ function AvatarGroup({ children, max, size = "sm", className }: AvatarGroupProps) {
124
124
  const avatars = Array.isArray(children) ? children : [children];
125
125
  const displayAvatars = max ? avatars.slice(0, max) : avatars;
126
126
  const excess = max ? Math.max(0, avatars.length - max) : 0;
@@ -31,7 +31,7 @@ const badgeVariants = cva(
31
31
  },
32
32
  defaultVariants: {
33
33
  variant: "default",
34
- size: "sm",
34
+ size: "xs",
35
35
  },
36
36
  }
37
37
  );
@@ -70,7 +70,7 @@ interface ChipProps extends BadgeProps {
70
70
  onClose?: () => void;
71
71
  }
72
72
 
73
- export function Chip({ className, variant, size = "sm", onClose, children, ...props }: ChipProps) {
73
+ export function Chip({ className, variant, size = "xs", onClose, children, ...props }: ChipProps) {
74
74
  return (
75
75
  <span
76
76
  id="chip-span"
@@ -36,7 +36,7 @@ const buttonVariants = cva(
36
36
  },
37
37
  defaultVariants: {
38
38
  variant: "default",
39
- size: "sm",
39
+ size: "xs",
40
40
  },
41
41
  }
42
42
  );
@@ -30,7 +30,7 @@ const cardVariants = cva(
30
30
  },
31
31
  defaultVariants: {
32
32
  variant: "default",
33
- size: "default",
33
+ size: "compact",
34
34
  },
35
35
  }
36
36
  );
@@ -19,7 +19,7 @@ const checkboxVariants = cva(
19
19
  },
20
20
  },
21
21
  defaultVariants: {
22
- size: "sm",
22
+ size: "xs",
23
23
  },
24
24
  }
25
25
  );
@@ -56,7 +56,7 @@ const getStateStyles = (state?: CheckboxState) => {
56
56
  };
57
57
 
58
58
  export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
59
- ({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, onCheckedChange, style, size = "sm", ...props }, ref) => {
59
+ ({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, onCheckedChange, style, size = "xs", ...props }, ref) => {
60
60
  const uniqueId = useId();
61
61
  const inputId = id || `checkbox-${uniqueId}`;
62
62
  const isDisabled = disabled || state === "disabled";
@@ -20,7 +20,7 @@ interface DialogProps {
20
20
  size?: "compact" | "default" | "spacious";
21
21
  }
22
22
 
23
- export function Dialog({ open, onClose, children, size = "default" }: DialogProps) {
23
+ export function Dialog({ open, onClose, children, size = "compact" }: DialogProps) {
24
24
  // Close on escape
25
25
  useEffect(() => {
26
26
  const handleEscape = (e: KeyboardEvent) => {
@@ -24,7 +24,7 @@ const inputVariants = cva(
24
24
  },
25
25
  },
26
26
  defaultVariants: {
27
- size: "sm",
27
+ size: "xs",
28
28
  inputVariant: "default",
29
29
  },
30
30
  }
@@ -34,7 +34,7 @@ const kbdVariants = cva(
34
34
  },
35
35
  defaultVariants: {
36
36
  variant: "default",
37
- size: "md",
37
+ size: "sm",
38
38
  },
39
39
  }
40
40
  );
@@ -209,7 +209,7 @@ interface StepperProps extends Omit<NumberInputProps, "hideControls"> {
209
209
  size?: "sm" | "md" | "lg";
210
210
  }
211
211
 
212
- export function Stepper({ size = "md", className, ...props }: StepperProps) {
212
+ export function Stepper({ size = "sm", className, ...props }: StepperProps) {
213
213
  const sizeClasses = {
214
214
  sm: "h-8 text-sm",
215
215
  md: "h-10 text-base",
@@ -19,7 +19,7 @@ const paginationButtonVariants = cva(
19
19
  },
20
20
  },
21
21
  defaultVariants: {
22
- size: "sm",
22
+ size: "xs",
23
23
  },
24
24
  }
25
25
  );
@@ -38,7 +38,7 @@ const getButtonStateStyles = (state?: PaginationButtonState) => {
38
38
  return stateMap[state] || "";
39
39
  };
40
40
 
41
- const PaginationSizeContext = createContext<"xs" | "sm" | "md" | "lg">("sm");
41
+ const PaginationSizeContext = createContext<"xs" | "sm" | "md" | "lg">("xs");
42
42
 
43
43
  const ellipsisSizes = {
44
44
  xs: "h-6 w-6 text-[10px]",
@@ -112,7 +112,7 @@ export function Pagination({
112
112
  siblingCount = 1,
113
113
  showFirstLast = true,
114
114
  className,
115
- size = "sm",
115
+ size = "xs",
116
116
  }: PaginationProps) {
117
117
  const pages = generatePaginationRange(currentPage, totalPages, siblingCount);
118
118
 
@@ -233,7 +233,7 @@ export function CompactPagination({
233
233
  totalPages,
234
234
  onPageChange,
235
235
  className,
236
- size = "sm",
236
+ size = "xs",
237
237
  }: CompactPaginationProps) {
238
238
  return (
239
239
  <PaginationSizeContext.Provider value={size}>
@@ -9,7 +9,7 @@ type PopoverPosition = "top" | "bottom" | "left" | "right";
9
9
  type PopoverSize = "compact" | "default" | "spacious";
10
10
  type PopoverVariant = "default" | "glass";
11
11
 
12
- const PopoverContext = createContext<{ size: PopoverSize }>({ size: "default" });
12
+ const PopoverContext = createContext<{ size: PopoverSize }>({ size: "compact" });
13
13
 
14
14
  const popoverVariants = cva(
15
15
  "fixed z-50 min-w-[200px] border shadow-xl animate-in fade-in zoom-in-95 duration-150",
@@ -28,7 +28,7 @@ const popoverVariants = cva(
28
28
  },
29
29
  },
30
30
  defaultVariants: {
31
- size: "default",
31
+ size: "compact",
32
32
  popoverVariant: "default",
33
33
  },
34
34
  }
@@ -59,7 +59,7 @@ export function Popover({
59
59
  position = "bottom",
60
60
  triggerOn = "click",
61
61
  className,
62
- size = "default",
62
+ size = "compact",
63
63
  popoverVariant = "default",
64
64
  }: PopoverProps) {
65
65
  const [isOpen, setIsOpen] = useState(false);
@@ -180,7 +180,7 @@ export function Popover({
180
180
  };
181
181
 
182
182
  return (
183
- <PopoverContext.Provider value={{ size: size ?? "default" }}>
183
+ <PopoverContext.Provider value={{ size: size ?? "compact" }}>
184
184
  <div data-sonance-name="popover"
185
185
  ref={containerRef}
186
186
  className="relative inline-block"
@@ -13,7 +13,7 @@ interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
13
13
  }
14
14
 
15
15
  export const Progress = forwardRef<HTMLDivElement, ProgressProps>(
16
- ({ className, value = 0, max = 100, label, showValue = false, size = "md", ...props }, ref) => {
16
+ ({ className, value = 0, max = 100, label, showValue = false, size = "sm", ...props }, ref) => {
17
17
  const percentage = Math.min(100, Math.max(0, (value / max) * 100));
18
18
 
19
19
  const heightClasses = {
@@ -79,7 +79,7 @@ const circularSizeMap = {
79
79
  export function CircularProgress({
80
80
  value = 0,
81
81
  max = 100,
82
- size = "md",
82
+ size = "sm",
83
83
  strokeWidth = 4,
84
84
  showValue = false,
85
85
  className,
@@ -27,7 +27,7 @@ const selectTriggerVariants = cva(
27
27
  },
28
28
  },
29
29
  defaultVariants: {
30
- size: "sm",
30
+ size: "xs",
31
31
  selectVariant: "default",
32
32
  },
33
33
  }
@@ -237,7 +237,7 @@ interface NativeSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectEl
237
237
  }
238
238
 
239
239
  export const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(
240
- ({ className, label, error, options, state, disabled, size = "sm", ...props }, ref) => {
240
+ ({ className, label, error, options, state, disabled, size = "xs", ...props }, ref) => {
241
241
  const isDisabled = disabled || state === "disabled";
242
242
  const hasError = error || state === "error";
243
243
 
@@ -6,7 +6,7 @@ interface SpinnerProps {
6
6
  label?: string;
7
7
  }
8
8
 
9
- export function Spinner({ size = "md", className, label }: SpinnerProps) {
9
+ export function Spinner({ size = "sm", className, label }: SpinnerProps) {
10
10
  const sizeClasses = {
11
11
  sm: "h-4 w-4 border-2",
12
12
  md: "h-6 w-6 border-2",
@@ -38,7 +38,7 @@ interface DotsSpinnerProps {
38
38
  className?: string;
39
39
  }
40
40
 
41
- export function DotsSpinner({ size = "md", className }: DotsSpinnerProps) {
41
+ export function DotsSpinner({ size = "sm", className }: DotsSpinnerProps) {
42
42
  const dotSizes = {
43
43
  sm: "h-1.5 w-1.5",
44
44
  md: "h-2 w-2",
@@ -18,7 +18,7 @@ const switchTrackVariants = cva(
18
18
  },
19
19
  },
20
20
  defaultVariants: {
21
- size: "sm",
21
+ size: "xs",
22
22
  },
23
23
  }
24
24
  );
@@ -53,7 +53,7 @@ const getTrackStateStyles = (state?: SwitchState) => {
53
53
  };
54
54
 
55
55
  export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
56
- ({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, style, size = "sm", ...props }, ref) => {
56
+ ({ className, label, description, id, state, disabled, checked, defaultChecked, onChange, style, size = "xs", ...props }, ref) => {
57
57
  const uniqueId = useId();
58
58
  const inputId = id || `switch-${uniqueId}`;
59
59
  const isDisabled = disabled || state === "disabled";
@@ -18,7 +18,7 @@ const tabsTriggerVariants = cva(
18
18
  },
19
19
  },
20
20
  defaultVariants: {
21
- size: "sm",
21
+ size: "xs",
22
22
  },
23
23
  }
24
24
  );
@@ -61,7 +61,7 @@ export function Tabs({
61
61
  onValueChange,
62
62
  className,
63
63
  children,
64
- size = "sm",
64
+ size = "xs",
65
65
  }: TabsProps) {
66
66
  const [internalValue, setInternalValue] = useState(defaultValue);
67
67
  const value = controlledValue ?? internalValue;
@@ -31,7 +31,7 @@ const tooltipContentVariants = cva(
31
31
  },
32
32
  },
33
33
  defaultVariants: {
34
- size: "sm",
34
+ size: "xs",
35
35
  tooltipVariant: "default",
36
36
  },
37
37
  }
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
  // ============================================
@@ -2915,7 +2916,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2915
2916
  try {
2916
2917
  const content = fs.readFileSync(getAssetPath("css"), "utf-8");
2917
2918
  return {
2918
- content: [{ type: "text", text: `/* Sonance Brand Theme - globals.css */\n\n${content}` }],
2919
+ content: [{
2920
+ type: "text",
2921
+ text: `# Sonance Brand Theme
2922
+
2923
+ ${formatCodeBlock(content, { language: "css", filename: "globals.css" })}`
2924
+ }],
2919
2925
  };
2920
2926
  }
2921
2927
  catch (e) {
@@ -2964,10 +2970,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2964
2970
  const match = files.find(f => f.toLowerCase() === `${componentName}.tsx`);
2965
2971
  if (match) {
2966
2972
  const content = fs.readFileSync(path.join(componentsDir, match), "utf-8");
2973
+ const matchedName = match.replace('.tsx', '');
2967
2974
  return {
2968
2975
  content: [{
2969
2976
  type: "text",
2970
- text: `// File: components/ui/${match}\n\n${content}`
2977
+ text: `# ${matchedName} Component
2978
+
2979
+ ${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${match}` })}`
2971
2980
  }],
2972
2981
  };
2973
2982
  }
@@ -2980,7 +2989,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2980
2989
  return {
2981
2990
  content: [{
2982
2991
  type: "text",
2983
- text: `// File: components/ui/${componentName}.tsx\n\n${content}`
2992
+ text: `# ${componentName} Component
2993
+
2994
+ ${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${componentName}.tsx` })}`
2984
2995
  }],
2985
2996
  };
2986
2997
  }
@@ -2997,7 +3008,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2997
3008
  return {
2998
3009
  content: [{
2999
3010
  type: "text",
3000
- text: `// File: lib/utils.ts\n// These utilities are used by Sonance UI components\n\n${content}`
3011
+ text: `# Sonance UI Utilities
3012
+
3013
+ These utilities are used by Sonance UI components.
3014
+
3015
+ ${formatCodeBlock(content, { language: "typescript", filename: "lib/utils.ts" })}`
3001
3016
  }],
3002
3017
  };
3003
3018
  }
@@ -3280,36 +3295,73 @@ ${!validation.valid ? `
3280
3295
  }
3281
3296
  case "get_full_library": {
3282
3297
  try {
3283
- let fullLibrary = "# Sonance Component Library\n\n";
3284
- fullLibrary += "This is the complete Sonance UI component library. Copy these files into your project.\n\n";
3285
- // Add brand guidelines
3286
- fullLibrary += "---\n\n## 1. Brand Guidelines\n\n";
3298
+ // Build structured response with multiple content blocks for artifact rendering
3299
+ const contentBlocks = [];
3300
+ // Validate and embed logos first
3301
+ validateLogoMap();
3302
+ const lightLogoPath = LOGO_MAP.default.light;
3303
+ const darkLogoPath = LOGO_MAP.default.dark;
3304
+ await embedLogoWithFeedback(lightLogoPath, "Brand Logo (for Light Backgrounds)", contentBlocks);
3305
+ await embedLogoWithFeedback(darkLogoPath, "Brand Logo (for Dark Backgrounds)", contentBlocks);
3306
+ // Section 1: Overview
3307
+ contentBlocks.push({
3308
+ type: "text",
3309
+ text: `# Sonance Component Library
3310
+
3311
+ This is the complete Sonance UI component library. Copy these files into your project.
3312
+
3313
+ ## Table of Contents
3314
+ 1. [Brand Guidelines](#brand-guidelines)
3315
+ 2. [CSS Theme](#css-theme)
3316
+ 3. [Utilities](#utilities)
3317
+ 4. [UI Components](#ui-components)
3318
+ 5. [Brand Logos](#brand-logos)`
3319
+ });
3320
+ // Section 2: Brand Guidelines
3321
+ let guidelines = "*Brand guidelines not found*";
3287
3322
  try {
3288
- fullLibrary += fs.readFileSync(getAssetPath("guidelines"), "utf-8");
3323
+ guidelines = fs.readFileSync(getAssetPath("guidelines"), "utf-8");
3289
3324
  }
3290
3325
  catch (e) {
3291
- fullLibrary += "*Brand guidelines not found*\n";
3326
+ // Use fallback
3292
3327
  }
3293
- // Add CSS theme
3294
- fullLibrary += "\n\n---\n\n## 2. CSS Theme (globals.css)\n\n```css\n";
3328
+ contentBlocks.push({
3329
+ type: "text",
3330
+ text: `## 1. Brand Guidelines\n\n${guidelines}`
3331
+ });
3332
+ // Section 3: CSS Theme (with filename hint)
3333
+ let cssTheme = "/* CSS theme not found */";
3295
3334
  try {
3296
- fullLibrary += fs.readFileSync(getAssetPath("css"), "utf-8");
3335
+ cssTheme = fs.readFileSync(getAssetPath("css"), "utf-8");
3297
3336
  }
3298
3337
  catch (e) {
3299
- fullLibrary += "/* CSS theme not found */";
3338
+ // Use fallback
3300
3339
  }
3301
- fullLibrary += "\n```\n";
3302
- // Add utilities
3303
- fullLibrary += "\n\n---\n\n## 3. Utilities (lib/utils.ts)\n\n```typescript\n";
3340
+ contentBlocks.push({
3341
+ type: "text",
3342
+ text: `## 2. CSS Theme
3343
+
3344
+ ${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
3345
+ });
3346
+ // Section 4: Utilities (with filename hint)
3347
+ let utils = "// Utils not found";
3304
3348
  try {
3305
- fullLibrary += fs.readFileSync(getAssetPath("utils"), "utf-8");
3349
+ utils = fs.readFileSync(getAssetPath("utils"), "utf-8");
3306
3350
  }
3307
3351
  catch (e) {
3308
- fullLibrary += "// Utils not found";
3352
+ // Use fallback
3309
3353
  }
3310
- fullLibrary += "\n```\n";
3311
- // Add components
3312
- fullLibrary += "\n\n---\n\n## 4. UI Components\n\n";
3354
+ contentBlocks.push({
3355
+ type: "text",
3356
+ text: `## 3. Utilities
3357
+
3358
+ ${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
3359
+ });
3360
+ // Section 5: Components - each as a separate block
3361
+ contentBlocks.push({
3362
+ type: "text",
3363
+ text: `## 4. UI Components`
3364
+ });
3313
3365
  try {
3314
3366
  const componentsDir = getAssetPath("components");
3315
3367
  const files = fs.readdirSync(componentsDir)
@@ -3318,24 +3370,35 @@ ${!validation.valid ? `
3318
3370
  for (const file of files) {
3319
3371
  const componentName = file.replace('.tsx', '');
3320
3372
  const content = fs.readFileSync(path.join(componentsDir, file), "utf-8");
3321
- fullLibrary += `### ${componentName}\n\nFile: \`components/ui/${file}\`\n\n\`\`\`tsx\n${content}\n\`\`\`\n\n`;
3373
+ contentBlocks.push({
3374
+ type: "text",
3375
+ text: `### ${componentName}
3376
+
3377
+ ${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${file}` })}`
3378
+ });
3322
3379
  }
3323
3380
  }
3324
3381
  catch (e) {
3325
- fullLibrary += "*Components directory not found*\n";
3382
+ contentBlocks.push({
3383
+ type: "text",
3384
+ text: "*Components directory not found*"
3385
+ });
3326
3386
  }
3327
- // Add logo section with implementation guidance
3328
- fullLibrary += "\n\n---\n\n## 5. Brand Logos\n\n";
3329
- fullLibrary += "Use these logos in your application. The Sonance+James+IPORT lockup is the default.\n\n";
3330
- fullLibrary += `### Logo Paths
3387
+ // Section 6: Logo guidance
3388
+ contentBlocks.push({
3389
+ type: "text",
3390
+ text: `## 5. Brand Logos
3391
+
3392
+ Use these logos in your application. The Sonance+James+IPORT lockup is the default.
3393
+
3394
+ ### Logo Paths
3331
3395
  | Variant | Path | Use On |
3332
3396
  |---------|------|--------|
3333
3397
  | **Light Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Dark.png\` | White, light gray backgrounds |
3334
3398
  | **Dark Backgrounds** | \`/logos/sonance-james-iport/Sonance_James_IPORT_Lockup_Light.png\` | Charcoal, dark backgrounds |
3335
3399
 
3336
3400
  ### Implementation (Next.js)
3337
- \`\`\`tsx
3338
- import Image from 'next/image';
3401
+ ${formatCodeBlock(`import Image from 'next/image';
3339
3402
 
3340
3403
  // Light mode logo (for light backgrounds)
3341
3404
  <Image
@@ -3355,8 +3418,7 @@ import Image from 'next/image';
3355
3418
  height={40}
3356
3419
  className="h-10 w-auto hidden dark:block"
3357
3420
  priority
3358
- />
3359
- \`\`\`
3421
+ />`, { language: "tsx", filename: "components/Logo.tsx" })}
3360
3422
 
3361
3423
  ### Sizing Guidelines
3362
3424
  - Header/Navbar: \`h-8\` to \`h-10\` (32-40px)
@@ -3364,18 +3426,8 @@ import Image from 'next/image';
3364
3426
  - Footer: \`h-6\` to \`h-8\` (24-32px)
3365
3427
 
3366
3428
  ### 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 });
3429
+ Use \`list_logos\` to see all available logos, or \`get_logo\` to retrieve a specific logo with implementation code.`
3430
+ });
3379
3431
  return {
3380
3432
  content: contentBlocks,
3381
3433
  };
@@ -5380,7 +5432,26 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
5380
5432
 
5381
5433
  4. Copy the CSS and component files as described below
5382
5434
  `;
5383
- const response = `# ${palette.name} App Starter Kit
5435
+ // Build structured response with multiple content blocks for artifact rendering
5436
+ const contentBlocks = [];
5437
+ // Validate logo map on first use
5438
+ validateLogoMap();
5439
+ // Map brand to centralized LOGO_MAP (sonance uses default lockup)
5440
+ const brandLogoKey = brand === "sonance" ? "default" : brand;
5441
+ const lightLogoPath = LOGO_MAP[brandLogoKey]?.light || LOGO_MAP.default.light;
5442
+ const darkLogoPath = LOGO_MAP[brandLogoKey]?.dark || LOGO_MAP.default.dark;
5443
+ const lightEmbedded = await embedLogoWithFeedback(lightLogoPath, "Logo for Light Backgrounds", contentBlocks);
5444
+ const darkEmbedded = await embedLogoWithFeedback(darkLogoPath, "Logo for Dark Backgrounds", contentBlocks);
5445
+ if (!lightEmbedded && !darkEmbedded) {
5446
+ contentBlocks.push({
5447
+ type: "text",
5448
+ text: `> **Logo Note:** Brand logos could not be embedded. Use \`list_logos\` and \`get_logo\` to retrieve logo assets manually.`,
5449
+ });
5450
+ }
5451
+ // Section 1: Overview and Setup Instructions
5452
+ contentBlocks.push({
5453
+ type: "text",
5454
+ text: `# ${palette.name} App Starter Kit
5384
5455
 
5385
5456
  **App Type:** ${appType}
5386
5457
  **Framework:** ${framework}
@@ -5390,37 +5461,39 @@ npm install @radix-ui/react-slot @radix-ui/react-dialog
5390
5461
 
5391
5462
  ---
5392
5463
 
5393
- ${setupInstructions}
5394
-
5395
- ---
5396
-
5397
- ## Global Styles (copy to globals.css)
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
5464
+ ${setupInstructions}`
5465
+ });
5466
+ // Section 2: Global Styles (with filename hint for artifact rendering)
5467
+ contentBlocks.push({
5468
+ type: "text",
5469
+ text: `## Global Styles
5416
5470
 
5417
- \`\`\`tsx
5418
- ${c.source}
5419
- \`\`\``).join("\n\n")}
5471
+ ${formatCodeBlock(cssTheme, { language: "css", filename: "globals.css" })}`
5472
+ });
5473
+ // Section 3: Utility Functions (with filename hint)
5474
+ contentBlocks.push({
5475
+ type: "text",
5476
+ text: `## Utility Functions
5420
5477
 
5421
- ---
5478
+ ${formatCodeBlock(utils, { language: "typescript", filename: "lib/utils.ts" })}`
5479
+ });
5480
+ // Section 4: Each component as a separate content block
5481
+ contentBlocks.push({
5482
+ type: "text",
5483
+ text: `## Required Components (${componentSources.length} total)`
5484
+ });
5485
+ for (const comp of componentSources) {
5486
+ contentBlocks.push({
5487
+ type: "text",
5488
+ text: `### ${comp.name}.tsx
5422
5489
 
5423
- ## Page Template: ${appType}
5490
+ ${formatCodeBlock(comp.source, { language: "tsx", filename: `components/ui/${comp.name}.tsx` })}`
5491
+ });
5492
+ }
5493
+ // Section 5: Page Template
5494
+ contentBlocks.push({
5495
+ type: "text",
5496
+ text: `## Page Template: ${appType}
5424
5497
 
5425
5498
  ${layout.description}
5426
5499
 
@@ -5430,13 +5503,12 @@ ${layout.structure}
5430
5503
  - Grid: \`${layout.gridLayout}\`
5431
5504
  - Spacing: \`${layout.spacing}\`
5432
5505
 
5433
- \`\`\`tsx
5434
- ${layout.template}
5435
- \`\`\`
5436
-
5437
- ---
5438
-
5439
- ## Quick Reference
5506
+ ${formatCodeBlock(layout.template, { language: "tsx", filename: "app/page.tsx" })}`
5507
+ });
5508
+ // Section 6: Quick Reference
5509
+ contentBlocks.push({
5510
+ type: "text",
5511
+ text: `## Quick Reference
5440
5512
 
5441
5513
  ### ${palette.name} Brand Colors
5442
5514
  - **Primary:** ${palette.primary} (bg-primary, text-primary)
@@ -5461,25 +5533,8 @@ ${layout.template}
5461
5533
 
5462
5534
  ---
5463
5535
 
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 });
5536
+ Now you have everything needed to build a ${palette.name}-branded ${appType} application!`
5537
+ });
5483
5538
  return {
5484
5539
  content: contentBlocks,
5485
5540
  };
@@ -5957,31 +6012,49 @@ Use \`redesign_app\` tool on individual files for detailed transformation instru
5957
6012
  }
5958
6013
  const components = COMPONENT_CATEGORIES[category];
5959
6014
  const componentsDir = getAssetPath("components");
5960
- let response = `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length} components)\n\n`;
5961
- response += `Use these components for ${category === "forms" ? "building forms and capturing user input" :
6015
+ // Build structured response with multiple content blocks
6016
+ const contentBlocks = [];
6017
+ const categoryDescription = category === "forms" ? "building forms and capturing user input" :
5962
6018
  category === "navigation" ? "navigation and menus" :
5963
6019
  category === "feedback" ? "user feedback and notifications" :
5964
6020
  category === "overlays" ? "modals, dialogs, and overlays" :
5965
6021
  category === "data-display" ? "displaying data and content" :
5966
6022
  category === "layout" ? "page structure and layouts" :
5967
- "utility purposes"}.\n\n---\n\n`;
6023
+ "utility purposes";
6024
+ contentBlocks.push({
6025
+ type: "text",
6026
+ text: `# ${category.charAt(0).toUpperCase() + category.slice(1)} Components (${components.length} components)
6027
+
6028
+ Use these components for ${categoryDescription}.`
6029
+ });
5968
6030
  for (const compName of components) {
5969
6031
  const filePath = path.join(componentsDir, `${compName}.tsx`);
5970
6032
  if (fs.existsSync(filePath)) {
5971
6033
  try {
5972
6034
  const content = fs.readFileSync(filePath, "utf-8");
5973
- response += `## ${compName}\n\nFile: \`components/ui/${compName}.tsx\`\n\n\`\`\`tsx\n${content}\n\`\`\`\n\n---\n\n`;
6035
+ contentBlocks.push({
6036
+ type: "text",
6037
+ text: `## ${compName}
6038
+
6039
+ ${formatCodeBlock(content, { language: "tsx", filename: `components/ui/${compName}.tsx` })}`
6040
+ });
5974
6041
  }
5975
6042
  catch {
5976
- response += `## ${compName}\n\n*Could not read component file*\n\n---\n\n`;
6043
+ contentBlocks.push({
6044
+ type: "text",
6045
+ text: `## ${compName}\n\n*Could not read component file*`
6046
+ });
5977
6047
  }
5978
6048
  }
5979
6049
  else {
5980
- response += `## ${compName}\n\n*Component file not found*\n\n---\n\n`;
6050
+ contentBlocks.push({
6051
+ type: "text",
6052
+ text: `## ${compName}\n\n*Component file not found*`
6053
+ });
5981
6054
  }
5982
6055
  }
5983
6056
  return {
5984
- content: [{ type: "text", text: response }],
6057
+ content: contentBlocks,
5985
6058
  };
5986
6059
  }
5987
6060
  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.115",
3
+ "version": "1.3.117",
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",