@agent-scope/cli 1.5.0 → 1.7.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/dist/cli.js +359 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +345 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +345 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.d.cts
CHANGED
|
@@ -62,4 +62,9 @@ interface ScopeCLIOptions {
|
|
|
62
62
|
/** Build and return the Scope CLI program */
|
|
63
63
|
declare function createProgram(options?: ScopeCLIOptions): Command;
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Create and return the `tokens` command group.
|
|
67
|
+
*/
|
|
68
|
+
declare function createTokensCommand(): Command;
|
|
69
|
+
|
|
70
|
+
export { type ListRow, type QueryRow, type ScopeCLIOptions, createManifestCommand, createProgram, createTokensCommand, isTTY, matchGlob };
|
package/dist/index.d.ts
CHANGED
|
@@ -62,4 +62,9 @@ interface ScopeCLIOptions {
|
|
|
62
62
|
/** Build and return the Scope CLI program */
|
|
63
63
|
declare function createProgram(options?: ScopeCLIOptions): Command;
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Create and return the `tokens` command group.
|
|
67
|
+
*/
|
|
68
|
+
declare function createTokensCommand(): Command;
|
|
69
|
+
|
|
70
|
+
export { type ListRow, type QueryRow, type ScopeCLIOptions, createManifestCommand, createProgram, createTokensCommand, isTTY, matchGlob };
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { chromium } from 'playwright';
|
|
|
7
7
|
import { safeRender, ALL_CONTEXT_IDS, contextAxis, stressAxis, ALL_STRESS_IDS, RenderMatrix, SatoriRenderer, BrowserPool } from '@agent-scope/render';
|
|
8
8
|
import * as esbuild from 'esbuild';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
|
+
import { TokenResolver, validateTokenFile, TokenValidationError, parseTokenFileSync, TokenParseError } from '@agent-scope/tokens';
|
|
10
11
|
|
|
11
12
|
// src/manifest-commands.ts
|
|
12
13
|
|
|
@@ -1465,6 +1466,348 @@ function buildStructuredReport(report) {
|
|
|
1465
1466
|
route: report.route?.pattern ?? null
|
|
1466
1467
|
};
|
|
1467
1468
|
}
|
|
1469
|
+
var DEFAULT_TOKEN_FILE = "reactscope.tokens.json";
|
|
1470
|
+
var CONFIG_FILE = "reactscope.config.json";
|
|
1471
|
+
function isTTY2() {
|
|
1472
|
+
return process.stdout.isTTY === true;
|
|
1473
|
+
}
|
|
1474
|
+
function pad3(value, width) {
|
|
1475
|
+
return value.length >= width ? value.slice(0, width) : value + " ".repeat(width - value.length);
|
|
1476
|
+
}
|
|
1477
|
+
function buildTable2(headers, rows) {
|
|
1478
|
+
const colWidths = headers.map(
|
|
1479
|
+
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
|
|
1480
|
+
);
|
|
1481
|
+
const divider = colWidths.map((w) => "-".repeat(w)).join(" ");
|
|
1482
|
+
const headerRow = headers.map((h, i) => pad3(h, colWidths[i] ?? 0)).join(" ");
|
|
1483
|
+
const dataRows = rows.map(
|
|
1484
|
+
(row2) => row2.map((cell, i) => pad3(cell ?? "", colWidths[i] ?? 0)).join(" ")
|
|
1485
|
+
);
|
|
1486
|
+
return [headerRow, divider, ...dataRows].join("\n");
|
|
1487
|
+
}
|
|
1488
|
+
function resolveTokenFilePath(fileFlag) {
|
|
1489
|
+
if (fileFlag !== void 0) {
|
|
1490
|
+
return resolve(process.cwd(), fileFlag);
|
|
1491
|
+
}
|
|
1492
|
+
const configPath = resolve(process.cwd(), CONFIG_FILE);
|
|
1493
|
+
if (existsSync(configPath)) {
|
|
1494
|
+
try {
|
|
1495
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
1496
|
+
const config = JSON.parse(raw);
|
|
1497
|
+
if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
|
|
1498
|
+
const file = config.tokens.file;
|
|
1499
|
+
return resolve(process.cwd(), file);
|
|
1500
|
+
}
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return resolve(process.cwd(), DEFAULT_TOKEN_FILE);
|
|
1505
|
+
}
|
|
1506
|
+
function loadTokens(absPath) {
|
|
1507
|
+
if (!existsSync(absPath)) {
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
`Token file not found at ${absPath}.
|
|
1510
|
+
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
const raw = readFileSync(absPath, "utf-8");
|
|
1514
|
+
return parseTokenFileSync(raw);
|
|
1515
|
+
}
|
|
1516
|
+
function getRawValue(node, segments) {
|
|
1517
|
+
const [head, ...rest] = segments;
|
|
1518
|
+
if (head === void 0) return null;
|
|
1519
|
+
const child = node[head];
|
|
1520
|
+
if (child === void 0 || child === null) return null;
|
|
1521
|
+
if (rest.length === 0) {
|
|
1522
|
+
if (typeof child === "object" && !Array.isArray(child) && "value" in child) {
|
|
1523
|
+
const v = child.value;
|
|
1524
|
+
return typeof v === "string" || typeof v === "number" ? v : null;
|
|
1525
|
+
}
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
if (typeof child === "object" && !Array.isArray(child)) {
|
|
1529
|
+
return getRawValue(child, rest);
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
function buildResolutionChain(startPath, rawTokens) {
|
|
1534
|
+
const chain = [];
|
|
1535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1536
|
+
let current = startPath;
|
|
1537
|
+
while (!seen.has(current)) {
|
|
1538
|
+
seen.add(current);
|
|
1539
|
+
const rawValue = getRawValue(rawTokens, current.split("."));
|
|
1540
|
+
if (rawValue === null) break;
|
|
1541
|
+
chain.push({ path: current, rawValue: String(rawValue) });
|
|
1542
|
+
const refMatch = /^\{([^}]+)\}$/.exec(String(rawValue));
|
|
1543
|
+
if (refMatch === null) break;
|
|
1544
|
+
current = refMatch[1] ?? "";
|
|
1545
|
+
}
|
|
1546
|
+
return chain;
|
|
1547
|
+
}
|
|
1548
|
+
function registerGet2(tokensCmd) {
|
|
1549
|
+
tokensCmd.command("get <path>").description("Resolve a token path to its computed value").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((tokenPath, opts) => {
|
|
1550
|
+
try {
|
|
1551
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
1552
|
+
const { tokens } = loadTokens(filePath);
|
|
1553
|
+
const resolver = new TokenResolver(tokens);
|
|
1554
|
+
const resolvedValue = resolver.resolve(tokenPath);
|
|
1555
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
1556
|
+
if (useJson) {
|
|
1557
|
+
const token = tokens.find((t) => t.path === tokenPath);
|
|
1558
|
+
process.stdout.write(
|
|
1559
|
+
`${JSON.stringify({ path: tokenPath, value: token?.value, resolvedValue, type: token?.type }, null, 2)}
|
|
1560
|
+
`
|
|
1561
|
+
);
|
|
1562
|
+
} else {
|
|
1563
|
+
process.stdout.write(`${resolvedValue}
|
|
1564
|
+
`);
|
|
1565
|
+
}
|
|
1566
|
+
} catch (err) {
|
|
1567
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1568
|
+
`);
|
|
1569
|
+
process.exit(1);
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
function registerList2(tokensCmd) {
|
|
1574
|
+
tokensCmd.command("list [category]").description("List tokens, optionally filtered by category or type").option("--type <type>", "Filter by token type (color, dimension, fontFamily, etc.)").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or table (default: auto-detect)").action(
|
|
1575
|
+
(category, opts) => {
|
|
1576
|
+
try {
|
|
1577
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
1578
|
+
const { tokens } = loadTokens(filePath);
|
|
1579
|
+
const resolver = new TokenResolver(tokens);
|
|
1580
|
+
const filtered = resolver.list(opts.type, category);
|
|
1581
|
+
const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
|
|
1582
|
+
if (useJson) {
|
|
1583
|
+
process.stdout.write(`${JSON.stringify(filtered, null, 2)}
|
|
1584
|
+
`);
|
|
1585
|
+
} else {
|
|
1586
|
+
if (filtered.length === 0) {
|
|
1587
|
+
process.stdout.write("No tokens found.\n");
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
const headers = ["PATH", "VALUE", "RESOLVED", "TYPE"];
|
|
1591
|
+
const rows = filtered.map((t) => [t.path, String(t.value), t.resolvedValue, t.type]);
|
|
1592
|
+
process.stdout.write(`${buildTable2(headers, rows)}
|
|
1593
|
+
`);
|
|
1594
|
+
}
|
|
1595
|
+
} catch (err) {
|
|
1596
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1597
|
+
`);
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
function registerSearch(tokensCmd) {
|
|
1604
|
+
tokensCmd.command("search <value>").description("Find which token(s) match a computed value (supports fuzzy color matching)").option("--type <type>", "Restrict search to a specific token type").option("--fuzzy", "Return nearest match even if no exact match exists", false).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or table (default: auto-detect)").action(
|
|
1605
|
+
(value, opts) => {
|
|
1606
|
+
try {
|
|
1607
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
1608
|
+
const { tokens } = loadTokens(filePath);
|
|
1609
|
+
const resolver = new TokenResolver(tokens);
|
|
1610
|
+
const useJson = opts.format === "json" || opts.format !== "table" && !isTTY2();
|
|
1611
|
+
const typesToSearch = opts.type ? [opts.type] : [
|
|
1612
|
+
"color",
|
|
1613
|
+
"dimension",
|
|
1614
|
+
"fontFamily",
|
|
1615
|
+
"fontWeight",
|
|
1616
|
+
"number",
|
|
1617
|
+
"shadow",
|
|
1618
|
+
"duration",
|
|
1619
|
+
"cubicBezier"
|
|
1620
|
+
];
|
|
1621
|
+
const exactMatches = [];
|
|
1622
|
+
const nearestMatches = [];
|
|
1623
|
+
for (const type of typesToSearch) {
|
|
1624
|
+
const exact = resolver.match(value, type);
|
|
1625
|
+
if (exact !== null) {
|
|
1626
|
+
exactMatches.push({
|
|
1627
|
+
path: exact.token.path,
|
|
1628
|
+
resolvedValue: exact.token.resolvedValue,
|
|
1629
|
+
type: exact.token.type,
|
|
1630
|
+
exact: true,
|
|
1631
|
+
distance: 0
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (exactMatches.length === 0 && opts.fuzzy) {
|
|
1636
|
+
for (const type of typesToSearch) {
|
|
1637
|
+
const typeTokens = tokens.filter((t) => t.type === type);
|
|
1638
|
+
if (typeTokens.length === 0) continue;
|
|
1639
|
+
try {
|
|
1640
|
+
const near = resolver.nearest(value, type);
|
|
1641
|
+
nearestMatches.push({
|
|
1642
|
+
path: near.token.path,
|
|
1643
|
+
resolvedValue: near.token.resolvedValue,
|
|
1644
|
+
type: near.token.type,
|
|
1645
|
+
exact: near.exact,
|
|
1646
|
+
distance: near.distance
|
|
1647
|
+
});
|
|
1648
|
+
} catch {
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
nearestMatches.sort((a, b) => a.distance - b.distance);
|
|
1652
|
+
nearestMatches.splice(3);
|
|
1653
|
+
}
|
|
1654
|
+
const results = exactMatches.length > 0 ? exactMatches : nearestMatches;
|
|
1655
|
+
if (useJson) {
|
|
1656
|
+
process.stdout.write(`${JSON.stringify(results, null, 2)}
|
|
1657
|
+
`);
|
|
1658
|
+
} else {
|
|
1659
|
+
if (results.length === 0) {
|
|
1660
|
+
process.stdout.write(
|
|
1661
|
+
`No tokens found matching "${value}".
|
|
1662
|
+
Tip: use --fuzzy for nearest-match search.
|
|
1663
|
+
`
|
|
1664
|
+
);
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const headers = ["PATH", "RESOLVED VALUE", "TYPE", "MATCH", "DISTANCE"];
|
|
1668
|
+
const rows = results.map((r) => [
|
|
1669
|
+
r.path,
|
|
1670
|
+
r.resolvedValue,
|
|
1671
|
+
r.type,
|
|
1672
|
+
r.exact ? "exact" : "nearest",
|
|
1673
|
+
r.exact ? "\u2014" : r.distance.toFixed(2)
|
|
1674
|
+
]);
|
|
1675
|
+
process.stdout.write(`${buildTable2(headers, rows)}
|
|
1676
|
+
`);
|
|
1677
|
+
}
|
|
1678
|
+
} catch (err) {
|
|
1679
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1680
|
+
`);
|
|
1681
|
+
process.exit(1);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1686
|
+
function registerResolve(tokensCmd) {
|
|
1687
|
+
tokensCmd.command("resolve <path>").description("Show the full resolution chain for a token").option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((tokenPath, opts) => {
|
|
1688
|
+
try {
|
|
1689
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
1690
|
+
const absFilePath = filePath;
|
|
1691
|
+
const { tokens, rawFile } = loadTokens(absFilePath);
|
|
1692
|
+
const resolver = new TokenResolver(tokens);
|
|
1693
|
+
resolver.resolve(tokenPath);
|
|
1694
|
+
const chain = buildResolutionChain(tokenPath, rawFile.tokens);
|
|
1695
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
1696
|
+
if (useJson) {
|
|
1697
|
+
process.stdout.write(`${JSON.stringify({ path: tokenPath, chain }, null, 2)}
|
|
1698
|
+
`);
|
|
1699
|
+
} else {
|
|
1700
|
+
if (chain.length === 0) {
|
|
1701
|
+
process.stdout.write(`Token "${tokenPath}" not found.
|
|
1702
|
+
`);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const parts = chain.map((step, i) => {
|
|
1706
|
+
if (i < chain.length - 1) {
|
|
1707
|
+
return `${step.path} \u2192 ${step.rawValue}`;
|
|
1708
|
+
}
|
|
1709
|
+
return step.rawValue;
|
|
1710
|
+
});
|
|
1711
|
+
process.stdout.write(`${parts.join("\n ")}
|
|
1712
|
+
`);
|
|
1713
|
+
}
|
|
1714
|
+
} catch (err) {
|
|
1715
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1716
|
+
`);
|
|
1717
|
+
process.exit(1);
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
function registerValidate(tokensCmd) {
|
|
1722
|
+
tokensCmd.command("validate").description(
|
|
1723
|
+
"Validate the token file for errors (circular refs, missing refs, type mismatches)"
|
|
1724
|
+
).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
|
|
1725
|
+
try {
|
|
1726
|
+
const filePath = resolveTokenFilePath(opts.file);
|
|
1727
|
+
if (!existsSync(filePath)) {
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`Token file not found at ${filePath}.
|
|
1730
|
+
Create a reactscope.tokens.json file or use --file to specify a path.`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
1734
|
+
const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
|
|
1735
|
+
const errors = [];
|
|
1736
|
+
let parsed;
|
|
1737
|
+
try {
|
|
1738
|
+
parsed = JSON.parse(raw);
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
errors.push({
|
|
1741
|
+
code: "PARSE_ERROR",
|
|
1742
|
+
message: `Failed to parse token file as JSON: ${String(err)}`
|
|
1743
|
+
});
|
|
1744
|
+
outputValidationResult(filePath, errors, useJson);
|
|
1745
|
+
process.exit(1);
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
validateTokenFile(parsed);
|
|
1749
|
+
} catch (err) {
|
|
1750
|
+
if (err instanceof TokenValidationError) {
|
|
1751
|
+
for (const e of err.errors) {
|
|
1752
|
+
errors.push({ code: e.code, path: e.path, message: e.message });
|
|
1753
|
+
}
|
|
1754
|
+
outputValidationResult(filePath, errors, useJson);
|
|
1755
|
+
process.exit(1);
|
|
1756
|
+
}
|
|
1757
|
+
throw err;
|
|
1758
|
+
}
|
|
1759
|
+
try {
|
|
1760
|
+
parseTokenFileSync(raw);
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
if (err instanceof TokenParseError) {
|
|
1763
|
+
errors.push({ code: err.code, path: err.path, message: err.message });
|
|
1764
|
+
} else {
|
|
1765
|
+
errors.push({ code: "UNKNOWN", message: String(err) });
|
|
1766
|
+
}
|
|
1767
|
+
outputValidationResult(filePath, errors, useJson);
|
|
1768
|
+
process.exit(1);
|
|
1769
|
+
}
|
|
1770
|
+
outputValidationResult(filePath, errors, useJson);
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
|
|
1773
|
+
`);
|
|
1774
|
+
process.exit(1);
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
function outputValidationResult(filePath, errors, useJson) {
|
|
1779
|
+
const valid = errors.length === 0;
|
|
1780
|
+
if (useJson) {
|
|
1781
|
+
process.stdout.write(`${JSON.stringify({ valid, file: filePath, errors }, null, 2)}
|
|
1782
|
+
`);
|
|
1783
|
+
} else {
|
|
1784
|
+
if (valid) {
|
|
1785
|
+
process.stdout.write(`\u2713 Token file is valid: ${filePath}
|
|
1786
|
+
`);
|
|
1787
|
+
} else {
|
|
1788
|
+
process.stderr.write(`\u2717 Token file has ${errors.length} error(s): ${filePath}
|
|
1789
|
+
|
|
1790
|
+
`);
|
|
1791
|
+
for (const e of errors) {
|
|
1792
|
+
const pathPrefix = e.path ? ` [${e.path}]` : "";
|
|
1793
|
+
process.stderr.write(` ${e.code}${pathPrefix}: ${e.message}
|
|
1794
|
+
`);
|
|
1795
|
+
}
|
|
1796
|
+
process.exit(1);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
function createTokensCommand() {
|
|
1801
|
+
const tokensCmd = new Command("tokens").description(
|
|
1802
|
+
"Query and validate design tokens from a reactscope.tokens.json file"
|
|
1803
|
+
);
|
|
1804
|
+
registerGet2(tokensCmd);
|
|
1805
|
+
registerList2(tokensCmd);
|
|
1806
|
+
registerSearch(tokensCmd);
|
|
1807
|
+
registerResolve(tokensCmd);
|
|
1808
|
+
registerValidate(tokensCmd);
|
|
1809
|
+
return tokensCmd;
|
|
1810
|
+
}
|
|
1468
1811
|
|
|
1469
1812
|
// src/program.ts
|
|
1470
1813
|
function createProgram(options = {}) {
|
|
@@ -1552,9 +1895,10 @@ function createProgram(options = {}) {
|
|
|
1552
1895
|
});
|
|
1553
1896
|
program.addCommand(createManifestCommand());
|
|
1554
1897
|
program.addCommand(createRenderCommand());
|
|
1898
|
+
program.addCommand(createTokensCommand());
|
|
1555
1899
|
return program;
|
|
1556
1900
|
}
|
|
1557
1901
|
|
|
1558
|
-
export { createManifestCommand, createProgram, isTTY, matchGlob };
|
|
1902
|
+
export { createManifestCommand, createProgram, createTokensCommand, isTTY, matchGlob };
|
|
1559
1903
|
//# sourceMappingURL=index.js.map
|
|
1560
1904
|
//# sourceMappingURL=index.js.map
|