@atomixstudio/mcp 0.1.1 → 1.0.1

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/src/utils.ts DELETED
@@ -1,465 +0,0 @@
1
- /**
2
- * Utility functions for token traversal and manipulation
3
- */
4
-
5
- import { TOKEN_CATEGORIES, type TokenCategory } from "./tokens.js";
6
-
7
- /**
8
- * Get a token value by its dot-notation path
9
- * @example getTokenByPath(primitives, "colors.static.brand.primary")
10
- */
11
- export function getTokenByPath(obj: Record<string, unknown>, path: string): unknown {
12
- const parts = path.split(".");
13
- let current: unknown = obj;
14
-
15
- for (const part of parts) {
16
- if (current === null || current === undefined) return undefined;
17
- if (typeof current !== "object") return undefined;
18
- current = (current as Record<string, unknown>)[part];
19
- }
20
-
21
- return current;
22
- }
23
-
24
- /**
25
- * List all tokens in a category, optionally filtered by subcategory
26
- */
27
- export function listTokensInCategory(
28
- primitives: Record<string, unknown>,
29
- category: string,
30
- subcategory?: string
31
- ): Record<string, unknown> {
32
- let base = primitives[category];
33
-
34
- if (!base) return {};
35
-
36
- if (subcategory) {
37
- const subParts = subcategory.split(".");
38
- for (const part of subParts) {
39
- if (typeof base !== "object" || base === null) return {};
40
- base = (base as Record<string, unknown>)[part];
41
- }
42
- }
43
-
44
- if (typeof base !== "object" || base === null) {
45
- return { [subcategory || category]: base };
46
- }
47
-
48
- return flattenTokens(base as Record<string, unknown>);
49
- }
50
-
51
- /**
52
- * Flatten a nested token object into dot-notation paths
53
- */
54
- export function flattenTokens(
55
- obj: Record<string, unknown>,
56
- prefix = ""
57
- ): Record<string, unknown> {
58
- const result: Record<string, unknown> = {};
59
-
60
- for (const [key, value] of Object.entries(obj)) {
61
- const path = prefix ? `${prefix}.${key}` : key;
62
-
63
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
64
- Object.assign(result, flattenTokens(value as Record<string, unknown>, path));
65
- } else {
66
- result[path] = value;
67
- }
68
- }
69
-
70
- return result;
71
- }
72
-
73
- /**
74
- * Get all available token categories
75
- */
76
- export function getTokenCategories(): TokenCategory[] {
77
- return [...TOKEN_CATEGORIES];
78
- }
79
-
80
- /**
81
- * Convert a token path to a CSS variable name
82
- * @example getCssVariableName("colors.static.brand.primary") → "--atomix-colors-static-brand-primary"
83
- */
84
- export function getCssVariableName(path: string): string {
85
- // Convert dot notation to dash notation
86
- const dashPath = path
87
- .replace(/\./g, "-")
88
- .replace(/([a-z])([A-Z])/g, "$1-$2") // camelCase to kebab-case
89
- .toLowerCase();
90
-
91
- return `--atomix-${dashPath}`;
92
- }
93
-
94
- /**
95
- * Parse a CSS variable name back to a token path
96
- * @example parseVariableName("--atomix-colors-static-brand-primary") → "colors.static.brand.primary"
97
- */
98
- export function parseVariableName(varName: string): string | null {
99
- if (!varName.startsWith("--atomix-")) return null;
100
-
101
- return varName
102
- .replace("--atomix-", "")
103
- .replace(/-/g, ".");
104
- }
105
-
106
- /**
107
- * Check if a value looks like a hardcoded token (hex, rgb, px, etc.)
108
- */
109
- export function isHardcodedValue(value: string): {
110
- isHardcoded: boolean;
111
- type?: "color" | "spacing" | "unknown";
112
- details?: string;
113
- } {
114
- // Hex colors
115
- if (/^#[0-9A-Fa-f]{3,8}$/.test(value)) {
116
- return { isHardcoded: true, type: "color", details: "Hex color" };
117
- }
118
-
119
- // RGB/RGBA/HSL
120
- if (/^(rgb|rgba|hsl|hsla)\(/.test(value)) {
121
- return { isHardcoded: true, type: "color", details: "Color function" };
122
- }
123
-
124
- // Pixel values
125
- if (/^\d+px$/.test(value)) {
126
- return { isHardcoded: true, type: "spacing", details: "Pixel value" };
127
- }
128
-
129
- // Rem values (outside of tokens)
130
- if (/^\d+(\.\d+)?rem$/.test(value)) {
131
- return { isHardcoded: true, type: "spacing", details: "Rem value" };
132
- }
133
-
134
- // Tailwind arbitrary values
135
- if (/\[.+\]/.test(value)) {
136
- return { isHardcoded: true, type: "unknown", details: "Tailwind arbitrary value" };
137
- }
138
-
139
- return { isHardcoded: false };
140
- }
141
-
142
- /**
143
- * Find tokens matching a hex color value
144
- */
145
- export function findTokensByColor(
146
- primitives: Record<string, unknown>,
147
- hexColor: string
148
- ): Array<{ path: string; value: string }> {
149
- const allTokens = flattenTokens(primitives);
150
- const normalizedHex = hexColor.toLowerCase();
151
-
152
- return Object.entries(allTokens)
153
- .filter(([_, value]) => {
154
- if (typeof value !== "string") return false;
155
- return value.toLowerCase() === normalizedHex;
156
- })
157
- .map(([path, value]) => ({ path, value: value as string }));
158
- }
159
-
160
- /**
161
- * Get semantic alias for a token path (for shorter CSS variable names)
162
- */
163
- export function getSemanticAlias(path: string): string | null {
164
- // Common semantic aliases
165
- const aliases: Record<string, string> = {
166
- "colors.static.brand.primary": "--atomix-brand",
167
- "colors.modes.light.bgPage": "--atomix-bg-page",
168
- "colors.modes.light.bgSurface": "--atomix-bg-surface",
169
- "colors.modes.light.textPrimary": "--atomix-text-primary",
170
- "colors.modes.light.borderPrimary": "--atomix-border-primary",
171
- // Icon colors
172
- "colors.modes.light.iconBrand": "--atomix-icon-brand",
173
- "colors.modes.light.iconStrong": "--atomix-icon-strong",
174
- "colors.modes.light.iconSubtle": "--atomix-icon-subtle",
175
- "colors.modes.light.iconDisabled": "--atomix-icon-disabled",
176
- "typography.fontFamily.sans": "--atomix-font-sans",
177
- };
178
-
179
- return aliases[path] || null;
180
- }
181
-
182
- // ============================================
183
- // TOKEN TIER CLASSIFICATION
184
- // ============================================
185
-
186
- export type TokenTier = "primitive" | "semantic" | "component";
187
-
188
- export interface TokenMetadata {
189
- /** Token tier: primitive (raw values), semantic (purpose-driven), component (usage-specific) */
190
- tier: TokenTier;
191
- /** Whether this token can be modified by external tools */
192
- mutable: boolean;
193
- /** How to edit this token if mutable */
194
- editVia?: "designer" | "code" | "api";
195
- /** Usage guidance for AI tools */
196
- guidance: string;
197
- }
198
-
199
- /**
200
- * Determine the tier and metadata for a token path.
201
- *
202
- * TIER DEFINITIONS:
203
- * - Primitive: Raw foundational values (scales, raw colors, px/rem values)
204
- * → Read-only reference for validation/a11y/docs
205
- *
206
- * - Semantic: Purpose-driven mappings that reference primitives
207
- * → The primary API for styling, editable via Designer
208
- *
209
- * - Component: Token assignments for specific components
210
- * → Editable via Designer, specific to component variants/sizes
211
- */
212
- export function getTokenMetadata(path: string): TokenMetadata {
213
- // ============================================
214
- // PRIMITIVE TIER (Read-Only Reference)
215
- // Raw values that should not be modified by AI
216
- // ============================================
217
-
218
- // Color scales (raw palette)
219
- if (path.match(/^colors\.scales\./)) {
220
- return {
221
- tier: "primitive",
222
- mutable: false,
223
- guidance: "Raw color palette. Use semantic colors (colors.modes.*) in components instead.",
224
- };
225
- }
226
-
227
- // Alpha scales
228
- if (path.match(/^colors\.scales\.(black|white|green)Alpha\./)) {
229
- return {
230
- tier: "primitive",
231
- mutable: false,
232
- guidance: "Alpha transparency scale. Reference only for overlays/effects.",
233
- };
234
- }
235
-
236
- // Spacing scale
237
- if (path.match(/^spacing\.scale\./)) {
238
- return {
239
- tier: "primitive",
240
- mutable: false,
241
- guidance: "Spacing scale values. Use semantic spacing keys (xs, sm, md, lg) in components.",
242
- };
243
- }
244
-
245
- // Spacing inset/stack/inline (derived from scale)
246
- if (path.match(/^spacing\.(inset|stack|inline|gap)\./)) {
247
- return {
248
- tier: "primitive",
249
- mutable: false,
250
- guidance: "Spacing presets derived from scale. Reference only.",
251
- };
252
- }
253
-
254
- // Typography raw values (fontSize, fontWeight, lineHeight)
255
- if (path.match(/^typography\.(fontSize|fontWeight|lineHeight|lineHeightPx|letterSpacing|paragraphSpacing)\./)) {
256
- return {
257
- tier: "primitive",
258
- mutable: false,
259
- guidance: "Raw typography values. Use typeSets (title-lg, text-normal-regular) in components.",
260
- };
261
- }
262
-
263
- // Radius scale
264
- if (path.match(/^radius\.scale\./)) {
265
- return {
266
- tier: "primitive",
267
- mutable: false,
268
- guidance: "Border radius scale. Use semantic radius (radius.semantic.*) or scale keys in components.",
269
- };
270
- }
271
-
272
- // Border width scale
273
- if (path.match(/^borders\.width\./)) {
274
- return {
275
- tier: "primitive",
276
- mutable: false,
277
- guidance: "Border width scale. Use semantic border keys in components.",
278
- };
279
- }
280
-
281
- // Shadow elevation (raw shadows)
282
- if (path.match(/^shadows\.elevation\./)) {
283
- return {
284
- tier: "primitive",
285
- mutable: false,
286
- guidance: "Elevation shadow values. Use elevation keys (sm, md, lg) in components.",
287
- };
288
- }
289
-
290
- // Motion duration/easing (raw values)
291
- if (path.match(/^motion\.(duration|easing)\./)) {
292
- return {
293
- tier: "primitive",
294
- mutable: false,
295
- guidance: "Motion primitives. Use semantic motion presets (motion.semantic.*) when available.",
296
- };
297
- }
298
-
299
- // Z-index scale
300
- if (path.match(/^zIndex\.scale\./)) {
301
- return {
302
- tier: "primitive",
303
- mutable: false,
304
- guidance: "Z-index scale. Use semantic z-index (zIndex.semantic.*) in components.",
305
- };
306
- }
307
-
308
- // Sizing raw values
309
- if (path.match(/^sizing\./)) {
310
- return {
311
- tier: "primitive",
312
- mutable: false,
313
- guidance: "Component sizing values. Reference only for layout calculations.",
314
- };
315
- }
316
-
317
- // ============================================
318
- // SEMANTIC TIER (Primary API)
319
- // Purpose-driven tokens, editable via Designer
320
- // ============================================
321
-
322
- // Mode colors (light/dark semantic colors)
323
- if (path.match(/^colors\.modes\.(light|dark)\./)) {
324
- return {
325
- tier: "semantic",
326
- mutable: true,
327
- editVia: "designer",
328
- guidance: "Semantic color for theming. Use this in components. Editable via Atomix Designer.",
329
- };
330
- }
331
-
332
- // Adaptive colors (feedback colors)
333
- if (path.match(/^colors\.adaptive\./)) {
334
- return {
335
- tier: "semantic",
336
- mutable: true,
337
- editVia: "designer",
338
- guidance: "Adaptive feedback color (error, warning, success, info). Use for validation states.",
339
- };
340
- }
341
-
342
- // Static brand colors
343
- if (path.match(/^colors\.static\.brand/)) {
344
- return {
345
- tier: "semantic",
346
- mutable: false, // Brand colors are locked
347
- guidance: "Brand identity color. Use for primary brand elements. Protected from modification.",
348
- };
349
- }
350
-
351
- // Static utility colors (white, black, transparent)
352
- if (path.match(/^colors\.static\.(white|black|transparent)/)) {
353
- return {
354
- tier: "primitive",
355
- mutable: false,
356
- guidance: "Static utility color. Use for absolute white/black when needed.",
357
- };
358
- }
359
-
360
- // Semantic radius
361
- if (path.match(/^radius\.semantic\./)) {
362
- return {
363
- tier: "semantic",
364
- mutable: true,
365
- editVia: "designer",
366
- guidance: "Semantic border radius for component types. Use this in component styling.",
367
- };
368
- }
369
-
370
- // Semantic borders
371
- if (path.match(/^borders\.semantic\./)) {
372
- return {
373
- tier: "semantic",
374
- mutable: true,
375
- editVia: "designer",
376
- guidance: "Semantic border width for component types. Use this in component styling.",
377
- };
378
- }
379
-
380
- // Semantic motion
381
- if (path.match(/^motion\.semantic\./)) {
382
- return {
383
- tier: "semantic",
384
- mutable: true,
385
- editVia: "designer",
386
- guidance: "Semantic animation preset. Use for consistent motion patterns.",
387
- };
388
- }
389
-
390
- // Semantic z-index
391
- if (path.match(/^zIndex\.semantic\./)) {
392
- return {
393
- tier: "semantic",
394
- mutable: true,
395
- editVia: "designer",
396
- guidance: "Semantic z-index for UI layers. Use for proper stacking order.",
397
- };
398
- }
399
-
400
- // Focus shadows
401
- if (path.match(/^shadows\.focus\./)) {
402
- return {
403
- tier: "semantic",
404
- mutable: true,
405
- editVia: "designer",
406
- guidance: "Focus ring styles for accessibility. Use for keyboard focus indicators.",
407
- };
408
- }
409
-
410
- // Typography typeSets
411
- if (path.match(/^typography\.typeSets\./)) {
412
- return {
413
- tier: "semantic",
414
- mutable: true,
415
- editVia: "designer",
416
- guidance: "Composed typography style. Use these for consistent text styling.",
417
- };
418
- }
419
-
420
- // Typography font families
421
- if (path.match(/^typography\.fontFamily\./)) {
422
- return {
423
- tier: "semantic",
424
- mutable: true,
425
- editVia: "designer",
426
- guidance: "Font family selection. Use 'sans', 'mono', or 'display' keys.",
427
- };
428
- }
429
-
430
- // Typography text styles
431
- if (path.match(/^typography\.textStyles\./)) {
432
- return {
433
- tier: "semantic",
434
- mutable: true,
435
- editVia: "designer",
436
- guidance: "Complete text style definition. Use for consistent typography.",
437
- };
438
- }
439
-
440
- // ============================================
441
- // DEFAULT: Assume primitive if unknown
442
- // ============================================
443
- return {
444
- tier: "primitive",
445
- mutable: false,
446
- guidance: "Token classification unknown. Treat as read-only reference.",
447
- };
448
- }
449
-
450
- /**
451
- * Check if a token path is a semantic token (the primary API for AI tools)
452
- */
453
- export function isSemanticToken(path: string): boolean {
454
- const metadata = getTokenMetadata(path);
455
- return metadata.tier === "semantic";
456
- }
457
-
458
- /**
459
- * Check if a token path is a primitive (read-only reference)
460
- */
461
- export function isPrimitiveToken(path: string): boolean {
462
- const metadata = getTokenMetadata(path);
463
- return metadata.tier === "primitive";
464
- }
465
-