@loworbitstudio/visor 1.14.0 → 1.16.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.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync28 } from "fs";
5
- import { dirname as dirname10, join as join25 } from "path";
4
+ import { readFileSync as readFileSync32 } from "fs";
5
+ import { dirname as dirname12, join as join32 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { Command as Command2 } from "commander";
8
8
 
@@ -1191,10 +1191,10 @@ function checkCommand() {
1191
1191
  }
1192
1192
 
1193
1193
  // src/commands/init.ts
1194
- import { existsSync as existsSync4, writeFileSync as writeFileSync2, mkdirSync, readFileSync as readFileSync7 } from "fs";
1195
- import { join as join6, dirname as dirname3 } from "path";
1194
+ import { existsSync as existsSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync9 } from "fs";
1195
+ import { join as join8, dirname as dirname4, basename as basename2 } from "path";
1196
1196
  import { fileURLToPath as fileURLToPath2 } from "url";
1197
- import * as childProcess from "child_process";
1197
+ import * as childProcess2 from "child_process";
1198
1198
 
1199
1199
  // src/config/config.ts
1200
1200
  import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync2 } from "fs";
@@ -1378,6 +1378,156 @@ export default function RootLayout({ children }: { children: ReactNode }) {
1378
1378
  `;
1379
1379
  }
1380
1380
 
1381
+ // src/commands/init-plays/registry.ts
1382
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
1383
+ import { dirname as dirname3, join as join6 } from "path";
1384
+
1385
+ // src/commands/init-plays/pattern-build.ts
1386
+ var patternBuildPlay = {
1387
+ id: "pattern-build",
1388
+ loSubdir: "pattern-builds",
1389
+ label: "Pattern build",
1390
+ description: "Design + converge a reusable Borealis pattern (admin-ui, forms, ...)."
1391
+ };
1392
+
1393
+ // src/commands/init-plays/new-web-app.ts
1394
+ var newWebAppPlay = {
1395
+ id: "new-web-app",
1396
+ loSubdir: "new-web-apps",
1397
+ label: "New web app",
1398
+ description: "A fresh NextJS app entering the Playbook lifecycle."
1399
+ };
1400
+
1401
+ // src/commands/init-plays/feature-addition.ts
1402
+ var featureAdditionPlay = {
1403
+ id: "feature-addition",
1404
+ loSubdir: "feature-additions",
1405
+ label: "Feature addition",
1406
+ description: "An existing project onboarding into the Playbook mid-stream (safe, idempotent)."
1407
+ };
1408
+
1409
+ // src/commands/init-plays/registry.ts
1410
+ var KNOWN_PLAYS = [
1411
+ patternBuildPlay,
1412
+ newWebAppPlay,
1413
+ featureAdditionPlay
1414
+ ];
1415
+ function getPlay(id) {
1416
+ return KNOWN_PLAYS.find((p) => p.id === id);
1417
+ }
1418
+ function knownPlayIds() {
1419
+ return KNOWN_PLAYS.map((p) => p.id);
1420
+ }
1421
+ function playStatePaths(cwd, def, name) {
1422
+ const relDir = join6(".lo", def.loSubdir, name);
1423
+ const dir = join6(cwd, relDir);
1424
+ return {
1425
+ dir,
1426
+ statePath: join6(dir, "state.json"),
1427
+ relStatePath: join6(relDir, "state.json")
1428
+ };
1429
+ }
1430
+ function readPlayState(statePath) {
1431
+ if (!existsSync4(statePath)) return null;
1432
+ try {
1433
+ return JSON.parse(readFileSync7(statePath, "utf-8"));
1434
+ } catch {
1435
+ return null;
1436
+ }
1437
+ }
1438
+ function writePlayState(statePath, state) {
1439
+ mkdirSync(dirname3(statePath), { recursive: true });
1440
+ writeFileSync2(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
1441
+ }
1442
+
1443
+ // src/lib/lo-ports-bridge.ts
1444
+ import * as childProcess from "child_process";
1445
+ var LO_PORTS_COMMAND = "lo-ports";
1446
+ var LO_PORTS_ARGS = ["next", "--json"];
1447
+ var FALLBACK_RANGE_START = 4200;
1448
+ var FALLBACK_RANGE_SIZE = 100;
1449
+ function allocatePort(name, options = {}) {
1450
+ const runCommand = options.runCommand ?? defaultRunCommand;
1451
+ const stdout = safeRun(runCommand);
1452
+ const fromRegistry = stdout != null ? parsePort(stdout) : null;
1453
+ if (fromRegistry != null) {
1454
+ return { port: fromRegistry, source: "lo-ports" };
1455
+ }
1456
+ const port = heuristicPort(name);
1457
+ return {
1458
+ port,
1459
+ source: "fallback",
1460
+ warning: `Could not allocate a dev port via /lo-ports (no \`${LO_PORTS_COMMAND}\` command found). Used heuristic port ${port} instead \u2014 register it with \`/lo-ports\` when convenient.`
1461
+ };
1462
+ }
1463
+ function safeRun(runCommand) {
1464
+ try {
1465
+ return runCommand();
1466
+ } catch {
1467
+ return null;
1468
+ }
1469
+ }
1470
+ function defaultRunCommand() {
1471
+ const result = childProcess.spawnSync(LO_PORTS_COMMAND, LO_PORTS_ARGS, {
1472
+ encoding: "utf-8"
1473
+ });
1474
+ if (result.error) return null;
1475
+ if (typeof result.status === "number" && result.status !== 0) return null;
1476
+ return result.stdout ?? null;
1477
+ }
1478
+ function parsePort(stdout) {
1479
+ const trimmed = stdout.trim();
1480
+ if (trimmed.length === 0) return null;
1481
+ try {
1482
+ const parsed = JSON.parse(trimmed);
1483
+ if (typeof parsed === "number" && isValidPort(parsed)) return parsed;
1484
+ if (parsed != null && typeof parsed === "object" && "port" in parsed && isValidPort(parsed.port)) {
1485
+ return parsed.port;
1486
+ }
1487
+ } catch {
1488
+ }
1489
+ const bare = Number(trimmed);
1490
+ return isValidPort(bare) ? bare : null;
1491
+ }
1492
+ function isValidPort(value) {
1493
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value < 65536;
1494
+ }
1495
+ function heuristicPort(name) {
1496
+ let hash = 0;
1497
+ for (let i = 0; i < name.length; i++) {
1498
+ hash = hash * 31 + name.charCodeAt(i) >>> 0;
1499
+ }
1500
+ return FALLBACK_RANGE_START + hash % FALLBACK_RANGE_SIZE;
1501
+ }
1502
+
1503
+ // src/lib/lo-play-checklist.ts
1504
+ import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
1505
+ import { homedir } from "os";
1506
+ import { join as join7 } from "path";
1507
+ function defaultChecklistRoot() {
1508
+ return join7(homedir(), ".claude", "skills", "lo-play");
1509
+ }
1510
+ function readEntryChecklist(playType, options = {}) {
1511
+ const root = options.root ?? defaultChecklistRoot();
1512
+ const path2 = join7(root, playType, "entry-checklist.md");
1513
+ if (!existsSync5(path2)) {
1514
+ return {
1515
+ found: false,
1516
+ path: path2,
1517
+ fallbackMessage: `Entry checklist not found at ${path2}. Run the play manually from \`/lo-play ${playType}\`.`
1518
+ };
1519
+ }
1520
+ try {
1521
+ return { found: true, path: path2, content: readFileSync8(path2, "utf-8") };
1522
+ } catch {
1523
+ return {
1524
+ found: false,
1525
+ path: path2,
1526
+ fallbackMessage: `Entry checklist at ${path2} could not be read. Run the play manually from \`/lo-play ${playType}\`.`
1527
+ };
1528
+ }
1529
+ }
1530
+
1381
1531
  // src/commands/init.ts
1382
1532
  import { generateThemeData as generateThemeData2 } from "@loworbitstudio/visor-theme-engine";
1383
1533
  import { nextjsAdapter } from "@loworbitstudio/visor-theme-engine/adapters";
@@ -1390,7 +1540,27 @@ function initCommand(cwd, options) {
1390
1540
  emitError(json, `Unknown template: ${options.template}. Available templates: nextjs`);
1391
1541
  process.exit(1);
1392
1542
  }
1393
- if (options?.template === "nextjs" && existsSync4(join6(cwd, "package.json"))) {
1543
+ let playDef;
1544
+ let playName = "";
1545
+ if (options?.for) {
1546
+ playDef = getPlay(options.for);
1547
+ if (!playDef) {
1548
+ emitError(
1549
+ json,
1550
+ `Unknown play: ${options.for}. Known plays: ${knownPlayIds().join(", ")}`
1551
+ );
1552
+ process.exit(1);
1553
+ }
1554
+ playName = options.playName ?? basename2(cwd);
1555
+ if (!/^[a-z0-9][a-z0-9-_]*$/i.test(playName)) {
1556
+ emitError(
1557
+ json,
1558
+ `Invalid play name '${playName}'. Use letters, digits, '-' or '_' (e.g. --play-name organization-management).`
1559
+ );
1560
+ process.exit(1);
1561
+ }
1562
+ }
1563
+ if (options?.template === "nextjs" && existsSync6(join8(cwd, "package.json"))) {
1394
1564
  emitError(
1395
1565
  json,
1396
1566
  "package.json already exists in this directory. visor init --template nextjs only scaffolds into empty directories. For an existing app, see the retrofit flow: https://visor.loworbit.studio/docs/guides/migration"
@@ -1431,6 +1601,10 @@ function initCommand(cwd, options) {
1431
1601
  }
1432
1602
  }
1433
1603
  }
1604
+ let playResult;
1605
+ if (playDef) {
1606
+ playResult = runPlayInit(cwd, playDef, playName, options ?? {}, json, warnings);
1607
+ }
1434
1608
  if (json) {
1435
1609
  const nextSteps = buildNextSteps(options, warnings);
1436
1610
  const result = {
@@ -1438,12 +1612,91 @@ function initCommand(cwd, options) {
1438
1612
  config: DEFAULT_CONFIG,
1439
1613
  files: { created: filesCreated, skipped: filesSkipped },
1440
1614
  warnings,
1441
- nextSteps
1615
+ nextSteps,
1616
+ ...playResult ? { play: playResult } : {}
1442
1617
  };
1443
1618
  console.log(JSON.stringify(result, null, 2));
1444
1619
  process.exit(0);
1445
1620
  }
1446
1621
  }
1622
+ function runPlayInit(cwd, def, name, options, json, warnings) {
1623
+ const { statePath, relStatePath } = playStatePaths(cwd, def, name);
1624
+ const existing = readPlayState(statePath);
1625
+ const checklist = readEntryChecklist(def.id);
1626
+ if (existing) {
1627
+ if (!json) {
1628
+ logger.blank();
1629
+ logger.warn(
1630
+ `Play '${def.id}' / '${name}' already initialized at ${relStatePath} (phase ${existing.phase}${existing.devPort ? `, port ${existing.devPort}` : ""}). Nothing to do.`
1631
+ );
1632
+ printChecklist(def.id, checklist);
1633
+ }
1634
+ return {
1635
+ play: def.id,
1636
+ name,
1637
+ statePath: relStatePath,
1638
+ phase: existing.phase,
1639
+ devPort: existing.devPort,
1640
+ portSource: existing.portSource,
1641
+ theme: existing.theme,
1642
+ from: existing.from,
1643
+ alreadyInitialized: true,
1644
+ checklist: { found: checklist.found, path: checklist.path }
1645
+ };
1646
+ }
1647
+ const port = allocatePort(name);
1648
+ if (port.warning) warnings.push(port.warning);
1649
+ const state = {
1650
+ play: def.id,
1651
+ name,
1652
+ phase: 0,
1653
+ ...options.theme ? { theme: options.theme } : {},
1654
+ ...options.from ? { from: options.from } : {},
1655
+ devPort: port.port,
1656
+ portSource: port.source,
1657
+ createdWith: `@loworbitstudio/visor@${readVisorCliVersion()}`,
1658
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1659
+ };
1660
+ writePlayState(statePath, state);
1661
+ if (!json) {
1662
+ logger.blank();
1663
+ if (options.template === "nextjs") {
1664
+ logger.success("Visor scaffold: NextJS + tokens + FOWT layout");
1665
+ }
1666
+ logger.success(`Playbook state: ${relStatePath} (phase 0)`);
1667
+ if (port.source === "lo-ports") {
1668
+ logger.success(`Dev port allocated: ${port.port} (via /lo-ports)`);
1669
+ } else {
1670
+ logger.warn(port.warning ?? `Dev port ${port.port} (heuristic fallback)`);
1671
+ logger.success(`Dev port: ${port.port} (heuristic fallback)`);
1672
+ }
1673
+ if (options.theme) logger.success(`Theme recorded: ${options.theme}`);
1674
+ printChecklist(def.id, checklist);
1675
+ }
1676
+ return {
1677
+ play: def.id,
1678
+ name,
1679
+ statePath: relStatePath,
1680
+ phase: 0,
1681
+ devPort: port.port,
1682
+ portSource: port.source,
1683
+ theme: options.theme,
1684
+ from: options.from,
1685
+ alreadyInitialized: false,
1686
+ checklist: { found: checklist.found, path: checklist.path }
1687
+ };
1688
+ }
1689
+ function printChecklist(playId, checklist) {
1690
+ logger.blank();
1691
+ if (checklist.found) {
1692
+ logger.info(`Next steps (from ${checklist.path}):`);
1693
+ for (const line of checklist.content.split("\n")) {
1694
+ logger.item(line);
1695
+ }
1696
+ } else {
1697
+ logger.warn(checklist.fallbackMessage);
1698
+ }
1699
+ }
1447
1700
  function buildNextSteps(options, warnings) {
1448
1701
  const steps = [];
1449
1702
  if (options?.template === "nextjs") {
@@ -1473,14 +1726,14 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1473
1726
  }
1474
1727
  runCreateNextApp(cwd, json);
1475
1728
  runInstallVisorDeps(cwd, json);
1476
- const yamlPath = join6(cwd, ".visor.yaml");
1477
- if (existsSync4(yamlPath)) {
1729
+ const yamlPath = join8(cwd, ".visor.yaml");
1730
+ if (existsSync6(yamlPath)) {
1478
1731
  filesSkipped.push(".visor.yaml");
1479
1732
  if (!json) {
1480
1733
  logger.warn(".visor.yaml already exists. Skipping.");
1481
1734
  }
1482
1735
  } else {
1483
- writeFileSync2(yamlPath, NEXTJS_STARTER_YAML, "utf-8");
1736
+ writeFileSync3(yamlPath, NEXTJS_STARTER_YAML, "utf-8");
1484
1737
  filesCreated.push(".visor.yaml");
1485
1738
  if (!json) {
1486
1739
  logger.success("Created .visor.yaml");
@@ -1492,40 +1745,40 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1492
1745
  tokens: data.tokens,
1493
1746
  config: data.config
1494
1747
  });
1495
- const globalsPath = join6(cwd, "app", "globals.css");
1496
- const globalsDir = dirname3(globalsPath);
1497
- mkdirSync(globalsDir, { recursive: true });
1498
- if (existsSync4(globalsPath)) {
1499
- writeFileSync2(globalsPath, css, "utf-8");
1748
+ const globalsPath = join8(cwd, "app", "globals.css");
1749
+ const globalsDir = dirname4(globalsPath);
1750
+ mkdirSync2(globalsDir, { recursive: true });
1751
+ if (existsSync6(globalsPath)) {
1752
+ writeFileSync3(globalsPath, css, "utf-8");
1500
1753
  filesCreated.push("app/globals.css");
1501
1754
  } else {
1502
- writeFileSync2(globalsPath, css, "utf-8");
1755
+ writeFileSync3(globalsPath, css, "utf-8");
1503
1756
  filesCreated.push("app/globals.css");
1504
1757
  }
1505
1758
  if (!json) {
1506
1759
  logger.success("Created app/globals.css with theme tokens");
1507
1760
  }
1508
- const layoutPath = join6(cwd, "app", "layout.tsx");
1761
+ const layoutPath = join8(cwd, "app", "layout.tsx");
1509
1762
  const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
1510
- writeFileSync2(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
1763
+ writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
1511
1764
  filesCreated.push("app/layout.tsx");
1512
1765
  if (!json) {
1513
1766
  logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
1514
1767
  }
1515
- const stampDir = join6(cwd, ".lo");
1516
- const stampPath = join6(stampDir, "borealis.json");
1517
- if (existsSync4(stampPath)) {
1768
+ const stampDir = join8(cwd, ".lo");
1769
+ const stampPath = join8(stampDir, "borealis.json");
1770
+ if (existsSync6(stampPath)) {
1518
1771
  filesSkipped.push(".lo/borealis.json");
1519
1772
  if (!json) {
1520
1773
  logger.warn(".lo/borealis.json already exists. Skipping.");
1521
1774
  }
1522
1775
  } else {
1523
- mkdirSync(stampDir, { recursive: true });
1776
+ mkdirSync2(stampDir, { recursive: true });
1524
1777
  const stamp = {
1525
1778
  visorVersion: readVisorCliVersion(),
1526
1779
  initializedAt: (/* @__PURE__ */ new Date()).toISOString()
1527
1780
  };
1528
- writeFileSync2(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
1781
+ writeFileSync3(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
1529
1782
  filesCreated.push(".lo/borealis.json");
1530
1783
  if (!json) {
1531
1784
  logger.success("Stamped .lo/borealis.json");
@@ -1546,7 +1799,7 @@ function runCreateNextApp(cwd, json) {
1546
1799
  if (!json) {
1547
1800
  logger.info(`Running create-next-app@${NEXTJS_PINNED_VERSION}...`);
1548
1801
  }
1549
- const result = childProcess.spawnSync(
1802
+ const result = childProcess2.spawnSync(
1550
1803
  "npx",
1551
1804
  [`create-next-app@${NEXTJS_PINNED_VERSION}`, ".", ...CREATE_NEXT_APP_FLAGS],
1552
1805
  { cwd, stdio: json ? "ignore" : "inherit" }
@@ -1557,7 +1810,7 @@ function runInstallVisorDeps(cwd, json) {
1557
1810
  if (!json) {
1558
1811
  logger.info("Installing @loworbitstudio/visor-core and visor-theme-engine...");
1559
1812
  }
1560
- const result = childProcess.spawnSync(
1813
+ const result = childProcess2.spawnSync(
1561
1814
  "npm",
1562
1815
  [
1563
1816
  "install",
@@ -1578,12 +1831,12 @@ function assertSpawnSuccess(result, label) {
1578
1831
  }
1579
1832
  function readVisorCliVersion() {
1580
1833
  try {
1581
- const here = dirname3(fileURLToPath2(import.meta.url));
1834
+ const here = dirname4(fileURLToPath2(import.meta.url));
1582
1835
  for (let i = 0; i < 5; i++) {
1583
1836
  const segments = new Array(i).fill("..");
1584
- const candidate = join6(here, ...segments, "package.json");
1837
+ const candidate = join8(here, ...segments, "package.json");
1585
1838
  try {
1586
- const pkg2 = JSON.parse(readFileSync7(candidate, "utf-8"));
1839
+ const pkg2 = JSON.parse(readFileSync9(candidate, "utf-8"));
1587
1840
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
1588
1841
  return pkg2.version;
1589
1842
  }
@@ -1597,53 +1850,53 @@ function readVisorCliVersion() {
1597
1850
 
1598
1851
  // src/utils/fs.ts
1599
1852
  import {
1600
- writeFileSync as writeFileSync3,
1601
- readFileSync as readFileSync8,
1602
- existsSync as existsSync5,
1603
- mkdirSync as mkdirSync2
1853
+ writeFileSync as writeFileSync4,
1854
+ readFileSync as readFileSync10,
1855
+ existsSync as existsSync7,
1856
+ mkdirSync as mkdirSync3
1604
1857
  } from "fs";
1605
- import { dirname as dirname4, join as join7 } from "path";
1858
+ import { dirname as dirname5, join as join9 } from "path";
1606
1859
  function resolveOutputPath(registryPath, type, config, cwd) {
1607
1860
  let relativePath;
1608
1861
  if (type === "registry:block") {
1609
1862
  relativePath = registryPath.replace(/^blocks\//, "");
1610
- return join7(cwd, config.paths.blocks, relativePath);
1863
+ return join9(cwd, config.paths.blocks, relativePath);
1611
1864
  }
1612
1865
  if (type === "registry:ui") {
1613
1866
  if (registryPath.startsWith("components/deck/")) {
1614
1867
  relativePath = registryPath.replace(/^components\/deck\//, "");
1615
- return join7(cwd, config.paths.deckComponents, relativePath);
1868
+ return join9(cwd, config.paths.deckComponents, relativePath);
1616
1869
  }
1617
1870
  if (registryPath.startsWith("components/flutter/")) {
1618
1871
  relativePath = registryPath.replace(/^components\/flutter\//, "");
1619
- return join7(cwd, config.paths.flutterComponents, relativePath);
1872
+ return join9(cwd, config.paths.flutterComponents, relativePath);
1620
1873
  }
1621
1874
  relativePath = registryPath.replace(/^components\/ui\//, "");
1622
- return join7(cwd, config.paths.components, relativePath);
1875
+ return join9(cwd, config.paths.components, relativePath);
1623
1876
  }
1624
1877
  if (type === "registry:hook") {
1625
1878
  relativePath = registryPath.replace(/^hooks\//, "");
1626
- return join7(cwd, config.paths.hooks, relativePath);
1879
+ return join9(cwd, config.paths.hooks, relativePath);
1627
1880
  }
1628
1881
  if (type === "registry:lib") {
1629
1882
  relativePath = registryPath.replace(/^lib\//, "");
1630
- return join7(cwd, config.paths.lib, relativePath);
1883
+ return join9(cwd, config.paths.lib, relativePath);
1631
1884
  }
1632
- return join7(cwd, registryPath);
1885
+ return join9(cwd, registryPath);
1633
1886
  }
1634
1887
  function writeFile(filePath, content) {
1635
- const dir = dirname4(filePath);
1636
- if (!existsSync5(dir)) {
1637
- mkdirSync2(dir, { recursive: true });
1888
+ const dir = dirname5(filePath);
1889
+ if (!existsSync7(dir)) {
1890
+ mkdirSync3(dir, { recursive: true });
1638
1891
  }
1639
- writeFileSync3(filePath, content, "utf-8");
1892
+ writeFileSync4(filePath, content, "utf-8");
1640
1893
  }
1641
1894
  function readFile(filePath) {
1642
- if (!existsSync5(filePath)) return null;
1643
- return readFileSync8(filePath, "utf-8");
1895
+ if (!existsSync7(filePath)) return null;
1896
+ return readFileSync10(filePath, "utf-8");
1644
1897
  }
1645
1898
  function fileExists(filePath) {
1646
- return existsSync5(filePath);
1899
+ return existsSync7(filePath);
1647
1900
  }
1648
1901
 
1649
1902
  // src/commands/list.ts
@@ -1784,8 +2037,8 @@ function listCommand(cwd, options = {}) {
1784
2037
  }
1785
2038
 
1786
2039
  // src/utils/pubspec.ts
1787
- import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
1788
- import { join as join8 } from "path";
2040
+ import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
2041
+ import { join as join10 } from "path";
1789
2042
  import { parseDocument, YAMLMap } from "yaml";
1790
2043
  function mergePubspec(pubspecText, deps) {
1791
2044
  const doc = parseDocument(pubspecText);
@@ -1807,14 +2060,14 @@ function mergePubspec(pubspecText, deps) {
1807
2060
  return { text: doc.toString(), added, skipped };
1808
2061
  }
1809
2062
  function pubspecPath(cwd) {
1810
- return join8(cwd, "pubspec.yaml");
2063
+ return join10(cwd, "pubspec.yaml");
1811
2064
  }
1812
2065
  function pubspecExists(cwd) {
1813
- return existsSync6(pubspecPath(cwd));
2066
+ return existsSync8(pubspecPath(cwd));
1814
2067
  }
1815
2068
  function isPubPackageInstalled(packageName, cwd) {
1816
2069
  if (!pubspecExists(cwd)) return false;
1817
- const text = readFileSync9(pubspecPath(cwd), "utf-8");
2070
+ const text = readFileSync11(pubspecPath(cwd), "utf-8");
1818
2071
  const doc = parseDocument(text);
1819
2072
  const depsNode = doc.get("dependencies");
1820
2073
  if (!(depsNode instanceof YAMLMap)) return false;
@@ -1825,24 +2078,24 @@ function getUninstalledPubDeps(deps, cwd) {
1825
2078
  }
1826
2079
  function addPubDependencies(deps, cwd) {
1827
2080
  const path2 = pubspecPath(cwd);
1828
- if (!existsSync6(path2)) {
2081
+ if (!existsSync8(path2)) {
1829
2082
  throw new Error(
1830
2083
  `No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
1831
2084
  );
1832
2085
  }
1833
- const text = readFileSync9(path2, "utf-8");
2086
+ const text = readFileSync11(path2, "utf-8");
1834
2087
  const result = mergePubspec(text, deps);
1835
2088
  if (result.added.length > 0) {
1836
- writeFileSync4(path2, result.text, "utf-8");
2089
+ writeFileSync5(path2, result.text, "utf-8");
1837
2090
  }
1838
2091
  return result;
1839
2092
  }
1840
2093
 
1841
2094
  // src/utils/flutter.ts
1842
2095
  import { execFileSync as execFileSync2 } from "child_process";
1843
- import { existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
1844
- import { homedir } from "os";
1845
- import { join as join9 } from "path";
2096
+ import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
2097
+ import { homedir as homedir2 } from "os";
2098
+ import { join as join11 } from "path";
1846
2099
  function isExecutable(path2) {
1847
2100
  try {
1848
2101
  const s = statSync4(path2);
@@ -1857,20 +2110,20 @@ function fromPath(env) {
1857
2110
  const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
1858
2111
  for (const dir of pathVar.split(sep2)) {
1859
2112
  if (!dir) continue;
1860
- const candidate = join9(dir, bin);
2113
+ const candidate = join11(dir, bin);
1861
2114
  if (isExecutable(candidate)) return candidate;
1862
2115
  }
1863
2116
  return null;
1864
2117
  }
1865
2118
  function fromFvm(home) {
1866
- const fvmDefault = join9(home, "fvm", "default", "bin", "flutter");
2119
+ const fvmDefault = join11(home, "fvm", "default", "bin", "flutter");
1867
2120
  if (isExecutable(fvmDefault)) return fvmDefault;
1868
- const versionsDir = join9(home, "fvm", "versions");
1869
- if (!existsSync7(versionsDir)) return null;
2121
+ const versionsDir = join11(home, "fvm", "versions");
2122
+ if (!existsSync9(versionsDir)) return null;
1870
2123
  let best = null;
1871
2124
  try {
1872
2125
  for (const name of readdirSync3(versionsDir)) {
1873
- const candidate = join9(versionsDir, name, "bin", "flutter");
2126
+ const candidate = join11(versionsDir, name, "bin", "flutter");
1874
2127
  if (!isExecutable(candidate)) continue;
1875
2128
  if (!best || compareSemver(name, best.version) > 0) {
1876
2129
  best = { version: name, path: candidate };
@@ -1893,10 +2146,10 @@ function compareSemver(a, b) {
1893
2146
  }
1894
2147
  function findFlutterBin(options = {}) {
1895
2148
  const env = options.env ?? process.env;
1896
- const home = options.home ?? homedir();
2149
+ const home = options.home ?? homedir2();
1897
2150
  const envRoot = env.FLUTTER_ROOT;
1898
2151
  if (envRoot) {
1899
- const bin = join9(envRoot, "bin", "flutter");
2152
+ const bin = join11(envRoot, "bin", "flutter");
1900
2153
  if (isExecutable(bin)) return bin;
1901
2154
  }
1902
2155
  const fromPathBin = fromPath(env);
@@ -2539,8 +2792,8 @@ function infoCommand(name, cwd, options = {}) {
2539
2792
  }
2540
2793
 
2541
2794
  // src/commands/theme-apply.ts
2542
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
2543
- import { resolve as resolve5, dirname as dirname5, join as join10 } from "path";
2795
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
2796
+ import { resolve as resolve7, dirname as dirname7, join as join15 } from "path";
2544
2797
  import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
2545
2798
  import {
2546
2799
  nextjsAdapter as nextjsAdapter2,
@@ -2549,6 +2802,143 @@ import {
2549
2802
  docsAdapter,
2550
2803
  flutterAdapter
2551
2804
  } from "@loworbitstudio/visor-theme-engine/adapters";
2805
+
2806
+ // src/lib/blessed-manifest.ts
2807
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
2808
+ import { join as join12 } from "path";
2809
+ import { z } from "zod";
2810
+ var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
2811
+ var themeApplyTargetSchema = z.discriminatedUnion("kind", [
2812
+ z.object({
2813
+ kind: z.literal("globals-css"),
2814
+ /** Build-root-relative destination file. Defaults to `app/globals.css`. */
2815
+ path: z.string().min(1).optional()
2816
+ }).strict(),
2817
+ z.object({
2818
+ kind: z.literal("themes-css-dir"),
2819
+ /** Build-root-relative directory. The adapter output is written to `<path>/<theme-id>.css`. */
2820
+ path: z.string().min(1)
2821
+ }).strict()
2822
+ ]);
2823
+ var blessedManifestSchema = z.object({
2824
+ /** Blessed-build shape, e.g. `admin-ui`. Matched against the `{shape}` in `blessed:{shape}:{pattern}`. */
2825
+ shape: z.string().min(1),
2826
+ /** Pattern name within the shape, e.g. `organization-management`. */
2827
+ pattern: z.string().min(1),
2828
+ /** The theme the reference build was authored + captured against. */
2829
+ base_theme: z.string().min(1),
2830
+ /** Minimum Visor version this build is known to work with (semver range). */
2831
+ requires_visor: z.string().min(1),
2832
+ /** Path (relative to the build root) to the approved capture baseline. */
2833
+ captures_baseline: z.string().min(1),
2834
+ /** Three-gates disposition at the time the build was blessed. */
2835
+ three_gates_status: z.string().min(1),
2836
+ /** Optional: where to write the nextjs-adapter CSS. Absent = `globals-css`. */
2837
+ theme_apply_target: themeApplyTargetSchema.optional()
2838
+ }).strict();
2839
+ function parseBlessedManifest(buildDir) {
2840
+ const manifestPath = join12(buildDir, BLESSED_MANIFEST_FILENAME);
2841
+ if (!existsSync10(manifestPath)) {
2842
+ return {
2843
+ ok: false,
2844
+ error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
2845
+ };
2846
+ }
2847
+ let raw;
2848
+ try {
2849
+ raw = readFileSync12(manifestPath, "utf-8");
2850
+ } catch {
2851
+ return { ok: false, error: `could not read ${manifestPath}` };
2852
+ }
2853
+ let json;
2854
+ try {
2855
+ json = JSON.parse(raw);
2856
+ } catch (err) {
2857
+ const message = err instanceof Error ? err.message : "invalid JSON";
2858
+ return { ok: false, error: `invalid JSON in ${manifestPath}: ${message}` };
2859
+ }
2860
+ const result = blessedManifestSchema.safeParse(json);
2861
+ if (!result.success) {
2862
+ const issues = result.error.issues.map((issue) => {
2863
+ const path2 = issue.path.join(".") || "(root)";
2864
+ return `${path2}: ${issue.message}`;
2865
+ }).join("; ");
2866
+ return {
2867
+ ok: false,
2868
+ error: `invalid ${BLESSED_MANIFEST_FILENAME} at ${manifestPath}: ${issues}`
2869
+ };
2870
+ }
2871
+ return { ok: true, manifest: result.data };
2872
+ }
2873
+
2874
+ // src/lib/theme-apply-targets/globals-css.ts
2875
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
2876
+ import { dirname as dirname6, isAbsolute, join as join13, resolve as resolve5 } from "path";
2877
+ function applyGlobalsCss(input) {
2878
+ const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
2879
+ mkdirSync4(dirname6(outFile), { recursive: true });
2880
+ writeFileSync6(outFile, input.adapterCss, "utf-8");
2881
+ return outFile;
2882
+ }
2883
+ function resolveDefaultGlobalsCss(buildDir) {
2884
+ const candidates = [
2885
+ join13(buildDir, "app", "globals.css"),
2886
+ join13(buildDir, "src", "app", "globals.css")
2887
+ ];
2888
+ return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0];
2889
+ }
2890
+ function resolveInsideBuild(buildDir, relativeOrAbsolute) {
2891
+ return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : resolve5(buildDir, relativeOrAbsolute);
2892
+ }
2893
+
2894
+ // src/lib/theme-apply-targets/themes-css-dir.ts
2895
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
2896
+ import { isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
2897
+ function applyThemesCssDir(input) {
2898
+ const dir = isAbsolute2(input.path) ? input.path : resolve6(input.buildDir, input.path);
2899
+ const outFile = join14(dir, `${input.themeId}.css`);
2900
+ mkdirSync5(dir, { recursive: true });
2901
+ writeFileSync7(outFile, input.adapterCss, "utf-8");
2902
+ return outFile;
2903
+ }
2904
+
2905
+ // src/lib/theme-apply-targets/index.ts
2906
+ var IMPLICIT_TARGET = { kind: "globals-css" };
2907
+ function applyThemeToBuild(input) {
2908
+ const target = input.manifest.theme_apply_target ?? IMPLICIT_TARGET;
2909
+ switch (target.kind) {
2910
+ case "globals-css": {
2911
+ const writtenPath = applyGlobalsCss({
2912
+ buildDir: input.buildDir,
2913
+ adapterCss: input.adapterCss,
2914
+ path: target.path
2915
+ });
2916
+ return { writtenPath, target };
2917
+ }
2918
+ case "themes-css-dir": {
2919
+ if (!input.themeId || input.themeId.length === 0) {
2920
+ throw new Error(
2921
+ `theme_apply_target kind 'themes-css-dir' requires a theme id (used to name the emitted file); see docs/blessed-builds.md`
2922
+ );
2923
+ }
2924
+ const writtenPath = applyThemesCssDir({
2925
+ buildDir: input.buildDir,
2926
+ adapterCss: input.adapterCss,
2927
+ themeId: input.themeId,
2928
+ path: target.path
2929
+ });
2930
+ return { writtenPath, target };
2931
+ }
2932
+ default: {
2933
+ const unknown = target.kind;
2934
+ throw new Error(
2935
+ `Unknown theme_apply_target kind: '${unknown}'. Supported: 'globals-css', 'themes-css-dir'. See docs/blessed-builds.md.`
2936
+ );
2937
+ }
2938
+ }
2939
+ }
2940
+
2941
+ // src/commands/theme-apply.ts
2552
2942
  function defaultOutputPath(adapter, themeName) {
2553
2943
  switch (adapter) {
2554
2944
  case "nextjs":
@@ -2570,10 +2960,10 @@ function defaultOutputPath(adapter, themeName) {
2570
2960
  }
2571
2961
  }
2572
2962
  function themeApplyCommand(file, cwd, options) {
2573
- const filePath = resolve5(cwd, file);
2963
+ const filePath = resolve7(cwd, file);
2574
2964
  let yamlContent;
2575
2965
  try {
2576
- yamlContent = readFileSync10(filePath, "utf-8");
2966
+ yamlContent = readFileSync13(filePath, "utf-8");
2577
2967
  } catch {
2578
2968
  if (options.json) {
2579
2969
  console.log(
@@ -2651,16 +3041,74 @@ function themeApplyCommand(file, cwd, options) {
2651
3041
  }
2652
3042
  process.exit(1);
2653
3043
  }
3044
+ if (options.targetPath) {
3045
+ if (options.adapter !== "nextjs") {
3046
+ const message = "--target-path requires --adapter nextjs; see docs/blessed-builds.md";
3047
+ if (options.json) console.log(JSON.stringify({ success: false, error: message }));
3048
+ else logger.error(message);
3049
+ process.exit(1);
3050
+ }
3051
+ if (css === null) {
3052
+ process.exit(1);
3053
+ }
3054
+ if (!themeName || themeName.length === 0) {
3055
+ const message = "theme config is missing a `name` \u2014 required to derive the theme id for --target-path dispatch; see docs/blessed-builds.md";
3056
+ if (options.json) console.log(JSON.stringify({ success: false, error: message }));
3057
+ else logger.error(message);
3058
+ process.exit(1);
3059
+ }
3060
+ const buildDir = resolve7(cwd, options.targetPath);
3061
+ const manifestResult = parseBlessedManifest(buildDir);
3062
+ if (!manifestResult.ok) {
3063
+ if (options.json) {
3064
+ console.log(JSON.stringify({ success: false, error: manifestResult.error }));
3065
+ } else {
3066
+ logger.error(manifestResult.error);
3067
+ }
3068
+ process.exit(2);
3069
+ }
3070
+ let writtenPath;
3071
+ try {
3072
+ writtenPath = applyThemeToBuild({
3073
+ manifest: manifestResult.manifest,
3074
+ buildDir,
3075
+ themeId: themeName,
3076
+ adapterCss: css
3077
+ }).writtenPath;
3078
+ } catch (err) {
3079
+ const message = err instanceof Error ? err.message : String(err);
3080
+ if (options.json) console.log(JSON.stringify({ success: false, error: message }));
3081
+ else logger.error(message);
3082
+ process.exit(1);
3083
+ }
3084
+ if (options.json) {
3085
+ console.log(
3086
+ JSON.stringify({
3087
+ success: true,
3088
+ file: writtenPath,
3089
+ adapter: options.adapter,
3090
+ size: css.length,
3091
+ targetPath: buildDir
3092
+ })
3093
+ );
3094
+ } else {
3095
+ logger.success(`Theme CSS written via blessed-manifest dispatch: ${writtenPath}`);
3096
+ logger.info(`Adapter: ${options.adapter}`);
3097
+ logger.item(`Build root: ${buildDir}`);
3098
+ logger.item(`Size: ${formatSize(css.length)}`);
3099
+ }
3100
+ return;
3101
+ }
2654
3102
  const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
2655
- const outputPath = resolve5(cwd, outputTarget);
3103
+ const outputPath = resolve7(cwd, outputTarget);
2656
3104
  if (fileMap) {
2657
3105
  try {
2658
- mkdirSync3(outputPath, { recursive: true });
3106
+ mkdirSync6(outputPath, { recursive: true });
2659
3107
  let totalBytes = 0;
2660
3108
  for (const [relPath, content] of Object.entries(fileMap.files)) {
2661
- const filePath2 = join10(outputPath, relPath);
2662
- mkdirSync3(dirname5(filePath2), { recursive: true });
2663
- writeFileSync5(filePath2, content, "utf-8");
3109
+ const filePath2 = join15(outputPath, relPath);
3110
+ mkdirSync6(dirname7(filePath2), { recursive: true });
3111
+ writeFileSync8(filePath2, content, "utf-8");
2664
3112
  totalBytes += content.length;
2665
3113
  }
2666
3114
  if (options.json) {
@@ -2697,10 +3145,10 @@ function themeApplyCommand(file, cwd, options) {
2697
3145
  if (css === null) {
2698
3146
  process.exit(1);
2699
3147
  }
2700
- const outputDir = dirname5(outputPath);
3148
+ const outputDir = dirname7(outputPath);
2701
3149
  try {
2702
- mkdirSync3(outputDir, { recursive: true });
2703
- writeFileSync5(outputPath, css, "utf-8");
3150
+ mkdirSync6(outputDir, { recursive: true });
3151
+ writeFileSync8(outputPath, css, "utf-8");
2704
3152
  } catch {
2705
3153
  if (options.json) {
2706
3154
  console.log(
@@ -2741,8 +3189,8 @@ function formatSize(bytes) {
2741
3189
  }
2742
3190
 
2743
3191
  // src/commands/theme-export.ts
2744
- import { readFileSync as readFileSync11 } from "fs";
2745
- import { resolve as resolve6 } from "path";
3192
+ import { readFileSync as readFileSync14 } from "fs";
3193
+ import { resolve as resolve8 } from "path";
2746
3194
  import {
2747
3195
  parseConfig,
2748
3196
  resolveConfig,
@@ -2750,10 +3198,10 @@ import {
2750
3198
  exportTheme
2751
3199
  } from "@loworbitstudio/visor-theme-engine";
2752
3200
  function themeExportCommand(file, cwd, options) {
2753
- const filePath = resolve6(cwd, file ?? ".visor.yaml");
3201
+ const filePath = resolve8(cwd, file ?? ".visor.yaml");
2754
3202
  let yamlContent;
2755
3203
  try {
2756
- yamlContent = readFileSync11(filePath, "utf-8");
3204
+ yamlContent = readFileSync14(filePath, "utf-8");
2757
3205
  } catch {
2758
3206
  if (options.json) {
2759
3207
  console.log(
@@ -2805,16 +3253,16 @@ function themeExportCommand(file, cwd, options) {
2805
3253
  }
2806
3254
 
2807
3255
  // src/commands/theme-validate.ts
2808
- import { readFileSync as readFileSync12 } from "fs";
2809
- import { resolve as resolve7 } from "path";
3256
+ import { readFileSync as readFileSync15 } from "fs";
3257
+ import { resolve as resolve9 } from "path";
2810
3258
  import { parse as parseYaml2 } from "yaml";
2811
3259
  import { validate } from "@loworbitstudio/visor-theme-engine";
2812
3260
  import pc3 from "picocolors";
2813
3261
  function themeValidateCommand(file, cwd, options) {
2814
- const filePath = resolve7(cwd, file);
3262
+ const filePath = resolve9(cwd, file);
2815
3263
  let fileContent;
2816
3264
  try {
2817
- fileContent = readFileSync12(filePath, "utf-8");
3265
+ fileContent = readFileSync15(filePath, "utf-8");
2818
3266
  } catch {
2819
3267
  if (options.json) {
2820
3268
  console.log(
@@ -2903,8 +3351,8 @@ function printIssue(issue) {
2903
3351
 
2904
3352
  // src/commands/theme-verify.ts
2905
3353
  import { spawnSync as _spawnSync } from "child_process";
2906
- import { existsSync as existsSync8 } from "fs";
2907
- import { resolve as resolve8 } from "path";
3354
+ import { existsSync as existsSync12 } from "fs";
3355
+ import { resolve as resolve10 } from "path";
2908
3356
  function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
2909
3357
  const target = options.target ?? "flutter";
2910
3358
  if (target !== "flutter") {
@@ -2926,8 +3374,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
2926
3374
  }
2927
3375
  process.exit(1);
2928
3376
  }
2929
- const dirPath = resolve8(cwd, dir);
2930
- if (!existsSync8(dirPath)) {
3377
+ const dirPath = resolve10(cwd, dir);
3378
+ if (!existsSync12(dirPath)) {
2931
3379
  if (options.json) {
2932
3380
  console.log(
2933
3381
  JSON.stringify({
@@ -3014,8 +3462,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3014
3462
  }
3015
3463
 
3016
3464
  // src/commands/theme-extract.ts
3017
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3018
- import { resolve as resolve9, join as join11, basename as basename2, extname as extname3, relative } from "path";
3465
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3466
+ import { resolve as resolve11, join as join16, basename as basename3, extname as extname3, relative } from "path";
3019
3467
  import { stringify as stringifyYaml } from "yaml";
3020
3468
  import {
3021
3469
  extractFromCSS,
@@ -3044,8 +3492,8 @@ var CSS_DIRS = [
3044
3492
  "packages/design-tokens"
3045
3493
  ];
3046
3494
  function themeExtractCommand(cwd, options) {
3047
- const targetDir = resolve9(cwd, options.from ?? ".");
3048
- if (!existsSync9(targetDir)) {
3495
+ const targetDir = resolve11(cwd, options.from ?? ".");
3496
+ if (!existsSync13(targetDir)) {
3049
3497
  if (options.json) {
3050
3498
  console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
3051
3499
  } else {
@@ -3099,16 +3547,16 @@ function collectCSSFiles(targetDir) {
3099
3547
  const files = [];
3100
3548
  const seen = /* @__PURE__ */ new Set();
3101
3549
  for (const pattern2 of CSS_FILE_PATTERNS) {
3102
- const rootPath = join11(targetDir, pattern2);
3550
+ const rootPath = join16(targetDir, pattern2);
3103
3551
  addFileIfExists(rootPath, files, seen);
3104
3552
  for (const dir of CSS_DIRS) {
3105
- const dirPath = join11(targetDir, dir, pattern2);
3553
+ const dirPath = join16(targetDir, dir, pattern2);
3106
3554
  addFileIfExists(dirPath, files, seen);
3107
3555
  }
3108
3556
  }
3109
3557
  for (const dir of CSS_DIRS) {
3110
- const dirPath = join11(targetDir, dir);
3111
- if (existsSync9(dirPath) && statSync5(dirPath).isDirectory()) {
3558
+ const dirPath = join16(targetDir, dir);
3559
+ if (existsSync13(dirPath) && statSync5(dirPath).isDirectory()) {
3112
3560
  scanDirForCSS(dirPath, files, seen, 2);
3113
3561
  }
3114
3562
  }
@@ -3116,11 +3564,11 @@ function collectCSSFiles(targetDir) {
3116
3564
  return files;
3117
3565
  }
3118
3566
  function addFileIfExists(filePath, files, seen) {
3119
- const resolved = resolve9(filePath);
3567
+ const resolved = resolve11(filePath);
3120
3568
  if (seen.has(resolved)) return;
3121
- if (!existsSync9(resolved)) return;
3569
+ if (!existsSync13(resolved)) return;
3122
3570
  try {
3123
- const content = readFileSync13(resolved, "utf-8");
3571
+ const content = readFileSync16(resolved, "utf-8");
3124
3572
  if (content.includes("--")) {
3125
3573
  files.push({ path: resolved, content });
3126
3574
  seen.add(resolved);
@@ -3129,7 +3577,7 @@ function addFileIfExists(filePath, files, seen) {
3129
3577
  }
3130
3578
  }
3131
3579
  function scanDirForCSS(dir, files, seen, maxDepth) {
3132
- if (!existsSync9(dir)) return;
3580
+ if (!existsSync13(dir)) return;
3133
3581
  const SKIP_DIRS = /* @__PURE__ */ new Set([
3134
3582
  "node_modules",
3135
3583
  ".next",
@@ -3148,10 +3596,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
3148
3596
  if (entry.isDirectory()) {
3149
3597
  if (SKIP_DIRS.has(entry.name)) continue;
3150
3598
  if (maxDepth > 0) {
3151
- scanDirForCSS(join11(dir, entry.name), files, seen, maxDepth - 1);
3599
+ scanDirForCSS(join16(dir, entry.name), files, seen, maxDepth - 1);
3152
3600
  }
3153
3601
  } else if (entry.isFile() && extname3(entry.name) === ".css") {
3154
- addFileIfExists(join11(dir, entry.name), files, seen);
3602
+ addFileIfExists(join16(dir, entry.name), files, seen);
3155
3603
  }
3156
3604
  }
3157
3605
  } catch {
@@ -3233,10 +3681,10 @@ function extractVarName(varExpr) {
3233
3681
  function parseNextFontFromLayouts(targetDir) {
3234
3682
  const fontMap = /* @__PURE__ */ new Map();
3235
3683
  for (const relPath of LAYOUT_FILE_PATHS) {
3236
- const fullPath = join11(targetDir, relPath);
3237
- if (!existsSync9(fullPath)) continue;
3684
+ const fullPath = join16(targetDir, relPath);
3685
+ if (!existsSync13(fullPath)) continue;
3238
3686
  try {
3239
- const content = readFileSync13(fullPath, "utf-8");
3687
+ const content = readFileSync16(fullPath, "utf-8");
3240
3688
  parseNextFontDeclarations(content, fontMap);
3241
3689
  } catch {
3242
3690
  }
@@ -3283,7 +3731,7 @@ function parseNextFontDeclarations(content, fontMap) {
3283
3731
  const srcMatch = block.match(/src\s*:\s*["']([^"']+)["']/);
3284
3732
  if (srcMatch) {
3285
3733
  const srcPath = srcMatch[1];
3286
- const fileName = basename2(srcPath, extname3(srcPath));
3734
+ const fileName = basename3(srcPath, extname3(srcPath));
3287
3735
  const fontBaseName = fileName.replace(/[-_](Variable|Regular|Bold|Light|Medium|SemiBold|ExtraBold|Thin|Black|Italic).*$/i, "").replace(/[-_]/g, " ").trim();
3288
3736
  if (fontBaseName) {
3289
3737
  fontMap.set(varName, fontBaseName);
@@ -3321,10 +3769,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
3321
3769
  "IBM Plex Mono"
3322
3770
  ]);
3323
3771
  function extractFontHints(targetDir) {
3324
- const pkgPath = join11(targetDir, "package.json");
3325
- if (!existsSync9(pkgPath)) return void 0;
3772
+ const pkgPath = join16(targetDir, "package.json");
3773
+ if (!existsSync13(pkgPath)) return void 0;
3326
3774
  try {
3327
- const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3775
+ const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
3328
3776
  const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
3329
3777
  const fonts2 = [];
3330
3778
  for (const [dep, _] of Object.entries(allDeps)) {
@@ -3360,10 +3808,10 @@ function extractFontHints(targetDir) {
3360
3808
  }
3361
3809
  }
3362
3810
  function inferThemeName(targetDir) {
3363
- const pkgPath = join11(targetDir, "package.json");
3364
- if (existsSync9(pkgPath)) {
3811
+ const pkgPath = join16(targetDir, "package.json");
3812
+ if (existsSync13(pkgPath)) {
3365
3813
  try {
3366
- const pkg2 = JSON.parse(readFileSync13(pkgPath, "utf-8"));
3814
+ const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
3367
3815
  if (pkg2.name) {
3368
3816
  const name = pkg2.name.replace(/^@[\w-]+\//, "");
3369
3817
  return `${name}-theme`;
@@ -3371,7 +3819,7 @@ function inferThemeName(targetDir) {
3371
3819
  } catch {
3372
3820
  }
3373
3821
  }
3374
- return `${basename2(targetDir)}-theme`;
3822
+ return `${basename3(targetDir)}-theme`;
3375
3823
  }
3376
3824
  function confidenceComment(confidence) {
3377
3825
  return `# confidence: ${confidence}`;
@@ -3400,7 +3848,7 @@ function outputJSON(result, validationResult) {
3400
3848
  }
3401
3849
  function outputYAML(result, outputPath, cwd, validationResult) {
3402
3850
  const yamlStr = buildAnnotatedYAML(result);
3403
- const outFile = resolve9(cwd, outputPath ?? ".visor.yaml");
3851
+ const outFile = resolve11(cwd, outputPath ?? ".visor.yaml");
3404
3852
  const high = result.tokens.filter((t) => t.confidence === "high").length;
3405
3853
  const med = result.tokens.filter((t) => t.confidence === "medium").length;
3406
3854
  const low = result.tokens.filter((t) => t.confidence === "low").length;
@@ -3428,7 +3876,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
3428
3876
  }
3429
3877
  logger.blank();
3430
3878
  }
3431
- writeFileSync6(outFile, yamlStr, "utf-8");
3879
+ writeFileSync9(outFile, yamlStr, "utf-8");
3432
3880
  logger.success(`Theme written to ${relative(cwd, outFile)}`);
3433
3881
  if (validationResult) {
3434
3882
  logger.blank();
@@ -3486,14 +3934,14 @@ function buildAnnotatedYAML(result) {
3486
3934
  }
3487
3935
 
3488
3936
  // src/commands/theme-register.ts
3489
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync11 } from "fs";
3490
- import { resolve as resolve11, join as join13 } from "path";
3937
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
3938
+ import { resolve as resolve13, join as join18 } from "path";
3491
3939
  import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
3492
3940
  import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
3493
3941
 
3494
3942
  // src/utils/theme-helpers.ts
3495
- import { existsSync as existsSync10, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync14, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
3496
- import { resolve as resolve10, dirname as dirname6, join as join12 } from "path";
3943
+ import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync17, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
3944
+ import { resolve as resolve12, dirname as dirname8, join as join17 } from "path";
3497
3945
  import { execFileSync as execFileSync3 } from "child_process";
3498
3946
  function toSlug(name) {
3499
3947
  return name.toLowerCase().replace(/\s+/g, "-");
@@ -3502,12 +3950,12 @@ function toLabel(name) {
3502
3950
  return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
3503
3951
  }
3504
3952
  function findRepoRoot(startDir) {
3505
- let current = resolve10(startDir);
3953
+ let current = resolve12(startDir);
3506
3954
  while (true) {
3507
- if (existsSync10(join12(current, "packages", "docs"))) {
3955
+ if (existsSync14(join17(current, "packages", "docs"))) {
3508
3956
  return current;
3509
3957
  }
3510
- const parent = dirname6(current);
3958
+ const parent = dirname8(current);
3511
3959
  if (parent === current) break;
3512
3960
  current = parent;
3513
3961
  }
@@ -3521,9 +3969,9 @@ function findMainRepoRoot(startDir) {
3521
3969
  stdio: ["ignore", "pipe", "ignore"]
3522
3970
  }).trim();
3523
3971
  if (commonDir) {
3524
- const absoluteCommonDir = resolve10(startDir, commonDir);
3525
- const candidate = dirname6(absoluteCommonDir);
3526
- if (existsSync10(join12(candidate, "packages", "docs"))) {
3972
+ const absoluteCommonDir = resolve12(startDir, commonDir);
3973
+ const candidate = dirname8(absoluteCommonDir);
3974
+ if (existsSync14(join17(candidate, "packages", "docs"))) {
3527
3975
  return candidate;
3528
3976
  }
3529
3977
  }
@@ -3542,10 +3990,10 @@ var BrokenSymlinkError = class extends Error {
3542
3990
  code = "BROKEN_SYMLINK";
3543
3991
  };
3544
3992
  function assertNoBrokenSymlinks(dir) {
3545
- if (!existsSync10(dir)) return;
3993
+ if (!existsSync14(dir)) return;
3546
3994
  const entries = readdirSync5(dir, { withFileTypes: true });
3547
3995
  for (const entry of entries) {
3548
- const entryPath = join12(dir, entry.name);
3996
+ const entryPath = join17(dir, entry.name);
3549
3997
  let lst;
3550
3998
  try {
3551
3999
  lst = lstatSync(entryPath);
@@ -3563,39 +4011,39 @@ function assertNoBrokenSymlinks(dir) {
3563
4011
  }
3564
4012
  }
3565
4013
  function scanParentForPrivateThemes(parentDir) {
3566
- if (!existsSync10(parentDir)) return [];
4014
+ if (!existsSync14(parentDir)) return [];
3567
4015
  const entries = readdirSync5(parentDir, { withFileTypes: true });
3568
4016
  const matches = [];
3569
4017
  for (const entry of entries) {
3570
4018
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
3571
- const candidate = join12(parentDir, entry.name, "visor-themes-private", "themes");
3572
- if (existsSync10(candidate)) {
4019
+ const candidate = join17(parentDir, entry.name, "visor-themes-private", "themes");
4020
+ if (existsSync14(candidate)) {
3573
4021
  matches.push(candidate);
3574
4022
  }
3575
4023
  }
3576
4024
  return matches.sort();
3577
4025
  }
3578
4026
  function scanNestedThemeDir(dir) {
3579
- if (!existsSync10(dir)) return [];
4027
+ if (!existsSync14(dir)) return [];
3580
4028
  assertNoBrokenSymlinks(dir);
3581
4029
  const entries = readdirSync5(dir, { withFileTypes: true });
3582
4030
  const out = [];
3583
4031
  for (const entry of entries) {
3584
4032
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
3585
- const themeFile = join12(dir, entry.name, "theme.visor.yaml");
3586
- if (existsSync10(themeFile)) {
4033
+ const themeFile = join17(dir, entry.name, "theme.visor.yaml");
4034
+ if (existsSync14(themeFile)) {
3587
4035
  out.push({ filePath: themeFile, slug: entry.name });
3588
4036
  }
3589
4037
  }
3590
4038
  return out;
3591
4039
  }
3592
4040
  function detectVisorWorkspace(cwd) {
3593
- let current = resolve10(cwd);
4041
+ let current = resolve12(cwd);
3594
4042
  while (true) {
3595
- const pkgPath = join12(current, "package.json");
3596
- if (existsSync10(pkgPath)) {
4043
+ const pkgPath = join17(current, "package.json");
4044
+ if (existsSync14(pkgPath)) {
3597
4045
  try {
3598
- const pkg2 = JSON.parse(readFileSync14(pkgPath, "utf-8"));
4046
+ const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
3599
4047
  const workspaces = pkg2.workspaces ?? [];
3600
4048
  const hasCli = workspaces.some((w) => w.includes("packages/cli"));
3601
4049
  const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
@@ -3605,7 +4053,7 @@ function detectVisorWorkspace(cwd) {
3605
4053
  } catch {
3606
4054
  }
3607
4055
  }
3608
- const parent = dirname6(current);
4056
+ const parent = dirname8(current);
3609
4057
  if (parent === current) break;
3610
4058
  current = parent;
3611
4059
  }
@@ -3613,18 +4061,18 @@ function detectVisorWorkspace(cwd) {
3613
4061
  }
3614
4062
  function isLocalVisorBinary(workspaceRoot, scriptPath) {
3615
4063
  if (!scriptPath) return false;
3616
- const expectedPrefix = join12(workspaceRoot, "packages", "cli", "dist");
4064
+ const expectedPrefix = join17(workspaceRoot, "packages", "cli", "dist");
3617
4065
  let resolvedScript;
3618
4066
  let resolvedPrefix;
3619
4067
  try {
3620
4068
  resolvedScript = realpathSync(scriptPath);
3621
4069
  } catch {
3622
- resolvedScript = resolve10(scriptPath);
4070
+ resolvedScript = resolve12(scriptPath);
3623
4071
  }
3624
4072
  try {
3625
4073
  resolvedPrefix = realpathSync(expectedPrefix);
3626
4074
  } catch {
3627
- resolvedPrefix = resolve10(expectedPrefix);
4075
+ resolvedPrefix = resolve12(expectedPrefix);
3628
4076
  }
3629
4077
  return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
3630
4078
  }
@@ -3714,10 +4162,10 @@ ${indent}${newEntry},
3714
4162
  return { updated, changed: true };
3715
4163
  }
3716
4164
  function themeRegisterCommand(file, cwd, options) {
3717
- const filePath = resolve11(cwd, file);
4165
+ const filePath = resolve13(cwd, file);
3718
4166
  let yamlContent;
3719
4167
  try {
3720
- yamlContent = readFileSync15(filePath, "utf-8");
4168
+ yamlContent = readFileSync18(filePath, "utf-8");
3721
4169
  } catch {
3722
4170
  if (options.json) {
3723
4171
  console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
@@ -3760,11 +4208,11 @@ function themeRegisterCommand(file, cwd, options) {
3760
4208
  process.exit(1);
3761
4209
  return;
3762
4210
  }
3763
- const docsAppDir = join13(repoRoot, "packages", "docs", "app");
3764
- const cssFilePath = join13(docsAppDir, `${slug2}-theme.css`);
3765
- const globalsPath = join13(docsAppDir, "globals.css");
3766
- const themeConfigPath = join13(repoRoot, "packages", "docs", "lib", "theme-config.ts");
3767
- if (!existsSync11(docsAppDir)) {
4211
+ const docsAppDir = join18(repoRoot, "packages", "docs", "app");
4212
+ const cssFilePath = join18(docsAppDir, `${slug2}-theme.css`);
4213
+ const globalsPath = join18(docsAppDir, "globals.css");
4214
+ const themeConfigPath = join18(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4215
+ if (!existsSync15(docsAppDir)) {
3768
4216
  const msg = `Docs app directory not found: ${docsAppDir}`;
3769
4217
  if (options.json) {
3770
4218
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -3777,8 +4225,8 @@ function themeRegisterCommand(file, cwd, options) {
3777
4225
  let globalsContent = "";
3778
4226
  let themeConfigContent = "";
3779
4227
  try {
3780
- globalsContent = readFileSync15(globalsPath, "utf-8");
3781
- themeConfigContent = readFileSync15(themeConfigPath, "utf-8");
4228
+ globalsContent = readFileSync18(globalsPath, "utf-8");
4229
+ themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
3782
4230
  } catch (err) {
3783
4231
  const msg = err instanceof Error ? err.message : "Could not read docs files";
3784
4232
  if (options.json) {
@@ -3789,8 +4237,8 @@ function themeRegisterCommand(file, cwd, options) {
3789
4237
  process.exit(1);
3790
4238
  return;
3791
4239
  }
3792
- const cssExists = existsSync11(cssFilePath);
3793
- const cssChanged = !cssExists || readFileSync15(cssFilePath, "utf-8") !== css;
4240
+ const cssExists = existsSync15(cssFilePath);
4241
+ const cssChanged = !cssExists || readFileSync18(cssFilePath, "utf-8") !== css;
3794
4242
  const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
3795
4243
  const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
3796
4244
  themeConfigContent,
@@ -3833,14 +4281,14 @@ function themeRegisterCommand(file, cwd, options) {
3833
4281
  }
3834
4282
  try {
3835
4283
  if (cssChanged) {
3836
- mkdirSync4(docsAppDir, { recursive: true });
3837
- writeFileSync7(cssFilePath, css, "utf-8");
4284
+ mkdirSync7(docsAppDir, { recursive: true });
4285
+ writeFileSync10(cssFilePath, css, "utf-8");
3838
4286
  }
3839
4287
  if (globalsChanged) {
3840
- writeFileSync7(globalsPath, newGlobals, "utf-8");
4288
+ writeFileSync10(globalsPath, newGlobals, "utf-8");
3841
4289
  }
3842
4290
  if (themeConfigChanged) {
3843
- writeFileSync7(themeConfigPath, newThemeConfig, "utf-8");
4291
+ writeFileSync10(themeConfigPath, newThemeConfig, "utf-8");
3844
4292
  }
3845
4293
  } catch (err) {
3846
4294
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -3874,8 +4322,8 @@ function themeRegisterCommand(file, cwd, options) {
3874
4322
  }
3875
4323
 
3876
4324
  // src/commands/theme-unregister.ts
3877
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync8, existsSync as existsSync12, unlinkSync } from "fs";
3878
- import { join as join14 } from "path";
4325
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, existsSync as existsSync16, unlinkSync } from "fs";
4326
+ import { join as join19 } from "path";
3879
4327
  function removeGlobalsImport(content, slug2) {
3880
4328
  const importLine = `@import './${slug2}-theme.css';`;
3881
4329
  if (!content.includes(importLine)) {
@@ -3907,11 +4355,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
3907
4355
  process.exit(1);
3908
4356
  return;
3909
4357
  }
3910
- const docsAppDir = join14(repoRoot, "packages", "docs", "app");
3911
- const cssFilePath = join14(docsAppDir, `${slug2}-theme.css`);
3912
- const globalsPath = join14(docsAppDir, "globals.css");
3913
- const themeConfigPath = join14(repoRoot, "packages", "docs", "lib", "theme-config.ts");
3914
- if (!existsSync12(docsAppDir)) {
4358
+ const docsAppDir = join19(repoRoot, "packages", "docs", "app");
4359
+ const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
4360
+ const globalsPath = join19(docsAppDir, "globals.css");
4361
+ const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4362
+ if (!existsSync16(docsAppDir)) {
3915
4363
  const msg = `Docs app directory not found: ${docsAppDir}`;
3916
4364
  if (options.json) {
3917
4365
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -3924,8 +4372,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
3924
4372
  let globalsContent = "";
3925
4373
  let themeConfigContent = "";
3926
4374
  try {
3927
- globalsContent = readFileSync16(globalsPath, "utf-8");
3928
- themeConfigContent = readFileSync16(themeConfigPath, "utf-8");
4375
+ globalsContent = readFileSync19(globalsPath, "utf-8");
4376
+ themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
3929
4377
  } catch (err) {
3930
4378
  const msg = err instanceof Error ? err.message : "Could not read docs files";
3931
4379
  if (options.json) {
@@ -3936,7 +4384,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
3936
4384
  process.exit(1);
3937
4385
  return;
3938
4386
  }
3939
- const cssExists = existsSync12(cssFilePath);
4387
+ const cssExists = existsSync16(cssFilePath);
3940
4388
  const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
3941
4389
  const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
3942
4390
  if (!cssExists && !globalsChanged && !themeConfigChanged) {
@@ -3949,8 +4397,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
3949
4397
  }
3950
4398
  try {
3951
4399
  if (cssExists) unlinkSync(cssFilePath);
3952
- if (globalsChanged) writeFileSync8(globalsPath, newGlobals, "utf-8");
3953
- if (themeConfigChanged) writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
4400
+ if (globalsChanged) writeFileSync11(globalsPath, newGlobals, "utf-8");
4401
+ if (themeConfigChanged) writeFileSync11(themeConfigPath, newThemeConfig, "utf-8");
3954
4402
  } catch (err) {
3955
4403
  const msg = err instanceof Error ? err.message : "Write failed";
3956
4404
  if (options.json) {
@@ -3977,15 +4425,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
3977
4425
 
3978
4426
  // src/commands/theme-sync.ts
3979
4427
  import {
3980
- readFileSync as readFileSync17,
3981
- writeFileSync as writeFileSync9,
3982
- mkdirSync as mkdirSync5,
3983
- existsSync as existsSync13,
4428
+ readFileSync as readFileSync20,
4429
+ writeFileSync as writeFileSync12,
4430
+ mkdirSync as mkdirSync8,
4431
+ existsSync as existsSync17,
3984
4432
  readdirSync as readdirSync6,
3985
4433
  unlinkSync as unlinkSync2,
3986
4434
  copyFileSync
3987
4435
  } from "fs";
3988
- import { join as join15, basename as basename3, resolve as resolve12, sep, relative as relative2 } from "path";
4436
+ import { join as join20, basename as basename4, resolve as resolve14, sep, relative as relative2 } from "path";
3989
4437
  import { parse as parseYaml3 } from "yaml";
3990
4438
  import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
3991
4439
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
@@ -3999,9 +4447,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
3999
4447
  var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
4000
4448
  var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
4001
4449
  function scanThemeDir(dir) {
4002
- if (!existsSync13(dir)) return [];
4450
+ if (!existsSync17(dir)) return [];
4003
4451
  assertNoBrokenSymlinks(dir);
4004
- return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join15(dir, f));
4452
+ return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join20(dir, f));
4005
4453
  }
4006
4454
  function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4007
4455
  const merged = /* @__PURE__ */ new Map();
@@ -4025,20 +4473,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4025
4473
  };
4026
4474
  const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
4027
4475
  if (envPath && envPath.trim() !== "") {
4028
- const resolved = resolve12(envPath);
4029
- if (!existsSync13(resolved)) {
4476
+ const resolved = resolve14(envPath);
4477
+ if (!existsSync17(resolved)) {
4030
4478
  throw new Error(
4031
4479
  `${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
4032
4480
  );
4033
4481
  }
4034
4482
  addNested(resolved, "env");
4035
4483
  }
4036
- const siblingPath = join15(mainRepoRoot, "..", "visor-themes-private", "themes");
4037
- const siblingExists = existsSync13(siblingPath);
4484
+ const siblingPath = join20(mainRepoRoot, "..", "visor-themes-private", "themes");
4485
+ const siblingExists = existsSync17(siblingPath);
4038
4486
  if (siblingExists) {
4039
4487
  addNested(siblingPath, "sibling");
4040
4488
  }
4041
- const parentDir = join15(mainRepoRoot, "..");
4489
+ const parentDir = join20(mainRepoRoot, "..");
4042
4490
  const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
4043
4491
  (path2) => !path2.startsWith(mainRepoRoot + sep)
4044
4492
  );
@@ -4059,10 +4507,10 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4059
4507
  }
4060
4508
  }
4061
4509
  }
4062
- const legacyDir = join15(repoRoot, "custom-themes");
4510
+ const legacyDir = join20(repoRoot, "custom-themes");
4063
4511
  const legacyFiles = scanThemeDir(legacyDir);
4064
4512
  for (const legacyFile of legacyFiles) {
4065
- const slug2 = basename3(legacyFile).replace(/\.visor\.yaml$/, "");
4513
+ const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
4066
4514
  const existing = merged.get(slug2);
4067
4515
  if (existing) {
4068
4516
  warn(
@@ -4116,14 +4564,14 @@ function reportBrokenSymlink(err, options) {
4116
4564
  }
4117
4565
  }
4118
4566
  function buildEmptySourcesMessage(mainRepoRoot) {
4119
- const expectedSibling = join15(mainRepoRoot, "..", "visor-themes-private");
4567
+ const expectedSibling = join20(mainRepoRoot, "..", "visor-themes-private");
4120
4568
  return [
4121
4569
  "No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
4122
4570
  "",
4123
4571
  "Resolution order checked:",
4124
4572
  ` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
4125
4573
  ` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
4126
- ` 3. One-level-deeper at ${join15(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
4574
+ ` 3. One-level-deeper at ${join20(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
4127
4575
  " 4. Legacy custom-themes/ (no .visor.yaml files)",
4128
4576
  "",
4129
4577
  "To fix, clone the private themes repo as a sibling:",
@@ -4328,14 +4776,14 @@ function themeSyncCommand(cwd, options) {
4328
4776
  return;
4329
4777
  }
4330
4778
  const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
4331
- const themesDir = join15(repoRoot, "themes");
4332
- const docsAppDir = join15(repoRoot, "packages", "docs", "app");
4333
- const docsLibDir = join15(repoRoot, "packages", "docs", "lib");
4334
- const docsPublicThemesDir = join15(repoRoot, "packages", "docs", "public", "themes");
4335
- const themeConfigPath = join15(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4336
- const globalsPath = join15(docsAppDir, "globals.css");
4337
- const customOverlayCssPath = join15(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
4338
- const customOverlayTsPath = join15(repoRoot, CUSTOM_OVERLAY_TS_PATH);
4779
+ const themesDir = join20(repoRoot, "themes");
4780
+ const docsAppDir = join20(repoRoot, "packages", "docs", "app");
4781
+ const docsLibDir = join20(repoRoot, "packages", "docs", "lib");
4782
+ const docsPublicThemesDir = join20(repoRoot, "packages", "docs", "public", "themes");
4783
+ const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4784
+ const globalsPath = join20(docsAppDir, "globals.css");
4785
+ const customOverlayCssPath = join20(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
4786
+ const customOverlayTsPath = join20(repoRoot, CUSTOM_OVERLAY_TS_PATH);
4339
4787
  let stockFiles;
4340
4788
  try {
4341
4789
  stockFiles = scanThemeDir(themesDir);
@@ -4392,7 +4840,7 @@ function themeSyncCommand(cwd, options) {
4392
4840
  const processFile = (filePath, isCustom, slugOverride) => {
4393
4841
  let yamlContent;
4394
4842
  try {
4395
- yamlContent = readFileSync17(filePath, "utf-8");
4843
+ yamlContent = readFileSync20(filePath, "utf-8");
4396
4844
  } catch {
4397
4845
  failures.push({ filePath, error: `Could not read: ${filePath}` });
4398
4846
  return;
@@ -4403,7 +4851,7 @@ function themeSyncCommand(cwd, options) {
4403
4851
  } catch (err) {
4404
4852
  failures.push({
4405
4853
  filePath,
4406
- error: `Failed to parse ${basename3(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
4854
+ error: `Failed to parse ${basename4(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
4407
4855
  });
4408
4856
  return;
4409
4857
  }
@@ -4417,12 +4865,12 @@ function themeSyncCommand(cwd, options) {
4417
4865
  for (const e of coverage.errors) {
4418
4866
  failures.push({
4419
4867
  filePath,
4420
- error: formatFontCoverageError(basename3(filePath), e.declaredAt, e.family)
4868
+ error: formatFontCoverageError(basename4(filePath), e.declaredAt, e.family)
4421
4869
  });
4422
4870
  }
4423
4871
  return;
4424
4872
  }
4425
- const yamlFilename = slugOverride ?? basename3(filePath).replace(/\.visor\.yaml$/, "");
4873
+ const yamlFilename = slugOverride ?? basename4(filePath).replace(/\.visor\.yaml$/, "");
4426
4874
  manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
4427
4875
  };
4428
4876
  for (const f of stockFiles) processFile(f, false);
@@ -4442,8 +4890,8 @@ function themeSyncCommand(cwd, options) {
4442
4890
  let globalsContent;
4443
4891
  let themeConfigContent;
4444
4892
  try {
4445
- globalsContent = readFileSync17(globalsPath, "utf-8");
4446
- themeConfigContent = readFileSync17(themeConfigPath, "utf-8");
4893
+ globalsContent = readFileSync20(globalsPath, "utf-8");
4894
+ themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
4447
4895
  } catch (err) {
4448
4896
  const msg = err instanceof Error ? err.message : "Could not read docs files";
4449
4897
  if (options.json) {
@@ -4458,12 +4906,12 @@ function themeSyncCommand(cwd, options) {
4458
4906
  const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
4459
4907
  const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
4460
4908
  const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
4461
- const existingCssFiles = existsSync13(docsAppDir) ? readdirSync6(docsAppDir).filter(
4909
+ const existingCssFiles = existsSync17(docsAppDir) ? readdirSync6(docsAppDir).filter(
4462
4910
  (f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
4463
4911
  ) : [];
4464
4912
  const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
4465
4913
  const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
4466
- const existingPublicYamls = existsSync13(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
4914
+ const existingPublicYamls = existsSync17(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
4467
4915
  const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
4468
4916
  const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
4469
4917
  if (options.dryRun) {
@@ -4503,28 +4951,28 @@ function themeSyncCommand(cwd, options) {
4503
4951
  return;
4504
4952
  }
4505
4953
  try {
4506
- mkdirSync5(docsAppDir, { recursive: true });
4507
- mkdirSync5(docsLibDir, { recursive: true });
4508
- mkdirSync5(docsPublicThemesDir, { recursive: true });
4954
+ mkdirSync8(docsAppDir, { recursive: true });
4955
+ mkdirSync8(docsLibDir, { recursive: true });
4956
+ mkdirSync8(docsPublicThemesDir, { recursive: true });
4509
4957
  for (const entry of manifest) {
4510
- writeFileSync9(join15(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
4958
+ writeFileSync12(join20(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
4511
4959
  }
4512
4960
  for (const stale of staleCssFiles) {
4513
- unlinkSync2(join15(docsAppDir, stale));
4961
+ unlinkSync2(join20(docsAppDir, stale));
4514
4962
  }
4515
- writeFileSync9(customOverlayCssPath, newCustomOverlayCss, "utf-8");
4516
- writeFileSync9(customOverlayTsPath, newCustomOverlayTs, "utf-8");
4517
- writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
4518
- writeFileSync9(globalsPath, newGlobals, "utf-8");
4963
+ writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
4964
+ writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
4965
+ writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
4966
+ writeFileSync12(globalsPath, newGlobals, "utf-8");
4519
4967
  for (const srcFile of stockFiles) {
4520
- copyFileSync(srcFile, join15(docsPublicThemesDir, basename3(srcFile)));
4968
+ copyFileSync(srcFile, join20(docsPublicThemesDir, basename4(srcFile)));
4521
4969
  }
4522
4970
  for (const c of customSources) {
4523
- const targetName = c.origin === "legacy" ? basename3(c.filePath) : `${c.slug}.visor.yaml`;
4524
- copyFileSync(c.filePath, join15(docsPublicThemesDir, targetName));
4971
+ const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
4972
+ copyFileSync(c.filePath, join20(docsPublicThemesDir, targetName));
4525
4973
  }
4526
4974
  for (const stale of stalePublicYamls) {
4527
- unlinkSync2(join15(docsPublicThemesDir, stale));
4975
+ unlinkSync2(join20(docsPublicThemesDir, stale));
4528
4976
  }
4529
4977
  } catch (err) {
4530
4978
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -4577,19 +5025,19 @@ function themeSyncCommand(cwd, options) {
4577
5025
 
4578
5026
  // src/commands/theme-batch-apply-flutter.ts
4579
5027
  import {
4580
- readFileSync as readFileSync18,
4581
- writeFileSync as writeFileSync10,
4582
- mkdirSync as mkdirSync6,
4583
- existsSync as existsSync14,
5028
+ readFileSync as readFileSync21,
5029
+ writeFileSync as writeFileSync13,
5030
+ mkdirSync as mkdirSync9,
5031
+ existsSync as existsSync18,
4584
5032
  readdirSync as readdirSync7,
4585
5033
  rmSync
4586
5034
  } from "fs";
4587
- import { join as join16, basename as basename4, dirname as dirname7 } from "path";
5035
+ import { join as join21, basename as basename5, dirname as dirname9 } from "path";
4588
5036
  import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
4589
5037
  import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
4590
5038
  function scanThemeDir2(dir) {
4591
- if (!existsSync14(dir)) return [];
4592
- return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join16(dir, f)).sort();
5039
+ if (!existsSync18(dir)) return [];
5040
+ return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f)).sort();
4593
5041
  }
4594
5042
  function slugToCamel(slug2) {
4595
5043
  return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -4722,9 +5170,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4722
5170
  process.exit(1);
4723
5171
  return;
4724
5172
  }
4725
- const themesDir = join16(repoRoot, "themes");
4726
- const customThemesDir = join16(repoRoot, "custom-themes");
4727
- const outputDir = join16(repoRoot, "packages", "visor_themes");
5173
+ const themesDir = join21(repoRoot, "themes");
5174
+ const customThemesDir = join21(repoRoot, "custom-themes");
5175
+ const outputDir = join21(repoRoot, "packages", "visor_themes");
4728
5176
  const stockFiles = scanThemeDir2(themesDir);
4729
5177
  const customFiles = scanThemeDir2(customThemesDir);
4730
5178
  const allFiles = [...stockFiles, ...customFiles];
@@ -4745,7 +5193,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4745
5193
  for (const filePath of allFiles) {
4746
5194
  let yamlContent;
4747
5195
  try {
4748
- yamlContent = readFileSync18(filePath, "utf-8");
5196
+ yamlContent = readFileSync21(filePath, "utf-8");
4749
5197
  } catch {
4750
5198
  errors.push(`Could not read: ${filePath}`);
4751
5199
  continue;
@@ -4755,7 +5203,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4755
5203
  data = generateThemeData6(yamlContent);
4756
5204
  } catch (err) {
4757
5205
  errors.push(
4758
- `Failed to parse ${basename4(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
5206
+ `Failed to parse ${basename5(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
4759
5207
  );
4760
5208
  continue;
4761
5209
  }
@@ -4824,8 +5272,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4824
5272
  return;
4825
5273
  }
4826
5274
  const slugs = processed.map((p) => p.slug);
4827
- const libSrcDir = join16(outputDir, "lib", "src");
4828
- if (existsSync14(libSrcDir)) {
5275
+ const libSrcDir = join21(outputDir, "lib", "src");
5276
+ if (existsSync18(libSrcDir)) {
4829
5277
  rmSync(libSrcDir, { recursive: true, force: true });
4830
5278
  }
4831
5279
  const packageFiles = {
@@ -4837,17 +5285,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4837
5285
  };
4838
5286
  let totalFiles = 0;
4839
5287
  for (const [relPath, content] of Object.entries(packageFiles)) {
4840
- const absPath = join16(outputDir, relPath);
4841
- mkdirSync6(dirname7(absPath), { recursive: true });
4842
- writeFileSync10(absPath, content, "utf-8");
5288
+ const absPath = join21(outputDir, relPath);
5289
+ mkdirSync9(dirname9(absPath), { recursive: true });
5290
+ writeFileSync13(absPath, content, "utf-8");
4843
5291
  totalFiles++;
4844
5292
  }
4845
5293
  for (const { slug: slug2, tokenFiles } of processed) {
4846
- const themeBaseDir = join16(outputDir, "lib", "src", slug2);
5294
+ const themeBaseDir = join21(outputDir, "lib", "src", slug2);
4847
5295
  for (const [relPath, content] of Object.entries(tokenFiles)) {
4848
- const absPath = join16(themeBaseDir, relPath);
4849
- mkdirSync6(dirname7(absPath), { recursive: true });
4850
- writeFileSync10(absPath, content, "utf-8");
5296
+ const absPath = join21(themeBaseDir, relPath);
5297
+ mkdirSync9(dirname9(absPath), { recursive: true });
5298
+ writeFileSync13(absPath, content, "utf-8");
4851
5299
  totalFiles++;
4852
5300
  }
4853
5301
  }
@@ -4868,11 +5316,11 @@ function themeBatchApplyFlutterCommand(cwd, options) {
4868
5316
  }
4869
5317
 
4870
5318
  // src/commands/fonts-add.ts
4871
- import { existsSync as existsSync15, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync19 } from "fs";
4872
- import { resolve as resolve13, basename as basename5, extname as extname4 } from "path";
5319
+ import { existsSync as existsSync19, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync22 } from "fs";
5320
+ import { resolve as resolve15, basename as basename6, extname as extname4 } from "path";
4873
5321
  import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
4874
5322
  function deriveFamilySlug(filename) {
4875
- const name = basename5(filename, extname4(filename));
5323
+ const name = basename6(filename, extname4(filename));
4876
5324
  const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
4877
5325
  "thin",
4878
5326
  "hairline",
@@ -4913,21 +5361,21 @@ function deriveFamilySlug(filename) {
4913
5361
  return parts.join("-").toLowerCase();
4914
5362
  }
4915
5363
  function collectWoff2Files(inputPath) {
4916
- const resolved = resolve13(inputPath);
4917
- if (!existsSync15(resolved)) {
5364
+ const resolved = resolve15(inputPath);
5365
+ if (!existsSync19(resolved)) {
4918
5366
  throw new Error(`Path not found: ${resolved}`);
4919
5367
  }
4920
5368
  const stat = statSync7(resolved);
4921
5369
  if (stat.isFile()) {
4922
5370
  if (extname4(resolved).toLowerCase() !== ".woff2") {
4923
5371
  throw new Error(
4924
- `Invalid file format: ${basename5(resolved)}. Only .woff2 files are accepted.`
5372
+ `Invalid file format: ${basename6(resolved)}. Only .woff2 files are accepted.`
4925
5373
  );
4926
5374
  }
4927
5375
  return [resolved];
4928
5376
  }
4929
5377
  if (stat.isDirectory()) {
4930
- const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve13(resolved, f));
5378
+ const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve15(resolved, f));
4931
5379
  if (files.length === 0) {
4932
5380
  throw new Error(
4933
5381
  `No .woff2 files found in directory: ${resolved}`
@@ -4942,8 +5390,8 @@ function collectWoff2Files(inputPath) {
4942
5390
  throw new Error(`Path is neither a file nor a directory: ${resolved}`);
4943
5391
  }
4944
5392
  function getNonWoff2Fonts(inputPath) {
4945
- const resolved = resolve13(inputPath);
4946
- if (!existsSync15(resolved) || !statSync7(resolved).isDirectory()) {
5393
+ const resolved = resolve15(inputPath);
5394
+ if (!existsSync19(resolved) || !statSync7(resolved).isDirectory()) {
4947
5395
  return [];
4948
5396
  }
4949
5397
  return readdirSync8(resolved).filter((f) => {
@@ -4980,7 +5428,7 @@ function createR2Client(config) {
4980
5428
  });
4981
5429
  }
4982
5430
  async function uploadFile(client, bucket, key, filePath) {
4983
- const body = readFileSync19(filePath);
5431
+ const body = readFileSync22(filePath);
4984
5432
  await client.send(
4985
5433
  new PutObjectCommand({
4986
5434
  Bucket: bucket,
@@ -4995,8 +5443,8 @@ async function fontsAddCommand(inputPath, options) {
4995
5443
  try {
4996
5444
  const r2Config = getR2Config();
4997
5445
  const files = collectWoff2Files(inputPath);
4998
- const familySlug = options.family ?? deriveFamilySlug(basename5(files[0]));
4999
- const resolved = resolve13(inputPath);
5446
+ const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
5447
+ const resolved = resolve15(inputPath);
5000
5448
  const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
5001
5449
  if (!json) {
5002
5450
  logger.heading("Visor Font Upload");
@@ -5015,7 +5463,7 @@ async function fontsAddCommand(inputPath, options) {
5015
5463
  const bucket = "visor-fonts";
5016
5464
  const results = [];
5017
5465
  for (const filePath of files) {
5018
- const filename = basename5(filePath);
5466
+ const filename = basename6(filePath);
5019
5467
  const key = buildS3Key(org, familySlug, filename);
5020
5468
  if (!json) {
5021
5469
  logger.info(`Uploading ${filename}...`);
@@ -5284,27 +5732,27 @@ function findCssFiles(dir, maxDepth = 3) {
5284
5732
  }
5285
5733
 
5286
5734
  // src/utils/patterns.ts
5287
- import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
5288
- import { join as join18 } from "path";
5735
+ import { existsSync as existsSync21, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
5736
+ import { join as join23 } from "path";
5289
5737
  import { parse as parseYAML } from "yaml";
5290
5738
  function loadPatternsFromYaml(repoRoot) {
5291
- const patternsDir = join18(repoRoot, "patterns");
5292
- if (!existsSync17(patternsDir)) return [];
5739
+ const patternsDir = join23(repoRoot, "patterns");
5740
+ if (!existsSync21(patternsDir)) return [];
5293
5741
  const files = readdirSync10(patternsDir).filter(
5294
5742
  (f) => f.endsWith(".visor-pattern.yaml")
5295
5743
  );
5296
5744
  return files.map((file) => {
5297
- const content = readFileSync21(join18(patternsDir, file), "utf-8");
5745
+ const content = readFileSync24(join23(patternsDir, file), "utf-8");
5298
5746
  return parseYAML(content);
5299
5747
  }).filter(Boolean);
5300
5748
  }
5301
5749
  function findRepoRoot2(startDir) {
5302
5750
  let current = startDir;
5303
5751
  while (true) {
5304
- if (existsSync17(join18(current, "patterns"))) {
5752
+ if (existsSync21(join23(current, "patterns"))) {
5305
5753
  return current;
5306
5754
  }
5307
- const parent = join18(current, "..");
5755
+ const parent = join23(current, "..");
5308
5756
  if (parent === current) return null;
5309
5757
  current = parent;
5310
5758
  }
@@ -5683,8 +6131,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
5683
6131
  }
5684
6132
 
5685
6133
  // src/commands/migrate-token-substitution.ts
5686
- import { readFileSync as readFileSync22, writeFileSync as writeFileSync11, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync18 } from "fs";
5687
- import { resolve as resolve14, join as join19, relative as relative4 } from "path";
6134
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync22 } from "fs";
6135
+ import { resolve as resolve16, join as join24, relative as relative4 } from "path";
5688
6136
  import { parse as parseYaml4 } from "yaml";
5689
6137
  import pc4 from "picocolors";
5690
6138
  var V7_ENTR_SUBSTITUTION_MAP = {
@@ -5707,14 +6155,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
5707
6155
  var DEFAULT_THEME_ID = "entr";
5708
6156
  function readMapFromThemeFile(themeId, cwd) {
5709
6157
  const candidates = [
5710
- join19(cwd, "themes", `${themeId}.visor.yaml`),
5711
- join19(cwd, "custom-themes", `${themeId}.visor.yaml`),
5712
- join19(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
6158
+ join24(cwd, "themes", `${themeId}.visor.yaml`),
6159
+ join24(cwd, "custom-themes", `${themeId}.visor.yaml`),
6160
+ join24(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
5713
6161
  ];
5714
6162
  for (const candidate of candidates) {
5715
- if (existsSync18(candidate)) {
6163
+ if (existsSync22(candidate)) {
5716
6164
  try {
5717
- const raw = readFileSync22(candidate, "utf-8");
6165
+ const raw = readFileSync25(candidate, "utf-8");
5718
6166
  const parsed = parseYaml4(raw);
5719
6167
  if (parsed?.migrate?.["token-substitution"]) {
5720
6168
  return parsed.migrate["token-substitution"];
@@ -5770,7 +6218,7 @@ function collectCssFiles(dirPath) {
5770
6218
  function walk2(current) {
5771
6219
  const entries = readdirSync11(current, { withFileTypes: true });
5772
6220
  for (const entry of entries) {
5773
- const fullPath = join19(current, entry.name);
6221
+ const fullPath = join24(current, entry.name);
5774
6222
  if (entry.isDirectory()) {
5775
6223
  if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
5776
6224
  continue;
@@ -5798,7 +6246,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
5798
6246
  const fileResults = [];
5799
6247
  let totalSubstitutions = 0;
5800
6248
  for (const file of files) {
5801
- const content = readFileSync22(file, "utf-8");
6249
+ const content = readFileSync25(file, "utf-8");
5802
6250
  const { newContent, substitutions } = applySubstitutionsToContent(content, map);
5803
6251
  if (substitutions.length > 0) {
5804
6252
  fileResults.push({ file, substitutions, originalContent: content, newContent });
@@ -5832,7 +6280,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5832
6280
  process.exit(1);
5833
6281
  return;
5834
6282
  }
5835
- const targetPath = resolve14(cwd, targetArg ?? ".");
6283
+ const targetPath = resolve16(cwd, targetArg ?? ".");
5836
6284
  try {
5837
6285
  statSync8(targetPath);
5838
6286
  } catch {
@@ -5860,7 +6308,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5860
6308
  if (options.json) {
5861
6309
  if (apply) {
5862
6310
  for (const f of result.files) {
5863
- writeFileSync11(f.file, f.newContent, "utf-8");
6311
+ writeFileSync14(f.file, f.newContent, "utf-8");
5864
6312
  }
5865
6313
  console.log(JSON.stringify({
5866
6314
  success: true,
@@ -5900,7 +6348,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5900
6348
  logger.warn(`Dry run \u2014 no files written. Re-run with ${pc4.bold("--apply")} to commit changes.`);
5901
6349
  } else {
5902
6350
  for (const f of result.files) {
5903
- writeFileSync11(f.file, f.newContent, "utf-8");
6351
+ writeFileSync14(f.file, f.newContent, "utf-8");
5904
6352
  }
5905
6353
  logger.heading(`visor migrate token-substitution \u2014 applied`);
5906
6354
  logger.blank();
@@ -5921,23 +6369,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
5921
6369
  }
5922
6370
 
5923
6371
  // src/commands/sandbox/init.ts
5924
- import { existsSync as existsSync21, mkdirSync as mkdirSync9, readFileSync as readFileSync25, rmSync as rmSync2 } from "fs";
5925
- import { isAbsolute as isAbsolute2, join as join22, resolve as resolve16, dirname as dirname9 } from "path";
6372
+ import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync2 } from "fs";
6373
+ import { isAbsolute as isAbsolute4, join as join27, resolve as resolve18, dirname as dirname11 } from "path";
5926
6374
  import { fileURLToPath as fileURLToPath3 } from "url";
5927
- import * as childProcess2 from "child_process";
6375
+ import * as childProcess3 from "child_process";
5928
6376
 
5929
6377
  // src/commands/sandbox/parse-handoff.ts
5930
- import { readFileSync as readFileSync23, existsSync as existsSync19 } from "fs";
5931
- import { dirname as dirname8, resolve as resolve15, isAbsolute } from "path";
6378
+ import { readFileSync as readFileSync26, existsSync as existsSync23 } from "fs";
6379
+ import { dirname as dirname10, resolve as resolve17, isAbsolute as isAbsolute3 } from "path";
5932
6380
  function parseHandoff(handoffPath) {
5933
- const text = readFileSync23(handoffPath, "utf-8");
6381
+ const text = readFileSync26(handoffPath, "utf-8");
5934
6382
  const lines2 = text.split("\n");
5935
6383
  const warnings = [];
5936
6384
  const pattern2 = extractPatternSlug(lines2, warnings);
5937
6385
  const theme2 = extractMetaField(lines2, "Theme");
5938
6386
  const recipePath = extractRecipePath(text, handoffPath, warnings);
5939
6387
  const primitives = extractPrimitives(text, warnings);
5940
- const { screens, mockShapes } = recipePath && existsSync19(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
6388
+ const { screens, mockShapes } = recipePath && existsSync23(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
5941
6389
  return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
5942
6390
  }
5943
6391
  function extractPatternSlug(lines2, warnings) {
@@ -5968,8 +6416,8 @@ function extractRecipePath(text, handoffPath, warnings) {
5968
6416
  return void 0;
5969
6417
  }
5970
6418
  const href = m[2].trim();
5971
- if (isAbsolute(href)) return href;
5972
- return resolve15(dirname8(handoffPath), href);
6419
+ if (isAbsolute3(href)) return href;
6420
+ return resolve17(dirname10(handoffPath), href);
5973
6421
  }
5974
6422
  var STATUS_PATTERNS = [
5975
6423
  { re: /NEW\s+gap/i, status: "gap-new" },
@@ -6016,7 +6464,7 @@ function extractPrimitives(text, warnings) {
6016
6464
  return primitives;
6017
6465
  }
6018
6466
  function extractRecipeArtifacts(recipePath, warnings) {
6019
- const text = readFileSync23(recipePath, "utf-8");
6467
+ const text = readFileSync26(recipePath, "utf-8");
6020
6468
  const screens = extractScreens(text);
6021
6469
  const mockShapes = extractMockFields(text, warnings);
6022
6470
  return { screens, mockShapes };
@@ -6090,8 +6538,8 @@ function tryPort(port) {
6090
6538
  }
6091
6539
 
6092
6540
  // src/commands/sandbox/scaffold.ts
6093
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync12, existsSync as existsSync20, readdirSync as readdirSync12 } from "fs";
6094
- import { join as join20 } from "path";
6541
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
6542
+ import { join as join25 } from "path";
6095
6543
 
6096
6544
  // src/commands/sandbox/templates.ts
6097
6545
  var SANDBOX_PACKAGE_VERSION = "16.2.6";
@@ -6591,12 +7039,12 @@ function toPascalCase(s) {
6591
7039
 
6592
7040
  // src/commands/sandbox/scaffold.ts
6593
7041
  function writeScaffold(sandboxDir, manifest, port, options = {}) {
6594
- mkdirSync7(sandboxDir, { recursive: true });
7042
+ mkdirSync10(sandboxDir, { recursive: true });
6595
7043
  const created = [];
6596
7044
  const writeIfNew = (rel, contents) => {
6597
- const full = join20(sandboxDir, rel);
6598
- mkdirSync7(dirOf(full), { recursive: true });
6599
- writeFileSync12(full, contents, "utf-8");
7045
+ const full = join25(sandboxDir, rel);
7046
+ mkdirSync10(dirOf(full), { recursive: true });
7047
+ writeFileSync15(full, contents, "utf-8");
6600
7048
  created.push(rel);
6601
7049
  };
6602
7050
  writeIfNew("package.json", packageJsonTemplate(manifest.pattern));
@@ -6622,7 +7070,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
6622
7070
  }
6623
7071
  }
6624
7072
  writeIfNew("playwright.capture.mjs", captureScriptTemplate());
6625
- mkdirSync7(join20(sandboxDir, "captures", "approved"), { recursive: true });
7073
+ mkdirSync10(join25(sandboxDir, "captures", "approved"), { recursive: true });
6626
7074
  return { created };
6627
7075
  }
6628
7076
  function dirOf(p) {
@@ -6656,10 +7104,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
6656
7104
  stripChromeSelectors: options.prototypeImport.stripChromeSelectors
6657
7105
  } : null
6658
7106
  };
6659
- writeFileSync12(join20(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
7107
+ writeFileSync15(join25(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
6660
7108
  }
6661
7109
  function sandboxIsEmpty(sandboxDir) {
6662
- if (!existsSync20(sandboxDir)) return true;
7110
+ if (!existsSync24(sandboxDir)) return true;
6663
7111
  try {
6664
7112
  return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
6665
7113
  } catch {
@@ -6668,8 +7116,8 @@ function sandboxIsEmpty(sandboxDir) {
6668
7116
  }
6669
7117
 
6670
7118
  // src/commands/sandbox/html-prototype.ts
6671
- import { mkdirSync as mkdirSync8, readdirSync as readdirSync13, readFileSync as readFileSync24, statSync as statSync9, writeFileSync as writeFileSync13 } from "fs";
6672
- import { join as join21 } from "path";
7119
+ import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync27, statSync as statSync9, writeFileSync as writeFileSync16 } from "fs";
7120
+ import { join as join26 } from "path";
6673
7121
 
6674
7122
  // src/commands/sandbox/strip-chrome.ts
6675
7123
  var DEFAULT_STRIP_SELECTORS = [
@@ -6853,8 +7301,8 @@ function stripDocumentaryChrome(html, selectors) {
6853
7301
  var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
6854
7302
  function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
6855
7303
  const warnings = [];
6856
- const destAbs = join21(sandboxDir, "public", "prototype");
6857
- mkdirSync8(destAbs, { recursive: true });
7304
+ const destAbs = join26(sandboxDir, "public", "prototype");
7305
+ mkdirSync11(destAbs, { recursive: true });
6858
7306
  const stripChromeSelectors = options.stripChromeSelectors ?? [];
6859
7307
  const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
6860
7308
  const screenFiles = listOrderedScreenFiles(sourceDir);
@@ -6907,24 +7355,24 @@ function deriveStateCoverageScreen(file, existingNames) {
6907
7355
  }
6908
7356
  function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
6909
7357
  const out = [];
6910
- const entries = readdirSync13(join21(srcDir, relDir));
7358
+ const entries = readdirSync13(join26(srcDir, relDir));
6911
7359
  for (const name of entries) {
6912
7360
  if (name.startsWith(".")) continue;
6913
- const srcPath = join21(srcDir, relDir, name);
6914
- const destPath = join21(destDir, relDir, name);
7361
+ const srcPath = join26(srcDir, relDir, name);
7362
+ const destPath = join26(destDir, relDir, name);
6915
7363
  const stat = statSync9(srcPath);
6916
7364
  if (stat.isDirectory()) {
6917
- mkdirSync8(destPath, { recursive: true });
6918
- out.push(...copyTreeRelative(srcDir, destDir, join21(relDir, name), stripChromeSelectors));
7365
+ mkdirSync11(destPath, { recursive: true });
7366
+ out.push(...copyTreeRelative(srcDir, destDir, join26(relDir, name), stripChromeSelectors));
6919
7367
  } else if (stat.isFile()) {
6920
7368
  if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
6921
- const html = readFileSync24(srcPath, "utf-8");
7369
+ const html = readFileSync27(srcPath, "utf-8");
6922
7370
  const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
6923
- writeFileSync13(destPath, stripped);
7371
+ writeFileSync16(destPath, stripped);
6924
7372
  } else {
6925
- writeFileSync13(destPath, readFileSync24(srcPath));
7373
+ writeFileSync16(destPath, readFileSync27(srcPath));
6926
7374
  }
6927
- out.push(join21("public", "prototype", relDir, name).replace(/\\/g, "/"));
7375
+ out.push(join26("public", "prototype", relDir, name).replace(/\\/g, "/"));
6928
7376
  }
6929
7377
  }
6930
7378
  return out;
@@ -6976,8 +7424,8 @@ async function runInit(name, cwd, options) {
6976
7424
  if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
6977
7425
  throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
6978
7426
  }
6979
- const handoffPath = isAbsolute2(options.handoff) ? options.handoff : resolve16(cwd, options.handoff);
6980
- if (!existsSync21(handoffPath)) {
7427
+ const handoffPath = isAbsolute4(options.handoff) ? options.handoff : resolve18(cwd, options.handoff);
7428
+ if (!existsSync25(handoffPath)) {
6981
7429
  throw new Error(`Handoff manifest not found: ${handoffPath}`);
6982
7430
  }
6983
7431
  const manifest = parseHandoff(handoffPath);
@@ -6986,7 +7434,7 @@ async function runInit(name, cwd, options) {
6986
7434
  `Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
6987
7435
  );
6988
7436
  }
6989
- const sandboxDir = join22(cwd, ".lo", "sandbox", name);
7437
+ const sandboxDir = join27(cwd, ".lo", "sandbox", name);
6990
7438
  if (!sandboxIsEmpty(sandboxDir)) {
6991
7439
  if (!options.overwrite) {
6992
7440
  throw new Error(
@@ -6995,12 +7443,12 @@ async function runInit(name, cwd, options) {
6995
7443
  }
6996
7444
  rmSync2(sandboxDir, { recursive: true, force: true });
6997
7445
  }
6998
- mkdirSync9(sandboxDir, { recursive: true });
7446
+ mkdirSync12(sandboxDir, { recursive: true });
6999
7447
  const port = await findOpenPort();
7000
7448
  let prototypeImport;
7001
7449
  if (options.fromHtmlPrototype) {
7002
- const prototypeDir = isAbsolute2(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve16(cwd, options.fromHtmlPrototype);
7003
- if (!existsSync21(prototypeDir)) {
7450
+ const prototypeDir = isAbsolute4(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve18(cwd, options.fromHtmlPrototype);
7451
+ if (!existsSync25(prototypeDir)) {
7004
7452
  throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
7005
7453
  }
7006
7454
  const stripChromeSelectors = resolveStripSelectors(
@@ -7059,22 +7507,22 @@ async function runInit(name, cwd, options) {
7059
7507
  function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
7060
7508
  const candidates = [];
7061
7509
  if (themeFile) {
7062
- candidates.push(isAbsolute2(themeFile) ? themeFile : resolve16(cwd, themeFile));
7510
+ candidates.push(isAbsolute4(themeFile) ? themeFile : resolve18(cwd, themeFile));
7063
7511
  }
7064
- candidates.push(isAbsolute2(theme2) ? theme2 : resolve16(cwd, theme2));
7512
+ candidates.push(isAbsolute4(theme2) ? theme2 : resolve18(cwd, theme2));
7065
7513
  const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
7066
7514
  if (privateRoot && privateRoot.length > 0) {
7067
- candidates.push(join22(privateRoot, "themes", theme2, "theme.visor.yaml"));
7515
+ candidates.push(join27(privateRoot, "themes", theme2, "theme.visor.yaml"));
7068
7516
  }
7069
7517
  candidates.push(
7070
- join22(cwd, "themes", `${theme2}.visor.yaml`),
7071
- join22(cwd, "custom-themes", `${theme2}.visor.yaml`)
7518
+ join27(cwd, "themes", `${theme2}.visor.yaml`),
7519
+ join27(cwd, "custom-themes", `${theme2}.visor.yaml`)
7072
7520
  );
7073
- const yamlPath = candidates.find((p) => existsSync21(p));
7521
+ const yamlPath = candidates.find((p) => existsSync25(p));
7074
7522
  if (!yamlPath) {
7075
7523
  const searched = candidates.join(", ");
7076
7524
  manifest.warnings.push(
7077
- `Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join22(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
7525
+ `Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
7078
7526
  );
7079
7527
  if (!json) {
7080
7528
  logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
@@ -7082,12 +7530,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
7082
7530
  `Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
7083
7531
  );
7084
7532
  logger.item(
7085
- `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join22(sandboxDir, "app", "globals.css")}`
7533
+ `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}`
7086
7534
  );
7087
7535
  }
7088
7536
  return;
7089
7537
  }
7090
- const globalsOut = join22(sandboxDir, "app", "globals.css");
7538
+ const globalsOut = join27(sandboxDir, "app", "globals.css");
7091
7539
  try {
7092
7540
  themeApplyCommand(yamlPath, sandboxDir, {
7093
7541
  output: globalsOut,
@@ -7118,7 +7566,7 @@ function tryAddPrimitive(primitive, sandboxDir, json) {
7118
7566
  }
7119
7567
  function runNpmInstall(sandboxDir, json) {
7120
7568
  if (!json) logger.info("Installing sandbox dependencies...");
7121
- const result = childProcess2.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
7569
+ const result = childProcess3.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
7122
7570
  cwd: sandboxDir,
7123
7571
  stdio: json ? "ignore" : "inherit"
7124
7572
  });
@@ -7140,12 +7588,12 @@ function loadKnownPrimitives() {
7140
7588
  }
7141
7589
  function readCliVersion() {
7142
7590
  try {
7143
- const here = dirname9(fileURLToPath3(import.meta.url));
7591
+ const here = dirname11(fileURLToPath3(import.meta.url));
7144
7592
  for (let i = 0; i < 6; i++) {
7145
7593
  const segments = new Array(i).fill("..");
7146
- const candidate = join22(here, ...segments, "package.json");
7594
+ const candidate = join27(here, ...segments, "package.json");
7147
7595
  try {
7148
- const pkg2 = JSON.parse(readFileSync25(candidate, "utf-8"));
7596
+ const pkg2 = JSON.parse(readFileSync28(candidate, "utf-8"));
7149
7597
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
7150
7598
  } catch {
7151
7599
  }
@@ -7156,14 +7604,14 @@ function readCliVersion() {
7156
7604
  }
7157
7605
 
7158
7606
  // src/commands/sandbox/dev.ts
7159
- import { existsSync as existsSync22, readFileSync as readFileSync26 } from "fs";
7160
- import { join as join23 } from "path";
7161
- import * as childProcess3 from "child_process";
7607
+ import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
7608
+ import { join as join28 } from "path";
7609
+ import * as childProcess4 from "child_process";
7162
7610
  function sandboxDevCommand(cwd, options) {
7163
7611
  const json = options.json ?? false;
7164
- const sandboxDir = join23(cwd, ".lo", "sandbox", options.name);
7165
- const configPath = join23(sandboxDir, "sandbox.json");
7166
- if (!existsSync22(configPath)) {
7612
+ const sandboxDir = join28(cwd, ".lo", "sandbox", options.name);
7613
+ const configPath = join28(sandboxDir, "sandbox.json");
7614
+ if (!existsSync26(configPath)) {
7167
7615
  fail(
7168
7616
  json,
7169
7617
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
@@ -7172,7 +7620,7 @@ function sandboxDevCommand(cwd, options) {
7172
7620
  }
7173
7621
  let config;
7174
7622
  try {
7175
- config = JSON.parse(readFileSync26(configPath, "utf-8"));
7623
+ config = JSON.parse(readFileSync29(configPath, "utf-8"));
7176
7624
  } catch (err) {
7177
7625
  fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
7178
7626
  return;
@@ -7199,7 +7647,7 @@ function sandboxDevCommand(cwd, options) {
7199
7647
  spawnNextDev(sandboxDir, config.port, json);
7200
7648
  }
7201
7649
  function spawnNextDev(sandboxDir, port, json) {
7202
- const child = childProcess3.spawn(
7650
+ const child = childProcess4.spawn(
7203
7651
  "npx",
7204
7652
  ["--no-install", "next", "dev", "--port", String(port)],
7205
7653
  {
@@ -7230,20 +7678,20 @@ function fail(json, message) {
7230
7678
  // src/commands/sandbox/approve.ts
7231
7679
  import {
7232
7680
  copyFileSync as copyFileSync2,
7233
- existsSync as existsSync23,
7234
- mkdirSync as mkdirSync10,
7235
- readFileSync as readFileSync27,
7681
+ existsSync as existsSync27,
7682
+ mkdirSync as mkdirSync13,
7683
+ readFileSync as readFileSync30,
7236
7684
  readdirSync as readdirSync14,
7237
7685
  rmSync as rmSync3,
7238
- writeFileSync as writeFileSync14
7686
+ writeFileSync as writeFileSync17
7239
7687
  } from "fs";
7240
- import { basename as basename6, join as join24 } from "path";
7241
- import * as childProcess4 from "child_process";
7688
+ import { basename as basename7, join as join29 } from "path";
7689
+ import * as childProcess5 from "child_process";
7242
7690
  function sandboxApproveCommand(cwd, options) {
7243
7691
  const json = options.json ?? false;
7244
- const sandboxDir = join24(cwd, ".lo", "sandbox", options.name);
7245
- const configPath = join24(sandboxDir, "sandbox.json");
7246
- if (!existsSync23(configPath)) {
7692
+ const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
7693
+ const configPath = join29(sandboxDir, "sandbox.json");
7694
+ if (!existsSync27(configPath)) {
7247
7695
  fail2(
7248
7696
  json,
7249
7697
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
@@ -7257,11 +7705,11 @@ function sandboxApproveCommand(cwd, options) {
7257
7705
  runCapture(sandboxDir, configPath, options.name, json);
7258
7706
  }
7259
7707
  function runCapture(sandboxDir, configPath, sandboxName, json) {
7260
- const config = JSON.parse(readFileSync27(configPath, "utf-8"));
7261
- const captureScriptPath = join24(sandboxDir, "playwright.capture.mjs");
7262
- writeFileSync14(captureScriptPath, captureScriptTemplate(), "utf-8");
7708
+ const config = JSON.parse(readFileSync30(configPath, "utf-8"));
7709
+ const captureScriptPath = join29(sandboxDir, "playwright.capture.mjs");
7710
+ writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
7263
7711
  ensurePlaywrightInstalled(sandboxDir, json);
7264
- const result = childProcess4.spawnSync(
7712
+ const result = childProcess5.spawnSync(
7265
7713
  "npx",
7266
7714
  ["--no-install", "node", "playwright.capture.mjs"],
7267
7715
  {
@@ -7280,9 +7728,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
7280
7728
  ${result.stderr}`);
7281
7729
  return;
7282
7730
  }
7283
- const pendingDir = join24(sandboxDir, "captures", "pending");
7284
- const diffsDir = join24(sandboxDir, "captures", "diffs");
7285
- const approvedDir = join24(sandboxDir, "captures", "approved");
7731
+ const pendingDir = join29(sandboxDir, "captures", "pending");
7732
+ const diffsDir = join29(sandboxDir, "captures", "diffs");
7733
+ const approvedDir = join29(sandboxDir, "captures", "approved");
7286
7734
  const pendingFiles = safeListPngs(pendingDir);
7287
7735
  const diffFiles = safeListPngs(diffsDir);
7288
7736
  const hasBaseline = safeListPngs(approvedDir).length > 0;
@@ -7319,9 +7767,9 @@ ${result.stderr}`);
7319
7767
  }
7320
7768
  }
7321
7769
  function runPromotion(sandboxDir, sandboxName, json) {
7322
- const pendingDir = join24(sandboxDir, "captures", "pending");
7323
- const approvedDir = join24(sandboxDir, "captures", "approved");
7324
- const diffsDir = join24(sandboxDir, "captures", "diffs");
7770
+ const pendingDir = join29(sandboxDir, "captures", "pending");
7771
+ const approvedDir = join29(sandboxDir, "captures", "approved");
7772
+ const diffsDir = join29(sandboxDir, "captures", "diffs");
7325
7773
  const pendingFiles = safeListPngs(pendingDir);
7326
7774
  if (pendingFiles.length === 0) {
7327
7775
  fail2(
@@ -7330,10 +7778,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
7330
7778
  );
7331
7779
  return;
7332
7780
  }
7333
- mkdirSync10(approvedDir, { recursive: true });
7781
+ mkdirSync13(approvedDir, { recursive: true });
7334
7782
  const promoted = [];
7335
7783
  for (const src of pendingFiles) {
7336
- const dest = join24(approvedDir, basename6(src));
7784
+ const dest = join29(approvedDir, basename7(src));
7337
7785
  copyFileSync2(src, dest);
7338
7786
  promoted.push(dest);
7339
7787
  }
@@ -7353,10 +7801,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
7353
7801
  }
7354
7802
  }
7355
7803
  function ensurePlaywrightInstalled(sandboxDir, json) {
7356
- const markerPath = join24(sandboxDir, ".playwright-installed");
7357
- if (existsSync23(markerPath)) return;
7804
+ const markerPath = join29(sandboxDir, ".playwright-installed");
7805
+ if (existsSync27(markerPath)) return;
7358
7806
  if (!json) logger.info("Installing Playwright Chromium (one-time)...");
7359
- const result = childProcess4.spawnSync(
7807
+ const result = childProcess5.spawnSync(
7360
7808
  "npx",
7361
7809
  ["--no-install", "playwright", "install", "chromium"],
7362
7810
  {
@@ -7370,11 +7818,11 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
7370
7818
  if (typeof result.status === "number" && result.status !== 0) {
7371
7819
  throw new Error(`playwright install exited with code ${result.status}`);
7372
7820
  }
7373
- writeFileSync14(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
7821
+ writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
7374
7822
  }
7375
7823
  function safeListPngs(dir) {
7376
- if (!existsSync23(dir)) return [];
7377
- return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join24(dir, f));
7824
+ if (!existsSync27(dir)) return [];
7825
+ return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join29(dir, f));
7378
7826
  }
7379
7827
  function tryParseJson(s) {
7380
7828
  try {
@@ -7392,15 +7840,315 @@ function fail2(json, message) {
7392
7840
  process.exit(1);
7393
7841
  }
7394
7842
 
7843
+ // src/commands/spawn.ts
7844
+ import {
7845
+ cpSync,
7846
+ existsSync as existsSync29,
7847
+ readFileSync as readFileSync31,
7848
+ readdirSync as readdirSync16,
7849
+ rmSync as rmSync4
7850
+ } from "fs";
7851
+ import { basename as basename8, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
7852
+ import { homedir as homedir3 } from "os";
7853
+ import * as childProcess6 from "child_process";
7854
+ import { parse as parseYaml5 } from "yaml";
7855
+ import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
7856
+ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
7857
+ import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
7858
+
7859
+ // src/lib/blessed-discovery.ts
7860
+ import { existsSync as existsSync28, readdirSync as readdirSync15 } from "fs";
7861
+ import { join as join30 } from "path";
7862
+ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
7863
+ "node_modules",
7864
+ ".next",
7865
+ ".git",
7866
+ "dist",
7867
+ ".turbo",
7868
+ "coverage",
7869
+ ".cache"
7870
+ ]);
7871
+ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
7872
+ const builds = [];
7873
+ const errors = [];
7874
+ if (!existsSync28(blessedDir)) {
7875
+ return { builds, errors };
7876
+ }
7877
+ walk2(blessedDir, 0);
7878
+ builds.sort((a, b) => {
7879
+ if (a.manifest.shape !== b.manifest.shape) {
7880
+ return a.manifest.shape.localeCompare(b.manifest.shape);
7881
+ }
7882
+ return a.manifest.pattern.localeCompare(b.manifest.pattern);
7883
+ });
7884
+ return { builds, errors };
7885
+ function walk2(dir, depth) {
7886
+ if (depth > maxDepth) return;
7887
+ let entries;
7888
+ try {
7889
+ entries = readdirSync15(dir, { withFileTypes: true });
7890
+ } catch {
7891
+ return;
7892
+ }
7893
+ const hasManifest = entries.some(
7894
+ (entry) => entry.isFile() && entry.name === BLESSED_MANIFEST_FILENAME
7895
+ );
7896
+ if (hasManifest) {
7897
+ const result = parseBlessedManifest(dir);
7898
+ if (result.ok) {
7899
+ builds.push({ dir, manifest: result.manifest });
7900
+ } else {
7901
+ errors.push({ dir, error: result.error });
7902
+ }
7903
+ return;
7904
+ }
7905
+ for (const entry of entries) {
7906
+ if (!entry.isDirectory()) continue;
7907
+ if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
7908
+ walk2(join30(dir, entry.name), depth + 1);
7909
+ }
7910
+ }
7911
+ }
7912
+ function resolveBlessedBuild(blessedDir, shape, pattern2) {
7913
+ const { builds } = discoverBlessedBuilds(blessedDir);
7914
+ const build = builds.find(
7915
+ (candidate) => candidate.manifest.shape === shape && candidate.manifest.pattern === pattern2
7916
+ );
7917
+ return { build, available: builds };
7918
+ }
7919
+
7920
+ // src/commands/spawn.ts
7921
+ var DEFAULT_BLESSED_DIR = join31(
7922
+ homedir3(),
7923
+ "Code",
7924
+ "low-orbit",
7925
+ "low-orbit-playbook",
7926
+ "design-prototypes"
7927
+ );
7928
+ var FORK_EXCLUDE = /* @__PURE__ */ new Set([
7929
+ "node_modules",
7930
+ ".next",
7931
+ ".git",
7932
+ "dist",
7933
+ ".turbo",
7934
+ ".cache",
7935
+ "coverage",
7936
+ ".DS_Store",
7937
+ "tsconfig.tsbuildinfo"
7938
+ ]);
7939
+ function parseBlessedIdentifier(from) {
7940
+ const PREFIX = "blessed:";
7941
+ if (!from.startsWith(PREFIX)) {
7942
+ throw new Error(
7943
+ `Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
7944
+ );
7945
+ }
7946
+ const rest = from.slice(PREFIX.length);
7947
+ const parts = rest.split(":");
7948
+ if (parts.length !== 2 || parts[0].length === 0 || parts[1].length === 0) {
7949
+ throw new Error(
7950
+ `Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
7951
+ );
7952
+ }
7953
+ return { shape: parts[0], pattern: parts[1] };
7954
+ }
7955
+ function resolveBlessedDir(cwd, options) {
7956
+ const raw = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0) ?? DEFAULT_BLESSED_DIR;
7957
+ return isAbsolute5(raw) ? raw : resolve19(cwd, raw);
7958
+ }
7959
+ function resolveThemeFile(theme2, cwd, themeFile) {
7960
+ const candidates = [];
7961
+ if (themeFile) {
7962
+ candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
7963
+ }
7964
+ candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
7965
+ const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
7966
+ if (privateRoot && privateRoot.length > 0) {
7967
+ candidates.push(join31(privateRoot, "themes", theme2, "theme.visor.yaml"));
7968
+ }
7969
+ candidates.push(
7970
+ join31(cwd, "themes", `${theme2}.visor.yaml`),
7971
+ join31(cwd, "custom-themes", `${theme2}.visor.yaml`)
7972
+ );
7973
+ const found = candidates.find((candidate) => existsSync29(candidate));
7974
+ if (!found) {
7975
+ throw new Error(
7976
+ `Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
7977
+ );
7978
+ }
7979
+ return { path: found, searched: candidates };
7980
+ }
7981
+ function applyThemeToFork(themeFile, outputDir, manifest) {
7982
+ const yaml = readFileSync31(themeFile, "utf-8");
7983
+ const data = generateThemeData7(yaml);
7984
+ const themeId = data.config.name;
7985
+ if (!themeId || themeId.length === 0) {
7986
+ throw new Error(
7987
+ `Theme file '${themeFile}' is missing a config.name; required to derive the theme id for spawn's theme-apply dispatch. See docs/blessed-builds.md.`
7988
+ );
7989
+ }
7990
+ const css = nextjsAdapter3(
7991
+ { primitives: data.primitives, tokens: data.tokens, config: data.config },
7992
+ {}
7993
+ );
7994
+ applyThemeToBuild({
7995
+ manifest,
7996
+ buildDir: outputDir,
7997
+ themeId,
7998
+ adapterCss: css
7999
+ });
8000
+ }
8001
+ function runNpmInstall2(outputDir, json) {
8002
+ const result = childProcess6.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
8003
+ cwd: outputDir,
8004
+ stdio: json ? "ignore" : "inherit"
8005
+ });
8006
+ if (result.error) {
8007
+ throw new Error(`npm install failed to start: ${result.error.message}`);
8008
+ }
8009
+ if (typeof result.status === "number" && result.status !== 0) {
8010
+ throw new Error(`npm install exited with code ${result.status}`);
8011
+ }
8012
+ }
8013
+ function runSpawn(cwd, options) {
8014
+ if (!options.from) throw new Error("Missing required --from blessed:{shape}:{pattern}.");
8015
+ if (!options.theme) throw new Error("Missing required --theme <id>.");
8016
+ if (!options.output) throw new Error("Missing required --output <path>.");
8017
+ const { shape, pattern: pattern2 } = parseBlessedIdentifier(options.from);
8018
+ const blessedDir = resolveBlessedDir(cwd, options);
8019
+ const { build, available } = resolveBlessedBuild(blessedDir, shape, pattern2);
8020
+ if (!build) {
8021
+ const list = available.length > 0 ? available.map((b) => ` - blessed:${b.manifest.shape}:${b.manifest.pattern}`).join("\n") : " (none found)";
8022
+ throw new Error(
8023
+ `No blessed build found for blessed:${shape}:${pattern2} under ${blessedDir}.
8024
+ Available builds:
8025
+ ${list}`
8026
+ );
8027
+ }
8028
+ const outputDir = isAbsolute5(options.output) ? options.output : resolve19(cwd, options.output);
8029
+ if (existsSync29(outputDir) && readdirSync16(outputDir).length > 0) {
8030
+ throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
8031
+ }
8032
+ const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
8033
+ cpSync(build.dir, outputDir, {
8034
+ recursive: true,
8035
+ filter: (src) => !FORK_EXCLUDE.has(basename8(src))
8036
+ });
8037
+ let validated = false;
8038
+ let installed = false;
8039
+ try {
8040
+ applyThemeToFork(themeFile, outputDir, build.manifest);
8041
+ if (options.validate) {
8042
+ const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
8043
+ const result = validate3(parsed);
8044
+ if (!result.valid) {
8045
+ const messages = result.errors.map((e) => e.message).join("; ");
8046
+ throw new Error(`Theme validation failed: ${messages}`);
8047
+ }
8048
+ validated = true;
8049
+ }
8050
+ if (options.install) {
8051
+ runNpmInstall2(outputDir, options.json ?? false);
8052
+ installed = true;
8053
+ }
8054
+ } catch (err) {
8055
+ rmSync4(outputDir, { recursive: true, force: true });
8056
+ throw err;
8057
+ }
8058
+ return {
8059
+ success: true,
8060
+ build: {
8061
+ shape: build.manifest.shape,
8062
+ pattern: build.manifest.pattern,
8063
+ requiresVisor: build.manifest.requires_visor,
8064
+ source: build.dir
8065
+ },
8066
+ output: outputDir,
8067
+ themeApplied: options.theme,
8068
+ themeFile,
8069
+ installed,
8070
+ validated
8071
+ };
8072
+ }
8073
+ function listBlessed(cwd, options) {
8074
+ const blessedDir = resolveBlessedDir(cwd, options);
8075
+ const { builds, errors } = discoverBlessedBuilds(blessedDir);
8076
+ if (options.json) {
8077
+ console.log(
8078
+ JSON.stringify({
8079
+ success: true,
8080
+ blessedDir,
8081
+ builds: builds.map((b) => ({
8082
+ from: `blessed:${b.manifest.shape}:${b.manifest.pattern}`,
8083
+ shape: b.manifest.shape,
8084
+ pattern: b.manifest.pattern,
8085
+ base_theme: b.manifest.base_theme,
8086
+ requires_visor: b.manifest.requires_visor,
8087
+ three_gates_status: b.manifest.three_gates_status,
8088
+ dir: b.dir
8089
+ })),
8090
+ errors
8091
+ })
8092
+ );
8093
+ return;
8094
+ }
8095
+ logger.heading(`Blessed builds under ${blessedDir}`);
8096
+ if (builds.length === 0) {
8097
+ logger.info(" (none found)");
8098
+ }
8099
+ for (const b of builds) {
8100
+ logger.success(`blessed:${b.manifest.shape}:${b.manifest.pattern}`);
8101
+ logger.item(`base theme: ${b.manifest.base_theme} \xB7 requires visor ${b.manifest.requires_visor} \xB7 gates: ${b.manifest.three_gates_status}`);
8102
+ }
8103
+ for (const e of errors) {
8104
+ logger.warn(`Skipped ${e.dir}: ${e.error}`);
8105
+ }
8106
+ }
8107
+ function spawnCommand(cwd, options) {
8108
+ const json = options.json ?? false;
8109
+ if (options.listBlessed) {
8110
+ try {
8111
+ listBlessed(cwd, options);
8112
+ } catch (err) {
8113
+ const message = err instanceof Error ? err.message : String(err);
8114
+ if (json) console.log(JSON.stringify({ success: false, error: message }));
8115
+ else logger.error(message);
8116
+ process.exit(1);
8117
+ }
8118
+ return;
8119
+ }
8120
+ try {
8121
+ const result = runSpawn(cwd, options);
8122
+ if (json) {
8123
+ console.log(JSON.stringify(result));
8124
+ return;
8125
+ }
8126
+ logger.success(
8127
+ `Discovered blessed build: ${result.build.shape}/${result.build.pattern} (requires visor ${result.build.requiresVisor})`
8128
+ );
8129
+ logger.success(`Forked to ${result.output} (excluded node_modules, .next, .git)`);
8130
+ logger.success(`Theme applied: ${result.themeApplied} \u2192 ${result.output}`);
8131
+ if (result.validated) logger.success("Theme validated");
8132
+ if (result.installed) logger.success("Installed dependencies");
8133
+ logger.blank();
8134
+ logger.info(`Next: cd ${result.output} && npm run dev`);
8135
+ } catch (err) {
8136
+ const message = err instanceof Error ? err.message : String(err);
8137
+ if (json) console.log(JSON.stringify({ success: false, error: message }));
8138
+ else logger.error(message);
8139
+ process.exit(1);
8140
+ }
8141
+ }
8142
+
7395
8143
  // src/index.ts
7396
- var __dirname2 = dirname10(fileURLToPath4(import.meta.url));
8144
+ var __dirname2 = dirname12(fileURLToPath4(import.meta.url));
7397
8145
  var pkg = JSON.parse(
7398
- readFileSync28(join25(__dirname2, "..", "package.json"), "utf-8")
8146
+ readFileSync32(join32(__dirname2, "..", "package.json"), "utf-8")
7399
8147
  );
7400
8148
  var program = new Command2();
7401
8149
  program.name("visor").description("CLI for the Visor design system").version(pkg.version);
7402
8150
  program.addCommand(checkCommand());
7403
- program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--json", "output structured JSON (for AI agents)").action((options) => {
8151
+ program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--for <play>", "bootstrap a Playbook play (pattern-build, new-web-app, feature-addition)").option("--play-name <name>", "name for the play instance (default: current directory name)").option("--theme <id>", "theme id to record in the play's state metadata").option("--from <ref>", "brief source for the play (e.g. a PL-N Linear ticket)").option("--json", "output structured JSON (for AI agents)").action((options) => {
7404
8152
  initCommand(process.cwd(), options);
7405
8153
  });
7406
8154
  program.command("list").description("List all available registry items").option("--json", "output structured JSON (for AI agents)").option("--category <name>", "filter items by category").action((options) => {
@@ -7448,6 +8196,9 @@ theme.command("apply").description(
7448
8196
  ).option(
7449
8197
  "--theme-class-name <name>",
7450
8198
  "(flutter) class name for generated theme (default: VisorAppTheme)"
8199
+ ).option(
8200
+ "--target-path <path>",
8201
+ "(nextjs) path to a blessed-build root; dispatches CSS through the build's theme_apply_target (VI-601), ignores --output"
7451
8202
  ).action(
7452
8203
  (file, options) => {
7453
8204
  themeApplyCommand(file, process.cwd(), {
@@ -7585,4 +8336,9 @@ sandbox.command("approve").description(
7585
8336
  sandboxApproveCommand(process.cwd(), options);
7586
8337
  }
7587
8338
  );
8339
+ program.command("spawn").description(
8340
+ "Fork a Playbook blessed reference build with atomic theme re-skinning \u2014 one command replaces cp -R + npm install + theme apply"
8341
+ ).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR or ~/Code/low-orbit/low-orbit-playbook/design-prototypes)").option("--install", "run npm install in the forked project (default: skip)").option("--validate", "validate the applied theme after forking (default: skip)").option("--list-blessed", "list all discoverable blessed builds and exit").option("--json", "output structured JSON (for AI agents)").action((options) => {
8342
+ spawnCommand(process.cwd(), options);
8343
+ });
7588
8344
  program.parse();