@tendrilapp/cli 0.1.44 → 0.1.45

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/tendril.js CHANGED
@@ -1980,8 +1980,8 @@ var init_src = __esm({
1980
1980
  function variableNameToPath(name) {
1981
1981
  return name.split("/").map(canonicalCssIdentPart).filter((part) => part.length > 0);
1982
1982
  }
1983
- function tokenPathToCssVar(path56) {
1984
- return `--${path56.join("-")}`;
1983
+ function tokenPathToCssVar(path58) {
1984
+ return `--${path58.join("-")}`;
1985
1985
  }
1986
1986
  function toDtcgToken(variable, defaultMode) {
1987
1987
  const modes = Object.keys(variable.valuesByMode);
@@ -2025,11 +2025,11 @@ function toDtcgToken(variable, defaultMode) {
2025
2025
  }
2026
2026
  function mapVariablesToDtcg(variables, defaultMode = "light") {
2027
2027
  const entries = variables.map((variable) => {
2028
- const path56 = variableNameToPath(variable.name);
2029
- if (path56.length === 0) {
2028
+ const path58 = variableNameToPath(variable.name);
2029
+ if (path58.length === 0) {
2030
2030
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
2031
2031
  }
2032
- return { variable, path: path56 };
2032
+ return { variable, path: path58 };
2033
2033
  });
2034
2034
  const groupPrefixes = /* @__PURE__ */ new Set();
2035
2035
  for (const e of entries) {
@@ -2050,21 +2050,21 @@ function mapVariablesToDtcg(variables, defaultMode = "light") {
2050
2050
  }
2051
2051
  const tokens = {};
2052
2052
  const flat = [];
2053
- for (const { variable, path: path56 } of entries) {
2053
+ for (const { variable, path: path58 } of entries) {
2054
2054
  const token = toDtcgToken(variable, defaultMode);
2055
2055
  let group = tokens;
2056
- for (const segment of path56.slice(0, -1)) {
2056
+ for (const segment of path58.slice(0, -1)) {
2057
2057
  const existing = group[segment];
2058
2058
  group = existing ?? (group[segment] = {});
2059
2059
  }
2060
- const leaf = path56[path56.length - 1];
2060
+ const leaf = path58[path58.length - 1];
2061
2061
  if (group[leaf] !== void 0) {
2062
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path56.join(".")}" (variable ${variable.id})`);
2062
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path58.join(".")}" (variable ${variable.id})`);
2063
2063
  }
2064
2064
  group[leaf] = token;
2065
2065
  flat.push({
2066
- path: path56.join("."),
2067
- cssVar: tokenPathToCssVar(path56),
2066
+ path: path58.join("."),
2067
+ cssVar: tokenPathToCssVar(path58),
2068
2068
  type: token.$type,
2069
2069
  value: token.$value
2070
2070
  });
@@ -2253,9 +2253,9 @@ function boundId(value) {
2253
2253
  return isObject(value) && typeof value["boundVariableId"] === "string" ? value["boundVariableId"] : void 0;
2254
2254
  }
2255
2255
  function resolveBinding(ctx, id) {
2256
- const path56 = ctx.pathById.get(id);
2257
- if (path56 === void 0) ctx.unresolved.add(id);
2258
- return path56;
2256
+ const path58 = ctx.pathById.get(id);
2257
+ if (path58 === void 0) ctx.unresolved.add(id);
2258
+ return path58;
2259
2259
  }
2260
2260
  function parseVariantProps(name) {
2261
2261
  if (!name.includes("=")) return void 0;
@@ -2290,8 +2290,8 @@ function walk(ctx, raw) {
2290
2290
  if (!isObject(paint) || paint["visible"] === false) continue;
2291
2291
  const id = boundId(paint);
2292
2292
  if (id !== void 0) {
2293
- const path56 = resolveBinding(ctx, id);
2294
- if (path56 !== void 0) tokens.add(path56);
2293
+ const path58 = resolveBinding(ctx, id);
2294
+ if (path58 !== void 0) tokens.add(path58);
2295
2295
  } else if (typeof paint["color"] === "string") {
2296
2296
  ctx.hardcoded.push({ node: name, property, value: paint["color"] });
2297
2297
  }
@@ -2299,8 +2299,8 @@ function walk(ctx, raw) {
2299
2299
  }
2300
2300
  const radiusId = boundId(raw["cornerRadius"]);
2301
2301
  if (radiusId !== void 0) {
2302
- const path56 = resolveBinding(ctx, radiusId);
2303
- if (path56 !== void 0) tokens.add(path56);
2302
+ const path58 = resolveBinding(ctx, radiusId);
2303
+ if (path58 !== void 0) tokens.add(path58);
2304
2304
  } else if (typeof raw["cornerRadius"] === "number" && raw["cornerRadius"] !== 0) {
2305
2305
  ctx.hardcoded.push({ node: name, property: "border-radius", value: `${raw["cornerRadius"]}px` });
2306
2306
  }
@@ -2310,10 +2310,10 @@ function walk(ctx, raw) {
2310
2310
  layout = { mode: layoutMode === "HORIZONTAL" ? "flex-row" : "flex-column" };
2311
2311
  const gapId = boundId(raw["itemSpacing"]);
2312
2312
  if (gapId !== void 0) {
2313
- const path56 = resolveBinding(ctx, gapId);
2314
- if (path56 !== void 0) {
2315
- layout.gap = path56;
2316
- tokens.add(path56);
2313
+ const path58 = resolveBinding(ctx, gapId);
2314
+ if (path58 !== void 0) {
2315
+ layout.gap = path58;
2316
+ tokens.add(path58);
2317
2317
  }
2318
2318
  } else if (typeof raw["itemSpacing"] === "number" && raw["itemSpacing"] !== 0) {
2319
2319
  ctx.hardcoded.push({ node: name, property: "gap", value: `${raw["itemSpacing"]}px` });
@@ -2322,10 +2322,10 @@ function walk(ctx, raw) {
2322
2322
  for (const field of PADDING_FIELDS) {
2323
2323
  const id = boundId(raw[field]);
2324
2324
  if (id !== void 0) {
2325
- const path56 = resolveBinding(ctx, id);
2326
- if (path56 !== void 0) {
2327
- paddingPaths.push(path56);
2328
- tokens.add(path56);
2325
+ const path58 = resolveBinding(ctx, id);
2326
+ if (path58 !== void 0) {
2327
+ paddingPaths.push(path58);
2328
+ tokens.add(path58);
2329
2329
  }
2330
2330
  } else if (typeof raw[field] === "number" && raw[field] !== 0) {
2331
2331
  ctx.hardcoded.push({ node: name, property: field, value: `${raw[field]}px` });
@@ -6239,43 +6239,43 @@ function classifyBundleSurface(files, opts) {
6239
6239
  const excluded = [];
6240
6240
  const unknown = [];
6241
6241
  for (const raw of files) {
6242
- const path56 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6243
- const inEvidence = path56.startsWith(`${EVIDENCE_DIR}/`);
6244
- if (path56.startsWith("fonts/")) {
6245
- const fname = path56.slice("fonts/".length);
6242
+ const path58 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6243
+ const inEvidence = path58.startsWith(`${EVIDENCE_DIR}/`);
6244
+ if (path58.startsWith("fonts/")) {
6245
+ const fname = path58.slice("fonts/".length);
6246
6246
  if (!fname.includes("/") && (/\.(woff2?|ttf|otf)$/i.test(fname) || /^(NOTICE|LICENSE|LICENCE)[^/]*\.txt$/i.test(fname))) {
6247
- excluded.push({ path: path56, reason: "font payload \u2014 not published (fonts policy pending); faces are sha-pinned in component.json requiredFonts" });
6247
+ excluded.push({ path: path58, reason: "font payload \u2014 not published (fonts policy pending); faces are sha-pinned in component.json requiredFonts" });
6248
6248
  continue;
6249
6249
  }
6250
- unknown.push(path56);
6250
+ unknown.push(path58);
6251
6251
  continue;
6252
6252
  }
6253
- const name = inEvidence ? path56.slice(EVIDENCE_DIR.length + 1) : path56;
6253
+ const name = inEvidence ? path58.slice(EVIDENCE_DIR.length + 1) : path58;
6254
6254
  if (name.includes("/")) {
6255
- unknown.push(path56);
6255
+ unknown.push(path58);
6256
6256
  continue;
6257
6257
  }
6258
6258
  if (inEvidence) {
6259
- if (name === "verify-report.json") published.push({ path: path56, role: "verify-report" });
6260
- else if (name === "diff-legend.txt") published.push({ path: path56, role: "diff-legend" });
6261
- else if (name === "inspect.html") published.push({ path: path56, role: "inspect-sheet" });
6262
- else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path56, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
6259
+ if (name === "verify-report.json") published.push({ path: path58, role: "verify-report" });
6260
+ else if (name === "diff-legend.txt") published.push({ path: path58, role: "diff-legend" });
6261
+ else if (name === "inspect.html") published.push({ path: path58, role: "inspect-sheet" });
6262
+ else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path58, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
6263
6263
  else {
6264
6264
  const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
6265
- if (hit !== void 0) published.push({ path: path56, role: hit.role });
6266
- else unknown.push(path56);
6265
+ if (hit !== void 0) published.push({ path: path58, role: hit.role });
6266
+ else unknown.push(path58);
6267
6267
  }
6268
6268
  continue;
6269
6269
  }
6270
- if (name === opts.entry) published.push({ path: path56, role: "entry" });
6271
- else if (name === "styles.css") published.push({ path: path56, role: "styles" });
6272
- else if (name === "tokens.css") published.push({ path: path56, role: "tokens" });
6273
- else if (name === "fonts.css") published.push({ path: path56, role: "fonts" });
6274
- else if (name === "component.json") published.push({ path: path56, role: "manifest" });
6270
+ if (name === opts.entry) published.push({ path: path58, role: "entry" });
6271
+ else if (name === "styles.css") published.push({ path: path58, role: "styles" });
6272
+ else if (name === "tokens.css") published.push({ path: path58, role: "tokens" });
6273
+ else if (name === "fonts.css") published.push({ path: path58, role: "fonts" });
6274
+ else if (name === "component.json") published.push({ path: path58, role: "manifest" });
6275
6275
  else {
6276
6276
  const skip = EXCLUDED.find((e) => e.test(name));
6277
- if (skip !== void 0) excluded.push({ path: path56, reason: skip.reason });
6278
- else unknown.push(path56);
6277
+ if (skip !== void 0) excluded.push({ path: path58, reason: skip.reason });
6278
+ else unknown.push(path58);
6279
6279
  }
6280
6280
  }
6281
6281
  const roles = new Set(published.map((p) => p.role));
@@ -6285,8 +6285,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
6285
6285
  const held = new Set(publishedPaths);
6286
6286
  const missing = /* @__PURE__ */ new Set();
6287
6287
  for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
6288
- const path56 = `${EVIDENCE_DIR}/${name}`;
6289
- if (!held.has(path56)) missing.add(path56);
6288
+ const path58 = `${EVIDENCE_DIR}/${name}`;
6289
+ if (!held.has(path58)) missing.add(path58);
6290
6290
  }
6291
6291
  return [...missing].sort();
6292
6292
  }
@@ -6394,10 +6394,10 @@ function readScoredFiles(report) {
6394
6394
  const entries = Object.entries(value);
6395
6395
  if (entries.length === 0) return void 0;
6396
6396
  const out = {};
6397
- for (const [path56, digest] of entries) {
6398
- if (path56 === "" || path56.startsWith("/") || path56.includes("..")) return void 0;
6397
+ for (const [path58, digest] of entries) {
6398
+ if (path58 === "" || path58.startsWith("/") || path58.includes("..")) return void 0;
6399
6399
  if (!isSetHash(digest)) return void 0;
6400
- out[path56] = digest;
6400
+ out[path58] = digest;
6401
6401
  }
6402
6402
  return out;
6403
6403
  }
@@ -6405,11 +6405,11 @@ function compareScoredFiles(recorded, actual) {
6405
6405
  const missing = [];
6406
6406
  const unscored = [];
6407
6407
  const changed = [];
6408
- for (const [path56, digest] of Object.entries(recorded)) {
6409
- if (!(path56 in actual)) missing.push(path56);
6410
- else if (actual[path56] !== digest) changed.push(path56);
6408
+ for (const [path58, digest] of Object.entries(recorded)) {
6409
+ if (!(path58 in actual)) missing.push(path58);
6410
+ else if (actual[path58] !== digest) changed.push(path58);
6411
6411
  }
6412
- for (const path56 of Object.keys(actual)) if (!(path56 in recorded)) unscored.push(path56);
6412
+ for (const path58 of Object.keys(actual)) if (!(path58 in recorded)) unscored.push(path58);
6413
6413
  return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
6414
6414
  }
6415
6415
  function scoredRecordingSetHash(report) {
@@ -8573,18 +8573,50 @@ var init_publish_client = __esm({
8573
8573
  }
8574
8574
  });
8575
8575
 
8576
+ // packages/cli/src/figma-token.ts
8577
+ import { existsSync as existsSync22, mkdirSync as mkdirSync5, readFileSync as readFileSync20, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
8578
+ import path28 from "node:path";
8579
+ function figmaTokenPath() {
8580
+ return path28.join(path28.dirname(sessionPath()), "figma-oauth.json");
8581
+ }
8582
+ function readFigmaTokens(file = figmaTokenPath()) {
8583
+ if (!existsSync22(file)) return void 0;
8584
+ try {
8585
+ const parsed = JSON.parse(readFileSync20(file, "utf8"));
8586
+ if (typeof parsed.origin !== "string" || typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string" || typeof parsed.tokenExpiresAt !== "string") {
8587
+ return void 0;
8588
+ }
8589
+ return parsed;
8590
+ } catch {
8591
+ return void 0;
8592
+ }
8593
+ }
8594
+ function writeFigmaTokens(tokens, file = figmaTokenPath()) {
8595
+ mkdirSync5(path28.dirname(file), { recursive: true });
8596
+ writeFileSync9(file, `${JSON.stringify(tokens, null, 2)}
8597
+ `, { mode: 384 });
8598
+ }
8599
+ var RENEW_WITHIN_MS;
8600
+ var init_figma_token = __esm({
8601
+ "packages/cli/src/figma-token.ts"() {
8602
+ "use strict";
8603
+ init_publish_client();
8604
+ RENEW_WITHIN_MS = 7 * 24 * 60 * 6e4;
8605
+ }
8606
+ });
8607
+
8576
8608
  // packages/cli/src/entitlement.ts
8577
- import { chmodSync as chmodSync2, existsSync as existsSync22, mkdirSync as mkdirSync5, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "node:fs";
8609
+ import { chmodSync as chmodSync2, existsSync as existsSync23, mkdirSync as mkdirSync6, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "node:fs";
8578
8610
  import crypto from "node:crypto";
8579
8611
  import os5 from "node:os";
8580
- import path28 from "node:path";
8612
+ import path29 from "node:path";
8581
8613
  function entitlementPath() {
8582
- return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path28.join(os5.homedir(), ".tendril", "entitlement.json");
8614
+ return process.env["TENDRIL_ENTITLEMENT_PATH"] ?? path29.join(os5.homedir(), ".tendril", "entitlement.json");
8583
8615
  }
8584
8616
  function readStoredEntitlement(file = entitlementPath()) {
8585
- if (!existsSync22(file)) return void 0;
8617
+ if (!existsSync23(file)) return void 0;
8586
8618
  try {
8587
- const parsed = JSON.parse(readFileSync20(file, "utf8"));
8619
+ const parsed = JSON.parse(readFileSync21(file, "utf8"));
8588
8620
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
8589
8621
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
8590
8622
  } catch {
@@ -8592,8 +8624,8 @@ function readStoredEntitlement(file = entitlementPath()) {
8592
8624
  }
8593
8625
  }
8594
8626
  function writeStoredEntitlement(stored, file = entitlementPath()) {
8595
- mkdirSync5(path28.dirname(file), { recursive: true });
8596
- writeFileSync9(file, `${JSON.stringify(stored, null, 2)}
8627
+ mkdirSync6(path29.dirname(file), { recursive: true });
8628
+ writeFileSync10(file, `${JSON.stringify(stored, null, 2)}
8597
8629
  `);
8598
8630
  chmodSync2(file, 384);
8599
8631
  }
@@ -8677,9 +8709,9 @@ var init_entitlement = __esm({
8677
8709
 
8678
8710
  // packages/cli/src/commands/doctor.ts
8679
8711
  import { spawnSync } from "node:child_process";
8680
- import { existsSync as existsSync23, readFileSync as readFileSync21, readdirSync as readdirSync8 } from "node:fs";
8712
+ import { existsSync as existsSync24, readFileSync as readFileSync22, readdirSync as readdirSync8 } from "node:fs";
8681
8713
  import os6 from "node:os";
8682
- import path29 from "node:path";
8714
+ import path30 from "node:path";
8683
8715
  function withDeadline(work, ms) {
8684
8716
  return Promise.race([
8685
8717
  work,
@@ -8739,17 +8771,17 @@ async function runDoctorChecks(options) {
8739
8771
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
8740
8772
  });
8741
8773
  }
8742
- const fontManifest = path29.join(fontCacheDir(), "manifest.json");
8774
+ const fontManifest = path30.join(fontCacheDir(), "manifest.json");
8743
8775
  checks.push(
8744
- existsSync23(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync21(fontManifest, "utf8")).length} faces)` } : {
8776
+ existsSync24(fontManifest) ? { name: "font-cache", ok: true, detail: `resolved font cache at ${fontCacheDir()} (${JSON.parse(readFileSync22(fontManifest, "utf8")).length} faces)` } : {
8745
8777
  name: "font-cache",
8746
8778
  ok: true,
8747
8779
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
8748
8780
  remediation: `Nothing to do now: \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` fetches exactly what a recording declares, and generate/verify name that command \u2014 with the set filled in \u2014 when they need it.`
8749
8781
  }
8750
8782
  );
8751
- const pluginRoot = path29.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8752
- if (existsSync23(pluginRoot)) {
8783
+ const pluginRoot = path30.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8784
+ if (existsSync24(pluginRoot)) {
8753
8785
  try {
8754
8786
  const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8755
8787
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -8786,12 +8818,15 @@ async function runDoctorChecks(options) {
8786
8818
  ent.ok ? ent.mode === "pre-launch" ? { name: "entitlement", ok: true, detail: "pre-launch build \u2014 entitlement gates present but unarmed (no public key shipped); record/generate run freely" } : { name: "entitlement", ok: true, detail: `active plan "${ent.claims.plan}" until ${new Date(ent.claims.exp).toISOString().slice(0, 10)}${ent.stale ? " (stale \u2014 renews at next opportunity)" : ""}` } : { name: "entitlement", ok: false, detail: ent.error, remediation: ent.remediation }
8787
8819
  );
8788
8820
  checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
8821
+ checks.push(figmaRestCheck());
8789
8822
  const figmaToken = resolveCredential("FIGMA_TOKEN");
8790
- checks.push({
8791
- name: "figma-pat",
8792
- ok: true,
8793
- detail: figmaToken ? "FIGMA_TOKEN configured but unused \u2014 recording runs over the Figma MCP transport" : "FIGMA_TOKEN not set \u2014 not needed; recording runs over the Figma MCP transport"
8794
- });
8823
+ if (figmaToken) {
8824
+ checks.push({
8825
+ name: "figma-pat",
8826
+ ok: true,
8827
+ detail: `FIGMA_TOKEN is set but unused by any command \u2014 Figma REST access is granted with ${tendrilCommand("figma-connect")} (OAuth), not tokens`
8828
+ });
8829
+ }
8795
8830
  return {
8796
8831
  ok: checks.filter((c) => c.name !== "figma-pat" && c.name !== "openrouter-key" && c.name !== "path-skew" && c.name !== "portal-session").every((c) => c.ok),
8797
8832
  checks
@@ -8832,6 +8867,25 @@ async function portalSessionCheck(fetchImpl) {
8832
8867
  ...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
8833
8868
  };
8834
8869
  }
8870
+ function figmaRestCheck() {
8871
+ const tokens = readFigmaTokens();
8872
+ if (tokens === void 0) {
8873
+ return {
8874
+ name: "figma-rest",
8875
+ ok: true,
8876
+ detail: `Figma is not connected for REST recording \u2014 large component sets then pay the MCP daily call quota. Run ${tendrilCommand("figma-connect")} and click Allow once (agents: the tendril_figma_connect tool).`
8877
+ };
8878
+ }
8879
+ const daysLeft = Math.floor((Date.parse(tokens.tokenExpiresAt) - Date.now()) / 864e5);
8880
+ if (daysLeft < 0) {
8881
+ return {
8882
+ name: "figma-rest",
8883
+ ok: true,
8884
+ detail: `the stored Figma credential lapsed ${String(-daysLeft)} day(s) ago \u2014 recording renews it automatically on next use, or reconnect with ${tendrilCommand("figma-connect")}`
8885
+ };
8886
+ }
8887
+ return { name: "figma-rest", ok: true, detail: `Figma connected via ${tokens.origin}, credential good ~${String(daysLeft)} more day(s) (renews itself)` };
8888
+ }
8835
8889
  function probeVersion(binary) {
8836
8890
  const windowsShim = /\.(cmd|bat)$/i.test(binary);
8837
8891
  const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
@@ -8906,6 +8960,7 @@ var init_doctor = __esm({
8906
8960
  init_invocation();
8907
8961
  init_output();
8908
8962
  init_publish_client();
8963
+ init_figma_token();
8909
8964
  init_entitlement();
8910
8965
  DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
8911
8966
  DOCTOR_DESCRIPTION = {
@@ -10021,8 +10076,8 @@ var init_engine_curated = __esm({
10021
10076
  });
10022
10077
 
10023
10078
  // packages/generate/src/loop.ts
10024
- import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync23, renameSync, writeFileSync as writeFileSync11 } from "node:fs";
10025
- import path32 from "node:path";
10079
+ import { existsSync as existsSync26, mkdirSync as mkdirSync8, readFileSync as readFileSync24, renameSync, writeFileSync as writeFileSync12 } from "node:fs";
10080
+ import path33 from "node:path";
10026
10081
  import { z as z13 } from "zod";
10027
10082
  function objective(scores, behaviors) {
10028
10083
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -10064,9 +10119,9 @@ ${absentLines.join("\n")}` : ""}
10064
10119
  Fix the FAIL configs (region coordinates are in the recorded screenshot's frame; ink<1 means recorded foreground pixels your render does not cover). Reading the pair: ink credits a recorded pixel when ANY render ink lands within 1px of it, so ink=1 proves coverage \u2014 not presence, shape, or colour. A wrong-shaped mark over the right region, or a wrong-coloured one that is still ink, scores 1.000; and a recorded mark within ~30 channel-sum of the backdrop (#f6f6f6 on white is 27) is not ink at all, so it can be absent at ink 1.000 with no MISSING line. With ink=1 AND low sim, coverage held while pixels differ, so start with the TWO causes below \u2014 but never read ink=1 as "nothing is missing": confirm every faint or small recorded mark (hairlines, dividers, low-contrast controls) exists in your render. GEOMETRY: wrong position/size/radius; the diff shows shifted edges and bands. WRONG TEXT WEIGHT OR FACE: the diff shows a uniform haze over glyph runs; CSS binds the font FAMILY before the weight, a later family in the stack is never consulted for a weight the first one lacks, and font-synthesis: none rules out faux-bold \u2014 so a stack led by a family the kit holds at one weight renders every other weight in that face, silently. Check the font stack against the kit's cached faces before concluding geometry is at fault. LOW ink with high sim means recorded marks are absent or painted invisibly. Do not regress PASS configs. ${mode === "files" ? "Update the files in your candidate directory and re-run the score." : "Reply with the complete corrected files in the same FILE format."}`;
10065
10120
  }
10066
10121
  function archivePriorRun(outDir) {
10067
- if (!existsSync25(path32.join(outDir, "run-log.json")) && !existsSync25(path32.join(outDir, "loop-state.json"))) return void 0;
10122
+ if (!existsSync26(path33.join(outDir, "run-log.json")) && !existsSync26(path33.join(outDir, "loop-state.json"))) return void 0;
10068
10123
  let n = 1;
10069
- while (existsSync25(`${outDir}-prev-${n}`)) n += 1;
10124
+ while (existsSync26(`${outDir}-prev-${n}`)) n += 1;
10070
10125
  renameSync(outDir, `${outDir}-prev-${n}`);
10071
10126
  return `${outDir}-prev-${n}`;
10072
10127
  }
@@ -10075,14 +10130,14 @@ async function runEngineLoop(opts) {
10075
10130
  const plateau = opts.plateau ?? 2;
10076
10131
  const progress = opts.onProgress ?? (() => {
10077
10132
  });
10078
- const statePath = path32.join(opts.outDir, "loop-state.json");
10079
- const resuming = opts.resume === true && existsSync25(statePath);
10133
+ const statePath = path33.join(opts.outDir, "loop-state.json");
10134
+ const resuming = opts.resume === true && existsSync26(statePath);
10080
10135
  if (!resuming) {
10081
10136
  const archived = archivePriorRun(opts.outDir);
10082
10137
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
10083
10138
  }
10084
- mkdirSync7(opts.outDir, { recursive: true });
10085
- const scratch = path32.join(opts.outDir, ".candidate");
10139
+ mkdirSync8(opts.outDir, { recursive: true });
10140
+ const scratch = path33.join(opts.outDir, ".candidate");
10086
10141
  let attempts = [];
10087
10142
  let log = [];
10088
10143
  let best;
@@ -10090,7 +10145,7 @@ async function runEngineLoop(opts) {
10090
10145
  let nonAccepted = 0;
10091
10146
  let stopReason = "max-iterations";
10092
10147
  if (resuming) {
10093
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync23(statePath, "utf8")));
10148
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync24(statePath, "utf8")));
10094
10149
  attempts = restored.attempts;
10095
10150
  log = restored.iterations;
10096
10151
  spentUsd = restored.spentUsd;
@@ -10105,12 +10160,12 @@ async function runEngineLoop(opts) {
10105
10160
  progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
10106
10161
  }
10107
10162
  const persist = () => {
10108
- writeFileSync11(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10163
+ writeFileSync12(statePath, `${JSON.stringify({ version: 1, spentUsd, attempts, iterations: log }, null, 1)}
10109
10164
  `);
10110
10165
  };
10111
10166
  const writeCandidate = (files) => {
10112
- mkdirSync7(scratch, { recursive: true });
10113
- for (const [name, content] of Object.entries(files)) writeFileSync11(path32.join(scratch, name), content);
10167
+ mkdirSync8(scratch, { recursive: true });
10168
+ for (const [name, content] of Object.entries(files)) writeFileSync12(path33.join(scratch, name), content);
10114
10169
  };
10115
10170
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
10116
10171
  writeCandidate(candidate.files);
@@ -10168,8 +10223,8 @@ async function runEngineLoop(opts) {
10168
10223
  const usd = candidate.usage?.usd ?? 0;
10169
10224
  spentUsd += usd;
10170
10225
  if (candidate.raw !== void 0) {
10171
- mkdirSync7(path32.join(opts.outDir, "responses"), { recursive: true });
10172
- writeFileSync11(path32.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10226
+ mkdirSync8(path33.join(opts.outDir, "responses"), { recursive: true });
10227
+ writeFileSync12(path33.join(opts.outDir, "responses", `iter-${iter}.md`), candidate.raw);
10173
10228
  }
10174
10229
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
10175
10230
  const finish = candidate.usage?.finishReason ?? "?";
@@ -10195,10 +10250,10 @@ async function runEngineLoop(opts) {
10195
10250
  }
10196
10251
  }
10197
10252
  }
10198
- if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync11(path32.join(opts.outDir, name), content);
10253
+ if (best !== void 0) for (const [name, content] of Object.entries(best.files)) writeFileSync12(path33.join(opts.outDir, name), content);
10199
10254
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
10200
- writeFileSync11(
10201
- path32.join(opts.outDir, "run-log.json"),
10255
+ writeFileSync12(
10256
+ path33.join(opts.outDir, "run-log.json"),
10202
10257
  `${JSON.stringify(
10203
10258
  {
10204
10259
  ...opts.meta,
@@ -10265,8 +10320,8 @@ var init_loop2 = __esm({
10265
10320
  });
10266
10321
 
10267
10322
  // packages/generate/src/brief.ts
10268
- import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
10269
- import path33 from "node:path";
10323
+ import { existsSync as existsSync27, readFileSync as readFileSync25 } from "node:fs";
10324
+ import path34 from "node:path";
10270
10325
  import { PNG as PNG3 } from "pngjs";
10271
10326
  function singleAxes2(name) {
10272
10327
  const parsed = parseVariantAxes(name);
@@ -10706,15 +10761,15 @@ function authorBehaviors(api, extras = {}) {
10706
10761
  };
10707
10762
  }
10708
10763
  function envelopeText(file) {
10709
- return envelopeFirstTextPart(JSON.parse(readFileSync24(file, "utf8")));
10764
+ return envelopeFirstTextPart(JSON.parse(readFileSync25(file, "utf8")));
10710
10765
  }
10711
10766
  function metadataText(file) {
10712
- return envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
10767
+ return envelopeTextContent(JSON.parse(readFileSync25(file, "utf8")));
10713
10768
  }
10714
10769
  function dismissEvidence(setDir, repSlugs) {
10715
10770
  for (const slug of repSlugs) {
10716
- const f = path33.join(setDir, slug, "get_design_context.json");
10717
- if (!existsSync26(f)) continue;
10771
+ const f = path34.join(setDir, slug, "get_design_context.json");
10772
+ if (!existsSync27(f)) continue;
10718
10773
  const text = envelopeText(f);
10719
10774
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10720
10775
  if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
@@ -10741,9 +10796,9 @@ function dismissEvidence(setDir, repSlugs) {
10741
10796
  const glyphIsTheComponent = (() => {
10742
10797
  const slugToCheck = vis.visibleIn[0];
10743
10798
  if (slugToCheck === void 0) return false;
10744
- const metaFile = path33.join(setDir, slugToCheck, "get_metadata.json");
10799
+ const metaFile = path34.join(setDir, slugToCheck, "get_metadata.json");
10745
10800
  try {
10746
- const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync24(metaFile, "utf8"))));
10801
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync25(metaFile, "utf8"))));
10747
10802
  if (root.children.length !== 1) return false;
10748
10803
  const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10749
10804
  return contains(root.children[0]);
@@ -10763,10 +10818,10 @@ function dismissEvidence(setDir, repSlugs) {
10763
10818
  return void 0;
10764
10819
  }
10765
10820
  function recordedReferencePng(setDir, slug) {
10766
- const f = path33.join(setDir, slug, "get_screenshot.json");
10767
- if (!existsSync26(f)) return void 0;
10821
+ const f = path34.join(setDir, slug, "get_screenshot.json");
10822
+ if (!existsSync27(f)) return void 0;
10768
10823
  try {
10769
- const env = JSON.parse(readFileSync24(f, "utf8")).content.find((c) => c.type === "image");
10824
+ const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
10770
10825
  return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10771
10826
  } catch {
10772
10827
  return void 0;
@@ -10846,13 +10901,13 @@ function recordedFontNeeds(setDir, opts = {}) {
10846
10901
  }
10847
10902
  }
10848
10903
  const manifest = loadManifest(setDir);
10849
- const setDefs = path33.join(setDir, "get_variable_defs.json");
10850
- if (existsSync26(setDefs)) fromDefs(envelopeText(setDefs));
10904
+ const setDefs = path34.join(setDir, "get_variable_defs.json");
10905
+ if (existsSync27(setDefs)) fromDefs(envelopeText(setDefs));
10851
10906
  for (const rep of manifest.reps) {
10852
- const ctx = path33.join(setDir, rep.slug, "get_design_context.json");
10853
- if (existsSync26(ctx)) fromEmission(envelopeText(ctx));
10854
- const defs = path33.join(setDir, rep.slug, "get_variable_defs.json");
10855
- if (existsSync26(defs)) fromDefs(envelopeText(defs));
10907
+ const ctx = path34.join(setDir, rep.slug, "get_design_context.json");
10908
+ if (existsSync27(ctx)) fromEmission(envelopeText(ctx));
10909
+ const defs = path34.join(setDir, rep.slug, "get_variable_defs.json");
10910
+ if (existsSync27(defs)) fromDefs(envelopeText(defs));
10856
10911
  }
10857
10912
  return [...byFamily.entries()].map(([family, paired]) => {
10858
10913
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10863,10 +10918,10 @@ function symbolFontGlyphCount(setDir, reps) {
10863
10918
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10864
10919
  const glyphs = /* @__PURE__ */ new Set();
10865
10920
  for (const rep of reps) {
10866
- const file = path33.join(setDir, rep, "get_metadata.json");
10867
- if (!existsSync26(file)) continue;
10921
+ const file = path34.join(setDir, rep, "get_metadata.json");
10922
+ if (!existsSync27(file)) continue;
10868
10923
  try {
10869
- const text = JSON.parse(readFileSync24(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10924
+ const text = JSON.parse(readFileSync25(file, "utf8")).content.map((c) => c.text ?? "").join("\n");
10870
10925
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10871
10926
  const name = decodeXmlEntities(m[1]).replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16))).replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(Number(d)));
10872
10927
  if (PUA.test(name)) glyphs.add(name);
@@ -10892,8 +10947,8 @@ function recordedTextSlots(setDir, repSlugs) {
10892
10947
  const propRep = [];
10893
10948
  const perRep = [];
10894
10949
  for (const slug of repSlugs) {
10895
- const f = path33.join(setDir, slug, "get_design_context.json");
10896
- if (!existsSync26(f)) continue;
10950
+ const f = path34.join(setDir, slug, "get_design_context.json");
10951
+ if (!existsSync27(f)) continue;
10897
10952
  const code = envelopeText(f);
10898
10953
  const props = /* @__PURE__ */ new Map();
10899
10954
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10919,8 +10974,8 @@ function recordedTextSlots(setDir, repSlugs) {
10919
10974
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10920
10975
  const valuesByAxis = /* @__PURE__ */ new Map();
10921
10976
  for (const slug of repSlugs) {
10922
- const metaFile = path33.join(setDir, slug, "get_metadata.json");
10923
- if (!existsSync26(metaFile)) continue;
10977
+ const metaFile = path34.join(setDir, slug, "get_metadata.json");
10978
+ if (!existsSync27(metaFile)) continue;
10924
10979
  const name = symbolName(metadataText(metaFile));
10925
10980
  if (name === void 0) continue;
10926
10981
  const values = /* @__PURE__ */ new Set();
@@ -11035,8 +11090,8 @@ function authorTaskFromSet(setDir, opts = {}) {
11035
11090
  const poses = [];
11036
11091
  const missing = [];
11037
11092
  for (const rep of manifest.reps) {
11038
- const metaFile = path33.join(setDir, rep.slug, "get_metadata.json");
11039
- if (!existsSync26(metaFile)) {
11093
+ const metaFile = path34.join(setDir, rep.slug, "get_metadata.json");
11094
+ if (!existsSync27(metaFile)) {
11040
11095
  missing.push(rep.slug);
11041
11096
  continue;
11042
11097
  }
@@ -11050,8 +11105,8 @@ function authorTaskFromSet(setDir, opts = {}) {
11050
11105
  if (missing.length > 0) {
11051
11106
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
11052
11107
  }
11053
- const setMeta = path33.join(setDir, "get_metadata.json");
11054
- const latticeNames = manifest.latticeNames ?? (existsSync26(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
11108
+ const setMeta = path34.join(setDir, "get_metadata.json");
11109
+ const latticeNames = manifest.latticeNames ?? (existsSync27(setMeta) ? [...metadataText(setMeta).matchAll(/name="([^"]*)"/g)].map((m) => decodeXmlEntities(m[1])) : void 0);
11055
11110
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
11056
11111
  const recordedFonts = recordedFontFamilies(setDir);
11057
11112
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -11252,17 +11307,17 @@ var init_brief = __esm({
11252
11307
  });
11253
11308
 
11254
11309
  // packages/generate/src/segments.ts
11255
- import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync10 } from "node:fs";
11256
- import path34 from "node:path";
11310
+ import { existsSync as existsSync28, readFileSync as readFileSync26, readdirSync as readdirSync10 } from "node:fs";
11311
+ import path35 from "node:path";
11257
11312
  function repText(set, rep, tool) {
11258
- const env = JSON.parse(readFileSync25(path34.join(set, rep, `${tool}.json`), "utf8"));
11313
+ const env = JSON.parse(readFileSync26(path35.join(set, rep, `${tool}.json`), "utf8"));
11259
11314
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
11260
11315
  }
11261
11316
  function refPngDims(set, rep) {
11262
- const f = path34.join(set, rep, "get_screenshot.json");
11263
- if (!existsSync27(f)) return void 0;
11317
+ const f = path35.join(set, rep, "get_screenshot.json");
11318
+ if (!existsSync28(f)) return void 0;
11264
11319
  try {
11265
- const env = JSON.parse(readFileSync25(f, "utf8")).content.find((c) => c.type === "image");
11320
+ const env = JSON.parse(readFileSync26(f, "utf8")).content.find((c) => c.type === "image");
11266
11321
  if (env?.data === void 0) return void 0;
11267
11322
  const buf = Buffer.from(env.data, "base64");
11268
11323
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
@@ -11328,20 +11383,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
11328
11383
  }
11329
11384
  function buildSegments(task, mode = "fenced") {
11330
11385
  const SET = task.set;
11331
- let defsRecorded = existsSync27(path34.join(SET, "get_variable_defs.json"));
11386
+ let defsRecorded = existsSync28(path35.join(SET, "get_variable_defs.json"));
11332
11387
  let rawDefs = {};
11333
- if (existsSync27(path34.join(SET, "get_variable_defs.json"))) {
11334
- const text = envelopeFirstTextPart(JSON.parse(readFileSync25(path34.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11388
+ if (existsSync28(path35.join(SET, "get_variable_defs.json"))) {
11389
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync26(path35.join(SET, "get_variable_defs.json"), "utf8"))) || "{}";
11335
11390
  try {
11336
11391
  rawDefs = JSON.parse(text);
11337
11392
  } catch {
11338
11393
  }
11339
11394
  } else {
11340
11395
  for (const cfg of task.configs) {
11341
- const f = path34.join(SET, cfg.rep, "get_variable_defs.json");
11342
- if (!existsSync27(f)) continue;
11396
+ const f = path35.join(SET, cfg.rep, "get_variable_defs.json");
11397
+ if (!existsSync28(f)) continue;
11343
11398
  defsRecorded = true;
11344
- const text = envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) || "{}";
11399
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) || "{}";
11345
11400
  try {
11346
11401
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
11347
11402
  } catch {
@@ -11349,8 +11404,8 @@ function buildSegments(task, mode = "fenced") {
11349
11404
  }
11350
11405
  }
11351
11406
  const emissionTexts = task.configs.map((cfg) => {
11352
- const f = path34.join(SET, cfg.rep, "get_design_context.json");
11353
- return existsSync27(f) ? envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) : "";
11407
+ const f = path35.join(SET, cfg.rep, "get_design_context.json");
11408
+ return existsSync28(f) ? envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) : "";
11354
11409
  });
11355
11410
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
11356
11411
  const defs = JSON.stringify(map, null, 1);
@@ -11368,9 +11423,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
11368
11423
  for (const cfg of task.configs) {
11369
11424
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
11370
11425
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
11371
- const assets = readdirSync10(path34.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11426
+ const assets = readdirSync10(path35.join(SET, cfg.rep)).filter((f) => f.startsWith("asset-") && f.endsWith(".svg")).map((f) => `asset ${f}:
11372
11427
  \`\`\`svg
11373
- ${readFileSync25(path34.join(SET, cfg.rep, f), "utf8")}
11428
+ ${readFileSync26(path35.join(SET, cfg.rep, f), "utf8")}
11374
11429
  \`\`\``).join("\n");
11375
11430
  const refNote = (() => {
11376
11431
  const dims = refPngDims(SET, cfg.rep);
@@ -11406,7 +11461,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
11406
11461
  } else {
11407
11462
  parts.push(`
11408
11463
  ## Output format
11409
- Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path34.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
11464
+ Write the COMPLETE files (${task.entry}, styles.css, optional tokens.css) into ONE candidate directory: \`tendril-out/${path35.basename(task.set)}-candidate/\` unless you were handed another path. Pass that same directory to \`tendril engine score\` every round and keep writing into it \u2014 the scorer stamps the bundle there, writes its evidence beside your files, and appends its score-history.jsonl lines there (one when a round starts, one when it scores); a fresh directory each round throws all of that away. Do not paste file contents into chat \u2014 the scorer reads the directory.`);
11410
11465
  }
11411
11466
  return parts.join("\n");
11412
11467
  }
@@ -11474,8 +11529,8 @@ var init_adapter = __esm({
11474
11529
 
11475
11530
  // packages/generate/src/bundle-emit.ts
11476
11531
  import { createHash as createHash6 } from "node:crypto";
11477
- import { copyFileSync, existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync26, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
11478
- import path35 from "node:path";
11532
+ import { copyFileSync, existsSync as existsSync29, mkdirSync as mkdirSync9, readFileSync as readFileSync27, readdirSync as readdirSync11, rmSync as rmSync5, writeFileSync as writeFileSync13 } from "node:fs";
11533
+ import path36 from "node:path";
11479
11534
  function pinFromConfigs(configs) {
11480
11535
  const domains = /* @__PURE__ */ new Map();
11481
11536
  const kinds = /* @__PURE__ */ new Map();
@@ -11544,9 +11599,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11544
11599
  const notices = [];
11545
11600
  const licenseTexts = /* @__PURE__ */ new Map();
11546
11601
  for (const face of faces) {
11547
- const src = path35.join(cacheDir, path35.basename(face.file));
11548
- const target = `./fonts/${path35.basename(face.file)}`;
11549
- const format = FONT_FORMATS[path35.extname(face.file).toLowerCase()] ?? "truetype";
11602
+ const src = path36.join(cacheDir, path36.basename(face.file));
11603
+ const target = `./fonts/${path36.basename(face.file)}`;
11604
+ const format = FONT_FORMATS[path36.extname(face.file).toLowerCase()] ?? "truetype";
11550
11605
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
11551
11606
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
11552
11607
  const license = normalizeFontLicense(face.license);
@@ -11584,14 +11639,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11584
11639
  `/* ${decl} */`
11585
11640
  );
11586
11641
  }
11587
- } else if (existsSync28(src) && createHash6("sha256").update(readFileSync26(src)).digest("hex") === face.sha256) {
11588
- mkdirSync8(path35.join(bundleDir, "fonts"), { recursive: true });
11589
- copyFileSync(src, path35.join(bundleDir, "fonts", path35.basename(face.file)));
11642
+ } else if (existsSync29(src) && createHash6("sha256").update(readFileSync27(src)).digest("hex") === face.sha256) {
11643
+ mkdirSync9(path36.join(bundleDir, "fonts"), { recursive: true });
11644
+ copyFileSync(src, path36.join(bundleDir, "fonts", path36.basename(face.file)));
11590
11645
  licenseTexts.set(terms.file, terms.text);
11591
11646
  const upstream = upstreamAttribution(face);
11592
11647
  notices.push(
11593
11648
  "",
11594
- `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path35.basename(face.file)}`,
11649
+ `${face.family.replace(/[\r\n]+/g, " ")} ${face.weight} \u2014 ./${path36.basename(face.file)}`,
11595
11650
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
11596
11651
  ` source: ${face.source}`,
11597
11652
  ` sha256: ${face.sha256}`,
@@ -11605,9 +11660,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11605
11660
  }
11606
11661
  if (lines.length === 0) return null;
11607
11662
  if (notices.length > 0) {
11608
- const fontsDir = path35.join(bundleDir, "fonts");
11609
- for (const [file, text] of licenseTexts) writeFileSync12(path35.join(fontsDir, file), text);
11610
- writeFileSync12(path35.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11663
+ const fontsDir = path36.join(bundleDir, "fonts");
11664
+ for (const [file, text] of licenseTexts) writeFileSync13(path36.join(fontsDir, file), text);
11665
+ writeFileSync13(path36.join(fontsDir, "NOTICE.txt"), `${[NOTICE_PREAMBLE, ...notices].join("\n")}
11611
11666
  `);
11612
11667
  header.push(
11613
11668
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -11619,10 +11674,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11619
11674
  `;
11620
11675
  }
11621
11676
  function countLatticeSymbols(setDir) {
11622
- const manifestFile = path35.join(setDir, "recording-set.json");
11623
- if (existsSync28(manifestFile)) {
11677
+ const manifestFile = path36.join(setDir, "recording-set.json");
11678
+ if (existsSync29(manifestFile)) {
11624
11679
  try {
11625
- const stored = JSON.parse(readFileSync26(manifestFile, "utf8"));
11680
+ const stored = JSON.parse(readFileSync27(manifestFile, "utf8"));
11626
11681
  if (stored.variantScope !== "component-set") return null;
11627
11682
  const lattice = stored.latticeNames;
11628
11683
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -11630,13 +11685,13 @@ function countLatticeSymbols(setDir) {
11630
11685
  }
11631
11686
  }
11632
11687
  const files = [
11633
- path35.join(setDir, "get_metadata.json"),
11634
- ...existsSync28(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path35.join(setDir, f)) : []
11635
- ].filter((f) => existsSync28(f));
11688
+ path36.join(setDir, "get_metadata.json"),
11689
+ ...existsSync29(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path36.join(setDir, f)) : []
11690
+ ].filter((f) => existsSync29(f));
11636
11691
  if (files.length === 0) return null;
11637
11692
  let count = 0;
11638
11693
  for (const f of files) {
11639
- const text = envelopeTextContent(JSON.parse(readFileSync26(f, "utf8")));
11694
+ const text = envelopeTextContent(JSON.parse(readFileSync27(f, "utf8")));
11640
11695
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
11641
11696
  }
11642
11697
  return count > 0 ? count : null;
@@ -11644,21 +11699,21 @@ function countLatticeSymbols(setDir) {
11644
11699
  function recordingSetHash(setDir, configs) {
11645
11700
  const relPaths = [];
11646
11701
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
11647
- if (existsSync28(path35.join(setDir, name))) relPaths.push(name);
11702
+ if (existsSync29(path36.join(setDir, name))) relPaths.push(name);
11648
11703
  }
11649
11704
  for (const cfg of configs) {
11650
11705
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
11651
- if (existsSync28(path35.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11706
+ if (existsSync29(path36.join(setDir, cfg.rep, f))) relPaths.push(`${cfg.rep}/${f}`);
11652
11707
  }
11653
- if (existsSync28(path35.join(setDir, cfg.rep))) {
11654
- for (const asset of readdirSync11(path35.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11708
+ if (existsSync29(path36.join(setDir, cfg.rep))) {
11709
+ for (const asset of readdirSync11(path36.join(setDir, cfg.rep)).filter((f) => f.startsWith("asset-"))) {
11655
11710
  relPaths.push(`${cfg.rep}/${asset}`);
11656
11711
  }
11657
11712
  }
11658
11713
  }
11659
11714
  return hashRecordingSet(
11660
11715
  relPaths,
11661
- (p) => new Uint8Array(readFileSync26(path35.join(setDir, p))),
11716
+ (p) => new Uint8Array(readFileSync27(path36.join(setDir, p))),
11662
11717
  (chunks) => {
11663
11718
  const h = createHash6("sha256");
11664
11719
  for (const c of chunks) h.update(c);
@@ -11699,8 +11754,8 @@ function emitBundleV1(opts) {
11699
11754
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
11700
11755
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
11701
11756
  const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
11702
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path35.join(opts.bundleDir, f)).filter((f) => existsSync28(f));
11703
- const families = cssFontFamilies(cssFiles.map((f) => readFileSync26(f, "utf8")).join("\n"));
11757
+ const cssFiles = ["styles.css", "tokens.css"].map((f) => path36.join(opts.bundleDir, f)).filter((f) => existsSync29(f));
11758
+ const families = cssFontFamilies(cssFiles.map((f) => readFileSync27(f, "utf8")).join("\n"));
11704
11759
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
11705
11760
  family: f.family,
11706
11761
  weight: f.weight,
@@ -11729,7 +11784,7 @@ function emitBundleV1(opts) {
11729
11784
  // resolvable via verify's --set override).
11730
11785
  path: (() => {
11731
11786
  const base = process.env["INIT_CWD"] ?? process.cwd();
11732
- const rel = path35.relative(base, opts.task.set);
11787
+ const rel = path36.relative(base, opts.task.set);
11733
11788
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
11734
11789
  })(),
11735
11790
  component: opts.componentName,
@@ -11766,25 +11821,25 @@ function emitBundleV1(opts) {
11766
11821
  })
11767
11822
  };
11768
11823
  const written = [];
11769
- const manifestPath2 = path35.join(opts.bundleDir, "component.json");
11770
- writeFileSync12(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11824
+ const manifestPath2 = path36.join(opts.bundleDir, "component.json");
11825
+ writeFileSync13(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11771
11826
  `);
11772
11827
  written.push(manifestPath2);
11773
- const stylesPath = path35.join(opts.bundleDir, "styles.css");
11774
- if (existsSync28(stylesPath)) {
11828
+ const stylesPath = path36.join(opts.bundleDir, "styles.css");
11829
+ if (existsSync29(stylesPath)) {
11775
11830
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
11776
- const current = readFileSync26(stylesPath, "utf8");
11831
+ const current = readFileSync27(stylesPath, "utf8");
11777
11832
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
11778
- writeFileSync12(stylesPath, `${comment}
11833
+ writeFileSync13(stylesPath, `${comment}
11779
11834
  ${stripped}`);
11780
11835
  written.push(stylesPath);
11781
11836
  }
11782
- const fontsCssPath = path35.join(opts.bundleDir, "fonts.css");
11783
- rmSync4(path35.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11784
- rmSync4(fontsCssPath, { force: true });
11837
+ const fontsCssPath = path36.join(opts.bundleDir, "fonts.css");
11838
+ rmSync5(path36.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11839
+ rmSync5(fontsCssPath, { force: true });
11785
11840
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
11786
11841
  if (fontsCss !== null) {
11787
- writeFileSync12(fontsCssPath, fontsCss);
11842
+ writeFileSync13(fontsCssPath, fontsCss);
11788
11843
  written.push(fontsCssPath);
11789
11844
  }
11790
11845
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
@@ -12213,8 +12268,8 @@ DEALINGS IN THE FONT SOFTWARE.
12213
12268
 
12214
12269
  // packages/generate/src/compose-pins.ts
12215
12270
  import { createHash as createHash7 } from "node:crypto";
12216
- import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12217
- import path36 from "node:path";
12271
+ import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12272
+ import path37 from "node:path";
12218
12273
  function bundleDirs(roots, depth = 4) {
12219
12274
  const found = [];
12220
12275
  const seen = /* @__PURE__ */ new Set();
@@ -12223,11 +12278,11 @@ function bundleDirs(roots, depth = 4) {
12223
12278
  try {
12224
12279
  key = realpathSync3(dir);
12225
12280
  } catch {
12226
- key = path36.resolve(dir);
12281
+ key = path37.resolve(dir);
12227
12282
  }
12228
12283
  if (seen.has(key)) return;
12229
12284
  seen.add(key);
12230
- if (existsSync29(path36.join(dir, "component.json"))) {
12285
+ if (existsSync30(path37.join(dir, "component.json"))) {
12231
12286
  found.push(key);
12232
12287
  return;
12233
12288
  }
@@ -12240,14 +12295,14 @@ function bundleDirs(roots, depth = 4) {
12240
12295
  }
12241
12296
  for (const e of entries) {
12242
12297
  if (e === "node_modules" || e.startsWith(".")) continue;
12243
- const full = path36.join(dir, e);
12298
+ const full = path37.join(dir, e);
12244
12299
  try {
12245
12300
  if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
12246
12301
  } catch {
12247
12302
  }
12248
12303
  }
12249
12304
  };
12250
- for (const r of roots) walk2(path36.resolve(r), depth);
12305
+ for (const r of roots) walk2(path37.resolve(r), depth);
12251
12306
  return found;
12252
12307
  }
12253
12308
  function composedPins(hostSet, libraryRoots) {
@@ -12266,7 +12321,7 @@ function composedPins(hostSet, libraryRoots) {
12266
12321
  let pinned = false;
12267
12322
  const failures = [];
12268
12323
  for (const rel of partnerRels) {
12269
- const partnerSet = path36.resolve(hostSet, rel);
12324
+ const partnerSet = path37.resolve(hostSet, rel);
12270
12325
  let partnerTask;
12271
12326
  let partnerManifest;
12272
12327
  try {
@@ -12295,7 +12350,7 @@ function composedPins(hostSet, libraryRoots) {
12295
12350
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
12296
12351
  const matches = candidates.filter((dir) => {
12297
12352
  try {
12298
- const parsed = readBundleManifest(readFileSync27(path36.join(dir, "component.json"), "utf8"));
12353
+ const parsed = readBundleManifest(readFileSync28(path37.join(dir, "component.json"), "utf8"));
12299
12354
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
12300
12355
  } catch {
12301
12356
  return false;
@@ -12308,13 +12363,13 @@ function composedPins(hostSet, libraryRoots) {
12308
12363
  continue;
12309
12364
  }
12310
12365
  if (matches.length > 1) {
12311
- failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path36.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
12366
+ failures.push(`${rel}: ${matches.length} bundles join the same recording hash (${matches.map((m) => path37.basename(m)).join(", ")}) \u2014 ambiguous; remove or point --library away from the duplicates`);
12312
12367
  continue;
12313
12368
  }
12314
12369
  const bundleDir = matches[0];
12315
12370
  let manifest;
12316
12371
  try {
12317
- manifest = readBundleManifest(readFileSync27(path36.join(bundleDir, "component.json"), "utf8")).manifest;
12372
+ manifest = readBundleManifest(readFileSync28(path37.join(bundleDir, "component.json"), "utf8")).manifest;
12318
12373
  } catch {
12319
12374
  manifest = void 0;
12320
12375
  }
@@ -12331,8 +12386,8 @@ function composedPins(hostSet, libraryRoots) {
12331
12386
  const moduleFiles = [];
12332
12387
  let fileIssue;
12333
12388
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
12334
- const file = path36.join(bundleDir, name);
12335
- if (!existsSync29(file)) {
12389
+ const file = path37.join(bundleDir, name);
12390
+ if (!existsSync30(file)) {
12336
12391
  if (name === manifest.entry || name === "styles.css") {
12337
12392
  fileIssue = `${rel}: partner bundle is missing ${name}`;
12338
12393
  break;
@@ -12341,7 +12396,7 @@ function composedPins(hostSet, libraryRoots) {
12341
12396
  }
12342
12397
  let bytes;
12343
12398
  try {
12344
- bytes = readFileSync27(file);
12399
+ bytes = readFileSync28(file);
12345
12400
  } catch {
12346
12401
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
12347
12402
  break;
@@ -12413,14 +12468,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
12413
12468
  const checks = [];
12414
12469
  let entrySource = "";
12415
12470
  try {
12416
- entrySource = readFileSync27(path36.join(candidateDir, hostEntry), "utf8");
12471
+ entrySource = readFileSync28(path37.join(candidateDir, hostEntry), "utf8");
12417
12472
  } catch {
12418
12473
  }
12419
- const candidateRoot = path36.resolve(candidateDir);
12474
+ const candidateRoot = path37.resolve(candidateDir);
12420
12475
  for (const pin of pins) {
12421
12476
  const dir = composedModuleDir(pin.partnerName);
12422
- const resolvedDir = path36.resolve(candidateDir, dir);
12423
- if (!resolvedDir.startsWith(candidateRoot + path36.sep)) {
12477
+ const resolvedDir = path37.resolve(candidateDir, dir);
12478
+ if (!resolvedDir.startsWith(candidateRoot + path37.sep)) {
12424
12479
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12425
12480
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12426
12481
  continue;
@@ -12431,12 +12486,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
12431
12486
  wrong.push(`${f.name} refused (not a plain path segment)`);
12432
12487
  continue;
12433
12488
  }
12434
- const target = path36.join(candidateDir, dir, f.name);
12435
- if (!existsSync29(target)) {
12489
+ const target = path37.join(candidateDir, dir, f.name);
12490
+ if (!existsSync30(target)) {
12436
12491
  wrong.push(`${f.name} missing`);
12437
12492
  continue;
12438
12493
  }
12439
- const sha = createHash7("sha256").update(readFileSync27(target)).digest("hex");
12494
+ const sha = createHash7("sha256").update(readFileSync28(target)).digest("hex");
12440
12495
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
12441
12496
  }
12442
12497
  checks.push({
@@ -12461,10 +12516,10 @@ function rootClassesFor(emission, nodeId) {
12461
12516
  }
12462
12517
  function regionOverrides(hostSet, partnerSet, instances) {
12463
12518
  const read = (setDir, rep) => {
12464
- const f = path36.join(setDir, rep, "get_design_context.json");
12465
- if (!existsSync29(f)) return void 0;
12519
+ const f = path37.join(setDir, rep, "get_design_context.json");
12520
+ if (!existsSync30(f)) return void 0;
12466
12521
  try {
12467
- return envelopeFirstTextPart(JSON.parse(readFileSync27(f, "utf8")));
12522
+ return envelopeFirstTextPart(JSON.parse(readFileSync28(f, "utf8")));
12468
12523
  } catch {
12469
12524
  return void 0;
12470
12525
  }
@@ -12494,7 +12549,7 @@ var init_compose_pins = __esm({
12494
12549
  init_src4();
12495
12550
  init_brief();
12496
12551
  init_bundle_emit();
12497
- composedModuleDir = (partnerName) => path36.posix.join("composed", partnerName);
12552
+ composedModuleDir = (partnerName) => path37.posix.join("composed", partnerName);
12498
12553
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
12499
12554
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
12500
12555
  CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
@@ -12502,8 +12557,8 @@ var init_compose_pins = __esm({
12502
12557
  });
12503
12558
 
12504
12559
  // packages/generate/src/motion.ts
12505
- import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12506
- import path37 from "node:path";
12560
+ import { existsSync as existsSync31, readFileSync as readFileSync29, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12561
+ import path38 from "node:path";
12507
12562
  function springProgress(u, bounce) {
12508
12563
  const decay = Math.log(100);
12509
12564
  if (bounce <= 0) {
@@ -12574,10 +12629,10 @@ function reportsNoMotion(text) {
12574
12629
  });
12575
12630
  }
12576
12631
  function motionTruthFor(setDir) {
12577
- const file = path37.join(setDir, "get_motion_context.json");
12578
- if (existsSync30(file) && usableEnvelope(file, "get_motion_context").ok) {
12632
+ const file = path38.join(setDir, "get_motion_context.json");
12633
+ if (existsSync31(file) && usableEnvelope(file, "get_motion_context").ok) {
12579
12634
  try {
12580
- const text = envelopeTextContent(JSON.parse(readFileSync28(file, "utf8")));
12635
+ const text = envelopeTextContent(JSON.parse(readFileSync29(file, "utf8")));
12581
12636
  if (text.trim() === "") return { state: "recorded-empty" };
12582
12637
  return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
12583
12638
  } catch {
@@ -12590,21 +12645,21 @@ function motionTruthFor(setDir) {
12590
12645
  }
12591
12646
  }
12592
12647
  function motionDisclosure(bundleDir, setDir) {
12593
- const sheets = ["styles.css", "tokens.css"].map((f) => path37.join(bundleDir, f));
12594
- const composedRoot = path37.join(bundleDir, "composed");
12648
+ const sheets = ["styles.css", "tokens.css"].map((f) => path38.join(bundleDir, f));
12649
+ const composedRoot = path38.join(bundleDir, "composed");
12595
12650
  try {
12596
12651
  for (const entry of readdirSync13(composedRoot).sort()) {
12597
- const dir = path37.join(composedRoot, entry);
12652
+ const dir = path38.join(composedRoot, entry);
12598
12653
  try {
12599
12654
  if (!statSync5(dir).isDirectory()) continue;
12600
12655
  } catch {
12601
12656
  continue;
12602
12657
  }
12603
- sheets.push(path37.join(dir, "styles.css"), path37.join(dir, "tokens.css"));
12658
+ sheets.push(path38.join(dir, "styles.css"), path38.join(dir, "tokens.css"));
12604
12659
  }
12605
12660
  } catch {
12606
12661
  }
12607
- const css = sheets.filter((f) => existsSync30(f)).map((f) => readFileSync28(f, "utf8")).join("\n");
12662
+ const css = sheets.filter((f) => existsSync31(f)).map((f) => readFileSync29(f, "utf8")).join("\n");
12608
12663
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
12609
12664
  return {
12610
12665
  present: true,
@@ -12971,12 +13026,12 @@ var init_components = __esm({
12971
13026
 
12972
13027
  // packages/generate/src/codebase/walk.ts
12973
13028
  import fs2 from "node:fs";
12974
- import path38 from "node:path";
13029
+ import path39 from "node:path";
12975
13030
  function resolvedPathIsExcluded(real, roots) {
12976
- if (isNeverRead(path38.basename(real))) return true;
13031
+ if (isNeverRead(path39.basename(real))) return true;
12977
13032
  for (const root of roots) {
12978
- if (real !== root && !real.startsWith(root + path38.sep)) continue;
12979
- for (const segment of path38.relative(root, real).split(path38.sep).slice(0, -1)) {
13033
+ if (real !== root && !real.startsWith(root + path39.sep)) continue;
13034
+ for (const segment of path39.relative(root, real).split(path39.sep).slice(0, -1)) {
12980
13035
  if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
12981
13036
  }
12982
13037
  }
@@ -12990,7 +13045,7 @@ function containedRealpath(abs, roots) {
12990
13045
  return null;
12991
13046
  }
12992
13047
  for (const root of roots) {
12993
- if (real === root || real.startsWith(root + path38.sep)) return real;
13048
+ if (real === root || real.startsWith(root + path39.sep)) return real;
12994
13049
  }
12995
13050
  return null;
12996
13051
  }
@@ -13023,7 +13078,7 @@ function walkRepo(roots, limits, accept) {
13023
13078
  continue;
13024
13079
  }
13025
13080
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
13026
- const abs = path38.join(frame.dir, entry.name);
13081
+ const abs = path39.join(frame.dir, entry.name);
13027
13082
  if (EXCLUDED_DIRS.has(entry.name)) continue;
13028
13083
  if (isNeverRead(entry.name)) continue;
13029
13084
  const real = containedRealpath(abs, realRoots);
@@ -13111,18 +13166,18 @@ var init_walk = __esm({
13111
13166
  /^\.netrc$/i
13112
13167
  ];
13113
13168
  isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
13114
- toRel = (root, abs) => path38.relative(root, abs).split(path38.sep).join(path38.posix.sep);
13169
+ toRel = (root, abs) => path39.relative(root, abs).split(path39.sep).join(path39.posix.sep);
13115
13170
  }
13116
13171
  });
13117
13172
 
13118
13173
  // packages/generate/src/codebase/scan.ts
13119
13174
  import crypto2 from "node:crypto";
13120
13175
  import fs3 from "node:fs";
13121
- import path39 from "node:path";
13176
+ import path40 from "node:path";
13122
13177
  import postcss3 from "postcss";
13123
13178
  function scanCodebase(options) {
13124
13179
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
13125
- const roots = options.roots.map((r) => path39.resolve(r));
13180
+ const roots = options.roots.map((r) => path40.resolve(r));
13126
13181
  const walk2 = walkRepo(
13127
13182
  roots,
13128
13183
  {
@@ -13142,7 +13197,7 @@ function scanCodebase(options) {
13142
13197
  let bytesRead = 0;
13143
13198
  let filesRead = 0;
13144
13199
  for (const file of walk2.files) {
13145
- const base = path39.posix.basename(file.rel);
13200
+ const base = path40.posix.basename(file.rel);
13146
13201
  configFiles.add(file.rel);
13147
13202
  if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
13148
13203
  const text = readTextFile(file.abs);
@@ -13158,7 +13213,7 @@ function scanCodebase(options) {
13158
13213
  }
13159
13214
  const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
13160
13215
  const components = scanComponents(componentFiles);
13161
- const packages = manifests.filter((m) => path39.posix.basename(m.rel) === "package.json");
13216
+ const packages = manifests.filter((m) => path40.posix.basename(m.rel) === "package.json");
13162
13217
  const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
13163
13218
  const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
13164
13219
  const disclosures = buildDisclosures(
@@ -13201,13 +13256,13 @@ function scanCodebase(options) {
13201
13256
  },
13202
13257
  components: {
13203
13258
  entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
13204
- fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path39.posix.basename(f.rel)))),
13259
+ fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path40.posix.basename(f.rel)))),
13205
13260
  directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
13206
13261
  exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
13207
13262
  classNameStyle,
13208
13263
  colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
13209
13264
  barrelFiles: componentFiles.filter(
13210
- (f) => /^index\.[tj]sx?$/.test(path39.posix.basename(f.rel)) && isReExportOnly(f.text)
13265
+ (f) => /^index\.[tj]sx?$/.test(path40.posix.basename(f.rel)) && isReExportOnly(f.text)
13211
13266
  ).length,
13212
13267
  refForwarding: {
13213
13268
  forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
@@ -13230,7 +13285,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13230
13285
  };
13231
13286
  const deps = /* @__PURE__ */ new Map();
13232
13287
  for (const manifest of manifests) {
13233
- if (path39.posix.basename(manifest.rel) !== "package.json") continue;
13288
+ if (path40.posix.basename(manifest.rel) !== "package.json") continue;
13234
13289
  try {
13235
13290
  const parsed = JSON.parse(manifest.text);
13236
13291
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -13242,7 +13297,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13242
13297
  }
13243
13298
  }
13244
13299
  for (const cfg of configFiles) {
13245
- const base = path39.posix.basename(cfg);
13300
+ const base = path40.posix.basename(cfg);
13246
13301
  if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
13247
13302
  if (base === "components.json") add("shadcn-style", "file", cfg);
13248
13303
  }
@@ -13300,8 +13355,8 @@ function collectClassNames(cssFiles) {
13300
13355
  return [...distinct].sort().map(classifyClassName);
13301
13356
  }
13302
13357
  function classifyDirectoryLayout(rel) {
13303
- const base = path39.posix.basename(rel).replace(/\.[^.]+$/, "");
13304
- const dir = path39.posix.basename(path39.posix.dirname(rel));
13358
+ const base = path40.posix.basename(rel).replace(/\.[^.]+$/, "");
13359
+ const dir = path40.posix.basename(path40.posix.dirname(rel));
13305
13360
  if (base === "index") return "component-dir";
13306
13361
  if (base === dir) return "component-dir";
13307
13362
  return "flat-file";
@@ -13325,7 +13380,7 @@ function detectFormatting(manifests, componentFiles) {
13325
13380
  (a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
13326
13381
  );
13327
13382
  for (const manifest of byDepth) {
13328
- const base = path39.posix.basename(manifest.rel);
13383
+ const base = path40.posix.basename(manifest.rel);
13329
13384
  if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
13330
13385
  try {
13331
13386
  const parsed = JSON.parse(manifest.text);
@@ -13343,7 +13398,7 @@ function detectFormatting(manifests, componentFiles) {
13343
13398
  }
13344
13399
  }
13345
13400
  for (const manifest of byDepth) {
13346
- if (path39.posix.basename(manifest.rel) !== ".editorconfig") continue;
13401
+ if (path40.posix.basename(manifest.rel) !== ".editorconfig") continue;
13347
13402
  const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
13348
13403
  const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
13349
13404
  if (style || width) {
@@ -13414,12 +13469,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
13414
13469
  return out;
13415
13470
  }
13416
13471
  function outPathIsGitIgnored(outPath) {
13417
- const dir = path39.dirname(outPath);
13472
+ const dir = path40.dirname(outPath);
13418
13473
  try {
13419
- const ignoreFile = path39.join(path39.dirname(dir), ".gitignore");
13474
+ const ignoreFile = path40.join(path40.dirname(dir), ".gitignore");
13420
13475
  if (!fs3.existsSync(ignoreFile)) return false;
13421
13476
  const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
13422
- const base = path39.basename(dir);
13477
+ const base = path40.basename(dir);
13423
13478
  return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
13424
13479
  } catch {
13425
13480
  return false;
@@ -13566,8 +13621,8 @@ __export(profile_exports, {
13566
13621
  PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
13567
13622
  runProfile: () => runProfile
13568
13623
  });
13569
- import { closeSync, constants, existsSync as existsSync31, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync13 } from "node:fs";
13570
- import path40 from "node:path";
13624
+ import { closeSync, constants, existsSync as existsSync32, mkdirSync as mkdirSync10, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync14 } from "node:fs";
13625
+ import path41 from "node:path";
13571
13626
  function escapesScanRoot(outPath, scanRoot) {
13572
13627
  const resolveExisting = (target) => {
13573
13628
  let cursor = target;
@@ -13575,23 +13630,23 @@ function escapesScanRoot(outPath, scanRoot) {
13575
13630
  try {
13576
13631
  return realpathSync4(cursor);
13577
13632
  } catch {
13578
- const parent = path40.dirname(cursor);
13633
+ const parent = path41.dirname(cursor);
13579
13634
  if (parent === cursor) return cursor;
13580
13635
  cursor = parent;
13581
13636
  }
13582
13637
  }
13583
13638
  };
13584
13639
  const root = resolveExisting(scanRoot);
13585
- const dir = resolveExisting(path40.dirname(outPath));
13586
- return dir !== root && !dir.startsWith(root + path40.sep);
13640
+ const dir = resolveExisting(path41.dirname(outPath));
13641
+ return dir !== root && !dir.startsWith(root + path41.sep);
13587
13642
  }
13588
13643
  function runProfile(options) {
13589
13644
  if (options.describe) {
13590
13645
  printDescription(PROFILE_DESCRIPTION);
13591
13646
  return;
13592
13647
  }
13593
- const dir = path40.resolve(options.dir ?? ".");
13594
- if (!existsSync31(dir)) {
13648
+ const dir = path41.resolve(options.dir ?? ".");
13649
+ if (!existsSync32(dir)) {
13595
13650
  fail(options, ExitCode.InputValidation, {
13596
13651
  error: `no such directory: ${dir}`,
13597
13652
  code: "profile_dir_missing",
@@ -13599,7 +13654,7 @@ function runProfile(options) {
13599
13654
  });
13600
13655
  }
13601
13656
  const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
13602
- const outPath = path40.resolve(options.out ?? path40.join(dir, "tendril-out", "codebase-profile.json"));
13657
+ const outPath = path41.resolve(options.out ?? path41.join(dir, "tendril-out", "codebase-profile.json"));
13603
13658
  if (!options.dryRun) {
13604
13659
  if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
13605
13660
  fail(options, ExitCode.InputValidation, {
@@ -13608,10 +13663,10 @@ function runProfile(options) {
13608
13663
  remediation: `\`tendril-out\` in that project is a symlink pointing outside it, so writing the profile there could overwrite an unrelated file. Remove the symlink, or choose an explicit destination: \`${tendrilCommand(`profile --dir ${quoteArg(dir)} --out ./codebase-profile.json`)}\`.`
13609
13664
  });
13610
13665
  }
13611
- mkdirSync9(path40.dirname(outPath), { recursive: true });
13666
+ mkdirSync10(path41.dirname(outPath), { recursive: true });
13612
13667
  const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
13613
13668
  try {
13614
- writeFileSync13(handle, `${JSON.stringify(profile, null, 2)}
13669
+ writeFileSync14(handle, `${JSON.stringify(profile, null, 2)}
13615
13670
  `, "utf8");
13616
13671
  } finally {
13617
13672
  closeSync(handle);
@@ -13673,7 +13728,7 @@ Written to ${outPath}
13673
13728
  `);
13674
13729
  if (!ignored) {
13675
13730
  process.stdout.write(
13676
- ` NOTE: ${path40.basename(path40.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
13731
+ ` NOTE: ${path41.basename(path41.dirname(outPath))}/ is not gitignored here \u2014 add it to .gitignore, or this profile will show up in your next commit.
13677
13732
  `
13678
13733
  );
13679
13734
  }
@@ -13807,19 +13862,19 @@ var init_activate = __esm({
13807
13862
 
13808
13863
  // packages/cli/src/run-presence.ts
13809
13864
  import { createHash as createHash8 } from "node:crypto";
13810
- import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
13811
- import path41 from "node:path";
13865
+ import { existsSync as existsSync33, mkdirSync as mkdirSync11, readFileSync as readFileSync30, rmSync as rmSync6, writeFileSync as writeFileSync15 } from "node:fs";
13866
+ import path42 from "node:path";
13812
13867
  function presenceDir() {
13813
- return path41.join(path41.dirname(sessionPath()), "runs");
13868
+ return path42.join(path42.dirname(sessionPath()), "runs");
13814
13869
  }
13815
13870
  function presenceFile(componentName) {
13816
- return path41.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
13871
+ return path42.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
13817
13872
  }
13818
13873
  function readCached(componentName) {
13819
13874
  const file = presenceFile(componentName);
13820
- if (!existsSync32(file)) return void 0;
13875
+ if (!existsSync33(file)) return void 0;
13821
13876
  try {
13822
- const parsed = JSON.parse(readFileSync29(file, "utf8"));
13877
+ const parsed = JSON.parse(readFileSync30(file, "utf8"));
13823
13878
  return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
13824
13879
  } catch {
13825
13880
  return void 0;
@@ -13846,8 +13901,8 @@ async function reportRunPresence(componentName, phase) {
13846
13901
  }
13847
13902
  const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
13848
13903
  if (typeof started.runId !== "string") return;
13849
- mkdirSync10(presenceDir(), { recursive: true });
13850
- writeFileSync14(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
13904
+ mkdirSync11(presenceDir(), { recursive: true });
13905
+ writeFileSync15(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
13851
13906
  `, { mode: 384 });
13852
13907
  } catch {
13853
13908
  }
@@ -13856,7 +13911,7 @@ async function endRunPresence(componentName) {
13856
13911
  try {
13857
13912
  const session = readStoredSession();
13858
13913
  const cached2 = readCached(componentName);
13859
- rmSync5(presenceFile(componentName), { force: true });
13914
+ rmSync6(presenceFile(componentName), { force: true });
13860
13915
  if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
13861
13916
  await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
13862
13917
  } catch {
@@ -13880,14 +13935,14 @@ __export(compose_exports, {
13880
13935
  runCompose: () => runCompose
13881
13936
  });
13882
13937
  import { createHash as createHash9 } from "node:crypto";
13883
- import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
13884
- import path42 from "node:path";
13938
+ import { existsSync as existsSync34, readFileSync as readFileSync31, readdirSync as readdirSync14 } from "node:fs";
13939
+ import path43 from "node:path";
13885
13940
  function compositionPairsFor(hostSet, roots) {
13886
- const parent = path42.dirname(hostSet);
13941
+ const parent = path43.dirname(hostSet);
13887
13942
  const explicitRoots = [...new Set(roots)];
13888
13943
  let skippedParent;
13889
13944
  let parentRoot = [];
13890
- if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
13945
+ if (!explicitRoots.some((r) => path43.resolve(r) === path43.resolve(parent))) {
13891
13946
  let parentEntries = 0;
13892
13947
  try {
13893
13948
  parentEntries = readdirSync14(parent).length;
@@ -13928,7 +13983,7 @@ function substitutionPairs(edges, hostSet) {
13928
13983
  });
13929
13984
  }
13930
13985
  const pair = pairs.get(key);
13931
- const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
13986
+ const poseDisplay = e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ");
13932
13987
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13933
13988
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13934
13989
  }
@@ -13940,7 +13995,7 @@ function runCompose(flags) {
13940
13995
  return;
13941
13996
  }
13942
13997
  const base = process.env["INIT_CWD"] ?? process.cwd();
13943
- const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
13998
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path43.resolve(base, d)) : [base];
13944
13999
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13945
14000
  fail(flags, ExitCode.InputValidation, {
13946
14001
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13949,7 +14004,7 @@ function runCompose(flags) {
13949
14004
  });
13950
14005
  }
13951
14006
  if (flags.set !== void 0) {
13952
- runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
14007
+ runComposeConfirm(flags, path43.resolve(base, flags.set), roots);
13953
14008
  return;
13954
14009
  }
13955
14010
  const index = buildComposeIndex(roots);
@@ -13967,7 +14022,7 @@ function runCompose(flags) {
13967
14022
  }
13968
14023
  let lastHost = "";
13969
14024
  for (const e of edges) {
13970
- const host = `${path42.basename(e.hostSet)}`;
14025
+ const host = `${path43.basename(e.hostSet)}`;
13971
14026
  if (host !== lastHost) {
13972
14027
  process.stdout.write(`
13973
14028
  ${host}
@@ -13975,7 +14030,7 @@ ${host}
13975
14030
  lastHost = host;
13976
14031
  }
13977
14032
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13978
- const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
14033
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path43.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13979
14034
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13980
14035
  `);
13981
14036
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13987,14 +14042,14 @@ ${NOTE}
13987
14042
  });
13988
14043
  }
13989
14044
  function runComposeConfirm(flags, hostSet, roots) {
13990
- if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
14045
+ if (!existsSync34(path43.join(hostSet, "recording-set.json"))) {
13991
14046
  fail(flags, ExitCode.InputValidation, {
13992
14047
  error: `no recording-set.json in ${hostSet}`,
13993
14048
  code: "no-recording-set",
13994
14049
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13995
14050
  });
13996
14051
  }
13997
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
14052
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path43.dirname(hostSet)])];
13998
14053
  const index = buildComposeIndex(scanRoots);
13999
14054
  const edges = composeReport(index);
14000
14055
  const pairs = substitutionPairs(edges, hostSet);
@@ -14077,7 +14132,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
14077
14132
  // full recording-set hash join lands with pin authoring, where
14078
14133
  // task configs exist.)
14079
14134
  manifestSha256: Object.fromEntries(
14080
- p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash9("sha256").update(readFileSync30(path42.join(d, "recording-set.json"))).digest("hex")])
14135
+ p.partnerDirs.map((d) => [path43.relative(hostSet, d), createHash9("sha256").update(readFileSync31(path43.join(d, "recording-set.json"))).digest("hex")])
14081
14136
  )
14082
14137
  },
14083
14138
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -14157,10 +14212,10 @@ __export(record_exports, {
14157
14212
  runRecordPlan: () => runRecordPlan,
14158
14213
  runRecordStatus: () => runRecordStatus
14159
14214
  });
14160
- import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
14215
+ import { existsSync as existsSync35, mkdtempSync as mkdtempSync2, readFileSync as readFileSync32, readdirSync as readdirSync15 } from "node:fs";
14161
14216
  import os7 from "node:os";
14162
- import path43 from "node:path";
14163
- import { writeFileSync as writeFileSync15 } from "node:fs";
14217
+ import path44 from "node:path";
14218
+ import { writeFileSync as writeFileSync16 } from "node:fs";
14164
14219
  function recordsInteractionState(reports) {
14165
14220
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
14166
14221
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -14182,7 +14237,7 @@ function interactionDisclosure(component, reports) {
14182
14237
  };
14183
14238
  }
14184
14239
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
14185
- const env = JSON.parse(readFileSync31(file, "utf8"));
14240
+ const env = JSON.parse(readFileSync32(file, "utf8"));
14186
14241
  const text = env.content.map((c) => c.text ?? "").join("\n");
14187
14242
  const symbols = [];
14188
14243
  const walk2 = (node, ancestor) => {
@@ -14240,8 +14295,8 @@ function runRecordPlan(opts) {
14240
14295
  if (rawFile !== void 0) {
14241
14296
  try {
14242
14297
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
14243
- const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14244
- writeFileSync15(tmp, JSON.stringify(envelope));
14298
+ const tmp = path44.join(mkdtempSync2(path44.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14299
+ writeFileSync16(tmp, JSON.stringify(envelope));
14245
14300
  metadataEntries.push({ file: tmp });
14246
14301
  } catch (err) {
14247
14302
  fail(opts, ExitCode.InputValidation, {
@@ -14262,7 +14317,7 @@ function runRecordPlan(opts) {
14262
14317
  let metadataTruncated = false;
14263
14318
  for (const { file, frame } of metadataEntries) {
14264
14319
  try {
14265
- const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
14320
+ const parsed = symbolsFromMetadataEnvelope(path44.resolve(file), frame);
14266
14321
  symbols.push(...parsed.symbols);
14267
14322
  if (parsed.truncated) metadataTruncated = true;
14268
14323
  } catch (err) {
@@ -14296,7 +14351,7 @@ function runRecordPlan(opts) {
14296
14351
  if (symbols.length === 0) {
14297
14352
  const leads = metadataEntries.flatMap(({ file }) => {
14298
14353
  try {
14299
- const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14354
+ const env = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14300
14355
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14301
14356
  } catch {
14302
14357
  return [];
@@ -14389,7 +14444,7 @@ function runRecordPlan(opts) {
14389
14444
  // a CLI flag and the MCP surface has no parameter for it.
14390
14445
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14391
14446
  text: "Record a reduced pose set (anchor + one-factor sweeps + conflict crosses) instead of the full variant matrix. This buys calls with coverage \u2014 sampling is blind to multi-axis interactions and the report discloses the poses it skipped \u2014 so only the user may accept that trade. There is no tool parameter for it; the user runs it themselves in their own terminal, before any pose is recorded.",
14392
- userRuns: [`rm ${quoteArg(path43.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14447
+ userRuns: [`rm ${quoteArg(path44.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14393
14448
  },
14394
14449
  {
14395
14450
  id: "larger-allowance",
@@ -14588,7 +14643,7 @@ function runRecordNext(opts) {
14588
14643
  const progress = payload["progress"];
14589
14644
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14590
14645
  \u2192 ${payload["note"]}
14591
- \u2192 then: ${tendrilCommand(`record ingest --set ${path43.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14646
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path44.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14592
14647
  `);
14593
14648
  });
14594
14649
  }
@@ -14662,7 +14717,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14662
14717
  const skipped = [];
14663
14718
  const failed = [];
14664
14719
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14665
- if (existsSync34(path43.join(setDir, rep, name))) {
14720
+ if (existsSync35(path44.join(setDir, rep, name))) {
14666
14721
  skipped.push(name);
14667
14722
  continue;
14668
14723
  }
@@ -14684,16 +14739,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14684
14739
  }
14685
14740
  function rawEnvelopeFromFile(file, parts) {
14686
14741
  if (parts) {
14687
- const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14742
+ const blocks = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14688
14743
  if (!Array.isArray(blocks) || blocks.length === 0 || blocks.some((p) => typeof p !== "string")) throw new Error("parts file must be a non-empty JSON array of strings");
14689
14744
  return { content: blocks.map((text) => ({ type: "text", text })) };
14690
14745
  }
14691
- return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
14746
+ return { content: [{ type: "text", text: readFileSync32(path44.resolve(file), "utf8") }] };
14692
14747
  }
14693
14748
  async function runRecordIngest(opts) {
14694
14749
  let payload;
14695
14750
  try {
14696
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync31(path43.resolve(opts.file), "utf8"));
14751
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync32(path44.resolve(opts.file), "utf8"));
14697
14752
  } catch (err) {
14698
14753
  fail(opts, ExitCode.InputValidation, {
14699
14754
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14705,7 +14760,7 @@ async function runRecordIngest(opts) {
14705
14760
  fail(opts, ExitCode.InputValidation, {
14706
14761
  error: "--raw is for text tool responses; screenshots are binary",
14707
14762
  code: "envelope-invalid",
14708
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14763
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14709
14764
  });
14710
14765
  }
14711
14766
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14725,7 +14780,7 @@ async function runRecordIngest(opts) {
14725
14780
  remediation: REINGEST_GUIDANCE
14726
14781
  });
14727
14782
  }
14728
- writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14783
+ writeFileSync16(path44.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14729
14784
  `);
14730
14785
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14731
14786
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14749,7 +14804,7 @@ async function runRecordIngest(opts) {
14749
14804
  remediation: REINGEST_GUIDANCE
14750
14805
  });
14751
14806
  }
14752
- writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14807
+ writeFileSync16(path44.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14753
14808
  `);
14754
14809
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14755
14810
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14765,7 +14820,7 @@ async function runRecordIngest(opts) {
14765
14820
  if (assets !== void 0) {
14766
14821
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14767
14822
  `);
14768
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14823
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14769
14824
  `);
14770
14825
  }
14771
14826
  });
@@ -14838,14 +14893,14 @@ async function runRecordIngestRep(opts) {
14838
14893
  if (assets !== void 0) {
14839
14894
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14840
14895
  `);
14841
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14896
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14842
14897
  `);
14843
14898
  }
14844
14899
  });
14845
14900
  }
14846
14901
  function runRecordAsset(opts) {
14847
14902
  if (opts.dir !== void 0) {
14848
- const dir = path43.resolve(opts.dir);
14903
+ const dir = path44.resolve(opts.dir);
14849
14904
  const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14850
14905
  if (names.length === 0) {
14851
14906
  fail(opts, ExitCode.InputValidation, {
@@ -14857,7 +14912,7 @@ function runRecordAsset(opts) {
14857
14912
  const ingested = [];
14858
14913
  try {
14859
14914
  for (const name of names) {
14860
- ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
14915
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync32(path44.join(dir, name)));
14861
14916
  ingested.push(name);
14862
14917
  }
14863
14918
  } catch (err) {
@@ -14877,11 +14932,11 @@ function runRecordAsset(opts) {
14877
14932
  fail(opts, ExitCode.InputValidation, {
14878
14933
  error: "pass --name and --file for a single asset, or --dir for a batch",
14879
14934
  code: "asset-rejected",
14880
- remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14935
+ remediation: tendrilCommand(`record asset --set ${path44.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14881
14936
  });
14882
14937
  }
14883
14938
  try {
14884
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
14939
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync32(path44.resolve(opts.file)));
14885
14940
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14886
14941
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14887
14942
  `);
@@ -14898,8 +14953,8 @@ function runRecordStatus(opts) {
14898
14953
  const status = sessionStatus(opts.setDir);
14899
14954
  const composition = (() => {
14900
14955
  try {
14901
- const setDir = path43.resolve(opts.setDir);
14902
- const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
14956
+ const setDir = path44.resolve(opts.setDir);
14957
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path44.dirname(setDir)]);
14903
14958
  return {
14904
14959
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14905
14960
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14920,7 +14975,7 @@ function runRecordStatus(opts) {
14920
14975
  }
14921
14976
  }
14922
14977
  process.stdout.write(
14923
- status.motion.recorded ? motionTruthFor(path43.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14978
+ status.motion.recorded ? motionTruthFor(path44.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14924
14979
  ` : "MOTION set-level motion context not yet recorded \u2014 `record next` names the call once the reps and token map are done\n" : "MOTION never asked \u2014 this set predates the motion-capture obligation (fresh plans record it; briefs prescribe default motion doctrine only)\n"
14925
14980
  );
14926
14981
  if ("unavailable" in composition) {
@@ -14928,7 +14983,7 @@ function runRecordStatus(opts) {
14928
14983
  `);
14929
14984
  } else if (composition.openPairs.length > 0) {
14930
14985
  process.stdout.write(
14931
- `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path43.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14986
+ `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path44.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14932
14987
  `
14933
14988
  );
14934
14989
  } else if (composition.confirmed > 0) {
@@ -14968,7 +15023,7 @@ function narrowedRoles(derived, override) {
14968
15023
  function rolesFromFile(opts, file, derived) {
14969
15024
  let json;
14970
15025
  try {
14971
- json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
15026
+ json = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14972
15027
  } catch (err) {
14973
15028
  fail(opts, ExitCode.InputValidation, {
14974
15029
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -15006,11 +15061,11 @@ function rolesFromFile(opts, file, derived) {
15006
15061
  };
15007
15062
  }
15008
15063
  function runRecordFinish(opts) {
15009
- if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
15064
+ if (!existsSync35(path44.join(opts.setDir, "recording-set.json"))) {
15010
15065
  fail(opts, ExitCode.InputValidation, {
15011
15066
  error: `no recording-set.json in ${opts.setDir}`,
15012
15067
  code: "no-recording-set",
15013
- remediation: `Run \`${tendrilCommand(`record plan --set ${path43.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
15068
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path44.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
15014
15069
  });
15015
15070
  }
15016
15071
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -15038,17 +15093,17 @@ function runRecordFinish(opts) {
15038
15093
  fail(opts, ExitCode.ConfirmationRequired, {
15039
15094
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
15040
15095
  code: "roles-confirmation-not-interactive",
15041
- remediation: `A human runs \`${tendrilCommand(`record finish --set ${path43.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
15096
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path44.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
15042
15097
  });
15043
15098
  }
15044
15099
  const merged = { ...raw, roles };
15045
- const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
15100
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync35(path44.join(opts.setDir, rel)));
15046
15101
  const errors = issues.filter((i) => i.severity === "error");
15047
15102
  if (errors.length > 0) {
15048
15103
  fail(opts, ExitCode.InputValidation, {
15049
15104
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
15050
15105
  code: "recording-set-invalid",
15051
- remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path43.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
15106
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path44.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
15052
15107
  });
15053
15108
  }
15054
15109
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -15099,9 +15154,9 @@ var init_record = __esm({
15099
15154
  });
15100
15155
 
15101
15156
  // packages/cli/src/font-guidance.ts
15102
- import path44 from "node:path";
15157
+ import path45 from "node:path";
15103
15158
  function fontsUnprovenRemediation(setDir) {
15104
- const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
15159
+ const set = setDir === void 0 ? void 0 : path45.resolve(setDir);
15105
15160
  if (set !== void 0) {
15106
15161
  try {
15107
15162
  const needs = recordedFontNeeds(set);
@@ -15176,8 +15231,8 @@ __export(fonts_exports, {
15176
15231
  runFontsResolveSet: () => runFontsResolveSet,
15177
15232
  runFontsStatus: () => runFontsStatus
15178
15233
  });
15179
- import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15180
- import path45 from "node:path";
15234
+ import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15235
+ import path46 from "node:path";
15181
15236
  async function runFontsResolve(opts) {
15182
15237
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
15183
15238
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15198,7 +15253,7 @@ async function runFontsResolve(opts) {
15198
15253
  }
15199
15254
  }
15200
15255
  async function runFontsResolveSet(opts) {
15201
- const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15256
+ const setDir = path46.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15202
15257
  let needs = [];
15203
15258
  try {
15204
15259
  needs = recordedFontNeeds(setDir);
@@ -15293,16 +15348,16 @@ async function runFontsResolveSet(opts) {
15293
15348
  }
15294
15349
  }
15295
15350
  function runFontsStatus(opts) {
15296
- const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
15297
- if (!existsSync35(manifestPath2)) {
15351
+ const manifestPath2 = path46.join(opts.cacheDir, "manifest.json");
15352
+ if (!existsSync36(manifestPath2)) {
15298
15353
  fail(opts, ExitCode.FontsUnproven, {
15299
15354
  error: `no font cache at ${opts.cacheDir}`,
15300
15355
  code: "fonts-unresolved",
15301
15356
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15302
15357
  });
15303
15358
  }
15304
- const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
15305
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path45.resolve(opts.lock), opts.cacheDir) : null;
15359
+ const faces = JSON.parse(readFileSync33(manifestPath2, "utf8"));
15360
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path46.resolve(opts.lock), opts.cacheDir) : null;
15306
15361
  emitData(opts, { faces, lockVerdicts }, () => {
15307
15362
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15308
15363
  `);
@@ -15346,13 +15401,13 @@ function familyMismatch(family, declared) {
15346
15401
  }
15347
15402
  function runFontsAdd(opts) {
15348
15403
  if (opts.set !== void 0) {
15349
- const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15404
+ const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
15350
15405
  const mismatch = familyMismatch(opts.family, declared);
15351
15406
  if (mismatch !== void 0) {
15352
15407
  fail(opts, ExitCode.InputValidation, {
15353
15408
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. Adding it under this name would cache a face the mount never matches, and scoring would keep refusing for the family that is still missing.`,
15354
15409
  code: "font-family-not-declared",
15355
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path45.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
15410
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path46.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
15356
15411
  });
15357
15412
  }
15358
15413
  } else {
@@ -15411,13 +15466,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15411
15466
  }
15412
15467
  function runFontsAddSystem(opts) {
15413
15468
  if (opts.set !== void 0) {
15414
- const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15469
+ const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
15415
15470
  const mismatch = familyMismatch(opts.family, declared);
15416
15471
  if (mismatch !== void 0) {
15417
15472
  fail(opts, ExitCode.InputValidation, {
15418
15473
  error: `the recording set does not declare a family named "${opts.family}" \u2014 it declares ${declared.map((d) => `"${d}"`).join(", ")}. A face cached under a name the mount never matches leaves scoring refusing for the family that is still missing.`,
15419
15474
  code: "font-family-not-declared",
15420
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path45.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15475
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path46.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15421
15476
  });
15422
15477
  }
15423
15478
  } else {
@@ -15467,12 +15522,12 @@ var init_fonts = __esm({
15467
15522
  });
15468
15523
 
15469
15524
  // packages/cli/src/profile-input.ts
15470
- import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15471
- import path46 from "node:path";
15525
+ import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
15526
+ import path47 from "node:path";
15472
15527
  function loadCodebaseProfile(flags, profilePath) {
15473
15528
  if (profilePath === void 0) return null;
15474
- const abs = path46.resolve(profilePath);
15475
- if (!existsSync36(abs)) {
15529
+ const abs = path47.resolve(profilePath);
15530
+ if (!existsSync37(abs)) {
15476
15531
  fail(flags, ExitCode.InputValidation, {
15477
15532
  error: `no profile at ${abs}`,
15478
15533
  code: "profile_missing",
@@ -15480,7 +15535,7 @@ function loadCodebaseProfile(flags, profilePath) {
15480
15535
  });
15481
15536
  }
15482
15537
  try {
15483
- return readCodebaseProfile(readFileSync33(abs, "utf8"));
15538
+ return readCodebaseProfile(readFileSync34(abs, "utf8"));
15484
15539
  } catch (error) {
15485
15540
  fail(flags, ExitCode.InputValidation, {
15486
15541
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15522,8 +15577,8 @@ __export(verify_exports, {
15522
15577
  runVerify: () => runVerify,
15523
15578
  verdictCaveatsFor: () => verdictCaveatsFor
15524
15579
  });
15525
- import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
15526
- import path47 from "node:path";
15580
+ import { existsSync as existsSync38, readFileSync as readFileSync35, rmSync as rmSync7, writeFileSync as writeFileSync17 } from "node:fs";
15581
+ import path48 from "node:path";
15527
15582
  function interactionCoverage(behaviors) {
15528
15583
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15529
15584
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15764,12 +15819,12 @@ function compositionReport(input) {
15764
15819
  function eyeCheck(bundleDir) {
15765
15820
  return {
15766
15821
  command: tendrilCommand(`inspect "${bundleDir}"`),
15767
- sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
15822
+ sheetPath: path48.join(bundleDir, "verify-evidence", "inspect.html"),
15768
15823
  note: "magnified recorded-vs-rendered crops of every small node; scores cannot see shape. The command WRITES/refreshes the sheet at sheetPath \u2014 re-run it after every verify; an existing sheet may show stale crops."
15769
15824
  };
15770
15825
  }
15771
15826
  function evidenceArtifacts(evidenceDir, reps) {
15772
- const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
15827
+ const named = (name) => existsSync38(path48.join(evidenceDir, name)) ? name : null;
15773
15828
  return {
15774
15829
  legend: named("diff-legend.txt"),
15775
15830
  configs: reps.map((rep) => {
@@ -15817,7 +15872,7 @@ function taskFromManifest(opts, manifest, setDir) {
15817
15872
  const adapterSlugs = Object.keys(manifest.propAdapter);
15818
15873
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15819
15874
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15820
- const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15875
+ const registry = Object.values(TASKS).find((t) => path48.resolve(t.set) === path48.resolve(setDir));
15821
15876
  const authored = (() => {
15822
15877
  if (registry !== void 0) return void 0;
15823
15878
  try {
@@ -15878,19 +15933,19 @@ function verdictCaveatsFor(input) {
15878
15933
  async function runVerify(opts) {
15879
15934
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15880
15935
  let recordingSetDrift;
15881
- const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
15882
- opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15883
- if (!existsSync37(opts.bundleDir)) {
15936
+ const setOverride = opts.set !== void 0 ? path48.resolve(callerCwd, opts.set) : void 0;
15937
+ opts = { ...opts, bundleDir: path48.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15938
+ if (!existsSync38(opts.bundleDir)) {
15884
15939
  fail(opts, ExitCode.InputValidation, {
15885
15940
  error: `bundle directory not found: ${opts.bundleDir}`,
15886
15941
  code: "bundle-missing",
15887
15942
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15888
15943
  });
15889
15944
  }
15890
- const manifestPath2 = path47.join(opts.bundleDir, "component.json");
15945
+ const manifestPath2 = path48.join(opts.bundleDir, "component.json");
15891
15946
  let manifest;
15892
- if (existsSync37(manifestPath2)) {
15893
- const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
15947
+ if (existsSync38(manifestPath2)) {
15948
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync35(manifestPath2, "utf8"));
15894
15949
  if (issues.length > 0) {
15895
15950
  fail(opts, ExitCode.InputValidation, {
15896
15951
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15921,21 +15976,21 @@ async function runVerify(opts) {
15921
15976
  task = registry;
15922
15977
  } else if (manifest !== void 0) {
15923
15978
  const resolveSetDir = (p) => {
15924
- if (path47.isAbsolute(p)) return p;
15925
- const fromRepo = path47.resolve(REPO_ROOT, p);
15926
- if (existsSync37(fromRepo)) return fromRepo;
15927
- return path47.resolve(callerCwd, p);
15979
+ if (path48.isAbsolute(p)) return p;
15980
+ const fromRepo = path48.resolve(REPO_ROOT, p);
15981
+ if (existsSync38(fromRepo)) return fromRepo;
15982
+ return path48.resolve(callerCwd, p);
15928
15983
  };
15929
15984
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
15930
- if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
15985
+ if (!existsSync38(path48.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path48.resolve(t.set) === path48.resolve(setDir))) {
15931
15986
  fail(opts, ExitCode.RecordingIncomplete, {
15932
15987
  error: `recording set not found or unmanifested: ${setDir}`,
15933
15988
  code: "recording-set-missing",
15934
15989
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15935
15990
  });
15936
15991
  }
15937
- const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15938
- if (registry !== void 0 && !existsSync37(path47.join(setDir, "recording-set.json"))) {
15992
+ const registry = Object.values(TASKS).find((t) => path48.resolve(t.set) === path48.resolve(setDir));
15993
+ if (registry !== void 0 && !existsSync38(path48.join(setDir, "recording-set.json"))) {
15939
15994
  const recordedSlugs = registry.configs.map((c) => c.rep);
15940
15995
  const adapterSlugs = Object.keys(manifest.propAdapter);
15941
15996
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15967,9 +16022,9 @@ async function runVerify(opts) {
15967
16022
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15968
16023
  }
15969
16024
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
15970
- const p = path47.join(opts.bundleDir, name);
15971
- if (!existsSync37(p)) continue;
15972
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync34(p)));
16025
+ const p = path48.join(opts.bundleDir, name);
16026
+ if (!existsSync38(p)) continue;
16027
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync35(p)));
15973
16028
  if (issues.length > 0) {
15974
16029
  fail(opts, ExitCode.InputValidation, {
15975
16030
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -16007,7 +16062,7 @@ async function runVerify(opts) {
16007
16062
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
16008
16063
  }
16009
16064
  const missing = task.configs.filter(
16010
- (c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
16065
+ (c) => !existsSync38(path48.join(task.set, c.rep, "get_screenshot.json")) || !existsSync38(path48.join(task.set, c.rep, "get_metadata.json"))
16011
16066
  );
16012
16067
  if (missing.length > 0) {
16013
16068
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -16017,8 +16072,8 @@ async function runVerify(opts) {
16017
16072
  });
16018
16073
  }
16019
16074
  const bar = BARS2[opts.bar];
16020
- const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
16021
- rmSync6(evidenceDir, { recursive: true, force: true });
16075
+ const evidenceDir = path48.join(opts.bundleDir, "verify-evidence");
16076
+ rmSync7(evidenceDir, { recursive: true, force: true });
16022
16077
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
16023
16078
  const quality = await checkBundleQuality(
16024
16079
  opts.bundleDir,
@@ -16035,7 +16090,7 @@ async function runVerify(opts) {
16035
16090
  // ASKED, never "follows every convention".
16036
16091
  loadCodebaseProfile(opts, opts.profile) ?? void 0
16037
16092
  );
16038
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n");
16093
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path48.join(opts.bundleDir, f)).filter((f) => existsSync38(f)).map((f) => readFileSync35(f, "utf8")).join("\n");
16039
16094
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
16040
16095
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
16041
16096
  ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
@@ -16056,10 +16111,10 @@ async function runVerify(opts) {
16056
16111
  warn(opts, `compositions extension REJECTED (${crossComposition.malformed}) \u2014 the cross-bundle backstop did NOT run over it; repair the manifest entry and re-verify. This is an instrument failure, not a clean bill.`);
16057
16112
  }
16058
16113
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
16059
- const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path47.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
16114
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path48.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
16060
16115
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
16061
16116
  const renderExpectations = verifyPins.pins.map((pin) => ({
16062
- modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16117
+ modulePath: path48.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16063
16118
  component: pin.entryComponent,
16064
16119
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
16065
16120
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -16352,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16352
16407
  }
16353
16408
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16354
16409
  `);
16355
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n")));
16410
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path48.join(opts.bundleDir, f)).filter((f) => existsSync38(f)).map((f) => readFileSync35(f, "utf8")).join("\n")));
16356
16411
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16357
16412
  process.stdout.write(`fonts: scored with Tendril-cache faces \u2014 a consuming app must provision the same families (the bundle ships fonts.css when faces are shippable; sha-pinned list in component.json requiredFonts)
16358
16413
  `);
@@ -16407,7 +16462,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16407
16462
  persistReport(opts, report, evidenceDir);
16408
16463
  }
16409
16464
  function persistReport(opts, report, evidenceDir) {
16410
- if (!existsSync37(evidenceDir)) return;
16465
+ if (!existsSync38(evidenceDir)) return;
16411
16466
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16412
16467
  const withExit = {
16413
16468
  ...report,
@@ -16415,9 +16470,9 @@ function persistReport(opts, report, evidenceDir) {
16415
16470
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16416
16471
  };
16417
16472
  try {
16418
- writeFileSync16(
16419
- path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
16420
- `${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
16473
+ writeFileSync17(
16474
+ path48.join(evidenceDir, VERIFY_REPORT_FILENAME),
16475
+ `${JSON.stringify(verifyReportForTransport(withExit, path48.basename(opts.bundleDir)), null, 2)}
16421
16476
  `
16422
16477
  );
16423
16478
  } catch (e) {
@@ -16467,11 +16522,11 @@ __export(engine_exports, {
16467
16522
  runEngineBrief: () => runEngineBrief,
16468
16523
  runEngineScore: () => runEngineScore
16469
16524
  });
16470
- import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16471
- import path48 from "node:path";
16525
+ import { appendFileSync, existsSync as existsSync39, mkdirSync as mkdirSync12, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16526
+ import path49 from "node:path";
16472
16527
  function resolveEngineTask(opts, callerCwd) {
16473
- const asPath = path48.resolve(callerCwd, opts.taskOrSet);
16474
- const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
16528
+ const asPath = path49.resolve(callerCwd, opts.taskOrSet);
16529
+ const isSet = existsSync39(path49.join(asPath, "recording-set.json"));
16475
16530
  const registry = TASKS[opts.taskOrSet];
16476
16531
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16477
16532
  if (isSet) {
@@ -16480,7 +16535,7 @@ function resolveEngineTask(opts, callerCwd) {
16480
16535
  for (const d of authored.disclosures) warn(opts, d);
16481
16536
  return {
16482
16537
  task: authored.task,
16483
- name: path48.basename(asPath),
16538
+ name: path49.basename(asPath),
16484
16539
  ref: asPath,
16485
16540
  disclosures: authored.disclosures,
16486
16541
  interactionEvidence: authored.api.interactionEvidence,
@@ -16509,9 +16564,9 @@ function runEngineBrief(opts) {
16509
16564
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16510
16565
  void reportRunPresence(name, "implementing");
16511
16566
  const bar = BARS3[opts.bar];
16512
- if (existsSync38(path48.join(task.set, "recording-set.json"))) {
16567
+ if (existsSync39(path49.join(task.set, "recording-set.json"))) {
16513
16568
  try {
16514
- const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16569
+ const { open, skippedParent } = compositionPairsFor(path49.resolve(task.set), [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
16515
16570
  if (skippedParent !== void 0) {
16516
16571
  disclosures.push(
16517
16572
  `COMPOSITION DISCOVERY PARTIAL: the set's parent directory (${skippedParent.dir}) holds ${String(skippedParent.entries)} entries and was not scanned as a recordings library \u2014 sibling sets there are invisible to pairing. Pass --library <dir> to scan a specific library deliberately.`
@@ -16520,7 +16575,7 @@ function runEngineBrief(opts) {
16520
16575
  if (open.length > 0) {
16521
16576
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16522
16577
  disclosures.push(
16523
- `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path48.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16578
+ `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path49.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16524
16579
  );
16525
16580
  }
16526
16581
  } catch (err) {
@@ -16537,9 +16592,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16537
16592
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16538
16593
  const segments = buildSegments(task, "files");
16539
16594
  let notRecorded;
16540
- const manifestPath2 = path48.join(task.set, "recording-set.json");
16541
- if (existsSync38(manifestPath2)) {
16542
- notRecorded = JSON.parse(readFileSync35(manifestPath2, "utf8")).notRecorded;
16595
+ const manifestPath2 = path49.join(task.set, "recording-set.json");
16596
+ if (existsSync39(manifestPath2)) {
16597
+ notRecorded = JSON.parse(readFileSync36(manifestPath2, "utf8")).notRecorded;
16543
16598
  }
16544
16599
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16545
16600
 
@@ -16547,7 +16602,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16547
16602
  DO NOT INVENT PIXELS FOR THESE POSES. Implement them ONLY as the composition of recorded per-axis truth (the custom-property composition rule: one paint axis sets variables, the other consumes) and list every such pose in your report as UNRECORDED-COMPOSED. Recording them is the real fix: re-plan the set (full matrix is the default) and record the missing poses.
16548
16603
  ${notRecorded}` : "";
16549
16604
  let fontProvisioning;
16550
- if (existsSync38(manifestPath2)) {
16605
+ if (existsSync39(manifestPath2)) {
16551
16606
  const missingFams = unprovisionedFamilies(task.set);
16552
16607
  const unprovided = unprovisionedFaces(task.set);
16553
16608
  const weightOnly = missingFams.length === 0;
@@ -16569,7 +16624,7 @@ ${notRecorded}` : "";
16569
16624
  };
16570
16625
  }
16571
16626
  }
16572
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16627
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
16573
16628
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16574
16629
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16575
16630
 
@@ -16605,10 +16660,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16605
16660
 
16606
16661
  === TASK PAYLOAD (recorded truth, verbatim) ===
16607
16662
  ${segments}`;
16608
- const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16609
- const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
16610
- mkdirSync11(path48.dirname(payloadFile), { recursive: true });
16611
- writeFileSync17(payloadFile, payload);
16663
+ const payloadFile = path49.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16664
+ const candidateDirSuggestion = path49.resolve(callerCwd, `tendril-out/${name}-candidate`);
16665
+ mkdirSync12(path49.dirname(payloadFile), { recursive: true });
16666
+ writeFileSync18(payloadFile, payload);
16612
16667
  emitData(
16613
16668
  opts,
16614
16669
  {
@@ -16654,7 +16709,7 @@ ${segments}`;
16654
16709
  // command must search the same bundle roots the pins came
16655
16710
  // from, or the oracle and the brief describe different worlds.
16656
16711
  `Run \`${tendrilCommand(
16657
- `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path48.resolve(callerCwd, opts.library))}` : ""} --json`
16712
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path49.resolve(callerCwd, opts.library))}` : ""} --json`
16658
16713
  )}\` \u2014 the CLI is the sole judge, and it refuses to score an undeclared model. If you wrote the bundle elsewhere, pass that directory instead.`,
16659
16714
  "Apply the returned feedback and re-score. Stop when all checks pass. Two consecutive non-improving scores mean INSPECT the verify-evidence diffs before deciding \u2014 stop only when inspection yields no fix hypothesis (a measured run was byte-identical twice, then went 27/27 after reading the diffs).",
16660
16715
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16669,8 +16724,8 @@ ${segments}`;
16669
16724
  );
16670
16725
  }
16671
16726
  function appendScoreHistory(candidateDir, entry) {
16672
- const file = path48.join(candidateDir, "score-history.jsonl");
16673
- const starts = existsSync38(file) ? readFileSync35(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16727
+ const file = path49.join(candidateDir, "score-history.jsonl");
16728
+ const starts = existsSync39(file) ? readFileSync36(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16674
16729
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16675
16730
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16676
16731
  `);
@@ -16678,10 +16733,10 @@ function appendScoreHistory(candidateDir, entry) {
16678
16733
  async function runEngineScore(opts) {
16679
16734
  requireEntitlement(opts);
16680
16735
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16681
- const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
16736
+ const candidateDir = path49.resolve(callerCwd, opts.candidateDir);
16682
16737
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16683
16738
  void reportRunPresence(name, "implementing");
16684
- if (!existsSync38(candidateDir)) {
16739
+ if (!existsSync39(candidateDir)) {
16685
16740
  fail(opts, ExitCode.InputValidation, {
16686
16741
  error: `candidate directory not found: ${candidateDir}`,
16687
16742
  code: "candidate-missing",
@@ -16706,10 +16761,10 @@ async function runEngineScore(opts) {
16706
16761
  for (const g of missingWeights(task.set)) {
16707
16762
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
16708
16763
  }
16709
- if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
16764
+ if (opts.rebind !== true && existsSync39(path49.join(candidateDir, "component.json"))) {
16710
16765
  const prior = (() => {
16711
16766
  try {
16712
- const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
16767
+ const read = readBundleManifest(readFileSync36(path49.join(candidateDir, "component.json"), "utf8"));
16713
16768
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16714
16769
  } catch {
16715
16770
  return { unreadable: true };
@@ -16731,13 +16786,13 @@ async function runEngineScore(opts) {
16731
16786
  }
16732
16787
  }
16733
16788
  const bar = BARS3[opts.bar];
16734
- const evidenceDir = path48.join(candidateDir, "verify-evidence");
16789
+ const evidenceDir = path49.join(candidateDir, "verify-evidence");
16735
16790
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16736
16791
  const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16737
16792
  const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16738
16793
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16739
16794
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16740
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16795
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path49.resolve(callerCwd, opts.library) : callerCwd]);
16741
16796
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16742
16797
  const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16743
16798
  const parityCoverage = parity.length > 0 ? `${parity.filter((p) => p.pass).length}/${parity.length} hover-forced configs` : "not applicable (no hover-forced configs in this set)";
@@ -16956,11 +17011,11 @@ var codeconnect_exports = {};
16956
17011
  __export(codeconnect_exports, {
16957
17012
  runCodeConnect: () => runCodeConnect
16958
17013
  });
16959
- import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16960
- import path49 from "node:path";
17014
+ import { existsSync as existsSync40, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17015
+ import path50 from "node:path";
16961
17016
  function runCodeConnect(opts) {
16962
17017
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16963
- const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
17018
+ const bundleDir = path50.resolve(callerCwd, opts.bundleDir);
16964
17019
  let url;
16965
17020
  try {
16966
17021
  url = new URL(opts.figmaUrl);
@@ -16976,7 +17031,7 @@ function runCodeConnect(opts) {
16976
17031
  }
16977
17032
  let manifest;
16978
17033
  try {
16979
- const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
17034
+ const read = readBundleManifest(readFileSync37(path50.join(bundleDir, "component.json"), "utf8"));
16980
17035
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16981
17036
  manifest = read.manifest;
16982
17037
  } catch (err) {
@@ -16986,8 +17041,8 @@ function runCodeConnect(opts) {
16986
17041
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16987
17042
  });
16988
17043
  }
16989
- const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16990
- if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
17044
+ const setDir = path50.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
17045
+ if (!existsSync40(path50.join(setDir, "recording-set.json"))) {
16991
17046
  fail(opts, ExitCode.InputValidation, {
16992
17047
  error: `recording set not found at ${setDir}`,
16993
17048
  code: "codeconnect-no-set",
@@ -17008,10 +17063,10 @@ function runCodeConnect(opts) {
17008
17063
  const component = api.component;
17009
17064
  const recManifest = loadManifest(setDir);
17010
17065
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
17011
- const meta = path49.join(setDir, r.slug, "get_metadata.json");
17012
- if (!existsSync39(meta)) return void 0;
17066
+ const meta = path50.join(setDir, r.slug, "get_metadata.json");
17067
+ if (!existsSync40(meta)) return void 0;
17013
17068
  try {
17014
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
17069
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync37(meta, "utf8"))))?.[1];
17015
17070
  } catch {
17016
17071
  return void 0;
17017
17072
  }
@@ -17076,7 +17131,7 @@ function runCodeConnect(opts) {
17076
17131
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
17077
17132
  fragmentVars.push(varName);
17078
17133
  }
17079
- const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
17134
+ const entryRel = path50.relative(callerCwd, path50.join(bundleDir, manifest.entry));
17080
17135
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
17081
17136
  const lines = [
17082
17137
  `// url=${opts.figmaUrl}`,
@@ -17100,8 +17155,8 @@ function runCodeConnect(opts) {
17100
17155
  `}`,
17101
17156
  ``
17102
17157
  ].join("\n");
17103
- const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
17104
- writeFileSync18(outFile, lines);
17158
+ const outFile = path50.resolve(callerCwd, opts.out ?? path50.join(bundleDir, `${component}.figma.ts`));
17159
+ writeFileSync19(outFile, lines);
17105
17160
  emitData(
17106
17161
  opts,
17107
17162
  {
@@ -17140,17 +17195,17 @@ var init_codeconnect = __esm({
17140
17195
 
17141
17196
  // packages/mcp/src/server.ts
17142
17197
  import { createHash as createHash10 } from "node:crypto";
17143
- import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, readdirSync as readdirSync16, writeFileSync as writeFileSync19 } from "node:fs";
17198
+ import { existsSync as existsSync41, mkdtempSync as mkdtempSync3, readFileSync as readFileSync38, readdirSync as readdirSync16, writeFileSync as writeFileSync20 } from "node:fs";
17144
17199
  import os8 from "node:os";
17145
- import path50 from "node:path";
17200
+ import path51 from "node:path";
17146
17201
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17147
17202
  import { z as z14 } from "zod";
17148
17203
  function sourceHash() {
17149
- const dir = path50.dirname(fileURLToPath6(import.meta.url));
17204
+ const dir = path51.dirname(fileURLToPath6(import.meta.url));
17150
17205
  const h = createHash10("sha256");
17151
17206
  for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
17152
17207
  h.update(f);
17153
- h.update(readFileSync37(path50.join(dir, f)));
17208
+ h.update(readFileSync38(path51.join(dir, f)));
17154
17209
  }
17155
17210
  return h.digest("hex").slice(0, 16);
17156
17211
  }
@@ -17158,10 +17213,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
17158
17213
  var init_server = __esm({
17159
17214
  "packages/mcp/src/server.ts"() {
17160
17215
  "use strict";
17161
- REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17162
- CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17163
- BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17164
- CLI_SPAWN = existsSync40(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
17216
+ REPO_ROOT3 = path51.resolve(path51.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17217
+ CLI_BIN = path51.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17218
+ BUNDLED_CLI = path51.join(path51.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17219
+ CLI_SPAWN = existsSync41(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
17165
17220
  str = (d) => z14.string().describe(d);
17166
17221
  optStr = (d) => z14.string().optional().describe(d);
17167
17222
  TOOLS = [
@@ -17192,13 +17247,13 @@ var init_server = __esm({
17192
17247
  const single = i["metadata"];
17193
17248
  const parts = i["metadataParts"];
17194
17249
  if (single !== void 0 || parts !== void 0) {
17195
- const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17250
+ const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17196
17251
  if (single !== void 0) {
17197
- writeFileSync19(tmp, single);
17252
+ writeFileSync20(tmp, single);
17198
17253
  argvOut.push("--metadata-raw-file", tmp);
17199
17254
  } else {
17200
17255
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
17201
- writeFileSync19(tmp, JSON.stringify(parts));
17256
+ writeFileSync20(tmp, JSON.stringify(parts));
17202
17257
  argvOut.push("--metadata-raw-parts-file", tmp);
17203
17258
  }
17204
17259
  }
@@ -17248,6 +17303,20 @@ var init_server = __esm({
17248
17303
  schema: z14.object({}),
17249
17304
  argv: () => ["login", "--device-wait"]
17250
17305
  },
17306
+ {
17307
+ name: "tendril_figma_connect",
17308
+ description: "Grant this machine read access to the user's Figma files over Figma's API \u2014 phase one of the browser Figma connection. Recording over the Figma MCP transport pays a hard per-day call quota; this connection lets recording fetch over Figma's REST API instead (per-minute, batched), so large component sets stop hitting daily limits. Starts the handshake and returns a connect link \u2014 RELAY IT to the user verbatim: they sign in to their portal account if asked, then click Allow on Figma's consent screen (read-only file access; revocable any time in their Figma settings). Then call tendril_figma_connect_wait to finish. You cannot consent for them: the portal only accepts the browser's signed-in click, never this machine's token. Offer it when a recording plan warns about Figma call limits; requires a portal session (tendril_login).",
17309
+ schema: z14.object({
17310
+ portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
17311
+ }),
17312
+ argv: (i) => ["figma-connect", "--start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17313
+ },
17314
+ {
17315
+ name: "tendril_figma_connect_wait",
17316
+ description: "Phase two of the browser Figma connection: waits (with live progress) for the user's Allow click on Figma's consent screen, then stores the credential on this machine (it renews itself from then on). Call it right after relaying the connect link. A decline, a lapse, and success each come back as their own sentence \u2014 report the outcome to the user.",
17317
+ schema: z14.object({}),
17318
+ argv: () => ["figma-connect", "--wait"]
17319
+ },
17251
17320
  {
17252
17321
  name: "tendril_publish",
17253
17322
  description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim, along with which account the result says to be signed in as (they click Approve in the browser; approving includes accepting the design system's publishing terms, which is their decision to make, never yours to urge). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
@@ -17307,14 +17376,14 @@ var init_server = __esm({
17307
17376
  const bridge = (label, single, parts) => {
17308
17377
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17309
17378
  if (single === void 0 && parts === void 0) return;
17310
- const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17379
+ const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17311
17380
  if (single !== void 0) {
17312
- writeFileSync19(tmp, single);
17381
+ writeFileSync20(tmp, single);
17313
17382
  argvOut.push(`--${label}-file`, tmp);
17314
17383
  } else {
17315
17384
  const blocks = parts;
17316
17385
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17317
- writeFileSync19(tmp, JSON.stringify(blocks));
17386
+ writeFileSync20(tmp, JSON.stringify(blocks));
17318
17387
  argvOut.push(`--${label}-parts-file`, tmp);
17319
17388
  }
17320
17389
  };
@@ -17355,12 +17424,12 @@ var init_server = __esm({
17355
17424
  const file = i["file"];
17356
17425
  if ([text, texts, file].filter((x) => x !== void 0).length !== 1) throw new Error("pass exactly one of `text` (single-block response), `texts` (multi-block response), or `file` (a saved envelope)");
17357
17426
  if (file !== void 0) return [...base, "--file", file];
17358
- const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17427
+ const tmp = path51.join(mkdtempSync3(path51.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17359
17428
  if (text !== void 0) {
17360
- writeFileSync19(tmp, text);
17429
+ writeFileSync20(tmp, text);
17361
17430
  return [...base, "--file", tmp, "--raw"];
17362
17431
  }
17363
- writeFileSync19(tmp, JSON.stringify(texts));
17432
+ writeFileSync20(tmp, JSON.stringify(texts));
17364
17433
  return [...base, "--file", tmp, "--raw-parts"];
17365
17434
  }
17366
17435
  },
@@ -17517,13 +17586,13 @@ __export(permissions_exports, {
17517
17586
  runPermissions: () => runPermissions,
17518
17587
  writeSelection: () => writeSelection
17519
17588
  });
17520
- import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17589
+ import { existsSync as existsSync42, mkdirSync as mkdirSync13, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17521
17590
  import os9 from "node:os";
17522
- import path51 from "node:path";
17591
+ import path52 from "node:path";
17523
17592
  function mergeAllowlist(file, entries, denyEntries = []) {
17524
17593
  let settings = {};
17525
- if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
17526
- settings = JSON.parse(readFileSync38(file, "utf8"));
17594
+ if (existsSync42(file) && readFileSync39(file, "utf8").trim() !== "") {
17595
+ settings = JSON.parse(readFileSync39(file, "utf8"));
17527
17596
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17528
17597
  }
17529
17598
  const permissions = settings["permissions"] ??= {};
@@ -17543,8 +17612,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17543
17612
  }
17544
17613
  if (added.length > 0 || denyAdded.length > 0) {
17545
17614
  allow.push(...added);
17546
- mkdirSync12(path51.dirname(file), { recursive: true });
17547
- writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
17615
+ mkdirSync13(path52.dirname(file), { recursive: true });
17616
+ writeFileSync21(file, `${JSON.stringify(settings, null, 2)}
17548
17617
  `);
17549
17618
  }
17550
17619
  return { added, alreadyPresent, denyAdded };
@@ -17608,7 +17677,7 @@ async function runPermissions(flags) {
17608
17677
  }
17609
17678
  if (flags.write) {
17610
17679
  const base = process.env["INIT_CWD"] ?? process.cwd();
17611
- const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
17680
+ const file = flags.user ? path52.join(os9.homedir(), ".claude", "settings.json") : path52.join(base, ".claude", "settings.local.json");
17612
17681
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17613
17682
  if (flags.dryRun) {
17614
17683
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17757,13 +17826,13 @@ __export(inspect_exports, {
17757
17826
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17758
17827
  runInspect: () => runInspect
17759
17828
  });
17760
- import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17761
- import path52 from "node:path";
17829
+ import { existsSync as existsSync43, readFileSync as readFileSync40, writeFileSync as writeFileSync22 } from "node:fs";
17830
+ import path53 from "node:path";
17762
17831
  function readVerifyReport(evidenceDir) {
17763
- const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
17764
- if (!existsSync42(p)) return void 0;
17832
+ const p = path53.join(evidenceDir, VERIFY_REPORT_FILENAME);
17833
+ if (!existsSync43(p)) return void 0;
17765
17834
  try {
17766
- return JSON.parse(readFileSync39(p, "utf8"));
17835
+ return JSON.parse(readFileSync40(p, "utf8"));
17767
17836
  } catch {
17768
17837
  return void 0;
17769
17838
  }
@@ -17791,17 +17860,17 @@ async function runInspect(opts) {
17791
17860
  printDescription(INSPECT_DESCRIPTION);
17792
17861
  return;
17793
17862
  }
17794
- const bundleDir = path52.resolve(opts.bundleDir);
17795
- const evidenceDir = path52.join(bundleDir, "verify-evidence");
17796
- const manifestPath2 = path52.join(bundleDir, "component.json");
17797
- if (!existsSync42(evidenceDir) || !existsSync42(manifestPath2)) {
17863
+ const bundleDir = path53.resolve(opts.bundleDir);
17864
+ const evidenceDir = path53.join(bundleDir, "verify-evidence");
17865
+ const manifestPath2 = path53.join(bundleDir, "component.json");
17866
+ if (!existsSync43(evidenceDir) || !existsSync43(manifestPath2)) {
17798
17867
  fail(opts, ExitCode.InputValidation, {
17799
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17868
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync43(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17800
17869
  code: "no-evidence",
17801
17870
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17802
17871
  });
17803
17872
  }
17804
- const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
17873
+ const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
17805
17874
  if (manifest === void 0) {
17806
17875
  fail(opts, ExitCode.InputValidation, {
17807
17876
  error: "component.json did not parse as a bundle manifest",
@@ -17809,9 +17878,9 @@ async function runInspect(opts) {
17809
17878
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17810
17879
  });
17811
17880
  }
17812
- const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17881
+ const setDir = path53.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17813
17882
  const report = readVerifyReport(evidenceDir);
17814
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync42(path52.join(evidenceDir, `${rep}-ref.png`)) && existsSync42(path52.join(evidenceDir, `${rep}-render.png`)));
17883
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync43(path53.join(evidenceDir, `${rep}-ref.png`)) && existsSync43(path53.join(evidenceDir, `${rep}-render.png`)));
17815
17884
  if (reps.length === 0) {
17816
17885
  fail(opts, ExitCode.InputValidation, {
17817
17886
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17822,15 +17891,15 @@ async function runInspect(opts) {
17822
17891
  let crops = 0;
17823
17892
  const sections = [];
17824
17893
  for (const rep of reps) {
17825
- const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
17826
- const render = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-render.png`)));
17894
+ const ref = new Uint8Array(readFileSync40(path53.join(evidenceDir, `${rep}-ref.png`)));
17895
+ const render = new Uint8Array(readFileSync40(path53.join(evidenceDir, `${rep}-render.png`)));
17827
17896
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17828
17897
  const cells = [];
17829
17898
  for (const [i, n] of nodes.entries()) {
17830
17899
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17831
17900
  try {
17832
- writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17833
- writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17901
+ writeFileSync22(path53.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17902
+ writeFileSync22(path53.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17834
17903
  } catch {
17835
17904
  continue;
17836
17905
  }
@@ -17847,8 +17916,8 @@ async function runInspect(opts) {
17847
17916
  if (reps.includes(c.rep)) continue;
17848
17917
  sections.push(`<section class="missing"><h2>${esc(c.rep)}</h2>${scoreLine(report, c.rep)}<p class="none">No evidence images for this config \u2014 it was scored, but nothing was captured to look at.</p></section>`);
17849
17918
  }
17850
- const sheet = path52.join(evidenceDir, "inspect.html");
17851
- writeFileSync21(
17919
+ const sheet = path53.join(evidenceDir, "inspect.html");
17920
+ writeFileSync22(
17852
17921
  sheet,
17853
17922
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17854
17923
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17921,8 +17990,8 @@ __export(login_exports, {
17921
17990
  runLogout: () => runLogout
17922
17991
  });
17923
17992
  import { spawn } from "node:child_process";
17924
- import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
17925
- import path53 from "node:path";
17993
+ import { existsSync as existsSync44, mkdirSync as mkdirSync14, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
17994
+ import path54 from "node:path";
17926
17995
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17927
17996
  async function runLogin(opts, deps) {
17928
17997
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -18016,13 +18085,13 @@ function settleDecision(opts, origin, outcome) {
18016
18085
  }
18017
18086
  }
18018
18087
  function pendingLoginPath() {
18019
- return path53.join(path53.dirname(sessionPath()), "pending-login.json");
18088
+ return path54.join(path54.dirname(sessionPath()), "pending-login.json");
18020
18089
  }
18021
18090
  async function deviceStartPhase(opts, origin, deps) {
18022
18091
  const started = await startHandshake(opts, origin, deps);
18023
18092
  const file = pendingLoginPath();
18024
- mkdirSync13(path53.dirname(file), { recursive: true });
18025
- writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18093
+ mkdirSync14(path54.dirname(file), { recursive: true });
18094
+ writeFileSync23(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18026
18095
  `, { mode: 384 });
18027
18096
  deps.openBrowser(started.verificationUrl);
18028
18097
  emitData(
@@ -18047,9 +18116,9 @@ async function deviceStartPhase(opts, origin, deps) {
18047
18116
  async function deviceWaitPhase(opts, deps) {
18048
18117
  const file = pendingLoginPath();
18049
18118
  let pending;
18050
- if (existsSync43(file)) {
18119
+ if (existsSync44(file)) {
18051
18120
  try {
18052
- const parsed = JSON.parse(readFileSync40(file, "utf8"));
18121
+ const parsed = JSON.parse(readFileSync41(file, "utf8"));
18053
18122
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
18054
18123
  pending = parsed;
18055
18124
  }
@@ -18063,7 +18132,7 @@ async function deviceWaitPhase(opts, deps) {
18063
18132
  remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
18064
18133
  });
18065
18134
  }
18066
- const done = () => rmSync7(file, { force: true });
18135
+ const done = () => rmSync8(file, { force: true });
18067
18136
  const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
18068
18137
  let ticks = 0;
18069
18138
  const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
@@ -18186,6 +18255,196 @@ var init_login = __esm({
18186
18255
  }
18187
18256
  });
18188
18257
 
18258
+ // packages/cli/src/commands/figma-connect.ts
18259
+ var figma_connect_exports = {};
18260
+ __export(figma_connect_exports, {
18261
+ runFigmaConnect: () => runFigmaConnect
18262
+ });
18263
+ import { existsSync as existsSync45, mkdirSync as mkdirSync15, readFileSync as readFileSync42, rmSync as rmSync9, writeFileSync as writeFileSync24 } from "node:fs";
18264
+ import path55 from "node:path";
18265
+ function pendingConnectPath() {
18266
+ return path55.join(path55.dirname(sessionPath()), "pending-figma-connect.json");
18267
+ }
18268
+ function resolveOrigin(opts) {
18269
+ const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18270
+ if (named !== "") return named;
18271
+ if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
18272
+ return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
18273
+ }
18274
+ function bearerFor(opts, origin) {
18275
+ if (origin === "") {
18276
+ fail(opts, ExitCode.InputValidation, {
18277
+ error: "no portal to connect through",
18278
+ code: "no-portal-configured",
18279
+ remediation: `Sign in first (${tendrilCommand("login")}) \u2014 the connect uses your portal account.`
18280
+ });
18281
+ }
18282
+ const found = tokenFor(origin);
18283
+ if (!found.ok) {
18284
+ fail(opts, ExitCode.Auth, {
18285
+ error: `no session for ${origin}`,
18286
+ code: "not-signed-in",
18287
+ remediation: `Run ${tendrilCommand("login")} first \u2014 connecting Figma needs your signed-in portal account.`
18288
+ });
18289
+ }
18290
+ return found.token;
18291
+ }
18292
+ async function runFigmaConnect(opts) {
18293
+ const send = opts.fetchImpl ?? globalThis.fetch;
18294
+ const origin = resolveOrigin(opts);
18295
+ if (opts.wait === true) {
18296
+ await waitPhase(opts, send);
18297
+ return;
18298
+ }
18299
+ const token = bearerFor(opts, origin);
18300
+ const started = await startConnect(opts, send, origin, token);
18301
+ if (opts.start === true) {
18302
+ const pending = { origin, ...started };
18303
+ mkdirSync15(path55.dirname(pendingConnectPath()), { recursive: true });
18304
+ writeFileSync24(pendingConnectPath(), `${JSON.stringify(pending, null, 2)}
18305
+ `, { mode: 384 });
18306
+ (opts.openBrowser ?? (() => {
18307
+ }))(started.connectUrl);
18308
+ emitData(
18309
+ opts,
18310
+ {
18311
+ connectUrl: started.connectUrl,
18312
+ expiresAt: started.expiresAt,
18313
+ origin,
18314
+ next: "Show the user the link \u2014 they sign in (same account) and click Allow on Figma's consent screen. Then run figma-connect --wait (the tendril_figma_connect_wait tool) to finish."
18315
+ },
18316
+ () => {
18317
+ process.stdout.write(`open ${started.connectUrl}
18318
+ `);
18319
+ process.stdout.write(` then: ${tendrilCommand("figma-connect --wait")}
18320
+ `);
18321
+ }
18322
+ );
18323
+ return;
18324
+ }
18325
+ process.stderr.write(`Connect Figma in your browser:
18326
+ ${started.connectUrl}
18327
+ `);
18328
+ process.stderr.write(`Waiting for the Allow click (lapses at ${started.expiresAt.slice(11, 16)} UTC)\u2026
18329
+ `);
18330
+ const outcome = await pollToDecision(opts, send, origin, token, started);
18331
+ settle2(opts, origin, outcome);
18332
+ }
18333
+ async function startConnect(opts, send, origin, token) {
18334
+ let response;
18335
+ try {
18336
+ response = await send(`${origin}/api/figma-connect`, { method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, body: "{}" });
18337
+ } catch (error) {
18338
+ fail(opts, ExitCode.General, {
18339
+ error: `could not reach ${origin}: ${error.message}`,
18340
+ code: "portal-unreachable",
18341
+ remediation: "Check the address and your connection, then try again."
18342
+ });
18343
+ }
18344
+ const body = await response.json().catch(() => ({}));
18345
+ if (!response.ok || typeof body.connectId !== "string" || typeof body.connectUrl !== "string" || typeof body.expiresAt !== "string") {
18346
+ fail(opts, ExitCode.General, {
18347
+ error: body.refusal ?? `the portal answered ${String(response.status)} to the connect start`,
18348
+ code: "figma-connect-refused",
18349
+ remediation: body.remediation ?? "Fix what is named above and run the connect again."
18350
+ });
18351
+ }
18352
+ return { connectId: body.connectId, connectUrl: body.connectUrl, expiresAt: body.expiresAt, pollSeconds: typeof body.pollSeconds === "number" ? body.pollSeconds : 3 };
18353
+ }
18354
+ async function waitPhase(opts, send) {
18355
+ const file = pendingConnectPath();
18356
+ let pending;
18357
+ if (existsSync45(file)) {
18358
+ try {
18359
+ const parsed = JSON.parse(readFileSync42(file, "utf8"));
18360
+ if (typeof parsed.origin === "string" && typeof parsed.connectId === "string") pending = parsed;
18361
+ } catch {
18362
+ }
18363
+ }
18364
+ if (pending === void 0) {
18365
+ fail(opts, ExitCode.InputValidation, {
18366
+ error: "there is no Figma connection waiting to finish",
18367
+ code: "no-pending-figma-connect",
18368
+ remediation: `Start one first: ${tendrilCommand("figma-connect --start")} (the tendril_figma_connect tool).`
18369
+ });
18370
+ }
18371
+ const token = bearerFor(opts, pending.origin);
18372
+ const outcome = await pollToDecision(opts, send, pending.origin, token, pending);
18373
+ rmSync9(file, { force: true });
18374
+ settle2(opts, pending.origin, outcome);
18375
+ }
18376
+ async function pollToDecision(opts, send, origin, token, started) {
18377
+ const interval = Math.max(1, started.pollSeconds) * 1e3;
18378
+ const total = Math.ceil(WAIT_CAP_SECONDS * 1e3 / interval);
18379
+ for (let tick = 1; tick <= total; tick += 1) {
18380
+ let body = {};
18381
+ try {
18382
+ const response = await send(`${origin}/api/figma-connect/${encodeURIComponent(started.connectId)}`, {
18383
+ headers: { authorization: `Bearer ${token}` }
18384
+ });
18385
+ if (response.ok) body = await response.json();
18386
+ } catch {
18387
+ }
18388
+ if (body.status === "connected" && typeof body.accessToken === "string" && typeof body.refreshToken === "string") {
18389
+ return {
18390
+ status: "connected",
18391
+ tokens: {
18392
+ origin,
18393
+ accessToken: body.accessToken,
18394
+ refreshToken: body.refreshToken,
18395
+ tokenExpiresAt: typeof body.tokenExpiresAt === "string" ? body.tokenExpiresAt : new Date(Date.now() + 80 * 24 * 60 * 6e4).toISOString()
18396
+ }
18397
+ };
18398
+ }
18399
+ if (body.status === "failed") return { status: "failed", reason: body.reason ?? "declined" };
18400
+ if (body.status === "expired") return { status: "expired" };
18401
+ if (body.status === "unknown") return { status: "unknown" };
18402
+ emitProgress(tick, total, "waiting for the Allow click in the browser");
18403
+ await new Promise((resolve) => setTimeout(resolve, interval));
18404
+ }
18405
+ return { status: "expired" };
18406
+ }
18407
+ function settle2(opts, origin, outcome) {
18408
+ if (outcome.status === "connected") {
18409
+ writeFigmaTokens(outcome.tokens);
18410
+ emitData(
18411
+ opts,
18412
+ { status: "connected", origin, tokenExpiresAt: outcome.tokens.tokenExpiresAt, storedAt: figmaTokenPath() },
18413
+ () => {
18414
+ process.stdout.write(`Figma is connected \u2014 recording can use the REST channel now.
18415
+ `);
18416
+ process.stdout.write(` credential: ${figmaTokenPath()} (0600; renews itself through the portal)
18417
+ `);
18418
+ }
18419
+ );
18420
+ return;
18421
+ }
18422
+ if (outcome.status === "failed") {
18423
+ fail(opts, ExitCode.ConfirmationRequired, {
18424
+ error: `the Figma connection was not granted: ${outcome.reason}`,
18425
+ code: "figma-connect-declined",
18426
+ remediation: "Nothing was stored. If minds change, run the connect again \u2014 it makes a fresh request."
18427
+ });
18428
+ }
18429
+ fail(opts, ExitCode.ConfirmationRequired, {
18430
+ error: outcome.status === "expired" ? "the Figma connection lapsed before the Allow click" : "the Figma connection is gone \u2014 it lapsed, or was collected by another terminal",
18431
+ code: "figma-connect-lapsed",
18432
+ remediation: "Run the connect again and finish it within its ten-minute window."
18433
+ });
18434
+ }
18435
+ var WAIT_CAP_SECONDS;
18436
+ var init_figma_connect = __esm({
18437
+ "packages/cli/src/commands/figma-connect.ts"() {
18438
+ "use strict";
18439
+ init_src3();
18440
+ init_figma_token();
18441
+ init_invocation();
18442
+ init_output();
18443
+ init_publish_client();
18444
+ WAIT_CAP_SECONDS = 660;
18445
+ }
18446
+ });
18447
+
18189
18448
  // packages/cli/src/commands/share.ts
18190
18449
  var share_exports = {};
18191
18450
  __export(share_exports, {
@@ -18298,13 +18557,13 @@ var init_share = __esm({
18298
18557
  // packages/cli/src/commands/publish.ts
18299
18558
  var publish_exports = {};
18300
18559
  __export(publish_exports, {
18301
- resolveOrigin: () => resolveOrigin,
18560
+ resolveOrigin: () => resolveOrigin2,
18302
18561
  runPublish: () => runPublish
18303
18562
  });
18304
- import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
18305
- import path54 from "node:path";
18563
+ import { existsSync as existsSync46, readFileSync as readFileSync43, rmSync as rmSync10, writeFileSync as writeFileSync25 } from "node:fs";
18564
+ import path56 from "node:path";
18306
18565
  async function runPublish(opts) {
18307
- const bundleDir = path54.resolve(opts.bundleDir);
18566
+ const bundleDir = path56.resolve(opts.bundleDir);
18308
18567
  const bundle = readBundle(opts, bundleDir);
18309
18568
  const report = bundle.report;
18310
18569
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18349,7 +18608,7 @@ async function runPublish(opts) {
18349
18608
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18350
18609
  if (sheetEntry !== void 0) {
18351
18610
  const missingCrops = missingInspectCrops(
18352
- readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
18611
+ readFileSync43(path56.join(bundleDir, sheetEntry.path), "utf8"),
18353
18612
  surface.published.map((p) => p.path)
18354
18613
  );
18355
18614
  if (missingCrops.length > 0) {
@@ -18414,7 +18673,7 @@ async function runPublish(opts) {
18414
18673
  );
18415
18674
  return;
18416
18675
  }
18417
- const origin = resolveOrigin(opts);
18676
+ const origin = resolveOrigin2(opts);
18418
18677
  const client = opts.client ?? httpClient(opts, origin);
18419
18678
  if (opts.approveWait === true) {
18420
18679
  await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
@@ -18448,8 +18707,8 @@ async function runPublish(opts) {
18448
18707
  }
18449
18708
  const uploaded = [];
18450
18709
  for (const object of opened.value.plan.objects) {
18451
- const file = path54.join(bundleDir, object.relPath);
18452
- if (!existsSync44(file)) {
18710
+ const file = path56.join(bundleDir, object.relPath);
18711
+ if (!existsSync46(file)) {
18453
18712
  fail(opts, ExitCode.InputValidation, {
18454
18713
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18455
18714
  code: "planned-file-missing",
@@ -18459,10 +18718,11 @@ async function runPublish(opts) {
18459
18718
  const sent = await client.upload({
18460
18719
  publicationId: opened.value.publicationId,
18461
18720
  relPath: object.relPath,
18462
- bytes: new Uint8Array(readFileSync41(file))
18721
+ bytes: new Uint8Array(readFileSync43(file))
18463
18722
  });
18464
18723
  if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
18465
18724
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18725
+ emitProgress(uploaded.length, opened.value.plan.objects.length, `uploading ${object.relPath}`);
18466
18726
  }
18467
18727
  const committed = await client.commit({ publicationId: opened.value.publicationId });
18468
18728
  if (committed.ok) {
@@ -18504,23 +18764,23 @@ async function runPublish(opts) {
18504
18764
  );
18505
18765
  }
18506
18766
  function readBundle(opts, bundleDir) {
18507
- const manifestPath2 = path54.join(bundleDir, "component.json");
18508
- const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18509
- if (!existsSync44(manifestPath2)) {
18767
+ const manifestPath2 = path56.join(bundleDir, "component.json");
18768
+ const reportPath = path56.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18769
+ if (!existsSync46(manifestPath2)) {
18510
18770
  fail(opts, ExitCode.InputValidation, {
18511
18771
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18512
18772
  code: "not-a-bundle",
18513
18773
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18514
18774
  });
18515
18775
  }
18516
- if (!existsSync44(reportPath)) {
18776
+ if (!existsSync46(reportPath)) {
18517
18777
  fail(opts, ExitCode.InputValidation, {
18518
18778
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18519
18779
  code: "bundle-not-verified",
18520
18780
  remediation: `Run \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` first. Verification is free and needs no account; publishing without it would put a page up with no verdict on it.`
18521
18781
  });
18522
18782
  }
18523
- const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
18783
+ const { manifest } = readBundleManifest(readFileSync43(manifestPath2, "utf8"));
18524
18784
  if (manifest === void 0) {
18525
18785
  fail(opts, ExitCode.InputValidation, {
18526
18786
  error: "component.json did not parse as a bundle manifest",
@@ -18528,7 +18788,7 @@ function readBundle(opts, bundleDir) {
18528
18788
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18529
18789
  });
18530
18790
  }
18531
- const reportText = readFileSync41(reportPath, "utf8");
18791
+ const reportText = readFileSync43(reportPath, "utf8");
18532
18792
  let report;
18533
18793
  try {
18534
18794
  report = JSON.parse(reportText);
@@ -18548,7 +18808,7 @@ function readBundle(opts, bundleDir) {
18548
18808
  }
18549
18809
  return { manifest, report, reportText, files: bundleFiles(bundleDir) };
18550
18810
  }
18551
- function resolveOrigin(opts) {
18811
+ function resolveOrigin2(opts) {
18552
18812
  const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18553
18813
  if (named !== "") return named;
18554
18814
  if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
@@ -18592,11 +18852,11 @@ function refuse2(opts, sent, code, rejoins = false) {
18592
18852
  fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18593
18853
  error: `${sent.refusal}${detail}`,
18594
18854
  code,
18595
- remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
18855
+ remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin2(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
18596
18856
  });
18597
18857
  }
18598
18858
  function pendingApprovalPath() {
18599
- return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
18859
+ return path56.join(path56.dirname(sessionPath()), "pending-publish.json");
18600
18860
  }
18601
18861
  async function runApprovalFlow(opts, client, bundleDir, input) {
18602
18862
  const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
@@ -18606,7 +18866,7 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
18606
18866
  const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
18607
18867
  if (opts.approveStart === true) {
18608
18868
  const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
18609
- writeFileSync23(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
18869
+ writeFileSync25(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
18610
18870
  `, { mode: 384 });
18611
18871
  await endRunPresence(input.componentName);
18612
18872
  emitData(
@@ -18643,9 +18903,9 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
18643
18903
  async function approveWaitPhase(opts, client, bundleDir, subject) {
18644
18904
  const file = pendingApprovalPath();
18645
18905
  let pending;
18646
- if (existsSync44(file)) {
18906
+ if (existsSync46(file)) {
18647
18907
  try {
18648
- const parsed = JSON.parse(readFileSync41(file, "utf8"));
18908
+ const parsed = JSON.parse(readFileSync43(file, "utf8"));
18649
18909
  if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
18650
18910
  pending = { pollSeconds: 3, bundleDir, ...parsed };
18651
18911
  }
@@ -18666,7 +18926,7 @@ async function approveWaitPhase(opts, client, bundleDir, subject) {
18666
18926
  remediation: `Finish that one first (${tendrilCommand(`publish ${pending.bundleDir} --approve-wait`)}), or start this one fresh with --approve-start \u2014 starting one replaces the waiting slot.`
18667
18927
  });
18668
18928
  }
18669
- const done = () => rmSync8(file, { force: true });
18929
+ const done = () => rmSync10(file, { force: true });
18670
18930
  const decided = await waitForApproval(opts, client, pending);
18671
18931
  done();
18672
18932
  if (decided !== "approved") failDecision(opts, decided);
@@ -18735,17 +18995,17 @@ __export(generate_recorded_exports, {
18735
18995
  runGenerateRecorded: () => runGenerateRecorded
18736
18996
  });
18737
18997
  import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
18738
- import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
18739
- import path55 from "node:path";
18998
+ import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
18999
+ import path57 from "node:path";
18740
19000
  async function runGenerateRecorded(opts) {
18741
19001
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18742
- const outDirAbs = path55.resolve(callerCwd, opts.out);
18743
- const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
19002
+ const outDirAbs = path57.resolve(callerCwd, opts.out);
19003
+ const recordedAsPath = path57.resolve(callerCwd, opts.recorded);
18744
19004
  let task;
18745
19005
  let taskName;
18746
19006
  let authoredApi;
18747
19007
  let composition;
18748
- const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
19008
+ const isSet = existsSync47(path57.join(recordedAsPath, "recording-set.json"));
18749
19009
  const registry = TASKS[opts.recorded];
18750
19010
  if (registry !== void 0 && !isSet) {
18751
19011
  task = registry;
@@ -18754,7 +19014,7 @@ async function runGenerateRecorded(opts) {
18754
19014
  try {
18755
19015
  const authored = authorTaskFromSet(recordedAsPath);
18756
19016
  task = authored.task;
18757
- taskName = path55.basename(recordedAsPath);
19017
+ taskName = path57.basename(recordedAsPath);
18758
19018
  authoredApi = authored.api;
18759
19019
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18760
19020
  if (roles.success) composition = roles.data;
@@ -18788,7 +19048,7 @@ async function runGenerateRecorded(opts) {
18788
19048
  warn(opts, `font weights (advisory): the recording implies weights ${g.declared.join(", ")} for "${g.family}" \u2014 cache provides ${g.provided.join(", ")}; nearest-weight rendering may shift stroke density (certification stays family-gated; implied weights can leak across families)`);
18789
19049
  }
18790
19050
  const missing = task.configs.filter(
18791
- (c) => !existsSync45(path55.join(task.set, c.rep, "get_screenshot.json")) || !existsSync45(path55.join(task.set, c.rep, "get_metadata.json")) || !existsSync45(path55.join(task.set, c.rep, "get_design_context.json"))
19051
+ (c) => !existsSync47(path57.join(task.set, c.rep, "get_screenshot.json")) || !existsSync47(path57.join(task.set, c.rep, "get_metadata.json")) || !existsSync47(path57.join(task.set, c.rep, "get_design_context.json"))
18792
19052
  );
18793
19053
  if (missing.length > 0) {
18794
19054
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18858,8 +19118,8 @@ async function runGenerateRecorded(opts) {
18858
19118
  ` : `${line}
18859
19119
  `);
18860
19120
  if (opts.dryRun) {
18861
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
18862
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path55.join(outDirAbs, taskName)})
19121
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path57.join(outDirAbs, taskName) }, () => {
19122
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path57.join(outDirAbs, taskName)})
18863
19123
  `);
18864
19124
  });
18865
19125
  return;
@@ -18882,10 +19142,10 @@ async function runGenerateRecorded(opts) {
18882
19142
  });
18883
19143
  }
18884
19144
  }
18885
- const bundleDir = path55.join(outDirAbs, taskName);
18886
- if (existsSync45(path55.join(bundleDir, "component.json"))) {
19145
+ const bundleDir = path57.join(outDirAbs, taskName);
19146
+ if (existsSync47(path57.join(bundleDir, "component.json"))) {
18887
19147
  try {
18888
- const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
19148
+ const prior = readBundleManifest(readFileSync44(path57.join(bundleDir, "component.json"), "utf8")).manifest;
18889
19149
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18890
19150
  fail(opts, ExitCode.InputValidation, {
18891
19151
  error: `${bundleDir} already holds a bundle bound to recording set "${prior.provenance.recordingSet.path}" \u2014 generating here against a different set would silently rewrite its verification identity`,
@@ -19052,7 +19312,7 @@ init_invocation();
19052
19312
  init_output();
19053
19313
  import { intro, isCancel, outro, password } from "@clack/prompts";
19054
19314
  import fs from "node:fs";
19055
- import path30 from "node:path";
19315
+ import path31 from "node:path";
19056
19316
  var INIT_DESCRIPTION = {
19057
19317
  name: "init",
19058
19318
  summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
@@ -19094,7 +19354,7 @@ async function runInit(flags) {
19094
19354
  printDescription(INIT_DESCRIPTION);
19095
19355
  return;
19096
19356
  }
19097
- const envPath = path30.resolve(process.cwd(), ".env");
19357
+ const envPath = path31.resolve(process.cwd(), ".env");
19098
19358
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
19099
19359
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
19100
19360
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -19115,7 +19375,7 @@ async function runInit(flags) {
19115
19375
  if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
19116
19376
  if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
19117
19377
  const changed = [...next].some(([key, value]) => existing.get(key) !== value);
19118
- const gitignorePath = path30.resolve(process.cwd(), ".gitignore");
19378
+ const gitignorePath = path31.resolve(process.cwd(), ".gitignore");
19119
19379
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
19120
19380
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
19121
19381
  if (flags.dryRun) {
@@ -19171,14 +19431,14 @@ init_invocation();
19171
19431
  init_output();
19172
19432
  init_entitlement();
19173
19433
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
19174
- import { readFileSync as readFileSync22, readdirSync as readdirSync9, existsSync as existsSync24 } from "node:fs";
19434
+ import { readFileSync as readFileSync23, readdirSync as readdirSync9, existsSync as existsSync25 } from "node:fs";
19175
19435
 
19176
19436
  // packages/cli/src/pipeline.ts
19177
19437
  init_src2();
19178
19438
  init_src5();
19179
19439
  init_src4();
19180
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
19181
- import path31 from "node:path";
19440
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
19441
+ import path32 from "node:path";
19182
19442
 
19183
19443
  // packages/cli/src/assets-module.ts
19184
19444
  init_src();
@@ -19514,8 +19774,8 @@ async function runGenerationPipeline(input) {
19514
19774
  });
19515
19775
  const written = [];
19516
19776
  if (!input.dryRun) {
19517
- const dir = path31.resolve(input.outDir, semantics.componentName);
19518
- mkdirSync6(dir, { recursive: true });
19777
+ const dir = path32.resolve(input.outDir, semantics.componentName);
19778
+ mkdirSync7(dir, { recursive: true });
19519
19779
  const files = {
19520
19780
  // Bundle-local tokens: THE emission the component's CSS resolves
19521
19781
  // against — the same artifact the verify harness injects. A preview
@@ -19538,14 +19798,14 @@ async function runGenerationPipeline(input) {
19538
19798
  `
19539
19799
  };
19540
19800
  for (const [name, content] of Object.entries(files)) {
19541
- const filePath = path31.join(dir, name);
19542
- writeFileSync10(filePath, content);
19801
+ const filePath = path32.join(dir, name);
19802
+ writeFileSync11(filePath, content);
19543
19803
  written.push(filePath);
19544
19804
  }
19545
19805
  for (const artifact of emitTokenArtifacts(input.mapping)) {
19546
- const filePath = path31.resolve(input.outDir, artifact.path);
19547
- mkdirSync6(path31.dirname(filePath), { recursive: true });
19548
- writeFileSync10(filePath, artifact.content);
19806
+ const filePath = path32.resolve(input.outDir, artifact.path);
19807
+ mkdirSync7(path32.dirname(filePath), { recursive: true });
19808
+ writeFileSync11(filePath, artifact.content);
19549
19809
  written.push(filePath);
19550
19810
  }
19551
19811
  }
@@ -19603,7 +19863,7 @@ var GENERATE_DESCRIPTION = {
19603
19863
  function resolveProvidedSource(flags, contextFile) {
19604
19864
  let raw;
19605
19865
  try {
19606
- raw = readFileSync22(contextFile, "utf8");
19866
+ raw = readFileSync23(contextFile, "utf8");
19607
19867
  } catch {
19608
19868
  fail(flags, ExitCode.InputValidation, {
19609
19869
  error: `Cannot read context file "${contextFile}".`,
@@ -19723,11 +19983,11 @@ token mapping (${mapping.flat.length} variables):
19723
19983
  let initialCode;
19724
19984
  let initialSemantics;
19725
19985
  try {
19726
- if (existsSync24(flags.out)) {
19986
+ if (existsSync25(flags.out)) {
19727
19987
  for (const entry of readdirSync9(flags.out)) {
19728
19988
  const cjPath = `${flags.out}/${entry}/component.json`;
19729
- if (!existsSync24(cjPath)) continue;
19730
- const cj = JSON.parse(readFileSync22(cjPath, "utf8"));
19989
+ if (!existsSync25(cjPath)) continue;
19990
+ const cj = JSON.parse(readFileSync23(cjPath, "utf8"));
19731
19991
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
19732
19992
  previousApi = JSON.stringify({
19733
19993
  componentName: cj.name,
@@ -19735,14 +19995,14 @@ token mapping (${mapping.flat.length} variables):
19735
19995
  });
19736
19996
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
19737
19997
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
19738
- if (flags.refine && existsSync24(tsxPath) && existsSync24(cssPath)) {
19998
+ if (flags.refine && existsSync25(tsxPath) && existsSync25(cssPath)) {
19739
19999
  initialCode = {
19740
- tsx: readFileSync22(tsxPath, "utf8"),
19741
- css: readFileSync22(cssPath, "utf8")
20000
+ tsx: readFileSync23(tsxPath, "utf8"),
20001
+ css: readFileSync23(cssPath, "utf8")
19742
20002
  };
19743
20003
  const semPath = `${flags.out}/${entry}/semantics.json`;
19744
- if (existsSync24(semPath)) {
19745
- initialSemantics = JSON.parse(readFileSync22(semPath, "utf8"));
20004
+ if (existsSync25(semPath)) {
20005
+ initialSemantics = JSON.parse(readFileSync23(semPath, "utf8"));
19746
20006
  }
19747
20007
  }
19748
20008
  break;
@@ -20159,6 +20419,17 @@ function buildProgram() {
20159
20419
  ...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
20160
20420
  });
20161
20421
  });
20422
+ program.command("figma-connect").description("Grant this machine read access to your Figma files over Figma's API (approve in your browser, once) \u2014 recording then escapes the MCP daily call limit.").option("--to <url>", "the portal that brokers the connection (defaults to your signed-in portal)").option("--start", "agents: start the browser consent and return the link immediately").option("--wait", "agents: wait for the Allow click started by --start").action(async (_o, cmd) => {
20423
+ const flags = globalFlags(cmd.parent);
20424
+ const local = cmd.opts();
20425
+ const { runFigmaConnect: runFigmaConnect2 } = await Promise.resolve().then(() => (init_figma_connect(), figma_connect_exports));
20426
+ await runFigmaConnect2({
20427
+ ...flags,
20428
+ ...local["to"] !== void 0 ? { to: local["to"] } : {},
20429
+ ...local["start"] !== void 0 ? { start: local["start"] } : {},
20430
+ ...local["wait"] !== void 0 ? { wait: local["wait"] } : {}
20431
+ });
20432
+ });
20162
20433
  program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
20163
20434
  const flags = globalFlags(cmd.parent);
20164
20435
  const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));