designagent-cli 0.1.0 → 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +18 -7
  2. package/dist/index.js +418 -803
  3. package/package.json +6 -8
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { createRequire } from "node:module";
5
- import pc4 from "picocolors";
5
+ import pc3 from "picocolors";
6
6
 
7
7
  // src/commands/init.ts
8
8
  import { basename as basename5 } from "node:path";
@@ -438,15 +438,43 @@ function fontSizeToPx(value) {
438
438
  // ../scanner/dist/readers/jsx-tsx.js
439
439
  var HEX_RE2 = /#[0-9a-fA-F]{3,8}\b/g;
440
440
  var PX_STR_RE = /['"](-?\d*\.?\d+)px['"]/g;
441
- function componentName(text, file) {
442
- const fn = text.match(/export\s+(?:default\s+)?function\s+([A-Z]\w+)/);
443
- if (fn)
444
- return fn[1];
445
- const cn = text.match(/export\s+(?:default\s+)?const\s+([A-Z]\w+)\s*[:=]/);
446
- if (cn)
447
- return cn[1];
448
- const base = basename(file).replace(/\.(tsx|jsx)$/, "");
449
- return /^[A-Z]/.test(base) ? base : null;
441
+ function isComponentName(name) {
442
+ return /^[A-Z][A-Za-z0-9]*$/.test(name) && /[a-z]/.test(name);
443
+ }
444
+ var INLINE_EXPORT_RE = /export\s+(?:default\s+)?(?:async\s+)?(?:function|const|class)\s+([A-Z]\w*)/g;
445
+ var EXPORT_BLOCK_RE = /export\s*\{([^}]*)\}(?!\s*from\b)/g;
446
+ var DEFAULT_IDENT_RE = /export\s+default\s+([A-Z]\w*)\s*;?/g;
447
+ function componentNames(text, file) {
448
+ const names = new Set;
449
+ let m;
450
+ INLINE_EXPORT_RE.lastIndex = 0;
451
+ while ((m = INLINE_EXPORT_RE.exec(text)) !== null) {
452
+ if (isComponentName(m[1]))
453
+ names.add(m[1]);
454
+ }
455
+ EXPORT_BLOCK_RE.lastIndex = 0;
456
+ while ((m = EXPORT_BLOCK_RE.exec(text)) !== null) {
457
+ for (const part of m[1].split(",")) {
458
+ const seg = part.trim();
459
+ if (!seg || seg.startsWith("type "))
460
+ continue;
461
+ const asMatch = seg.match(/\bas\s+(\w+)/);
462
+ const exported = asMatch ? asMatch[1] : seg;
463
+ if (isComponentName(exported))
464
+ names.add(exported);
465
+ }
466
+ }
467
+ DEFAULT_IDENT_RE.lastIndex = 0;
468
+ while ((m = DEFAULT_IDENT_RE.exec(text)) !== null) {
469
+ if (isComponentName(m[1]))
470
+ names.add(m[1]);
471
+ }
472
+ if (names.size === 0) {
473
+ const base = basename(file).replace(/\.(tsx|jsx)$/, "");
474
+ if (isComponentName(base))
475
+ names.add(base);
476
+ }
477
+ return [...names];
450
478
  }
451
479
  function lineOfIndex2(text, index) {
452
480
  let line = 1;
@@ -485,9 +513,9 @@ function readJsxTsx(root) {
485
513
  continue;
486
514
  }
487
515
  const rel = relative3(root, file);
488
- const name = componentName(text, file);
489
- if (name)
516
+ for (const name of componentNames(text, file)) {
490
517
  components.push({ name, file: rel });
518
+ }
491
519
  let t;
492
520
  TEXT_SIZE_RE.lastIndex = 0;
493
521
  while ((t = TEXT_SIZE_RE.exec(text)) !== null) {
@@ -581,9 +609,16 @@ var DESIGN_SYSTEMS = [
581
609
  { dep: "@chakra-ui/react", name: "chakra" },
582
610
  { dep: "antd", name: "antd" }
583
611
  ];
612
+ var FRAMEWORKS = [
613
+ { dep: "react-native", name: "react-native" },
614
+ { dep: "vue", name: "vue" },
615
+ { dep: "svelte", name: "svelte" },
616
+ { dep: "react", name: "react" }
617
+ ];
584
618
  function detectStack(root) {
585
619
  let designSystem = null;
586
620
  let tailwind = false;
621
+ const frameworks = [];
587
622
  const pkgPath = join3(root, "package.json");
588
623
  if (existsSync2(pkgPath)) {
589
624
  try {
@@ -600,6 +635,13 @@ function detectStack(root) {
600
635
  if (designSystem === "tailwind" && (("class-variance-authority" in deps) || ("cmdk" in deps))) {
601
636
  designSystem = "shadcn";
602
637
  }
638
+ const hasRN = "react-native" in deps;
639
+ for (const { dep, name } of FRAMEWORKS) {
640
+ if (name === "react" && hasRN)
641
+ continue;
642
+ if (dep in deps)
643
+ frameworks.push(name);
644
+ }
603
645
  } catch {}
604
646
  }
605
647
  let canvas = null;
@@ -614,7 +656,7 @@ function detectStack(root) {
614
656
  canvas = "pencil";
615
657
  } catch {}
616
658
  }
617
- return { tailwind, designSystem, canvas };
659
+ return { tailwind, designSystem, canvas, frameworks };
618
660
  }
619
661
 
620
662
  // ../scanner/dist/readers/theme.js
@@ -956,8 +998,143 @@ function readTokensJson(root) {
956
998
  return { found, colors, dimensions, typography };
957
999
  }
958
1000
 
1001
+ // ../scanner/dist/readers/design-md.js
1002
+ import { existsSync as existsSync3, readFileSync as readFileSync11, readdirSync as readdirSync2 } from "node:fs";
1003
+ import { join as join4 } from "node:path";
1004
+ var EMPTY = { found: false, colors: [], dimensions: [], typography: [] };
1005
+ function unquote(v) {
1006
+ const t = v.trim();
1007
+ if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
1008
+ return t.slice(1, -1);
1009
+ }
1010
+ return t;
1011
+ }
1012
+ function indentOf(line) {
1013
+ let n = 0;
1014
+ while (n < line.length && line[n] === " ")
1015
+ n++;
1016
+ return n;
1017
+ }
1018
+ function findDesignMd(root) {
1019
+ for (const name of ["DESIGN.md", "design.md", "Design.md"]) {
1020
+ const p = join4(root, name);
1021
+ if (existsSync3(p))
1022
+ return p;
1023
+ }
1024
+ try {
1025
+ const hit = readdirSync2(root).find((f) => f.toLowerCase() === "design.md");
1026
+ if (hit)
1027
+ return join4(root, hit);
1028
+ } catch {}
1029
+ return null;
1030
+ }
1031
+ function readDesignMd(root) {
1032
+ const file = findDesignMd(root);
1033
+ if (!file)
1034
+ return EMPTY;
1035
+ let text;
1036
+ try {
1037
+ text = readFileSync11(file, "utf8");
1038
+ } catch {
1039
+ return EMPTY;
1040
+ }
1041
+ const fm = text.match(/^---\n([\s\S]*?)\n---/);
1042
+ if (!fm)
1043
+ return EMPTY;
1044
+ const lines = fm[1].split(`
1045
+ `);
1046
+ const rel = "DESIGN.md";
1047
+ const colors = [];
1048
+ const dimensions = [];
1049
+ const typography = [];
1050
+ let section = "";
1051
+ let typeName = null;
1052
+ let typeAcc = {};
1053
+ const flushType = () => {
1054
+ if (typeName && (typeAcc.fontSize || typeAcc.fontFamily || typeAcc.fontWeight)) {
1055
+ typography.push({ name: typeName, ...typeAcc, origin: "design-md", source: { file: rel, line: 0 } });
1056
+ }
1057
+ typeName = null;
1058
+ typeAcc = {};
1059
+ };
1060
+ lines.forEach((raw, i) => {
1061
+ if (!raw.trim() || raw.trim().startsWith("#"))
1062
+ return;
1063
+ const indent = indentOf(raw);
1064
+ const line = raw.trim();
1065
+ const colon = line.indexOf(":");
1066
+ if (colon === -1)
1067
+ return;
1068
+ const key = line.slice(0, colon).trim();
1069
+ const value = line.slice(colon + 1).trim();
1070
+ if (indent === 0) {
1071
+ flushType();
1072
+ if (["colors", "typography", "spacing", "rounded", "components"].includes(key)) {
1073
+ section = key;
1074
+ } else {
1075
+ section = "other";
1076
+ }
1077
+ return;
1078
+ }
1079
+ if (section === "colors" && indent >= 2 && value) {
1080
+ const hex = normalizeHex(unquote(value));
1081
+ if (hex)
1082
+ colors.push({ hex, name: key, origin: "design-md", source: { file: rel, line: i } });
1083
+ return;
1084
+ }
1085
+ if ((section === "spacing" || section === "rounded") && indent >= 2 && value) {
1086
+ const dim = toPx3(unquote(value));
1087
+ if (dim && dim.px >= 0) {
1088
+ dimensions.push({
1089
+ px: dim.px,
1090
+ raw: dim.raw,
1091
+ name: key,
1092
+ origin: "design-md",
1093
+ category: section === "rounded" ? "radius" : "spacing",
1094
+ source: { file: rel, line: i }
1095
+ });
1096
+ }
1097
+ return;
1098
+ }
1099
+ if (section === "typography") {
1100
+ if (indent === 2 && !value) {
1101
+ flushType();
1102
+ typeName = key;
1103
+ return;
1104
+ }
1105
+ if (indent >= 4 && typeName && value) {
1106
+ const v = unquote(value);
1107
+ if (key === "fontFamily")
1108
+ typeAcc.fontFamily = v;
1109
+ else if (key === "fontSize")
1110
+ typeAcc.fontSize = v;
1111
+ else if (key === "fontWeight")
1112
+ typeAcc.fontWeight = Number(v) || undefined;
1113
+ else if (key === "lineHeight")
1114
+ typeAcc.lineHeight = /^[\d.]+$/.test(v) ? Number(v) : v;
1115
+ }
1116
+ return;
1117
+ }
1118
+ });
1119
+ flushType();
1120
+ const found = colors.length > 0 || dimensions.length > 0 || typography.length > 0;
1121
+ return { found, colors, dimensions, typography };
1122
+ }
1123
+
959
1124
  // ../scanner/dist/analyzers/colors.js
960
1125
  var DUPLICATE_THRESHOLD = 5;
1126
+ var SAME_PIXEL_THRESHOLD = 1.5;
1127
+ function topName(names) {
1128
+ let best = "";
1129
+ let n = 0;
1130
+ for (const [k, v] of names) {
1131
+ if (v > n) {
1132
+ best = k;
1133
+ n = v;
1134
+ }
1135
+ }
1136
+ return best;
1137
+ }
961
1138
  function analyzeColors(raw) {
962
1139
  const exact = new Map;
963
1140
  for (const c of raw) {
@@ -971,7 +1148,17 @@ function analyzeColors(raw) {
971
1148
  const buckets = [];
972
1149
  for (const [hex, { count, names }] of sorted) {
973
1150
  const lab = rgbToLab(hexToRgb(hex));
974
- const hit = buckets.find((b) => deltaE(b.lab, lab) < DUPLICATE_THRESHOLD);
1151
+ const inName = topName(names);
1152
+ const hit = buckets.find((b) => {
1153
+ const d = deltaE(b.lab, lab);
1154
+ if (d >= DUPLICATE_THRESHOLD)
1155
+ return false;
1156
+ const bName = topName(b.names);
1157
+ const namedDifferently = inName && bName && inName !== bName;
1158
+ if (namedDifferently && d >= SAME_PIXEL_THRESHOLD)
1159
+ return false;
1160
+ return true;
1161
+ });
975
1162
  if (hit) {
976
1163
  hit.count += count;
977
1164
  hit.members.set(hex, count);
@@ -1022,7 +1209,7 @@ function nameCluster(b, index) {
1022
1209
  }
1023
1210
 
1024
1211
  // ../scanner/dist/analyzers/spacing.js
1025
- var DEFINITION_ORIGINS = new Set(["tailwind", "css-var", "scss", "tokens", "theme"]);
1212
+ var DEFINITION_ORIGINS = new Set(["tailwind", "css-var", "scss", "tokens", "theme", "design-md"]);
1026
1213
  function isDefinition(d) {
1027
1214
  return DEFINITION_ORIGINS.has(d.origin);
1028
1215
  }
@@ -1116,7 +1303,7 @@ function analyzeDrift(colors, dimensions, clusters, grid) {
1116
1303
  }
1117
1304
 
1118
1305
  // ../scanner/dist/analyzers/typography.js
1119
- var DEFINITION_ORIGINS2 = new Set(["tailwind", "css-var", "scss", "tokens", "theme"]);
1306
+ var DEFINITION_ORIGINS2 = new Set(["tailwind", "css-var", "scss", "tokens", "theme", "design-md"]);
1120
1307
  function isDefinition2(t) {
1121
1308
  return t.origin !== undefined && DEFINITION_ORIGINS2.has(t.origin);
1122
1309
  }
@@ -1379,8 +1566,10 @@ function scan(root, options = {}) {
1379
1566
  const tokens = readTokensJson(root);
1380
1567
  const jsx = readJsxTsx(root);
1381
1568
  const stories = readStorybook(root);
1569
+ const designMd = readDesignMd(root);
1382
1570
  const detected = detectStack(root);
1383
1571
  const colors = [
1572
+ ...designMd.colors,
1384
1573
  ...tailwind.colors,
1385
1574
  ...css.colors,
1386
1575
  ...scss.colors,
@@ -1391,6 +1580,7 @@ function scan(root, options = {}) {
1391
1580
  ...jsx.colors
1392
1581
  ];
1393
1582
  const dimensions = [
1583
+ ...designMd.dimensions,
1394
1584
  ...tailwind.dimensions,
1395
1585
  ...css.dimensions,
1396
1586
  ...scss.dimensions,
@@ -1399,6 +1589,7 @@ function scan(root, options = {}) {
1399
1589
  ...jsx.dimensions
1400
1590
  ];
1401
1591
  const rawTypography = [
1592
+ ...designMd.typography,
1402
1593
  ...tailwind.typography,
1403
1594
  ...css.typography,
1404
1595
  ...scss.typography,
@@ -1439,13 +1630,13 @@ function scan(root, options = {}) {
1439
1630
  // src/scaffold.ts
1440
1631
  import {
1441
1632
  copyFileSync,
1442
- existsSync as existsSync5,
1633
+ existsSync as existsSync6,
1443
1634
  mkdirSync as mkdirSync2,
1444
- readFileSync as readFileSync12,
1635
+ readFileSync as readFileSync13,
1445
1636
  renameSync,
1446
1637
  writeFileSync
1447
1638
  } from "node:fs";
1448
- import { join as join6 } from "node:path";
1639
+ import { join as join7 } from "node:path";
1449
1640
 
1450
1641
  // src/types.ts
1451
1642
  var FRAMEWORK_LABELS = {
@@ -1470,21 +1661,21 @@ var CANVAS_LABELS = {
1470
1661
  };
1471
1662
 
1472
1663
  // src/templates.ts
1473
- import { readFileSync as readFileSync11, existsSync as existsSync3 } from "node:fs";
1474
- import { dirname, join as join4, resolve } from "node:path";
1664
+ import { readFileSync as readFileSync12, existsSync as existsSync4 } from "node:fs";
1665
+ import { dirname, join as join5, resolve } from "node:path";
1475
1666
  import { fileURLToPath } from "node:url";
1476
1667
  var here = dirname(fileURLToPath(import.meta.url));
1477
1668
  function resolveTemplatesDir() {
1478
- const candidates = [join4(here, "templates"), resolve(here, "..", "templates")];
1669
+ const candidates = [join5(here, "templates"), resolve(here, "..", "templates")];
1479
1670
  for (const dir of candidates) {
1480
- if (existsSync3(dir))
1671
+ if (existsSync4(dir))
1481
1672
  return dir;
1482
1673
  }
1483
1674
  return candidates[0];
1484
1675
  }
1485
1676
  var TEMPLATES_DIR = resolveTemplatesDir();
1486
1677
  function readTemplate(relativePath) {
1487
- return readFileSync11(join4(TEMPLATES_DIR, relativePath), "utf8");
1678
+ return readFileSync12(join5(TEMPLATES_DIR, relativePath), "utf8");
1488
1679
  }
1489
1680
  function fill(template, vars) {
1490
1681
  return template.replace(/\{\{(\w+)\}\}/g, (match, key) => (key in vars) ? vars[key] : match);
@@ -1636,20 +1827,20 @@ function lintDesignMd(filePath) {
1636
1827
  }
1637
1828
 
1638
1829
  // src/skills.ts
1639
- import { cpSync, existsSync as existsSync4, mkdirSync, readdirSync as readdirSync2 } from "node:fs";
1830
+ import { cpSync, existsSync as existsSync5, mkdirSync, readdirSync as readdirSync3 } from "node:fs";
1640
1831
  import { homedir } from "node:os";
1641
- import { dirname as dirname2, join as join5, resolve as resolve2 } from "node:path";
1832
+ import { dirname as dirname2, join as join6, resolve as resolve2 } from "node:path";
1642
1833
  import { fileURLToPath as fileURLToPath3 } from "node:url";
1643
1834
  var here2 = dirname2(fileURLToPath3(import.meta.url));
1644
1835
  function resolveSkillsDir() {
1645
1836
  const candidates = [
1646
- join5(here2, "skills"),
1837
+ join6(here2, "skills"),
1647
1838
  resolve2(here2, "..", "..", "skills")
1648
1839
  ];
1649
- return candidates.find((dir) => existsSync4(dir)) ?? null;
1840
+ return candidates.find((dir) => existsSync5(dir)) ?? null;
1650
1841
  }
1651
1842
  function skillsInstallDir() {
1652
- return join5(homedir(), ".designagent", "skills");
1843
+ return join6(homedir(), ".designagent", "skills");
1653
1844
  }
1654
1845
  function installSkills() {
1655
1846
  const src = resolveSkillsDir();
@@ -1658,10 +1849,10 @@ function installSkills() {
1658
1849
  const dest = skillsInstallDir();
1659
1850
  mkdirSync(dest, { recursive: true });
1660
1851
  const installed = [];
1661
- for (const entry of readdirSync2(src, { withFileTypes: true })) {
1852
+ for (const entry of readdirSync3(src, { withFileTypes: true })) {
1662
1853
  if (!entry.isDirectory())
1663
1854
  continue;
1664
- cpSync(join5(src, entry.name), join5(dest, entry.name), { recursive: true });
1855
+ cpSync(join6(src, entry.name), join6(dest, entry.name), { recursive: true });
1665
1856
  installed.push(entry.name);
1666
1857
  }
1667
1858
  return installed.sort();
@@ -1678,22 +1869,22 @@ function scaffold(config, cwd, optsOrToday) {
1678
1869
  const claudeMd = generateClaudeMd(config);
1679
1870
  const decisionsMd = generateDecisionsMd(config, today);
1680
1871
  const mcpConfig = generateMcpConfig(config);
1681
- const designMdPath = join6(cwd, "DESIGN.md");
1682
- const claudeMdPath = join6(cwd, "CLAUDE.md");
1683
- const decisionsMdPath = join6(cwd, "DECISIONS.md");
1684
- const mcpPath = join6(cwd, ".mcp.json");
1872
+ const designMdPath = join7(cwd, "DESIGN.md");
1873
+ const claudeMdPath = join7(cwd, "CLAUDE.md");
1874
+ const decisionsMdPath = join7(cwd, "DECISIONS.md");
1875
+ const mcpPath = join7(cwd, ".mcp.json");
1685
1876
  const backedUp = [];
1686
1877
  let claudeAugmented = false;
1687
1878
  if (mode === "fresh") {
1688
- const backupDir = join6(cwd, ".designagent", "backup", today);
1879
+ const backupDir = join7(cwd, ".designagent", "backup", today);
1689
1880
  for (const [path, name] of [
1690
1881
  [designMdPath, "DESIGN.md"],
1691
1882
  [claudeMdPath, "CLAUDE.md"],
1692
1883
  [decisionsMdPath, "DECISIONS.md"]
1693
1884
  ]) {
1694
- if (existsSync5(path)) {
1885
+ if (existsSync6(path)) {
1695
1886
  mkdirSync2(backupDir, { recursive: true });
1696
- copyFileSync(path, join6(backupDir, name));
1887
+ copyFileSync(path, join7(backupDir, name));
1697
1888
  backedUp.push(name);
1698
1889
  }
1699
1890
  }
@@ -1701,14 +1892,14 @@ function scaffold(config, cwd, optsOrToday) {
1701
1892
  writeFileSync(claudeMdPath, claudeMd, "utf8");
1702
1893
  writeFileSync(decisionsMdPath, decisionsMd, "utf8");
1703
1894
  } else {
1704
- if (!existsSync5(designMdPath))
1895
+ if (!existsSync6(designMdPath))
1705
1896
  writeFileSync(designMdPath, designMd, "utf8");
1706
- if (!existsSync5(decisionsMdPath))
1897
+ if (!existsSync6(decisionsMdPath))
1707
1898
  writeFileSync(decisionsMdPath, decisionsMd, "utf8");
1708
- if (!existsSync5(claudeMdPath)) {
1899
+ if (!existsSync6(claudeMdPath)) {
1709
1900
  writeFileSync(claudeMdPath, claudeMd, "utf8");
1710
1901
  } else {
1711
- const current = readFileSync12(claudeMdPath, "utf8");
1902
+ const current = readFileSync13(claudeMdPath, "utf8");
1712
1903
  if (!current.includes(AUGMENT_MARKER)) {
1713
1904
  const block = `
1714
1905
 
@@ -1723,15 +1914,15 @@ ${claudeMd}
1723
1914
  }
1724
1915
  let mcpWritten = false;
1725
1916
  if (mcpConfig) {
1726
- const shouldWrite = mode === "fresh" || !existsSync5(mcpPath);
1917
+ const shouldWrite = mode === "fresh" || !existsSync6(mcpPath);
1727
1918
  if (shouldWrite) {
1728
1919
  writeFileSync(mcpPath, mcpConfig, "utf8");
1729
1920
  mcpWritten = true;
1730
1921
  }
1731
1922
  }
1732
- const configDir = join6(cwd, ".designagent");
1923
+ const configDir = join7(cwd, ".designagent");
1733
1924
  mkdirSync2(configDir, { recursive: true });
1734
- writeFileSync(join6(configDir, "config.json"), JSON.stringify(config, null, 2) + `
1925
+ writeFileSync(join7(configDir, "config.json"), JSON.stringify(config, null, 2) + `
1735
1926
  `, "utf8");
1736
1927
  const lint = lintDesignMd(designMdPath);
1737
1928
  const installedSkills = installSkills();
@@ -1745,20 +1936,21 @@ ${claudeMd}
1745
1936
  };
1746
1937
  }
1747
1938
  function migrateLowercaseDesignMd(cwd) {
1748
- const lower = join6(cwd, "design.md");
1749
- if (!existsSync5(lower))
1939
+ const lower = join7(cwd, "design.md");
1940
+ if (!existsSync6(lower))
1750
1941
  return null;
1751
- const bak = join6(cwd, ".design.md.bak");
1942
+ const bak = join7(cwd, ".design.md.bak");
1752
1943
  renameSync(lower, bak);
1753
1944
  return bak;
1754
1945
  }
1755
1946
 
1756
1947
  // src/detect.ts
1757
- import { readdirSync as readdirSync3 } from "node:fs";
1948
+ import { existsSync as existsSync7, readdirSync as readdirSync4 } from "node:fs";
1949
+ import { join as join8 } from "node:path";
1758
1950
  function detectExisting(cwd) {
1759
1951
  let entries = [];
1760
1952
  try {
1761
- entries = readdirSync3(cwd, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
1953
+ entries = readdirSync4(cwd, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name);
1762
1954
  } catch {}
1763
1955
  const names = new Set(entries);
1764
1956
  const claudeMd = names.has("CLAUDE.md");
@@ -1776,6 +1968,17 @@ function detectExisting(cwd) {
1776
1968
  migratable: designMdLower
1777
1969
  };
1778
1970
  }
1971
+ var SOURCE_DIRS = ["src", "app", "components", "lib", "pages", "styles"];
1972
+ var SOURCE_EXTS = [".tsx", ".jsx", ".ts", ".vue", ".svelte", ".swift", ".kt", ".css", ".scss"];
1973
+ function looksLikeExistingCodebase(cwd) {
1974
+ if (SOURCE_DIRS.some((d) => existsSync7(join8(cwd, d))))
1975
+ return true;
1976
+ try {
1977
+ return readdirSync4(cwd).some((f) => SOURCE_EXTS.some((e) => f.endsWith(e)));
1978
+ } catch {
1979
+ return false;
1980
+ }
1981
+ }
1779
1982
 
1780
1983
  // src/commands/init.ts
1781
1984
  function bail() {
@@ -1801,6 +2004,32 @@ function mapDetectedCanvas(detected) {
1801
2004
  return detected;
1802
2005
  return "none";
1803
2006
  }
2007
+ var FRAMEWORK_VALUES = [
2008
+ "react",
2009
+ "vue",
2010
+ "svelte",
2011
+ "react-native",
2012
+ "swiftui",
2013
+ "compose"
2014
+ ];
2015
+ function mapDetectedFrameworks(detected) {
2016
+ const valid = detected.filter((f) => FRAMEWORK_VALUES.includes(f));
2017
+ return valid.length > 0 ? valid : ["react"];
2018
+ }
2019
+ var FRAMEWORK_LABELS2 = {
2020
+ react: "React",
2021
+ vue: "Vue",
2022
+ svelte: "Svelte",
2023
+ "react-native": "React Native",
2024
+ swiftui: "SwiftUI",
2025
+ compose: "Jetpack Compose",
2026
+ none: "none"
2027
+ };
2028
+ var DESIGN_SYSTEM_LABELS2 = {
2029
+ tailwind: "Tailwind",
2030
+ shadcn: "shadcn/ui",
2031
+ custom: "Custom"
2032
+ };
1804
2033
  function scanSummary(report) {
1805
2034
  const { colors, spacing, radius, typography, components, drift, detected } = report;
1806
2035
  const gridLine = spacing.grid ? `${spacing.values.length} (${spacing.values.length - spacing.outliers.length} on ${spacing.grid}pt grid, ${spacing.outliers.length} off)` : `${spacing.values.length}`;
@@ -1821,7 +2050,7 @@ function scanSummary(report) {
1821
2050
  }
1822
2051
  async function init() {
1823
2052
  console.log("");
1824
- intro(pc.bold(pc.cyan("Design Agent")) + pc.dim(" — reading your codebase"));
2053
+ intro(pc.bold(pc.cyan("Design Agent")) + pc.dim(" — design intelligence for Claude Code"));
1825
2054
  const cwd = process.cwd();
1826
2055
  const existing = detectExisting(cwd);
1827
2056
  let mode = "fresh";
@@ -1868,67 +2097,132 @@ async function init() {
1868
2097
  mode = choice;
1869
2098
  }
1870
2099
  }
1871
- const scanSpin = spinner();
1872
- scanSpin.start("Scanning your codebase");
1873
- const report = scan(cwd);
1874
- scanSpin.stop(`Scanned ${report.colors.raw} colors, ${report.components.length} components, ${report.drift.length} drift`);
1875
- note(scanSummary(report), "What we found");
1876
- const answers = await group({
1877
- canvas: () => select({
1878
- message: "What's your canvas?",
1879
- options: [
1880
- { value: "figma", label: "Figma" },
1881
- { value: "pencil", label: "Pencil" },
1882
- { value: "openpencil", label: "OpenPencil" },
1883
- { value: "none", label: "None / code only" }
1884
- ],
1885
- initialValue: mapDetectedCanvas(report.detected.canvas)
1886
- }),
1887
- figmaUrl: ({ results }) => results.canvas === "figma" ? text({
1888
- message: "Paste your Figma file URL",
1889
- placeholder: "https://figma.com/design/…",
1890
- validate: validateFigmaUrl
1891
- }) : Promise.resolve(undefined),
1892
- designSystem: () => select({
1893
- message: "What's your design system?",
1894
- options: [
1895
- { value: "tailwind", label: "Tailwind" },
1896
- { value: "shadcn", label: "shadcn/ui" },
1897
- { value: "custom", label: "Custom" }
1898
- ],
1899
- initialValue: mapDetectedDesignSystem(report.detected.designSystem)
1900
- }),
1901
- frameworks: () => multiselect({
1902
- message: "Component framework? (space to select, enter to confirm)",
2100
+ let projectType = "existing";
2101
+ if (!existing.hasAny) {
2102
+ const choice = await select({
2103
+ message: "Is this a new project or an existing codebase?",
1903
2104
  options: [
1904
- { value: "react", label: "React", hint: "Web" },
1905
- { value: "vue", label: "Vue", hint: "Web" },
1906
- { value: "svelte", label: "Svelte", hint: "Web" },
1907
- { value: "react-native", label: "React Native", hint: "Cross-platform" },
1908
- { value: "swiftui", label: "SwiftUI", hint: "Native mobile" },
1909
- { value: "compose", label: "Jetpack Compose", hint: "Native mobile" },
1910
- { value: "none", label: "None / other" }
2105
+ {
2106
+ value: "existing",
2107
+ label: "Existing codebase",
2108
+ hint: "scan it and reverse-engineer your design system"
2109
+ },
2110
+ {
2111
+ value: "new",
2112
+ label: "New / greenfield",
2113
+ hint: "skip the scan, start from a clean DESIGN.md"
2114
+ }
1911
2115
  ],
1912
- initialValues: ["react"],
1913
- required: true
1914
- }),
1915
- projectName: () => text({
1916
- message: "Project name?",
1917
- placeholder: basename5(cwd),
1918
- defaultValue: basename5(cwd)
1919
- })
1920
- }, { onCancel: bail });
1921
- const config = {
1922
- projectName: answers.projectName || basename5(cwd),
1923
- canvas: answers.canvas,
1924
- figmaUrl: answers.figmaUrl ?? null,
1925
- designSystem: answers.designSystem,
1926
- frameworks: answers.frameworks
1927
- };
2116
+ initialValue: looksLikeExistingCodebase(cwd) ? "existing" : "new"
2117
+ });
2118
+ if (isCancel(choice))
2119
+ bail();
2120
+ projectType = choice;
2121
+ }
2122
+ let report = null;
2123
+ if (projectType === "existing") {
2124
+ const scanSpin = spinner();
2125
+ scanSpin.start("Scanning your codebase");
2126
+ report = scan(cwd);
2127
+ scanSpin.stop(`Scanned — ${report.colors.raw} colors, ${report.components.length} components, ${report.drift.length} drift`);
2128
+ note(scanSummary(report), "What we found");
2129
+ }
2130
+ const detectedFrameworks = mapDetectedFrameworks(report?.detected.frameworks ?? []);
2131
+ const detectedDesignSystem = mapDetectedDesignSystem(report?.detected.designSystem ?? null);
2132
+ const detectedCanvas = mapDetectedCanvas(report?.detected.canvas ?? null);
2133
+ const canFastPath = report !== null && report.detected.frameworks.length > 0 && report.detected.designSystem !== null;
2134
+ let config = null;
2135
+ if (canFastPath) {
2136
+ const stackSummary = [
2137
+ detectedFrameworks.map((f) => FRAMEWORK_LABELS2[f]).join(", "),
2138
+ DESIGN_SYSTEM_LABELS2[detectedDesignSystem],
2139
+ detectedCanvas === "none" ? null : `${detectedCanvas} canvas`
2140
+ ].filter(Boolean).join(" · ");
2141
+ const useDetected = await confirm({
2142
+ message: `Use the detected setup? ${pc.dim(stackSummary)}`,
2143
+ initialValue: true
2144
+ });
2145
+ if (isCancel(useDetected))
2146
+ bail();
2147
+ if (useDetected) {
2148
+ let figmaUrl = null;
2149
+ if (detectedCanvas === "figma") {
2150
+ const url = await text({
2151
+ message: "Paste your Figma file URL",
2152
+ placeholder: "https://figma.com/design/…",
2153
+ validate: validateFigmaUrl
2154
+ });
2155
+ if (isCancel(url))
2156
+ bail();
2157
+ figmaUrl = url;
2158
+ }
2159
+ config = {
2160
+ projectName: basename5(cwd),
2161
+ canvas: detectedCanvas,
2162
+ figmaUrl,
2163
+ designSystem: detectedDesignSystem,
2164
+ frameworks: detectedFrameworks
2165
+ };
2166
+ }
2167
+ }
2168
+ if (!config) {
2169
+ const answers = await group({
2170
+ canvas: () => select({
2171
+ message: "What's your canvas?",
2172
+ options: [
2173
+ { value: "figma", label: "Figma" },
2174
+ { value: "pencil", label: "Pencil" },
2175
+ { value: "openpencil", label: "OpenPencil" },
2176
+ { value: "none", label: "None / code only" }
2177
+ ],
2178
+ initialValue: detectedCanvas
2179
+ }),
2180
+ figmaUrl: ({ results }) => results.canvas === "figma" ? text({
2181
+ message: "Paste your Figma file URL",
2182
+ placeholder: "https://figma.com/design/…",
2183
+ validate: validateFigmaUrl
2184
+ }) : Promise.resolve(undefined),
2185
+ designSystem: () => select({
2186
+ message: "What's your design system?",
2187
+ options: [
2188
+ { value: "tailwind", label: "Tailwind" },
2189
+ { value: "shadcn", label: "shadcn/ui" },
2190
+ { value: "custom", label: "Custom" }
2191
+ ],
2192
+ initialValue: detectedDesignSystem
2193
+ }),
2194
+ frameworks: () => multiselect({
2195
+ message: "Component framework? (space to select, enter to confirm)",
2196
+ options: [
2197
+ { value: "react", label: "React", hint: "Web" },
2198
+ { value: "vue", label: "Vue", hint: "Web" },
2199
+ { value: "svelte", label: "Svelte", hint: "Web" },
2200
+ { value: "react-native", label: "React Native", hint: "Cross-platform" },
2201
+ { value: "swiftui", label: "SwiftUI", hint: "Native mobile" },
2202
+ { value: "compose", label: "Jetpack Compose", hint: "Native mobile" },
2203
+ { value: "none", label: "None / other" }
2204
+ ],
2205
+ initialValues: detectedFrameworks,
2206
+ required: true
2207
+ }),
2208
+ projectName: () => text({
2209
+ message: "Project name?",
2210
+ placeholder: basename5(cwd),
2211
+ defaultValue: basename5(cwd)
2212
+ })
2213
+ }, { onCancel: bail });
2214
+ config = {
2215
+ projectName: answers.projectName || basename5(cwd),
2216
+ canvas: answers.canvas,
2217
+ figmaUrl: answers.figmaUrl ?? null,
2218
+ designSystem: answers.designSystem,
2219
+ frameworks: answers.frameworks
2220
+ };
2221
+ }
1928
2222
  const willWriteDesignMd = mode === "fresh" || !existing.designMd;
1929
- const scanHasTokens = report.colors.clusters.length > 0;
2223
+ const scanHasTokens = report !== null && report.colors.clusters.length > 0;
1930
2224
  let designMdOverride;
1931
- if (willWriteDesignMd && scanHasTokens) {
2225
+ if (willWriteDesignMd && report && scanHasTokens) {
1932
2226
  const useScan = await confirm({
1933
2227
  message: `Generate DESIGN.md from what we found? ${pc.dim(`(${report.colors.clusters.length} tokens, ${report.drift.length} drift)`)}`,
1934
2228
  initialValue: true
@@ -1964,7 +2258,7 @@ async function init() {
1964
2258
  if (!result.lint.ok && !result.lint.skipped && result.lint.output) {
1965
2259
  note(pc.dim(result.lint.output), "Lint output");
1966
2260
  }
1967
- outro(pc.bold("You're agent-ready.") + pc.dim(" Start Claude Code and build with design intelligence."));
2261
+ outro(projectType === "new" ? pc.bold("Scaffolded.") + pc.dim(" Add your tokens to DESIGN.md, then `npx designagent-cli docs` to see them.") : pc.bold("You're agent-ready.") + pc.dim(" Start Claude Code and build with design intelligence."));
1968
2262
  }
1969
2263
  function frameworkSummary(frameworks) {
1970
2264
  const real = frameworks.filter((f) => f !== "none");
@@ -1977,7 +2271,7 @@ function frameworkSummary(frameworks) {
1977
2271
 
1978
2272
  // src/commands/docs.ts
1979
2273
  import { mkdirSync as mkdirSync3, watch, writeFileSync as writeFileSync2 } from "node:fs";
1980
- import { join as join7 } from "node:path";
2274
+ import { join as join9 } from "node:path";
1981
2275
  import { spawn } from "node:child_process";
1982
2276
  import { platform } from "node:os";
1983
2277
  import pc2 from "picocolors";
@@ -2326,13 +2620,13 @@ function openInBrowser(path) {
2326
2620
  function docs(cwd, options = {}) {
2327
2621
  let outPath;
2328
2622
  if (options.exportStatic) {
2329
- const dir = join7(cwd, "designagent-docs");
2623
+ const dir = join9(cwd, "designagent-docs");
2330
2624
  mkdirSync3(dir, { recursive: true });
2331
- outPath = join7(dir, "index.html");
2625
+ outPath = join9(dir, "index.html");
2332
2626
  } else {
2333
- const dir = join7(cwd, ".designagent");
2627
+ const dir = join9(cwd, ".designagent");
2334
2628
  mkdirSync3(dir, { recursive: true });
2335
- outPath = join7(dir, "docs.html");
2629
+ outPath = join9(dir, "docs.html");
2336
2630
  }
2337
2631
  const reloadSeconds = options.watch ? 2 : undefined;
2338
2632
  const stats = buildDocs(cwd, outPath, reloadSeconds);
@@ -2369,692 +2663,24 @@ function docs(cwd, options = {}) {
2369
2663
  return outPath;
2370
2664
  }
2371
2665
 
2372
- // src/commands/studio.ts
2373
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
2374
- import { join as join9 } from "node:path";
2375
- import { spawn as spawn2 } from "node:child_process";
2376
- import { platform as platform2 } from "node:os";
2377
- import pc3 from "picocolors";
2378
-
2379
- // ../studio/dist/model.js
2380
- import { basename as basename6 } from "node:path";
2381
-
2382
- // ../studio/dist/discovery/components.js
2383
- import { readFileSync as readFileSync13 } from "node:fs";
2384
- import { join as join8 } from "node:path";
2385
-
2386
- // ../studio/dist/discovery/props.js
2387
- import { existsSync as existsSync6 } from "node:fs";
2388
- import { Project } from "ts-morph";
2389
- function literalUnion(typeText) {
2390
- if (!typeText.includes("|"))
2391
- return [];
2392
- const matches = typeText.match(/'[^']*'|"[^"]*"/g);
2393
- if (!matches)
2394
- return [];
2395
- const parts = typeText.split("|").map((p) => p.trim());
2396
- if (matches.length < parts.length)
2397
- return [];
2398
- return matches.map((m) => m.slice(1, -1));
2399
- }
2400
- function propsFromMembers(decl) {
2401
- const props = [];
2402
- const variants = [];
2403
- const members = "getProperties" in decl && typeof decl.getProperties === "function" ? decl.getProperties() : [];
2404
- for (const m of members) {
2405
- let name;
2406
- let typeText = "unknown";
2407
- let required = true;
2408
- let description;
2409
- try {
2410
- name = m.getName();
2411
- const node = m.getTypeNode();
2412
- typeText = node ? node.getText() : m.getType().getText();
2413
- required = !m.hasQuestionToken();
2414
- const docs2 = m.getJsDocs();
2415
- if (docs2.length) {
2416
- const d = docs2[docs2.length - 1].getDescription().trim();
2417
- if (d)
2418
- description = d;
2419
- }
2420
- } catch {
2421
- continue;
2422
- }
2423
- props.push({ name, type: typeText, required, description });
2424
- const values = literalUnion(typeText);
2425
- if (values.length >= 2)
2426
- variants.push({ prop: name, values });
2427
- }
2428
- return { props, variants };
2429
- }
2430
- function discoverProps(absFilePath, componentName2) {
2431
- if (!existsSync6(absFilePath))
2432
- return { props: [], variants: [] };
2433
- let source;
2434
- try {
2435
- const project = new Project({
2436
- useInMemoryFileSystem: false,
2437
- skipAddingFilesFromTsConfig: true,
2438
- compilerOptions: { allowJs: true, jsx: 4 }
2439
- });
2440
- source = project.addSourceFileAtPath(absFilePath);
2441
- } catch {
2442
- return { props: [], variants: [] };
2443
- }
2444
- try {
2445
- const wanted = `${componentName2}Props`;
2446
- const iface = source.getInterface(wanted) ?? source.getInterfaces().find((i) => /props$/i.test(i.getName()));
2447
- if (iface) {
2448
- const out = propsFromMembers(iface);
2449
- const docs2 = iface.getJsDocs();
2450
- if (docs2.length) {
2451
- const d = docs2[docs2.length - 1].getDescription().trim();
2452
- if (d)
2453
- out.description = d;
2454
- }
2455
- return out;
2456
- }
2457
- const alias = source.getTypeAlias(wanted) ?? source.getTypeAliases().find((t) => /props$/i.test(t.getName()));
2458
- if (alias) {
2459
- const props = [];
2460
- const variants = [];
2461
- const body = alias.getTypeNode()?.getText() ?? "";
2462
- const memberRe = /([a-zA-Z_$][\w$]*)\s*(\?)?\s*:\s*([^;\n}]+)/g;
2463
- let m;
2464
- while ((m = memberRe.exec(body)) !== null) {
2465
- const name = m[1];
2466
- const required = !m[2];
2467
- const typeText = m[3].trim();
2468
- props.push({ name, type: typeText, required });
2469
- const values = literalUnion(typeText);
2470
- if (values.length >= 2)
2471
- variants.push({ prop: name, values });
2472
- }
2473
- return { props, variants };
2474
- }
2475
- } catch {}
2476
- return { props: [], variants: [] };
2477
- }
2478
-
2479
- // ../studio/dist/discovery/components.js
2480
- var COMPONENT_DIRS = ["components/", "src/components/", "ui/", "src/ui/", "app/components/"];
2481
- function isComponentFile(file) {
2482
- const f = file.replace(/\\/g, "/");
2483
- return COMPONENT_DIRS.some((d) => f.includes(d)) || /\.(t|j)sx$/.test(f);
2484
- }
2485
- function escapeRe(s) {
2486
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2487
- }
2488
- function bareForms(name) {
2489
- const base = name.replace(/^colors\.|^spacing\.|^rounded\./, "");
2490
- const forms = new Set([base]);
2491
- forms.add(base.replace(/^(color|colour|spacing|space|radius|rounded)-/, ""));
2492
- return [...forms].filter(Boolean);
2493
- }
2494
- function findTokenRefs(fileText, tokenNames) {
2495
- const refs = new Set;
2496
- const UTIL = "(?:bg|text|border|fill|stroke|ring|p|m|px|py|pt|pb|pl|pr|mx|my|gap|space|rounded|w|h)";
2497
- for (const name of tokenNames) {
2498
- for (const bare of bareForms(name)) {
2499
- if (!bare || /^\d+$/.test(bare) === false && bare.length < 2)
2500
- continue;
2501
- const e = escapeRe(bare);
2502
- const patterns = [
2503
- new RegExp(`var\\(--(?:color-|colour-|spacing-|space-|radius-|rounded-)?${e}\\b`),
2504
- new RegExp(`${UTIL}-${e}\\b`)
2505
- ];
2506
- if (patterns.some((re) => re.test(fileText))) {
2507
- refs.add(name);
2508
- break;
2509
- }
2510
- }
2511
- }
2512
- return [...refs].sort();
2513
- }
2514
- function discoverComponents(cwd, report) {
2515
- const tokenNames = [
2516
- ...report.colors.clusters.map((c) => `colors.${c.name}`),
2517
- ...report.spacing.values.map((v) => `spacing.${v}`)
2518
- ];
2519
- const driftByFile = new Map;
2520
- for (const d of report.drift) {
2521
- const list = driftByFile.get(d.file) ?? [];
2522
- list.push(d);
2523
- driftByFile.set(d.file, list);
2524
- }
2525
- const out = [];
2526
- for (const c of report.components) {
2527
- if (!isComponentFile(c.file))
2528
- continue;
2529
- const abs = join8(cwd, c.file);
2530
- let fileText = "";
2531
- try {
2532
- fileText = readFileSync13(abs, "utf8");
2533
- } catch {}
2534
- const { props, variants, description } = discoverProps(abs, c.name);
2535
- const tokenRefs = fileText ? findTokenRefs(fileText, tokenNames) : [];
2536
- const drift = driftByFile.get(c.file) ?? [];
2537
- const variantSets = [...variants];
2538
- if (c.variants?.length)
2539
- variantSets.push({ prop: "story", values: c.variants });
2540
- out.push({
2541
- name: c.name,
2542
- file: c.file,
2543
- description,
2544
- props,
2545
- variants: variantSets,
2546
- tokenRefs,
2547
- drift,
2548
- stories: c.variants
2549
- });
2550
- }
2551
- const seen = new Map;
2552
- for (const c of out)
2553
- if (!seen.has(c.name))
2554
- seen.set(c.name, c);
2555
- return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
2556
- }
2557
-
2558
- // ../studio/dist/model.js
2559
- function buildStudioModel(cwd, options = {}) {
2560
- const report = scan(cwd, { now: options.now ?? null });
2561
- const components = discoverComponents(cwd, report);
2562
- return {
2563
- project: basename6(cwd) || "project",
2564
- generatedAt: options.now ?? null,
2565
- tokens: {
2566
- colors: report.colors.clusters.map((c) => ({
2567
- name: `colors.${c.name}`,
2568
- hex: c.hex,
2569
- count: c.count,
2570
- members: c.members
2571
- })),
2572
- spacing: report.spacing,
2573
- radius: report.radius.values,
2574
- typography: report.typography.map((t) => ({
2575
- name: t.name,
2576
- fontSize: t.fontSize,
2577
- fontFamily: t.fontFamily,
2578
- fontWeight: t.fontWeight
2579
- }))
2580
- },
2581
- components,
2582
- drift: report.drift,
2583
- detected: report.detected
2584
- };
2585
- }
2586
- // ../studio/dist/styles.js
2587
- var STUDIO_STYLES = `
2588
- :root{
2589
- --bg:#0a0b0d; --panel:#121419; --panel-2:#181b21; --border:#23272f;
2590
- --text:#e9ecf1; --text-2:#969db0; --text-3:#646b7a;
2591
- --accent:#6ea8fe; --good:#56d364; --warn:#e3b341; --bad:#f4716a;
2592
- --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
2593
- --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
2594
- }
2595
- *{box-sizing:border-box;margin:0;padding:0}
2596
- html,body{height:100%}
2597
- body{font-family:var(--sans);background:var(--bg);color:var(--text);display:flex;height:100vh;overflow:hidden}
2598
-
2599
- /* Sidebar */
2600
- .sidebar{width:248px;flex-shrink:0;background:var(--panel);border-right:1px solid var(--border);overflow-y:auto;padding:16px 0}
2601
- .brand{padding:0 16px 14px;display:flex;align-items:baseline;gap:8px}
2602
- .brand b{font-size:15px;font-weight:650}
2603
- .brand span{font-size:11px;color:var(--text-3);font-family:var(--mono)}
2604
- .filter{margin:0 12px 12px;width:calc(100% - 24px);background:var(--panel-2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;padding:7px 10px;font-family:var(--sans)}
2605
- .filter::placeholder{color:var(--text-3)}
2606
- .nav-group{font-size:10px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--text-3);padding:14px 16px 6px}
2607
- .nav-item{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 16px;font-size:13px;color:var(--text-2);cursor:pointer;border-left:2px solid transparent}
2608
- .nav-item:hover{background:var(--panel-2);color:var(--text)}
2609
- .nav-item.active{background:var(--panel-2);color:var(--text);border-left-color:var(--accent)}
2610
- .nav-item .tag{font-size:10px;font-family:var(--mono);color:var(--text-3)}
2611
- .nav-item .tag.bad{color:var(--bad)}
2612
-
2613
- /* Main */
2614
- .main{flex:1;overflow-y:auto;padding:32px 40px 80px}
2615
- .main-head{margin-bottom:22px}
2616
- .main-head h1{font-size:22px;font-weight:650;margin-bottom:3px}
2617
- .main-head .sub{font-size:13px;color:var(--text-2)}
2618
- .main-head .path{font-family:var(--mono);font-size:12px;color:var(--text-3)}
2619
- h2.sec{font-size:12px;font-weight:600;letter-spacing:.07em;text-transform:uppercase;color:var(--text-3);margin:26px 0 12px}
2620
- .empty{color:var(--text-3);font-size:14px;padding:14px 0}
2621
- .muted{color:var(--text-3)}
2622
- code{font-family:var(--mono);font-size:12px;background:var(--panel-2);padding:1px 5px;border-radius:5px}
2623
- .badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;padding:1px 6px;border-radius:5px;background:var(--panel-2);color:var(--text-3)}
2624
- .badge.good{background:rgba(86,211,100,.14);color:var(--good)}
2625
- .badge.bad{background:rgba(244,113,106,.14);color:var(--bad)}
2626
- .badge.warn{background:rgba(227,179,65,.14);color:var(--warn)}
2627
-
2628
- /* Swatches */
2629
- .swatch-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px}
2630
- .swatch{background:var(--panel);border:1px solid var(--border);border-radius:11px;overflow:hidden}
2631
- .swatch .chip{height:78px;display:flex;align-items:flex-end;padding:8px}
2632
- .swatch .chip .ratio{font-size:11px;font-family:var(--mono)}
2633
- .swatch .meta{padding:9px 11px}
2634
- .swatch .nm{font-family:var(--mono);font-size:12px;font-weight:600}
2635
- .swatch .hx{font-family:var(--mono);font-size:12px;color:var(--text-2);text-transform:uppercase}
2636
- .swatch .ct{font-size:11px;color:var(--text-3);margin-top:2px}
2637
-
2638
- /* Ruler */
2639
- .ruler{display:flex;flex-direction:column;gap:6px}
2640
- .ruler-row{display:grid;grid-template-columns:120px 1fr;align-items:center;gap:12px}
2641
- .ruler-row .lbl{font-family:var(--mono);font-size:12px;color:var(--text-2)}
2642
- .ruler-row .track{background:var(--panel);border-radius:6px;height:18px;overflow:hidden}
2643
- .ruler-row .bar{height:100%;background:var(--accent)}
2644
- .ruler-row.off .bar{background:var(--warn)}
2645
-
2646
- /* Type list */
2647
- .type-row{display:grid;grid-template-columns:90px 1fr;gap:16px;align-items:center;padding:10px 12px;background:var(--panel);border:1px solid var(--border);border-radius:10px;margin-bottom:6px}
2648
- .type-row .prev{font-size:26px;line-height:1;text-align:center}
2649
- .type-row .nm{font-family:var(--mono);font-size:13px;font-weight:600}
2650
- .type-row .pp{font-size:12px;color:var(--text-2)}
2651
-
2652
- /* Component viewer */
2653
- .tabs{display:flex;gap:4px;margin-bottom:16px;border-bottom:1px solid var(--border)}
2654
- .tab{font-size:13px;color:var(--text-2);padding:8px 12px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px}
2655
- .tab:hover{color:var(--text)}
2656
- .tab.active{color:var(--text);border-bottom-color:var(--accent)}
2657
- .panel-box{background:var(--panel);border:1px solid var(--border);border-radius:11px;padding:16px}
2658
- table.props{width:100%;border-collapse:collapse;font-size:13px}
2659
- table.props th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-3);padding:6px 10px;border-bottom:1px solid var(--border)}
2660
- table.props td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:top}
2661
- table.props td .req{color:var(--bad);font-size:11px}
2662
- .chips{display:flex;flex-wrap:wrap;gap:6px}
2663
- .chip-tok{font-family:var(--mono);font-size:12px;padding:3px 8px;border-radius:6px;background:rgba(86,211,100,.1);color:var(--good)}
2664
- .chip-drift{font-family:var(--mono);font-size:12px;padding:3px 8px;border-radius:6px;background:rgba(244,113,106,.1);color:var(--bad)}
2665
- .kv{display:flex;gap:8px;font-size:13px;padding:5px 0;border-bottom:1px solid var(--border)}
2666
- .kv .k{width:160px;color:var(--text-3);font-family:var(--mono);font-size:12px}
2667
- pre.code{background:var(--panel-2);border:1px solid var(--border);border-radius:9px;padding:12px;overflow:auto;font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--text)}
2668
- .drift-table{width:100%;border-collapse:collapse;font-size:13px}
2669
- .drift-table th{text-align:left;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-3);padding:6px 10px;border-bottom:1px solid var(--border)}
2670
- .drift-table td{padding:7px 10px;border-bottom:1px solid var(--border)}
2671
- .dot{display:inline-block;width:11px;height:11px;border-radius:3px;border:1px solid var(--border);vertical-align:-1px;margin-right:6px}
2672
- .suggest{color:var(--good);font-family:var(--mono);font-size:12px}
2673
- .variant-pills{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
2674
- .vp{font-size:12px;padding:4px 10px;border-radius:7px;border:1px solid var(--border);color:var(--text-2);cursor:pointer;background:var(--panel)}
2675
- .vp.active{border-color:var(--accent);color:var(--text)}
2676
- `;
2677
-
2678
- // ../studio/dist/client.js
2679
- var STUDIO_CLIENT = String.raw`
2680
- (function(){
2681
- "use strict";
2682
- var M = window.MODEL;
2683
- var state = { view: "tokens:colors", componentTab: "preview", variant: null };
2684
-
2685
- function el(tag, attrs, html){
2686
- var e = document.createElement(tag);
2687
- if (attrs) for (var k in attrs){ if(k==="class") e.className=attrs[k]; else e.setAttribute(k, attrs[k]); }
2688
- if (html != null) e.innerHTML = html;
2689
- return e;
2690
- }
2691
- function esc(s){ return String(s==null?"":s).replace(/[&<>"']/g, function(c){ return {"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[c]; }); }
2692
- function rgb(hex){ hex=hex.replace("#",""); return {r:parseInt(hex.slice(0,2),16),g:parseInt(hex.slice(2,4),16),b:parseInt(hex.slice(4,6),16)}; }
2693
- function lin(c){ c/=255; return c<=0.04045?c/12.92:Math.pow((c+0.055)/1.055,2.4); }
2694
- function lum(hex){ var c=rgb(hex); return 0.2126*lin(c.r)+0.7152*lin(c.g)+0.0722*lin(c.b); }
2695
- function contrast(a,b){ var la=lum(a),lb=lum(b); var hi=Math.max(la,lb),lo=Math.min(la,lb); return (hi+0.05)/(lo+0.05); }
2696
- function readable(hex){ return contrast(hex,"#ffffff")>=contrast(hex,"#111111")?"#ffffff":"#111111"; }
2697
- function rating(r){ return r>=7?"AAA":r>=4.5?"AA":r>=3?"AA large":"Fail"; }
2698
-
2699
- // ---- Sidebar ----
2700
- function buildSidebar(){
2701
- var sb = document.querySelector(".sidebar");
2702
- sb.innerHTML = "";
2703
- sb.appendChild(el("div",{class:"brand"},"<b>"+esc(M.project)+"</b><span>studio</span>"));
2704
- var filter = el("input",{class:"filter",placeholder:"Filter components…"});
2705
- filter.addEventListener("input", function(){ renderComponentNav(filter.value.toLowerCase()); });
2706
- sb.appendChild(filter);
2707
-
2708
- sb.appendChild(el("div",{class:"nav-group"},"Tokens"));
2709
- [["tokens:colors","Colors", M.tokens.colors.length],
2710
- ["tokens:typography","Typography", M.tokens.typography.length],
2711
- ["tokens:spacing","Spacing", M.tokens.spacing.values.length]
2712
- ].forEach(function(it){ sb.appendChild(navItem(it[0],it[1],it[2])); });
2713
-
2714
- sb.appendChild(el("div",{class:"nav-group"},"Components"));
2715
- var holder = el("div",{class:"comp-nav"});
2716
- sb.appendChild(holder);
2717
-
2718
- sb.appendChild(el("div",{class:"nav-group"},"Report"));
2719
- var driftBad = M.drift.length>0;
2720
- var di = navItem("drift","Drift report", M.drift.length);
2721
- if (driftBad) di.querySelector(".tag").className = "tag bad";
2722
- sb.appendChild(di);
2723
-
2724
- renderComponentNav("");
2725
- }
2726
- function navItem(view,label,count){
2727
- var it = el("div",{class:"nav-item"+(state.view===view?" active":"")});
2728
- it.innerHTML = "<span>"+esc(label)+"</span><span class='tag'>"+count+"</span>";
2729
- it.addEventListener("click", function(){ state.view=view; state.variant=null; render(); });
2730
- return it;
2731
- }
2732
- function renderComponentNav(q){
2733
- var holder = document.querySelector(".comp-nav"); if(!holder) return;
2734
- holder.innerHTML = "";
2735
- M.components.filter(function(c){ return c.name.toLowerCase().indexOf(q)>=0; }).forEach(function(c){
2736
- var view = "component:"+c.name;
2737
- var it = el("div",{class:"nav-item"+(state.view===view?" active":"")});
2738
- var tag = c.drift.length? "<span class='tag bad'>"+c.drift.length+"</span>" : "";
2739
- it.innerHTML = "<span>"+esc(c.name)+"</span>"+tag;
2740
- it.addEventListener("click", function(){ state.view=view; state.componentTab="preview"; state.variant=null; render(); });
2741
- holder.appendChild(it);
2742
- });
2743
- if (!M.components.length) holder.appendChild(el("div",{class:"nav-item muted"},"<span>none found</span>"));
2744
- }
2745
-
2746
- // ---- Main ----
2747
- function head(title, sub, path){
2748
- var h = el("div",{class:"main-head"});
2749
- h.innerHTML = "<h1>"+esc(title)+"</h1>"+(sub?"<div class='sub'>"+sub+"</div>":"")+(path?"<div class='path'>"+esc(path)+"</div>":"");
2750
- return h;
2751
- }
2752
-
2753
- function viewColors(main){
2754
- main.appendChild(head("Colors", M.tokens.colors.length+" tokens"));
2755
- if(!M.tokens.colors.length){ main.appendChild(el("div",{class:"empty"},"No colors found.")); return; }
2756
- var grid = el("div",{class:"swatch-grid"});
2757
- M.tokens.colors.forEach(function(c){
2758
- var t = readable(c.hex), r = contrast(c.hex,t);
2759
- var dup = c.members.length>1? "<div class='ct' style='color:var(--warn)'>+"+(c.members.length-1)+" near-dup</div>":"";
2760
- var s = el("div",{class:"swatch"});
2761
- s.innerHTML = "<div class='chip' style='background:"+esc(c.hex)+";color:"+t+"'><span class='ratio'>"+r.toFixed(1)+":1 "+rating(r)+"</span></div>"+
2762
- "<div class='meta'><div class='nm'>"+esc(c.name)+"</div><div class='hx'>"+esc(c.hex)+"</div><div class='ct'>"+c.count+" uses</div>"+dup+"</div>";
2763
- grid.appendChild(s);
2764
- });
2765
- main.appendChild(grid);
2766
- }
2767
- function viewTypography(main){
2768
- main.appendChild(head("Typography", M.tokens.typography.length+" tokens"));
2769
- if(!M.tokens.typography.length){ main.appendChild(el("div",{class:"empty"},"No typography tokens found.")); return; }
2770
- M.tokens.typography.forEach(function(t){
2771
- var row = el("div",{class:"type-row"});
2772
- var fs = t.fontSize && /^[0-9]/.test(t.fontSize) ? t.fontSize : "";
2773
- var prev = fs ? "<div class='prev' style='font-size:"+esc(fs)+";font-family:"+esc(t.fontFamily||"inherit")+"'>Ag</div>" : "<div class='prev muted'>Ag</div>";
2774
- row.innerHTML = prev+"<div><div class='nm'>"+esc(t.name)+"</div><div class='pp'>"+esc(t.fontSize||"—")+(t.fontFamily?" · "+esc(t.fontFamily):"")+(t.fontWeight?" · "+t.fontWeight:"")+"</div></div>";
2775
- main.appendChild(row);
2776
- });
2777
- }
2778
- function viewSpacing(main){
2779
- var sp = M.tokens.spacing;
2780
- main.appendChild(head("Spacing", sp.grid? sp.grid+"pt grid · "+sp.outliers.length+" off-grid":"no grid detected"));
2781
- if(!sp.values.length){ main.appendChild(el("div",{class:"empty"},"No spacing scale detected.")); return; }
2782
- var max = Math.max.apply(null, sp.values.concat([1]));
2783
- var ruler = el("div",{class:"ruler"});
2784
- sp.values.forEach(function(v){
2785
- var off = sp.outliers.indexOf(v)>=0;
2786
- var w = Math.max(2, Math.round(v/max*100));
2787
- var row = el("div",{class:"ruler-row"+(off?" off":"")});
2788
- row.innerHTML = "<div class='lbl'>"+v+"px"+(off?" <span class='badge warn'>off</span>":"")+"</div><div class='track'><div class='bar' style='width:"+w+"%'></div></div>";
2789
- ruler.appendChild(row);
2790
- });
2791
- main.appendChild(ruler);
2792
- }
2793
- function viewDrift(main){
2794
- main.appendChild(head("Drift report", M.drift.length+" hardcoded value"+(M.drift.length===1?"":"s")));
2795
- if(!M.drift.length){ main.appendChild(el("div",{class:"empty"},"✓ No drift. Everything references a token.")); return; }
2796
- var t = el("table",{class:"drift-table"});
2797
- t.innerHTML = "<thead><tr><th>Location</th><th>Value</th><th>Suggested token</th></tr></thead>";
2798
- var tb = el("tbody");
2799
- M.drift.forEach(function(d){
2800
- var chip = d.kind==="color"? "<span class='dot' style='background:"+esc(d.value)+"'></span>":"";
2801
- var sug = d.suggestion? "<span class='suggest'>"+esc(d.suggestion)+"</span>":"<span class='muted'>—</span>";
2802
- tb.appendChild(el("tr",null,"<td><code>"+esc(d.file)+"</code>:"+d.line+"</td><td>"+chip+"<code>"+esc(d.value)+"</code></td><td>"+sug+"</td>"));
2803
- });
2804
- t.appendChild(tb); main.appendChild(t);
2805
- }
2806
-
2807
- function viewComponent(main, name){
2808
- var c = M.components.filter(function(x){return x.name===name;})[0];
2809
- if(!c){ main.appendChild(el("div",{class:"empty"},"Component not found.")); return; }
2810
- main.appendChild(head(c.name, c.description? esc(c.description) : (c.props.length+" props · "+c.tokenRefs.length+" tokens · "+c.drift.length+" drift"), c.file));
2811
-
2812
- var tabs = el("div",{class:"tabs"});
2813
- [["preview","Preview"],["props","Props"],["tokens","Tokens"],["drift","Drift"],["spec","Spec"]].forEach(function(t){
2814
- var tab = el("div",{class:"tab"+(state.componentTab===t[0]?" active":"")}, t[1]);
2815
- tab.addEventListener("click", function(){ state.componentTab=t[0]; render(); });
2816
- tabs.appendChild(tab);
2817
- });
2818
- main.appendChild(tabs);
2819
-
2820
- var box = el("div");
2821
- if (state.componentTab==="preview") renderPreview(box, c);
2822
- else if (state.componentTab==="props") renderProps(box, c);
2823
- else if (state.componentTab==="tokens") renderTokens(box, c);
2824
- else if (state.componentTab==="drift") renderCompDrift(box, c);
2825
- else if (state.componentTab==="spec") renderSpec(box, c);
2826
- main.appendChild(box);
2827
- }
2828
- function renderPreview(box, c){
2829
- if (c.variants.length){
2830
- var pills = el("div",{class:"variant-pills"});
2831
- c.variants.forEach(function(v){
2832
- v.values.forEach(function(val){
2833
- var key = v.prop+"="+val;
2834
- var p = el("div",{class:"vp"+(state.variant===key?" active":"")}, esc(v.prop)+": "+esc(val));
2835
- p.addEventListener("click", function(){ state.variant = state.variant===key?null:key; render(); });
2836
- pills.appendChild(p);
2837
- });
2838
- });
2839
- box.appendChild(pills);
2840
- }
2841
- var note = "Live component rendering needs a bundler (v2). For now Studio shows the API, token usage, drift, and an auto-spec.";
2842
- var ex = el("div",{class:"panel-box"});
2843
- var usage = "<"+c.name+(state.variant? " "+esc(state.variant.replace("=", "=\""))+"\"" : "")+" />";
2844
- ex.innerHTML = "<div class='muted' style='font-size:12px;margin-bottom:10px'>"+note+"</div><pre class='code'>"+esc(usage)+"</pre>";
2845
- box.appendChild(ex);
2846
- }
2847
- function renderProps(box, c){
2848
- if(!c.props.length){ box.appendChild(el("div",{class:"empty"},"No typed props found.")); return; }
2849
- var t = el("table",{class:"props"});
2850
- t.innerHTML="<thead><tr><th>Prop</th><th>Type</th><th>Required</th><th>Notes</th></tr></thead>";
2851
- var tb=el("tbody");
2852
- c.props.forEach(function(p){
2853
- tb.appendChild(el("tr",null,"<td><code>"+esc(p.name)+"</code></td><td><code>"+esc(p.type)+"</code></td><td>"+(p.required?"<span class='req'>required</span>":"<span class='muted'>optional</span>")+"</td><td class='muted'>"+(p.description?esc(p.description):"")+"</td>"));
2854
- });
2855
- t.appendChild(tb);
2856
- var wrap = el("div",{class:"panel-box"}); wrap.appendChild(t); box.appendChild(wrap);
2857
- }
2858
- function renderTokens(box, c){
2859
- var wrap = el("div",{class:"panel-box"});
2860
- wrap.appendChild(el("h2",{class:"sec"},"Using correctly ("+c.tokenRefs.length+")"));
2861
- if(c.tokenRefs.length){ var ch=el("div",{class:"chips"}); c.tokenRefs.forEach(function(t){ ch.appendChild(el("span",{class:"chip-tok"},esc(t))); }); wrap.appendChild(ch); }
2862
- else wrap.appendChild(el("div",{class:"empty"},"No token references detected."));
2863
- wrap.appendChild(el("h2",{class:"sec"},"Hardcoded / drift ("+c.drift.length+")"));
2864
- if(c.drift.length){ var cd=el("div",{class:"chips"}); c.drift.forEach(function(d){ cd.appendChild(el("span",{class:"chip-drift"},esc(d.value)+(d.suggestion?" → "+esc(d.suggestion):""))); }); wrap.appendChild(cd); }
2865
- else wrap.appendChild(el("div",{class:"empty good muted"},"✓ no drift in this component"));
2866
- box.appendChild(wrap);
2867
- }
2868
- function renderCompDrift(box, c){
2869
- if(!c.drift.length){ box.appendChild(el("div",{class:"empty"},"✓ No hardcoded values in this component.")); return; }
2870
- var t=el("table",{class:"drift-table"});
2871
- t.innerHTML="<thead><tr><th>Value</th><th>Line</th><th>Fix</th></tr></thead>";
2872
- var tb=el("tbody");
2873
- c.drift.forEach(function(d){
2874
- var chip=d.kind==="color"?"<span class='dot' style='background:"+esc(d.value)+"'></span>":"";
2875
- tb.appendChild(el("tr",null,"<td>"+chip+"<code>"+esc(d.value)+"</code></td><td class='muted'>"+esc(c.file)+":"+d.line+"</td><td>"+(d.suggestion?"<span class='suggest'>"+esc(d.suggestion)+"</span>":"<span class='muted'>add a token</span>")+"</td>"));
2876
- });
2877
- t.appendChild(tb); var w=el("div",{class:"panel-box"}); w.appendChild(t); box.appendChild(w);
2878
- }
2879
- function renderSpec(box, c){
2880
- var w=el("div",{class:"panel-box"});
2881
- var rows="";
2882
- function kv(k,v){ return "<div class='kv'><div class='k'>"+esc(k)+"</div><div>"+v+"</div></div>"; }
2883
- rows+=kv("Component","<code>"+esc(c.name)+"</code>");
2884
- rows+=kv("Source","<code>"+esc(c.file)+"</code>");
2885
- rows+=kv("Props", c.props.length? c.props.map(function(p){return "<code>"+esc(p.name)+"</code>";}).join(" ") : "<span class='muted'>none</span>");
2886
- c.variants.forEach(function(v){ rows+=kv("Variant: "+v.prop, v.values.map(function(x){return "<code>"+esc(x)+"</code>";}).join(" ")); });
2887
- rows+=kv("Tokens used", c.tokenRefs.length? c.tokenRefs.map(function(t){return "<span class='suggest'>"+esc(t)+"</span>";}).join(" ") : "<span class='muted'>none detected</span>");
2888
- rows+=kv("Drift", c.drift.length? "<span class='badge bad'>"+c.drift.length+"</span>" : "<span class='badge good'>clean</span>");
2889
- w.innerHTML=rows; box.appendChild(w);
2890
- }
2891
-
2892
- function render(){
2893
- buildSidebar();
2894
- var main = document.querySelector(".main"); main.innerHTML="";
2895
- var v = state.view;
2896
- if (v==="tokens:colors") viewColors(main);
2897
- else if (v==="tokens:typography") viewTypography(main);
2898
- else if (v==="tokens:spacing") viewSpacing(main);
2899
- else if (v==="drift") viewDrift(main);
2900
- else if (v.indexOf("component:")===0) viewComponent(main, v.slice("component:".length));
2901
- else viewColors(main);
2902
- }
2903
-
2904
- // Live reload via SSE (watch mode only — endpoint may not exist).
2905
- try {
2906
- var es = new EventSource("/events");
2907
- es.onmessage = function(){ location.reload(); };
2908
- es.onerror = function(){ es.close(); };
2909
- } catch(e){}
2910
-
2911
- render();
2912
- })();
2913
- `;
2914
-
2915
- // ../studio/dist/renderer.js
2916
- function renderStudio(model) {
2917
- const json = JSON.stringify(model).replace(/</g, "\\u003c");
2918
- return `<!doctype html>
2919
- <html lang="en">
2920
- <head>
2921
- <meta charset="utf-8">
2922
- <meta name="viewport" content="width=device-width, initial-scale=1">
2923
- <title>${escapeHtml(model.project)} — DesignAgent Studio</title>
2924
- <style>${STUDIO_STYLES}</style>
2925
- </head>
2926
- <body>
2927
- <aside class="sidebar"></aside>
2928
- <main class="main"></main>
2929
- <script>window.MODEL=${json};</script>
2930
- <script>${STUDIO_CLIENT}</script>
2931
- </body>
2932
- </html>
2933
- `;
2934
- }
2935
- function escapeHtml(s) {
2936
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2937
- }
2938
- // ../studio/dist/server.js
2939
- import { createServer } from "node:http";
2940
- import { watch as watch2 } from "node:fs";
2941
- var DEFAULT_PORT = 4321;
2942
- function startStudioServer(cwd, options = {}) {
2943
- const port = options.port ?? DEFAULT_PORT;
2944
- const clients = new Set;
2945
- const server = createServer((req, res) => {
2946
- if (req.url === "/events") {
2947
- res.writeHead(200, {
2948
- "content-type": "text/event-stream",
2949
- "cache-control": "no-cache",
2950
- connection: "keep-alive"
2951
- });
2952
- res.write(`: connected
2953
-
2954
- `);
2955
- clients.add(res);
2956
- req.on("close", () => clients.delete(res));
2957
- return;
2958
- }
2959
- const html = renderStudio(buildStudioModel(cwd));
2960
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
2961
- res.end(html);
2962
- });
2963
- if (options.watch) {
2964
- let timer = null;
2965
- try {
2966
- watch2(cwd, { recursive: true }, (_e, filename) => {
2967
- if (!filename)
2968
- return;
2969
- const f = filename.toString();
2970
- if (f.includes(".designagent") || f.includes("node_modules") || f.includes("designagent-studio"))
2971
- return;
2972
- if (timer)
2973
- clearTimeout(timer);
2974
- timer = setTimeout(() => {
2975
- for (const c of clients)
2976
- c.write(`data: reload
2977
-
2978
- `);
2979
- }, 150);
2980
- });
2981
- } catch {}
2982
- }
2983
- return new Promise((resolve3) => {
2984
- server.listen(port, () => {
2985
- resolve3({
2986
- server,
2987
- port,
2988
- url: `http://localhost:${port}`,
2989
- close: () => {
2990
- for (const c of clients)
2991
- c.end();
2992
- server.close();
2993
- }
2994
- });
2995
- });
2996
- });
2997
- }
2998
- // src/commands/studio.ts
2999
- function openInBrowser2(target) {
3000
- const os = platform2();
3001
- const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
3002
- try {
3003
- const child = spawn2(cmd, [target], { stdio: "ignore", detached: true, shell: os === "win32" });
3004
- child.on("error", () => {});
3005
- child.unref();
3006
- } catch {}
3007
- }
3008
- async function studio(cwd, options = {}) {
3009
- if (options.exportStatic) {
3010
- const model2 = buildStudioModel(cwd);
3011
- const dir = join9(cwd, "designagent-studio");
3012
- mkdirSync4(dir, { recursive: true });
3013
- const out = join9(dir, "index.html");
3014
- writeFileSync3(out, renderStudio(model2), "utf8");
3015
- console.log(pc3.green("✓ ") + `Studio exported to ${pc3.bold(out)}
3016
- ` + pc3.dim(` ${model2.components.length} components · ${model2.tokens.colors.length} color tokens · ${model2.drift.length} drift`));
3017
- return;
3018
- }
3019
- const { url, close } = await startStudioServer(cwd, {
3020
- port: options.port,
3021
- watch: options.watch
3022
- });
3023
- const model = buildStudioModel(cwd);
3024
- console.log(pc3.green("◆ ") + pc3.bold("DesignAgent Studio") + ` running at ${pc3.cyan(url)}
3025
- ` + pc3.dim(` ${model.components.length} components · ${model.tokens.colors.length} color tokens · ${model.drift.length} drift`) + (options.watch ? pc3.dim(`
3026
- watching for changes — Ctrl-C to stop`) : pc3.dim(`
3027
- Ctrl-C to stop`)));
3028
- if (!options.noOpen)
3029
- openInBrowser2(url);
3030
- const shutdown = () => {
3031
- close();
3032
- process.exit(0);
3033
- };
3034
- process.on("SIGINT", shutdown);
3035
- process.on("SIGTERM", shutdown);
3036
- }
3037
-
3038
2666
  // src/index.ts
3039
2667
  var require2 = createRequire(import.meta.url);
3040
2668
  var { version } = require2("../package.json");
3041
2669
  var HELP = `
3042
- ${pc4.bold("designagent")} ${pc4.dim(`v${version}`)} — the design brain Claude Code needs.
2670
+ ${pc3.bold("designagent")} ${pc3.dim(`v${version}`)} — the design brain Claude Code needs.
3043
2671
 
3044
- ${pc4.bold("Usage")}
2672
+ ${pc3.bold("Usage")}
3045
2673
  npx designagent <command>
3046
2674
 
3047
- ${pc4.bold("Commands")}
2675
+ ${pc3.bold("Commands")}
3048
2676
  init Set up DESIGN.md, CLAUDE.md, and DECISIONS.md in this project
3049
2677
  docs Scan the codebase and open the visual design-system docs
3050
- ${pc4.dim("--export static HTML · --watch live-reload · --no-open")}
3051
- studio Launch DesignAgent Studio the AI-native Storybook alternative
3052
- ${pc4.dim("--export static HTML · --watch live-reload · --no-open · --port")}
3053
- update Refresh an existing setup ${pc4.dim("(coming soon)")}
2678
+ ${pc3.dim("--export static HTML · --watch live-reload · --no-open")}
2679
+ update Refresh an existing setup ${pc3.dim("(coming soon)")}
3054
2680
  help Show this help
3055
2681
  version Print the version
3056
2682
 
3057
- ${pc4.dim("Docs: https://designagent.dev")}
2683
+ ${pc3.dim("Docs: https://designagent.dev")}
3058
2684
  `;
3059
2685
  async function main() {
3060
2686
  const command = process.argv[2] ?? "init";
@@ -3071,20 +2697,9 @@ async function main() {
3071
2697
  });
3072
2698
  break;
3073
2699
  }
3074
- case "studio": {
3075
- const args = process.argv.slice(3);
3076
- const portArg = args.find((a) => a.startsWith("--port="));
3077
- await studio(process.cwd(), {
3078
- exportStatic: args.includes("--export"),
3079
- watch: args.includes("--watch"),
3080
- noOpen: args.includes("--no-open"),
3081
- port: portArg ? Number(portArg.split("=")[1]) : undefined
3082
- });
3083
- break;
3084
- }
3085
2700
  case "update":
3086
- console.log(pc4.yellow("`designagent update` is coming in a future release.") + `
3087
- ` + pc4.dim("For now, re-run ") + pc4.bold("npx designagent init") + pc4.dim(" (it will ask before overwriting your files)."));
2701
+ console.log(pc3.yellow("`designagent update` is coming in a future release.") + `
2702
+ ` + pc3.dim("For now, re-run ") + pc3.bold("npx designagent init") + pc3.dim(" (it will ask before overwriting your files)."));
3088
2703
  break;
3089
2704
  case "version":
3090
2705
  case "--version":
@@ -3097,12 +2712,12 @@ async function main() {
3097
2712
  console.log(HELP);
3098
2713
  break;
3099
2714
  default:
3100
- console.error(pc4.red(`Unknown command: ${command}`));
2715
+ console.error(pc3.red(`Unknown command: ${command}`));
3101
2716
  console.log(HELP);
3102
2717
  process.exit(1);
3103
2718
  }
3104
2719
  }
3105
2720
  main().catch((err) => {
3106
- console.error(pc4.red("designagent failed:"), err instanceof Error ? err.message : err);
2721
+ console.error(pc3.red("designagent failed:"), err instanceof Error ? err.message : err);
3107
2722
  process.exit(1);
3108
2723
  });