@agent-scope/cli 1.17.3 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
 
3
3
  // src/program.ts
4
- import { readFileSync as readFileSync11 } from "fs";
4
+ import { readFileSync as readFileSync12 } from "fs";
5
5
  import { generateTest, loadTrace } from "@agent-scope/playwright";
6
6
  import { Command as Command10 } from "commander";
7
7
 
@@ -61,9 +61,9 @@ import { Command } from "commander";
61
61
  // src/component-bundler.ts
62
62
  import { dirname } from "path";
63
63
  import * as esbuild from "esbuild";
64
- async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss, preScript) {
64
+ async function buildComponentHarness(filePath, componentName, props, viewportWidth, projectCss, wrapperScript) {
65
65
  const bundledScript = await bundleComponentToIIFE(filePath, componentName, props);
66
- return wrapInHtml(bundledScript, viewportWidth, projectCss, preScript);
66
+ return wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript);
67
67
  }
68
68
  async function bundleComponentToIIFE(filePath, componentName, props) {
69
69
  const propsJson = JSON.stringify(props).replace(/<\/script>/gi, "<\\/script>");
@@ -99,7 +99,12 @@ import { createElement } from "react";
99
99
  window.__SCOPE_RENDER_COMPLETE__ = true;
100
100
  return;
101
101
  }
102
- createRoot(rootEl).render(createElement(Component, props));
102
+ // If a scope file wrapper was injected, use it to wrap the component
103
+ var Wrapper = (window).__SCOPE_WRAPPER__;
104
+ var element = Wrapper
105
+ ? createElement(Wrapper, null, createElement(Component, props))
106
+ : createElement(Component, props);
107
+ createRoot(rootEl).render(element);
103
108
  // Use requestAnimationFrame to let React flush the render
104
109
  requestAnimationFrame(function() {
105
110
  window.__SCOPE_RENDER_COMPLETE__ = true;
@@ -136,6 +141,21 @@ import { createElement } from "react";
136
141
  // Suppress "React must be in scope" warnings from old JSX (we use automatic)
137
142
  banner: {
138
143
  js: "/* @agent-scope/cli component harness */"
144
+ },
145
+ // CSS imports (e.g. `import './styles.css'`) are handled at the page level via
146
+ // globalCSS injection. Tell esbuild to treat CSS files as empty modules so
147
+ // components that import CSS directly (e.g. App.tsx) don't error during bundling.
148
+ loader: {
149
+ ".css": "empty",
150
+ ".svg": "dataurl",
151
+ ".png": "dataurl",
152
+ ".jpg": "dataurl",
153
+ ".jpeg": "dataurl",
154
+ ".gif": "dataurl",
155
+ ".webp": "dataurl",
156
+ ".ttf": "dataurl",
157
+ ".woff": "dataurl",
158
+ ".woff2": "dataurl"
139
159
  }
140
160
  });
141
161
  if (result.errors.length > 0) {
@@ -149,12 +169,11 @@ ${msg}`);
149
169
  }
150
170
  return outputFile.text;
151
171
  }
152
- function wrapInHtml(bundledScript, viewportWidth, projectCss, preScript) {
172
+ function wrapInHtml(bundledScript, viewportWidth, projectCss, wrapperScript) {
153
173
  const projectStyleBlock = projectCss != null && projectCss.length > 0 ? `<style id="scope-project-css">
154
174
  ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
155
175
  </style>` : "";
156
- const preScriptBlock = preScript != null && preScript.length > 0 ? `<script>${preScript}</script>
157
- ` : "";
176
+ const wrapperScriptBlock = wrapperScript != null && wrapperScript.length > 0 ? `<script id="scope-wrapper-script">${wrapperScript}</script>` : "";
158
177
  return `<!DOCTYPE html>
159
178
  <html lang="en">
160
179
  <head>
@@ -169,7 +188,8 @@ ${projectCss.replace(/<\/style>/gi, "<\\/style>")}
169
188
  </head>
170
189
  <body>
171
190
  <div id="scope-root" data-reactscope-root></div>
172
- ${preScriptBlock}<script>${bundledScript}</script>
191
+ ${wrapperScriptBlock}
192
+ <script>${bundledScript}</script>
173
193
  </body>
174
194
  </html>`;
175
195
  }
@@ -537,16 +557,67 @@ async function getTailwindCompiler(cwd) {
537
557
  from: entryPath,
538
558
  loadStylesheet
539
559
  });
540
- const build2 = result.build.bind(result);
541
- compilerCache = { cwd, build: build2 };
542
- return build2;
560
+ const build3 = result.build.bind(result);
561
+ compilerCache = { cwd, build: build3 };
562
+ return build3;
543
563
  }
544
564
  async function getCompiledCssForClasses(cwd, classes) {
545
- const build2 = await getTailwindCompiler(cwd);
546
- if (build2 === null) return null;
565
+ const build3 = await getTailwindCompiler(cwd);
566
+ if (build3 === null) return null;
547
567
  const deduped = [...new Set(classes)].filter(Boolean);
548
568
  if (deduped.length === 0) return null;
549
- return build2(deduped);
569
+ return build3(deduped);
570
+ }
571
+ async function compileGlobalCssFile(cssFilePath, cwd) {
572
+ const { existsSync: existsSync15, readFileSync: readFileSync13 } = await import("fs");
573
+ const { createRequire: createRequire3 } = await import("module");
574
+ if (!existsSync15(cssFilePath)) return null;
575
+ const raw = readFileSync13(cssFilePath, "utf-8");
576
+ const needsCompile = /@tailwind|@import\s+['"]tailwindcss/.test(raw);
577
+ if (!needsCompile) {
578
+ return raw;
579
+ }
580
+ try {
581
+ const require2 = createRequire3(resolve(cwd, "package.json"));
582
+ let postcss;
583
+ let twPlugin;
584
+ try {
585
+ postcss = require2("postcss");
586
+ twPlugin = require2("tailwindcss");
587
+ } catch {
588
+ return raw;
589
+ }
590
+ let autoprefixerPlugin;
591
+ try {
592
+ autoprefixerPlugin = require2("autoprefixer");
593
+ } catch {
594
+ autoprefixerPlugin = null;
595
+ }
596
+ const plugins = autoprefixerPlugin ? [twPlugin, autoprefixerPlugin] : [twPlugin];
597
+ const result = await postcss(plugins).process(raw, {
598
+ from: cssFilePath,
599
+ to: cssFilePath
600
+ });
601
+ return result.css;
602
+ } catch (err) {
603
+ process.stderr.write(
604
+ `[scope/render] Warning: CSS compilation failed for ${cssFilePath}: ${err instanceof Error ? err.message : String(err)}
605
+ `
606
+ );
607
+ return raw;
608
+ }
609
+ }
610
+ async function loadGlobalCss(globalCssFiles, cwd) {
611
+ if (globalCssFiles.length === 0) return null;
612
+ const parts = [];
613
+ for (const relPath of globalCssFiles) {
614
+ const absPath = resolve(cwd, relPath);
615
+ const css = await compileGlobalCssFile(absPath, cwd);
616
+ if (css !== null && css.trim().length > 0) {
617
+ parts.push(css);
618
+ }
619
+ }
620
+ return parts.length > 0 ? parts.join("\n") : null;
550
621
  }
551
622
 
552
623
  // src/ci/commands.ts
@@ -1060,6 +1131,20 @@ function detectComponentPatterns(rootDir, typescript) {
1060
1131
  }
1061
1132
  return unique;
1062
1133
  }
1134
+ var GLOBAL_CSS_CANDIDATES = [
1135
+ "src/styles.css",
1136
+ "src/index.css",
1137
+ "src/global.css",
1138
+ "src/globals.css",
1139
+ "src/app.css",
1140
+ "src/main.css",
1141
+ "styles/globals.css",
1142
+ "styles/global.css",
1143
+ "styles/index.css"
1144
+ ];
1145
+ function detectGlobalCSSFiles(rootDir) {
1146
+ return GLOBAL_CSS_CANDIDATES.filter((rel) => existsSync3(join(rootDir, rel)));
1147
+ }
1063
1148
  var TAILWIND_STEMS = ["tailwind.config"];
1064
1149
  var CSS_EXTS = [".css", ".scss", ".sass", ".less"];
1065
1150
  var THEME_SUFFIXES = [".theme.ts", ".theme.js", ".theme.tsx"];
@@ -1127,13 +1212,15 @@ function detectProject(rootDir) {
1127
1212
  const packageManager = detectPackageManager(rootDir);
1128
1213
  const componentPatterns = detectComponentPatterns(rootDir, typescript);
1129
1214
  const tokenSources = detectTokenSources(rootDir);
1215
+ const globalCSSFiles = detectGlobalCSSFiles(rootDir);
1130
1216
  return {
1131
1217
  framework,
1132
1218
  typescript,
1133
1219
  tsconfigPath,
1134
1220
  componentPatterns,
1135
1221
  tokenSources,
1136
- packageManager
1222
+ packageManager,
1223
+ globalCSSFiles
1137
1224
  };
1138
1225
  }
1139
1226
 
@@ -1145,7 +1232,7 @@ function buildDefaultConfig(detected, tokenFile, outputDir) {
1145
1232
  components: {
1146
1233
  include,
1147
1234
  exclude: ["**/*.test.tsx", "**/*.stories.tsx"],
1148
- wrappers: { providers: [], globalCSS: [] }
1235
+ wrappers: { providers: [], globalCSS: detected.globalCSSFiles ?? [] }
1149
1236
  },
1150
1237
  render: {
1151
1238
  viewport: { default: { width: 1280, height: 800 } },
@@ -1176,9 +1263,9 @@ function createRL() {
1176
1263
  });
1177
1264
  }
1178
1265
  async function ask(rl, question) {
1179
- return new Promise((resolve17) => {
1266
+ return new Promise((resolve18) => {
1180
1267
  rl.question(question, (answer) => {
1181
- resolve17(answer.trim());
1268
+ resolve18(answer.trim());
1182
1269
  });
1183
1270
  });
1184
1271
  }
@@ -1203,18 +1290,118 @@ function ensureGitignoreEntry(rootDir, entry) {
1203
1290
  `);
1204
1291
  }
1205
1292
  }
1293
+ function extractTailwindTokens(tokenSources) {
1294
+ const tailwindSource = tokenSources.find((s) => s.kind === "tailwind-config");
1295
+ if (!tailwindSource) return null;
1296
+ try {
1297
+ let parseBlock2 = function(block) {
1298
+ const result = {};
1299
+ const lineRe = /['"]?(\w[\w.-]*|\d+)['"]?\s*:\s*['"]?(#[0-9a-fA-F]{3,8}|\d+(?:px|rem|em|%)|[\w-]+(?:\/[\w]+)?)['"]?/g;
1300
+ for (const m of block.matchAll(lineRe)) {
1301
+ if (m[1] !== void 0 && m[2] !== void 0) {
1302
+ result[m[1]] = m[2];
1303
+ }
1304
+ }
1305
+ return result;
1306
+ };
1307
+ var parseBlock = parseBlock2;
1308
+ const raw = readFileSync4(tailwindSource.path, "utf-8");
1309
+ const tokens = {};
1310
+ const colorsKeyIdx = raw.indexOf("colors:");
1311
+ if (colorsKeyIdx !== -1) {
1312
+ const colorsBraceStart = raw.indexOf("{", colorsKeyIdx);
1313
+ if (colorsBraceStart !== -1) {
1314
+ let colorDepth = 0;
1315
+ let colorsBraceEnd = -1;
1316
+ for (let ci = colorsBraceStart; ci < raw.length; ci++) {
1317
+ if (raw[ci] === "{") colorDepth++;
1318
+ else if (raw[ci] === "}") {
1319
+ colorDepth--;
1320
+ if (colorDepth === 0) {
1321
+ colorsBraceEnd = ci;
1322
+ break;
1323
+ }
1324
+ }
1325
+ }
1326
+ if (colorsBraceEnd > colorsBraceStart) {
1327
+ const colorSection = raw.slice(colorsBraceStart + 1, colorsBraceEnd);
1328
+ const scaleRe = /(\w+)\s*:\s*\{([^}]+)\}/g;
1329
+ const colorTokens = {};
1330
+ for (const sm of colorSection.matchAll(scaleRe)) {
1331
+ if (sm[1] === void 0 || sm[2] === void 0) continue;
1332
+ const scaleName = sm[1];
1333
+ const scaleValues = parseBlock2(sm[2]);
1334
+ if (Object.keys(scaleValues).length > 0) {
1335
+ const scaleTokens = {};
1336
+ for (const [step, hex] of Object.entries(scaleValues)) {
1337
+ scaleTokens[step] = { value: hex, type: "color" };
1338
+ }
1339
+ colorTokens[scaleName] = scaleTokens;
1340
+ }
1341
+ }
1342
+ if (Object.keys(colorTokens).length > 0) {
1343
+ tokens["color"] = colorTokens;
1344
+ }
1345
+ }
1346
+ }
1347
+ }
1348
+ const spacingMatch = raw.match(/spacing\s*:\s*\{([\s\S]*?)\n\s*\}/);
1349
+ if (spacingMatch?.[1] !== void 0) {
1350
+ const spacingValues = parseBlock2(spacingMatch[1]);
1351
+ if (Object.keys(spacingValues).length > 0) {
1352
+ const spacingTokens = {};
1353
+ for (const [key, val] of Object.entries(spacingValues)) {
1354
+ spacingTokens[key] = { value: val, type: "dimension" };
1355
+ }
1356
+ tokens["spacing"] = spacingTokens;
1357
+ }
1358
+ }
1359
+ const fontFamilyMatch = raw.match(/fontFamily\s*:\s*\{([\s\S]*?)\n\s*\}/);
1360
+ if (fontFamilyMatch?.[1] !== void 0) {
1361
+ const fontFamilyRe = /(\w+)\s*:\s*\[\s*['"]([^'"]+)['"]/g;
1362
+ const fontTokens = {};
1363
+ for (const fm of fontFamilyMatch[1].matchAll(fontFamilyRe)) {
1364
+ if (fm[1] !== void 0 && fm[2] !== void 0) {
1365
+ fontTokens[fm[1]] = { value: fm[2], type: "fontFamily" };
1366
+ }
1367
+ }
1368
+ if (Object.keys(fontTokens).length > 0) {
1369
+ tokens["font"] = fontTokens;
1370
+ }
1371
+ }
1372
+ const borderRadiusMatch = raw.match(/borderRadius\s*:\s*\{([\s\S]*?)\n\s*\}/);
1373
+ if (borderRadiusMatch?.[1] !== void 0) {
1374
+ const radiusValues = parseBlock2(borderRadiusMatch[1]);
1375
+ if (Object.keys(radiusValues).length > 0) {
1376
+ const radiusTokens = {};
1377
+ for (const [key, val] of Object.entries(radiusValues)) {
1378
+ radiusTokens[key] = { value: val, type: "dimension" };
1379
+ }
1380
+ tokens["radius"] = radiusTokens;
1381
+ }
1382
+ }
1383
+ return Object.keys(tokens).length > 0 ? tokens : null;
1384
+ } catch {
1385
+ return null;
1386
+ }
1387
+ }
1206
1388
  function scaffoldConfig(rootDir, config) {
1207
1389
  const path = join2(rootDir, "reactscope.config.json");
1208
1390
  writeFileSync3(path, `${JSON.stringify(config, null, 2)}
1209
1391
  `);
1210
1392
  return path;
1211
1393
  }
1212
- function scaffoldTokenFile(rootDir, tokenFile) {
1394
+ function scaffoldTokenFile(rootDir, tokenFile, extractedTokens) {
1213
1395
  const path = join2(rootDir, tokenFile);
1214
1396
  if (!existsSync4(path)) {
1215
1397
  const stub = {
1216
1398
  $schema: "https://raw.githubusercontent.com/FlatFilers/Scope/main/packages/tokens/schema.json",
1217
- tokens: {}
1399
+ version: "1.0.0",
1400
+ meta: {
1401
+ name: "Design Tokens",
1402
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1403
+ },
1404
+ tokens: extractedTokens ?? {}
1218
1405
  };
1219
1406
  writeFileSync3(path, `${JSON.stringify(stub, null, 2)}
1220
1407
  `);
@@ -1292,7 +1479,13 @@ async function runInit(options) {
1292
1479
  }
1293
1480
  const cfgPath = scaffoldConfig(rootDir, config);
1294
1481
  created.push(cfgPath);
1295
- const tokPath = scaffoldTokenFile(rootDir, config.tokens.file);
1482
+ const extractedTokens = extractTailwindTokens(detected.tokenSources);
1483
+ if (extractedTokens !== null) {
1484
+ const tokenGroupCount = Object.keys(extractedTokens).length;
1485
+ process.stdout.write(` Extracted ${tokenGroupCount} token group(s) from Tailwind config
1486
+ `);
1487
+ }
1488
+ const tokPath = scaffoldTokenFile(rootDir, config.tokens.file, extractedTokens ?? void 0);
1296
1489
  created.push(tokPath);
1297
1490
  const outDirPath = scaffoldOutputDir(rootDir, config.output.dir);
1298
1491
  created.push(outDirPath);
@@ -1404,7 +1597,10 @@ Available: ${available}${hint}`
1404
1597
  });
1405
1598
  }
1406
1599
  function registerQuery(manifestCmd) {
1407
- manifestCmd.command("query").description("Query components by attributes").option("--context <name>", "Find components consuming a context").option("--hook <name>", "Find components using a specific hook").option("--complexity <class>", "Filter by complexity class: simple or complex").option("--side-effects", "Find components with any side effects", false).option("--has-fetch", "Find components with fetch calls", false).option("--format <fmt>", "Output format: json or table (default: auto-detect)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH).action(
1600
+ manifestCmd.command("query").description("Query components by attributes").option("--context <name>", "Find components consuming a context").option("--hook <name>", "Find components using a specific hook").option("--complexity <class>", "Filter by complexity class: simple or complex").option("--side-effects", "Find components with any side effects", false).option("--has-fetch", "Find components with fetch calls", false).option(
1601
+ "--has-prop <spec>",
1602
+ "Find components with a prop matching name or name:type (e.g. 'loading' or 'variant:union')"
1603
+ ).option("--composed-by <name>", "Find components that compose the named component").option("--format <fmt>", "Output format: json or table (default: auto-detect)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH).action(
1408
1604
  (opts) => {
1409
1605
  try {
1410
1606
  const manifest = loadManifest(opts.manifest);
@@ -1415,9 +1611,11 @@ function registerQuery(manifestCmd) {
1415
1611
  if (opts.complexity !== void 0) queryParts.push(`complexity=${opts.complexity}`);
1416
1612
  if (opts.sideEffects) queryParts.push("side-effects");
1417
1613
  if (opts.hasFetch) queryParts.push("has-fetch");
1614
+ if (opts.hasProp !== void 0) queryParts.push(`has-prop=${opts.hasProp}`);
1615
+ if (opts.composedBy !== void 0) queryParts.push(`composed-by=${opts.composedBy}`);
1418
1616
  if (queryParts.length === 0) {
1419
1617
  process.stderr.write(
1420
- "No query flags specified. Use --context, --hook, --complexity, --side-effects, or --has-fetch.\n"
1618
+ "No query flags specified. Use --context, --hook, --complexity, --side-effects, --has-fetch, --has-prop, or --composed-by.\n"
1421
1619
  );
1422
1620
  process.exit(1);
1423
1621
  }
@@ -1444,6 +1642,27 @@ function registerQuery(manifestCmd) {
1444
1642
  if (opts.hasFetch) {
1445
1643
  entries = entries.filter(([, d]) => d.sideEffects.fetches.length > 0);
1446
1644
  }
1645
+ if (opts.hasProp !== void 0) {
1646
+ const spec = opts.hasProp;
1647
+ const colonIdx = spec.indexOf(":");
1648
+ const propName = colonIdx >= 0 ? spec.slice(0, colonIdx) : spec;
1649
+ const propType = colonIdx >= 0 ? spec.slice(colonIdx + 1) : void 0;
1650
+ entries = entries.filter(([, d]) => {
1651
+ const props = d.props;
1652
+ if (!props || !(propName in props)) return false;
1653
+ if (propType !== void 0) {
1654
+ return props[propName]?.type === propType;
1655
+ }
1656
+ return true;
1657
+ });
1658
+ }
1659
+ if (opts.composedBy !== void 0) {
1660
+ const targetName = opts.composedBy;
1661
+ entries = entries.filter(([, d]) => {
1662
+ const composedBy = d.composedBy;
1663
+ return composedBy !== void 0 && composedBy.includes(targetName);
1664
+ });
1665
+ }
1447
1666
  const rows = entries.map(([name, d]) => ({
1448
1667
  name,
1449
1668
  file: d.filePath,
@@ -2996,8 +3215,8 @@ function createInstrumentCommand() {
2996
3215
  }
2997
3216
 
2998
3217
  // src/render-commands.ts
2999
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
3000
- import { resolve as resolve8 } from "path";
3218
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
3219
+ import { resolve as resolve9 } from "path";
3001
3220
  import {
3002
3221
  ALL_CONTEXT_IDS,
3003
3222
  ALL_STRESS_IDS,
@@ -3009,6 +3228,140 @@ import {
3009
3228
  stressAxis
3010
3229
  } from "@agent-scope/render";
3011
3230
  import { Command as Command6 } from "commander";
3231
+
3232
+ // src/scope-file.ts
3233
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync } from "fs";
3234
+ import { createRequire as createRequire2 } from "module";
3235
+ import { tmpdir } from "os";
3236
+ import { dirname as dirname2, join as join3, resolve as resolve8 } from "path";
3237
+ import * as esbuild2 from "esbuild";
3238
+ var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
3239
+ function findScopeFile(componentFilePath) {
3240
+ const dir = dirname2(componentFilePath);
3241
+ const stem = componentFilePath.replace(/\.(tsx?|jsx?)$/, "");
3242
+ const baseName = stem.slice(dir.length + 1);
3243
+ for (const ext of SCOPE_EXTENSIONS) {
3244
+ const candidate = join3(dir, `${baseName}${ext}`);
3245
+ if (existsSync6(candidate)) return candidate;
3246
+ }
3247
+ return null;
3248
+ }
3249
+ async function loadScopeFile(scopeFilePath) {
3250
+ const tmpDir = join3(tmpdir(), `scope-file-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3251
+ mkdirSync3(tmpDir, { recursive: true });
3252
+ const outFile = join3(tmpDir, "scope-file.cjs");
3253
+ try {
3254
+ const result = await esbuild2.build({
3255
+ entryPoints: [scopeFilePath],
3256
+ bundle: true,
3257
+ format: "cjs",
3258
+ platform: "node",
3259
+ target: "node18",
3260
+ outfile: outFile,
3261
+ write: true,
3262
+ jsx: "automatic",
3263
+ jsxImportSource: "react",
3264
+ // Externalize React — we don't need to execute JSX, just extract plain data
3265
+ external: ["react", "react-dom", "react/jsx-runtime"],
3266
+ define: {
3267
+ "process.env.NODE_ENV": '"development"'
3268
+ },
3269
+ logLevel: "silent"
3270
+ });
3271
+ if (result.errors.length > 0) {
3272
+ const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
3273
+ throw new Error(`Failed to bundle scope file ${scopeFilePath}:
3274
+ ${msg}`);
3275
+ }
3276
+ const req = createRequire2(import.meta.url);
3277
+ delete req.cache[resolve8(outFile)];
3278
+ const mod = req(outFile);
3279
+ const scenarios = extractScenarios(mod, scopeFilePath);
3280
+ const hasWrapper = typeof mod.wrapper === "function" || typeof mod.default?.wrapper === "function";
3281
+ return { filePath: scopeFilePath, scenarios, hasWrapper };
3282
+ } finally {
3283
+ try {
3284
+ rmSync(tmpDir, { recursive: true, force: true });
3285
+ } catch {
3286
+ }
3287
+ }
3288
+ }
3289
+ async function loadScopeFileForComponent(componentFilePath) {
3290
+ const scopeFilePath = findScopeFile(componentFilePath);
3291
+ if (scopeFilePath === null) return null;
3292
+ return loadScopeFile(scopeFilePath);
3293
+ }
3294
+ function extractScenarios(mod, filePath) {
3295
+ const raw = mod.scenarios ?? mod.default?.scenarios;
3296
+ if (raw === void 0) return {};
3297
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
3298
+ console.warn(`[scope] ${filePath}: "scenarios" export is not a plain object \u2014 ignoring.`);
3299
+ return {};
3300
+ }
3301
+ const result = {};
3302
+ for (const [name, props] of Object.entries(raw)) {
3303
+ if (typeof props !== "object" || props === null || Array.isArray(props)) {
3304
+ console.warn(`[scope] ${filePath}: scenario "${name}" is not a plain object \u2014 skipping.`);
3305
+ continue;
3306
+ }
3307
+ result[name] = props;
3308
+ }
3309
+ return result;
3310
+ }
3311
+ async function buildWrapperScript(scopeFilePath) {
3312
+ const wrapperEntry = (
3313
+ /* ts */
3314
+ `
3315
+ import * as __scopeMod from ${JSON.stringify(scopeFilePath)};
3316
+ // Expose the wrapper on window so the harness can access it
3317
+ var wrapper =
3318
+ __scopeMod.wrapper ??
3319
+ (__scopeMod.default && __scopeMod.default.wrapper) ??
3320
+ null;
3321
+ window.__SCOPE_WRAPPER__ = wrapper;
3322
+ `
3323
+ );
3324
+ const result = await esbuild2.build({
3325
+ stdin: {
3326
+ contents: wrapperEntry,
3327
+ resolveDir: dirname2(scopeFilePath),
3328
+ loader: "tsx",
3329
+ sourcefile: "__scope_wrapper_entry__.tsx"
3330
+ },
3331
+ bundle: true,
3332
+ format: "iife",
3333
+ platform: "browser",
3334
+ target: "es2020",
3335
+ write: false,
3336
+ jsx: "automatic",
3337
+ jsxImportSource: "react",
3338
+ external: [],
3339
+ define: {
3340
+ "process.env.NODE_ENV": '"development"',
3341
+ global: "globalThis"
3342
+ },
3343
+ logLevel: "silent"
3344
+ });
3345
+ if (result.errors.length > 0) {
3346
+ const msg = result.errors.map((e) => `${e.text}${e.location ? ` (${e.location.file}:${e.location.line})` : ""}`).join("\n");
3347
+ throw new Error(`Failed to build wrapper script from ${scopeFilePath}:
3348
+ ${msg}`);
3349
+ }
3350
+ return result.outputFiles?.[0]?.text ?? "";
3351
+ }
3352
+
3353
+ // src/render-commands.ts
3354
+ function loadGlobalCssFilesFromConfig(cwd) {
3355
+ const configPath = resolve9(cwd, "reactscope.config.json");
3356
+ if (!existsSync7(configPath)) return [];
3357
+ try {
3358
+ const raw = readFileSync6(configPath, "utf-8");
3359
+ const cfg = JSON.parse(raw);
3360
+ return cfg.components?.wrappers?.globalCSS ?? [];
3361
+ } catch {
3362
+ return [];
3363
+ }
3364
+ }
3012
3365
  var MANIFEST_PATH6 = ".reactscope/manifest.json";
3013
3366
  var DEFAULT_OUTPUT_DIR = ".reactscope/renders";
3014
3367
  var _pool3 = null;
@@ -3029,7 +3382,7 @@ async function shutdownPool3() {
3029
3382
  _pool3 = null;
3030
3383
  }
3031
3384
  }
3032
- function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
3385
+ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight, globalCssFiles = [], projectCwd = process.cwd(), wrapperScript) {
3033
3386
  const satori = new SatoriRenderer({
3034
3387
  defaultViewport: { width: viewportWidth, height: viewportHeight }
3035
3388
  });
@@ -3038,11 +3391,14 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
3038
3391
  async renderCell(props, _complexityClass) {
3039
3392
  const startMs = performance.now();
3040
3393
  const pool = await getPool3(viewportWidth, viewportHeight);
3394
+ const projectCss = await loadGlobalCss(globalCssFiles, projectCwd);
3041
3395
  const htmlHarness = await buildComponentHarness(
3042
3396
  filePath,
3043
3397
  componentName,
3044
3398
  props,
3045
- viewportWidth
3399
+ viewportWidth,
3400
+ projectCss ?? void 0,
3401
+ wrapperScript
3046
3402
  );
3047
3403
  const slot = await pool.acquire();
3048
3404
  const { page } = slot;
@@ -3071,9 +3427,9 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
3071
3427
  });
3072
3428
  return [...set];
3073
3429
  });
3074
- const projectCss = await getCompiledCssForClasses(rootDir, classes);
3075
- if (projectCss != null && projectCss.length > 0) {
3076
- await page.addStyleTag({ content: projectCss });
3430
+ const projectCss2 = await getCompiledCssForClasses(rootDir, classes);
3431
+ if (projectCss2 != null && projectCss2.length > 0) {
3432
+ await page.addStyleTag({ content: projectCss2 });
3077
3433
  }
3078
3434
  const renderTimeMs = performance.now() - startMs;
3079
3435
  const rootLocator = page.locator("[data-reactscope-root]");
@@ -3133,8 +3489,37 @@ function buildRenderer(filePath, componentName, viewportWidth, viewportHeight) {
3133
3489
  }
3134
3490
  };
3135
3491
  }
3492
+ function buildScenarioMap(opts, scopeData) {
3493
+ if (opts.scenario !== void 0) {
3494
+ if (scopeData === null) {
3495
+ throw new Error(`--scenario "${opts.scenario}" requires a .scope file next to the component`);
3496
+ }
3497
+ const props = scopeData.scenarios[opts.scenario];
3498
+ if (props === void 0) {
3499
+ const available = Object.keys(scopeData.scenarios).join(", ") || "(none)";
3500
+ throw new Error(
3501
+ `Scenario "${opts.scenario}" not found in scope file.
3502
+ Available: ${available}`
3503
+ );
3504
+ }
3505
+ return { [opts.scenario]: props };
3506
+ }
3507
+ if (opts.props !== void 0) {
3508
+ let parsed;
3509
+ try {
3510
+ parsed = JSON.parse(opts.props);
3511
+ } catch {
3512
+ throw new Error(`Invalid props JSON: ${opts.props}`);
3513
+ }
3514
+ return { __default__: parsed };
3515
+ }
3516
+ if (scopeData !== null && Object.keys(scopeData.scenarios).length > 0) {
3517
+ return scopeData.scenarios;
3518
+ }
3519
+ return { __default__: {} };
3520
+ }
3136
3521
  function registerRenderSingle(renderCmd) {
3137
- renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
3522
+ renderCmd.command("component <component>", { isDefault: true }).description("Render a single component to PNG or JSON").option("--props <json>", `Inline props JSON, e.g. '{"variant":"primary"}'`).option("--scenario <name>", "Run a named scenario from the component's .scope file").option("--viewport <WxH>", "Viewport size e.g. 1280x720", "375x812").option("--theme <name>", "Theme name from the token system").option("-o, --output <path>", "Write PNG to file instead of stdout").option("--format <fmt>", "Output format: png or json (default: auto)").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
3138
3523
  async (componentName, opts) => {
3139
3524
  try {
3140
3525
  const manifest = loadManifest(opts.manifest);
@@ -3154,72 +3539,96 @@ Available: ${available}`
3154
3539
  throw new Error(`Invalid props JSON: ${opts.props}`);
3155
3540
  }
3156
3541
  }
3542
+ if (descriptor.props !== void 0) {
3543
+ const propDefs = descriptor.props;
3544
+ for (const [propName, propDef] of Object.entries(propDefs)) {
3545
+ if (propName in props) continue;
3546
+ if (!propDef.required && propDef.default !== void 0) continue;
3547
+ if (propDef.type === "node" || propDef.type === "string") {
3548
+ props[propName] = propName === "children" ? componentName : propName;
3549
+ } else if (propDef.type === "union" && propDef.values && propDef.values.length > 0) {
3550
+ props[propName] = propDef.values[0];
3551
+ } else if (propDef.type === "boolean") {
3552
+ props[propName] = false;
3553
+ } else if (propDef.type === "number") {
3554
+ props[propName] = 0;
3555
+ }
3556
+ }
3557
+ }
3157
3558
  const { width, height } = parseViewport(opts.viewport);
3158
3559
  const rootDir = process.cwd();
3159
- const filePath = resolve8(rootDir, descriptor.filePath);
3160
- const renderer = buildRenderer(filePath, componentName, width, height);
3560
+ const filePath = resolve9(rootDir, descriptor.filePath);
3561
+ const scopeData = await loadScopeFileForComponent(filePath);
3562
+ const wrapperScript = scopeData?.hasWrapper === true ? await buildWrapperScript(scopeData.filePath) : void 0;
3563
+ const scenarios = buildScenarioMap(opts, scopeData);
3564
+ const globalCssFiles = loadGlobalCssFilesFromConfig(rootDir);
3565
+ const renderer = buildRenderer(
3566
+ filePath,
3567
+ componentName,
3568
+ width,
3569
+ height,
3570
+ globalCssFiles,
3571
+ rootDir,
3572
+ wrapperScript
3573
+ );
3161
3574
  process.stderr.write(
3162
3575
  `Rendering ${componentName} [${descriptor.complexityClass}] at ${width}\xD7${height}\u2026
3163
3576
  `
3164
3577
  );
3165
- const outcome = await safeRender2(
3166
- () => renderer.renderCell(props, descriptor.complexityClass),
3167
- {
3168
- props,
3169
- sourceLocation: {
3170
- file: descriptor.filePath,
3171
- line: descriptor.loc.start,
3172
- column: 0
3578
+ const fmt2 = resolveSingleFormat(opts.format);
3579
+ let anyFailed = false;
3580
+ for (const [scenarioName, props2] of Object.entries(scenarios)) {
3581
+ const isNamed = scenarioName !== "__default__";
3582
+ const label = isNamed ? `${componentName}:${scenarioName}` : componentName;
3583
+ const outcome = await safeRender2(
3584
+ () => renderer.renderCell(props2, descriptor.complexityClass),
3585
+ {
3586
+ props: props2,
3587
+ sourceLocation: {
3588
+ file: descriptor.filePath,
3589
+ line: descriptor.loc.start,
3590
+ column: 0
3591
+ }
3173
3592
  }
3174
- }
3175
- );
3176
- await shutdownPool3();
3177
- if (outcome.crashed) {
3178
- process.stderr.write(`\u2717 Render failed: ${outcome.error.message}
3593
+ );
3594
+ if (outcome.crashed) {
3595
+ process.stderr.write(`\u2717 ${label} render failed: ${outcome.error.message}
3179
3596
  `);
3180
- const hintList = outcome.error.heuristicFlags.join(", ");
3181
- if (hintList.length > 0) {
3182
- process.stderr.write(` Hints: ${hintList}
3597
+ const hintList = outcome.error.heuristicFlags.join(", ");
3598
+ if (hintList.length > 0) {
3599
+ process.stderr.write(` Hints: ${hintList}
3183
3600
  `);
3601
+ }
3602
+ anyFailed = true;
3603
+ continue;
3184
3604
  }
3185
- process.exit(1);
3186
- }
3187
- const result = outcome.result;
3188
- if (opts.output !== void 0) {
3189
- const outPath = resolve8(process.cwd(), opts.output);
3190
- writeFileSync5(outPath, result.screenshot);
3191
- process.stdout.write(
3192
- `\u2713 ${componentName} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
3605
+ const result = outcome.result;
3606
+ const outFileName = isNamed ? `${componentName}-${scenarioName}.png` : `${componentName}.png`;
3607
+ if (opts.output !== void 0 && !isNamed) {
3608
+ const outPath = resolve9(process.cwd(), opts.output);
3609
+ writeFileSync5(outPath, result.screenshot);
3610
+ process.stdout.write(
3611
+ `\u2713 ${label} \u2192 ${opts.output} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
3193
3612
  `
3194
- );
3195
- return;
3196
- }
3197
- const fmt2 = resolveSingleFormat(opts.format);
3198
- if (fmt2 === "json") {
3199
- const json = formatRenderJson(componentName, props, result);
3200
- process.stdout.write(`${JSON.stringify(json, null, 2)}
3613
+ );
3614
+ } else if (fmt2 === "json") {
3615
+ const json = formatRenderJson(label, props2, result);
3616
+ process.stdout.write(`${JSON.stringify(json, null, 2)}
3201
3617
  `);
3202
- } else if (fmt2 === "file") {
3203
- const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
3204
- mkdirSync3(dir, { recursive: true });
3205
- const outPath = resolve8(dir, `${componentName}.png`);
3206
- writeFileSync5(outPath, result.screenshot);
3207
- const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
3208
- process.stdout.write(
3209
- `\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
3210
- `
3211
- );
3212
- } else {
3213
- const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
3214
- mkdirSync3(dir, { recursive: true });
3215
- const outPath = resolve8(dir, `${componentName}.png`);
3216
- writeFileSync5(outPath, result.screenshot);
3217
- const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}.png`;
3218
- process.stdout.write(
3219
- `\u2713 ${componentName} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
3618
+ } else {
3619
+ const dir = resolve9(process.cwd(), DEFAULT_OUTPUT_DIR);
3620
+ mkdirSync4(dir, { recursive: true });
3621
+ const outPath = resolve9(dir, outFileName);
3622
+ writeFileSync5(outPath, result.screenshot);
3623
+ const relPath = `${DEFAULT_OUTPUT_DIR}/${outFileName}`;
3624
+ process.stdout.write(
3625
+ `\u2713 ${label} \u2192 ${relPath} (${result.width}\xD7${result.height}, ${result.renderTimeMs.toFixed(0)}ms)
3220
3626
  `
3221
- );
3627
+ );
3628
+ }
3222
3629
  }
3630
+ await shutdownPool3();
3631
+ if (anyFailed) process.exit(1);
3223
3632
  } catch (err) {
3224
3633
  await shutdownPool3();
3225
3634
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}
@@ -3230,7 +3639,10 @@ Available: ${available}`
3230
3639
  );
3231
3640
  }
3232
3641
  function registerRenderMatrix(renderCmd) {
3233
- renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option("--axes <spec>", "Axis definitions e.g. 'variant:primary,secondary size:sm,md,lg'").option(
3642
+ renderCmd.command("matrix <component>").description("Render a component across a matrix of prop axes").option(
3643
+ "--axes <spec>",
3644
+ `Axis definitions: key:v1,v2 space-separated OR JSON object e.g. 'variant:primary,ghost size:sm,lg' or '{"variant":["primary","ghost"],"size":["sm","lg"]}'`
3645
+ ).option(
3234
3646
  "--contexts <ids>",
3235
3647
  "Composition context IDs, comma-separated (e.g. centered,rtl,sidebar)"
3236
3648
  ).option("--stress <ids>", "Stress preset IDs, comma-separated (e.g. text.long,text.unicode)").option("--sprite <path>", "Write sprite sheet PNG to file").option("--format <fmt>", "Output format: json|png|html|csv (default: auto)").option("--concurrency <n>", "Max parallel renders", "8").option("--manifest <path>", "Path to manifest.json", MANIFEST_PATH6).action(
@@ -3248,22 +3660,48 @@ Available: ${available}`
3248
3660
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 8);
3249
3661
  const { width, height } = { width: 375, height: 812 };
3250
3662
  const rootDir = process.cwd();
3251
- const filePath = resolve8(rootDir, descriptor.filePath);
3252
- const renderer = buildRenderer(filePath, componentName, width, height);
3663
+ const filePath = resolve9(rootDir, descriptor.filePath);
3664
+ const matrixCssFiles = loadGlobalCssFilesFromConfig(rootDir);
3665
+ const renderer = buildRenderer(
3666
+ filePath,
3667
+ componentName,
3668
+ width,
3669
+ height,
3670
+ matrixCssFiles,
3671
+ rootDir
3672
+ );
3253
3673
  const axes = [];
3254
3674
  if (opts.axes !== void 0) {
3255
- const axisSpecs = opts.axes.trim().split(/\s+/);
3256
- for (const spec of axisSpecs) {
3257
- const colonIdx = spec.indexOf(":");
3258
- if (colonIdx < 0) {
3259
- throw new Error(`Invalid axis spec "${spec}". Expected format: name:val1,val2,...`);
3675
+ const axesRaw = opts.axes.trim();
3676
+ if (axesRaw.startsWith("{")) {
3677
+ let parsed;
3678
+ try {
3679
+ parsed = JSON.parse(axesRaw);
3680
+ } catch {
3681
+ throw new Error(`Invalid JSON in --axes: ${axesRaw}`);
3260
3682
  }
3261
- const name = spec.slice(0, colonIdx);
3262
- const values = spec.slice(colonIdx + 1).split(",").map((v) => v.trim());
3263
- if (name.length === 0 || values.length === 0) {
3264
- throw new Error(`Invalid axis spec "${spec}"`);
3683
+ for (const [name, vals] of Object.entries(parsed)) {
3684
+ if (!Array.isArray(vals)) {
3685
+ throw new Error(`Axis "${name}" must be an array of values in JSON format`);
3686
+ }
3687
+ axes.push({ name, values: vals.map(String) });
3688
+ }
3689
+ } else {
3690
+ const axisSpecs = axesRaw.split(/\s+/);
3691
+ for (const spec of axisSpecs) {
3692
+ const colonIdx = spec.indexOf(":");
3693
+ if (colonIdx < 0) {
3694
+ throw new Error(
3695
+ `Invalid axis spec "${spec}". Expected format: name:val1,val2,...`
3696
+ );
3697
+ }
3698
+ const name = spec.slice(0, colonIdx);
3699
+ const values = spec.slice(colonIdx + 1).split(",").map((v) => v.trim());
3700
+ if (name.length === 0 || values.length === 0) {
3701
+ throw new Error(`Invalid axis spec "${spec}"`);
3702
+ }
3703
+ axes.push({ name, values });
3265
3704
  }
3266
- axes.push({ name, values });
3267
3705
  }
3268
3706
  }
3269
3707
  if (opts.contexts !== void 0) {
@@ -3315,7 +3753,7 @@ Available: ${available}`
3315
3753
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
3316
3754
  const gen = new SpriteSheetGenerator2();
3317
3755
  const sheet = await gen.generate(result);
3318
- const spritePath = resolve8(process.cwd(), opts.sprite);
3756
+ const spritePath = resolve9(process.cwd(), opts.sprite);
3319
3757
  writeFileSync5(spritePath, sheet.png);
3320
3758
  process.stderr.write(`Sprite sheet saved to ${spritePath}
3321
3759
  `);
@@ -3325,9 +3763,9 @@ Available: ${available}`
3325
3763
  const { SpriteSheetGenerator: SpriteSheetGenerator2 } = await import("@agent-scope/render");
3326
3764
  const gen = new SpriteSheetGenerator2();
3327
3765
  const sheet = await gen.generate(result);
3328
- const dir = resolve8(process.cwd(), DEFAULT_OUTPUT_DIR);
3329
- mkdirSync3(dir, { recursive: true });
3330
- const outPath = resolve8(dir, `${componentName}-matrix.png`);
3766
+ const dir = resolve9(process.cwd(), DEFAULT_OUTPUT_DIR);
3767
+ mkdirSync4(dir, { recursive: true });
3768
+ const outPath = resolve9(dir, `${componentName}-matrix.png`);
3331
3769
  writeFileSync5(outPath, sheet.png);
3332
3770
  const relPath = `${DEFAULT_OUTPUT_DIR}/${componentName}-matrix.png`;
3333
3771
  process.stdout.write(
@@ -3371,8 +3809,8 @@ function registerRenderAll(renderCmd) {
3371
3809
  return;
3372
3810
  }
3373
3811
  const concurrency = Math.max(1, parseInt(opts.concurrency, 10) || 4);
3374
- const outputDir = resolve8(process.cwd(), opts.outputDir);
3375
- mkdirSync3(outputDir, { recursive: true });
3812
+ const outputDir = resolve9(process.cwd(), opts.outputDir);
3813
+ mkdirSync4(outputDir, { recursive: true });
3376
3814
  const rootDir = process.cwd();
3377
3815
  process.stderr.write(`Rendering ${total} components (concurrency: ${concurrency})\u2026
3378
3816
  `);
@@ -3381,8 +3819,9 @@ function registerRenderAll(renderCmd) {
3381
3819
  const renderOne = async (name) => {
3382
3820
  const descriptor = manifest.components[name];
3383
3821
  if (descriptor === void 0) return;
3384
- const filePath = resolve8(rootDir, descriptor.filePath);
3385
- const renderer = buildRenderer(filePath, name, 375, 812);
3822
+ const filePath = resolve9(rootDir, descriptor.filePath);
3823
+ const allCssFiles = loadGlobalCssFilesFromConfig(process.cwd());
3824
+ const renderer = buildRenderer(filePath, name, 375, 812, allCssFiles, process.cwd());
3386
3825
  const outcome = await safeRender2(
3387
3826
  () => renderer.renderCell({}, descriptor.complexityClass),
3388
3827
  {
@@ -3404,7 +3843,7 @@ function registerRenderAll(renderCmd) {
3404
3843
  success: false,
3405
3844
  errorMessage: outcome.error.message
3406
3845
  });
3407
- const errPath = resolve8(outputDir, `${name}.error.json`);
3846
+ const errPath = resolve9(outputDir, `${name}.error.json`);
3408
3847
  writeFileSync5(
3409
3848
  errPath,
3410
3849
  JSON.stringify(
@@ -3422,9 +3861,9 @@ function registerRenderAll(renderCmd) {
3422
3861
  }
3423
3862
  const result = outcome.result;
3424
3863
  results.push({ name, renderTimeMs: result.renderTimeMs, success: true });
3425
- const pngPath = resolve8(outputDir, `${name}.png`);
3864
+ const pngPath = resolve9(outputDir, `${name}.png`);
3426
3865
  writeFileSync5(pngPath, result.screenshot);
3427
- const jsonPath = resolve8(outputDir, `${name}.json`);
3866
+ const jsonPath = resolve9(outputDir, `${name}.json`);
3428
3867
  writeFileSync5(jsonPath, JSON.stringify(formatRenderJson(name, {}, result), null, 2));
3429
3868
  if (isTTY()) {
3430
3869
  process.stdout.write(
@@ -3497,8 +3936,8 @@ function createRenderCommand() {
3497
3936
  }
3498
3937
 
3499
3938
  // src/report/baseline.ts
3500
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync, writeFileSync as writeFileSync6 } from "fs";
3501
- import { resolve as resolve9 } from "path";
3939
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
3940
+ import { resolve as resolve10 } from "path";
3502
3941
  import { generateManifest as generateManifest3 } from "@agent-scope/manifest";
3503
3942
  import { BrowserPool as BrowserPool4, safeRender as safeRender3 } from "@agent-scope/render";
3504
3943
  import { ComplianceEngine as ComplianceEngine2, TokenResolver as TokenResolver2 } from "@agent-scope/tokens";
@@ -3647,20 +4086,20 @@ async function runBaseline(options = {}) {
3647
4086
  } = options;
3648
4087
  const startTime = performance.now();
3649
4088
  const rootDir = process.cwd();
3650
- const baselineDir = resolve9(rootDir, outputDir);
3651
- const rendersDir = resolve9(baselineDir, "renders");
3652
- if (existsSync6(baselineDir)) {
3653
- rmSync(baselineDir, { recursive: true, force: true });
4089
+ const baselineDir = resolve10(rootDir, outputDir);
4090
+ const rendersDir = resolve10(baselineDir, "renders");
4091
+ if (existsSync8(baselineDir)) {
4092
+ rmSync2(baselineDir, { recursive: true, force: true });
3654
4093
  }
3655
- mkdirSync4(rendersDir, { recursive: true });
4094
+ mkdirSync5(rendersDir, { recursive: true });
3656
4095
  let manifest;
3657
4096
  if (manifestPath !== void 0) {
3658
- const { readFileSync: readFileSync12 } = await import("fs");
3659
- const absPath = resolve9(rootDir, manifestPath);
3660
- if (!existsSync6(absPath)) {
4097
+ const { readFileSync: readFileSync13 } = await import("fs");
4098
+ const absPath = resolve10(rootDir, manifestPath);
4099
+ if (!existsSync8(absPath)) {
3661
4100
  throw new Error(`Manifest not found at ${absPath}.`);
3662
4101
  }
3663
- manifest = JSON.parse(readFileSync12(absPath, "utf-8"));
4102
+ manifest = JSON.parse(readFileSync13(absPath, "utf-8"));
3664
4103
  process.stderr.write(`Loaded manifest from ${manifestPath}
3665
4104
  `);
3666
4105
  } else {
@@ -3670,7 +4109,7 @@ async function runBaseline(options = {}) {
3670
4109
  process.stderr.write(`Found ${count} components.
3671
4110
  `);
3672
4111
  }
3673
- writeFileSync6(resolve9(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
4112
+ writeFileSync6(resolve10(baselineDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
3674
4113
  let componentNames = Object.keys(manifest.components);
3675
4114
  if (componentsGlob !== void 0) {
3676
4115
  componentNames = componentNames.filter((name) => matchGlob(componentsGlob, name));
@@ -3691,7 +4130,7 @@ async function runBaseline(options = {}) {
3691
4130
  auditedAt: (/* @__PURE__ */ new Date()).toISOString()
3692
4131
  };
3693
4132
  writeFileSync6(
3694
- resolve9(baselineDir, "compliance.json"),
4133
+ resolve10(baselineDir, "compliance.json"),
3695
4134
  JSON.stringify(emptyReport, null, 2),
3696
4135
  "utf-8"
3697
4136
  );
@@ -3712,7 +4151,7 @@ async function runBaseline(options = {}) {
3712
4151
  const renderOne = async (name) => {
3713
4152
  const descriptor = manifest.components[name];
3714
4153
  if (descriptor === void 0) return;
3715
- const filePath = resolve9(rootDir, descriptor.filePath);
4154
+ const filePath = resolve10(rootDir, descriptor.filePath);
3716
4155
  const outcome = await safeRender3(
3717
4156
  () => renderComponent2(filePath, name, {}, viewportWidth, viewportHeight),
3718
4157
  {
@@ -3731,7 +4170,7 @@ async function runBaseline(options = {}) {
3731
4170
  }
3732
4171
  if (outcome.crashed) {
3733
4172
  failureCount++;
3734
- const errPath = resolve9(rendersDir, `${name}.error.json`);
4173
+ const errPath = resolve10(rendersDir, `${name}.error.json`);
3735
4174
  writeFileSync6(
3736
4175
  errPath,
3737
4176
  JSON.stringify(
@@ -3749,10 +4188,10 @@ async function runBaseline(options = {}) {
3749
4188
  return;
3750
4189
  }
3751
4190
  const result = outcome.result;
3752
- writeFileSync6(resolve9(rendersDir, `${name}.png`), result.screenshot);
4191
+ writeFileSync6(resolve10(rendersDir, `${name}.png`), result.screenshot);
3753
4192
  const jsonOutput = formatRenderJson(name, {}, result);
3754
4193
  writeFileSync6(
3755
- resolve9(rendersDir, `${name}.json`),
4194
+ resolve10(rendersDir, `${name}.json`),
3756
4195
  JSON.stringify(jsonOutput, null, 2),
3757
4196
  "utf-8"
3758
4197
  );
@@ -3780,7 +4219,7 @@ async function runBaseline(options = {}) {
3780
4219
  const engine = new ComplianceEngine2(resolver);
3781
4220
  const batchReport = engine.auditBatch(computedStylesMap);
3782
4221
  writeFileSync6(
3783
- resolve9(baselineDir, "compliance.json"),
4222
+ resolve10(baselineDir, "compliance.json"),
3784
4223
  JSON.stringify(batchReport, null, 2),
3785
4224
  "utf-8"
3786
4225
  );
@@ -3823,22 +4262,22 @@ function registerBaselineSubCommand(reportCmd) {
3823
4262
  }
3824
4263
 
3825
4264
  // src/report/diff.ts
3826
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
3827
- import { resolve as resolve10 } from "path";
4265
+ import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
4266
+ import { resolve as resolve11 } from "path";
3828
4267
  import { generateManifest as generateManifest4 } from "@agent-scope/manifest";
3829
4268
  import { BrowserPool as BrowserPool5, safeRender as safeRender4 } from "@agent-scope/render";
3830
4269
  import { ComplianceEngine as ComplianceEngine3, TokenResolver as TokenResolver3 } from "@agent-scope/tokens";
3831
4270
  var DEFAULT_BASELINE_DIR2 = ".reactscope/baseline";
3832
4271
  function loadBaselineCompliance(baselineDir) {
3833
- const compliancePath = resolve10(baselineDir, "compliance.json");
3834
- if (!existsSync7(compliancePath)) return null;
3835
- const raw = JSON.parse(readFileSync6(compliancePath, "utf-8"));
4272
+ const compliancePath = resolve11(baselineDir, "compliance.json");
4273
+ if (!existsSync9(compliancePath)) return null;
4274
+ const raw = JSON.parse(readFileSync7(compliancePath, "utf-8"));
3836
4275
  return raw;
3837
4276
  }
3838
4277
  function loadBaselineRenderJson2(baselineDir, componentName) {
3839
- const jsonPath = resolve10(baselineDir, "renders", `${componentName}.json`);
3840
- if (!existsSync7(jsonPath)) return null;
3841
- return JSON.parse(readFileSync6(jsonPath, "utf-8"));
4278
+ const jsonPath = resolve11(baselineDir, "renders", `${componentName}.json`);
4279
+ if (!existsSync9(jsonPath)) return null;
4280
+ return JSON.parse(readFileSync7(jsonPath, "utf-8"));
3842
4281
  }
3843
4282
  var _pool5 = null;
3844
4283
  async function getPool5(viewportWidth, viewportHeight) {
@@ -4005,19 +4444,19 @@ async function runDiff(options = {}) {
4005
4444
  } = options;
4006
4445
  const startTime = performance.now();
4007
4446
  const rootDir = process.cwd();
4008
- const baselineDir = resolve10(rootDir, baselineDirRaw);
4009
- if (!existsSync7(baselineDir)) {
4447
+ const baselineDir = resolve11(rootDir, baselineDirRaw);
4448
+ if (!existsSync9(baselineDir)) {
4010
4449
  throw new Error(
4011
4450
  `Baseline directory not found at "${baselineDir}". Run \`scope report baseline\` first to create a baseline snapshot.`
4012
4451
  );
4013
4452
  }
4014
- const baselineManifestPath = resolve10(baselineDir, "manifest.json");
4015
- if (!existsSync7(baselineManifestPath)) {
4453
+ const baselineManifestPath = resolve11(baselineDir, "manifest.json");
4454
+ if (!existsSync9(baselineManifestPath)) {
4016
4455
  throw new Error(
4017
4456
  `Baseline manifest.json not found at "${baselineManifestPath}". The baseline directory may be incomplete \u2014 re-run \`scope report baseline\`.`
4018
4457
  );
4019
4458
  }
4020
- const baselineManifest = JSON.parse(readFileSync6(baselineManifestPath, "utf-8"));
4459
+ const baselineManifest = JSON.parse(readFileSync7(baselineManifestPath, "utf-8"));
4021
4460
  const baselineCompliance = loadBaselineCompliance(baselineDir);
4022
4461
  const baselineComponentNames = new Set(Object.keys(baselineManifest.components));
4023
4462
  process.stderr.write(
@@ -4026,11 +4465,11 @@ async function runDiff(options = {}) {
4026
4465
  );
4027
4466
  let currentManifest;
4028
4467
  if (manifestPath !== void 0) {
4029
- const absPath = resolve10(rootDir, manifestPath);
4030
- if (!existsSync7(absPath)) {
4468
+ const absPath = resolve11(rootDir, manifestPath);
4469
+ if (!existsSync9(absPath)) {
4031
4470
  throw new Error(`Manifest not found at "${absPath}".`);
4032
4471
  }
4033
- currentManifest = JSON.parse(readFileSync6(absPath, "utf-8"));
4472
+ currentManifest = JSON.parse(readFileSync7(absPath, "utf-8"));
4034
4473
  process.stderr.write(`Loaded manifest from ${manifestPath}
4035
4474
  `);
4036
4475
  } else {
@@ -4063,7 +4502,7 @@ async function runDiff(options = {}) {
4063
4502
  const renderOne = async (name) => {
4064
4503
  const descriptor = currentManifest.components[name];
4065
4504
  if (descriptor === void 0) return;
4066
- const filePath = resolve10(rootDir, descriptor.filePath);
4505
+ const filePath = resolve11(rootDir, descriptor.filePath);
4067
4506
  const outcome = await safeRender4(
4068
4507
  () => renderComponent3(filePath, name, {}, viewportWidth, viewportHeight),
4069
4508
  {
@@ -4303,8 +4742,8 @@ function registerDiffSubCommand(reportCmd) {
4303
4742
  }
4304
4743
 
4305
4744
  // src/report/pr-comment.ts
4306
- import { existsSync as existsSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "fs";
4307
- import { resolve as resolve11 } from "path";
4745
+ import { existsSync as existsSync10, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
4746
+ import { resolve as resolve12 } from "path";
4308
4747
  var STATUS_BADGE = {
4309
4748
  added: "\u2705 added",
4310
4749
  removed: "\u{1F5D1}\uFE0F removed",
@@ -4387,13 +4826,13 @@ function formatPrComment(diff) {
4387
4826
  return lines.join("\n");
4388
4827
  }
4389
4828
  function loadDiffResult(filePath) {
4390
- const abs = resolve11(filePath);
4391
- if (!existsSync8(abs)) {
4829
+ const abs = resolve12(filePath);
4830
+ if (!existsSync10(abs)) {
4392
4831
  throw new Error(`DiffResult file not found: ${abs}`);
4393
4832
  }
4394
4833
  let raw;
4395
4834
  try {
4396
- raw = readFileSync7(abs, "utf-8");
4835
+ raw = readFileSync8(abs, "utf-8");
4397
4836
  } catch (err) {
4398
4837
  throw new Error(
4399
4838
  `Failed to read DiffResult file: ${err instanceof Error ? err.message : String(err)}`
@@ -4420,7 +4859,7 @@ function registerPrCommentSubCommand(reportCmd) {
4420
4859
  const diff = loadDiffResult(opts.input);
4421
4860
  const comment = formatPrComment(diff);
4422
4861
  if (opts.output !== void 0) {
4423
- writeFileSync8(resolve11(opts.output), comment, "utf-8");
4862
+ writeFileSync8(resolve12(opts.output), comment, "utf-8");
4424
4863
  process.stderr.write(`PR comment written to ${opts.output}
4425
4864
  `);
4426
4865
  } else {
@@ -4715,9 +5154,9 @@ function buildStructuredReport(report) {
4715
5154
  }
4716
5155
 
4717
5156
  // src/site-commands.ts
4718
- import { createReadStream, existsSync as existsSync9, statSync } from "fs";
5157
+ import { createReadStream, existsSync as existsSync11, statSync } from "fs";
4719
5158
  import { createServer } from "http";
4720
- import { extname, join as join3, resolve as resolve12 } from "path";
5159
+ import { extname, join as join4, resolve as resolve13 } from "path";
4721
5160
  import { buildSite } from "@agent-scope/site";
4722
5161
  import { Command as Command7 } from "commander";
4723
5162
  var MIME_TYPES = {
@@ -4735,16 +5174,16 @@ function registerBuild(siteCmd) {
4735
5174
  siteCmd.command("build").description("Build a static HTML gallery from .reactscope/ output").option("-i, --input <path>", "Path to .reactscope input directory", ".reactscope").option("-o, --output <path>", "Output directory for generated site", ".reactscope/site").option("--base-path <path>", "Base URL path prefix for subdirectory deployment", "/").option("--compliance <path>", "Path to compliance batch report JSON").option("--title <text>", "Site title", "Scope \u2014 Component Gallery").action(
4736
5175
  async (opts) => {
4737
5176
  try {
4738
- const inputDir = resolve12(process.cwd(), opts.input);
4739
- const outputDir = resolve12(process.cwd(), opts.output);
4740
- if (!existsSync9(inputDir)) {
5177
+ const inputDir = resolve13(process.cwd(), opts.input);
5178
+ const outputDir = resolve13(process.cwd(), opts.output);
5179
+ if (!existsSync11(inputDir)) {
4741
5180
  throw new Error(
4742
5181
  `Input directory not found: ${inputDir}
4743
5182
  Run \`scope manifest generate\` and \`scope render\` first.`
4744
5183
  );
4745
5184
  }
4746
- const manifestPath = join3(inputDir, "manifest.json");
4747
- if (!existsSync9(manifestPath)) {
5185
+ const manifestPath = join4(inputDir, "manifest.json");
5186
+ if (!existsSync11(manifestPath)) {
4748
5187
  throw new Error(
4749
5188
  `Manifest not found at ${manifestPath}
4750
5189
  Run \`scope manifest generate\` first.`
@@ -4757,7 +5196,7 @@ Run \`scope manifest generate\` first.`
4757
5196
  outputDir,
4758
5197
  basePath: opts.basePath,
4759
5198
  ...opts.compliance !== void 0 && {
4760
- compliancePath: resolve12(process.cwd(), opts.compliance)
5199
+ compliancePath: resolve13(process.cwd(), opts.compliance)
4761
5200
  },
4762
5201
  title: opts.title
4763
5202
  });
@@ -4780,8 +5219,8 @@ function registerServe(siteCmd) {
4780
5219
  if (Number.isNaN(port) || port < 1 || port > 65535) {
4781
5220
  throw new Error(`Invalid port: ${opts.port}`);
4782
5221
  }
4783
- const serveDir = resolve12(process.cwd(), opts.dir);
4784
- if (!existsSync9(serveDir)) {
5222
+ const serveDir = resolve13(process.cwd(), opts.dir);
5223
+ if (!existsSync11(serveDir)) {
4785
5224
  throw new Error(
4786
5225
  `Serve directory not found: ${serveDir}
4787
5226
  Run \`scope site build\` first.`
@@ -4790,13 +5229,13 @@ Run \`scope site build\` first.`
4790
5229
  const server = createServer((req, res) => {
4791
5230
  const rawUrl = req.url ?? "/";
4792
5231
  const urlPath = decodeURIComponent(rawUrl.split("?")[0] ?? "/");
4793
- const filePath = join3(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
5232
+ const filePath = join4(serveDir, urlPath.endsWith("/") ? `${urlPath}index.html` : urlPath);
4794
5233
  if (!filePath.startsWith(serveDir)) {
4795
5234
  res.writeHead(403, { "Content-Type": "text/plain" });
4796
5235
  res.end("Forbidden");
4797
5236
  return;
4798
5237
  }
4799
- if (existsSync9(filePath) && statSync(filePath).isFile()) {
5238
+ if (existsSync11(filePath) && statSync(filePath).isFile()) {
4800
5239
  const ext = extname(filePath).toLowerCase();
4801
5240
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
4802
5241
  res.writeHead(200, { "Content-Type": contentType });
@@ -4804,7 +5243,7 @@ Run \`scope site build\` first.`
4804
5243
  return;
4805
5244
  }
4806
5245
  const htmlPath = `${filePath}.html`;
4807
- if (existsSync9(htmlPath) && statSync(htmlPath).isFile()) {
5246
+ if (existsSync11(htmlPath) && statSync(htmlPath).isFile()) {
4808
5247
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
4809
5248
  createReadStream(htmlPath).pipe(res);
4810
5249
  return;
@@ -4846,8 +5285,8 @@ function createSiteCommand() {
4846
5285
  }
4847
5286
 
4848
5287
  // src/tokens/commands.ts
4849
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4850
- import { resolve as resolve16 } from "path";
5288
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
5289
+ import { resolve as resolve17 } from "path";
4851
5290
  import {
4852
5291
  parseTokenFileSync as parseTokenFileSync2,
4853
5292
  TokenParseError,
@@ -4858,23 +5297,23 @@ import {
4858
5297
  import { Command as Command9 } from "commander";
4859
5298
 
4860
5299
  // src/tokens/compliance.ts
4861
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
4862
- import { resolve as resolve13 } from "path";
5300
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
5301
+ import { resolve as resolve14 } from "path";
4863
5302
  import {
4864
5303
  ComplianceEngine as ComplianceEngine4,
4865
5304
  TokenResolver as TokenResolver4
4866
5305
  } from "@agent-scope/tokens";
4867
5306
  var DEFAULT_STYLES_PATH = ".reactscope/compliance-styles.json";
4868
5307
  function loadStylesFile(stylesPath) {
4869
- const absPath = resolve13(process.cwd(), stylesPath);
4870
- if (!existsSync10(absPath)) {
5308
+ const absPath = resolve14(process.cwd(), stylesPath);
5309
+ if (!existsSync12(absPath)) {
4871
5310
  throw new Error(
4872
5311
  `Compliance styles file not found at ${absPath}.
4873
5312
  Run \`scope render all\` first to generate component styles, or use --styles to specify a path.
4874
5313
  Expected format: { "ComponentName": { colors: {}, spacing: {}, typography: {}, borders: {}, shadows: {} } }`
4875
5314
  );
4876
5315
  }
4877
- const raw = readFileSync8(absPath, "utf-8");
5316
+ const raw = readFileSync9(absPath, "utf-8");
4878
5317
  let parsed;
4879
5318
  try {
4880
5319
  parsed = JSON.parse(raw);
@@ -5034,8 +5473,8 @@ function registerCompliance(tokensCmd) {
5034
5473
  }
5035
5474
 
5036
5475
  // src/tokens/export.ts
5037
- import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "fs";
5038
- import { resolve as resolve14 } from "path";
5476
+ import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "fs";
5477
+ import { resolve as resolve15 } from "path";
5039
5478
  import {
5040
5479
  exportTokens,
5041
5480
  parseTokenFileSync,
@@ -5048,21 +5487,21 @@ var CONFIG_FILE = "reactscope.config.json";
5048
5487
  var SUPPORTED_FORMATS = ["css", "ts", "scss", "tailwind", "flat-json", "figma"];
5049
5488
  function resolveTokenFilePath2(fileFlag) {
5050
5489
  if (fileFlag !== void 0) {
5051
- return resolve14(process.cwd(), fileFlag);
5490
+ return resolve15(process.cwd(), fileFlag);
5052
5491
  }
5053
- const configPath = resolve14(process.cwd(), CONFIG_FILE);
5054
- if (existsSync11(configPath)) {
5492
+ const configPath = resolve15(process.cwd(), CONFIG_FILE);
5493
+ if (existsSync13(configPath)) {
5055
5494
  try {
5056
- const raw = readFileSync9(configPath, "utf-8");
5495
+ const raw = readFileSync10(configPath, "utf-8");
5057
5496
  const config = JSON.parse(raw);
5058
5497
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
5059
5498
  const file = config.tokens.file;
5060
- return resolve14(process.cwd(), file);
5499
+ return resolve15(process.cwd(), file);
5061
5500
  }
5062
5501
  } catch {
5063
5502
  }
5064
5503
  }
5065
- return resolve14(process.cwd(), DEFAULT_TOKEN_FILE);
5504
+ return resolve15(process.cwd(), DEFAULT_TOKEN_FILE);
5066
5505
  }
5067
5506
  function createTokensExportCommand() {
5068
5507
  return new Command8("export").description("Export design tokens to a downstream format").requiredOption("--format <fmt>", `Output format: ${SUPPORTED_FORMATS.join(", ")}`).option("--file <path>", "Path to token file (overrides config)").option("--out <path>", "Write output to file instead of stdout").option("--prefix <prefix>", "CSS/SCSS: prefix for variable names (e.g. 'scope')").option("--selector <selector>", "CSS: custom root selector (default: ':root')").option(
@@ -5071,23 +5510,33 @@ function createTokensExportCommand() {
5071
5510
  ).action(
5072
5511
  (opts) => {
5073
5512
  if (!SUPPORTED_FORMATS.includes(opts.format)) {
5513
+ const FORMAT_ALIASES = {
5514
+ json: "flat-json",
5515
+ "json-flat": "flat-json",
5516
+ javascript: "ts",
5517
+ js: "ts",
5518
+ sass: "scss",
5519
+ tw: "tailwind"
5520
+ };
5521
+ const hint = FORMAT_ALIASES[opts.format.toLowerCase()];
5074
5522
  process.stderr.write(
5075
5523
  `Error: unsupported format "${opts.format}".
5076
5524
  Supported formats: ${SUPPORTED_FORMATS.join(", ")}
5077
- `
5525
+ ` + (hint ? `Did you mean "${hint}"?
5526
+ ` : "")
5078
5527
  );
5079
5528
  process.exit(1);
5080
5529
  }
5081
5530
  const format = opts.format;
5082
5531
  try {
5083
5532
  const filePath = resolveTokenFilePath2(opts.file);
5084
- if (!existsSync11(filePath)) {
5533
+ if (!existsSync13(filePath)) {
5085
5534
  throw new Error(
5086
5535
  `Token file not found at ${filePath}.
5087
5536
  Create a reactscope.tokens.json file or use --file to specify a path.`
5088
5537
  );
5089
5538
  }
5090
- const raw = readFileSync9(filePath, "utf-8");
5539
+ const raw = readFileSync10(filePath, "utf-8");
5091
5540
  const { tokens, rawFile } = parseTokenFileSync(raw);
5092
5541
  let themesMap;
5093
5542
  if (opts.theme !== void 0) {
@@ -5126,7 +5575,7 @@ Available themes: ${themeNames.join(", ")}`
5126
5575
  themes: themesMap
5127
5576
  });
5128
5577
  if (opts.out !== void 0) {
5129
- const outPath = resolve14(process.cwd(), opts.out);
5578
+ const outPath = resolve15(process.cwd(), opts.out);
5130
5579
  writeFileSync9(outPath, output, "utf-8");
5131
5580
  process.stderr.write(`Exported ${tokens.length} tokens to ${outPath}
5132
5581
  `);
@@ -5241,8 +5690,8 @@ ${formatImpactSummary(report)}
5241
5690
  }
5242
5691
 
5243
5692
  // src/tokens/preview.ts
5244
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync10 } from "fs";
5245
- import { resolve as resolve15 } from "path";
5693
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "fs";
5694
+ import { resolve as resolve16 } from "path";
5246
5695
  import { BrowserPool as BrowserPool6, SpriteSheetGenerator } from "@agent-scope/render";
5247
5696
  import { ComplianceEngine as ComplianceEngine6, ImpactAnalyzer as ImpactAnalyzer2, TokenResolver as TokenResolver7 } from "@agent-scope/tokens";
5248
5697
  var DEFAULT_STYLES_PATH3 = ".reactscope/compliance-styles.json";
@@ -5423,9 +5872,9 @@ function registerPreview(tokensCmd) {
5423
5872
  });
5424
5873
  const spriteResult = await generator.generate(matrixResult);
5425
5874
  const tokenLabel = tokenPath.replace(/\./g, "-");
5426
- const outputPath = opts.output ?? resolve15(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
5427
- const outputDir = resolve15(outputPath, "..");
5428
- mkdirSync5(outputDir, { recursive: true });
5875
+ const outputPath = opts.output ?? resolve16(process.cwd(), DEFAULT_OUTPUT_DIR2, `preview-${tokenLabel}.png`);
5876
+ const outputDir = resolve16(outputPath, "..");
5877
+ mkdirSync6(outputDir, { recursive: true });
5429
5878
  writeFileSync10(outputPath, spriteResult.png);
5430
5879
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY();
5431
5880
  if (useJson) {
@@ -5485,30 +5934,30 @@ function buildTable2(headers, rows) {
5485
5934
  }
5486
5935
  function resolveTokenFilePath(fileFlag) {
5487
5936
  if (fileFlag !== void 0) {
5488
- return resolve16(process.cwd(), fileFlag);
5937
+ return resolve17(process.cwd(), fileFlag);
5489
5938
  }
5490
- const configPath = resolve16(process.cwd(), CONFIG_FILE2);
5491
- if (existsSync12(configPath)) {
5939
+ const configPath = resolve17(process.cwd(), CONFIG_FILE2);
5940
+ if (existsSync14(configPath)) {
5492
5941
  try {
5493
- const raw = readFileSync10(configPath, "utf-8");
5942
+ const raw = readFileSync11(configPath, "utf-8");
5494
5943
  const config = JSON.parse(raw);
5495
5944
  if (typeof config === "object" && config !== null && "tokens" in config && typeof config.tokens === "object" && config.tokens !== null && typeof config.tokens?.file === "string") {
5496
5945
  const file = config.tokens.file;
5497
- return resolve16(process.cwd(), file);
5946
+ return resolve17(process.cwd(), file);
5498
5947
  }
5499
5948
  } catch {
5500
5949
  }
5501
5950
  }
5502
- return resolve16(process.cwd(), DEFAULT_TOKEN_FILE2);
5951
+ return resolve17(process.cwd(), DEFAULT_TOKEN_FILE2);
5503
5952
  }
5504
5953
  function loadTokens(absPath) {
5505
- if (!existsSync12(absPath)) {
5954
+ if (!existsSync14(absPath)) {
5506
5955
  throw new Error(
5507
5956
  `Token file not found at ${absPath}.
5508
5957
  Create a reactscope.tokens.json file or use --file to specify a path.`
5509
5958
  );
5510
5959
  }
5511
- const raw = readFileSync10(absPath, "utf-8");
5960
+ const raw = readFileSync11(absPath, "utf-8");
5512
5961
  return parseTokenFileSync2(raw);
5513
5962
  }
5514
5963
  function getRawValue(node, segments) {
@@ -5722,13 +6171,13 @@ function registerValidate(tokensCmd) {
5722
6171
  ).option("--file <path>", "Path to token file (overrides config)").option("--format <fmt>", "Output format: json or text (default: auto-detect)").action((opts) => {
5723
6172
  try {
5724
6173
  const filePath = resolveTokenFilePath(opts.file);
5725
- if (!existsSync12(filePath)) {
6174
+ if (!existsSync14(filePath)) {
5726
6175
  throw new Error(
5727
6176
  `Token file not found at ${filePath}.
5728
6177
  Create a reactscope.tokens.json file or use --file to specify a path.`
5729
6178
  );
5730
6179
  }
5731
- const raw = readFileSync10(filePath, "utf-8");
6180
+ const raw = readFileSync11(filePath, "utf-8");
5732
6181
  const useJson = opts.format === "json" || opts.format !== "text" && !isTTY2();
5733
6182
  const errors = [];
5734
6183
  let parsed;
@@ -5886,7 +6335,7 @@ function createProgram(options = {}) {
5886
6335
  }
5887
6336
  );
5888
6337
  program2.command("generate").description("Generate a Playwright test from a Scope trace file").argument("<trace>", "Path to a serialized Scope trace (.json)").option("-o, --output <path>", "Output file path", "scope.spec.ts").option("-d, --description <text>", "Test description").action((tracePath, opts) => {
5889
- const raw = readFileSync11(tracePath, "utf-8");
6338
+ const raw = readFileSync12(tracePath, "utf-8");
5890
6339
  const trace = loadTrace(raw);
5891
6340
  const source = generateTest(trace, {
5892
6341
  description: opts.description,