@atomixstudio/mcp 0.1.1 → 1.0.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/dist/index.js CHANGED
@@ -7,2090 +7,155 @@ import {
7
7
  CallToolRequestSchema,
8
8
  ListToolsRequestSchema,
9
9
  ListResourcesRequestSchema,
10
- ReadResourceRequestSchema
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
11
13
  } from "@modelcontextprotocol/sdk/types.js";
12
-
13
- // src/tokens.ts
14
- import { fileURLToPath } from "url";
15
- import { dirname, join } from "path";
16
- var __filename = fileURLToPath(import.meta.url);
17
- var __dirname = dirname(__filename);
18
- var atomixPath = join(__dirname, "../../atomix/dist/index.mjs");
19
- var loadedPrimitives = null;
20
- async function loadPrimitives() {
21
- if (loadedPrimitives) return loadedPrimitives;
22
- try {
23
- const module = await import(atomixPath);
24
- loadedPrimitives = module.primitives;
25
- return loadedPrimitives;
26
- } catch (error) {
27
- console.error(`Failed to load @atomix/tokens from ${atomixPath}:`, error);
28
- console.error("Falling back to inline primitives...");
29
- loadedPrimitives = FALLBACK_PRIMITIVES;
30
- return FALLBACK_PRIMITIVES;
31
- }
32
- }
33
- var primitives = {};
34
- loadPrimitives().then((p) => {
35
- primitives = p;
36
- });
37
- var TOKEN_CATEGORIES = [
38
- "colors",
39
- "typography",
40
- "spacing",
41
- "sizing",
42
- "shadows",
43
- "radius",
44
- "motion",
45
- "zIndex",
46
- "borders"
47
- ];
48
- var FALLBACK_PRIMITIVES = {
49
- colors: {
50
- static: {
51
- brand: {
52
- primary: "#007061",
53
- primaryLight: "#00A389",
54
- primaryDark: "#005A4D",
55
- primaryForeground: "#FFFFFF"
56
- },
57
- white: "#FFFFFF",
58
- black: "#000000"
59
- },
60
- modes: {
61
- light: {
62
- bgPage: "#FFFFFF",
63
- bgSurface: "#FFFFFF",
64
- bgMuted: "#F5F5F5",
65
- textPrimary: "#171717",
66
- textSecondary: "#525252",
67
- textMuted: "#A3A3A3",
68
- // Icon colors (derived from brand/text)
69
- iconBrand: "#007061",
70
- // = brand.primary
71
- iconStrong: "#171717",
72
- // = textPrimary
73
- iconSubtle: "#525252",
74
- // = textSecondary
75
- iconDisabled: "#A3A3A3",
76
- // = textMuted
77
- borderPrimary: "#E5E5E5"
78
- },
79
- dark: {
80
- bgPage: "#0A0A0A",
81
- bgSurface: "#1A1A1A",
82
- bgMuted: "#262626",
83
- textPrimary: "#FAFAFA",
84
- textSecondary: "#A3A3A3",
85
- textMuted: "#737373",
86
- // Icon colors (derived from brand/text)
87
- iconBrand: "#007061",
88
- // = brand.primary
89
- iconStrong: "#FAFAFA",
90
- // = textPrimary
91
- iconSubtle: "#A3A3A3",
92
- // = textSecondary
93
- iconDisabled: "#737373",
94
- // = textMuted
95
- borderPrimary: "#404040"
96
- }
97
- },
98
- scales: {
99
- green: {
100
- 50: "#E6F5F2",
101
- 500: "#007061",
102
- 900: "#002E28"
103
- }
104
- }
105
- },
106
- typography: {
107
- fontFamily: {
108
- sans: "Inter, system-ui, sans-serif",
109
- mono: "JetBrains Mono, monospace"
110
- },
111
- fontSize: {
112
- xs: "0.75rem",
113
- sm: "0.875rem",
114
- md: "1rem",
115
- lg: "1.125rem",
116
- xl: "1.25rem"
117
- },
118
- fontWeight: {
119
- regular: 400,
120
- medium: 500,
121
- semibold: 600,
122
- bold: 700
123
- },
124
- lineHeight: {
125
- tight: 1.25,
126
- normal: 1.5,
127
- relaxed: 1.625
128
- }
129
- },
130
- spacing: {
131
- scale: {
132
- xs: "0.25rem",
133
- sm: "0.5rem",
134
- md: "1rem",
135
- lg: "1.5rem",
136
- xl: "2rem"
137
- },
138
- inset: {
139
- xs: "0.25rem",
140
- sm: "0.5rem",
141
- md: "1rem",
142
- lg: "1.5rem"
143
- }
144
- },
145
- sizing: {
146
- button: {
147
- sm: { height: "32px" },
148
- md: { height: "40px" },
149
- lg: { height: "48px" }
150
- }
151
- },
152
- shadows: {
153
- elevation: {
154
- none: "none",
155
- sm: "0 1px 2px rgba(0, 0, 0, 0.05)",
156
- md: "0 4px 6px rgba(0, 0, 0, 0.1)",
157
- lg: "0 10px 15px rgba(0, 0, 0, 0.1)"
158
- },
159
- focus: {
160
- ring: "0 0 0 2px var(--atomix-brand)"
161
- }
162
- },
163
- radius: {
164
- scale: {
165
- none: "0",
166
- sm: "0.25rem",
167
- md: "0.5rem",
168
- lg: "0.75rem",
169
- xl: "1rem",
170
- full: "9999px"
171
- }
172
- },
173
- motion: {
174
- duration: {
175
- instant: "0ms",
176
- fast: "150ms",
177
- normal: "200ms",
178
- slow: "300ms"
179
- },
180
- easing: {
181
- ease: "cubic-bezier(0.4, 0, 0.2, 1)",
182
- easeIn: "cubic-bezier(0.4, 0, 1, 1)",
183
- easeOut: "cubic-bezier(0, 0, 0.2, 1)"
184
- }
185
- },
186
- zIndex: {
187
- dropdown: 1e3,
188
- modal: 1100,
189
- tooltip: 1200
190
- },
191
- borders: {
192
- width: {
193
- none: "0",
194
- thin: "1px",
195
- medium: "2px"
196
- }
197
- }
198
- };
199
-
200
- // src/utils.ts
201
- function getTokenByPath(obj, path) {
202
- const parts = path.split(".");
203
- let current = obj;
204
- for (const part of parts) {
205
- if (current === null || current === void 0) return void 0;
206
- if (typeof current !== "object") return void 0;
207
- current = current[part];
208
- }
209
- return current;
210
- }
211
- function listTokensInCategory(primitives3, category, subcategory) {
212
- let base = primitives3[category];
213
- if (!base) return {};
214
- if (subcategory) {
215
- const subParts = subcategory.split(".");
216
- for (const part of subParts) {
217
- if (typeof base !== "object" || base === null) return {};
218
- base = base[part];
219
- }
220
- }
221
- if (typeof base !== "object" || base === null) {
222
- return { [subcategory || category]: base };
223
- }
224
- return flattenTokens(base);
225
- }
226
- function flattenTokens(obj, prefix = "") {
227
- const result = {};
228
- for (const [key, value] of Object.entries(obj)) {
229
- const path = prefix ? `${prefix}.${key}` : key;
230
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
231
- Object.assign(result, flattenTokens(value, path));
232
- } else {
233
- result[path] = value;
234
- }
235
- }
236
- return result;
237
- }
238
- function getTokenCategories() {
239
- return [...TOKEN_CATEGORIES];
240
- }
241
- function getCssVariableName(path) {
242
- const dashPath = path.replace(/\./g, "-").replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
243
- return `--atomix-${dashPath}`;
244
- }
245
- function getTokenMetadata(path) {
246
- if (path.match(/^colors\.scales\./)) {
247
- return {
248
- tier: "primitive",
249
- mutable: false,
250
- guidance: "Raw color palette. Use semantic colors (colors.modes.*) in components instead."
251
- };
252
- }
253
- if (path.match(/^colors\.scales\.(black|white|green)Alpha\./)) {
254
- return {
255
- tier: "primitive",
256
- mutable: false,
257
- guidance: "Alpha transparency scale. Reference only for overlays/effects."
258
- };
259
- }
260
- if (path.match(/^spacing\.scale\./)) {
261
- return {
262
- tier: "primitive",
263
- mutable: false,
264
- guidance: "Spacing scale values. Use semantic spacing keys (xs, sm, md, lg) in components."
265
- };
266
- }
267
- if (path.match(/^spacing\.(inset|stack|inline|gap)\./)) {
268
- return {
269
- tier: "primitive",
270
- mutable: false,
271
- guidance: "Spacing presets derived from scale. Reference only."
272
- };
273
- }
274
- if (path.match(/^typography\.(fontSize|fontWeight|lineHeight|lineHeightPx|letterSpacing|paragraphSpacing)\./)) {
275
- return {
276
- tier: "primitive",
277
- mutable: false,
278
- guidance: "Raw typography values. Use typeSets (title-lg, text-normal-regular) in components."
279
- };
280
- }
281
- if (path.match(/^radius\.scale\./)) {
282
- return {
283
- tier: "primitive",
284
- mutable: false,
285
- guidance: "Border radius scale. Use semantic radius (radius.semantic.*) or scale keys in components."
286
- };
287
- }
288
- if (path.match(/^borders\.width\./)) {
289
- return {
290
- tier: "primitive",
291
- mutable: false,
292
- guidance: "Border width scale. Use semantic border keys in components."
293
- };
294
- }
295
- if (path.match(/^shadows\.elevation\./)) {
296
- return {
297
- tier: "primitive",
298
- mutable: false,
299
- guidance: "Elevation shadow values. Use elevation keys (sm, md, lg) in components."
300
- };
301
- }
302
- if (path.match(/^motion\.(duration|easing)\./)) {
303
- return {
304
- tier: "primitive",
305
- mutable: false,
306
- guidance: "Motion primitives. Use semantic motion presets (motion.semantic.*) when available."
307
- };
308
- }
309
- if (path.match(/^zIndex\.scale\./)) {
310
- return {
311
- tier: "primitive",
312
- mutable: false,
313
- guidance: "Z-index scale. Use semantic z-index (zIndex.semantic.*) in components."
314
- };
315
- }
316
- if (path.match(/^sizing\./)) {
317
- return {
318
- tier: "primitive",
319
- mutable: false,
320
- guidance: "Component sizing values. Reference only for layout calculations."
321
- };
322
- }
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
- if (path.match(/^colors\.adaptive\./)) {
332
- return {
333
- tier: "semantic",
334
- mutable: true,
335
- editVia: "designer",
336
- guidance: "Adaptive feedback color (error, warning, success, info). Use for validation states."
337
- };
338
- }
339
- if (path.match(/^colors\.static\.brand/)) {
340
- return {
341
- tier: "semantic",
342
- mutable: false,
343
- // Brand colors are locked
344
- guidance: "Brand identity color. Use for primary brand elements. Protected from modification."
345
- };
346
- }
347
- if (path.match(/^colors\.static\.(white|black|transparent)/)) {
348
- return {
349
- tier: "primitive",
350
- mutable: false,
351
- guidance: "Static utility color. Use for absolute white/black when needed."
352
- };
353
- }
354
- if (path.match(/^radius\.semantic\./)) {
355
- return {
356
- tier: "semantic",
357
- mutable: true,
358
- editVia: "designer",
359
- guidance: "Semantic border radius for component types. Use this in component styling."
360
- };
361
- }
362
- if (path.match(/^borders\.semantic\./)) {
363
- return {
364
- tier: "semantic",
365
- mutable: true,
366
- editVia: "designer",
367
- guidance: "Semantic border width for component types. Use this in component styling."
368
- };
369
- }
370
- if (path.match(/^motion\.semantic\./)) {
371
- return {
372
- tier: "semantic",
373
- mutable: true,
374
- editVia: "designer",
375
- guidance: "Semantic animation preset. Use for consistent motion patterns."
376
- };
377
- }
378
- if (path.match(/^zIndex\.semantic\./)) {
379
- return {
380
- tier: "semantic",
381
- mutable: true,
382
- editVia: "designer",
383
- guidance: "Semantic z-index for UI layers. Use for proper stacking order."
384
- };
385
- }
386
- if (path.match(/^shadows\.focus\./)) {
387
- return {
388
- tier: "semantic",
389
- mutable: true,
390
- editVia: "designer",
391
- guidance: "Focus ring styles for accessibility. Use for keyboard focus indicators."
392
- };
393
- }
394
- if (path.match(/^typography\.typeSets\./)) {
395
- return {
396
- tier: "semantic",
397
- mutable: true,
398
- editVia: "designer",
399
- guidance: "Composed typography style. Use these for consistent text styling."
400
- };
401
- }
402
- if (path.match(/^typography\.fontFamily\./)) {
403
- return {
404
- tier: "semantic",
405
- mutable: true,
406
- editVia: "designer",
407
- guidance: "Font family selection. Use 'sans', 'mono', or 'display' keys."
408
- };
409
- }
410
- if (path.match(/^typography\.textStyles\./)) {
411
- return {
412
- tier: "semantic",
413
- mutable: true,
414
- editVia: "designer",
415
- guidance: "Complete text style definition. Use for consistent typography."
416
- };
417
- }
418
- return {
419
- tier: "primitive",
420
- mutable: false,
421
- guidance: "Token classification unknown. Treat as read-only reference."
422
- };
423
- }
424
-
425
- // src/component-tokens.ts
426
- var COMPONENT_TOKENS = {
427
- "button": {
428
- "description": "Interactive button component with multiple variants and sizes",
429
- "sizes": {
430
- "sm": {
431
- "tokens": {
432
- "top": "xs",
433
- "right": "sm",
434
- "bottom": "xs",
435
- "left": "sm",
436
- "borderRadius": "radius.scale.lg",
437
- "typeSet": "typography.typeSets.text-small-bold"
438
- }
439
- },
440
- "padding": {
441
- "tokens": {
442
- "top": "xs",
443
- "right": "sm",
444
- "bottom": "xs",
445
- "left": "sm",
446
- "borderRadius": "radius.scale.lg",
447
- "typeSet": "typography.typeSets.text-small-bold"
448
- }
449
- },
450
- "md": {
451
- "tokens": {
452
- "top": "sm",
453
- "right": "lg",
454
- "bottom": "sm",
455
- "left": "lg",
456
- "borderRadius": "radius.scale.4xl",
457
- "typeSet": "typography.typeSets.text-normal-bold"
458
- }
459
- },
460
- "lg": {
461
- "tokens": {
462
- "top": "sm",
463
- "right": "lg",
464
- "bottom": "sm",
465
- "left": "lg",
466
- "borderRadius": "radius.scale.4xl",
467
- "typeSet": "typography.typeSets.text-large-bold"
468
- }
469
- }
470
- }
471
- },
472
- "card": {
473
- "description": "Container component for grouping related content",
474
- "variants": {
475
- "default": {
476
- "description": "default variant",
477
- "tokens": {
478
- "background": "colors.modes.{mode}.bgSurface",
479
- "borderColor": "colors.modes.{mode}.borderStrong",
480
- "borderWidth": "sm",
481
- "borderRadius": "xl",
482
- "shadow": "none",
483
- "titleColor": "colors.modes.{mode}.textPrimary",
484
- "bodyColor": "colors.modes.{mode}.textSecondary",
485
- "eyebrowColor": "colors.modes.{mode}.textMuted",
486
- "interaction": "elevation",
487
- "hoverShadow": "elevation-lg",
488
- "hoverTranslateY": "-2px",
489
- "hoverBorderColor": "colors.modes.{mode}.borderStrong"
490
- }
491
- },
492
- "elevated": {
493
- "description": "elevated variant",
494
- "tokens": {
495
- "background": "colors.modes.{mode}.bgSurface",
496
- "borderColor": "colors.modes.{mode}.borderPrimary",
497
- "borderWidth": "sm",
498
- "borderRadius": "2xl",
499
- "shadow": "elevation-md",
500
- "titleColor": "colors.modes.{mode}.textPrimary",
501
- "bodyColor": "colors.modes.{mode}.textSecondary",
502
- "eyebrowColor": "colors.modes.{mode}.textMuted",
503
- "interaction": "elevation",
504
- "hoverShadow": "elevation-lg",
505
- "hoverTranslateY": "-2px",
506
- "hoverBorderColor": "transparent"
507
- }
508
- },
509
- "outlined": {
510
- "description": "outlined variant",
511
- "tokens": {
512
- "background": "transparent",
513
- "borderColor": "colors.modes.{mode}.borderSecondary",
514
- "borderWidth": "lg",
515
- "borderRadius": "2xl",
516
- "shadow": "none",
517
- "titleColor": "colors.modes.{mode}.textPrimary",
518
- "bodyColor": "colors.modes.{mode}.textSecondary",
519
- "eyebrowColor": "colors.modes.{mode}.textMuted",
520
- "interaction": "border",
521
- "hoverShadow": "none",
522
- "hoverTranslateY": "0",
523
- "hoverBorderColor": "colors.modes.{mode}.borderStrong"
524
- }
525
- },
526
- "ghost": {
527
- "description": "ghost variant",
528
- "tokens": {
529
- "background": "transparent",
530
- "borderColor": "transparent",
531
- "borderWidth": "none",
532
- "borderRadius": "xl",
533
- "shadow": "none",
534
- "titleColor": "colors.modes.{mode}.textPrimary",
535
- "bodyColor": "colors.modes.{mode}.textMuted",
536
- "eyebrowColor": "colors.modes.{mode}.textMuted",
537
- "interaction": "none",
538
- "hoverShadow": "none",
539
- "hoverTranslateY": "0",
540
- "hoverBorderColor": "transparent"
541
- }
542
- }
543
- },
544
- "sizes": {
545
- "sm": {
546
- "tokens": {
547
- "top": "lg",
548
- "right": "xl",
549
- "bottom": "lg",
550
- "left": "xl",
551
- "headingSize": "sm"
552
- }
553
- },
554
- "padding": {
555
- "tokens": {
556
- "top": "lg",
557
- "right": "xl",
558
- "bottom": "lg",
559
- "left": "xl",
560
- "headingSize": "sm"
561
- }
562
- },
563
- "md": {
564
- "tokens": {
565
- "top": "xl",
566
- "right": "2xl",
567
- "bottom": "xl",
568
- "left": "2xl",
569
- "headingSize": "md"
570
- }
571
- },
572
- "lg": {
573
- "tokens": {
574
- "top": "2xl",
575
- "right": "3xl",
576
- "bottom": "2xl",
577
- "left": "3xl",
578
- "headingSize": "lg"
579
- }
580
- }
581
- },
582
- "shared": {
583
- "titleFontFamily": "typography.fontFamily.sans",
584
- "bodyFontFamily": "typography.fontFamily.sans"
585
- }
586
- },
587
- "input": {
588
- "description": "Text input field component with validation states",
589
- "variants": {
590
- "default": {
591
- "description": "default variant",
592
- "tokens": {
593
- "borderWidth": "sm",
594
- "borderColor": "colors.modes.{mode}.borderPrimary",
595
- "borderColorFocus": "colors.modes.{mode}.borderStrong",
596
- "helperTextColor": "colors.modes.{mode}.textMuted"
597
- }
598
- },
599
- "error": {
600
- "description": "error variant",
601
- "tokens": {
602
- "borderWidth": "md",
603
- "borderColor": "colors.adaptive.error.{mode}.border",
604
- "borderColorFocus": "colors.adaptive.error.{mode}.border",
605
- "helperTextColor": "colors.adaptive.error.{mode}.text"
606
- }
607
- },
608
- "success": {
609
- "description": "success variant",
610
- "tokens": {
611
- "borderWidth": "md",
612
- "borderColor": "colors.modes.{mode}.ring",
613
- "borderColorFocus": "colors.modes.{mode}.ring",
614
- "helperTextColor": "colors.adaptive.success.{mode}.text"
615
- }
616
- }
617
- },
618
- "sizes": {
619
- "sm": {
620
- "tokens": {
621
- "fieldHeight": "32px",
622
- "helperTypeSet": "helper-text",
623
- "borderRadius": "radius.scale.lg",
624
- "top": "xs",
625
- "right": "md",
626
- "bottom": "xs",
627
- "left": "md"
628
- }
629
- },
630
- "padding": {
631
- "tokens": {
632
- "top": "xs",
633
- "right": "md",
634
- "bottom": "xs",
635
- "left": "md"
636
- }
637
- },
638
- "md": {
639
- "tokens": {
640
- "fieldHeight": "40px",
641
- "helperTypeSet": "helper-text",
642
- "borderRadius": "radius.scale.xl",
643
- "top": "sm",
644
- "right": "md",
645
- "bottom": "sm",
646
- "left": "md"
647
- }
648
- },
649
- "lg": {
650
- "tokens": {
651
- "fieldHeight": "48px",
652
- "helperTypeSet": "helper-text",
653
- "borderRadius": "radius.scale.2xl",
654
- "top": "xl",
655
- "right": "lg",
656
- "bottom": "xl",
657
- "left": "lg"
658
- }
659
- }
660
- }
661
- },
662
- "select": {
663
- "description": "Unified text field component supporting input, select, and textarea",
664
- "variants": {
665
- "default": {
666
- "description": "default variant",
667
- "tokens": {
668
- "borderWidth": "md",
669
- "borderColor": "colors.modes.{mode}.borderPrimary",
670
- "borderColorFocus": "colors.modes.{mode}.borderSecondary",
671
- "helperTextColor": "colors.modes.{mode}.textMuted"
672
- }
673
- },
674
- "error": {
675
- "description": "error variant",
676
- "tokens": {
677
- "borderWidth": "md",
678
- "borderColor": "colors.adaptive.error.{mode}.border",
679
- "borderColorFocus": "colors.adaptive.error.{mode}.border",
680
- "helperTextColor": "colors.adaptive.error.{mode}.text"
681
- }
682
- },
683
- "success": {
684
- "description": "success variant",
685
- "tokens": {
686
- "borderWidth": "md",
687
- "borderColor": "colors.modes.{mode}.ring",
688
- "borderColorFocus": "colors.modes.{mode}.ring",
689
- "helperTextColor": "colors.adaptive.success.{mode}.text"
690
- }
691
- },
692
- "disabled": {
693
- "description": "disabled variant",
694
- "tokens": {
695
- "borderWidth": "sm",
696
- "borderColor": "colors.modes.{mode}.borderSecondary",
697
- "borderColorFocus": "colors.modes.{mode}.borderSecondary",
698
- "helperTextColor": "colors.modes.{mode}.textDisabled"
699
- }
700
- }
701
- },
702
- "sizes": {
703
- "sm": {
704
- "tokens": {
705
- "fieldHeight": "32px",
706
- "helperTypeSet": "helper-text",
707
- "borderRadius": "radius.scale.lg",
708
- "top": "xs",
709
- "right": "md",
710
- "bottom": "xs",
711
- "left": "md"
712
- }
713
- },
714
- "padding": {
715
- "tokens": {
716
- "top": "xs",
717
- "right": "md",
718
- "bottom": "xs",
719
- "left": "md"
720
- }
721
- },
722
- "md": {
723
- "tokens": {
724
- "fieldHeight": "44px",
725
- "helperTypeSet": "helper-text",
726
- "borderRadius": "none",
727
- "top": "sm",
728
- "right": "md",
729
- "bottom": "sm",
730
- "left": "md"
731
- }
732
- },
733
- "lg": {
734
- "tokens": {
735
- "fieldHeight": "48px",
736
- "helperTypeSet": "helper-text",
737
- "borderRadius": "radius.scale.2xl",
738
- "top": "xl",
739
- "right": "lg",
740
- "bottom": "xl",
741
- "left": "lg"
742
- }
743
- }
744
- }
745
- },
746
- "dialog": {
747
- "description": "Modal dialog for focused interactions",
748
- "variants": {
749
- "default": {
750
- "description": "default variant",
751
- "tokens": {
752
- "titleColor": "colors.modes.{mode}.textPrimary",
753
- "bodyColor": "colors.modes.{mode}.textSecondary",
754
- "contentAlign": "center",
755
- "eyebrowColor": "colors.modes.{mode}.textMuted",
756
- "buttonAlign": "center"
757
- }
758
- },
759
- "with-footer": {
760
- "description": "with-footer variant",
761
- "tokens": {
762
- "titleColor": "colors.modes.{mode}.textPrimary",
763
- "bodyColor": "colors.modes.{mode}.textSecondary",
764
- "contentAlign": "left",
765
- "eyebrowColor": "colors.modes.{mode}.textMuted",
766
- "buttonAlign": "space-between"
767
- }
768
- },
769
- "minimal": {
770
- "description": "minimal variant",
771
- "tokens": {
772
- "titleColor": "colors.modes.{mode}.textPrimary",
773
- "bodyColor": "colors.modes.{mode}.textMuted",
774
- "contentAlign": "center",
775
- "eyebrowColor": "colors.modes.{mode}.textMuted",
776
- "buttonAlign": "center"
777
- }
778
- }
779
- },
780
- "sizes": {
781
- "sm": {
782
- "tokens": {
783
- "headingSize": "sm",
784
- "top": "xl",
785
- "right": "xl",
786
- "bottom": "xl",
787
- "left": "xl",
788
- "buttonSize": "sm"
789
- }
790
- },
791
- "padding": {
792
- "tokens": {
793
- "top": "xl",
794
- "right": "xl",
795
- "bottom": "xl",
796
- "left": "xl",
797
- "buttonSize": "sm"
798
- }
799
- },
800
- "md": {
801
- "tokens": {
802
- "headingSize": "md",
803
- "top": "xl",
804
- "right": "xl",
805
- "bottom": "xl",
806
- "left": "xl",
807
- "buttonSize": "md"
808
- }
809
- },
810
- "lg": {
811
- "tokens": {
812
- "headingSize": "lg",
813
- "top": "xl",
814
- "right": "xl",
815
- "bottom": "xl",
816
- "left": "xl",
817
- "buttonSize": "lg"
818
- }
819
- }
820
- },
821
- "shared": {
822
- "titleFontFamily": "typography.fontFamily.sans",
823
- "bodyFontFamily": "typography.fontFamily.sans"
824
- }
825
- },
826
- "heading": {
827
- "description": "Typography component for titles, descriptions, and eyebrows",
828
- "variants": {
829
- "title-only": {
830
- "description": "title-only variant",
831
- "tokens": {
832
- "eyebrowColor": "colors.modes.{mode}.textMuted",
833
- "titleColor": "colors.modes.{mode}.textPrimary",
834
- "descriptionColor": "colors.modes.{mode}.textSecondary",
835
- "textAlign": "left",
836
- "eyebrowGap": "xs",
837
- "descriptionGap": "sm",
838
- "footerGap": "sm"
839
- }
840
- },
841
- "title-description": {
842
- "description": "title-description variant",
843
- "tokens": {
844
- "eyebrowColor": "colors.modes.{mode}.textMuted",
845
- "titleColor": "colors.modes.{mode}.textPrimary",
846
- "descriptionColor": "colors.modes.{mode}.textSecondary",
847
- "textAlign": "left",
848
- "eyebrowGap": "xs",
849
- "descriptionGap": "xs",
850
- "footerGap": "sm"
851
- }
852
- },
853
- "full": {
854
- "description": "full variant",
855
- "tokens": {
856
- "eyebrowColor": "colors.modes.{mode}.textSecondary",
857
- "titleColor": "colors.modes.{mode}.textPrimary",
858
- "descriptionColor": "colors.modes.{mode}.textSecondary",
859
- "textAlign": "left",
860
- "eyebrowGap": "xs",
861
- "descriptionGap": "xs",
862
- "footerGap": "sm"
863
- }
864
- }
865
- },
866
- "sizes": {
867
- "sm": {
868
- "tokens": {
869
- "eyebrowTypeSet": "typography.typeSets.title-sm",
870
- "titleTypeSet": "typography.typeSets.title-md",
871
- "descriptionTypeSet": "typography.typeSets.text-normal-regular"
872
- }
873
- },
874
- "md": {
875
- "tokens": {
876
- "eyebrowTypeSet": "typography.typeSets.title-sm",
877
- "titleTypeSet": "typography.typeSets.title-lg",
878
- "descriptionTypeSet": "typography.typeSets.text-normal-regular"
879
- }
880
- },
881
- "lg": {
882
- "tokens": {
883
- "eyebrowTypeSet": "typography.typeSets.title-sm",
884
- "titleTypeSet": "typography.typeSets.title-2xl",
885
- "descriptionTypeSet": "typography.typeSets.text-normal-regular"
886
- }
887
- }
888
- },
889
- "shared": {
890
- "fontFamily": "typography.fontFamily.sans"
891
- }
892
- },
893
- "selectionControls": {
894
- "description": "Selection control components: checkbox, radio, and toggle switch",
895
- "variants": {
896
- "checkbox": {
897
- "description": "checkbox variant",
898
- "tokens": {
899
- "background": "colors.modes.{mode}.bgMuted",
900
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
901
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
902
- "backgroundError": "colors.modes.{mode}.bgMuted",
903
- "borderWidth": "md",
904
- "borderWidthError": "md",
905
- "borderWidthDisabled": "md",
906
- "borderColor": "colors.modes.{mode}.borderStrong",
907
- "borderColorChecked": "transparent",
908
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
909
- "borderColorError": "colors.adaptive.error.{mode}.border",
910
- "indicatorColor": "colors.static.white",
911
- "labelColor": "colors.modes.{mode}.textPrimary",
912
- "interaction": "subtle"
913
- }
914
- },
915
- "radio": {
916
- "description": "radio variant",
917
- "tokens": {
918
- "background": "colors.modes.{mode}.bgMuted",
919
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
920
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
921
- "backgroundError": "colors.modes.{mode}.bgMuted",
922
- "borderWidth": "md",
923
- "borderWidthError": "md",
924
- "borderWidthDisabled": "md",
925
- "borderColor": "colors.modes.{mode}.borderStrong",
926
- "borderColorChecked": "transparent",
927
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
928
- "borderColorError": "colors.adaptive.error.{mode}.border",
929
- "indicatorColor": "colors.static.white",
930
- "labelColor": "colors.modes.{mode}.textPrimary",
931
- "interaction": "subtle"
932
- }
933
- },
934
- "toggle": {
935
- "description": "toggle variant",
936
- "tokens": {
937
- "background": "colors.modes.{mode}.bgMuted",
938
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
939
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
940
- "backgroundError": "colors.modes.{mode}.bgMuted",
941
- "borderWidth": "md",
942
- "borderWidthError": "md",
943
- "borderWidthDisabled": "md",
944
- "borderColor": "colors.modes.{mode}.borderStrong",
945
- "borderColorChecked": "transparent",
946
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
947
- "borderColorError": "colors.adaptive.error.{mode}.border",
948
- "indicatorColor": "colors.static.white",
949
- "labelColor": "colors.modes.{mode}.textPrimary",
950
- "interaction": "press"
951
- }
952
- }
953
- },
954
- "sizes": {
955
- "md": {
956
- "tokens": {
957
- "controlSize": "20px",
958
- "labelTypeSet": "typography.typeSets.text-normal-regular",
959
- "gap": "spacing.scale.sm",
960
- "borderRadius": "radius.scale.lg"
961
- }
962
- }
963
- }
964
- },
965
- "checkbox": {
966
- "description": "Checkbox selection control",
967
- "variants": {
968
- "default": {
969
- "description": "checkbox variant",
970
- "tokens": {
971
- "background": "colors.modes.{mode}.bgMuted",
972
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
973
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
974
- "backgroundError": "colors.modes.{mode}.bgMuted",
975
- "borderWidth": "md",
976
- "borderWidthError": "md",
977
- "borderWidthDisabled": "md",
978
- "borderColor": "colors.modes.{mode}.borderStrong",
979
- "borderColorChecked": "transparent",
980
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
981
- "borderColorError": "colors.adaptive.error.{mode}.border",
982
- "indicatorColor": "colors.static.white",
983
- "labelColor": "colors.modes.{mode}.textPrimary",
984
- "interaction": "subtle"
985
- }
986
- }
987
- },
988
- "sizes": {
989
- "md": {
990
- "tokens": {
991
- "controlSize": "20px",
992
- "labelTypeSet": "typography.typeSets.text-normal-regular",
993
- "gap": "spacing.scale.sm",
994
- "borderRadius": "radius.scale.lg"
995
- }
996
- }
997
- },
998
- "shared": {
999
- "transitionDuration": "motion.duration.fast",
1000
- "focusRing": "shadows.focus.ring"
1001
- }
1002
- },
1003
- "radio": {
1004
- "description": "Radio selection control",
1005
- "variants": {
1006
- "default": {
1007
- "description": "radio variant",
1008
- "tokens": {
1009
- "background": "colors.modes.{mode}.bgMuted",
1010
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
1011
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
1012
- "backgroundError": "colors.modes.{mode}.bgMuted",
1013
- "borderWidth": "md",
1014
- "borderWidthError": "md",
1015
- "borderWidthDisabled": "md",
1016
- "borderColor": "colors.modes.{mode}.borderStrong",
1017
- "borderColorChecked": "transparent",
1018
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
1019
- "borderColorError": "colors.adaptive.error.{mode}.border",
1020
- "indicatorColor": "colors.static.white",
1021
- "labelColor": "colors.modes.{mode}.textPrimary",
1022
- "interaction": "subtle"
1023
- }
1024
- }
1025
- },
1026
- "sizes": {
1027
- "md": {
1028
- "tokens": {
1029
- "controlSize": "20px",
1030
- "labelTypeSet": "typography.typeSets.text-normal-regular",
1031
- "gap": "spacing.scale.sm",
1032
- "borderRadius": "radius.scale.lg"
1033
- }
1034
- }
1035
- },
1036
- "shared": {
1037
- "transitionDuration": "motion.duration.fast",
1038
- "focusRing": "shadows.focus.ring"
1039
- }
1040
- },
1041
- "toggle": {
1042
- "description": "Toggle selection control",
1043
- "variants": {
1044
- "default": {
1045
- "description": "toggle variant",
1046
- "tokens": {
1047
- "background": "colors.modes.{mode}.bgMuted",
1048
- "backgroundChecked": "colors.modes.{mode}.actionPrimary",
1049
- "backgroundDisabled": "colors.modes.{mode}.actionSecondary",
1050
- "backgroundError": "colors.modes.{mode}.bgMuted",
1051
- "borderWidth": "md",
1052
- "borderWidthError": "md",
1053
- "borderWidthDisabled": "md",
1054
- "borderColor": "colors.modes.{mode}.borderStrong",
1055
- "borderColorChecked": "transparent",
1056
- "borderColorDisabled": "colors.modes.{mode}.borderSecondary",
1057
- "borderColorError": "colors.adaptive.error.{mode}.border",
1058
- "indicatorColor": "colors.static.white",
1059
- "labelColor": "colors.modes.{mode}.textPrimary",
1060
- "interaction": "press"
1061
- }
1062
- }
1063
- },
1064
- "sizes": {
1065
- "md": {
1066
- "tokens": {
1067
- "controlSize": "20px",
1068
- "labelTypeSet": "typography.typeSets.text-normal-regular",
1069
- "gap": "spacing.scale.sm",
1070
- "borderRadius": "radius.scale.lg"
1071
- }
1072
- }
1073
- },
1074
- "shared": {
1075
- "transitionDuration": "motion.duration.fast",
1076
- "focusRing": "shadows.focus.ring"
1077
- }
1078
- }
1079
- };
1080
-
1081
- // src/user-tokens.ts
1082
- var DEFAULT_BASE_URL = "https://atomixstudio.eu";
1083
- async function fetchUserDesignSystem(options) {
1084
- const { dsId, apiKey, baseUrl = DEFAULT_BASE_URL } = options;
1085
- try {
1086
- const headers = {
1087
- "Content-Type": "application/json"
1088
- };
1089
- if (apiKey) {
1090
- headers["Authorization"] = `Bearer ${apiKey}`;
1091
- }
1092
- const tokensResponse = await fetch(`${baseUrl}/api/ds/${dsId}/tokens`, {
1093
- method: "GET",
1094
- headers
1095
- });
1096
- if (!tokensResponse.ok) {
1097
- const error = await tokensResponse.text();
1098
- return {
1099
- success: false,
1100
- error: `Failed to fetch tokens: ${tokensResponse.status} ${error}`
1101
- };
1102
- }
1103
- const tokensData = await tokensResponse.json();
1104
- const meta = "meta" in tokensData && tokensData.meta ? tokensData.meta : void 0;
1105
- let governance;
1106
- try {
1107
- const rulesResponse = await fetch(`${baseUrl}/api/ds/${dsId}/rules`, {
1108
- method: "GET",
1109
- headers
1110
- });
1111
- if (rulesResponse.ok) {
1112
- const rulesData = await rulesResponse.json();
1113
- governance = rulesData;
1114
- }
1115
- } catch {
1116
- console.error("[atomix-mcp] Could not fetch governance rules, continuing without them");
1117
- }
1118
- const tokens = "tokens" in tokensData && tokensData.tokens ? tokensData.tokens : tokensData;
1119
- return {
1120
- success: true,
1121
- tokens,
1122
- governance,
1123
- meta
1124
- };
1125
- } catch (error) {
1126
- return {
1127
- success: false,
1128
- error: `Network error: ${error instanceof Error ? error.message : String(error)}`
1129
- };
1130
- }
1131
- }
1132
- function parseCLIArgs(argv = process.argv) {
1133
- const args = {};
1134
- for (let i = 0; i < argv.length; i++) {
1135
- const arg = argv[i];
1136
- const next = argv[i + 1];
1137
- switch (arg) {
1138
- case "--ds-id":
1139
- if (next && !next.startsWith("--")) {
1140
- args.dsId = next;
1141
- i++;
1142
- }
1143
- break;
1144
- case "--api-key":
1145
- if (next && !next.startsWith("--")) {
1146
- args.apiKey = next;
1147
- i++;
1148
- }
1149
- break;
1150
- case "--tenant":
1151
- if (next && !next.startsWith("--")) {
1152
- args.tenant = next;
1153
- args.dsId = args.dsId || next;
1154
- i++;
1155
- }
1156
- break;
1157
- case "--base-url":
1158
- if (next && !next.startsWith("--")) {
1159
- args.baseUrl = next;
1160
- i++;
1161
- }
1162
- break;
1163
- }
1164
- }
1165
- return args;
1166
- }
1167
- function isUserDSMode(args) {
1168
- return Boolean(args.dsId);
1169
- }
1170
- function transformUserTokens(userTokens) {
1171
- if (userTokens.colors && userTokens.typography && userTokens.spacing) {
1172
- return userTokens;
1173
- }
1174
- if (typeof userTokens.tokens === "object" && userTokens.tokens !== null) {
1175
- return userTokens.tokens;
1176
- }
1177
- return userTokens;
1178
- }
1179
-
1180
- // src/ai-rules-generator.ts
1181
- var AI_TOOLS = {
1182
- cursor: {
1183
- id: "cursor",
1184
- name: "Cursor",
1185
- rulesFilename: ".cursorrules",
1186
- rulesPath: ".cursorrules",
1187
- mcpConfigPath: ".cursor/mcp.json",
1188
- supportsMarkdown: true,
1189
- supportsMCP: true,
1190
- description: "Cursor IDE - AI-first code editor"
1191
- },
1192
- copilot: {
1193
- id: "copilot",
1194
- name: "GitHub Copilot",
1195
- rulesFilename: "copilot-instructions.md",
1196
- rulesPath: ".github/copilot-instructions.md",
1197
- supportsMarkdown: true,
1198
- supportsMCP: false,
1199
- // Uses rules file instead
1200
- description: "GitHub Copilot in VS Code, JetBrains, etc."
1201
- },
1202
- windsurf: {
1203
- id: "windsurf",
1204
- name: "Windsurf",
1205
- rulesFilename: ".windsurfrules",
1206
- rulesPath: ".windsurfrules",
1207
- mcpConfigPath: ".windsurf/mcp.json",
1208
- supportsMarkdown: true,
1209
- supportsMCP: true,
1210
- description: "Windsurf Editor by Codeium"
1211
- },
1212
- cline: {
1213
- id: "cline",
1214
- name: "Cline",
1215
- rulesFilename: ".clinerules",
1216
- rulesPath: ".clinerules",
1217
- supportsMarkdown: true,
1218
- supportsMCP: true,
1219
- description: "Cline VS Code extension (formerly Claude Dev)"
1220
- },
1221
- continue: {
1222
- id: "continue",
1223
- name: "Continue",
1224
- rulesFilename: ".continuerules",
1225
- rulesPath: ".continuerules",
1226
- mcpConfigPath: ".continue/config.json",
1227
- supportsMarkdown: true,
1228
- supportsMCP: true,
1229
- description: "Continue - open source AI code assistant"
1230
- },
1231
- zed: {
1232
- id: "zed",
1233
- name: "Zed",
1234
- rulesFilename: ".zed/assistant/rules.md",
1235
- rulesPath: ".zed/assistant/rules.md",
1236
- supportsMarkdown: true,
1237
- supportsMCP: true,
1238
- description: "Zed Editor with AI assistant"
1239
- },
1240
- "claude-desktop": {
1241
- id: "claude-desktop",
1242
- name: "Claude Desktop",
1243
- rulesFilename: "",
1244
- rulesPath: "",
1245
- mcpConfigPath: "~/Library/Application Support/Claude/claude_desktop_config.json",
1246
- supportsMarkdown: false,
1247
- supportsMCP: true,
1248
- description: "Claude Desktop App by Anthropic"
1249
- },
1250
- generic: {
1251
- id: "generic",
1252
- name: "Generic AI",
1253
- rulesFilename: "AI_GUIDELINES.md",
1254
- rulesPath: "AI_GUIDELINES.md",
1255
- supportsMarkdown: true,
1256
- supportsMCP: false,
1257
- description: "Generic guidelines for any AI tool"
1258
- }
1259
- };
1260
- function generateMCPConfig(tool, options = {}) {
1261
- const {
1262
- serverPath,
1263
- useNpx = true,
1264
- dsId,
1265
- apiKey,
1266
- tenantId,
1267
- // Legacy support
1268
- projectName = "my-project"
1269
- } = options;
1270
- const npxCommand = useNpx ? "npx" : "node";
1271
- const npxArgs = useNpx ? ["@atomixstudio/mcp@latest"] : [serverPath || "./node_modules/@atomixstudio/mcp/dist/index.js"];
1272
- const effectiveDsId = dsId || tenantId;
1273
- if (effectiveDsId && effectiveDsId !== "default") {
1274
- npxArgs.push("--ds-id", effectiveDsId);
1275
- if (apiKey) {
1276
- npxArgs.push("--api-key", apiKey);
1277
- }
1278
- }
1279
- switch (tool) {
1280
- case "cursor": {
1281
- const config = {
1282
- mcpServers: {
1283
- atomix: {
1284
- command: npxCommand,
1285
- args: npxArgs
1286
- }
1287
- }
1288
- };
1289
- return {
1290
- tool: "cursor",
1291
- filename: "mcp.json",
1292
- path: ".cursor/mcp.json",
1293
- content: JSON.stringify(config, null, 2),
1294
- instructions: `
1295
- ## Cursor MCP Setup
1296
-
1297
- 1. Create the file \`.cursor/mcp.json\` in your project root
1298
- 2. Paste the configuration below
1299
- 3. Restart Cursor IDE
1300
- 4. The Atomix MCP server will be available in Cursor's AI features
1301
-
1302
- ### Configuration File
1303
- \`\`\`json
1304
- ${JSON.stringify(config, null, 2)}
1305
- \`\`\`
1306
-
1307
- ### Verify Setup
1308
- After restarting Cursor, you can verify the MCP server is working by:
1309
- - Opening the AI chat (Cmd/Ctrl + L)
1310
- - Asking: "What design tokens are available?"
1311
- - The AI should query the Atomix MCP server and list token categories
1312
- `.trim()
1313
- };
1314
- }
1315
- case "claude-desktop": {
1316
- const config = {
1317
- mcpServers: {
1318
- atomix: {
1319
- command: npxCommand,
1320
- args: npxArgs
1321
- }
1322
- }
1323
- };
1324
- const configPath = process.platform === "darwin" ? "~/Library/Application Support/Claude/claude_desktop_config.json" : process.platform === "win32" ? "%APPDATA%\\Claude\\claude_desktop_config.json" : "~/.config/Claude/claude_desktop_config.json";
1325
- return {
1326
- tool: "claude-desktop",
1327
- filename: "claude_desktop_config.json",
1328
- path: configPath,
1329
- content: JSON.stringify(config, null, 2),
1330
- instructions: `
1331
- ## Claude Desktop MCP Setup
1332
-
1333
- 1. Locate your Claude Desktop config file:
1334
- - **macOS**: \`${configPath}\`
1335
- - **Windows**: \`%APPDATA%\\Claude\\claude_desktop_config.json\`
1336
- - **Linux**: \`~/.config/Claude/claude_desktop_config.json\`
1337
-
1338
- 2. Create or edit the file with the configuration below
1339
-
1340
- 3. Restart Claude Desktop
1341
-
1342
- 4. The Atomix MCP server will be available when chatting with Claude
1343
-
1344
- ### Configuration File
1345
- \`\`\`json
1346
- ${JSON.stringify(config, null, 2)}
1347
- \`\`\`
1348
-
1349
- ### Verify Setup
1350
- After restarting Claude Desktop:
1351
- - Start a new conversation
1352
- - Ask: "Can you check what Atomix design tokens are available?"
1353
- - Claude should use the MCP server to query tokens
1354
- `.trim()
1355
- };
1356
- }
1357
- case "windsurf": {
1358
- const config = {
1359
- mcpServers: {
1360
- atomix: {
1361
- command: npxCommand,
1362
- args: npxArgs
1363
- }
1364
- }
1365
- };
1366
- return {
1367
- tool: "windsurf",
1368
- filename: "mcp.json",
1369
- path: ".windsurf/mcp.json",
1370
- content: JSON.stringify(config, null, 2),
1371
- instructions: `
1372
- ## Windsurf MCP Setup
1373
-
1374
- 1. Create the file \`.windsurf/mcp.json\` in your project root
1375
- 2. Paste the configuration below
1376
- 3. Restart Windsurf Editor
1377
- 4. The Atomix MCP server will be available in Cascade
1378
-
1379
- ### Configuration File
1380
- \`\`\`json
1381
- ${JSON.stringify(config, null, 2)}
1382
- \`\`\`
1383
- `.trim()
1384
- };
1385
- }
1386
- case "continue": {
1387
- const config = {
1388
- models: [],
1389
- mcpServers: [
1390
- {
1391
- name: "atomix",
1392
- command: npxCommand,
1393
- args: npxArgs
1394
- }
1395
- ]
1396
- };
1397
- return {
1398
- tool: "continue",
1399
- filename: "config.json",
1400
- path: ".continue/config.json",
1401
- content: JSON.stringify(config, null, 2),
1402
- instructions: `
1403
- ## Continue MCP Setup
1404
-
1405
- 1. Create or edit \`.continue/config.json\` in your project root
1406
- 2. Add the mcpServers section below
1407
- 3. Restart VS Code
1408
- 4. The Atomix MCP server will be available in Continue
1409
-
1410
- ### Configuration File
1411
- \`\`\`json
1412
- ${JSON.stringify(config, null, 2)}
1413
- \`\`\`
1414
-
1415
- Note: If you already have a config.json, merge the mcpServers array.
1416
- `.trim()
1417
- };
1418
- }
1419
- case "vscode": {
1420
- return {
1421
- tool: "vscode",
1422
- filename: "settings.json",
1423
- path: ".vscode/settings.json",
1424
- content: JSON.stringify({
1425
- "mcp.servers": {
1426
- atomix: {
1427
- command: npxCommand,
1428
- args: npxArgs
1429
- }
1430
- }
1431
- }, null, 2),
1432
- instructions: `
1433
- ## VS Code MCP Setup
1434
-
1435
- For VS Code, MCP support depends on your AI extension:
1436
-
1437
- ### Option 1: Use GitHub Copilot Instructions
1438
- Create \`.github/copilot-instructions.md\` with Atomix rules (use getAIToolRules)
1439
-
1440
- ### Option 2: Use Continue Extension
1441
- See Continue setup instructions above
1442
-
1443
- ### Option 3: Use Cline Extension
1444
- 1. Install Cline extension
1445
- 2. Cline will auto-detect MCP servers in .cursor/mcp.json or .vscode/settings.json
1446
-
1447
- ### Settings.json (for MCP-aware extensions)
1448
- \`\`\`json
1449
- {
1450
- "mcp.servers": {
1451
- "atomix": {
1452
- "command": "${npxCommand}",
1453
- "args": ${JSON.stringify(npxArgs)}
1454
- }
1455
- }
1456
- }
1457
- \`\`\`
1458
- `.trim()
1459
- };
1460
- }
1461
- default:
1462
- throw new Error(`Unknown MCP config tool: ${tool}`);
1463
- }
1464
- }
1465
- function generateAllMCPConfigs(options = {}) {
1466
- const tools = ["cursor", "claude-desktop", "windsurf", "continue", "vscode"];
1467
- return tools.map((tool) => generateMCPConfig(tool, options));
1468
- }
1469
- function getSetupInstructions(toolId) {
1470
- const tool = AI_TOOLS[toolId];
1471
- if (!tool) {
1472
- throw new Error(`Unknown AI tool: ${toolId}. Available: ${Object.keys(AI_TOOLS).join(", ")}`);
1473
- }
1474
- const baseInstructions = `
1475
- # ${tool.name} Setup for Atomix Design System
1476
-
1477
- ${tool.description}
1478
-
1479
- ## Overview
1480
-
1481
- ${tool.supportsMCP ? `${tool.name} supports MCP (Model Context Protocol), allowing direct integration with the Atomix design token server.` : `${tool.name} uses a rules file to understand the Atomix design system.`}
1482
-
1483
- `;
1484
- switch (toolId) {
1485
- case "cursor":
1486
- return baseInstructions + `
1487
- ## Quick Setup
1488
-
1489
- ### Step 1: Add MCP Configuration
1490
-
1491
- Create \`.cursor/mcp.json\` in your project root:
1492
-
1493
- \`\`\`json
1494
- {
1495
- "mcpServers": {
1496
- "atomix": {
1497
- "command": "npx",
1498
- "args": ["@atomixstudio/mcp@latest"]
1499
- }
1500
- }
1501
- }
1502
- \`\`\`
1503
-
1504
- ### Step 2: Add Rules File (Optional but Recommended)
1505
-
1506
- Create \`.cursorrules\` in your project root with Atomix guidelines.
1507
- Use \`getAIToolRules({ tool: "cursor" })\` to generate this file.
1508
-
1509
- ### Step 3: Restart Cursor
1510
-
1511
- After adding the MCP config, restart Cursor IDE completely.
1512
-
1513
- ## Verification
1514
-
1515
- 1. Open AI chat (Cmd/Ctrl + L)
1516
- 2. Ask: "What Atomix design tokens are available?"
1517
- 3. Cursor should query the MCP server and respond with token information
1518
-
1519
- ## Available MCP Tools
1520
-
1521
- Once configured, you can use these in conversations:
1522
- - \`getToken("colors.static.brand.primary")\` - Get a specific token
1523
- - \`listTokens("colors")\` - List tokens in a category
1524
- - \`getComponentTokens("button")\` - Get component tokens
1525
- - \`validateUsage("#ff0000")\` - Check if a value is allowed
1526
- - \`searchTokens("brand")\` - Search for tokens
1527
- `;
1528
- case "claude-desktop":
1529
- return baseInstructions + `
1530
- ## Quick Setup
1531
-
1532
- ### Step 1: Locate Config File
1533
-
1534
- - **macOS**: \`~/Library/Application Support/Claude/claude_desktop_config.json\`
1535
- - **Windows**: \`%APPDATA%\\Claude\\claude_desktop_config.json\`
1536
- - **Linux**: \`~/.config/Claude/claude_desktop_config.json\`
1537
-
1538
- ### Step 2: Add MCP Configuration
1539
-
1540
- Create or edit the config file:
1541
-
1542
- \`\`\`json
1543
- {
1544
- "mcpServers": {
1545
- "atomix": {
1546
- "command": "npx",
1547
- "args": ["@atomixstudio/mcp@latest"]
1548
- }
1549
- }
1550
- }
1551
- \`\`\`
1552
-
1553
- ### Step 3: Restart Claude Desktop
1554
-
1555
- Completely quit and reopen Claude Desktop.
1556
-
1557
- ## Verification
1558
-
1559
- Start a new conversation and ask:
1560
- "Can you check what Atomix design tokens are available?"
1561
-
1562
- Claude should use the MCP server to query and list tokens.
1563
- `;
1564
- case "copilot":
1565
- return baseInstructions + `
1566
- ## Quick Setup
1567
-
1568
- GitHub Copilot doesn't support MCP directly, but uses instruction files.
1569
-
1570
- ### Step 1: Create Instructions File
1571
-
1572
- Create \`.github/copilot-instructions.md\` in your project:
1573
-
1574
- Use \`getAIToolRules({ tool: "copilot" })\` to generate the content.
1575
-
1576
- ### Step 2: Enable Custom Instructions
1577
-
1578
- In VS Code settings, ensure:
1579
- \`\`\`json
1580
- {
1581
- "github.copilot.chat.codeGeneration.useInstructionFiles": true
1582
- }
1583
- \`\`\`
1584
-
1585
- ## Verification
1586
-
1587
- 1. Open Copilot Chat
1588
- 2. Ask about design tokens
1589
- 3. Copilot should follow the Atomix guidelines from the instructions file
1590
- `;
1591
- case "windsurf":
1592
- return baseInstructions + `
1593
- ## Quick Setup
1594
-
1595
- ### Step 1: Add MCP Configuration
1596
-
1597
- Create \`.windsurf/mcp.json\` in your project root:
1598
-
1599
- \`\`\`json
1600
- {
1601
- "mcpServers": {
1602
- "atomix": {
1603
- "command": "npx",
1604
- "args": ["@atomixstudio/mcp@latest"]
1605
- }
1606
- }
1607
- }
1608
- \`\`\`
1609
-
1610
- ### Step 2: Add Rules File
1611
-
1612
- Create \`.windsurfrules\` in your project root.
1613
- Use \`getAIToolRules({ tool: "windsurf" })\` to generate this file.
1614
-
1615
- ### Step 3: Restart Windsurf
1616
-
1617
- Restart the editor to load the MCP configuration.
1618
- `;
1619
- case "cline":
1620
- return baseInstructions + `
1621
- ## Quick Setup
1622
-
1623
- ### Step 1: Create Rules File
1624
-
1625
- Create \`.clinerules\` in your project root.
1626
- Use \`getAIToolRules({ tool: "cline" })\` to generate this file.
1627
-
1628
- ### Step 2: MCP Auto-Detection
1629
-
1630
- Cline can auto-detect MCP servers from:
1631
- - \`.cursor/mcp.json\`
1632
- - Project configuration
1633
-
1634
- If you have Cursor's MCP config, Cline will use it automatically.
1635
-
1636
- ### Step 3: Manual MCP Setup (if needed)
1637
-
1638
- In Cline settings, add MCP server configuration:
1639
- - Command: \`npx\`
1640
- - Args: \`@atomixstudio/mcp@latest\`
1641
- `;
1642
- case "continue":
1643
- return baseInstructions + `
1644
- ## Quick Setup
1645
-
1646
- ### Step 1: Add MCP Configuration
1647
-
1648
- Create or edit \`.continue/config.json\`:
1649
-
1650
- \`\`\`json
1651
- {
1652
- "mcpServers": [
1653
- {
1654
- "name": "atomix",
1655
- "command": "npx",
1656
- "args": ["@atomixstudio/mcp@latest"]
1657
- }
1658
- ]
1659
- }
1660
- \`\`\`
1661
-
1662
- ### Step 2: Add Rules File (Optional)
1663
-
1664
- Create \`.continuerules\` in your project root.
1665
- Use \`getAIToolRules({ tool: "continue" })\` to generate this file.
1666
-
1667
- ### Step 3: Restart VS Code
1668
-
1669
- Restart VS Code to load the Continue configuration.
1670
- `;
1671
- case "zed":
1672
- return baseInstructions + `
1673
- ## Quick Setup
1674
-
1675
- ### Step 1: Add Rules File
1676
-
1677
- Create \`.zed/assistant/rules.md\` in your project root.
1678
- Use \`getAIToolRules({ tool: "zed" })\` to generate this file.
1679
-
1680
- ### Step 2: MCP Configuration
1681
-
1682
- Zed's MCP support is configured in Zed settings.
1683
- Check Zed documentation for the latest MCP configuration format.
1684
- `;
1685
- default:
1686
- return baseInstructions + `
1687
- ## Generic Setup
1688
-
1689
- ### Step 1: Create Guidelines File
1690
-
1691
- Create \`AI_GUIDELINES.md\` in your project root.
1692
- Use \`getAIToolRules({ tool: "generic" })\` to generate this file.
1693
-
1694
- ### Step 2: Reference in Prompts
1695
-
1696
- When working with AI tools, reference the guidelines file in your prompts
1697
- or include it in your context.
1698
- `;
1699
- }
1700
- }
1701
- function generateCursorRules(projectName, strict) {
1702
- const componentList = Object.keys(COMPONENT_TOKENS).join(", ");
1703
- const categoryList = TOKEN_CATEGORIES.join(", ");
1704
- const strictRules = strict ? `
1705
- ## Hard Rules (Strict Mode)
1706
-
1707
- 1. **NO arbitrary values** \u2014 \`w-[350px]\`, \`bg-[#ff0000]\` are FORBIDDEN
1708
- 2. **NO hardcoded colors** \u2014 All colors must use CSS variables or token imports
1709
- 3. **NO hardcoded spacing** \u2014 Use \`spacing.*\` tokens (e.g., \`@spacing.md\`, \`@spacing.lg\`)
1710
- 4. **NO hardcoded typography** \u2014 Use \`typography.fontSize.*\` and \`typography.fontWeight.*\`
1711
- 5. **Token vocabulary only** \u2014 If a value isn't in the design system, don't use it
1712
-
1713
- ### Escape Hatch
1714
-
1715
- If external constraints require arbitrary values:
1716
-
1717
- \`\`\`tsx
1718
- {/* @design-override: YouTube embed requires 16:9 aspect ratio */}
1719
- <div className="aspect-[16/9]">
1720
- \`\`\`
1721
-
1722
- Document the reason. The override comment signals intentional deviation.
1723
- ` : `
1724
- ## Guidelines (Non-Strict Mode)
1725
-
1726
- 1. **Prefer tokens** \u2014 Use design system tokens when available
1727
- 2. **Document overrides** \u2014 If using arbitrary values, add a comment explaining why
1728
- 3. **Consistency** \u2014 Match existing patterns in the codebase
1729
- `;
1730
- const tierGuidance = `
1731
- ## Token Tier System (Critical)
1732
-
1733
- Atomix tokens are organized in tiers. Understanding this hierarchy is essential:
1734
-
1735
- ### Tier 1: Primitives (Read-Only Reference)
1736
-
1737
- Raw foundational values. **DO NOT use these directly in components.**
1738
-
1739
- | Type | Example Path | Example Value | Use For |
1740
- |------|-------------|---------------|---------|
1741
- | Color scales | \`colors.scales.green.500\` | \`#10B981\` | Reference only |
1742
- | Spacing scale | \`spacing.md\` | \`1rem\` | Token reference |
1743
- | Font sizes | \`typography.fontSize.sm\` | \`0.875rem\` | Reference only |
1744
- | Radius scale | \`radius.lg\` | \`12px\` | Token reference |
1745
-
1746
- These values are **locked** \u2014 AI tools should never suggest modifying them.
1747
-
1748
- ### Tier 2: Semantics (Primary API)
1749
-
1750
- Purpose-driven tokens that reference primitives. **USE THESE in components.**
1751
-
1752
- | Type | Example Path | Maps To | Use For |
1753
- |------|-------------|---------|---------|
1754
- | Mode colors | \`colors.modes.light.bgSurface\` | \`neutral[5]\` | Backgrounds |
1755
- | Semantic radius | \`radius.semantic.button\` | \`scale.md\` | Component corners |
1756
- | TypeSets | \`typography.typeSets.title-lg\` | Composed style | Text styling |
1757
- | Focus rings | \`shadows.focus.ring\` | Composed shadow | A11y focus |
1758
-
1759
- These are **editable via Atomix Designer** and auto-switch for dark mode.
1760
-
1761
- ### Tier 3: Component Tokens
1762
-
1763
- Component-specific token assignments. Managed via Atomix Designer.
1764
-
1765
- - Button variants: primary, secondary, outline, ghost
1766
- - Card variants: default, outlined, elevated
1767
- - Sizes: sm, md, lg
1768
-
1769
- ---
1770
-
1771
- ### Correct Token Usage
1772
-
1773
- \`\`\`tsx
1774
- // \u2705 CORRECT: Use semantic color (Tier 2)
1775
- style={{ backgroundColor: "var(--atomix-bg-surface)" }}
1776
-
1777
- // \u274C WRONG: Using primitive directly (Tier 1)
1778
- style={{ backgroundColor: colors.scales.neutral[5] }}
1779
-
1780
- // \u2705 CORRECT: Use typeSet (Tier 2)
1781
- style={{ ...typography.typeSets["title-lg"] }}
1782
-
1783
- // \u274C WRONG: Using primitive font size (Tier 1)
1784
- style={{ fontSize: typography.fontSize["2xl"] }}
1785
- \`\`\`
1786
-
1787
- ### When to Reference Primitives
1788
-
1789
- Primitives are useful for:
1790
- 1. **Validation** \u2014 Checking if a hardcoded value matches a token
1791
- 2. **Documentation** \u2014 Explaining what a semantic token resolves to
1792
- 3. **A11y calculations** \u2014 Contrast ratio checks need actual hex values
1793
- 4. **Debug logging** \u2014 Showing resolved values in dev tools
1794
-
1795
- But **NEVER** use primitive paths directly in component styling code.
1796
- `;
1797
- return `# ${projectName} \u2014 Atomix Design System Rules
1798
-
1799
- This project uses the **Atomix Design System**. All UI code must follow these guidelines.
1800
-
1801
- ---
1802
- ${strictRules}
1803
- ---
1804
- ${tierGuidance}
1805
- ---
1806
-
1807
- ## Token Categories
1808
-
1809
- Available token categories: ${categoryList}
1810
-
1811
- ### Color Tokens
1812
-
1813
- Use CSS variables for all colors:
1814
-
1815
- \`\`\`tsx
1816
- // \u2705 Correct
1817
- style={{ backgroundColor: "var(--atomix-bg-surface)" }}
1818
- style={{ color: "var(--atomix-text-primary)" }}
1819
-
1820
- // \u274C Wrong
1821
- style={{ backgroundColor: "#ffffff" }}
1822
- style={{ color: "rgb(0, 0, 0)" }}
1823
- \`\`\`
1824
-
1825
- **Semantic color aliases:**
1826
- - \`--atomix-brand\` \u2014 Primary brand color
1827
- - \`--atomix-bg-page\` \u2014 Page background
1828
- - \`--atomix-bg-surface\` \u2014 Card/surface background
1829
- - \`--atomix-bg-muted\` \u2014 Muted/secondary background
1830
- - \`--atomix-text-primary\` \u2014 Primary text color
1831
- - \`--atomix-text-secondary\` \u2014 Secondary text color
1832
- - \`--atomix-text-muted\` \u2014 Muted/placeholder text
1833
- - \`--atomix-icon-brand\` \u2014 Brand-colored icons
1834
- - \`--atomix-icon-strong\` \u2014 High contrast icons
1835
- - \`--atomix-icon-subtle\` \u2014 Medium contrast icons
1836
- - \`--atomix-icon-disabled\` \u2014 Disabled state icons
1837
- - \`--atomix-border-primary\` \u2014 Default border color
1838
-
1839
- ### Spacing Tokens
1840
-
1841
- Use spacing tokens for all padding, margin, and gap values:
1842
-
1843
- \`\`\`tsx
1844
- import { spacing } from "@atomix/tokens/primitives";
1845
-
1846
- // \u2705 Correct
1847
- style={{ padding: spacing.inset.md }}
1848
- style={{ gap: spacing.gap.sm }}
1849
-
1850
- // \u274C Wrong
1851
- style={{ padding: "16px" }}
1852
- style={{ gap: "8px" }}
1853
- \`\`\`
1854
-
1855
- **Spacing scale:** xs, sm, md, lg, xl, 2xl, 3xl
1856
-
1857
- ### Typography Tokens
1858
-
1859
- Use typography tokens for font properties:
1860
-
1861
- \`\`\`tsx
1862
- import { typography } from "@atomix/tokens/primitives";
1863
-
1864
- // \u2705 Correct
1865
- style={{
1866
- fontSize: typography.fontSize.sm,
1867
- fontWeight: typography.fontWeight.medium,
1868
- lineHeight: typography.lineHeight.normal,
1869
- }}
1870
-
1871
- // \u274C Wrong
1872
- style={{ fontSize: "14px", fontWeight: 500 }}
1873
- \`\`\`
1874
-
1875
- ### Motion Tokens
1876
-
1877
- Use motion tokens for animations:
1878
-
1879
- \`\`\`tsx
1880
- import { motion } from "@atomix/tokens/primitives";
1881
-
1882
- // \u2705 Correct
1883
- style={{
1884
- transitionDuration: motion.duration.fast,
1885
- transitionTimingFunction: motion.easing.ease,
1886
- }}
1887
-
1888
- // \u274C Wrong
1889
- style={{ transition: "all 150ms ease" }}
1890
- \`\`\`
1891
-
1892
- ---
1893
-
1894
- ## Component Usage
1895
-
1896
- Available components: ${componentList}
1897
-
1898
- ### Using the MCP Server
1899
-
1900
- This project has an MCP server for querying design tokens. Use these tools:
1901
-
1902
- - \`getToken("path.to.token")\` \u2014 Get a specific token value
1903
- - \`listTokens("colors")\` \u2014 List all tokens in a category
1904
- - \`getComponentTokens("button")\` \u2014 Get tokens for a component
1905
- - \`validateUsage("value")\` \u2014 Check if a value follows the design system
1906
- - \`searchTokens("brand")\` \u2014 Search for tokens by name or value
1907
-
1908
- ### Component Patterns
1909
-
1910
- When implementing components:
1911
-
1912
- 1. Check the component tokens first: \`getComponentTokens("componentName")\`
1913
- 2. Use the documented variants and sizes
1914
- 3. Apply tokens through CSS variables or direct imports
1915
- 4. Follow the existing component architecture
1916
-
1917
- ---
1918
-
1919
- ## Import Patterns
1920
-
1921
- \`\`\`tsx
1922
- // Import primitives for direct use
1923
- import { colors, spacing, typography, motion } from "@atomix/tokens/primitives";
1924
-
1925
- // Or import the combined object
1926
- import { primitives } from "@atomix/tokens";
1927
-
1928
- // Use CSS variables in styles (preferred for colors)
1929
- const style = {
1930
- backgroundColor: "var(--atomix-bg-surface)",
1931
- padding: spacing.inset.md,
1932
- borderRadius: "var(--atomix-radius-scale-md)",
1933
- };
1934
- \`\`\`
1935
-
1936
- ---
1937
-
1938
- ## Dark Mode
1939
-
1940
- Colors automatically switch in dark mode when using CSS variables:
1941
-
1942
- \`\`\`tsx
1943
- // This works in both light and dark mode
1944
- style={{
1945
- backgroundColor: "var(--atomix-bg-surface)",
1946
- color: "var(--atomix-text-primary)",
1947
- }}
1948
- \`\`\`
1949
-
1950
- The \`.dark\` class on the root element toggles all color variables.
1951
-
1952
- ---
1953
-
1954
- ## When Unsure
1955
-
1956
- 1. **Search tokens first:** Use \`searchTokens("query")\` to find relevant tokens
1957
- 2. **Check component tokens:** Use \`getComponentTokens("component")\` for component-specific values
1958
- 3. **Use the nearest token:** If exact match unavailable, use the closest semantic token
1959
- 4. **Flag for review:** Add \`{/* TODO: need new token? */}\` comment if a token seems missing
1960
-
1961
- ---
1962
-
1963
- ## Reference
1964
-
1965
- - Token package: \`@atomix/tokens\`
1966
- - CSS variables: All prefixed with \`--atomix-\`
1967
- - MCP server: \`atomix-mcp\` (run \`npm run start\` in packages/atomix-mcp)
1968
- `;
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import * as readline from "readline";
17
+ function parseArgs() {
18
+ const args = process.argv.slice(2);
19
+ let command = "server";
20
+ let dsId2 = null;
21
+ let apiKey2 = null;
22
+ let apiBase2 = null;
23
+ let output = null;
24
+ let format = null;
25
+ let exclude = [];
26
+ let yes = false;
27
+ if (args[0] && !args[0].startsWith("-")) {
28
+ const cmd = args[0].toLowerCase();
29
+ if (cmd === "sync") command = "sync";
30
+ else if (cmd === "init") command = "init";
31
+ else if (cmd === "help" || cmd === "--help" || cmd === "-h") command = "help";
32
+ }
33
+ for (let i = 0; i < args.length; i++) {
34
+ if (args[i] === "--ds-id" && args[i + 1]) {
35
+ dsId2 = args[i + 1];
36
+ i++;
37
+ } else if (args[i] === "--api-key" && args[i + 1]) {
38
+ apiKey2 = args[i + 1];
39
+ i++;
40
+ } else if (args[i] === "--api-base" && args[i + 1]) {
41
+ apiBase2 = args[i + 1];
42
+ i++;
43
+ } else if ((args[i] === "--output" || args[i] === "-o") && args[i + 1]) {
44
+ output = args[i + 1];
45
+ i++;
46
+ } else if (args[i] === "--format" && args[i + 1]) {
47
+ const f = args[i + 1].toLowerCase();
48
+ const validFormats = ["css", "scss", "less", "json", "ts", "js", "swift", "kotlin", "dart"];
49
+ if (validFormats.includes(f)) format = f;
50
+ i++;
51
+ } else if (args[i] === "--exclude" && args[i + 1]) {
52
+ exclude.push(args[i + 1]);
53
+ i++;
54
+ } else if (args[i] === "-y" || args[i] === "--yes") {
55
+ yes = true;
56
+ }
57
+ }
58
+ return { command, dsId: dsId2, apiKey: apiKey2, apiBase: apiBase2, output, format, exclude, yes };
1969
59
  }
1970
- function generateRulesForTool(toolId, projectName, strict) {
1971
- const tool = AI_TOOLS[toolId];
1972
- if (!tool) {
1973
- throw new Error(`Unknown AI tool: ${toolId}. Available: ${Object.keys(AI_TOOLS).join(", ")}`);
60
+ var cliArgs = parseArgs();
61
+ var { dsId, apiKey } = cliArgs;
62
+ var apiBase = cliArgs.apiBase || "https://atomixstudio.eu";
63
+ var cachedData = null;
64
+ async function fetchDesignSystem() {
65
+ if (cachedData) return cachedData;
66
+ if (!dsId) {
67
+ throw new Error("Missing --ds-id argument. Usage: npx @atomixstudio/mcp --ds-id <id> --api-key <key>");
1974
68
  }
1975
- const header = `<!-- Generated for ${tool.name} by Atomix MCP Server -->
1976
- <!-- Tool: ${tool.description} -->
1977
- <!-- Rules file: ${tool.rulesPath} -->
1978
- <!-- Generated: ${(/* @__PURE__ */ new Date()).toISOString()} -->
1979
-
1980
- `;
1981
- const content = header + generateCursorRules(projectName, strict);
1982
- return {
1983
- tool,
1984
- filename: tool.rulesFilename,
1985
- path: tool.rulesPath,
1986
- content
69
+ const url = `${apiBase}/api/ds/${dsId}/tokens?format=export`;
70
+ const headers = {
71
+ "Content-Type": "application/json"
1987
72
  };
73
+ if (apiKey) {
74
+ headers["x-api-key"] = apiKey;
75
+ }
76
+ const response = await fetch(url, { headers });
77
+ if (!response.ok) {
78
+ const text = await response.text();
79
+ throw new Error(`Failed to fetch design system: ${response.status} ${text}`);
80
+ }
81
+ const data = await response.json();
82
+ cachedData = data;
83
+ return data;
1988
84
  }
1989
- function generateRulesForAllTools(projectName, strict) {
1990
- return Object.keys(AI_TOOLS).filter((id) => AI_TOOLS[id].rulesPath !== "").map(
1991
- (toolId) => generateRulesForTool(toolId, projectName, strict)
1992
- );
1993
- }
1994
- function getSupportedAITools() {
1995
- return Object.values(AI_TOOLS);
85
+ var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
86
+ function getTokenByPath(tokens, path2) {
87
+ const parts = path2.split(".");
88
+ let current = tokens;
89
+ for (const part of parts) {
90
+ if (current && typeof current === "object" && part in current) {
91
+ current = current[part];
92
+ } else {
93
+ return void 0;
94
+ }
95
+ }
96
+ return current;
1996
97
  }
1997
- function generateToolSetupGuide(projectName) {
1998
- const tools = getSupportedAITools();
1999
- const mcpTools = tools.filter((t) => t.supportsMCP);
2000
- const rulesTools = tools.filter((t) => t.rulesPath);
2001
- return `# AI Tool Setup Guide for ${projectName}
2002
-
2003
- This project uses the **Atomix Design System** with MCP (Model Context Protocol).
2004
-
2005
- ## Quick Start
2006
-
2007
- Choose your AI tool and follow the setup:
2008
-
2009
- | Tool | MCP Support | Rules File | Setup Command |
2010
- |------|-------------|------------|---------------|
2011
- ${tools.map((t) => `| ${t.name} | ${t.supportsMCP ? "Yes" : "No"} | ${t.rulesPath || "N/A"} | \`getSetupInstructions("${t.id}")\` |`).join("\n")}
2012
-
2013
- ## MCP-Enabled Tools (Recommended)
2014
-
2015
- These tools can query Atomix tokens directly:
2016
-
2017
- ${mcpTools.map((t) => `- **${t.name}** \u2014 ${t.description}`).join("\n")}
2018
-
2019
- ### Generate MCP Config
2020
-
2021
- \`\`\`
2022
- exportMCPConfig({ tool: "cursor" }) // For Cursor
2023
- exportMCPConfig({ tool: "claude-desktop" }) // For Claude Desktop
2024
- exportMCPConfig({ tool: "all" }) // All configs at once
2025
- \`\`\`
2026
-
2027
- ## Rules-Based Tools
2028
-
2029
- These tools use instruction files:
2030
-
2031
- ${rulesTools.filter((t) => !t.supportsMCP).map((t) => `- **${t.name}** \u2014 \`${t.rulesPath}\``).join("\n")}
2032
-
2033
- ### Generate Rules Files
2034
-
2035
- \`\`\`
2036
- getAIToolRules({ tool: "copilot" }) // For GitHub Copilot
2037
- getAIToolRules({ tool: "all" }) // All rules at once
2038
- \`\`\`
2039
-
2040
- ## Available MCP Tools
2041
-
2042
- Once connected, AI tools can use:
2043
-
2044
- | Tool | Description |
2045
- |------|-------------|
2046
- | \`getToken\` | Get a specific token by path |
2047
- | \`listTokens\` | List tokens in a category |
2048
- | \`getComponentTokens\` | Get tokens for a component |
2049
- | \`validateUsage\` | Check if a value follows the design system |
2050
- | \`searchTokens\` | Search tokens by name or value |
2051
- | \`exportMCPConfig\` | Generate MCP configuration files |
2052
- | \`getSetupInstructions\` | Get detailed setup instructions |
2053
-
2054
- ## Token Tier System
2055
-
2056
- | Tier | Mutable | Use For |
2057
- |------|---------|---------|
2058
- | Primitive | No | Reference only (validation, a11y) |
2059
- | Semantic | Yes (Designer) | Primary API for styling |
2060
- | Component | Yes (Designer) | Component-specific tokens |
2061
-
2062
- **Rule**: Always use semantic tokens in code. Never use primitive paths directly.
2063
- `;
98
+ function flattenTokens(obj, prefix = "") {
99
+ const results = [];
100
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
101
+ for (const [key, value] of Object.entries(obj)) {
102
+ const newPath = prefix ? `${prefix}.${key}` : key;
103
+ if (value && typeof value === "object" && !Array.isArray(value)) {
104
+ results.push(...flattenTokens(value, newPath));
105
+ } else {
106
+ results.push({ path: newPath, value });
107
+ }
108
+ }
109
+ }
110
+ return results;
2064
111
  }
2065
-
2066
- // src/index.ts
2067
- function formatTimeAgo(timestamp) {
2068
- const now = Date.now();
2069
- const diffMs = now - timestamp;
2070
- const diffSec = Math.floor(diffMs / 1e3);
2071
- const diffMin = Math.floor(diffSec / 60);
2072
- const diffHour = Math.floor(diffMin / 60);
2073
- const diffDay = Math.floor(diffHour / 24);
2074
- if (diffSec < 60) return "just now";
2075
- if (diffMin < 60) return `${diffMin} minute${diffMin > 1 ? "s" : ""} ago`;
2076
- if (diffHour < 24) return `${diffHour} hour${diffHour > 1 ? "s" : ""} ago`;
2077
- if (diffDay < 7) return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`;
2078
- const date = new Date(timestamp);
2079
- return date.toLocaleDateString("en-US", {
2080
- month: "short",
2081
- day: "numeric",
2082
- year: date.getFullYear() !== (/* @__PURE__ */ new Date()).getFullYear() ? "numeric" : void 0
112
+ function searchTokens(tokens, query) {
113
+ const flat = flattenTokens(tokens);
114
+ const lowerQuery = query.toLowerCase();
115
+ return flat.filter(({ path: path2, value }) => {
116
+ const pathMatch = path2.toLowerCase().includes(lowerQuery);
117
+ const valueMatch = String(value).toLowerCase().includes(lowerQuery);
118
+ return pathMatch || valueMatch;
2083
119
  });
2084
120
  }
121
+ function countTokens(tokens, prefix = "") {
122
+ let count = 0;
123
+ for (const [key, value] of Object.entries(tokens)) {
124
+ if (value && typeof value === "object" && !Array.isArray(value)) {
125
+ count += countTokens(value, `${prefix}${key}.`);
126
+ } else if (value !== void 0 && value !== null) {
127
+ count++;
128
+ }
129
+ }
130
+ return count;
131
+ }
132
+ function getTokenStats(data) {
133
+ const byCategory = {};
134
+ let total = 0;
135
+ for (const [category, value] of Object.entries(data.tokens)) {
136
+ if (value && typeof value === "object") {
137
+ const count = countTokens(value);
138
+ byCategory[category] = count;
139
+ total += count;
140
+ }
141
+ }
142
+ return {
143
+ total,
144
+ byCategory,
145
+ cssVariables: Object.keys(data.cssVariables).length,
146
+ governanceRules: data.governance.rules.length
147
+ };
148
+ }
2085
149
  var server = new Server(
2086
150
  {
2087
- name: "atomix-mcp",
2088
- version: "0.1.0"
151
+ name: "atomix-mcp-user",
152
+ version: "1.0.0"
2089
153
  },
2090
154
  {
2091
155
  capabilities: {
2092
156
  tools: {},
2093
- resources: {}
157
+ resources: {},
158
+ prompts: {}
2094
159
  }
2095
160
  }
2096
161
  );
@@ -2099,17 +164,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2099
164
  tools: [
2100
165
  {
2101
166
  name: "getToken",
2102
- description: "Get a specific design token by its path. Returns value, CSS variable, and tier metadata. IMPORTANT: Tokens are tiered - 'primitive' tokens are read-only reference values, 'semantic' tokens are the primary API for styling.",
167
+ description: "Get a specific design token by its path. Returns the value and CSS variable name.",
2103
168
  inputSchema: {
2104
169
  type: "object",
2105
170
  properties: {
2106
171
  path: {
2107
172
  type: "string",
2108
- description: "Token path in dot notation. Prefer semantic paths (colors.modes.light.bgPage) over primitive paths (colors.scales.green.500)"
2109
- },
2110
- tenantId: {
2111
- type: "string",
2112
- description: "Tenant ID for multi-tenant support (optional, defaults to 'default')"
173
+ description: "Token path in dot notation (e.g., 'colors.brand.primary', 'spacing.scale.md')"
2113
174
  }
2114
175
  },
2115
176
  required: ["path"]
@@ -2117,164 +178,81 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2117
178
  },
2118
179
  {
2119
180
  name: "listTokens",
2120
- description: "List tokens in a category. Use 'tier' filter to get only semantic tokens (recommended) or primitives (reference only). Semantic tokens are the primary API; primitives are read-only reference values.",
181
+ description: "List all tokens in a category (colors, typography, spacing, sizing, shadows, radius, borders, motion, zIndex).",
2121
182
  inputSchema: {
2122
183
  type: "object",
2123
184
  properties: {
2124
185
  category: {
2125
186
  type: "string",
2126
- enum: ["colors", "typography", "spacing", "sizing", "shadows", "radius", "motion", "zIndex", "borders"],
187
+ enum: TOKEN_CATEGORIES,
2127
188
  description: "Token category to list"
2128
189
  },
2129
190
  subcategory: {
2130
191
  type: "string",
2131
- description: "Optional subcategory, e.g., 'modes.light' for semantic colors, 'scale' for primitive spacing"
2132
- },
2133
- tier: {
2134
- type: "string",
2135
- enum: ["semantic", "primitive", "all"],
2136
- description: "Filter by token tier. 'semantic' (recommended) returns purpose-driven tokens for styling. 'primitive' returns raw reference values. Default: 'all'"
2137
- },
2138
- tenantId: {
2139
- type: "string",
2140
- description: "Tenant ID for multi-tenant support (optional, defaults to 'default')"
192
+ description: "Optional subcategory (e.g., 'brand' for colors, 'scale' for spacing)"
2141
193
  }
2142
194
  },
2143
195
  required: ["category"]
2144
196
  }
2145
197
  },
2146
198
  {
2147
- name: "getComponentTokens",
2148
- description: "Get all design tokens used by a specific component (Button, Card, Dialog, etc.)",
199
+ name: "searchTokens",
200
+ description: "Search for tokens by name or value.",
2149
201
  inputSchema: {
2150
202
  type: "object",
2151
203
  properties: {
2152
- component: {
2153
- type: "string",
2154
- enum: ["button", "card", "dialog", "input", "select", "heading", "checkbox", "radio", "toggle", "selectionControls"],
2155
- description: "Component name"
2156
- },
2157
- variant: {
2158
- type: "string",
2159
- description: "Optional variant name (e.g., 'primary', 'outline', 'ghost')"
2160
- },
2161
- size: {
2162
- type: "string",
2163
- description: "Optional size name (e.g., 'sm', 'md', 'lg')"
2164
- },
2165
- tenantId: {
204
+ query: {
2166
205
  type: "string",
2167
- description: "Tenant ID for multi-tenant support (optional, defaults to 'default')"
206
+ description: "Search query (matches token paths or values)"
2168
207
  }
2169
208
  },
2170
- required: ["component"]
209
+ required: ["query"]
2171
210
  }
2172
211
  },
2173
212
  {
2174
213
  name: "validateUsage",
2175
- description: "Check if a CSS value follows the Atomix design system. Detects arbitrary values that should use tokens.",
214
+ description: "Check if a CSS value follows the design system. Detects hardcoded values that should use tokens.",
2176
215
  inputSchema: {
2177
216
  type: "object",
2178
217
  properties: {
2179
218
  value: {
2180
219
  type: "string",
2181
- description: "CSS value to validate, e.g., '#ff0000', '16px', 'rgb(0,112,97)'"
220
+ description: "CSS value to validate (e.g., '#ff0000', '16px', 'rgb(0,112,97)')"
2182
221
  },
2183
222
  context: {
2184
223
  type: "string",
2185
224
  enum: ["color", "spacing", "radius", "shadow", "typography", "any"],
2186
- description: "Context of the value (helps find the right token)"
225
+ description: "Context of the value to help find the right token"
2187
226
  }
2188
227
  },
2189
228
  required: ["value"]
2190
229
  }
2191
230
  },
2192
- {
2193
- name: "generateCursorRules",
2194
- description: "[DEPRECATED] Use 'getAIToolRules' instead. Generate .cursorrules content for a project.",
2195
- inputSchema: {
2196
- type: "object",
2197
- properties: {
2198
- projectName: {
2199
- type: "string",
2200
- description: "Name of the project"
2201
- },
2202
- strict: {
2203
- type: "boolean",
2204
- description: "Whether to enforce strict token usage (no arbitrary values)"
2205
- }
2206
- },
2207
- required: []
2208
- }
2209
- },
2210
231
  {
2211
232
  name: "getAIToolRules",
2212
- description: "Generate design system rules for AI coding tools. Supports Cursor, GitHub Copilot, Windsurf, Cline, Continue, Zed, and more.",
233
+ description: "Generate design system rules for AI coding tools (Cursor, Copilot, Windsurf, etc.).",
2213
234
  inputSchema: {
2214
235
  type: "object",
2215
236
  properties: {
2216
237
  tool: {
2217
238
  type: "string",
2218
239
  enum: ["cursor", "copilot", "windsurf", "cline", "continue", "zed", "generic", "all"],
2219
- description: "AI tool to generate rules for. Use 'all' to generate for all supported tools."
2220
- },
2221
- projectName: {
2222
- type: "string",
2223
- description: "Name of the project (default: 'My Project')"
2224
- },
2225
- strict: {
2226
- type: "boolean",
2227
- description: "Enforce strict token usage - no arbitrary values allowed (default: true)"
240
+ description: "AI tool to generate rules for. Use 'all' to get rules for all tools."
2228
241
  }
2229
242
  },
2230
243
  required: ["tool"]
2231
244
  }
2232
245
  },
2233
- {
2234
- name: "listAITools",
2235
- description: "List all supported AI coding tools and their rules file conventions.",
2236
- inputSchema: {
2237
- type: "object",
2238
- properties: {},
2239
- required: []
2240
- }
2241
- },
2242
- {
2243
- name: "getAIToolSetupGuide",
2244
- description: "Get a complete setup guide for integrating Atomix with AI coding tools.",
2245
- inputSchema: {
2246
- type: "object",
2247
- properties: {
2248
- projectName: {
2249
- type: "string",
2250
- description: "Name of the project"
2251
- }
2252
- },
2253
- required: []
2254
- }
2255
- },
2256
246
  {
2257
247
  name: "exportMCPConfig",
2258
- description: "Generate MCP configuration file for AI tools. Creates ready-to-use config for Cursor (.cursor/mcp.json), Claude Desktop, Windsurf, Continue, or VS Code.",
248
+ description: "Generate MCP configuration file for AI tools.",
2259
249
  inputSchema: {
2260
250
  type: "object",
2261
251
  properties: {
2262
252
  tool: {
2263
253
  type: "string",
2264
254
  enum: ["cursor", "claude-desktop", "windsurf", "continue", "vscode", "all"],
2265
- description: "AI tool to generate MCP config for. Use 'all' to generate for all supported tools."
2266
- },
2267
- tenantId: {
2268
- type: "string",
2269
- description: "Tenant ID for multi-tenant setups (optional, defaults to 'default')"
2270
- },
2271
- projectName: {
2272
- type: "string",
2273
- description: "Project name for identification (optional)"
2274
- },
2275
- useNpx: {
2276
- type: "boolean",
2277
- description: "Use npx to run the server (default: true). Set to false for local development."
255
+ description: "AI tool to generate MCP config for."
2278
256
  }
2279
257
  },
2280
258
  required: ["tool"]
@@ -2282,7 +260,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2282
260
  },
2283
261
  {
2284
262
  name: "getSetupInstructions",
2285
- description: "Get detailed step-by-step setup instructions for a specific AI tool. Includes MCP configuration, rules file setup, and verification steps.",
263
+ description: "Get detailed setup instructions for a specific AI tool.",
2286
264
  inputSchema: {
2287
265
  type: "object",
2288
266
  properties: {
@@ -2294,681 +272,1251 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
2294
272
  },
2295
273
  required: ["tool"]
2296
274
  }
2297
- },
2298
- {
2299
- name: "searchTokens",
2300
- description: "Search for tokens by name or value. Results include tier metadata - prefer using 'semantic' tier tokens in code, use 'primitive' tier only for reference/validation.",
2301
- inputSchema: {
2302
- type: "object",
2303
- properties: {
2304
- query: {
2305
- type: "string",
2306
- description: "Search query (matches token paths or values)"
2307
- },
2308
- tier: {
2309
- type: "string",
2310
- enum: ["semantic", "primitive", "all"],
2311
- description: "Filter results by tier. 'semantic' recommended for styling. Default: 'all'"
2312
- }
2313
- },
2314
- required: ["query"]
2315
- }
2316
275
  }
2317
276
  ]
2318
277
  };
2319
278
  });
2320
279
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
2321
280
  const { name, arguments: args } = request.params;
2322
- switch (name) {
2323
- case "getToken": {
2324
- const path = args?.path;
2325
- const value = getTokenByPath(primitives2, path);
2326
- if (value === void 0) {
2327
- return {
2328
- content: [
2329
- {
281
+ try {
282
+ const data = await fetchDesignSystem();
283
+ switch (name) {
284
+ case "getToken": {
285
+ const path2 = args?.path;
286
+ const value = getTokenByPath(data.tokens, path2);
287
+ if (value === void 0) {
288
+ return {
289
+ content: [{
2330
290
  type: "text",
2331
291
  text: JSON.stringify({
2332
- error: `Token not found: ${path}`,
2333
- suggestion: `Use listTokens to see available tokens. Available categories: ${getTokenCategories().join(", ")}`
292
+ error: `Token not found: ${path2}`,
293
+ suggestion: "Use listTokens or searchTokens to find available tokens.",
294
+ availableCategories: TOKEN_CATEGORIES
2334
295
  }, null, 2)
2335
- }
2336
- ]
2337
- };
2338
- }
2339
- const cssVar = getCssVariableName(path);
2340
- const metadata = getTokenMetadata(path);
2341
- return {
2342
- content: [
2343
- {
296
+ }]
297
+ };
298
+ }
299
+ const cssVarKey = `--atmx-${path2.replace(/\./g, "-")}`;
300
+ const cssVar = data.cssVariables[cssVarKey];
301
+ return {
302
+ content: [{
2344
303
  type: "text",
2345
304
  text: JSON.stringify({
2346
- path,
305
+ path: path2,
2347
306
  value,
2348
- cssVariable: cssVar,
2349
- // Token tier classification
2350
- tier: metadata.tier,
2351
- mutable: metadata.mutable,
2352
- editVia: metadata.editVia || null,
2353
- guidance: metadata.guidance,
2354
- // Usage examples
2355
- usage: metadata.tier === "semantic" ? {
2356
- css: `var(${cssVar})`,
2357
- tailwind: getTailwindClass(path, value),
2358
- recommendation: "Use this token in your components."
2359
- } : {
2360
- css: `var(${cssVar})`,
2361
- tailwind: getTailwindClass(path, value),
2362
- recommendation: "This is a primitive (read-only reference). Consider using a semantic token instead.",
2363
- semanticAlternatives: getSuggestedSemanticTokens(path)
2364
- }
307
+ cssVariable: cssVar || `var(${cssVarKey})`,
308
+ usage: `style={{ property: "var(${cssVarKey})" }}`
2365
309
  }, null, 2)
2366
- }
2367
- ]
2368
- };
2369
- }
2370
- case "listTokens": {
2371
- const category = args?.category;
2372
- const subcategory = args?.subcategory;
2373
- const tierFilter = args?.tier || "all";
2374
- const rawTokens = listTokensInCategory(primitives2, category, subcategory);
2375
- const tokensWithMetadata = {};
2376
- for (const [tokenPath, value] of Object.entries(rawTokens)) {
2377
- const fullPath = subcategory ? `${category}.${subcategory}.${tokenPath}` : `${category}.${tokenPath}`;
2378
- const metadata = getTokenMetadata(fullPath);
2379
- if (tierFilter !== "all" && metadata.tier !== tierFilter) {
2380
- continue;
2381
- }
2382
- tokensWithMetadata[tokenPath] = {
2383
- value,
2384
- tier: metadata.tier,
2385
- mutable: metadata.mutable,
2386
- guidance: metadata.guidance
310
+ }]
2387
311
  };
2388
312
  }
2389
- return {
2390
- content: [
2391
- {
313
+ case "listTokens": {
314
+ const category = args?.category;
315
+ const subcategory = args?.subcategory;
316
+ let tokensToList = data.tokens[category];
317
+ if (subcategory && tokensToList && typeof tokensToList === "object") {
318
+ tokensToList = getTokenByPath(tokensToList, subcategory);
319
+ }
320
+ if (!tokensToList) {
321
+ return {
322
+ content: [{
323
+ type: "text",
324
+ text: JSON.stringify({
325
+ error: `Category not found: ${category}${subcategory ? `.${subcategory}` : ""}`,
326
+ availableCategories: TOKEN_CATEGORIES
327
+ }, null, 2)
328
+ }]
329
+ };
330
+ }
331
+ const flat = flattenTokens(tokensToList);
332
+ return {
333
+ content: [{
2392
334
  type: "text",
2393
335
  text: JSON.stringify({
2394
336
  category,
2395
- subcategory: subcategory || null,
2396
- tierFilter,
2397
- count: Object.keys(tokensWithMetadata).length,
2398
- note: tierFilter === "all" ? "Results include both primitive (read-only) and semantic (usable) tokens. Use tier='semantic' for styling recommendations." : tierFilter === "semantic" ? "Showing semantic tokens - these are the recommended tokens for styling." : "Showing primitive tokens - use these for reference/validation only, not direct styling.",
2399
- tokens: tokensWithMetadata
337
+ subcategory,
338
+ count: flat.length,
339
+ tokens: flat.slice(0, 50),
340
+ // Limit to 50 for readability
341
+ truncated: flat.length > 50
2400
342
  }, null, 2)
2401
- }
2402
- ]
2403
- };
2404
- }
2405
- case "getComponentTokens": {
2406
- const component = args?.component;
2407
- const variant = args?.variant;
2408
- const size = args?.size;
2409
- const componentKey = component.toLowerCase();
2410
- const componentData = COMPONENT_TOKENS[componentKey];
2411
- if (!componentData) {
2412
- return {
2413
- content: [
2414
- {
2415
- type: "text",
2416
- text: JSON.stringify({
2417
- error: `Component not found: ${component}`,
2418
- available: Object.keys(COMPONENT_TOKENS)
2419
- }, null, 2)
2420
- }
2421
- ]
343
+ }]
2422
344
  };
2423
345
  }
2424
- let result = { ...componentData };
2425
- if (variant && result.variants) {
2426
- result.variants = { [variant]: result.variants[variant] };
2427
- }
2428
- if (size && result.sizes) {
2429
- result.sizes = { [size]: result.sizes[size] };
2430
- }
2431
- return {
2432
- content: [
2433
- {
2434
- type: "text",
2435
- text: JSON.stringify(result, null, 2)
2436
- }
2437
- ]
2438
- };
2439
- }
2440
- case "validateUsage": {
2441
- const value = args?.value;
2442
- const context = args?.context || "any";
2443
- const validation = validateValue(value, context);
2444
- return {
2445
- content: [
2446
- {
2447
- type: "text",
2448
- text: JSON.stringify(validation, null, 2)
2449
- }
2450
- ]
2451
- };
2452
- }
2453
- case "generateCursorRules": {
2454
- const projectName = args?.projectName || "My Project";
2455
- const strict = args?.strict ?? true;
2456
- const rules = generateCursorRules(projectName, strict);
2457
- return {
2458
- content: [
2459
- {
2460
- type: "text",
2461
- text: `<!-- DEPRECATED: Use getAIToolRules instead -->
2462
- ${rules}`
2463
- }
2464
- ]
2465
- };
2466
- }
2467
- case "getAIToolRules": {
2468
- const tool = args?.tool;
2469
- const projectName = args?.projectName || "My Project";
2470
- const strict = args?.strict ?? true;
2471
- if (tool === "all") {
2472
- const allRules = generateRulesForAllTools(projectName, strict);
346
+ case "searchTokens": {
347
+ const query = args?.query;
348
+ const results = searchTokens(data.tokens, query);
2473
349
  return {
2474
- content: [
2475
- {
2476
- type: "text",
2477
- text: JSON.stringify({
2478
- message: `Generated rules for ${allRules.length} AI tools`,
2479
- tools: allRules.map((r) => ({
2480
- tool: r.tool.name,
2481
- filename: r.filename,
2482
- path: r.path,
2483
- description: r.tool.description
2484
- })),
2485
- files: allRules.map((r) => ({
2486
- path: r.path,
2487
- content: r.content
2488
- }))
2489
- }, null, 2)
2490
- }
2491
- ]
350
+ content: [{
351
+ type: "text",
352
+ text: JSON.stringify({
353
+ query,
354
+ count: results.length,
355
+ results: results.slice(0, 30),
356
+ truncated: results.length > 30
357
+ }, null, 2)
358
+ }]
2492
359
  };
2493
360
  }
2494
- try {
2495
- const result = generateRulesForTool(tool, projectName, strict);
2496
- return {
2497
- content: [
2498
- {
361
+ case "validateUsage": {
362
+ const value = args?.value;
363
+ const context = args?.context || "any";
364
+ const isHexColor = /^#[0-9A-Fa-f]{3,8}$/.test(value);
365
+ const isRgbColor = /^rgb\(|^rgba\(|^hsl\(/.test(value);
366
+ const isPixelValue = /^\d+px$/.test(value);
367
+ if (!isHexColor && !isRgbColor && !isPixelValue) {
368
+ return {
369
+ content: [{
2499
370
  type: "text",
2500
371
  text: JSON.stringify({
2501
- tool: result.tool.name,
2502
- filename: result.filename,
2503
- path: result.path,
2504
- description: result.tool.description,
2505
- content: result.content
372
+ value,
373
+ valid: true,
374
+ message: "Value appears to be using tokens or is not a design token value."
2506
375
  }, null, 2)
2507
- }
2508
- ]
2509
- };
2510
- } catch (error) {
2511
- return {
2512
- content: [
2513
- {
376
+ }]
377
+ };
378
+ }
379
+ const matches = searchTokens(data.tokens, value);
380
+ if (matches.length > 0) {
381
+ return {
382
+ content: [{
2514
383
  type: "text",
2515
384
  text: JSON.stringify({
2516
- error: String(error),
2517
- availableTools: Object.keys(AI_TOOLS)
385
+ value,
386
+ valid: false,
387
+ message: "Hardcoded value detected. Use a token instead.",
388
+ matchingTokens: matches.slice(0, 5),
389
+ suggestion: `Use var(--atmx-${matches[0].path.replace(/\./g, "-")}) instead of ${value}`
2518
390
  }, null, 2)
2519
- }
2520
- ]
2521
- };
2522
- }
2523
- }
2524
- case "listAITools": {
2525
- const tools = getSupportedAITools();
2526
- return {
2527
- content: [
2528
- {
391
+ }]
392
+ };
393
+ }
394
+ return {
395
+ content: [{
2529
396
  type: "text",
2530
397
  text: JSON.stringify({
2531
- count: tools.length,
2532
- tools: tools.map((t) => ({
2533
- id: t.id,
2534
- name: t.name,
2535
- rulesFile: t.rulesPath,
2536
- description: t.description
2537
- })),
2538
- note: "Use getAIToolRules({ tool: 'toolId' }) to generate rules for a specific tool."
398
+ value,
399
+ valid: false,
400
+ message: "Hardcoded value detected. No exact token match found.",
401
+ suggestion: `Consider adding this value to the design system or use the closest token.`,
402
+ context
2539
403
  }, null, 2)
404
+ }]
405
+ };
406
+ }
407
+ case "getAIToolRules": {
408
+ const tool = args?.tool;
409
+ const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=${tool === "all" ? "all" : tool}`;
410
+ console.error(`[getAIToolRules] Fetching: ${rulesUrl}`);
411
+ const headers = { "Content-Type": "application/json" };
412
+ if (apiKey) headers["x-api-key"] = apiKey;
413
+ try {
414
+ const response = await fetch(rulesUrl, { headers });
415
+ console.error(`[getAIToolRules] Response status: ${response.status}`);
416
+ if (!response.ok) {
417
+ const errorText = await response.text();
418
+ console.error(`[getAIToolRules] Error response: ${errorText}`);
419
+ throw new Error(`Failed to fetch rules: ${response.status} - ${errorText}`);
2540
420
  }
2541
- ]
2542
- };
2543
- }
2544
- case "getAIToolSetupGuide": {
2545
- const projectName = args?.projectName || "My Project";
2546
- const guide = generateToolSetupGuide(projectName);
2547
- return {
2548
- content: [
2549
- {
2550
- type: "text",
2551
- text: guide
421
+ const rules = await response.json();
422
+ console.error(`[getAIToolRules] Got ${rules.rules?.length || 0} rules`);
423
+ return {
424
+ content: [{
425
+ type: "text",
426
+ text: JSON.stringify(rules, null, 2)
427
+ }]
428
+ };
429
+ } catch (fetchError) {
430
+ console.error(`[getAIToolRules] Fetch error:`, fetchError);
431
+ throw fetchError;
432
+ }
433
+ }
434
+ case "exportMCPConfig": {
435
+ const tool = args?.tool;
436
+ const serverName = data.meta.name.toLowerCase().replace(/[^a-z0-9]/g, "-");
437
+ const npxArgs = ["@atomixstudio/mcp@latest"];
438
+ if (dsId) npxArgs.push("--ds-id", dsId);
439
+ if (apiKey) npxArgs.push("--api-key", apiKey);
440
+ const config = {
441
+ mcpServers: {
442
+ [serverName]: {
443
+ command: "npx",
444
+ args: npxArgs
445
+ }
2552
446
  }
2553
- ]
2554
- };
2555
- }
2556
- case "exportMCPConfig": {
2557
- const tool = args?.tool;
2558
- const tenantId = args?.tenantId || "default";
2559
- const projectName = args?.projectName || "my-project";
2560
- const useNpx = args?.useNpx ?? true;
2561
- const options = { tenantId, projectName, useNpx };
2562
- if (tool === "all") {
2563
- const allConfigs = generateAllMCPConfigs(options);
2564
- return {
2565
- content: [
2566
- {
447
+ };
448
+ const configs = {
449
+ cursor: { path: ".cursor/mcp.json", content: JSON.stringify(config, null, 2) },
450
+ "claude-desktop": { path: "claude_desktop_config.json", content: JSON.stringify(config, null, 2) },
451
+ windsurf: { path: ".windsurf/mcp.json", content: JSON.stringify(config, null, 2) },
452
+ continue: { path: ".continue/config.json", content: JSON.stringify({ models: [], mcpServers: [{ name: serverName, command: "npx", args: npxArgs }] }, null, 2) },
453
+ vscode: { path: ".vscode/settings.json", content: JSON.stringify({ "mcp.servers": config.mcpServers }, null, 2) }
454
+ };
455
+ if (tool === "all") {
456
+ return {
457
+ content: [{
2567
458
  type: "text",
2568
459
  text: JSON.stringify({
2569
- message: `Generated MCP configs for ${allConfigs.length} AI tools`,
2570
- configs: allConfigs.map((c) => ({
2571
- tool: c.tool,
2572
- path: c.path,
2573
- filename: c.filename
2574
- })),
2575
- files: allConfigs.map((c) => ({
2576
- tool: c.tool,
460
+ message: "MCP configurations for all tools",
461
+ configs: Object.entries(configs).map(([t, c]) => ({
462
+ tool: t,
2577
463
  path: c.path,
2578
- content: c.content,
2579
- instructions: c.instructions
464
+ content: JSON.parse(c.content)
2580
465
  }))
2581
466
  }, null, 2)
2582
- }
2583
- ]
2584
- };
2585
- }
2586
- try {
2587
- const config = generateMCPConfig(tool, options);
2588
- return {
2589
- content: [
2590
- {
467
+ }]
468
+ };
469
+ }
470
+ const selectedConfig = configs[tool];
471
+ if (!selectedConfig) {
472
+ return {
473
+ content: [{
2591
474
  type: "text",
2592
475
  text: JSON.stringify({
2593
- tool: config.tool,
2594
- path: config.path,
2595
- filename: config.filename,
2596
- content: config.content,
2597
- instructions: config.instructions
476
+ error: `Unknown tool: ${tool}`,
477
+ availableTools: Object.keys(configs)
2598
478
  }, null, 2)
2599
- }
2600
- ]
2601
- };
2602
- } catch (error) {
479
+ }]
480
+ };
481
+ }
2603
482
  return {
2604
- content: [
2605
- {
2606
- type: "text",
2607
- text: JSON.stringify({
2608
- error: String(error),
2609
- availableTools: ["cursor", "claude-desktop", "windsurf", "continue", "vscode", "all"]
2610
- }, null, 2)
2611
- }
2612
- ]
483
+ content: [{
484
+ type: "text",
485
+ text: JSON.stringify({
486
+ tool,
487
+ path: selectedConfig.path,
488
+ content: JSON.parse(selectedConfig.content),
489
+ instructions: `Create the file at ${selectedConfig.path} with the content above, then restart your IDE.`
490
+ }, null, 2)
491
+ }]
2613
492
  };
2614
493
  }
2615
- }
2616
- case "getSetupInstructions": {
2617
- const toolId = args?.tool;
2618
- try {
2619
- const instructions = getSetupInstructions(toolId);
2620
- return {
2621
- content: [
2622
- {
2623
- type: "text",
2624
- text: instructions
2625
- }
2626
- ]
494
+ case "getSetupInstructions": {
495
+ const tool = args?.tool;
496
+ const instructions = {
497
+ cursor: `# Cursor MCP Setup
498
+
499
+ 1. Create \`.cursor/mcp.json\` in your project root
500
+ 2. Add the MCP configuration (use exportMCPConfig to get it)
501
+ 3. Restart Cursor IDE
502
+ 4. Verify by asking: "What design tokens are available?"`,
503
+ copilot: `# GitHub Copilot Setup
504
+
505
+ 1. Create \`.github/copilot-instructions.md\` in your project
506
+ 2. Use getAIToolRules({ tool: "copilot" }) to get the content
507
+ 3. Enable custom instructions in VS Code settings:
508
+ "github.copilot.chat.codeGeneration.useInstructionFiles": true`,
509
+ windsurf: `# Windsurf Setup
510
+
511
+ 1. Create \`.windsurf/mcp.json\` in your project root
512
+ 2. Create \`.windsurfrules\` with rules from getAIToolRules
513
+ 3. Restart Windsurf Editor`,
514
+ cline: `# Cline Setup
515
+
516
+ 1. Create \`.clinerules\` in your project root
517
+ 2. Cline auto-detects MCP from .cursor/mcp.json`,
518
+ continue: `# Continue Setup
519
+
520
+ 1. Create/edit \`.continue/config.json\`
521
+ 2. Add mcpServers configuration
522
+ 3. Restart VS Code`,
523
+ zed: `# Zed Setup
524
+
525
+ 1. Create \`.zed/assistant/rules.md\` in your project
526
+ 2. Use getAIToolRules({ tool: "zed" }) for content`,
527
+ "claude-desktop": `# Claude Desktop Setup
528
+
529
+ 1. Find your Claude config:
530
+ - macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
531
+ - Windows: %APPDATA%\\Claude\\claude_desktop_config.json
532
+ 2. Add MCP server configuration
533
+ 3. Restart Claude Desktop`,
534
+ generic: `# Generic AI Tool Setup
535
+
536
+ 1. Create AI_GUIDELINES.md in your project root
537
+ 2. Use getAIToolRules({ tool: "generic" }) for content
538
+ 3. Reference in your prompts or context`
2627
539
  };
2628
- } catch (error) {
2629
- return {
2630
- content: [
2631
- {
540
+ const instruction = instructions[tool];
541
+ if (!instruction) {
542
+ return {
543
+ content: [{
2632
544
  type: "text",
2633
545
  text: JSON.stringify({
2634
- error: String(error),
2635
- availableTools: Object.keys(AI_TOOLS)
546
+ error: `Unknown tool: ${tool}`,
547
+ availableTools: Object.keys(instructions)
2636
548
  }, null, 2)
2637
- }
2638
- ]
2639
- };
2640
- }
2641
- }
2642
- case "searchTokens": {
2643
- const query = (args?.query).toLowerCase();
2644
- const tierFilter = args?.tier || "all";
2645
- const allTokens = flattenTokens(primitives2);
2646
- const matches = Object.entries(allTokens).filter(([path, value]) => {
2647
- const pathLower = path.toLowerCase();
2648
- const valueStr = String(value).toLowerCase();
2649
- const matchesQuery = pathLower.includes(query) || valueStr.includes(query);
2650
- if (!matchesQuery) return false;
2651
- if (tierFilter !== "all") {
2652
- const metadata = getTokenMetadata(path);
2653
- if (metadata.tier !== tierFilter) return false;
549
+ }]
550
+ };
2654
551
  }
2655
- return true;
2656
- }).slice(0, 50).map(([path, value]) => {
2657
- const metadata = getTokenMetadata(path);
2658
552
  return {
2659
- path,
2660
- value,
2661
- cssVariable: getCssVariableName(path),
2662
- tier: metadata.tier,
2663
- mutable: metadata.mutable,
2664
- guidance: metadata.guidance
553
+ content: [{
554
+ type: "text",
555
+ text: instruction
556
+ }]
2665
557
  };
2666
- });
2667
- matches.sort((a, b) => {
2668
- if (a.tier === "semantic" && b.tier !== "semantic") return -1;
2669
- if (a.tier !== "semantic" && b.tier === "semantic") return 1;
2670
- return 0;
2671
- });
2672
- return {
2673
- content: [
2674
- {
558
+ }
559
+ default:
560
+ return {
561
+ content: [{
2675
562
  type: "text",
2676
563
  text: JSON.stringify({
2677
- query,
2678
- tierFilter,
2679
- count: matches.length,
2680
- note: "Semantic tokens are listed first. Use semantic tokens for styling; primitives are for reference only.",
2681
- matches
564
+ error: `Unknown tool: ${name}`,
565
+ availableTools: ["getToken", "listTokens", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions"]
2682
566
  }, null, 2)
2683
- }
2684
- ]
2685
- };
567
+ }]
568
+ };
2686
569
  }
2687
- default:
2688
- return {
2689
- content: [
2690
- {
2691
- type: "text",
2692
- text: JSON.stringify({ error: `Unknown tool: ${name}` })
2693
- }
2694
- ]
2695
- };
570
+ } catch (error) {
571
+ return {
572
+ content: [{
573
+ type: "text",
574
+ text: JSON.stringify({
575
+ error: error instanceof Error ? error.message : "Unknown error",
576
+ suggestion: "Make sure --ds-id and --api-key are correct."
577
+ }, null, 2)
578
+ }],
579
+ isError: true
580
+ };
2696
581
  }
2697
582
  });
583
+ var AI_TOOLS = ["cursor", "copilot", "windsurf", "cline", "continue", "zed", "generic"];
2698
584
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
585
+ const data = await fetchDesignSystem();
586
+ const stats = getTokenStats(data);
587
+ const resources = [
588
+ {
589
+ uri: "atomix://welcome",
590
+ name: `Welcome to ${data.meta.name}`,
591
+ description: `Design system overview with ${stats.total} tokens and ${stats.governanceRules} governance rules`,
592
+ mimeType: "text/markdown"
593
+ },
594
+ ...AI_TOOLS.map((tool) => ({
595
+ uri: `atomix://rules/${tool}`,
596
+ name: `${tool.charAt(0).toUpperCase() + tool.slice(1)} Rules`,
597
+ description: `Design system rules file for ${tool}`,
598
+ mimeType: "text/markdown"
599
+ }))
600
+ ];
601
+ return { resources };
602
+ });
603
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
604
+ const { uri } = request.params;
605
+ const data = await fetchDesignSystem();
606
+ const stats = getTokenStats(data);
607
+ if (uri === "atomix://welcome") {
608
+ const welcome = generateWelcomeMessage(data, stats);
609
+ return {
610
+ contents: [{
611
+ uri,
612
+ mimeType: "text/markdown",
613
+ text: welcome
614
+ }]
615
+ };
616
+ }
617
+ const rulesMatch = uri.match(/^atomix:\/\/rules\/(.+)$/);
618
+ if (rulesMatch) {
619
+ const tool = rulesMatch[1];
620
+ if (!AI_TOOLS.includes(tool)) {
621
+ throw new Error(`Unknown tool: ${tool}. Available: ${AI_TOOLS.join(", ")}`);
622
+ }
623
+ const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=${tool}`;
624
+ const headers = { "Content-Type": "application/json" };
625
+ if (apiKey) headers["x-api-key"] = apiKey;
626
+ const response = await fetch(rulesUrl, { headers });
627
+ if (!response.ok) {
628
+ throw new Error(`Failed to fetch rules: ${response.status}`);
629
+ }
630
+ const rulesData = await response.json();
631
+ return {
632
+ contents: [{
633
+ uri,
634
+ mimeType: "text/markdown",
635
+ text: rulesData.content || JSON.stringify(rulesData, null, 2)
636
+ }]
637
+ };
638
+ }
639
+ throw new Error(`Unknown resource: ${uri}`);
640
+ });
641
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2699
642
  return {
2700
- resources: [
2701
- {
2702
- uri: "atomix://tokens/all",
2703
- name: "All Atomix Tokens",
2704
- description: "Complete design token reference",
2705
- mimeType: "application/json"
2706
- },
2707
- {
2708
- uri: "atomix://tokens/colors",
2709
- name: "Color Tokens",
2710
- description: "All color tokens (static, scales, modes)",
2711
- mimeType: "application/json"
2712
- },
2713
- {
2714
- uri: "atomix://tokens/typography",
2715
- name: "Typography Tokens",
2716
- description: "Font families, sizes, weights, line heights",
2717
- mimeType: "application/json"
2718
- },
643
+ prompts: [
2719
644
  {
2720
- uri: "atomix://tokens/spacing",
2721
- name: "Spacing Tokens",
2722
- description: "Spacing scale, insets, gaps",
2723
- mimeType: "application/json"
645
+ name: "welcome",
646
+ description: "Get started with this design system - shows overview, available tokens, and tools. Run this first!"
2724
647
  },
2725
648
  {
2726
- uri: "atomix://components",
2727
- name: "Component Tokens",
2728
- description: "Token mappings for all components",
2729
- mimeType: "application/json"
649
+ name: "design-system-rules",
650
+ description: "Get the design system governance rules for your AI coding tool",
651
+ arguments: [
652
+ {
653
+ name: "tool",
654
+ description: "AI tool to generate rules for (cursor, copilot, windsurf, cline, continue, zed, generic)",
655
+ required: false
656
+ }
657
+ ]
2730
658
  }
2731
659
  ]
2732
660
  };
2733
661
  });
2734
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
2735
- const uri = request.params.uri;
2736
- switch (uri) {
2737
- case "atomix://tokens/all":
2738
- return {
2739
- contents: [
2740
- {
2741
- uri,
2742
- mimeType: "application/json",
2743
- text: JSON.stringify(primitives2, null, 2)
2744
- }
2745
- ]
2746
- };
2747
- case "atomix://tokens/colors":
662
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
663
+ const { name, arguments: args } = request.params;
664
+ const data = await fetchDesignSystem();
665
+ const stats = getTokenStats(data);
666
+ switch (name) {
667
+ case "welcome": {
668
+ const welcome = generateWelcomeMessage(data, stats);
2748
669
  return {
2749
- contents: [
670
+ description: `Welcome to ${data.meta.name} Design System`,
671
+ messages: [
2750
672
  {
2751
- uri,
2752
- mimeType: "application/json",
2753
- text: JSON.stringify(primitives2.colors, null, 2)
2754
- }
2755
- ]
2756
- };
2757
- case "atomix://tokens/typography":
2758
- return {
2759
- contents: [
673
+ role: "user",
674
+ content: {
675
+ type: "text",
676
+ text: "Show me the design system overview and available tools."
677
+ }
678
+ },
2760
679
  {
2761
- uri,
2762
- mimeType: "application/json",
2763
- text: JSON.stringify(primitives2.typography, null, 2)
680
+ role: "assistant",
681
+ content: {
682
+ type: "text",
683
+ text: welcome
684
+ }
2764
685
  }
2765
686
  ]
2766
687
  };
2767
- case "atomix://tokens/spacing":
688
+ }
689
+ case "design-system-rules": {
690
+ const tool = args?.tool || "cursor";
691
+ const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=${tool}`;
692
+ const headers = { "Content-Type": "application/json" };
693
+ if (apiKey) headers["x-api-key"] = apiKey;
694
+ const response = await fetch(rulesUrl, { headers });
695
+ if (!response.ok) {
696
+ throw new Error(`Failed to fetch rules: ${response.status}`);
697
+ }
698
+ const rulesData = await response.json();
2768
699
  return {
2769
- contents: [
700
+ description: `Design system rules for ${tool}`,
701
+ messages: [
2770
702
  {
2771
- uri,
2772
- mimeType: "application/json",
2773
- text: JSON.stringify(primitives2.spacing, null, 2)
2774
- }
2775
- ]
2776
- };
2777
- case "atomix://components":
2778
- return {
2779
- contents: [
703
+ role: "user",
704
+ content: {
705
+ type: "text",
706
+ text: `Show me the design system rules for ${tool}.`
707
+ }
708
+ },
2780
709
  {
2781
- uri,
2782
- mimeType: "application/json",
2783
- text: JSON.stringify(COMPONENT_TOKENS, null, 2)
710
+ role: "assistant",
711
+ content: {
712
+ type: "text",
713
+ text: rulesData.content || JSON.stringify(rulesData, null, 2)
714
+ }
2784
715
  }
2785
716
  ]
2786
717
  };
718
+ }
2787
719
  default:
2788
- throw new Error(`Unknown resource: ${uri}`);
720
+ throw new Error(`Unknown prompt: ${name}`);
2789
721
  }
2790
722
  });
2791
- function getTailwindClass(path, value) {
2792
- if (path.startsWith("colors.static.brand.primary")) return "bg-brand text-brand";
2793
- if (path.startsWith("spacing.") && !path.includes(".inset")) {
2794
- const size = path.split(".").pop();
2795
- return `p-${size} m-${size} gap-${size}`;
2796
- }
2797
- if (path.startsWith("radius.")) {
2798
- const size = path.split(".").pop();
2799
- return `rounded-${size}`;
723
+ function generateWelcomeMessage(data, stats) {
724
+ const toolsList = [
725
+ "getToken - Get a specific token by path",
726
+ "listTokens - List all tokens in a category",
727
+ "searchTokens - Search tokens by name or value",
728
+ "validateUsage - Check if a CSS value follows the design system",
729
+ "getAIToolRules - Generate AI coding rules for any tool",
730
+ "exportMCPConfig - Generate MCP config for AI tools",
731
+ "getSetupInstructions - Get setup guide for specific tools"
732
+ ];
733
+ const categoryBreakdown = Object.entries(stats.byCategory).map(([cat, count]) => ` - ${cat}: ${count} tokens`).join("\n");
734
+ const asciiArt = `
735
+ \`\`\`
736
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
737
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
738
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
739
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
740
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
741
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
742
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
743
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
744
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
745
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
746
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
747
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198 \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
748
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198 \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
749
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198 \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
750
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198 \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
751
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
752
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
753
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
754
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
755
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
756
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
757
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
758
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
759
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
760
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
761
+ \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
762
+ \`\`\`
763
+ `;
764
+ return `${asciiArt}
765
+ # Welcome to ${data.meta.name}
766
+
767
+ Your design system is connected and ready to use.
768
+
769
+ ## Design System Overview
770
+
771
+ | Metric | Value |
772
+ |--------|-------|
773
+ | Name | ${data.meta.name} |
774
+ | DS ID | ${dsId} |
775
+ | Total Tokens | ${stats.total} |
776
+ | CSS Variables | ${stats.cssVariables} |
777
+ | Governance Rules | ${stats.governanceRules} |
778
+ | Version | ${data.meta.version || 1} |
779
+
780
+ ### Token Breakdown
781
+
782
+ ${categoryBreakdown}
783
+
784
+ ## Available Tools
785
+
786
+ ${toolsList.map((t, i) => `${i + 1}. **${t.split(" - ")[0]}** - ${t.split(" - ")[1]}`).join("\n")}
787
+
788
+ ## Quick Start
789
+
790
+ ### Get a Token
791
+ \`\`\`
792
+ Use getToken with path "colors.static.brand.primary"
793
+ \`\`\`
794
+
795
+ ### List All Spacing Tokens
796
+ \`\`\`
797
+ Use listTokens with category "spacing"
798
+ \`\`\`
799
+
800
+ ### Validate a Hardcoded Value
801
+ \`\`\`
802
+ Use validateUsage with value "#ff0000" and context "color"
803
+ \`\`\`
804
+
805
+ ## Resources Available
806
+
807
+ This MCP server exposes rules files as resources that your AI tool can read:
808
+
809
+ | Resource | Description |
810
+ |----------|-------------|
811
+ | \`atomix://welcome\` | This welcome message |
812
+ | \`atomix://rules/cursor\` | .cursorrules file content |
813
+ | \`atomix://rules/copilot\` | GitHub Copilot instructions |
814
+ | \`atomix://rules/windsurf\` | .windsurfrules file content |
815
+ | \`atomix://rules/cline\` | .clinerules file content |
816
+ | \`atomix://rules/continue\` | Continue rules |
817
+ | \`atomix://rules/zed\` | Zed assistant rules |
818
+ | \`atomix://rules/generic\` | Generic AI guidelines |
819
+
820
+ ## Checking for Updates
821
+
822
+ Your design system may be updated over time. To ensure you have the latest tokens:
823
+
824
+ 1. **Check version**: The current version is ${data.meta.version || 1}
825
+ 2. **Refresh tokens**: Restart the MCP server to fetch latest tokens
826
+ 3. **View changes**: Visit https://atomixstudio.eu/ds/${dsId} to see recent changes
827
+
828
+ ---
829
+
830
+ *Powered by Atomix Studio - https://atomixstudio.eu*
831
+ `;
832
+ }
833
+ function findConfig() {
834
+ const configNames = [".atomixrc", ".atomixrc.json", "atomix.config.json"];
835
+ const cwd = process.cwd();
836
+ for (const name of configNames) {
837
+ const configPath = path.join(cwd, name);
838
+ if (fs.existsSync(configPath)) {
839
+ try {
840
+ const content = fs.readFileSync(configPath, "utf-8");
841
+ return JSON.parse(content);
842
+ } catch {
843
+ console.error(`Error parsing ${name}`);
844
+ }
845
+ }
2800
846
  }
2801
847
  return null;
2802
848
  }
2803
- function getSuggestedSemanticTokens(primitivePath) {
2804
- const suggestions = [];
2805
- if (primitivePath.startsWith("colors.scales.")) {
2806
- suggestions.push(
2807
- "colors.modes.light.bgPage (page background)",
2808
- "colors.modes.light.bgSurface (card/panel background)",
2809
- "colors.modes.light.textPrimary (main text)",
2810
- "colors.modes.light.borderPrimary (borders)",
2811
- "colors.modes.light.actionPrimary (interactive elements)"
2812
- );
849
+ function generateCSSOutput(cssVariables) {
850
+ const lines = [
851
+ "/* Atomix Design System Tokens",
852
+ " * Auto-generated - do not edit manually",
853
+ ` * Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
854
+ " */",
855
+ "",
856
+ ":root {"
857
+ ];
858
+ for (const [key, value] of Object.entries(cssVariables)) {
859
+ lines.push(` ${key}: ${value};`);
2813
860
  }
2814
- if (primitivePath.startsWith("spacing.")) {
2815
- suggestions.push(
2816
- "Use spacing tokens (spacing.xs, spacing.sm, spacing.md, spacing.lg, etc.)",
2817
- "These resolve to CSS variables automatically"
2818
- );
861
+ lines.push("}");
862
+ lines.push("");
863
+ return lines.join("\n");
864
+ }
865
+ function generateJSONOutput(tokens) {
866
+ return JSON.stringify(tokens, null, 2);
867
+ }
868
+ function generateTSOutput(tokens) {
869
+ return `// Atomix Design System Tokens
870
+ // Auto-generated - do not edit manually
871
+ // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
872
+
873
+ export const tokens = ${JSON.stringify(tokens, null, 2)} as const;
874
+
875
+ export type Tokens = typeof tokens;
876
+ `;
877
+ }
878
+ function generateJSOutput(tokens) {
879
+ return `// Atomix Design System Tokens
880
+ // Auto-generated - do not edit manually
881
+ // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
882
+
883
+ export const tokens = ${JSON.stringify(tokens, null, 2)};
884
+ `;
885
+ }
886
+ function generateSCSSOutput(cssVariables) {
887
+ const lines = [
888
+ "// Atomix Design System Tokens",
889
+ "// Auto-generated - do not edit manually",
890
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
891
+ "",
892
+ "// CSS Custom Properties (for use in CSS/HTML)",
893
+ ":root {"
894
+ ];
895
+ for (const [key, value] of Object.entries(cssVariables)) {
896
+ lines.push(` ${key}: ${value};`);
2819
897
  }
2820
- if (primitivePath.startsWith("typography.fontSize.")) {
2821
- suggestions.push(
2822
- "typography.typeSets.title-lg (large titles)",
2823
- "typography.typeSets.title-md (medium titles)",
2824
- "typography.typeSets.text-normal-regular (body text)",
2825
- "typography.typeSets.label-md (form labels)"
2826
- );
898
+ lines.push("}");
899
+ lines.push("");
900
+ lines.push("// SCSS Variables (for use in SCSS)");
901
+ for (const [key, value] of Object.entries(cssVariables)) {
902
+ const scssVar = "$" + key.replace(/^--/, "");
903
+ lines.push(`${scssVar}: ${value};`);
2827
904
  }
2828
- if (primitivePath.startsWith("radius.")) {
2829
- suggestions.push(
2830
- "Use radius tokens (radius.sm, radius.md, radius.lg, radius.full, etc.)",
2831
- "Describe semantic usage in AI guidance (e.g., 'Buttons use @radius.md')"
2832
- );
905
+ lines.push("");
906
+ return lines.join("\n");
907
+ }
908
+ function generateLessOutput(cssVariables) {
909
+ const lines = [
910
+ "// Atomix Design System Tokens",
911
+ "// Auto-generated - do not edit manually",
912
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
913
+ "",
914
+ "// CSS Custom Properties (for use in CSS/HTML)",
915
+ ":root {"
916
+ ];
917
+ for (const [key, value] of Object.entries(cssVariables)) {
918
+ lines.push(` ${key}: ${value};`);
2833
919
  }
2834
- if (primitivePath.startsWith("shadows.elevation.")) {
2835
- suggestions.push(
2836
- "Use elevation keys (none, sm, md, lg, xl) in component defaults",
2837
- "These resolve to CSS variables automatically"
2838
- );
920
+ lines.push("}");
921
+ lines.push("");
922
+ lines.push("// Less Variables (for use in Less)");
923
+ for (const [key, value] of Object.entries(cssVariables)) {
924
+ const lessVar = "@" + key.replace(/^--/, "");
925
+ lines.push(`${lessVar}: ${value};`);
2839
926
  }
2840
- return suggestions;
927
+ lines.push("");
928
+ return lines.join("\n");
2841
929
  }
2842
- function validateValue(value, context) {
2843
- if (/^#[0-9A-Fa-f]{3,8}$/.test(value)) {
2844
- const allTokens = flattenTokens(primitives2);
2845
- const matches = Object.entries(allTokens).filter(([path, v]) => {
2846
- if (typeof v !== "string") return false;
2847
- return v.toLowerCase() === value.toLowerCase();
2848
- }).map(([path, v]) => ({
2849
- path,
2850
- value: v,
2851
- cssVariable: getCssVariableName(path)
2852
- }));
2853
- if (matches.length > 0) {
2854
- return {
2855
- valid: false,
2856
- issue: "Hardcoded hex color detected",
2857
- suggestion: `Use token instead: ${matches[0].cssVariable}`,
2858
- matchingTokens: matches
2859
- };
930
+ function toSwiftName(cssVar) {
931
+ return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
932
+ }
933
+ function toKotlinName(cssVar) {
934
+ const camel = toSwiftName(cssVar);
935
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
936
+ }
937
+ function toDartName(cssVar) {
938
+ return toSwiftName(cssVar);
939
+ }
940
+ function isColorValue(value) {
941
+ return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
942
+ }
943
+ function hexToSwiftColor(hex) {
944
+ const clean = hex.replace("#", "");
945
+ if (clean.length === 3) {
946
+ const r = clean[0], g = clean[1], b = clean[2];
947
+ return `Color(hex: 0x${r}${r}${g}${g}${b}${b})`;
948
+ }
949
+ if (clean.length === 6) {
950
+ return `Color(hex: 0x${clean})`;
951
+ }
952
+ if (clean.length === 8) {
953
+ const rgb = clean.substring(0, 6);
954
+ const alpha = parseInt(clean.substring(6, 8), 16) / 255;
955
+ return `Color(hex: 0x${rgb}).opacity(${alpha.toFixed(2)})`;
956
+ }
957
+ return `Color.clear // Could not parse: ${hex}`;
958
+ }
959
+ function hexToKotlinColor(hex) {
960
+ const clean = hex.replace("#", "").toUpperCase();
961
+ if (clean.length === 3) {
962
+ const r = clean[0], g = clean[1], b = clean[2];
963
+ return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
964
+ }
965
+ if (clean.length === 6) {
966
+ return `Color(0xFF${clean})`;
967
+ }
968
+ if (clean.length === 8) {
969
+ const rgb = clean.substring(0, 6);
970
+ const alpha = clean.substring(6, 8);
971
+ return `Color(0x${alpha}${rgb})`;
972
+ }
973
+ return `Color.Transparent // Could not parse: ${hex}`;
974
+ }
975
+ function hexToDartColor(hex) {
976
+ const clean = hex.replace("#", "").toUpperCase();
977
+ if (clean.length === 3) {
978
+ const r = clean[0], g = clean[1], b = clean[2];
979
+ return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
980
+ }
981
+ if (clean.length === 6) {
982
+ return `Color(0xFF${clean})`;
983
+ }
984
+ if (clean.length === 8) {
985
+ const rgb = clean.substring(0, 6);
986
+ const alpha = clean.substring(6, 8);
987
+ return `Color(0x${alpha}${rgb})`;
988
+ }
989
+ return `Colors.transparent // Could not parse: ${hex}`;
990
+ }
991
+ function generateSwiftOutput(cssVariables) {
992
+ const lines = [
993
+ "// Atomix Design System Tokens",
994
+ "// Auto-generated - do not edit manually",
995
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
996
+ "",
997
+ "import SwiftUI",
998
+ "",
999
+ "// MARK: - Color Extension for Hex",
1000
+ "extension Color {",
1001
+ " init(hex: UInt, alpha: Double = 1.0) {",
1002
+ " self.init(",
1003
+ " .sRGB,",
1004
+ " red: Double((hex >> 16) & 0xFF) / 255.0,",
1005
+ " green: Double((hex >> 8) & 0xFF) / 255.0,",
1006
+ " blue: Double(hex & 0xFF) / 255.0,",
1007
+ " opacity: alpha",
1008
+ " )",
1009
+ " }",
1010
+ "}",
1011
+ "",
1012
+ "// MARK: - Design Tokens",
1013
+ "enum DesignTokens {",
1014
+ "",
1015
+ " // MARK: Colors",
1016
+ " enum Colors {"
1017
+ ];
1018
+ const colors = [];
1019
+ const spacing = [];
1020
+ const typography = [];
1021
+ const other = [];
1022
+ for (const [key, value] of Object.entries(cssVariables)) {
1023
+ if (key.includes("-color-")) colors.push([key, value]);
1024
+ else if (key.includes("-spacing-")) spacing.push([key, value]);
1025
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value]);
1026
+ else other.push([key, value]);
1027
+ }
1028
+ for (const [key, value] of colors) {
1029
+ const name = toSwiftName(key);
1030
+ if (isColorValue(value)) {
1031
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
2860
1032
  }
2861
- return {
2862
- valid: false,
2863
- issue: "Hardcoded hex color with no matching token",
2864
- suggestion: "Check colors.static, colors.scales, or colors.modes for appropriate tokens"
2865
- };
2866
1033
  }
2867
- if (/^(rgb|rgba|hsl|hsla)\(/.test(value)) {
2868
- return {
2869
- valid: false,
2870
- issue: "Hardcoded color function detected",
2871
- suggestion: "Use CSS variable: var(--atomix-colors-*) or primitives.colors.*"
2872
- };
1034
+ lines.push(" }");
1035
+ lines.push("");
1036
+ lines.push(" // MARK: Spacing");
1037
+ lines.push(" enum Spacing {");
1038
+ for (const [key, value] of spacing) {
1039
+ const name = toSwiftName(key);
1040
+ const numValue = parseFloat(value);
1041
+ if (!isNaN(numValue)) {
1042
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
1043
+ }
2873
1044
  }
2874
- if (context === "spacing" && /^\d+px$/.test(value)) {
2875
- return {
2876
- valid: false,
2877
- issue: "Hardcoded pixel value for spacing",
2878
- suggestion: "Use spacing.* tokens (e.g., @spacing.md, @spacing.lg)"
2879
- };
1045
+ lines.push(" }");
1046
+ lines.push("");
1047
+ lines.push(" // MARK: Typography");
1048
+ lines.push(" enum Typography {");
1049
+ for (const [key, value] of typography) {
1050
+ const name = toSwiftName(key);
1051
+ if (key.includes("size")) {
1052
+ const numValue = parseFloat(value);
1053
+ if (!isNaN(numValue)) {
1054
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
1055
+ }
1056
+ } else if (key.includes("weight")) {
1057
+ lines.push(` static let ${name} = "${value}"`);
1058
+ } else if (key.includes("family")) {
1059
+ lines.push(` static let ${name} = "${value}"`);
1060
+ }
2880
1061
  }
2881
- if (/^\d+px$/.test(value)) {
2882
- return {
2883
- valid: false,
2884
- issue: "Hardcoded pixel value detected",
2885
- suggestion: "Consider using design tokens from spacing, sizing, or typography"
2886
- };
1062
+ lines.push(" }");
1063
+ lines.push("");
1064
+ lines.push(" // MARK: Other");
1065
+ lines.push(" enum Other {");
1066
+ for (const [key, value] of other) {
1067
+ const name = toSwiftName(key);
1068
+ if (isColorValue(value)) {
1069
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
1070
+ } else {
1071
+ const numValue = parseFloat(value);
1072
+ if (!isNaN(numValue)) {
1073
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
1074
+ } else {
1075
+ lines.push(` static let ${name} = "${value}"`);
1076
+ }
1077
+ }
2887
1078
  }
2888
- if (/\[.*\]/.test(value)) {
2889
- return {
2890
- valid: false,
2891
- issue: "Tailwind arbitrary value detected",
2892
- suggestion: "Replace with token-based class or CSS variable"
2893
- };
1079
+ lines.push(" }");
1080
+ lines.push("}");
1081
+ lines.push("");
1082
+ return lines.join("\n");
1083
+ }
1084
+ function generateKotlinOutput(cssVariables) {
1085
+ const lines = [
1086
+ "// Atomix Design System Tokens",
1087
+ "// Auto-generated - do not edit manually",
1088
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
1089
+ "",
1090
+ "package com.atomix.design",
1091
+ "",
1092
+ "import androidx.compose.ui.graphics.Color",
1093
+ "import androidx.compose.ui.unit.dp",
1094
+ "import androidx.compose.ui.unit.sp",
1095
+ "",
1096
+ "object DesignTokens {",
1097
+ "",
1098
+ " object Colors {"
1099
+ ];
1100
+ const colors = [];
1101
+ const spacing = [];
1102
+ const typography = [];
1103
+ const other = [];
1104
+ for (const [key, value] of Object.entries(cssVariables)) {
1105
+ if (key.includes("-color-")) colors.push([key, value]);
1106
+ else if (key.includes("-spacing-")) spacing.push([key, value]);
1107
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value]);
1108
+ else other.push([key, value]);
1109
+ }
1110
+ for (const [key, value] of colors) {
1111
+ const name = toKotlinName(key);
1112
+ if (isColorValue(value)) {
1113
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
1114
+ }
1115
+ }
1116
+ lines.push(" }");
1117
+ lines.push("");
1118
+ lines.push(" object Spacing {");
1119
+ for (const [key, value] of spacing) {
1120
+ const name = toKotlinName(key);
1121
+ const numValue = parseFloat(value);
1122
+ if (!isNaN(numValue)) {
1123
+ lines.push(` val ${name} = ${numValue}.dp`);
1124
+ }
1125
+ }
1126
+ lines.push(" }");
1127
+ lines.push("");
1128
+ lines.push(" object Typography {");
1129
+ for (const [key, value] of typography) {
1130
+ const name = toKotlinName(key);
1131
+ if (key.includes("size")) {
1132
+ const numValue = parseFloat(value);
1133
+ if (!isNaN(numValue)) {
1134
+ lines.push(` val ${name} = ${numValue}.sp`);
1135
+ }
1136
+ } else {
1137
+ lines.push(` const val ${name} = "${value}"`);
1138
+ }
1139
+ }
1140
+ lines.push(" }");
1141
+ lines.push("");
1142
+ lines.push(" object Other {");
1143
+ for (const [key, value] of other) {
1144
+ const name = toKotlinName(key);
1145
+ if (isColorValue(value)) {
1146
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
1147
+ } else {
1148
+ const numValue = parseFloat(value);
1149
+ if (!isNaN(numValue)) {
1150
+ lines.push(` val ${name} = ${numValue}.dp`);
1151
+ } else {
1152
+ lines.push(` const val ${name} = "${value}"`);
1153
+ }
1154
+ }
2894
1155
  }
2895
- return { valid: true };
1156
+ lines.push(" }");
1157
+ lines.push("}");
1158
+ lines.push("");
1159
+ return lines.join("\n");
2896
1160
  }
2897
- var primitives2 = {};
2898
- var userGovernance;
2899
- var isUserMode = false;
2900
- var userDsId;
2901
- async function main() {
2902
- const cliArgs = parseCLIArgs();
2903
- if (isUserDSMode(cliArgs)) {
2904
- console.error(`[atomix-mcp] User DS mode: loading design system ${cliArgs.dsId}`);
2905
- isUserMode = true;
2906
- userDsId = cliArgs.dsId;
2907
- const result = await fetchUserDesignSystem({
2908
- dsId: cliArgs.dsId,
2909
- apiKey: cliArgs.apiKey,
2910
- baseUrl: cliArgs.baseUrl
2911
- });
2912
- if (!result.success || !result.tokens) {
2913
- console.error(`[atomix-mcp] Failed to load user DS: ${result.error}`);
2914
- console.error("[atomix-mcp] Falling back to Atomix internal tokens");
2915
- primitives2 = await loadPrimitives();
1161
+ function generateDartOutput(cssVariables) {
1162
+ const lines = [
1163
+ "// Atomix Design System Tokens",
1164
+ "// Auto-generated - do not edit manually",
1165
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
1166
+ "",
1167
+ "import 'package:flutter/material.dart';",
1168
+ "",
1169
+ "class DesignTokens {",
1170
+ " DesignTokens._();",
1171
+ "",
1172
+ " // Colors"
1173
+ ];
1174
+ const colors = [];
1175
+ const spacing = [];
1176
+ const typography = [];
1177
+ const other = [];
1178
+ for (const [key, value] of Object.entries(cssVariables)) {
1179
+ if (key.includes("-color-")) colors.push([key, value]);
1180
+ else if (key.includes("-spacing-")) spacing.push([key, value]);
1181
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value]);
1182
+ else other.push([key, value]);
1183
+ }
1184
+ for (const [key, value] of colors) {
1185
+ const name = toDartName(key);
1186
+ if (isColorValue(value)) {
1187
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
1188
+ }
1189
+ }
1190
+ lines.push("");
1191
+ lines.push(" // Spacing");
1192
+ for (const [key, value] of spacing) {
1193
+ const name = toDartName(key);
1194
+ const numValue = parseFloat(value);
1195
+ if (!isNaN(numValue)) {
1196
+ lines.push(` static const double ${name} = ${numValue};`);
1197
+ }
1198
+ }
1199
+ lines.push("");
1200
+ lines.push(" // Typography");
1201
+ for (const [key, value] of typography) {
1202
+ const name = toDartName(key);
1203
+ if (key.includes("size")) {
1204
+ const numValue = parseFloat(value);
1205
+ if (!isNaN(numValue)) {
1206
+ lines.push(` static const double ${name} = ${numValue};`);
1207
+ }
1208
+ } else {
1209
+ lines.push(` static const String ${name} = '${value}';`);
1210
+ }
1211
+ }
1212
+ lines.push("");
1213
+ lines.push(" // Other");
1214
+ for (const [key, value] of other) {
1215
+ const name = toDartName(key);
1216
+ if (isColorValue(value)) {
1217
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
2916
1218
  } else {
2917
- primitives2 = transformUserTokens(result.tokens);
2918
- userGovernance = result.governance;
2919
- const meta = result.meta;
2920
- const dsName = meta?.name || cliArgs.dsId;
2921
- const tokenCategories = Object.keys(primitives2);
2922
- const rulesCount = userGovernance?.rules?.length || 0;
2923
- console.error("");
2924
- console.error(`[atomix-mcp] ========================================`);
2925
- console.error(`[atomix-mcp] Design System: ${dsName}`);
2926
- console.error(`[atomix-mcp] ----------------------------------------`);
2927
- if (meta?.publishedAt) {
2928
- const publishedDate = new Date(meta.publishedAt);
2929
- const timeAgo = formatTimeAgo(meta.publishedAt);
2930
- console.error(`[atomix-mcp] Published: ${timeAgo}`);
2931
- console.error(`[atomix-mcp] (${publishedDate.toLocaleString()})`);
1219
+ const numValue = parseFloat(value);
1220
+ if (!isNaN(numValue)) {
1221
+ lines.push(` static const double ${name} = ${numValue};`);
1222
+ } else {
1223
+ lines.push(` static const String ${name} = '${value}';`);
1224
+ }
1225
+ }
1226
+ }
1227
+ lines.push("}");
1228
+ lines.push("");
1229
+ return lines.join("\n");
1230
+ }
1231
+ function diffTokens(oldContent, newCssVars, format) {
1232
+ const added = [];
1233
+ const modified = [];
1234
+ const removed = [];
1235
+ if (format === "css" || format === "scss" || format === "less") {
1236
+ const oldVars = {};
1237
+ const varRegex = /(--[\w-]+):\s*([^;]+);/g;
1238
+ let match;
1239
+ while ((match = varRegex.exec(oldContent)) !== null) {
1240
+ oldVars[match[1]] = match[2].trim();
1241
+ }
1242
+ for (const [key, value] of Object.entries(newCssVars)) {
1243
+ if (!(key in oldVars)) {
1244
+ added.push(key);
1245
+ } else if (oldVars[key] !== value) {
1246
+ modified.push({ key, old: oldVars[key], new: value });
1247
+ }
1248
+ }
1249
+ for (const key of Object.keys(oldVars)) {
1250
+ if (!(key in newCssVars)) {
1251
+ removed.push(key);
1252
+ }
1253
+ }
1254
+ }
1255
+ return { added, modified, removed };
1256
+ }
1257
+ async function promptConfirm(message) {
1258
+ const rl = readline.createInterface({
1259
+ input: process.stdin,
1260
+ output: process.stdout
1261
+ });
1262
+ return new Promise((resolve2) => {
1263
+ rl.question(`${message} [Y/n] `, (answer) => {
1264
+ rl.close();
1265
+ const normalized = answer.toLowerCase().trim();
1266
+ resolve2(normalized === "" || normalized === "y" || normalized === "yes");
1267
+ });
1268
+ });
1269
+ }
1270
+ async function runSync() {
1271
+ console.log("");
1272
+ console.log(" \u2198\u2198\u2198 Atomix Token Sync");
1273
+ console.log("");
1274
+ const config = findConfig();
1275
+ const effectiveDsId = cliArgs.dsId || config?.dsId;
1276
+ const effectiveApiKey = cliArgs.apiKey || config?.apiKey;
1277
+ const effectiveApiBase = cliArgs.apiBase || config?.apiBase || "https://atomixstudio.eu";
1278
+ const effectiveOutput = cliArgs.output || config?.output || "./tokens.css";
1279
+ const effectiveFormat = cliArgs.format || config?.format || "css";
1280
+ if (!effectiveDsId) {
1281
+ console.error(" Error: Missing design system ID");
1282
+ console.error("");
1283
+ console.error(" Either:");
1284
+ console.error(" 1. Run: npx atomix sync --ds-id <your-ds-id>");
1285
+ console.error(' 2. Create .atomixrc with { "dsId": "<your-ds-id>" }');
1286
+ console.error(" 3. Run: npx atomix init");
1287
+ console.error("");
1288
+ process.exit(1);
1289
+ }
1290
+ console.log(` Fetching tokens from ${effectiveApiBase}...`);
1291
+ const url = `${effectiveApiBase}/api/ds/${effectiveDsId}/tokens?format=export`;
1292
+ const headers = { "Content-Type": "application/json" };
1293
+ if (effectiveApiKey) headers["x-api-key"] = effectiveApiKey;
1294
+ let data;
1295
+ try {
1296
+ const response = await fetch(url, { headers });
1297
+ if (!response.ok) {
1298
+ const errorText = await response.text();
1299
+ console.error(` Error: Failed to fetch tokens (${response.status})`);
1300
+ console.error(` ${errorText}`);
1301
+ process.exit(1);
1302
+ }
1303
+ data = await response.json();
1304
+ } catch (error) {
1305
+ console.error(` Error: Could not connect to ${effectiveApiBase}`);
1306
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
1307
+ process.exit(1);
1308
+ }
1309
+ console.log(` Design System: ${data.meta.name} (v${data.meta.version})`);
1310
+ console.log("");
1311
+ let newContent;
1312
+ switch (effectiveFormat) {
1313
+ case "css":
1314
+ newContent = generateCSSOutput(data.cssVariables);
1315
+ break;
1316
+ case "scss":
1317
+ newContent = generateSCSSOutput(data.cssVariables);
1318
+ break;
1319
+ case "less":
1320
+ newContent = generateLessOutput(data.cssVariables);
1321
+ break;
1322
+ case "json":
1323
+ newContent = generateJSONOutput(data.tokens);
1324
+ break;
1325
+ case "js":
1326
+ newContent = generateJSOutput(data.tokens);
1327
+ break;
1328
+ case "ts":
1329
+ newContent = generateTSOutput(data.tokens);
1330
+ break;
1331
+ case "swift":
1332
+ newContent = generateSwiftOutput(data.cssVariables);
1333
+ break;
1334
+ case "kotlin":
1335
+ newContent = generateKotlinOutput(data.cssVariables);
1336
+ break;
1337
+ case "dart":
1338
+ newContent = generateDartOutput(data.cssVariables);
1339
+ break;
1340
+ default:
1341
+ newContent = generateCSSOutput(data.cssVariables);
1342
+ break;
1343
+ }
1344
+ const outputPath = path.resolve(process.cwd(), effectiveOutput);
1345
+ const fileExists = fs.existsSync(outputPath);
1346
+ const supportsDiff = ["css", "scss", "less"].includes(effectiveFormat);
1347
+ if (fileExists && supportsDiff) {
1348
+ const oldContent = fs.readFileSync(outputPath, "utf-8");
1349
+ const diff = diffTokens(oldContent, data.cssVariables, effectiveFormat);
1350
+ const totalChanges = diff.added.length + diff.modified.length + diff.removed.length;
1351
+ if (totalChanges === 0) {
1352
+ console.log(" \u2713 Already up to date!");
1353
+ console.log("");
1354
+ process.exit(0);
1355
+ }
1356
+ console.log(` Changes detected in ${path.basename(effectiveOutput)}:`);
1357
+ console.log("");
1358
+ if (diff.modified.length > 0) {
1359
+ for (const { key, old: oldVal, new: newVal } of diff.modified) {
1360
+ console.log(` ${key}`);
1361
+ console.log(` - ${oldVal}`);
1362
+ console.log(` + ${newVal}`);
1363
+ }
1364
+ }
1365
+ if (diff.added.length > 0) {
1366
+ console.log(` + ${diff.added.length} new token(s)`);
1367
+ }
1368
+ if (diff.removed.length > 0) {
1369
+ console.log(` - ${diff.removed.length} removed token(s)`);
1370
+ }
1371
+ console.log("");
1372
+ console.log(` Total: ${totalChanges} change(s)`);
1373
+ console.log("");
1374
+ if (!cliArgs.yes) {
1375
+ const confirmed = await promptConfirm(" Apply changes?");
1376
+ if (!confirmed) {
1377
+ console.log(" Cancelled.");
1378
+ process.exit(0);
2932
1379
  }
2933
- if (meta?.version) {
2934
- console.error(`[atomix-mcp] Version: ${meta.version}`);
1380
+ }
1381
+ } else if (!fileExists) {
1382
+ console.log(` Creating ${effectiveOutput}...`);
1383
+ console.log(` ${Object.keys(data.cssVariables).length} tokens`);
1384
+ console.log("");
1385
+ if (!cliArgs.yes) {
1386
+ const confirmed = await promptConfirm(" Create file?");
1387
+ if (!confirmed) {
1388
+ console.log(" Cancelled.");
1389
+ process.exit(0);
2935
1390
  }
2936
- console.error(`[atomix-mcp] ----------------------------------------`);
2937
- console.error(`[atomix-mcp] Tokens: ${tokenCategories.length} categories`);
2938
- console.error(`[atomix-mcp] ${tokenCategories.join(", ")}`);
2939
- console.error(`[atomix-mcp] Rules: ${rulesCount} governance rules`);
2940
- console.error(`[atomix-mcp] ========================================`);
2941
- console.error("");
2942
1391
  }
2943
- } else {
2944
- console.error("[atomix-mcp] Loading Atomix internal primitives...");
2945
- primitives2 = await loadPrimitives();
2946
- console.error(`[atomix-mcp] Loaded ${Object.keys(primitives2).length} token categories`);
1392
+ }
1393
+ const outputDir = path.dirname(outputPath);
1394
+ if (!fs.existsSync(outputDir)) {
1395
+ fs.mkdirSync(outputDir, { recursive: true });
1396
+ }
1397
+ fs.writeFileSync(outputPath, newContent);
1398
+ console.log("");
1399
+ console.log(` \u2713 Updated ${effectiveOutput}`);
1400
+ console.log("");
1401
+ }
1402
+ async function runInit() {
1403
+ console.log("");
1404
+ console.log(" \u2198\u2198\u2198 Atomix Config Setup");
1405
+ console.log("");
1406
+ const rl = readline.createInterface({
1407
+ input: process.stdin,
1408
+ output: process.stdout
1409
+ });
1410
+ const question = (prompt) => new Promise((resolve2) => rl.question(prompt, resolve2));
1411
+ const dsId2 = cliArgs.dsId || await question(" Design System ID: ");
1412
+ const output = await question(" Output file [./tokens.css]: ") || "./tokens.css";
1413
+ const format = await question(" Format (css/json/ts) [css]: ") || "css";
1414
+ rl.close();
1415
+ const config = {
1416
+ dsId: dsId2,
1417
+ output,
1418
+ format
1419
+ };
1420
+ const configPath = path.join(process.cwd(), ".atomixrc");
1421
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
1422
+ console.log("");
1423
+ console.log(` \u2713 Created .atomixrc`);
1424
+ console.log("");
1425
+ console.log(" Now run: npx atomix sync");
1426
+ console.log("");
1427
+ }
1428
+ function showHelp() {
1429
+ console.log(`
1430
+ \u2198\u2198\u2198 Atomix CLI
1431
+
1432
+ COMMANDS
1433
+ sync Sync design tokens to your project
1434
+ init Create .atomixrc config file
1435
+ help Show this help message
1436
+ (none) Start MCP server for AI tools
1437
+
1438
+ SYNC OPTIONS
1439
+ --ds-id Design system ID (or set in .atomixrc)
1440
+ --api-key API key for private design systems
1441
+ --output, -o Output file path [./tokens.css]
1442
+ --format Output format [css]
1443
+ WEB:
1444
+ css - CSS custom properties (:root { --var: value })
1445
+ scss - CSS vars + SCSS variables ($var: value)
1446
+ less - CSS vars + Less variables (@var: value)
1447
+ json - Raw token JSON
1448
+ ts - TypeScript with types
1449
+ js - JavaScript ES module
1450
+ NATIVE:
1451
+ swift - SwiftUI (iOS/macOS)
1452
+ kotlin - Jetpack Compose (Android)
1453
+ dart - Flutter
1454
+ --exclude Glob pattern to exclude (can use multiple times)
1455
+ -y, --yes Auto-confirm changes
1456
+
1457
+ MCP SERVER OPTIONS
1458
+ --ds-id Design system ID (required)
1459
+ --api-key API key for private design systems
1460
+ --api-base API base URL [https://atomixstudio.eu]
1461
+
1462
+ EXAMPLES
1463
+ npx atomix sync
1464
+ npx atomix sync --ds-id abc123 -o ./src/tokens.css
1465
+ npx atomix sync --format scss -o ./src/styles/_tokens.scss
1466
+ npx atomix sync --format ts -o ./src/design-tokens.ts
1467
+ npx atomix sync --format swift -o ./Sources/DesignTokens.swift
1468
+ npx atomix sync --format kotlin -o ./app/src/.../DesignTokens.kt
1469
+ npx atomix sync --format dart -o ./lib/design_tokens.dart
1470
+ npx atomix init
1471
+ npx atomix --ds-id abc123 (start MCP server)
1472
+
1473
+ CONFIG FILE (.atomixrc)
1474
+ {
1475
+ "dsId": "your-design-system-id",
1476
+ "output": "./src/styles/tokens.css",
1477
+ "format": "css",
1478
+ "exclude": ["legacy/**", "vendor/**"]
1479
+ }
1480
+
1481
+ DEFAULT SCANNED FILES
1482
+ *.tsx, *.jsx, *.ts, *.js, *.css, *.scss, *.less, *.vue, *.svelte
1483
+
1484
+ DEFAULT EXCLUDED
1485
+ node_modules/**, dist/**, build/**, .next/**, *.min.*, *.d.ts
1486
+ `);
1487
+ }
1488
+ async function startServer() {
1489
+ if (!dsId) {
1490
+ console.error("Error: Missing --ds-id argument");
1491
+ console.error("Usage: npx atomix --ds-id <id> --api-key <key>");
1492
+ console.error("");
1493
+ console.error("For sync command: npx atomix sync --help");
1494
+ console.error("Get your DS ID from https://atomixstudio.eu/ds/[your-ds-id]");
1495
+ process.exit(1);
2947
1496
  }
2948
1497
  const transport = new StdioServerTransport();
2949
1498
  await server.connect(transport);
2950
- console.error("[atomix-mcp] Atomix MCP server started");
2951
- if (isUserMode) {
2952
- console.error(`[atomix-mcp] Serving design system: ${userDsId}`);
1499
+ console.error(`Atomix MCP Server started for design system: ${dsId}`);
1500
+ }
1501
+ async function main() {
1502
+ switch (cliArgs.command) {
1503
+ case "sync":
1504
+ await runSync();
1505
+ break;
1506
+ case "init":
1507
+ await runInit();
1508
+ break;
1509
+ case "help":
1510
+ showHelp();
1511
+ break;
1512
+ case "server":
1513
+ default:
1514
+ await startServer();
1515
+ break;
2953
1516
  }
2954
1517
  }
2955
1518
  main().catch((error) => {
2956
- console.error("[atomix-mcp] Fatal error:", error);
1519
+ console.error("Failed:", error);
2957
1520
  process.exit(1);
2958
1521
  });
2959
- export {
2960
- AI_TOOLS,
2961
- fetchUserDesignSystem,
2962
- generateAllMCPConfigs,
2963
- generateCursorRules,
2964
- generateMCPConfig,
2965
- generateRulesForAllTools,
2966
- generateRulesForTool,
2967
- generateToolSetupGuide,
2968
- getSetupInstructions,
2969
- getSupportedAITools,
2970
- isUserDSMode,
2971
- parseCLIArgs,
2972
- transformUserTokens
2973
- };
2974
1522
  //# sourceMappingURL=index.js.map