@atomixstudio/mcp 1.0.23 → 1.0.25
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 -3
- package/dist/index.js +251 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1480,15 +1480,6 @@ function figmaColorNameWithGroup(key) {
|
|
|
1480
1480
|
const groupDisplay = group.charAt(0).toUpperCase() + group.slice(1).toLowerCase();
|
|
1481
1481
|
return `${groupDisplay} / ${name}`;
|
|
1482
1482
|
}
|
|
1483
|
-
var FIGMA_COLOR_GROUP_ORDER = {
|
|
1484
|
-
background: 0,
|
|
1485
|
-
text: 1,
|
|
1486
|
-
border: 2,
|
|
1487
|
-
icon: 3,
|
|
1488
|
-
brand: 4,
|
|
1489
|
-
action: 5,
|
|
1490
|
-
feedback: 6
|
|
1491
|
-
};
|
|
1492
1483
|
var FIGMA_SHADOW_ORDER = {
|
|
1493
1484
|
none: 0,
|
|
1494
1485
|
xs: 1,
|
|
@@ -1582,8 +1573,11 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1582
1573
|
if (Object.keys(light).length > 0) modes.push("Light");
|
|
1583
1574
|
if (Object.keys(dark).length > 0 && !modes.includes("Dark")) modes.push("Dark");
|
|
1584
1575
|
if (modes.length === 0) modes.push("Light");
|
|
1585
|
-
const
|
|
1586
|
-
for (const
|
|
1576
|
+
const orderedKeys = [...Object.keys(light)];
|
|
1577
|
+
for (const k of Object.keys(dark)) {
|
|
1578
|
+
if (!orderedKeys.includes(k)) orderedKeys.push(k);
|
|
1579
|
+
}
|
|
1580
|
+
for (const key of orderedKeys) {
|
|
1587
1581
|
const lightHex = light[key];
|
|
1588
1582
|
const darkHex = dark[key];
|
|
1589
1583
|
if (typeof lightHex === "string" && hexRe.test(lightHex)) {
|
|
@@ -1612,26 +1606,8 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1612
1606
|
}
|
|
1613
1607
|
}
|
|
1614
1608
|
if (variables.length === 0 && modes.length === 0) modes.push("Light");
|
|
1615
|
-
const
|
|
1616
|
-
|
|
1617
|
-
const group = slash >= 0 ? figmaName.slice(0, slash).toLowerCase() : "";
|
|
1618
|
-
const name = slash >= 0 ? figmaName.slice(slash + 3) : figmaName;
|
|
1619
|
-
const order = FIGMA_COLOR_GROUP_ORDER[group] ?? 999;
|
|
1620
|
-
return [order, name];
|
|
1621
|
-
};
|
|
1622
|
-
variables.sort((a, b) => {
|
|
1623
|
-
const [oA, nA] = colorSortKey(a.name);
|
|
1624
|
-
const [oB, nB] = colorSortKey(b.name);
|
|
1625
|
-
if (oA !== oB) return oA - oB;
|
|
1626
|
-
return nA.localeCompare(nB);
|
|
1627
|
-
});
|
|
1628
|
-
paintStyles.sort((a, b) => {
|
|
1629
|
-
const [oA, nA] = colorSortKey(a.name);
|
|
1630
|
-
const [oB, nB] = colorSortKey(b.name);
|
|
1631
|
-
if (oA !== oB) return oA - oB;
|
|
1632
|
-
return nA.localeCompare(nB);
|
|
1633
|
-
});
|
|
1634
|
-
const collectionName = data.meta?.name ? `${data.meta.name} Colors` : "Atomix Colors";
|
|
1609
|
+
const dsName = data.meta?.name;
|
|
1610
|
+
const collectionName = dsName ? `${dsName} Foundations` : "Foundations";
|
|
1635
1611
|
const textStyles = [];
|
|
1636
1612
|
const sizeToPx = (val, basePx = 16) => {
|
|
1637
1613
|
if (typeof val === "number") return Math.round(val);
|
|
@@ -1735,7 +1711,6 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1735
1711
|
if (a.fontSize !== b.fontSize) return a.fontSize - b.fontSize;
|
|
1736
1712
|
return (a.name || "").localeCompare(b.name || "");
|
|
1737
1713
|
});
|
|
1738
|
-
const dsName = data.meta?.name ?? "Atomix";
|
|
1739
1714
|
const numberVariableCollections = [];
|
|
1740
1715
|
const spacing = tokens?.spacing;
|
|
1741
1716
|
if (spacing?.scale && typeof spacing.scale === "object") {
|
|
@@ -1745,7 +1720,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1745
1720
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1746
1721
|
}
|
|
1747
1722
|
variables2.sort((a, b) => a.value - b.value);
|
|
1748
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1723
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Spacing", variables: variables2, scopes: ["GAP"] });
|
|
1749
1724
|
}
|
|
1750
1725
|
const radius = tokens?.radius;
|
|
1751
1726
|
if (radius?.scale && typeof radius.scale === "object") {
|
|
@@ -1755,7 +1730,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1755
1730
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1756
1731
|
}
|
|
1757
1732
|
variables2.sort((a, b) => a.value - b.value);
|
|
1758
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1733
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Radius", variables: variables2, scopes: ["CORNER_RADIUS"] });
|
|
1759
1734
|
}
|
|
1760
1735
|
const borders = tokens?.borders;
|
|
1761
1736
|
if (borders?.width && typeof borders.width === "object") {
|
|
@@ -1765,7 +1740,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1765
1740
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1766
1741
|
}
|
|
1767
1742
|
variables2.sort((a, b) => a.value - b.value);
|
|
1768
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1743
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Borders", variables: variables2, scopes: ["STROKE_FLOAT"] });
|
|
1769
1744
|
}
|
|
1770
1745
|
const sizing = tokens?.sizing;
|
|
1771
1746
|
if (sizing?.height && typeof sizing.height === "object") {
|
|
@@ -1775,7 +1750,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1775
1750
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1776
1751
|
}
|
|
1777
1752
|
variables2.sort((a, b) => a.value - b.value);
|
|
1778
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1753
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Height", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1779
1754
|
}
|
|
1780
1755
|
if (sizing?.icon && typeof sizing.icon === "object") {
|
|
1781
1756
|
const variables2 = [];
|
|
@@ -1784,7 +1759,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1784
1759
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1785
1760
|
}
|
|
1786
1761
|
variables2.sort((a, b) => a.value - b.value);
|
|
1787
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1762
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Icon", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1788
1763
|
}
|
|
1789
1764
|
const layout = tokens?.layout;
|
|
1790
1765
|
if (layout?.breakpoint && typeof layout.breakpoint === "object") {
|
|
@@ -1794,7 +1769,7 @@ function buildFigmaPayloadsFromDS(data) {
|
|
|
1794
1769
|
if (n >= 0) variables2.push({ name: key, value: n });
|
|
1795
1770
|
}
|
|
1796
1771
|
variables2.sort((a, b) => a.value - b.value);
|
|
1797
|
-
if (variables2.length > 0) numberVariableCollections.push({ collectionName
|
|
1772
|
+
if (variables2.length > 0) numberVariableCollections.push({ collectionName, categoryKey: "Breakpoint", variables: variables2, scopes: ["WIDTH_HEIGHT"] });
|
|
1798
1773
|
}
|
|
1799
1774
|
const effectStyles = [];
|
|
1800
1775
|
const shadows = tokens?.shadows;
|
|
@@ -1909,6 +1884,43 @@ ${changes.summary}`);
|
|
|
1909
1884
|
}
|
|
1910
1885
|
}
|
|
1911
1886
|
}
|
|
1887
|
+
function validateTokenFileAfterWrite(outputPath, format, expectedMinVariables) {
|
|
1888
|
+
if (!fs2.existsSync(outputPath)) {
|
|
1889
|
+
return { path: outputPath, status: "FAIL", detail: "File not found after write." };
|
|
1890
|
+
}
|
|
1891
|
+
const content = fs2.readFileSync(outputPath, "utf-8");
|
|
1892
|
+
if (!content || content.trim().length === 0) {
|
|
1893
|
+
return { path: outputPath, status: "FAIL", detail: "File is empty after write." };
|
|
1894
|
+
}
|
|
1895
|
+
if (["css", "scss", "less"].includes(format)) {
|
|
1896
|
+
const varPattern = /(--[a-zA-Z0-9-]+):\s*[^;]+;/g;
|
|
1897
|
+
let count = 0;
|
|
1898
|
+
let m;
|
|
1899
|
+
while ((m = varPattern.exec(content)) !== null) count++;
|
|
1900
|
+
if (count < expectedMinVariables) {
|
|
1901
|
+
return {
|
|
1902
|
+
path: outputPath,
|
|
1903
|
+
status: "FAIL",
|
|
1904
|
+
detail: `Expected at least ${expectedMinVariables} variables; found ${count}.`
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
return { path: outputPath, status: "OK", detail: `${count} variables written.` };
|
|
1908
|
+
}
|
|
1909
|
+
return { path: outputPath, status: "OK", detail: "File written (non-CSS format)." };
|
|
1910
|
+
}
|
|
1911
|
+
function formatValidationBlock(entries) {
|
|
1912
|
+
if (entries.length === 0) return "";
|
|
1913
|
+
const displayPath = (p) => p.startsWith("(") ? p : path2.relative(process.cwd(), p);
|
|
1914
|
+
const lines = [
|
|
1915
|
+
"",
|
|
1916
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
1917
|
+
"VALIDATION (confirm sync succeeded)",
|
|
1918
|
+
"\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501",
|
|
1919
|
+
...entries.map((e) => ` ${displayPath(e.path)}: ${e.status} \u2014 ${e.detail}`),
|
|
1920
|
+
""
|
|
1921
|
+
];
|
|
1922
|
+
return lines.join("\n");
|
|
1923
|
+
}
|
|
1912
1924
|
async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
1913
1925
|
if (!dsId) throw new Error("Missing --ds-id. Usage: npx @atomixstudio/mcp --ds-id <id> --atomix-token <token>");
|
|
1914
1926
|
if (!accessToken) throw new Error("Missing --atomix-token. Get your token from the Export modal or Settings.");
|
|
@@ -1928,10 +1940,50 @@ async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
|
1928
1940
|
return result.data;
|
|
1929
1941
|
}
|
|
1930
1942
|
var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
|
|
1943
|
+
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";
|
|
1948
|
+
if (prefix.startsWith("body")) return "body";
|
|
1949
|
+
return "body";
|
|
1950
|
+
}
|
|
1951
|
+
function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
1952
|
+
const fontSize = typography.fontSize;
|
|
1953
|
+
if (!fontSize || typeof fontSize !== "object") return [];
|
|
1954
|
+
const fontFamily = typography.fontFamily ?? {};
|
|
1955
|
+
const fontWeight = typography.fontWeight ?? {};
|
|
1956
|
+
const lineHeight = typography.lineHeight ?? {};
|
|
1957
|
+
const letterSpacing = typography.letterSpacing ?? {};
|
|
1958
|
+
const textTransform = typography.textTransform ?? {};
|
|
1959
|
+
const textDecoration = typography.textDecoration ?? {};
|
|
1960
|
+
const p = cssPrefix ? `${cssPrefix}-` : "";
|
|
1961
|
+
const typesets = [];
|
|
1962
|
+
for (const key of Object.keys(fontSize)) {
|
|
1963
|
+
const role = typesetKeyToFontFamilyRole(key);
|
|
1964
|
+
const familyName = fontFamily[role] ?? fontFamily.body;
|
|
1965
|
+
const fontFamilyVar = familyName ? `var(--${p}typography-font-family-${role})` : "";
|
|
1966
|
+
const keyKebab = key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1967
|
+
typesets.push({
|
|
1968
|
+
key,
|
|
1969
|
+
cssClass: `.typeset-${keyKebab}`,
|
|
1970
|
+
fontFamilyVar: fontFamilyVar || "inherit",
|
|
1971
|
+
fontSizeVar: `var(--${p}typography-${key}-size)`,
|
|
1972
|
+
fontWeightVar: `var(--${p}typography-${key}-weight)`,
|
|
1973
|
+
lineHeightVar: `var(--${p}typography-${key}-line-height)`,
|
|
1974
|
+
letterSpacingVar: letterSpacing[key] != null ? `var(--${p}typography-${key}-letter-spacing)` : void 0,
|
|
1975
|
+
textTransformVar: textTransform[key] != null ? `var(--${p}typography-${key}-text-transform)` : void 0,
|
|
1976
|
+
textDecorationVar: textDecoration[key] != null ? `var(--${p}typography-${key}-text-decoration)` : void 0,
|
|
1977
|
+
hasTextTransform: key in textTransform,
|
|
1978
|
+
hasTextDecoration: key in textDecoration
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
return typesets;
|
|
1982
|
+
}
|
|
1931
1983
|
var server = new Server(
|
|
1932
1984
|
{
|
|
1933
1985
|
name: "atomix-mcp-user",
|
|
1934
|
-
version: "1.0.
|
|
1986
|
+
version: "1.0.25"
|
|
1935
1987
|
},
|
|
1936
1988
|
{
|
|
1937
1989
|
capabilities: {
|
|
@@ -2017,6 +2069,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2017
2069
|
required: ["query"]
|
|
2018
2070
|
}
|
|
2019
2071
|
},
|
|
2072
|
+
{
|
|
2073
|
+
name: "listTypesets",
|
|
2074
|
+
description: "List every typography typeset in the design system with full CSS variable names. Use this when building typeset.css so you emit one class per typeset and include all properties (font-family, font-size, font-weight, line-height, letter-spacing, text-transform, text-decoration) for 1:1 match. Do not skip any typesets.",
|
|
2075
|
+
inputSchema: {
|
|
2076
|
+
type: "object",
|
|
2077
|
+
properties: {
|
|
2078
|
+
cssPrefix: {
|
|
2079
|
+
type: "string",
|
|
2080
|
+
description: "CSS variable prefix (default: atmx). Use the same prefix as the synced tokens file."
|
|
2081
|
+
}
|
|
2082
|
+
},
|
|
2083
|
+
required: []
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2020
2086
|
{
|
|
2021
2087
|
name: "validateUsage",
|
|
2022
2088
|
description: "Check if a CSS value follows the design system. Detects hardcoded values that should use tokens.",
|
|
@@ -2083,7 +2149,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2083
2149
|
},
|
|
2084
2150
|
{
|
|
2085
2151
|
name: "syncAll",
|
|
2086
|
-
description: "Sync tokens, AI rules, skills
|
|
2152
|
+
description: "Sync tokens, AI rules, skills (SKILL.md, FIGMA-SKILL.md), and atomix-dependencies.json. Use dryRun: true first to report what would change without writing; then dryRun: false to apply. Response includes a VALIDATION section\u2014agent must check it to confirm success. Optional: output (default ./tokens.css), format (default css), skipTokens (if true, only skills and manifest), dryRun (if true, report only; no files written).",
|
|
2087
2153
|
inputSchema: {
|
|
2088
2154
|
type: "object",
|
|
2089
2155
|
properties: {
|
|
@@ -2099,6 +2165,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2099
2165
|
skipTokens: {
|
|
2100
2166
|
type: "boolean",
|
|
2101
2167
|
description: "If true, skip token file and rules sync; only write skills and dependencies manifest. Default: false."
|
|
2168
|
+
},
|
|
2169
|
+
dryRun: {
|
|
2170
|
+
type: "boolean",
|
|
2171
|
+
description: "If true, compute and return what would be written (paths, token counts, diff summary) but do NOT write any files. Call with dryRun: false to apply. Default: false."
|
|
2102
2172
|
}
|
|
2103
2173
|
},
|
|
2104
2174
|
required: []
|
|
@@ -2311,7 +2381,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2311
2381
|
isError: true
|
|
2312
2382
|
};
|
|
2313
2383
|
}
|
|
2314
|
-
async function performTokenSyncAndRules(designSystemData, tokenOutput, tokenFormat) {
|
|
2384
|
+
async function performTokenSyncAndRules(designSystemData, tokenOutput, tokenFormat, dryRun) {
|
|
2315
2385
|
const output = tokenOutput;
|
|
2316
2386
|
const format = tokenFormat;
|
|
2317
2387
|
const outputPath = path2.resolve(process.cwd(), output);
|
|
@@ -2374,7 +2444,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2374
2444
|
const lightChanges = diff.added.length + diff.modified.length;
|
|
2375
2445
|
const darkChanges = diff.addedDark.length + diff.modifiedDark.length;
|
|
2376
2446
|
const totalChanges = lightChanges + darkChanges + deprecatedCount;
|
|
2377
|
-
if (totalChanges === 0) {
|
|
2447
|
+
if (totalChanges === 0 && !dryRun) {
|
|
2378
2448
|
const lastUpdated = designSystemData.meta.exportedAt ? new Date(designSystemData.meta.exportedAt).toLocaleString() : "N/A";
|
|
2379
2449
|
return {
|
|
2380
2450
|
responseText: `\u2713 Already up to date!
|
|
@@ -2383,7 +2453,19 @@ File: ${output}
|
|
|
2383
2453
|
Tokens: ${tokenCount}
|
|
2384
2454
|
Version: ${designSystemData.meta.version}
|
|
2385
2455
|
Last updated: ${lastUpdated}`,
|
|
2386
|
-
rulesResults: []
|
|
2456
|
+
rulesResults: [],
|
|
2457
|
+
validation: [{ path: outputPath, status: "OK", detail: "No changes needed; file already up to date." }]
|
|
2458
|
+
};
|
|
2459
|
+
}
|
|
2460
|
+
if (totalChanges === 0 && dryRun) {
|
|
2461
|
+
return {
|
|
2462
|
+
responseText: `[DRY RUN] Already up to date. No changes would be written.
|
|
2463
|
+
|
|
2464
|
+
File: ${output}
|
|
2465
|
+
Tokens: ${tokenCount}
|
|
2466
|
+
Version: ${designSystemData.meta.version}`,
|
|
2467
|
+
rulesResults: [],
|
|
2468
|
+
validation: [{ path: "(dry run)", status: "OK", detail: "No files written." }]
|
|
2387
2469
|
};
|
|
2388
2470
|
}
|
|
2389
2471
|
changes = [...diff.modified, ...diff.modifiedDark];
|
|
@@ -2397,9 +2479,34 @@ Last updated: ${lastUpdated}`,
|
|
|
2397
2479
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2398
2480
|
};
|
|
2399
2481
|
}
|
|
2482
|
+
if (dryRun) {
|
|
2483
|
+
const added = diff ? diff.added.length + diff.addedDark.length : 0;
|
|
2484
|
+
const modified = diff ? diff.modified.length + diff.modifiedDark.length : 0;
|
|
2485
|
+
const changeLine = fileExists && diff ? ` Changes: ${added} added, ${modified} modified` : !fileExists ? ` New file would be created with ${tokenCount} tokens.` : "";
|
|
2486
|
+
const report = [
|
|
2487
|
+
"[DRY RUN] No files were written. The following would be written if you run syncAll with dryRun: false.",
|
|
2488
|
+
"",
|
|
2489
|
+
"Token file:",
|
|
2490
|
+
` Path: ${output}`,
|
|
2491
|
+
` Format: ${format}`,
|
|
2492
|
+
` Tokens: ${tokenCount} (${deprecatedCount} deprecated preserved)`,
|
|
2493
|
+
changeLine,
|
|
2494
|
+
"",
|
|
2495
|
+
"Rules: .cursorrules (or existing rules files in project)",
|
|
2496
|
+
"",
|
|
2497
|
+
"Call syncAll again with dryRun: false to apply."
|
|
2498
|
+
].filter(Boolean).join("\n");
|
|
2499
|
+
return {
|
|
2500
|
+
responseText: report,
|
|
2501
|
+
rulesResults: [],
|
|
2502
|
+
validation: [{ path: "(dry run)", status: "OK", detail: "No files written." }]
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2400
2505
|
const outputDir = path2.dirname(outputPath);
|
|
2401
2506
|
if (!fs2.existsSync(outputDir)) fs2.mkdirSync(outputDir, { recursive: true });
|
|
2402
2507
|
fs2.writeFileSync(outputPath, newContent);
|
|
2508
|
+
const validation = [];
|
|
2509
|
+
validation.push(validateTokenFileAfterWrite(outputPath, format, tokenCount));
|
|
2403
2510
|
let rulesResults = [];
|
|
2404
2511
|
try {
|
|
2405
2512
|
rulesResults = await syncRulesFiles({
|
|
@@ -2408,6 +2515,14 @@ Last updated: ${lastUpdated}`,
|
|
|
2408
2515
|
apiBase: apiBase ?? void 0,
|
|
2409
2516
|
rulesDir: process.cwd()
|
|
2410
2517
|
});
|
|
2518
|
+
for (const r of rulesResults) {
|
|
2519
|
+
const fullPath = path2.resolve(process.cwd(), r.path);
|
|
2520
|
+
validation.push({
|
|
2521
|
+
path: fullPath,
|
|
2522
|
+
status: r.success && fs2.existsSync(fullPath) ? "OK" : "FAIL",
|
|
2523
|
+
detail: r.success ? "Written." : r.error || "Write failed."
|
|
2524
|
+
});
|
|
2525
|
+
}
|
|
2411
2526
|
} catch (error) {
|
|
2412
2527
|
console.error(`[syncAll] Failed to sync rules: ${error}`);
|
|
2413
2528
|
}
|
|
@@ -2428,7 +2543,7 @@ Last updated: ${lastUpdated}`,
|
|
|
2428
2543
|
hasRefactorRecommendation: !!lastSyncAffectedTokens?.removed.length,
|
|
2429
2544
|
deprecatedTokenCount: lastSyncAffectedTokens?.removed.length || 0
|
|
2430
2545
|
});
|
|
2431
|
-
return { responseText: response, rulesResults };
|
|
2546
|
+
return { responseText: response, rulesResults, validation };
|
|
2432
2547
|
}
|
|
2433
2548
|
switch (name) {
|
|
2434
2549
|
case "getToken": {
|
|
@@ -2525,6 +2640,29 @@ Last updated: ${lastUpdated}`,
|
|
|
2525
2640
|
}]
|
|
2526
2641
|
};
|
|
2527
2642
|
}
|
|
2643
|
+
case "listTypesets": {
|
|
2644
|
+
const typography = data.tokens.typography;
|
|
2645
|
+
const cssPrefix = args?.cssPrefix || "atmx";
|
|
2646
|
+
const typesets = typography ? buildTypesetsList(typography, cssPrefix) : [];
|
|
2647
|
+
const underlineThickness = typography?.underlineThickness;
|
|
2648
|
+
const underlineOffset = typography?.underlineOffset;
|
|
2649
|
+
return {
|
|
2650
|
+
content: [{
|
|
2651
|
+
type: "text",
|
|
2652
|
+
text: JSON.stringify({
|
|
2653
|
+
count: typesets.length,
|
|
2654
|
+
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.",
|
|
2656
|
+
...underlineThickness != null && underlineOffset != null && {
|
|
2657
|
+
underlineVars: {
|
|
2658
|
+
thickness: `var(--${cssPrefix}-typography-underline-thickness)`,
|
|
2659
|
+
offset: `var(--${cssPrefix}-typography-underline-offset)`
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
}, null, 2)
|
|
2663
|
+
}]
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2528
2666
|
case "validateUsage": {
|
|
2529
2667
|
const value = args?.value;
|
|
2530
2668
|
const context = args?.context || "any";
|
|
@@ -2796,27 +2934,55 @@ Last updated: ${lastUpdated}`,
|
|
|
2796
2934
|
}
|
|
2797
2935
|
case "syncAll": {
|
|
2798
2936
|
const skipTokens = args?.skipTokens === true;
|
|
2937
|
+
const dryRun = args?.dryRun === true;
|
|
2799
2938
|
const output = args?.output || "./tokens.css";
|
|
2800
2939
|
const format = args?.format || "css";
|
|
2801
|
-
const parts = ["\u2713 syncAll complete."];
|
|
2940
|
+
const parts = [dryRun ? "[DRY RUN] syncAll report (no files written)." : "\u2713 syncAll complete."];
|
|
2802
2941
|
let tokenResponseText = "";
|
|
2942
|
+
const allValidation = [];
|
|
2803
2943
|
if (!skipTokens) {
|
|
2804
|
-
const
|
|
2805
|
-
tokenResponseText = responseText;
|
|
2806
|
-
|
|
2807
|
-
if (rulesResults.length > 0) {
|
|
2808
|
-
parts.push(`Rules: ${rulesResults.map((r) => r.path).join(", ")}`);
|
|
2944
|
+
const result = await performTokenSyncAndRules(data, output, format, dryRun);
|
|
2945
|
+
tokenResponseText = result.responseText;
|
|
2946
|
+
allValidation.push(...result.validation);
|
|
2947
|
+
if (!dryRun && result.rulesResults.length > 0) {
|
|
2948
|
+
parts.push(`Rules: ${result.rulesResults.map((r) => r.path).join(", ")}`);
|
|
2949
|
+
}
|
|
2950
|
+
if (dryRun) {
|
|
2951
|
+
parts.push(`Would write tokens: ${output} (${format})`);
|
|
2952
|
+
} else {
|
|
2953
|
+
parts.push(`Tokens: ${output} (${format})`);
|
|
2809
2954
|
}
|
|
2810
2955
|
}
|
|
2811
|
-
const skillsDir = path2.resolve(process.cwd(), ".cursor/skills/atomix-ds");
|
|
2812
|
-
if (!fs2.existsSync(skillsDir)) fs2.mkdirSync(skillsDir, { recursive: true });
|
|
2813
2956
|
const dsVersion = String(data.meta.version ?? "1.0.0");
|
|
2814
2957
|
const dsExportedAt = data.meta.exportedAt;
|
|
2958
|
+
const skillsDir = path2.resolve(process.cwd(), ".cursor/skills/atomix-ds");
|
|
2959
|
+
const skillPath1 = path2.join(skillsDir, "SKILL.md");
|
|
2960
|
+
const skillPath2 = path2.join(skillsDir, "FIGMA-SKILL.md");
|
|
2961
|
+
const manifestPath = path2.resolve(process.cwd(), "atomix-dependencies.json");
|
|
2962
|
+
if (dryRun) {
|
|
2963
|
+
parts.push(
|
|
2964
|
+
cachedMcpTier === "pro" ? "Would write skills: .cursor/skills/atomix-ds/SKILL.md, .cursor/skills/atomix-ds/FIGMA-SKILL.md" : "Would write skills: .cursor/skills/atomix-ds/SKILL.md"
|
|
2965
|
+
);
|
|
2966
|
+
parts.push("Would write manifest: atomix-dependencies.json");
|
|
2967
|
+
const reportText = [parts.join("\n"), tokenResponseText].filter(Boolean).join("\n\n---\n\n");
|
|
2968
|
+
return {
|
|
2969
|
+
content: [{ type: "text", text: `syncAllResult: DRY_RUN (no files written)
|
|
2970
|
+
|
|
2971
|
+
${reportText}` }]
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
if (!fs2.existsSync(skillsDir)) fs2.mkdirSync(skillsDir, { recursive: true });
|
|
2815
2975
|
const genericWithVersion = injectSkillVersion(GENERIC_SKILL_MD, dsVersion, dsExportedAt);
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2976
|
+
fs2.writeFileSync(skillPath1, genericWithVersion);
|
|
2977
|
+
allValidation.push({ path: skillPath1, status: fs2.existsSync(skillPath1) ? "OK" : "FAIL", detail: "Written." });
|
|
2978
|
+
if (cachedMcpTier === "pro") {
|
|
2979
|
+
const figmaWithVersion = injectSkillVersion(FIGMA_DESIGN_SKILL_MD, dsVersion, dsExportedAt);
|
|
2980
|
+
fs2.writeFileSync(skillPath2, figmaWithVersion);
|
|
2981
|
+
allValidation.push({ path: skillPath2, status: fs2.existsSync(skillPath2) ? "OK" : "FAIL", detail: "Written." });
|
|
2982
|
+
}
|
|
2983
|
+
parts.push(
|
|
2984
|
+
cachedMcpTier === "pro" ? "Skills: .cursor/skills/atomix-ds/SKILL.md, .cursor/skills/atomix-ds/FIGMA-SKILL.md (synced at DS v" + dsVersion + ")" : "Skills: .cursor/skills/atomix-ds/SKILL.md (synced at DS v" + dsVersion + ")"
|
|
2985
|
+
);
|
|
2820
2986
|
const tokens = data.tokens;
|
|
2821
2987
|
const typography = tokens?.typography;
|
|
2822
2988
|
const fontFamily = typography?.fontFamily;
|
|
@@ -2847,19 +3013,22 @@ Last updated: ${lastUpdated}`,
|
|
|
2847
3013
|
fonts: { families: fontNames },
|
|
2848
3014
|
skills: {
|
|
2849
3015
|
skill: ".cursor/skills/atomix-ds/SKILL.md",
|
|
2850
|
-
skillFigmaDesign: ".cursor/skills/atomix-ds/
|
|
3016
|
+
...cachedMcpTier === "pro" ? { skillFigmaDesign: ".cursor/skills/atomix-ds/FIGMA-SKILL.md" } : {},
|
|
2851
3017
|
syncedAtVersion: data.meta.version ?? "1.0.0"
|
|
2852
3018
|
}
|
|
2853
3019
|
};
|
|
2854
|
-
const manifestPath = path2.resolve(process.cwd(), "atomix-dependencies.json");
|
|
2855
3020
|
fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
3021
|
+
allValidation.push({ path: manifestPath, status: fs2.existsSync(manifestPath) ? "OK" : "FAIL", detail: "Written." });
|
|
2856
3022
|
parts.push("Manifest: atomix-dependencies.json (icons, fonts, skill paths)");
|
|
2857
3023
|
const summary = parts.join("\n");
|
|
2858
|
-
const
|
|
3024
|
+
const validationBlock = formatValidationBlock(allValidation);
|
|
3025
|
+
const hasFailure = allValidation.some((e) => e.status === "FAIL");
|
|
3026
|
+
const resultLine = hasFailure ? "syncAllResult: FAIL \u2014 Check VALIDATION section below. Do not report success to the user.\n\n" : "syncAllResult: OK\n\n";
|
|
3027
|
+
const fullText = resultLine + (tokenResponseText ? `${summary}
|
|
2859
3028
|
|
|
2860
3029
|
---
|
|
2861
3030
|
|
|
2862
|
-
${tokenResponseText}` : summary;
|
|
3031
|
+
${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
2863
3032
|
return {
|
|
2864
3033
|
content: [{ type: "text", text: fullText }]
|
|
2865
3034
|
};
|
|
@@ -2895,16 +3064,18 @@ ${tokenResponseText}` : summary;
|
|
|
2895
3064
|
},
|
|
2896
3065
|
fonts: {
|
|
2897
3066
|
families: fontNames,
|
|
2898
|
-
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 typeset
|
|
3067
|
+
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."
|
|
2899
3068
|
},
|
|
2900
3069
|
skill: {
|
|
2901
3070
|
path: ".cursor/skills/atomix-ds/SKILL.md",
|
|
2902
3071
|
content: GENERIC_SKILL_MD
|
|
2903
3072
|
},
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
3073
|
+
...cachedMcpTier === "pro" ? {
|
|
3074
|
+
skillFigmaDesign: {
|
|
3075
|
+
path: ".cursor/skills/atomix-ds/FIGMA-SKILL.md",
|
|
3076
|
+
content: FIGMA_DESIGN_SKILL_MD
|
|
3077
|
+
}
|
|
3078
|
+
} : {},
|
|
2908
3079
|
tokenFiles: {
|
|
2909
3080
|
files: ["tokens.css", "tokens.json"],
|
|
2910
3081
|
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."
|
|
@@ -2971,13 +3142,14 @@ ${tokenResponseText}` : summary;
|
|
|
2971
3142
|
if (payloads.numberVariableCollections.length > 0) {
|
|
2972
3143
|
const numberResults = [];
|
|
2973
3144
|
try {
|
|
3145
|
+
const foundationsName = payloads.colorVariables.collectionName;
|
|
2974
3146
|
for (const coll of payloads.numberVariableCollections) {
|
|
2975
3147
|
const result = await sendBridgeRequest("create_number_variables", {
|
|
2976
|
-
collectionName:
|
|
2977
|
-
variables: coll.variables,
|
|
3148
|
+
collectionName: foundationsName,
|
|
3149
|
+
variables: coll.variables.map((v) => ({ name: `${coll.categoryKey} / ${v.name}`, value: v.value })),
|
|
2978
3150
|
scopes: coll.scopes
|
|
2979
3151
|
});
|
|
2980
|
-
numberResults.push({
|
|
3152
|
+
numberResults.push({ categoryKey: coll.categoryKey, result });
|
|
2981
3153
|
}
|
|
2982
3154
|
out.numberVariables = numberResults;
|
|
2983
3155
|
} catch (e) {
|
|
@@ -3302,7 +3474,7 @@ ${tokenResponseText}` : summary;
|
|
|
3302
3474
|
type: "text",
|
|
3303
3475
|
text: JSON.stringify({
|
|
3304
3476
|
error: `Unknown tool: ${name}`,
|
|
3305
|
-
availableTools: ["getToken", "listTokens", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "syncToFigma", "getFigmaVariablesAndStyles", "createDesignPlaceholder", "resolveFigmaIdsForTokens", "designCreateFrame", "designCreateText", "designCreateRectangle", "designSetAutoLayout", "designSetLayoutConstraints", "designAppendChild", "getDesignScreenshot", "finalizeDesignFrame"]
|
|
3477
|
+
availableTools: ["getToken", "listTokens", "listTypesets", "searchTokens", "validateUsage", "getAIToolRules", "exportMCPConfig", "getSetupInstructions", "syncAll", "getDependencies", "syncToFigma", "getFigmaVariablesAndStyles", "createDesignPlaceholder", "resolveFigmaIdsForTokens", "designCreateFrame", "designCreateText", "designCreateRectangle", "designSetAutoLayout", "designSetLayoutConstraints", "designAppendChild", "getDesignScreenshot", "finalizeDesignFrame"]
|
|
3306
3478
|
}, null, 2)
|
|
3307
3479
|
}]
|
|
3308
3480
|
};
|
|
@@ -3398,7 +3570,7 @@ Use the returned rules and token paths/values when generating or editing code. P
|
|
|
3398
3570
|
|
|
3399
3571
|
- **Fetch first:** Before writing UI or styles, call getAIToolRules and/or getToken/listTokens so you know the exact tokens and conventions.
|
|
3400
3572
|
- **Icons:** Apply the design system's icon tokens when rendering icons: sizing via \`getToken("sizing.icon.sm")\` or \`listTokens("sizing")\`, and stroke width via \`getToken("icons.strokeWidth")\` when the DS defines it; do not use hardcoded sizes or stroke widths.
|
|
3401
|
-
- **Typography:** Use typography tokens
|
|
3573
|
+
- **Typography:** Use typography tokens from the DS for any text. When creating global typeset CSS, call **listTypesets** and emit one CSS class per typeset (do not skip any); include text-transform and text-decoration when present for 1:1 match.
|
|
3402
3574
|
- **No guessing:** If a value is not in the rules or token list, use searchTokens or listTokens to find the closest match rather than inventing a value.
|
|
3403
3575
|
- **Version check:** If this skill file has frontmatter \`atomixDsVersion\`, compare it to the design system version from **getDependencies** (\`meta.designSystemVersion\`). If the design system is newer, suggest the user run **syncAll** to update skills and tokens.
|
|
3404
3576
|
`;
|
|
@@ -3463,18 +3635,15 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3463
3635
|
min-height: 100vh;
|
|
3464
3636
|
padding: 2rem;
|
|
3465
3637
|
display: flex;
|
|
3466
|
-
align-items: center;
|
|
3467
|
-
justify-content: center;
|
|
3468
|
-
text-align: center;
|
|
3469
3638
|
}
|
|
3470
3639
|
.wrap { width: 375px; max-width: 100%; }
|
|
3471
3640
|
.icon { width: 2rem; height: 2rem; margin: 0 auto 1rem; }
|
|
3472
3641
|
h1 {
|
|
3473
3642
|
font-family: {{HEADING_FONT_VAR}}, {{FONT_FAMILY_VAR}}, system-ui, sans-serif;
|
|
3474
|
-
font-size: clamp(
|
|
3643
|
+
font-size: clamp(3rem, 4vw, 5rem);
|
|
3475
3644
|
font-weight: 700;
|
|
3476
3645
|
margin: 0 0 0.75rem;
|
|
3477
|
-
line-height: 1.
|
|
3646
|
+
line-height: 1.2;
|
|
3478
3647
|
}
|
|
3479
3648
|
.lead { margin: 0 0 1.5rem; font-size: 1rem; line-height: 1.5; opacity: 0.95; }
|
|
3480
3649
|
.now { margin: 1.5rem 0 0; font-size: 0.875rem; line-height: 1.6; opacity: 0.95; text-align: left; }
|
|
@@ -3487,10 +3656,10 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
3487
3656
|
<body>
|
|
3488
3657
|
<div class="wrap">
|
|
3489
3658
|
<div class="icon" aria-hidden="true">
|
|
3490
|
-
<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="
|
|
3659
|
+
<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>
|
|
3491
3660
|
</div>
|
|
3492
3661
|
<h1>You're all set with {{DS_NAME}}</h1>
|
|
3493
|
-
<p class="lead">This page uses your design system: brand primary as background, headline and body typesets, and an icon
|
|
3662
|
+
<p class="lead">This page uses your design system: brand primary as background, headline and body typesets, and an icon.</p>
|
|
3494
3663
|
<div class="now">
|
|
3495
3664
|
<strong>What you can do now:</strong>
|
|
3496
3665
|
<ul>
|
|
@@ -4123,8 +4292,8 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4123
4292
|
|
|
4124
4293
|
- 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.
|
|
4125
4294
|
- Call **getDependencies** with \`platform\` and optional \`stack\`. If it fails, tell the user the design system could not be reached and stop.
|
|
4126
|
-
- Scan the repo for: .cursor/skills/atomix-ds/SKILL.md,
|
|
4127
|
-
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links, skill (.cursor/skills/atomix-ds/SKILL.md)
|
|
4295
|
+
- 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. If getDependencies returned \`skillFigmaDesign\`, also scan for .cursor/skills/atomix-ds/FIGMA-SKILL.md. **Web:** note any existing CSS (globals.css, main.css, Tailwind, etc.). **Native:** note any theme/style files (SwiftUI, Android themes, Compose).
|
|
4296
|
+
- Build two lists: **Suggested** (from getDependencies minus what exists) and **Already present**. Include: icon package, font links, skill (.cursor/skills/atomix-ds/SKILL.md). Include **Figma design skill** (.cursor/skills/atomix-ds/FIGMA-SKILL.md) in Suggested/Already present only if getDependencies returned a \`skillFigmaDesign\` object; otherwise omit it. Include token files; for web, also include the **showcase page** (atomix-setup-showcase.html) if getDependencies returned a \`showcase\` object.
|
|
4128
4297
|
- Do not write, create, or add anything in Phase 1.
|
|
4129
4298
|
|
|
4130
4299
|
## Phase 2 \u2013 Report and ask
|
|
@@ -4138,14 +4307,14 @@ Use \`/color\`, \`/spacing\`, \`/radius\`, \`/typography\`, \`/shadow\`, \`/bord
|
|
|
4138
4307
|
- Run only when the user has said yes (all or specific items).
|
|
4139
4308
|
- For each approved item:
|
|
4140
4309
|
- **Skill:** Write the skill content from getDependencies \`skill.content\` to \`skill.path\` (.cursor/skills/atomix-ds/SKILL.md).
|
|
4141
|
-
- **Figma design skill:**
|
|
4310
|
+
- **Figma design skill:** Only if getDependencies returned \`skillFigmaDesign\`, write \`skillFigmaDesign.content\` to \`skillFigmaDesign.path\` (.cursor/skills/atomix-ds/FIGMA-SKILL.md). Use this when designing in Figma so the agent follows principal-product-designer rules and prefers existing Figma variables. If \`skillFigmaDesign\` was not in the response, do not add this file.
|
|
4142
4311
|
- **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.
|
|
4143
4312
|
- **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.
|
|
4144
|
-
- **Fonts and typeset:** Add font links (e.g. \`<link>\` or \`@import\` from Google Fonts). Then build a **typeset
|
|
4313
|
+
- **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.
|
|
4145
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).
|
|
4146
4315
|
- Report only what you actually created or updated. Do not claim the token file was added if you did not call syncAll.
|
|
4147
4316
|
- **After reporting \u2013 styles/theme:**
|
|
4148
|
-
- **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**\
|
|
4317
|
+
- **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.
|
|
4149
4318
|
- **iOS/Android:** If the project already has theme/style files: recommend how to integrate Atomix tokens. Do not suggest a new global theme. Only if there is **no** theme/style at all, ask once: "There's no theme/style setup yet. Do you want a minimal token-based theme?" and add only if the user says yes.
|
|
4150
4319
|
|
|
4151
4320
|
Create your todo list first, then Phase 1 (resolve platform/stack, call getDependencies, scan, build lists), then Phase 2 (report and ask). Do not perform Phase 3 until the user replies.`;
|