@trishchuk/coolors-mcp 1.0.1 → 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.
- package/.github/workflows/ci.yml +23 -20
- package/.github/workflows/deploy-docs.yml +6 -3
- package/.github/workflows/release.yml +11 -9
- package/README.md +123 -14
- package/dist/bin/server.js +997 -256
- package/dist/bin/server.js.map +1 -1
- package/dist/{chunk-P3ARRKLS.js → chunk-HOMDMKUY.js} +3 -1
- package/dist/{chunk-P3ARRKLS.js.map → chunk-HOMDMKUY.js.map} +1 -1
- package/dist/{chunk-IQ7NN26V.js → chunk-LHW2ZTOU.js} +14 -2
- package/dist/chunk-LHW2ZTOU.js.map +1 -0
- package/dist/color/index.js +1 -1
- package/dist/coolors-mcp.d.ts +4 -4
- package/dist/coolors-mcp.js +1 -1
- package/eslint.config.ts +13 -0
- package/jsr.json +1 -1
- package/package.json +16 -12
- package/src/bin/server.ts +13 -1
- package/src/color/__tests__/extract-colors.test.ts +20 -30
- package/src/color/apca.ts +105 -0
- package/src/color/color-blindness.ts +109 -0
- package/src/coolors-mcp.ts +1 -1
- package/src/session.ts +10 -2
- package/src/theme/matcher.ts +1 -1
- package/src/theme/refactor.ts +1 -1
- package/src/theme/types.ts +3 -0
- package/src/tools/__tests__/cohesion.test.ts +97 -0
- package/src/tools/__tests__/color-blindness.test.ts +45 -0
- package/src/tools/__tests__/color-conversion.test.ts +38 -0
- package/src/tools/__tests__/contrast-checker.test.ts +56 -0
- package/src/tools/__tests__/palette-export.test.ts +54 -0
- package/src/tools/adjust-color.tool.ts +80 -0
- package/src/tools/cohesion.tools.ts +380 -0
- package/src/tools/color-blindness.tool.ts +168 -0
- package/src/tools/color-conversion.tool.ts +1 -1
- package/src/tools/contrast-checker.tool.ts +53 -14
- package/src/tools/dislike-analyzer.tool.ts +41 -54
- package/src/tools/image-extraction.tools.ts +62 -115
- package/src/tools/index.ts +15 -2
- package/src/tools/palette-export.tool.ts +174 -0
- package/src/tools/palette-with-locks.tool.ts +8 -6
- package/src/types.ts +2 -3
- package/tsconfig.json +12 -2
- package/vitest.config.js +1 -3
- package/.claude/settings.local.json +0 -35
- package/.env +0 -2
- package/.mcp.json +0 -12
- package/CLAUDE.md +0 -201
- package/DOCUMENTATION.md +0 -274
- package/GEMINI.md +0 -54
- package/TOOLS_UK.md +0 -233
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +0 -621
- package/demo/podcast-player.html +0 -903
- package/dist/chunk-IQ7NN26V.js.map +0 -1
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +0 -111
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +0 -7
- package/docs/.vitepress/cache/deps/_metadata.json +0 -127
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +0 -12
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -13614
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -10698
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -5609
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape.js +0 -36234
- package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
- package/docs/.vitepress/cache/deps/dayjs.js +0 -507
- package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/debug.js +0 -512
- package/docs/.vitepress/cache/deps/debug.js.map +0 -7
- package/docs/.vitepress/cache/deps/package.json +0 -3
- package/docs/.vitepress/cache/deps/prismjs.js +0 -1638
- package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -235
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -173
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +0 -27
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +0 -72
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -56
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -107
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -5074
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -584
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1483
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +0 -1779
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -2023
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +0 -7
- package/docs/.vitepress/cache/deps/vue.js +0 -344
- package/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/examples/theme-matching.md +0 -113
- package/mcp-config.json +0 -8
- package/note.md +0 -34
- package/research_results.md +0 -53
- package/src/tools/colors.ts +0 -31
- package/src/tools/registry.ts +0 -142
- package/src/tools/simple-tools.ts +0 -37
package/dist/bin/server.js
CHANGED
|
@@ -1,30 +1,658 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CoolorsMcp
|
|
4
|
-
} from "../chunk-
|
|
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
|
-
|
|
29
|
+
rgbToLab,
|
|
30
|
+
saturate,
|
|
31
|
+
toGrayscale
|
|
32
|
+
} from "../chunk-HOMDMKUY.js";
|
|
25
33
|
|
|
26
|
-
// src/tools/color
|
|
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
|
|
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:
|
|
58
|
-
color:
|
|
59
|
-
to:
|
|
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
|
|
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:
|
|
80
|
-
color1:
|
|
81
|
-
color2:
|
|
82
|
-
metric:
|
|
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/
|
|
87
|
-
import {
|
|
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/
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
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
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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:
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
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
|
-
|
|
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: (${
|
|
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
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
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:
|
|
542
|
-
colors:
|
|
543
|
-
easing:
|
|
544
|
-
format:
|
|
545
|
-
interpolation:
|
|
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:
|
|
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
|
|
598
|
-
return argbFromXyz(x, y,
|
|
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,
|
|
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] *
|
|
606
|
-
const linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] *
|
|
607
|
-
const linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] *
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
|
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:
|
|
1909
|
-
includeCustomColors:
|
|
1910
|
-
sourceColor:
|
|
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:
|
|
1955
|
-
colors:
|
|
1956
|
-
factor:
|
|
1957
|
-
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:
|
|
1995
|
-
color:
|
|
1996
|
-
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
|
|
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:
|
|
2088
|
-
baseColor:
|
|
2089
|
-
count:
|
|
2090
|
-
type:
|
|
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
|
|
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,
|
|
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,
|
|
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:
|
|
2241
|
-
colorSpace:
|
|
2242
|
-
lockedColors:
|
|
2243
|
-
mode:
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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:
|
|
3029
|
-
color:
|
|
3030
|
-
context:
|
|
3031
|
-
minConfidence:
|
|
3032
|
-
themeCSS:
|
|
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:
|
|
3082
|
-
css:
|
|
3083
|
-
generateReport:
|
|
3084
|
-
minConfidence:
|
|
3085
|
-
preserveOriginal:
|
|
3086
|
-
themeCSS:
|
|
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:
|
|
3120
|
-
colors:
|
|
3121
|
-
context:
|
|
3122
|
-
themeCSS:
|
|
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:
|
|
3239
|
-
includeTones:
|
|
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:
|
|
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:
|
|
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.
|
|
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);
|