@atomixstudio/mcp 1.0.26 → 1.0.28
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 +2 -2
- package/dist/index.js +161 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server and CLI for Atomix Design System. Query and sync design tokens from AI coding tools (Cursor, Claude Desktop, Windsurf, etc.) or from the command line.
|
|
4
4
|
|
|
5
|
-
**Version:** 1.0.
|
|
5
|
+
**Version:** 1.0.28 — Sync to Figma: removes variables/styles not in payload (same collections only); color scopes (Bg = frame fill only, Action/Feedback = all fill); update-or-create for paint/text/effect styles to avoid duplicates; change summary in sync response; instructions AI-tool agnostic.
|
|
6
6
|
|
|
7
7
|
## Getting Your Credentials
|
|
8
8
|
|
|
@@ -100,7 +100,7 @@ Run these prompts from your AI tool (e.g. **/--hello**, **/--get-started**):
|
|
|
100
100
|
|
|
101
101
|
| Prompt | Description |
|
|
102
102
|
|--------|-------------|
|
|
103
|
-
| **/--hello** | Get started — overview, tokens,
|
|
103
|
+
| **/--hello** | Get started — overview, tokens, essential commands (including **/--sync-to-figma**). Run this first. |
|
|
104
104
|
| **/--get-started** | Get started with design system in project. Three phases; creates files only after you approve. |
|
|
105
105
|
| **/--rules** | Governance rules for your AI tool (Cursor, Copilot, Windsurf, etc.). |
|
|
106
106
|
| **/--sync** | Sync tokens, AI rules, skills files, and dependencies manifest (icons, fonts). Use **/--refactor** to migrate deprecated tokens. |
|
package/dist/index.js
CHANGED
|
@@ -1300,8 +1300,8 @@ var FIGMA_BRIDGE_TIMEOUT_MS = 15e3;
|
|
|
1300
1300
|
var FIGMA_BRIDGE_TOKEN = process.env.FIGMA_BRIDGE_TOKEN || null;
|
|
1301
1301
|
var FIGMA_CONNECTION_INSTRUCTIONS = {
|
|
1302
1302
|
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
|
|
1303
|
+
connect: 'In the plugin UI, tap **Connect** and wait until the status shows "Connected".',
|
|
1304
|
+
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
1305
|
};
|
|
1306
1306
|
var bridgeWss = null;
|
|
1307
1307
|
var pluginWs = null;
|
|
@@ -1445,7 +1445,7 @@ function sendBridgeRequest(method, params, timeoutMs = FIGMA_BRIDGE_TIMEOUT_MS)
|
|
|
1445
1445
|
const ws = pluginWs;
|
|
1446
1446
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1447
1447
|
return Promise.reject(
|
|
1448
|
-
new Error("Figma plugin not connected. Open Figma, run Atomix plugin, and
|
|
1448
|
+
new Error("Figma plugin not connected. Open Figma, run Atomix plugin, and tap Connect.")
|
|
1449
1449
|
);
|
|
1450
1450
|
}
|
|
1451
1451
|
const id = `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
@@ -1607,7 +1607,8 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1607
1607
|
}
|
|
1608
1608
|
if (variables.length === 0 && modes.length === 0) modes.push("Light");
|
|
1609
1609
|
const dsName = data.meta?.name;
|
|
1610
|
-
const
|
|
1610
|
+
const collectionPrefix = dsName ? `${dsName} ` : "";
|
|
1611
|
+
const colorCollectionName = `${collectionPrefix}Colors`;
|
|
1611
1612
|
const textStyles = [];
|
|
1612
1613
|
const sizeToPx = (val, basePx = 16) => {
|
|
1613
1614
|
if (typeof val === "number") return Math.round(val);
|
|
@@ -1642,7 +1643,15 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1642
1643
|
}
|
|
1643
1644
|
return "Inter";
|
|
1644
1645
|
};
|
|
1645
|
-
const
|
|
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";
|
|
1646
1655
|
const fontSizeMap = typography?.fontSize;
|
|
1647
1656
|
const fontWeightMap = typography?.fontWeight;
|
|
1648
1657
|
const lineHeightMap = typography?.lineHeight;
|
|
@@ -1653,6 +1662,10 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1653
1662
|
for (const [key, sizeVal] of Object.entries(fontSizeMap)) {
|
|
1654
1663
|
const fontSize = sizeToPx(sizeVal);
|
|
1655
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
|
+
);
|
|
1656
1669
|
const lh = lineHeightMap && typeof lineHeightMap === "object" ? lineHeightMap[key] : void 0;
|
|
1657
1670
|
const weight = fontWeightMap && typeof fontWeightMap === "object" ? fontWeightMap[key] : void 0;
|
|
1658
1671
|
const fontWeight = weight != null ? String(weight) : "400";
|
|
@@ -1695,7 +1708,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1695
1708
|
const lineHeightUnitless = lhStr != null ? lhStr.endsWith("%") ? parseFloat(lhStr) / 100 : sizeToPx(lhStr) / fontSize : 1.5;
|
|
1696
1709
|
const payload = {
|
|
1697
1710
|
name: styleName.startsWith("Typography") ? styleName : `Typography / ${styleName.replace(/\//g, " / ")}`,
|
|
1698
|
-
fontFamily,
|
|
1711
|
+
fontFamily: defaultFontFamily,
|
|
1699
1712
|
fontWeight: String(style.fontWeight ?? "400"),
|
|
1700
1713
|
fontSize,
|
|
1701
1714
|
lineHeightUnit: "PERCENT",
|
|
@@ -1717,59 +1730,58 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1717
1730
|
const variables2 = [];
|
|
1718
1731
|
for (const [key, val] of Object.entries(spacing.scale)) {
|
|
1719
1732
|
const n = tokenValueToNumber(val);
|
|
1720
|
-
if (n >= 0) variables2.push({ name: key
|
|
1733
|
+
if (n >= 0) variables2.push({ name: `Spacing / ${key}`, value: n });
|
|
1721
1734
|
}
|
|
1722
1735
|
variables2.sort((a, b) => a.value - b.value);
|
|
1723
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1736
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Spacing`, categoryKey: "Spacing", variables: variables2, scopes: ["GAP"] });
|
|
1724
1737
|
}
|
|
1725
1738
|
const radius = tokens?.radius;
|
|
1726
1739
|
if (radius?.scale && typeof radius.scale === "object") {
|
|
1727
1740
|
const variables2 = [];
|
|
1728
1741
|
for (const [key, val] of Object.entries(radius.scale)) {
|
|
1729
1742
|
const n = tokenValueToNumber(val);
|
|
1730
|
-
if (n >= 0) variables2.push({ name: key
|
|
1743
|
+
if (n >= 0) variables2.push({ name: `Radius / ${key}`, value: n });
|
|
1731
1744
|
}
|
|
1732
1745
|
variables2.sort((a, b) => a.value - b.value);
|
|
1733
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1746
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Radius`, categoryKey: "Radius", variables: variables2, scopes: ["CORNER_RADIUS"] });
|
|
1734
1747
|
}
|
|
1735
1748
|
const borders = tokens?.borders;
|
|
1736
1749
|
if (borders?.width && typeof borders.width === "object") {
|
|
1737
1750
|
const variables2 = [];
|
|
1738
1751
|
for (const [key, val] of Object.entries(borders.width)) {
|
|
1739
1752
|
const n = tokenValueToNumber(val);
|
|
1740
|
-
if (n >= 0) variables2.push({ name: key
|
|
1753
|
+
if (n >= 0) variables2.push({ name: `Borders / ${key}`, value: n });
|
|
1741
1754
|
}
|
|
1742
1755
|
variables2.sort((a, b) => a.value - b.value);
|
|
1743
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1756
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Borders`, categoryKey: "Borders", variables: variables2, scopes: ["STROKE_FLOAT"] });
|
|
1744
1757
|
}
|
|
1745
1758
|
const sizing = tokens?.sizing;
|
|
1759
|
+
const sizingVariables = [];
|
|
1746
1760
|
if (sizing?.height && typeof sizing.height === "object") {
|
|
1747
|
-
const variables2 = [];
|
|
1748
1761
|
for (const [key, val] of Object.entries(sizing.height)) {
|
|
1749
1762
|
const n = tokenValueToNumber(val);
|
|
1750
|
-
if (n >= 0)
|
|
1763
|
+
if (n >= 0) sizingVariables.push({ name: `Height / ${key}`, value: n });
|
|
1751
1764
|
}
|
|
1752
|
-
variables2.sort((a, b) => a.value - b.value);
|
|
1753
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Height", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1754
1765
|
}
|
|
1755
1766
|
if (sizing?.icon && typeof sizing.icon === "object") {
|
|
1756
|
-
const variables2 = [];
|
|
1757
1767
|
for (const [key, val] of Object.entries(sizing.icon)) {
|
|
1758
1768
|
const n = tokenValueToNumber(val);
|
|
1759
|
-
if (n >= 0)
|
|
1769
|
+
if (n >= 0) sizingVariables.push({ name: `Icon / ${key}`, value: n });
|
|
1760
1770
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
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"] });
|
|
1763
1775
|
}
|
|
1764
1776
|
const layout = tokens?.layout;
|
|
1765
1777
|
if (layout?.breakpoint && typeof layout.breakpoint === "object") {
|
|
1766
1778
|
const variables2 = [];
|
|
1767
1779
|
for (const [key, val] of Object.entries(layout.breakpoint)) {
|
|
1768
1780
|
const n = tokenValueToNumber(val);
|
|
1769
|
-
if (n >= 0) variables2.push({ name: key
|
|
1781
|
+
if (n >= 0) variables2.push({ name: `Breakpoint / ${key}`, value: n });
|
|
1770
1782
|
}
|
|
1771
1783
|
variables2.sort((a, b) => a.value - b.value);
|
|
1772
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1784
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName: `${collectionPrefix}Layout`, categoryKey: "Layout", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1773
1785
|
}
|
|
1774
1786
|
const effectStyles = [];
|
|
1775
1787
|
const shadows = tokens?.shadows;
|
|
@@ -1800,7 +1812,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1800
1812
|
return nameA.localeCompare(nameB);
|
|
1801
1813
|
});
|
|
1802
1814
|
return {
|
|
1803
|
-
colorVariables: { collectionName, modes, variables },
|
|
1815
|
+
colorVariables: { collectionName: colorCollectionName, modes, variables },
|
|
1804
1816
|
paintStyles,
|
|
1805
1817
|
textStyles,
|
|
1806
1818
|
numberVariableCollections,
|
|
@@ -1810,7 +1822,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1810
1822
|
function getExpectedFigmaNamesFromDS(data) {
|
|
1811
1823
|
const payloads = buildFigmaPayloadsFromDS(data);
|
|
1812
1824
|
const numberVariableNames = payloads.numberVariableCollections.flatMap(
|
|
1813
|
-
(c) => c.variables.map((v) =>
|
|
1825
|
+
(c) => c.variables.map((v) => v.name)
|
|
1814
1826
|
);
|
|
1815
1827
|
return {
|
|
1816
1828
|
colorVariableNames: payloads.colorVariables.variables.map((v) => v.name),
|
|
@@ -1853,7 +1865,7 @@ var authFailedNoTools = false;
|
|
|
1853
1865
|
function hasValidAuthConfig() {
|
|
1854
1866
|
return !!(dsId && accessToken);
|
|
1855
1867
|
}
|
|
1856
|
-
var AUTH_REQUIRED_MESSAGE = "Atomix MCP requires authentication. Add both --ds-id and --atomix-token to your MCP config (Settings \u2192 MCP), then restart
|
|
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 your AI tool. Get your token from Atomix Studio: Export modal or Settings \u2192 Regenerate Atomix access token.";
|
|
1857
1869
|
var lastChangeSummary = null;
|
|
1858
1870
|
var FIGMA_SYNC_TOOL_NAME = "syncToFigma";
|
|
1859
1871
|
var FIGMA_DESIGN_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -1941,10 +1953,10 @@ async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
|
1941
1953
|
}
|
|
1942
1954
|
var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
|
|
1943
1955
|
function typesetKeyToFontFamilyRole(key) {
|
|
1944
|
-
const prefix = key.split("-")[0] ??
|
|
1945
|
-
if (prefix === "display") return "display";
|
|
1946
|
-
if (prefix === "heading") return "heading";
|
|
1947
|
-
if (prefix === "mono") return "mono";
|
|
1956
|
+
const prefix = (key.split("-")[0] ?? key).toLowerCase();
|
|
1957
|
+
if (prefix === "display" || prefix.startsWith("display")) return "display";
|
|
1958
|
+
if (prefix === "heading" || prefix.startsWith("heading")) return "heading";
|
|
1959
|
+
if (prefix === "mono" || prefix.startsWith("mono")) return "mono";
|
|
1948
1960
|
if (prefix.startsWith("body")) return "body";
|
|
1949
1961
|
return "body";
|
|
1950
1962
|
}
|
|
@@ -1962,12 +1974,15 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
1962
1974
|
for (const key of Object.keys(fontSize)) {
|
|
1963
1975
|
const role = typesetKeyToFontFamilyRole(key);
|
|
1964
1976
|
const familyName = fontFamily[role] ?? fontFamily.body;
|
|
1965
|
-
const
|
|
1977
|
+
const fontFamilyVarName = familyName ? `--${p}typography-font-family-${role}` : void 0;
|
|
1978
|
+
const fontFamilyVar = familyName ? `var(${fontFamilyVarName})` : "";
|
|
1966
1979
|
const keyKebab = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1967
1980
|
typesets.push({
|
|
1968
1981
|
key,
|
|
1969
1982
|
cssClass: `.typeset-${keyKebab}`,
|
|
1970
1983
|
fontFamilyVar: fontFamilyVar || "inherit",
|
|
1984
|
+
fontFamilyVarName,
|
|
1985
|
+
fontFamilyValue: familyName,
|
|
1971
1986
|
fontSizeVar: `var(--${p}typography-${key}-size)`,
|
|
1972
1987
|
fontWeightVar: `var(--${p}typography-${key}-weight)`,
|
|
1973
1988
|
lineHeightVar: `var(--${p}typography-${key}-line-height)`,
|
|
@@ -1983,7 +1998,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
1983
1998
|
var server = new Server(
|
|
1984
1999
|
{
|
|
1985
2000
|
name: "atomix-mcp-user",
|
|
1986
|
-
version: "1.0.
|
|
2001
|
+
version: "1.0.27"
|
|
1987
2002
|
},
|
|
1988
2003
|
{
|
|
1989
2004
|
capabilities: {
|
|
@@ -2014,7 +2029,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2014
2029
|
authFailedNoTools = true;
|
|
2015
2030
|
console.error(
|
|
2016
2031
|
"[Atomix MCP] Design system not loaded: ds-id or token invalid or API error. No tools will be shown.",
|
|
2017
|
-
msg.includes("401") ? " Token invalid or expired. Regenerate in Atomix Studio (Settings \u2192 Regenerate Atomix access token), update your MCP config, then restart
|
|
2032
|
+
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
|
|
2018
2033
|
);
|
|
2019
2034
|
}
|
|
2020
2035
|
}
|
|
@@ -2362,7 +2377,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2362
2377
|
return {
|
|
2363
2378
|
content: [{
|
|
2364
2379
|
type: "text",
|
|
2365
|
-
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
2380
|
+
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."
|
|
2366
2381
|
}],
|
|
2367
2382
|
isError: true
|
|
2368
2383
|
};
|
|
@@ -2652,7 +2667,7 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2652
2667
|
text: JSON.stringify({
|
|
2653
2668
|
count: typesets.length,
|
|
2654
2669
|
typesets,
|
|
2655
|
-
instruction: "Emit one CSS rule per typeset using the cssClass and the listed var() properties. Include font-family, font-size, font-weight, line-height; add letter-spacing when present; add text-transform and text-decoration when hasTextTransform/hasTextDecoration are true so the result is 1:1 with the design system.",
|
|
2670
|
+
instruction: "Emit one CSS rule per typeset using the cssClass and the listed var() properties. Include font-family, font-size, font-weight, line-height; add letter-spacing when present; add text-transform and text-decoration when hasTextTransform/hasTextDecoration are true so the result is 1:1 with the design system. The synced token file quotes font names that contain spaces; no override is needed in the typeset file. For native (iOS/Android) projects, font names with spaces must be quoted or use the platform's canonical font name in theme or resource files.",
|
|
2656
2671
|
...underlineThickness != null && underlineOffset != null && {
|
|
2657
2672
|
underlineVars: {
|
|
2658
2673
|
thickness: `var(--${cssPrefix}-typography-underline-thickness)`,
|
|
@@ -2799,11 +2814,11 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2799
2814
|
case "getSetupInstructions": {
|
|
2800
2815
|
const tool = args?.tool;
|
|
2801
2816
|
const instructions = {
|
|
2802
|
-
cursor: `#
|
|
2817
|
+
cursor: `# MCP Setup
|
|
2803
2818
|
|
|
2804
|
-
1. Create \`.cursor/mcp.json\`
|
|
2819
|
+
1. Create your MCP config file in the project (e.g. \`.cursor/mcp.json\` for Cursor)
|
|
2805
2820
|
2. Add the MCP configuration (use exportMCPConfig to get it)
|
|
2806
|
-
3. Restart
|
|
2821
|
+
3. Restart your IDE
|
|
2807
2822
|
4. Verify by asking: "What design tokens are available?"
|
|
2808
2823
|
|
|
2809
2824
|
## File Structure
|
|
@@ -3083,7 +3098,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3083
3098
|
showcase: platform2 === "web" || !platform2 ? {
|
|
3084
3099
|
path: "atomix-setup-showcase.html",
|
|
3085
3100
|
template: SHOWCASE_HTML_TEMPLATE,
|
|
3086
|
-
substitutionInstructions:
|
|
3101
|
+
substitutionInstructions: 'Replace placeholders with values from the synced token file. MCP/sync/export use the --atmx- prefix. {{TOKENS_CSS_PATH}} = path to the synced token file (e.g. ./tokens.css, same as syncAll output). {{TYPESETS_LINK}} = if a typeset CSS file was created (e.g. typesets.css), the full tag e.g. <link rel="stylesheet" href="typesets.css">, otherwise empty string. {{DS_NAME}} = design system name. {{BRAND_PRIMARY_VAR}} = var(--atmx-color-brand-primary). {{BRAND_PRIMARY_FOREGROUND_VAR}} = var(--atmx-color-brand-primary-foreground). {{HEADING_FONT_VAR}} = var(--atmx-typography-font-family-heading) or var(--atmx-typography-font-family-display). {{FONT_FAMILY_VAR}} = var(--atmx-typography-font-family-body). {{LARGEST_DISPLAY_TYPESET_CLASS}} = largest display typeset class from listTypesets (display role, largest font size; e.g. typeset-display-2xl or typeset-display-bold), or empty string if no typeset file. {{BODY_TYPESET_CLASS}} = typeset class for body text from listTypesets (e.g. typeset-body-md), or empty string if no typeset file. {{FONT_LINK_TAG}} = Google Fonts <link> for the font, or empty string. Do not invent CSS variable names; use only vars that exist in the export.'
|
|
3087
3102
|
} : void 0,
|
|
3088
3103
|
meta: {
|
|
3089
3104
|
dsName: data.meta.name,
|
|
@@ -3103,7 +3118,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3103
3118
|
case "syncToFigma": {
|
|
3104
3119
|
const payloads = buildFigmaPayloadsFromDS(data);
|
|
3105
3120
|
const out = {};
|
|
3106
|
-
const agentStartBridge = `The Figma bridge runs with this MCP server. Ensure
|
|
3121
|
+
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.`;
|
|
3107
3122
|
const userSteps = [
|
|
3108
3123
|
FIGMA_CONNECTION_INSTRUCTIONS.startBridge,
|
|
3109
3124
|
FIGMA_CONNECTION_INSTRUCTIONS.installAndRun,
|
|
@@ -3133,21 +3148,25 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3133
3148
|
out.colorVariables = await sendBridgeRequest("create_color_variables", {
|
|
3134
3149
|
collectionName: payloads.colorVariables.collectionName,
|
|
3135
3150
|
modes: payloads.colorVariables.modes,
|
|
3136
|
-
variables: payloads.colorVariables.variables
|
|
3151
|
+
variables: payloads.colorVariables.variables,
|
|
3152
|
+
removeVariablesNotInPayload: true
|
|
3137
3153
|
});
|
|
3138
3154
|
}
|
|
3139
3155
|
if (payloads.paintStyles.length > 0) {
|
|
3140
|
-
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
3156
|
+
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
3157
|
+
styles: payloads.paintStyles,
|
|
3158
|
+
removePaintStylesNotInPayload: true
|
|
3159
|
+
});
|
|
3141
3160
|
}
|
|
3142
3161
|
if (payloads.numberVariableCollections.length > 0) {
|
|
3143
3162
|
const numberResults = [];
|
|
3144
3163
|
try {
|
|
3145
|
-
const foundationsName = payloads.colorVariables.collectionName;
|
|
3146
3164
|
for (const coll of payloads.numberVariableCollections) {
|
|
3147
3165
|
const result = await sendBridgeRequest("create_number_variables", {
|
|
3148
|
-
collectionName:
|
|
3149
|
-
variables: coll.variables.map((v) => ({ name:
|
|
3150
|
-
scopes: coll.scopes
|
|
3166
|
+
collectionName: coll.collectionName,
|
|
3167
|
+
variables: coll.variables.map((v) => ({ name: v.name, value: v.value })),
|
|
3168
|
+
scopes: coll.scopes,
|
|
3169
|
+
removeVariablesNotInPayload: true
|
|
3151
3170
|
});
|
|
3152
3171
|
numberResults.push({ categoryKey: coll.categoryKey, result });
|
|
3153
3172
|
}
|
|
@@ -3161,10 +3180,16 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3161
3180
|
}
|
|
3162
3181
|
}
|
|
3163
3182
|
if (payloads.textStyles.length > 0) {
|
|
3164
|
-
out.textStyles = await sendBridgeRequest("create_text_styles", {
|
|
3183
|
+
out.textStyles = await sendBridgeRequest("create_text_styles", {
|
|
3184
|
+
styles: payloads.textStyles,
|
|
3185
|
+
removeTextStylesNotInPayload: true
|
|
3186
|
+
});
|
|
3165
3187
|
}
|
|
3166
3188
|
if (payloads.effectStyles.length > 0) {
|
|
3167
|
-
out.effectStyles = await sendBridgeRequest("create_effect_styles", {
|
|
3189
|
+
out.effectStyles = await sendBridgeRequest("create_effect_styles", {
|
|
3190
|
+
styles: payloads.effectStyles,
|
|
3191
|
+
removeShadowStylesNotInPayload: true
|
|
3192
|
+
});
|
|
3168
3193
|
}
|
|
3169
3194
|
out.figmaPayload = {
|
|
3170
3195
|
collectionName: payloads.colorVariables.collectionName,
|
|
@@ -3204,11 +3229,76 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3204
3229
|
if (out.numberVariablesHint) {
|
|
3205
3230
|
out.summary = [out.summary, out.numberVariablesHint].filter(Boolean).join(" ");
|
|
3206
3231
|
}
|
|
3232
|
+
const summaryParts = [];
|
|
3233
|
+
const colorResult = out.colorVariables;
|
|
3234
|
+
if (colorResult && (colorResult.variableNames?.length || (colorResult.removed ?? 0) > 0)) {
|
|
3235
|
+
const parts = [];
|
|
3236
|
+
if (colorResult.variableNames?.length) parts.push(`${colorResult.variableNames.length} synced`);
|
|
3237
|
+
if ((colorResult.removed ?? 0) > 0) parts.push(`${colorResult.removed} removed`);
|
|
3238
|
+
summaryParts.push(`Colors: ${parts.join(", ")}.`);
|
|
3239
|
+
}
|
|
3240
|
+
const paintResult = out.paintStyles;
|
|
3241
|
+
if (paintResult) {
|
|
3242
|
+
const c = paintResult.created ?? 0;
|
|
3243
|
+
const u = paintResult.updated ?? 0;
|
|
3244
|
+
const r = paintResult.removed ?? 0;
|
|
3245
|
+
if (c + u + r > 0) {
|
|
3246
|
+
const parts = [];
|
|
3247
|
+
if (c > 0) parts.push(`${c} created`);
|
|
3248
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
3249
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
3250
|
+
summaryParts.push(`Paint styles: ${parts.join(", ")}.`);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
const effectResult = out.effectStyles;
|
|
3254
|
+
if (effectResult) {
|
|
3255
|
+
const c = effectResult.created ?? 0;
|
|
3256
|
+
const u = effectResult.updated ?? 0;
|
|
3257
|
+
const r = effectResult.removed ?? 0;
|
|
3258
|
+
if (c + u + r > 0) {
|
|
3259
|
+
const parts = [];
|
|
3260
|
+
if (c > 0) parts.push(`${c} created`);
|
|
3261
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
3262
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
3263
|
+
summaryParts.push(`Effect styles (shadows): ${parts.join(", ")}.`);
|
|
3264
|
+
if (effectResult.removedNames?.length) {
|
|
3265
|
+
summaryParts.push(`Removed: ${effectResult.removedNames.join(", ")}.`);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
const numResult = out.numberVariables;
|
|
3270
|
+
if (Array.isArray(numResult)) {
|
|
3271
|
+
const total = numResult.reduce((acc, r) => acc + (r.result?.variableNames?.length ?? 0), 0);
|
|
3272
|
+
const totalRemoved = numResult.reduce((acc, r) => acc + (r.result?.removed ?? 0), 0);
|
|
3273
|
+
if (total > 0 || totalRemoved > 0) {
|
|
3274
|
+
const parts = [];
|
|
3275
|
+
if (total > 0) parts.push(`${total} synced`);
|
|
3276
|
+
if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
|
|
3277
|
+
summaryParts.push(`Number variables: ${parts.join(", ")}.`);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
const textStylesWithRemoved = out.textStyles;
|
|
3281
|
+
if (textStylesWithRemoved && (textStylesWithRemoved.created ?? 0) + (textStylesWithRemoved.updated ?? 0) + (textStylesWithRemoved.removed ?? 0) > 0) {
|
|
3282
|
+
const c = textStylesWithRemoved.created ?? 0;
|
|
3283
|
+
const u = textStylesWithRemoved.updated ?? 0;
|
|
3284
|
+
const r = textStylesWithRemoved.removed ?? 0;
|
|
3285
|
+
const parts = [];
|
|
3286
|
+
if (c > 0) parts.push(`${c} created`);
|
|
3287
|
+
if (u > 0) parts.push(`${u} updated`);
|
|
3288
|
+
if (r > 0) parts.push(`${r} removed`);
|
|
3289
|
+
summaryParts.push(`Text styles: ${parts.join(", ")}.`);
|
|
3290
|
+
}
|
|
3291
|
+
if (summaryParts.length > 0 && !out.error) {
|
|
3292
|
+
out.summary = [out.summary, summaryParts.join(" ")].filter(Boolean).join(" ");
|
|
3293
|
+
}
|
|
3207
3294
|
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.";
|
|
3295
|
+
const responseText = out.summary ? `${out.summary}
|
|
3296
|
+
|
|
3297
|
+
${JSON.stringify(out, null, 2)}` : JSON.stringify(out, null, 2);
|
|
3208
3298
|
return {
|
|
3209
3299
|
content: [{
|
|
3210
3300
|
type: "text",
|
|
3211
|
-
text:
|
|
3301
|
+
text: responseText
|
|
3212
3302
|
}],
|
|
3213
3303
|
...out.error ? { isError: true } : {}
|
|
3214
3304
|
};
|
|
@@ -3223,7 +3313,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3223
3313
|
text: JSON.stringify({
|
|
3224
3314
|
error: "Figma bridge not reachable.",
|
|
3225
3315
|
bridgeNotRunning: true,
|
|
3226
|
-
agentInstruction: "The Figma bridge runs with this MCP server. Ensure
|
|
3316
|
+
agentInstruction: "The Figma bridge runs with this MCP server. Ensure your AI environment has this MCP server running, then in Figma run the Atomix plugin and tap Connect. Then call getFigmaVariablesAndStyles again."
|
|
3227
3317
|
}, null, 2)
|
|
3228
3318
|
}],
|
|
3229
3319
|
isError: true
|
|
@@ -3253,7 +3343,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3253
3343
|
type: "text",
|
|
3254
3344
|
text: JSON.stringify({
|
|
3255
3345
|
error: "Figma bridge not reachable.",
|
|
3256
|
-
hint: "The bridge runs with this MCP server. Ensure
|
|
3346
|
+
hint: "The bridge runs with this MCP server. Ensure your AI environment has this MCP server running, then run the Atomix plugin and tap Connect."
|
|
3257
3347
|
}, null, 2)
|
|
3258
3348
|
}],
|
|
3259
3349
|
isError: true
|
|
@@ -3371,7 +3461,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3371
3461
|
type: "text",
|
|
3372
3462
|
text: JSON.stringify({
|
|
3373
3463
|
error: "Figma bridge not reachable.",
|
|
3374
|
-
hint: "Run the Atomix plugin in Figma and
|
|
3464
|
+
hint: "Run the Atomix plugin in Figma and tap Connect, then retry."
|
|
3375
3465
|
}, null, 2)
|
|
3376
3466
|
}],
|
|
3377
3467
|
isError: true
|
|
@@ -3412,7 +3502,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3412
3502
|
type: "text",
|
|
3413
3503
|
text: JSON.stringify({
|
|
3414
3504
|
error: "Figma bridge not reachable.",
|
|
3415
|
-
hint: "Run the Atomix plugin in Figma and
|
|
3505
|
+
hint: "Run the Atomix plugin in Figma and tap Connect, then retry."
|
|
3416
3506
|
}, null, 2)
|
|
3417
3507
|
}],
|
|
3418
3508
|
isError: true
|
|
@@ -3445,7 +3535,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
3445
3535
|
type: "text",
|
|
3446
3536
|
text: JSON.stringify({
|
|
3447
3537
|
error: "Figma bridge not reachable.",
|
|
3448
|
-
hint: "Run the Atomix plugin in Figma and
|
|
3538
|
+
hint: "Run the Atomix plugin in Figma and tap Connect, then retry."
|
|
3449
3539
|
}, null, 2)
|
|
3450
3540
|
}],
|
|
3451
3541
|
isError: true
|
|
@@ -3625,6 +3715,7 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3625
3715
|
<title>Setup complete \u2014 {{DS_NAME}}</title>
|
|
3626
3716
|
{{FONT_LINK_TAG}}
|
|
3627
3717
|
<link rel="stylesheet" href="{{TOKENS_CSS_PATH}}">
|
|
3718
|
+
{{TYPESETS_LINK}}
|
|
3628
3719
|
<style>
|
|
3629
3720
|
* { box-sizing: border-box; }
|
|
3630
3721
|
body {
|
|
@@ -3635,9 +3726,11 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3635
3726
|
min-height: 100vh;
|
|
3636
3727
|
padding: 2rem;
|
|
3637
3728
|
display: flex;
|
|
3729
|
+
justify-content: center;
|
|
3730
|
+
align-items: center;
|
|
3638
3731
|
}
|
|
3639
3732
|
.wrap { width: 375px; max-width: 100%; }
|
|
3640
|
-
.icon { width: 2rem; height: 2rem; margin: 0
|
|
3733
|
+
.icon { width: 2rem; height: 2rem; margin: 0 0 1rem; }
|
|
3641
3734
|
h1 {
|
|
3642
3735
|
font-family: {{HEADING_FONT_VAR}}, {{FONT_FAMILY_VAR}}, system-ui, sans-serif;
|
|
3643
3736
|
font-size: clamp(3rem, 4vw, 5rem);
|
|
@@ -3653,12 +3746,12 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3653
3746
|
.tips a { color: inherit; text-decoration: underline; }
|
|
3654
3747
|
</style>
|
|
3655
3748
|
</head>
|
|
3656
|
-
<body>
|
|
3749
|
+
<body class="{{BODY_TYPESET_CLASS}}">
|
|
3657
3750
|
<div class="wrap">
|
|
3658
3751
|
<div class="icon" aria-hidden="true">
|
|
3659
3752
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="44" height="44"><path d="M20 6L9 17l-5-5"/></svg>
|
|
3660
3753
|
</div>
|
|
3661
|
-
<h1>You're all set with {{DS_NAME}}</h1>
|
|
3754
|
+
<h1 class="{{LARGEST_DISPLAY_TYPESET_CLASS}}">You're all set with {{DS_NAME}}</h1>
|
|
3662
3755
|
<p class="lead">This page uses your design system: brand primary as background, headline and body typesets, and an icon.</p>
|
|
3663
3756
|
<div class="now">
|
|
3664
3757
|
<strong>What you can do now:</strong>
|
|
@@ -3714,7 +3807,7 @@ Get your DS ID and token from the Export modal or Settings \u2192 Regenerate Ato
|
|
|
3714
3807
|
};
|
|
3715
3808
|
}
|
|
3716
3809
|
if (!hasValidAuthConfig() || authFailedNoTools) {
|
|
3717
|
-
throw new Error("MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
3810
|
+
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.");
|
|
3718
3811
|
}
|
|
3719
3812
|
const data = await fetchDesignSystemForMCP();
|
|
3720
3813
|
const stats = getTokenStats(data);
|
|
@@ -3789,7 +3882,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3789
3882
|
role: "user",
|
|
3790
3883
|
content: {
|
|
3791
3884
|
type: "text",
|
|
3792
|
-
text: "MCP access requires valid --ds-id and --atomix-token. Add both to your MCP config and restart
|
|
3885
|
+
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."
|
|
3793
3886
|
}
|
|
3794
3887
|
}]
|
|
3795
3888
|
};
|
|
@@ -3815,7 +3908,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3815
3908
|
- \`--ds-id\`: Your design system ID (get it from https://atomixstudio.eu/ds/[your-ds-id])
|
|
3816
3909
|
- \`--atomix-token\`: Your access token (get it from the Export modal or Settings \u2192 Regenerate Atomix access token)
|
|
3817
3910
|
|
|
3818
|
-
Both are required. Configure the MCP server in your
|
|
3911
|
+
Both are required. Configure the MCP server in your AI tool's MCP settings, then restart your AI tool.`
|
|
3819
3912
|
}
|
|
3820
3913
|
}
|
|
3821
3914
|
]
|
|
@@ -4133,7 +4226,7 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
4133
4226
|
role: "user",
|
|
4134
4227
|
content: {
|
|
4135
4228
|
type: "text",
|
|
4136
|
-
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
|
|
4229
|
+
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.`
|
|
4137
4230
|
}
|
|
4138
4231
|
}
|
|
4139
4232
|
]
|
|
@@ -4149,7 +4242,7 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
4149
4242
|
type: "text",
|
|
4150
4243
|
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**.
|
|
4151
4244
|
|
|
4152
|
-
Prerequisites: Figma bridge runs inside this MCP server. In Figma, run the Atomix plugin and
|
|
4245
|
+
Prerequisites: Figma bridge runs inside this MCP server. In Figma, run the Atomix plugin and tap Connect; leave the plugin open.
|
|
4153
4246
|
|
|
4154
4247
|
**Setup (once):** syncToFigma \u2192 createDesignPlaceholder (save \`frameId\`) \u2192 getAIToolRules + listTokens \u2192 resolveFigmaIdsForTokens. Use the returned \`resolved\` map and \`frameId\` for all passes.
|
|
4155
4248
|
|
|
@@ -4162,7 +4255,7 @@ Apply the owner's design system: set all fills and text styles using only ids fr
|
|
|
4162
4255
|
**Pass 3 \u2014 Confirm no hardcoded values**
|
|
4163
4256
|
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.
|
|
4164
4257
|
|
|
4165
|
-
If the bridge is not reachable: tell the user to run the Atomix plugin in Figma and
|
|
4258
|
+
If the bridge is not reachable: tell the user to run the Atomix plugin in Figma and tap Connect, then retry.`
|
|
4166
4259
|
}
|
|
4167
4260
|
}
|
|
4168
4261
|
]
|
|
@@ -4311,7 +4404,7 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4311
4404
|
- **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.
|
|
4312
4405
|
- **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.
|
|
4313
4406
|
- **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.
|
|
4314
|
-
- **Showcase page (web only):** If platform is web and getDependencies returned a \`showcase\` object, create the file at \`showcase.path\` using \`showcase.template\`. Replace every placeholder per \`showcase.substitutionInstructions\`: TOKENS_CSS_PATH, DS_NAME, BRAND_PRIMARY_VAR (page background), BRAND_PRIMARY_FOREGROUND_VAR (text on brand), HEADING_FONT_VAR (h1), FONT_FAMILY_VAR (body), FONT_LINK_TAG. Use only CSS variable names that exist in the synced token file. Do not change the HTML structure. After creating the file, launch it in the default browser (e.g. \`open atomix-setup-showcase.html\` on macOS, \`xdg-open atomix-setup-showcase.html\` on Linux, or the equivalent on Windows).
|
|
4407
|
+
- **Showcase page (web only):** If platform is web and getDependencies returned a \`showcase\` object, create the file at \`showcase.path\` using \`showcase.template\`. Replace every placeholder per \`showcase.substitutionInstructions\`: TOKENS_CSS_PATH, TYPESETS_LINK (if you created a typeset CSS file, use the full \`<link rel="stylesheet" href="\u2026">\` tag; otherwise empty string), DS_NAME, BRAND_PRIMARY_VAR (page background), BRAND_PRIMARY_FOREGROUND_VAR (text on brand), HEADING_FONT_VAR (h1 fallback), FONT_FAMILY_VAR (body fallback), LARGEST_DISPLAY_TYPESET_CLASS (largest display typeset from listTypesets) and BODY_TYPESET_CLASS (e.g. typeset-body-md; leave empty if no typeset file), FONT_LINK_TAG. Use only CSS variable names that exist in the synced token file. Do not change the HTML structure. After creating the file, launch it in the default browser (e.g. \`open atomix-setup-showcase.html\` on macOS, \`xdg-open atomix-setup-showcase.html\` on Linux, or the equivalent on Windows).
|
|
4315
4408
|
- Report only what you actually created or updated. Do not claim the token file was added if you did not call syncAll.
|
|
4316
4409
|
- **After reporting \u2013 styles/theme:**
|
|
4317
4410
|
- **Web:** If the project already has at least one CSS file: recommend how to integrate Atomix (e.g. import the synced tokens file, use \`var(--atmx-*)\`). Do not suggest a new global CSS. Only if there is **no** CSS file at all, ask once: "There are no CSS files yet. Do you want me to build a global typeset from the design system?" If yes, create a CSS file that includes: (1) font \`@import\` or document that a font link is needed, and (2) **typeset rules**\u2014call **listTypesets** and emit **one CSS class per typeset** (do not skip any). For each class set font-family, font-size, font-weight, line-height, letter-spacing; when the typeset has text-transform or text-decoration, set those too for a 1:1 match. Use the CSS variable names returned by listTypesets. The output must not be only a font import; it must define every typeset with every style detail from the design system.
|
|
@@ -4391,8 +4484,9 @@ ${tokenSummary}
|
|
|
4391
4484
|
|---------|----------------|
|
|
4392
4485
|
| **/--hello** | Get started - overview, tokens, and tools. Run this first! |
|
|
4393
4486
|
| **/--get-started** | Get started with design system in project. Three phases; creates files only after you approve. |
|
|
4394
|
-
| **/--rules** | Governance rules for your AI tool (Cursor, Copilot, Windsurf
|
|
4487
|
+
| **/--rules** | Governance rules for your AI tool (e.g. Cursor, Copilot, Windsurf). |
|
|
4395
4488
|
| **/--sync** | Sync tokens, rules, skills, and dependencies manifest (icons, fonts). Safe: adds new, updates existing, marks deprecated. |
|
|
4489
|
+
| **/--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. |
|
|
4396
4490
|
| **/--refactor** | Migrate deprecated tokens in codebase. Run after /--sync. |
|
|
4397
4491
|
|
|
4398
4492
|
**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.
|
|
@@ -4423,7 +4517,7 @@ async function startServer() {
|
|
|
4423
4517
|
console.error(`Atomix MCP Server started for design system: ${dsId}`);
|
|
4424
4518
|
console.error(`Atomix MCP API base: ${apiBase}`);
|
|
4425
4519
|
console.error(
|
|
4426
|
-
"If you switched MCP config (e.g. free vs pro DS), restart
|
|
4520
|
+
"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."
|
|
4427
4521
|
);
|
|
4428
4522
|
}
|
|
4429
4523
|
function onShutdown() {
|
|
@@ -4431,6 +4525,8 @@ function onShutdown() {
|
|
|
4431
4525
|
}
|
|
4432
4526
|
process.on("SIGINT", onShutdown);
|
|
4433
4527
|
process.on("SIGTERM", onShutdown);
|
|
4528
|
+
process.stdin.on("end", onShutdown);
|
|
4529
|
+
process.stdin.on("close", onShutdown);
|
|
4434
4530
|
async function main() {
|
|
4435
4531
|
await startServer();
|
|
4436
4532
|
}
|