@tendrilapp/cli 0.1.43 → 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) {
@@ -8468,6 +8468,9 @@ var init_publish_client = __esm({
8468
8468
  pollApproval(input) {
8469
8469
  return this.json("GET", `/api/publish-approvals/${encodeURIComponent(input.approvalId)}`, null, true);
8470
8470
  }
8471
+ whoami() {
8472
+ return this.json("GET", "/api/whoami", null, true);
8473
+ }
8471
8474
  begin(input) {
8472
8475
  return this.json("POST", "/api/publications", input, true);
8473
8476
  }
@@ -8570,18 +8573,50 @@ var init_publish_client = __esm({
8570
8573
  }
8571
8574
  });
8572
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
+
8573
8608
  // packages/cli/src/entitlement.ts
8574
- 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";
8575
8610
  import crypto from "node:crypto";
8576
8611
  import os5 from "node:os";
8577
- import path28 from "node:path";
8612
+ import path29 from "node:path";
8578
8613
  function entitlementPath() {
8579
- 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");
8580
8615
  }
8581
8616
  function readStoredEntitlement(file = entitlementPath()) {
8582
- if (!existsSync22(file)) return void 0;
8617
+ if (!existsSync23(file)) return void 0;
8583
8618
  try {
8584
- const parsed = JSON.parse(readFileSync20(file, "utf8"));
8619
+ const parsed = JSON.parse(readFileSync21(file, "utf8"));
8585
8620
  if (typeof parsed.token !== "string" || typeof parsed.lastRefreshAt !== "number") return void 0;
8586
8621
  return { token: parsed.token, lastRefreshAt: parsed.lastRefreshAt };
8587
8622
  } catch {
@@ -8589,8 +8624,8 @@ function readStoredEntitlement(file = entitlementPath()) {
8589
8624
  }
8590
8625
  }
8591
8626
  function writeStoredEntitlement(stored, file = entitlementPath()) {
8592
- mkdirSync5(path28.dirname(file), { recursive: true });
8593
- writeFileSync9(file, `${JSON.stringify(stored, null, 2)}
8627
+ mkdirSync6(path29.dirname(file), { recursive: true });
8628
+ writeFileSync10(file, `${JSON.stringify(stored, null, 2)}
8594
8629
  `);
8595
8630
  chmodSync2(file, 384);
8596
8631
  }
@@ -8674,9 +8709,9 @@ var init_entitlement = __esm({
8674
8709
 
8675
8710
  // packages/cli/src/commands/doctor.ts
8676
8711
  import { spawnSync } from "node:child_process";
8677
- 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";
8678
8713
  import os6 from "node:os";
8679
- import path29 from "node:path";
8714
+ import path30 from "node:path";
8680
8715
  function withDeadline(work, ms) {
8681
8716
  return Promise.race([
8682
8717
  work,
@@ -8736,17 +8771,17 @@ async function runDoctorChecks(options) {
8736
8771
  remediation: "Install Google Chrome (or Chromium), or set CHROME_PATH to a Chromium-family browser binary."
8737
8772
  });
8738
8773
  }
8739
- const fontManifest = path29.join(fontCacheDir(), "manifest.json");
8774
+ const fontManifest = path30.join(fontCacheDir(), "manifest.json");
8740
8775
  checks.push(
8741
- 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)` } : {
8742
8777
  name: "font-cache",
8743
8778
  ok: true,
8744
8779
  detail: `font cache empty at ${fontCacheDir()} \u2014 normal on a fresh machine; faces resolve per design system at first use`,
8745
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.`
8746
8781
  }
8747
8782
  );
8748
- const pluginRoot = path29.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8749
- if (existsSync23(pluginRoot)) {
8783
+ const pluginRoot = path30.join(os6.homedir(), ".claude", "plugins", "cache", "tendrilapp", "tendril");
8784
+ if (existsSync24(pluginRoot)) {
8750
8785
  try {
8751
8786
  const versions = readdirSync8(pluginRoot).filter((v) => /^\d+\.\d+\.\d+$/.test(v));
8752
8787
  const newest = versions.sort((a, b) => versionIsNewer(a, b) ? 1 : -1)[0];
@@ -8783,12 +8818,15 @@ async function runDoctorChecks(options) {
8783
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 }
8784
8819
  );
8785
8820
  checks.push(await portalSessionCheck(options.fetchImpl ?? fetch));
8821
+ checks.push(figmaRestCheck());
8786
8822
  const figmaToken = resolveCredential("FIGMA_TOKEN");
8787
- checks.push({
8788
- name: "figma-pat",
8789
- ok: true,
8790
- 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"
8791
- });
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
+ }
8792
8830
  return {
8793
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),
8794
8832
  checks
@@ -8829,6 +8867,25 @@ async function portalSessionCheck(fetchImpl) {
8829
8867
  ...daysLeft !== null && daysLeft <= 7 ? { remediation: `That is ${String(daysLeft)} day(s) away \u2014 run ${tendrilCommand("login")} soon for a fresh session.` } : {}
8830
8868
  };
8831
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
+ }
8832
8889
  function probeVersion(binary) {
8833
8890
  const windowsShim = /\.(cmd|bat)$/i.test(binary);
8834
8891
  const res = windowsShim ? spawnSync(`"${binary}" --version`, { shell: true, timeout: 5e3, encoding: "utf8" }) : spawnSync(binary, ["--version"], { timeout: 5e3, encoding: "utf8" });
@@ -8903,6 +8960,7 @@ var init_doctor = __esm({
8903
8960
  init_invocation();
8904
8961
  init_output();
8905
8962
  init_publish_client();
8963
+ init_figma_token();
8906
8964
  init_entitlement();
8907
8965
  DEFAULT_MCP_URL = "http://127.0.0.1:3845/mcp";
8908
8966
  DOCTOR_DESCRIPTION = {
@@ -10018,8 +10076,8 @@ var init_engine_curated = __esm({
10018
10076
  });
10019
10077
 
10020
10078
  // packages/generate/src/loop.ts
10021
- import { existsSync as existsSync25, mkdirSync as mkdirSync7, readFileSync as readFileSync23, renameSync, writeFileSync as writeFileSync11 } from "node:fs";
10022
- 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";
10023
10081
  import { z as z13 } from "zod";
10024
10082
  function objective(scores, behaviors) {
10025
10083
  const vals = scores.map((s) => Math.min(s.similarity, s.inkRecall));
@@ -10061,9 +10119,9 @@ ${absentLines.join("\n")}` : ""}
10061
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."}`;
10062
10120
  }
10063
10121
  function archivePriorRun(outDir) {
10064
- 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;
10065
10123
  let n = 1;
10066
- while (existsSync25(`${outDir}-prev-${n}`)) n += 1;
10124
+ while (existsSync26(`${outDir}-prev-${n}`)) n += 1;
10067
10125
  renameSync(outDir, `${outDir}-prev-${n}`);
10068
10126
  return `${outDir}-prev-${n}`;
10069
10127
  }
@@ -10072,14 +10130,14 @@ async function runEngineLoop(opts) {
10072
10130
  const plateau = opts.plateau ?? 2;
10073
10131
  const progress = opts.onProgress ?? (() => {
10074
10132
  });
10075
- const statePath = path32.join(opts.outDir, "loop-state.json");
10076
- 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);
10077
10135
  if (!resuming) {
10078
10136
  const archived = archivePriorRun(opts.outDir);
10079
10137
  if (archived !== void 0) progress(`previous run archived to ${archived}`);
10080
10138
  }
10081
- mkdirSync7(opts.outDir, { recursive: true });
10082
- const scratch = path32.join(opts.outDir, ".candidate");
10139
+ mkdirSync8(opts.outDir, { recursive: true });
10140
+ const scratch = path33.join(opts.outDir, ".candidate");
10083
10141
  let attempts = [];
10084
10142
  let log = [];
10085
10143
  let best;
@@ -10087,7 +10145,7 @@ async function runEngineLoop(opts) {
10087
10145
  let nonAccepted = 0;
10088
10146
  let stopReason = "max-iterations";
10089
10147
  if (resuming) {
10090
- const restored = LoopStateSchema.parse(JSON.parse(readFileSync23(statePath, "utf8")));
10148
+ const restored = LoopStateSchema.parse(JSON.parse(readFileSync24(statePath, "utf8")));
10091
10149
  attempts = restored.attempts;
10092
10150
  log = restored.iterations;
10093
10151
  spentUsd = restored.spentUsd;
@@ -10102,12 +10160,12 @@ async function runEngineLoop(opts) {
10102
10160
  progress(`resumed: ${log.length} iteration(s), $${spentUsd.toFixed(3)} spent, best pass=${best?.objective[0] ?? 0}`);
10103
10161
  }
10104
10162
  const persist = () => {
10105
- 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)}
10106
10164
  `);
10107
10165
  };
10108
10166
  const writeCandidate = (files) => {
10109
- mkdirSync7(scratch, { recursive: true });
10110
- 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);
10111
10169
  };
10112
10170
  const scoreCandidate = async (candidate, iter, usd, modelMs) => {
10113
10171
  writeCandidate(candidate.files);
@@ -10165,8 +10223,8 @@ async function runEngineLoop(opts) {
10165
10223
  const usd = candidate.usage?.usd ?? 0;
10166
10224
  spentUsd += usd;
10167
10225
  if (candidate.raw !== void 0) {
10168
- mkdirSync7(path32.join(opts.outDir, "responses"), { recursive: true });
10169
- 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);
10170
10228
  }
10171
10229
  if (candidate.files[opts.entry] === void 0 || candidate.files["styles.css"] === void 0) {
10172
10230
  const finish = candidate.usage?.finishReason ?? "?";
@@ -10192,10 +10250,10 @@ async function runEngineLoop(opts) {
10192
10250
  }
10193
10251
  }
10194
10252
  }
10195
- 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);
10196
10254
  const final = best !== void 0 ? await opts.score(opts.outDir) : { scores: [], behaviors: [] };
10197
- writeFileSync11(
10198
- path32.join(opts.outDir, "run-log.json"),
10255
+ writeFileSync12(
10256
+ path33.join(opts.outDir, "run-log.json"),
10199
10257
  `${JSON.stringify(
10200
10258
  {
10201
10259
  ...opts.meta,
@@ -10262,8 +10320,8 @@ var init_loop2 = __esm({
10262
10320
  });
10263
10321
 
10264
10322
  // packages/generate/src/brief.ts
10265
- import { existsSync as existsSync26, readFileSync as readFileSync24 } from "node:fs";
10266
- import path33 from "node:path";
10323
+ import { existsSync as existsSync27, readFileSync as readFileSync25 } from "node:fs";
10324
+ import path34 from "node:path";
10267
10325
  import { PNG as PNG3 } from "pngjs";
10268
10326
  function singleAxes2(name) {
10269
10327
  const parsed = parseVariantAxes(name);
@@ -10703,15 +10761,15 @@ function authorBehaviors(api, extras = {}) {
10703
10761
  };
10704
10762
  }
10705
10763
  function envelopeText(file) {
10706
- return envelopeFirstTextPart(JSON.parse(readFileSync24(file, "utf8")));
10764
+ return envelopeFirstTextPart(JSON.parse(readFileSync25(file, "utf8")));
10707
10765
  }
10708
10766
  function metadataText(file) {
10709
- return envelopeTextContent(JSON.parse(readFileSync24(file, "utf8")));
10767
+ return envelopeTextContent(JSON.parse(readFileSync25(file, "utf8")));
10710
10768
  }
10711
10769
  function dismissEvidence(setDir, repSlugs) {
10712
10770
  for (const slug of repSlugs) {
10713
- const f = path33.join(setDir, slug, "get_design_context.json");
10714
- if (!existsSync26(f)) continue;
10771
+ const f = path34.join(setDir, slug, "get_design_context.json");
10772
+ if (!existsSync27(f)) continue;
10715
10773
  const text = envelopeText(f);
10716
10774
  const propHit = /[{,]\s*(\w*dismiss\w*)\s*=\s*true\b/i.exec(text);
10717
10775
  if (propHit !== null) return { evidence: `emission prop "${propHit[1]}"`, visibleReps: [] };
@@ -10738,9 +10796,9 @@ function dismissEvidence(setDir, repSlugs) {
10738
10796
  const glyphIsTheComponent = (() => {
10739
10797
  const slugToCheck = vis.visibleIn[0];
10740
10798
  if (slugToCheck === void 0) return false;
10741
- const metaFile = path33.join(setDir, slugToCheck, "get_metadata.json");
10799
+ const metaFile = path34.join(setDir, slugToCheck, "get_metadata.json");
10742
10800
  try {
10743
- const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync24(metaFile, "utf8"))));
10801
+ const root = parseMetadataStructure(envelopeTextContent(JSON.parse(readFileSync25(metaFile, "utf8"))));
10744
10802
  if (root.children.length !== 1) return false;
10745
10803
  const contains = (n) => normalizedLayerName(n.name) === norm2 || n.children.some(contains);
10746
10804
  return contains(root.children[0]);
@@ -10760,10 +10818,10 @@ function dismissEvidence(setDir, repSlugs) {
10760
10818
  return void 0;
10761
10819
  }
10762
10820
  function recordedReferencePng(setDir, slug) {
10763
- const f = path33.join(setDir, slug, "get_screenshot.json");
10764
- if (!existsSync26(f)) return void 0;
10821
+ const f = path34.join(setDir, slug, "get_screenshot.json");
10822
+ if (!existsSync27(f)) return void 0;
10765
10823
  try {
10766
- 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");
10767
10825
  return env?.data === void 0 ? void 0 : Uint8Array.from(Buffer.from(env.data, "base64"));
10768
10826
  } catch {
10769
10827
  return void 0;
@@ -10843,13 +10901,13 @@ function recordedFontNeeds(setDir, opts = {}) {
10843
10901
  }
10844
10902
  }
10845
10903
  const manifest = loadManifest(setDir);
10846
- const setDefs = path33.join(setDir, "get_variable_defs.json");
10847
- if (existsSync26(setDefs)) fromDefs(envelopeText(setDefs));
10904
+ const setDefs = path34.join(setDir, "get_variable_defs.json");
10905
+ if (existsSync27(setDefs)) fromDefs(envelopeText(setDefs));
10848
10906
  for (const rep of manifest.reps) {
10849
- const ctx = path33.join(setDir, rep.slug, "get_design_context.json");
10850
- if (existsSync26(ctx)) fromEmission(envelopeText(ctx));
10851
- const defs = path33.join(setDir, rep.slug, "get_variable_defs.json");
10852
- 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));
10853
10911
  }
10854
10912
  return [...byFamily.entries()].map(([family, paired]) => {
10855
10913
  const weights = /* @__PURE__ */ new Set([...paired, ...opts.pairedOnly === true ? [] : unpaired]);
@@ -10860,10 +10918,10 @@ function symbolFontGlyphCount(setDir, reps) {
10860
10918
  const PUA = /[\uE000-\uF8FF\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}]/u;
10861
10919
  const glyphs = /* @__PURE__ */ new Set();
10862
10920
  for (const rep of reps) {
10863
- const file = path33.join(setDir, rep, "get_metadata.json");
10864
- if (!existsSync26(file)) continue;
10921
+ const file = path34.join(setDir, rep, "get_metadata.json");
10922
+ if (!existsSync27(file)) continue;
10865
10923
  try {
10866
- 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");
10867
10925
  for (const m of text.matchAll(/<text\s[^>]*name="([^"]*)"/g)) {
10868
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)));
10869
10927
  if (PUA.test(name)) glyphs.add(name);
@@ -10889,8 +10947,8 @@ function recordedTextSlots(setDir, repSlugs) {
10889
10947
  const propRep = [];
10890
10948
  const perRep = [];
10891
10949
  for (const slug of repSlugs) {
10892
- const f = path33.join(setDir, slug, "get_design_context.json");
10893
- if (!existsSync26(f)) continue;
10950
+ const f = path34.join(setDir, slug, "get_design_context.json");
10951
+ if (!existsSync27(f)) continue;
10894
10952
  const code = envelopeText(f);
10895
10953
  const props = /* @__PURE__ */ new Map();
10896
10954
  for (const m of code.matchAll(/[{,]\s*(\w+)\s*=\s*"((?:[^"\\]|\\.)*)"/g)) {
@@ -10916,8 +10974,8 @@ function recordedTextSlots(setDir, repSlugs) {
10916
10974
  const axisValuesBySlug = /* @__PURE__ */ new Map();
10917
10975
  const valuesByAxis = /* @__PURE__ */ new Map();
10918
10976
  for (const slug of repSlugs) {
10919
- const metaFile = path33.join(setDir, slug, "get_metadata.json");
10920
- if (!existsSync26(metaFile)) continue;
10977
+ const metaFile = path34.join(setDir, slug, "get_metadata.json");
10978
+ if (!existsSync27(metaFile)) continue;
10921
10979
  const name = symbolName(metadataText(metaFile));
10922
10980
  if (name === void 0) continue;
10923
10981
  const values = /* @__PURE__ */ new Set();
@@ -11032,8 +11090,8 @@ function authorTaskFromSet(setDir, opts = {}) {
11032
11090
  const poses = [];
11033
11091
  const missing = [];
11034
11092
  for (const rep of manifest.reps) {
11035
- const metaFile = path33.join(setDir, rep.slug, "get_metadata.json");
11036
- if (!existsSync26(metaFile)) {
11093
+ const metaFile = path34.join(setDir, rep.slug, "get_metadata.json");
11094
+ if (!existsSync27(metaFile)) {
11037
11095
  missing.push(rep.slug);
11038
11096
  continue;
11039
11097
  }
@@ -11047,8 +11105,8 @@ function authorTaskFromSet(setDir, opts = {}) {
11047
11105
  if (missing.length > 0) {
11048
11106
  throw new Error(`recording set ${setDir} is missing metadata for ${missing.length} rep(s): ${missing.slice(0, 5).join(", ")}${missing.length > 5 ? ", \u2026" : ""}`);
11049
11107
  }
11050
- const setMeta = path33.join(setDir, "get_metadata.json");
11051
- 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);
11052
11110
  const defaults = { ...manifest.defaults ?? {}, ...opts.defaults ?? {} };
11053
11111
  const recordedFonts = recordedFontFamilies(setDir);
11054
11112
  const textSlots = recordedTextSlots(setDir, manifest.reps.map((r) => r.slug));
@@ -11249,17 +11307,17 @@ var init_brief = __esm({
11249
11307
  });
11250
11308
 
11251
11309
  // packages/generate/src/segments.ts
11252
- import { existsSync as existsSync27, readFileSync as readFileSync25, readdirSync as readdirSync10 } from "node:fs";
11253
- 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";
11254
11312
  function repText(set, rep, tool) {
11255
- 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"));
11256
11314
  return tool === "get_design_context" ? envelopeFirstTextPart(env) : envelopeTextContent(env);
11257
11315
  }
11258
11316
  function refPngDims(set, rep) {
11259
- const f = path34.join(set, rep, "get_screenshot.json");
11260
- if (!existsSync27(f)) return void 0;
11317
+ const f = path35.join(set, rep, "get_screenshot.json");
11318
+ if (!existsSync28(f)) return void 0;
11261
11319
  try {
11262
- 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");
11263
11321
  if (env?.data === void 0) return void 0;
11264
11322
  const buf = Buffer.from(env.data, "base64");
11265
11323
  if (buf.length < 24 || buf.readUInt32BE(0) !== 2303741511) return void 0;
@@ -11325,20 +11383,20 @@ DROPPED from the map, untrustworthy in the source export \u2014 for these, each
11325
11383
  }
11326
11384
  function buildSegments(task, mode = "fenced") {
11327
11385
  const SET = task.set;
11328
- let defsRecorded = existsSync27(path34.join(SET, "get_variable_defs.json"));
11386
+ let defsRecorded = existsSync28(path35.join(SET, "get_variable_defs.json"));
11329
11387
  let rawDefs = {};
11330
- if (existsSync27(path34.join(SET, "get_variable_defs.json"))) {
11331
- 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"))) || "{}";
11332
11390
  try {
11333
11391
  rawDefs = JSON.parse(text);
11334
11392
  } catch {
11335
11393
  }
11336
11394
  } else {
11337
11395
  for (const cfg of task.configs) {
11338
- const f = path34.join(SET, cfg.rep, "get_variable_defs.json");
11339
- if (!existsSync27(f)) continue;
11396
+ const f = path35.join(SET, cfg.rep, "get_variable_defs.json");
11397
+ if (!existsSync28(f)) continue;
11340
11398
  defsRecorded = true;
11341
- const text = envelopeFirstTextPart(JSON.parse(readFileSync25(f, "utf8"))) || "{}";
11399
+ const text = envelopeFirstTextPart(JSON.parse(readFileSync26(f, "utf8"))) || "{}";
11342
11400
  try {
11343
11401
  for (const [k, v] of Object.entries(JSON.parse(text))) rawDefs[k] ??= v;
11344
11402
  } catch {
@@ -11346,8 +11404,8 @@ function buildSegments(task, mode = "fenced") {
11346
11404
  }
11347
11405
  }
11348
11406
  const emissionTexts = task.configs.map((cfg) => {
11349
- const f = path34.join(SET, cfg.rep, "get_design_context.json");
11350
- 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"))) : "";
11351
11409
  });
11352
11410
  const { map, note } = cleanTokenMap(rawDefs, emissionTexts);
11353
11411
  const defs = JSON.stringify(map, null, 1);
@@ -11365,9 +11423,9 @@ TOKEN MAP NEVER RECORDED: this set holds no get_variable_defs envelope, so wheth
11365
11423
  for (const cfg of task.configs) {
11366
11424
  const meta = stripFigmaInstructions(repText(SET, cfg.rep, "get_metadata"));
11367
11425
  const emission = stripFigmaInstructions(repText(SET, cfg.rep, "get_design_context"));
11368
- 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}:
11369
11427
  \`\`\`svg
11370
- ${readFileSync25(path34.join(SET, cfg.rep, f), "utf8")}
11428
+ ${readFileSync26(path35.join(SET, cfg.rep, f), "utf8")}
11371
11429
  \`\`\``).join("\n");
11372
11430
  const refNote = (() => {
11373
11431
  const dims = refPngDims(SET, cfg.rep);
@@ -11403,7 +11461,7 @@ Optionally a third block with \`/* FILE: tokens.css */\`.`);
11403
11461
  } else {
11404
11462
  parts.push(`
11405
11463
  ## Output format
11406
- 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.`);
11407
11465
  }
11408
11466
  return parts.join("\n");
11409
11467
  }
@@ -11471,8 +11529,8 @@ var init_adapter = __esm({
11471
11529
 
11472
11530
  // packages/generate/src/bundle-emit.ts
11473
11531
  import { createHash as createHash6 } from "node:crypto";
11474
- import { copyFileSync, existsSync as existsSync28, mkdirSync as mkdirSync8, readFileSync as readFileSync26, readdirSync as readdirSync11, rmSync as rmSync4, writeFileSync as writeFileSync12 } from "node:fs";
11475
- 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";
11476
11534
  function pinFromConfigs(configs) {
11477
11535
  const domains = /* @__PURE__ */ new Map();
11478
11536
  const kinds = /* @__PURE__ */ new Map();
@@ -11541,9 +11599,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11541
11599
  const notices = [];
11542
11600
  const licenseTexts = /* @__PURE__ */ new Map();
11543
11601
  for (const face of faces) {
11544
- const src = path35.join(cacheDir, path35.basename(face.file));
11545
- const target = `./fonts/${path35.basename(face.file)}`;
11546
- 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";
11547
11605
  const family = face.family.replace(/['\\]/g, "").replace(/\*\//g, "");
11548
11606
  const decl = `@font-face { font-family: '${family}'; font-weight: ${face.weight}; font-style: normal; src: url(${target}) format('${format}'); }`;
11549
11607
  const license = normalizeFontLicense(face.license);
@@ -11581,14 +11639,14 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11581
11639
  `/* ${decl} */`
11582
11640
  );
11583
11641
  }
11584
- } else if (existsSync28(src) && createHash6("sha256").update(readFileSync26(src)).digest("hex") === face.sha256) {
11585
- mkdirSync8(path35.join(bundleDir, "fonts"), { recursive: true });
11586
- 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)));
11587
11645
  licenseTexts.set(terms.file, terms.text);
11588
11646
  const upstream = upstreamAttribution(face);
11589
11647
  notices.push(
11590
11648
  "",
11591
- `${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)}`,
11592
11650
  ` licence: ${terms.title} \u2014 full text in ./${terms.file}`,
11593
11651
  ` source: ${face.source}`,
11594
11652
  ` sha256: ${face.sha256}`,
@@ -11602,9 +11660,9 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11602
11660
  }
11603
11661
  if (lines.length === 0) return null;
11604
11662
  if (notices.length > 0) {
11605
- const fontsDir = path35.join(bundleDir, "fonts");
11606
- for (const [file, text] of licenseTexts) writeFileSync12(path35.join(fontsDir, file), text);
11607
- 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")}
11608
11666
  `);
11609
11667
  header.push(
11610
11668
  "/* REDISTRIBUTION: the face files in ./fonts/ are redistributed under the licences",
@@ -11616,10 +11674,10 @@ function fontsCssFor(faces, bundleDir, cacheDir = DEFAULT_FONT_CACHE, substitute
11616
11674
  `;
11617
11675
  }
11618
11676
  function countLatticeSymbols(setDir) {
11619
- const manifestFile = path35.join(setDir, "recording-set.json");
11620
- if (existsSync28(manifestFile)) {
11677
+ const manifestFile = path36.join(setDir, "recording-set.json");
11678
+ if (existsSync29(manifestFile)) {
11621
11679
  try {
11622
- const stored = JSON.parse(readFileSync26(manifestFile, "utf8"));
11680
+ const stored = JSON.parse(readFileSync27(manifestFile, "utf8"));
11623
11681
  if (stored.variantScope !== "component-set") return null;
11624
11682
  const lattice = stored.latticeNames;
11625
11683
  if (lattice !== void 0 && lattice.length > 0) return lattice.length;
@@ -11627,13 +11685,13 @@ function countLatticeSymbols(setDir) {
11627
11685
  }
11628
11686
  }
11629
11687
  const files = [
11630
- path35.join(setDir, "get_metadata.json"),
11631
- ...existsSync28(setDir) ? readdirSync11(setDir).filter((f) => /^get_metadata-.*\.json$/.test(f)).map((f) => path35.join(setDir, f)) : []
11632
- ].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));
11633
11691
  if (files.length === 0) return null;
11634
11692
  let count = 0;
11635
11693
  for (const f of files) {
11636
- const text = envelopeTextContent(JSON.parse(readFileSync26(f, "utf8")));
11694
+ const text = envelopeTextContent(JSON.parse(readFileSync27(f, "utf8")));
11637
11695
  count += [...text.matchAll(/name="([^"]*)"/g)].filter((m) => m[1].includes("=")).length;
11638
11696
  }
11639
11697
  return count > 0 ? count : null;
@@ -11641,21 +11699,21 @@ function countLatticeSymbols(setDir) {
11641
11699
  function recordingSetHash(setDir, configs) {
11642
11700
  const relPaths = [];
11643
11701
  for (const name of ["recording-set.json", "completeness-manifest.json", "get_variable_defs.json", "get_metadata.json"]) {
11644
- if (existsSync28(path35.join(setDir, name))) relPaths.push(name);
11702
+ if (existsSync29(path36.join(setDir, name))) relPaths.push(name);
11645
11703
  }
11646
11704
  for (const cfg of configs) {
11647
11705
  for (const f of ["get_design_context.json", "get_metadata.json", "get_screenshot.json", "get_variable_defs.json"]) {
11648
- 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}`);
11649
11707
  }
11650
- if (existsSync28(path35.join(setDir, cfg.rep))) {
11651
- 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-"))) {
11652
11710
  relPaths.push(`${cfg.rep}/${asset}`);
11653
11711
  }
11654
11712
  }
11655
11713
  }
11656
11714
  return hashRecordingSet(
11657
11715
  relPaths,
11658
- (p) => new Uint8Array(readFileSync26(path35.join(setDir, p))),
11716
+ (p) => new Uint8Array(readFileSync27(path36.join(setDir, p))),
11659
11717
  (chunks) => {
11660
11718
  const h = createHash6("sha256");
11661
11719
  for (const c of chunks) h.update(c);
@@ -11696,8 +11754,8 @@ function emitBundleV1(opts) {
11696
11754
  const prelude = opts.behaviors.filter((b) => b.id.startsWith("prelude:"));
11697
11755
  const parity = opts.behaviors.filter((b) => b.id.startsWith("parity:"));
11698
11756
  const contract = opts.behaviors.filter((b) => CONTRACT_CHECK_IDS.has(b.id));
11699
- const cssFiles = ["styles.css", "tokens.css"].map((f) => path35.join(opts.bundleDir, f)).filter((f) => existsSync28(f));
11700
- 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"));
11701
11759
  const requiredFonts = requiredFontsManifest(families, opts.fontCacheDir).map((f) => ({
11702
11760
  family: f.family,
11703
11761
  weight: f.weight,
@@ -11726,7 +11784,7 @@ function emitBundleV1(opts) {
11726
11784
  // resolvable via verify's --set override).
11727
11785
  path: (() => {
11728
11786
  const base = process.env["INIT_CWD"] ?? process.cwd();
11729
- const rel = path35.relative(base, opts.task.set);
11787
+ const rel = path36.relative(base, opts.task.set);
11730
11788
  return rel !== "" && !rel.startsWith("..") ? rel : opts.task.set;
11731
11789
  })(),
11732
11790
  component: opts.componentName,
@@ -11763,25 +11821,25 @@ function emitBundleV1(opts) {
11763
11821
  })
11764
11822
  };
11765
11823
  const written = [];
11766
- const manifestPath2 = path35.join(opts.bundleDir, "component.json");
11767
- writeFileSync12(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11824
+ const manifestPath2 = path36.join(opts.bundleDir, "component.json");
11825
+ writeFileSync13(manifestPath2, `${JSON.stringify(manifest, null, 2)}
11768
11826
  `);
11769
11827
  written.push(manifestPath2);
11770
- const stylesPath = path35.join(opts.bundleDir, "styles.css");
11771
- if (existsSync28(stylesPath)) {
11828
+ const stylesPath = path36.join(opts.bundleDir, "styles.css");
11829
+ if (existsSync29(stylesPath)) {
11772
11830
  const comment = cssProvenanceComment({ pass, scored: statuses.length, certified, latticeConfigs: lattice });
11773
- const current = readFileSync26(stylesPath, "utf8");
11831
+ const current = readFileSync27(stylesPath, "utf8");
11774
11832
  const stripped = current.replace(/^\/\* tendril bundle v\d+ [^]*?\*\/\n/, "");
11775
- writeFileSync12(stylesPath, `${comment}
11833
+ writeFileSync13(stylesPath, `${comment}
11776
11834
  ${stripped}`);
11777
11835
  written.push(stylesPath);
11778
11836
  }
11779
- const fontsCssPath = path35.join(opts.bundleDir, "fonts.css");
11780
- rmSync4(path35.join(opts.bundleDir, "fonts"), { recursive: true, force: true });
11781
- 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 });
11782
11840
  const fontsCss = fontsCssFor(requiredFontsManifest(families, opts.fontCacheDir), opts.bundleDir, opts.fontCacheDir, opts.substitutedFamilies ?? []);
11783
11841
  if (fontsCss !== null) {
11784
- writeFileSync12(fontsCssPath, fontsCss);
11842
+ writeFileSync13(fontsCssPath, fontsCss);
11785
11843
  written.push(fontsCssPath);
11786
11844
  }
11787
11845
  const unrecorded = lattice === null ? null : Math.max(0, lattice - statuses.length);
@@ -12210,8 +12268,8 @@ DEALINGS IN THE FONT SOFTWARE.
12210
12268
 
12211
12269
  // packages/generate/src/compose-pins.ts
12212
12270
  import { createHash as createHash7 } from "node:crypto";
12213
- import { existsSync as existsSync29, readFileSync as readFileSync27, readdirSync as readdirSync12, realpathSync as realpathSync3, statSync as statSync4 } from "node:fs";
12214
- 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";
12215
12273
  function bundleDirs(roots, depth = 4) {
12216
12274
  const found = [];
12217
12275
  const seen = /* @__PURE__ */ new Set();
@@ -12220,11 +12278,11 @@ function bundleDirs(roots, depth = 4) {
12220
12278
  try {
12221
12279
  key = realpathSync3(dir);
12222
12280
  } catch {
12223
- key = path36.resolve(dir);
12281
+ key = path37.resolve(dir);
12224
12282
  }
12225
12283
  if (seen.has(key)) return;
12226
12284
  seen.add(key);
12227
- if (existsSync29(path36.join(dir, "component.json"))) {
12285
+ if (existsSync30(path37.join(dir, "component.json"))) {
12228
12286
  found.push(key);
12229
12287
  return;
12230
12288
  }
@@ -12237,14 +12295,14 @@ function bundleDirs(roots, depth = 4) {
12237
12295
  }
12238
12296
  for (const e of entries) {
12239
12297
  if (e === "node_modules" || e.startsWith(".")) continue;
12240
- const full = path36.join(dir, e);
12298
+ const full = path37.join(dir, e);
12241
12299
  try {
12242
12300
  if (statSync4(full).isDirectory()) walk2(full, remaining - 1);
12243
12301
  } catch {
12244
12302
  }
12245
12303
  }
12246
12304
  };
12247
- for (const r of roots) walk2(path36.resolve(r), depth);
12305
+ for (const r of roots) walk2(path37.resolve(r), depth);
12248
12306
  return found;
12249
12307
  }
12250
12308
  function composedPins(hostSet, libraryRoots) {
@@ -12263,7 +12321,7 @@ function composedPins(hostSet, libraryRoots) {
12263
12321
  let pinned = false;
12264
12322
  const failures = [];
12265
12323
  for (const rel of partnerRels) {
12266
- const partnerSet = path36.resolve(hostSet, rel);
12324
+ const partnerSet = path37.resolve(hostSet, rel);
12267
12325
  let partnerTask;
12268
12326
  let partnerManifest;
12269
12327
  try {
@@ -12292,7 +12350,7 @@ function composedPins(hostSet, libraryRoots) {
12292
12350
  const wantHash = recordingSetHash(partnerSet, partnerTask.configs);
12293
12351
  const matches = candidates.filter((dir) => {
12294
12352
  try {
12295
- const parsed = readBundleManifest(readFileSync27(path36.join(dir, "component.json"), "utf8"));
12353
+ const parsed = readBundleManifest(readFileSync28(path37.join(dir, "component.json"), "utf8"));
12296
12354
  return parsed.manifest?.provenance.recordingSet.hash === wantHash;
12297
12355
  } catch {
12298
12356
  return false;
@@ -12305,13 +12363,13 @@ function composedPins(hostSet, libraryRoots) {
12305
12363
  continue;
12306
12364
  }
12307
12365
  if (matches.length > 1) {
12308
- 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`);
12309
12367
  continue;
12310
12368
  }
12311
12369
  const bundleDir = matches[0];
12312
12370
  let manifest;
12313
12371
  try {
12314
- manifest = readBundleManifest(readFileSync27(path36.join(bundleDir, "component.json"), "utf8")).manifest;
12372
+ manifest = readBundleManifest(readFileSync28(path37.join(bundleDir, "component.json"), "utf8")).manifest;
12315
12373
  } catch {
12316
12374
  manifest = void 0;
12317
12375
  }
@@ -12328,8 +12386,8 @@ function composedPins(hostSet, libraryRoots) {
12328
12386
  const moduleFiles = [];
12329
12387
  let fileIssue;
12330
12388
  for (const name of [manifest.entry, "styles.css", "tokens.css", "fonts.css"]) {
12331
- const file = path36.join(bundleDir, name);
12332
- if (!existsSync29(file)) {
12389
+ const file = path37.join(bundleDir, name);
12390
+ if (!existsSync30(file)) {
12333
12391
  if (name === manifest.entry || name === "styles.css") {
12334
12392
  fileIssue = `${rel}: partner bundle is missing ${name}`;
12335
12393
  break;
@@ -12338,7 +12396,7 @@ function composedPins(hostSet, libraryRoots) {
12338
12396
  }
12339
12397
  let bytes;
12340
12398
  try {
12341
- bytes = readFileSync27(file);
12399
+ bytes = readFileSync28(file);
12342
12400
  } catch {
12343
12401
  fileIssue = `${rel}: partner file ${name} at ${bundleDir} is unreadable`;
12344
12402
  break;
@@ -12410,14 +12468,14 @@ function composedChecks(candidateDir, hostEntry, pins) {
12410
12468
  const checks = [];
12411
12469
  let entrySource = "";
12412
12470
  try {
12413
- entrySource = readFileSync27(path36.join(candidateDir, hostEntry), "utf8");
12471
+ entrySource = readFileSync28(path37.join(candidateDir, hostEntry), "utf8");
12414
12472
  } catch {
12415
12473
  }
12416
- const candidateRoot = path36.resolve(candidateDir);
12474
+ const candidateRoot = path37.resolve(candidateDir);
12417
12475
  for (const pin of pins) {
12418
12476
  const dir = composedModuleDir(pin.partnerName);
12419
- const resolvedDir = path36.resolve(candidateDir, dir);
12420
- if (!resolvedDir.startsWith(candidateRoot + path36.sep)) {
12477
+ const resolvedDir = path37.resolve(candidateDir, dir);
12478
+ if (!resolvedDir.startsWith(candidateRoot + path37.sep)) {
12421
12479
  checks.push({ id: `composition:${pin.pairKey}:module-verbatim`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12422
12480
  checks.push({ id: `composition:${pin.pairKey}:imported`, pass: false, detail: `pin path ${dir}/ escapes the candidate directory \u2014 refused` });
12423
12481
  continue;
@@ -12428,12 +12486,12 @@ function composedChecks(candidateDir, hostEntry, pins) {
12428
12486
  wrong.push(`${f.name} refused (not a plain path segment)`);
12429
12487
  continue;
12430
12488
  }
12431
- const target = path36.join(candidateDir, dir, f.name);
12432
- if (!existsSync29(target)) {
12489
+ const target = path37.join(candidateDir, dir, f.name);
12490
+ if (!existsSync30(target)) {
12433
12491
  wrong.push(`${f.name} missing`);
12434
12492
  continue;
12435
12493
  }
12436
- const sha = createHash7("sha256").update(readFileSync27(target)).digest("hex");
12494
+ const sha = createHash7("sha256").update(readFileSync28(target)).digest("hex");
12437
12495
  if (sha !== f.sha256) wrong.push(`${f.name} differs from the pinned partner bytes`);
12438
12496
  }
12439
12497
  checks.push({
@@ -12458,10 +12516,10 @@ function rootClassesFor(emission, nodeId) {
12458
12516
  }
12459
12517
  function regionOverrides(hostSet, partnerSet, instances) {
12460
12518
  const read = (setDir, rep) => {
12461
- const f = path36.join(setDir, rep, "get_design_context.json");
12462
- if (!existsSync29(f)) return void 0;
12519
+ const f = path37.join(setDir, rep, "get_design_context.json");
12520
+ if (!existsSync30(f)) return void 0;
12463
12521
  try {
12464
- return envelopeFirstTextPart(JSON.parse(readFileSync27(f, "utf8")));
12522
+ return envelopeFirstTextPart(JSON.parse(readFileSync28(f, "utf8")));
12465
12523
  } catch {
12466
12524
  return void 0;
12467
12525
  }
@@ -12491,7 +12549,7 @@ var init_compose_pins = __esm({
12491
12549
  init_src4();
12492
12550
  init_brief();
12493
12551
  init_bundle_emit();
12494
- composedModuleDir = (partnerName) => path36.posix.join("composed", partnerName);
12552
+ composedModuleDir = (partnerName) => path37.posix.join("composed", partnerName);
12495
12553
  safeSegment = (s) => /^[A-Za-z0-9][A-Za-z0-9._ -]*$/.test(s) && !s.includes("..");
12496
12554
  MAX_PINNED_FILE_BYTES = 1024 * 1024;
12497
12555
  CONTEXT_LAYOUT_TOKENS = /* @__PURE__ */ new Set(["shrink-0", "grow", "flex-1", "basis-0", "self-stretch", "w-full", "h-full"]);
@@ -12499,8 +12557,8 @@ var init_compose_pins = __esm({
12499
12557
  });
12500
12558
 
12501
12559
  // packages/generate/src/motion.ts
12502
- import { existsSync as existsSync30, readFileSync as readFileSync28, readdirSync as readdirSync13, statSync as statSync5 } from "node:fs";
12503
- 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";
12504
12562
  function springProgress(u, bounce) {
12505
12563
  const decay = Math.log(100);
12506
12564
  if (bounce <= 0) {
@@ -12571,10 +12629,10 @@ function reportsNoMotion(text) {
12571
12629
  });
12572
12630
  }
12573
12631
  function motionTruthFor(setDir) {
12574
- const file = path37.join(setDir, "get_motion_context.json");
12575
- 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) {
12576
12634
  try {
12577
- const text = envelopeTextContent(JSON.parse(readFileSync28(file, "utf8")));
12635
+ const text = envelopeTextContent(JSON.parse(readFileSync29(file, "utf8")));
12578
12636
  if (text.trim() === "") return { state: "recorded-empty" };
12579
12637
  return reportsNoMotion(text) ? { state: "recorded-no-motion", text } : { state: "recorded", text };
12580
12638
  } catch {
@@ -12587,21 +12645,21 @@ function motionTruthFor(setDir) {
12587
12645
  }
12588
12646
  }
12589
12647
  function motionDisclosure(bundleDir, setDir) {
12590
- const sheets = ["styles.css", "tokens.css"].map((f) => path37.join(bundleDir, f));
12591
- 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");
12592
12650
  try {
12593
12651
  for (const entry of readdirSync13(composedRoot).sort()) {
12594
- const dir = path37.join(composedRoot, entry);
12652
+ const dir = path38.join(composedRoot, entry);
12595
12653
  try {
12596
12654
  if (!statSync5(dir).isDirectory()) continue;
12597
12655
  } catch {
12598
12656
  continue;
12599
12657
  }
12600
- 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"));
12601
12659
  }
12602
12660
  } catch {
12603
12661
  }
12604
- 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");
12605
12663
  if (!MOTION_CSS_PATTERN.test(scannableCss(css))) return { present: false };
12606
12664
  return {
12607
12665
  present: true,
@@ -12968,12 +13026,12 @@ var init_components = __esm({
12968
13026
 
12969
13027
  // packages/generate/src/codebase/walk.ts
12970
13028
  import fs2 from "node:fs";
12971
- import path38 from "node:path";
13029
+ import path39 from "node:path";
12972
13030
  function resolvedPathIsExcluded(real, roots) {
12973
- if (isNeverRead(path38.basename(real))) return true;
13031
+ if (isNeverRead(path39.basename(real))) return true;
12974
13032
  for (const root of roots) {
12975
- if (real !== root && !real.startsWith(root + path38.sep)) continue;
12976
- 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)) {
12977
13035
  if (segment.startsWith(".") || EXCLUDED_DIRS.has(segment)) return true;
12978
13036
  }
12979
13037
  }
@@ -12987,7 +13045,7 @@ function containedRealpath(abs, roots) {
12987
13045
  return null;
12988
13046
  }
12989
13047
  for (const root of roots) {
12990
- if (real === root || real.startsWith(root + path38.sep)) return real;
13048
+ if (real === root || real.startsWith(root + path39.sep)) return real;
12991
13049
  }
12992
13050
  return null;
12993
13051
  }
@@ -13020,7 +13078,7 @@ function walkRepo(roots, limits, accept) {
13020
13078
  continue;
13021
13079
  }
13022
13080
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
13023
- const abs = path38.join(frame.dir, entry.name);
13081
+ const abs = path39.join(frame.dir, entry.name);
13024
13082
  if (EXCLUDED_DIRS.has(entry.name)) continue;
13025
13083
  if (isNeverRead(entry.name)) continue;
13026
13084
  const real = containedRealpath(abs, realRoots);
@@ -13108,18 +13166,18 @@ var init_walk = __esm({
13108
13166
  /^\.netrc$/i
13109
13167
  ];
13110
13168
  isNeverRead = (basename) => NEVER_READ.some((re) => re.test(basename));
13111
- 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);
13112
13170
  }
13113
13171
  });
13114
13172
 
13115
13173
  // packages/generate/src/codebase/scan.ts
13116
13174
  import crypto2 from "node:crypto";
13117
13175
  import fs3 from "node:fs";
13118
- import path39 from "node:path";
13176
+ import path40 from "node:path";
13119
13177
  import postcss3 from "postcss";
13120
13178
  function scanCodebase(options) {
13121
13179
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
13122
- const roots = options.roots.map((r) => path39.resolve(r));
13180
+ const roots = options.roots.map((r) => path40.resolve(r));
13123
13181
  const walk2 = walkRepo(
13124
13182
  roots,
13125
13183
  {
@@ -13139,7 +13197,7 @@ function scanCodebase(options) {
13139
13197
  let bytesRead = 0;
13140
13198
  let filesRead = 0;
13141
13199
  for (const file of walk2.files) {
13142
- const base = path39.posix.basename(file.rel);
13200
+ const base = path40.posix.basename(file.rel);
13143
13201
  configFiles.add(file.rel);
13144
13202
  if (/^tailwind\.config\./.test(base) || file.rel === "babel.config.js") continue;
13145
13203
  const text = readTextFile(file.abs);
@@ -13155,7 +13213,7 @@ function scanCodebase(options) {
13155
13213
  }
13156
13214
  const css = extractCssCustomProperties(cssFiles.filter((f) => !f.rel.includes("..")));
13157
13215
  const components = scanComponents(componentFiles);
13158
- 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");
13159
13217
  const styling = detectStyling(configFiles, cssFiles, componentFiles, manifests);
13160
13218
  const classNameStyle = representativeClassNames(cssFiles, css.unparsed.length);
13161
13219
  const disclosures = buildDisclosures(
@@ -13198,13 +13256,13 @@ function scanCodebase(options) {
13198
13256
  },
13199
13257
  components: {
13200
13258
  entries: components.entries.slice(0, PROFILE_LIMITS.maxComponents),
13201
- fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path39.posix.basename(f.rel)))),
13259
+ fileNaming: buildHistogram(componentFiles.map((f) => classifyFileName(path40.posix.basename(f.rel)))),
13202
13260
  directoryLayout: buildHistogram(componentFiles.map((f) => classifyDirectoryLayout(f.rel))),
13203
13261
  exportStyle: buildHistogram(components.entries.map((e) => e.exportStyle)),
13204
13262
  classNameStyle,
13205
13263
  colocation: buildHistogram(collectColocation(componentFiles, cssFiles)),
13206
13264
  barrelFiles: componentFiles.filter(
13207
- (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)
13208
13266
  ).length,
13209
13267
  refForwarding: {
13210
13268
  forwardRef: componentFiles.filter((f) => /\bforwardRef\s*[(<]/.test(f.text)).length,
@@ -13227,7 +13285,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13227
13285
  };
13228
13286
  const deps = /* @__PURE__ */ new Map();
13229
13287
  for (const manifest of manifests) {
13230
- if (path39.posix.basename(manifest.rel) !== "package.json") continue;
13288
+ if (path40.posix.basename(manifest.rel) !== "package.json") continue;
13231
13289
  try {
13232
13290
  const parsed = JSON.parse(manifest.text);
13233
13291
  for (const field of ["dependencies", "devDependencies", "peerDependencies"]) {
@@ -13239,7 +13297,7 @@ function detectStyling(configFiles, cssFiles, componentFiles, manifests) {
13239
13297
  }
13240
13298
  }
13241
13299
  for (const cfg of configFiles) {
13242
- const base = path39.posix.basename(cfg);
13300
+ const base = path40.posix.basename(cfg);
13243
13301
  if (/^tailwind\.config\./.test(base)) add("tailwind-v3", "file", cfg);
13244
13302
  if (base === "components.json") add("shadcn-style", "file", cfg);
13245
13303
  }
@@ -13297,8 +13355,8 @@ function collectClassNames(cssFiles) {
13297
13355
  return [...distinct].sort().map(classifyClassName);
13298
13356
  }
13299
13357
  function classifyDirectoryLayout(rel) {
13300
- const base = path39.posix.basename(rel).replace(/\.[^.]+$/, "");
13301
- 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));
13302
13360
  if (base === "index") return "component-dir";
13303
13361
  if (base === dir) return "component-dir";
13304
13362
  return "flat-file";
@@ -13322,7 +13380,7 @@ function detectFormatting(manifests, componentFiles) {
13322
13380
  (a, b) => a.rel.split("/").length - b.rel.split("/").length || a.rel.localeCompare(b.rel)
13323
13381
  );
13324
13382
  for (const manifest of byDepth) {
13325
- const base = path39.posix.basename(manifest.rel);
13383
+ const base = path40.posix.basename(manifest.rel);
13326
13384
  if (!/^\.prettierrc/.test(base) && base !== "package.json") continue;
13327
13385
  try {
13328
13386
  const parsed = JSON.parse(manifest.text);
@@ -13340,7 +13398,7 @@ function detectFormatting(manifests, componentFiles) {
13340
13398
  }
13341
13399
  }
13342
13400
  for (const manifest of byDepth) {
13343
- if (path39.posix.basename(manifest.rel) !== ".editorconfig") continue;
13401
+ if (path40.posix.basename(manifest.rel) !== ".editorconfig") continue;
13344
13402
  const style = /indent_style\s*=\s*(tab|space)/.exec(manifest.text)?.[1];
13345
13403
  const width = /indent_size\s*=\s*(\d+)/.exec(manifest.text)?.[1];
13346
13404
  if (style || width) {
@@ -13411,12 +13469,12 @@ function buildDisclosures(detected, css, cappedOut, unrepresentativeClassNames)
13411
13469
  return out;
13412
13470
  }
13413
13471
  function outPathIsGitIgnored(outPath) {
13414
- const dir = path39.dirname(outPath);
13472
+ const dir = path40.dirname(outPath);
13415
13473
  try {
13416
- const ignoreFile = path39.join(path39.dirname(dir), ".gitignore");
13474
+ const ignoreFile = path40.join(path40.dirname(dir), ".gitignore");
13417
13475
  if (!fs3.existsSync(ignoreFile)) return false;
13418
13476
  const patterns = fs3.readFileSync(ignoreFile, "utf8").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#"));
13419
- const base = path39.basename(dir);
13477
+ const base = path40.basename(dir);
13420
13478
  return patterns.some((p) => p === base || p === `${base}/` || p === `/${base}` || p === `/${base}/`);
13421
13479
  } catch {
13422
13480
  return false;
@@ -13563,8 +13621,8 @@ __export(profile_exports, {
13563
13621
  PROFILE_DESCRIPTION: () => PROFILE_DESCRIPTION,
13564
13622
  runProfile: () => runProfile
13565
13623
  });
13566
- import { closeSync, constants, existsSync as existsSync31, mkdirSync as mkdirSync9, openSync, realpathSync as realpathSync4, writeFileSync as writeFileSync13 } from "node:fs";
13567
- 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";
13568
13626
  function escapesScanRoot(outPath, scanRoot) {
13569
13627
  const resolveExisting = (target) => {
13570
13628
  let cursor = target;
@@ -13572,23 +13630,23 @@ function escapesScanRoot(outPath, scanRoot) {
13572
13630
  try {
13573
13631
  return realpathSync4(cursor);
13574
13632
  } catch {
13575
- const parent = path40.dirname(cursor);
13633
+ const parent = path41.dirname(cursor);
13576
13634
  if (parent === cursor) return cursor;
13577
13635
  cursor = parent;
13578
13636
  }
13579
13637
  }
13580
13638
  };
13581
13639
  const root = resolveExisting(scanRoot);
13582
- const dir = resolveExisting(path40.dirname(outPath));
13583
- return dir !== root && !dir.startsWith(root + path40.sep);
13640
+ const dir = resolveExisting(path41.dirname(outPath));
13641
+ return dir !== root && !dir.startsWith(root + path41.sep);
13584
13642
  }
13585
13643
  function runProfile(options) {
13586
13644
  if (options.describe) {
13587
13645
  printDescription(PROFILE_DESCRIPTION);
13588
13646
  return;
13589
13647
  }
13590
- const dir = path40.resolve(options.dir ?? ".");
13591
- if (!existsSync31(dir)) {
13648
+ const dir = path41.resolve(options.dir ?? ".");
13649
+ if (!existsSync32(dir)) {
13592
13650
  fail(options, ExitCode.InputValidation, {
13593
13651
  error: `no such directory: ${dir}`,
13594
13652
  code: "profile_dir_missing",
@@ -13596,7 +13654,7 @@ function runProfile(options) {
13596
13654
  });
13597
13655
  }
13598
13656
  const profile = scanCodebase({ roots: [dir], ...options.now ? { now: options.now } : {} });
13599
- 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"));
13600
13658
  if (!options.dryRun) {
13601
13659
  if (options.out === void 0 && escapesScanRoot(outPath, dir)) {
13602
13660
  fail(options, ExitCode.InputValidation, {
@@ -13605,10 +13663,10 @@ function runProfile(options) {
13605
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`)}\`.`
13606
13664
  });
13607
13665
  }
13608
- mkdirSync9(path40.dirname(outPath), { recursive: true });
13666
+ mkdirSync10(path41.dirname(outPath), { recursive: true });
13609
13667
  const handle = openSync(outPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 420);
13610
13668
  try {
13611
- writeFileSync13(handle, `${JSON.stringify(profile, null, 2)}
13669
+ writeFileSync14(handle, `${JSON.stringify(profile, null, 2)}
13612
13670
  `, "utf8");
13613
13671
  } finally {
13614
13672
  closeSync(handle);
@@ -13670,7 +13728,7 @@ Written to ${outPath}
13670
13728
  `);
13671
13729
  if (!ignored) {
13672
13730
  process.stdout.write(
13673
- ` 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.
13674
13732
  `
13675
13733
  );
13676
13734
  }
@@ -13804,19 +13862,19 @@ var init_activate = __esm({
13804
13862
 
13805
13863
  // packages/cli/src/run-presence.ts
13806
13864
  import { createHash as createHash8 } from "node:crypto";
13807
- import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
13808
- 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";
13809
13867
  function presenceDir() {
13810
- return path41.join(path41.dirname(sessionPath()), "runs");
13868
+ return path42.join(path42.dirname(sessionPath()), "runs");
13811
13869
  }
13812
13870
  function presenceFile(componentName) {
13813
- 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`);
13814
13872
  }
13815
13873
  function readCached(componentName) {
13816
13874
  const file = presenceFile(componentName);
13817
- if (!existsSync32(file)) return void 0;
13875
+ if (!existsSync33(file)) return void 0;
13818
13876
  try {
13819
- const parsed = JSON.parse(readFileSync29(file, "utf8"));
13877
+ const parsed = JSON.parse(readFileSync30(file, "utf8"));
13820
13878
  return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
13821
13879
  } catch {
13822
13880
  return void 0;
@@ -13843,8 +13901,8 @@ async function reportRunPresence(componentName, phase) {
13843
13901
  }
13844
13902
  const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
13845
13903
  if (typeof started.runId !== "string") return;
13846
- mkdirSync10(presenceDir(), { recursive: true });
13847
- 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 })}
13848
13906
  `, { mode: 384 });
13849
13907
  } catch {
13850
13908
  }
@@ -13853,7 +13911,7 @@ async function endRunPresence(componentName) {
13853
13911
  try {
13854
13912
  const session = readStoredSession();
13855
13913
  const cached2 = readCached(componentName);
13856
- rmSync5(presenceFile(componentName), { force: true });
13914
+ rmSync6(presenceFile(componentName), { force: true });
13857
13915
  if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
13858
13916
  await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
13859
13917
  } catch {
@@ -13877,14 +13935,14 @@ __export(compose_exports, {
13877
13935
  runCompose: () => runCompose
13878
13936
  });
13879
13937
  import { createHash as createHash9 } from "node:crypto";
13880
- import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
13881
- 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";
13882
13940
  function compositionPairsFor(hostSet, roots) {
13883
- const parent = path42.dirname(hostSet);
13941
+ const parent = path43.dirname(hostSet);
13884
13942
  const explicitRoots = [...new Set(roots)];
13885
13943
  let skippedParent;
13886
13944
  let parentRoot = [];
13887
- if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
13945
+ if (!explicitRoots.some((r) => path43.resolve(r) === path43.resolve(parent))) {
13888
13946
  let parentEntries = 0;
13889
13947
  try {
13890
13948
  parentEntries = readdirSync14(parent).length;
@@ -13925,7 +13983,7 @@ function substitutionPairs(edges, hostSet) {
13925
13983
  });
13926
13984
  }
13927
13985
  const pair = pairs.get(key);
13928
- 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(", ");
13929
13987
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13930
13988
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13931
13989
  }
@@ -13937,7 +13995,7 @@ function runCompose(flags) {
13937
13995
  return;
13938
13996
  }
13939
13997
  const base = process.env["INIT_CWD"] ?? process.cwd();
13940
- 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];
13941
13999
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13942
14000
  fail(flags, ExitCode.InputValidation, {
13943
14001
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13946,7 +14004,7 @@ function runCompose(flags) {
13946
14004
  });
13947
14005
  }
13948
14006
  if (flags.set !== void 0) {
13949
- runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
14007
+ runComposeConfirm(flags, path43.resolve(base, flags.set), roots);
13950
14008
  return;
13951
14009
  }
13952
14010
  const index = buildComposeIndex(roots);
@@ -13964,7 +14022,7 @@ function runCompose(flags) {
13964
14022
  }
13965
14023
  let lastHost = "";
13966
14024
  for (const e of edges) {
13967
- const host = `${path42.basename(e.hostSet)}`;
14025
+ const host = `${path43.basename(e.hostSet)}`;
13968
14026
  if (host !== lastHost) {
13969
14027
  process.stdout.write(`
13970
14028
  ${host}
@@ -13972,7 +14030,7 @@ ${host}
13972
14030
  lastHost = host;
13973
14031
  }
13974
14032
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13975
- 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(", ")})` : "";
13976
14034
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13977
14035
  `);
13978
14036
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13984,14 +14042,14 @@ ${NOTE}
13984
14042
  });
13985
14043
  }
13986
14044
  function runComposeConfirm(flags, hostSet, roots) {
13987
- if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
14045
+ if (!existsSync34(path43.join(hostSet, "recording-set.json"))) {
13988
14046
  fail(flags, ExitCode.InputValidation, {
13989
14047
  error: `no recording-set.json in ${hostSet}`,
13990
14048
  code: "no-recording-set",
13991
14049
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13992
14050
  });
13993
14051
  }
13994
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
14052
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path43.dirname(hostSet)])];
13995
14053
  const index = buildComposeIndex(scanRoots);
13996
14054
  const edges = composeReport(index);
13997
14055
  const pairs = substitutionPairs(edges, hostSet);
@@ -14074,7 +14132,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
14074
14132
  // full recording-set hash join lands with pin authoring, where
14075
14133
  // task configs exist.)
14076
14134
  manifestSha256: Object.fromEntries(
14077
- 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")])
14078
14136
  )
14079
14137
  },
14080
14138
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -14154,10 +14212,10 @@ __export(record_exports, {
14154
14212
  runRecordPlan: () => runRecordPlan,
14155
14213
  runRecordStatus: () => runRecordStatus
14156
14214
  });
14157
- 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";
14158
14216
  import os7 from "node:os";
14159
- import path43 from "node:path";
14160
- import { writeFileSync as writeFileSync15 } from "node:fs";
14217
+ import path44 from "node:path";
14218
+ import { writeFileSync as writeFileSync16 } from "node:fs";
14161
14219
  function recordsInteractionState(reports) {
14162
14220
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
14163
14221
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -14179,7 +14237,7 @@ function interactionDisclosure(component, reports) {
14179
14237
  };
14180
14238
  }
14181
14239
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
14182
- const env = JSON.parse(readFileSync31(file, "utf8"));
14240
+ const env = JSON.parse(readFileSync32(file, "utf8"));
14183
14241
  const text = env.content.map((c) => c.text ?? "").join("\n");
14184
14242
  const symbols = [];
14185
14243
  const walk2 = (node, ancestor) => {
@@ -14237,8 +14295,8 @@ function runRecordPlan(opts) {
14237
14295
  if (rawFile !== void 0) {
14238
14296
  try {
14239
14297
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
14240
- const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14241
- 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));
14242
14300
  metadataEntries.push({ file: tmp });
14243
14301
  } catch (err) {
14244
14302
  fail(opts, ExitCode.InputValidation, {
@@ -14259,7 +14317,7 @@ function runRecordPlan(opts) {
14259
14317
  let metadataTruncated = false;
14260
14318
  for (const { file, frame } of metadataEntries) {
14261
14319
  try {
14262
- const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
14320
+ const parsed = symbolsFromMetadataEnvelope(path44.resolve(file), frame);
14263
14321
  symbols.push(...parsed.symbols);
14264
14322
  if (parsed.truncated) metadataTruncated = true;
14265
14323
  } catch (err) {
@@ -14293,7 +14351,7 @@ function runRecordPlan(opts) {
14293
14351
  if (symbols.length === 0) {
14294
14352
  const leads = metadataEntries.flatMap(({ file }) => {
14295
14353
  try {
14296
- const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14354
+ const env = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14297
14355
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14298
14356
  } catch {
14299
14357
  return [];
@@ -14386,7 +14444,7 @@ function runRecordPlan(opts) {
14386
14444
  // a CLI flag and the MCP surface has no parameter for it.
14387
14445
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14388
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.",
14389
- 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`]
14390
14448
  },
14391
14449
  {
14392
14450
  id: "larger-allowance",
@@ -14585,7 +14643,7 @@ function runRecordNext(opts) {
14585
14643
  const progress = payload["progress"];
14586
14644
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14587
14645
  \u2192 ${payload["note"]}
14588
- \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>`)}
14589
14647
  `);
14590
14648
  });
14591
14649
  }
@@ -14659,7 +14717,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14659
14717
  const skipped = [];
14660
14718
  const failed = [];
14661
14719
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14662
- if (existsSync34(path43.join(setDir, rep, name))) {
14720
+ if (existsSync35(path44.join(setDir, rep, name))) {
14663
14721
  skipped.push(name);
14664
14722
  continue;
14665
14723
  }
@@ -14681,16 +14739,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14681
14739
  }
14682
14740
  function rawEnvelopeFromFile(file, parts) {
14683
14741
  if (parts) {
14684
- const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14742
+ const blocks = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14685
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");
14686
14744
  return { content: blocks.map((text) => ({ type: "text", text })) };
14687
14745
  }
14688
- return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
14746
+ return { content: [{ type: "text", text: readFileSync32(path44.resolve(file), "utf8") }] };
14689
14747
  }
14690
14748
  async function runRecordIngest(opts) {
14691
14749
  let payload;
14692
14750
  try {
14693
- 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"));
14694
14752
  } catch (err) {
14695
14753
  fail(opts, ExitCode.InputValidation, {
14696
14754
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14702,7 +14760,7 @@ async function runRecordIngest(opts) {
14702
14760
  fail(opts, ExitCode.InputValidation, {
14703
14761
  error: "--raw is for text tool responses; screenshots are binary",
14704
14762
  code: "envelope-invalid",
14705
- 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.`
14706
14764
  });
14707
14765
  }
14708
14766
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14722,7 +14780,7 @@ async function runRecordIngest(opts) {
14722
14780
  remediation: REINGEST_GUIDANCE
14723
14781
  });
14724
14782
  }
14725
- 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)}
14726
14784
  `);
14727
14785
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14728
14786
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14746,7 +14804,7 @@ async function runRecordIngest(opts) {
14746
14804
  remediation: REINGEST_GUIDANCE
14747
14805
  });
14748
14806
  }
14749
- 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)}
14750
14808
  `);
14751
14809
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14752
14810
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14762,7 +14820,7 @@ async function runRecordIngest(opts) {
14762
14820
  if (assets !== void 0) {
14763
14821
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14764
14822
  `);
14765
- 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>`)}
14766
14824
  `);
14767
14825
  }
14768
14826
  });
@@ -14835,14 +14893,14 @@ async function runRecordIngestRep(opts) {
14835
14893
  if (assets !== void 0) {
14836
14894
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14837
14895
  `);
14838
- 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>`)}
14839
14897
  `);
14840
14898
  }
14841
14899
  });
14842
14900
  }
14843
14901
  function runRecordAsset(opts) {
14844
14902
  if (opts.dir !== void 0) {
14845
- const dir = path43.resolve(opts.dir);
14903
+ const dir = path44.resolve(opts.dir);
14846
14904
  const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14847
14905
  if (names.length === 0) {
14848
14906
  fail(opts, ExitCode.InputValidation, {
@@ -14854,7 +14912,7 @@ function runRecordAsset(opts) {
14854
14912
  const ingested = [];
14855
14913
  try {
14856
14914
  for (const name of names) {
14857
- ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
14915
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync32(path44.join(dir, name)));
14858
14916
  ingested.push(name);
14859
14917
  }
14860
14918
  } catch (err) {
@@ -14874,11 +14932,11 @@ function runRecordAsset(opts) {
14874
14932
  fail(opts, ExitCode.InputValidation, {
14875
14933
  error: "pass --name and --file for a single asset, or --dir for a batch",
14876
14934
  code: "asset-rejected",
14877
- 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>`)
14878
14936
  });
14879
14937
  }
14880
14938
  try {
14881
- 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)));
14882
14940
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14883
14941
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14884
14942
  `);
@@ -14895,8 +14953,8 @@ function runRecordStatus(opts) {
14895
14953
  const status = sessionStatus(opts.setDir);
14896
14954
  const composition = (() => {
14897
14955
  try {
14898
- const setDir = path43.resolve(opts.setDir);
14899
- 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)]);
14900
14958
  return {
14901
14959
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14902
14960
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14917,7 +14975,7 @@ function runRecordStatus(opts) {
14917
14975
  }
14918
14976
  }
14919
14977
  process.stdout.write(
14920
- 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\`
14921
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"
14922
14980
  );
14923
14981
  if ("unavailable" in composition) {
@@ -14925,7 +14983,7 @@ function runRecordStatus(opts) {
14925
14983
  `);
14926
14984
  } else if (composition.openPairs.length > 0) {
14927
14985
  process.stdout.write(
14928
- `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.
14929
14987
  `
14930
14988
  );
14931
14989
  } else if (composition.confirmed > 0) {
@@ -14965,7 +15023,7 @@ function narrowedRoles(derived, override) {
14965
15023
  function rolesFromFile(opts, file, derived) {
14966
15024
  let json;
14967
15025
  try {
14968
- json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
15026
+ json = JSON.parse(readFileSync32(path44.resolve(file), "utf8"));
14969
15027
  } catch (err) {
14970
15028
  fail(opts, ExitCode.InputValidation, {
14971
15029
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -15003,11 +15061,11 @@ function rolesFromFile(opts, file, derived) {
15003
15061
  };
15004
15062
  }
15005
15063
  function runRecordFinish(opts) {
15006
- if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
15064
+ if (!existsSync35(path44.join(opts.setDir, "recording-set.json"))) {
15007
15065
  fail(opts, ExitCode.InputValidation, {
15008
15066
  error: `no recording-set.json in ${opts.setDir}`,
15009
15067
  code: "no-recording-set",
15010
- 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.`
15011
15069
  });
15012
15070
  }
15013
15071
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -15035,17 +15093,17 @@ function runRecordFinish(opts) {
15035
15093
  fail(opts, ExitCode.ConfirmationRequired, {
15036
15094
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
15037
15095
  code: "roles-confirmation-not-interactive",
15038
- 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.`
15039
15097
  });
15040
15098
  }
15041
15099
  const merged = { ...raw, roles };
15042
- const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
15100
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync35(path44.join(opts.setDir, rel)));
15043
15101
  const errors = issues.filter((i) => i.severity === "error");
15044
15102
  if (errors.length > 0) {
15045
15103
  fail(opts, ExitCode.InputValidation, {
15046
15104
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
15047
15105
  code: "recording-set-invalid",
15048
- 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\`.`
15049
15107
  });
15050
15108
  }
15051
15109
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -15096,9 +15154,9 @@ var init_record = __esm({
15096
15154
  });
15097
15155
 
15098
15156
  // packages/cli/src/font-guidance.ts
15099
- import path44 from "node:path";
15157
+ import path45 from "node:path";
15100
15158
  function fontsUnprovenRemediation(setDir) {
15101
- const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
15159
+ const set = setDir === void 0 ? void 0 : path45.resolve(setDir);
15102
15160
  if (set !== void 0) {
15103
15161
  try {
15104
15162
  const needs = recordedFontNeeds(set);
@@ -15173,8 +15231,8 @@ __export(fonts_exports, {
15173
15231
  runFontsResolveSet: () => runFontsResolveSet,
15174
15232
  runFontsStatus: () => runFontsStatus
15175
15233
  });
15176
- import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15177
- import path45 from "node:path";
15234
+ import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15235
+ import path46 from "node:path";
15178
15236
  async function runFontsResolve(opts) {
15179
15237
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
15180
15238
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15195,7 +15253,7 @@ async function runFontsResolve(opts) {
15195
15253
  }
15196
15254
  }
15197
15255
  async function runFontsResolveSet(opts) {
15198
- 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);
15199
15257
  let needs = [];
15200
15258
  try {
15201
15259
  needs = recordedFontNeeds(setDir);
@@ -15290,16 +15348,16 @@ async function runFontsResolveSet(opts) {
15290
15348
  }
15291
15349
  }
15292
15350
  function runFontsStatus(opts) {
15293
- const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
15294
- if (!existsSync35(manifestPath2)) {
15351
+ const manifestPath2 = path46.join(opts.cacheDir, "manifest.json");
15352
+ if (!existsSync36(manifestPath2)) {
15295
15353
  fail(opts, ExitCode.FontsUnproven, {
15296
15354
  error: `no font cache at ${opts.cacheDir}`,
15297
15355
  code: "fonts-unresolved",
15298
15356
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15299
15357
  });
15300
15358
  }
15301
- const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
15302
- 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;
15303
15361
  emitData(opts, { faces, lockVerdicts }, () => {
15304
15362
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15305
15363
  `);
@@ -15343,13 +15401,13 @@ function familyMismatch(family, declared) {
15343
15401
  }
15344
15402
  function runFontsAdd(opts) {
15345
15403
  if (opts.set !== void 0) {
15346
- const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15404
+ const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
15347
15405
  const mismatch = familyMismatch(opts.family, declared);
15348
15406
  if (mismatch !== void 0) {
15349
15407
  fail(opts, ExitCode.InputValidation, {
15350
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.`,
15351
15409
  code: "font-family-not-declared",
15352
- 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.`
15353
15411
  });
15354
15412
  }
15355
15413
  } else {
@@ -15408,13 +15466,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15408
15466
  }
15409
15467
  function runFontsAddSystem(opts) {
15410
15468
  if (opts.set !== void 0) {
15411
- const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15469
+ const declared = taskFontFamilies(path46.resolve(opts.set)) ?? [];
15412
15470
  const mismatch = familyMismatch(opts.family, declared);
15413
15471
  if (mismatch !== void 0) {
15414
15472
  fail(opts, ExitCode.InputValidation, {
15415
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.`,
15416
15474
  code: "font-family-not-declared",
15417
- 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.`
15418
15476
  });
15419
15477
  }
15420
15478
  } else {
@@ -15464,12 +15522,12 @@ var init_fonts = __esm({
15464
15522
  });
15465
15523
 
15466
15524
  // packages/cli/src/profile-input.ts
15467
- import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15468
- import path46 from "node:path";
15525
+ import { existsSync as existsSync37, readFileSync as readFileSync34 } from "node:fs";
15526
+ import path47 from "node:path";
15469
15527
  function loadCodebaseProfile(flags, profilePath) {
15470
15528
  if (profilePath === void 0) return null;
15471
- const abs = path46.resolve(profilePath);
15472
- if (!existsSync36(abs)) {
15529
+ const abs = path47.resolve(profilePath);
15530
+ if (!existsSync37(abs)) {
15473
15531
  fail(flags, ExitCode.InputValidation, {
15474
15532
  error: `no profile at ${abs}`,
15475
15533
  code: "profile_missing",
@@ -15477,7 +15535,7 @@ function loadCodebaseProfile(flags, profilePath) {
15477
15535
  });
15478
15536
  }
15479
15537
  try {
15480
- return readCodebaseProfile(readFileSync33(abs, "utf8"));
15538
+ return readCodebaseProfile(readFileSync34(abs, "utf8"));
15481
15539
  } catch (error) {
15482
15540
  fail(flags, ExitCode.InputValidation, {
15483
15541
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15519,8 +15577,8 @@ __export(verify_exports, {
15519
15577
  runVerify: () => runVerify,
15520
15578
  verdictCaveatsFor: () => verdictCaveatsFor
15521
15579
  });
15522
- import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
15523
- 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";
15524
15582
  function interactionCoverage(behaviors) {
15525
15583
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15526
15584
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15761,12 +15819,12 @@ function compositionReport(input) {
15761
15819
  function eyeCheck(bundleDir) {
15762
15820
  return {
15763
15821
  command: tendrilCommand(`inspect "${bundleDir}"`),
15764
- sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
15822
+ sheetPath: path48.join(bundleDir, "verify-evidence", "inspect.html"),
15765
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."
15766
15824
  };
15767
15825
  }
15768
15826
  function evidenceArtifacts(evidenceDir, reps) {
15769
- const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
15827
+ const named = (name) => existsSync38(path48.join(evidenceDir, name)) ? name : null;
15770
15828
  return {
15771
15829
  legend: named("diff-legend.txt"),
15772
15830
  configs: reps.map((rep) => {
@@ -15814,7 +15872,7 @@ function taskFromManifest(opts, manifest, setDir) {
15814
15872
  const adapterSlugs = Object.keys(manifest.propAdapter);
15815
15873
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15816
15874
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15817
- 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));
15818
15876
  const authored = (() => {
15819
15877
  if (registry !== void 0) return void 0;
15820
15878
  try {
@@ -15875,19 +15933,19 @@ function verdictCaveatsFor(input) {
15875
15933
  async function runVerify(opts) {
15876
15934
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15877
15935
  let recordingSetDrift;
15878
- const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
15879
- opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15880
- 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)) {
15881
15939
  fail(opts, ExitCode.InputValidation, {
15882
15940
  error: `bundle directory not found: ${opts.bundleDir}`,
15883
15941
  code: "bundle-missing",
15884
15942
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15885
15943
  });
15886
15944
  }
15887
- const manifestPath2 = path47.join(opts.bundleDir, "component.json");
15945
+ const manifestPath2 = path48.join(opts.bundleDir, "component.json");
15888
15946
  let manifest;
15889
- if (existsSync37(manifestPath2)) {
15890
- const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
15947
+ if (existsSync38(manifestPath2)) {
15948
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync35(manifestPath2, "utf8"));
15891
15949
  if (issues.length > 0) {
15892
15950
  fail(opts, ExitCode.InputValidation, {
15893
15951
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15918,21 +15976,21 @@ async function runVerify(opts) {
15918
15976
  task = registry;
15919
15977
  } else if (manifest !== void 0) {
15920
15978
  const resolveSetDir = (p) => {
15921
- if (path47.isAbsolute(p)) return p;
15922
- const fromRepo = path47.resolve(REPO_ROOT, p);
15923
- if (existsSync37(fromRepo)) return fromRepo;
15924
- 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);
15925
15983
  };
15926
15984
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
15927
- 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))) {
15928
15986
  fail(opts, ExitCode.RecordingIncomplete, {
15929
15987
  error: `recording set not found or unmanifested: ${setDir}`,
15930
15988
  code: "recording-set-missing",
15931
15989
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15932
15990
  });
15933
15991
  }
15934
- const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15935
- 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"))) {
15936
15994
  const recordedSlugs = registry.configs.map((c) => c.rep);
15937
15995
  const adapterSlugs = Object.keys(manifest.propAdapter);
15938
15996
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15964,9 +16022,9 @@ async function runVerify(opts) {
15964
16022
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15965
16023
  }
15966
16024
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
15967
- const p = path47.join(opts.bundleDir, name);
15968
- if (!existsSync37(p)) continue;
15969
- 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)));
15970
16028
  if (issues.length > 0) {
15971
16029
  fail(opts, ExitCode.InputValidation, {
15972
16030
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -16004,7 +16062,7 @@ async function runVerify(opts) {
16004
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)`);
16005
16063
  }
16006
16064
  const missing = task.configs.filter(
16007
- (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"))
16008
16066
  );
16009
16067
  if (missing.length > 0) {
16010
16068
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -16014,8 +16072,8 @@ async function runVerify(opts) {
16014
16072
  });
16015
16073
  }
16016
16074
  const bar = BARS2[opts.bar];
16017
- const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
16018
- rmSync6(evidenceDir, { recursive: true, force: true });
16075
+ const evidenceDir = path48.join(opts.bundleDir, "verify-evidence");
16076
+ rmSync7(evidenceDir, { recursive: true, force: true });
16019
16077
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
16020
16078
  const quality = await checkBundleQuality(
16021
16079
  opts.bundleDir,
@@ -16032,7 +16090,7 @@ async function runVerify(opts) {
16032
16090
  // ASKED, never "follows every convention".
16033
16091
  loadCodebaseProfile(opts, opts.profile) ?? void 0
16034
16092
  );
16035
- 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");
16036
16094
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
16037
16095
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
16038
16096
  ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
@@ -16053,10 +16111,10 @@ async function runVerify(opts) {
16053
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.`);
16054
16112
  }
16055
16113
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
16056
- 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: [] };
16057
16115
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
16058
16116
  const renderExpectations = verifyPins.pins.map((pin) => ({
16059
- modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16117
+ modulePath: path48.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16060
16118
  component: pin.entryComponent,
16061
16119
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
16062
16120
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -16349,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16349
16407
  }
16350
16408
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16351
16409
  `);
16352
- 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")));
16353
16411
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16354
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)
16355
16413
  `);
@@ -16404,7 +16462,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16404
16462
  persistReport(opts, report, evidenceDir);
16405
16463
  }
16406
16464
  function persistReport(opts, report, evidenceDir) {
16407
- if (!existsSync37(evidenceDir)) return;
16465
+ if (!existsSync38(evidenceDir)) return;
16408
16466
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16409
16467
  const withExit = {
16410
16468
  ...report,
@@ -16412,9 +16470,9 @@ function persistReport(opts, report, evidenceDir) {
16412
16470
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16413
16471
  };
16414
16472
  try {
16415
- writeFileSync16(
16416
- path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
16417
- `${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)}
16418
16476
  `
16419
16477
  );
16420
16478
  } catch (e) {
@@ -16464,11 +16522,11 @@ __export(engine_exports, {
16464
16522
  runEngineBrief: () => runEngineBrief,
16465
16523
  runEngineScore: () => runEngineScore
16466
16524
  });
16467
- import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16468
- 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";
16469
16527
  function resolveEngineTask(opts, callerCwd) {
16470
- const asPath = path48.resolve(callerCwd, opts.taskOrSet);
16471
- 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"));
16472
16530
  const registry = TASKS[opts.taskOrSet];
16473
16531
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16474
16532
  if (isSet) {
@@ -16477,7 +16535,7 @@ function resolveEngineTask(opts, callerCwd) {
16477
16535
  for (const d of authored.disclosures) warn(opts, d);
16478
16536
  return {
16479
16537
  task: authored.task,
16480
- name: path48.basename(asPath),
16538
+ name: path49.basename(asPath),
16481
16539
  ref: asPath,
16482
16540
  disclosures: authored.disclosures,
16483
16541
  interactionEvidence: authored.api.interactionEvidence,
@@ -16506,9 +16564,9 @@ function runEngineBrief(opts) {
16506
16564
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16507
16565
  void reportRunPresence(name, "implementing");
16508
16566
  const bar = BARS3[opts.bar];
16509
- if (existsSync38(path48.join(task.set, "recording-set.json"))) {
16567
+ if (existsSync39(path49.join(task.set, "recording-set.json"))) {
16510
16568
  try {
16511
- 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]);
16512
16570
  if (skippedParent !== void 0) {
16513
16571
  disclosures.push(
16514
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.`
@@ -16517,7 +16575,7 @@ function runEngineBrief(opts) {
16517
16575
  if (open.length > 0) {
16518
16576
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16519
16577
  disclosures.push(
16520
- `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.`
16521
16579
  );
16522
16580
  }
16523
16581
  } catch (err) {
@@ -16534,9 +16592,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16534
16592
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16535
16593
  const segments = buildSegments(task, "files");
16536
16594
  let notRecorded;
16537
- const manifestPath2 = path48.join(task.set, "recording-set.json");
16538
- if (existsSync38(manifestPath2)) {
16539
- 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;
16540
16598
  }
16541
16599
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16542
16600
 
@@ -16544,7 +16602,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16544
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.
16545
16603
  ${notRecorded}` : "";
16546
16604
  let fontProvisioning;
16547
- if (existsSync38(manifestPath2)) {
16605
+ if (existsSync39(manifestPath2)) {
16548
16606
  const missingFams = unprovisionedFamilies(task.set);
16549
16607
  const unprovided = unprovisionedFaces(task.set);
16550
16608
  const weightOnly = missingFams.length === 0;
@@ -16566,7 +16624,7 @@ ${notRecorded}` : "";
16566
16624
  };
16567
16625
  }
16568
16626
  }
16569
- 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]);
16570
16628
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16571
16629
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16572
16630
 
@@ -16602,10 +16660,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16602
16660
 
16603
16661
  === TASK PAYLOAD (recorded truth, verbatim) ===
16604
16662
  ${segments}`;
16605
- const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16606
- const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
16607
- mkdirSync11(path48.dirname(payloadFile), { recursive: true });
16608
- 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);
16609
16667
  emitData(
16610
16668
  opts,
16611
16669
  {
@@ -16651,7 +16709,7 @@ ${segments}`;
16651
16709
  // command must search the same bundle roots the pins came
16652
16710
  // from, or the oracle and the brief describe different worlds.
16653
16711
  `Run \`${tendrilCommand(
16654
- `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`
16655
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.`,
16656
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).",
16657
16715
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16666,8 +16724,8 @@ ${segments}`;
16666
16724
  );
16667
16725
  }
16668
16726
  function appendScoreHistory(candidateDir, entry) {
16669
- const file = path48.join(candidateDir, "score-history.jsonl");
16670
- 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;
16671
16729
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16672
16730
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16673
16731
  `);
@@ -16675,10 +16733,10 @@ function appendScoreHistory(candidateDir, entry) {
16675
16733
  async function runEngineScore(opts) {
16676
16734
  requireEntitlement(opts);
16677
16735
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16678
- const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
16736
+ const candidateDir = path49.resolve(callerCwd, opts.candidateDir);
16679
16737
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16680
16738
  void reportRunPresence(name, "implementing");
16681
- if (!existsSync38(candidateDir)) {
16739
+ if (!existsSync39(candidateDir)) {
16682
16740
  fail(opts, ExitCode.InputValidation, {
16683
16741
  error: `candidate directory not found: ${candidateDir}`,
16684
16742
  code: "candidate-missing",
@@ -16703,10 +16761,10 @@ async function runEngineScore(opts) {
16703
16761
  for (const g of missingWeights(task.set)) {
16704
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)`);
16705
16763
  }
16706
- if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
16764
+ if (opts.rebind !== true && existsSync39(path49.join(candidateDir, "component.json"))) {
16707
16765
  const prior = (() => {
16708
16766
  try {
16709
- const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
16767
+ const read = readBundleManifest(readFileSync36(path49.join(candidateDir, "component.json"), "utf8"));
16710
16768
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16711
16769
  } catch {
16712
16770
  return { unreadable: true };
@@ -16728,13 +16786,13 @@ async function runEngineScore(opts) {
16728
16786
  }
16729
16787
  }
16730
16788
  const bar = BARS3[opts.bar];
16731
- const evidenceDir = path48.join(candidateDir, "verify-evidence");
16789
+ const evidenceDir = path49.join(candidateDir, "verify-evidence");
16732
16790
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16733
16791
  const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16734
16792
  const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16735
16793
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16736
16794
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16737
- 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]);
16738
16796
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16739
16797
  const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16740
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)";
@@ -16953,11 +17011,11 @@ var codeconnect_exports = {};
16953
17011
  __export(codeconnect_exports, {
16954
17012
  runCodeConnect: () => runCodeConnect
16955
17013
  });
16956
- import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16957
- 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";
16958
17016
  function runCodeConnect(opts) {
16959
17017
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16960
- const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
17018
+ const bundleDir = path50.resolve(callerCwd, opts.bundleDir);
16961
17019
  let url;
16962
17020
  try {
16963
17021
  url = new URL(opts.figmaUrl);
@@ -16973,7 +17031,7 @@ function runCodeConnect(opts) {
16973
17031
  }
16974
17032
  let manifest;
16975
17033
  try {
16976
- const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
17034
+ const read = readBundleManifest(readFileSync37(path50.join(bundleDir, "component.json"), "utf8"));
16977
17035
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16978
17036
  manifest = read.manifest;
16979
17037
  } catch (err) {
@@ -16983,8 +17041,8 @@ function runCodeConnect(opts) {
16983
17041
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16984
17042
  });
16985
17043
  }
16986
- const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16987
- 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"))) {
16988
17046
  fail(opts, ExitCode.InputValidation, {
16989
17047
  error: `recording set not found at ${setDir}`,
16990
17048
  code: "codeconnect-no-set",
@@ -17005,10 +17063,10 @@ function runCodeConnect(opts) {
17005
17063
  const component = api.component;
17006
17064
  const recManifest = loadManifest(setDir);
17007
17065
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
17008
- const meta = path49.join(setDir, r.slug, "get_metadata.json");
17009
- if (!existsSync39(meta)) return void 0;
17066
+ const meta = path50.join(setDir, r.slug, "get_metadata.json");
17067
+ if (!existsSync40(meta)) return void 0;
17010
17068
  try {
17011
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
17069
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync37(meta, "utf8"))))?.[1];
17012
17070
  } catch {
17013
17071
  return void 0;
17014
17072
  }
@@ -17073,7 +17131,7 @@ function runCodeConnect(opts) {
17073
17131
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
17074
17132
  fragmentVars.push(varName);
17075
17133
  }
17076
- const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
17134
+ const entryRel = path50.relative(callerCwd, path50.join(bundleDir, manifest.entry));
17077
17135
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
17078
17136
  const lines = [
17079
17137
  `// url=${opts.figmaUrl}`,
@@ -17097,8 +17155,8 @@ function runCodeConnect(opts) {
17097
17155
  `}`,
17098
17156
  ``
17099
17157
  ].join("\n");
17100
- const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
17101
- writeFileSync18(outFile, lines);
17158
+ const outFile = path50.resolve(callerCwd, opts.out ?? path50.join(bundleDir, `${component}.figma.ts`));
17159
+ writeFileSync19(outFile, lines);
17102
17160
  emitData(
17103
17161
  opts,
17104
17162
  {
@@ -17137,17 +17195,17 @@ var init_codeconnect = __esm({
17137
17195
 
17138
17196
  // packages/mcp/src/server.ts
17139
17197
  import { createHash as createHash10 } from "node:crypto";
17140
- 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";
17141
17199
  import os8 from "node:os";
17142
- import path50 from "node:path";
17200
+ import path51 from "node:path";
17143
17201
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17144
17202
  import { z as z14 } from "zod";
17145
17203
  function sourceHash() {
17146
- const dir = path50.dirname(fileURLToPath6(import.meta.url));
17204
+ const dir = path51.dirname(fileURLToPath6(import.meta.url));
17147
17205
  const h = createHash10("sha256");
17148
17206
  for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
17149
17207
  h.update(f);
17150
- h.update(readFileSync37(path50.join(dir, f)));
17208
+ h.update(readFileSync38(path51.join(dir, f)));
17151
17209
  }
17152
17210
  return h.digest("hex").slice(0, 16);
17153
17211
  }
@@ -17155,10 +17213,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
17155
17213
  var init_server = __esm({
17156
17214
  "packages/mcp/src/server.ts"() {
17157
17215
  "use strict";
17158
- REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17159
- CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17160
- BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17161
- 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] };
17162
17220
  str = (d) => z14.string().describe(d);
17163
17221
  optStr = (d) => z14.string().optional().describe(d);
17164
17222
  TOOLS = [
@@ -17189,13 +17247,13 @@ var init_server = __esm({
17189
17247
  const single = i["metadata"];
17190
17248
  const parts = i["metadataParts"];
17191
17249
  if (single !== void 0 || parts !== void 0) {
17192
- 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");
17193
17251
  if (single !== void 0) {
17194
- writeFileSync19(tmp, single);
17252
+ writeFileSync20(tmp, single);
17195
17253
  argvOut.push("--metadata-raw-file", tmp);
17196
17254
  } else {
17197
17255
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
17198
- writeFileSync19(tmp, JSON.stringify(parts));
17256
+ writeFileSync20(tmp, JSON.stringify(parts));
17199
17257
  argvOut.push("--metadata-raw-parts-file", tmp);
17200
17258
  }
17201
17259
  }
@@ -17245,9 +17303,23 @@ var init_server = __esm({
17245
17303
  schema: z14.object({}),
17246
17304
  argv: () => ["login", "--device-wait"]
17247
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
+ },
17248
17320
  {
17249
17321
  name: "tendril_publish",
17250
- 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 (they click Approve in the browser where they are signed in; when the page shows a terms checkbox, ticking it is part of their decision \u2014 never advise them to just tick it). 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).",
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).",
17251
17323
  schema: z14.object({
17252
17324
  bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
17253
17325
  portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
@@ -17304,14 +17376,14 @@ var init_server = __esm({
17304
17376
  const bridge = (label, single, parts) => {
17305
17377
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17306
17378
  if (single === void 0 && parts === void 0) return;
17307
- 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");
17308
17380
  if (single !== void 0) {
17309
- writeFileSync19(tmp, single);
17381
+ writeFileSync20(tmp, single);
17310
17382
  argvOut.push(`--${label}-file`, tmp);
17311
17383
  } else {
17312
17384
  const blocks = parts;
17313
17385
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17314
- writeFileSync19(tmp, JSON.stringify(blocks));
17386
+ writeFileSync20(tmp, JSON.stringify(blocks));
17315
17387
  argvOut.push(`--${label}-parts-file`, tmp);
17316
17388
  }
17317
17389
  };
@@ -17352,12 +17424,12 @@ var init_server = __esm({
17352
17424
  const file = i["file"];
17353
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)");
17354
17426
  if (file !== void 0) return [...base, "--file", file];
17355
- 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");
17356
17428
  if (text !== void 0) {
17357
- writeFileSync19(tmp, text);
17429
+ writeFileSync20(tmp, text);
17358
17430
  return [...base, "--file", tmp, "--raw"];
17359
17431
  }
17360
- writeFileSync19(tmp, JSON.stringify(texts));
17432
+ writeFileSync20(tmp, JSON.stringify(texts));
17361
17433
  return [...base, "--file", tmp, "--raw-parts"];
17362
17434
  }
17363
17435
  },
@@ -17514,13 +17586,13 @@ __export(permissions_exports, {
17514
17586
  runPermissions: () => runPermissions,
17515
17587
  writeSelection: () => writeSelection
17516
17588
  });
17517
- 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";
17518
17590
  import os9 from "node:os";
17519
- import path51 from "node:path";
17591
+ import path52 from "node:path";
17520
17592
  function mergeAllowlist(file, entries, denyEntries = []) {
17521
17593
  let settings = {};
17522
- if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
17523
- settings = JSON.parse(readFileSync38(file, "utf8"));
17594
+ if (existsSync42(file) && readFileSync39(file, "utf8").trim() !== "") {
17595
+ settings = JSON.parse(readFileSync39(file, "utf8"));
17524
17596
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17525
17597
  }
17526
17598
  const permissions = settings["permissions"] ??= {};
@@ -17540,8 +17612,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17540
17612
  }
17541
17613
  if (added.length > 0 || denyAdded.length > 0) {
17542
17614
  allow.push(...added);
17543
- mkdirSync12(path51.dirname(file), { recursive: true });
17544
- writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
17615
+ mkdirSync13(path52.dirname(file), { recursive: true });
17616
+ writeFileSync21(file, `${JSON.stringify(settings, null, 2)}
17545
17617
  `);
17546
17618
  }
17547
17619
  return { added, alreadyPresent, denyAdded };
@@ -17605,7 +17677,7 @@ async function runPermissions(flags) {
17605
17677
  }
17606
17678
  if (flags.write) {
17607
17679
  const base = process.env["INIT_CWD"] ?? process.cwd();
17608
- 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");
17609
17681
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17610
17682
  if (flags.dryRun) {
17611
17683
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17754,13 +17826,13 @@ __export(inspect_exports, {
17754
17826
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17755
17827
  runInspect: () => runInspect
17756
17828
  });
17757
- import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17758
- 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";
17759
17831
  function readVerifyReport(evidenceDir) {
17760
- const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
17761
- if (!existsSync42(p)) return void 0;
17832
+ const p = path53.join(evidenceDir, VERIFY_REPORT_FILENAME);
17833
+ if (!existsSync43(p)) return void 0;
17762
17834
  try {
17763
- return JSON.parse(readFileSync39(p, "utf8"));
17835
+ return JSON.parse(readFileSync40(p, "utf8"));
17764
17836
  } catch {
17765
17837
  return void 0;
17766
17838
  }
@@ -17788,17 +17860,17 @@ async function runInspect(opts) {
17788
17860
  printDescription(INSPECT_DESCRIPTION);
17789
17861
  return;
17790
17862
  }
17791
- const bundleDir = path52.resolve(opts.bundleDir);
17792
- const evidenceDir = path52.join(bundleDir, "verify-evidence");
17793
- const manifestPath2 = path52.join(bundleDir, "component.json");
17794
- 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)) {
17795
17867
  fail(opts, ExitCode.InputValidation, {
17796
- 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"}`,
17797
17869
  code: "no-evidence",
17798
17870
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17799
17871
  });
17800
17872
  }
17801
- const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
17873
+ const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
17802
17874
  if (manifest === void 0) {
17803
17875
  fail(opts, ExitCode.InputValidation, {
17804
17876
  error: "component.json did not parse as a bundle manifest",
@@ -17806,9 +17878,9 @@ async function runInspect(opts) {
17806
17878
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17807
17879
  });
17808
17880
  }
17809
- const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17881
+ const setDir = path53.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17810
17882
  const report = readVerifyReport(evidenceDir);
17811
- 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`)));
17812
17884
  if (reps.length === 0) {
17813
17885
  fail(opts, ExitCode.InputValidation, {
17814
17886
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17819,15 +17891,15 @@ async function runInspect(opts) {
17819
17891
  let crops = 0;
17820
17892
  const sections = [];
17821
17893
  for (const rep of reps) {
17822
- const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
17823
- 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`)));
17824
17896
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17825
17897
  const cells = [];
17826
17898
  for (const [i, n] of nodes.entries()) {
17827
17899
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17828
17900
  try {
17829
- writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17830
- 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));
17831
17903
  } catch {
17832
17904
  continue;
17833
17905
  }
@@ -17844,8 +17916,8 @@ async function runInspect(opts) {
17844
17916
  if (reps.includes(c.rep)) continue;
17845
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>`);
17846
17918
  }
17847
- const sheet = path52.join(evidenceDir, "inspect.html");
17848
- writeFileSync21(
17919
+ const sheet = path53.join(evidenceDir, "inspect.html");
17920
+ writeFileSync22(
17849
17921
  sheet,
17850
17922
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17851
17923
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17918,8 +17990,8 @@ __export(login_exports, {
17918
17990
  runLogout: () => runLogout
17919
17991
  });
17920
17992
  import { spawn } from "node:child_process";
17921
- import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
17922
- 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";
17923
17995
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17924
17996
  async function runLogin(opts, deps) {
17925
17997
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -18013,13 +18085,13 @@ function settleDecision(opts, origin, outcome) {
18013
18085
  }
18014
18086
  }
18015
18087
  function pendingLoginPath() {
18016
- return path53.join(path53.dirname(sessionPath()), "pending-login.json");
18088
+ return path54.join(path54.dirname(sessionPath()), "pending-login.json");
18017
18089
  }
18018
18090
  async function deviceStartPhase(opts, origin, deps) {
18019
18091
  const started = await startHandshake(opts, origin, deps);
18020
18092
  const file = pendingLoginPath();
18021
- mkdirSync13(path53.dirname(file), { recursive: true });
18022
- writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18093
+ mkdirSync14(path54.dirname(file), { recursive: true });
18094
+ writeFileSync23(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18023
18095
  `, { mode: 384 });
18024
18096
  deps.openBrowser(started.verificationUrl);
18025
18097
  emitData(
@@ -18044,9 +18116,9 @@ async function deviceStartPhase(opts, origin, deps) {
18044
18116
  async function deviceWaitPhase(opts, deps) {
18045
18117
  const file = pendingLoginPath();
18046
18118
  let pending;
18047
- if (existsSync43(file)) {
18119
+ if (existsSync44(file)) {
18048
18120
  try {
18049
- const parsed = JSON.parse(readFileSync40(file, "utf8"));
18121
+ const parsed = JSON.parse(readFileSync41(file, "utf8"));
18050
18122
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
18051
18123
  pending = parsed;
18052
18124
  }
@@ -18060,7 +18132,7 @@ async function deviceWaitPhase(opts, deps) {
18060
18132
  remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
18061
18133
  });
18062
18134
  }
18063
- const done = () => rmSync7(file, { force: true });
18135
+ const done = () => rmSync8(file, { force: true });
18064
18136
  const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
18065
18137
  let ticks = 0;
18066
18138
  const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
@@ -18183,6 +18255,196 @@ var init_login = __esm({
18183
18255
  }
18184
18256
  });
18185
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
+
18186
18448
  // packages/cli/src/commands/share.ts
18187
18449
  var share_exports = {};
18188
18450
  __export(share_exports, {
@@ -18295,12 +18557,13 @@ var init_share = __esm({
18295
18557
  // packages/cli/src/commands/publish.ts
18296
18558
  var publish_exports = {};
18297
18559
  __export(publish_exports, {
18560
+ resolveOrigin: () => resolveOrigin2,
18298
18561
  runPublish: () => runPublish
18299
18562
  });
18300
- import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
18301
- 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";
18302
18565
  async function runPublish(opts) {
18303
- const bundleDir = path54.resolve(opts.bundleDir);
18566
+ const bundleDir = path56.resolve(opts.bundleDir);
18304
18567
  const bundle = readBundle(opts, bundleDir);
18305
18568
  const report = bundle.report;
18306
18569
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18345,7 +18608,7 @@ async function runPublish(opts) {
18345
18608
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18346
18609
  if (sheetEntry !== void 0) {
18347
18610
  const missingCrops = missingInspectCrops(
18348
- readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
18611
+ readFileSync43(path56.join(bundleDir, sheetEntry.path), "utf8"),
18349
18612
  surface.published.map((p) => p.path)
18350
18613
  );
18351
18614
  if (missingCrops.length > 0) {
@@ -18410,10 +18673,10 @@ async function runPublish(opts) {
18410
18673
  );
18411
18674
  return;
18412
18675
  }
18413
- const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18676
+ const origin = resolveOrigin2(opts);
18414
18677
  const client = opts.client ?? httpClient(opts, origin);
18415
18678
  if (opts.approveWait === true) {
18416
- await approveWaitPhase(opts, client, bundleDir);
18679
+ await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
18417
18680
  }
18418
18681
  void reportRunPresence(componentName, "publishing");
18419
18682
  let opened = await client.begin({
@@ -18435,7 +18698,7 @@ async function runPublish(opts) {
18435
18698
  if (!opened.ok) {
18436
18699
  if (opened.needsConfirmation !== void 0) {
18437
18700
  fail(opts, ExitCode.ConfirmationRequired, {
18438
- error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18701
+ error: opened.refusal,
18439
18702
  code: "first-publish-unconfirmed",
18440
18703
  remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
18441
18704
  });
@@ -18444,8 +18707,8 @@ async function runPublish(opts) {
18444
18707
  }
18445
18708
  const uploaded = [];
18446
18709
  for (const object of opened.value.plan.objects) {
18447
- const file = path54.join(bundleDir, object.relPath);
18448
- if (!existsSync44(file)) {
18710
+ const file = path56.join(bundleDir, object.relPath);
18711
+ if (!existsSync46(file)) {
18449
18712
  fail(opts, ExitCode.InputValidation, {
18450
18713
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18451
18714
  code: "planned-file-missing",
@@ -18455,10 +18718,11 @@ async function runPublish(opts) {
18455
18718
  const sent = await client.upload({
18456
18719
  publicationId: opened.value.publicationId,
18457
18720
  relPath: object.relPath,
18458
- bytes: new Uint8Array(readFileSync41(file))
18721
+ bytes: new Uint8Array(readFileSync43(file))
18459
18722
  });
18460
- if (!sent.ok) refuse2(opts, sent, "upload-refused");
18723
+ if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
18461
18724
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18725
+ emitProgress(uploaded.length, opened.value.plan.objects.length, `uploading ${object.relPath}`);
18462
18726
  }
18463
18727
  const committed = await client.commit({ publicationId: opened.value.publicationId });
18464
18728
  if (committed.ok) {
@@ -18472,7 +18736,7 @@ async function runPublish(opts) {
18472
18736
  remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
18473
18737
  });
18474
18738
  }
18475
- refuse2(opts, committed, "commit-refused");
18739
+ refuse2(opts, committed, "commit-refused", true);
18476
18740
  }
18477
18741
  emitData(
18478
18742
  opts,
@@ -18500,23 +18764,23 @@ async function runPublish(opts) {
18500
18764
  );
18501
18765
  }
18502
18766
  function readBundle(opts, bundleDir) {
18503
- const manifestPath2 = path54.join(bundleDir, "component.json");
18504
- const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18505
- 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)) {
18506
18770
  fail(opts, ExitCode.InputValidation, {
18507
18771
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18508
18772
  code: "not-a-bundle",
18509
18773
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18510
18774
  });
18511
18775
  }
18512
- if (!existsSync44(reportPath)) {
18776
+ if (!existsSync46(reportPath)) {
18513
18777
  fail(opts, ExitCode.InputValidation, {
18514
18778
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18515
18779
  code: "bundle-not-verified",
18516
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.`
18517
18781
  });
18518
18782
  }
18519
- const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
18783
+ const { manifest } = readBundleManifest(readFileSync43(manifestPath2, "utf8"));
18520
18784
  if (manifest === void 0) {
18521
18785
  fail(opts, ExitCode.InputValidation, {
18522
18786
  error: "component.json did not parse as a bundle manifest",
@@ -18524,7 +18788,7 @@ function readBundle(opts, bundleDir) {
18524
18788
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18525
18789
  });
18526
18790
  }
18527
- const reportText = readFileSync41(reportPath, "utf8");
18791
+ const reportText = readFileSync43(reportPath, "utf8");
18528
18792
  let report;
18529
18793
  try {
18530
18794
  report = JSON.parse(reportText);
@@ -18544,6 +18808,12 @@ function readBundle(opts, bundleDir) {
18544
18808
  }
18545
18809
  return { manifest, report, reportText, files: bundleFiles(bundleDir) };
18546
18810
  }
18811
+ function resolveOrigin2(opts) {
18812
+ const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18813
+ if (named !== "") return named;
18814
+ if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
18815
+ return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
18816
+ }
18547
18817
  function httpClient(opts, origin) {
18548
18818
  if (origin === "") {
18549
18819
  fail(opts, ExitCode.InputValidation, {
@@ -18576,37 +18846,43 @@ function httpClient(opts, origin) {
18576
18846
  }
18577
18847
  return new HttpPublishClient({ origin, token: found.token });
18578
18848
  }
18579
- function refuse2(opts, sent, code) {
18849
+ function refuse2(opts, sent, code, rejoins = false) {
18580
18850
  const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
18851
+ const retry = rejoins ? `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one` : `run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again`;
18581
18852
  fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18582
18853
  error: `${sent.refusal}${detail}`,
18583
18854
  code,
18584
- remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
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}.`
18585
18856
  });
18586
18857
  }
18587
18858
  function pendingApprovalPath() {
18588
- return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
18859
+ return path56.join(path56.dirname(sessionPath()), "pending-publish.json");
18589
18860
  }
18590
18861
  async function runApprovalFlow(opts, client, bundleDir, input) {
18591
18862
  const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
18592
18863
  if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
18593
18864
  const approval = requested.value;
18865
+ const who = await client.whoami?.();
18866
+ const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
18594
18867
  if (opts.approveStart === true) {
18595
- writeFileSync23(pendingApprovalPath(), `${JSON.stringify({ ...approval, bundleDir }, null, 2)}
18868
+ const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
18869
+ writeFileSync25(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
18596
18870
  `, { mode: 384 });
18871
+ await endRunPresence(input.componentName);
18597
18872
  emitData(
18598
18873
  opts,
18599
18874
  {
18600
18875
  status: "approval-pending",
18601
18876
  approveUrl: approval.approveUrl,
18602
18877
  componentName: input.componentName,
18878
+ ...asAccount === "" ? {} : { approveAsAccount: who?.ok === true ? who.value.email : "" },
18603
18879
  expiresAt: approval.expiresAt,
18604
- next: "open the approve page in the signed-in browser, click Approve (tick the terms box if shown), then finish with --approve-wait"
18880
+ next: `open the approve page in the browser${asAccount}, click Approve, then finish with --approve-wait`
18605
18881
  },
18606
18882
  () => {
18607
18883
  process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
18608
18884
  `);
18609
- process.stdout.write(`Approve it here: ${approval.approveUrl}
18885
+ process.stdout.write(`Approve it here${asAccount}: ${approval.approveUrl}
18610
18886
  `);
18611
18887
  process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
18612
18888
  `);
@@ -18614,21 +18890,22 @@ async function runApprovalFlow(opts, client, bundleDir, input) {
18614
18890
  );
18615
18891
  return void 0;
18616
18892
  }
18617
- process.stderr.write(`This component's FIRST publish needs your approval in the browser:
18893
+ process.stderr.write(`This component's FIRST publish needs your approval in the browser${asAccount}:
18618
18894
  ${approval.approveUrl}
18619
18895
  `);
18620
18896
  process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
18621
18897
  `);
18622
18898
  const decided = await waitForApproval(opts, client, approval);
18623
18899
  if (decided === "approved") return input.begin();
18900
+ await endRunPresence(input.componentName);
18624
18901
  failDecision(opts, decided);
18625
18902
  }
18626
- async function approveWaitPhase(opts, client, bundleDir) {
18903
+ async function approveWaitPhase(opts, client, bundleDir, subject) {
18627
18904
  const file = pendingApprovalPath();
18628
18905
  let pending;
18629
- if (existsSync44(file)) {
18906
+ if (existsSync46(file)) {
18630
18907
  try {
18631
- const parsed = JSON.parse(readFileSync41(file, "utf8"));
18908
+ const parsed = JSON.parse(readFileSync43(file, "utf8"));
18632
18909
  if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
18633
18910
  pending = { pollSeconds: 3, bundleDir, ...parsed };
18634
18911
  }
@@ -18642,7 +18919,14 @@ async function approveWaitPhase(opts, client, bundleDir) {
18642
18919
  remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
18643
18920
  });
18644
18921
  }
18645
- const done = () => rmSync8(file, { force: true });
18922
+ if (pending.componentName !== void 0 && pending.componentName !== subject.componentName) {
18923
+ fail(opts, ExitCode.InputValidation, {
18924
+ error: `the waiting approval is for ${JSON.stringify(pending.componentName)}, and this publish is ${JSON.stringify(subject.componentName)}`,
18925
+ code: "pending-approval-mismatch",
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.`
18927
+ });
18928
+ }
18929
+ const done = () => rmSync10(file, { force: true });
18646
18930
  const decided = await waitForApproval(opts, client, pending);
18647
18931
  done();
18648
18932
  if (decided !== "approved") failDecision(opts, decided);
@@ -18670,7 +18954,7 @@ function failDecision(opts, decided) {
18670
18954
  fail(opts, ExitCode.ConfirmationRequired, {
18671
18955
  error: decided === "expired" ? "the approval request lapsed before anyone decided it" : "the approval request is gone \u2014 it lapsed and was cleaned up, or was already spent",
18672
18956
  code: "publish-approval-lapsed",
18673
- remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window."
18957
+ remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window. If the approve page showed nothing waiting, the browser is signed in to a DIFFERENT account than this CLI \u2014 the page lists only its own account's requests."
18674
18958
  });
18675
18959
  }
18676
18960
  var APPROVAL_WAIT_CAP_MS;
@@ -18711,17 +18995,17 @@ __export(generate_recorded_exports, {
18711
18995
  runGenerateRecorded: () => runGenerateRecorded
18712
18996
  });
18713
18997
  import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
18714
- import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
18715
- import path55 from "node:path";
18998
+ import { existsSync as existsSync47, readFileSync as readFileSync44 } from "node:fs";
18999
+ import path57 from "node:path";
18716
19000
  async function runGenerateRecorded(opts) {
18717
19001
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18718
- const outDirAbs = path55.resolve(callerCwd, opts.out);
18719
- const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
19002
+ const outDirAbs = path57.resolve(callerCwd, opts.out);
19003
+ const recordedAsPath = path57.resolve(callerCwd, opts.recorded);
18720
19004
  let task;
18721
19005
  let taskName;
18722
19006
  let authoredApi;
18723
19007
  let composition;
18724
- const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
19008
+ const isSet = existsSync47(path57.join(recordedAsPath, "recording-set.json"));
18725
19009
  const registry = TASKS[opts.recorded];
18726
19010
  if (registry !== void 0 && !isSet) {
18727
19011
  task = registry;
@@ -18730,7 +19014,7 @@ async function runGenerateRecorded(opts) {
18730
19014
  try {
18731
19015
  const authored = authorTaskFromSet(recordedAsPath);
18732
19016
  task = authored.task;
18733
- taskName = path55.basename(recordedAsPath);
19017
+ taskName = path57.basename(recordedAsPath);
18734
19018
  authoredApi = authored.api;
18735
19019
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18736
19020
  if (roles.success) composition = roles.data;
@@ -18764,7 +19048,7 @@ async function runGenerateRecorded(opts) {
18764
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)`);
18765
19049
  }
18766
19050
  const missing = task.configs.filter(
18767
- (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"))
18768
19052
  );
18769
19053
  if (missing.length > 0) {
18770
19054
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18834,8 +19118,8 @@ async function runGenerateRecorded(opts) {
18834
19118
  ` : `${line}
18835
19119
  `);
18836
19120
  if (opts.dryRun) {
18837
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
18838
- 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)})
18839
19123
  `);
18840
19124
  });
18841
19125
  return;
@@ -18858,10 +19142,10 @@ async function runGenerateRecorded(opts) {
18858
19142
  });
18859
19143
  }
18860
19144
  }
18861
- const bundleDir = path55.join(outDirAbs, taskName);
18862
- if (existsSync45(path55.join(bundleDir, "component.json"))) {
19145
+ const bundleDir = path57.join(outDirAbs, taskName);
19146
+ if (existsSync47(path57.join(bundleDir, "component.json"))) {
18863
19147
  try {
18864
- const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
19148
+ const prior = readBundleManifest(readFileSync44(path57.join(bundleDir, "component.json"), "utf8")).manifest;
18865
19149
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18866
19150
  fail(opts, ExitCode.InputValidation, {
18867
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`,
@@ -19028,7 +19312,7 @@ init_invocation();
19028
19312
  init_output();
19029
19313
  import { intro, isCancel, outro, password } from "@clack/prompts";
19030
19314
  import fs from "node:fs";
19031
- import path30 from "node:path";
19315
+ import path31 from "node:path";
19032
19316
  var INIT_DESCRIPTION = {
19033
19317
  name: "init",
19034
19318
  summary: "Configure the OpenRouter credential in .env, and optionally a Figma token (idempotent).",
@@ -19070,7 +19354,7 @@ async function runInit(flags) {
19070
19354
  printDescription(INIT_DESCRIPTION);
19071
19355
  return;
19072
19356
  }
19073
- const envPath = path30.resolve(process.cwd(), ".env");
19357
+ const envPath = path31.resolve(process.cwd(), ".env");
19074
19358
  const existing = fs.existsSync(envPath) ? parseEnv(fs.readFileSync(envPath, "utf8")) : /* @__PURE__ */ new Map();
19075
19359
  let figmaToken = flags.figmaToken ?? existing.get(ENV_KEYS.figma);
19076
19360
  let openrouterKey = flags.openrouterKey ?? existing.get(ENV_KEYS.openrouter);
@@ -19091,7 +19375,7 @@ async function runInit(flags) {
19091
19375
  if (openrouterKey) next.set(ENV_KEYS.openrouter, openrouterKey);
19092
19376
  if (figmaToken) next.set(ENV_KEYS.figma, figmaToken);
19093
19377
  const changed = [...next].some(([key, value]) => existing.get(key) !== value);
19094
- const gitignorePath = path30.resolve(process.cwd(), ".gitignore");
19378
+ const gitignorePath = path31.resolve(process.cwd(), ".gitignore");
19095
19379
  const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf8") : "";
19096
19380
  const gitignoreCoversEnv = gitignore.split("\n").some((line) => [".env", ".env", ".env*"].includes(line.trim()));
19097
19381
  if (flags.dryRun) {
@@ -19147,14 +19431,14 @@ init_invocation();
19147
19431
  init_output();
19148
19432
  init_entitlement();
19149
19433
  import { confirm as confirm2, isCancel as isCancel2 } from "@clack/prompts";
19150
- 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";
19151
19435
 
19152
19436
  // packages/cli/src/pipeline.ts
19153
19437
  init_src2();
19154
19438
  init_src5();
19155
19439
  init_src4();
19156
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "node:fs";
19157
- import path31 from "node:path";
19440
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
19441
+ import path32 from "node:path";
19158
19442
 
19159
19443
  // packages/cli/src/assets-module.ts
19160
19444
  init_src();
@@ -19490,8 +19774,8 @@ async function runGenerationPipeline(input) {
19490
19774
  });
19491
19775
  const written = [];
19492
19776
  if (!input.dryRun) {
19493
- const dir = path31.resolve(input.outDir, semantics.componentName);
19494
- mkdirSync6(dir, { recursive: true });
19777
+ const dir = path32.resolve(input.outDir, semantics.componentName);
19778
+ mkdirSync7(dir, { recursive: true });
19495
19779
  const files = {
19496
19780
  // Bundle-local tokens: THE emission the component's CSS resolves
19497
19781
  // against — the same artifact the verify harness injects. A preview
@@ -19514,14 +19798,14 @@ async function runGenerationPipeline(input) {
19514
19798
  `
19515
19799
  };
19516
19800
  for (const [name, content] of Object.entries(files)) {
19517
- const filePath = path31.join(dir, name);
19518
- writeFileSync10(filePath, content);
19801
+ const filePath = path32.join(dir, name);
19802
+ writeFileSync11(filePath, content);
19519
19803
  written.push(filePath);
19520
19804
  }
19521
19805
  for (const artifact of emitTokenArtifacts(input.mapping)) {
19522
- const filePath = path31.resolve(input.outDir, artifact.path);
19523
- mkdirSync6(path31.dirname(filePath), { recursive: true });
19524
- 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);
19525
19809
  written.push(filePath);
19526
19810
  }
19527
19811
  }
@@ -19579,7 +19863,7 @@ var GENERATE_DESCRIPTION = {
19579
19863
  function resolveProvidedSource(flags, contextFile) {
19580
19864
  let raw;
19581
19865
  try {
19582
- raw = readFileSync22(contextFile, "utf8");
19866
+ raw = readFileSync23(contextFile, "utf8");
19583
19867
  } catch {
19584
19868
  fail(flags, ExitCode.InputValidation, {
19585
19869
  error: `Cannot read context file "${contextFile}".`,
@@ -19699,11 +19983,11 @@ token mapping (${mapping.flat.length} variables):
19699
19983
  let initialCode;
19700
19984
  let initialSemantics;
19701
19985
  try {
19702
- if (existsSync24(flags.out)) {
19986
+ if (existsSync25(flags.out)) {
19703
19987
  for (const entry of readdirSync9(flags.out)) {
19704
19988
  const cjPath = `${flags.out}/${entry}/component.json`;
19705
- if (!existsSync24(cjPath)) continue;
19706
- const cj = JSON.parse(readFileSync22(cjPath, "utf8"));
19989
+ if (!existsSync25(cjPath)) continue;
19990
+ const cj = JSON.parse(readFileSync23(cjPath, "utf8"));
19707
19991
  if (cj.name !== void 0 && Array.isArray(cj.props)) {
19708
19992
  previousApi = JSON.stringify({
19709
19993
  componentName: cj.name,
@@ -19711,14 +19995,14 @@ token mapping (${mapping.flat.length} variables):
19711
19995
  });
19712
19996
  const tsxPath = `${flags.out}/${entry}/${cj.name}.tsx`;
19713
19997
  const cssPath = `${flags.out}/${entry}/${cj.name}.css`;
19714
- if (flags.refine && existsSync24(tsxPath) && existsSync24(cssPath)) {
19998
+ if (flags.refine && existsSync25(tsxPath) && existsSync25(cssPath)) {
19715
19999
  initialCode = {
19716
- tsx: readFileSync22(tsxPath, "utf8"),
19717
- css: readFileSync22(cssPath, "utf8")
20000
+ tsx: readFileSync23(tsxPath, "utf8"),
20001
+ css: readFileSync23(cssPath, "utf8")
19718
20002
  };
19719
20003
  const semPath = `${flags.out}/${entry}/semantics.json`;
19720
- if (existsSync24(semPath)) {
19721
- initialSemantics = JSON.parse(readFileSync22(semPath, "utf8"));
20004
+ if (existsSync25(semPath)) {
20005
+ initialSemantics = JSON.parse(readFileSync23(semPath, "utf8"));
19722
20006
  }
19723
20007
  }
19724
20008
  break;
@@ -20135,6 +20419,17 @@ function buildProgram() {
20135
20419
  ...local["deviceWait"] !== void 0 ? { deviceWait: local["deviceWait"] } : {}
20136
20420
  });
20137
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
+ });
20138
20433
  program.command("logout").description("Forget this machine's stored portal session. Does not end the session itself.").action(async (_o, cmd) => {
20139
20434
  const flags = globalFlags(cmd.parent);
20140
20435
  const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_login(), login_exports));