@atomixstudio/mcp 1.0.8 → 1.0.9

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
@@ -11,1134 +11,29 @@ import {
11
11
  ListPromptsRequestSchema,
12
12
  GetPromptRequestSchema
13
13
  } from "@modelcontextprotocol/sdk/types.js";
14
-
15
- // ../atomix-sync-core/src/fetch.ts
16
- function generateETag(meta) {
17
- const ts = meta.updatedAt ?? meta.exportedAt ?? "";
18
- const hash = `${meta.version}-${ts}`;
19
- return `"v${hash}"`;
20
- }
21
- function detectGovernanceChangesByFoundation(cached, fresh) {
22
- const changes = [];
23
- const cachedGov = cached.governance || { rules: [], categories: {} };
24
- const freshGov = fresh.governance || { rules: [], categories: {} };
25
- const cachedRulesStr = JSON.stringify((cachedGov.rules || []).sort());
26
- const freshRulesStr = JSON.stringify((freshGov.rules || []).sort());
27
- if (cachedRulesStr !== freshRulesStr) {
28
- changes.push("general");
29
- }
30
- const cachedCategories = cachedGov.categories || {};
31
- const freshCategories = freshGov.categories || {};
32
- const allFoundationKeys = /* @__PURE__ */ new Set([
33
- ...Object.keys(cachedCategories),
34
- ...Object.keys(freshCategories)
35
- ]);
36
- for (const foundation of allFoundationKeys) {
37
- const cachedRules = cachedCategories[foundation] || [];
38
- const freshRules = freshCategories[foundation] || [];
39
- const cachedRulesStr2 = JSON.stringify(cachedRules.sort());
40
- const freshRulesStr2 = JSON.stringify(freshRules.sort());
41
- if (cachedRulesStr2 !== freshRulesStr2) {
42
- changes.push(foundation);
43
- }
44
- }
45
- return changes;
46
- }
47
- async function fetchDesignSystem(options) {
48
- const { dsId: dsId2, apiKey: apiKey2, apiBase: apiBase2 = "https://atomixstudio.eu", etag, forceRefresh = false } = options;
49
- if (!dsId2) {
50
- throw new Error("Missing dsId. Usage: fetchDesignSystem({ dsId: '...' })");
51
- }
52
- const url = `${apiBase2}/api/ds/${dsId2}/tokens?format=export`;
53
- const headers = {
54
- "Content-Type": "application/json"
55
- };
56
- if (apiKey2) {
57
- headers["x-api-key"] = apiKey2;
58
- }
59
- if (etag) {
60
- headers["if-none-match"] = etag;
61
- }
62
- const response = await fetch(url, { headers });
63
- if (response.status === 304) {
64
- return {
65
- data: null,
66
- etag,
67
- status: 304
68
- };
69
- }
70
- if (!response.ok) {
71
- const text = await response.text();
72
- throw new Error(`Failed to fetch design system: ${response.status} ${text}`);
73
- }
74
- const data = await response.json();
75
- const responseETag = response.headers.get("etag");
76
- const finalETag = responseETag || generateETag(data.meta);
77
- const designSystemData = {
78
- tokens: data.tokens,
79
- cssVariables: data.cssVariables,
80
- governance: data.governance,
81
- meta: data.meta
82
- };
83
- return {
84
- data: designSystemData,
85
- etag: finalETag,
86
- status: 200
87
- };
88
- }
89
-
90
- // ../atomix-sync-core/src/diff.ts
91
- function diffTokens(oldContent, newCssVars, format, newDarkVars) {
92
- const added = [];
93
- const modified = [];
94
- const removed = [];
95
- const addedDark = [];
96
- const modifiedDark = [];
97
- const removedDark = [];
98
- if (format === "css" || format === "scss" || format === "less") {
99
- const rootMatch = oldContent.match(/:root\s*\{([^}]*)\}/);
100
- const darkMatch = oldContent.match(/\.dark[^{]*\{([^}]*)\}/);
101
- const oldLightVars = {};
102
- if (rootMatch) {
103
- const rootContent = rootMatch[1];
104
- const varRegex = /(--[\w-]+):\s*([^;]+);/g;
105
- let match;
106
- while ((match = varRegex.exec(rootContent)) !== null) {
107
- oldLightVars[match[1]] = match[2].trim();
108
- }
109
- } else {
110
- const varRegex = /(--[\w-]+):\s*([^;]+);/g;
111
- let match;
112
- while ((match = varRegex.exec(oldContent)) !== null) {
113
- if (!(match[1] in oldLightVars)) {
114
- oldLightVars[match[1]] = match[2].trim();
115
- }
116
- }
117
- }
118
- const oldDarkVars = {};
119
- if (darkMatch) {
120
- const darkContent = darkMatch[1];
121
- const varRegex = /(--[\w-]+):\s*([^;]+);/g;
122
- let match;
123
- while ((match = varRegex.exec(darkContent)) !== null) {
124
- oldDarkVars[match[1]] = match[2].trim();
125
- }
126
- }
127
- for (const [key, value] of Object.entries(newCssVars)) {
128
- if (!(key in oldLightVars)) {
129
- added.push(key);
130
- } else if (oldLightVars[key] !== value) {
131
- modified.push({ key, old: oldLightVars[key], new: value });
132
- }
133
- }
134
- for (const key of Object.keys(oldLightVars)) {
135
- if (!(key in newCssVars)) {
136
- removed.push(key);
137
- }
138
- }
139
- if (newDarkVars && Object.keys(newDarkVars).length > 0) {
140
- const firstKey = Object.keys(newCssVars)[0] || "--atmx-";
141
- const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
142
- const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
143
- for (const [key, value] of Object.entries(newDarkVars)) {
144
- const cssVarName = `${prefix}color-${key}`;
145
- if (!(cssVarName in oldDarkVars)) {
146
- addedDark.push(cssVarName);
147
- } else if (oldDarkVars[cssVarName] !== value) {
148
- modifiedDark.push({ key: cssVarName, old: oldDarkVars[cssVarName], new: value });
149
- }
150
- }
151
- for (const key of Object.keys(oldDarkVars)) {
152
- const shortKey = key.replace(new RegExp(`^${prefix}color-`), "");
153
- if (!(shortKey in newDarkVars)) {
154
- removedDark.push(key);
155
- }
156
- }
157
- }
158
- }
159
- return { added, modified, removed, addedDark, modifiedDark, removedDark };
160
- }
161
-
162
- // ../atomix-sync-core/src/rules.ts
163
- import * as fs from "fs";
14
+ import {
15
+ fetchDesignSystem,
16
+ getTokenByPath,
17
+ flattenTokens,
18
+ searchTokens,
19
+ getTokenStats,
20
+ compareDesignSystems,
21
+ generateCSSOutput,
22
+ generateSCSSOutput,
23
+ generateLessOutput,
24
+ generateJSONOutput,
25
+ generateTSOutput,
26
+ generateJSOutput,
27
+ generateSwiftOutput,
28
+ generateKotlinOutput,
29
+ generateDartOutput,
30
+ diffTokens,
31
+ formatSyncResponse,
32
+ detectGovernanceChangesByFoundation,
33
+ syncRulesFiles
34
+ } from "@atomixstudio/sync-core";
164
35
  import * as path from "path";
165
- async function syncRulesFiles(options) {
166
- const { dsId: dsId2, apiKey: apiKey2, apiBase: apiBase2 = "https://atomixstudio.eu", rulesDir = process.cwd() } = options;
167
- const rulesDirResolved = path.resolve(process.cwd(), rulesDir);
168
- const toolsToSync = [
169
- { tool: "cursor", filename: ".cursorrules" },
170
- { tool: "windsurf", filename: ".windsurfrules" },
171
- { tool: "cline", filename: ".clinerules" },
172
- { tool: "continue", filename: ".continuerules" },
173
- { tool: "copilot", filename: "copilot-instructions.md", dir: ".github" },
174
- { tool: "generic", filename: "AI_GUIDELINES.md" }
175
- ];
176
- const existingTools = toolsToSync.filter((t) => {
177
- const filePath = t.dir ? path.join(rulesDirResolved, t.dir, t.filename) : path.join(rulesDirResolved, t.filename);
178
- return fs.existsSync(filePath);
179
- });
180
- const toolsToWrite = existingTools.length > 0 ? existingTools : [{ tool: "cursor", filename: ".cursorrules" }];
181
- const results = [];
182
- for (const { tool, filename, dir } of toolsToWrite) {
183
- try {
184
- const rulesUrl = `${apiBase2}/api/ds/${dsId2}/rules?format=${tool}`;
185
- const headers = { "Content-Type": "application/json" };
186
- if (apiKey2) headers["x-api-key"] = apiKey2;
187
- const response = await fetch(rulesUrl, { headers });
188
- if (!response.ok) {
189
- results.push({
190
- tool,
191
- filename,
192
- path: dir ? `${dir}/${filename}` : filename,
193
- success: false,
194
- error: `Failed to fetch ${tool} rules: ${response.status}`
195
- });
196
- continue;
197
- }
198
- const rulesData = await response.json();
199
- if (!rulesData.content) {
200
- results.push({
201
- tool,
202
- filename,
203
- path: dir ? `${dir}/${filename}` : filename,
204
- success: false,
205
- error: `No content for ${tool} rules`
206
- });
207
- continue;
208
- }
209
- const targetDir = dir ? path.join(rulesDirResolved, dir) : rulesDirResolved;
210
- if (!fs.existsSync(targetDir)) {
211
- fs.mkdirSync(targetDir, { recursive: true });
212
- }
213
- const filePath = path.join(targetDir, filename);
214
- fs.writeFileSync(filePath, rulesData.content);
215
- results.push({
216
- tool,
217
- filename,
218
- path: dir ? `${dir}/${filename}` : filename,
219
- success: true
220
- });
221
- } catch (error) {
222
- results.push({
223
- tool,
224
- filename,
225
- path: dir ? `${dir}/${filename}` : filename,
226
- success: false,
227
- error: error instanceof Error ? error.message : String(error)
228
- });
229
- }
230
- }
231
- return results;
232
- }
233
-
234
- // ../atomix-sync-core/src/formats/css.ts
235
- function generateCSSOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
236
- const lines = [
237
- "/* Atomix Design System Tokens",
238
- " * Auto-generated - do not edit manually",
239
- ` * Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
240
- " *",
241
- " * WARNING: This file is completely rewritten on each sync.",
242
- " * Only CSS custom properties (variables) are preserved.",
243
- " * Custom CSS rules (selectors, classes, etc.) will be lost.",
244
- " * Keep custom CSS in a separate file.",
245
- " */",
246
- "",
247
- "/* Light mode (default) */",
248
- ":root {"
249
- ];
250
- const allVars = /* @__PURE__ */ new Map();
251
- for (const [key, value] of Object.entries(cssVariables)) {
252
- allVars.set(key, { value, deprecated: false });
253
- }
254
- for (const [key, value] of deprecatedTokens.entries()) {
255
- allVars.set(key, { value, deprecated: true });
256
- }
257
- const sortedKeys = Array.from(allVars.keys()).sort();
258
- for (const key of sortedKeys) {
259
- const { value, deprecated } = allVars.get(key);
260
- if (deprecated) {
261
- lines.push(` /* DEPRECATED */ ${key}: ${value};`);
262
- } else {
263
- lines.push(` ${key}: ${value};`);
264
- }
265
- }
266
- lines.push("}");
267
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
268
- lines.push("");
269
- lines.push("/* Dark mode */");
270
- lines.push(".dark,");
271
- lines.push('[data-theme="dark"] {');
272
- const firstKey = sortedKeys[0] || "--atmx-";
273
- const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
274
- const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
275
- const sortedDarkKeys = Object.keys(darkModeColors).sort();
276
- for (const key of sortedDarkKeys) {
277
- lines.push(` ${prefix}color-${key}: ${darkModeColors[key]};`);
278
- }
279
- lines.push("}");
280
- }
281
- lines.push("");
282
- return lines.join("\n");
283
- }
284
-
285
- // ../atomix-sync-core/src/formats/scss.ts
286
- function generateSCSSOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
287
- const lines = [
288
- "// Atomix Design System Tokens",
289
- "// Auto-generated - do not edit manually",
290
- `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
291
- "",
292
- "// CSS Custom Properties (for use in CSS/HTML)",
293
- "// Light mode (default)",
294
- ":root {"
295
- ];
296
- const allVars = /* @__PURE__ */ new Map();
297
- for (const [key, value] of Object.entries(cssVariables)) {
298
- allVars.set(key, { value, deprecated: false });
299
- }
300
- for (const [key, value] of deprecatedTokens.entries()) {
301
- allVars.set(key, { value, deprecated: true });
302
- }
303
- const sortedKeys = Array.from(allVars.keys()).sort();
304
- for (const key of sortedKeys) {
305
- const { value, deprecated } = allVars.get(key);
306
- if (deprecated) {
307
- lines.push(` // DEPRECATED ${key}: ${value};`);
308
- } else {
309
- lines.push(` ${key}: ${value};`);
310
- }
311
- }
312
- lines.push("}");
313
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
314
- lines.push("");
315
- lines.push("// Dark mode");
316
- lines.push(".dark,");
317
- lines.push('[data-theme="dark"] {');
318
- const firstKey = sortedKeys[0] || "--atmx-";
319
- const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
320
- const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
321
- for (const [key, value] of Object.entries(darkModeColors)) {
322
- lines.push(` ${prefix}color-${key}: ${value};`);
323
- }
324
- lines.push("}");
325
- }
326
- lines.push("");
327
- lines.push("// SCSS Variables (light mode values)");
328
- for (const key of sortedKeys) {
329
- const { value, deprecated } = allVars.get(key);
330
- const scssVar = "$" + key.replace(/^--/, "");
331
- if (deprecated) {
332
- lines.push(`// DEPRECATED ${scssVar}: ${value};`);
333
- } else {
334
- lines.push(`${scssVar}: ${value};`);
335
- }
336
- }
337
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
338
- lines.push("");
339
- lines.push("// SCSS Variables (dark mode values)");
340
- const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
341
- const prefixMatch = firstKey.match(/^--([a-z]+)-/);
342
- const prefix = prefixMatch ? prefixMatch[1] : "atmx";
343
- for (const [key, value] of Object.entries(darkModeColors)) {
344
- const scssVar = `$${prefix}-color-${key}-dark`;
345
- lines.push(`${scssVar}: ${value};`);
346
- }
347
- }
348
- lines.push("");
349
- return lines.join("\n");
350
- }
351
-
352
- // ../atomix-sync-core/src/formats/less.ts
353
- function generateLessOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
354
- const lines = [
355
- "// Atomix Design System Tokens",
356
- "// Auto-generated - do not edit manually",
357
- `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
358
- "",
359
- "// CSS Custom Properties (for use in CSS/HTML)",
360
- "// Light mode (default)",
361
- ":root {"
362
- ];
363
- const allVars = /* @__PURE__ */ new Map();
364
- for (const [key, value] of Object.entries(cssVariables)) {
365
- allVars.set(key, { value, deprecated: false });
366
- }
367
- for (const [key, value] of deprecatedTokens.entries()) {
368
- allVars.set(key, { value, deprecated: true });
369
- }
370
- const sortedKeys = Array.from(allVars.keys()).sort();
371
- for (const key of sortedKeys) {
372
- const { value, deprecated } = allVars.get(key);
373
- if (deprecated) {
374
- lines.push(` // DEPRECATED ${key}: ${value};`);
375
- } else {
376
- lines.push(` ${key}: ${value};`);
377
- }
378
- }
379
- lines.push("}");
380
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
381
- lines.push("");
382
- lines.push("// Dark mode");
383
- lines.push(".dark,");
384
- lines.push('[data-theme="dark"] {');
385
- const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
386
- const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
387
- const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
388
- for (const [key, value] of Object.entries(darkModeColors)) {
389
- lines.push(` ${prefix}color-${key}: ${value};`);
390
- }
391
- lines.push("}");
392
- }
393
- lines.push("");
394
- lines.push("// Less Variables (light mode values)");
395
- for (const [key, value] of Object.entries(cssVariables)) {
396
- const lessVar = "@" + key.replace(/^--/, "");
397
- lines.push(`${lessVar}: ${value};`);
398
- }
399
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
400
- lines.push("");
401
- lines.push("// Less Variables (dark mode values)");
402
- const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
403
- const prefixMatch = firstKey.match(/^--([a-z]+)-/);
404
- const prefix = prefixMatch ? prefixMatch[1] : "atmx";
405
- for (const [key, value] of Object.entries(darkModeColors)) {
406
- const lessVar = `@${prefix}-color-${key}-dark`;
407
- lines.push(`${lessVar}: ${value};`);
408
- }
409
- }
410
- lines.push("");
411
- return lines.join("\n");
412
- }
413
-
414
- // ../atomix-sync-core/src/formats/json.ts
415
- function generateJSONOutput(tokens) {
416
- return JSON.stringify(tokens, null, 2);
417
- }
418
-
419
- // ../atomix-sync-core/src/formats/ts.ts
420
- function generateTSOutput(tokens) {
421
- return `// Atomix Design System Tokens
422
- // Auto-generated - do not edit manually
423
- // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
424
-
425
- export const tokens = ${JSON.stringify(tokens, null, 2)} as const;
426
-
427
- export type Tokens = typeof tokens;
428
- `;
429
- }
430
-
431
- // ../atomix-sync-core/src/formats/js.ts
432
- function generateJSOutput(tokens) {
433
- return `// Atomix Design System Tokens
434
- // Auto-generated - do not edit manually
435
- // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
436
-
437
- export const tokens = ${JSON.stringify(tokens, null, 2)};
438
- `;
439
- }
440
-
441
- // ../atomix-sync-core/src/formats/swift.ts
442
- function toSwiftName(cssVar) {
443
- return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
444
- }
445
- function isColorValue(value) {
446
- return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
447
- }
448
- function hexToSwiftColor(hex) {
449
- const clean = hex.replace("#", "");
450
- if (clean.length === 3) {
451
- const r = clean[0], g = clean[1], b = clean[2];
452
- return `Color(hex: 0x${r}${r}${g}${g}${b}${b})`;
453
- }
454
- if (clean.length === 6) {
455
- return `Color(hex: 0x${clean})`;
456
- }
457
- if (clean.length === 8) {
458
- const rgb = clean.substring(0, 6);
459
- const alpha = parseInt(clean.substring(6, 8), 16) / 255;
460
- return `Color(hex: 0x${rgb}).opacity(${alpha.toFixed(2)})`;
461
- }
462
- return `Color.clear // Could not parse: ${hex}`;
463
- }
464
- function generateSwiftOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
465
- const lines = [
466
- "// Atomix Design System Tokens",
467
- "// Auto-generated - do not edit manually",
468
- `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
469
- "",
470
- "import SwiftUI",
471
- "",
472
- "// MARK: - Color Extension for Hex",
473
- "extension Color {",
474
- " init(hex: UInt, alpha: Double = 1.0) {",
475
- " self.init(",
476
- " .sRGB,",
477
- " red: Double((hex >> 16) & 0xFF) / 255.0,",
478
- " green: Double((hex >> 8) & 0xFF) / 255.0,",
479
- " blue: Double(hex & 0xFF) / 255.0,",
480
- " opacity: alpha",
481
- " )",
482
- " }",
483
- "}",
484
- "",
485
- "// MARK: - Design Tokens",
486
- "enum DesignTokens {",
487
- "",
488
- " // MARK: Colors (Light Mode)",
489
- " enum Colors {"
490
- ];
491
- const colors = [];
492
- const spacing = [];
493
- const typography = [];
494
- const other = [];
495
- for (const [key, value] of Object.entries(cssVariables)) {
496
- const isDeprecated = false;
497
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
498
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
499
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
500
- else other.push([key, value, isDeprecated]);
501
- }
502
- for (const [key, value] of deprecatedTokens.entries()) {
503
- const isDeprecated = true;
504
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
505
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
506
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
507
- else other.push([key, value, isDeprecated]);
508
- }
509
- colors.sort((a, b) => a[0].localeCompare(b[0]));
510
- spacing.sort((a, b) => a[0].localeCompare(b[0]));
511
- typography.sort((a, b) => a[0].localeCompare(b[0]));
512
- other.sort((a, b) => a[0].localeCompare(b[0]));
513
- for (const [key, value, isDeprecated] of colors) {
514
- const name = toSwiftName(key);
515
- if (isColorValue(value)) {
516
- if (isDeprecated) {
517
- lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
518
- lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
519
- } else {
520
- lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
521
- }
522
- }
523
- }
524
- lines.push(" }");
525
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
526
- lines.push("");
527
- lines.push(" // MARK: Colors (Dark Mode)");
528
- lines.push(" enum ColorsDark {");
529
- for (const [key, value] of Object.entries(darkModeColors)) {
530
- const name = `color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}`;
531
- if (isColorValue(value)) {
532
- lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
533
- }
534
- }
535
- lines.push(" }");
536
- }
537
- lines.push("");
538
- lines.push(" // MARK: Spacing");
539
- lines.push(" enum Spacing {");
540
- for (const [key, value, isDeprecated] of spacing) {
541
- const name = toSwiftName(key);
542
- const numValue = parseFloat(value);
543
- if (!isNaN(numValue)) {
544
- if (isDeprecated) {
545
- lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
546
- lines.push(` static let ${name}: CGFloat = ${numValue}`);
547
- } else {
548
- lines.push(` static let ${name}: CGFloat = ${numValue}`);
549
- }
550
- }
551
- }
552
- lines.push(" }");
553
- lines.push("");
554
- lines.push(" // MARK: Typography");
555
- lines.push(" enum Typography {");
556
- for (const [key, value, isDeprecated] of typography) {
557
- const name = toSwiftName(key);
558
- if (isDeprecated) {
559
- lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
560
- }
561
- if (key.includes("size")) {
562
- const numValue = parseFloat(value);
563
- if (!isNaN(numValue)) {
564
- lines.push(` static let ${name}: CGFloat = ${numValue}`);
565
- }
566
- } else if (key.includes("weight")) {
567
- lines.push(` static let ${name} = "${value}"`);
568
- } else if (key.includes("family")) {
569
- lines.push(` static let ${name} = "${value}"`);
570
- }
571
- }
572
- lines.push(" }");
573
- lines.push("");
574
- lines.push(" // MARK: Other");
575
- lines.push(" enum Other {");
576
- for (const [key, value, isDeprecated] of other) {
577
- const name = toSwiftName(key);
578
- if (isDeprecated) {
579
- lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
580
- }
581
- if (isColorValue(value)) {
582
- lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
583
- } else {
584
- const numValue = parseFloat(value);
585
- if (!isNaN(numValue)) {
586
- lines.push(` static let ${name}: CGFloat = ${numValue}`);
587
- } else {
588
- lines.push(` static let ${name} = "${value}"`);
589
- }
590
- }
591
- }
592
- lines.push(" }");
593
- lines.push("}");
594
- lines.push("");
595
- return lines.join("\n");
596
- }
597
-
598
- // ../atomix-sync-core/src/formats/kotlin.ts
599
- function toSwiftName2(cssVar) {
600
- return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
601
- }
602
- function toKotlinName(cssVar) {
603
- const camel = toSwiftName2(cssVar);
604
- return camel.charAt(0).toUpperCase() + camel.slice(1);
605
- }
606
- function isColorValue2(value) {
607
- return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
608
- }
609
- function hexToKotlinColor(hex) {
610
- const clean = hex.replace("#", "").toUpperCase();
611
- if (clean.length === 3) {
612
- const r = clean[0], g = clean[1], b = clean[2];
613
- return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
614
- }
615
- if (clean.length === 6) {
616
- return `Color(0xFF${clean})`;
617
- }
618
- if (clean.length === 8) {
619
- const rgb = clean.substring(0, 6);
620
- const alpha = clean.substring(6, 8);
621
- return `Color(0x${alpha}${rgb})`;
622
- }
623
- return `Color.Transparent // Could not parse: ${hex}`;
624
- }
625
- function generateKotlinOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
626
- const lines = [
627
- "// Atomix Design System Tokens",
628
- "// Auto-generated - do not edit manually",
629
- `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
630
- "",
631
- "package com.atomix.design",
632
- "",
633
- "import androidx.compose.ui.graphics.Color",
634
- "import androidx.compose.ui.unit.dp",
635
- "import androidx.compose.ui.unit.sp",
636
- "",
637
- "object DesignTokens {",
638
- "",
639
- " // Light mode colors",
640
- " object Colors {"
641
- ];
642
- const colors = [];
643
- const spacing = [];
644
- const typography = [];
645
- const other = [];
646
- for (const [key, value] of Object.entries(cssVariables)) {
647
- const isDeprecated = false;
648
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
649
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
650
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
651
- else other.push([key, value, isDeprecated]);
652
- }
653
- for (const [key, value] of deprecatedTokens.entries()) {
654
- const isDeprecated = true;
655
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
656
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
657
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
658
- else other.push([key, value, isDeprecated]);
659
- }
660
- colors.sort((a, b) => a[0].localeCompare(b[0]));
661
- spacing.sort((a, b) => a[0].localeCompare(b[0]));
662
- typography.sort((a, b) => a[0].localeCompare(b[0]));
663
- other.sort((a, b) => a[0].localeCompare(b[0]));
664
- for (const [key, value, isDeprecated] of colors) {
665
- const name = toKotlinName(key);
666
- if (isColorValue2(value)) {
667
- if (isDeprecated) {
668
- lines.push(` @Deprecated("This token has been removed from the design system")`);
669
- lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
670
- } else {
671
- lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
672
- }
673
- }
674
- }
675
- lines.push(" }");
676
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
677
- lines.push("");
678
- lines.push(" // Dark mode colors");
679
- lines.push(" object ColorsDark {");
680
- for (const [key, value] of Object.entries(darkModeColors)) {
681
- const name = `Color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}`;
682
- if (isColorValue2(value)) {
683
- lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
684
- }
685
- }
686
- lines.push(" }");
687
- }
688
- lines.push("");
689
- lines.push(" object Spacing {");
690
- for (const [key, value, isDeprecated] of spacing) {
691
- const name = toKotlinName(key);
692
- const numValue = parseFloat(value);
693
- if (!isNaN(numValue)) {
694
- if (isDeprecated) {
695
- lines.push(` @Deprecated("This token has been removed from the design system")`);
696
- lines.push(` val ${name} = ${numValue}.dp`);
697
- } else {
698
- lines.push(` val ${name} = ${numValue}.dp`);
699
- }
700
- }
701
- }
702
- lines.push(" }");
703
- lines.push("");
704
- lines.push(" object Typography {");
705
- for (const [key, value, isDeprecated] of typography) {
706
- const name = toKotlinName(key);
707
- if (isDeprecated) {
708
- lines.push(` @Deprecated("This token has been removed from the design system")`);
709
- }
710
- if (key.includes("size")) {
711
- const numValue = parseFloat(value);
712
- if (!isNaN(numValue)) {
713
- lines.push(` val ${name} = ${numValue}.sp`);
714
- }
715
- } else {
716
- lines.push(` const val ${name} = "${value}"`);
717
- }
718
- }
719
- lines.push(" }");
720
- lines.push("");
721
- lines.push(" object Other {");
722
- for (const [key, value, isDeprecated] of other) {
723
- const name = toKotlinName(key);
724
- if (isDeprecated) {
725
- lines.push(` @Deprecated("This token has been removed from the design system")`);
726
- }
727
- if (isColorValue2(value)) {
728
- lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
729
- } else {
730
- const numValue = parseFloat(value);
731
- if (!isNaN(numValue)) {
732
- lines.push(` val ${name} = ${numValue}.dp`);
733
- } else {
734
- lines.push(` const val ${name} = "${value}"`);
735
- }
736
- }
737
- }
738
- lines.push(" }");
739
- lines.push("}");
740
- lines.push("");
741
- return lines.join("\n");
742
- }
743
-
744
- // ../atomix-sync-core/src/formats/dart.ts
745
- function toSwiftName3(cssVar) {
746
- return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
747
- }
748
- function toDartName(cssVar) {
749
- return toSwiftName3(cssVar);
750
- }
751
- function isColorValue3(value) {
752
- return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
753
- }
754
- function hexToDartColor(hex) {
755
- const clean = hex.replace("#", "").toUpperCase();
756
- if (clean.length === 3) {
757
- const r = clean[0], g = clean[1], b = clean[2];
758
- return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
759
- }
760
- if (clean.length === 6) {
761
- return `Color(0xFF${clean})`;
762
- }
763
- if (clean.length === 8) {
764
- const rgb = clean.substring(0, 6);
765
- const alpha = clean.substring(6, 8);
766
- return `Color(0x${alpha}${rgb})`;
767
- }
768
- return `Colors.transparent // Could not parse: ${hex}`;
769
- }
770
- function generateDartOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
771
- const lines = [
772
- "// Atomix Design System Tokens",
773
- "// Auto-generated - do not edit manually",
774
- `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
775
- "",
776
- "import 'package:flutter/material.dart';",
777
- "",
778
- "class DesignTokens {",
779
- " DesignTokens._();",
780
- "",
781
- " // Colors (Light Mode)"
782
- ];
783
- const colors = [];
784
- const spacing = [];
785
- const typography = [];
786
- const other = [];
787
- for (const [key, value] of Object.entries(cssVariables)) {
788
- const isDeprecated = false;
789
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
790
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
791
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
792
- else other.push([key, value, isDeprecated]);
793
- }
794
- for (const [key, value] of deprecatedTokens.entries()) {
795
- const isDeprecated = true;
796
- if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
797
- else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
798
- else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
799
- else other.push([key, value, isDeprecated]);
800
- }
801
- colors.sort((a, b) => a[0].localeCompare(b[0]));
802
- spacing.sort((a, b) => a[0].localeCompare(b[0]));
803
- typography.sort((a, b) => a[0].localeCompare(b[0]));
804
- other.sort((a, b) => a[0].localeCompare(b[0]));
805
- for (const [key, value, isDeprecated] of colors) {
806
- const name = toDartName(key);
807
- if (isColorValue3(value)) {
808
- if (isDeprecated) {
809
- lines.push(` @Deprecated('This token has been removed from the design system')`);
810
- lines.push(` static const ${name} = ${hexToDartColor(value)};`);
811
- } else {
812
- lines.push(` static const ${name} = ${hexToDartColor(value)};`);
813
- }
814
- }
815
- }
816
- if (darkModeColors && Object.keys(darkModeColors).length > 0) {
817
- lines.push("");
818
- lines.push(" // Colors (Dark Mode)");
819
- for (const [key, value] of Object.entries(darkModeColors)) {
820
- const name = `color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}Dark`;
821
- if (isColorValue3(value)) {
822
- lines.push(` static const ${name} = ${hexToDartColor(value)};`);
823
- }
824
- }
825
- }
826
- lines.push("");
827
- lines.push(" // Spacing");
828
- for (const [key, value, isDeprecated] of spacing) {
829
- const name = toDartName(key);
830
- const numValue = parseFloat(value);
831
- if (!isNaN(numValue)) {
832
- if (isDeprecated) {
833
- lines.push(` @Deprecated('This token has been removed from the design system')`);
834
- lines.push(` static const double ${name} = ${numValue};`);
835
- } else {
836
- lines.push(` static const double ${name} = ${numValue};`);
837
- }
838
- }
839
- }
840
- lines.push("");
841
- lines.push(" // Typography");
842
- for (const [key, value, isDeprecated] of typography) {
843
- const name = toDartName(key);
844
- if (isDeprecated) {
845
- lines.push(` @Deprecated('This token has been removed from the design system')`);
846
- }
847
- if (key.includes("size")) {
848
- const numValue = parseFloat(value);
849
- if (!isNaN(numValue)) {
850
- lines.push(` static const double ${name} = ${numValue};`);
851
- }
852
- } else {
853
- lines.push(` static const String ${name} = '${value}';`);
854
- }
855
- }
856
- lines.push("");
857
- lines.push(" // Other");
858
- for (const [key, value, isDeprecated] of other) {
859
- const name = toDartName(key);
860
- if (isDeprecated) {
861
- lines.push(` @Deprecated('This token has been removed from the design system')`);
862
- }
863
- if (isColorValue3(value)) {
864
- lines.push(` static const ${name} = ${hexToDartColor(value)};`);
865
- } else {
866
- const numValue = parseFloat(value);
867
- if (!isNaN(numValue)) {
868
- lines.push(` static const double ${name} = ${numValue};`);
869
- } else {
870
- lines.push(` static const String ${name} = '${value}';`);
871
- }
872
- }
873
- }
874
- lines.push("}");
875
- lines.push("");
876
- return lines.join("\n");
877
- }
878
-
879
- // ../atomix-sync-core/src/response.ts
880
- import * as path2 from "path";
881
- function formatSyncResponse(options) {
882
- const {
883
- data,
884
- output,
885
- format,
886
- dsTokenCount,
887
- deprecatedCount,
888
- deprecatedTokens,
889
- diff,
890
- changes = [],
891
- fileExists,
892
- rulesResults = [],
893
- governanceChanges = [],
894
- changeSummary,
895
- hasRefactorRecommendation = false,
896
- deprecatedTokenCount = 0
897
- } = options;
898
- const lastUpdated = data.meta.exportedAt ? new Date(data.meta.exportedAt).toLocaleString() : "N/A";
899
- let response = `\u2713 Synced ${dsTokenCount} tokens to ${output}
900
- `;
901
- response += `Format: ${format}
902
- `;
903
- response += `Design System: ${data.meta.name} (v${data.meta.version})
904
- `;
905
- if (deprecatedCount > 0) {
906
- response += `Tokens: ${dsTokenCount} active, ${deprecatedCount} deprecated (run /refactor to migrate)
907
- `;
908
- } else {
909
- response += `Tokens: ${dsTokenCount}
910
- `;
911
- }
912
- response += `Version: ${data.meta.version}
913
- `;
914
- response += `Last updated: ${lastUpdated}
915
- `;
916
- if (deprecatedCount > 0) {
917
- response += `
918
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
919
- `;
920
- response += `\u{1F4CB} DEPRECATED TOKENS (${deprecatedCount})
921
- `;
922
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
923
-
924
- `;
925
- response += `These tokens are no longer in the design system but are preserved in your file for backward compatibility.
926
- `;
927
- response += `Run \`/refactor\` to find and migrate usage in your codebase.
928
-
929
- `;
930
- const deprecatedTokenList = Array.from(deprecatedTokens.keys()).sort();
931
- deprecatedTokenList.forEach((token, index) => {
932
- const value = deprecatedTokens.get(token);
933
- response += ` ${index + 1}. ${token}`;
934
- if (value) {
935
- response += ` = ${value}`;
936
- }
937
- response += `
938
- `;
939
- });
940
- }
941
- const newTokenList = [];
942
- if (diff) {
943
- newTokenList.push(...diff.added, ...diff.addedDark);
944
- }
945
- if (changeSummary && changeSummary.includes("\u2795 Added:")) {
946
- const addedSection = changeSummary.match(/➕ Added: \d+ token\(s\)([\s\S]*?)(?=➖|🔄|📝|$)/);
947
- if (addedSection) {
948
- const addedLines = addedSection[0].match(/ - (--[^\n]+)/g) || [];
949
- addedLines.forEach((line) => {
950
- const token = line.replace(/ - /, "").trim();
951
- if (token && !newTokenList.includes(token)) {
952
- newTokenList.push(token);
953
- }
954
- });
955
- }
956
- }
957
- if (newTokenList.length > 0) {
958
- response += `
959
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
960
- `;
961
- response += `\u2728 NEW TOKENS (${newTokenList.length})
962
- `;
963
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
964
-
965
- `;
966
- newTokenList.sort().forEach((token, index) => {
967
- response += ` ${index + 1}. ${token}
968
- `;
969
- });
970
- }
971
- const modifiedFiles = [output];
972
- rulesResults.forEach((result) => {
973
- if (result.success) {
974
- const fullPath = path2.resolve(process.cwd(), result.path);
975
- if (!modifiedFiles.includes(fullPath)) {
976
- modifiedFiles.push(fullPath);
977
- }
978
- }
979
- });
980
- response += `
981
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
982
- `;
983
- response += `\u{1F4C1} MODIFIED FILES (${modifiedFiles.length})
984
- `;
985
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
986
-
987
- `;
988
- modifiedFiles.forEach((file, index) => {
989
- const relativePath = path2.relative(process.cwd(), file);
990
- response += ` ${index + 1}. ${relativePath}
991
- `;
992
- });
993
- if (governanceChanges.length > 0) {
994
- response += `
995
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
996
- `;
997
- response += `\u{1F4DD} AI RULES CHANGES
998
- `;
999
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1000
-
1001
- `;
1002
- response += `The following AI guidance sections have been updated:
1003
-
1004
- `;
1005
- const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
1006
- governanceChanges.forEach((foundation, index) => {
1007
- const displayName = foundation === "general" ? "General Rules" : capitalize(foundation);
1008
- response += ` ${index + 1}. ${displayName}
1009
- `;
1010
- });
1011
- response += `
1012
- These changes are reflected in your AI tool rules files (e.g., .cursorrules).
1013
- `;
1014
- response += `Review the updated rules files to see the new guidance.
1015
- `;
1016
- }
1017
- if (diff) {
1018
- const lightChanges = diff.added.length + diff.modified.length;
1019
- const darkChanges = diff.addedDark.length + diff.modifiedDark.length;
1020
- if (lightChanges > 0 || darkChanges > 0 || deprecatedCount > 0) {
1021
- response += `
1022
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1023
- `;
1024
- response += `\u{1F4CA} DETAILED CHANGES
1025
- `;
1026
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1027
-
1028
- `;
1029
- const lightSummary = lightChanges > 0 ? `Light: ${diff.modified.length} modified, ${diff.added.length} added` : "";
1030
- const darkSummary = darkChanges > 0 ? `Dark: ${diff.modifiedDark.length} modified, ${diff.addedDark.length} added` : "";
1031
- const deprecatedSummary = deprecatedCount > 0 ? `${deprecatedCount} deprecated (use /refactor)` : "";
1032
- const diffSummary = [lightSummary, darkSummary, deprecatedSummary].filter(Boolean).join(" | ");
1033
- response += `${diffSummary}
1034
- `;
1035
- if (changes.length > 0 && changes.length <= 10) {
1036
- response += "\nModified tokens:\n";
1037
- for (const { key, old: oldVal, new: newVal } of changes) {
1038
- response += ` ${key}: ${oldVal} \u2192 ${newVal}
1039
- `;
1040
- }
1041
- }
1042
- }
1043
- } else if (!fileExists) {
1044
- response += `
1045
- (New file created)
1046
- `;
1047
- }
1048
- if (hasRefactorRecommendation && deprecatedTokenCount > 0) {
1049
- response += `
1050
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1051
- `;
1052
- response += `\u{1F4A1} NEXT STEP
1053
- `;
1054
- response += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1055
-
1056
- `;
1057
- response += `${deprecatedTokenCount} token(s) have been deprecated.
1058
- `;
1059
- response += `Your codebase may still reference these deprecated tokens.
1060
-
1061
- `;
1062
- response += `Run \`/refactor\` to:
1063
- `;
1064
- response += ` \u2022 Scan your codebase for deprecated token usage
1065
- `;
1066
- response += ` \u2022 See which files need updates
1067
- `;
1068
- response += ` \u2022 Review and apply changes with your confirmation
1069
- `;
1070
- }
1071
- return response;
1072
- }
1073
-
1074
- // ../atomix-sync-core/src/tokens.ts
1075
- function getTokenByPath(tokens, path4) {
1076
- const parts = path4.split(".");
1077
- let current = tokens;
1078
- for (const part of parts) {
1079
- if (current && typeof current === "object" && part in current) {
1080
- current = current[part];
1081
- } else {
1082
- return void 0;
1083
- }
1084
- }
1085
- return current;
1086
- }
1087
- function flattenTokens(obj, prefix = "") {
1088
- const results = [];
1089
- if (obj && typeof obj === "object" && !Array.isArray(obj)) {
1090
- for (const [key, value] of Object.entries(obj)) {
1091
- const newPath = prefix ? `${prefix}.${key}` : key;
1092
- if (value && typeof value === "object" && !Array.isArray(value)) {
1093
- results.push(...flattenTokens(value, newPath));
1094
- } else {
1095
- results.push({ path: newPath, value });
1096
- }
1097
- }
1098
- }
1099
- return results;
1100
- }
1101
- function searchTokens(tokens, query) {
1102
- const flat = flattenTokens(tokens);
1103
- const lowerQuery = query.toLowerCase();
1104
- return flat.filter(({ path: path4, value }) => {
1105
- const pathMatch = path4.toLowerCase().includes(lowerQuery);
1106
- const valueMatch = String(value).toLowerCase().includes(lowerQuery);
1107
- return pathMatch || valueMatch;
1108
- });
1109
- }
1110
- function countTokens(tokens, prefix = "") {
1111
- let count = 0;
1112
- for (const [key, value] of Object.entries(tokens)) {
1113
- if (value && typeof value === "object" && !Array.isArray(value)) {
1114
- count += countTokens(value, `${prefix}${key}.`);
1115
- } else if (value !== void 0 && value !== null) {
1116
- count++;
1117
- }
1118
- }
1119
- return count;
1120
- }
1121
- function getTokenStats(data) {
1122
- const byCategory = {};
1123
- let total = 0;
1124
- for (const [category, value] of Object.entries(data.tokens)) {
1125
- if (value && typeof value === "object") {
1126
- const count = countTokens(value);
1127
- byCategory[category] = count;
1128
- total += count;
1129
- }
1130
- }
1131
- return {
1132
- total,
1133
- byCategory,
1134
- cssVariables: Object.keys(data.cssVariables).length,
1135
- governanceRules: data.governance.rules.length
1136
- };
1137
- }
1138
-
1139
- // src/index.ts
1140
- import * as path3 from "path";
1141
- import * as fs2 from "fs";
36
+ import * as fs from "fs";
1142
37
  function parseArgs() {
1143
38
  const args = process.argv.slice(2);
1144
39
  let dsId2 = null;
@@ -1165,35 +60,40 @@ var cachedData = null;
1165
60
  var cachedETag = null;
1166
61
  var lastChangeSummary = null;
1167
62
  var lastSyncAffectedTokens = null;
1168
- async function fetchDesignSystemForMCP(forceRefresh = false) {
1169
- if (!dsId) {
1170
- throw new Error("Missing --ds-id argument. Usage: npx @atomixstudio/mcp --ds-id <id> [--api-key <key>]");
63
+ function getLastChangeSummary() {
64
+ return lastChangeSummary;
65
+ }
66
+ async function updateChangeSummary(freshData) {
67
+ if (cachedData) {
68
+ const changes = compareDesignSystems(cachedData, freshData);
69
+ lastChangeSummary = changes.summary;
70
+ if (changes.hasChanges) {
71
+ console.error(`[mcp-user] Design system changes detected:
72
+ ${changes.summary}`);
73
+ }
1171
74
  }
75
+ }
76
+ async function fetchDesignSystemForMCP(forceRefresh = false) {
77
+ if (!dsId) throw new Error("Missing --ds-id. Usage: npx @atomixstudio/mcp --ds-id <id>");
1172
78
  const result = await fetchDesignSystem({
1173
79
  dsId,
1174
80
  apiKey: apiKey ?? void 0,
1175
- apiBase,
81
+ apiBase: apiBase ?? void 0,
1176
82
  etag: forceRefresh ? void 0 : cachedETag ?? void 0,
1177
83
  forceRefresh
1178
84
  });
1179
- if (result.status === 304 && cachedData) {
1180
- return cachedData;
1181
- }
1182
- if (!result.data) {
1183
- throw new Error("Failed to fetch design system: no data returned");
1184
- }
85
+ if (result.status === 304 && cachedData) return cachedData;
86
+ if (result.status === 304 || !result.data) throw new Error("No design system data (304 or null)");
1185
87
  cachedData = result.data;
1186
88
  cachedETag = result.etag;
89
+ await updateChangeSummary(result.data);
1187
90
  return result.data;
1188
91
  }
1189
- function getLastChangeSummary() {
1190
- return lastChangeSummary;
1191
- }
1192
92
  var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
1193
93
  var server = new Server(
1194
94
  {
1195
95
  name: "atomix-mcp-user",
1196
- version: "1.0.0"
96
+ version: "1.0.9"
1197
97
  },
1198
98
  {
1199
99
  capabilities: {
@@ -1335,6 +235,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
1335
235
  },
1336
236
  required: ["output"]
1337
237
  }
238
+ },
239
+ {
240
+ name: "getDependencies",
241
+ description: "Get suggested dependencies for this design system (icon package, fonts, SKILL.md, MCP config, token files). Use with atomix/install. Optional platform and stack for tailored suggestions.",
242
+ inputSchema: {
243
+ type: "object",
244
+ properties: {
245
+ platform: {
246
+ type: "string",
247
+ enum: ["web", "ios", "android"],
248
+ description: "Target platform (web, ios, android). Optional."
249
+ },
250
+ stack: {
251
+ type: "string",
252
+ description: "Stack or framework (e.g. react, vue, next, swift, kotlin). Optional."
253
+ }
254
+ },
255
+ required: []
256
+ }
1338
257
  }
1339
258
  ]
1340
259
  };
@@ -1346,27 +265,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1346
265
  const data = await fetchDesignSystemForMCP(shouldForceRefresh);
1347
266
  switch (name) {
1348
267
  case "getToken": {
1349
- const path4 = args?.path;
1350
- const value = getTokenByPath(data.tokens, path4);
268
+ const path2 = args?.path;
269
+ const value = getTokenByPath(data.tokens, path2);
1351
270
  if (value === void 0) {
1352
271
  return {
1353
272
  content: [{
1354
273
  type: "text",
1355
274
  text: JSON.stringify({
1356
- error: `Token not found: ${path4}`,
275
+ error: `Token not found: ${path2}`,
1357
276
  suggestion: "Use listTokens or searchTokens to find available tokens.",
1358
277
  availableCategories: TOKEN_CATEGORIES
1359
278
  }, null, 2)
1360
279
  }]
1361
280
  };
1362
281
  }
1363
- const cssVarKey = `--atmx-${path4.replace(/\./g, "-")}`;
282
+ const cssVarKey = `--atmx-${path2.replace(/\./g, "-")}`;
1364
283
  const cssVar = data.cssVariables[cssVarKey];
1365
284
  return {
1366
285
  content: [{
1367
286
  type: "text",
1368
287
  text: JSON.stringify({
1369
- path: path4,
288
+ path: path2,
1370
289
  value,
1371
290
  cssVariable: cssVar || `var(${cssVarKey})`,
1372
291
  usage: `style={{ property: "var(${cssVarKey})" }}`
@@ -1393,13 +312,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1393
312
  };
1394
313
  }
1395
314
  const flat = flattenTokens(tokensToList);
1396
- const tokensWithCssVars = flat.map(({ path: path4, value }) => {
1397
- const fullPath = subcategory ? `${category}.${subcategory}.${path4}` : `${category}.${path4}`;
315
+ const tokensWithCssVars = flat.map(({ path: path2, value }) => {
316
+ const fullPath = subcategory ? `${category}.${subcategory}.${path2}` : `${category}.${path2}`;
1398
317
  let cssVar;
1399
318
  if (category === "colors" && subcategory === "static.brand") {
1400
- cssVar = data.cssVariables[`--atmx-color-brand-${path4}`];
319
+ cssVar = data.cssVariables[`--atmx-color-brand-${path2}`];
1401
320
  } else if (category === "colors" && subcategory?.startsWith("modes.")) {
1402
- cssVar = data.cssVariables[`--atmx-color-${path4}`];
321
+ cssVar = data.cssVariables[`--atmx-color-${path2}`];
1403
322
  } else {
1404
323
  const cssVarKey = `--atmx-${fullPath.replace(/\./g, "-")}`;
1405
324
  cssVar = data.cssVariables[cssVarKey];
@@ -1590,12 +509,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1590
509
  - \u274C **Lost**: Regular CSS rules (selectors, classes, media queries, etc.)
1591
510
 
1592
511
  **Best Practice**: Keep \`tokens.css\` separate from your custom CSS. Use a separate file (e.g., \`custom.css\`) for custom styles.`,
1593
- copilot: `# GitHub Copilot Setup
512
+ copilot: `# Copilot Setup
1594
513
 
1595
- 1. Create \`.github/copilot-instructions.md\` in your project
514
+ 1. Create a Copilot instructions file in your project (e.g. \`.github/copilot-instructions.md\`)
1596
515
  2. Use getAIToolRules({ tool: "copilot" }) to get the content
1597
- 3. Enable custom instructions in VS Code settings:
1598
- "github.copilot.chat.codeGeneration.useInstructionFiles": true
516
+ 3. Enable custom instructions in your editor (e.g. \`github.copilot.chat.codeGeneration.useInstructionFiles\`: true in settings)
1599
517
 
1600
518
  ## File Structure
1601
519
 
@@ -1720,12 +638,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1720
638
  }]
1721
639
  };
1722
640
  }
1723
- const outputPath = path3.resolve(process.cwd(), output);
1724
- const fileExists = fs2.existsSync(outputPath);
641
+ const outputPath = path.resolve(process.cwd(), output);
642
+ const fileExists = fs.existsSync(outputPath);
1725
643
  const deprecatedTokens = /* @__PURE__ */ new Map();
1726
644
  const existingTokens = /* @__PURE__ */ new Map();
1727
645
  if (fileExists && ["css", "scss", "less"].includes(format)) {
1728
- const oldContent = fs2.readFileSync(outputPath, "utf-8");
646
+ const oldContent = fs.readFileSync(outputPath, "utf-8");
1729
647
  const oldVarPattern = /(?:^|\n)\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?(--[a-zA-Z0-9-]+):\s*([^;]+);/gm;
1730
648
  let match;
1731
649
  while ((match = oldVarPattern.exec(oldContent)) !== null) {
@@ -1778,7 +696,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1778
696
  let changes = [];
1779
697
  let diff;
1780
698
  if (fileExists && ["css", "scss", "less"].includes(format)) {
1781
- const oldContent = fs2.readFileSync(outputPath, "utf-8");
699
+ const oldContent = fs.readFileSync(outputPath, "utf-8");
1782
700
  diff = diffTokens(oldContent, mergedCssVariables, format, darkModeColors?.dark);
1783
701
  const lightChanges = diff.added.length + diff.modified.length;
1784
702
  const darkChanges = diff.addedDark.length + diff.modifiedDark.length;
@@ -1818,17 +736,17 @@ Last updated: ${lastUpdated}`
1818
736
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1819
737
  };
1820
738
  }
1821
- const outputDir = path3.dirname(outputPath);
1822
- if (!fs2.existsSync(outputDir)) {
1823
- fs2.mkdirSync(outputDir, { recursive: true });
739
+ const outputDir = path.dirname(outputPath);
740
+ if (!fs.existsSync(outputDir)) {
741
+ fs.mkdirSync(outputDir, { recursive: true });
1824
742
  }
1825
- fs2.writeFileSync(outputPath, newContent);
743
+ fs.writeFileSync(outputPath, newContent);
1826
744
  let rulesResults = [];
1827
745
  try {
1828
746
  rulesResults = await syncRulesFiles({
1829
747
  dsId,
1830
748
  apiKey: apiKey ?? void 0,
1831
- apiBase,
749
+ apiBase: apiBase ?? void 0,
1832
750
  rulesDir: process.cwd()
1833
751
  });
1834
752
  } catch (error) {
@@ -1858,13 +776,74 @@ Last updated: ${lastUpdated}`
1858
776
  }]
1859
777
  };
1860
778
  }
779
+ case "getDependencies": {
780
+ const platform = args?.platform;
781
+ const stack = args?.stack;
782
+ const tokens = data.tokens;
783
+ const typography = tokens?.typography;
784
+ const fontFamily = typography?.fontFamily;
785
+ const fontNames = [];
786
+ if (fontFamily) {
787
+ for (const key of ["display", "heading", "body"]) {
788
+ const v = fontFamily[key];
789
+ if (typeof v === "string" && v && !fontNames.includes(v)) fontNames.push(v);
790
+ }
791
+ }
792
+ const icons = tokens?.icons;
793
+ const ICON_PACKAGES = {
794
+ lucide: { web: "lucide-react", native: "lucide-react-native" },
795
+ heroicons: { web: "@heroicons/react", native: "heroicons-react-native" },
796
+ phosphor: { web: "phosphor-react", native: "phosphor-react-native" }
797
+ };
798
+ const lib = icons?.library || "lucide";
799
+ const iconPkgs = ICON_PACKAGES[lib] || ICON_PACKAGES.lucide;
800
+ const serverName = data.meta.name.toLowerCase().replace(/[^a-z0-9]/g, "-");
801
+ const npxArgs = ["@atomixstudio/mcp@latest"];
802
+ if (dsId) npxArgs.push("--ds-id", dsId);
803
+ if (apiKey) npxArgs.push("--api-key", apiKey);
804
+ const mcpConfig = {
805
+ path: ".cursor/mcp.json",
806
+ content: JSON.stringify({
807
+ mcpServers: {
808
+ [serverName]: { command: "npx", args: npxArgs }
809
+ }
810
+ }, null, 2)
811
+ };
812
+ const payload = {
813
+ iconLibrary: {
814
+ package: iconPkgs.web,
815
+ nativePackage: iconPkgs.native,
816
+ performanceHint: "Use individual SVG imports for tree-shaking; avoid loading entire icon libraries or icon fonts."
817
+ },
818
+ fonts: {
819
+ families: fontNames,
820
+ performanceHint: "Implement self-hosted fonts and local @font-face declarations; avoid external CDN links; use font-display: swap."
821
+ },
822
+ skill: {
823
+ path: ".cursor/skills/atomix-ds/SKILL.md",
824
+ content: GENERIC_SKILL_MD
825
+ },
826
+ mcpConfig,
827
+ tokenFiles: {
828
+ files: ["tokens.css", "tokens.json"],
829
+ copyInstructions: "Copy tokens.css and tokens.json from the design system export (or use syncTokens) into your project."
830
+ },
831
+ meta: { dsName: data.meta.name, platform: platform ?? void 0, stack: stack ?? void 0 }
832
+ };
833
+ return {
834
+ content: [{
835
+ type: "text",
836
+ text: JSON.stringify(payload, null, 2)
837
+ }]
838
+ };
839
+ }
1861
840
  default:
1862
841
  return {
1863
842
  content: [{
1864
843
  type: "text",
1865
844
  text: JSON.stringify({
1866
845
  error: `Unknown tool: ${name}`,
1867
- availableTools: ["getToken", "listTokens", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncTokens"]
846
+ availableTools: ["getToken", "listTokens", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncTokens", "getDependencies"]
1868
847
  }, null, 2)
1869
848
  }]
1870
849
  };
@@ -1906,6 +885,38 @@ Last updated: ${lastUpdated}`
1906
885
  };
1907
886
  }
1908
887
  });
888
+ var GENERIC_SKILL_MD = `---
889
+ name: atomix-ds
890
+ description: Use this design system when editing UI, design system files, or when the user asks to follow the project's design system. Fetch rules dynamically via MCP.
891
+ ---
892
+
893
+ # Atomix Design System
894
+
895
+ ## When to use
896
+
897
+ - Editing UI components, pages, or styles
898
+ - Working in design system or token files
899
+ - User asks to follow the project's design system
900
+
901
+ ## How to use
902
+
903
+ 1. Call MCP tool **getAIToolRules** with the tool id that matches your **current AI coding environment**:
904
+ - **cursor** (Cursor IDE)
905
+ - **windsurf** (Windsurf)
906
+ - **copilot** (Copilot)
907
+ - **cline** (Cline)
908
+ - **continue** (Continue)
909
+ - **zed** (Zed)
910
+ - **generic** (other tools)
911
+
912
+ Example: \`getAIToolRules({ tool: "cursor" })\` when in Cursor.
913
+
914
+ 2. Alternatively use the **design-system-rules** prompt or the resource \`atomix://rules/<tool>\` with the same tool id.
915
+
916
+ 3. Apply the returned rules (tokens, governance) when generating or editing code.
917
+
918
+ 4. Ensure Atomix MCP is configured with the project's \`--ds-id\` (config path varies by platform: e.g. \`.cursor/mcp.json\`, \`.windsurf/mcp.json\`).
919
+ `;
1909
920
  var AI_TOOLS = ["cursor", "copilot", "windsurf", "cline", "continue", "zed", "generic"];
1910
921
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
1911
922
  try {
@@ -1913,7 +924,7 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1913
924
  const stats = getTokenStats(data);
1914
925
  const resources = [
1915
926
  {
1916
- uri: "atomix://welcome",
927
+ uri: "atomix://hello",
1917
928
  name: `Welcome to ${data.meta.name}`,
1918
929
  description: `Design system overview with ${stats.total} tokens and ${stats.governanceRules} governance rules`,
1919
930
  mimeType: "text/markdown"
@@ -1968,7 +979,7 @@ Get your DS ID from: https://atomixstudio.eu/ds/[your-ds-id]`
1968
979
  }
1969
980
  const data = await fetchDesignSystemForMCP();
1970
981
  const stats = getTokenStats(data);
1971
- if (uri === "atomix://welcome") {
982
+ if (uri === "atomix://hello") {
1972
983
  const welcome = generateWelcomeMessage(data, stats);
1973
984
  return {
1974
985
  contents: [{
@@ -2005,7 +1016,7 @@ Get your DS ID from: https://atomixstudio.eu/ds/[your-ds-id]`
2005
1016
  server.setRequestHandler(ListPromptsRequestSchema, async () => {
2006
1017
  const prompts = [
2007
1018
  {
2008
- name: "welcome",
1019
+ name: "hello",
2009
1020
  description: "Get started with this design system - shows overview, available tokens, and tools. Run this first!"
2010
1021
  },
2011
1022
  {
@@ -2058,6 +1069,14 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2058
1069
  {
2059
1070
  name: "refactor",
2060
1071
  description: "Migrate deprecated tokens in codebase. Scans for deprecated tokens marked by /sync and suggests replacements. Run after /sync to update code."
1072
+ },
1073
+ {
1074
+ name: "install",
1075
+ description: "Suggest dependencies for this design system (icons, fonts, SKILL.md, MCP config, tokens). Asks before installing. Run getDependencies then follow phased instructions.",
1076
+ arguments: [
1077
+ { name: "platform", description: "Target platform (web, ios, android). Optional; ask user if unknown.", required: false },
1078
+ { name: "stack", description: "Stack or framework (e.g. react, vue, next, swift, kotlin). Optional.", required: false }
1079
+ ]
2061
1080
  }
2062
1081
  ];
2063
1082
  return { prompts };
@@ -2170,10 +1189,10 @@ Configure the MCP server in your Cursor settings, then restart Cursor.`
2170
1189
  return lines.join("\n");
2171
1190
  };
2172
1191
  switch (name) {
2173
- case "welcome": {
1192
+ case "hello": {
2174
1193
  const welcome = generateWelcomeMessage(data, stats);
2175
1194
  return {
2176
- description: `Welcome to ${data.meta.name} Design System`,
1195
+ description: `Hello \u2014 ${data.meta.name} Design System`,
2177
1196
  messages: [
2178
1197
  {
2179
1198
  role: "user",
@@ -2492,29 +1511,71 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
2492
1511
  ]
2493
1512
  };
2494
1513
  }
1514
+ case "install": {
1515
+ const installInstructions = `You are running **atomix/install**. Follow these phases in order; do not skip any phase.
1516
+
1517
+ ## Phase 1 \u2013 Resolve platform and stack
1518
+
1519
+ - If the project clearly indicates platform (e.g. package.json + web deps, or build.gradle / Android, or Xcode / iOS), infer \`platform\` (e.g. web, ios, android) and optionally \`stack\` (e.g. react, next, vue, swift, kotlin).
1520
+ - If the project gives **no hint** (blank repo, empty folder, or ambiguous): **Ask the user:** "Which platform are you building for? (e.g. web, Android, iOS)" and, if relevant, "Which stack or framework? (e.g. React, Vue, Next, Swift, Kotlin)." Do **not** assume web or any default.
1521
+ - Proceed only once platform (and optionally stack) is known or confirmed.
1522
+
1523
+ ## Phase 2 \u2013 Get suggested dependencies
1524
+
1525
+ - Call MCP tool **getDependencies** with the resolved \`platform\` (and optional \`stack\`). Use the parameter names the tool expects (e.g. platform, stack).
1526
+ - If the call fails (e.g. MCP not connected or no ds-id), tell the user: "Atomix MCP is not connected or design system ID is missing. Configure MCP with --ds-id and try again."
1527
+
1528
+ ## Phase 3 \u2013 Scan codebase and build suggestion list
1529
+
1530
+ - Scan the repo for: package.json (or equivalent), existing skill path (e.g. .cursor/skills), presence of tokens.css or project token file, font imports/config, icon package usage.
1531
+ - Build two lists: **Suggested dependencies** (from getDependencies, minus what is already present) and **Already present**. Include: icon package, fonts, SKILL path, MCP config, token files.
1532
+ - Do **not** install, copy, or write any file in this phase.
1533
+
1534
+ ## Phase 4 \u2013 Present list and ask before install
1535
+
1536
+ - Reply with: "Suggested dependencies: \u2026" and "Already present: \u2026" and state: "Do not install or copy anything until you confirm. Would you like me to install or add these?"
1537
+ - If the user says yes, perform only the steps they approved. If no or only part, perform only that.
1538
+
1539
+ ## Phase 5 \u2013 Optional: suggest global styles (if missing)
1540
+
1541
+ - After presenting the dependency list (Phase 4), or after the user has approved install, check whether the project has **global styles that use the design system tokens** (e.g. typography scale, semantic color classes, or theme file referencing DS tokens). If **not** (e.g. no globals.css using tokens, or blank project), ask the user **verbatim**:
1542
+
1543
+ "Your project doesn't appear to have global styles that use the design system tokens (e.g. semantic typography scale or semantic color classes). Would you like me to suggest or generate a minimal set (e.g. typography scale + semantic utilities) so you can develop consistently with the DS?"
1544
+
1545
+ - If the user says yes, suggest or generate a minimal set; do not overwrite existing global styles without confirmation.
1546
+
1547
+ ## Phase 6 \u2013 Report what was created
1548
+
1549
+ - After any install or copy steps (and optional global-styles step), list **what was created or updated** (e.g. "Installed: lucide-react. Added: .cursor/skills/atomix-ds/SKILL.md. Updated: .cursor/mcp.json. Copied: tokens.css."). If global styles were added, include that.
1550
+
1551
+ ---
1552
+
1553
+ Execute Phase 1 now (resolve or ask platform/stack), then Phase 2 (call getDependencies), then continue through Phase 6.`;
1554
+ return {
1555
+ description: "Suggest dependencies for this design system (atomix/install)",
1556
+ messages: [
1557
+ {
1558
+ role: "user",
1559
+ content: {
1560
+ type: "text",
1561
+ text: installInstructions
1562
+ }
1563
+ }
1564
+ ]
1565
+ };
1566
+ }
2495
1567
  default:
2496
1568
  throw new Error(`Unknown prompt: ${name}`);
2497
1569
  }
2498
1570
  });
2499
1571
  function generateWelcomeMessage(data, stats) {
2500
- const toolsList = [
2501
- "getToken - Get a specific token by path",
2502
- "listTokens - List all tokens in a category",
2503
- "searchTokens - Search tokens by name or value",
2504
- "validateUsage - Check if a CSS value follows the design system",
2505
- "getAIToolRules - Generate AI coding rules for any tool",
2506
- "exportMCPConfig - Generate MCP config for AI tools",
2507
- "getSetupInstructions - Get setup guide for specific tools",
2508
- "syncTokens - Sync tokens to a local file (css, scss, swift, kotlin, dart, etc.)"
2509
- ];
2510
- const categoryBreakdown = Object.entries(stats.byCategory).map(([cat, count]) => ` - ${cat}: ${count} tokens`).join("\n");
1572
+ const tokenSummary = Object.entries(stats.byCategory).map(([cat, count]) => `${cat}: ${count}`).join(", ");
2511
1573
  const asciiArt = `
2512
1574
  \`\`\`
2513
1575
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2514
1576
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2515
1577
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2516
1578
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2517
- \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2518
1579
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2519
1580
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2520
1581
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
@@ -2532,7 +1593,6 @@ function generateWelcomeMessage(data, stats) {
2532
1593
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2533
1594
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2534
1595
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2535
- \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2536
1596
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2537
1597
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
2538
1598
  \u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198\u2198
@@ -2541,8 +1601,6 @@ function generateWelcomeMessage(data, stats) {
2541
1601
  return `${asciiArt}
2542
1602
  # Welcome to ${data.meta.name}
2543
1603
 
2544
- Your design system is connected and ready to use.
2545
-
2546
1604
  ## Design System Overview
2547
1605
 
2548
1606
  | Metric | Value |
@@ -2554,57 +1612,39 @@ Your design system is connected and ready to use.
2554
1612
  | Governance Rules | ${stats.governanceRules} |
2555
1613
  | Version | ${data.meta.version || 1} |
2556
1614
 
2557
- ### Token Breakdown
2558
-
2559
- ${categoryBreakdown}
1615
+ ### Token breakdown
2560
1616
 
2561
- ## Available Tools
1617
+ ${tokenSummary}
2562
1618
 
2563
- ${toolsList.map((t, i) => `${i + 1}. **${t.split(" - ")[0]}** - ${t.split(" - ")[1]}`).join("\n")}
2564
-
2565
- ## Quick Start
2566
-
2567
- ### Get a Token
2568
- \`\`\`
2569
- Use getToken with path "colors.static.brand.primary"
2570
- \`\`\`
2571
-
2572
- ### List All Spacing Tokens
2573
- \`\`\`
2574
- Use listTokens with category "spacing"
2575
- \`\`\`
2576
-
2577
- ### Validate a Hardcoded Value
2578
- \`\`\`
2579
- Use validateUsage with value "#ff0000" and context "color"
2580
- \`\`\`
2581
-
2582
- ## Resources Available
1619
+ ---
2583
1620
 
2584
- This MCP server exposes rules files as resources that your AI tool can read:
1621
+ ## Essential commands
2585
1622
 
2586
- | Resource | Description |
2587
- |----------|-------------|
2588
- | \`atomix://welcome\` | This welcome message |
2589
- | \`atomix://rules/cursor\` | .cursorrules file content |
2590
- | \`atomix://rules/copilot\` | GitHub Copilot instructions |
2591
- | \`atomix://rules/windsurf\` | .windsurfrules file content |
2592
- | \`atomix://rules/cline\` | .clinerules file content |
2593
- | \`atomix://rules/continue\` | Continue rules |
2594
- | \`atomix://rules/zed\` | Zed assistant rules |
2595
- | \`atomix://rules/generic\` | Generic AI guidelines |
1623
+ | Command | What to expect |
1624
+ |---------|----------------|
1625
+ | **install** | Scans your repo to set up global styles (if none), icons and fonts. Safe: Won't install until you confirm. |
1626
+ | **sync** | Syncs tokens to a local file. Adds new, updates existing, marks deprecated. Safe: No changes until you confirm. |
1627
+ | **refactor** | Scans codebase for deprecated tokens (after sync), suggests replacements. Run after sync to migrate code. |
2596
1628
 
2597
- ## Checking for Updates
1629
+ ## Helper commands
2598
1630
 
2599
- Your design system may be updated over time. To ensure you have the latest tokens:
1631
+ | Command | What to expect |
1632
+ |---------|----------------|
1633
+ | **design-system-rules** | Returns governance rules for your AI tools. |
1634
+ | **spacing** | Table of all spacing tokens and values. |
1635
+ | **color** | Table of color tokens with light/dark values. |
1636
+ | **typography** | Table of typography tokens and values. |
1637
+ | **radius** | Table of border radius tokens. |
1638
+ | **shadow** | Table of shadow/elevation tokens. |
1639
+ | **border** | Table of border width tokens. |
1640
+ | **sizing** | Table of height and icon size tokens. |
1641
+ | **motion** | Table of duration and easing tokens. |
2600
1642
 
2601
- 1. **Check version**: The current version is ${data.meta.version || 1}
2602
- 2. **Refresh tokens**: Restart the MCP server to fetch latest tokens
2603
- 3. **View changes**: Visit https://atomixstudio.eu/ds/${dsId} to see recent changes
1643
+ **Suggested next step:** Run **install** to set up global styles, icons, fonts, and token files; the AI will list options and ask before adding anything.
2604
1644
 
2605
1645
  ---
2606
1646
 
2607
- *Powered by Atomix Studio - https://atomixstudio.eu*
1647
+ *Atomix Studio \u2014 https://atomixstudio.eu | DS: https://atomixstudio.eu/ds/${dsId}*
2608
1648
  `;
2609
1649
  }
2610
1650
  async function startServer() {