@atomixstudio/mcp 1.0.9 → 1.0.11

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,29 +11,1281 @@ import {
11
11
  ListPromptsRequestSchema,
12
12
  GetPromptRequestSchema
13
13
  } from "@modelcontextprotocol/sdk/types.js";
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";
35
- import * as path from "path";
14
+
15
+ // node_modules/.pnpm/@atomixstudio+sync-core@file+..+atomix-sync-core/node_modules/@atomixstudio/sync-core/dist/index.js
36
16
  import * as fs from "fs";
17
+ import * as path from "path";
18
+ import * as path3 from "path";
19
+ function generateETag(meta) {
20
+ const ts = meta.updatedAt ?? meta.exportedAt ?? "";
21
+ const hash = `${meta.version}-${ts}`;
22
+ return `"v${hash}"`;
23
+ }
24
+ function compareDesignSystems(cached, fresh) {
25
+ const changes = {
26
+ hasChanges: false,
27
+ versionChanged: false,
28
+ timestampChanged: false,
29
+ addedTokens: [],
30
+ removedTokens: [],
31
+ modifiedTokens: [],
32
+ governanceChanged: false,
33
+ summary: ""
34
+ };
35
+ if (cached.meta.version !== fresh.meta.version) {
36
+ changes.versionChanged = true;
37
+ changes.hasChanges = true;
38
+ }
39
+ if (cached.meta.exportedAt !== fresh.meta.exportedAt) {
40
+ changes.timestampChanged = true;
41
+ changes.hasChanges = true;
42
+ }
43
+ const cachedVars = Object.keys(cached.cssVariables);
44
+ const freshVars = Object.keys(fresh.cssVariables);
45
+ for (const varName of freshVars) {
46
+ if (!(varName in cached.cssVariables)) {
47
+ changes.addedTokens.push(varName);
48
+ changes.hasChanges = true;
49
+ }
50
+ }
51
+ for (const varName of cachedVars) {
52
+ if (!(varName in fresh.cssVariables)) {
53
+ changes.removedTokens.push(varName);
54
+ changes.hasChanges = true;
55
+ }
56
+ }
57
+ for (const varName of cachedVars) {
58
+ if (varName in fresh.cssVariables) {
59
+ const oldValue = cached.cssVariables[varName];
60
+ const newValue = fresh.cssVariables[varName];
61
+ if (oldValue !== newValue) {
62
+ changes.modifiedTokens.push({
63
+ token: varName,
64
+ oldValue,
65
+ newValue
66
+ });
67
+ changes.hasChanges = true;
68
+ }
69
+ }
70
+ }
71
+ const cachedDark = cached.tokens?.colors?.modes;
72
+ const freshDark = fresh.tokens?.colors?.modes;
73
+ const cachedDarkColors = cachedDark?.dark || {};
74
+ const freshDarkColors = freshDark?.dark || {};
75
+ const cachedDarkKeys = Object.keys(cachedDarkColors);
76
+ const freshDarkKeys = Object.keys(freshDarkColors);
77
+ const firstColorVar = cachedVars.find((v) => v.includes("-color-")) || freshVars.find((v) => v.includes("-color-"));
78
+ const prefixMatch = firstColorVar?.match(/^(--[a-z]+-color-)/);
79
+ const colorPrefix = prefixMatch ? prefixMatch[1] : "--atmx-color-";
80
+ for (const key of freshDarkKeys) {
81
+ if (!(key in cachedDarkColors)) {
82
+ const cssVarName = `${colorPrefix}${key}`;
83
+ if (!changes.addedTokens.includes(cssVarName)) {
84
+ changes.addedTokens.push(cssVarName);
85
+ changes.hasChanges = true;
86
+ }
87
+ }
88
+ }
89
+ for (const key of cachedDarkKeys) {
90
+ if (!(key in freshDarkColors)) {
91
+ const cssVarName = `${colorPrefix}${key}`;
92
+ if (!changes.removedTokens.includes(cssVarName)) {
93
+ changes.removedTokens.push(cssVarName);
94
+ changes.hasChanges = true;
95
+ }
96
+ }
97
+ }
98
+ for (const key of cachedDarkKeys) {
99
+ if (key in freshDarkColors) {
100
+ const oldValue = cachedDarkColors[key];
101
+ const newValue = freshDarkColors[key];
102
+ if (oldValue !== newValue) {
103
+ const cssVarName = `${colorPrefix}${key}`;
104
+ const existingIndex = changes.modifiedTokens.findIndex((m) => m.token === cssVarName);
105
+ if (existingIndex >= 0) {
106
+ changes.modifiedTokens[existingIndex].oldValue += ` | dark: ${oldValue}`;
107
+ changes.modifiedTokens[existingIndex].newValue += ` | dark: ${newValue}`;
108
+ } else {
109
+ changes.modifiedTokens.push({
110
+ token: `${cssVarName} (dark mode)`,
111
+ oldValue,
112
+ newValue
113
+ });
114
+ changes.hasChanges = true;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ const cachedGovernance = cached.governance || { rules: [], categories: {} };
120
+ const freshGovernance = fresh.governance || { rules: [], categories: {} };
121
+ const cachedRules = cachedGovernance.rules || [];
122
+ const freshRules = freshGovernance.rules || [];
123
+ const cachedCategories = cachedGovernance.categories || {};
124
+ const freshCategories = freshGovernance.categories || {};
125
+ const cachedRulesStr = JSON.stringify(cachedRules.sort());
126
+ const freshRulesStr = JSON.stringify(freshRules.sort());
127
+ const cachedCategoriesStr = JSON.stringify(cachedCategories, Object.keys(cachedCategories).sort());
128
+ const freshCategoriesStr = JSON.stringify(freshCategories, Object.keys(freshCategories).sort());
129
+ if (cachedRulesStr !== freshRulesStr || cachedCategoriesStr !== freshCategoriesStr) {
130
+ changes.governanceChanged = true;
131
+ changes.hasChanges = true;
132
+ }
133
+ const summaryLines = [];
134
+ if (!changes.hasChanges) {
135
+ summaryLines.push("\u2713 No changes detected - design system is up to date");
136
+ } else {
137
+ summaryLines.push("\u{1F4E6} Design System Changes Detected:");
138
+ summaryLines.push("");
139
+ if (changes.versionChanged) {
140
+ summaryLines.push(` Version: ${cached.meta.version} \u2192 ${fresh.meta.version}`);
141
+ }
142
+ if (changes.timestampChanged) {
143
+ const cachedDate = new Date(cached.meta.exportedAt).toLocaleString();
144
+ const freshDate = new Date(fresh.meta.exportedAt).toLocaleString();
145
+ summaryLines.push(` Published: ${cachedDate} \u2192 ${freshDate}`);
146
+ }
147
+ if (changes.addedTokens.length > 0) {
148
+ summaryLines.push(` \u2795 Added: ${changes.addedTokens.length} token(s)`);
149
+ if (changes.addedTokens.length <= 10) {
150
+ changes.addedTokens.forEach((token) => {
151
+ summaryLines.push(` - ${token}`);
152
+ });
153
+ } else {
154
+ changes.addedTokens.slice(0, 10).forEach((token) => {
155
+ summaryLines.push(` - ${token}`);
156
+ });
157
+ summaryLines.push(` ... and ${changes.addedTokens.length - 10} more`);
158
+ }
159
+ }
160
+ if (changes.removedTokens.length > 0) {
161
+ summaryLines.push(` \u2796 Removed: ${changes.removedTokens.length} token(s)`);
162
+ if (changes.removedTokens.length <= 10) {
163
+ changes.removedTokens.forEach((token) => {
164
+ summaryLines.push(` - ${token}`);
165
+ });
166
+ } else {
167
+ changes.removedTokens.slice(0, 10).forEach((token) => {
168
+ summaryLines.push(` - ${token}`);
169
+ });
170
+ summaryLines.push(` ... and ${changes.removedTokens.length - 10} more`);
171
+ }
172
+ }
173
+ if (changes.modifiedTokens.length > 0) {
174
+ summaryLines.push(` \u{1F504} Modified: ${changes.modifiedTokens.length} token(s)`);
175
+ if (changes.modifiedTokens.length <= 10) {
176
+ changes.modifiedTokens.forEach(({ token, oldValue, newValue }) => {
177
+ summaryLines.push(` ${token}:`);
178
+ summaryLines.push(` - ${oldValue}`);
179
+ summaryLines.push(` + ${newValue}`);
180
+ });
181
+ } else {
182
+ changes.modifiedTokens.slice(0, 5).forEach(({ token, oldValue, newValue }) => {
183
+ summaryLines.push(` ${token}:`);
184
+ summaryLines.push(` - ${oldValue}`);
185
+ summaryLines.push(` + ${newValue}`);
186
+ });
187
+ summaryLines.push(` ... and ${changes.modifiedTokens.length - 5} more`);
188
+ }
189
+ }
190
+ if (changes.governanceChanged) {
191
+ summaryLines.push(` \u{1F4DD} AI guide updated`);
192
+ }
193
+ }
194
+ changes.summary = summaryLines.join("\n");
195
+ return changes;
196
+ }
197
+ function detectGovernanceChangesByFoundation(cached, fresh) {
198
+ const changes = [];
199
+ const cachedGov = cached.governance || { rules: [], categories: {} };
200
+ const freshGov = fresh.governance || { rules: [], categories: {} };
201
+ const cachedRulesStr = JSON.stringify((cachedGov.rules || []).sort());
202
+ const freshRulesStr = JSON.stringify((freshGov.rules || []).sort());
203
+ if (cachedRulesStr !== freshRulesStr) {
204
+ changes.push("general");
205
+ }
206
+ const cachedCategories = cachedGov.categories || {};
207
+ const freshCategories = freshGov.categories || {};
208
+ const allFoundationKeys = /* @__PURE__ */ new Set([
209
+ ...Object.keys(cachedCategories),
210
+ ...Object.keys(freshCategories)
211
+ ]);
212
+ for (const foundation of allFoundationKeys) {
213
+ const cachedRules = cachedCategories[foundation] || [];
214
+ const freshRules = freshCategories[foundation] || [];
215
+ const cachedRulesStr2 = JSON.stringify(cachedRules.sort());
216
+ const freshRulesStr2 = JSON.stringify(freshRules.sort());
217
+ if (cachedRulesStr2 !== freshRulesStr2) {
218
+ changes.push(foundation);
219
+ }
220
+ }
221
+ return changes;
222
+ }
223
+ async function fetchDesignSystem(options) {
224
+ const { dsId: dsId2, apiKey: apiKey2, apiBase: apiBase2 = "https://atomixstudio.eu", etag, forceRefresh = false } = options;
225
+ if (!dsId2) {
226
+ throw new Error("Missing dsId. Usage: fetchDesignSystem({ dsId: '...' })");
227
+ }
228
+ const url = `${apiBase2}/api/ds/${dsId2}/tokens?format=export`;
229
+ const headers = {
230
+ "Content-Type": "application/json"
231
+ };
232
+ if (apiKey2) {
233
+ headers["x-api-key"] = apiKey2;
234
+ }
235
+ if (etag) {
236
+ headers["if-none-match"] = etag;
237
+ }
238
+ const response = await fetch(url, { headers });
239
+ if (response.status === 304) {
240
+ return {
241
+ data: null,
242
+ etag,
243
+ status: 304
244
+ };
245
+ }
246
+ if (!response.ok) {
247
+ const text = await response.text();
248
+ throw new Error(`Failed to fetch design system: ${response.status} ${text}`);
249
+ }
250
+ const data = await response.json();
251
+ const responseETag = response.headers.get("etag");
252
+ const finalETag = responseETag || generateETag(data.meta);
253
+ const designSystemData = {
254
+ tokens: data.tokens,
255
+ cssVariables: data.cssVariables,
256
+ governance: data.governance,
257
+ meta: data.meta
258
+ };
259
+ return {
260
+ data: designSystemData,
261
+ etag: finalETag,
262
+ status: 200
263
+ };
264
+ }
265
+ function diffTokens(oldContent, newCssVars, format, newDarkVars) {
266
+ const added = [];
267
+ const modified = [];
268
+ const removed = [];
269
+ const addedDark = [];
270
+ const modifiedDark = [];
271
+ const removedDark = [];
272
+ if (format === "css" || format === "scss" || format === "less") {
273
+ const rootMatch = oldContent.match(/:root\s*\{([^}]*)\}/);
274
+ const darkMatch = oldContent.match(/\.dark[^{]*\{([^}]*)\}/);
275
+ const oldLightVars = {};
276
+ if (rootMatch) {
277
+ const rootContent = rootMatch[1];
278
+ const varRegex = /(--[\w-]+):\s*([^;]+);/g;
279
+ let match;
280
+ while ((match = varRegex.exec(rootContent)) !== null) {
281
+ oldLightVars[match[1]] = match[2].trim();
282
+ }
283
+ } else {
284
+ const varRegex = /(--[\w-]+):\s*([^;]+);/g;
285
+ let match;
286
+ while ((match = varRegex.exec(oldContent)) !== null) {
287
+ if (!(match[1] in oldLightVars)) {
288
+ oldLightVars[match[1]] = match[2].trim();
289
+ }
290
+ }
291
+ }
292
+ const oldDarkVars = {};
293
+ if (darkMatch) {
294
+ const darkContent = darkMatch[1];
295
+ const varRegex = /(--[\w-]+):\s*([^;]+);/g;
296
+ let match;
297
+ while ((match = varRegex.exec(darkContent)) !== null) {
298
+ oldDarkVars[match[1]] = match[2].trim();
299
+ }
300
+ }
301
+ for (const [key, value] of Object.entries(newCssVars)) {
302
+ if (!(key in oldLightVars)) {
303
+ added.push(key);
304
+ } else if (oldLightVars[key] !== value) {
305
+ modified.push({ key, old: oldLightVars[key], new: value });
306
+ }
307
+ }
308
+ for (const key of Object.keys(oldLightVars)) {
309
+ if (!(key in newCssVars)) {
310
+ removed.push(key);
311
+ }
312
+ }
313
+ if (newDarkVars && Object.keys(newDarkVars).length > 0) {
314
+ const firstKey = Object.keys(newCssVars)[0] || "--atmx-";
315
+ const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
316
+ const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
317
+ for (const [key, value] of Object.entries(newDarkVars)) {
318
+ const cssVarName = `${prefix}color-${key}`;
319
+ if (!(cssVarName in oldDarkVars)) {
320
+ addedDark.push(cssVarName);
321
+ } else if (oldDarkVars[cssVarName] !== value) {
322
+ modifiedDark.push({ key: cssVarName, old: oldDarkVars[cssVarName], new: value });
323
+ }
324
+ }
325
+ for (const key of Object.keys(oldDarkVars)) {
326
+ const shortKey = key.replace(new RegExp(`^${prefix}color-`), "");
327
+ if (!(shortKey in newDarkVars)) {
328
+ removedDark.push(key);
329
+ }
330
+ }
331
+ }
332
+ }
333
+ return { added, modified, removed, addedDark, modifiedDark, removedDark };
334
+ }
335
+ async function syncRulesFiles(options) {
336
+ const { dsId: dsId2, apiKey: apiKey2, apiBase: apiBase2 = "https://atomixstudio.eu", rulesDir = process.cwd() } = options;
337
+ const rulesDirResolved = path.resolve(process.cwd(), rulesDir);
338
+ const toolsToSync = [
339
+ { tool: "cursor", filename: ".cursorrules" },
340
+ { tool: "windsurf", filename: ".windsurfrules" },
341
+ { tool: "cline", filename: ".clinerules" },
342
+ { tool: "continue", filename: ".continuerules" },
343
+ { tool: "copilot", filename: "copilot-instructions.md", dir: ".github" },
344
+ { tool: "generic", filename: "AI_GUIDELINES.md" }
345
+ ];
346
+ const existingTools = toolsToSync.filter((t) => {
347
+ const filePath = t.dir ? path.join(rulesDirResolved, t.dir, t.filename) : path.join(rulesDirResolved, t.filename);
348
+ return fs.existsSync(filePath);
349
+ });
350
+ const toolsToWrite = existingTools.length > 0 ? existingTools : [{ tool: "cursor", filename: ".cursorrules" }];
351
+ const results = [];
352
+ for (const { tool, filename, dir } of toolsToWrite) {
353
+ try {
354
+ const rulesUrl = `${apiBase2}/api/ds/${dsId2}/rules?format=${tool}`;
355
+ const headers = { "Content-Type": "application/json" };
356
+ if (apiKey2) headers["x-api-key"] = apiKey2;
357
+ const response = await fetch(rulesUrl, { headers });
358
+ if (!response.ok) {
359
+ results.push({
360
+ tool,
361
+ filename,
362
+ path: dir ? `${dir}/${filename}` : filename,
363
+ success: false,
364
+ error: `Failed to fetch ${tool} rules: ${response.status}`
365
+ });
366
+ continue;
367
+ }
368
+ const rulesData = await response.json();
369
+ if (!rulesData.content) {
370
+ results.push({
371
+ tool,
372
+ filename,
373
+ path: dir ? `${dir}/${filename}` : filename,
374
+ success: false,
375
+ error: `No content for ${tool} rules`
376
+ });
377
+ continue;
378
+ }
379
+ const targetDir = dir ? path.join(rulesDirResolved, dir) : rulesDirResolved;
380
+ if (!fs.existsSync(targetDir)) {
381
+ fs.mkdirSync(targetDir, { recursive: true });
382
+ }
383
+ const filePath = path.join(targetDir, filename);
384
+ fs.writeFileSync(filePath, rulesData.content);
385
+ results.push({
386
+ tool,
387
+ filename,
388
+ path: dir ? `${dir}/${filename}` : filename,
389
+ success: true
390
+ });
391
+ } catch (error) {
392
+ results.push({
393
+ tool,
394
+ filename,
395
+ path: dir ? `${dir}/${filename}` : filename,
396
+ success: false,
397
+ error: error instanceof Error ? error.message : String(error)
398
+ });
399
+ }
400
+ }
401
+ return results;
402
+ }
403
+ function generateCSSOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
404
+ const lines = [
405
+ "/* Atomix Design System Tokens",
406
+ " * Auto-generated - do not edit manually",
407
+ ` * Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
408
+ " *",
409
+ " * WARNING: This file is completely rewritten on each sync.",
410
+ " * Only CSS custom properties (variables) are preserved.",
411
+ " * Custom CSS rules (selectors, classes, etc.) will be lost.",
412
+ " * Keep custom CSS in a separate file.",
413
+ " */",
414
+ "",
415
+ "/* Light mode (default) */",
416
+ ":root {"
417
+ ];
418
+ const allVars = /* @__PURE__ */ new Map();
419
+ for (const [key, value] of Object.entries(cssVariables)) {
420
+ allVars.set(key, { value, deprecated: false });
421
+ }
422
+ for (const [key, value] of deprecatedTokens.entries()) {
423
+ allVars.set(key, { value, deprecated: true });
424
+ }
425
+ const sortedKeys = Array.from(allVars.keys()).sort();
426
+ for (const key of sortedKeys) {
427
+ const { value, deprecated } = allVars.get(key);
428
+ if (deprecated) {
429
+ lines.push(` /* DEPRECATED */ ${key}: ${value};`);
430
+ } else {
431
+ lines.push(` ${key}: ${value};`);
432
+ }
433
+ }
434
+ lines.push("}");
435
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
436
+ lines.push("");
437
+ lines.push("/* Dark mode */");
438
+ lines.push(".dark,");
439
+ lines.push('[data-theme="dark"] {');
440
+ const firstKey = sortedKeys[0] || "--atmx-";
441
+ const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
442
+ const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
443
+ const sortedDarkKeys = Object.keys(darkModeColors).sort();
444
+ for (const key of sortedDarkKeys) {
445
+ lines.push(` ${prefix}color-${key}: ${darkModeColors[key]};`);
446
+ }
447
+ lines.push("}");
448
+ }
449
+ lines.push("");
450
+ return lines.join("\n");
451
+ }
452
+ function generateSCSSOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
453
+ const lines = [
454
+ "// Atomix Design System Tokens",
455
+ "// Auto-generated - do not edit manually",
456
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
457
+ "",
458
+ "// CSS Custom Properties (for use in CSS/HTML)",
459
+ "// Light mode (default)",
460
+ ":root {"
461
+ ];
462
+ const allVars = /* @__PURE__ */ new Map();
463
+ for (const [key, value] of Object.entries(cssVariables)) {
464
+ allVars.set(key, { value, deprecated: false });
465
+ }
466
+ for (const [key, value] of deprecatedTokens.entries()) {
467
+ allVars.set(key, { value, deprecated: true });
468
+ }
469
+ const sortedKeys = Array.from(allVars.keys()).sort();
470
+ for (const key of sortedKeys) {
471
+ const { value, deprecated } = allVars.get(key);
472
+ if (deprecated) {
473
+ lines.push(` // DEPRECATED ${key}: ${value};`);
474
+ } else {
475
+ lines.push(` ${key}: ${value};`);
476
+ }
477
+ }
478
+ lines.push("}");
479
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
480
+ lines.push("");
481
+ lines.push("// Dark mode");
482
+ lines.push(".dark,");
483
+ lines.push('[data-theme="dark"] {');
484
+ const firstKey = sortedKeys[0] || "--atmx-";
485
+ const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
486
+ const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
487
+ for (const [key, value] of Object.entries(darkModeColors)) {
488
+ lines.push(` ${prefix}color-${key}: ${value};`);
489
+ }
490
+ lines.push("}");
491
+ }
492
+ lines.push("");
493
+ lines.push("// SCSS Variables (light mode values)");
494
+ for (const key of sortedKeys) {
495
+ const { value, deprecated } = allVars.get(key);
496
+ const scssVar = "$" + key.replace(/^--/, "");
497
+ if (deprecated) {
498
+ lines.push(`// DEPRECATED ${scssVar}: ${value};`);
499
+ } else {
500
+ lines.push(`${scssVar}: ${value};`);
501
+ }
502
+ }
503
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
504
+ lines.push("");
505
+ lines.push("// SCSS Variables (dark mode values)");
506
+ const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
507
+ const prefixMatch = firstKey.match(/^--([a-z]+)-/);
508
+ const prefix = prefixMatch ? prefixMatch[1] : "atmx";
509
+ for (const [key, value] of Object.entries(darkModeColors)) {
510
+ const scssVar = `$${prefix}-color-${key}-dark`;
511
+ lines.push(`${scssVar}: ${value};`);
512
+ }
513
+ }
514
+ lines.push("");
515
+ return lines.join("\n");
516
+ }
517
+ function generateLessOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
518
+ const lines = [
519
+ "// Atomix Design System Tokens",
520
+ "// Auto-generated - do not edit manually",
521
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
522
+ "",
523
+ "// CSS Custom Properties (for use in CSS/HTML)",
524
+ "// Light mode (default)",
525
+ ":root {"
526
+ ];
527
+ const allVars = /* @__PURE__ */ new Map();
528
+ for (const [key, value] of Object.entries(cssVariables)) {
529
+ allVars.set(key, { value, deprecated: false });
530
+ }
531
+ for (const [key, value] of deprecatedTokens.entries()) {
532
+ allVars.set(key, { value, deprecated: true });
533
+ }
534
+ const sortedKeys = Array.from(allVars.keys()).sort();
535
+ for (const key of sortedKeys) {
536
+ const { value, deprecated } = allVars.get(key);
537
+ if (deprecated) {
538
+ lines.push(` // DEPRECATED ${key}: ${value};`);
539
+ } else {
540
+ lines.push(` ${key}: ${value};`);
541
+ }
542
+ }
543
+ lines.push("}");
544
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
545
+ lines.push("");
546
+ lines.push("// Dark mode");
547
+ lines.push(".dark,");
548
+ lines.push('[data-theme="dark"] {');
549
+ const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
550
+ const prefixMatch = firstKey.match(/^(--[a-z]+-)/);
551
+ const prefix = prefixMatch ? prefixMatch[1] : "--atmx-";
552
+ for (const [key, value] of Object.entries(darkModeColors)) {
553
+ lines.push(` ${prefix}color-${key}: ${value};`);
554
+ }
555
+ lines.push("}");
556
+ }
557
+ lines.push("");
558
+ lines.push("// Less Variables (light mode values)");
559
+ for (const [key, value] of Object.entries(cssVariables)) {
560
+ const lessVar = "@" + key.replace(/^--/, "");
561
+ lines.push(`${lessVar}: ${value};`);
562
+ }
563
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
564
+ lines.push("");
565
+ lines.push("// Less Variables (dark mode values)");
566
+ const firstKey = Object.keys(cssVariables)[0] || "--atmx-";
567
+ const prefixMatch = firstKey.match(/^--([a-z]+)-/);
568
+ const prefix = prefixMatch ? prefixMatch[1] : "atmx";
569
+ for (const [key, value] of Object.entries(darkModeColors)) {
570
+ const lessVar = `@${prefix}-color-${key}-dark`;
571
+ lines.push(`${lessVar}: ${value};`);
572
+ }
573
+ }
574
+ lines.push("");
575
+ return lines.join("\n");
576
+ }
577
+ function generateJSONOutput(tokens) {
578
+ return JSON.stringify(tokens, null, 2);
579
+ }
580
+ function generateTSOutput(tokens) {
581
+ return `// Atomix Design System Tokens
582
+ // Auto-generated - do not edit manually
583
+ // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
584
+
585
+ export const tokens = ${JSON.stringify(tokens, null, 2)} as const;
586
+
587
+ export type Tokens = typeof tokens;
588
+ `;
589
+ }
590
+ function generateJSOutput(tokens) {
591
+ return `// Atomix Design System Tokens
592
+ // Auto-generated - do not edit manually
593
+ // Synced: ${(/* @__PURE__ */ new Date()).toISOString()}
594
+
595
+ export const tokens = ${JSON.stringify(tokens, null, 2)};
596
+ `;
597
+ }
598
+ function toSwiftName(cssVar) {
599
+ return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
600
+ }
601
+ function isColorValue(value) {
602
+ return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
603
+ }
604
+ function hexToSwiftColor(hex) {
605
+ const clean = hex.replace("#", "");
606
+ if (clean.length === 3) {
607
+ const r = clean[0], g = clean[1], b = clean[2];
608
+ return `Color(hex: 0x${r}${r}${g}${g}${b}${b})`;
609
+ }
610
+ if (clean.length === 6) {
611
+ return `Color(hex: 0x${clean})`;
612
+ }
613
+ if (clean.length === 8) {
614
+ const rgb = clean.substring(0, 6);
615
+ const alpha = parseInt(clean.substring(6, 8), 16) / 255;
616
+ return `Color(hex: 0x${rgb}).opacity(${alpha.toFixed(2)})`;
617
+ }
618
+ return `Color.clear // Could not parse: ${hex}`;
619
+ }
620
+ function generateSwiftOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
621
+ const lines = [
622
+ "// Atomix Design System Tokens",
623
+ "// Auto-generated - do not edit manually",
624
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
625
+ "",
626
+ "import SwiftUI",
627
+ "",
628
+ "// MARK: - Color Extension for Hex",
629
+ "extension Color {",
630
+ " init(hex: UInt, alpha: Double = 1.0) {",
631
+ " self.init(",
632
+ " .sRGB,",
633
+ " red: Double((hex >> 16) & 0xFF) / 255.0,",
634
+ " green: Double((hex >> 8) & 0xFF) / 255.0,",
635
+ " blue: Double(hex & 0xFF) / 255.0,",
636
+ " opacity: alpha",
637
+ " )",
638
+ " }",
639
+ "}",
640
+ "",
641
+ "// MARK: - Design Tokens",
642
+ "enum DesignTokens {",
643
+ "",
644
+ " // MARK: Colors (Light Mode)",
645
+ " enum Colors {"
646
+ ];
647
+ const colors = [];
648
+ const spacing = [];
649
+ const typography = [];
650
+ const other = [];
651
+ for (const [key, value] of Object.entries(cssVariables)) {
652
+ const isDeprecated = false;
653
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
654
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
655
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
656
+ else other.push([key, value, isDeprecated]);
657
+ }
658
+ for (const [key, value] of deprecatedTokens.entries()) {
659
+ const isDeprecated = true;
660
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
661
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
662
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
663
+ else other.push([key, value, isDeprecated]);
664
+ }
665
+ colors.sort((a, b) => a[0].localeCompare(b[0]));
666
+ spacing.sort((a, b) => a[0].localeCompare(b[0]));
667
+ typography.sort((a, b) => a[0].localeCompare(b[0]));
668
+ other.sort((a, b) => a[0].localeCompare(b[0]));
669
+ for (const [key, value, isDeprecated] of colors) {
670
+ const name = toSwiftName(key);
671
+ if (isColorValue(value)) {
672
+ if (isDeprecated) {
673
+ lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
674
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
675
+ } else {
676
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
677
+ }
678
+ }
679
+ }
680
+ lines.push(" }");
681
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
682
+ lines.push("");
683
+ lines.push(" // MARK: Colors (Dark Mode)");
684
+ lines.push(" enum ColorsDark {");
685
+ for (const [key, value] of Object.entries(darkModeColors)) {
686
+ const name = `color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}`;
687
+ if (isColorValue(value)) {
688
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
689
+ }
690
+ }
691
+ lines.push(" }");
692
+ }
693
+ lines.push("");
694
+ lines.push(" // MARK: Spacing");
695
+ lines.push(" enum Spacing {");
696
+ for (const [key, value, isDeprecated] of spacing) {
697
+ const name = toSwiftName(key);
698
+ const numValue = parseFloat(value);
699
+ if (!isNaN(numValue)) {
700
+ if (isDeprecated) {
701
+ lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
702
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
703
+ } else {
704
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
705
+ }
706
+ }
707
+ }
708
+ lines.push(" }");
709
+ lines.push("");
710
+ lines.push(" // MARK: Typography");
711
+ lines.push(" enum Typography {");
712
+ for (const [key, value, isDeprecated] of typography) {
713
+ const name = toSwiftName(key);
714
+ if (isDeprecated) {
715
+ lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
716
+ }
717
+ if (key.includes("size")) {
718
+ const numValue = parseFloat(value);
719
+ if (!isNaN(numValue)) {
720
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
721
+ }
722
+ } else if (key.includes("weight")) {
723
+ lines.push(` static let ${name} = "${value}"`);
724
+ } else if (key.includes("family")) {
725
+ lines.push(` static let ${name} = "${value}"`);
726
+ }
727
+ }
728
+ lines.push(" }");
729
+ lines.push("");
730
+ lines.push(" // MARK: Other");
731
+ lines.push(" enum Other {");
732
+ for (const [key, value, isDeprecated] of other) {
733
+ const name = toSwiftName(key);
734
+ if (isDeprecated) {
735
+ lines.push(` @available(*, deprecated, message: "This token has been removed from the design system")`);
736
+ }
737
+ if (isColorValue(value)) {
738
+ lines.push(` static let ${name} = ${hexToSwiftColor(value)}`);
739
+ } else {
740
+ const numValue = parseFloat(value);
741
+ if (!isNaN(numValue)) {
742
+ lines.push(` static let ${name}: CGFloat = ${numValue}`);
743
+ } else {
744
+ lines.push(` static let ${name} = "${value}"`);
745
+ }
746
+ }
747
+ }
748
+ lines.push(" }");
749
+ lines.push("}");
750
+ lines.push("");
751
+ return lines.join("\n");
752
+ }
753
+ function toSwiftName2(cssVar) {
754
+ return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
755
+ }
756
+ function toKotlinName(cssVar) {
757
+ const camel = toSwiftName2(cssVar);
758
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
759
+ }
760
+ function isColorValue2(value) {
761
+ return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
762
+ }
763
+ function hexToKotlinColor(hex) {
764
+ const clean = hex.replace("#", "").toUpperCase();
765
+ if (clean.length === 3) {
766
+ const r = clean[0], g = clean[1], b = clean[2];
767
+ return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
768
+ }
769
+ if (clean.length === 6) {
770
+ return `Color(0xFF${clean})`;
771
+ }
772
+ if (clean.length === 8) {
773
+ const rgb = clean.substring(0, 6);
774
+ const alpha = clean.substring(6, 8);
775
+ return `Color(0x${alpha}${rgb})`;
776
+ }
777
+ return `Color.Transparent // Could not parse: ${hex}`;
778
+ }
779
+ function generateKotlinOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
780
+ const lines = [
781
+ "// Atomix Design System Tokens",
782
+ "// Auto-generated - do not edit manually",
783
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
784
+ "",
785
+ "package com.atomix.design",
786
+ "",
787
+ "import androidx.compose.ui.graphics.Color",
788
+ "import androidx.compose.ui.unit.dp",
789
+ "import androidx.compose.ui.unit.sp",
790
+ "",
791
+ "object DesignTokens {",
792
+ "",
793
+ " // Light mode colors",
794
+ " object Colors {"
795
+ ];
796
+ const colors = [];
797
+ const spacing = [];
798
+ const typography = [];
799
+ const other = [];
800
+ for (const [key, value] of Object.entries(cssVariables)) {
801
+ const isDeprecated = false;
802
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
803
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
804
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
805
+ else other.push([key, value, isDeprecated]);
806
+ }
807
+ for (const [key, value] of deprecatedTokens.entries()) {
808
+ const isDeprecated = true;
809
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
810
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
811
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
812
+ else other.push([key, value, isDeprecated]);
813
+ }
814
+ colors.sort((a, b) => a[0].localeCompare(b[0]));
815
+ spacing.sort((a, b) => a[0].localeCompare(b[0]));
816
+ typography.sort((a, b) => a[0].localeCompare(b[0]));
817
+ other.sort((a, b) => a[0].localeCompare(b[0]));
818
+ for (const [key, value, isDeprecated] of colors) {
819
+ const name = toKotlinName(key);
820
+ if (isColorValue2(value)) {
821
+ if (isDeprecated) {
822
+ lines.push(` @Deprecated("This token has been removed from the design system")`);
823
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
824
+ } else {
825
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
826
+ }
827
+ }
828
+ }
829
+ lines.push(" }");
830
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
831
+ lines.push("");
832
+ lines.push(" // Dark mode colors");
833
+ lines.push(" object ColorsDark {");
834
+ for (const [key, value] of Object.entries(darkModeColors)) {
835
+ const name = `Color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}`;
836
+ if (isColorValue2(value)) {
837
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
838
+ }
839
+ }
840
+ lines.push(" }");
841
+ }
842
+ lines.push("");
843
+ lines.push(" object Spacing {");
844
+ for (const [key, value, isDeprecated] of spacing) {
845
+ const name = toKotlinName(key);
846
+ const numValue = parseFloat(value);
847
+ if (!isNaN(numValue)) {
848
+ if (isDeprecated) {
849
+ lines.push(` @Deprecated("This token has been removed from the design system")`);
850
+ lines.push(` val ${name} = ${numValue}.dp`);
851
+ } else {
852
+ lines.push(` val ${name} = ${numValue}.dp`);
853
+ }
854
+ }
855
+ }
856
+ lines.push(" }");
857
+ lines.push("");
858
+ lines.push(" object Typography {");
859
+ for (const [key, value, isDeprecated] of typography) {
860
+ const name = toKotlinName(key);
861
+ if (isDeprecated) {
862
+ lines.push(` @Deprecated("This token has been removed from the design system")`);
863
+ }
864
+ if (key.includes("size")) {
865
+ const numValue = parseFloat(value);
866
+ if (!isNaN(numValue)) {
867
+ lines.push(` val ${name} = ${numValue}.sp`);
868
+ }
869
+ } else {
870
+ lines.push(` const val ${name} = "${value}"`);
871
+ }
872
+ }
873
+ lines.push(" }");
874
+ lines.push("");
875
+ lines.push(" object Other {");
876
+ for (const [key, value, isDeprecated] of other) {
877
+ const name = toKotlinName(key);
878
+ if (isDeprecated) {
879
+ lines.push(` @Deprecated("This token has been removed from the design system")`);
880
+ }
881
+ if (isColorValue2(value)) {
882
+ lines.push(` val ${name} = ${hexToKotlinColor(value)}`);
883
+ } else {
884
+ const numValue = parseFloat(value);
885
+ if (!isNaN(numValue)) {
886
+ lines.push(` val ${name} = ${numValue}.dp`);
887
+ } else {
888
+ lines.push(` const val ${name} = "${value}"`);
889
+ }
890
+ }
891
+ }
892
+ lines.push(" }");
893
+ lines.push("}");
894
+ lines.push("");
895
+ return lines.join("\n");
896
+ }
897
+ function toSwiftName3(cssVar) {
898
+ return cssVar.replace(/^--atmx-/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
899
+ }
900
+ function toDartName(cssVar) {
901
+ return toSwiftName3(cssVar);
902
+ }
903
+ function isColorValue3(value) {
904
+ return /^#[0-9A-Fa-f]{3,8}$/.test(value) || /^rgb/.test(value) || /^hsl/.test(value);
905
+ }
906
+ function hexToDartColor(hex) {
907
+ const clean = hex.replace("#", "").toUpperCase();
908
+ if (clean.length === 3) {
909
+ const r = clean[0], g = clean[1], b = clean[2];
910
+ return `Color(0xFF${r}${r}${g}${g}${b}${b})`;
911
+ }
912
+ if (clean.length === 6) {
913
+ return `Color(0xFF${clean})`;
914
+ }
915
+ if (clean.length === 8) {
916
+ const rgb = clean.substring(0, 6);
917
+ const alpha = clean.substring(6, 8);
918
+ return `Color(0x${alpha}${rgb})`;
919
+ }
920
+ return `Colors.transparent // Could not parse: ${hex}`;
921
+ }
922
+ function generateDartOutput(cssVariables, darkModeColors, deprecatedTokens = /* @__PURE__ */ new Map()) {
923
+ const lines = [
924
+ "// Atomix Design System Tokens",
925
+ "// Auto-generated - do not edit manually",
926
+ `// Synced: ${(/* @__PURE__ */ new Date()).toISOString()}`,
927
+ "",
928
+ "import 'package:flutter/material.dart';",
929
+ "",
930
+ "class DesignTokens {",
931
+ " DesignTokens._();",
932
+ "",
933
+ " // Colors (Light Mode)"
934
+ ];
935
+ const colors = [];
936
+ const spacing = [];
937
+ const typography = [];
938
+ const other = [];
939
+ for (const [key, value] of Object.entries(cssVariables)) {
940
+ const isDeprecated = false;
941
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
942
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
943
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
944
+ else other.push([key, value, isDeprecated]);
945
+ }
946
+ for (const [key, value] of deprecatedTokens.entries()) {
947
+ const isDeprecated = true;
948
+ if (key.includes("-color-")) colors.push([key, value, isDeprecated]);
949
+ else if (key.includes("-spacing-")) spacing.push([key, value, isDeprecated]);
950
+ else if (key.includes("-typography-") || key.includes("-font-")) typography.push([key, value, isDeprecated]);
951
+ else other.push([key, value, isDeprecated]);
952
+ }
953
+ colors.sort((a, b) => a[0].localeCompare(b[0]));
954
+ spacing.sort((a, b) => a[0].localeCompare(b[0]));
955
+ typography.sort((a, b) => a[0].localeCompare(b[0]));
956
+ other.sort((a, b) => a[0].localeCompare(b[0]));
957
+ for (const [key, value, isDeprecated] of colors) {
958
+ const name = toDartName(key);
959
+ if (isColorValue3(value)) {
960
+ if (isDeprecated) {
961
+ lines.push(` @Deprecated('This token has been removed from the design system')`);
962
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
963
+ } else {
964
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
965
+ }
966
+ }
967
+ }
968
+ if (darkModeColors && Object.keys(darkModeColors).length > 0) {
969
+ lines.push("");
970
+ lines.push(" // Colors (Dark Mode)");
971
+ for (const [key, value] of Object.entries(darkModeColors)) {
972
+ const name = `color${key.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("")}Dark`;
973
+ if (isColorValue3(value)) {
974
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
975
+ }
976
+ }
977
+ }
978
+ lines.push("");
979
+ lines.push(" // Spacing");
980
+ for (const [key, value, isDeprecated] of spacing) {
981
+ const name = toDartName(key);
982
+ const numValue = parseFloat(value);
983
+ if (!isNaN(numValue)) {
984
+ if (isDeprecated) {
985
+ lines.push(` @Deprecated('This token has been removed from the design system')`);
986
+ lines.push(` static const double ${name} = ${numValue};`);
987
+ } else {
988
+ lines.push(` static const double ${name} = ${numValue};`);
989
+ }
990
+ }
991
+ }
992
+ lines.push("");
993
+ lines.push(" // Typography");
994
+ for (const [key, value, isDeprecated] of typography) {
995
+ const name = toDartName(key);
996
+ if (isDeprecated) {
997
+ lines.push(` @Deprecated('This token has been removed from the design system')`);
998
+ }
999
+ if (key.includes("size")) {
1000
+ const numValue = parseFloat(value);
1001
+ if (!isNaN(numValue)) {
1002
+ lines.push(` static const double ${name} = ${numValue};`);
1003
+ }
1004
+ } else {
1005
+ lines.push(` static const String ${name} = '${value}';`);
1006
+ }
1007
+ }
1008
+ lines.push("");
1009
+ lines.push(" // Other");
1010
+ for (const [key, value, isDeprecated] of other) {
1011
+ const name = toDartName(key);
1012
+ if (isDeprecated) {
1013
+ lines.push(` @Deprecated('This token has been removed from the design system')`);
1014
+ }
1015
+ if (isColorValue3(value)) {
1016
+ lines.push(` static const ${name} = ${hexToDartColor(value)};`);
1017
+ } else {
1018
+ const numValue = parseFloat(value);
1019
+ if (!isNaN(numValue)) {
1020
+ lines.push(` static const double ${name} = ${numValue};`);
1021
+ } else {
1022
+ lines.push(` static const String ${name} = '${value}';`);
1023
+ }
1024
+ }
1025
+ }
1026
+ lines.push("}");
1027
+ lines.push("");
1028
+ return lines.join("\n");
1029
+ }
1030
+ function formatSyncResponse(options) {
1031
+ const {
1032
+ data,
1033
+ output,
1034
+ format,
1035
+ dsTokenCount,
1036
+ deprecatedCount,
1037
+ deprecatedTokens,
1038
+ diff,
1039
+ changes = [],
1040
+ fileExists,
1041
+ rulesResults = [],
1042
+ governanceChanges = [],
1043
+ changeSummary,
1044
+ hasRefactorRecommendation = false,
1045
+ deprecatedTokenCount = 0
1046
+ } = options;
1047
+ const lastUpdated = data.meta.exportedAt ? new Date(data.meta.exportedAt).toLocaleString() : "N/A";
1048
+ let response = `\u2713 Synced ${dsTokenCount} tokens to ${output}
1049
+ `;
1050
+ response += `Format: ${format}
1051
+ `;
1052
+ response += `Design System: ${data.meta.name} (v${data.meta.version})
1053
+ `;
1054
+ if (deprecatedCount > 0) {
1055
+ response += `Tokens: ${dsTokenCount} active, ${deprecatedCount} deprecated (run /refactor to migrate)
1056
+ `;
1057
+ } else {
1058
+ response += `Tokens: ${dsTokenCount}
1059
+ `;
1060
+ }
1061
+ response += `Version: ${data.meta.version}
1062
+ `;
1063
+ response += `Last updated: ${lastUpdated}
1064
+ `;
1065
+ if (deprecatedCount > 0) {
1066
+ response += `
1067
+ \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
1068
+ `;
1069
+ response += `\u{1F4CB} DEPRECATED TOKENS (${deprecatedCount})
1070
+ `;
1071
+ 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
1072
+
1073
+ `;
1074
+ response += `These tokens are no longer in the design system but are preserved in your file for backward compatibility.
1075
+ `;
1076
+ response += `Run \`/refactor\` to find and migrate usage in your codebase.
1077
+
1078
+ `;
1079
+ const deprecatedTokenList = Array.from(deprecatedTokens.keys()).sort();
1080
+ deprecatedTokenList.forEach((token, index) => {
1081
+ const value = deprecatedTokens.get(token);
1082
+ response += ` ${index + 1}. ${token}`;
1083
+ if (value) {
1084
+ response += ` = ${value}`;
1085
+ }
1086
+ response += `
1087
+ `;
1088
+ });
1089
+ }
1090
+ const newTokenList = [];
1091
+ if (diff) {
1092
+ newTokenList.push(...diff.added, ...diff.addedDark);
1093
+ }
1094
+ if (changeSummary && changeSummary.includes("\u2795 Added:")) {
1095
+ const addedSection = changeSummary.match(/➕ Added: \d+ token\(s\)([\s\S]*?)(?=➖|🔄|📝|$)/);
1096
+ if (addedSection) {
1097
+ const addedLines = addedSection[0].match(/ - (--[^\n]+)/g) || [];
1098
+ addedLines.forEach((line) => {
1099
+ const token = line.replace(/ - /, "").trim();
1100
+ if (token && !newTokenList.includes(token)) {
1101
+ newTokenList.push(token);
1102
+ }
1103
+ });
1104
+ }
1105
+ }
1106
+ if (newTokenList.length > 0) {
1107
+ response += `
1108
+ \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
1109
+ `;
1110
+ response += `\u2728 NEW TOKENS (${newTokenList.length})
1111
+ `;
1112
+ 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
1113
+
1114
+ `;
1115
+ newTokenList.sort().forEach((token, index) => {
1116
+ response += ` ${index + 1}. ${token}
1117
+ `;
1118
+ });
1119
+ }
1120
+ const modifiedFiles = [output];
1121
+ rulesResults.forEach((result) => {
1122
+ if (result.success) {
1123
+ const fullPath = path3.resolve(process.cwd(), result.path);
1124
+ if (!modifiedFiles.includes(fullPath)) {
1125
+ modifiedFiles.push(fullPath);
1126
+ }
1127
+ }
1128
+ });
1129
+ response += `
1130
+ \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
1131
+ `;
1132
+ response += `\u{1F4C1} MODIFIED FILES (${modifiedFiles.length})
1133
+ `;
1134
+ 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
1135
+
1136
+ `;
1137
+ modifiedFiles.forEach((file, index) => {
1138
+ const relativePath = path3.relative(process.cwd(), file);
1139
+ response += ` ${index + 1}. ${relativePath}
1140
+ `;
1141
+ });
1142
+ if (governanceChanges.length > 0) {
1143
+ response += `
1144
+ \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
1145
+ `;
1146
+ response += `\u{1F4DD} AI RULES CHANGES
1147
+ `;
1148
+ 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
1149
+
1150
+ `;
1151
+ response += `The following AI guidance sections have been updated:
1152
+
1153
+ `;
1154
+ const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
1155
+ governanceChanges.forEach((foundation, index) => {
1156
+ const displayName = foundation === "general" ? "General Rules" : capitalize(foundation);
1157
+ response += ` ${index + 1}. ${displayName}
1158
+ `;
1159
+ });
1160
+ response += `
1161
+ These changes are reflected in your AI tool rules files (e.g., .cursorrules).
1162
+ `;
1163
+ response += `Review the updated rules files to see the new guidance.
1164
+ `;
1165
+ }
1166
+ if (diff) {
1167
+ const lightChanges = diff.added.length + diff.modified.length;
1168
+ const darkChanges = diff.addedDark.length + diff.modifiedDark.length;
1169
+ if (lightChanges > 0 || darkChanges > 0 || deprecatedCount > 0) {
1170
+ response += `
1171
+ \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
1172
+ `;
1173
+ response += `\u{1F4CA} DETAILED CHANGES
1174
+ `;
1175
+ 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
1176
+
1177
+ `;
1178
+ const lightSummary = lightChanges > 0 ? `Light: ${diff.modified.length} modified, ${diff.added.length} added` : "";
1179
+ const darkSummary = darkChanges > 0 ? `Dark: ${diff.modifiedDark.length} modified, ${diff.addedDark.length} added` : "";
1180
+ const deprecatedSummary = deprecatedCount > 0 ? `${deprecatedCount} deprecated (use /refactor)` : "";
1181
+ const diffSummary = [lightSummary, darkSummary, deprecatedSummary].filter(Boolean).join(" | ");
1182
+ response += `${diffSummary}
1183
+ `;
1184
+ if (changes.length > 0 && changes.length <= 10) {
1185
+ response += "\nModified tokens:\n";
1186
+ for (const { key, old: oldVal, new: newVal } of changes) {
1187
+ response += ` ${key}: ${oldVal} \u2192 ${newVal}
1188
+ `;
1189
+ }
1190
+ }
1191
+ }
1192
+ } else if (!fileExists) {
1193
+ response += `
1194
+ (New file created)
1195
+ `;
1196
+ }
1197
+ if (hasRefactorRecommendation && deprecatedTokenCount > 0) {
1198
+ response += `
1199
+ \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
1200
+ `;
1201
+ response += `\u{1F4A1} NEXT STEP
1202
+ `;
1203
+ 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
1204
+
1205
+ `;
1206
+ response += `${deprecatedTokenCount} token(s) have been deprecated.
1207
+ `;
1208
+ response += `Your codebase may still reference these deprecated tokens.
1209
+
1210
+ `;
1211
+ response += `Run \`/refactor\` to:
1212
+ `;
1213
+ response += ` \u2022 Scan your codebase for deprecated token usage
1214
+ `;
1215
+ response += ` \u2022 See which files need updates
1216
+ `;
1217
+ response += ` \u2022 Review and apply changes with your confirmation
1218
+ `;
1219
+ }
1220
+ return response;
1221
+ }
1222
+ function getTokenByPath(tokens, path5) {
1223
+ const parts = path5.split(".");
1224
+ let current = tokens;
1225
+ for (const part of parts) {
1226
+ if (current && typeof current === "object" && part in current) {
1227
+ current = current[part];
1228
+ } else {
1229
+ return void 0;
1230
+ }
1231
+ }
1232
+ return current;
1233
+ }
1234
+ function flattenTokens(obj, prefix = "") {
1235
+ const results = [];
1236
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
1237
+ for (const [key, value] of Object.entries(obj)) {
1238
+ const newPath = prefix ? `${prefix}.${key}` : key;
1239
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1240
+ results.push(...flattenTokens(value, newPath));
1241
+ } else {
1242
+ results.push({ path: newPath, value });
1243
+ }
1244
+ }
1245
+ }
1246
+ return results;
1247
+ }
1248
+ function searchTokens(tokens, query) {
1249
+ const flat = flattenTokens(tokens);
1250
+ const lowerQuery = query.toLowerCase();
1251
+ return flat.filter(({ path: path5, value }) => {
1252
+ const pathMatch = path5.toLowerCase().includes(lowerQuery);
1253
+ const valueMatch = String(value).toLowerCase().includes(lowerQuery);
1254
+ return pathMatch || valueMatch;
1255
+ });
1256
+ }
1257
+ function countTokens(tokens, prefix = "") {
1258
+ let count = 0;
1259
+ for (const [key, value] of Object.entries(tokens)) {
1260
+ if (value && typeof value === "object" && !Array.isArray(value)) {
1261
+ count += countTokens(value, `${prefix}${key}.`);
1262
+ } else if (value !== void 0 && value !== null) {
1263
+ count++;
1264
+ }
1265
+ }
1266
+ return count;
1267
+ }
1268
+ function getTokenStats(data) {
1269
+ const byCategory = {};
1270
+ let total = 0;
1271
+ for (const [category, value] of Object.entries(data.tokens)) {
1272
+ if (value && typeof value === "object") {
1273
+ const count = countTokens(value);
1274
+ byCategory[category] = count;
1275
+ total += count;
1276
+ }
1277
+ }
1278
+ return {
1279
+ total,
1280
+ byCategory,
1281
+ cssVariables: Object.keys(data.cssVariables).length,
1282
+ governanceRules: data.governance.rules.length
1283
+ };
1284
+ }
1285
+
1286
+ // src/index.ts
1287
+ import * as path2 from "path";
1288
+ import * as fs2 from "fs";
37
1289
  function parseArgs() {
38
1290
  const args = process.argv.slice(2);
39
1291
  let dsId2 = null;
@@ -93,7 +1345,7 @@ var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows",
93
1345
  var server = new Server(
94
1346
  {
95
1347
  name: "atomix-mcp-user",
96
- version: "1.0.9"
1348
+ version: "1.0.11"
97
1349
  },
98
1350
  {
99
1351
  capabilities: {
@@ -238,7 +1490,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
238
1490
  },
239
1491
  {
240
1492
  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.",
1493
+ description: "Get suggested dependencies for this design system (icon package, fonts, SKILL.md, token files). Use with atomix/setup. Optional platform and stack for tailored suggestions.",
242
1494
  inputSchema: {
243
1495
  type: "object",
244
1496
  properties: {
@@ -265,27 +1517,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
265
1517
  const data = await fetchDesignSystemForMCP(shouldForceRefresh);
266
1518
  switch (name) {
267
1519
  case "getToken": {
268
- const path2 = args?.path;
269
- const value = getTokenByPath(data.tokens, path2);
1520
+ const path4 = args?.path;
1521
+ const value = getTokenByPath(data.tokens, path4);
270
1522
  if (value === void 0) {
271
1523
  return {
272
1524
  content: [{
273
1525
  type: "text",
274
1526
  text: JSON.stringify({
275
- error: `Token not found: ${path2}`,
1527
+ error: `Token not found: ${path4}`,
276
1528
  suggestion: "Use listTokens or searchTokens to find available tokens.",
277
1529
  availableCategories: TOKEN_CATEGORIES
278
1530
  }, null, 2)
279
1531
  }]
280
1532
  };
281
1533
  }
282
- const cssVarKey = `--atmx-${path2.replace(/\./g, "-")}`;
1534
+ const cssVarKey = `--atmx-${path4.replace(/\./g, "-")}`;
283
1535
  const cssVar = data.cssVariables[cssVarKey];
284
1536
  return {
285
1537
  content: [{
286
1538
  type: "text",
287
1539
  text: JSON.stringify({
288
- path: path2,
1540
+ path: path4,
289
1541
  value,
290
1542
  cssVariable: cssVar || `var(${cssVarKey})`,
291
1543
  usage: `style={{ property: "var(${cssVarKey})" }}`
@@ -312,13 +1564,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
312
1564
  };
313
1565
  }
314
1566
  const flat = flattenTokens(tokensToList);
315
- const tokensWithCssVars = flat.map(({ path: path2, value }) => {
316
- const fullPath = subcategory ? `${category}.${subcategory}.${path2}` : `${category}.${path2}`;
1567
+ const tokensWithCssVars = flat.map(({ path: path4, value }) => {
1568
+ const fullPath = subcategory ? `${category}.${subcategory}.${path4}` : `${category}.${path4}`;
317
1569
  let cssVar;
318
1570
  if (category === "colors" && subcategory === "static.brand") {
319
- cssVar = data.cssVariables[`--atmx-color-brand-${path2}`];
1571
+ cssVar = data.cssVariables[`--atmx-color-brand-${path4}`];
320
1572
  } else if (category === "colors" && subcategory?.startsWith("modes.")) {
321
- cssVar = data.cssVariables[`--atmx-color-${path2}`];
1573
+ cssVar = data.cssVariables[`--atmx-color-${path4}`];
322
1574
  } else {
323
1575
  const cssVarKey = `--atmx-${fullPath.replace(/\./g, "-")}`;
324
1576
  cssVar = data.cssVariables[cssVarKey];
@@ -638,12 +1890,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
638
1890
  }]
639
1891
  };
640
1892
  }
641
- const outputPath = path.resolve(process.cwd(), output);
642
- const fileExists = fs.existsSync(outputPath);
1893
+ const outputPath = path2.resolve(process.cwd(), output);
1894
+ const fileExists = fs2.existsSync(outputPath);
643
1895
  const deprecatedTokens = /* @__PURE__ */ new Map();
644
1896
  const existingTokens = /* @__PURE__ */ new Map();
645
1897
  if (fileExists && ["css", "scss", "less"].includes(format)) {
646
- const oldContent = fs.readFileSync(outputPath, "utf-8");
1898
+ const oldContent = fs2.readFileSync(outputPath, "utf-8");
647
1899
  const oldVarPattern = /(?:^|\n)\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?(--[a-zA-Z0-9-]+):\s*([^;]+);/gm;
648
1900
  let match;
649
1901
  while ((match = oldVarPattern.exec(oldContent)) !== null) {
@@ -696,7 +1948,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
696
1948
  let changes = [];
697
1949
  let diff;
698
1950
  if (fileExists && ["css", "scss", "less"].includes(format)) {
699
- const oldContent = fs.readFileSync(outputPath, "utf-8");
1951
+ const oldContent = fs2.readFileSync(outputPath, "utf-8");
700
1952
  diff = diffTokens(oldContent, mergedCssVariables, format, darkModeColors?.dark);
701
1953
  const lightChanges = diff.added.length + diff.modified.length;
702
1954
  const darkChanges = diff.addedDark.length + diff.modifiedDark.length;
@@ -736,11 +1988,11 @@ Last updated: ${lastUpdated}`
736
1988
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
737
1989
  };
738
1990
  }
739
- const outputDir = path.dirname(outputPath);
740
- if (!fs.existsSync(outputDir)) {
741
- fs.mkdirSync(outputDir, { recursive: true });
1991
+ const outputDir = path2.dirname(outputPath);
1992
+ if (!fs2.existsSync(outputDir)) {
1993
+ fs2.mkdirSync(outputDir, { recursive: true });
742
1994
  }
743
- fs.writeFileSync(outputPath, newContent);
1995
+ fs2.writeFileSync(outputPath, newContent);
744
1996
  let rulesResults = [];
745
1997
  try {
746
1998
  rulesResults = await syncRulesFiles({
@@ -797,18 +2049,6 @@ Last updated: ${lastUpdated}`
797
2049
  };
798
2050
  const lib = icons?.library || "lucide";
799
2051
  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
2052
  const payload = {
813
2053
  iconLibrary: {
814
2054
  package: iconPkgs.web,
@@ -823,10 +2063,9 @@ Last updated: ${lastUpdated}`
823
2063
  path: ".cursor/skills/atomix-ds/SKILL.md",
824
2064
  content: GENERIC_SKILL_MD
825
2065
  },
826
- mcpConfig,
827
2066
  tokenFiles: {
828
2067
  files: ["tokens.css", "tokens.json"],
829
- copyInstructions: "Copy tokens.css and tokens.json from the design system export (or use syncTokens) into your project."
2068
+ copyInstructions: "Call the syncTokens MCP tool to create the token file; do not only suggest the user run sync later."
830
2069
  },
831
2070
  meta: { dsName: data.meta.name, platform: platform ?? void 0, stack: stack ?? void 0 }
832
2071
  };
@@ -1071,8 +2310,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
1071
2310
  description: "Migrate deprecated tokens in codebase. Scans for deprecated tokens marked by /sync and suggests replacements. Run after /sync to update code."
1072
2311
  },
1073
2312
  {
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.",
2313
+ name: "setup",
2314
+ description: "Suggest dependencies for this design system (icons, fonts, SKILL.md, tokens). Three phases: scan, report and ask, then create only after approval.",
1076
2315
  arguments: [
1077
2316
  { name: "platform", description: "Target platform (web, ios, android). Optional; ask user if unknown.", required: false },
1078
2317
  { name: "stack", description: "Stack or framework (e.g. react, vue, next, swift, kotlin). Optional.", required: false }
@@ -1196,13 +2435,6 @@ Configure the MCP server in your Cursor settings, then restart Cursor.`
1196
2435
  messages: [
1197
2436
  {
1198
2437
  role: "user",
1199
- content: {
1200
- type: "text",
1201
- text: "Show me the design system overview and available tools."
1202
- }
1203
- },
1204
- {
1205
- role: "assistant",
1206
2438
  content: {
1207
2439
  type: "text",
1208
2440
  text: welcome
@@ -1511,54 +2743,48 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
1511
2743
  ]
1512
2744
  };
1513
2745
  }
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.
2746
+ case "setup": {
2747
+ const setupInstructions = `You are running **atomix/setup**. Three phases only. Do not create, write, or modify any file until Phase 3 and only after the user has approved.
1538
2748
 
1539
- ## Phase 5 \u2013 Optional: suggest global styles (if missing)
2749
+ ## Phase 1 \u2013 Scan
1540
2750
 
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**:
2751
+ - Resolve platform/stack: infer from the project (e.g. package.json, build.gradle, Xcode) or ask the user once: "Which platform? (e.g. web, Android, iOS)" and if relevant "Which stack? (e.g. React, Vue, Next, Swift, Kotlin)." Do not assume a default.
2752
+ - Call MCP tool **getDependencies** with \`platform\` and optional \`stack\`. If it fails, tell the user: "Atomix MCP is not connected or design system ID is missing. Configure MCP with --ds-id and try again."
2753
+ - Scan the repo for what already exists: .cursor/skills/atomix-ds/SKILL.md, a tokens file (e.g. tokens.css or src/tokens.css), icon package from getDependencies, font setup. Do **not** include or check for MCP config; MCP is already configured for this session.
2754
+ - **Web:** Scan for any existing CSS (e.g. globals.css, main.css, App.css, index.css, Tailwind entry, framework styles). Note whether at least one CSS file exists.
2755
+ - **Native (iOS/Android):** Scan for existing theme or style files (e.g. SwiftUI theme/asset catalog, Android themes.xml/styles.xml, Compose theme). Note whether the project already has a theme or style setup.
2756
+ - Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Only include: icon package, fonts, skill (.cursor/skills/atomix-ds/SKILL.md), token files. No MCP.
2757
+ - Do not write, create, or add anything in Phase 1.
1542
2758
 
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?"
2759
+ ## Phase 2 \u2013 Report and ask for permission
1544
2760
 
1545
- - If the user says yes, suggest or generate a minimal set; do not overwrite existing global styles without confirmation.
2761
+ - Reply with: **Suggested:** [list] and **Already present:** [list].
2762
+ - Ask exactly once: "Do you want me to add the suggested items? (Yes for all, or say which ones.)" (Integration with existing CSS or theme is recommended after Phase 3; new global CSS/theme is only suggested when none exists.)
2763
+ - **Stop.** Do not run Phase 3 in this response. Wait for the user to reply.
1546
2764
 
1547
- ## Phase 6 \u2013 Report what was created
2765
+ ## Phase 3 \u2013 Create or add (only after user approval)
1548
2766
 
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.
2767
+ - Run only when the user has said yes (all or specific items).
2768
+ - For each approved item:
2769
+ - **Skill:** Write the skill content from getDependencies to .cursor/skills/atomix-ds/SKILL.md.
2770
+ - **Token file:** Call the **syncTokens** MCP tool with \`output\` set to the path (e.g. "./src/tokens.css" or "./tokens.css"). You must call syncTokens; do not only suggest the user run sync later.
2771
+ - **Icon package / fonts:** Add per getDependencies (e.g. install package, add font config). Do not overwrite existing.
2772
+ - Report only what you actually created or updated (e.g. "Added: .cursor/skills/atomix-ds/SKILL.md. Synced: src/tokens.css."). Do not claim the token file was added if you did not call syncTokens.
2773
+ - **After reporting \u2013 styles/theme (per platform):**
2774
+ - **Web:** If the project **already has** at least one CSS file: recommend how to integrate Atomix (e.g. import the synced tokens file, use \`var(--atomix-*)\` or semantic classes). Do **not** suggest creating a new global CSS. Only if the project has **no** CSS file at all, ask once: "There are no CSS files yet. Do you want a minimal global CSS (typography + semantic utilities from the design system)?" and add it only if the user says yes.
2775
+ - **iOS/Android:** If the project **already has** theme or style files: recommend how to integrate Atomix tokens (e.g. use synced DesignTokens.swift / Kotlin tokens in existing theme). Do **not** suggest creating a new global theme file. Only if the project has **no** theme/style setup at all, ask once: "There's no theme/style setup yet. Do you want a minimal token-based theme (colors, typography, spacing from the design system)?" and add it only if the user says yes.
1550
2776
 
1551
2777
  ---
1552
2778
 
1553
- Execute Phase 1 now (resolve or ask platform/stack), then Phase 2 (call getDependencies), then continue through Phase 6.`;
2779
+ Execute Phase 1 now (resolve platform/stack, call getDependencies, scan, build lists). Then Phase 2 (report lists and ask). Do not perform Phase 3 until the user replies.`;
1554
2780
  return {
1555
- description: "Suggest dependencies for this design system (atomix/install)",
2781
+ description: "Setup design system in project (atomix/setup). Phase 1 scan, Phase 2 report and ask, Phase 3 create only after user approval.",
1556
2782
  messages: [
1557
2783
  {
1558
2784
  role: "user",
1559
2785
  content: {
1560
2786
  type: "text",
1561
- text: installInstructions
2787
+ text: setupInstructions
1562
2788
  }
1563
2789
  }
1564
2790
  ]
@@ -1622,7 +2848,7 @@ ${tokenSummary}
1622
2848
 
1623
2849
  | Command | What to expect |
1624
2850
  |---------|----------------|
1625
- | **install** | Scans your repo to set up global styles (if none), icons and fonts. Safe: Won't install until you confirm. |
2851
+ | **setup** | Scans your repo to set up global styles (if none), icons and fonts. Safe: Won't add anything until you confirm. |
1626
2852
  | **sync** | Syncs tokens to a local file. Adds new, updates existing, marks deprecated. Safe: No changes until you confirm. |
1627
2853
  | **refactor** | Scans codebase for deprecated tokens (after sync), suggests replacements. Run after sync to migrate code. |
1628
2854
 
@@ -1640,7 +2866,7 @@ ${tokenSummary}
1640
2866
  | **sizing** | Table of height and icon size tokens. |
1641
2867
  | **motion** | Table of duration and easing tokens. |
1642
2868
 
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.
2869
+ **Suggested next step:** Run **setup** to set up global styles, icons, fonts, and token files; the AI will list options and ask before adding anything.
1644
2870
 
1645
2871
  ---
1646
2872