@atomixstudio/mcp 1.0.35 → 1.0.38
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 +5 -33
- package/dist/index.js +29 -854
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
- package/dist/chunk-426RNS3G.js +0 -1782
- package/dist/chunk-426RNS3G.js.map +0 -1
- package/dist/figma-bridge-protocol.d.ts +0 -28
- package/dist/figma-bridge-protocol.js +0 -14
- package/dist/figma-bridge-protocol.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
FIGMA_DESIGN_CATALOG,
|
|
4
|
-
FIGMA_DESIGN_SKILL_MD,
|
|
5
|
-
buildFigmaPayloadsFromDS,
|
|
6
|
-
buildResolvers,
|
|
7
|
-
formatCatalogForMCP,
|
|
8
|
-
getDesignMethodNames,
|
|
9
|
-
getQueryMethodNames,
|
|
10
|
-
isAllowedMethod,
|
|
11
|
-
normalizeBridgeMethod,
|
|
12
|
-
resolveStepParams
|
|
13
|
-
} from "./chunk-426RNS3G.js";
|
|
14
2
|
|
|
15
3
|
// src/index.ts
|
|
16
4
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -1227,180 +1215,6 @@ function getTokenStats(data) {
|
|
|
1227
1215
|
// src/index.ts
|
|
1228
1216
|
import * as path from "path";
|
|
1229
1217
|
import * as fs from "fs";
|
|
1230
|
-
import { execSync } from "child_process";
|
|
1231
|
-
import { platform } from "os";
|
|
1232
|
-
import WebSocket, { WebSocketServer } from "ws";
|
|
1233
|
-
var FIGMA_BRIDGE_PORT = Number(process.env.FIGMA_BRIDGE_PORT) || 8765;
|
|
1234
|
-
var FIGMA_BRIDGE_HOST = process.env.FIGMA_BRIDGE_HOST || "127.0.0.1";
|
|
1235
|
-
var FIGMA_BRIDGE_TIMEOUT_MS = 15e3;
|
|
1236
|
-
var FIGMA_BRIDGE_TOKEN = process.env.FIGMA_BRIDGE_TOKEN || null;
|
|
1237
|
-
var FIGMA_CONNECTION_INSTRUCTIONS = {
|
|
1238
|
-
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.",
|
|
1239
|
-
connect: 'In the plugin UI, tap **Connect** and wait until the status shows "Connected".',
|
|
1240
|
-
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."
|
|
1241
|
-
};
|
|
1242
|
-
var bridgeWss = null;
|
|
1243
|
-
var pluginWs = null;
|
|
1244
|
-
var pendingBridgeRequests = /* @__PURE__ */ new Map();
|
|
1245
|
-
function ensureFigmaBridgePortFree(port) {
|
|
1246
|
-
const portStr = String(port);
|
|
1247
|
-
const ourPid = String(process.pid);
|
|
1248
|
-
try {
|
|
1249
|
-
if (platform() === "win32") {
|
|
1250
|
-
const out = execSync(`netstat -ano`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
1251
|
-
const pids = /* @__PURE__ */ new Set();
|
|
1252
|
-
for (const line of out.split(/\r?\n/)) {
|
|
1253
|
-
if (line.includes(`:${portStr}`) && line.includes("LISTENING")) {
|
|
1254
|
-
const parts = line.trim().split(/\s+/);
|
|
1255
|
-
const pid = parts[parts.length - 1];
|
|
1256
|
-
if (/^\d+$/.test(pid) && pid !== ourPid) pids.add(pid);
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
for (const pid of pids) {
|
|
1260
|
-
try {
|
|
1261
|
-
execSync(`taskkill /PID ${pid} /F`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
1262
|
-
console.error(`[atomix-mcp] Freed Figma bridge port ${port} (killed PID ${pid})`);
|
|
1263
|
-
} catch (_) {
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
} else {
|
|
1267
|
-
const out = execSync(`lsof -ti :${portStr}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1268
|
-
if (!out) return;
|
|
1269
|
-
const pids = out.split(/\s+/).filter((p) => p && p !== ourPid);
|
|
1270
|
-
for (const pid of pids) {
|
|
1271
|
-
try {
|
|
1272
|
-
execSync(`kill -9 ${pid}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
1273
|
-
console.error(`[atomix-mcp] Freed Figma bridge port ${port} (killed PID ${pid})`);
|
|
1274
|
-
} catch (_) {
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
} catch {
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
function startFigmaBridge() {
|
|
1282
|
-
if (bridgeWss) return;
|
|
1283
|
-
try {
|
|
1284
|
-
ensureFigmaBridgePortFree(FIGMA_BRIDGE_PORT);
|
|
1285
|
-
bridgeWss = new WebSocketServer({
|
|
1286
|
-
host: FIGMA_BRIDGE_HOST,
|
|
1287
|
-
port: FIGMA_BRIDGE_PORT,
|
|
1288
|
-
clientTracking: true
|
|
1289
|
-
});
|
|
1290
|
-
bridgeWss.on("connection", (ws, req) => {
|
|
1291
|
-
const url = req.url || "";
|
|
1292
|
-
const params = new URLSearchParams(url.startsWith("/") ? url.slice(1) : url);
|
|
1293
|
-
const token = params.get("token");
|
|
1294
|
-
const role = params.get("role");
|
|
1295
|
-
if (FIGMA_BRIDGE_TOKEN && token !== FIGMA_BRIDGE_TOKEN) {
|
|
1296
|
-
ws.close(4003, "Invalid or missing bridge token");
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
if (role !== "plugin") {
|
|
1300
|
-
ws.close(4002, "Only role=plugin is accepted (bridge runs in MCP server)");
|
|
1301
|
-
return;
|
|
1302
|
-
}
|
|
1303
|
-
if (pluginWs) {
|
|
1304
|
-
try {
|
|
1305
|
-
pluginWs.close();
|
|
1306
|
-
} catch (_) {
|
|
1307
|
-
}
|
|
1308
|
-
pluginWs = null;
|
|
1309
|
-
}
|
|
1310
|
-
pluginWs = ws;
|
|
1311
|
-
ws.on("message", (raw) => {
|
|
1312
|
-
const text = typeof raw === "string" ? raw : raw.toString("utf8");
|
|
1313
|
-
let msg;
|
|
1314
|
-
try {
|
|
1315
|
-
msg = JSON.parse(text);
|
|
1316
|
-
} catch {
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
const parsed = msg;
|
|
1320
|
-
if (parsed?.type === "ping" && typeof parsed.id === "string") {
|
|
1321
|
-
try {
|
|
1322
|
-
ws.send(JSON.stringify({ type: "pong", id: parsed.id }));
|
|
1323
|
-
} catch (_) {
|
|
1324
|
-
}
|
|
1325
|
-
return;
|
|
1326
|
-
}
|
|
1327
|
-
if (typeof parsed.id === "string" && ("result" in parsed || "error" in parsed)) {
|
|
1328
|
-
const pending = pendingBridgeRequests.get(parsed.id);
|
|
1329
|
-
if (pending) {
|
|
1330
|
-
clearTimeout(pending.timeout);
|
|
1331
|
-
pendingBridgeRequests.delete(parsed.id);
|
|
1332
|
-
if (parsed.error) pending.reject(new Error(parsed.error));
|
|
1333
|
-
else pending.resolve(parsed.result);
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
ws.on("close", () => {
|
|
1338
|
-
if (pluginWs === ws) pluginWs = null;
|
|
1339
|
-
});
|
|
1340
|
-
ws.on("error", () => {
|
|
1341
|
-
if (pluginWs === ws) pluginWs = null;
|
|
1342
|
-
});
|
|
1343
|
-
});
|
|
1344
|
-
bridgeWss.on("listening", () => {
|
|
1345
|
-
console.error(`[atomix-mcp] Figma bridge listening on ws://${FIGMA_BRIDGE_HOST}:${FIGMA_BRIDGE_PORT} (local only)`);
|
|
1346
|
-
if (FIGMA_BRIDGE_TOKEN) {
|
|
1347
|
-
console.error("[atomix-mcp] Figma bridge token required (FIGMA_BRIDGE_TOKEN)");
|
|
1348
|
-
}
|
|
1349
|
-
});
|
|
1350
|
-
bridgeWss.on("error", (err) => {
|
|
1351
|
-
console.error("[atomix-mcp] Figma bridge server error:", err);
|
|
1352
|
-
});
|
|
1353
|
-
} catch (err) {
|
|
1354
|
-
console.error("[atomix-mcp] Failed to start Figma bridge:", err);
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
function closeFigmaBridge() {
|
|
1358
|
-
if (pluginWs) {
|
|
1359
|
-
try {
|
|
1360
|
-
pluginWs.close();
|
|
1361
|
-
} catch (_) {
|
|
1362
|
-
}
|
|
1363
|
-
pluginWs = null;
|
|
1364
|
-
}
|
|
1365
|
-
if (bridgeWss) {
|
|
1366
|
-
try {
|
|
1367
|
-
bridgeWss.close();
|
|
1368
|
-
} catch (_) {
|
|
1369
|
-
}
|
|
1370
|
-
bridgeWss = null;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
function isBridgeReachable() {
|
|
1374
|
-
return Promise.resolve(!!(pluginWs && pluginWs.readyState === WebSocket.OPEN));
|
|
1375
|
-
}
|
|
1376
|
-
function sendBridgeRequest(method, params, timeoutMs = FIGMA_BRIDGE_TIMEOUT_MS) {
|
|
1377
|
-
const normalized = normalizeBridgeMethod(method);
|
|
1378
|
-
if (!isAllowedMethod(normalized)) {
|
|
1379
|
-
return Promise.reject(new Error(`Bridge method not allowed: ${method}`));
|
|
1380
|
-
}
|
|
1381
|
-
const ws = pluginWs;
|
|
1382
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
1383
|
-
return Promise.reject(
|
|
1384
|
-
new Error("Figma plugin not connected. Open Figma, run Atomix plugin, and tap Connect.")
|
|
1385
|
-
);
|
|
1386
|
-
}
|
|
1387
|
-
const id = `mcp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1388
|
-
return new Promise((resolve3, reject) => {
|
|
1389
|
-
const timeout = setTimeout(() => {
|
|
1390
|
-
if (pendingBridgeRequests.delete(id)) {
|
|
1391
|
-
reject(new Error("Figma bridge timeout. " + FIGMA_CONNECTION_INSTRUCTIONS.startBridge + " Then " + FIGMA_CONNECTION_INSTRUCTIONS.connect));
|
|
1392
|
-
}
|
|
1393
|
-
}, timeoutMs);
|
|
1394
|
-
pendingBridgeRequests.set(id, { resolve: resolve3, reject, timeout });
|
|
1395
|
-
try {
|
|
1396
|
-
ws.send(JSON.stringify({ id, method: normalized, params }));
|
|
1397
|
-
} catch (e) {
|
|
1398
|
-
pendingBridgeRequests.delete(id);
|
|
1399
|
-
clearTimeout(timeout);
|
|
1400
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
1401
|
-
}
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
1404
1218
|
function parseArgs() {
|
|
1405
1219
|
const args = process.argv.slice(2);
|
|
1406
1220
|
let dsId2 = null;
|
|
@@ -1427,7 +1241,7 @@ function parseArgs() {
|
|
|
1427
1241
|
var cliArgs = parseArgs();
|
|
1428
1242
|
var { dsId, apiKey, accessToken } = cliArgs;
|
|
1429
1243
|
var apiBase = cliArgs.apiBase || "https://atomix.studio";
|
|
1430
|
-
var MCP_VERSION = "1.0.
|
|
1244
|
+
var MCP_VERSION = "1.0.36";
|
|
1431
1245
|
var cachedData = null;
|
|
1432
1246
|
var cachedETag = null;
|
|
1433
1247
|
var cachedMcpTier = null;
|
|
@@ -1601,7 +1415,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1601
1415
|
if (cachedMcpTier === "pro") {
|
|
1602
1416
|
console.error("[Atomix MCP] Resolved tier = pro.");
|
|
1603
1417
|
} else if (cachedMcpTier === "free") {
|
|
1604
|
-
console.error("[Atomix MCP] Resolved tier = free.
|
|
1418
|
+
console.error("[Atomix MCP] Resolved tier = free.");
|
|
1605
1419
|
}
|
|
1606
1420
|
} catch (err) {
|
|
1607
1421
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1742,7 +1556,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1742
1556
|
},
|
|
1743
1557
|
{
|
|
1744
1558
|
name: "syncAll",
|
|
1745
|
-
description: "Sync tokens, AI rules,
|
|
1559
|
+
description: "Sync tokens, AI rules, skill (.cursor/skills/atomix-ds/SKILL.md), and atomix-dependencies.json. All paths are resolved under workspaceRoot so files are written inside the project repo (committable). Use dryRun: true first to report what would change without writing; then dryRun: false to apply. Optional: workspaceRoot (project root; default: ATOMIX_PROJECT_ROOT env or process.cwd()), output (default ./tokens.css), format (default css), skipTokens, dryRun.",
|
|
1746
1560
|
inputSchema: {
|
|
1747
1561
|
type: "object",
|
|
1748
1562
|
properties: {
|
|
@@ -1798,54 +1612,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1798
1612
|
properties: {},
|
|
1799
1613
|
required: []
|
|
1800
1614
|
}
|
|
1801
|
-
},
|
|
1802
|
-
{
|
|
1803
|
-
name: "syncToFigma",
|
|
1804
|
-
description: "Push the owner's design system to Figma: creates color variable collection (Light/Dark), color and paint styles, number variables (spacing, radius, borders, sizing, breakpoints), text styles, and shadow effect styles. Uses local WebSocket bridge and Atomix Figma plugin (no Figma REST API). No arguments. If the bridge is not running, the response includes agentInstruction to start it; only if that fails should the user start the bridge and connect the plugin. Call this when the user asks to 'sync to Figma' or 'push DS to Figma'.",
|
|
1805
|
-
inputSchema: {
|
|
1806
|
-
type: "object",
|
|
1807
|
-
properties: {},
|
|
1808
|
-
required: []
|
|
1809
|
-
}
|
|
1810
1615
|
}
|
|
1811
1616
|
];
|
|
1812
|
-
if (cachedMcpTier === "pro") {
|
|
1813
|
-
toolsList.push({
|
|
1814
|
-
name: "designInFigma",
|
|
1815
|
-
description: "Design UI in the connected Figma file using the design system tokens. Call with action:'catalog' to discover available bridge methods, their parameters, and the file's variables/styles. Call with action:'query' to read from Figma: get_selection (current selection), get_node_info (nodeId), get_design_screenshot (frameId; returns PNG image for review). Call with action:'execute' and an array of steps to create the design on canvas. Requires the Atomix Figma plugin to be connected.",
|
|
1816
|
-
inputSchema: {
|
|
1817
|
-
type: "object",
|
|
1818
|
-
properties: {
|
|
1819
|
-
action: {
|
|
1820
|
-
type: "string",
|
|
1821
|
-
enum: ["catalog", "query", "execute"],
|
|
1822
|
-
description: "catalog = discover methods + file context; query = read selection/node/screenshot; execute = run design steps"
|
|
1823
|
-
},
|
|
1824
|
-
queryMethod: {
|
|
1825
|
-
type: "string",
|
|
1826
|
-
description: "Required when action is 'query'. One of: get_selection, get_node_info, get_document_info, get_design_screenshot, get_figma_variables_and_styles, list_local_components, get_component_catalog, get_variable_collection_modes, get_frame_variable_mode."
|
|
1827
|
-
},
|
|
1828
|
-
queryParams: {
|
|
1829
|
-
type: "object",
|
|
1830
|
-
description: "Optional params for query. get_node_info needs { nodeId }. get_design_screenshot needs { frameId } and optional { scale }."
|
|
1831
|
-
},
|
|
1832
|
-
steps: {
|
|
1833
|
-
type: "array",
|
|
1834
|
-
description: "Required when action is 'execute'. Array of { method, params } design commands.",
|
|
1835
|
-
items: {
|
|
1836
|
-
type: "object",
|
|
1837
|
-
properties: {
|
|
1838
|
-
method: { type: "string" },
|
|
1839
|
-
params: { type: "object" }
|
|
1840
|
-
},
|
|
1841
|
-
required: ["method"]
|
|
1842
|
-
}
|
|
1843
|
-
}
|
|
1844
|
-
},
|
|
1845
|
-
required: ["action"]
|
|
1846
|
-
}
|
|
1847
|
-
});
|
|
1848
|
-
}
|
|
1849
1617
|
return { tools: toolsList };
|
|
1850
1618
|
});
|
|
1851
1619
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1988,7 +1756,7 @@ Version: ${designSystemData.meta.version}`,
|
|
|
1988
1756
|
` Tokens: ${tokenCount} (${deprecatedCount} deprecated preserved)`,
|
|
1989
1757
|
changeLine,
|
|
1990
1758
|
"",
|
|
1991
|
-
"Would write skills: .cursor/skills/atomix-ds/SKILL.md
|
|
1759
|
+
"Would write skills: .cursor/skills/atomix-ds/SKILL.md",
|
|
1992
1760
|
"",
|
|
1993
1761
|
"Call syncAll again with dryRun: false to apply."
|
|
1994
1762
|
].filter(Boolean).join("\n");
|
|
@@ -2194,7 +1962,7 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2194
1962
|
const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=json`;
|
|
2195
1963
|
console.error(`[getRules] Fetching: ${rulesUrl}${topic ? ` topic=${topic}` : ""}`);
|
|
2196
1964
|
const headers = { "Content-Type": "application/json" };
|
|
2197
|
-
if (
|
|
1965
|
+
if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
|
|
2198
1966
|
try {
|
|
2199
1967
|
const response = await fetch(rulesUrl, { headers });
|
|
2200
1968
|
console.error(`[getRules] Response status: ${response.status}`);
|
|
@@ -2470,7 +2238,7 @@ Version: ${designSystemData.meta.version}`,
|
|
|
2470
2238
|
const dsExportedAt = data.meta.exportedAt;
|
|
2471
2239
|
const skillsDir = path.resolve(projectRoot, ".cursor/skills/atomix-ds");
|
|
2472
2240
|
const manifestPath = path.resolve(projectRoot, "atomix-dependencies.json");
|
|
2473
|
-
const dependencySkills = getSyncDependencySkills(
|
|
2241
|
+
const dependencySkills = getSyncDependencySkills(dsVersion, dsExportedAt);
|
|
2474
2242
|
if (dryRun) {
|
|
2475
2243
|
const skillList = dependencySkills.map((s) => s.path).join(", ");
|
|
2476
2244
|
parts.push(`Would write skills: ${skillList}`);
|
|
@@ -2514,9 +2282,6 @@ ${reportText}` }]
|
|
|
2514
2282
|
const lib = icons?.library || "lucide";
|
|
2515
2283
|
const iconPkgs = ICON_PACKAGES[lib] || ICON_PACKAGES.lucide;
|
|
2516
2284
|
const skillsManifestEntry = { skill: ".cursor/skills/atomix-ds/SKILL.md", syncedAtVersion: String(data.meta.version ?? "1.0.0") };
|
|
2517
|
-
if (dependencySkills.some((s) => s.shortName === "figma-design-SKILL.md")) {
|
|
2518
|
-
skillsManifestEntry.figmaDesignSkill = ".cursor/skills/atomix-ds/figma-design-SKILL.md";
|
|
2519
|
-
}
|
|
2520
2285
|
const manifest = {
|
|
2521
2286
|
designSystem: { name: data.meta.name, version: data.meta.version },
|
|
2522
2287
|
tokenFile: skipTokens ? void 0 : output,
|
|
@@ -2593,7 +2358,7 @@ ${tokenResponseText}` : "") + validationBlock;
|
|
|
2593
2358
|
};
|
|
2594
2359
|
}
|
|
2595
2360
|
case "getDependencies": {
|
|
2596
|
-
const
|
|
2361
|
+
const platform = args?.platform;
|
|
2597
2362
|
const stack = args?.stack;
|
|
2598
2363
|
const tokens = data.tokens;
|
|
2599
2364
|
const typography = tokens?.typography;
|
|
@@ -2626,28 +2391,22 @@ ${tokenResponseText}` : "") + validationBlock;
|
|
|
2626
2391
|
performanceHint: "Link fonts via URL (e.g. Google Fonts <link> or CSS @import); no need to download font files or add them to the repo. Prefer font-display: swap when possible. You must also build a complete typeset CSS: call listTypesets to get every typeset from the design system, then 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 so the result is 1:1 with the DS. Use the CSS variable names returned by listTypesets. Do not create a file that only contains a font import."
|
|
2627
2392
|
},
|
|
2628
2393
|
skill: (() => {
|
|
2629
|
-
const list = getSyncDependencySkills(
|
|
2394
|
+
const list = getSyncDependencySkills(String(data.meta.version ?? "1.0.0"), data.meta.exportedAt);
|
|
2630
2395
|
const generic = list.find((s) => s.shortName === "SKILL.md");
|
|
2631
2396
|
return generic ? { path: generic.path, content: GENERIC_SKILL_MD } : { path: ".cursor/skills/atomix-ds/SKILL.md", content: GENERIC_SKILL_MD };
|
|
2632
2397
|
})(),
|
|
2633
|
-
...getEffectiveTier() === "pro" ? {
|
|
2634
|
-
skillFigmaDesign: {
|
|
2635
|
-
path: ".cursor/skills/atomix-ds/figma-design-SKILL.md",
|
|
2636
|
-
content: FIGMA_DESIGN_SKILL_MD
|
|
2637
|
-
}
|
|
2638
|
-
} : {},
|
|
2639
2398
|
tokenFiles: {
|
|
2640
2399
|
files: ["tokens.css", "tokens.json"],
|
|
2641
2400
|
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."
|
|
2642
2401
|
},
|
|
2643
|
-
showcase:
|
|
2402
|
+
showcase: platform === "web" || !platform ? {
|
|
2644
2403
|
path: "atomix-setup-showcase.html",
|
|
2645
2404
|
template: SHOWCASE_HTML_TEMPLATE,
|
|
2646
2405
|
substitutionInstructions: 'The synced token file (from syncAll) always uses the --atmx- prefix for every CSS variable. Keep all var(--atmx-*) references in the template; do not remove or change the prefix. Replace placeholders with values from the synced token file. {{TOKENS_CSS_PATH}} = path to the synced token file (e.g. ./tokens.css). {{TYPESETS_LINK}} = if a typeset CSS file was created, the full <link rel=\\"stylesheet\\" href=\\"typesets.css\\"> tag, otherwise empty string. {{DS_NAME}} = design system name. {{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 empty string if no typeset file. {{LARGEST_BODY_TYPESET_CLASS}} = largest body typeset class from listTypesets (body role, largest font size; e.g. typeset-body-lg), or empty string if no typeset file. {{BODY_TYPESET_CLASS}} = default body typeset class from listTypesets (e.g. typeset-body-md), or empty string. {{FONT_LINK_TAG}} = Google Fonts <link> for the font, or empty string. {{BRAND_PRIMARY_VAR}} = var(--atmx-color-brand-primary). Icon on circle uses luminance of brand primary (script sets white or black); no semantic foreground var. {{BUTTON_PADDING_VAR}} = var(--atmx-spacing-scale-md) or closest spacing token for button padding. {{BUTTON_HEIGHT_VAR}} = var(--atmx-sizing-height-md) or closest height token. {{BUTTON_RADIUS_VAR}} = var(--atmx-radius-scale-md) or var(--atmx-radius-scale-lg). {{CIRCLE_PADDING_VAR}} = var(--atmx-spacing-scale-md) or var(--atmx-spacing-scale-sm) for icon circle padding. {{ICON_SIZE_VAR}} = var(--atmx-sizing-icon-md) or var(--atmx-sizing-icon-lg). {{CHECK_ICON_SVG}} = inline SVG for Check icon from the design system icon library (getDependencies.iconLibrary.package: lucide-react, @heroicons/react, or phosphor-react). Use 24x24 viewBox; stroke=\\"currentColor\\" for Lucide/Heroicons, fill=\\"currentColor\\" for Phosphor so the script can set icon color by luminance. If unavailable, use: <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\\"><path d=\\"M20 6L9 17l-5-5\\"/></svg>. Do not invent CSS variable names; use only vars that exist in the export.'
|
|
2647
2406
|
} : void 0,
|
|
2648
2407
|
meta: {
|
|
2649
2408
|
dsName: data.meta.name,
|
|
2650
|
-
platform:
|
|
2409
|
+
platform: platform ?? void 0,
|
|
2651
2410
|
stack: stack ?? void 0,
|
|
2652
2411
|
designSystemVersion: data.meta.version ?? "1.0.0",
|
|
2653
2412
|
designSystemExportedAt: data.meta.exportedAt
|
|
@@ -2660,482 +2419,13 @@ ${tokenResponseText}` : "") + validationBlock;
|
|
|
2660
2419
|
}]
|
|
2661
2420
|
};
|
|
2662
2421
|
}
|
|
2663
|
-
case "syncToFigma": {
|
|
2664
|
-
const payloads = buildFigmaPayloadsFromDS(data);
|
|
2665
|
-
const out = {};
|
|
2666
|
-
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.`;
|
|
2667
|
-
const userSteps = [
|
|
2668
|
-
FIGMA_CONNECTION_INSTRUCTIONS.startBridge,
|
|
2669
|
-
FIGMA_CONNECTION_INSTRUCTIONS.installAndRun,
|
|
2670
|
-
FIGMA_CONNECTION_INSTRUCTIONS.connect,
|
|
2671
|
-
"Then run Sync to Figma again."
|
|
2672
|
-
].join(" ");
|
|
2673
|
-
try {
|
|
2674
|
-
const reachable = await isBridgeReachable();
|
|
2675
|
-
if (!reachable) {
|
|
2676
|
-
out.bridgeNotRunning = true;
|
|
2677
|
-
out.agentInstruction = agentStartBridge;
|
|
2678
|
-
out.userInstruction = `If the bridge still does not connect: ${userSteps}`;
|
|
2679
|
-
out.figmaPayload = {
|
|
2680
|
-
colorVariableCollections: payloads.colorVariableCollections,
|
|
2681
|
-
paintStyles: payloads.paintStyles,
|
|
2682
|
-
textStyles: payloads.textStyles,
|
|
2683
|
-
numberVariableCollections: payloads.numberVariableCollections,
|
|
2684
|
-
effectStyles: payloads.effectStyles
|
|
2685
|
-
};
|
|
2686
|
-
return {
|
|
2687
|
-
content: [{ type: "text", text: JSON.stringify(out, null, 2) }]
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
const colorResults = [];
|
|
2691
|
-
for (const coll of payloads.colorVariableCollections) {
|
|
2692
|
-
if (coll.variables.length > 0) {
|
|
2693
|
-
const result = await sendBridgeRequest("create_color_variables", {
|
|
2694
|
-
collectionName: coll.collectionName,
|
|
2695
|
-
modes: coll.modes,
|
|
2696
|
-
variables: coll.variables,
|
|
2697
|
-
removeVariablesNotInPayload: true,
|
|
2698
|
-
applyScopes: coll.applyScopes
|
|
2699
|
-
});
|
|
2700
|
-
colorResults.push({ collectionName: coll.collectionName, result });
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
if (colorResults.length > 0) {
|
|
2704
|
-
out.colorVariables = colorResults.length === 1 ? colorResults[0].result : colorResults;
|
|
2705
|
-
}
|
|
2706
|
-
if (payloads.paintStyles.length > 0) {
|
|
2707
|
-
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
2708
|
-
styles: payloads.paintStyles,
|
|
2709
|
-
removePaintStylesNotInPayload: true
|
|
2710
|
-
});
|
|
2711
|
-
}
|
|
2712
|
-
if (payloads.numberVariableCollections.length > 0) {
|
|
2713
|
-
const numberResults = [];
|
|
2714
|
-
try {
|
|
2715
|
-
for (const coll of payloads.numberVariableCollections) {
|
|
2716
|
-
const result = await sendBridgeRequest("create_number_variables", {
|
|
2717
|
-
collectionName: coll.collectionName,
|
|
2718
|
-
variables: coll.variables.map((v) => ({ name: v.name, value: v.value })),
|
|
2719
|
-
scopes: coll.scopes,
|
|
2720
|
-
removeVariablesNotInPayload: true
|
|
2721
|
-
});
|
|
2722
|
-
numberResults.push({ categoryKey: coll.categoryKey, result });
|
|
2723
|
-
}
|
|
2724
|
-
out.numberVariables = numberResults;
|
|
2725
|
-
} catch (e) {
|
|
2726
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2727
|
-
out.numberVariables = { error: msg };
|
|
2728
|
-
if (msg.includes("Method not allowed") && msg.includes("create_number_variables")) {
|
|
2729
|
-
out.numberVariablesHint = "Number variables require the latest Atomix Figma plugin and bridge. Rebuild the plugin and bridge, reload the plugin in Figma, restart the bridge, then sync again.";
|
|
2730
|
-
}
|
|
2731
|
-
}
|
|
2732
|
-
}
|
|
2733
|
-
if (payloads.textStyles.length > 0) {
|
|
2734
|
-
out.textStyles = await sendBridgeRequest("create_text_styles", {
|
|
2735
|
-
styles: payloads.textStyles,
|
|
2736
|
-
removeTextStylesNotInPayload: true
|
|
2737
|
-
});
|
|
2738
|
-
}
|
|
2739
|
-
if (payloads.effectStyles.length > 0) {
|
|
2740
|
-
out.effectStyles = await sendBridgeRequest("create_effect_styles", {
|
|
2741
|
-
styles: payloads.effectStyles,
|
|
2742
|
-
removeShadowStylesNotInPayload: true
|
|
2743
|
-
});
|
|
2744
|
-
}
|
|
2745
|
-
out.figmaPayload = {
|
|
2746
|
-
colorVariableCollections: payloads.colorVariableCollections,
|
|
2747
|
-
paintStyles: payloads.paintStyles,
|
|
2748
|
-
textStyles: payloads.textStyles,
|
|
2749
|
-
numberVariableCollections: payloads.numberVariableCollections,
|
|
2750
|
-
effectStyles: payloads.effectStyles
|
|
2751
|
-
};
|
|
2752
|
-
} catch (e) {
|
|
2753
|
-
out.error = e instanceof Error ? e.message : String(e);
|
|
2754
|
-
out.figmaPayload = {
|
|
2755
|
-
colorVariableCollections: payloads.colorVariableCollections,
|
|
2756
|
-
paintStyles: payloads.paintStyles,
|
|
2757
|
-
textStyles: payloads.textStyles,
|
|
2758
|
-
numberVariableCollections: payloads.numberVariableCollections,
|
|
2759
|
-
effectStyles: payloads.effectStyles
|
|
2760
|
-
};
|
|
2761
|
-
const errMsg = out.error.toLowerCase();
|
|
2762
|
-
const connectionFailure = errMsg.includes("econnrefused") || errMsg.includes("bridge timeout") || errMsg.includes("websocket") || errMsg.includes("network");
|
|
2763
|
-
if (connectionFailure) {
|
|
2764
|
-
out.bridgeNotRunning = true;
|
|
2765
|
-
out.agentInstruction = agentStartBridge;
|
|
2766
|
-
out.userInstruction = `If the bridge still does not connect: ${userSteps}`;
|
|
2767
|
-
} else if (errMsg.includes("plugin not connected") || errMsg.includes("figma plugin")) {
|
|
2768
|
-
out.userInstruction = `${FIGMA_CONNECTION_INSTRUCTIONS.installAndRun} ${FIGMA_CONNECTION_INSTRUCTIONS.connect}`;
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
const textStylesResult = out.textStyles;
|
|
2772
|
-
if (textStylesResult?.failed && textStylesResult.failures?.length) {
|
|
2773
|
-
const firstReason = textStylesResult.failures[0].reason;
|
|
2774
|
-
out.summary = `Text styles: ${textStylesResult.failed} could not be created. ${firstReason}`;
|
|
2775
|
-
}
|
|
2776
|
-
if (out.numberVariablesHint) {
|
|
2777
|
-
out.summary = [out.summary, out.numberVariablesHint].filter(Boolean).join(" ");
|
|
2778
|
-
}
|
|
2779
|
-
const summaryParts = [];
|
|
2780
|
-
const colorResult = out.colorVariables;
|
|
2781
|
-
if (colorResult) {
|
|
2782
|
-
const results = Array.isArray(colorResult) ? colorResult : [{ result: colorResult }];
|
|
2783
|
-
let totalSynced = 0;
|
|
2784
|
-
let totalRemoved = 0;
|
|
2785
|
-
for (const r of results) {
|
|
2786
|
-
const res = r.result;
|
|
2787
|
-
if (res?.variableNames?.length) totalSynced += res.variableNames.length;
|
|
2788
|
-
if ((res?.removed ?? 0) > 0) totalRemoved += res.removed ?? 0;
|
|
2789
|
-
}
|
|
2790
|
-
if (totalSynced > 0 || totalRemoved > 0) {
|
|
2791
|
-
const parts = [];
|
|
2792
|
-
if (totalSynced > 0) parts.push(`${totalSynced} synced`);
|
|
2793
|
-
if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
|
|
2794
|
-
summaryParts.push(`Colors: ${parts.join(", ")}.`);
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
const paintResult = out.paintStyles;
|
|
2798
|
-
if (paintResult) {
|
|
2799
|
-
const c = paintResult.created ?? 0;
|
|
2800
|
-
const u = paintResult.updated ?? 0;
|
|
2801
|
-
const r = paintResult.removed ?? 0;
|
|
2802
|
-
if (c + u + r > 0) {
|
|
2803
|
-
const parts = [];
|
|
2804
|
-
if (c > 0) parts.push(`${c} created`);
|
|
2805
|
-
if (u > 0) parts.push(`${u} updated`);
|
|
2806
|
-
if (r > 0) parts.push(`${r} removed`);
|
|
2807
|
-
summaryParts.push(`Paint styles: ${parts.join(", ")}.`);
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
const effectResult = out.effectStyles;
|
|
2811
|
-
if (effectResult) {
|
|
2812
|
-
const c = effectResult.created ?? 0;
|
|
2813
|
-
const u = effectResult.updated ?? 0;
|
|
2814
|
-
const r = effectResult.removed ?? 0;
|
|
2815
|
-
if (c + u + r > 0) {
|
|
2816
|
-
const parts = [];
|
|
2817
|
-
if (c > 0) parts.push(`${c} created`);
|
|
2818
|
-
if (u > 0) parts.push(`${u} updated`);
|
|
2819
|
-
if (r > 0) parts.push(`${r} removed`);
|
|
2820
|
-
summaryParts.push(`Effect styles (shadows): ${parts.join(", ")}.`);
|
|
2821
|
-
if (effectResult.removedNames?.length) {
|
|
2822
|
-
summaryParts.push(`Removed: ${effectResult.removedNames.join(", ")}.`);
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
const numResult = out.numberVariables;
|
|
2827
|
-
if (Array.isArray(numResult)) {
|
|
2828
|
-
const total = numResult.reduce((acc, r) => acc + (r.result?.variableNames?.length ?? 0), 0);
|
|
2829
|
-
const totalRemoved = numResult.reduce((acc, r) => acc + (r.result?.removed ?? 0), 0);
|
|
2830
|
-
if (total > 0 || totalRemoved > 0) {
|
|
2831
|
-
const parts = [];
|
|
2832
|
-
if (total > 0) parts.push(`${total} synced`);
|
|
2833
|
-
if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
|
|
2834
|
-
summaryParts.push(`Number variables: ${parts.join(", ")}.`);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
const textStylesWithRemoved = out.textStyles;
|
|
2838
|
-
if (textStylesWithRemoved && (textStylesWithRemoved.created ?? 0) + (textStylesWithRemoved.updated ?? 0) + (textStylesWithRemoved.removed ?? 0) > 0) {
|
|
2839
|
-
const c = textStylesWithRemoved.created ?? 0;
|
|
2840
|
-
const u = textStylesWithRemoved.updated ?? 0;
|
|
2841
|
-
const r = textStylesWithRemoved.removed ?? 0;
|
|
2842
|
-
const parts = [];
|
|
2843
|
-
if (c > 0) parts.push(`${c} created`);
|
|
2844
|
-
if (u > 0) parts.push(`${u} updated`);
|
|
2845
|
-
if (r > 0) parts.push(`${r} removed`);
|
|
2846
|
-
summaryParts.push(`Text styles: ${parts.join(", ")}.`);
|
|
2847
|
-
}
|
|
2848
|
-
if (summaryParts.length > 0 && !out.error) {
|
|
2849
|
-
out.summary = [out.summary, summaryParts.join(" ")].filter(Boolean).join(" ");
|
|
2850
|
-
}
|
|
2851
|
-
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.";
|
|
2852
|
-
const responseText = out.summary ? `${out.summary}
|
|
2853
|
-
|
|
2854
|
-
${JSON.stringify(out, null, 2)}` : JSON.stringify(out, null, 2);
|
|
2855
|
-
return {
|
|
2856
|
-
content: [{
|
|
2857
|
-
type: "text",
|
|
2858
|
-
text: responseText
|
|
2859
|
-
}],
|
|
2860
|
-
...out.error ? { isError: true } : {}
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
|
-
case "designInFigma": {
|
|
2864
|
-
if (cachedMcpTier !== "pro") {
|
|
2865
|
-
return {
|
|
2866
|
-
content: [{ type: "text", text: JSON.stringify({ error: "designInFigma requires a Pro subscription. Upgrade at https://atomix.studio" }, null, 2) }],
|
|
2867
|
-
isError: true
|
|
2868
|
-
};
|
|
2869
|
-
}
|
|
2870
|
-
const action = args?.action;
|
|
2871
|
-
if (action === "catalog") {
|
|
2872
|
-
try {
|
|
2873
|
-
const reachable = await isBridgeReachable();
|
|
2874
|
-
let fileCtx;
|
|
2875
|
-
if (reachable) {
|
|
2876
|
-
try {
|
|
2877
|
-
const raw = await sendBridgeRequest("get_figma_variables_and_styles", {});
|
|
2878
|
-
fileCtx = raw;
|
|
2879
|
-
} catch {
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
const localVars = [];
|
|
2883
|
-
const libVars = [];
|
|
2884
|
-
const textStyleNames = [];
|
|
2885
|
-
const effectStyleNames = [];
|
|
2886
|
-
if (fileCtx) {
|
|
2887
|
-
for (const coll of fileCtx.variableCollections ?? []) {
|
|
2888
|
-
for (const v of coll.variables) localVars.push(v.name);
|
|
2889
|
-
}
|
|
2890
|
-
for (const coll of fileCtx.variableCollectionsLibrary ?? []) {
|
|
2891
|
-
for (const v of coll.variables) libVars.push(v.name);
|
|
2892
|
-
}
|
|
2893
|
-
for (const s of fileCtx.textStyles ?? []) textStyleNames.push(s.name);
|
|
2894
|
-
for (const s of fileCtx.effectStyles ?? []) effectStyleNames.push(s.name);
|
|
2895
|
-
}
|
|
2896
|
-
const catalogPayload = formatCatalogForMCP(FIGMA_DESIGN_CATALOG, {
|
|
2897
|
-
localVariables: localVars,
|
|
2898
|
-
libraryVariables: libVars,
|
|
2899
|
-
textStyles: textStyleNames,
|
|
2900
|
-
effectStyles: effectStyleNames
|
|
2901
|
-
});
|
|
2902
|
-
let designAssets = void 0;
|
|
2903
|
-
if (reachable) {
|
|
2904
|
-
try {
|
|
2905
|
-
const raw = await sendBridgeRequest("get_component_catalog", {});
|
|
2906
|
-
if (raw && typeof raw === "object") {
|
|
2907
|
-
designAssets = raw;
|
|
2908
|
-
}
|
|
2909
|
-
} catch {
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2912
|
-
return {
|
|
2913
|
-
content: [{
|
|
2914
|
-
type: "text",
|
|
2915
|
-
text: JSON.stringify({
|
|
2916
|
-
...catalogPayload,
|
|
2917
|
-
pluginConnected: reachable,
|
|
2918
|
-
...designAssets ? { designAssets } : {},
|
|
2919
|
-
...reachable ? {
|
|
2920
|
-
queryMethods: getQueryMethodNames(),
|
|
2921
|
-
queryHint: "Use action:'query' with queryMethod (e.g. get_selection, get_node_info, get_design_screenshot) and optional queryParams to read selection, node details, or a frame screenshot for review."
|
|
2922
|
-
} : {},
|
|
2923
|
-
...reachable ? {} : { hint: "Figma plugin not connected. Open Figma, run Atomix plugin, and tap Connect." }
|
|
2924
|
-
}, null, 2)
|
|
2925
|
-
}]
|
|
2926
|
-
};
|
|
2927
|
-
} catch (e) {
|
|
2928
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2929
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: msg }, null, 2) }], isError: true };
|
|
2930
|
-
}
|
|
2931
|
-
}
|
|
2932
|
-
if (action === "query") {
|
|
2933
|
-
const queryMethodRaw = args?.queryMethod;
|
|
2934
|
-
const queryParams = args?.queryParams ?? {};
|
|
2935
|
-
if (!queryMethodRaw || typeof queryMethodRaw !== "string") {
|
|
2936
|
-
return {
|
|
2937
|
-
content: [{ type: "text", text: JSON.stringify({ error: "query requires queryMethod (e.g. get_selection, get_node_info, get_design_screenshot)" }, null, 2) }],
|
|
2938
|
-
isError: true
|
|
2939
|
-
};
|
|
2940
|
-
}
|
|
2941
|
-
const queryMethod = queryMethodRaw.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
2942
|
-
const allowedQueryMethods = new Set(getQueryMethodNames());
|
|
2943
|
-
if (!allowedQueryMethods.has(queryMethod)) {
|
|
2944
|
-
return {
|
|
2945
|
-
content: [{ type: "text", text: JSON.stringify({ error: `Unknown query method "${queryMethod}". Allowed: ${[...allowedQueryMethods].join(", ")}` }, null, 2) }],
|
|
2946
|
-
isError: true
|
|
2947
|
-
};
|
|
2948
|
-
}
|
|
2949
|
-
try {
|
|
2950
|
-
const reachable = await isBridgeReachable();
|
|
2951
|
-
if (!reachable) {
|
|
2952
|
-
return {
|
|
2953
|
-
content: [{
|
|
2954
|
-
type: "text",
|
|
2955
|
-
text: JSON.stringify({
|
|
2956
|
-
error: "Figma plugin not connected.",
|
|
2957
|
-
bridgeNotRunning: true,
|
|
2958
|
-
agentInstruction: "Run the Atomix plugin in Figma and tap Connect, then call designInFigma with action:'query' again."
|
|
2959
|
-
}, null, 2)
|
|
2960
|
-
}],
|
|
2961
|
-
isError: true
|
|
2962
|
-
};
|
|
2963
|
-
}
|
|
2964
|
-
const result = await sendBridgeRequest(queryMethod, queryParams);
|
|
2965
|
-
const content = [];
|
|
2966
|
-
const summary = { method: queryMethod, success: true };
|
|
2967
|
-
if (result && typeof result === "object" && "imageBase64" in result && typeof result.imageBase64 === "string") {
|
|
2968
|
-
summary.screenshot = "included below (base64 PNG)";
|
|
2969
|
-
summary.format = result.format ?? "PNG";
|
|
2970
|
-
summary.scale = result.scale;
|
|
2971
|
-
content.push({ type: "text", text: JSON.stringify(summary, null, 2) });
|
|
2972
|
-
content.push({
|
|
2973
|
-
type: "image",
|
|
2974
|
-
data: result.imageBase64,
|
|
2975
|
-
mimeType: "image/png"
|
|
2976
|
-
});
|
|
2977
|
-
} else {
|
|
2978
|
-
summary.result = result;
|
|
2979
|
-
content.push({ type: "text", text: JSON.stringify(summary, null, 2) });
|
|
2980
|
-
}
|
|
2981
|
-
return { content };
|
|
2982
|
-
} catch (e) {
|
|
2983
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
2984
|
-
return { content: [{ type: "text", text: JSON.stringify({ method: queryMethod, success: false, error: msg }, null, 2) }], isError: true };
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
if (action === "execute") {
|
|
2988
|
-
const rawSteps = args?.steps;
|
|
2989
|
-
if (!rawSteps || !Array.isArray(rawSteps) || rawSteps.length === 0) {
|
|
2990
|
-
return {
|
|
2991
|
-
content: [{ type: "text", text: JSON.stringify({ error: "steps array is required for action:'execute'" }, null, 2) }],
|
|
2992
|
-
isError: true
|
|
2993
|
-
};
|
|
2994
|
-
}
|
|
2995
|
-
try {
|
|
2996
|
-
const reachable = await isBridgeReachable();
|
|
2997
|
-
if (!reachable) {
|
|
2998
|
-
return {
|
|
2999
|
-
content: [{
|
|
3000
|
-
type: "text",
|
|
3001
|
-
text: JSON.stringify({
|
|
3002
|
-
error: "Figma plugin not connected.",
|
|
3003
|
-
bridgeNotRunning: true,
|
|
3004
|
-
agentInstruction: "Run the Atomix plugin in Figma and tap Connect, then call designInFigma again."
|
|
3005
|
-
}, null, 2)
|
|
3006
|
-
}],
|
|
3007
|
-
isError: true
|
|
3008
|
-
};
|
|
3009
|
-
}
|
|
3010
|
-
const designMethods = /* @__PURE__ */ new Set([
|
|
3011
|
-
...getDesignMethodNames(),
|
|
3012
|
-
"list_local_components",
|
|
3013
|
-
"get_component_catalog",
|
|
3014
|
-
"get_design_screenshot",
|
|
3015
|
-
"get_variable_collection_modes",
|
|
3016
|
-
"get_frame_variable_mode"
|
|
3017
|
-
]);
|
|
3018
|
-
const warnings = [];
|
|
3019
|
-
let fileCtx;
|
|
3020
|
-
try {
|
|
3021
|
-
const raw = await sendBridgeRequest("get_figma_variables_and_styles", {});
|
|
3022
|
-
fileCtx = raw;
|
|
3023
|
-
} catch {
|
|
3024
|
-
}
|
|
3025
|
-
const resolvers = fileCtx ? buildResolvers(fileCtx) : null;
|
|
3026
|
-
const isFigmaNodeId = (s) => /^\d+:\d+$/.test(s);
|
|
3027
|
-
let rootFrameId = null;
|
|
3028
|
-
let lastCreatedFrameNodeId = null;
|
|
3029
|
-
const namedNodeIds = /* @__PURE__ */ new Map();
|
|
3030
|
-
const results = [];
|
|
3031
|
-
for (let i = 0; i < rawSteps.length; i++) {
|
|
3032
|
-
const step = rawSteps[i];
|
|
3033
|
-
const method = (step.method ?? "").replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
|
|
3034
|
-
if (!designMethods.has(method)) {
|
|
3035
|
-
warnings.push({ step: i, method, issue: `Unknown method "${method}" \u2014 skipped` });
|
|
3036
|
-
continue;
|
|
3037
|
-
}
|
|
3038
|
-
let params = { ...step.params ?? {} };
|
|
3039
|
-
if (resolvers) {
|
|
3040
|
-
params = resolveStepParams(params, resolvers);
|
|
3041
|
-
}
|
|
3042
|
-
if (rootFrameId) {
|
|
3043
|
-
const parentId = params.parentId;
|
|
3044
|
-
const frameId = params.frameId;
|
|
3045
|
-
if (typeof parentId === "string" && !isFigmaNodeId(parentId)) {
|
|
3046
|
-
params.parentId = namedNodeIds.get(parentId) ?? rootFrameId;
|
|
3047
|
-
}
|
|
3048
|
-
if (typeof frameId === "string" && !isFigmaNodeId(frameId)) {
|
|
3049
|
-
params.frameId = namedNodeIds.get(frameId) ?? rootFrameId;
|
|
3050
|
-
}
|
|
3051
|
-
if (method === "finalize_design_frame" && !params.frameId) {
|
|
3052
|
-
params.frameId = rootFrameId;
|
|
3053
|
-
}
|
|
3054
|
-
const needsParent = method.startsWith("design_create_") || method === "design_create_component";
|
|
3055
|
-
if (needsParent && !params.parentId) {
|
|
3056
|
-
params.parentId = rootFrameId;
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
const nodeId = params.nodeId;
|
|
3060
|
-
if (typeof nodeId === "string" && nodeId && !isFigmaNodeId(nodeId)) {
|
|
3061
|
-
const resolved = namedNodeIds.get(nodeId) ?? lastCreatedFrameNodeId ?? rootFrameId;
|
|
3062
|
-
params.nodeId = resolved ?? nodeId;
|
|
3063
|
-
}
|
|
3064
|
-
const needsNode = [
|
|
3065
|
-
"design_set_auto_layout",
|
|
3066
|
-
"design_set_layout_sizing",
|
|
3067
|
-
"design_set_effects",
|
|
3068
|
-
"design_set_strokes",
|
|
3069
|
-
"design_resize_node",
|
|
3070
|
-
"design_set_resize_constraints",
|
|
3071
|
-
"design_set_layout_constraints",
|
|
3072
|
-
"design_set_node_position",
|
|
3073
|
-
"design_set_text_properties"
|
|
3074
|
-
].includes(method);
|
|
3075
|
-
if (needsNode && !params.nodeId) {
|
|
3076
|
-
params.nodeId = lastCreatedFrameNodeId ?? rootFrameId;
|
|
3077
|
-
}
|
|
3078
|
-
const childId = params.childId;
|
|
3079
|
-
if (typeof childId === "string" && childId && !isFigmaNodeId(childId)) {
|
|
3080
|
-
params.childId = namedNodeIds.get(childId) ?? childId;
|
|
3081
|
-
}
|
|
3082
|
-
try {
|
|
3083
|
-
const response = await sendBridgeRequest(method, params);
|
|
3084
|
-
const res = response;
|
|
3085
|
-
if (method === "create_design_placeholder" && typeof res?.frameId === "string") {
|
|
3086
|
-
rootFrameId = res.frameId;
|
|
3087
|
-
}
|
|
3088
|
-
const isCreateMethod = method.startsWith("design_create_") || method === "design_convert_to_component" || method === "design_combine_as_variants" || method === "design_create_frame_from_preset" || method === "design_group_nodes";
|
|
3089
|
-
if (isCreateMethod && typeof res?.nodeId === "string") {
|
|
3090
|
-
const createdNodeId = res.nodeId;
|
|
3091
|
-
if (method === "design_create_frame" || method === "design_create_frame_from_preset") {
|
|
3092
|
-
lastCreatedFrameNodeId = createdNodeId;
|
|
3093
|
-
if (!rootFrameId && res?.isRoot === true) {
|
|
3094
|
-
rootFrameId = createdNodeId;
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
const stepName = step.params?.name ?? "";
|
|
3098
|
-
if (stepName) namedNodeIds.set(stepName, createdNodeId);
|
|
3099
|
-
}
|
|
3100
|
-
results.push({ step: i, method, result: response });
|
|
3101
|
-
} catch (e) {
|
|
3102
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
3103
|
-
results.push({ step: i, method, error: errMsg });
|
|
3104
|
-
if (!rootFrameId && (method === "design_create_frame" || method === "create_design_placeholder")) break;
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
const errorCount = results.filter((r) => r.error).length;
|
|
3108
|
-
const success = errorCount === 0;
|
|
3109
|
-
return {
|
|
3110
|
-
content: [{
|
|
3111
|
-
type: "text",
|
|
3112
|
-
text: JSON.stringify({
|
|
3113
|
-
success,
|
|
3114
|
-
stepsExecuted: results.length,
|
|
3115
|
-
errors: errorCount,
|
|
3116
|
-
warnings,
|
|
3117
|
-
results
|
|
3118
|
-
}, null, 2)
|
|
3119
|
-
}],
|
|
3120
|
-
...errorCount > 0 ? { isError: true } : {}
|
|
3121
|
-
};
|
|
3122
|
-
} catch (e) {
|
|
3123
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3124
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: msg }, null, 2) }], isError: true };
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
return {
|
|
3128
|
-
content: [{ type: "text", text: JSON.stringify({ error: "action must be 'catalog', 'query', or 'execute'" }, null, 2) }],
|
|
3129
|
-
isError: true
|
|
3130
|
-
};
|
|
3131
|
-
}
|
|
3132
2422
|
default:
|
|
3133
2423
|
return {
|
|
3134
2424
|
content: [{
|
|
3135
2425
|
type: "text",
|
|
3136
2426
|
text: JSON.stringify({
|
|
3137
2427
|
error: `Unknown tool: ${name}`,
|
|
3138
|
-
availableTools: ["getToken", "listTokens", "listTypesets", "searchTokens", "validateUsage", "getRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "getMcpVersion"
|
|
2428
|
+
availableTools: ["getToken", "listTokens", "listTypesets", "searchTokens", "validateUsage", "getRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "getMcpVersion"]
|
|
3139
2429
|
}, null, 2)
|
|
3140
2430
|
}]
|
|
3141
2431
|
};
|
|
@@ -3177,32 +2467,15 @@ ${JSON.stringify(out, null, 2)}` : JSON.stringify(out, null, 2);
|
|
|
3177
2467
|
};
|
|
3178
2468
|
}
|
|
3179
2469
|
});
|
|
3180
|
-
function
|
|
3181
|
-
return cachedMcpTier;
|
|
3182
|
-
}
|
|
3183
|
-
function getSyncDependencySkills(data, dsVersion, dsExportedAt) {
|
|
3184
|
-
const tier = getEffectiveTier();
|
|
3185
|
-
const skills = [];
|
|
2470
|
+
function getSyncDependencySkills(dsVersion, dsExportedAt) {
|
|
3186
2471
|
const genericWithVersion = injectSkillVersion(GENERIC_SKILL_MD, dsVersion, dsExportedAt);
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
name: atomix-figma-design
|
|
3195
|
-
description: Figma design skill \u2014 create high-quality, token-bound designs in Figma from any reference (screenshots, images, code snippets, URLs). Covers mobile, web, token application, and component/variant creation. Use with syncToFigma and designInFigma MCP tools when the Atomix plugin is connected.
|
|
3196
|
-
---
|
|
3197
|
-
${FIGMA_DESIGN_SKILL_MD}`;
|
|
3198
|
-
const figmaSkillWithVersion = injectSkillVersion(figmaSkillWithFrontmatter, dsVersion, dsExportedAt);
|
|
3199
|
-
skills.push({
|
|
3200
|
-
path: ".cursor/skills/atomix-ds/figma-design-SKILL.md",
|
|
3201
|
-
content: figmaSkillWithVersion,
|
|
3202
|
-
shortName: "figma-design-SKILL.md"
|
|
3203
|
-
});
|
|
3204
|
-
}
|
|
3205
|
-
return skills;
|
|
2472
|
+
return [
|
|
2473
|
+
{
|
|
2474
|
+
path: ".cursor/skills/atomix-ds/SKILL.md",
|
|
2475
|
+
content: genericWithVersion,
|
|
2476
|
+
shortName: "SKILL.md"
|
|
2477
|
+
}
|
|
2478
|
+
];
|
|
3206
2479
|
}
|
|
3207
2480
|
function readSkillVersionFromFile(filePath) {
|
|
3208
2481
|
if (!fs.existsSync(filePath)) return null;
|
|
@@ -3306,7 +2579,7 @@ When a semantic token doesn't exist for your use case, use the closest primitive
|
|
|
3306
2579
|
|
|
3307
2580
|
### 4. Syncing tokens to a file
|
|
3308
2581
|
|
|
3309
|
-
\`syncAll({ output?, format?, skipTokens? })\` \u2014 writes tokens to a file (default \`./tokens.css\`),
|
|
2582
|
+
\`syncAll({ output?, format?, skipTokens? })\` \u2014 writes tokens to a file (default \`./tokens.css\`), skill (.cursor/skills/atomix-ds/SKILL.md), and manifest. Use \`skipTokens: true\` to only write skills and manifest.
|
|
3310
2583
|
|
|
3311
2584
|
## Workflow
|
|
3312
2585
|
|
|
@@ -3565,7 +2838,7 @@ Get your DS ID and token from the Export modal or Settings \u2192 Regenerate Ato
|
|
|
3565
2838
|
const topicRaw = rulesMatch[1]?.toLowerCase().trim();
|
|
3566
2839
|
const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=json`;
|
|
3567
2840
|
const headers = { "Content-Type": "application/json" };
|
|
3568
|
-
if (
|
|
2841
|
+
if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
|
|
3569
2842
|
const response = await fetch(rulesUrl, { headers });
|
|
3570
2843
|
if (!response.ok) throw new Error(`Failed to fetch rules: ${response.status}`);
|
|
3571
2844
|
const payload = await response.json();
|
|
@@ -3622,12 +2895,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
3622
2895
|
{ name: "--get-started", description: "Get started with design system in project. Three phases: scan, report and ask, then create only after you approve." },
|
|
3623
2896
|
{ name: "--rules", description: "Get design system governance rules (optionally by topic: colors, typo, motion, icons, layout, visual)." },
|
|
3624
2897
|
{ name: "--sync", description: "Sync tokens, AI rules, skills files, and dependencies manifest (icons, fonts). Use /--refactor to migrate deprecated tokens." },
|
|
3625
|
-
{ name: "--refactor", description: "Migrate deprecated tokens in codebase. Run after /--sync." }
|
|
3626
|
-
{ name: "--sync-to-figma", description: "Push this design system to Figma (variables, color + typography styles). Uses local bridge + plugin; no Figma token." }
|
|
2898
|
+
{ name: "--refactor", description: "Migrate deprecated tokens in codebase. Run after /--sync." }
|
|
3627
2899
|
];
|
|
3628
|
-
if (cachedMcpTier === "pro") {
|
|
3629
|
-
prompts.push({ name: "--design-in-figma", description: "Load Figma design skill and use designInFigma tool to design UI in the connected Figma file. Pro only." });
|
|
3630
|
-
}
|
|
3631
2900
|
return { prompts };
|
|
3632
2901
|
});
|
|
3633
2902
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
@@ -3644,7 +2913,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3644
2913
|
}]
|
|
3645
2914
|
};
|
|
3646
2915
|
}
|
|
3647
|
-
const canonicalName = name === "--hello" ? "hello" : name === "--get-started" ? "atomix-setup" : name === "--rules" ? "design-system-rules" : name === "--sync" ? "sync" : name === "--refactor" ? "refactor" : name
|
|
2916
|
+
const canonicalName = name === "--hello" ? "hello" : name === "--get-started" ? "atomix-setup" : name === "--rules" ? "design-system-rules" : name === "--sync" ? "sync" : name === "--refactor" ? "refactor" : name;
|
|
3648
2917
|
const shouldForceRefresh = [
|
|
3649
2918
|
"hello",
|
|
3650
2919
|
"atomix-setup",
|
|
@@ -3652,9 +2921,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3652
2921
|
"design-system-rules",
|
|
3653
2922
|
// --rules
|
|
3654
2923
|
"sync",
|
|
3655
|
-
"refactor"
|
|
3656
|
-
"sync-to-figma",
|
|
3657
|
-
"design-in-figma"
|
|
2924
|
+
"refactor"
|
|
3658
2925
|
].includes(canonicalName);
|
|
3659
2926
|
let data = null;
|
|
3660
2927
|
let stats = null;
|
|
@@ -3801,7 +3068,7 @@ ${welcome}`;
|
|
|
3801
3068
|
const topic = args?.topic?.toLowerCase().trim();
|
|
3802
3069
|
const rulesUrl = `${apiBase}/api/ds/${dsId}/rules?format=json`;
|
|
3803
3070
|
const headers = { "Content-Type": "application/json" };
|
|
3804
|
-
if (
|
|
3071
|
+
if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
|
|
3805
3072
|
const response = await fetch(rulesUrl, { headers });
|
|
3806
3073
|
if (!response.ok) throw new Error(`Failed to fetch rules: ${response.status}`);
|
|
3807
3074
|
const payload = await response.json();
|
|
@@ -4000,94 +3267,6 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
4000
3267
|
]
|
|
4001
3268
|
});
|
|
4002
3269
|
}
|
|
4003
|
-
case "sync-to-figma": {
|
|
4004
|
-
return withMcpNotice({
|
|
4005
|
-
description: "Push design system to Figma via MCP tool",
|
|
4006
|
-
messages: [
|
|
4007
|
-
{
|
|
4008
|
-
role: "user",
|
|
4009
|
-
content: {
|
|
4010
|
-
type: "text",
|
|
4011
|
-
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.`
|
|
4012
|
-
}
|
|
4013
|
-
}
|
|
4014
|
-
]
|
|
4015
|
-
});
|
|
4016
|
-
}
|
|
4017
|
-
case "design-in-figma": {
|
|
4018
|
-
if (getEffectiveTier() !== "pro") {
|
|
4019
|
-
return withMcpNotice({
|
|
4020
|
-
description: "Design in Figma (Pro required)",
|
|
4021
|
-
messages: [
|
|
4022
|
-
{
|
|
4023
|
-
role: "user",
|
|
4024
|
-
content: {
|
|
4025
|
-
type: "text",
|
|
4026
|
-
text: "designInFigma requires a Pro subscription. Upgrade at https://atomix.studio to use Design in Figma."
|
|
4027
|
-
}
|
|
4028
|
-
}
|
|
4029
|
-
]
|
|
4030
|
-
});
|
|
4031
|
-
}
|
|
4032
|
-
if (!data) {
|
|
4033
|
-
return withMcpNotice({
|
|
4034
|
-
description: "Design in Figma",
|
|
4035
|
-
messages: [
|
|
4036
|
-
{
|
|
4037
|
-
role: "user",
|
|
4038
|
-
content: {
|
|
4039
|
-
type: "text",
|
|
4040
|
-
text: "Failed to fetch design system. Check your --ds-id and --atomix-token configuration, then try /--design-in-figma again."
|
|
4041
|
-
}
|
|
4042
|
-
}
|
|
4043
|
-
]
|
|
4044
|
-
});
|
|
4045
|
-
}
|
|
4046
|
-
const figmaSkillPath = path.resolve(process.cwd(), ".cursor/skills/atomix-ds/figma-design-SKILL.md");
|
|
4047
|
-
const skillExists = fs.existsSync(figmaSkillPath);
|
|
4048
|
-
const currentVersion = String(data.meta.version ?? "1.0.0");
|
|
4049
|
-
const fileVersion = skillExists ? readSkillVersionFromFile(figmaSkillPath) : null;
|
|
4050
|
-
const isOutdated = !skillExists || fileVersion === null || fileVersion !== currentVersion;
|
|
4051
|
-
if (isOutdated) {
|
|
4052
|
-
return withMcpNotice({
|
|
4053
|
-
description: "Figma design skill missing or outdated",
|
|
4054
|
-
messages: [
|
|
4055
|
-
{
|
|
4056
|
-
role: "user",
|
|
4057
|
-
content: {
|
|
4058
|
-
type: "text",
|
|
4059
|
-
text: skillExists ? `The Figma design skill at \`.cursor/skills/atomix-ds/figma-design-SKILL.md\` is outdated (design system version ${currentVersion}). Run **/--sync** to update the skill and other design system files, then run **/--design-in-figma** again.` : `The Figma design skill is missing. Run **/--sync** to write \`.cursor/skills/atomix-ds/figma-design-SKILL.md\` (and other design system files), then run **/--design-in-figma** again.`
|
|
4060
|
-
}
|
|
4061
|
-
}
|
|
4062
|
-
]
|
|
4063
|
-
});
|
|
4064
|
-
}
|
|
4065
|
-
return withMcpNotice({
|
|
4066
|
-
description: "Load Figma design skill and use designInFigma tool",
|
|
4067
|
-
messages: [
|
|
4068
|
-
{
|
|
4069
|
-
role: "user",
|
|
4070
|
-
content: {
|
|
4071
|
-
type: "text",
|
|
4072
|
-
text: `You are using **/--design-in-figma**: load the Figma design skill below and use the **designInFigma** MCP tool to design UI in the connected Figma file.
|
|
4073
|
-
|
|
4074
|
-
**Instructions:**
|
|
4075
|
-
1. Treat the following as the Figma design skill (advisory and generative UI on the Figma canvas). Follow it when creating or editing designs.
|
|
4076
|
-
2. Call **designInFigma** with \`action: "catalog"\` first to discover available bridge methods, file variables/styles, and query/execute capabilities.
|
|
4077
|
-
3. Use \`action: "query"\` to read from Figma (e.g. get_selection, get_node_info, get_design_screenshot).
|
|
4078
|
-
4. Use \`action: "execute"\` with an array of steps to create or modify the design on the canvas.
|
|
4079
|
-
5. If the response includes \`bridgeNotRunning\` or \`pluginConnected: false\`, tell the user to run the Atomix plugin in Figma and tap Connect, then try again.
|
|
4080
|
-
|
|
4081
|
-
---
|
|
4082
|
-
|
|
4083
|
-
## Figma design skill
|
|
4084
|
-
|
|
4085
|
-
${FIGMA_DESIGN_SKILL_MD}`
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
]
|
|
4089
|
-
});
|
|
4090
|
-
}
|
|
4091
3270
|
case "refactor": {
|
|
4092
3271
|
const refactorOutput = args?.output || "./tokens.css";
|
|
4093
3272
|
const refactorFormat = args?.format || "css";
|
|
@@ -4234,8 +3413,8 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4234
3413
|
|
|
4235
3414
|
- 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.
|
|
4236
3415
|
- Call **getDependencies** with \`platform\` and optional \`stack\`. If it fails, tell the user the design system could not be reached and stop.
|
|
4237
|
-
- Scan the repo for: .cursor/skills/atomix-ds/SKILL.md,
|
|
4238
|
-
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links,
|
|
3416
|
+
- 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).
|
|
3417
|
+
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links, skill (SKILL.md), token files; for web, also include the **showcase page** (atomix-setup-showcase.html) if getDependencies returned a \`showcase\` object.
|
|
4239
3418
|
- Do not write, create, or add anything in Phase 1.
|
|
4240
3419
|
|
|
4241
3420
|
## Phase 2 \u2013 Report and ask
|
|
@@ -4248,8 +3427,8 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4248
3427
|
|
|
4249
3428
|
- Run only when the user has said yes (all or specific items).
|
|
4250
3429
|
- For each approved item:
|
|
4251
|
-
- **
|
|
4252
|
-
- **Token file and
|
|
3430
|
+
- **Skill:** Prefer calling **syncAll** (it writes the skill into the repo). If writing manually, write getDependencies \`skill.content\` to \`skill.path\` under the project root.
|
|
3431
|
+
- **Token file and skill in repo:** Call **syncAll** with \`output\` set to the path (e.g. "./src/tokens.css" or "./tokens.css") and **workspaceRoot** set to the absolute path of the current project/workspace root. This ensures .cursor/skills/atomix-ds/SKILL.md and atomix-dependencies.json are written inside the repo so they can be committed. You must call syncAll; do not only suggest the user run it later.
|
|
4253
3432
|
- **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.
|
|
4254
3433
|
- **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.
|
|
4255
3434
|
- **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, DS_NAME, HEADING_FONT_VAR, FONT_FAMILY_VAR, LARGEST_DISPLAY_TYPESET_CLASS, LARGEST_BODY_TYPESET_CLASS, BODY_TYPESET_CLASS, FONT_LINK_TAG, BRAND_PRIMARY_VAR, BUTTON_PADDING_VAR, BUTTON_HEIGHT_VAR, BUTTON_RADIUS_VAR, CIRCLE_PADDING_VAR, ICON_SIZE_VAR, CHECK_ICON_SVG (inline SVG from the design system icon library). The page uses semantic colors (mode-aware) and a Dark/Light toggle. 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).
|
|
@@ -4334,8 +3513,6 @@ ${tokenSummary}
|
|
|
4334
3513
|
| **/--get-started** | Get started with design system in project. Three phases; creates files only after you approve. |
|
|
4335
3514
|
| **/--rules** | Governance rules for your AI tool (e.g. Cursor, Copilot, Windsurf). |
|
|
4336
3515
|
| **/--sync** | Sync tokens, rules, skills, and dependencies manifest (icons, fonts). Safe: adds new, updates existing, marks deprecated. |
|
|
4337
|
-
| **/--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. |
|
|
4338
|
-
| **/--design-in-figma** | Design in Figma: loads Figma design skill and use designInFigma tool (catalog, query, execute). Pro only. |
|
|
4339
3516
|
| **/--refactor** | Migrate deprecated tokens in codebase. Run after /--sync. |
|
|
4340
3517
|
|
|
4341
3518
|
**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.
|
|
@@ -4360,7 +3537,6 @@ async function startServer() {
|
|
|
4360
3537
|
console.error("");
|
|
4361
3538
|
process.exit(1);
|
|
4362
3539
|
}
|
|
4363
|
-
startFigmaBridge();
|
|
4364
3540
|
const transport = new StdioServerTransport();
|
|
4365
3541
|
await server.connect(transport);
|
|
4366
3542
|
console.error(`Atomix MCP Server started for design system: ${dsId}`);
|
|
@@ -4370,7 +3546,6 @@ async function startServer() {
|
|
|
4370
3546
|
);
|
|
4371
3547
|
}
|
|
4372
3548
|
function onShutdown() {
|
|
4373
|
-
closeFigmaBridge();
|
|
4374
3549
|
}
|
|
4375
3550
|
process.on("SIGINT", onShutdown);
|
|
4376
3551
|
process.on("SIGTERM", onShutdown);
|