@trishchuk/coolors-mcp 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
  3. package/.github/pull_request_template.md +33 -8
  4. package/.github/workflows/ci.yml +107 -104
  5. package/.github/workflows/deploy-docs.yml +14 -11
  6. package/.github/workflows/release.yml +25 -23
  7. package/README.md +149 -15
  8. package/dist/bin/server.js +997 -256
  9. package/dist/bin/server.js.map +1 -1
  10. package/dist/{chunk-P3ARRKLS.js → chunk-HOMDMKUY.js} +3 -1
  11. package/dist/{chunk-P3ARRKLS.js.map → chunk-HOMDMKUY.js.map} +1 -1
  12. package/dist/{chunk-IQ7NN26V.js → chunk-LHW2ZTOU.js} +14 -2
  13. package/dist/chunk-LHW2ZTOU.js.map +1 -0
  14. package/dist/color/index.js +1 -1
  15. package/dist/coolors-mcp.d.ts +4 -4
  16. package/dist/coolors-mcp.js +1 -1
  17. package/docs/.vitepress/components/ClientGrid.vue +9 -3
  18. package/docs/.vitepress/components/CodeBlock.vue +51 -44
  19. package/docs/.vitepress/components/ConfigModal.vue +151 -67
  20. package/docs/.vitepress/components/DiagramModal.vue +186 -154
  21. package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
  22. package/docs/.vitepress/config.js +171 -141
  23. package/docs/.vitepress/theme/FundingLayout.vue +65 -54
  24. package/docs/.vitepress/theme/Layout.vue +21 -21
  25. package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
  26. package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
  27. package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
  28. package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
  29. package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
  30. package/docs/.vitepress/theme/custom-app.css +19 -12
  31. package/docs/.vitepress/theme/custom.css +33 -25
  32. package/docs/.vitepress/theme/index.js +19 -16
  33. package/docs/concepts/accessibility.md +59 -47
  34. package/docs/concepts/color-spaces.md +28 -6
  35. package/docs/concepts/distance-metrics.md +45 -30
  36. package/docs/concepts/hct.md +30 -27
  37. package/docs/concepts/image-analysis.md +52 -21
  38. package/docs/concepts/material-design.md +43 -17
  39. package/docs/concepts/theme-matching.md +64 -40
  40. package/docs/examples/basic-colors.md +92 -108
  41. package/docs/examples/creating-themes.md +104 -108
  42. package/docs/examples/css-refactoring.md +33 -29
  43. package/docs/examples/image-extraction.md +145 -138
  44. package/docs/getting-started.md +45 -34
  45. package/docs/index.md +5 -1
  46. package/docs/installation.md +15 -1
  47. package/docs/tools/accessibility.md +74 -68
  48. package/docs/tools/image-extraction.md +62 -54
  49. package/docs/tools/theme-matching.md +45 -42
  50. package/eslint.config.ts +13 -0
  51. package/jsr.json +1 -1
  52. package/package.json +17 -13
  53. package/src/bin/server.ts +13 -1
  54. package/src/color/__tests__/extract-colors.test.ts +20 -30
  55. package/src/color/apca.ts +105 -0
  56. package/src/color/color-blindness.ts +109 -0
  57. package/src/coolors-mcp.ts +1 -1
  58. package/src/session.ts +10 -2
  59. package/src/theme/matcher.ts +1 -1
  60. package/src/theme/refactor.ts +1 -1
  61. package/src/theme/types.ts +3 -0
  62. package/src/tools/__tests__/cohesion.test.ts +97 -0
  63. package/src/tools/__tests__/color-blindness.test.ts +45 -0
  64. package/src/tools/__tests__/color-conversion.test.ts +38 -0
  65. package/src/tools/__tests__/contrast-checker.test.ts +56 -0
  66. package/src/tools/__tests__/palette-export.test.ts +54 -0
  67. package/src/tools/adjust-color.tool.ts +80 -0
  68. package/src/tools/cohesion.tools.ts +380 -0
  69. package/src/tools/color-blindness.tool.ts +168 -0
  70. package/src/tools/color-conversion.tool.ts +1 -1
  71. package/src/tools/contrast-checker.tool.ts +53 -14
  72. package/src/tools/dislike-analyzer.tool.ts +41 -54
  73. package/src/tools/image-extraction.tools.ts +62 -115
  74. package/src/tools/index.ts +15 -2
  75. package/src/tools/palette-export.tool.ts +174 -0
  76. package/src/tools/palette-with-locks.tool.ts +8 -6
  77. package/src/types.ts +2 -3
  78. package/tsconfig.json +12 -2
  79. package/vitest.config.js +1 -3
  80. package/.claude/settings.local.json +0 -39
  81. package/.env +0 -2
  82. package/.mcp.json +0 -12
  83. package/CLAUDE.md +0 -201
  84. package/DOCUMENTATION.md +0 -274
  85. package/GEMINI.md +0 -54
  86. package/demo/content_based_color.png +0 -0
  87. package/demo/music-player.html +0 -621
  88. package/demo/podcast-player.html +0 -903
  89. package/dist/chunk-IQ7NN26V.js.map +0 -1
  90. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +0 -93
  91. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +0 -7
  92. package/docs/.vitepress/cache/deps/_metadata.json +0 -127
  93. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +0 -9
  94. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
  95. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -12683
  96. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
  97. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -9719
  98. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
  99. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -4710
  100. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
  101. package/docs/.vitepress/cache/deps/cytoscape.js +0 -30278
  102. package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
  103. package/docs/.vitepress/cache/deps/dayjs.js +0 -285
  104. package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
  105. package/docs/.vitepress/cache/deps/debug.js +0 -468
  106. package/docs/.vitepress/cache/deps/debug.js.map +0 -7
  107. package/docs/.vitepress/cache/deps/package.json +0 -3
  108. package/docs/.vitepress/cache/deps/prismjs.js +0 -1466
  109. package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
  110. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -228
  111. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
  112. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -142
  113. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +0 -7
  114. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +0 -27
  115. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +0 -7
  116. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +0 -65
  117. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
  118. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -53
  119. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
  120. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -73
  121. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
  122. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4507
  123. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  124. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -584
  125. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  126. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1146
  127. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
  128. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +0 -1667
  129. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
  130. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -1814
  131. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +0 -7
  132. package/docs/.vitepress/cache/deps/vue.js +0 -344
  133. package/docs/.vitepress/cache/deps/vue.js.map +0 -7
  134. package/examples/theme-matching.md +0 -113
  135. package/mcp-config.json +0 -8
  136. package/note.md +0 -35
  137. package/research_results.md +0 -53
  138. package/src/tools/colors.ts +0 -31
  139. package/src/tools/registry.ts +0 -142
  140. package/src/tools/simple-tools.ts +0 -37
@@ -1,30 +1,658 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CoolorsMcp
4
- } from "../chunk-IQ7NN26V.js";
4
+ } from "../chunk-LHW2ZTOU.js";
5
5
  import {
6
6
  DislikeAnalyzer,
7
7
  Hct,
8
8
  TonalPalette,
9
9
  adjustTemperature,
10
+ argbToRgb,
10
11
  blend,
11
12
  colorDistance,
12
13
  corePaletteFromRgb,
14
+ darken,
15
+ desaturate,
13
16
  getContrastRatio,
14
17
  harmonize,
15
18
  hexToRgb,
16
19
  hslToRgb,
20
+ invertColor,
17
21
  labToRgb,
22
+ lighten,
23
+ mixColors,
18
24
  parseColor,
19
25
  rgbToArgb,
20
26
  rgbToHct,
21
27
  rgbToHex,
22
28
  rgbToHsl,
23
- rgbToLab
24
- } from "../chunk-P3ARRKLS.js";
29
+ rgbToLab,
30
+ saturate,
31
+ toGrayscale
32
+ } from "../chunk-HOMDMKUY.js";
25
33
 
26
- // src/tools/color-conversion.tool.ts
34
+ // src/tools/adjust-color.tool.ts
27
35
  import { z } from "zod";
36
+ var OPS = [
37
+ "lighten",
38
+ "darken",
39
+ "saturate",
40
+ "desaturate",
41
+ "grayscale",
42
+ "invert",
43
+ "mix"
44
+ ];
45
+ var adjustColorTool = {
46
+ description: "Adjust a color: lighten, darken, saturate, desaturate, grayscale, invert, or mix with a second color. Amount is 0-100 (percent) for lighten/darken/saturate/desaturate, 0-1 for mix weight.",
47
+ execute: async (args) => {
48
+ const rgb = parseColor(args.color);
49
+ if (!rgb) return `Invalid color format: ${args.color}`;
50
+ switch (args.operation) {
51
+ case "darken":
52
+ return rgbToHex(darken(rgb, args.amount ?? 10));
53
+ case "desaturate":
54
+ return rgbToHex(desaturate(rgb, args.amount ?? 10));
55
+ case "grayscale":
56
+ return rgbToHex(toGrayscale(rgb));
57
+ case "invert":
58
+ return rgbToHex(invertColor(rgb));
59
+ case "lighten":
60
+ return rgbToHex(lighten(rgb, args.amount ?? 10));
61
+ case "mix": {
62
+ if (!args.with) return "Error: 'with' is required for mix operation";
63
+ const other = parseColor(args.with);
64
+ if (!other) return `Invalid color format: ${args.with}`;
65
+ const weight = args.amount ?? 0.5;
66
+ if (weight < 0 || weight > 1) {
67
+ return "Error: mix amount must be between 0 and 1";
68
+ }
69
+ return rgbToHex(mixColors(rgb, other, weight));
70
+ }
71
+ case "saturate":
72
+ return rgbToHex(saturate(rgb, args.amount ?? 10));
73
+ }
74
+ },
75
+ name: "adjust_color",
76
+ parameters: z.object({
77
+ amount: z.number().optional().describe(
78
+ "Amount of change. For lighten/darken/saturate/desaturate: 0-100 (percent). For mix: 0-1 (weight of first color). Default 10 / 0.5."
79
+ ),
80
+ color: z.string().describe("Color to adjust (hex, rgb, hsl)"),
81
+ operation: z.enum(OPS).describe("Adjustment operation"),
82
+ with: z.string().optional().describe("Second color (only for mix)")
83
+ })
84
+ };
85
+
86
+ // src/tools/cohesion.tools.ts
87
+ import { z as z2 } from "zod";
88
+ var TONAL_STOPS = [
89
+ 50,
90
+ 100,
91
+ 200,
92
+ 300,
93
+ 400,
94
+ 500,
95
+ 600,
96
+ 700,
97
+ 800,
98
+ 900,
99
+ 950
100
+ ];
101
+ function hctFromColor(input) {
102
+ const rgb = parseColor(input);
103
+ if (!rgb) return null;
104
+ return Hct.fromInt(rgbToArgb(rgb));
105
+ }
106
+ function hctToHex(h, c, t) {
107
+ const hct = Hct.from(h, c, t);
108
+ return rgbToHex(argbToRgb(hct.toInt()));
109
+ }
110
+ var generateTonalScaleTool = {
111
+ description: "Generate a complete tonal scale (Tailwind-style 50/100/.../900/950) from a seed color. Uses Google's HCT color space so each step is perceptually even \u2014 ideal for building shadable design-system colors.",
112
+ execute: async (args) => {
113
+ const hct = hctFromColor(args.seed);
114
+ if (!hct) return `Invalid color format: ${args.seed}`;
115
+ const stops = args.stops?.length ? args.stops : Array.from(TONAL_STOPS);
116
+ const chromaBoost = args.chromaBoost ?? 1;
117
+ const name = (args.name ?? "color").toLowerCase();
118
+ const tones = stops.map((s) => Math.max(0, Math.min(100, 100 - s / 10)));
119
+ let output = `# Tonal scale for ${args.seed}
120
+ `;
121
+ output += `Seed HCT: H=${hct.hue.toFixed(1)}\xB0 C=${hct.chroma.toFixed(1)} T=${hct.tone.toFixed(1)}
122
+
123
+ `;
124
+ output += `| stop | tone | hex |
125
+ |---|---|---|
126
+ `;
127
+ const rows = [];
128
+ for (let i = 0; i < stops.length; i++) {
129
+ const tone = tones[i];
130
+ const edge = Math.abs(50 - tone) / 50;
131
+ const chroma = hct.chroma * chromaBoost * (1 - 0.35 * edge);
132
+ const hex = hctToHex(hct.hue, chroma, tone);
133
+ rows.push({ hex, stop: stops[i], tone });
134
+ output += `| ${stops[i]} | ${tone.toFixed(0)} | ${hex} |
135
+ `;
136
+ }
137
+ output += `
138
+ ## CSS
139
+ \`\`\`css
140
+ :root {
141
+ `;
142
+ for (const r of rows) output += ` --${name}-${r.stop}: ${r.hex};
143
+ `;
144
+ output += `}
145
+ \`\`\`
146
+ `;
147
+ return output;
148
+ },
149
+ name: "generate_tonal_scale",
150
+ parameters: z2.object({
151
+ chromaBoost: z2.number().min(0).max(2).optional().default(1).describe("Multiplier on seed chroma (1 = preserve, <1 = muted)"),
152
+ name: z2.string().optional().describe("CSS variable base name (default: 'color')"),
153
+ seed: z2.string().describe("Seed color (hex, rgb, hsl)"),
154
+ stops: z2.array(z2.number().min(0).max(1e3)).optional().describe(
155
+ "Custom stops. Default: [50,100,200,300,400,500,600,700,800,900,950]."
156
+ )
157
+ })
158
+ };
159
+ var generateStateColorsTool = {
160
+ description: "Generate consistent interaction-state colors (hover / active / pressed / focus / disabled / selected) from a base color. Steps are computed in HCT space so they keep equal perceptual weight regardless of base hue.",
161
+ execute: async (args) => {
162
+ const hct = hctFromColor(args.base);
163
+ if (!hct) return `Invalid color format: ${args.base}`;
164
+ const dark = args.isDark ?? false;
165
+ const dir = dark ? 1 : -1;
166
+ const states = [
167
+ { hex: "", note: "resting", state: "base", tone: hct.tone },
168
+ {
169
+ hex: "",
170
+ note: "+/- 8 tone",
171
+ state: "hover",
172
+ tone: hct.tone + dir * 8
173
+ },
174
+ {
175
+ hex: "",
176
+ note: "+/- 16 tone",
177
+ state: "active",
178
+ tone: hct.tone + dir * 16
179
+ },
180
+ {
181
+ hex: "",
182
+ note: "same as active, alias",
183
+ state: "pressed",
184
+ tone: hct.tone + dir * 16
185
+ },
186
+ {
187
+ hex: "",
188
+ note: "+/- 4 tone, used as outline",
189
+ state: "focus",
190
+ tone: hct.tone + dir * 4
191
+ },
192
+ {
193
+ hex: "",
194
+ note: "low chroma, mid tone",
195
+ state: "disabled",
196
+ tone: dark ? 30 : 70
197
+ },
198
+ {
199
+ hex: "",
200
+ note: "alpha-blend friendly base",
201
+ state: "selected",
202
+ tone: dark ? 25 : 90
203
+ }
204
+ ];
205
+ for (const s of states) {
206
+ const chroma = s.state === "disabled" ? Math.min(hct.chroma, 6) : hct.chroma;
207
+ const tone = Math.max(0, Math.min(100, s.tone));
208
+ s.hex = hctToHex(hct.hue, chroma, tone);
209
+ s.tone = tone;
210
+ }
211
+ let out = `# Interaction states for ${args.base} (${dark ? "dark" : "light"} mode)
212
+
213
+ `;
214
+ out += `| state | tone | hex | notes |
215
+ |---|---|---|---|
216
+ `;
217
+ for (const s of states)
218
+ out += `| ${s.state} | ${s.tone.toFixed(0)} | ${s.hex} | ${s.note} |
219
+ `;
220
+ return out;
221
+ },
222
+ name: "generate_state_colors",
223
+ parameters: z2.object({
224
+ base: z2.string().describe("Base/resting color (hex, rgb, hsl)"),
225
+ isDark: z2.boolean().optional().default(false).describe(
226
+ "Whether the base color sits on a dark background. Hover lightens on dark, darkens on light."
227
+ )
228
+ })
229
+ };
230
+ var analyzePaletteConsistencyTool = {
231
+ description: "Score how visually cohesive a palette is. Reports tonal step uniformity, chroma spread, hue distribution, and a single 0-100 cohesion score, plus targeted suggestions for tightening it up.",
232
+ execute: async (args) => {
233
+ if (args.colors.length < 2) {
234
+ return "Need at least 2 colors to analyze cohesion.";
235
+ }
236
+ const parsed = args.colors.map((c) => ({ input: c, rgb: parseColor(c) }));
237
+ if (parsed.some((p) => !p.rgb)) {
238
+ const bad = parsed.filter((p) => !p.rgb).map((p) => p.input);
239
+ return `Invalid color format: ${bad.join(", ")}`;
240
+ }
241
+ const hcts = parsed.map((p) => Hct.fromInt(rgbToArgb(p.rgb)));
242
+ const tones = hcts.map((h) => h.tone).sort((a, b) => a - b);
243
+ const chromas = hcts.map((h) => h.chroma);
244
+ const hues = hcts.map((h) => h.hue);
245
+ const gaps = [];
246
+ for (let i = 1; i < tones.length; i++) gaps.push(tones[i] - tones[i - 1]);
247
+ const avgGap = gaps.reduce((a, b) => a + b, 0) / (gaps.length || 1);
248
+ const gapStd = Math.sqrt(
249
+ gaps.reduce((a, b) => a + (b - avgGap) ** 2, 0) / (gaps.length || 1)
250
+ );
251
+ const toneUniformity = Math.max(
252
+ 0,
253
+ 100 - gapStd / Math.max(1, avgGap) * 50
254
+ );
255
+ const avgChroma = chromas.reduce((a, b) => a + b, 0) / chromas.length;
256
+ const chromaStd = Math.sqrt(
257
+ chromas.reduce((a, b) => a + (b - avgChroma) ** 2, 0) / chromas.length
258
+ );
259
+ const chromaCohesion = Math.max(0, 100 - chromaStd * 2);
260
+ const targets = [0, 30, 60, 90, 120, 150, 180];
261
+ let harmonyHits = 0;
262
+ for (let i = 0; i < hues.length; i++) {
263
+ for (let j = i + 1; j < hues.length; j++) {
264
+ let d = Math.abs(hues[i] - hues[j]);
265
+ if (d > 180) d = 360 - d;
266
+ if (targets.some((t) => Math.abs(d - t) <= 15)) harmonyHits++;
267
+ }
268
+ }
269
+ const totalPairs = hues.length * (hues.length - 1) / 2;
270
+ const hueHarmony = totalPairs ? harmonyHits / totalPairs * 100 : 100;
271
+ const cohesion = 0.4 * toneUniformity + 0.3 * chromaCohesion + 0.3 * hueHarmony;
272
+ let outlier = -1;
273
+ let outlierGain = 0;
274
+ for (let i = 0; i < hcts.length; i++) {
275
+ const reduced = hcts.filter((_, k) => k !== i).map((h) => h.chroma);
276
+ const m = reduced.reduce((a, b) => a + b, 0) / reduced.length;
277
+ const std = Math.sqrt(
278
+ reduced.reduce((a, b) => a + (b - m) ** 2, 0) / reduced.length
279
+ );
280
+ const gain = chromaStd - std;
281
+ if (gain > outlierGain) {
282
+ outlierGain = gain;
283
+ outlier = i;
284
+ }
285
+ }
286
+ let out = `# Palette cohesion analysis
287
+
288
+ `;
289
+ out += `Colors: ${args.colors.length}
290
+
291
+ `;
292
+ out += `| metric | score | detail |
293
+ |---|---|---|
294
+ `;
295
+ out += `| Tonal step uniformity | ${toneUniformity.toFixed(0)} | gap avg ${avgGap.toFixed(1)} \xB1 ${gapStd.toFixed(1)} |
296
+ `;
297
+ out += `| Chroma cohesion | ${chromaCohesion.toFixed(0)} | chroma avg ${avgChroma.toFixed(1)} \xB1 ${chromaStd.toFixed(1)} |
298
+ `;
299
+ out += `| Hue harmony | ${hueHarmony.toFixed(0)} | ${harmonyHits}/${totalPairs} pairs at harmonic angles |
300
+ `;
301
+ out += `| **Overall cohesion** | **${cohesion.toFixed(0)}** | weighted 0.4 / 0.3 / 0.3 |
302
+
303
+ `;
304
+ const suggestions = [];
305
+ if (toneUniformity < 60)
306
+ suggestions.push(
307
+ `Tone gaps are uneven (std ${gapStd.toFixed(1)}). Snap colors to a fixed scale (e.g. tones 10/30/50/70/90).`
308
+ );
309
+ if (chromaCohesion < 60)
310
+ suggestions.push(
311
+ `Chroma varies widely (std ${chromaStd.toFixed(1)}). Mute saturated colors or boost flat ones toward chroma ${avgChroma.toFixed(0)}.`
312
+ );
313
+ if (hueHarmony < 50)
314
+ suggestions.push(
315
+ `Hues don't sit at harmonic angles (30/60/90/120/180\xB0). Try rotating outliers onto the nearest harmonic.`
316
+ );
317
+ if (outlier >= 0)
318
+ suggestions.push(
319
+ `Likely outlier: ${args.colors[outlier]} (chroma ${hcts[outlier].chroma.toFixed(0)} vs avg ${avgChroma.toFixed(0)}).`
320
+ );
321
+ if (suggestions.length === 0)
322
+ out += `\u2713 Palette is visually cohesive across all three axes.
323
+ `;
324
+ else {
325
+ out += `## Suggestions
326
+ `;
327
+ for (const s of suggestions) out += `- ${s}
328
+ `;
329
+ }
330
+ return out;
331
+ },
332
+ name: "analyze_palette_consistency",
333
+ parameters: z2.object({
334
+ colors: z2.array(z2.string()).min(2).describe("Palette colors to analyze")
335
+ })
336
+ };
337
+ var SEMANTIC_OFFSETS = {
338
+ // Hue offsets from brand color (in HCT degrees).
339
+ // Secondary/tertiary use harmonic rotations; semantic statuses anchor to
340
+ // conventional hue ranges (red/yellow/blue) but adjust chroma/tone to feel
341
+ // like they belong to the same family.
342
+ primary: 0,
343
+ secondary: -30,
344
+ tertiary: 60
345
+ };
346
+ var SEMANTIC_ANCHORS = {
347
+ // Anchored hues for status colors. Chroma is clamped down to whatever the
348
+ // brand can muster so the status palette doesn't out-shout the brand.
349
+ error: { chromaMin: 40, hue: 25 },
350
+ // red
351
+ info: { chromaMin: 30, hue: 240 },
352
+ // blue
353
+ success: { chromaMin: 30, hue: 142 },
354
+ // green
355
+ warning: { chromaMin: 50, hue: 80 }
356
+ // amber
357
+ };
358
+ var generateSemanticPaletteTool = {
359
+ description: "From a single brand color, generate a complete visually-cohesive semantic palette: primary, secondary, tertiary, plus success/warning/error/info status colors. Tone and chroma are normalized in HCT space so every color feels like part of the same family.",
360
+ execute: async (args) => {
361
+ const hct = hctFromColor(args.brand);
362
+ if (!hct) return `Invalid color format: ${args.brand}`;
363
+ const dark = args.isDark ?? false;
364
+ const targetTone = dark ? 80 : 40;
365
+ const familyChroma = Math.max(24, Math.min(hct.chroma, 80));
366
+ const entries = [];
367
+ for (const [name, offset] of Object.entries(SEMANTIC_OFFSETS)) {
368
+ const hue = (hct.hue + offset + 360) % 360;
369
+ const hex = hctToHex(hue, familyChroma, targetTone);
370
+ entries.push({ hex, hue, name, tone: targetTone });
371
+ }
372
+ for (const [name, anchor] of Object.entries(SEMANTIC_ANCHORS)) {
373
+ const chroma = Math.max(anchor.chromaMin, Math.min(familyChroma, 90));
374
+ const hex = hctToHex(anchor.hue, chroma, targetTone);
375
+ entries.push({ hex, hue: anchor.hue, name, tone: targetTone });
376
+ }
377
+ const brandRgb = parseColor(args.brand);
378
+ let out = `# Semantic palette derived from ${args.brand}
379
+ `;
380
+ out += `Mode: ${dark ? "dark" : "light"} (target tone ${targetTone})
381
+ `;
382
+ out += `Family chroma: ${familyChroma.toFixed(0)} (brand was ${hct.chroma.toFixed(0)})
383
+
384
+ `;
385
+ out += `| role | hue | hex | \u0394E2000 from brand | hsl |
386
+ |---|---|---|---|---|
387
+ `;
388
+ for (const e of entries) {
389
+ const rgb = parseColor(e.hex);
390
+ const delta = colorDistance(brandRgb, rgb, { metric: "deltaE2000" });
391
+ const hsl = rgbToHsl(rgb);
392
+ out += `| ${e.name} | ${e.hue.toFixed(0)}\xB0 | ${e.hex} | ${delta.toFixed(1)} | hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%) |
393
+ `;
394
+ }
395
+ out += `
396
+ ## CSS
397
+ \`\`\`css
398
+ :root {
399
+ `;
400
+ for (const e of entries) out += ` --color-${e.name}: ${e.hex};
401
+ `;
402
+ out += `}
403
+ \`\`\`
404
+ `;
405
+ return out;
406
+ },
407
+ name: "generate_semantic_palette",
408
+ parameters: z2.object({
409
+ brand: z2.string().describe("Brand / seed color (hex, rgb, hsl)"),
410
+ isDark: z2.boolean().optional().default(false).describe("Generate the palette for a dark theme background")
411
+ })
412
+ };
413
+
414
+ // src/tools/color-blindness.tool.ts
415
+ import { z as z3 } from "zod";
416
+
417
+ // src/color/color-blindness.ts
418
+ var CVD_MATRICES = {
419
+ deuteranomaly: [
420
+ 0.547494,
421
+ 0.607765,
422
+ -0.155259,
423
+ 0.181692,
424
+ 0.781742,
425
+ 0.036566,
426
+ -0.01041,
427
+ 0.027275,
428
+ 0.983136
429
+ ],
430
+ deuteranopia: [
431
+ 0.367322,
432
+ 0.860646,
433
+ -0.227968,
434
+ 0.280085,
435
+ 0.672501,
436
+ 0.047413,
437
+ -0.01182,
438
+ 0.04294,
439
+ 0.968881
440
+ ],
441
+ // Mild forms (-omaly: severity ~0.6, Machado severity 0.6 table)
442
+ protanomaly: [
443
+ 0.458064,
444
+ 0.679578,
445
+ -0.137642,
446
+ 0.092785,
447
+ 0.846313,
448
+ 0.060902,
449
+ -7494e-6,
450
+ -0.016807,
451
+ 1.024301
452
+ ],
453
+ // Strong forms (-opia: full dichromacy, severity 1.0)
454
+ protanopia: [
455
+ 0.152286,
456
+ 1.052583,
457
+ -0.204868,
458
+ 0.114503,
459
+ 0.786281,
460
+ 0.099216,
461
+ -3882e-6,
462
+ -0.048116,
463
+ 1.051998
464
+ ],
465
+ tritanomaly: [
466
+ 1.193214,
467
+ -0.109812,
468
+ -0.083402,
469
+ 0.058694,
470
+ 0.901185,
471
+ 0.040121,
472
+ -5978e-6,
473
+ 0.401901,
474
+ 0.604077
475
+ ],
476
+ tritanopia: [
477
+ 1.255528,
478
+ -0.076749,
479
+ -0.178779,
480
+ -0.078411,
481
+ 0.930809,
482
+ 0.147602,
483
+ 4733e-6,
484
+ 0.691367,
485
+ 0.3039
486
+ ]
487
+ };
488
+ function simulateCvd(rgb, type) {
489
+ if (type === "achromatopsia") {
490
+ const y = 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
491
+ const g2 = Math.max(0, Math.min(255, Math.round(y)));
492
+ return { b: g2, g: g2, r: g2 };
493
+ }
494
+ const m = CVD_MATRICES[type];
495
+ const r = srgbToLinear(rgb.r);
496
+ const g = srgbToLinear(rgb.g);
497
+ const b = srgbToLinear(rgb.b);
498
+ const rOut = m[0] * r + m[1] * g + m[2] * b;
499
+ const gOut = m[3] * r + m[4] * g + m[5] * b;
500
+ const bOut = m[6] * r + m[7] * g + m[8] * b;
501
+ return {
502
+ b: linearToSrgb(bOut),
503
+ g: linearToSrgb(gOut),
504
+ r: linearToSrgb(rOut)
505
+ };
506
+ }
507
+ function linearToSrgb(c) {
508
+ const v = c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
509
+ return Math.max(0, Math.min(255, Math.round(v * 255)));
510
+ }
511
+ function srgbToLinear(c) {
512
+ const n = c / 255;
513
+ return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
514
+ }
515
+ var CVD_PREVALENCE = {
516
+ achromatopsia: 3e-3,
517
+ deuteranomaly: 5,
518
+ deuteranopia: 1,
519
+ protanomaly: 1,
520
+ protanopia: 1,
521
+ tritanomaly: 0.01,
522
+ tritanopia: 3e-3
523
+ };
524
+
525
+ // src/tools/color-blindness.tool.ts
526
+ var CVD_TYPES = [
527
+ "protanopia",
528
+ "deuteranopia",
529
+ "tritanopia",
530
+ "protanomaly",
531
+ "deuteranomaly",
532
+ "tritanomaly",
533
+ "achromatopsia"
534
+ ];
535
+ var simulateColorBlindnessTool = {
536
+ description: "Simulate how one or more colors appear to viewers with color vision deficiency (protanopia, deuteranopia, tritanopia, their milder anomaly forms, or achromatopsia).",
537
+ execute: async (args) => {
538
+ const types = args.types && args.types.length > 0 ? args.types : CVD_TYPES;
539
+ const parsed = args.colors.map((c) => ({ input: c, rgb: parseColor(c) }));
540
+ const invalid = parsed.filter((p) => !p.rgb);
541
+ if (invalid.length) {
542
+ return `Invalid color format: ${invalid.map((p) => p.input).join(", ")}`;
543
+ }
544
+ let output = `# Color Blindness Simulation
545
+
546
+ `;
547
+ output += `| Original | ${types.join(" | ")} |
548
+ `;
549
+ output += `|${"---|".repeat(types.length + 1)}
550
+ `;
551
+ for (const { input, rgb } of parsed) {
552
+ const simulated = types.map((t) => rgbToHex(simulateCvd(rgb, t)));
553
+ output += `| ${rgbToHex(rgb)} (${input}) | ${simulated.join(" | ")} |
554
+ `;
555
+ }
556
+ output += `
557
+ ## Population prevalence
558
+ `;
559
+ for (const t of types) {
560
+ output += `- **${t}**: ~${CVD_PREVALENCE[t]}% of population
561
+ `;
562
+ }
563
+ return output;
564
+ },
565
+ name: "simulate_color_blindness",
566
+ parameters: z3.object({
567
+ colors: z3.array(z3.string()).min(1).describe("Colors to simulate (hex, rgb, hsl)"),
568
+ types: z3.array(z3.enum(CVD_TYPES)).optional().describe(
569
+ "Deficiency types to simulate. Defaults to all 7 (dichromacy + anomaly + achromatopsia)."
570
+ )
571
+ })
572
+ };
573
+ var checkPaletteAccessibilityTool = {
574
+ description: "Audit a color palette for color-blind accessibility. For each pair of colors, reports the perceptual distance (Delta E 2000) under each CVD type and flags pairs that become indistinguishable.",
575
+ execute: async (args) => {
576
+ const types = args.types && args.types.length > 0 ? args.types : CVD_TYPES;
577
+ const threshold = args.indistinguishableThreshold ?? 10;
578
+ const parsed = args.colors.map((c) => ({ input: c, rgb: parseColor(c) }));
579
+ const invalid = parsed.filter((p) => !p.rgb);
580
+ if (invalid.length) {
581
+ return `Invalid color format: ${invalid.map((p) => p.input).join(", ")}`;
582
+ }
583
+ const colors = parsed.map((p) => p.rgb);
584
+ const labels = parsed.map((p) => rgbToHex(p.rgb));
585
+ let output = `# Palette Accessibility Audit
586
+
587
+ `;
588
+ output += `Indistinguishable threshold: \u0394E2000 < ${threshold}
589
+
590
+ `;
591
+ const problems = [];
592
+ for (const type of types) {
593
+ const simulated = colors.map((c) => simulateCvd(c, type));
594
+ const collisions = [];
595
+ for (let i = 0; i < simulated.length; i++) {
596
+ for (let j = i + 1; j < simulated.length; j++) {
597
+ const d = colorDistance(simulated[i], simulated[j], {
598
+ metric: "deltaE2000"
599
+ });
600
+ if (d < threshold) {
601
+ collisions.push(
602
+ ` - ${labels[i]} \u2194 ${labels[j]}: \u0394E=${d.toFixed(1)} (sim: ${rgbToHex(
603
+ simulated[i]
604
+ )} vs ${rgbToHex(simulated[j])})`
605
+ );
606
+ }
607
+ }
608
+ }
609
+ output += `## ${type} (~${CVD_PREVALENCE[type]}% of population)
610
+ `;
611
+ if (collisions.length === 0) {
612
+ output += `\u2713 All ${colors.length} colors remain distinguishable
613
+
614
+ `;
615
+ } else {
616
+ output += `\u26A0 ${collisions.length} indistinguishable pair${collisions.length === 1 ? "" : "s"}:
617
+ `;
618
+ output += collisions.join("\n") + "\n\n";
619
+ problems.push(type);
620
+ }
621
+ }
622
+ output += `## Summary
623
+ `;
624
+ if (problems.length === 0) {
625
+ output += `\u2713 Palette is accessible across all tested CVD types.
626
+ `;
627
+ } else {
628
+ output += `\u26A0 Issues detected in: ${problems.join(", ")}.
629
+ `;
630
+ output += `Consider increasing tonal contrast (vary lightness) or chroma; CVD-friendly palettes rely on lightness differences rather than hue alone.
631
+ `;
632
+ let worst = Infinity;
633
+ for (let i = 0; i < colors.length; i++) {
634
+ for (let j = i + 1; j < colors.length; j++) {
635
+ const r = getContrastRatio(colors[i], colors[j]);
636
+ if (r < worst) worst = r;
637
+ }
638
+ }
639
+ output += `Lowest WCAG luminance ratio in palette: ${worst.toFixed(2)}:1 (target \u2265 3:1 for adjacent UI swatches).
640
+ `;
641
+ }
642
+ return output;
643
+ },
644
+ name: "check_palette_accessibility",
645
+ parameters: z3.object({
646
+ colors: z3.array(z3.string()).min(2).describe("Palette colors to audit (at least 2)"),
647
+ indistinguishableThreshold: z3.number().min(1).max(30).optional().default(10).describe(
648
+ "\u0394E2000 below which two simulated colors are considered indistinguishable (default 10)"
649
+ ),
650
+ types: z3.array(z3.enum(CVD_TYPES)).optional().describe("CVD types to audit. Defaults to all 7.")
651
+ })
652
+ };
653
+
654
+ // src/tools/color-conversion.tool.ts
655
+ import { z as z4 } from "zod";
28
656
  var colorConversionTool = {
29
657
  description: "Convert colors between different formats (hex, rgb, hsl, lab, hct)",
30
658
  execute: async (args) => {
@@ -48,20 +676,20 @@ var colorConversionTool = {
48
676
  return `lab(${lab.l.toFixed(2)}, ${lab.a.toFixed(2)}, ${lab.b.toFixed(2)})`;
49
677
  }
50
678
  case "rgb":
51
- return `rgb(${Math.round(rgb.r * 255)}, ${Math.round(rgb.g * 255)}, ${Math.round(rgb.b * 255)})`;
679
+ return `rgb(${Math.round(rgb.r)}, ${Math.round(rgb.g)}, ${Math.round(rgb.b)})`;
52
680
  default:
53
681
  return `Invalid format: ${args.to}`;
54
682
  }
55
683
  },
56
684
  name: "convert_color",
57
- parameters: z.object({
58
- color: z.string().describe("Color to convert (hex, rgb(), or hsl())"),
59
- to: z.enum(["hex", "rgb", "hsl", "lab", "hct"]).describe("Target format")
685
+ parameters: z4.object({
686
+ color: z4.string().describe("Color to convert (hex, rgb(), or hsl())"),
687
+ to: z4.enum(["hex", "rgb", "hsl", "lab", "hct"]).describe("Target format")
60
688
  })
61
689
  };
62
690
 
63
691
  // src/tools/color-distance.tool.ts
64
- import { z as z2 } from "zod";
692
+ import { z as z5 } from "zod";
65
693
  var colorDistanceTool = {
66
694
  description: "Calculate perceptual distance between two colors using Delta E 2000",
67
695
  execute: async (args) => {
@@ -76,84 +704,129 @@ var colorDistanceTool = {
76
704
  return `Distance between ${args.color1} and ${args.color2}: ${distance.toFixed(2)}`;
77
705
  },
78
706
  name: "color_distance",
79
- parameters: z2.object({
80
- color1: z2.string().describe("First color (hex, rgb, or hsl)"),
81
- color2: z2.string().describe("Second color (hex, rgb, or hsl)"),
82
- metric: z2.enum(["euclidean", "deltaE76", "deltaE94", "deltaE2000", "weighted"]).optional().default("deltaE2000").describe("Distance metric to use")
707
+ parameters: z5.object({
708
+ color1: z5.string().describe("First color (hex, rgb, or hsl)"),
709
+ color2: z5.string().describe("Second color (hex, rgb, or hsl)"),
710
+ metric: z5.enum(["euclidean", "deltaE76", "deltaE94", "deltaE2000", "weighted"]).optional().default("deltaE2000").describe("Distance metric to use")
83
711
  })
84
712
  };
85
713
 
86
- // src/tools/colors.ts
87
- import { formatHex, formatHsl, formatRgb, parse } from "culori";
88
- import { z as z3 } from "zod";
714
+ // src/tools/contrast-checker.tool.ts
715
+ import { z as z6 } from "zod";
89
716
 
90
- // src/constants.ts
91
- var ColorFormat = /* @__PURE__ */ ((ColorFormat2) => {
92
- ColorFormat2["HEX"] = "hex";
93
- ColorFormat2["HSL"] = "hsl";
94
- ColorFormat2["RGB"] = "rgb";
95
- return ColorFormat2;
96
- })(ColorFormat || {});
97
-
98
- // src/tools/colors.ts
99
- var convertColor = {
100
- description: "Convert a color from one format to another",
101
- execute: async (args) => {
102
- const { color, to = "hex" /* HEX */ } = args;
103
- const parsed = parse(color);
104
- if (!parsed) {
105
- throw new Error(`Invalid color: ${color}`);
106
- }
107
- switch (to) {
108
- case "hex" /* HEX */:
109
- return formatHex(parsed);
110
- case "hsl" /* HSL */:
111
- return formatHsl(parsed);
112
- case "rgb" /* RGB */:
113
- return formatRgb(parsed);
114
- default:
115
- throw new Error(`Invalid output format: ${to}`);
116
- }
117
- },
118
- name: "convertColor",
119
- parameters: z3.object({
120
- color: z3.string().describe("The color to convert"),
121
- to: z3.nativeEnum(ColorFormat).optional().describe("The output format")
122
- })
717
+ // src/color/apca.ts
718
+ var SA98G = {
719
+ blkClmp: 1.414,
720
+ blkThrs: 0.022,
721
+ deltaYmin: 5e-4,
722
+ loBoTclip: -0.6,
723
+ loBoTexp: 0.74,
724
+ loClip: 0.1,
725
+ // Polarity exponents/factors
726
+ normBG: 0.56,
727
+ normTXT: 0.57,
728
+ revBG: 0.62,
729
+ revTXT: 0.65,
730
+ // Soft-clip and scale
731
+ scaleBoW: 1.14,
732
+ scaleWoB: 1.14,
733
+ trailingW: 0.027
123
734
  };
735
+ var sRGBtrc = 2.4;
736
+ var Rco = 0.2126729;
737
+ var Gco = 0.7151522;
738
+ var Bco = 0.072175;
739
+ function apcaContrast(text, bg) {
740
+ let txtY = apcaY(text);
741
+ let bgY = apcaY(bg);
742
+ if (txtY <= SA98G.blkThrs) {
743
+ txtY += Math.pow(SA98G.blkThrs - txtY, SA98G.blkClmp);
744
+ }
745
+ if (bgY <= SA98G.blkThrs) {
746
+ bgY += Math.pow(SA98G.blkThrs - bgY, SA98G.blkClmp);
747
+ }
748
+ if (Math.abs(bgY - txtY) < SA98G.deltaYmin) return 0;
749
+ let outputContrast;
750
+ if (bgY > txtY) {
751
+ const SAPC = (Math.pow(bgY, SA98G.normBG) - Math.pow(txtY, SA98G.normTXT)) * SA98G.scaleBoW;
752
+ outputContrast = SAPC < SA98G.loClip ? 0 : SAPC - SA98G.trailingW;
753
+ } else {
754
+ const SAPC = (Math.pow(bgY, SA98G.revBG) - Math.pow(txtY, SA98G.revTXT)) * SA98G.scaleWoB;
755
+ outputContrast = SAPC > -SA98G.loClip ? 0 : SAPC + SA98G.trailingW;
756
+ }
757
+ return outputContrast * 100;
758
+ }
759
+ function apcaLevel(lc) {
760
+ const abs = Math.abs(lc);
761
+ return {
762
+ body: abs >= 75,
763
+ content: abs >= 60,
764
+ large: abs >= 45,
765
+ spot: abs >= 30
766
+ };
767
+ }
768
+ function apcaY(rgb) {
769
+ const r = Math.pow(rgb.r / 255, sRGBtrc);
770
+ const g = Math.pow(rgb.g / 255, sRGBtrc);
771
+ const b = Math.pow(rgb.b / 255, sRGBtrc);
772
+ return Rco * r + Gco * g + Bco * b;
773
+ }
124
774
 
125
775
  // src/tools/contrast-checker.tool.ts
126
- import { z as z4 } from "zod";
127
776
  var contrastCheckerTool = {
128
- description: "Check WCAG contrast ratio between two colors",
777
+ description: "Check contrast between two colors. Supports WCAG 2.x luminance ratio (default), APCA Lc (WCAG 3 draft), or both algorithms side-by-side.",
129
778
  execute: async (args) => {
130
779
  const fg = parseColor(args.foreground);
131
780
  const bg = parseColor(args.background);
132
781
  if (!fg || !bg) {
133
782
  return `Invalid color format: ${!fg ? args.foreground : args.background}`;
134
783
  }
135
- const ratio = getContrastRatio(fg, bg);
136
- const aa = ratio >= 4.5;
137
- const aaLarge = ratio >= 3;
138
- const aaa = ratio >= 7;
139
- const aaaLarge = ratio >= 4.5;
140
- return `Contrast Ratio: ${ratio.toFixed(2)}:1
141
- WCAG AA: ${aa ? "\u2713 Pass" : "\u2717 Fail"} (normal text)
142
- WCAG AA: ${aaLarge ? "\u2713 Pass" : "\u2717 Fail"} (large text)
143
- WCAG AAA: ${aaa ? "\u2713 Pass" : "\u2717 Fail"} (normal text)
144
- WCAG AAA: ${aaaLarge ? "\u2713 Pass" : "\u2717 Fail"} (large text)`;
784
+ const algorithm = args.algorithm ?? "wcag";
785
+ const wcagBlock = () => {
786
+ const ratio = getContrastRatio(fg, bg);
787
+ const aaLarge = ratio >= 3;
788
+ const aa = ratio >= 4.5;
789
+ const aaaLarge = ratio >= 4.5;
790
+ const aaa = ratio >= 7;
791
+ return `## WCAG 2.x luminance ratio
792
+ Contrast Ratio: ${ratio.toFixed(2)}:1
793
+ - AA (normal text): ${aa ? "\u2713 Pass" : "\u2717 Fail"} (need 4.5:1)
794
+ - AA (large text): ${aaLarge ? "\u2713 Pass" : "\u2717 Fail"} (need 3:1)
795
+ - AAA (normal text): ${aaa ? "\u2713 Pass" : "\u2717 Fail"} (need 7:1)
796
+ - AAA (large text): ${aaaLarge ? "\u2713 Pass" : "\u2717 Fail"} (need 4.5:1)`;
797
+ };
798
+ const apcaBlock = () => {
799
+ const lc = apcaContrast(fg, bg);
800
+ const level = apcaLevel(lc);
801
+ const polarity = lc > 0 ? "dark text on light bg" : lc < 0 ? "light text on dark bg" : "no contrast";
802
+ return `## APCA (WCAG 3 draft)
803
+ Lc: ${lc.toFixed(1)} (${polarity})
804
+ - Body text (|Lc| \u2265 75): ${level.body ? "\u2713 Pass" : "\u2717 Fail"}
805
+ - Content text (|Lc| \u2265 60): ${level.content ? "\u2713 Pass" : "\u2717 Fail"}
806
+ - Large text (|Lc| \u2265 45): ${level.large ? "\u2713 Pass" : "\u2717 Fail"}
807
+ - Spot / non-content (|Lc| \u2265 30): ${level.spot ? "\u2713 Pass" : "\u2717 Fail"}`;
808
+ };
809
+ if (algorithm === "wcag") return wcagBlock();
810
+ if (algorithm === "apca") return apcaBlock();
811
+ return `${wcagBlock()}
812
+
813
+ ${apcaBlock()}`;
145
814
  },
146
815
  name: "check_contrast",
147
- parameters: z4.object({
148
- background: z4.string().describe("Background color"),
149
- foreground: z4.string().describe("Foreground/text color")
816
+ parameters: z6.object({
817
+ algorithm: z6.enum(["wcag", "apca", "both"]).optional().default("wcag").describe(
818
+ "Contrast algorithm: 'wcag' (WCAG 2.x ratio), 'apca' (Lc, WCAG 3 draft), or 'both'"
819
+ ),
820
+ background: z6.string().describe("Background color"),
821
+ foreground: z6.string().describe("Foreground/text color")
150
822
  })
151
823
  };
152
824
 
153
825
  // src/tools/dislike-analyzer.tool.ts
826
+ import { z as z7 } from "zod";
154
827
  var analyzeColorLikabilityTool = {
155
828
  description: "Check if a color is universally disliked (dark yellow-green associated with biological waste) and get a fixed version if needed",
156
- execute: async (args, _context) => {
829
+ execute: async (args) => {
157
830
  const { autoFix = true, color } = args;
158
831
  try {
159
832
  const rgb = parseColor(color);
@@ -242,25 +915,15 @@ var analyzeColorLikabilityTool = {
242
915
  return `Error analyzing color: ${error instanceof Error ? error.message : String(error)}`;
243
916
  }
244
917
  },
245
- inputSchema: {
246
- properties: {
247
- autoFix: {
248
- description: "Automatically return fixed version if disliked (default: true)",
249
- type: "boolean"
250
- },
251
- color: {
252
- description: "Color to analyze (hex, rgb, hsl, etc.)",
253
- type: "string"
254
- }
255
- },
256
- required: ["color"],
257
- type: "object"
258
- },
259
- name: "analyze_color_likability"
918
+ name: "analyze_color_likability",
919
+ parameters: z7.object({
920
+ autoFix: z7.boolean().optional().default(true).describe("Automatically return fixed version if disliked"),
921
+ color: z7.string().describe("Color to analyze (hex, rgb, hsl, etc.)")
922
+ })
260
923
  };
261
924
  var fixDislikedColorsBatchTool = {
262
925
  description: "Analyze and fix multiple colors, returning only liked versions",
263
- execute: async (args, _context) => {
926
+ execute: async (args) => {
264
927
  const { colors, includeAnalysis = false } = args;
265
928
  try {
266
929
  const results = [];
@@ -330,17 +993,19 @@ var fixDislikedColorsBatchTool = {
330
993
  } else if (result.wasDisliked) {
331
994
  output += `- **${result.original}** \u2192 **${result.fixed}** (fixed)
332
995
  `;
333
- if (includeAnalysis && result.hct) {
334
- output += ` - Original HCT: (${result.hct.original.h.toFixed(0)}\xB0, ${result.hct.original.c.toFixed(0)}, ${result.hct.original.t.toFixed(0)})
996
+ if (includeAnalysis && result.hct && "original" in result.hct) {
997
+ const { fixed, original } = result.hct;
998
+ output += ` - Original HCT: (${original.h.toFixed(0)}\xB0, ${original.c.toFixed(0)}, ${original.t.toFixed(0)})
335
999
  `;
336
- output += ` - Fixed HCT: (${result.hct.fixed.h.toFixed(0)}\xB0, ${result.hct.fixed.c.toFixed(0)}, ${result.hct.fixed.t.toFixed(0)})
1000
+ output += ` - Fixed HCT: (${fixed.h.toFixed(0)}\xB0, ${fixed.c.toFixed(0)}, ${fixed.t.toFixed(0)})
337
1001
  `;
338
1002
  }
339
1003
  } else {
340
1004
  output += `- **${result.original}** \u2713 (already liked)
341
1005
  `;
342
- if (includeAnalysis && result.hct) {
343
- output += ` - HCT: (${result.hct.h.toFixed(0)}\xB0, ${result.hct.c.toFixed(0)}, ${result.hct.t.toFixed(0)})
1006
+ if (includeAnalysis && result.hct && "h" in result.hct) {
1007
+ const { c, h, t } = result.hct;
1008
+ output += ` - HCT: (${h.toFixed(0)}\xB0, ${c.toFixed(0)}, ${t.toFixed(0)})
344
1009
  `;
345
1010
  }
346
1011
  }
@@ -358,28 +1023,15 @@ var fixDislikedColorsBatchTool = {
358
1023
  return `Error processing colors: ${error instanceof Error ? error.message : String(error)}`;
359
1024
  }
360
1025
  },
361
- inputSchema: {
362
- properties: {
363
- colors: {
364
- description: "Array of colors to analyze (hex, rgb, hsl, etc.)",
365
- items: {
366
- type: "string"
367
- },
368
- type: "array"
369
- },
370
- includeAnalysis: {
371
- description: "Include detailed analysis for each color (default: false)",
372
- type: "boolean"
373
- }
374
- },
375
- required: ["colors"],
376
- type: "object"
377
- },
378
- name: "fix_disliked_colors_batch"
1026
+ name: "fix_disliked_colors_batch",
1027
+ parameters: z7.object({
1028
+ colors: z7.array(z7.string()).min(1).describe("Array of colors to analyze (hex, rgb, hsl, etc.)"),
1029
+ includeAnalysis: z7.boolean().optional().default(false).describe("Include detailed analysis for each color")
1030
+ })
379
1031
  };
380
1032
 
381
1033
  // src/tools/gradient-generator.tool.ts
382
- import { z as z5 } from "zod";
1034
+ import { z as z8 } from "zod";
383
1035
  var gradientGeneratorTool = {
384
1036
  description: "Generate a smooth gradient between colors with multiple interpolation methods",
385
1037
  execute: async (args) => {
@@ -538,17 +1190,20 @@ Input colors: ${colors.join(" \u2192 ")}`;
538
1190
  }
539
1191
  },
540
1192
  name: "generate_gradient",
541
- parameters: z5.object({
542
- colors: z5.array(z5.string()).min(2).describe("Colors to create gradient between (minimum 2)"),
543
- easing: z5.enum(["linear", "ease-in", "ease-out", "ease-in-out"]).default("linear").describe("Easing function for gradient transition"),
544
- format: z5.enum(["hex", "css-linear", "css-radial", "array"]).default("array").describe("Output format for the gradient"),
545
- interpolation: z5.enum(["rgb", "hsl", "lab", "lch", "hct"]).default("lab").describe(
1193
+ parameters: z8.object({
1194
+ colors: z8.array(z8.string()).min(2).describe("Colors to create gradient between (minimum 2)"),
1195
+ easing: z8.enum(["linear", "ease-in", "ease-out", "ease-in-out"]).default("linear").describe("Easing function for gradient transition"),
1196
+ format: z8.enum(["hex", "css-linear", "css-radial", "array"]).default("array").describe("Output format for the gradient"),
1197
+ interpolation: z8.enum(["rgb", "hsl", "lab", "lch", "hct"]).default("lab").describe(
546
1198
  "Color space for interpolation (lab/lch/hct are perceptually smooth)"
547
1199
  ),
548
- steps: z5.number().min(3).max(100).describe("Number of colors in the gradient")
1200
+ steps: z8.number().min(3).max(100).describe("Number of colors in the gradient")
549
1201
  })
550
1202
  };
551
1203
 
1204
+ // src/tools/image-extraction.tools.ts
1205
+ import { z as z9 } from "zod";
1206
+
552
1207
  // src/color/utils/math_utils.ts
553
1208
  function clampInt(min, max, input) {
554
1209
  if (input < min) {
@@ -594,17 +1249,17 @@ function argbFromLab(l, a, b) {
594
1249
  const zNormalized = labInvf(fz);
595
1250
  const x = xNormalized * whitePoint[0];
596
1251
  const y = yNormalized * whitePoint[1];
597
- const z10 = zNormalized * whitePoint[2];
598
- return argbFromXyz(x, y, z10);
1252
+ const z15 = zNormalized * whitePoint[2];
1253
+ return argbFromXyz(x, y, z15);
599
1254
  }
600
1255
  function argbFromRgb(red, green, blue) {
601
1256
  return (255 << 24 | (red & 255) << 16 | (green & 255) << 8 | blue & 255) >>> 0;
602
1257
  }
603
- function argbFromXyz(x, y, z10) {
1258
+ function argbFromXyz(x, y, z15) {
604
1259
  const matrix = XYZ_TO_SRGB;
605
- const linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z10;
606
- const linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z10;
607
- const linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z10;
1260
+ const linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z15;
1261
+ const linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z15;
1262
+ const linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z15;
608
1263
  const r = delinearized(linearR);
609
1264
  const g = delinearized(linearG);
610
1265
  const b = delinearized(linearB);
@@ -633,11 +1288,11 @@ function labFromArgb(argb) {
633
1288
  const matrix = SRGB_TO_XYZ;
634
1289
  const x = matrix[0][0] * linearR + matrix[0][1] * linearG + matrix[0][2] * linearB;
635
1290
  const y = matrix[1][0] * linearR + matrix[1][1] * linearG + matrix[1][2] * linearB;
636
- const z10 = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
1291
+ const z15 = matrix[2][0] * linearR + matrix[2][1] * linearG + matrix[2][2] * linearB;
637
1292
  const whitePoint = WHITE_POINT_D65;
638
1293
  const xNormalized = x / whitePoint[0];
639
1294
  const yNormalized = y / whitePoint[1];
640
- const zNormalized = z10 / whitePoint[2];
1295
+ const zNormalized = z15 / whitePoint[2];
641
1296
  const fx = labF(xNormalized);
642
1297
  const fy = labF(yNormalized);
643
1298
  const fz = labF(zNormalized);
@@ -954,6 +1609,13 @@ var Box = class {
954
1609
  this.b1 = b1;
955
1610
  this.vol = vol;
956
1611
  }
1612
+ r0;
1613
+ r1;
1614
+ g0;
1615
+ g1;
1616
+ b0;
1617
+ b1;
1618
+ vol;
957
1619
  };
958
1620
  var CreateBoxesResult = class {
959
1621
  /**
@@ -966,12 +1628,16 @@ var CreateBoxesResult = class {
966
1628
  this.requestedCount = requestedCount;
967
1629
  this.resultCount = resultCount;
968
1630
  }
1631
+ requestedCount;
1632
+ resultCount;
969
1633
  };
970
1634
  var MaximizeResult = class {
971
1635
  constructor(cutLocation, maximum) {
972
1636
  this.cutLocation = cutLocation;
973
1637
  this.maximum = maximum;
974
1638
  }
1639
+ cutLocation;
1640
+ maximum;
975
1641
  };
976
1642
  var QuantizerWu = class {
977
1643
  constructor(weights = [], momentsR = [], momentsG = [], momentsB = [], moments = [], cubes = []) {
@@ -982,6 +1648,12 @@ var QuantizerWu = class {
982
1648
  this.moments = moments;
983
1649
  this.cubes = cubes;
984
1650
  }
1651
+ weights;
1652
+ momentsR;
1653
+ momentsG;
1654
+ momentsB;
1655
+ moments;
1656
+ cubes;
985
1657
  /**
986
1658
  * @param pixels Colors in ARGB format.
987
1659
  * @param maxColors The number of colors to divide the image into. A lower
@@ -1581,9 +2253,14 @@ function generateMaterialTheme(sourceColor, options = {}) {
1581
2253
  }
1582
2254
 
1583
2255
  // src/tools/image-extraction.tools.ts
2256
+ var imageDataSchema = z9.object({
2257
+ data: z9.array(z9.number()).describe("Flat array of RGBA values (0-255)"),
2258
+ height: z9.number().int().positive().describe("Image height in pixels"),
2259
+ width: z9.number().int().positive().describe("Image width in pixels")
2260
+ }).describe("Image data with RGBA values");
1584
2261
  var extractImageColorsTool = {
1585
2262
  description: "Extract dominant colors from an image. Input should be image data as an array of RGBA values.",
1586
- execute: async (args, _context) => {
2263
+ execute: async (args) => {
1587
2264
  const {
1588
2265
  format = "json",
1589
2266
  imageData,
@@ -1591,7 +2268,7 @@ var extractImageColorsTool = {
1591
2268
  quality = "medium"
1592
2269
  } = args;
1593
2270
  try {
1594
- const data = imageData.data instanceof Uint8ClampedArray ? imageData.data : new Uint8ClampedArray(imageData.data);
2271
+ const data = new Uint8ClampedArray(imageData.data);
1595
2272
  const processedImageData = {
1596
2273
  data,
1597
2274
  height: imageData.height,
@@ -1616,60 +2293,20 @@ var extractImageColorsTool = {
1616
2293
  return `Error extracting colors: ${error instanceof Error ? error.message : String(error)}`;
1617
2294
  }
1618
2295
  },
1619
- inputSchema: {
1620
- properties: {
1621
- format: {
1622
- description: "Output format: json, css, or palette (default: json)",
1623
- enum: ["json", "css", "palette"],
1624
- type: "string"
1625
- },
1626
- imageData: {
1627
- description: "Image data with RGBA values",
1628
- properties: {
1629
- data: {
1630
- description: "Flat array of RGBA values (0-255)",
1631
- items: { type: "number" },
1632
- type: "array"
1633
- },
1634
- height: {
1635
- description: "Image height in pixels",
1636
- type: "number"
1637
- },
1638
- width: {
1639
- description: "Image width in pixels",
1640
- type: "number"
1641
- }
1642
- },
1643
- required: ["data", "width", "height"],
1644
- type: "object"
1645
- },
1646
- maxColors: {
1647
- description: "Maximum number of colors to extract (default: 5)",
1648
- maximum: 20,
1649
- minimum: 1,
1650
- type: "number"
1651
- },
1652
- quality: {
1653
- description: "Extraction quality: low, medium, or high (default: medium)",
1654
- enum: ["low", "medium", "high"],
1655
- type: "string"
1656
- }
1657
- },
1658
- required: ["imageData"],
1659
- type: "object"
1660
- },
1661
- name: "extract_image_colors"
2296
+ name: "extract_image_colors",
2297
+ parameters: z9.object({
2298
+ format: z9.enum(["json", "css", "palette"]).optional().default("json").describe("Output format"),
2299
+ imageData: imageDataSchema,
2300
+ maxColors: z9.number().int().min(1).max(20).optional().default(5).describe("Maximum number of colors to extract"),
2301
+ quality: z9.enum(["low", "medium", "high"]).optional().default("medium").describe("Extraction quality")
2302
+ })
1662
2303
  };
1663
2304
  var generateThemeFromImageTool = {
1664
2305
  description: "Generate a complete Material Design 3 theme from an image",
1665
- execute: async (args, _context) => {
1666
- const {
1667
- imageData,
1668
- includeCustomColors = true,
1669
- isDark = false
1670
- } = args;
2306
+ execute: async (args) => {
2307
+ const { imageData, includeCustomColors = true, isDark = false } = args;
1671
2308
  try {
1672
- const data = imageData.data instanceof Uint8ClampedArray ? imageData.data : new Uint8ClampedArray(imageData.data);
2309
+ const data = new Uint8ClampedArray(imageData.data);
1673
2310
  const processedImageData = {
1674
2311
  data,
1675
2312
  height: imageData.height,
@@ -1746,41 +2383,12 @@ var generateThemeFromImageTool = {
1746
2383
  return `Error generating theme: ${error instanceof Error ? error.message : String(error)}`;
1747
2384
  }
1748
2385
  },
1749
- inputSchema: {
1750
- properties: {
1751
- imageData: {
1752
- description: "Image data with RGBA values",
1753
- properties: {
1754
- data: {
1755
- description: "Flat array of RGBA values (0-255)",
1756
- items: { type: "number" },
1757
- type: "array"
1758
- },
1759
- height: {
1760
- description: "Image height in pixels",
1761
- type: "number"
1762
- },
1763
- width: {
1764
- description: "Image width in pixels",
1765
- type: "number"
1766
- }
1767
- },
1768
- required: ["data", "width", "height"],
1769
- type: "object"
1770
- },
1771
- includeCustomColors: {
1772
- description: "Include custom colors from image (default: true)",
1773
- type: "boolean"
1774
- },
1775
- isDark: {
1776
- description: "Generate dark theme (default: false for light theme)",
1777
- type: "boolean"
1778
- }
1779
- },
1780
- required: ["imageData"],
1781
- type: "object"
1782
- },
1783
- name: "generate_theme_from_image"
2386
+ name: "generate_theme_from_image",
2387
+ parameters: z9.object({
2388
+ imageData: imageDataSchema,
2389
+ includeCustomColors: z9.boolean().optional().default(true).describe("Include custom colors from image"),
2390
+ isDark: z9.boolean().optional().default(false).describe("Generate dark theme (false for light theme)")
2391
+ })
1784
2392
  };
1785
2393
  function formatAsCSS(colors) {
1786
2394
  let css = ":root {\n";
@@ -1827,7 +2435,7 @@ function formatAsPalette(colors) {
1827
2435
  }
1828
2436
 
1829
2437
  // src/tools/material-theme.tools.ts
1830
- import { z as z6 } from "zod";
2438
+ import { z as z10 } from "zod";
1831
2439
  var generateMaterialThemeTool = {
1832
2440
  description: "Generate a complete Material Design 3 color theme from a source color",
1833
2441
  execute: async (args) => {
@@ -1905,9 +2513,9 @@ Tertiary: ${[40, 80].map((t) => rgbToHex(corePalette.tertiary.tone(t))).join(",
1905
2513
  return result;
1906
2514
  },
1907
2515
  name: "generate_material_theme",
1908
- parameters: z6.object({
1909
- includeCustomColors: z6.boolean().optional().default(false).describe("Include custom color palettes"),
1910
- sourceColor: z6.string().describe("Source color for theme generation")
2516
+ parameters: z10.object({
2517
+ includeCustomColors: z10.boolean().optional().default(false).describe("Include custom color palettes"),
2518
+ sourceColor: z10.string().describe("Source color for theme generation")
1911
2519
  })
1912
2520
  };
1913
2521
  var harmonizeColorsTool = {
@@ -1951,10 +2559,10 @@ Original: ${args.colors.join(", ")}
1951
2559
  Result: ${results.join(", ")}`;
1952
2560
  },
1953
2561
  name: "harmonize_colors",
1954
- parameters: z6.object({
1955
- colors: z6.array(z6.string()).min(2).max(10).describe("Array of colors to harmonize"),
1956
- factor: z6.number().min(0).max(1).optional().default(0.5).describe("Harmonization strength (0-1)"),
1957
- method: z6.enum(["blend", "harmonize", "temperature"]).optional().default("harmonize").describe("Harmonization method")
2562
+ parameters: z10.object({
2563
+ colors: z10.array(z10.string()).min(2).max(10).describe("Array of colors to harmonize"),
2564
+ factor: z10.number().min(0).max(1).optional().default(0.5).describe("Harmonization strength (0-1)"),
2565
+ method: z10.enum(["blend", "harmonize", "temperature"]).optional().default("harmonize").describe("Harmonization method")
1958
2566
  })
1959
2567
  };
1960
2568
  var generateTonalPaletteTool = {
@@ -1991,14 +2599,137 @@ HCT: h=${hct.h.toFixed(1)}\xB0, c=${hct.c.toFixed(1)}, t=${hct.t.toFixed(1)}
1991
2599
  ${colors.map(({ hex, tone }) => `Tone ${tone}: ${hex}`).join("\n")}`;
1992
2600
  },
1993
2601
  name: "generate_tonal_palette",
1994
- parameters: z6.object({
1995
- color: z6.string().describe("Base color for palette"),
1996
- tones: z6.array(z6.number()).optional().describe("Custom tone values (default: Material standard tones)")
2602
+ parameters: z10.object({
2603
+ color: z10.string().describe("Base color for palette"),
2604
+ tones: z10.array(z10.number()).optional().describe("Custom tone values (default: Material standard tones)")
2605
+ })
2606
+ };
2607
+
2608
+ // src/tools/palette-export.tool.ts
2609
+ import { z as z11 } from "zod";
2610
+ var FORMATS = ["css", "scss", "tailwind", "tokens", "json"];
2611
+ function asCss(items) {
2612
+ const body = items.map(({ hex, name }) => ` --${name}: ${hex};`).join("\n");
2613
+ return `:root {
2614
+ ${body}
2615
+ }
2616
+ `;
2617
+ }
2618
+ function asJson(items) {
2619
+ const obj = {};
2620
+ for (const { hex, name } of items) {
2621
+ const rgb = parseColor(hex);
2622
+ const hsl = rgbToHsl(rgb);
2623
+ obj[name] = {
2624
+ hex,
2625
+ hsl: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`,
2626
+ rgb: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`
2627
+ };
2628
+ }
2629
+ return JSON.stringify(obj, null, 2) + "\n";
2630
+ }
2631
+ function asScss(items) {
2632
+ return items.map(({ hex, name }) => `$${name}: ${hex};`).join("\n") + "\n";
2633
+ }
2634
+ function asTailwind(items, prefix) {
2635
+ const key = prefix ?? "palette";
2636
+ const lines = items.map(({ hex, name }) => {
2637
+ const entry = prefix && name.startsWith(`${prefix}-`) ? name.slice(prefix.length + 1) : name;
2638
+ return ` "${entry}": "${hex}",`;
2639
+ }).join("\n");
2640
+ return `// tailwind.config.js
2641
+ module.exports = {
2642
+ theme: {
2643
+ extend: {
2644
+ colors: {
2645
+ "${key}": {
2646
+ ${lines}
2647
+ },
2648
+ },
2649
+ },
2650
+ },
2651
+ };
2652
+ `;
2653
+ }
2654
+ function asTokens(items, prefix) {
2655
+ const group = prefix ?? "color";
2656
+ const obj = {
2657
+ [group]: Object.fromEntries(
2658
+ items.map(({ hex, name }) => {
2659
+ const entry = prefix && name.startsWith(`${prefix}-`) ? name.slice(prefix.length + 1) : name;
2660
+ return [entry, { $type: "color", $value: hex }];
2661
+ })
2662
+ )
2663
+ };
2664
+ return JSON.stringify(obj, null, 2) + "\n";
2665
+ }
2666
+ function defaultName(index) {
2667
+ const scale = [
2668
+ "50",
2669
+ "100",
2670
+ "200",
2671
+ "300",
2672
+ "400",
2673
+ "500",
2674
+ "600",
2675
+ "700",
2676
+ "800",
2677
+ "900"
2678
+ ];
2679
+ return scale[index] ?? String((index + 1) * 100);
2680
+ }
2681
+ function resolveNames(colors, names, prefix) {
2682
+ return colors.map((c, i) => {
2683
+ const parsed = parseColor(c);
2684
+ if (!parsed) throw new Error(`Invalid color: ${c}`);
2685
+ const hex = rgbToHex(parsed);
2686
+ const raw = names?.[i] ?? defaultName(i);
2687
+ const slug = raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
2688
+ const name = prefix ? `${prefix}-${slug}` : slug;
2689
+ return { hex, name };
2690
+ });
2691
+ }
2692
+ var exportPaletteTool = {
2693
+ description: "Export a list of colors as CSS custom properties, SCSS variables, a Tailwind config snippet, W3C design tokens, or JSON.",
2694
+ execute: async (args) => {
2695
+ const { colors, format, names, prefix } = args;
2696
+ if (names && names.length !== colors.length) {
2697
+ return `Error: names array length (${names.length}) must match colors length (${colors.length}).`;
2698
+ }
2699
+ let items;
2700
+ try {
2701
+ items = resolveNames(colors, names, prefix);
2702
+ } catch (e) {
2703
+ return e instanceof Error ? e.message : String(e);
2704
+ }
2705
+ switch (format) {
2706
+ case "css":
2707
+ return asCss(items);
2708
+ case "json":
2709
+ return asJson(items);
2710
+ case "scss":
2711
+ return asScss(items);
2712
+ case "tailwind":
2713
+ return asTailwind(items, prefix);
2714
+ case "tokens":
2715
+ return asTokens(items, prefix);
2716
+ }
2717
+ },
2718
+ name: "export_palette",
2719
+ parameters: z11.object({
2720
+ colors: z11.array(z11.string()).min(1).describe("Palette colors"),
2721
+ format: z11.enum(FORMATS).describe(
2722
+ "Output format: css (custom properties), scss (variables), tailwind (config), tokens (W3C design tokens), json"
2723
+ ),
2724
+ names: z11.array(z11.string()).optional().describe(
2725
+ "Optional names for each color. Length must match colors. Defaults to a 50/100\u2026900 scale."
2726
+ ),
2727
+ prefix: z11.string().optional().describe("Optional prefix for variable/token names (e.g. 'brand')")
1997
2728
  })
1998
2729
  };
1999
2730
 
2000
2731
  // src/tools/palette-generator.tool.ts
2001
- import { z as z7 } from "zod";
2732
+ import { z as z12 } from "zod";
2002
2733
  var paletteGeneratorTool = {
2003
2734
  description: "Generate a color palette from a base color",
2004
2735
  execute: async (args) => {
@@ -2084,10 +2815,10 @@ var paletteGeneratorTool = {
2084
2815
  ${palette.map((color, i) => `${i + 1}. ${color}`).join("\n")}`;
2085
2816
  },
2086
2817
  name: "generate_palette",
2087
- parameters: z7.object({
2088
- baseColor: z7.string().describe("Base color for palette generation"),
2089
- count: z7.number().min(3).max(10).default(5).describe("Number of colors to generate"),
2090
- type: z7.enum([
2818
+ parameters: z12.object({
2819
+ baseColor: z12.string().describe("Base color for palette generation"),
2820
+ count: z12.number().min(3).max(10).default(5).describe("Number of colors to generate"),
2821
+ type: z12.enum([
2091
2822
  "monochromatic",
2092
2823
  "analogous",
2093
2824
  "complementary",
@@ -2098,7 +2829,7 @@ ${palette.map((color, i) => `${i + 1}. ${color}`).join("\n")}`;
2098
2829
  };
2099
2830
 
2100
2831
  // src/tools/palette-with-locks.tool.ts
2101
- import { z as z8 } from "zod";
2832
+ import { z as z13 } from "zod";
2102
2833
  var paletteWithLocksTool = {
2103
2834
  description: "Generate a color palette while preserving specific locked colors",
2104
2835
  execute: async (args) => {
@@ -2135,7 +2866,9 @@ var paletteWithLocksTool = {
2135
2866
  };
2136
2867
  let minDistance = Infinity;
2137
2868
  for (const locked of parsedLocked) {
2138
- const dist = colorDistance(candidate, locked, "deltaE2000");
2869
+ const dist = colorDistance(candidate, locked, {
2870
+ metric: "deltaE2000"
2871
+ });
2139
2872
  if (dist < minDistance) minDistance = dist;
2140
2873
  }
2141
2874
  if (minDistance > maxMinDistance) {
@@ -2188,14 +2921,12 @@ var paletteWithLocksTool = {
2188
2921
  }
2189
2922
  case "harmony": {
2190
2923
  const baseHsl = rgbToHsl(parsedLocked[0]);
2191
- const hueStep = 360 / totalColors;
2192
2924
  for (let i = 0; i < remainingSlots; i++) {
2193
- let newHue = baseHsl.h;
2194
2925
  let attempts = 0;
2195
2926
  let bestColor = null;
2196
2927
  let maxMinDistance = 0;
2197
2928
  while (attempts < 36) {
2198
- newHue = (baseHsl.h + attempts * 10) % 360;
2929
+ const newHue = (baseHsl.h + attempts * 10) % 360;
2199
2930
  const candidate = hslToRgb({
2200
2931
  h: newHue,
2201
2932
  l: baseHsl.l + (Math.random() - 0.5) * 20,
@@ -2203,7 +2934,9 @@ var paletteWithLocksTool = {
2203
2934
  });
2204
2935
  let minDistance = Infinity;
2205
2936
  for (const locked of parsedLocked) {
2206
- const dist = colorDistance(candidate, locked, "deltaE2000");
2937
+ const dist = colorDistance(candidate, locked, {
2938
+ metric: "deltaE2000"
2939
+ });
2207
2940
  if (dist < minDistance) minDistance = dist;
2208
2941
  }
2209
2942
  if (minDistance > maxMinDistance && minDistance > 10) {
@@ -2237,18 +2970,18 @@ Color space: ${colorSpace}
2237
2970
  Total colors: ${totalColors}`;
2238
2971
  },
2239
2972
  name: "generate_palette_with_locks",
2240
- parameters: z8.object({
2241
- colorSpace: z8.enum(["hsl", "lab"]).default("hsl").describe("Color space for interpolation (affects gradient smoothness)"),
2242
- lockedColors: z8.array(z8.string()).min(1).describe("Colors that must be included in the palette"),
2243
- mode: z8.enum(["harmony", "contrast", "gradient"]).default("harmony").describe(
2973
+ parameters: z13.object({
2974
+ colorSpace: z13.enum(["hsl", "lab"]).default("hsl").describe("Color space for interpolation (affects gradient smoothness)"),
2975
+ lockedColors: z13.array(z13.string()).min(1).describe("Colors that must be included in the palette"),
2976
+ mode: z13.enum(["harmony", "contrast", "gradient"]).default("harmony").describe(
2244
2977
  "Generation mode: harmony (similar), contrast (different), or gradient (smooth transition)"
2245
2978
  ),
2246
- totalColors: z8.number().min(2).max(20).describe("Total number of colors in the final palette")
2979
+ totalColors: z13.number().min(2).max(20).describe("Total number of colors in the final palette")
2247
2980
  })
2248
2981
  };
2249
2982
 
2250
2983
  // src/tools/theme-matching.tools.ts
2251
- import { z as z9 } from "zod";
2984
+ import { z as z14 } from "zod";
2252
2985
 
2253
2986
  // src/theme/matcher.ts
2254
2987
  var DEFAULT_WEIGHTS = {
@@ -2398,7 +3131,7 @@ function calculateHctDistance(color1, color2) {
2398
3131
  Math.pow(hueDiff * hueWeight, 2) + Math.pow(chromaDiff * chromaWeight, 2) + Math.pow(toneDiff * toneWeight, 2)
2399
3132
  ) * 100;
2400
3133
  }
2401
- function calculateMatchScore(inputHct, candidate, distance, weights, options) {
3134
+ function calculateMatchScore(_inputHct, candidate, distance, weights, options) {
2402
3135
  const perceptualScore = Math.max(0, 100 - distance) / 100;
2403
3136
  let semanticScore = 0.5;
2404
3137
  if (options.preferredRole && candidate.role) {
@@ -2831,7 +3564,7 @@ function refactorCss(css, themeVariables, options = {}) {
2831
3564
  } = options;
2832
3565
  const replacements = [];
2833
3566
  const warnings = [];
2834
- let refactoredCss = css;
3567
+ let refactoredCss;
2835
3568
  let totalColors = 0;
2836
3569
  let replacedColors = 0;
2837
3570
  let totalConfidence = 0;
@@ -3025,11 +3758,11 @@ Alternatives:`;
3025
3758
  return result;
3026
3759
  },
3027
3760
  name: "match_theme_color",
3028
- parameters: z9.object({
3029
- color: z9.string().describe("Color to match (hex, rgb, hsl)"),
3030
- context: z9.enum(["text", "background", "border", "shadow", "accent", "decorative"]).optional().describe("Usage context for better matching"),
3031
- minConfidence: z9.number().min(0).max(100).optional().default(70).describe("Minimum confidence threshold (0-100)"),
3032
- themeCSS: z9.string().describe("CSS containing theme variables")
3761
+ parameters: z14.object({
3762
+ color: z14.string().describe("Color to match (hex, rgb, hsl)"),
3763
+ context: z14.enum(["text", "background", "border", "shadow", "accent", "decorative"]).optional().describe("Usage context for better matching"),
3764
+ minConfidence: z14.number().min(0).max(100).optional().default(70).describe("Minimum confidence threshold (0-100)"),
3765
+ themeCSS: z14.string().describe("CSS containing theme variables")
3033
3766
  })
3034
3767
  };
3035
3768
  var refactorCssWithThemeTool = {
@@ -3078,12 +3811,12 @@ ${generateRefactoringReport(result)}`;
3078
3811
  return output;
3079
3812
  },
3080
3813
  name: "refactor_css_with_theme",
3081
- parameters: z9.object({
3082
- css: z9.string().describe("CSS content to refactor"),
3083
- generateReport: z9.boolean().optional().default(false).describe("Generate detailed refactoring report"),
3084
- minConfidence: z9.number().min(0).max(100).optional().default(70).describe("Minimum confidence for replacements"),
3085
- preserveOriginal: z9.boolean().optional().default(true).describe("Keep original values as comments"),
3086
- themeCSS: z9.string().describe("CSS containing theme variables")
3814
+ parameters: z14.object({
3815
+ css: z14.string().describe("CSS content to refactor"),
3816
+ generateReport: z14.boolean().optional().default(false).describe("Generate detailed refactoring report"),
3817
+ minConfidence: z14.number().min(0).max(100).optional().default(70).describe("Minimum confidence for replacements"),
3818
+ preserveOriginal: z14.boolean().optional().default(true).describe("Keep original values as comments"),
3819
+ themeCSS: z14.string().describe("CSS containing theme variables")
3087
3820
  })
3088
3821
  };
3089
3822
  var matchThemeColorsBatchTool = {
@@ -3116,10 +3849,10 @@ Summary:
3116
3849
  return result;
3117
3850
  },
3118
3851
  name: "match_theme_colors_batch",
3119
- parameters: z9.object({
3120
- colors: z9.array(z9.string()).min(1).max(50).describe("Array of colors to match"),
3121
- context: z9.enum(["text", "background", "border", "shadow", "accent", "decorative"]).optional().describe("Usage context for all colors"),
3122
- themeCSS: z9.string().describe("CSS containing theme variables")
3852
+ parameters: z14.object({
3853
+ colors: z14.array(z14.string()).min(1).max(50).describe("Array of colors to match"),
3854
+ context: z14.enum(["text", "background", "border", "shadow", "accent", "decorative"]).optional().describe("Usage context for all colors"),
3855
+ themeCSS: z14.string().describe("CSS containing theme variables")
3123
3856
  })
3124
3857
  };
3125
3858
  var generateThemeCssTool = {
@@ -3235,14 +3968,14 @@ var generateThemeCssTool = {
3235
3968
  return css;
3236
3969
  },
3237
3970
  name: "generate_theme_css",
3238
- parameters: z9.object({
3239
- includeTones: z9.array(z9.number()).optional().describe(
3971
+ parameters: z14.object({
3972
+ includeTones: z14.array(z14.number()).optional().describe(
3240
3973
  "Tone values to include (default: 0,10,20,30,40,50,60,70,80,90,95,99,100)"
3241
3974
  ),
3242
- prefix: z9.string().optional().default("color").describe(
3975
+ prefix: z14.string().optional().default("color").describe(
3243
3976
  "Prefix for CSS variables (e.g., 'color' \u2192 --color-primary-50)"
3244
3977
  ),
3245
- sourceColor: z9.string().describe("Source color for theme generation")
3978
+ sourceColor: z14.string().describe("Source color for theme generation")
3246
3979
  })
3247
3980
  };
3248
3981
 
@@ -3250,14 +3983,22 @@ var generateThemeCssTool = {
3250
3983
  var server = new CoolorsMcp({
3251
3984
  instructions: "Advanced color operations server with Material Design 3 support, CSS theme matching, image extraction, and accessibility compliance. Uses HCT color space for perceptually accurate operations.",
3252
3985
  name: "coolors-mcp",
3253
- version: "1.0.0"
3986
+ version: "1.1.0"
3254
3987
  });
3255
3988
  server.addTool(colorConversionTool);
3256
3989
  server.addTool(colorDistanceTool);
3257
3990
  server.addTool(contrastCheckerTool);
3991
+ server.addTool(adjustColorTool);
3258
3992
  server.addTool(paletteGeneratorTool);
3259
3993
  server.addTool(paletteWithLocksTool);
3260
3994
  server.addTool(gradientGeneratorTool);
3995
+ server.addTool(exportPaletteTool);
3996
+ server.addTool(simulateColorBlindnessTool);
3997
+ server.addTool(checkPaletteAccessibilityTool);
3998
+ server.addTool(generateTonalScaleTool);
3999
+ server.addTool(generateStateColorsTool);
4000
+ server.addTool(analyzePaletteConsistencyTool);
4001
+ server.addTool(generateSemanticPaletteTool);
3261
4002
  server.addTool(generateMaterialThemeTool);
3262
4003
  server.addTool(harmonizeColorsTool);
3263
4004
  server.addTool(generateTonalPaletteTool);