@atomixstudio/mcp 1.0.30 → 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/README.md +4 -4
- package/dist/{chunk-BXWOCXYT.js → chunk-CE6J5MJX.js} +5 -1
- package/dist/chunk-CE6J5MJX.js.map +1 -0
- package/dist/figma-bridge-protocol.d.ts +1 -1
- package/dist/figma-bridge-protocol.js +1 -1
- package/dist/index.js +411 -108
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-BXWOCXYT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isAllowedMethod,
|
|
4
4
|
normalizeBridgeMethod
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CE6J5MJX.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -1289,6 +1289,159 @@ function getTokenStats(data) {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
1291
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
|
+
}
|
|
1292
1445
|
function figmaColorNameWithGroup(key) {
|
|
1293
1446
|
if (key.includes("/")) {
|
|
1294
1447
|
const [group2, ...rest] = key.split("/");
|
|
@@ -1415,17 +1568,114 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1415
1568
|
const tokens = data.tokens;
|
|
1416
1569
|
const colors = tokens?.colors;
|
|
1417
1570
|
const typography = tokens?.typography;
|
|
1418
|
-
const modes = [];
|
|
1419
|
-
const variables = [];
|
|
1420
|
-
const paintStyles = [];
|
|
1421
1571
|
const hexRe = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
|
|
1422
|
-
const
|
|
1572
|
+
const modes = [];
|
|
1423
1573
|
if (colors?.modes) {
|
|
1424
1574
|
const light = colors.modes.light ?? {};
|
|
1425
1575
|
const dark = colors.modes.dark ?? {};
|
|
1426
1576
|
if (Object.keys(light).length > 0) modes.push("Light");
|
|
1427
1577
|
if (Object.keys(dark).length > 0 && !modes.includes("Dark")) modes.push("Dark");
|
|
1428
|
-
|
|
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 ?? {};
|
|
1429
1679
|
const orderedKeys = [...Object.keys(light)];
|
|
1430
1680
|
for (const k of Object.keys(dark)) {
|
|
1431
1681
|
if (!orderedKeys.includes(k)) orderedKeys.push(k);
|
|
@@ -1436,14 +1686,34 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1436
1686
|
const lightHex = typeof lightVal === "string" ? colorTo8DigitHex(lightVal) ?? (hexRe.test(lightVal) ? lightVal : null) : null;
|
|
1437
1687
|
if (lightHex && hexRe.test(lightHex)) {
|
|
1438
1688
|
const figmaName = figmaColorNameWithGroup(key);
|
|
1439
|
-
if (
|
|
1440
|
-
|
|
1689
|
+
if (semanticNames.has(figmaName)) continue;
|
|
1690
|
+
semanticNames.add(figmaName);
|
|
1441
1691
|
const darkHex = typeof darkVal === "string" ? colorTo8DigitHex(darkVal) ?? (hexRe.test(darkVal) ? darkVal : null) : null;
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
+
});
|
|
1447
1717
|
}
|
|
1448
1718
|
}
|
|
1449
1719
|
}
|
|
@@ -1451,50 +1721,43 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1451
1721
|
for (const [key, hex] of Object.entries(colors.static.brand)) {
|
|
1452
1722
|
if (typeof hex !== "string" || !hexRe.test(hex)) continue;
|
|
1453
1723
|
const figmaName = figmaColorNameWithGroup(`brand/${key}`);
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1456
|
-
paintStyles.push({ name: figmaName, color: hex });
|
|
1457
|
-
if (modes.length === 0) modes.push("Light");
|
|
1724
|
+
if (semanticNames.has(figmaName)) continue;
|
|
1725
|
+
semanticNames.add(figmaName);
|
|
1458
1726
|
const values = {};
|
|
1459
1727
|
for (const m of modes) values[m] = hex;
|
|
1460
|
-
|
|
1728
|
+
semanticVariables.push({ name: figmaName, values });
|
|
1461
1729
|
}
|
|
1462
1730
|
}
|
|
1463
|
-
const
|
|
1464
|
-
if (
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
if (addedNames.has(figmaName)) continue;
|
|
1472
|
-
addedNames.add(figmaName);
|
|
1473
|
-
const values = {};
|
|
1474
|
-
for (const m of modes) values[m] = hex;
|
|
1475
|
-
variables.push({ name: figmaName, values });
|
|
1476
|
-
paintStyles.push({ name: figmaName, color: hex });
|
|
1477
|
-
}
|
|
1731
|
+
const colorVariableCollections = [];
|
|
1732
|
+
if (primitiveVariables.length > 0) {
|
|
1733
|
+
colorVariableCollections.push({
|
|
1734
|
+
collectionName: primitivesCollectionName,
|
|
1735
|
+
modes: primitiveModes,
|
|
1736
|
+
variables: primitiveVariables,
|
|
1737
|
+
applyScopes: false
|
|
1738
|
+
});
|
|
1478
1739
|
}
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
if (addedNames.has(figmaName)) continue;
|
|
1487
|
-
addedNames.add(figmaName);
|
|
1488
|
-
const values = {};
|
|
1489
|
-
for (const m of modes) values[m] = hex;
|
|
1490
|
-
variables.push({ name: figmaName, values });
|
|
1491
|
-
paintStyles.push({ name: figmaName, color: hex });
|
|
1492
|
-
}
|
|
1740
|
+
if (radixVariables.length > 0) {
|
|
1741
|
+
colorVariableCollections.push({
|
|
1742
|
+
collectionName: radixCollectionName,
|
|
1743
|
+
modes,
|
|
1744
|
+
variables: radixVariables,
|
|
1745
|
+
applyScopes: false
|
|
1746
|
+
});
|
|
1493
1747
|
}
|
|
1494
|
-
if (
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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 = [];
|
|
1498
1761
|
const textStyles = [];
|
|
1499
1762
|
const sizeToPx = (val, basePx = 16) => {
|
|
1500
1763
|
if (typeof val === "number") return Math.round(val);
|
|
@@ -1720,7 +1983,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1720
1983
|
return nameA.localeCompare(nameB);
|
|
1721
1984
|
});
|
|
1722
1985
|
return {
|
|
1723
|
-
|
|
1986
|
+
colorVariableCollections,
|
|
1724
1987
|
paintStyles,
|
|
1725
1988
|
textStyles,
|
|
1726
1989
|
numberVariableCollections,
|
|
@@ -1930,7 +2193,7 @@ function parseArgs() {
|
|
|
1930
2193
|
}
|
|
1931
2194
|
var cliArgs = parseArgs();
|
|
1932
2195
|
var { dsId, apiKey, accessToken } = cliArgs;
|
|
1933
|
-
var apiBase = cliArgs.apiBase || "https://
|
|
2196
|
+
var apiBase = cliArgs.apiBase || "https://atomix.studio";
|
|
1934
2197
|
var cachedData = null;
|
|
1935
2198
|
var cachedETag = null;
|
|
1936
2199
|
var cachedMcpTier = null;
|
|
@@ -1994,6 +2257,10 @@ function formatValidationBlock(entries) {
|
|
|
1994
2257
|
async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
1995
2258
|
if (!dsId) throw new Error("Missing --ds-id. Usage: npx @atomixstudio/mcp --ds-id <id> --atomix-token <token>");
|
|
1996
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
|
+
}
|
|
1997
2264
|
const result = await fetchDesignSystem({
|
|
1998
2265
|
dsId,
|
|
1999
2266
|
accessToken,
|
|
@@ -2056,7 +2323,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
2056
2323
|
var server = new Server(
|
|
2057
2324
|
{
|
|
2058
2325
|
name: "atomix-mcp-user",
|
|
2059
|
-
version: "1.0.
|
|
2326
|
+
version: "1.0.30"
|
|
2060
2327
|
},
|
|
2061
2328
|
{
|
|
2062
2329
|
capabilities: {
|
|
@@ -2355,8 +2622,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2355
2622
|
const totalChanges = lightChanges + darkChanges + deprecatedCount;
|
|
2356
2623
|
if (totalChanges === 0 && !dryRun) {
|
|
2357
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() };
|
|
2358
2626
|
return {
|
|
2359
|
-
responseText: `\u2713 Already up to date
|
|
2627
|
+
responseText: `\u2713 Already up to date (checked DB v${designSystemData.meta.version}, exported ${lastUpdated}).
|
|
2360
2628
|
|
|
2361
2629
|
File: ${output}
|
|
2362
2630
|
Tokens: ${tokenCount}
|
|
@@ -2367,8 +2635,9 @@ Last updated: ${lastUpdated}`,
|
|
|
2367
2635
|
};
|
|
2368
2636
|
}
|
|
2369
2637
|
if (totalChanges === 0 && dryRun) {
|
|
2638
|
+
lastSyncAffectedTokens = { modified: [], removed: [], added: [], format, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2370
2639
|
return {
|
|
2371
|
-
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.
|
|
2372
2641
|
|
|
2373
2642
|
File: ${output}
|
|
2374
2643
|
Tokens: ${tokenCount}
|
|
@@ -3009,9 +3278,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3009
3278
|
out.agentInstruction = agentStartBridge;
|
|
3010
3279
|
out.userInstruction = `If the bridge still does not connect: ${userSteps}`;
|
|
3011
3280
|
out.figmaPayload = {
|
|
3012
|
-
|
|
3013
|
-
modes: payloads.colorVariables.modes,
|
|
3014
|
-
variables: payloads.colorVariables.variables,
|
|
3281
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
3015
3282
|
paintStyles: payloads.paintStyles,
|
|
3016
3283
|
textStyles: payloads.textStyles,
|
|
3017
3284
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -3021,13 +3288,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3021
3288
|
content: [{ type: "text", text: JSON.stringify(out, null, 2) }]
|
|
3022
3289
|
};
|
|
3023
3290
|
}
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
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;
|
|
3031
3306
|
}
|
|
3032
3307
|
if (payloads.paintStyles.length > 0) {
|
|
3033
3308
|
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
@@ -3069,9 +3344,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3069
3344
|
});
|
|
3070
3345
|
}
|
|
3071
3346
|
out.figmaPayload = {
|
|
3072
|
-
|
|
3073
|
-
modes: payloads.colorVariables.modes,
|
|
3074
|
-
variables: payloads.colorVariables.variables,
|
|
3347
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
3075
3348
|
paintStyles: payloads.paintStyles,
|
|
3076
3349
|
textStyles: payloads.textStyles,
|
|
3077
3350
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -3080,9 +3353,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3080
3353
|
} catch (e) {
|
|
3081
3354
|
out.error = e instanceof Error ? e.message : String(e);
|
|
3082
3355
|
out.figmaPayload = {
|
|
3083
|
-
|
|
3084
|
-
modes: payloads.colorVariables.modes,
|
|
3085
|
-
variables: payloads.colorVariables.variables,
|
|
3356
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
3086
3357
|
paintStyles: payloads.paintStyles,
|
|
3087
3358
|
textStyles: payloads.textStyles,
|
|
3088
3359
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -3108,11 +3379,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3108
3379
|
}
|
|
3109
3380
|
const summaryParts = [];
|
|
3110
3381
|
const colorResult = out.colorVariables;
|
|
3111
|
-
if (colorResult
|
|
3112
|
-
const
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
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
|
+
}
|
|
3116
3397
|
}
|
|
3117
3398
|
const paintResult = out.paintStyles;
|
|
3118
3399
|
if (paintResult) {
|
|
@@ -3340,7 +3621,7 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3340
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>
|
|
3341
3622
|
</ul>
|
|
3342
3623
|
</div>
|
|
3343
|
-
<p class="tips">Keep the source of truth at <a href="https://
|
|
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>
|
|
3344
3625
|
</div>
|
|
3345
3626
|
</body>
|
|
3346
3627
|
</html>
|
|
@@ -3464,7 +3745,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3464
3745
|
};
|
|
3465
3746
|
}
|
|
3466
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;
|
|
3467
|
-
const shouldForceRefresh = canonicalName === "sync";
|
|
3748
|
+
const shouldForceRefresh = canonicalName === "sync" || canonicalName === "refactor";
|
|
3468
3749
|
let data = null;
|
|
3469
3750
|
let stats = null;
|
|
3470
3751
|
try {
|
|
@@ -3481,7 +3762,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3481
3762
|
content: {
|
|
3482
3763
|
type: "text",
|
|
3483
3764
|
text: `The MCP server isn't configured. To use design system prompts, you need to configure the server with:
|
|
3484
|
-
- \`--ds-id\`: Your design system ID (get it from https://
|
|
3765
|
+
- \`--ds-id\`: Your design system ID (get it from https://atomix.studio/ds/[your-ds-id])
|
|
3485
3766
|
- \`--atomix-token\`: Your access token (get it from the Export modal or Settings \u2192 Regenerate Atomix access token)
|
|
3486
3767
|
|
|
3487
3768
|
Both are required. Configure the MCP server in your AI tool's MCP settings, then restart your AI tool.`
|
|
@@ -3795,42 +4076,59 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
3795
4076
|
};
|
|
3796
4077
|
}
|
|
3797
4078
|
case "refactor": {
|
|
3798
|
-
|
|
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) {
|
|
3799
4084
|
return {
|
|
3800
4085
|
description: "Refactor codebase for deprecated tokens",
|
|
3801
|
-
messages: [
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
type: "text",
|
|
3806
|
-
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.`
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
]
|
|
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
|
+
}]
|
|
3810
4090
|
};
|
|
3811
4091
|
}
|
|
3812
|
-
if (
|
|
4092
|
+
if (!refactorFileExists) {
|
|
3813
4093
|
return {
|
|
3814
4094
|
description: "Refactor codebase for deprecated tokens",
|
|
3815
|
-
messages: [
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
type: "text",
|
|
3820
|
-
text: `\u2713 No deprecated tokens found in last sync.
|
|
3821
|
-
|
|
3822
|
-
Your codebase is up to date! The last sync on ${new Date(lastSyncAffectedTokens.timestamp).toLocaleString()} did not remove any tokens.`
|
|
3823
|
-
}
|
|
3824
|
-
}
|
|
3825
|
-
]
|
|
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
|
+
}]
|
|
3826
4099
|
};
|
|
3827
4100
|
}
|
|
3828
|
-
const
|
|
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
|
+
}]
|
|
4123
|
+
};
|
|
4124
|
+
}
|
|
4125
|
+
const format = refactorFormat;
|
|
3829
4126
|
const isNativeFormat = ["swift", "kotlin", "dart"].includes(format);
|
|
4127
|
+
const removed = Array.from(deprecatedTokens.entries()).map(([token, lastValue]) => ({ token, lastValue }));
|
|
3830
4128
|
let searchPatterns = "";
|
|
3831
4129
|
let fileExtensions = "";
|
|
3832
4130
|
if (isNativeFormat) {
|
|
3833
|
-
const nativeTokens =
|
|
4131
|
+
const nativeTokens = removed.map((r) => {
|
|
3834
4132
|
const withoutPrefix = r.token.replace(/^--atmx-/, "");
|
|
3835
4133
|
const camelCase = withoutPrefix.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
3836
4134
|
const pascalCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
@@ -3848,15 +4146,20 @@ Your codebase is up to date! The last sync on ${new Date(lastSyncAffectedTokens.
|
|
|
3848
4146
|
}
|
|
3849
4147
|
} else {
|
|
3850
4148
|
fileExtensions = ".tsx, .ts, .jsx, .js, .css, .scss, .less, .vue, .svelte files";
|
|
3851
|
-
searchPatterns =
|
|
4149
|
+
searchPatterns = removed.map(
|
|
3852
4150
|
(r) => ` \u2022 \`var(${r.token})\` or \`"${r.token}"\` (was: ${r.lastValue})`
|
|
3853
4151
|
).join("\n");
|
|
3854
4152
|
}
|
|
3855
4153
|
const instructions = `Scan the codebase for deprecated design tokens and help update them.
|
|
3856
4154
|
|
|
3857
|
-
##
|
|
4155
|
+
## Design System (fresh from DB)
|
|
4156
|
+
- **Version:** ${dsVersion}
|
|
4157
|
+
- **Last exported:** ${dsExportedAt}
|
|
4158
|
+
- **Token file:** ${refactorOutput}
|
|
4159
|
+
|
|
4160
|
+
## Deprecated Tokens (${removed.length})
|
|
3858
4161
|
|
|
3859
|
-
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:
|
|
3860
4163
|
|
|
3861
4164
|
${searchPatterns}
|
|
3862
4165
|
|
|
@@ -4025,7 +4328,7 @@ ${tokenSummary}
|
|
|
4025
4328
|
|
|
4026
4329
|
---
|
|
4027
4330
|
|
|
4028
|
-
*Atomix Studio \u2014 https://
|
|
4331
|
+
*Atomix Studio \u2014 https://atomix.studio | DS: https://atomix.studio/ds/${dsId}*
|
|
4029
4332
|
`;
|
|
4030
4333
|
}
|
|
4031
4334
|
async function startServer() {
|