@tendrilapp/cli 0.1.42 → 0.1.44

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(path55) {
1984
- return `--${path55.join("-")}`;
1983
+ function tokenPathToCssVar(path56) {
1984
+ return `--${path56.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 path55 = variableNameToPath(variable.name);
2029
- if (path55.length === 0) {
2028
+ const path56 = variableNameToPath(variable.name);
2029
+ if (path56.length === 0) {
2030
2030
  throw new DtcgMappingError("empty-name", `Figma variable ${variable.id} has an empty name`);
2031
2031
  }
2032
- return { variable, path: path55 };
2032
+ return { variable, path: path56 };
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: path55 } of entries) {
2053
+ for (const { variable, path: path56 } of entries) {
2054
2054
  const token = toDtcgToken(variable, defaultMode);
2055
2055
  let group = tokens;
2056
- for (const segment of path55.slice(0, -1)) {
2056
+ for (const segment of path56.slice(0, -1)) {
2057
2057
  const existing = group[segment];
2058
2058
  group = existing ?? (group[segment] = {});
2059
2059
  }
2060
- const leaf = path55[path55.length - 1];
2060
+ const leaf = path56[path56.length - 1];
2061
2061
  if (group[leaf] !== void 0) {
2062
- throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path55.join(".")}" (variable ${variable.id})`);
2062
+ throw new DtcgMappingError("duplicate-path", `Duplicate token path "${path56.join(".")}" (variable ${variable.id})`);
2063
2063
  }
2064
2064
  group[leaf] = token;
2065
2065
  flat.push({
2066
- path: path55.join("."),
2067
- cssVar: tokenPathToCssVar(path55),
2066
+ path: path56.join("."),
2067
+ cssVar: tokenPathToCssVar(path56),
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 path55 = ctx.pathById.get(id);
2257
- if (path55 === void 0) ctx.unresolved.add(id);
2258
- return path55;
2256
+ const path56 = ctx.pathById.get(id);
2257
+ if (path56 === void 0) ctx.unresolved.add(id);
2258
+ return path56;
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 path55 = resolveBinding(ctx, id);
2294
- if (path55 !== void 0) tokens.add(path55);
2293
+ const path56 = resolveBinding(ctx, id);
2294
+ if (path56 !== void 0) tokens.add(path56);
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 path55 = resolveBinding(ctx, radiusId);
2303
- if (path55 !== void 0) tokens.add(path55);
2302
+ const path56 = resolveBinding(ctx, radiusId);
2303
+ if (path56 !== void 0) tokens.add(path56);
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 path55 = resolveBinding(ctx, gapId);
2314
- if (path55 !== void 0) {
2315
- layout.gap = path55;
2316
- tokens.add(path55);
2313
+ const path56 = resolveBinding(ctx, gapId);
2314
+ if (path56 !== void 0) {
2315
+ layout.gap = path56;
2316
+ tokens.add(path56);
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 path55 = resolveBinding(ctx, id);
2326
- if (path55 !== void 0) {
2327
- paddingPaths.push(path55);
2328
- tokens.add(path55);
2325
+ const path56 = resolveBinding(ctx, id);
2326
+ if (path56 !== void 0) {
2327
+ paddingPaths.push(path56);
2328
+ tokens.add(path56);
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,34 +6239,43 @@ function classifyBundleSurface(files, opts) {
6239
6239
  const excluded = [];
6240
6240
  const unknown = [];
6241
6241
  for (const raw of files) {
6242
- const path55 = raw.replace(/\\/g, "/").replace(/^\.\//, "");
6243
- const inEvidence = path55.startsWith(`${EVIDENCE_DIR}/`);
6244
- const name = inEvidence ? path55.slice(EVIDENCE_DIR.length + 1) : path55;
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);
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" });
6248
+ continue;
6249
+ }
6250
+ unknown.push(path56);
6251
+ continue;
6252
+ }
6253
+ const name = inEvidence ? path56.slice(EVIDENCE_DIR.length + 1) : path56;
6245
6254
  if (name.includes("/")) {
6246
- unknown.push(path55);
6255
+ unknown.push(path56);
6247
6256
  continue;
6248
6257
  }
6249
6258
  if (inEvidence) {
6250
- if (name === "verify-report.json") published.push({ path: path55, role: "verify-report" });
6251
- else if (name === "diff-legend.txt") published.push({ path: path55, role: "diff-legend" });
6252
- else if (name === "inspect.html") published.push({ path: path55, role: "inspect-sheet" });
6253
- else if (/-mount-failure\.png$/.test(name)) excluded.push({ path: path55, reason: "harness failure diagnostic (regenerated every verify run, never published)" });
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)" });
6254
6263
  else {
6255
6264
  const hit = EVIDENCE_PATTERNS.find((p) => p.re.test(name));
6256
- if (hit !== void 0) published.push({ path: path55, role: hit.role });
6257
- else unknown.push(path55);
6265
+ if (hit !== void 0) published.push({ path: path56, role: hit.role });
6266
+ else unknown.push(path56);
6258
6267
  }
6259
6268
  continue;
6260
6269
  }
6261
- if (name === opts.entry) published.push({ path: path55, role: "entry" });
6262
- else if (name === "styles.css") published.push({ path: path55, role: "styles" });
6263
- else if (name === "tokens.css") published.push({ path: path55, role: "tokens" });
6264
- else if (name === "fonts.css") published.push({ path: path55, role: "fonts" });
6265
- else if (name === "component.json") published.push({ path: path55, role: "manifest" });
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" });
6266
6275
  else {
6267
6276
  const skip = EXCLUDED.find((e) => e.test(name));
6268
- if (skip !== void 0) excluded.push({ path: path55, reason: skip.reason });
6269
- else unknown.push(path55);
6277
+ if (skip !== void 0) excluded.push({ path: path56, reason: skip.reason });
6278
+ else unknown.push(path56);
6270
6279
  }
6271
6280
  }
6272
6281
  const roles = new Set(published.map((p) => p.role));
@@ -6276,8 +6285,8 @@ function missingInspectCrops(sheetText, publishedPaths) {
6276
6285
  const held = new Set(publishedPaths);
6277
6286
  const missing = /* @__PURE__ */ new Set();
6278
6287
  for (const [, name] of sheetText.matchAll(/src="\.\/([^"]+)"/g)) {
6279
- const path55 = `${EVIDENCE_DIR}/${name}`;
6280
- if (!held.has(path55)) missing.add(path55);
6288
+ const path56 = `${EVIDENCE_DIR}/${name}`;
6289
+ if (!held.has(path56)) missing.add(path56);
6281
6290
  }
6282
6291
  return [...missing].sort();
6283
6292
  }
@@ -6385,10 +6394,10 @@ function readScoredFiles(report) {
6385
6394
  const entries = Object.entries(value);
6386
6395
  if (entries.length === 0) return void 0;
6387
6396
  const out = {};
6388
- for (const [path55, digest] of entries) {
6389
- if (path55 === "" || path55.startsWith("/") || path55.includes("..")) return void 0;
6397
+ for (const [path56, digest] of entries) {
6398
+ if (path56 === "" || path56.startsWith("/") || path56.includes("..")) return void 0;
6390
6399
  if (!isSetHash(digest)) return void 0;
6391
- out[path55] = digest;
6400
+ out[path56] = digest;
6392
6401
  }
6393
6402
  return out;
6394
6403
  }
@@ -6396,11 +6405,11 @@ function compareScoredFiles(recorded, actual) {
6396
6405
  const missing = [];
6397
6406
  const unscored = [];
6398
6407
  const changed = [];
6399
- for (const [path55, digest] of Object.entries(recorded)) {
6400
- if (!(path55 in actual)) missing.push(path55);
6401
- else if (actual[path55] !== digest) changed.push(path55);
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);
6402
6411
  }
6403
- for (const path55 of Object.keys(actual)) if (!(path55 in recorded)) unscored.push(path55);
6412
+ for (const path56 of Object.keys(actual)) if (!(path56 in recorded)) unscored.push(path56);
6404
6413
  return { missing: missing.sort(), unscored: unscored.sort(), changed: changed.sort() };
6405
6414
  }
6406
6415
  function scoredRecordingSetHash(report) {
@@ -8453,8 +8462,14 @@ var init_publish_client = __esm({
8453
8462
  this.token = options.token;
8454
8463
  this.send = options.fetch ?? globalThis.fetch;
8455
8464
  }
8456
- acceptTerms(input) {
8457
- return this.json("POST", "/api/consent", input);
8465
+ requestApproval(input) {
8466
+ return this.json("POST", "/api/publish-approvals", input);
8467
+ }
8468
+ pollApproval(input) {
8469
+ return this.json("GET", `/api/publish-approvals/${encodeURIComponent(input.approvalId)}`, null, true);
8470
+ }
8471
+ whoami() {
8472
+ return this.json("GET", "/api/whoami", null, true);
8458
8473
  }
8459
8474
  begin(input) {
8460
8475
  return this.json("POST", "/api/publications", input, true);
@@ -8548,7 +8563,6 @@ var init_publish_client = __esm({
8548
8563
  refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
8549
8564
  ...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
8550
8565
  ...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
8551
- ...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
8552
8566
  ...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
8553
8567
  };
8554
8568
  }
@@ -13791,6 +13805,72 @@ var init_activate = __esm({
13791
13805
  }
13792
13806
  });
13793
13807
 
13808
+ // packages/cli/src/run-presence.ts
13809
+ import { createHash as createHash8 } from "node:crypto";
13810
+ import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
13811
+ import path41 from "node:path";
13812
+ function presenceDir() {
13813
+ return path41.join(path41.dirname(sessionPath()), "runs");
13814
+ }
13815
+ function presenceFile(componentName) {
13816
+ return path41.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
13817
+ }
13818
+ function readCached(componentName) {
13819
+ const file = presenceFile(componentName);
13820
+ if (!existsSync32(file)) return void 0;
13821
+ try {
13822
+ const parsed = JSON.parse(readFileSync29(file, "utf8"));
13823
+ return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
13824
+ } catch {
13825
+ return void 0;
13826
+ }
13827
+ }
13828
+ async function post(origin, token, pathname, body, method = "POST") {
13829
+ const response = await fetch(`${origin}${pathname}`, {
13830
+ method,
13831
+ headers: { authorization: `Bearer ${token}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
13832
+ ...body === void 0 ? {} : { body: JSON.stringify(body) },
13833
+ signal: AbortSignal.timeout(PRESENCE_TIMEOUT_MS)
13834
+ });
13835
+ if (!response.ok) throw new Error(String(response.status));
13836
+ return response.json();
13837
+ }
13838
+ async function reportRunPresence(componentName, phase) {
13839
+ try {
13840
+ const session = readStoredSession();
13841
+ if (session === void 0) return;
13842
+ const cached2 = readCached(componentName);
13843
+ if (cached2 !== void 0 && cached2.origin === session.origin) {
13844
+ const beat = await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, { phase });
13845
+ if (beat.alive === true) return;
13846
+ }
13847
+ const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
13848
+ if (typeof started.runId !== "string") return;
13849
+ mkdirSync10(presenceDir(), { recursive: true });
13850
+ writeFileSync14(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
13851
+ `, { mode: 384 });
13852
+ } catch {
13853
+ }
13854
+ }
13855
+ async function endRunPresence(componentName) {
13856
+ try {
13857
+ const session = readStoredSession();
13858
+ const cached2 = readCached(componentName);
13859
+ rmSync5(presenceFile(componentName), { force: true });
13860
+ if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
13861
+ await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
13862
+ } catch {
13863
+ }
13864
+ }
13865
+ var PRESENCE_TIMEOUT_MS;
13866
+ var init_run_presence = __esm({
13867
+ "packages/cli/src/run-presence.ts"() {
13868
+ "use strict";
13869
+ init_publish_client();
13870
+ PRESENCE_TIMEOUT_MS = 1500;
13871
+ }
13872
+ });
13873
+
13794
13874
  // packages/cli/src/commands/compose.ts
13795
13875
  var compose_exports = {};
13796
13876
  __export(compose_exports, {
@@ -13799,15 +13879,15 @@ __export(compose_exports, {
13799
13879
  compositionPairsFor: () => compositionPairsFor,
13800
13880
  runCompose: () => runCompose
13801
13881
  });
13802
- import { createHash as createHash8 } from "node:crypto";
13803
- import { existsSync as existsSync32, readFileSync as readFileSync29, readdirSync as readdirSync14 } from "node:fs";
13804
- import path41 from "node:path";
13882
+ import { createHash as createHash9 } from "node:crypto";
13883
+ import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
13884
+ import path42 from "node:path";
13805
13885
  function compositionPairsFor(hostSet, roots) {
13806
- const parent = path41.dirname(hostSet);
13886
+ const parent = path42.dirname(hostSet);
13807
13887
  const explicitRoots = [...new Set(roots)];
13808
13888
  let skippedParent;
13809
13889
  let parentRoot = [];
13810
- if (!explicitRoots.some((r) => path41.resolve(r) === path41.resolve(parent))) {
13890
+ if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
13811
13891
  let parentEntries = 0;
13812
13892
  try {
13813
13893
  parentEntries = readdirSync14(parent).length;
@@ -13848,7 +13928,7 @@ function substitutionPairs(edges, hostSet) {
13848
13928
  });
13849
13929
  }
13850
13930
  const pair = pairs.get(key);
13851
- const poseDisplay = e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ");
13931
+ const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
13852
13932
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13853
13933
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13854
13934
  }
@@ -13860,7 +13940,7 @@ function runCompose(flags) {
13860
13940
  return;
13861
13941
  }
13862
13942
  const base = process.env["INIT_CWD"] ?? process.cwd();
13863
- const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path41.resolve(base, d)) : [base];
13943
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
13864
13944
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13865
13945
  fail(flags, ExitCode.InputValidation, {
13866
13946
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13869,7 +13949,7 @@ function runCompose(flags) {
13869
13949
  });
13870
13950
  }
13871
13951
  if (flags.set !== void 0) {
13872
- runComposeConfirm(flags, path41.resolve(base, flags.set), roots);
13952
+ runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
13873
13953
  return;
13874
13954
  }
13875
13955
  const index = buildComposeIndex(roots);
@@ -13887,7 +13967,7 @@ function runCompose(flags) {
13887
13967
  }
13888
13968
  let lastHost = "";
13889
13969
  for (const e of edges) {
13890
- const host = `${path41.basename(e.hostSet)}`;
13970
+ const host = `${path42.basename(e.hostSet)}`;
13891
13971
  if (host !== lastHost) {
13892
13972
  process.stdout.write(`
13893
13973
  ${host}
@@ -13895,7 +13975,7 @@ ${host}
13895
13975
  lastHost = host;
13896
13976
  }
13897
13977
  const partners = e.partners.map((p) => p.displayName).join(" / ") || "\u2014";
13898
- const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13978
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13899
13979
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13900
13980
  `);
13901
13981
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13907,14 +13987,14 @@ ${NOTE}
13907
13987
  });
13908
13988
  }
13909
13989
  function runComposeConfirm(flags, hostSet, roots) {
13910
- if (!existsSync32(path41.join(hostSet, "recording-set.json"))) {
13990
+ if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
13911
13991
  fail(flags, ExitCode.InputValidation, {
13912
13992
  error: `no recording-set.json in ${hostSet}`,
13913
13993
  code: "no-recording-set",
13914
13994
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13915
13995
  });
13916
13996
  }
13917
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path41.dirname(hostSet)])];
13997
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
13918
13998
  const index = buildComposeIndex(scanRoots);
13919
13999
  const edges = composeReport(index);
13920
14000
  const pairs = substitutionPairs(edges, hostSet);
@@ -13997,7 +14077,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13997
14077
  // full recording-set hash join lands with pin authoring, where
13998
14078
  // task configs exist.)
13999
14079
  manifestSha256: Object.fromEntries(
14000
- p.partnerDirs.map((d) => [path41.relative(hostSet, d), createHash8("sha256").update(readFileSync29(path41.join(d, "recording-set.json"))).digest("hex")])
14080
+ p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash9("sha256").update(readFileSync30(path42.join(d, "recording-set.json"))).digest("hex")])
14001
14081
  )
14002
14082
  },
14003
14083
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -14077,10 +14157,10 @@ __export(record_exports, {
14077
14157
  runRecordPlan: () => runRecordPlan,
14078
14158
  runRecordStatus: () => runRecordStatus
14079
14159
  });
14080
- import { existsSync as existsSync33, mkdtempSync as mkdtempSync2, readFileSync as readFileSync30, readdirSync as readdirSync15 } from "node:fs";
14160
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
14081
14161
  import os7 from "node:os";
14082
- import path42 from "node:path";
14083
- import { writeFileSync as writeFileSync14 } from "node:fs";
14162
+ import path43 from "node:path";
14163
+ import { writeFileSync as writeFileSync15 } from "node:fs";
14084
14164
  function recordsInteractionState(reports) {
14085
14165
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
14086
14166
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -14102,7 +14182,7 @@ function interactionDisclosure(component, reports) {
14102
14182
  };
14103
14183
  }
14104
14184
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
14105
- const env = JSON.parse(readFileSync30(file, "utf8"));
14185
+ const env = JSON.parse(readFileSync31(file, "utf8"));
14106
14186
  const text = env.content.map((c) => c.text ?? "").join("\n");
14107
14187
  const symbols = [];
14108
14188
  const walk2 = (node, ancestor) => {
@@ -14160,8 +14240,8 @@ function runRecordPlan(opts) {
14160
14240
  if (rawFile !== void 0) {
14161
14241
  try {
14162
14242
  const envelope = rawEnvelopeFromFile(rawFile, opts.metadataRawPartsFile !== void 0);
14163
- const tmp = path42.join(mkdtempSync2(path42.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14164
- writeFileSync14(tmp, JSON.stringify(envelope));
14243
+ const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14244
+ writeFileSync15(tmp, JSON.stringify(envelope));
14165
14245
  metadataEntries.push({ file: tmp });
14166
14246
  } catch (err) {
14167
14247
  fail(opts, ExitCode.InputValidation, {
@@ -14182,7 +14262,7 @@ function runRecordPlan(opts) {
14182
14262
  let metadataTruncated = false;
14183
14263
  for (const { file, frame } of metadataEntries) {
14184
14264
  try {
14185
- const parsed = symbolsFromMetadataEnvelope(path42.resolve(file), frame);
14265
+ const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
14186
14266
  symbols.push(...parsed.symbols);
14187
14267
  if (parsed.truncated) metadataTruncated = true;
14188
14268
  } catch (err) {
@@ -14216,7 +14296,7 @@ function runRecordPlan(opts) {
14216
14296
  if (symbols.length === 0) {
14217
14297
  const leads = metadataEntries.flatMap(({ file }) => {
14218
14298
  try {
14219
- const env = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14299
+ const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14220
14300
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14221
14301
  } catch {
14222
14302
  return [];
@@ -14309,7 +14389,7 @@ function runRecordPlan(opts) {
14309
14389
  // a CLI flag and the MCP surface has no parameter for it.
14310
14390
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14311
14391
  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.",
14312
- userRuns: [`rm ${quoteArg(path42.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14392
+ userRuns: [`rm ${quoteArg(path43.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14313
14393
  },
14314
14394
  {
14315
14395
  id: "larger-allowance",
@@ -14319,6 +14399,7 @@ function runRecordPlan(opts) {
14319
14399
  ],
14320
14400
  limitHintIfWhoamiIsSilent: LIMIT_HINT
14321
14401
  };
14402
+ void reportRunPresence(opts.component, "recording");
14322
14403
  emitData(
14323
14404
  opts,
14324
14405
  {
@@ -14507,7 +14588,7 @@ function runRecordNext(opts) {
14507
14588
  const progress = payload["progress"];
14508
14589
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14509
14590
  \u2192 ${payload["note"]}
14510
- \u2192 then: ${tendrilCommand(`record ingest --set ${path42.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14591
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path43.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14511
14592
  `);
14512
14593
  });
14513
14594
  }
@@ -14581,7 +14662,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14581
14662
  const skipped = [];
14582
14663
  const failed = [];
14583
14664
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14584
- if (existsSync33(path42.join(setDir, rep, name))) {
14665
+ if (existsSync34(path43.join(setDir, rep, name))) {
14585
14666
  skipped.push(name);
14586
14667
  continue;
14587
14668
  }
@@ -14603,16 +14684,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14603
14684
  }
14604
14685
  function rawEnvelopeFromFile(file, parts) {
14605
14686
  if (parts) {
14606
- const blocks = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14687
+ const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14607
14688
  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");
14608
14689
  return { content: blocks.map((text) => ({ type: "text", text })) };
14609
14690
  }
14610
- return { content: [{ type: "text", text: readFileSync30(path42.resolve(file), "utf8") }] };
14691
+ return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
14611
14692
  }
14612
14693
  async function runRecordIngest(opts) {
14613
14694
  let payload;
14614
14695
  try {
14615
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync30(path42.resolve(opts.file), "utf8"));
14696
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync31(path43.resolve(opts.file), "utf8"));
14616
14697
  } catch (err) {
14617
14698
  fail(opts, ExitCode.InputValidation, {
14618
14699
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14624,7 +14705,7 @@ async function runRecordIngest(opts) {
14624
14705
  fail(opts, ExitCode.InputValidation, {
14625
14706
  error: "--raw is for text tool responses; screenshots are binary",
14626
14707
  code: "envelope-invalid",
14627
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14708
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14628
14709
  });
14629
14710
  }
14630
14711
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14644,7 +14725,7 @@ async function runRecordIngest(opts) {
14644
14725
  remediation: REINGEST_GUIDANCE
14645
14726
  });
14646
14727
  }
14647
- writeFileSync14(path42.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14728
+ writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14648
14729
  `);
14649
14730
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14650
14731
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14668,7 +14749,7 @@ async function runRecordIngest(opts) {
14668
14749
  remediation: REINGEST_GUIDANCE
14669
14750
  });
14670
14751
  }
14671
- writeFileSync14(path42.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14752
+ writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14672
14753
  `);
14673
14754
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14674
14755
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14684,7 +14765,7 @@ async function runRecordIngest(opts) {
14684
14765
  if (assets !== void 0) {
14685
14766
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14686
14767
  `);
14687
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14768
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14688
14769
  `);
14689
14770
  }
14690
14771
  });
@@ -14757,14 +14838,14 @@ async function runRecordIngestRep(opts) {
14757
14838
  if (assets !== void 0) {
14758
14839
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14759
14840
  `);
14760
- for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14841
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14761
14842
  `);
14762
14843
  }
14763
14844
  });
14764
14845
  }
14765
14846
  function runRecordAsset(opts) {
14766
14847
  if (opts.dir !== void 0) {
14767
- const dir = path42.resolve(opts.dir);
14848
+ const dir = path43.resolve(opts.dir);
14768
14849
  const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14769
14850
  if (names.length === 0) {
14770
14851
  fail(opts, ExitCode.InputValidation, {
@@ -14776,7 +14857,7 @@ function runRecordAsset(opts) {
14776
14857
  const ingested = [];
14777
14858
  try {
14778
14859
  for (const name of names) {
14779
- ingestAsset(opts.setDir, opts.rep, name, readFileSync30(path42.join(dir, name)));
14860
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
14780
14861
  ingested.push(name);
14781
14862
  }
14782
14863
  } catch (err) {
@@ -14796,11 +14877,11 @@ function runRecordAsset(opts) {
14796
14877
  fail(opts, ExitCode.InputValidation, {
14797
14878
  error: "pass --name and --file for a single asset, or --dir for a batch",
14798
14879
  code: "asset-rejected",
14799
- remediation: tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14880
+ remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14800
14881
  });
14801
14882
  }
14802
14883
  try {
14803
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync30(path42.resolve(opts.file)));
14884
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
14804
14885
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14805
14886
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14806
14887
  `);
@@ -14817,8 +14898,8 @@ function runRecordStatus(opts) {
14817
14898
  const status = sessionStatus(opts.setDir);
14818
14899
  const composition = (() => {
14819
14900
  try {
14820
- const setDir = path42.resolve(opts.setDir);
14821
- const { open, standing, invalid } = compositionPairsFor(setDir, [path42.dirname(setDir)]);
14901
+ const setDir = path43.resolve(opts.setDir);
14902
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
14822
14903
  return {
14823
14904
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14824
14905
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14839,7 +14920,7 @@ function runRecordStatus(opts) {
14839
14920
  }
14840
14921
  }
14841
14922
  process.stdout.write(
14842
- status.motion.recorded ? motionTruthFor(path42.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\`
14923
+ status.motion.recorded ? motionTruthFor(path43.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14843
14924
  ` : "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"
14844
14925
  );
14845
14926
  if ("unavailable" in composition) {
@@ -14847,7 +14928,7 @@ function runRecordStatus(opts) {
14847
14928
  `);
14848
14929
  } else if (composition.openPairs.length > 0) {
14849
14930
  process.stdout.write(
14850
- `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 ${path42.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14931
+ `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path43.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14851
14932
  `
14852
14933
  );
14853
14934
  } else if (composition.confirmed > 0) {
@@ -14887,7 +14968,7 @@ function narrowedRoles(derived, override) {
14887
14968
  function rolesFromFile(opts, file, derived) {
14888
14969
  let json;
14889
14970
  try {
14890
- json = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14971
+ json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14891
14972
  } catch (err) {
14892
14973
  fail(opts, ExitCode.InputValidation, {
14893
14974
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -14925,11 +15006,11 @@ function rolesFromFile(opts, file, derived) {
14925
15006
  };
14926
15007
  }
14927
15008
  function runRecordFinish(opts) {
14928
- if (!existsSync33(path42.join(opts.setDir, "recording-set.json"))) {
15009
+ if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
14929
15010
  fail(opts, ExitCode.InputValidation, {
14930
15011
  error: `no recording-set.json in ${opts.setDir}`,
14931
15012
  code: "no-recording-set",
14932
- remediation: `Run \`${tendrilCommand(`record plan --set ${path42.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
15013
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path43.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14933
15014
  });
14934
15015
  }
14935
15016
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -14957,17 +15038,17 @@ function runRecordFinish(opts) {
14957
15038
  fail(opts, ExitCode.ConfirmationRequired, {
14958
15039
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
14959
15040
  code: "roles-confirmation-not-interactive",
14960
- remediation: `A human runs \`${tendrilCommand(`record finish --set ${path42.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
15041
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path43.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14961
15042
  });
14962
15043
  }
14963
15044
  const merged = { ...raw, roles };
14964
- const { issues } = validateRecordingSet(merged, (rel) => existsSync33(path42.join(opts.setDir, rel)));
15045
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
14965
15046
  const errors = issues.filter((i) => i.severity === "error");
14966
15047
  if (errors.length > 0) {
14967
15048
  fail(opts, ExitCode.InputValidation, {
14968
15049
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
14969
15050
  code: "recording-set-invalid",
14970
- remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path42.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
15051
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path43.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14971
15052
  });
14972
15053
  }
14973
15054
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -14986,6 +15067,7 @@ var init_record = __esm({
14986
15067
  init_src();
14987
15068
  init_src4();
14988
15069
  init_output();
15070
+ init_run_presence();
14989
15071
  init_entitlement();
14990
15072
  init_invocation();
14991
15073
  init_compose2();
@@ -15017,9 +15099,9 @@ var init_record = __esm({
15017
15099
  });
15018
15100
 
15019
15101
  // packages/cli/src/font-guidance.ts
15020
- import path43 from "node:path";
15102
+ import path44 from "node:path";
15021
15103
  function fontsUnprovenRemediation(setDir) {
15022
- const set = setDir === void 0 ? void 0 : path43.resolve(setDir);
15104
+ const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
15023
15105
  if (set !== void 0) {
15024
15106
  try {
15025
15107
  const needs = recordedFontNeeds(set);
@@ -15094,8 +15176,8 @@ __export(fonts_exports, {
15094
15176
  runFontsResolveSet: () => runFontsResolveSet,
15095
15177
  runFontsStatus: () => runFontsStatus
15096
15178
  });
15097
- import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15098
- import path44 from "node:path";
15179
+ import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15180
+ import path45 from "node:path";
15099
15181
  async function runFontsResolve(opts) {
15100
15182
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
15101
15183
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15116,7 +15198,7 @@ async function runFontsResolve(opts) {
15116
15198
  }
15117
15199
  }
15118
15200
  async function runFontsResolveSet(opts) {
15119
- const setDir = path44.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15201
+ const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15120
15202
  let needs = [];
15121
15203
  try {
15122
15204
  needs = recordedFontNeeds(setDir);
@@ -15211,16 +15293,16 @@ async function runFontsResolveSet(opts) {
15211
15293
  }
15212
15294
  }
15213
15295
  function runFontsStatus(opts) {
15214
- const manifestPath2 = path44.join(opts.cacheDir, "manifest.json");
15215
- if (!existsSync34(manifestPath2)) {
15296
+ const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
15297
+ if (!existsSync35(manifestPath2)) {
15216
15298
  fail(opts, ExitCode.FontsUnproven, {
15217
15299
  error: `no font cache at ${opts.cacheDir}`,
15218
15300
  code: "fonts-unresolved",
15219
15301
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15220
15302
  });
15221
15303
  }
15222
- const faces = JSON.parse(readFileSync31(manifestPath2, "utf8"));
15223
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path44.resolve(opts.lock), opts.cacheDir) : null;
15304
+ const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
15305
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path45.resolve(opts.lock), opts.cacheDir) : null;
15224
15306
  emitData(opts, { faces, lockVerdicts }, () => {
15225
15307
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15226
15308
  `);
@@ -15264,13 +15346,13 @@ function familyMismatch(family, declared) {
15264
15346
  }
15265
15347
  function runFontsAdd(opts) {
15266
15348
  if (opts.set !== void 0) {
15267
- const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15349
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15268
15350
  const mismatch = familyMismatch(opts.family, declared);
15269
15351
  if (mismatch !== void 0) {
15270
15352
  fail(opts, ExitCode.InputValidation, {
15271
15353
  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.`,
15272
15354
  code: "font-family-not-declared",
15273
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path44.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.`
15355
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path45.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
15274
15356
  });
15275
15357
  }
15276
15358
  } else {
@@ -15329,13 +15411,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15329
15411
  }
15330
15412
  function runFontsAddSystem(opts) {
15331
15413
  if (opts.set !== void 0) {
15332
- const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15414
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15333
15415
  const mismatch = familyMismatch(opts.family, declared);
15334
15416
  if (mismatch !== void 0) {
15335
15417
  fail(opts, ExitCode.InputValidation, {
15336
15418
  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.`,
15337
15419
  code: "font-family-not-declared",
15338
- remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path44.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15420
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path45.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15339
15421
  });
15340
15422
  }
15341
15423
  } else {
@@ -15385,12 +15467,12 @@ var init_fonts = __esm({
15385
15467
  });
15386
15468
 
15387
15469
  // packages/cli/src/profile-input.ts
15388
- import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15389
- import path45 from "node:path";
15470
+ import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15471
+ import path46 from "node:path";
15390
15472
  function loadCodebaseProfile(flags, profilePath) {
15391
15473
  if (profilePath === void 0) return null;
15392
- const abs = path45.resolve(profilePath);
15393
- if (!existsSync35(abs)) {
15474
+ const abs = path46.resolve(profilePath);
15475
+ if (!existsSync36(abs)) {
15394
15476
  fail(flags, ExitCode.InputValidation, {
15395
15477
  error: `no profile at ${abs}`,
15396
15478
  code: "profile_missing",
@@ -15398,7 +15480,7 @@ function loadCodebaseProfile(flags, profilePath) {
15398
15480
  });
15399
15481
  }
15400
15482
  try {
15401
- return readCodebaseProfile(readFileSync32(abs, "utf8"));
15483
+ return readCodebaseProfile(readFileSync33(abs, "utf8"));
15402
15484
  } catch (error) {
15403
15485
  fail(flags, ExitCode.InputValidation, {
15404
15486
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15440,8 +15522,8 @@ __export(verify_exports, {
15440
15522
  runVerify: () => runVerify,
15441
15523
  verdictCaveatsFor: () => verdictCaveatsFor
15442
15524
  });
15443
- import { existsSync as existsSync36, readFileSync as readFileSync33, rmSync as rmSync5, writeFileSync as writeFileSync15 } from "node:fs";
15444
- import path46 from "node:path";
15525
+ import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
15526
+ import path47 from "node:path";
15445
15527
  function interactionCoverage(behaviors) {
15446
15528
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15447
15529
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15682,12 +15764,12 @@ function compositionReport(input) {
15682
15764
  function eyeCheck(bundleDir) {
15683
15765
  return {
15684
15766
  command: tendrilCommand(`inspect "${bundleDir}"`),
15685
- sheetPath: path46.join(bundleDir, "verify-evidence", "inspect.html"),
15767
+ sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
15686
15768
  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."
15687
15769
  };
15688
15770
  }
15689
15771
  function evidenceArtifacts(evidenceDir, reps) {
15690
- const named = (name) => existsSync36(path46.join(evidenceDir, name)) ? name : null;
15772
+ const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
15691
15773
  return {
15692
15774
  legend: named("diff-legend.txt"),
15693
15775
  configs: reps.map((rep) => {
@@ -15735,7 +15817,7 @@ function taskFromManifest(opts, manifest, setDir) {
15735
15817
  const adapterSlugs = Object.keys(manifest.propAdapter);
15736
15818
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15737
15819
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15738
- const registry = Object.values(TASKS).find((t) => path46.resolve(t.set) === path46.resolve(setDir));
15820
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15739
15821
  const authored = (() => {
15740
15822
  if (registry !== void 0) return void 0;
15741
15823
  try {
@@ -15796,19 +15878,19 @@ function verdictCaveatsFor(input) {
15796
15878
  async function runVerify(opts) {
15797
15879
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15798
15880
  let recordingSetDrift;
15799
- const setOverride = opts.set !== void 0 ? path46.resolve(callerCwd, opts.set) : void 0;
15800
- opts = { ...opts, bundleDir: path46.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15801
- if (!existsSync36(opts.bundleDir)) {
15881
+ const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
15882
+ opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15883
+ if (!existsSync37(opts.bundleDir)) {
15802
15884
  fail(opts, ExitCode.InputValidation, {
15803
15885
  error: `bundle directory not found: ${opts.bundleDir}`,
15804
15886
  code: "bundle-missing",
15805
15887
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15806
15888
  });
15807
15889
  }
15808
- const manifestPath2 = path46.join(opts.bundleDir, "component.json");
15890
+ const manifestPath2 = path47.join(opts.bundleDir, "component.json");
15809
15891
  let manifest;
15810
- if (existsSync36(manifestPath2)) {
15811
- const { manifest: parsed, issues } = readBundleManifest(readFileSync33(manifestPath2, "utf8"));
15892
+ if (existsSync37(manifestPath2)) {
15893
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
15812
15894
  if (issues.length > 0) {
15813
15895
  fail(opts, ExitCode.InputValidation, {
15814
15896
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15839,21 +15921,21 @@ async function runVerify(opts) {
15839
15921
  task = registry;
15840
15922
  } else if (manifest !== void 0) {
15841
15923
  const resolveSetDir = (p) => {
15842
- if (path46.isAbsolute(p)) return p;
15843
- const fromRepo = path46.resolve(REPO_ROOT, p);
15844
- if (existsSync36(fromRepo)) return fromRepo;
15845
- return path46.resolve(callerCwd, p);
15924
+ if (path47.isAbsolute(p)) return p;
15925
+ const fromRepo = path47.resolve(REPO_ROOT, p);
15926
+ if (existsSync37(fromRepo)) return fromRepo;
15927
+ return path47.resolve(callerCwd, p);
15846
15928
  };
15847
15929
  const setDir = opts.set ?? resolveSetDir(manifest.provenance.recordingSet.path);
15848
- if (!existsSync36(path46.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path46.resolve(t.set) === path46.resolve(setDir))) {
15930
+ if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
15849
15931
  fail(opts, ExitCode.RecordingIncomplete, {
15850
15932
  error: `recording set not found or unmanifested: ${setDir}`,
15851
15933
  code: "recording-set-missing",
15852
15934
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15853
15935
  });
15854
15936
  }
15855
- const registry = Object.values(TASKS).find((t) => path46.resolve(t.set) === path46.resolve(setDir));
15856
- if (registry !== void 0 && !existsSync36(path46.join(setDir, "recording-set.json"))) {
15937
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15938
+ if (registry !== void 0 && !existsSync37(path47.join(setDir, "recording-set.json"))) {
15857
15939
  const recordedSlugs = registry.configs.map((c) => c.rep);
15858
15940
  const adapterSlugs = Object.keys(manifest.propAdapter);
15859
15941
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15885,9 +15967,9 @@ async function runVerify(opts) {
15885
15967
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15886
15968
  }
15887
15969
  for (const name of [manifest.entry, "styles.css", "tokens.css"]) {
15888
- const p = path46.join(opts.bundleDir, name);
15889
- if (!existsSync36(p)) continue;
15890
- const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync33(p)));
15970
+ const p = path47.join(opts.bundleDir, name);
15971
+ if (!existsSync37(p)) continue;
15972
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync34(p)));
15891
15973
  if (issues.length > 0) {
15892
15974
  fail(opts, ExitCode.InputValidation, {
15893
15975
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15925,7 +16007,7 @@ async function runVerify(opts) {
15925
16007
  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)`);
15926
16008
  }
15927
16009
  const missing = task.configs.filter(
15928
- (c) => !existsSync36(path46.join(task.set, c.rep, "get_screenshot.json")) || !existsSync36(path46.join(task.set, c.rep, "get_metadata.json"))
16010
+ (c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
15929
16011
  );
15930
16012
  if (missing.length > 0) {
15931
16013
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -15935,8 +16017,8 @@ async function runVerify(opts) {
15935
16017
  });
15936
16018
  }
15937
16019
  const bar = BARS2[opts.bar];
15938
- const evidenceDir = path46.join(opts.bundleDir, "verify-evidence");
15939
- rmSync5(evidenceDir, { recursive: true, force: true });
16020
+ const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
16021
+ rmSync6(evidenceDir, { recursive: true, force: true });
15940
16022
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
15941
16023
  const quality = await checkBundleQuality(
15942
16024
  opts.bundleDir,
@@ -15953,7 +16035,7 @@ async function runVerify(opts) {
15953
16035
  // ASKED, never "follows every convention".
15954
16036
  loadCodebaseProfile(opts, opts.profile) ?? void 0
15955
16037
  );
15956
- const bundleCss = ["tokens.css", "styles.css"].map((f) => path46.join(opts.bundleDir, f)).filter((f) => existsSync36(f)).map((f) => readFileSync33(f, "utf8")).join("\n");
16038
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n");
15957
16039
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
15958
16040
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
15959
16041
  ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
@@ -15974,10 +16056,10 @@ async function runVerify(opts) {
15974
16056
  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.`);
15975
16057
  }
15976
16058
  const verifyCallerCwd = process.env["INIT_CWD"] ?? process.cwd();
15977
- const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path46.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
16059
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path47.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15978
16060
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
15979
16061
  const renderExpectations = verifyPins.pins.map((pin) => ({
15980
- modulePath: path46.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16062
+ modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15981
16063
  component: pin.entryComponent,
15982
16064
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
15983
16065
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -16270,7 +16352,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16270
16352
  }
16271
16353
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16272
16354
  `);
16273
- const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path46.join(opts.bundleDir, f)).filter((f) => existsSync36(f)).map((f) => readFileSync33(f, "utf8")).join("\n")));
16355
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n")));
16274
16356
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16275
16357
  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)
16276
16358
  `);
@@ -16325,7 +16407,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16325
16407
  persistReport(opts, report, evidenceDir);
16326
16408
  }
16327
16409
  function persistReport(opts, report, evidenceDir) {
16328
- if (!existsSync36(evidenceDir)) return;
16410
+ if (!existsSync37(evidenceDir)) return;
16329
16411
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16330
16412
  const withExit = {
16331
16413
  ...report,
@@ -16333,9 +16415,9 @@ function persistReport(opts, report, evidenceDir) {
16333
16415
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16334
16416
  };
16335
16417
  try {
16336
- writeFileSync15(
16337
- path46.join(evidenceDir, VERIFY_REPORT_FILENAME),
16338
- `${JSON.stringify(verifyReportForTransport(withExit, path46.basename(opts.bundleDir)), null, 2)}
16418
+ writeFileSync16(
16419
+ path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
16420
+ `${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
16339
16421
  `
16340
16422
  );
16341
16423
  } catch (e) {
@@ -16385,11 +16467,11 @@ __export(engine_exports, {
16385
16467
  runEngineBrief: () => runEngineBrief,
16386
16468
  runEngineScore: () => runEngineScore
16387
16469
  });
16388
- import { appendFileSync, existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16389
- import path47 from "node:path";
16470
+ import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16471
+ import path48 from "node:path";
16390
16472
  function resolveEngineTask(opts, callerCwd) {
16391
- const asPath = path47.resolve(callerCwd, opts.taskOrSet);
16392
- const isSet = existsSync37(path47.join(asPath, "recording-set.json"));
16473
+ const asPath = path48.resolve(callerCwd, opts.taskOrSet);
16474
+ const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
16393
16475
  const registry = TASKS[opts.taskOrSet];
16394
16476
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16395
16477
  if (isSet) {
@@ -16398,7 +16480,7 @@ function resolveEngineTask(opts, callerCwd) {
16398
16480
  for (const d of authored.disclosures) warn(opts, d);
16399
16481
  return {
16400
16482
  task: authored.task,
16401
- name: path47.basename(asPath),
16483
+ name: path48.basename(asPath),
16402
16484
  ref: asPath,
16403
16485
  disclosures: authored.disclosures,
16404
16486
  interactionEvidence: authored.api.interactionEvidence,
@@ -16425,10 +16507,11 @@ function runEngineBrief(opts) {
16425
16507
  requireEntitlement(opts);
16426
16508
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16427
16509
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16510
+ void reportRunPresence(name, "implementing");
16428
16511
  const bar = BARS3[opts.bar];
16429
- if (existsSync37(path47.join(task.set, "recording-set.json"))) {
16512
+ if (existsSync38(path48.join(task.set, "recording-set.json"))) {
16430
16513
  try {
16431
- const { open, skippedParent } = compositionPairsFor(path47.resolve(task.set), [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16514
+ const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16432
16515
  if (skippedParent !== void 0) {
16433
16516
  disclosures.push(
16434
16517
  `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.`
@@ -16437,7 +16520,7 @@ function runEngineBrief(opts) {
16437
16520
  if (open.length > 0) {
16438
16521
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16439
16522
  disclosures.push(
16440
- `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 ${path47.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16523
+ `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path48.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16441
16524
  );
16442
16525
  }
16443
16526
  } catch (err) {
@@ -16454,9 +16537,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16454
16537
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16455
16538
  const segments = buildSegments(task, "files");
16456
16539
  let notRecorded;
16457
- const manifestPath2 = path47.join(task.set, "recording-set.json");
16458
- if (existsSync37(manifestPath2)) {
16459
- notRecorded = JSON.parse(readFileSync34(manifestPath2, "utf8")).notRecorded;
16540
+ const manifestPath2 = path48.join(task.set, "recording-set.json");
16541
+ if (existsSync38(manifestPath2)) {
16542
+ notRecorded = JSON.parse(readFileSync35(manifestPath2, "utf8")).notRecorded;
16460
16543
  }
16461
16544
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16462
16545
 
@@ -16464,7 +16547,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16464
16547
  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.
16465
16548
  ${notRecorded}` : "";
16466
16549
  let fontProvisioning;
16467
- if (existsSync37(manifestPath2)) {
16550
+ if (existsSync38(manifestPath2)) {
16468
16551
  const missingFams = unprovisionedFamilies(task.set);
16469
16552
  const unprovided = unprovisionedFaces(task.set);
16470
16553
  const weightOnly = missingFams.length === 0;
@@ -16486,7 +16569,7 @@ ${notRecorded}` : "";
16486
16569
  };
16487
16570
  }
16488
16571
  }
16489
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16572
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16490
16573
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16491
16574
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16492
16575
 
@@ -16522,10 +16605,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16522
16605
 
16523
16606
  === TASK PAYLOAD (recorded truth, verbatim) ===
16524
16607
  ${segments}`;
16525
- const payloadFile = path47.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16526
- const candidateDirSuggestion = path47.resolve(callerCwd, `tendril-out/${name}-candidate`);
16527
- mkdirSync10(path47.dirname(payloadFile), { recursive: true });
16528
- writeFileSync16(payloadFile, payload);
16608
+ const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16609
+ const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
16610
+ mkdirSync11(path48.dirname(payloadFile), { recursive: true });
16611
+ writeFileSync17(payloadFile, payload);
16529
16612
  emitData(
16530
16613
  opts,
16531
16614
  {
@@ -16571,7 +16654,7 @@ ${segments}`;
16571
16654
  // command must search the same bundle roots the pins came
16572
16655
  // from, or the oracle and the brief describe different worlds.
16573
16656
  `Run \`${tendrilCommand(
16574
- `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path47.resolve(callerCwd, opts.library))}` : ""} --json`
16657
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path48.resolve(callerCwd, opts.library))}` : ""} --json`
16575
16658
  )}\` \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.`,
16576
16659
  "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).",
16577
16660
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16586,8 +16669,8 @@ ${segments}`;
16586
16669
  );
16587
16670
  }
16588
16671
  function appendScoreHistory(candidateDir, entry) {
16589
- const file = path47.join(candidateDir, "score-history.jsonl");
16590
- const starts = existsSync37(file) ? readFileSync34(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16672
+ const file = path48.join(candidateDir, "score-history.jsonl");
16673
+ const starts = existsSync38(file) ? readFileSync35(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16591
16674
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16592
16675
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16593
16676
  `);
@@ -16595,9 +16678,10 @@ function appendScoreHistory(candidateDir, entry) {
16595
16678
  async function runEngineScore(opts) {
16596
16679
  requireEntitlement(opts);
16597
16680
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16598
- const candidateDir = path47.resolve(callerCwd, opts.candidateDir);
16681
+ const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
16599
16682
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16600
- if (!existsSync37(candidateDir)) {
16683
+ void reportRunPresence(name, "implementing");
16684
+ if (!existsSync38(candidateDir)) {
16601
16685
  fail(opts, ExitCode.InputValidation, {
16602
16686
  error: `candidate directory not found: ${candidateDir}`,
16603
16687
  code: "candidate-missing",
@@ -16622,10 +16706,10 @@ async function runEngineScore(opts) {
16622
16706
  for (const g of missingWeights(task.set)) {
16623
16707
  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)`);
16624
16708
  }
16625
- if (opts.rebind !== true && existsSync37(path47.join(candidateDir, "component.json"))) {
16709
+ if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
16626
16710
  const prior = (() => {
16627
16711
  try {
16628
- const read = readBundleManifest(readFileSync34(path47.join(candidateDir, "component.json"), "utf8"));
16712
+ const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
16629
16713
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16630
16714
  } catch {
16631
16715
  return { unreadable: true };
@@ -16647,13 +16731,13 @@ async function runEngineScore(opts) {
16647
16731
  }
16648
16732
  }
16649
16733
  const bar = BARS3[opts.bar];
16650
- const evidenceDir = path47.join(candidateDir, "verify-evidence");
16734
+ const evidenceDir = path48.join(candidateDir, "verify-evidence");
16651
16735
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16652
16736
  const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16653
16737
  const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16654
16738
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16655
16739
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16656
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16740
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16657
16741
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16658
16742
  const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16659
16743
  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)";
@@ -16858,6 +16942,7 @@ var init_engine2 = __esm({
16858
16942
  init_entitlement();
16859
16943
  init_verify();
16860
16944
  init_compose2();
16945
+ init_run_presence();
16861
16946
  init_src5();
16862
16947
  BARS3 = {
16863
16948
  pass: { sim: 0.95, ink: 0.95 },
@@ -16871,11 +16956,11 @@ var codeconnect_exports = {};
16871
16956
  __export(codeconnect_exports, {
16872
16957
  runCodeConnect: () => runCodeConnect
16873
16958
  });
16874
- import { existsSync as existsSync38, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16875
- import path48 from "node:path";
16959
+ import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16960
+ import path49 from "node:path";
16876
16961
  function runCodeConnect(opts) {
16877
16962
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16878
- const bundleDir = path48.resolve(callerCwd, opts.bundleDir);
16963
+ const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
16879
16964
  let url;
16880
16965
  try {
16881
16966
  url = new URL(opts.figmaUrl);
@@ -16891,7 +16976,7 @@ function runCodeConnect(opts) {
16891
16976
  }
16892
16977
  let manifest;
16893
16978
  try {
16894
- const read = readBundleManifest(readFileSync35(path48.join(bundleDir, "component.json"), "utf8"));
16979
+ const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
16895
16980
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16896
16981
  manifest = read.manifest;
16897
16982
  } catch (err) {
@@ -16901,8 +16986,8 @@ function runCodeConnect(opts) {
16901
16986
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16902
16987
  });
16903
16988
  }
16904
- const setDir = path48.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16905
- if (!existsSync38(path48.join(setDir, "recording-set.json"))) {
16989
+ const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16990
+ if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
16906
16991
  fail(opts, ExitCode.InputValidation, {
16907
16992
  error: `recording set not found at ${setDir}`,
16908
16993
  code: "codeconnect-no-set",
@@ -16923,10 +17008,10 @@ function runCodeConnect(opts) {
16923
17008
  const component = api.component;
16924
17009
  const recManifest = loadManifest(setDir);
16925
17010
  const poseNames = recManifest.latticeNames ?? recManifest.reps.map((r) => {
16926
- const meta = path48.join(setDir, r.slug, "get_metadata.json");
16927
- if (!existsSync38(meta)) return void 0;
17011
+ const meta = path49.join(setDir, r.slug, "get_metadata.json");
17012
+ if (!existsSync39(meta)) return void 0;
16928
17013
  try {
16929
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync35(meta, "utf8"))))?.[1];
17014
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
16930
17015
  } catch {
16931
17016
  return void 0;
16932
17017
  }
@@ -16991,7 +17076,7 @@ function runCodeConnect(opts) {
16991
17076
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
16992
17077
  fragmentVars.push(varName);
16993
17078
  }
16994
- const entryRel = path48.relative(callerCwd, path48.join(bundleDir, manifest.entry));
17079
+ const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
16995
17080
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
16996
17081
  const lines = [
16997
17082
  `// url=${opts.figmaUrl}`,
@@ -17015,8 +17100,8 @@ function runCodeConnect(opts) {
17015
17100
  `}`,
17016
17101
  ``
17017
17102
  ].join("\n");
17018
- const outFile = path48.resolve(callerCwd, opts.out ?? path48.join(bundleDir, `${component}.figma.ts`));
17019
- writeFileSync17(outFile, lines);
17103
+ const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
17104
+ writeFileSync18(outFile, lines);
17020
17105
  emitData(
17021
17106
  opts,
17022
17107
  {
@@ -17054,18 +17139,18 @@ var init_codeconnect = __esm({
17054
17139
  });
17055
17140
 
17056
17141
  // packages/mcp/src/server.ts
17057
- import { createHash as createHash9 } from "node:crypto";
17058
- import { existsSync as existsSync39, mkdtempSync as mkdtempSync3, readFileSync as readFileSync36, readdirSync as readdirSync16, writeFileSync as writeFileSync18 } from "node:fs";
17142
+ import { createHash as createHash10 } from "node:crypto";
17143
+ import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, readdirSync as readdirSync16, writeFileSync as writeFileSync19 } from "node:fs";
17059
17144
  import os8 from "node:os";
17060
- import path49 from "node:path";
17145
+ import path50 from "node:path";
17061
17146
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17062
17147
  import { z as z14 } from "zod";
17063
17148
  function sourceHash() {
17064
- const dir = path49.dirname(fileURLToPath6(import.meta.url));
17065
- const h = createHash9("sha256");
17149
+ const dir = path50.dirname(fileURLToPath6(import.meta.url));
17150
+ const h = createHash10("sha256");
17066
17151
  for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
17067
17152
  h.update(f);
17068
- h.update(readFileSync36(path49.join(dir, f)));
17153
+ h.update(readFileSync37(path50.join(dir, f)));
17069
17154
  }
17070
17155
  return h.digest("hex").slice(0, 16);
17071
17156
  }
@@ -17073,10 +17158,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
17073
17158
  var init_server = __esm({
17074
17159
  "packages/mcp/src/server.ts"() {
17075
17160
  "use strict";
17076
- REPO_ROOT3 = path49.resolve(path49.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17077
- CLI_BIN = path49.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17078
- BUNDLED_CLI = path49.join(path49.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17079
- CLI_SPAWN = existsSync39(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
17161
+ REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17162
+ CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17163
+ BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17164
+ CLI_SPAWN = existsSync40(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
17080
17165
  str = (d) => z14.string().describe(d);
17081
17166
  optStr = (d) => z14.string().optional().describe(d);
17082
17167
  TOOLS = [
@@ -17107,13 +17192,13 @@ var init_server = __esm({
17107
17192
  const single = i["metadata"];
17108
17193
  const parts = i["metadataParts"];
17109
17194
  if (single !== void 0 || parts !== void 0) {
17110
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17195
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17111
17196
  if (single !== void 0) {
17112
- writeFileSync18(tmp, single);
17197
+ writeFileSync19(tmp, single);
17113
17198
  argvOut.push("--metadata-raw-file", tmp);
17114
17199
  } else {
17115
17200
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
17116
- writeFileSync18(tmp, JSON.stringify(parts));
17201
+ writeFileSync19(tmp, JSON.stringify(parts));
17117
17202
  argvOut.push("--metadata-raw-parts-file", tmp);
17118
17203
  }
17119
17204
  }
@@ -17163,6 +17248,24 @@ var init_server = __esm({
17163
17248
  schema: z14.object({}),
17164
17249
  argv: () => ["login", "--device-wait"]
17165
17250
  },
17251
+ {
17252
+ name: "tendril_publish",
17253
+ 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).",
17254
+ schema: z14.object({
17255
+ bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
17256
+ portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
17257
+ }),
17258
+ argv: (i) => ["publish", i["bundleDir"], "--approve-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17259
+ },
17260
+ {
17261
+ name: "tendril_publish_wait",
17262
+ description: "Phase two of the browser-approved publish: waits (with live progress) for the user's Approve click on the page tendril_publish returned, then uploads the bundle, commits it, and returns the live publication URL. Call it right after relaying the approve link. A denial, a lapse, and success each come back as their own sentence \u2014 report the outcome and the URL to the user.",
17263
+ schema: z14.object({
17264
+ bundleDir: str("the same bundle directory tendril_publish was called with"),
17265
+ portal: optStr("portal origin override (must match tendril_publish's)")
17266
+ }),
17267
+ argv: (i) => ["publish", i["bundleDir"], "--approve-wait", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17268
+ },
17166
17269
  {
17167
17270
  name: "tendril_record_next",
17168
17271
  annotations: { readOnlyHint: true },
@@ -17204,14 +17307,14 @@ var init_server = __esm({
17204
17307
  const bridge = (label, single, parts) => {
17205
17308
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17206
17309
  if (single === void 0 && parts === void 0) return;
17207
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17310
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17208
17311
  if (single !== void 0) {
17209
- writeFileSync18(tmp, single);
17312
+ writeFileSync19(tmp, single);
17210
17313
  argvOut.push(`--${label}-file`, tmp);
17211
17314
  } else {
17212
17315
  const blocks = parts;
17213
17316
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17214
- writeFileSync18(tmp, JSON.stringify(blocks));
17317
+ writeFileSync19(tmp, JSON.stringify(blocks));
17215
17318
  argvOut.push(`--${label}-parts-file`, tmp);
17216
17319
  }
17217
17320
  };
@@ -17252,12 +17355,12 @@ var init_server = __esm({
17252
17355
  const file = i["file"];
17253
17356
  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)");
17254
17357
  if (file !== void 0) return [...base, "--file", file];
17255
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17358
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17256
17359
  if (text !== void 0) {
17257
- writeFileSync18(tmp, text);
17360
+ writeFileSync19(tmp, text);
17258
17361
  return [...base, "--file", tmp, "--raw"];
17259
17362
  }
17260
- writeFileSync18(tmp, JSON.stringify(texts));
17363
+ writeFileSync19(tmp, JSON.stringify(texts));
17261
17364
  return [...base, "--file", tmp, "--raw-parts"];
17262
17365
  }
17263
17366
  },
@@ -17414,13 +17517,13 @@ __export(permissions_exports, {
17414
17517
  runPermissions: () => runPermissions,
17415
17518
  writeSelection: () => writeSelection
17416
17519
  });
17417
- import { existsSync as existsSync40, mkdirSync as mkdirSync11, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17520
+ import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17418
17521
  import os9 from "node:os";
17419
- import path50 from "node:path";
17522
+ import path51 from "node:path";
17420
17523
  function mergeAllowlist(file, entries, denyEntries = []) {
17421
17524
  let settings = {};
17422
- if (existsSync40(file) && readFileSync37(file, "utf8").trim() !== "") {
17423
- settings = JSON.parse(readFileSync37(file, "utf8"));
17525
+ if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
17526
+ settings = JSON.parse(readFileSync38(file, "utf8"));
17424
17527
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17425
17528
  }
17426
17529
  const permissions = settings["permissions"] ??= {};
@@ -17440,8 +17543,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17440
17543
  }
17441
17544
  if (added.length > 0 || denyAdded.length > 0) {
17442
17545
  allow.push(...added);
17443
- mkdirSync11(path50.dirname(file), { recursive: true });
17444
- writeFileSync19(file, `${JSON.stringify(settings, null, 2)}
17546
+ mkdirSync12(path51.dirname(file), { recursive: true });
17547
+ writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
17445
17548
  `);
17446
17549
  }
17447
17550
  return { added, alreadyPresent, denyAdded };
@@ -17505,7 +17608,7 @@ async function runPermissions(flags) {
17505
17608
  }
17506
17609
  if (flags.write) {
17507
17610
  const base = process.env["INIT_CWD"] ?? process.cwd();
17508
- const file = flags.user ? path50.join(os9.homedir(), ".claude", "settings.json") : path50.join(base, ".claude", "settings.local.json");
17611
+ const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
17509
17612
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17510
17613
  if (flags.dryRun) {
17511
17614
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17654,13 +17757,13 @@ __export(inspect_exports, {
17654
17757
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17655
17758
  runInspect: () => runInspect
17656
17759
  });
17657
- import { existsSync as existsSync41, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17658
- import path51 from "node:path";
17760
+ import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17761
+ import path52 from "node:path";
17659
17762
  function readVerifyReport(evidenceDir) {
17660
- const p = path51.join(evidenceDir, VERIFY_REPORT_FILENAME);
17661
- if (!existsSync41(p)) return void 0;
17763
+ const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
17764
+ if (!existsSync42(p)) return void 0;
17662
17765
  try {
17663
- return JSON.parse(readFileSync38(p, "utf8"));
17766
+ return JSON.parse(readFileSync39(p, "utf8"));
17664
17767
  } catch {
17665
17768
  return void 0;
17666
17769
  }
@@ -17688,17 +17791,17 @@ async function runInspect(opts) {
17688
17791
  printDescription(INSPECT_DESCRIPTION);
17689
17792
  return;
17690
17793
  }
17691
- const bundleDir = path51.resolve(opts.bundleDir);
17692
- const evidenceDir = path51.join(bundleDir, "verify-evidence");
17693
- const manifestPath2 = path51.join(bundleDir, "component.json");
17694
- if (!existsSync41(evidenceDir) || !existsSync41(manifestPath2)) {
17794
+ const bundleDir = path52.resolve(opts.bundleDir);
17795
+ const evidenceDir = path52.join(bundleDir, "verify-evidence");
17796
+ const manifestPath2 = path52.join(bundleDir, "component.json");
17797
+ if (!existsSync42(evidenceDir) || !existsSync42(manifestPath2)) {
17695
17798
  fail(opts, ExitCode.InputValidation, {
17696
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync41(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17799
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17697
17800
  code: "no-evidence",
17698
17801
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17699
17802
  });
17700
17803
  }
17701
- const { manifest } = readBundleManifest(readFileSync38(manifestPath2, "utf8"));
17804
+ const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
17702
17805
  if (manifest === void 0) {
17703
17806
  fail(opts, ExitCode.InputValidation, {
17704
17807
  error: "component.json did not parse as a bundle manifest",
@@ -17706,9 +17809,9 @@ async function runInspect(opts) {
17706
17809
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17707
17810
  });
17708
17811
  }
17709
- const setDir = path51.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17812
+ const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17710
17813
  const report = readVerifyReport(evidenceDir);
17711
- const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync41(path51.join(evidenceDir, `${rep}-ref.png`)) && existsSync41(path51.join(evidenceDir, `${rep}-render.png`)));
17814
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync42(path52.join(evidenceDir, `${rep}-ref.png`)) && existsSync42(path52.join(evidenceDir, `${rep}-render.png`)));
17712
17815
  if (reps.length === 0) {
17713
17816
  fail(opts, ExitCode.InputValidation, {
17714
17817
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17719,15 +17822,15 @@ async function runInspect(opts) {
17719
17822
  let crops = 0;
17720
17823
  const sections = [];
17721
17824
  for (const rep of reps) {
17722
- const ref = new Uint8Array(readFileSync38(path51.join(evidenceDir, `${rep}-ref.png`)));
17723
- const render = new Uint8Array(readFileSync38(path51.join(evidenceDir, `${rep}-render.png`)));
17825
+ const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
17826
+ const render = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-render.png`)));
17724
17827
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17725
17828
  const cells = [];
17726
17829
  for (const [i, n] of nodes.entries()) {
17727
17830
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17728
17831
  try {
17729
- writeFileSync20(path51.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17730
- writeFileSync20(path51.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17832
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17833
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17731
17834
  } catch {
17732
17835
  continue;
17733
17836
  }
@@ -17744,8 +17847,8 @@ async function runInspect(opts) {
17744
17847
  if (reps.includes(c.rep)) continue;
17745
17848
  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>`);
17746
17849
  }
17747
- const sheet = path51.join(evidenceDir, "inspect.html");
17748
- writeFileSync20(
17850
+ const sheet = path52.join(evidenceDir, "inspect.html");
17851
+ writeFileSync21(
17749
17852
  sheet,
17750
17853
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17751
17854
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17818,8 +17921,8 @@ __export(login_exports, {
17818
17921
  runLogout: () => runLogout
17819
17922
  });
17820
17923
  import { spawn } from "node:child_process";
17821
- import { existsSync as existsSync42, mkdirSync as mkdirSync12, readFileSync as readFileSync39, rmSync as rmSync6, writeFileSync as writeFileSync21 } from "node:fs";
17822
- import path52 from "node:path";
17924
+ import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
17925
+ import path53 from "node:path";
17823
17926
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17824
17927
  async function runLogin(opts, deps) {
17825
17928
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -17913,13 +18016,13 @@ function settleDecision(opts, origin, outcome) {
17913
18016
  }
17914
18017
  }
17915
18018
  function pendingLoginPath() {
17916
- return path52.join(path52.dirname(sessionPath()), "pending-login.json");
18019
+ return path53.join(path53.dirname(sessionPath()), "pending-login.json");
17917
18020
  }
17918
18021
  async function deviceStartPhase(opts, origin, deps) {
17919
18022
  const started = await startHandshake(opts, origin, deps);
17920
18023
  const file = pendingLoginPath();
17921
- mkdirSync12(path52.dirname(file), { recursive: true });
17922
- writeFileSync21(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18024
+ mkdirSync13(path53.dirname(file), { recursive: true });
18025
+ writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17923
18026
  `, { mode: 384 });
17924
18027
  deps.openBrowser(started.verificationUrl);
17925
18028
  emitData(
@@ -17944,9 +18047,9 @@ async function deviceStartPhase(opts, origin, deps) {
17944
18047
  async function deviceWaitPhase(opts, deps) {
17945
18048
  const file = pendingLoginPath();
17946
18049
  let pending;
17947
- if (existsSync42(file)) {
18050
+ if (existsSync43(file)) {
17948
18051
  try {
17949
- const parsed = JSON.parse(readFileSync39(file, "utf8"));
18052
+ const parsed = JSON.parse(readFileSync40(file, "utf8"));
17950
18053
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
17951
18054
  pending = parsed;
17952
18055
  }
@@ -17960,7 +18063,7 @@ async function deviceWaitPhase(opts, deps) {
17960
18063
  remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
17961
18064
  });
17962
18065
  }
17963
- const done = () => rmSync6(file, { force: true });
18066
+ const done = () => rmSync7(file, { force: true });
17964
18067
  const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
17965
18068
  let ticks = 0;
17966
18069
  const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
@@ -18195,12 +18298,13 @@ var init_share = __esm({
18195
18298
  // packages/cli/src/commands/publish.ts
18196
18299
  var publish_exports = {};
18197
18300
  __export(publish_exports, {
18301
+ resolveOrigin: () => resolveOrigin,
18198
18302
  runPublish: () => runPublish
18199
18303
  });
18200
- import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
18201
- import path53 from "node:path";
18304
+ import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
18305
+ import path54 from "node:path";
18202
18306
  async function runPublish(opts) {
18203
- const bundleDir = path53.resolve(opts.bundleDir);
18307
+ const bundleDir = path54.resolve(opts.bundleDir);
18204
18308
  const bundle = readBundle(opts, bundleDir);
18205
18309
  const report = bundle.report;
18206
18310
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18245,7 +18349,7 @@ async function runPublish(opts) {
18245
18349
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18246
18350
  if (sheetEntry !== void 0) {
18247
18351
  const missingCrops = missingInspectCrops(
18248
- readFileSync40(path53.join(bundleDir, sheetEntry.path), "utf8"),
18352
+ readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
18249
18353
  surface.published.map((p) => p.path)
18250
18354
  );
18251
18355
  if (missingCrops.length > 0) {
@@ -18310,48 +18414,42 @@ async function runPublish(opts) {
18310
18414
  );
18311
18415
  return;
18312
18416
  }
18313
- const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18417
+ const origin = resolveOrigin(opts);
18314
18418
  const client = opts.client ?? httpClient(opts, origin);
18315
- if (opts.confirmPublish === true && process.stdin.isTTY !== true) {
18316
- fail(opts, ExitCode.ConfirmationRequired, {
18317
- error: "--confirm-publish requires an interactive terminal \u2014 putting a component on the internet is a human-only decision",
18318
- code: "publish-confirmation-not-interactive",
18319
- remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Agents: show this to your operator instead of confirming it.`
18320
- });
18419
+ if (opts.approveWait === true) {
18420
+ await approveWaitPhase(opts, client, bundleDir, { componentName, figmaFile });
18321
18421
  }
18322
- if (opts.acceptTerms === true) {
18323
- const accepted = await client.acceptTerms({ figmaFile });
18324
- if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
18325
- }
18326
- const opened = await client.begin({
18422
+ void reportRunPresence(componentName, "publishing");
18423
+ let opened = await client.begin({
18327
18424
  componentName,
18328
18425
  figmaFile,
18329
18426
  entry: bundle.manifest.entry,
18330
18427
  files: bundle.files,
18331
- report: bundle.reportText,
18332
- confirmed: opts.confirmPublish === true
18428
+ report: bundle.reportText
18333
18429
  });
18430
+ if (!opened.ok && opened.needsConfirmation !== void 0 && opts.approveWait !== true) {
18431
+ const flow = await runApprovalFlow(opts, client, bundleDir, {
18432
+ componentName,
18433
+ figmaFile,
18434
+ begin: () => client.begin({ componentName, figmaFile, entry: bundle.manifest.entry, files: bundle.files, report: bundle.reportText })
18435
+ });
18436
+ if (flow === void 0) return;
18437
+ opened = flow;
18438
+ }
18334
18439
  if (!opened.ok) {
18335
- if (opened.needsConsent !== void 0) {
18336
- fail(opts, ExitCode.ConfirmationRequired, {
18337
- error: `publishing this design system needs an acceptance of the publishing terms (version ${opened.needsConsent.termsVersion}) first`,
18338
- code: "publishing-terms-not-accepted",
18339
- remediation: `Read the terms, then run \`${tendrilCommand(`publish ${opts.bundleDir} --accept-terms --confirm-publish`)}\`. The acceptance covers this design system only.`
18340
- });
18341
- }
18342
18440
  if (opened.needsConfirmation !== void 0) {
18343
18441
  fail(opts, ExitCode.ConfirmationRequired, {
18344
- error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18442
+ error: opened.refusal,
18345
18443
  code: "first-publish-unconfirmed",
18346
- remediation: `A human runs \`${tendrilCommand(`publish ${opts.bundleDir} --confirm-publish`)}\` in their own terminal. Later publishes of this component need no flag.`
18444
+ remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
18347
18445
  });
18348
18446
  }
18349
18447
  refuse2(opts, opened, "publish-refused");
18350
18448
  }
18351
18449
  const uploaded = [];
18352
18450
  for (const object of opened.value.plan.objects) {
18353
- const file = path53.join(bundleDir, object.relPath);
18354
- if (!existsSync43(file)) {
18451
+ const file = path54.join(bundleDir, object.relPath);
18452
+ if (!existsSync44(file)) {
18355
18453
  fail(opts, ExitCode.InputValidation, {
18356
18454
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18357
18455
  code: "planned-file-missing",
@@ -18361,12 +18459,15 @@ async function runPublish(opts) {
18361
18459
  const sent = await client.upload({
18362
18460
  publicationId: opened.value.publicationId,
18363
18461
  relPath: object.relPath,
18364
- bytes: new Uint8Array(readFileSync40(file))
18462
+ bytes: new Uint8Array(readFileSync41(file))
18365
18463
  });
18366
- if (!sent.ok) refuse2(opts, sent, "upload-refused");
18464
+ if (!sent.ok) refuse2(opts, sent, "upload-refused", true);
18367
18465
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18368
18466
  }
18369
18467
  const committed = await client.commit({ publicationId: opened.value.publicationId });
18468
+ if (committed.ok) {
18469
+ await endRunPresence(componentName);
18470
+ }
18370
18471
  if (!committed.ok) {
18371
18472
  if (committed.missing !== void 0 && committed.missing.length > 0) {
18372
18473
  fail(opts, ExitCode.General, {
@@ -18375,7 +18476,7 @@ async function runPublish(opts) {
18375
18476
  remediation: `Run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication and re-sends only what is missing.`
18376
18477
  });
18377
18478
  }
18378
- refuse2(opts, committed, "commit-refused");
18479
+ refuse2(opts, committed, "commit-refused", true);
18379
18480
  }
18380
18481
  emitData(
18381
18482
  opts,
@@ -18403,23 +18504,23 @@ async function runPublish(opts) {
18403
18504
  );
18404
18505
  }
18405
18506
  function readBundle(opts, bundleDir) {
18406
- const manifestPath2 = path53.join(bundleDir, "component.json");
18407
- const reportPath = path53.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18408
- if (!existsSync43(manifestPath2)) {
18507
+ const manifestPath2 = path54.join(bundleDir, "component.json");
18508
+ const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18509
+ if (!existsSync44(manifestPath2)) {
18409
18510
  fail(opts, ExitCode.InputValidation, {
18410
18511
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18411
18512
  code: "not-a-bundle",
18412
18513
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18413
18514
  });
18414
18515
  }
18415
- if (!existsSync43(reportPath)) {
18516
+ if (!existsSync44(reportPath)) {
18416
18517
  fail(opts, ExitCode.InputValidation, {
18417
18518
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18418
18519
  code: "bundle-not-verified",
18419
18520
  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.`
18420
18521
  });
18421
18522
  }
18422
- const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
18523
+ const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
18423
18524
  if (manifest === void 0) {
18424
18525
  fail(opts, ExitCode.InputValidation, {
18425
18526
  error: "component.json did not parse as a bundle manifest",
@@ -18427,7 +18528,7 @@ function readBundle(opts, bundleDir) {
18427
18528
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18428
18529
  });
18429
18530
  }
18430
- const reportText = readFileSync40(reportPath, "utf8");
18531
+ const reportText = readFileSync41(reportPath, "utf8");
18431
18532
  let report;
18432
18533
  try {
18433
18534
  report = JSON.parse(reportText);
@@ -18447,6 +18548,12 @@ function readBundle(opts, bundleDir) {
18447
18548
  }
18448
18549
  return { manifest, report, reportText, files: bundleFiles(bundleDir) };
18449
18550
  }
18551
+ function resolveOrigin(opts) {
18552
+ const named = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18553
+ if (named !== "") return named;
18554
+ if ((process.env["TENDRIL_TOKEN"] ?? "") !== "") return "";
18555
+ return (readStoredSession()?.origin ?? "").replace(/\/+$/, "");
18556
+ }
18450
18557
  function httpClient(opts, origin) {
18451
18558
  if (origin === "") {
18452
18559
  fail(opts, ExitCode.InputValidation, {
@@ -18479,14 +18586,118 @@ function httpClient(opts, origin) {
18479
18586
  }
18480
18587
  return new HttpPublishClient({ origin, token: found.token });
18481
18588
  }
18482
- function refuse2(opts, sent, code) {
18589
+ function refuse2(opts, sent, code, rejoins = false) {
18483
18590
  const detail = sent.detail === void 0 || sent.detail.length === 0 ? "" : `: ${sent.detail.join(", ")}`;
18591
+ 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`;
18484
18592
  fail(opts, sent.status === 401 ? ExitCode.Auth : ExitCode.General, {
18485
18593
  error: `${sent.refusal}${detail}`,
18486
18594
  code,
18487
- 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.`
18595
+ remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${resolveOrigin(opts)}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then ${retry}.` : `Fix what is named above and ${retry}.`
18596
+ });
18597
+ }
18598
+ function pendingApprovalPath() {
18599
+ return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
18600
+ }
18601
+ async function runApprovalFlow(opts, client, bundleDir, input) {
18602
+ const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
18603
+ if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
18604
+ const approval = requested.value;
18605
+ const who = await client.whoami?.();
18606
+ const asAccount = who?.ok === true && who.value.email !== "" ? ` signed in as ${who.value.email}` : "";
18607
+ if (opts.approveStart === true) {
18608
+ const pending = { ...approval, bundleDir, componentName: input.componentName, figmaFile: input.figmaFile };
18609
+ writeFileSync23(pendingApprovalPath(), `${JSON.stringify(pending, null, 2)}
18610
+ `, { mode: 384 });
18611
+ await endRunPresence(input.componentName);
18612
+ emitData(
18613
+ opts,
18614
+ {
18615
+ status: "approval-pending",
18616
+ approveUrl: approval.approveUrl,
18617
+ componentName: input.componentName,
18618
+ ...asAccount === "" ? {} : { approveAsAccount: who?.ok === true ? who.value.email : "" },
18619
+ expiresAt: approval.expiresAt,
18620
+ next: `open the approve page in the browser${asAccount}, click Approve, then finish with --approve-wait`
18621
+ },
18622
+ () => {
18623
+ process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
18624
+ `);
18625
+ process.stdout.write(`Approve it here${asAccount}: ${approval.approveUrl}
18626
+ `);
18627
+ process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
18628
+ `);
18629
+ }
18630
+ );
18631
+ return void 0;
18632
+ }
18633
+ process.stderr.write(`This component's FIRST publish needs your approval in the browser${asAccount}:
18634
+ ${approval.approveUrl}
18635
+ `);
18636
+ process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
18637
+ `);
18638
+ const decided = await waitForApproval(opts, client, approval);
18639
+ if (decided === "approved") return input.begin();
18640
+ await endRunPresence(input.componentName);
18641
+ failDecision(opts, decided);
18642
+ }
18643
+ async function approveWaitPhase(opts, client, bundleDir, subject) {
18644
+ const file = pendingApprovalPath();
18645
+ let pending;
18646
+ if (existsSync44(file)) {
18647
+ try {
18648
+ const parsed = JSON.parse(readFileSync41(file, "utf8"));
18649
+ if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
18650
+ pending = { pollSeconds: 3, bundleDir, ...parsed };
18651
+ }
18652
+ } catch {
18653
+ }
18654
+ }
18655
+ if (pending === void 0) {
18656
+ fail(opts, ExitCode.InputValidation, {
18657
+ error: "there is no publish approval waiting to finish",
18658
+ code: "no-pending-approval",
18659
+ remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
18660
+ });
18661
+ }
18662
+ if (pending.componentName !== void 0 && pending.componentName !== subject.componentName) {
18663
+ fail(opts, ExitCode.InputValidation, {
18664
+ error: `the waiting approval is for ${JSON.stringify(pending.componentName)}, and this publish is ${JSON.stringify(subject.componentName)}`,
18665
+ code: "pending-approval-mismatch",
18666
+ remediation: `Finish that one first (${tendrilCommand(`publish ${pending.bundleDir} --approve-wait`)}), or start this one fresh with --approve-start \u2014 starting one replaces the waiting slot.`
18667
+ });
18668
+ }
18669
+ const done = () => rmSync8(file, { force: true });
18670
+ const decided = await waitForApproval(opts, client, pending);
18671
+ done();
18672
+ if (decided !== "approved") failDecision(opts, decided);
18673
+ }
18674
+ async function waitForApproval(opts, client, approval) {
18675
+ const interval = Math.max(1, approval.pollSeconds) * 1e3;
18676
+ const total = Math.ceil(APPROVAL_WAIT_CAP_MS / interval);
18677
+ for (let tick = 1; tick <= total; tick += 1) {
18678
+ const polled = await client.pollApproval({ approvalId: approval.approvalId });
18679
+ if (!polled.ok) refuse2(opts, polled, "approval-poll-refused");
18680
+ if (polled.value.status !== "pending") return polled.value.status;
18681
+ emitProgress(tick, total, "waiting for the browser approval");
18682
+ await new Promise((resolve) => setTimeout(resolve, interval));
18683
+ }
18684
+ return "expired";
18685
+ }
18686
+ function failDecision(opts, decided) {
18687
+ if (decided === "denied") {
18688
+ fail(opts, ExitCode.ConfirmationRequired, {
18689
+ error: "the publish was DENIED in the browser \u2014 the human said no",
18690
+ code: "publish-approval-denied",
18691
+ remediation: "Nothing was published. If minds change, run the publish again \u2014 it makes a fresh request."
18692
+ });
18693
+ }
18694
+ fail(opts, ExitCode.ConfirmationRequired, {
18695
+ 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",
18696
+ code: "publish-approval-lapsed",
18697
+ 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."
18488
18698
  });
18489
18699
  }
18700
+ var APPROVAL_WAIT_CAP_MS;
18490
18701
  var init_publish = __esm({
18491
18702
  "packages/cli/src/commands/publish.ts"() {
18492
18703
  "use strict";
@@ -18495,6 +18706,8 @@ var init_publish = __esm({
18495
18706
  init_invocation();
18496
18707
  init_output();
18497
18708
  init_publish_client();
18709
+ init_run_presence();
18710
+ APPROVAL_WAIT_CAP_MS = 31 * 6e4;
18498
18711
  }
18499
18712
  });
18500
18713
 
@@ -18522,17 +18735,17 @@ __export(generate_recorded_exports, {
18522
18735
  runGenerateRecorded: () => runGenerateRecorded
18523
18736
  });
18524
18737
  import { confirm as confirm3, isCancel as isCancel4 } from "@clack/prompts";
18525
- import { existsSync as existsSync44, readFileSync as readFileSync41 } from "node:fs";
18526
- import path54 from "node:path";
18738
+ import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
18739
+ import path55 from "node:path";
18527
18740
  async function runGenerateRecorded(opts) {
18528
18741
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18529
- const outDirAbs = path54.resolve(callerCwd, opts.out);
18530
- const recordedAsPath = path54.resolve(callerCwd, opts.recorded);
18742
+ const outDirAbs = path55.resolve(callerCwd, opts.out);
18743
+ const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
18531
18744
  let task;
18532
18745
  let taskName;
18533
18746
  let authoredApi;
18534
18747
  let composition;
18535
- const isSet = existsSync44(path54.join(recordedAsPath, "recording-set.json"));
18748
+ const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
18536
18749
  const registry = TASKS[opts.recorded];
18537
18750
  if (registry !== void 0 && !isSet) {
18538
18751
  task = registry;
@@ -18541,7 +18754,7 @@ async function runGenerateRecorded(opts) {
18541
18754
  try {
18542
18755
  const authored = authorTaskFromSet(recordedAsPath);
18543
18756
  task = authored.task;
18544
- taskName = path54.basename(recordedAsPath);
18757
+ taskName = path55.basename(recordedAsPath);
18545
18758
  authoredApi = authored.api;
18546
18759
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18547
18760
  if (roles.success) composition = roles.data;
@@ -18575,7 +18788,7 @@ async function runGenerateRecorded(opts) {
18575
18788
  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)`);
18576
18789
  }
18577
18790
  const missing = task.configs.filter(
18578
- (c) => !existsSync44(path54.join(task.set, c.rep, "get_screenshot.json")) || !existsSync44(path54.join(task.set, c.rep, "get_metadata.json")) || !existsSync44(path54.join(task.set, c.rep, "get_design_context.json"))
18791
+ (c) => !existsSync45(path55.join(task.set, c.rep, "get_screenshot.json")) || !existsSync45(path55.join(task.set, c.rep, "get_metadata.json")) || !existsSync45(path55.join(task.set, c.rep, "get_design_context.json"))
18579
18792
  );
18580
18793
  if (missing.length > 0) {
18581
18794
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18645,8 +18858,8 @@ async function runGenerateRecorded(opts) {
18645
18858
  ` : `${line}
18646
18859
  `);
18647
18860
  if (opts.dryRun) {
18648
- emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path54.join(outDirAbs, taskName) }, () => {
18649
- process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path54.join(outDirAbs, taskName)})
18861
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
18862
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path55.join(outDirAbs, taskName)})
18650
18863
  `);
18651
18864
  });
18652
18865
  return;
@@ -18669,10 +18882,10 @@ async function runGenerateRecorded(opts) {
18669
18882
  });
18670
18883
  }
18671
18884
  }
18672
- const bundleDir = path54.join(outDirAbs, taskName);
18673
- if (existsSync44(path54.join(bundleDir, "component.json"))) {
18885
+ const bundleDir = path55.join(outDirAbs, taskName);
18886
+ if (existsSync45(path55.join(bundleDir, "component.json"))) {
18674
18887
  try {
18675
- const prior = readBundleManifest(readFileSync41(path54.join(bundleDir, "component.json"), "utf8")).manifest;
18888
+ const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
18676
18889
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18677
18890
  fail(opts, ExitCode.InputValidation, {
18678
18891
  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`,
@@ -19965,7 +20178,7 @@ function buildProgram() {
19965
20178
  ...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
19966
20179
  });
19967
20180
  });
19968
- program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--accept-terms", "accept the current publishing terms for this design system as part of this run").option("--confirm-publish", "you, a person, saying this component may be published \u2014 required the first time, and refused without an interactive terminal").action(async (bundleDir, _o, cmd) => {
20181
+ program.command("publish").description("Put a verified bundle and its evidence on a page, at a URL you can send to someone. Refuses a run the ruler declined.").argument("<bundleDir>", "bundle directory that has already been verified").option("--to <url>", "the portal to publish to (or set TENDRIL_PORTAL_URL)").option("--name <name>", "library label for this component (default: the bundle manifest's own name)").option("--approve-start", "first publish only: request the browser approval, print the link, persist the pending state and exit \u2014 the tendril_publish tool's phase one").option("--approve-wait", "resume a pending approval: poll until the human decides in the browser, then publish \u2014 phase two").action(async (bundleDir, _o, cmd) => {
19969
20182
  const flags = globalFlags(cmd.parent);
19970
20183
  const local = cmd.opts();
19971
20184
  const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
@@ -19974,8 +20187,8 @@ function buildProgram() {
19974
20187
  bundleDir,
19975
20188
  ...local["to"] !== void 0 ? { to: local["to"] } : {},
19976
20189
  ...local["name"] !== void 0 ? { name: local["name"] } : {},
19977
- ...local["acceptTerms"] !== void 0 ? { acceptTerms: local["acceptTerms"] } : {},
19978
- ...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
20190
+ ...local["approveStart"] !== void 0 ? { approveStart: local["approveStart"] } : {},
20191
+ ...local["approveWait"] !== void 0 ? { approveWait: local["approveWait"] } : {}
19979
20192
  });
19980
20193
  });
19981
20194
  program.command("verify").description("Recompute verification for a generated bundle against its recording set (per-config status, behaviors, honest exit codes).").argument("<bundleDir>", "bundle directory to verify").option("--task <name>", "legacy task adapter for bundles WITHOUT component.json (bundle v1 carries its own prop manifest)").option("--set <dir>", "recording set directory (overrides the bundle's provenance path)").option("--bar <bar>", "target bar: pass (0.95) or cert (0.97)", "pass").option("--library <dir>", "workspace root holding confirmed partners generated bundles (ADR-013 composed pins; default: current directory)").option("--profile <file>", "a `tendril profile` artifact; reports whether the bundle followed your codebase conventions (never gates the verdict)").option("--hover-timeout <ms>", "hover actionability budget in ms (default 2000) \u2014 for diagnosing a slow machine; a non-default value is recorded in the report as a verdict caveat, never silently").action(async (bundleDir, _opts, cmd) => {