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.
- package/README.md +18 -7
- package/dist/index.js +418 -803
- 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
|
|
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
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
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
|
|
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
|
|
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
|
|
1633
|
+
existsSync as existsSync6,
|
|
1443
1634
|
mkdirSync as mkdirSync2,
|
|
1444
|
-
readFileSync as
|
|
1635
|
+
readFileSync as readFileSync13,
|
|
1445
1636
|
renameSync,
|
|
1446
1637
|
writeFileSync
|
|
1447
1638
|
} from "node:fs";
|
|
1448
|
-
import { join as
|
|
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
|
|
1474
|
-
import { dirname, join as
|
|
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 = [
|
|
1669
|
+
const candidates = [join5(here, "templates"), resolve(here, "..", "templates")];
|
|
1479
1670
|
for (const dir of candidates) {
|
|
1480
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1837
|
+
join6(here2, "skills"),
|
|
1647
1838
|
resolve2(here2, "..", "..", "skills")
|
|
1648
1839
|
];
|
|
1649
|
-
return candidates.find((dir) =>
|
|
1840
|
+
return candidates.find((dir) => existsSync5(dir)) ?? null;
|
|
1650
1841
|
}
|
|
1651
1842
|
function skillsInstallDir() {
|
|
1652
|
-
return
|
|
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
|
|
1852
|
+
for (const entry of readdirSync3(src, { withFileTypes: true })) {
|
|
1662
1853
|
if (!entry.isDirectory())
|
|
1663
1854
|
continue;
|
|
1664
|
-
cpSync(
|
|
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 =
|
|
1682
|
-
const claudeMdPath =
|
|
1683
|
-
const decisionsMdPath =
|
|
1684
|
-
const mcpPath =
|
|
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 =
|
|
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 (
|
|
1885
|
+
if (existsSync6(path)) {
|
|
1695
1886
|
mkdirSync2(backupDir, { recursive: true });
|
|
1696
|
-
copyFileSync(path,
|
|
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 (!
|
|
1895
|
+
if (!existsSync6(designMdPath))
|
|
1705
1896
|
writeFileSync(designMdPath, designMd, "utf8");
|
|
1706
|
-
if (!
|
|
1897
|
+
if (!existsSync6(decisionsMdPath))
|
|
1707
1898
|
writeFileSync(decisionsMdPath, decisionsMd, "utf8");
|
|
1708
|
-
if (!
|
|
1899
|
+
if (!existsSync6(claudeMdPath)) {
|
|
1709
1900
|
writeFileSync(claudeMdPath, claudeMd, "utf8");
|
|
1710
1901
|
} else {
|
|
1711
|
-
const current =
|
|
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" || !
|
|
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 =
|
|
1923
|
+
const configDir = join7(cwd, ".designagent");
|
|
1733
1924
|
mkdirSync2(configDir, { recursive: true });
|
|
1734
|
-
writeFileSync(
|
|
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 =
|
|
1749
|
-
if (!
|
|
1939
|
+
const lower = join7(cwd, "design.md");
|
|
1940
|
+
if (!existsSync6(lower))
|
|
1750
1941
|
return null;
|
|
1751
|
-
const 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
|
|
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 =
|
|
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(" —
|
|
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
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
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
|
-
{
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
{
|
|
1910
|
-
|
|
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
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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
|
|
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 =
|
|
2623
|
+
const dir = join9(cwd, "designagent-docs");
|
|
2330
2624
|
mkdirSync3(dir, { recursive: true });
|
|
2331
|
-
outPath =
|
|
2625
|
+
outPath = join9(dir, "index.html");
|
|
2332
2626
|
} else {
|
|
2333
|
-
const dir =
|
|
2627
|
+
const dir = join9(cwd, ".designagent");
|
|
2334
2628
|
mkdirSync3(dir, { recursive: true });
|
|
2335
|
-
outPath =
|
|
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 {"&":"&","<":"<",">":">","\"":""","'":"'"}[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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
${
|
|
2670
|
+
${pc3.bold("designagent")} ${pc3.dim(`v${version}`)} — the design brain Claude Code needs.
|
|
3043
2671
|
|
|
3044
|
-
${
|
|
2672
|
+
${pc3.bold("Usage")}
|
|
3045
2673
|
npx designagent <command>
|
|
3046
2674
|
|
|
3047
|
-
${
|
|
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
|
-
${
|
|
3051
|
-
|
|
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
|
-
${
|
|
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(
|
|
3087
|
-
` +
|
|
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(
|
|
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(
|
|
2721
|
+
console.error(pc3.red("designagent failed:"), err instanceof Error ? err.message : err);
|
|
3107
2722
|
process.exit(1);
|
|
3108
2723
|
});
|