@atomixstudio/mcp 1.0.27 → 1.0.29
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 +1 -1
- package/dist/index.js +112 -940
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1289,6 +1289,9 @@ function getTokenStats(data) {
|
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
1291
1291
|
// src/index.ts
|
|
1292
|
+
import {
|
|
1293
|
+
buildFigmaPayloadsFromDS
|
|
1294
|
+
} from "@atomix/figma_tools";
|
|
1292
1295
|
import * as path2 from "path";
|
|
1293
1296
|
import * as fs2 from "fs";
|
|
1294
1297
|
import { execSync } from "child_process";
|
|
@@ -1300,8 +1303,8 @@ var FIGMA_BRIDGE_TIMEOUT_MS = 15e3;
|
|
|
1300
1303
|
var FIGMA_BRIDGE_TOKEN = process.env.FIGMA_BRIDGE_TOKEN || null;
|
|
1301
1304
|
var FIGMA_CONNECTION_INSTRUCTIONS = {
|
|
1302
1305
|
installAndRun: "In Figma: Open Plugins and run the Atomix plugin (Atomix Token Extractor). If it's not installed yet, install it from the Figma Community or your team's plugin library, then run it.",
|
|
1303
|
-
connect: 'In the plugin UI, tap **Connect
|
|
1304
|
-
startBridge: "The Figma bridge runs with this MCP server. Ensure
|
|
1306
|
+
connect: 'In the plugin UI, tap **Connect** and wait until the status shows "Connected".',
|
|
1307
|
+
startBridge: "The Figma bridge runs with this MCP server. Ensure your AI environment has this MCP server running (e.g. in MCP settings), then in Figma run the Atomix plugin and tap Connect."
|
|
1305
1308
|
};
|
|
1306
1309
|
var bridgeWss = null;
|
|
1307
1310
|
var pluginWs = null;
|
|
@@ -1445,7 +1448,7 @@ function sendBridgeRequest(method, params, timeoutMs = FIGMA_BRIDGE_TIMEOUT_MS)
|
|
|
1445
1448
|
const ws = pluginWs;
|
|
1446
1449
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1447
1450
|
return Promise.reject(
|
|
1448
|
-
new Error("Figma plugin not connected. Open Figma, run Atomix plugin, and
|
|
1451
|
+
new Error("Figma plugin not connected. Open Figma, run Atomix plugin, and tap Connect.")
|
|
1449
1452
|
);
|
|
1450
1453
|
}
|
|
1451
1454
|
const id = `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -1465,373 +1468,6 @@ function sendBridgeRequest(method, params, timeoutMs = FIGMA_BRIDGE_TIMEOUT_MS)
|
|
|
1465
1468
|
}
|
|
1466
1469
|
});
|
|
1467
1470
|
}
|
|
1468
|
-
function figmaColorNameWithGroup(key) {
|
|
1469
|
-
if (key.includes("/")) {
|
|
1470
|
-
const [group2, ...rest] = key.split("/");
|
|
1471
|
-
const name2 = rest.join("/").trim();
|
|
1472
|
-
if (!name2) return key;
|
|
1473
|
-
const groupDisplay2 = group2.charAt(0).toUpperCase() + group2.slice(1).toLowerCase();
|
|
1474
|
-
return `${groupDisplay2} / ${name2}`;
|
|
1475
|
-
}
|
|
1476
|
-
const firstDash = key.indexOf("-");
|
|
1477
|
-
if (firstDash <= 0) return key;
|
|
1478
|
-
const group = key.slice(0, firstDash);
|
|
1479
|
-
const name = key.slice(firstDash + 1);
|
|
1480
|
-
const groupDisplay = group.charAt(0).toUpperCase() + group.slice(1).toLowerCase();
|
|
1481
|
-
return `${groupDisplay} / ${name}`;
|
|
1482
|
-
}
|
|
1483
|
-
var FIGMA_SHADOW_ORDER = {
|
|
1484
|
-
none: 0,
|
|
1485
|
-
xs: 1,
|
|
1486
|
-
sm: 2,
|
|
1487
|
-
md: 3,
|
|
1488
|
-
lg: 4,
|
|
1489
|
-
xl: 5,
|
|
1490
|
-
"2xl": 6,
|
|
1491
|
-
focus: 7
|
|
1492
|
-
};
|
|
1493
|
-
function tokenValueToNumber(s) {
|
|
1494
|
-
if (typeof s !== "string" || !s.trim()) return 0;
|
|
1495
|
-
const t = s.trim();
|
|
1496
|
-
if (t.endsWith("rem")) {
|
|
1497
|
-
const n2 = parseFloat(t.replace(/rem$/, ""));
|
|
1498
|
-
return Number.isFinite(n2) ? Math.round(n2 * 16) : 0;
|
|
1499
|
-
}
|
|
1500
|
-
if (t.endsWith("px")) {
|
|
1501
|
-
const n2 = parseFloat(t.replace(/px$/, ""));
|
|
1502
|
-
return Number.isFinite(n2) ? Math.round(n2) : 0;
|
|
1503
|
-
}
|
|
1504
|
-
const n = parseFloat(t);
|
|
1505
|
-
return Number.isFinite(n) ? n : 0;
|
|
1506
|
-
}
|
|
1507
|
-
function parseBoxShadowToFigmaEffect(shadowStr) {
|
|
1508
|
-
const s = shadowStr.trim();
|
|
1509
|
-
if (!s || s.toLowerCase() === "none") return null;
|
|
1510
|
-
const parsePx = (x) => typeof x === "string" ? parseFloat(x.replace(/px$/i, "")) : NaN;
|
|
1511
|
-
const colorMatch = s.match(/(rgba?\s*\([^)]+\)|#[0-9A-Fa-f]{3,8})\s*$/i);
|
|
1512
|
-
const colorStr = colorMatch ? colorMatch[1].trim() : void 0;
|
|
1513
|
-
const rest = (colorMatch ? s.slice(0, colorMatch.index) : s).trim();
|
|
1514
|
-
const parts = rest ? rest.split(/\s+/) : [];
|
|
1515
|
-
if (parts.length < 3) return null;
|
|
1516
|
-
const offsetX = parsePx(parts[0]);
|
|
1517
|
-
const offsetY = parsePx(parts[1]);
|
|
1518
|
-
const blur = parsePx(parts[2]);
|
|
1519
|
-
let spread = 0;
|
|
1520
|
-
if (parts.length >= 4) spread = parsePx(parts[3]);
|
|
1521
|
-
let r = 0, g = 0, b = 0, a = 0.1;
|
|
1522
|
-
if (colorStr) {
|
|
1523
|
-
const rgbaMatch = colorStr.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
|
|
1524
|
-
if (rgbaMatch) {
|
|
1525
|
-
r = Number(rgbaMatch[1]) / 255;
|
|
1526
|
-
g = Number(rgbaMatch[2]) / 255;
|
|
1527
|
-
b = Number(rgbaMatch[3]) / 255;
|
|
1528
|
-
a = rgbaMatch[4] != null ? parseFloat(rgbaMatch[4]) : 1;
|
|
1529
|
-
} else {
|
|
1530
|
-
const hexMatch = colorStr.match(/^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/);
|
|
1531
|
-
if (hexMatch) {
|
|
1532
|
-
r = parseInt(hexMatch[1].slice(0, 2), 16) / 255;
|
|
1533
|
-
g = parseInt(hexMatch[1].slice(2, 4), 16) / 255;
|
|
1534
|
-
b = parseInt(hexMatch[1].slice(4, 6), 16) / 255;
|
|
1535
|
-
a = hexMatch[2] ? parseInt(hexMatch[2], 16) / 255 : 0.1;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
if (!Number.isFinite(offsetX) || !Number.isFinite(offsetY) || !Number.isFinite(blur)) return null;
|
|
1540
|
-
return {
|
|
1541
|
-
type: "DROP_SHADOW",
|
|
1542
|
-
offset: { x: offsetX, y: offsetY },
|
|
1543
|
-
radius: Math.max(0, blur),
|
|
1544
|
-
spread: Number.isFinite(spread) ? spread : 0,
|
|
1545
|
-
color: { r, g, b, a },
|
|
1546
|
-
visible: true,
|
|
1547
|
-
blendMode: "NORMAL"
|
|
1548
|
-
};
|
|
1549
|
-
}
|
|
1550
|
-
function parseBoxShadowToFigmaEffects(shadowStr) {
|
|
1551
|
-
const s = (shadowStr || "").trim();
|
|
1552
|
-
if (!s || s.toLowerCase() === "none") return [];
|
|
1553
|
-
const out = [];
|
|
1554
|
-
const segments = s.split(/\s*,\s*/);
|
|
1555
|
-
for (const seg of segments) {
|
|
1556
|
-
const effect = parseBoxShadowToFigmaEffect(seg.trim());
|
|
1557
|
-
if (effect) out.push(effect);
|
|
1558
|
-
}
|
|
1559
|
-
return out;
|
|
1560
|
-
}
|
|
1561
|
-
function buildFigmaPayloadsFromDS(data) {
|
|
1562
|
-
const tokens = data.tokens;
|
|
1563
|
-
const colors = tokens?.colors;
|
|
1564
|
-
const typography = tokens?.typography;
|
|
1565
|
-
const modes = [];
|
|
1566
|
-
const variables = [];
|
|
1567
|
-
const paintStyles = [];
|
|
1568
|
-
const hexRe = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
|
|
1569
|
-
const addedNames = /* @__PURE__ */ new Set();
|
|
1570
|
-
if (colors?.modes) {
|
|
1571
|
-
const light = colors.modes.light ?? {};
|
|
1572
|
-
const dark = colors.modes.dark ?? {};
|
|
1573
|
-
if (Object.keys(light).length > 0) modes.push("Light");
|
|
1574
|
-
if (Object.keys(dark).length > 0 && !modes.includes("Dark")) modes.push("Dark");
|
|
1575
|
-
if (modes.length === 0) modes.push("Light");
|
|
1576
|
-
const orderedKeys = [...Object.keys(light)];
|
|
1577
|
-
for (const k of Object.keys(dark)) {
|
|
1578
|
-
if (!orderedKeys.includes(k)) orderedKeys.push(k);
|
|
1579
|
-
}
|
|
1580
|
-
for (const key of orderedKeys) {
|
|
1581
|
-
const lightHex = light[key];
|
|
1582
|
-
const darkHex = dark[key];
|
|
1583
|
-
if (typeof lightHex === "string" && hexRe.test(lightHex)) {
|
|
1584
|
-
const figmaName = figmaColorNameWithGroup(key);
|
|
1585
|
-
if (addedNames.has(figmaName)) continue;
|
|
1586
|
-
addedNames.add(figmaName);
|
|
1587
|
-
const values = {};
|
|
1588
|
-
if (modes.includes("Light")) values.Light = lightHex;
|
|
1589
|
-
if (modes.includes("Dark")) values.Dark = typeof darkHex === "string" && hexRe.test(darkHex) ? darkHex : lightHex;
|
|
1590
|
-
variables.push({ name: figmaName, values });
|
|
1591
|
-
paintStyles.push({ name: figmaName, color: lightHex });
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
if (colors?.static?.brand && typeof colors.static.brand === "object") {
|
|
1596
|
-
for (const [key, hex] of Object.entries(colors.static.brand)) {
|
|
1597
|
-
if (typeof hex !== "string" || !hexRe.test(hex)) continue;
|
|
1598
|
-
const figmaName = figmaColorNameWithGroup(`brand/${key}`);
|
|
1599
|
-
if (addedNames.has(figmaName)) continue;
|
|
1600
|
-
addedNames.add(figmaName);
|
|
1601
|
-
paintStyles.push({ name: figmaName, color: hex });
|
|
1602
|
-
if (modes.length === 0) modes.push("Light");
|
|
1603
|
-
const values = {};
|
|
1604
|
-
for (const m of modes) values[m] = hex;
|
|
1605
|
-
variables.push({ name: figmaName, values });
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
if (variables.length === 0 && modes.length === 0) modes.push("Light");
|
|
1609
|
-
const dsName = data.meta?.name;
|
|
1610
|
-
const collectionPrefix = dsName ? `${dsName} ` : "";
|
|
1611
|
-
const colorCollectionName = `${collectionPrefix}Colors`;
|
|
1612
|
-
const textStyles = [];
|
|
1613
|
-
const sizeToPx = (val, basePx = 16) => {
|
|
1614
|
-
if (typeof val === "number") return Math.round(val);
|
|
1615
|
-
const s = String(val).trim();
|
|
1616
|
-
const pxMatch = s.match(/^([\d.]+)\s*px$/i);
|
|
1617
|
-
if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
|
|
1618
|
-
const remMatch = s.match(/^([\d.]+)\s*rem$/i);
|
|
1619
|
-
if (remMatch) return Math.round(parseFloat(remMatch[1]) * basePx);
|
|
1620
|
-
const n = parseFloat(s);
|
|
1621
|
-
if (Number.isFinite(n)) return n <= 0 ? basePx : n < 50 ? Math.round(n * basePx) : Math.round(n);
|
|
1622
|
-
return basePx;
|
|
1623
|
-
};
|
|
1624
|
-
const letterSpacingToPx = (val, fontSizePx) => {
|
|
1625
|
-
if (val === void 0 || val === null) return void 0;
|
|
1626
|
-
if (typeof val === "number") return Math.round(val);
|
|
1627
|
-
const s = String(val).trim();
|
|
1628
|
-
const pxMatch = s.match(/^([-\d.]+)\s*px$/i);
|
|
1629
|
-
if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
|
|
1630
|
-
const emMatch = s.match(/^([-\d.]+)\s*em$/i);
|
|
1631
|
-
if (emMatch) return Math.round(parseFloat(emMatch[1]) * fontSizePx);
|
|
1632
|
-
const n = parseFloat(s);
|
|
1633
|
-
return Number.isFinite(n) ? Math.round(n) : void 0;
|
|
1634
|
-
};
|
|
1635
|
-
const firstFont = (obj) => {
|
|
1636
|
-
if (typeof obj === "string") {
|
|
1637
|
-
const primary = obj.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
|
|
1638
|
-
return primary || "Inter";
|
|
1639
|
-
}
|
|
1640
|
-
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
1641
|
-
const v = obj.body ?? obj.heading ?? obj.display ?? Object.values(obj)[0];
|
|
1642
|
-
return firstFont(v);
|
|
1643
|
-
}
|
|
1644
|
-
return "Inter";
|
|
1645
|
-
};
|
|
1646
|
-
const toFontFamilyString = (val) => {
|
|
1647
|
-
if (typeof val === "string") {
|
|
1648
|
-
const s = val.trim().replace(/^["']|["']$/g, "");
|
|
1649
|
-
return s || "Inter";
|
|
1650
|
-
}
|
|
1651
|
-
return firstFont(val);
|
|
1652
|
-
};
|
|
1653
|
-
const fontFamilyMap = typography?.fontFamily ?? {};
|
|
1654
|
-
const defaultFontFamily = typography ? firstFont(typography.fontFamily ?? "Inter") : "Inter";
|
|
1655
|
-
const fontSizeMap = typography?.fontSize;
|
|
1656
|
-
const fontWeightMap = typography?.fontWeight;
|
|
1657
|
-
const lineHeightMap = typography?.lineHeight;
|
|
1658
|
-
const letterSpacingMap = typography?.letterSpacing;
|
|
1659
|
-
const textTransformMap = typography?.textTransform;
|
|
1660
|
-
const textDecorationMap = typography?.textDecoration;
|
|
1661
|
-
if (fontSizeMap && typeof fontSizeMap === "object" && Object.keys(fontSizeMap).length > 0) {
|
|
1662
|
-
for (const [key, sizeVal] of Object.entries(fontSizeMap)) {
|
|
1663
|
-
const fontSize = sizeToPx(sizeVal);
|
|
1664
|
-
if (fontSize <= 0) continue;
|
|
1665
|
-
const role = typesetKeyToFontFamilyRole(key);
|
|
1666
|
-
const fontFamily = toFontFamilyString(
|
|
1667
|
-
fontFamilyMap[role] ?? fontFamilyMap.body ?? fontFamilyMap.heading ?? fontFamilyMap.display ?? defaultFontFamily
|
|
1668
|
-
);
|
|
1669
|
-
const lh = lineHeightMap && typeof lineHeightMap === "object" ? lineHeightMap[key] : void 0;
|
|
1670
|
-
const weight = fontWeightMap && typeof fontWeightMap === "object" ? fontWeightMap[key] : void 0;
|
|
1671
|
-
const fontWeight = weight != null ? String(weight) : "400";
|
|
1672
|
-
const letterSpacingPx = letterSpacingToPx(
|
|
1673
|
-
letterSpacingMap && typeof letterSpacingMap === "object" ? letterSpacingMap[key] : void 0,
|
|
1674
|
-
fontSize
|
|
1675
|
-
);
|
|
1676
|
-
const textTransform = textTransformMap && typeof textTransformMap === "object" ? textTransformMap[key] : void 0;
|
|
1677
|
-
const textDecoration = textDecorationMap && typeof textDecorationMap === "object" ? textDecorationMap[key] : void 0;
|
|
1678
|
-
const namePart = key.replace(/-/g, " / ");
|
|
1679
|
-
const style = {
|
|
1680
|
-
name: namePart.startsWith("Typography") ? namePart : `Typography / ${namePart}`,
|
|
1681
|
-
fontFamily,
|
|
1682
|
-
fontWeight,
|
|
1683
|
-
fontSize,
|
|
1684
|
-
lineHeightUnit: "PERCENT",
|
|
1685
|
-
letterSpacingUnit: "PIXELS",
|
|
1686
|
-
...letterSpacingPx !== void 0 && letterSpacingPx !== 0 ? { letterSpacingValue: letterSpacingPx } : {}
|
|
1687
|
-
};
|
|
1688
|
-
if (lh != null && typeof lh === "number" && lh > 0) {
|
|
1689
|
-
style.lineHeightValue = lh >= 10 ? Math.round(lh / fontSize * 100) : Math.round(lh * 100);
|
|
1690
|
-
} else {
|
|
1691
|
-
style.lineHeightValue = 150;
|
|
1692
|
-
}
|
|
1693
|
-
if (textTransform === "uppercase") style.textCase = "UPPER";
|
|
1694
|
-
else if (textTransform === "lowercase") style.textCase = "LOWER";
|
|
1695
|
-
else if (textTransform === "capitalize") style.textCase = "TITLE";
|
|
1696
|
-
else style.textCase = "ORIGINAL";
|
|
1697
|
-
if (textDecoration === "underline") style.textDecoration = "UNDERLINE";
|
|
1698
|
-
else style.textDecoration = "NONE";
|
|
1699
|
-
textStyles.push(style);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
const textStylesMap = typography?.textStyles;
|
|
1703
|
-
if (textStyles.length === 0 && textStylesMap && typeof textStylesMap === "object") {
|
|
1704
|
-
for (const [styleName, style] of Object.entries(textStylesMap)) {
|
|
1705
|
-
if (!style || typeof style !== "object") continue;
|
|
1706
|
-
const fontSize = sizeToPx(style.fontSize ?? "1rem");
|
|
1707
|
-
const lhStr = style.lineHeight;
|
|
1708
|
-
const lineHeightUnitless = lhStr != null ? lhStr.endsWith("%") ? parseFloat(lhStr) / 100 : sizeToPx(lhStr) / fontSize : 1.5;
|
|
1709
|
-
const payload = {
|
|
1710
|
-
name: styleName.startsWith("Typography") ? styleName : `Typography / ${styleName.replace(/\//g, " / ")}`,
|
|
1711
|
-
fontFamily: defaultFontFamily,
|
|
1712
|
-
fontWeight: String(style.fontWeight ?? "400"),
|
|
1713
|
-
fontSize,
|
|
1714
|
-
lineHeightUnit: "PERCENT",
|
|
1715
|
-
lineHeightValue: Math.round((Number.isFinite(lineHeightUnitless) ? lineHeightUnitless : 1.5) * 100),
|
|
1716
|
-
letterSpacingUnit: "PIXELS",
|
|
1717
|
-
textCase: "ORIGINAL",
|
|
1718
|
-
textDecoration: "NONE"
|
|
1719
|
-
};
|
|
1720
|
-
textStyles.push(payload);
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
textStyles.sort((a, b) => {
|
|
1724
|
-
if (a.fontSize !== b.fontSize) return a.fontSize - b.fontSize;
|
|
1725
|
-
return (a.name || "").localeCompare(b.name || "");
|
|
1726
|
-
});
|
|
1727
|
-
const numberVariableCollections = [];
|
|
1728
|
-
const spacing = tokens?.spacing;
|
|
1729
|
-
if (spacing?.scale && typeof spacing.scale === "object") {
|
|
1730
|
-
const variables2 = [];
|
|
1731
|
-
for (const [key, val] of Object.entries(spacing.scale)) {
|
|
1732
|
-
const n = tokenValueToNumber(val);
|
|
1733
|
-
if (n >= 0) variables2.push({ name: `Spacing / ${key}`, value: n });
|
|
1734
|
-
}
|
|
1735
|
-
variables2.sort((a, b) => a.value - b.value);
|
|
1736
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Spacing`, categoryKey: "Spacing", variables: variables2, scopes: ["GAP"] });
|
|
1737
|
-
}
|
|
1738
|
-
const radius = tokens?.radius;
|
|
1739
|
-
if (radius?.scale && typeof radius.scale === "object") {
|
|
1740
|
-
const variables2 = [];
|
|
1741
|
-
for (const [key, val] of Object.entries(radius.scale)) {
|
|
1742
|
-
const n = tokenValueToNumber(val);
|
|
1743
|
-
if (n >= 0) variables2.push({ name: `Radius / ${key}`, value: n });
|
|
1744
|
-
}
|
|
1745
|
-
variables2.sort((a, b) => a.value - b.value);
|
|
1746
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Radius`, categoryKey: "Radius", variables: variables2, scopes: ["CORNER_RADIUS"] });
|
|
1747
|
-
}
|
|
1748
|
-
const borders = tokens?.borders;
|
|
1749
|
-
if (borders?.width && typeof borders.width === "object") {
|
|
1750
|
-
const variables2 = [];
|
|
1751
|
-
for (const [key, val] of Object.entries(borders.width)) {
|
|
1752
|
-
const n = tokenValueToNumber(val);
|
|
1753
|
-
if (n >= 0) variables2.push({ name: `Borders / ${key}`, value: n });
|
|
1754
|
-
}
|
|
1755
|
-
variables2.sort((a, b) => a.value - b.value);
|
|
1756
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Borders`, categoryKey: "Borders", variables: variables2, scopes: ["STROKE_FLOAT"] });
|
|
1757
|
-
}
|
|
1758
|
-
const sizing = tokens?.sizing;
|
|
1759
|
-
const sizingVariables = [];
|
|
1760
|
-
if (sizing?.height && typeof sizing.height === "object") {
|
|
1761
|
-
for (const [key, val] of Object.entries(sizing.height)) {
|
|
1762
|
-
const n = tokenValueToNumber(val);
|
|
1763
|
-
if (n >= 0) sizingVariables.push({ name: `Height / ${key}`, value: n });
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
if (sizing?.icon && typeof sizing.icon === "object") {
|
|
1767
|
-
for (const [key, val] of Object.entries(sizing.icon)) {
|
|
1768
|
-
const n = tokenValueToNumber(val);
|
|
1769
|
-
if (n >= 0) sizingVariables.push({ name: `Icon / ${key}`, value: n });
|
|
1770
|
-
}
|
|
1771
|
-
}
|
|
1772
|
-
sizingVariables.sort((a, b) => a.value - b.value);
|
|
1773
|
-
if (sizingVariables.length > 0) {
|
|
1774
|
-
numberVariableCollections.push({ collectionName: `${collectionPrefix}Sizing`, categoryKey: "Sizing", variables: sizingVariables, scopes: ["WIDTH_HEIGHT"] });
|
|
1775
|
-
}
|
|
1776
|
-
const layout = tokens?.layout;
|
|
1777
|
-
if (layout?.breakpoint && typeof layout.breakpoint === "object") {
|
|
1778
|
-
const variables2 = [];
|
|
1779
|
-
for (const [key, val] of Object.entries(layout.breakpoint)) {
|
|
1780
|
-
const n = tokenValueToNumber(val);
|
|
1781
|
-
if (n >= 0) variables2.push({ name: `Breakpoint / ${key}`, value: n });
|
|
1782
|
-
}
|
|
1783
|
-
variables2.sort((a, b) => a.value - b.value);
|
|
1784
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Layout`, categoryKey: "Layout", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1785
|
-
}
|
|
1786
|
-
const effectStyles = [];
|
|
1787
|
-
const shadows = tokens?.shadows;
|
|
1788
|
-
if (shadows?.elevation && typeof shadows.elevation === "object") {
|
|
1789
|
-
for (const [key, val] of Object.entries(shadows.elevation)) {
|
|
1790
|
-
if (typeof val !== "string") continue;
|
|
1791
|
-
const effects = parseBoxShadowToFigmaEffects(val);
|
|
1792
|
-
if (effects.length > 0) {
|
|
1793
|
-
effectStyles.push({
|
|
1794
|
-
name: `Shadow / ${key}`,
|
|
1795
|
-
effects
|
|
1796
|
-
});
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
if (shadows?.focus && typeof shadows.focus === "string") {
|
|
1801
|
-
const effects = parseBoxShadowToFigmaEffects(shadows.focus);
|
|
1802
|
-
if (effects.length > 0) {
|
|
1803
|
-
effectStyles.push({ name: "Shadow / focus", effects });
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
effectStyles.sort((a, b) => {
|
|
1807
|
-
const nameA = a.name.startsWith("Shadow / ") ? a.name.slice(9) : a.name;
|
|
1808
|
-
const nameB = b.name.startsWith("Shadow / ") ? b.name.slice(9) : b.name;
|
|
1809
|
-
const orderA = FIGMA_SHADOW_ORDER[nameA] ?? 100;
|
|
1810
|
-
const orderB = FIGMA_SHADOW_ORDER[nameB] ?? 100;
|
|
1811
|
-
if (orderA !== orderB) return orderA - orderB;
|
|
1812
|
-
return nameA.localeCompare(nameB);
|
|
1813
|
-
});
|
|
1814
|
-
return {
|
|
1815
|
-
colorVariables: { collectionName: colorCollectionName, modes, variables },
|
|
1816
|
-
paintStyles,
|
|
1817
|
-
textStyles,
|
|
1818
|
-
numberVariableCollections,
|
|
1819
|
-
effectStyles
|
|
1820
|
-
};
|
|
1821
|
-
}
|
|
1822
|
-
function getExpectedFigmaNamesFromDS(data) {
|
|
1823
|
-
const payloads = buildFigmaPayloadsFromDS(data);
|
|
1824
|
-
const numberVariableNames = payloads.numberVariableCollections.flatMap(
|
|
1825
|
-
(c) => c.variables.map((v) => v.name)
|
|
1826
|
-
);
|
|
1827
|
-
return {
|
|
1828
|
-
colorVariableNames: payloads.colorVariables.variables.map((v) => v.name),
|
|
1829
|
-
paintStyleNames: payloads.paintStyles.map((s) => s.name),
|
|
1830
|
-
textStyleNames: payloads.textStyles.map((s) => s.name),
|
|
1831
|
-
effectStyleNames: payloads.effectStyles.map((s) => s.name),
|
|
1832
|
-
numberVariableNames
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
1471
|
function parseArgs() {
|
|
1836
1472
|
const args = process.argv.slice(2);
|
|
1837
1473
|
let dsId2 = null;
|
|
@@ -1865,23 +1501,8 @@ var authFailedNoTools = false;
|
|
|
1865
1501
|
function hasValidAuthConfig() {
|
|
1866
1502
|
return !!(dsId && accessToken);
|
|
1867
1503
|
}
|
|
1868
|
-
var AUTH_REQUIRED_MESSAGE = "Atomix MCP requires authentication. Add both --ds-id and --atomix-token to your MCP config (Settings \u2192 MCP), then restart
|
|
1504
|
+
var AUTH_REQUIRED_MESSAGE = "Atomix MCP requires authentication. Add both --ds-id and --atomix-token to your MCP config (Settings \u2192 MCP), then restart your AI tool. Get your token from Atomix Studio: Export modal or Settings \u2192 Regenerate Atomix access token.";
|
|
1869
1505
|
var lastChangeSummary = null;
|
|
1870
|
-
var FIGMA_SYNC_TOOL_NAME = "syncToFigma";
|
|
1871
|
-
var FIGMA_DESIGN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
1872
|
-
"getFigmaVariablesAndStyles",
|
|
1873
|
-
"createDesignPlaceholder",
|
|
1874
|
-
"resolveFigmaIdsForTokens",
|
|
1875
|
-
"designCreateFrame",
|
|
1876
|
-
"designCreateText",
|
|
1877
|
-
"designCreateRectangle",
|
|
1878
|
-
"designSetAutoLayout",
|
|
1879
|
-
"designSetLayoutConstraints",
|
|
1880
|
-
"designAppendChild",
|
|
1881
|
-
"getDesignScreenshot",
|
|
1882
|
-
"finalizeDesignFrame"
|
|
1883
|
-
]);
|
|
1884
|
-
var FIGMA_TOOL_NAMES = /* @__PURE__ */ new Set([FIGMA_SYNC_TOOL_NAME, ...FIGMA_DESIGN_TOOL_NAMES]);
|
|
1885
1506
|
var lastSyncAffectedTokens = null;
|
|
1886
1507
|
function getLastChangeSummary() {
|
|
1887
1508
|
return lastChangeSummary;
|
|
@@ -1998,7 +1619,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
1998
1619
|
var server = new Server(
|
|
1999
1620
|
{
|
|
2000
1621
|
name: "atomix-mcp-user",
|
|
2001
|
-
version: "1.0.
|
|
1622
|
+
version: "1.0.28"
|
|
2002
1623
|
},
|
|
2003
1624
|
{
|
|
2004
1625
|
capabilities: {
|
|
@@ -2018,18 +1639,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2018
1639
|
try {
|
|
2019
1640
|
await fetchDesignSystemForMCP(true);
|
|
2020
1641
|
if (cachedMcpTier === "pro") {
|
|
2021
|
-
console.error("[Atomix MCP] Resolved tier = pro.
|
|
1642
|
+
console.error("[Atomix MCP] Resolved tier = pro.");
|
|
2022
1643
|
} else if (cachedMcpTier === "free") {
|
|
2023
|
-
console.error(
|
|
2024
|
-
"[Atomix MCP] Resolved tier = free. Figma design tools and --design-in-figma are hidden. syncToFigma and /--sync-to-figma are available. Pro design tools appear when the DS owner has Pro and the pro_figma_export flag is enabled."
|
|
2025
|
-
);
|
|
1644
|
+
console.error("[Atomix MCP] Resolved tier = free. syncToFigma and /--sync-to-figma are available.");
|
|
2026
1645
|
}
|
|
2027
1646
|
} catch (err) {
|
|
2028
1647
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2029
1648
|
authFailedNoTools = true;
|
|
2030
1649
|
console.error(
|
|
2031
1650
|
"[Atomix MCP] Design system not loaded: ds-id or token invalid or API error. No tools will be shown.",
|
|
2032
|
-
msg.includes("401") ? " Token invalid or expired. Regenerate in Atomix Studio (Settings \u2192 Regenerate Atomix access token), update your MCP config, then restart
|
|
1651
|
+
msg.includes("401") ? " Token invalid or expired. Regenerate in Atomix Studio (Settings \u2192 Regenerate Atomix access token), update your MCP config, then restart your AI tool." : msg.includes("403") ? " You do not have access to this design system (owner or invited guest)." : msg.includes("404") ? " Design system not found (invalid ds-id)." : msg
|
|
2033
1652
|
);
|
|
2034
1653
|
}
|
|
2035
1654
|
}
|
|
@@ -2164,7 +1783,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2164
1783
|
},
|
|
2165
1784
|
{
|
|
2166
1785
|
name: "syncAll",
|
|
2167
|
-
description: "Sync tokens, AI rules, skills (
|
|
1786
|
+
description: "Sync tokens, AI rules, skills (.cursor/skills/atomix-ds/SKILL.md), and atomix-dependencies.json. Use dryRun: true first to report what would change without writing; then dryRun: false to apply. Response includes a VALIDATION section\u2014agent must check it to confirm success. Optional: output (default ./tokens.css), format (default css), skipTokens (if true, only skills and manifest), dryRun (if true, report only; no files written).",
|
|
2168
1787
|
inputSchema: {
|
|
2169
1788
|
type: "object",
|
|
2170
1789
|
properties: {
|
|
@@ -2216,160 +1835,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2216
1835
|
properties: {},
|
|
2217
1836
|
required: []
|
|
2218
1837
|
}
|
|
2219
|
-
},
|
|
2220
|
-
{
|
|
2221
|
-
name: "getFigmaVariablesAndStyles",
|
|
2222
|
-
description: "Get all local variable collections (with variable names and ids) and local paint, text, and effect styles from the open Figma file. Call after syncToFigma when designing in Figma; use with resolveFigmaIdsForTokens to get ids for granular design commands. Requires Atomix Figma plugin connected.",
|
|
2223
|
-
inputSchema: {
|
|
2224
|
-
type: "object",
|
|
2225
|
-
properties: {},
|
|
2226
|
-
required: []
|
|
2227
|
-
}
|
|
2228
|
-
},
|
|
2229
|
-
{
|
|
2230
|
-
name: "createDesignPlaceholder",
|
|
2231
|
-
description: "Create a placeholder frame in Figma with name 'Atomix: Preparing the design..', temporary background, and 'Design in progress...' text. Call this first when designing in Figma so the user sees the placeholder. Returns frameId; use that as parentId for designCreateFrame and other design commands. Optional width and height (default 400); use dimensions inferred from the request (e.g. mobile 390x844, card 400x300).",
|
|
2232
|
-
inputSchema: {
|
|
2233
|
-
type: "object",
|
|
2234
|
-
properties: {
|
|
2235
|
-
width: { type: "number", description: "Frame width in px (optional, default 400)" },
|
|
2236
|
-
height: { type: "number", description: "Frame height in px (optional, default 400)" }
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
name: "resolveFigmaIdsForTokens",
|
|
2242
|
-
description: "Resolve design system token names to Figma variable and style ids in the open file. Call after syncToFigma and getFigmaVariablesAndStyles. Returns a map keyed by Figma name (e.g. 'Spacing / lg', 'Background / surface') to { variableId?, paintStyleId?, textStyleId?, effectStyleId? }. Use these ids in designCreateFrame, designCreateText, designSetAutoLayout, etc. No arguments.",
|
|
2243
|
-
inputSchema: {
|
|
2244
|
-
type: "object",
|
|
2245
|
-
properties: {},
|
|
2246
|
-
required: []
|
|
2247
|
-
}
|
|
2248
|
-
},
|
|
2249
|
-
{
|
|
2250
|
-
name: "designCreateFrame",
|
|
2251
|
-
description: "Create a frame in Figma under the given parent. Use fillVariableId or fillPaintStyleId from resolveFigmaIdsForTokens for background. Returns nodeId.",
|
|
2252
|
-
inputSchema: {
|
|
2253
|
-
type: "object",
|
|
2254
|
-
properties: {
|
|
2255
|
-
parentId: { type: "string", description: "Parent frame/node id (e.g. from createDesignPlaceholder)" },
|
|
2256
|
-
name: { type: "string", description: "Layer name" },
|
|
2257
|
-
width: { type: "number", description: "Width in px (optional)" },
|
|
2258
|
-
height: { type: "number", description: "Height in px (optional)" },
|
|
2259
|
-
fillVariableId: { type: "string", description: "Color variable id from resolveFigmaIdsForTokens" },
|
|
2260
|
-
fillPaintStyleId: { type: "string", description: "Paint style id from resolveFigmaIdsForTokens" }
|
|
2261
|
-
},
|
|
2262
|
-
required: ["parentId", "name"]
|
|
2263
|
-
}
|
|
2264
|
-
},
|
|
2265
|
-
{
|
|
2266
|
-
name: "designCreateText",
|
|
2267
|
-
description: "Create a text node in Figma. Bind to a text style via textStyleId from resolveFigmaIdsForTokens.",
|
|
2268
|
-
inputSchema: {
|
|
2269
|
-
type: "object",
|
|
2270
|
-
properties: {
|
|
2271
|
-
parentId: { type: "string", description: "Parent frame id" },
|
|
2272
|
-
characters: { type: "string", description: "Text content" },
|
|
2273
|
-
textStyleId: { type: "string", description: "Text style id from resolveFigmaIdsForTokens (required)" },
|
|
2274
|
-
name: { type: "string", description: "Layer name (optional)" }
|
|
2275
|
-
},
|
|
2276
|
-
required: ["parentId", "characters", "textStyleId"]
|
|
2277
|
-
}
|
|
2278
|
-
},
|
|
2279
|
-
{
|
|
2280
|
-
name: "designCreateRectangle",
|
|
2281
|
-
description: "Create a rectangle in Figma. Use fillVariableId or fillPaintStyleId from resolveFigmaIdsForTokens.",
|
|
2282
|
-
inputSchema: {
|
|
2283
|
-
type: "object",
|
|
2284
|
-
properties: {
|
|
2285
|
-
parentId: { type: "string", description: "Parent frame id" },
|
|
2286
|
-
width: { type: "number", description: "Width in px" },
|
|
2287
|
-
height: { type: "number", description: "Height in px" },
|
|
2288
|
-
fillVariableId: { type: "string", description: "Color variable id" },
|
|
2289
|
-
fillPaintStyleId: { type: "string", description: "Paint style id" },
|
|
2290
|
-
name: { type: "string", description: "Layer name (optional)" }
|
|
2291
|
-
},
|
|
2292
|
-
required: ["parentId", "width", "height"]
|
|
2293
|
-
}
|
|
2294
|
-
},
|
|
2295
|
-
{
|
|
2296
|
-
name: "designSetAutoLayout",
|
|
2297
|
-
description: "Set auto-layout on a frame. Use variable ids from resolveFigmaIdsForTokens for padding and itemSpacing (e.g. Spacing / lg).",
|
|
2298
|
-
inputSchema: {
|
|
2299
|
-
type: "object",
|
|
2300
|
-
properties: {
|
|
2301
|
-
nodeId: { type: "string", description: "Frame node id" },
|
|
2302
|
-
direction: { type: "string", description: "HORIZONTAL or VERTICAL" },
|
|
2303
|
-
paddingVariableId: { type: "string", description: "Number variable id for all padding" },
|
|
2304
|
-
paddingTopVariableId: { type: "string" },
|
|
2305
|
-
paddingRightVariableId: { type: "string" },
|
|
2306
|
-
paddingBottomVariableId: { type: "string" },
|
|
2307
|
-
paddingLeftVariableId: { type: "string" },
|
|
2308
|
-
itemSpacingVariableId: { type: "string", description: "Number variable id for gap between children" },
|
|
2309
|
-
primaryAxisAlignItems: { type: "string", description: "MIN, CENTER, MAX, SPACE_BETWEEN" },
|
|
2310
|
-
counterAxisAlignItems: { type: "string", description: "MIN, CENTER, MAX, BASELINE" },
|
|
2311
|
-
layoutSizingHorizontal: { type: "string", description: "HUG or FILL" },
|
|
2312
|
-
layoutSizingVertical: { type: "string", description: "HUG or FILL" }
|
|
2313
|
-
},
|
|
2314
|
-
required: ["nodeId", "direction"]
|
|
2315
|
-
}
|
|
2316
|
-
},
|
|
2317
|
-
{
|
|
2318
|
-
name: "designSetLayoutConstraints",
|
|
2319
|
-
description: "Set min/max width and height on a frame (e.g. breakpoint variables). Use number variable ids from resolveFigmaIdsForTokens.",
|
|
2320
|
-
inputSchema: {
|
|
2321
|
-
type: "object",
|
|
2322
|
-
properties: {
|
|
2323
|
-
nodeId: { type: "string", description: "Frame node id" },
|
|
2324
|
-
minWidthVariableId: { type: "string" },
|
|
2325
|
-
maxWidthVariableId: { type: "string" },
|
|
2326
|
-
minHeightVariableId: { type: "string" },
|
|
2327
|
-
maxHeightVariableId: { type: "string" }
|
|
2328
|
-
},
|
|
2329
|
-
required: ["nodeId"]
|
|
2330
|
-
}
|
|
2331
|
-
},
|
|
2332
|
-
{
|
|
2333
|
-
name: "designAppendChild",
|
|
2334
|
-
description: "Move a node under a new parent (reparent). Use to reorder or nest nodes.",
|
|
2335
|
-
inputSchema: {
|
|
2336
|
-
type: "object",
|
|
2337
|
-
properties: {
|
|
2338
|
-
parentId: { type: "string", description: "New parent frame id" },
|
|
2339
|
-
childId: { type: "string", description: "Node id to move" }
|
|
2340
|
-
},
|
|
2341
|
-
required: ["parentId", "childId"]
|
|
2342
|
-
}
|
|
2343
|
-
},
|
|
2344
|
-
{
|
|
2345
|
-
name: "getDesignScreenshot",
|
|
2346
|
-
description: "Export a frame as PNG and return it as base64 so you can verify layout, content fill, and hug. Call after each design pass to check your work against the user's intent. Use the returned image to decide if another pass is needed.",
|
|
2347
|
-
inputSchema: {
|
|
2348
|
-
type: "object",
|
|
2349
|
-
properties: {
|
|
2350
|
-
frameId: { type: "string", description: "Frame node id (e.g. from createDesignPlaceholder)" },
|
|
2351
|
-
scale: { type: "number", description: "Export scale 1\u20134 (optional, default 1)" }
|
|
2352
|
-
},
|
|
2353
|
-
required: ["frameId"]
|
|
2354
|
-
}
|
|
2355
|
-
},
|
|
2356
|
-
{
|
|
2357
|
-
name: "finalizeDesignFrame",
|
|
2358
|
-
description: "Rename the design frame and remove the placeholder background. Call after the final design pass. Set name to a short description of the design plus ' \u2705'. Use fillVariableId or fillPaintStyleId from resolveFigmaIdsForTokens (e.g. Background / surface) so the gray placeholder is replaced.",
|
|
2359
|
-
inputSchema: {
|
|
2360
|
-
type: "object",
|
|
2361
|
-
properties: {
|
|
2362
|
-
frameId: { type: "string", description: "Frame node id to finalize" },
|
|
2363
|
-
name: { type: "string", description: "New frame name (e.g. 'Login card \u2705')" },
|
|
2364
|
-
fillVariableId: { type: "string", description: "Variable id for frame fill (removes placeholder bg)" },
|
|
2365
|
-
fillPaintStyleId: { type: "string", description: "Paint style id for frame fill" }
|
|
2366
|
-
},
|
|
2367
|
-
required: ["frameId", "name"]
|
|
2368
|
-
}
|
|
2369
1838
|
}
|
|
2370
1839
|
];
|
|
2371
|
-
|
|
2372
|
-
return { tools };
|
|
1840
|
+
return { tools: toolsList };
|
|
2373
1841
|
});
|
|
2374
1842
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2375
1843
|
const { name, arguments: args } = request.params;
|
|
@@ -2377,7 +1845,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2377
1845
|
return {
|
|
2378
1846
|
content: [{
|
|
2379
1847
|
type: "text",
|
|
2380
|
-
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
1848
|
+
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart your AI tool. No tools are available until then."
|
|
2381
1849
|
}],
|
|
2382
1850
|
isError: true
|
|
2383
1851
|
};
|
|
@@ -2385,17 +1853,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2385
1853
|
try {
|
|
2386
1854
|
const shouldForceRefresh = name === "syncAll";
|
|
2387
1855
|
const data = await fetchDesignSystemForMCP(shouldForceRefresh);
|
|
2388
|
-
if (FIGMA_DESIGN_TOOL_NAMES.has(name) && cachedMcpTier !== "pro") {
|
|
2389
|
-
return {
|
|
2390
|
-
content: [
|
|
2391
|
-
{
|
|
2392
|
-
type: "text",
|
|
2393
|
-
text: "This design system does not have Pro Figma access. Figma design tools (design-in-Figma, createDesignPlaceholder, resolveFigmaIdsForTokens, etc.) are available when the design system owner has a Pro subscription. Sync to Figma (syncToFigma) is available on all tiers."
|
|
2394
|
-
}
|
|
2395
|
-
],
|
|
2396
|
-
isError: true
|
|
2397
|
-
};
|
|
2398
|
-
}
|
|
2399
1856
|
async function performTokenSyncAndRules(designSystemData, tokenOutput, tokenFormat, dryRun) {
|
|
2400
1857
|
const output = tokenOutput;
|
|
2401
1858
|
const format = tokenFormat;
|
|
@@ -2814,11 +2271,11 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2814
2271
|
case "getSetupInstructions": {
|
|
2815
2272
|
const tool = args?.tool;
|
|
2816
2273
|
const instructions = {
|
|
2817
|
-
cursor: `#
|
|
2274
|
+
cursor: `# MCP Setup
|
|
2818
2275
|
|
|
2819
|
-
1. Create \`.cursor/mcp.json\`
|
|
2276
|
+
1. Create your MCP config file in the project (e.g. \`.cursor/mcp.json\` for Cursor)
|
|
2820
2277
|
2. Add the MCP configuration (use exportMCPConfig to get it)
|
|
2821
|
-
3. Restart
|
|
2278
|
+
3. Restart your IDE
|
|
2822
2279
|
4. Verify by asking: "What design tokens are available?"
|
|
2823
2280
|
|
|
2824
2281
|
## File Structure
|
|
@@ -2972,12 +2429,9 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2972
2429
|
const dsExportedAt = data.meta.exportedAt;
|
|
2973
2430
|
const skillsDir = path2.resolve(process.cwd(), ".cursor/skills/atomix-ds");
|
|
2974
2431
|
const skillPath1 = path2.join(skillsDir, "SKILL.md");
|
|
2975
|
-
const skillPath2 = path2.join(skillsDir, "FIGMA-SKILL.md");
|
|
2976
2432
|
const manifestPath = path2.resolve(process.cwd(), "atomix-dependencies.json");
|
|
2977
2433
|
if (dryRun) {
|
|
2978
|
-
parts.push(
|
|
2979
|
-
cachedMcpTier === "pro" ? "Would write skills: .cursor/skills/atomix-ds/SKILL.md, .cursor/skills/atomix-ds/FIGMA-SKILL.md" : "Would write skills: .cursor/skills/atomix-ds/SKILL.md"
|
|
2980
|
-
);
|
|
2434
|
+
parts.push("Would write skills: .cursor/skills/atomix-ds/SKILL.md");
|
|
2981
2435
|
parts.push("Would write manifest: atomix-dependencies.json");
|
|
2982
2436
|
const reportText = [parts.join("\n"), tokenResponseText].filter(Boolean).join("\n\n---\n\n");
|
|
2983
2437
|
return {
|
|
@@ -2990,14 +2444,7 @@ ${reportText}` }]
|
|
|
2990
2444
|
const genericWithVersion = injectSkillVersion(GENERIC_SKILL_MD, dsVersion, dsExportedAt);
|
|
2991
2445
|
fs2.writeFileSync(skillPath1, genericWithVersion);
|
|
2992
2446
|
allValidation.push({ path: skillPath1, status: fs2.existsSync(skillPath1) ? "OK" : "FAIL", detail: "Written." });
|
|
2993
|
-
|
|
2994
|
-
const figmaWithVersion = injectSkillVersion(FIGMA_DESIGN_SKILL_MD, dsVersion, dsExportedAt);
|
|
2995
|
-
fs2.writeFileSync(skillPath2, figmaWithVersion);
|
|
2996
|
-
allValidation.push({ path: skillPath2, status: fs2.existsSync(skillPath2) ? "OK" : "FAIL", detail: "Written." });
|
|
2997
|
-
}
|
|
2998
|
-
parts.push(
|
|
2999
|
-
cachedMcpTier === "pro" ? "Skills: .cursor/skills/atomix-ds/SKILL.md, .cursor/skills/atomix-ds/FIGMA-SKILL.md (synced at DS v" + dsVersion + ")" : "Skills: .cursor/skills/atomix-ds/SKILL.md (synced at DS v" + dsVersion + ")"
|
|
3000
|
-
);
|
|
2447
|
+
parts.push("Skills: .cursor/skills/atomix-ds/SKILL.md (synced at DS v" + dsVersion + ")");
|
|
3001
2448
|
const tokens = data.tokens;
|
|
3002
2449
|
const typography = tokens?.typography;
|
|
3003
2450
|
const fontFamily = typography?.fontFamily;
|
|
@@ -3028,7 +2475,6 @@ ${reportText}` }]
|
|
|
3028
2475
|
fonts: { families: fontNames },
|
|
3029
2476
|
skills: {
|
|
3030
2477
|
skill: ".cursor/skills/atomix-ds/SKILL.md",
|
|
3031
|
-
...cachedMcpTier === "pro" ? { skillFigmaDesign: ".cursor/skills/atomix-ds/FIGMA-SKILL.md" } : {},
|
|
3032
2478
|
syncedAtVersion: data.meta.version ?? "1.0.0"
|
|
3033
2479
|
}
|
|
3034
2480
|
};
|
|
@@ -3085,12 +2531,6 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3085
2531
|
path: ".cursor/skills/atomix-ds/SKILL.md",
|
|
3086
2532
|
content: GENERIC_SKILL_MD
|
|
3087
2533
|
},
|
|
3088
|
-
...cachedMcpTier === "pro" ? {
|
|
3089
|
-
skillFigmaDesign: {
|
|
3090
|
-
path: ".cursor/skills/atomix-ds/FIGMA-SKILL.md",
|
|
3091
|
-
content: FIGMA_DESIGN_SKILL_MD
|
|
3092
|
-
}
|
|
3093
|
-
} : {},
|
|
3094
2534
|
tokenFiles: {
|
|
3095
2535
|
files: ["tokens.css", "tokens.json"],
|
|
3096
2536
|
copyInstructions: "Call the syncAll MCP tool to create the token file, skills, and atomix-dependencies.json; do not only suggest the user run sync later."
|
|
@@ -3118,7 +2558,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3118
2558
|
case "syncToFigma": {
|
|
3119
2559
|
const payloads = buildFigmaPayloadsFromDS(data);
|
|
3120
2560
|
const out = {};
|
|
3121
|
-
const agentStartBridge = `The Figma bridge runs with this MCP server. Ensure
|
|
2561
|
+
const agentStartBridge = `The Figma bridge runs with this MCP server. Ensure your AI environment has this MCP server configured and running, then in Figma run the Atomix plugin and tap Connect. Then call **syncToFigma** again.`;
|
|
3122
2562
|
const userSteps = [
|
|
3123
2563
|
FIGMA_CONNECTION_INSTRUCTIONS.startBridge,
|
|
3124
2564
|
FIGMA_CONNECTION_INSTRUCTIONS.installAndRun,
|
|
@@ -3148,11 +2588,15 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3148
2588
|
out.colorVariables = await sendBridgeRequest("create_color_variables", {
|
|
3149
2589
|
collectionName: payloads.colorVariables.collectionName,
|
|
3150
2590
|
modes: payloads.colorVariables.modes,
|
|
3151
|
-
variables: payloads.colorVariables.variables
|
|
2591
|
+
variables: payloads.colorVariables.variables,
|
|
2592
|
+
removeVariablesNotInPayload: true
|
|
3152
2593
|
});
|
|
3153
2594
|
}
|
|
3154
2595
|
if (payloads.paintStyles.length > 0) {
|
|
3155
|
-
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
2596
|
+
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
2597
|
+
styles: payloads.paintStyles,
|
|
2598
|
+
removePaintStylesNotInPayload: true
|
|
2599
|
+
});
|
|
3156
2600
|
}
|
|
3157
2601
|
if (payloads.numberVariableCollections.length > 0) {
|
|
3158
2602
|
const numberResults = [];
|
|
@@ -3161,7 +2605,8 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3161
2605
|
const result = await sendBridgeRequest("create_number_variables", {
|
|
3162
2606
|
collectionName: coll.collectionName,
|
|
3163
2607
|
variables: coll.variables.map((v) => ({ name: v.name, value: v.value })),
|
|
3164
|
-
scopes: coll.scopes
|
|
2608
|
+
scopes: coll.scopes,
|
|
2609
|
+
removeVariablesNotInPayload: true
|
|
3165
2610
|
});
|
|
3166
2611
|
numberResults.push({ categoryKey: coll.categoryKey, result });
|
|
3167
2612
|
}
|
|
@@ -3175,10 +2620,16 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3175
2620
|
}
|
|
3176
2621
|
}
|
|
3177
2622
|
if (payloads.textStyles.length > 0) {
|
|
3178
|
-
out.textStyles = await sendBridgeRequest("create_text_styles", {
|
|
2623
|
+
out.textStyles = await sendBridgeRequest("create_text_styles", {
|
|
2624
|
+
styles: payloads.textStyles,
|
|
2625
|
+
removeTextStylesNotInPayload: true
|
|
2626
|
+
});
|
|
3179
2627
|
}
|
|
3180
2628
|
if (payloads.effectStyles.length > 0) {
|
|
3181
|
-
out.effectStyles = await sendBridgeRequest("create_effect_styles", {
|
|
2629
|
+
out.effectStyles = await sendBridgeRequest("create_effect_styles", {
|
|
2630
|
+
styles: payloads.effectStyles,
|
|
2631
|
+
removeShadowStylesNotInPayload: true
|
|
2632
|
+
});
|
|
3182
2633
|
}
|
|
3183
2634
|
out.figmaPayload = {
|
|
3184
2635
|
collectionName: payloads.colorVariables.collectionName,
|
|
@@ -3218,269 +2669,79 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3218
2669
|
if (out.numberVariablesHint) {
|
|
3219
2670
|
out.summary = [out.summary, out.numberVariablesHint].filter(Boolean).join(" ");
|
|
3220
2671
|
}
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
}
|
|
3227
|
-
|
|
3228
|
-
};
|
|
3229
|
-
}
|
|
3230
|
-
case "getFigmaVariablesAndStyles": {
|
|
3231
|
-
try {
|
|
3232
|
-
const reachable = await isBridgeReachable();
|
|
3233
|
-
if (!reachable) {
|
|
3234
|
-
return {
|
|
3235
|
-
content: [{
|
|
3236
|
-
type: "text",
|
|
3237
|
-
text: JSON.stringify({
|
|
3238
|
-
error: "Figma bridge not reachable.",
|
|
3239
|
-
bridgeNotRunning: true,
|
|
3240
|
-
agentInstruction: "The Figma bridge runs with this MCP server. Ensure Cursor has this MCP running, then in Figma run the Atomix plugin and click Connect to Cursor. Then call getFigmaVariablesAndStyles again."
|
|
3241
|
-
}, null, 2)
|
|
3242
|
-
}],
|
|
3243
|
-
isError: true
|
|
3244
|
-
};
|
|
3245
|
-
}
|
|
3246
|
-
const result = await sendBridgeRequest("get_figma_variables_and_styles");
|
|
3247
|
-
return {
|
|
3248
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3249
|
-
};
|
|
3250
|
-
} catch (e) {
|
|
3251
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3252
|
-
return {
|
|
3253
|
-
content: [{
|
|
3254
|
-
type: "text",
|
|
3255
|
-
text: JSON.stringify({ error: message, hint: "Ensure the bridge is running and the Atomix plugin is connected." }, null, 2)
|
|
3256
|
-
}],
|
|
3257
|
-
isError: true
|
|
3258
|
-
};
|
|
2672
|
+
const summaryParts = [];
|
|
2673
|
+
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(", ")}.`);
|
|
3259
2679
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
const
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
}, null, 2)
|
|
3272
|
-
}],
|
|
3273
|
-
isError: true
|
|
3274
|
-
};
|
|
2680
|
+
const paintResult = out.paintStyles;
|
|
2681
|
+
if (paintResult) {
|
|
2682
|
+
const c = paintResult.created ?? 0;
|
|
2683
|
+
const u = paintResult.updated ?? 0;
|
|
2684
|
+
const r = paintResult.removed ?? 0;
|
|
2685
|
+
if (c + u + r > 0) {
|
|
2686
|
+
const parts = [];
|
|
2687
|
+
if (c > 0) parts.push(`${c} created`);
|
|
2688
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
2689
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
2690
|
+
summaryParts.push(`Paint styles: ${parts.join(", ")}.`);
|
|
3275
2691
|
}
|
|
3276
|
-
const w = args?.width;
|
|
3277
|
-
const h = args?.height;
|
|
3278
|
-
const placeholderParams = {};
|
|
3279
|
-
if (typeof w === "number" && w > 0) placeholderParams.width = Math.round(w);
|
|
3280
|
-
if (typeof h === "number" && h > 0) placeholderParams.height = Math.round(h);
|
|
3281
|
-
const result = await sendBridgeRequest("create_design_placeholder", placeholderParams);
|
|
3282
|
-
return {
|
|
3283
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3284
|
-
};
|
|
3285
|
-
} catch (e) {
|
|
3286
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3287
|
-
return {
|
|
3288
|
-
content: [{
|
|
3289
|
-
type: "text",
|
|
3290
|
-
text: JSON.stringify({ error: message }, null, 2)
|
|
3291
|
-
}],
|
|
3292
|
-
isError: true
|
|
3293
|
-
};
|
|
3294
2692
|
}
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
const
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
}
|
|
3308
|
-
isError: true
|
|
3309
|
-
};
|
|
3310
|
-
}
|
|
3311
|
-
const raw = await sendBridgeRequest("get_figma_variables_and_styles");
|
|
3312
|
-
const response = raw;
|
|
3313
|
-
const variableByName = /* @__PURE__ */ new Map();
|
|
3314
|
-
if (response.variableCollections) {
|
|
3315
|
-
const numberCategorySuffixes = [" Spacing", " Radius", " Borders", " Height", " Icon", " Breakpoint"];
|
|
3316
|
-
for (const coll of response.variableCollections) {
|
|
3317
|
-
if (coll.name.endsWith(" Colors")) {
|
|
3318
|
-
for (const v of coll.variables || []) {
|
|
3319
|
-
variableByName.set(v.name, v.id);
|
|
3320
|
-
}
|
|
3321
|
-
continue;
|
|
3322
|
-
}
|
|
3323
|
-
const numberSuffix = numberCategorySuffixes.find((s) => coll.name.endsWith(s));
|
|
3324
|
-
if (numberSuffix) {
|
|
3325
|
-
const category = numberSuffix.slice(1);
|
|
3326
|
-
for (const v of coll.variables || []) {
|
|
3327
|
-
variableByName.set(`${category} / ${v.name}`, v.id);
|
|
3328
|
-
}
|
|
3329
|
-
}
|
|
2693
|
+
const effectResult = out.effectStyles;
|
|
2694
|
+
if (effectResult) {
|
|
2695
|
+
const c = effectResult.created ?? 0;
|
|
2696
|
+
const u = effectResult.updated ?? 0;
|
|
2697
|
+
const r = effectResult.removed ?? 0;
|
|
2698
|
+
if (c + u + r > 0) {
|
|
2699
|
+
const parts = [];
|
|
2700
|
+
if (c > 0) parts.push(`${c} created`);
|
|
2701
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
2702
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
2703
|
+
summaryParts.push(`Effect styles (shadows): ${parts.join(", ")}.`);
|
|
2704
|
+
if (effectResult.removedNames?.length) {
|
|
2705
|
+
summaryParts.push(`Removed: ${effectResult.removedNames.join(", ")}.`);
|
|
3330
2706
|
}
|
|
3331
2707
|
}
|
|
3332
|
-
const paintByName = /* @__PURE__ */ new Map();
|
|
3333
|
-
for (const s of response.paintStyles || []) {
|
|
3334
|
-
paintByName.set(s.name, s.id);
|
|
3335
|
-
}
|
|
3336
|
-
const textByName = /* @__PURE__ */ new Map();
|
|
3337
|
-
for (const s of response.textStyles || []) {
|
|
3338
|
-
textByName.set(s.name, s.id);
|
|
3339
|
-
}
|
|
3340
|
-
const effectByName = /* @__PURE__ */ new Map();
|
|
3341
|
-
for (const s of response.effectStyles || []) {
|
|
3342
|
-
effectByName.set(s.name, s.id);
|
|
3343
|
-
}
|
|
3344
|
-
const expected = getExpectedFigmaNamesFromDS(data);
|
|
3345
|
-
const resolved = {};
|
|
3346
|
-
const allNames = /* @__PURE__ */ new Set([
|
|
3347
|
-
...expected.colorVariableNames,
|
|
3348
|
-
...expected.paintStyleNames,
|
|
3349
|
-
...expected.textStyleNames,
|
|
3350
|
-
...expected.effectStyleNames,
|
|
3351
|
-
...expected.numberVariableNames
|
|
3352
|
-
]);
|
|
3353
|
-
for (const name2 of allNames) {
|
|
3354
|
-
const entry = {};
|
|
3355
|
-
const vId = variableByName.get(name2);
|
|
3356
|
-
if (vId) entry.variableId = vId;
|
|
3357
|
-
const pId = paintByName.get(name2);
|
|
3358
|
-
if (pId) entry.paintStyleId = pId;
|
|
3359
|
-
const tId = textByName.get(name2);
|
|
3360
|
-
if (tId) entry.textStyleId = tId;
|
|
3361
|
-
const eId = effectByName.get(name2);
|
|
3362
|
-
if (eId) entry.effectStyleId = eId;
|
|
3363
|
-
if (vId || pId || tId || eId) resolved[name2] = entry;
|
|
3364
|
-
}
|
|
3365
|
-
return {
|
|
3366
|
-
content: [{ type: "text", text: JSON.stringify({ resolved }, null, 2) }]
|
|
3367
|
-
};
|
|
3368
|
-
} catch (e) {
|
|
3369
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3370
|
-
return {
|
|
3371
|
-
content: [{
|
|
3372
|
-
type: "text",
|
|
3373
|
-
text: JSON.stringify({ error: message }, null, 2)
|
|
3374
|
-
}],
|
|
3375
|
-
isError: true
|
|
3376
|
-
};
|
|
3377
2708
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
const
|
|
3382
|
-
if (
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
error: "Figma bridge not reachable.",
|
|
3388
|
-
hint: "Run the Atomix plugin in Figma and click Connect to Cursor, then retry."
|
|
3389
|
-
}, null, 2)
|
|
3390
|
-
}],
|
|
3391
|
-
isError: true
|
|
3392
|
-
};
|
|
3393
|
-
}
|
|
3394
|
-
const frameId = args?.frameId;
|
|
3395
|
-
const scale = args?.scale;
|
|
3396
|
-
const params = { frameId };
|
|
3397
|
-
if (typeof scale === "number" && scale >= 1 && scale <= 4) params.scale = scale;
|
|
3398
|
-
const result = await sendBridgeRequest("get_design_screenshot", params);
|
|
3399
|
-
if (result.error) {
|
|
3400
|
-
return {
|
|
3401
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
3402
|
-
isError: true
|
|
3403
|
-
};
|
|
3404
|
-
}
|
|
3405
|
-
const content = [
|
|
3406
|
-
{ type: "text", text: `Screenshot captured (format: ${result.format ?? "PNG"}, scale: ${result.scale ?? 1}). Use the image below to verify layout, fill, and content hug.` }
|
|
3407
|
-
];
|
|
3408
|
-
if (result.imageBase64) {
|
|
3409
|
-
content.push({ type: "image", data: result.imageBase64, mimeType: "image/png" });
|
|
2709
|
+
const numResult = out.numberVariables;
|
|
2710
|
+
if (Array.isArray(numResult)) {
|
|
2711
|
+
const total = numResult.reduce((acc, r) => acc + (r.result?.variableNames?.length ?? 0), 0);
|
|
2712
|
+
const totalRemoved = numResult.reduce((acc, r) => acc + (r.result?.removed ?? 0), 0);
|
|
2713
|
+
if (total > 0 || totalRemoved > 0) {
|
|
2714
|
+
const parts = [];
|
|
2715
|
+
if (total > 0) parts.push(`${total} synced`);
|
|
2716
|
+
if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
|
|
2717
|
+
summaryParts.push(`Number variables: ${parts.join(", ")}.`);
|
|
3410
2718
|
}
|
|
3411
|
-
return { content };
|
|
3412
|
-
} catch (e) {
|
|
3413
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3414
|
-
return {
|
|
3415
|
-
content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
|
|
3416
|
-
isError: true
|
|
3417
|
-
};
|
|
3418
2719
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
const
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
hint: "Run the Atomix plugin in Figma and click Connect to Cursor, then retry."
|
|
3430
|
-
}, null, 2)
|
|
3431
|
-
}],
|
|
3432
|
-
isError: true
|
|
3433
|
-
};
|
|
3434
|
-
}
|
|
3435
|
-
const params = args && typeof args === "object" ? args : {};
|
|
3436
|
-
const result = await sendBridgeRequest("finalize_design_frame", params);
|
|
3437
|
-
return {
|
|
3438
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3439
|
-
};
|
|
3440
|
-
} catch (e) {
|
|
3441
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3442
|
-
return {
|
|
3443
|
-
content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
|
|
3444
|
-
isError: true
|
|
3445
|
-
};
|
|
2720
|
+
const textStylesWithRemoved = out.textStyles;
|
|
2721
|
+
if (textStylesWithRemoved && (textStylesWithRemoved.created ?? 0) + (textStylesWithRemoved.updated ?? 0) + (textStylesWithRemoved.removed ?? 0) > 0) {
|
|
2722
|
+
const c = textStylesWithRemoved.created ?? 0;
|
|
2723
|
+
const u = textStylesWithRemoved.updated ?? 0;
|
|
2724
|
+
const r = textStylesWithRemoved.removed ?? 0;
|
|
2725
|
+
const parts = [];
|
|
2726
|
+
if (c > 0) parts.push(`${c} created`);
|
|
2727
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
2728
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
2729
|
+
summaryParts.push(`Text styles: ${parts.join(", ")}.`);
|
|
3446
2730
|
}
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
case "designCreateText":
|
|
3450
|
-
case "designCreateRectangle":
|
|
3451
|
-
case "designSetAutoLayout":
|
|
3452
|
-
case "designSetLayoutConstraints":
|
|
3453
|
-
case "designAppendChild": {
|
|
3454
|
-
try {
|
|
3455
|
-
const reachable = await isBridgeReachable();
|
|
3456
|
-
if (!reachable) {
|
|
3457
|
-
return {
|
|
3458
|
-
content: [{
|
|
3459
|
-
type: "text",
|
|
3460
|
-
text: JSON.stringify({
|
|
3461
|
-
error: "Figma bridge not reachable.",
|
|
3462
|
-
hint: "Run the Atomix plugin in Figma and click Connect to Cursor, then retry."
|
|
3463
|
-
}, null, 2)
|
|
3464
|
-
}],
|
|
3465
|
-
isError: true
|
|
3466
|
-
};
|
|
3467
|
-
}
|
|
3468
|
-
const method = normalizeBridgeMethod(name);
|
|
3469
|
-
const params = args && typeof args === "object" ? args : {};
|
|
3470
|
-
const result = await sendBridgeRequest(method, params);
|
|
3471
|
-
return {
|
|
3472
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
3473
|
-
};
|
|
3474
|
-
} catch (e) {
|
|
3475
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
3476
|
-
return {
|
|
3477
|
-
content: [{
|
|
3478
|
-
type: "text",
|
|
3479
|
-
text: JSON.stringify({ error: message }, null, 2)
|
|
3480
|
-
}],
|
|
3481
|
-
isError: true
|
|
3482
|
-
};
|
|
2731
|
+
if (summaryParts.length > 0 && !out.error) {
|
|
2732
|
+
out.summary = [out.summary, summaryParts.join(" ")].filter(Boolean).join(" ");
|
|
3483
2733
|
}
|
|
2734
|
+
out.motionEasingNote = "Motion easing tokens are not synced as Figma styles; Figma has no reusable easing style. Easing is only used in prototype transitions (e.g. smart animate). Duration/easing remain available as number variables (duration) or in export JSON.";
|
|
2735
|
+
const responseText = out.summary ? `${out.summary}
|
|
2736
|
+
|
|
2737
|
+
${JSON.stringify(out, null, 2)}` : JSON.stringify(out, null, 2);
|
|
2738
|
+
return {
|
|
2739
|
+
content: [{
|
|
2740
|
+
type: "text",
|
|
2741
|
+
text: responseText
|
|
2742
|
+
}],
|
|
2743
|
+
...out.error ? { isError: true } : {}
|
|
2744
|
+
};
|
|
3484
2745
|
}
|
|
3485
2746
|
default:
|
|
3486
2747
|
return {
|
|
@@ -3488,7 +2749,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3488
2749
|
type: "text",
|
|
3489
2750
|
text: JSON.stringify({
|
|
3490
2751
|
error: `Unknown tool: ${name}`,
|
|
3491
|
-
availableTools: ["getToken", "listTokens", "listTypesets", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "syncToFigma"
|
|
2752
|
+
availableTools: ["getToken", "listTokens", "listTypesets", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "syncToFigma"]
|
|
3492
2753
|
}, null, 2)
|
|
3493
2754
|
}]
|
|
3494
2755
|
};
|
|
@@ -3588,49 +2849,6 @@ Use the returned rules and token paths/values when generating or editing code. P
|
|
|
3588
2849
|
- **No guessing:** If a value is not in the rules or token list, use searchTokens or listTokens to find the closest match rather than inventing a value.
|
|
3589
2850
|
- **Version check:** If this skill file has frontmatter \`atomixDsVersion\`, compare it to the design system version from **getDependencies** (\`meta.designSystemVersion\`). If the design system is newer, suggest the user run **syncAll** to update skills and tokens.
|
|
3590
2851
|
`;
|
|
3591
|
-
var FIGMA_DESIGN_SKILL_MD = `---
|
|
3592
|
-
name: atomix-design-in-figma
|
|
3593
|
-
description: Design in Figma using granular MCP commands. Three passes: wireframe, then tokens/rules, then audit. Use getDesignScreenshot to verify; finalizeDesignFrame to rename and remove placeholder.
|
|
3594
|
-
---
|
|
3595
|
-
|
|
3596
|
-
# Design in Figma (principal product designer)
|
|
3597
|
-
|
|
3598
|
-
Use this skill when the user asks to **design in Figma** (e.g. /--design-in-figma). Design by calling a **sequence of MCP tools**\u2014no script generation. All values come from the owner's Figma variables and styles (via **syncToFigma** and **resolveFigmaIdsForTokens**).
|
|
3599
|
-
|
|
3600
|
-
## Mandatory: no hardcoding
|
|
3601
|
-
|
|
3602
|
-
- **Only variable and style ids.** Use **resolveFigmaIdsForTokens** to get \`variableId\`, \`paintStyleId\`, \`textStyleId\`, \`effectStyleId\` keyed by Figma name (e.g. "Spacing / lg", "Background / surface"). Pass these ids into designCreateFrame, designCreateText, designSetAutoLayout, designSetLayoutConstraints. Never pass raw px, hex, or font sizes.
|
|
3603
|
-
|
|
3604
|
-
## Mandatory: user AI rules (colors, typography, buttons)
|
|
3605
|
-
|
|
3606
|
-
- **Colors:** Only variable/paint style ids from resolveFigmaIdsForTokens and getAIToolRules; no hex or raw values.
|
|
3607
|
-
- **Typography:** Only text style ids from resolved + rules; no raw font size/weight/family.
|
|
3608
|
-
- **Buttons:** Use token-based sizing and hierarchy from getAIToolRules; primary/secondary/ghost from resolved styles.
|
|
3609
|
-
|
|
3610
|
-
## Mandatory: set up variables first
|
|
3611
|
-
|
|
3612
|
-
- **Call syncToFigma first.** Then getAIToolRules, listTokens, and resolveFigmaIdsForTokens. Use the \`resolved\` map for every fill, text style, and spacing.
|
|
3613
|
-
|
|
3614
|
-
## Mandatory: auto-layout and breakpoints
|
|
3615
|
-
|
|
3616
|
-
- **Every frame with children:** Call **designSetAutoLayout** with \`nodeId\`, \`direction\`, \`paddingVariableId\`, \`itemSpacingVariableId\` from resolved. Use \`layoutSizingHorizontal\` / \`layoutSizingVertical\` (HUG or FILL) so content hugs or fills as intended.
|
|
3617
|
-
- **Breakpoints (non-mobile):** Use **designSetLayoutConstraints** with \`maxWidthVariableId\` from resolved for container frames.
|
|
3618
|
-
|
|
3619
|
-
## Three-pass flow (strict order)
|
|
3620
|
-
|
|
3621
|
-
**Setup (once):** syncToFigma \u2192 createDesignPlaceholder (save \`frameId\`) \u2192 getAIToolRules + listTokens \u2192 resolveFigmaIdsForTokens. Keep \`resolved\` and \`frameId\` for all passes.
|
|
3622
|
-
|
|
3623
|
-
**Pass 1 \u2014 Layout and contents (wireframe)**
|
|
3624
|
-
Build structure only: frames, text nodes, rectangles, auto-layout, layout constraints. Focus on hierarchy, spacing, and content placement; temporary or neutral fills are acceptable. Then call **getDesignScreenshot** with \`frameId\`. Check the image: layout correct? Content and frames hugging/filling as intended? If not, fix with design commands and take another screenshot until satisfied.
|
|
3625
|
-
|
|
3626
|
-
**Pass 2 \u2014 Apply design tokens and AI rules**
|
|
3627
|
-
Apply the owner's design system: set all fills to variable/paint ids from \`resolved\` (e.g. Background / surface, Brand / primary). Set all text to text style ids from \`resolved\`. Apply button and component tokens from getAIToolRules (colors, typography, buttons). Then call **getDesignScreenshot**. Check: do colors, typography, and buttons match the design system and rules? If not, fix and re-check.
|
|
3628
|
-
|
|
3629
|
-
**Pass 3 \u2014 Confirm no hardcoded values and follow AI rules**
|
|
3630
|
-
Audit: ensure no raw px, hex, or font values were introduced. Verify every fill, text style, and spacing uses an id from \`resolved\`. Check getAIToolRules again for colors, typography, buttons. Then call **getDesignScreenshot** one last time. If all good: call **finalizeDesignFrame** with \`frameId\`, \`name\` = short description of the design + " \u2705", and \`fillVariableId\` (or \`fillPaintStyleId\`) for the surface/background so the placeholder gray is removed. Then **summarise** what was built and any fixes made across passes.
|
|
3631
|
-
|
|
3632
|
-
Do not generate or run any JavaScript code. Use only the MCP tools listed above.
|
|
3633
|
-
`;
|
|
3634
2852
|
var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
3635
2853
|
<html lang="en">
|
|
3636
2854
|
<head>
|
|
@@ -3731,7 +2949,7 @@ Get your DS ID and token from the Export modal or Settings \u2192 Regenerate Ato
|
|
|
3731
2949
|
};
|
|
3732
2950
|
}
|
|
3733
2951
|
if (!hasValidAuthConfig() || authFailedNoTools) {
|
|
3734
|
-
throw new Error("MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
2952
|
+
throw new Error("MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart your AI tool. See atomix://setup for instructions.");
|
|
3735
2953
|
}
|
|
3736
2954
|
const data = await fetchDesignSystemForMCP();
|
|
3737
2955
|
const stats = getTokenStats(data);
|
|
@@ -3769,7 +2987,6 @@ Get your DS ID and token from the Export modal or Settings \u2192 Regenerate Ato
|
|
|
3769
2987
|
}
|
|
3770
2988
|
throw new Error(`Unknown resource: ${uri}`);
|
|
3771
2989
|
});
|
|
3772
|
-
var FIGMA_DESIGN_PROMPT_NAMES = /* @__PURE__ */ new Set(["--design-in-figma"]);
|
|
3773
2990
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
3774
2991
|
if (!hasValidAuthConfig()) {
|
|
3775
2992
|
authFailedNoTools = true;
|
|
@@ -3785,16 +3002,14 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
3785
3002
|
if (authFailedNoTools) {
|
|
3786
3003
|
throw new Error(AUTH_REQUIRED_MESSAGE);
|
|
3787
3004
|
}
|
|
3788
|
-
const
|
|
3005
|
+
const prompts = [
|
|
3789
3006
|
{ name: "--hello", description: "Get started with this design system - overview, tokens, and tools. Run this first!" },
|
|
3790
3007
|
{ name: "--get-started", description: "Get started with design system in project. Three phases: scan, report and ask, then create only after you approve." },
|
|
3791
3008
|
{ name: "--rules", description: "Get the design system governance rules for your AI coding tool (default: cursor)." },
|
|
3792
3009
|
{ name: "--sync", description: "Sync tokens, AI rules, skills files, and dependencies manifest (icons, fonts). Use /--refactor to migrate deprecated tokens." },
|
|
3793
3010
|
{ name: "--refactor", description: "Migrate deprecated tokens in codebase. Run after /--sync." },
|
|
3794
|
-
{ name: "--sync-to-figma", description: "Push this design system to Figma (variables, color + typography styles). Uses local bridge + plugin; no Figma token." }
|
|
3795
|
-
{ name: "--design-in-figma", description: "Design UI in Figma: 3 passes (wireframe \u2192 tokens/rules \u2192 audit), getDesignScreenshot after each pass, then finalizeDesignFrame (rename + \u2705, remove placeholder) and summarise." }
|
|
3011
|
+
{ name: "--sync-to-figma", description: "Push this design system to Figma (variables, color + typography styles). Uses local bridge + plugin; no Figma token." }
|
|
3796
3012
|
];
|
|
3797
|
-
const prompts = cachedMcpTier === "pro" ? allPrompts : allPrompts.filter((p) => !FIGMA_DESIGN_PROMPT_NAMES.has(p.name));
|
|
3798
3013
|
return { prompts };
|
|
3799
3014
|
});
|
|
3800
3015
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
@@ -3806,12 +3021,12 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3806
3021
|
role: "user",
|
|
3807
3022
|
content: {
|
|
3808
3023
|
type: "text",
|
|
3809
|
-
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
3024
|
+
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart your AI tool. No tools or prompts are available until then."
|
|
3810
3025
|
}
|
|
3811
3026
|
}]
|
|
3812
3027
|
};
|
|
3813
3028
|
}
|
|
3814
|
-
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
|
|
3029
|
+
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;
|
|
3815
3030
|
const shouldForceRefresh = canonicalName === "sync";
|
|
3816
3031
|
let data = null;
|
|
3817
3032
|
let stats = null;
|
|
@@ -3832,7 +3047,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3832
3047
|
- \`--ds-id\`: Your design system ID (get it from https://atomixstudio.eu/ds/[your-ds-id])
|
|
3833
3048
|
- \`--atomix-token\`: Your access token (get it from the Export modal or Settings \u2192 Regenerate Atomix access token)
|
|
3834
3049
|
|
|
3835
|
-
Both are required. Configure the MCP server in your
|
|
3050
|
+
Both are required. Configure the MCP server in your AI tool's MCP settings, then restart your AI tool.`
|
|
3836
3051
|
}
|
|
3837
3052
|
}
|
|
3838
3053
|
]
|
|
@@ -3896,20 +3111,6 @@ Both are required. Configure the MCP server in your Cursor settings, then restar
|
|
|
3896
3111
|
}
|
|
3897
3112
|
throw error;
|
|
3898
3113
|
}
|
|
3899
|
-
if (FIGMA_DESIGN_PROMPT_NAMES.has(name) && cachedMcpTier !== "pro") {
|
|
3900
|
-
return {
|
|
3901
|
-
description: "Pro Figma required",
|
|
3902
|
-
messages: [
|
|
3903
|
-
{
|
|
3904
|
-
role: "user",
|
|
3905
|
-
content: {
|
|
3906
|
-
type: "text",
|
|
3907
|
-
text: "This design system does not have Pro Figma access. The design-in-Figma prompt is available when the design system owner has a Pro subscription. Sync to Figma (/--sync-to-figma) is available on all tiers."
|
|
3908
|
-
}
|
|
3909
|
-
}
|
|
3910
|
-
]
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3913
3114
|
const buildCategoryPrompt = (category, instructions) => {
|
|
3914
3115
|
const lines = [];
|
|
3915
3116
|
lines.push(`## Design System Information`);
|
|
@@ -4150,36 +3351,7 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
4150
3351
|
role: "user",
|
|
4151
3352
|
content: {
|
|
4152
3353
|
type: "text",
|
|
4153
|
-
text: `Call the MCP tool **syncToFigma** now (no arguments). It pushes the design system into the open Figma file via the built-in bridge and Atomix plugin. Do not use the Figma REST API or external scripts. If the response includes \`bridgeNotRunning\` and \`agentInstruction\`, ensure
|
|
4154
|
-
}
|
|
4155
|
-
}
|
|
4156
|
-
]
|
|
4157
|
-
};
|
|
4158
|
-
}
|
|
4159
|
-
case "design-in-figma": {
|
|
4160
|
-
return {
|
|
4161
|
-
description: "Design in Figma using granular MCP commands: 3 passes (wireframe \u2192 tokens \u2192 audit), screenshot checks, then finalize and summarise",
|
|
4162
|
-
messages: [
|
|
4163
|
-
{
|
|
4164
|
-
role: "user",
|
|
4165
|
-
content: {
|
|
4166
|
-
type: "text",
|
|
4167
|
-
text: `You must **design in Figma** by calling a **sequence of MCP tools**\u2014do not generate or run any JavaScript code. Follow the design-in-Figma skill if present; otherwise follow this **three-pass flow**.
|
|
4168
|
-
|
|
4169
|
-
Prerequisites: Figma bridge runs inside this MCP server. In Figma, run the Atomix plugin and click "Connect to Cursor"; leave the plugin open.
|
|
4170
|
-
|
|
4171
|
-
**Setup (once):** syncToFigma \u2192 createDesignPlaceholder (save \`frameId\`) \u2192 getAIToolRules + listTokens \u2192 resolveFigmaIdsForTokens. Use the returned \`resolved\` map and \`frameId\` for all passes.
|
|
4172
|
-
|
|
4173
|
-
**Pass 1 \u2014 Layout and contents (wireframe)**
|
|
4174
|
-
Build the structure: designCreateFrame, designCreateText, designCreateRectangle, designSetAutoLayout (use paddingVariableId, itemSpacingVariableId, and layoutSizingHorizontal/layoutSizingVertical HUG or FILL from resolved), designSetLayoutConstraints, designAppendChild. Focus on hierarchy, spacing, and content placement. Then call **getDesignScreenshot** with \`frameId\`. Check the returned image: is layout correct? Do elements hug or fill as intended? If not, fix and take another screenshot until satisfied.
|
|
4175
|
-
|
|
4176
|
-
**Pass 2 \u2014 Apply design tokens and AI rules**
|
|
4177
|
-
Apply the owner's design system: set all fills and text styles using only ids from \`resolved\` (no raw px/hex). Strictly follow getAIToolRules for **colors, typography, and buttons**. Then call **getDesignScreenshot**. Check: do colors, typography, and buttons match the design system? If not, fix and re-check.
|
|
4178
|
-
|
|
4179
|
-
**Pass 3 \u2014 Confirm no hardcoded values**
|
|
4180
|
-
Audit: ensure no raw px, hex, or font values remain; every fill and text style must use an id from \`resolved\`. Call **getDesignScreenshot** once more. If everything is correct: call **finalizeDesignFrame** with \`frameId\`, \`name\` = a short description of the design + " \u2705", and \`fillVariableId\` (or \`fillPaintStyleId\`) from resolved for the surface/background so the placeholder gray is removed. Then **summarise** what was built and any fixes made across the three passes.
|
|
4181
|
-
|
|
4182
|
-
If the bridge is not reachable: tell the user to run the Atomix plugin in Figma and click Connect to Cursor, then retry.`
|
|
3354
|
+
text: `Call the MCP tool **syncToFigma** now (no arguments). It pushes the design system into the open Figma file via the built-in bridge and Atomix plugin. Do not use the Figma REST API or external scripts. If the response includes \`bridgeNotRunning\` and \`agentInstruction\`, ensure your AI environment has this MCP server running, then in Figma run the Atomix plugin and tap Connect, then call syncToFigma again. Only if that fails, tell the user: (1) Ensure this MCP server is configured and running in your AI tool. (2) In Figma, open and run the Atomix plugin, then tap **Connect**. (3) Run Sync to Figma again. After a successful sync, report the **summary** from the response (what was created, updated, or removed)\u2014do not say "we added the whole design system" when only some items changed.`
|
|
4183
3355
|
}
|
|
4184
3356
|
}
|
|
4185
3357
|
]
|
|
@@ -4309,8 +3481,8 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4309
3481
|
|
|
4310
3482
|
- Resolve platform/stack: infer from the project (e.g. package.json, build.gradle, Xcode) or ask 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.
|
|
4311
3483
|
- Call **getDependencies** with \`platform\` and optional \`stack\`. If it fails, tell the user the design system could not be reached and stop.
|
|
4312
|
-
- Scan the repo for: .cursor/skills/atomix-ds/SKILL.md, a tokens file (e.g. tokens.css or src/tokens.css), icon package from getDependencies, font links.
|
|
4313
|
-
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links, skill (.cursor/skills/atomix-ds/SKILL.md)
|
|
3484
|
+
- Scan the repo for: .cursor/skills/atomix-ds/SKILL.md, a tokens file (e.g. tokens.css or src/tokens.css), icon package from getDependencies, font links. **Web:** note any existing CSS (globals.css, main.css, Tailwind, etc.). **Native:** note any theme/style files (SwiftUI, Android themes, Compose).
|
|
3485
|
+
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links, skill (.cursor/skills/atomix-ds/SKILL.md), token files; for web, also include the **showcase page** (atomix-setup-showcase.html) if getDependencies returned a \`showcase\` object.
|
|
4314
3486
|
- Do not write, create, or add anything in Phase 1.
|
|
4315
3487
|
|
|
4316
3488
|
## Phase 2 \u2013 Report and ask
|
|
@@ -4324,7 +3496,6 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4324
3496
|
- Run only when the user has said yes (all or specific items).
|
|
4325
3497
|
- For each approved item:
|
|
4326
3498
|
- **Skill:** Write the skill content from getDependencies \`skill.content\` to \`skill.path\` (.cursor/skills/atomix-ds/SKILL.md).
|
|
4327
|
-
- **Figma design skill:** Only if getDependencies returned \`skillFigmaDesign\`, write \`skillFigmaDesign.content\` to \`skillFigmaDesign.path\` (.cursor/skills/atomix-ds/FIGMA-SKILL.md). Use this when designing in Figma so the agent follows principal-product-designer rules and prefers existing Figma variables. If \`skillFigmaDesign\` was not in the response, do not add this file.
|
|
4328
3499
|
- **Token file:** Call **syncAll** with \`output\` set to the path (e.g. "./src/tokens.css" or "./tokens.css"). syncAll also writes skills and atomix-dependencies.json. You must call syncAll; do not only suggest the user run it later.
|
|
4329
3500
|
- **Icon package:** Install per getDependencies. When rendering icons, apply the design system's icon tokens: use getToken(\`sizing.icon.*\`) or listTokens(\`sizing\`) for size, and getToken(\`icons.strokeWidth\`) for stroke width when the DS defines it; do not use hardcoded sizes or stroke widths.
|
|
4330
3501
|
- **Fonts and typeset:** Add font links (e.g. \`<link>\` or \`@import\` from Google Fonts). Then build a **complete typeset CSS**: call **listTypesets** to get every typeset from the owner's design system (do not skip any). Emit **one CSS rule per typeset** using the \`cssClass\` and the \`fontFamilyVar\`, \`fontSizeVar\`, \`fontWeightVar\`, \`lineHeightVar\` (and \`letterSpacingVar\`, \`textTransformVar\`, \`textDecorationVar\` when present) returned by listTypesets. Include text-transform and text-decoration when the typeset has them so the result is **1:1** with the design system. The typeset file must define the full type scale\u2014not only a font import. Do not create a CSS file that contains only a font import.
|
|
@@ -4408,8 +3579,9 @@ ${tokenSummary}
|
|
|
4408
3579
|
|---------|----------------|
|
|
4409
3580
|
| **/--hello** | Get started - overview, tokens, and tools. Run this first! |
|
|
4410
3581
|
| **/--get-started** | Get started with design system in project. Three phases; creates files only after you approve. |
|
|
4411
|
-
| **/--rules** | Governance rules for your AI tool (Cursor, Copilot, Windsurf
|
|
3582
|
+
| **/--rules** | Governance rules for your AI tool (e.g. Cursor, Copilot, Windsurf). |
|
|
4412
3583
|
| **/--sync** | Sync tokens, rules, skills, and dependencies manifest (icons, fonts). Safe: adds new, updates existing, marks deprecated. |
|
|
3584
|
+
| **/--sync-to-figma** | Push design system to Figma (variables, paint/text/effect styles). Uses built-in bridge + Atomix plugin; connect plugin in Figma then run. Available on all tiers. |
|
|
4413
3585
|
| **/--refactor** | Migrate deprecated tokens in codebase. Run after /--sync. |
|
|
4414
3586
|
|
|
4415
3587
|
**Suggested next step:** Run **/--get-started** to set up global styles, icons, fonts, and token files; the AI will list options and ask before adding anything.
|
|
@@ -4440,7 +3612,7 @@ async function startServer() {
|
|
|
4440
3612
|
console.error(`Atomix MCP Server started for design system: ${dsId}`);
|
|
4441
3613
|
console.error(`Atomix MCP API base: ${apiBase}`);
|
|
4442
3614
|
console.error(
|
|
4443
|
-
"If you switched MCP config (e.g. free vs pro DS), restart
|
|
3615
|
+
"If you switched MCP config (e.g. free vs pro DS), restart your AI tool so this process uses the new --ds-id and --atomix-token."
|
|
4444
3616
|
);
|
|
4445
3617
|
}
|
|
4446
3618
|
function onShutdown() {
|