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