@atomixstudio/mcp 1.0.29 → 1.0.31

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
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  isAllowedMethod,
4
4
  normalizeBridgeMethod
5
- } from "./chunk-BXWOCXYT.js";
5
+ } from "./chunk-CE6J5MJX.js";
6
6
 
7
7
  // src/index.ts
8
8
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1288,10 +1288,710 @@ function getTokenStats(data) {
1288
1288
  };
1289
1289
  }
1290
1290
 
1291
+ // ../figma-tools/dist/index.js
1292
+ var LEGACY_GROUP_PREFIX = {
1293
+ background: "bg",
1294
+ text: "text",
1295
+ icon: "icon",
1296
+ border: "border",
1297
+ brand: "brand",
1298
+ action: "action",
1299
+ feedback: "feedback"
1300
+ };
1301
+ var RESERVED_COLOR_KEYS = /* @__PURE__ */ new Set(["customScales", "neutral"]);
1302
+ function getColorGroupOrder(storedColors) {
1303
+ if (Array.isArray(storedColors._groupOrder) && storedColors._groupOrder.length > 0) {
1304
+ return storedColors._groupOrder;
1305
+ }
1306
+ return Object.keys(storedColors).filter(
1307
+ (k) => !k.startsWith("_") && !RESERVED_COLOR_KEYS.has(k) && typeof storedColors[k] === "object"
1308
+ );
1309
+ }
1310
+ function getGroupPrefix(storedColors, groupName) {
1311
+ const custom = storedColors._groupPrefix;
1312
+ if (custom && typeof custom[groupName] === "string") return custom[groupName];
1313
+ return LEGACY_GROUP_PREFIX[groupName] ?? groupName;
1314
+ }
1315
+ function refScaleToFigmaDisplayName(scaleFromRef) {
1316
+ const s = scaleFromRef.trim();
1317
+ if (!s) return s;
1318
+ return s.charAt(0).toUpperCase() + s.slice(1);
1319
+ }
1320
+ function referenceToFigmaPrimitiveName(ref, primitiveNames) {
1321
+ if (!ref || typeof ref !== "string") return null;
1322
+ const r = ref.trim();
1323
+ const scaleStep = /^([a-zA-Z]+)\.(\d+|[a-z]+)$/.exec(r);
1324
+ if (scaleStep) {
1325
+ const [, scale, step] = scaleStep;
1326
+ const scaleDisplay = refScaleToFigmaDisplayName(scale);
1327
+ const name = `${scaleDisplay} / ${step}`;
1328
+ return primitiveNames.has(name) ? name : null;
1329
+ }
1330
+ const radixMatch = /^radix\.([a-zA-Z]+)\.(\d+)$/.exec(r);
1331
+ if (radixMatch) {
1332
+ const [, family, step] = radixMatch;
1333
+ const scaleName = refScaleToFigmaDisplayName(family);
1334
+ const name = `${scaleName} / ${step}`;
1335
+ return primitiveNames.has(name) ? name : null;
1336
+ }
1337
+ const oneOffMatch = /^(?:new|oneOff)\.(.+)$/.exec(r);
1338
+ if (oneOffMatch) {
1339
+ const name = `One-off / ${oneOffMatch[1]}`;
1340
+ return primitiveNames.has(name) ? name : null;
1341
+ }
1342
+ const whiteAlpha = /^whiteAlpha\.(.+)$/.exec(r);
1343
+ if (whiteAlpha) {
1344
+ const name = `WhiteAlpha / ${whiteAlpha[1]}`;
1345
+ return primitiveNames.has(name) ? name : null;
1346
+ }
1347
+ const blackAlpha = /^blackAlpha\.(.+)$/.exec(r);
1348
+ if (blackAlpha) {
1349
+ const name = `BlackAlpha / ${blackAlpha[1]}`;
1350
+ return primitiveNames.has(name) ? name : null;
1351
+ }
1352
+ return null;
1353
+ }
1354
+ function referenceToScaleName(ref) {
1355
+ if (!ref || typeof ref !== "string") return null;
1356
+ const r = ref.trim();
1357
+ const scaleStep = /^([a-zA-Z]+)\.(\d+|[a-z]+)$/.exec(r);
1358
+ if (scaleStep) return refScaleToFigmaDisplayName(scaleStep[1]);
1359
+ const radixMatch = /^radix\.([a-zA-Z]+)\.(\d+)$/.exec(r);
1360
+ if (radixMatch) return refScaleToFigmaDisplayName(radixMatch[1]);
1361
+ return null;
1362
+ }
1363
+ function getReferencedScaleNames(storedColors) {
1364
+ const names = /* @__PURE__ */ new Set();
1365
+ const groupOrder = getColorGroupOrder(storedColors);
1366
+ for (const groupName of groupOrder) {
1367
+ const group = storedColors[groupName];
1368
+ if (!group || typeof group !== "object") continue;
1369
+ const keys = Object.keys(group).filter((k) => !k.startsWith("_") && k !== "governance");
1370
+ for (const key of keys) {
1371
+ const value = group[key];
1372
+ if (value === void 0 || typeof value !== "object" || value === null) continue;
1373
+ const theme = value;
1374
+ const lightScale = theme.light?.reference ? referenceToScaleName(theme.light.reference) : null;
1375
+ const darkScale = theme.dark?.reference ? referenceToScaleName(theme.dark.reference) : null;
1376
+ if (lightScale) names.add(lightScale);
1377
+ if (darkScale) names.add(darkScale);
1378
+ }
1379
+ }
1380
+ return names;
1381
+ }
1382
+ function getFullScaleFromStored(storedColors, scaleName) {
1383
+ const key = scaleName.toLowerCase();
1384
+ if (key === "neutral" || key === "gray") {
1385
+ const neutral = storedColors.neutral;
1386
+ if (!neutral?.steps || typeof neutral.steps !== "object") return null;
1387
+ const out = {};
1388
+ for (const [step, stepValue] of Object.entries(neutral.steps)) {
1389
+ const hex = stepValue?.hex;
1390
+ if (typeof hex === "string" && /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/i.test(hex)) {
1391
+ out[step] = hex.startsWith("#") ? hex : `#${hex}`;
1392
+ }
1393
+ }
1394
+ return Object.keys(out).length > 0 ? out : null;
1395
+ }
1396
+ const customScales = storedColors.customScales;
1397
+ if (Array.isArray(customScales)) {
1398
+ const keyNorm = key.replace(/\s+/g, "");
1399
+ const scale = customScales.find(
1400
+ (s) => s.name?.toLowerCase() === key || s.name?.toLowerCase().replace(/\s+/g, "") === keyNorm
1401
+ );
1402
+ if (scale?.steps && typeof scale.steps === "object") {
1403
+ const out = {};
1404
+ for (const [step, stepValue] of Object.entries(scale.steps)) {
1405
+ const hex = stepValue?.hex;
1406
+ if (typeof hex === "string" && /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/i.test(hex)) {
1407
+ out[step] = hex.startsWith("#") ? hex : `#${hex}`;
1408
+ }
1409
+ }
1410
+ if (Object.keys(out).length > 0) return out;
1411
+ }
1412
+ }
1413
+ return null;
1414
+ }
1415
+ function buildSemanticRefMap(storedColors, primitiveNames) {
1416
+ const out = {};
1417
+ const groupOrder = getColorGroupOrder(storedColors);
1418
+ for (const groupName of groupOrder) {
1419
+ const group = storedColors[groupName];
1420
+ if (!group || typeof group !== "object") continue;
1421
+ const prefix = getGroupPrefix(storedColors, groupName);
1422
+ const rowOrder = Array.isArray(group._rowOrder) ? group._rowOrder : void 0;
1423
+ const keys = Array.isArray(rowOrder) ? rowOrder : Object.keys(group).filter((k) => !k.startsWith("_") && k !== "governance");
1424
+ const toKebab = prefix === "action" || prefix === "brand" || prefix === "feedback";
1425
+ for (const key of keys) {
1426
+ const value = group[key];
1427
+ if (value === void 0 || typeof value !== "object" || value === null) continue;
1428
+ const theme = value;
1429
+ let cssKey = key === "app" ? "page" : key;
1430
+ if (toKebab) {
1431
+ cssKey = String(key).replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
1432
+ }
1433
+ const fullKey = `${prefix}-${cssKey}`;
1434
+ const lightRef = theme.light?.reference ? referenceToFigmaPrimitiveName(theme.light.reference, primitiveNames) : null;
1435
+ const darkRef = theme.dark?.reference ? referenceToFigmaPrimitiveName(theme.dark.reference, primitiveNames) : null;
1436
+ if (lightRef || darkRef) {
1437
+ out[fullKey] = {};
1438
+ if (lightRef) out[fullKey].Light = lightRef;
1439
+ if (darkRef) out[fullKey].Dark = darkRef;
1440
+ }
1441
+ }
1442
+ }
1443
+ return out;
1444
+ }
1445
+ function figmaColorNameWithGroup(key) {
1446
+ if (key.includes("/")) {
1447
+ const [group2, ...rest] = key.split("/");
1448
+ const name2 = rest.join("/").trim();
1449
+ if (!name2) return key;
1450
+ const groupDisplay2 = group2.charAt(0).toUpperCase() + group2.slice(1).toLowerCase();
1451
+ return `${groupDisplay2} / ${name2}`;
1452
+ }
1453
+ const firstDash = key.indexOf("-");
1454
+ if (firstDash <= 0) return key;
1455
+ const group = key.slice(0, firstDash);
1456
+ const name = key.slice(firstDash + 1);
1457
+ const groupDisplay = group.charAt(0).toUpperCase() + group.slice(1).toLowerCase();
1458
+ return `${groupDisplay} / ${name}`;
1459
+ }
1460
+ var FIGMA_SHADOW_ORDER = {
1461
+ none: 0,
1462
+ xs: 1,
1463
+ sm: 2,
1464
+ md: 3,
1465
+ lg: 4,
1466
+ xl: 5,
1467
+ "2xl": 6,
1468
+ focus: 7
1469
+ };
1470
+ function tokenValueToNumber(s) {
1471
+ if (typeof s !== "string" || !s.trim()) return 0;
1472
+ const t = s.trim();
1473
+ if (t.endsWith("rem")) {
1474
+ const n2 = parseFloat(t.replace(/rem$/, ""));
1475
+ return Number.isFinite(n2) ? Math.round(n2 * 16) : 0;
1476
+ }
1477
+ if (t.endsWith("px")) {
1478
+ const n2 = parseFloat(t.replace(/px$/, ""));
1479
+ return Number.isFinite(n2) ? Math.round(n2) : 0;
1480
+ }
1481
+ const n = parseFloat(t);
1482
+ return Number.isFinite(n) ? n : 0;
1483
+ }
1484
+ function parseBoxShadowToFigmaEffect(shadowStr) {
1485
+ const s = shadowStr.trim();
1486
+ if (!s || s.toLowerCase() === "none") return null;
1487
+ const parsePx = (x) => typeof x === "string" ? parseFloat(x.replace(/px$/i, "")) : NaN;
1488
+ const colorMatch = s.match(/(rgba?\s*\([^)]+\)|#[0-9A-Fa-f]{3,8})\s*$/i);
1489
+ const colorStr = colorMatch ? colorMatch[1].trim() : void 0;
1490
+ const rest = (colorMatch ? s.slice(0, colorMatch.index) : s).trim();
1491
+ const parts = rest ? rest.split(/\s+/) : [];
1492
+ if (parts.length < 3) return null;
1493
+ const offsetX = parsePx(parts[0]);
1494
+ const offsetY = parsePx(parts[1]);
1495
+ const blur = parsePx(parts[2]);
1496
+ let spread = 0;
1497
+ if (parts.length >= 4) spread = parsePx(parts[3]);
1498
+ let r = 0, g = 0, b = 0, a = 0.1;
1499
+ if (colorStr) {
1500
+ const rgbaMatch = colorStr.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
1501
+ if (rgbaMatch) {
1502
+ r = Number(rgbaMatch[1]) / 255;
1503
+ g = Number(rgbaMatch[2]) / 255;
1504
+ b = Number(rgbaMatch[3]) / 255;
1505
+ a = rgbaMatch[4] != null ? parseFloat(rgbaMatch[4]) : 1;
1506
+ } else {
1507
+ const hexMatch = colorStr.match(/^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/);
1508
+ if (hexMatch) {
1509
+ r = parseInt(hexMatch[1].slice(0, 2), 16) / 255;
1510
+ g = parseInt(hexMatch[1].slice(2, 4), 16) / 255;
1511
+ b = parseInt(hexMatch[1].slice(4, 6), 16) / 255;
1512
+ a = hexMatch[2] ? parseInt(hexMatch[2], 16) / 255 : 0.1;
1513
+ }
1514
+ }
1515
+ }
1516
+ if (!Number.isFinite(offsetX) || !Number.isFinite(offsetY) || !Number.isFinite(blur)) return null;
1517
+ return {
1518
+ type: "DROP_SHADOW",
1519
+ offset: { x: offsetX, y: offsetY },
1520
+ radius: Math.max(0, blur),
1521
+ spread: Number.isFinite(spread) ? spread : 0,
1522
+ color: { r, g, b, a },
1523
+ visible: true,
1524
+ blendMode: "NORMAL"
1525
+ };
1526
+ }
1527
+ function parseBoxShadowToFigmaEffects(shadowStr) {
1528
+ const s = (shadowStr || "").trim();
1529
+ if (!s || s.toLowerCase() === "none") return [];
1530
+ const out = [];
1531
+ const segments = s.split(/\s*,\s*/);
1532
+ for (const seg of segments) {
1533
+ const effect = parseBoxShadowToFigmaEffect(seg.trim());
1534
+ if (effect) out.push(effect);
1535
+ }
1536
+ return out;
1537
+ }
1538
+ function colorTo8DigitHex(color) {
1539
+ if (!color || typeof color !== "string") return null;
1540
+ const s = color.trim();
1541
+ const rgbaMatch = s.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
1542
+ if (rgbaMatch) {
1543
+ const r = Math.max(0, Math.min(255, parseInt(rgbaMatch[1], 10)));
1544
+ const g = Math.max(0, Math.min(255, parseInt(rgbaMatch[2], 10)));
1545
+ const b = Math.max(0, Math.min(255, parseInt(rgbaMatch[3], 10)));
1546
+ const a = rgbaMatch[4] != null ? Math.max(0, Math.min(1, parseFloat(rgbaMatch[4]))) : 1;
1547
+ const aByte = Math.round(a * 255);
1548
+ const hex = "#" + [r, g, b, aByte].map((n) => n.toString(16).padStart(2, "0")).join("").toUpperCase();
1549
+ return hex;
1550
+ }
1551
+ const hexMatch = s.match(/^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/i);
1552
+ if (hexMatch) {
1553
+ const rgb = hexMatch[1];
1554
+ const aa = hexMatch[2] ?? "FF";
1555
+ return `#${rgb}${aa}`.toUpperCase();
1556
+ }
1557
+ return null;
1558
+ }
1559
+ function typesetKeyToFontFamilyRole(key) {
1560
+ const prefix = (key.split("-")[0] ?? key).toLowerCase();
1561
+ if (prefix === "display" || prefix.startsWith("display")) return "display";
1562
+ if (prefix === "heading" || prefix.startsWith("heading")) return "heading";
1563
+ if (prefix === "mono" || prefix.startsWith("mono")) return "mono";
1564
+ if (prefix.startsWith("body")) return "body";
1565
+ return "body";
1566
+ }
1567
+ function buildFigmaPayloadsFromDS(data) {
1568
+ const tokens = data.tokens;
1569
+ const colors = tokens?.colors;
1570
+ const typography = tokens?.typography;
1571
+ const hexRe = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
1572
+ const modes = [];
1573
+ if (colors?.modes) {
1574
+ const light = colors.modes.light ?? {};
1575
+ const dark = colors.modes.dark ?? {};
1576
+ if (Object.keys(light).length > 0) modes.push("Light");
1577
+ if (Object.keys(dark).length > 0 && !modes.includes("Dark")) modes.push("Dark");
1578
+ }
1579
+ if (modes.length === 0) modes.push("Light");
1580
+ const primitiveModes = ["Value"];
1581
+ const dsName = data.meta?.name;
1582
+ const collectionPrefix = dsName ? `${dsName} ` : "";
1583
+ const referencedScaleNames = data.storedColors ? getReferencedScaleNames(data.storedColors) : /* @__PURE__ */ new Set();
1584
+ const primitiveVariables = [];
1585
+ const primitiveNames = /* @__PURE__ */ new Set();
1586
+ const oneOffHexToFigmaName = /* @__PURE__ */ new Map();
1587
+ const alphaScales = colors?.alphaScales;
1588
+ if (alphaScales?.whiteAlpha && typeof alphaScales.whiteAlpha === "object") {
1589
+ for (const [step, value] of Object.entries(alphaScales.whiteAlpha)) {
1590
+ if (typeof value !== "string") continue;
1591
+ const hex = colorTo8DigitHex(value);
1592
+ if (!hex || !hexRe.test(hex)) continue;
1593
+ const figmaName = `WhiteAlpha / ${step}`;
1594
+ if (primitiveNames.has(figmaName)) continue;
1595
+ primitiveNames.add(figmaName);
1596
+ const values = {};
1597
+ for (const m of primitiveModes) values[m] = hex;
1598
+ primitiveVariables.push({ name: figmaName, values });
1599
+ }
1600
+ }
1601
+ if (alphaScales?.blackAlpha && typeof alphaScales.blackAlpha === "object") {
1602
+ for (const [step, value] of Object.entries(alphaScales.blackAlpha)) {
1603
+ if (typeof value !== "string") continue;
1604
+ const hex = colorTo8DigitHex(value);
1605
+ if (!hex || !hexRe.test(hex)) continue;
1606
+ const figmaName = `BlackAlpha / ${step}`;
1607
+ if (primitiveNames.has(figmaName)) continue;
1608
+ primitiveNames.add(figmaName);
1609
+ const values = {};
1610
+ for (const m of primitiveModes) values[m] = hex;
1611
+ primitiveVariables.push({ name: figmaName, values });
1612
+ }
1613
+ }
1614
+ if (colors?.scales && typeof colors.scales === "object") {
1615
+ for (const [scaleName, steps] of Object.entries(colors.scales)) {
1616
+ if (!steps || typeof steps !== "object") continue;
1617
+ let groupDisplay = scaleName.charAt(0).toUpperCase() + scaleName.slice(1);
1618
+ if (scaleName.toLowerCase() === "neutral" && data.storedColors) {
1619
+ const neutral = data.storedColors.neutral;
1620
+ if (neutral?.baseHueFamily) {
1621
+ groupDisplay = refScaleToFigmaDisplayName(neutral.baseHueFamily);
1622
+ }
1623
+ }
1624
+ for (const [step, hexVal] of Object.entries(steps)) {
1625
+ if (typeof hexVal !== "string") continue;
1626
+ const hex = colorTo8DigitHex(hexVal) ?? (hexRe.test(hexVal) ? hexVal : null);
1627
+ if (!hex || !hexRe.test(hex)) continue;
1628
+ const figmaName = `${groupDisplay} / ${step}`;
1629
+ if (primitiveNames.has(figmaName)) continue;
1630
+ primitiveNames.add(figmaName);
1631
+ const values = {};
1632
+ for (const m of primitiveModes) values[m] = hex;
1633
+ primitiveVariables.push({ name: figmaName, values });
1634
+ }
1635
+ }
1636
+ }
1637
+ const radixVariables = [];
1638
+ const radixCollectionName = `${collectionPrefix}Colors Radix`;
1639
+ for (const scaleName of referencedScaleNames) {
1640
+ if (primitiveNames.has(`${scaleName} / 50`) || primitiveNames.has(`${scaleName} / 100`)) continue;
1641
+ const fromStored = data.storedColors ? getFullScaleFromStored(data.storedColors, scaleName) : null;
1642
+ const scaleData = fromStored ?? data.getRadixScale?.(scaleName) ?? null;
1643
+ if (!scaleData) continue;
1644
+ for (const [step, hexVal] of Object.entries(scaleData)) {
1645
+ const figmaName = `${scaleName} / ${step}`;
1646
+ if (primitiveNames.has(figmaName)) continue;
1647
+ const hex = colorTo8DigitHex(hexVal) ?? (hexRe.test(hexVal) ? hexVal : null);
1648
+ if (!hex || !hexRe.test(hex)) continue;
1649
+ primitiveNames.add(figmaName);
1650
+ const values = {};
1651
+ for (const m of primitiveModes) values[m] = hex;
1652
+ primitiveVariables.push({ name: figmaName, values });
1653
+ }
1654
+ }
1655
+ if (colors?.oneOffs && Array.isArray(colors.oneOffs)) {
1656
+ for (const oneOff of colors.oneOffs) {
1657
+ if (!oneOff || typeof oneOff !== "object" || typeof oneOff.hex !== "string") continue;
1658
+ const hex = colorTo8DigitHex(oneOff.hex) ?? (hexRe.test(oneOff.hex) ? oneOff.hex : null);
1659
+ if (!hex || !hexRe.test(hex)) continue;
1660
+ const name = typeof oneOff.name === "string" && oneOff.name ? oneOff.name : oneOff.id ?? "unnamed";
1661
+ const figmaName = `One-off / ${name}`;
1662
+ if (primitiveNames.has(figmaName)) continue;
1663
+ primitiveNames.add(figmaName);
1664
+ const values = {};
1665
+ for (const m of primitiveModes) values[m] = hex;
1666
+ primitiveVariables.push({ name: figmaName, values });
1667
+ const normalizedHex = hex.replace(/^#/, "").toUpperCase();
1668
+ const key8 = normalizedHex.length === 6 ? normalizedHex + "FF" : normalizedHex;
1669
+ oneOffHexToFigmaName.set(key8, figmaName);
1670
+ }
1671
+ }
1672
+ const semanticRefMap = data.storedColors && primitiveNames.size > 0 ? buildSemanticRefMap(data.storedColors, primitiveNames) : {};
1673
+ const semanticVariables = [];
1674
+ const semanticNames = /* @__PURE__ */ new Set();
1675
+ const primitivesCollectionName = `${collectionPrefix}Colors Primitives`;
1676
+ if (colors?.modes) {
1677
+ const light = colors.modes.light ?? {};
1678
+ const dark = colors.modes.dark ?? {};
1679
+ const orderedKeys = [...Object.keys(light)];
1680
+ for (const k of Object.keys(dark)) {
1681
+ if (!orderedKeys.includes(k)) orderedKeys.push(k);
1682
+ }
1683
+ for (const key of orderedKeys) {
1684
+ const lightVal = light[key];
1685
+ const darkVal = dark[key];
1686
+ const lightHex = typeof lightVal === "string" ? colorTo8DigitHex(lightVal) ?? (hexRe.test(lightVal) ? lightVal : null) : null;
1687
+ if (lightHex && hexRe.test(lightHex)) {
1688
+ const figmaName = figmaColorNameWithGroup(key);
1689
+ if (semanticNames.has(figmaName)) continue;
1690
+ semanticNames.add(figmaName);
1691
+ const darkHex = typeof darkVal === "string" ? colorTo8DigitHex(darkVal) ?? (hexRe.test(darkVal) ? darkVal : null) : null;
1692
+ const refs = semanticRefMap[key];
1693
+ const values = {
1694
+ ...modes.includes("Light") && { Light: lightHex },
1695
+ ...modes.includes("Dark") && { Dark: darkHex && hexRe.test(darkHex) ? darkHex : lightHex }
1696
+ };
1697
+ const aliasByMode = {};
1698
+ for (const m of modes) {
1699
+ const aliasFromRef = m === "Light" ? refs?.Light : refs?.Dark;
1700
+ if (aliasFromRef && primitiveNames.has(aliasFromRef)) {
1701
+ aliasByMode[m] = aliasFromRef;
1702
+ continue;
1703
+ }
1704
+ const hexForMode = m === "Light" ? lightHex : darkHex && hexRe.test(darkHex) ? darkHex : lightHex;
1705
+ const norm = hexForMode.replace(/^#/, "").toUpperCase();
1706
+ const key8 = norm.length === 6 ? norm + "FF" : norm;
1707
+ const oneOffAlias = oneOffHexToFigmaName.get(key8);
1708
+ if (oneOffAlias) {
1709
+ aliasByMode[m] = oneOffAlias;
1710
+ }
1711
+ }
1712
+ semanticVariables.push({
1713
+ name: figmaName,
1714
+ values,
1715
+ ...Object.keys(aliasByMode).length > 0 ? { aliasByMode } : {}
1716
+ });
1717
+ }
1718
+ }
1719
+ }
1720
+ if (colors?.static?.brand && typeof colors.static.brand === "object") {
1721
+ for (const [key, hex] of Object.entries(colors.static.brand)) {
1722
+ if (typeof hex !== "string" || !hexRe.test(hex)) continue;
1723
+ const figmaName = figmaColorNameWithGroup(`brand/${key}`);
1724
+ if (semanticNames.has(figmaName)) continue;
1725
+ semanticNames.add(figmaName);
1726
+ const values = {};
1727
+ for (const m of modes) values[m] = hex;
1728
+ semanticVariables.push({ name: figmaName, values });
1729
+ }
1730
+ }
1731
+ const colorVariableCollections = [];
1732
+ if (primitiveVariables.length > 0) {
1733
+ colorVariableCollections.push({
1734
+ collectionName: primitivesCollectionName,
1735
+ modes: primitiveModes,
1736
+ variables: primitiveVariables,
1737
+ applyScopes: false
1738
+ });
1739
+ }
1740
+ if (radixVariables.length > 0) {
1741
+ colorVariableCollections.push({
1742
+ collectionName: radixCollectionName,
1743
+ modes,
1744
+ variables: radixVariables,
1745
+ applyScopes: false
1746
+ });
1747
+ }
1748
+ if (semanticVariables.length > 0) {
1749
+ const primitiveCollections = [];
1750
+ if (primitiveVariables.length > 0) primitiveCollections.push(primitivesCollectionName);
1751
+ if (radixVariables.length > 0) primitiveCollections.push(radixCollectionName);
1752
+ colorVariableCollections.push({
1753
+ collectionName: `${collectionPrefix}Colors Semantic`,
1754
+ modes,
1755
+ variables: semanticVariables,
1756
+ applyScopes: true,
1757
+ ...primitiveCollections.length > 1 ? { primitiveCollectionNames: primitiveCollections } : primitiveCollections.length === 1 ? { primitiveCollectionName: primitiveCollections[0] } : {}
1758
+ });
1759
+ }
1760
+ const paintStyles = [];
1761
+ const textStyles = [];
1762
+ const sizeToPx = (val, basePx = 16) => {
1763
+ if (typeof val === "number") return Math.round(val);
1764
+ const s = String(val).trim();
1765
+ const pxMatch = s.match(/^([\d.]+)\s*px$/i);
1766
+ if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
1767
+ const remMatch = s.match(/^([\d.]+)\s*rem$/i);
1768
+ if (remMatch) return Math.round(parseFloat(remMatch[1]) * basePx);
1769
+ const n = parseFloat(s);
1770
+ if (Number.isFinite(n)) return n <= 0 ? basePx : n < 50 ? Math.round(n * basePx) : Math.round(n);
1771
+ return basePx;
1772
+ };
1773
+ const letterSpacingToPx = (val, fontSizePx) => {
1774
+ if (val === void 0 || val === null) return void 0;
1775
+ if (typeof val === "number") return Math.round(val);
1776
+ const s = String(val).trim();
1777
+ const pxMatch = s.match(/^([-\d.]+)\s*px$/i);
1778
+ if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
1779
+ const emMatch = s.match(/^([-\d.]+)\s*em$/i);
1780
+ if (emMatch) return Math.round(parseFloat(emMatch[1]) * fontSizePx);
1781
+ const n = parseFloat(s);
1782
+ return Number.isFinite(n) ? Math.round(n) : void 0;
1783
+ };
1784
+ const firstFont = (obj) => {
1785
+ if (typeof obj === "string") {
1786
+ const primary = obj.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
1787
+ return primary || "Inter";
1788
+ }
1789
+ if (obj && typeof obj === "object" && !Array.isArray(obj)) {
1790
+ const v = obj.body ?? obj.heading ?? obj.display ?? Object.values(obj)[0];
1791
+ return firstFont(v);
1792
+ }
1793
+ return "Inter";
1794
+ };
1795
+ const toFontFamilyString = (val) => {
1796
+ if (typeof val === "string") {
1797
+ const s = val.trim().replace(/^["']|["']$/g, "");
1798
+ return s || "Inter";
1799
+ }
1800
+ return firstFont(val);
1801
+ };
1802
+ const fontFamilyMap = typography?.fontFamily ?? {};
1803
+ const defaultFontFamily = typography ? firstFont(typography.fontFamily ?? "Inter") : "Inter";
1804
+ const fontSizeMap = typography?.fontSize;
1805
+ const fontWeightMap = typography?.fontWeight;
1806
+ const lineHeightMap = typography?.lineHeight;
1807
+ const letterSpacingMap = typography?.letterSpacing;
1808
+ const textTransformMap = typography?.textTransform;
1809
+ const textDecorationMap = typography?.textDecoration;
1810
+ if (fontSizeMap && typeof fontSizeMap === "object" && Object.keys(fontSizeMap).length > 0) {
1811
+ for (const [key, sizeVal] of Object.entries(fontSizeMap)) {
1812
+ const fontSize = sizeToPx(sizeVal);
1813
+ if (fontSize <= 0) continue;
1814
+ const role = typesetKeyToFontFamilyRole(key);
1815
+ const fontFamily = toFontFamilyString(
1816
+ fontFamilyMap[role] ?? fontFamilyMap.body ?? fontFamilyMap.heading ?? fontFamilyMap.display ?? defaultFontFamily
1817
+ );
1818
+ const lh = lineHeightMap && typeof lineHeightMap === "object" ? lineHeightMap[key] : void 0;
1819
+ const weight = fontWeightMap && typeof fontWeightMap === "object" ? fontWeightMap[key] : void 0;
1820
+ const fontWeight = weight != null ? String(weight) : "400";
1821
+ const letterSpacingPx = letterSpacingToPx(
1822
+ letterSpacingMap && typeof letterSpacingMap === "object" ? letterSpacingMap[key] : void 0,
1823
+ fontSize
1824
+ );
1825
+ const textTransform = textTransformMap && typeof textTransformMap === "object" ? textTransformMap[key] : void 0;
1826
+ const textDecoration = textDecorationMap && typeof textDecorationMap === "object" ? textDecorationMap[key] : void 0;
1827
+ const namePart = key.replace(/-/g, " / ");
1828
+ const style = {
1829
+ name: namePart.startsWith("Typography") ? namePart : `Typography / ${namePart}`,
1830
+ fontFamily,
1831
+ fontWeight,
1832
+ fontSize,
1833
+ lineHeightUnit: "PERCENT",
1834
+ letterSpacingUnit: "PIXELS",
1835
+ ...letterSpacingPx !== void 0 && letterSpacingPx !== 0 ? { letterSpacingValue: letterSpacingPx } : {}
1836
+ };
1837
+ if (lh != null && typeof lh === "number" && lh > 0) {
1838
+ style.lineHeightValue = lh >= 10 ? Math.round(lh / fontSize * 100) : Math.round(lh * 100);
1839
+ } else {
1840
+ style.lineHeightValue = 150;
1841
+ }
1842
+ if (textTransform === "uppercase") style.textCase = "UPPER";
1843
+ else if (textTransform === "lowercase") style.textCase = "LOWER";
1844
+ else if (textTransform === "capitalize") style.textCase = "TITLE";
1845
+ else style.textCase = "ORIGINAL";
1846
+ if (textDecoration === "underline") style.textDecoration = "UNDERLINE";
1847
+ else style.textDecoration = "NONE";
1848
+ textStyles.push(style);
1849
+ }
1850
+ }
1851
+ const textStylesMap = typography?.textStyles;
1852
+ if (textStyles.length === 0 && textStylesMap && typeof textStylesMap === "object") {
1853
+ for (const [styleName, style] of Object.entries(textStylesMap)) {
1854
+ if (!style || typeof style !== "object") continue;
1855
+ const fontSize = sizeToPx(style.fontSize ?? "1rem");
1856
+ const lhStr = style.lineHeight;
1857
+ const lineHeightUnitless = lhStr != null ? lhStr.endsWith("%") ? parseFloat(lhStr) / 100 : sizeToPx(lhStr) / fontSize : 1.5;
1858
+ const payload = {
1859
+ name: styleName.startsWith("Typography") ? styleName : `Typography / ${styleName.replace(/\//g, " / ")}`,
1860
+ fontFamily: defaultFontFamily,
1861
+ fontWeight: String(style.fontWeight ?? "400"),
1862
+ fontSize,
1863
+ lineHeightUnit: "PERCENT",
1864
+ lineHeightValue: Math.round((Number.isFinite(lineHeightUnitless) ? lineHeightUnitless : 1.5) * 100),
1865
+ letterSpacingUnit: "PIXELS",
1866
+ textCase: "ORIGINAL",
1867
+ textDecoration: "NONE"
1868
+ };
1869
+ textStyles.push(payload);
1870
+ }
1871
+ }
1872
+ textStyles.sort((a, b) => {
1873
+ if (a.fontSize !== b.fontSize) return a.fontSize - b.fontSize;
1874
+ return (a.name || "").localeCompare(b.name || "");
1875
+ });
1876
+ const numberVariableCollections = [];
1877
+ const spacing = tokens?.spacing;
1878
+ if (spacing?.scale && typeof spacing.scale === "object") {
1879
+ const vars = [];
1880
+ for (const [key, val] of Object.entries(spacing.scale)) {
1881
+ const n = tokenValueToNumber(val);
1882
+ if (n >= 0) vars.push({ name: `Spacing / ${key}`, value: n });
1883
+ }
1884
+ vars.sort((a, b) => a.value - b.value);
1885
+ if (vars.length > 0)
1886
+ numberVariableCollections.push({
1887
+ collectionName: `${collectionPrefix}Spacing`,
1888
+ categoryKey: "Spacing",
1889
+ variables: vars,
1890
+ scopes: ["GAP"]
1891
+ });
1892
+ }
1893
+ const radius = tokens?.radius;
1894
+ if (radius?.scale && typeof radius.scale === "object") {
1895
+ const vars = [];
1896
+ for (const [key, val] of Object.entries(radius.scale)) {
1897
+ const n = tokenValueToNumber(val);
1898
+ if (n >= 0) vars.push({ name: `Radius / ${key}`, value: n });
1899
+ }
1900
+ vars.sort((a, b) => a.value - b.value);
1901
+ if (vars.length > 0)
1902
+ numberVariableCollections.push({
1903
+ collectionName: `${collectionPrefix}Radius`,
1904
+ categoryKey: "Radius",
1905
+ variables: vars,
1906
+ scopes: ["CORNER_RADIUS"]
1907
+ });
1908
+ }
1909
+ const borders = tokens?.borders;
1910
+ if (borders?.width && typeof borders.width === "object") {
1911
+ const vars = [];
1912
+ for (const [key, val] of Object.entries(borders.width)) {
1913
+ const n = tokenValueToNumber(val);
1914
+ if (n >= 0) vars.push({ name: `Borders / ${key}`, value: n });
1915
+ }
1916
+ vars.sort((a, b) => a.value - b.value);
1917
+ if (vars.length > 0)
1918
+ numberVariableCollections.push({
1919
+ collectionName: `${collectionPrefix}Borders`,
1920
+ categoryKey: "Borders",
1921
+ variables: vars,
1922
+ scopes: ["STROKE_FLOAT"]
1923
+ });
1924
+ }
1925
+ const sizing = tokens?.sizing;
1926
+ const sizingVariables = [];
1927
+ if (sizing?.height && typeof sizing.height === "object") {
1928
+ for (const [key, val] of Object.entries(sizing.height)) {
1929
+ const n = tokenValueToNumber(val);
1930
+ if (n >= 0) sizingVariables.push({ name: `Height / ${key}`, value: n });
1931
+ }
1932
+ }
1933
+ if (sizing?.icon && typeof sizing.icon === "object") {
1934
+ for (const [key, val] of Object.entries(sizing.icon)) {
1935
+ const n = tokenValueToNumber(val);
1936
+ if (n >= 0) sizingVariables.push({ name: `Icon / ${key}`, value: n });
1937
+ }
1938
+ }
1939
+ sizingVariables.sort((a, b) => a.value - b.value);
1940
+ if (sizingVariables.length > 0) {
1941
+ numberVariableCollections.push({
1942
+ collectionName: `${collectionPrefix}Sizing`,
1943
+ categoryKey: "Sizing",
1944
+ variables: sizingVariables,
1945
+ scopes: ["WIDTH_HEIGHT"]
1946
+ });
1947
+ }
1948
+ const layout = tokens?.layout;
1949
+ if (layout?.breakpoint && typeof layout.breakpoint === "object") {
1950
+ const vars = [];
1951
+ for (const [key, val] of Object.entries(layout.breakpoint)) {
1952
+ const n = tokenValueToNumber(val);
1953
+ if (n >= 0) vars.push({ name: `Breakpoint / ${key}`, value: n });
1954
+ }
1955
+ vars.sort((a, b) => a.value - b.value);
1956
+ if (vars.length > 0)
1957
+ numberVariableCollections.push({
1958
+ collectionName: `${collectionPrefix}Layout`,
1959
+ categoryKey: "Layout",
1960
+ variables: vars,
1961
+ scopes: ["WIDTH_HEIGHT"]
1962
+ });
1963
+ }
1964
+ const effectStyles = [];
1965
+ const shadows = tokens?.shadows;
1966
+ if (shadows?.elevation && typeof shadows.elevation === "object") {
1967
+ for (const [key, val] of Object.entries(shadows.elevation)) {
1968
+ if (typeof val !== "string") continue;
1969
+ const effects = parseBoxShadowToFigmaEffects(val);
1970
+ if (effects.length > 0) effectStyles.push({ name: `Shadow / ${key}`, effects });
1971
+ }
1972
+ }
1973
+ if (shadows?.focus && typeof shadows.focus === "string") {
1974
+ const effects = parseBoxShadowToFigmaEffects(shadows.focus);
1975
+ if (effects.length > 0) effectStyles.push({ name: "Shadow / focus", effects });
1976
+ }
1977
+ effectStyles.sort((a, b) => {
1978
+ const nameA = a.name.startsWith("Shadow / ") ? a.name.slice(9) : a.name;
1979
+ const nameB = b.name.startsWith("Shadow / ") ? b.name.slice(9) : b.name;
1980
+ const orderA = FIGMA_SHADOW_ORDER[nameA] ?? 100;
1981
+ const orderB = FIGMA_SHADOW_ORDER[nameB] ?? 100;
1982
+ if (orderA !== orderB) return orderA - orderB;
1983
+ return nameA.localeCompare(nameB);
1984
+ });
1985
+ return {
1986
+ colorVariableCollections,
1987
+ paintStyles,
1988
+ textStyles,
1989
+ numberVariableCollections,
1990
+ effectStyles
1991
+ };
1992
+ }
1993
+
1291
1994
  // src/index.ts
1292
- import {
1293
- buildFigmaPayloadsFromDS
1294
- } from "@atomix/figma_tools";
1295
1995
  import * as path2 from "path";
1296
1996
  import * as fs2 from "fs";
1297
1997
  import { execSync } from "child_process";
@@ -1493,7 +2193,7 @@ function parseArgs() {
1493
2193
  }
1494
2194
  var cliArgs = parseArgs();
1495
2195
  var { dsId, apiKey, accessToken } = cliArgs;
1496
- var apiBase = cliArgs.apiBase || "https://atomixstudio.eu";
2196
+ var apiBase = cliArgs.apiBase || "https://atomix.studio";
1497
2197
  var cachedData = null;
1498
2198
  var cachedETag = null;
1499
2199
  var cachedMcpTier = null;
@@ -1557,6 +2257,10 @@ function formatValidationBlock(entries) {
1557
2257
  async function fetchDesignSystemForMCP(forceRefresh = false) {
1558
2258
  if (!dsId) throw new Error("Missing --ds-id. Usage: npx @atomixstudio/mcp --ds-id <id> --atomix-token <token>");
1559
2259
  if (!accessToken) throw new Error("Missing --atomix-token. Get your token from the Export modal or Settings.");
2260
+ if (forceRefresh) {
2261
+ cachedData = null;
2262
+ cachedETag = null;
2263
+ }
1560
2264
  const result = await fetchDesignSystem({
1561
2265
  dsId,
1562
2266
  accessToken,
@@ -1573,7 +2277,7 @@ async function fetchDesignSystemForMCP(forceRefresh = false) {
1573
2277
  return result.data;
1574
2278
  }
1575
2279
  var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
1576
- function typesetKeyToFontFamilyRole(key) {
2280
+ function typesetKeyToFontFamilyRole2(key) {
1577
2281
  const prefix = (key.split("-")[0] ?? key).toLowerCase();
1578
2282
  if (prefix === "display" || prefix.startsWith("display")) return "display";
1579
2283
  if (prefix === "heading" || prefix.startsWith("heading")) return "heading";
@@ -1593,7 +2297,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
1593
2297
  const p = cssPrefix ? `${cssPrefix}-` : "";
1594
2298
  const typesets = [];
1595
2299
  for (const key of Object.keys(fontSize)) {
1596
- const role = typesetKeyToFontFamilyRole(key);
2300
+ const role = typesetKeyToFontFamilyRole2(key);
1597
2301
  const familyName = fontFamily[role] ?? fontFamily.body;
1598
2302
  const fontFamilyVarName = familyName ? `--${p}typography-font-family-${role}` : void 0;
1599
2303
  const fontFamilyVar = familyName ? `var(${fontFamilyVarName})` : "";
@@ -1619,7 +2323,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
1619
2323
  var server = new Server(
1620
2324
  {
1621
2325
  name: "atomix-mcp-user",
1622
- version: "1.0.28"
2326
+ version: "1.0.30"
1623
2327
  },
1624
2328
  {
1625
2329
  capabilities: {
@@ -1918,8 +2622,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1918
2622
  const totalChanges = lightChanges + darkChanges + deprecatedCount;
1919
2623
  if (totalChanges === 0 && !dryRun) {
1920
2624
  const lastUpdated = designSystemData.meta.exportedAt ? new Date(designSystemData.meta.exportedAt).toLocaleString() : "N/A";
2625
+ lastSyncAffectedTokens = { modified: [], removed: [], added: [], format, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1921
2626
  return {
1922
- responseText: `\u2713 Already up to date!
2627
+ responseText: `\u2713 Already up to date (checked DB v${designSystemData.meta.version}, exported ${lastUpdated}).
1923
2628
 
1924
2629
  File: ${output}
1925
2630
  Tokens: ${tokenCount}
@@ -1930,8 +2635,9 @@ Last updated: ${lastUpdated}`,
1930
2635
  };
1931
2636
  }
1932
2637
  if (totalChanges === 0 && dryRun) {
2638
+ lastSyncAffectedTokens = { modified: [], removed: [], added: [], format, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
1933
2639
  return {
1934
- responseText: `[DRY RUN] Already up to date. No changes would be written.
2640
+ responseText: `[DRY RUN] Already up to date (checked DB v${designSystemData.meta.version}). No changes would be written.
1935
2641
 
1936
2642
  File: ${output}
1937
2643
  Tokens: ${tokenCount}
@@ -2572,9 +3278,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
2572
3278
  out.agentInstruction = agentStartBridge;
2573
3279
  out.userInstruction = `If the bridge still does not connect: ${userSteps}`;
2574
3280
  out.figmaPayload = {
2575
- collectionName: payloads.colorVariables.collectionName,
2576
- modes: payloads.colorVariables.modes,
2577
- variables: payloads.colorVariables.variables,
3281
+ colorVariableCollections: payloads.colorVariableCollections,
2578
3282
  paintStyles: payloads.paintStyles,
2579
3283
  textStyles: payloads.textStyles,
2580
3284
  numberVariableCollections: payloads.numberVariableCollections,
@@ -2584,13 +3288,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
2584
3288
  content: [{ type: "text", text: JSON.stringify(out, null, 2) }]
2585
3289
  };
2586
3290
  }
2587
- if (payloads.colorVariables.variables.length > 0) {
2588
- out.colorVariables = await sendBridgeRequest("create_color_variables", {
2589
- collectionName: payloads.colorVariables.collectionName,
2590
- modes: payloads.colorVariables.modes,
2591
- variables: payloads.colorVariables.variables,
2592
- removeVariablesNotInPayload: true
2593
- });
3291
+ const colorResults = [];
3292
+ for (const coll of payloads.colorVariableCollections) {
3293
+ if (coll.variables.length > 0) {
3294
+ const result = await sendBridgeRequest("create_color_variables", {
3295
+ collectionName: coll.collectionName,
3296
+ modes: coll.modes,
3297
+ variables: coll.variables,
3298
+ removeVariablesNotInPayload: true,
3299
+ applyScopes: coll.applyScopes
3300
+ });
3301
+ colorResults.push({ collectionName: coll.collectionName, result });
3302
+ }
3303
+ }
3304
+ if (colorResults.length > 0) {
3305
+ out.colorVariables = colorResults.length === 1 ? colorResults[0].result : colorResults;
2594
3306
  }
2595
3307
  if (payloads.paintStyles.length > 0) {
2596
3308
  out.paintStyles = await sendBridgeRequest("create_paint_styles", {
@@ -2632,9 +3344,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
2632
3344
  });
2633
3345
  }
2634
3346
  out.figmaPayload = {
2635
- collectionName: payloads.colorVariables.collectionName,
2636
- modes: payloads.colorVariables.modes,
2637
- variables: payloads.colorVariables.variables,
3347
+ colorVariableCollections: payloads.colorVariableCollections,
2638
3348
  paintStyles: payloads.paintStyles,
2639
3349
  textStyles: payloads.textStyles,
2640
3350
  numberVariableCollections: payloads.numberVariableCollections,
@@ -2643,9 +3353,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
2643
3353
  } catch (e) {
2644
3354
  out.error = e instanceof Error ? e.message : String(e);
2645
3355
  out.figmaPayload = {
2646
- collectionName: payloads.colorVariables.collectionName,
2647
- modes: payloads.colorVariables.modes,
2648
- variables: payloads.colorVariables.variables,
3356
+ colorVariableCollections: payloads.colorVariableCollections,
2649
3357
  paintStyles: payloads.paintStyles,
2650
3358
  textStyles: payloads.textStyles,
2651
3359
  numberVariableCollections: payloads.numberVariableCollections,
@@ -2671,11 +3379,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
2671
3379
  }
2672
3380
  const summaryParts = [];
2673
3381
  const colorResult = out.colorVariables;
2674
- if (colorResult && (colorResult.variableNames?.length || (colorResult.removed ?? 0) > 0)) {
2675
- const parts = [];
2676
- if (colorResult.variableNames?.length) parts.push(`${colorResult.variableNames.length} synced`);
2677
- if ((colorResult.removed ?? 0) > 0) parts.push(`${colorResult.removed} removed`);
2678
- summaryParts.push(`Colors: ${parts.join(", ")}.`);
3382
+ if (colorResult) {
3383
+ const results = Array.isArray(colorResult) ? colorResult : [{ result: colorResult }];
3384
+ let totalSynced = 0;
3385
+ let totalRemoved = 0;
3386
+ for (const r of results) {
3387
+ const res = r.result;
3388
+ if (res?.variableNames?.length) totalSynced += res.variableNames.length;
3389
+ if ((res?.removed ?? 0) > 0) totalRemoved += res.removed ?? 0;
3390
+ }
3391
+ if (totalSynced > 0 || totalRemoved > 0) {
3392
+ const parts = [];
3393
+ if (totalSynced > 0) parts.push(`${totalSynced} synced`);
3394
+ if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
3395
+ summaryParts.push(`Colors: ${parts.join(", ")}.`);
3396
+ }
2679
3397
  }
2680
3398
  const paintResult = out.paintStyles;
2681
3399
  if (paintResult) {
@@ -2903,7 +3621,7 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
2903
3621
  <li>Run <code>/--rules</code> to load governance rules; run <code>/--sync</code> and <code>/--refactor</code> after you change tokens in Atomix Studio</li>
2904
3622
  </ul>
2905
3623
  </div>
2906
- <p class="tips">Keep the source of truth at <a href="https://atomixstudio.eu" target="_blank" rel="noopener">atomixstudio.eu</a> \u2014 avoid editing token values in this repo.</p>
3624
+ <p class="tips">Keep the source of truth at <a href="https://atomix.studio" target="_blank" rel="noopener">atomix.studio</a> \u2014 avoid editing token values in this repo.</p>
2907
3625
  </div>
2908
3626
  </body>
2909
3627
  </html>
@@ -3027,7 +3745,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
3027
3745
  };
3028
3746
  }
3029
3747
  const canonicalName = name === "--hello" ? "hello" : name === "--get-started" ? "atomix-setup" : name === "--rules" ? "design-system-rules" : name === "--sync" ? "sync" : name === "--refactor" ? "refactor" : name === "--sync-to-figma" || name === "syncToFigma" ? "sync-to-figma" : name;
3030
- const shouldForceRefresh = canonicalName === "sync";
3748
+ const shouldForceRefresh = canonicalName === "sync" || canonicalName === "refactor";
3031
3749
  let data = null;
3032
3750
  let stats = null;
3033
3751
  try {
@@ -3044,7 +3762,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
3044
3762
  content: {
3045
3763
  type: "text",
3046
3764
  text: `The MCP server isn't configured. To use design system prompts, you need to configure the server with:
3047
- - \`--ds-id\`: Your design system ID (get it from https://atomixstudio.eu/ds/[your-ds-id])
3765
+ - \`--ds-id\`: Your design system ID (get it from https://atomix.studio/ds/[your-ds-id])
3048
3766
  - \`--atomix-token\`: Your access token (get it from the Export modal or Settings \u2192 Regenerate Atomix access token)
3049
3767
 
3050
3768
  Both are required. Configure the MCP server in your AI tool's MCP settings, then restart your AI tool.`
@@ -3358,42 +4076,59 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
3358
4076
  };
3359
4077
  }
3360
4078
  case "refactor": {
3361
- if (!lastSyncAffectedTokens) {
4079
+ const refactorOutput = args?.output || "./tokens.css";
4080
+ const refactorFormat = args?.format || "css";
4081
+ const refactorOutputPath = path2.resolve(process.cwd(), refactorOutput);
4082
+ const refactorFileExists = fs2.existsSync(refactorOutputPath);
4083
+ if (!data) {
3362
4084
  return {
3363
4085
  description: "Refactor codebase for deprecated tokens",
3364
- messages: [
3365
- {
3366
- role: "user",
3367
- content: {
3368
- type: "text",
3369
- text: `No recent sync data available. Please run \`/sync\` first to update your token file, then run \`/refactor\` to scan your codebase for deprecated token usage.`
3370
- }
3371
- }
3372
- ]
4086
+ messages: [{
4087
+ role: "user",
4088
+ content: { type: "text", text: `Failed to fetch design system from DB. Check your --ds-id and --atomix-token configuration.` }
4089
+ }]
3373
4090
  };
3374
4091
  }
3375
- if (lastSyncAffectedTokens.removed.length === 0) {
4092
+ if (!refactorFileExists) {
3376
4093
  return {
3377
4094
  description: "Refactor codebase for deprecated tokens",
3378
- messages: [
3379
- {
3380
- role: "user",
3381
- content: {
3382
- type: "text",
3383
- text: `\u2713 No deprecated tokens found in last sync.
3384
-
3385
- Your codebase is up to date! The last sync on ${new Date(lastSyncAffectedTokens.timestamp).toLocaleString()} did not remove any tokens.`
3386
- }
3387
- }
3388
- ]
4095
+ messages: [{
4096
+ role: "user",
4097
+ content: { type: "text", text: `No token file found at \`${refactorOutput}\`. Please run \`/--sync\` first to create your token file, then run \`/--refactor\` to scan your codebase for deprecated token usage.` }
4098
+ }]
4099
+ };
4100
+ }
4101
+ const deprecatedTokens = /* @__PURE__ */ new Map();
4102
+ if (["css", "scss", "less"].includes(refactorFormat)) {
4103
+ const oldContent = fs2.readFileSync(refactorOutputPath, "utf-8");
4104
+ const oldVarPattern = /(?:^|\n)\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?(--[a-zA-Z0-9-]+):\s*([^;]+);/gm;
4105
+ let match;
4106
+ while ((match = oldVarPattern.exec(oldContent)) !== null) {
4107
+ const varName = match[1];
4108
+ const varValue = match[2].trim();
4109
+ if (!(varName in data.cssVariables)) deprecatedTokens.set(varName, varValue);
4110
+ }
4111
+ }
4112
+ const dsVersion = data.meta.version ?? "?";
4113
+ const dsExportedAt = data.meta.exportedAt ? new Date(data.meta.exportedAt).toLocaleString() : "N/A";
4114
+ if (deprecatedTokens.size === 0) {
4115
+ return {
4116
+ description: "Refactor codebase for deprecated tokens",
4117
+ messages: [{
4118
+ role: "user",
4119
+ content: { type: "text", text: `\u2713 No deprecated tokens found.
4120
+
4121
+ Your token file \`${refactorOutput}\` is aligned with the design system (v${dsVersion}, exported ${dsExportedAt}). No tokens need migration.` }
4122
+ }]
3389
4123
  };
3390
4124
  }
3391
- const format = lastSyncAffectedTokens.format;
4125
+ const format = refactorFormat;
3392
4126
  const isNativeFormat = ["swift", "kotlin", "dart"].includes(format);
4127
+ const removed = Array.from(deprecatedTokens.entries()).map(([token, lastValue]) => ({ token, lastValue }));
3393
4128
  let searchPatterns = "";
3394
4129
  let fileExtensions = "";
3395
4130
  if (isNativeFormat) {
3396
- const nativeTokens = lastSyncAffectedTokens.removed.map((r) => {
4131
+ const nativeTokens = removed.map((r) => {
3397
4132
  const withoutPrefix = r.token.replace(/^--atmx-/, "");
3398
4133
  const camelCase = withoutPrefix.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
3399
4134
  const pascalCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
@@ -3411,15 +4146,20 @@ Your codebase is up to date! The last sync on ${new Date(lastSyncAffectedTokens.
3411
4146
  }
3412
4147
  } else {
3413
4148
  fileExtensions = ".tsx, .ts, .jsx, .js, .css, .scss, .less, .vue, .svelte files";
3414
- searchPatterns = lastSyncAffectedTokens.removed.map(
4149
+ searchPatterns = removed.map(
3415
4150
  (r) => ` \u2022 \`var(${r.token})\` or \`"${r.token}"\` (was: ${r.lastValue})`
3416
4151
  ).join("\n");
3417
4152
  }
3418
4153
  const instructions = `Scan the codebase for deprecated design tokens and help update them.
3419
4154
 
3420
- ## Deprecated Tokens (${lastSyncAffectedTokens.removed.length})
4155
+ ## Design System (fresh from DB)
4156
+ - **Version:** ${dsVersion}
4157
+ - **Last exported:** ${dsExportedAt}
4158
+ - **Token file:** ${refactorOutput}
4159
+
4160
+ ## Deprecated Tokens (${removed.length})
3421
4161
 
3422
- The following tokens have been removed from the design system:
4162
+ The following tokens exist in your local token file but have been removed from the design system:
3423
4163
 
3424
4164
  ${searchPatterns}
3425
4165
 
@@ -3588,7 +4328,7 @@ ${tokenSummary}
3588
4328
 
3589
4329
  ---
3590
4330
 
3591
- *Atomix Studio \u2014 https://atomixstudio.eu | DS: https://atomixstudio.eu/ds/${dsId}*
4331
+ *Atomix Studio \u2014 https://atomix.studio | DS: https://atomix.studio/ds/${dsId}*
3592
4332
  `;
3593
4333
  }
3594
4334
  async function startServer() {