@tendrilapp/cli 0.1.42 → 0.1.43

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,11 @@ 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);
8458
8470
  }
8459
8471
  begin(input) {
8460
8472
  return this.json("POST", "/api/publications", input, true);
@@ -8548,7 +8560,6 @@ var init_publish_client = __esm({
8548
8560
  refusal: typeof record["refusal"] === "string" ? record["refusal"] : `the portal refused with ${String(response.status)}`,
8549
8561
  ...Array.isArray(record["detail"]) ? { detail: record["detail"] } : {},
8550
8562
  ...Array.isArray(record["missing"]) ? { missing: record["missing"] } : {},
8551
- ...isRecord(record["needsConsent"]) ? { needsConsent: record["needsConsent"] } : {},
8552
8563
  ...isRecord(record["needsConfirmation"]) ? { needsConfirmation: record["needsConfirmation"] } : {}
8553
8564
  };
8554
8565
  }
@@ -13791,6 +13802,72 @@ var init_activate = __esm({
13791
13802
  }
13792
13803
  });
13793
13804
 
13805
+ // packages/cli/src/run-presence.ts
13806
+ import { createHash as createHash8 } from "node:crypto";
13807
+ import { existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync29, rmSync as rmSync5, writeFileSync as writeFileSync14 } from "node:fs";
13808
+ import path41 from "node:path";
13809
+ function presenceDir() {
13810
+ return path41.join(path41.dirname(sessionPath()), "runs");
13811
+ }
13812
+ function presenceFile(componentName) {
13813
+ return path41.join(presenceDir(), `${createHash8("sha256").update(componentName).digest("hex").slice(0, 16)}.json`);
13814
+ }
13815
+ function readCached(componentName) {
13816
+ const file = presenceFile(componentName);
13817
+ if (!existsSync32(file)) return void 0;
13818
+ try {
13819
+ const parsed = JSON.parse(readFileSync29(file, "utf8"));
13820
+ return typeof parsed.runId === "string" && typeof parsed.origin === "string" ? parsed : void 0;
13821
+ } catch {
13822
+ return void 0;
13823
+ }
13824
+ }
13825
+ async function post(origin, token, pathname, body, method = "POST") {
13826
+ const response = await fetch(`${origin}${pathname}`, {
13827
+ method,
13828
+ headers: { authorization: `Bearer ${token}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
13829
+ ...body === void 0 ? {} : { body: JSON.stringify(body) },
13830
+ signal: AbortSignal.timeout(PRESENCE_TIMEOUT_MS)
13831
+ });
13832
+ if (!response.ok) throw new Error(String(response.status));
13833
+ return response.json();
13834
+ }
13835
+ async function reportRunPresence(componentName, phase) {
13836
+ try {
13837
+ const session = readStoredSession();
13838
+ if (session === void 0) return;
13839
+ const cached2 = readCached(componentName);
13840
+ if (cached2 !== void 0 && cached2.origin === session.origin) {
13841
+ const beat = await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, { phase });
13842
+ if (beat.alive === true) return;
13843
+ }
13844
+ const started = await post(session.origin, session.token, "/api/runs", { componentName, phase });
13845
+ if (typeof started.runId !== "string") return;
13846
+ mkdirSync10(presenceDir(), { recursive: true });
13847
+ writeFileSync14(presenceFile(componentName), `${JSON.stringify({ runId: started.runId, origin: session.origin })}
13848
+ `, { mode: 384 });
13849
+ } catch {
13850
+ }
13851
+ }
13852
+ async function endRunPresence(componentName) {
13853
+ try {
13854
+ const session = readStoredSession();
13855
+ const cached2 = readCached(componentName);
13856
+ rmSync5(presenceFile(componentName), { force: true });
13857
+ if (session === void 0 || cached2 === void 0 || cached2.origin !== session.origin) return;
13858
+ await post(session.origin, session.token, `/api/runs/${encodeURIComponent(cached2.runId)}`, void 0, "DELETE");
13859
+ } catch {
13860
+ }
13861
+ }
13862
+ var PRESENCE_TIMEOUT_MS;
13863
+ var init_run_presence = __esm({
13864
+ "packages/cli/src/run-presence.ts"() {
13865
+ "use strict";
13866
+ init_publish_client();
13867
+ PRESENCE_TIMEOUT_MS = 1500;
13868
+ }
13869
+ });
13870
+
13794
13871
  // packages/cli/src/commands/compose.ts
13795
13872
  var compose_exports = {};
13796
13873
  __export(compose_exports, {
@@ -13799,15 +13876,15 @@ __export(compose_exports, {
13799
13876
  compositionPairsFor: () => compositionPairsFor,
13800
13877
  runCompose: () => runCompose
13801
13878
  });
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";
13879
+ import { createHash as createHash9 } from "node:crypto";
13880
+ import { existsSync as existsSync33, readFileSync as readFileSync30, readdirSync as readdirSync14 } from "node:fs";
13881
+ import path42 from "node:path";
13805
13882
  function compositionPairsFor(hostSet, roots) {
13806
- const parent = path41.dirname(hostSet);
13883
+ const parent = path42.dirname(hostSet);
13807
13884
  const explicitRoots = [...new Set(roots)];
13808
13885
  let skippedParent;
13809
13886
  let parentRoot = [];
13810
- if (!explicitRoots.some((r) => path41.resolve(r) === path41.resolve(parent))) {
13887
+ if (!explicitRoots.some((r) => path42.resolve(r) === path42.resolve(parent))) {
13811
13888
  let parentEntries = 0;
13812
13889
  try {
13813
13890
  parentEntries = readdirSync14(parent).length;
@@ -13848,7 +13925,7 @@ function substitutionPairs(edges, hostSet) {
13848
13925
  });
13849
13926
  }
13850
13927
  const pair = pairs.get(key);
13851
- const poseDisplay = e.pose.reps.map((r) => `${path41.basename(r.dir)}:${r.slug}`).join(", ");
13928
+ const poseDisplay = e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ");
13852
13929
  pair.instances.push({ hostRep: e.hostRep, instanceId: e.instanceId, poseVariantNodeId: e.pose.variantNodeId, ...poseDisplay !== "" ? { poseDisplay } : {} });
13853
13930
  for (const d of e.disclosures) if (!pair.disclosures.includes(d)) pair.disclosures.push(d);
13854
13931
  }
@@ -13860,7 +13937,7 @@ function runCompose(flags) {
13860
13937
  return;
13861
13938
  }
13862
13939
  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];
13940
+ const roots = flags.library !== void 0 && flags.library.length > 0 ? flags.library.map((d) => path42.resolve(base, d)) : [base];
13864
13941
  if (flags.set === void 0 && (flags.confirmCompositions === true || flags.decline !== void 0 && flags.decline.length > 0)) {
13865
13942
  fail(flags, ExitCode.InputValidation, {
13866
13943
  error: "--confirm-compositions/--decline require --set <host> \u2014 a decision needs the host set it decides for",
@@ -13869,7 +13946,7 @@ function runCompose(flags) {
13869
13946
  });
13870
13947
  }
13871
13948
  if (flags.set !== void 0) {
13872
- runComposeConfirm(flags, path41.resolve(base, flags.set), roots);
13949
+ runComposeConfirm(flags, path42.resolve(base, flags.set), roots);
13873
13950
  return;
13874
13951
  }
13875
13952
  const index = buildComposeIndex(roots);
@@ -13887,7 +13964,7 @@ function runCompose(flags) {
13887
13964
  }
13888
13965
  let lastHost = "";
13889
13966
  for (const e of edges) {
13890
- const host = `${path41.basename(e.hostSet)}`;
13967
+ const host = `${path42.basename(e.hostSet)}`;
13891
13968
  if (host !== lastHost) {
13892
13969
  process.stdout.write(`
13893
13970
  ${host}
@@ -13895,7 +13972,7 @@ ${host}
13895
13972
  lastHost = host;
13896
13973
  }
13897
13974
  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(", ")})` : "";
13975
+ const pose = e.pose !== void 0 ? ` pose=${e.pose.variantNodeId} (${e.pose.reps.map((r) => `${path42.basename(r.dir)}:${r.slug}`).join(", ")})` : "";
13899
13976
  process.stdout.write(` ${e.kind.toUpperCase().padEnd(12)} ${e.hostRep}/${e.instanceId} "${e.instanceName}" \u2192 ${partners}${pose}
13900
13977
  `);
13901
13978
  for (const d of e.disclosures) process.stdout.write(` \xB7 ${d}
@@ -13907,14 +13984,14 @@ ${NOTE}
13907
13984
  });
13908
13985
  }
13909
13986
  function runComposeConfirm(flags, hostSet, roots) {
13910
- if (!existsSync32(path41.join(hostSet, "recording-set.json"))) {
13987
+ if (!existsSync33(path42.join(hostSet, "recording-set.json"))) {
13911
13988
  fail(flags, ExitCode.InputValidation, {
13912
13989
  error: `no recording-set.json in ${hostSet}`,
13913
13990
  code: "no-recording-set",
13914
13991
  remediation: "Point --set at a recorded host set (the directory holding recording-set.json)."
13915
13992
  });
13916
13993
  }
13917
- const scanRoots = [.../* @__PURE__ */ new Set([...roots, path41.dirname(hostSet)])];
13994
+ const scanRoots = [.../* @__PURE__ */ new Set([...roots, path42.dirname(hostSet)])];
13918
13995
  const index = buildComposeIndex(scanRoots);
13919
13996
  const edges = composeReport(index);
13920
13997
  const pairs = substitutionPairs(edges, hostSet);
@@ -13997,7 +14074,7 @@ PAIR ${p.displayName}${p.figmaFile !== void 0 ? ` (file ${p.figmaFile})` : " \u2
13997
14074
  // full recording-set hash join lands with pin authoring, where
13998
14075
  // task configs exist.)
13999
14076
  manifestSha256: Object.fromEntries(
14000
- p.partnerDirs.map((d) => [path41.relative(hostSet, d), createHash8("sha256").update(readFileSync29(path41.join(d, "recording-set.json"))).digest("hex")])
14077
+ p.partnerDirs.map((d) => [path42.relative(hostSet, d), createHash9("sha256").update(readFileSync30(path42.join(d, "recording-set.json"))).digest("hex")])
14001
14078
  )
14002
14079
  },
14003
14080
  // Schema shape ONLY (review: the display-side poseDisplay field
@@ -14077,10 +14154,10 @@ __export(record_exports, {
14077
14154
  runRecordPlan: () => runRecordPlan,
14078
14155
  runRecordStatus: () => runRecordStatus
14079
14156
  });
14080
- import { existsSync as existsSync33, mkdtempSync as mkdtempSync2, readFileSync as readFileSync30, readdirSync as readdirSync15 } from "node:fs";
14157
+ import { existsSync as existsSync34, mkdtempSync as mkdtempSync2, readFileSync as readFileSync31, readdirSync as readdirSync15 } from "node:fs";
14081
14158
  import os7 from "node:os";
14082
- import path42 from "node:path";
14083
- import { writeFileSync as writeFileSync14 } from "node:fs";
14159
+ import path43 from "node:path";
14160
+ import { writeFileSync as writeFileSync15 } from "node:fs";
14084
14161
  function recordsInteractionState(reports) {
14085
14162
  const evident = (s) => stateTokens(s).some((t) => INTERACTION_TREATMENT_VALUES.has(t));
14086
14163
  return reports.some((r) => evident(r.axis) || r.domain.some(evident));
@@ -14102,7 +14179,7 @@ function interactionDisclosure(component, reports) {
14102
14179
  };
14103
14180
  }
14104
14181
  function symbolsFromMetadataEnvelope(file, sourceFrame) {
14105
- const env = JSON.parse(readFileSync30(file, "utf8"));
14182
+ const env = JSON.parse(readFileSync31(file, "utf8"));
14106
14183
  const text = env.content.map((c) => c.text ?? "").join("\n");
14107
14184
  const symbols = [];
14108
14185
  const walk2 = (node, ancestor) => {
@@ -14160,8 +14237,8 @@ function runRecordPlan(opts) {
14160
14237
  if (rawFile !== void 0) {
14161
14238
  try {
14162
14239
  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));
14240
+ const tmp = path43.join(mkdtempSync2(path43.join(os7.tmpdir(), "tendril-plan-meta-")), "get_metadata.json");
14241
+ writeFileSync15(tmp, JSON.stringify(envelope));
14165
14242
  metadataEntries.push({ file: tmp });
14166
14243
  } catch (err) {
14167
14244
  fail(opts, ExitCode.InputValidation, {
@@ -14182,7 +14259,7 @@ function runRecordPlan(opts) {
14182
14259
  let metadataTruncated = false;
14183
14260
  for (const { file, frame } of metadataEntries) {
14184
14261
  try {
14185
- const parsed = symbolsFromMetadataEnvelope(path42.resolve(file), frame);
14262
+ const parsed = symbolsFromMetadataEnvelope(path43.resolve(file), frame);
14186
14263
  symbols.push(...parsed.symbols);
14187
14264
  if (parsed.truncated) metadataTruncated = true;
14188
14265
  } catch (err) {
@@ -14216,7 +14293,7 @@ function runRecordPlan(opts) {
14216
14293
  if (symbols.length === 0) {
14217
14294
  const leads = metadataEntries.flatMap(({ file }) => {
14218
14295
  try {
14219
- const env = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14296
+ const env = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14220
14297
  return instanceLeads(env.content.map((c) => c.text ?? "").join("\n"));
14221
14298
  } catch {
14222
14299
  return [];
@@ -14309,7 +14386,7 @@ function runRecordPlan(opts) {
14309
14386
  // a CLI flag and the MCP surface has no parameter for it.
14310
14387
  decidedBy: "HUMAN ONLY \u2014 offer it, never choose it, and you cannot run it",
14311
14388
  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`]
14389
+ userRuns: [`rm ${quoteArg(path43.join(opts.setDir, "recording-set.json"))}`, `${tendrilCommand(`record plan --set ${quoteArg(opts.setDir)} --component ${quoteArg(manifest.component)}`)} --sample`]
14313
14390
  },
14314
14391
  {
14315
14392
  id: "larger-allowance",
@@ -14319,6 +14396,7 @@ function runRecordPlan(opts) {
14319
14396
  ],
14320
14397
  limitHintIfWhoamiIsSilent: LIMIT_HINT
14321
14398
  };
14399
+ void reportRunPresence(opts.component, "recording");
14322
14400
  emitData(
14323
14401
  opts,
14324
14402
  {
@@ -14507,7 +14585,7 @@ function runRecordNext(opts) {
14507
14585
  const progress = payload["progress"];
14508
14586
  process.stdout.write(`[${progress.recordedReps}/${progress.totalReps} reps] ${payload["tool"]} for ${payload["slug"]} (node ${payload["nodeId"]})
14509
14587
  \u2192 ${payload["note"]}
14510
- \u2192 then: ${tendrilCommand(`record ingest --set ${path42.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14588
+ \u2192 then: ${tendrilCommand(`record ingest --set ${path43.resolve(opts.setDir)} --rep ${payload["slug"]} --tool ${payload["tool"]} --file <envelope.json>`)}
14511
14589
  `);
14512
14590
  });
14513
14591
  }
@@ -14581,7 +14659,7 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14581
14659
  const skipped = [];
14582
14660
  const failed = [];
14583
14661
  for (const { url, name } of assetUrlsFromEnvelopeText(envelopeText2)) {
14584
- if (existsSync33(path42.join(setDir, rep, name))) {
14662
+ if (existsSync34(path43.join(setDir, rep, name))) {
14585
14663
  skipped.push(name);
14586
14664
  continue;
14587
14665
  }
@@ -14603,16 +14681,16 @@ async function autoFetchAssets(setDir, rep, envelopeText2) {
14603
14681
  }
14604
14682
  function rawEnvelopeFromFile(file, parts) {
14605
14683
  if (parts) {
14606
- const blocks = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14684
+ const blocks = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14607
14685
  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
14686
  return { content: blocks.map((text) => ({ type: "text", text })) };
14609
14687
  }
14610
- return { content: [{ type: "text", text: readFileSync30(path42.resolve(file), "utf8") }] };
14688
+ return { content: [{ type: "text", text: readFileSync31(path43.resolve(file), "utf8") }] };
14611
14689
  }
14612
14690
  async function runRecordIngest(opts) {
14613
14691
  let payload;
14614
14692
  try {
14615
- payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync30(path42.resolve(opts.file), "utf8"));
14693
+ payload = opts.rawParts === true || opts.raw === true ? rawEnvelopeFromFile(opts.file, opts.rawParts === true) : JSON.parse(readFileSync31(path43.resolve(opts.file), "utf8"));
14616
14694
  } catch (err) {
14617
14695
  fail(opts, ExitCode.InputValidation, {
14618
14696
  error: `cannot read envelope file: ${err instanceof Error ? err.message : String(err)}`,
@@ -14624,7 +14702,7 @@ async function runRecordIngest(opts) {
14624
14702
  fail(opts, ExitCode.InputValidation, {
14625
14703
  error: "--raw is for text tool responses; screenshots are binary",
14626
14704
  code: "envelope-invalid",
14627
- remediation: `Use \`${tendrilCommand(`record fetch --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14705
+ remediation: `Use \`${tendrilCommand(`record fetch --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --tool ${opts.tool} --url <image_url>`)}\` instead.`
14628
14706
  });
14629
14707
  }
14630
14708
  if (opts.rep === "__set__" && opts.tool === "get_variable_defs") {
@@ -14644,7 +14722,7 @@ async function runRecordIngest(opts) {
14644
14722
  remediation: REINGEST_GUIDANCE
14645
14723
  });
14646
14724
  }
14647
- writeFileSync14(path42.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14725
+ writeFileSync15(path43.join(opts.setDir, "get_variable_defs.json"), `${JSON.stringify(payload, null, 1)}
14648
14726
  `);
14649
14727
  emitData(opts, { ingested: "get_variable_defs", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14650
14728
  process.stdout.write("set-level get_variable_defs ingested\n");
@@ -14668,7 +14746,7 @@ async function runRecordIngest(opts) {
14668
14746
  remediation: REINGEST_GUIDANCE
14669
14747
  });
14670
14748
  }
14671
- writeFileSync14(path42.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14749
+ writeFileSync15(path43.join(opts.setDir, "get_motion_context.json"), `${JSON.stringify(payload, null, 1)}
14672
14750
  `);
14673
14751
  emitData(opts, { ingested: "get_motion_context", rep: "__set__", setLevel: true, next: nextPayload(opts.setDir) }, () => {
14674
14752
  process.stdout.write("set-level get_motion_context ingested\n");
@@ -14684,7 +14762,7 @@ async function runRecordIngest(opts) {
14684
14762
  if (assets !== void 0) {
14685
14763
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14686
14764
  `);
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>`)}
14765
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14688
14766
  `);
14689
14767
  }
14690
14768
  });
@@ -14757,14 +14835,14 @@ async function runRecordIngestRep(opts) {
14757
14835
  if (assets !== void 0) {
14758
14836
  for (const a of assets.fetched) process.stdout.write(` asset fetched ${a}
14759
14837
  `);
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>`)}
14838
+ for (const f of assets.failed) process.stdout.write(` ASSET FAILED ${f.name}: ${f.reason} \u2014 download it, then: ${tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --name ${f.name} --file <downloaded-file>`)}
14761
14839
  `);
14762
14840
  }
14763
14841
  });
14764
14842
  }
14765
14843
  function runRecordAsset(opts) {
14766
14844
  if (opts.dir !== void 0) {
14767
- const dir = path42.resolve(opts.dir);
14845
+ const dir = path43.resolve(opts.dir);
14768
14846
  const names = readdirSync15(dir).filter((f) => /^asset-[\w.-]+\.(svg|png|jpe?g|webp|gif)$/i.test(f));
14769
14847
  if (names.length === 0) {
14770
14848
  fail(opts, ExitCode.InputValidation, {
@@ -14776,7 +14854,7 @@ function runRecordAsset(opts) {
14776
14854
  const ingested = [];
14777
14855
  try {
14778
14856
  for (const name of names) {
14779
- ingestAsset(opts.setDir, opts.rep, name, readFileSync30(path42.join(dir, name)));
14857
+ ingestAsset(opts.setDir, opts.rep, name, readFileSync31(path43.join(dir, name)));
14780
14858
  ingested.push(name);
14781
14859
  }
14782
14860
  } catch (err) {
@@ -14796,11 +14874,11 @@ function runRecordAsset(opts) {
14796
14874
  fail(opts, ExitCode.InputValidation, {
14797
14875
  error: "pass --name and --file for a single asset, or --dir for a batch",
14798
14876
  code: "asset-rejected",
14799
- remediation: tendrilCommand(`record asset --set ${path42.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14877
+ remediation: tendrilCommand(`record asset --set ${path43.resolve(opts.setDir)} --rep ${opts.rep} --dir <downloads-dir>`)
14800
14878
  });
14801
14879
  }
14802
14880
  try {
14803
- ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync30(path42.resolve(opts.file)));
14881
+ ingestAsset(opts.setDir, opts.rep, opts.name, readFileSync31(path43.resolve(opts.file)));
14804
14882
  emitData(opts, { rep: opts.rep, asset: opts.name }, () => {
14805
14883
  process.stdout.write(`ingested ${opts.rep}/${opts.name}
14806
14884
  `);
@@ -14817,8 +14895,8 @@ function runRecordStatus(opts) {
14817
14895
  const status = sessionStatus(opts.setDir);
14818
14896
  const composition = (() => {
14819
14897
  try {
14820
- const setDir = path42.resolve(opts.setDir);
14821
- const { open, standing, invalid } = compositionPairsFor(setDir, [path42.dirname(setDir)]);
14898
+ const setDir = path43.resolve(opts.setDir);
14899
+ const { open, standing, invalid } = compositionPairsFor(setDir, [path43.dirname(setDir)]);
14822
14900
  return {
14823
14901
  openPairs: open.map((p) => ({ displayName: p.displayName, pairKey: p.key, instances: p.instances.length })),
14824
14902
  confirmed: standing.filter((s) => s.status === "confirmed").length,
@@ -14839,7 +14917,7 @@ function runRecordStatus(opts) {
14839
14917
  }
14840
14918
  }
14841
14919
  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\`
14920
+ status.motion.recorded ? motionTruthFor(path43.resolve(opts.setDir)).state === "recorded-no-motion" ? "MOTION set-level motion context recorded \u2014 the response reports NO motion data (no keyframe tracks, no snippets); briefs prescribe default doctrine and say so. This is the instrument's answer, not proof the design has no transitions\n" : status.motion.asked ? "MOTION set-level motion context recorded\n" : "MOTION set-level motion context recorded (ingested onto a set that predates the obligation \u2014 briefs will quote it as recorded truth)\n" : status.motion.asked ? status.motion.invalid !== void 0 ? `MOTION set-level motion file is UNUSABLE (${status.motion.invalid}) \u2014 re-record it via \`record next\`
14843
14921
  ` : "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
14922
  );
14845
14923
  if ("unavailable" in composition) {
@@ -14847,7 +14925,7 @@ function runRecordStatus(opts) {
14847
14925
  `);
14848
14926
  } else if (composition.openPairs.length > 0) {
14849
14927
  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.
14928
+ `COMPOSITION ${composition.openPairs.length} unconfirmed partner pair(s): ${composition.openPairs.map((p) => `${p.displayName} [pair-key ${p.pairKey}] (${p.instances} instance(s))`).join(", ")} \u2014 this set's recording references other recorded components. Before generating the HOST, record/generate the partner and have a human decide the pairing: \`${tendrilCommand(`compose --set ${path43.resolve(opts.setDir)}`)}\` lists it; confirmation is human-only.
14851
14929
  `
14852
14930
  );
14853
14931
  } else if (composition.confirmed > 0) {
@@ -14887,7 +14965,7 @@ function narrowedRoles(derived, override) {
14887
14965
  function rolesFromFile(opts, file, derived) {
14888
14966
  let json;
14889
14967
  try {
14890
- json = JSON.parse(readFileSync30(path42.resolve(file), "utf8"));
14968
+ json = JSON.parse(readFileSync31(path43.resolve(file), "utf8"));
14891
14969
  } catch (err) {
14892
14970
  fail(opts, ExitCode.InputValidation, {
14893
14971
  error: `cannot read roles file ${file}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`,
@@ -14925,11 +15003,11 @@ function rolesFromFile(opts, file, derived) {
14925
15003
  };
14926
15004
  }
14927
15005
  function runRecordFinish(opts) {
14928
- if (!existsSync33(path42.join(opts.setDir, "recording-set.json"))) {
15006
+ if (!existsSync34(path43.join(opts.setDir, "recording-set.json"))) {
14929
15007
  fail(opts, ExitCode.InputValidation, {
14930
15008
  error: `no recording-set.json in ${opts.setDir}`,
14931
15009
  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.`
15010
+ remediation: `Run \`${tendrilCommand(`record plan --set ${path43.resolve(opts.setDir)} --component <name> --metadata <envelope.json>`)}\` first \u2014 \`record finish\` closes a set the planner opened.`
14933
15011
  });
14934
15012
  }
14935
15013
  const { manifest, raw } = readManifestFile(opts.setDir);
@@ -14957,17 +15035,17 @@ function runRecordFinish(opts) {
14957
15035
  fail(opts, ExitCode.ConfirmationRequired, {
14958
15036
  error: "--confirm-roles (and --roles-file) require an interactive terminal \u2014 this confirmation is human-only",
14959
15037
  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.`
15038
+ remediation: `A human runs \`${tendrilCommand(`record finish --set ${path43.resolve(opts.setDir)} --confirm-roles`)}\` in their own terminal. Agents: show the proposal to your operator instead of confirming it.`
14961
15039
  });
14962
15040
  }
14963
15041
  const merged = { ...raw, roles };
14964
- const { issues } = validateRecordingSet(merged, (rel) => existsSync33(path42.join(opts.setDir, rel)));
15042
+ const { issues } = validateRecordingSet(merged, (rel) => existsSync34(path43.join(opts.setDir, rel)));
14965
15043
  const errors = issues.filter((i) => i.severity === "error");
14966
15044
  if (errors.length > 0) {
14967
15045
  fail(opts, ExitCode.InputValidation, {
14968
15046
  error: `recording set rejected \u2014 roles NOT written: ${errors.map((e) => e.message).join("; ")}`,
14969
15047
  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\`.`
15048
+ remediation: `Fix the set (\`${tendrilCommand(`record status --set ${path43.resolve(opts.setDir)}`)}\` lists missing envelopes) or the roles graph, then re-run \`record finish\`.`
14971
15049
  });
14972
15050
  }
14973
15051
  for (const issue of issues) if (issue.severity === "warning") warn(opts, issue.message);
@@ -14986,6 +15064,7 @@ var init_record = __esm({
14986
15064
  init_src();
14987
15065
  init_src4();
14988
15066
  init_output();
15067
+ init_run_presence();
14989
15068
  init_entitlement();
14990
15069
  init_invocation();
14991
15070
  init_compose2();
@@ -15017,9 +15096,9 @@ var init_record = __esm({
15017
15096
  });
15018
15097
 
15019
15098
  // packages/cli/src/font-guidance.ts
15020
- import path43 from "node:path";
15099
+ import path44 from "node:path";
15021
15100
  function fontsUnprovenRemediation(setDir) {
15022
- const set = setDir === void 0 ? void 0 : path43.resolve(setDir);
15101
+ const set = setDir === void 0 ? void 0 : path44.resolve(setDir);
15023
15102
  if (set !== void 0) {
15024
15103
  try {
15025
15104
  const needs = recordedFontNeeds(set);
@@ -15094,8 +15173,8 @@ __export(fonts_exports, {
15094
15173
  runFontsResolveSet: () => runFontsResolveSet,
15095
15174
  runFontsStatus: () => runFontsStatus
15096
15175
  });
15097
- import { existsSync as existsSync34, readFileSync as readFileSync31 } from "node:fs";
15098
- import path44 from "node:path";
15176
+ import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15177
+ import path45 from "node:path";
15099
15178
  async function runFontsResolve(opts) {
15100
15179
  const result = await resolveFonts(opts.family, opts.weights, opts.cacheDir);
15101
15180
  const queryRefusal = systemFaceRefusal(opts.family.trim());
@@ -15116,7 +15195,7 @@ async function runFontsResolve(opts) {
15116
15195
  }
15117
15196
  }
15118
15197
  async function runFontsResolveSet(opts) {
15119
- const setDir = path44.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15198
+ const setDir = path45.resolve(process.env["INIT_CWD"] ?? process.cwd(), opts.set);
15120
15199
  let needs = [];
15121
15200
  try {
15122
15201
  needs = recordedFontNeeds(setDir);
@@ -15211,16 +15290,16 @@ async function runFontsResolveSet(opts) {
15211
15290
  }
15212
15291
  }
15213
15292
  function runFontsStatus(opts) {
15214
- const manifestPath2 = path44.join(opts.cacheDir, "manifest.json");
15215
- if (!existsSync34(manifestPath2)) {
15293
+ const manifestPath2 = path45.join(opts.cacheDir, "manifest.json");
15294
+ if (!existsSync35(manifestPath2)) {
15216
15295
  fail(opts, ExitCode.FontsUnproven, {
15217
15296
  error: `no font cache at ${opts.cacheDir}`,
15218
15297
  code: "fonts-unresolved",
15219
15298
  remediation: `Run \`${tendrilCommand("fonts resolve --set <recording-dir>")}\` (or \`${tendrilCommand('fonts resolve "<Family>" --weights 400 500 600')}\`) first.`
15220
15299
  });
15221
15300
  }
15222
- const faces = JSON.parse(readFileSync31(manifestPath2, "utf8"));
15223
- const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path44.resolve(opts.lock), opts.cacheDir) : null;
15301
+ const faces = JSON.parse(readFileSync32(manifestPath2, "utf8"));
15302
+ const lockVerdicts = opts.lock !== void 0 ? checkFontLock(path45.resolve(opts.lock), opts.cacheDir) : null;
15224
15303
  emitData(opts, { faces, lockVerdicts }, () => {
15225
15304
  for (const f of faces) process.stdout.write(`cached ${f.family} ${f.weight} (${f.sha256.slice(0, 12)}\u2026)
15226
15305
  `);
@@ -15264,13 +15343,13 @@ function familyMismatch(family, declared) {
15264
15343
  }
15265
15344
  function runFontsAdd(opts) {
15266
15345
  if (opts.set !== void 0) {
15267
- const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15346
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15268
15347
  const mismatch = familyMismatch(opts.family, declared);
15269
15348
  if (mismatch !== void 0) {
15270
15349
  fail(opts, ExitCode.InputValidation, {
15271
15350
  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
15351
  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.`
15352
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add "${mismatch.nearest}" ${opts.weight} ${opts.file} --set ${path45.resolve(opts.set)}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set to add a family this set does not declare.`
15274
15353
  });
15275
15354
  }
15276
15355
  } else {
@@ -15329,13 +15408,13 @@ cache them: ${tendrilCommand(`fonts add-system ${quoteArg(opts.family)}`)}
15329
15408
  }
15330
15409
  function runFontsAddSystem(opts) {
15331
15410
  if (opts.set !== void 0) {
15332
- const declared = taskFontFamilies(path44.resolve(opts.set)) ?? [];
15411
+ const declared = taskFontFamilies(path45.resolve(opts.set)) ?? [];
15333
15412
  const mismatch = familyMismatch(opts.family, declared);
15334
15413
  if (mismatch !== void 0) {
15335
15414
  fail(opts, ExitCode.InputValidation, {
15336
15415
  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
15416
  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.`
15417
+ remediation: mismatch.nearest !== void 0 ? `Did you mean "${mismatch.nearest}"? ${tendrilCommand(`fonts add-system ${quoteArg(mismatch.nearest)} --set ${quoteArg(path45.resolve(opts.set))}`)}` : `Use one of the declared families exactly as the recording spells it, or drop --set.`
15339
15418
  });
15340
15419
  }
15341
15420
  } else {
@@ -15385,12 +15464,12 @@ var init_fonts = __esm({
15385
15464
  });
15386
15465
 
15387
15466
  // packages/cli/src/profile-input.ts
15388
- import { existsSync as existsSync35, readFileSync as readFileSync32 } from "node:fs";
15389
- import path45 from "node:path";
15467
+ import { existsSync as existsSync36, readFileSync as readFileSync33 } from "node:fs";
15468
+ import path46 from "node:path";
15390
15469
  function loadCodebaseProfile(flags, profilePath) {
15391
15470
  if (profilePath === void 0) return null;
15392
- const abs = path45.resolve(profilePath);
15393
- if (!existsSync35(abs)) {
15471
+ const abs = path46.resolve(profilePath);
15472
+ if (!existsSync36(abs)) {
15394
15473
  fail(flags, ExitCode.InputValidation, {
15395
15474
  error: `no profile at ${abs}`,
15396
15475
  code: "profile_missing",
@@ -15398,7 +15477,7 @@ function loadCodebaseProfile(flags, profilePath) {
15398
15477
  });
15399
15478
  }
15400
15479
  try {
15401
- return readCodebaseProfile(readFileSync32(abs, "utf8"));
15480
+ return readCodebaseProfile(readFileSync33(abs, "utf8"));
15402
15481
  } catch (error) {
15403
15482
  fail(flags, ExitCode.InputValidation, {
15404
15483
  error: `profile at ${abs} is not a valid codebase profile: ${error instanceof Error ? error.message : String(error)}`,
@@ -15440,8 +15519,8 @@ __export(verify_exports, {
15440
15519
  runVerify: () => runVerify,
15441
15520
  verdictCaveatsFor: () => verdictCaveatsFor
15442
15521
  });
15443
- import { existsSync as existsSync36, readFileSync as readFileSync33, rmSync as rmSync5, writeFileSync as writeFileSync15 } from "node:fs";
15444
- import path46 from "node:path";
15522
+ import { existsSync as existsSync37, readFileSync as readFileSync34, rmSync as rmSync6, writeFileSync as writeFileSync16 } from "node:fs";
15523
+ import path47 from "node:path";
15445
15524
  function interactionCoverage(behaviors) {
15446
15525
  const parity = behaviors.filter((b) => b.id.startsWith("parity:"));
15447
15526
  const content = behaviors.filter((b) => b.id.startsWith("content-prop-renders("));
@@ -15682,12 +15761,12 @@ function compositionReport(input) {
15682
15761
  function eyeCheck(bundleDir) {
15683
15762
  return {
15684
15763
  command: tendrilCommand(`inspect "${bundleDir}"`),
15685
- sheetPath: path46.join(bundleDir, "verify-evidence", "inspect.html"),
15764
+ sheetPath: path47.join(bundleDir, "verify-evidence", "inspect.html"),
15686
15765
  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
15766
  };
15688
15767
  }
15689
15768
  function evidenceArtifacts(evidenceDir, reps) {
15690
- const named = (name) => existsSync36(path46.join(evidenceDir, name)) ? name : null;
15769
+ const named = (name) => existsSync37(path47.join(evidenceDir, name)) ? name : null;
15691
15770
  return {
15692
15771
  legend: named("diff-legend.txt"),
15693
15772
  configs: reps.map((rep) => {
@@ -15735,7 +15814,7 @@ function taskFromManifest(opts, manifest, setDir) {
15735
15814
  const adapterSlugs = Object.keys(manifest.propAdapter);
15736
15815
  const unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
15737
15816
  const adapterOnly = adapterSlugs.filter((s) => !recordedSlugs.includes(s));
15738
- const registry = Object.values(TASKS).find((t) => path46.resolve(t.set) === path46.resolve(setDir));
15817
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15739
15818
  const authored = (() => {
15740
15819
  if (registry !== void 0) return void 0;
15741
15820
  try {
@@ -15796,19 +15875,19 @@ function verdictCaveatsFor(input) {
15796
15875
  async function runVerify(opts) {
15797
15876
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
15798
15877
  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)) {
15878
+ const setOverride = opts.set !== void 0 ? path47.resolve(callerCwd, opts.set) : void 0;
15879
+ opts = { ...opts, bundleDir: path47.resolve(callerCwd, opts.bundleDir), ...setOverride !== void 0 ? { set: setOverride } : {} };
15880
+ if (!existsSync37(opts.bundleDir)) {
15802
15881
  fail(opts, ExitCode.InputValidation, {
15803
15882
  error: `bundle directory not found: ${opts.bundleDir}`,
15804
15883
  code: "bundle-missing",
15805
15884
  remediation: "Point at a generated bundle directory (containing component.json, the entry module, and styles.css)."
15806
15885
  });
15807
15886
  }
15808
- const manifestPath2 = path46.join(opts.bundleDir, "component.json");
15887
+ const manifestPath2 = path47.join(opts.bundleDir, "component.json");
15809
15888
  let manifest;
15810
- if (existsSync36(manifestPath2)) {
15811
- const { manifest: parsed, issues } = readBundleManifest(readFileSync33(manifestPath2, "utf8"));
15889
+ if (existsSync37(manifestPath2)) {
15890
+ const { manifest: parsed, issues } = readBundleManifest(readFileSync34(manifestPath2, "utf8"));
15812
15891
  if (issues.length > 0) {
15813
15892
  fail(opts, ExitCode.InputValidation, {
15814
15893
  error: `bundle manifest rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15839,21 +15918,21 @@ async function runVerify(opts) {
15839
15918
  task = registry;
15840
15919
  } else if (manifest !== void 0) {
15841
15920
  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);
15921
+ if (path47.isAbsolute(p)) return p;
15922
+ const fromRepo = path47.resolve(REPO_ROOT, p);
15923
+ if (existsSync37(fromRepo)) return fromRepo;
15924
+ return path47.resolve(callerCwd, p);
15846
15925
  };
15847
15926
  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))) {
15927
+ if (!existsSync37(path47.join(setDir, "recording-set.json")) && !Object.values(TASKS).some((t) => path47.resolve(t.set) === path47.resolve(setDir))) {
15849
15928
  fail(opts, ExitCode.RecordingIncomplete, {
15850
15929
  error: `recording set not found or unmanifested: ${setDir}`,
15851
15930
  code: "recording-set-missing",
15852
15931
  remediation: "Pass --set <dir> pointing at the recording set this bundle was generated from."
15853
15932
  });
15854
15933
  }
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"))) {
15934
+ const registry = Object.values(TASKS).find((t) => path47.resolve(t.set) === path47.resolve(setDir));
15935
+ if (registry !== void 0 && !existsSync37(path47.join(setDir, "recording-set.json"))) {
15857
15936
  const recordedSlugs = registry.configs.map((c) => c.rep);
15858
15937
  const adapterSlugs = Object.keys(manifest.propAdapter);
15859
15938
  unmapped = recordedSlugs.filter((s) => !adapterSlugs.includes(s));
@@ -15885,9 +15964,9 @@ async function runVerify(opts) {
15885
15964
  recordingSetDrift = { stamped: manifest.provenance.recordingSet.hash, current: hash };
15886
15965
  }
15887
15966
  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)));
15967
+ const p = path47.join(opts.bundleDir, name);
15968
+ if (!existsSync37(p)) continue;
15969
+ const issues = checkBundleSourceFile(name, new Uint8Array(readFileSync34(p)));
15891
15970
  if (issues.length > 0) {
15892
15971
  fail(opts, ExitCode.InputValidation, {
15893
15972
  error: `bundle source rejected at ingest: ${issues.map((i) => i.message).join("; ")}`,
@@ -15925,7 +16004,7 @@ async function runVerify(opts) {
15925
16004
  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
16005
  }
15927
16006
  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"))
16007
+ (c) => !existsSync37(path47.join(task.set, c.rep, "get_screenshot.json")) || !existsSync37(path47.join(task.set, c.rep, "get_metadata.json"))
15929
16008
  );
15930
16009
  if (missing.length > 0) {
15931
16010
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -15935,8 +16014,8 @@ async function runVerify(opts) {
15935
16014
  });
15936
16015
  }
15937
16016
  const bar = BARS2[opts.bar];
15938
- const evidenceDir = path46.join(opts.bundleDir, "verify-evidence");
15939
- rmSync5(evidenceDir, { recursive: true, force: true });
16017
+ const evidenceDir = path47.join(opts.bundleDir, "verify-evidence");
16018
+ rmSync6(evidenceDir, { recursive: true, force: true });
15940
16019
  const scores = await scoreBundleForTask(task, opts.bundleDir, bar, { evidenceDir, onProgress: emitProgress });
15941
16020
  const quality = await checkBundleQuality(
15942
16021
  opts.bundleDir,
@@ -15953,7 +16032,7 @@ async function runVerify(opts) {
15953
16032
  // ASKED, never "follows every convention".
15954
16033
  loadCodebaseProfile(opts, opts.profile) ?? void 0
15955
16034
  );
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");
16035
+ const bundleCss = ["tokens.css", "styles.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n");
15957
16036
  const occlusion = await checkSiblingOcclusion(task, opts.bundleDir, bundleCss, { ...authorityConfigs !== void 0 ? { authority: authorityConfigs } : {} });
15958
16037
  const parity = await checkHoverParity(task, opts.bundleDir, authorityConfigs ?? task.configs, {
15959
16038
  ...opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {},
@@ -15974,10 +16053,10 @@ async function runVerify(opts) {
15974
16053
  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
16054
  }
15976
16055
  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: [] };
16056
+ const verifyPins = crossComposition.rows.length > 0 ? composedPins(task.set, [opts.library !== void 0 ? path47.resolve(verifyCallerCwd, opts.library) : verifyCallerCwd]) : { pins: [], issues: [] };
15978
16057
  const staticComposedChecks = composedChecks(opts.bundleDir, task.entry, verifyPins.pins);
15979
16058
  const renderExpectations = verifyPins.pins.map((pin) => ({
15980
- modulePath: path46.join(composedModuleDir(pin.partnerName), pin.entryComponent),
16059
+ modulePath: path47.join(composedModuleDir(pin.partnerName), pin.entryComponent),
15981
16060
  component: pin.entryComponent,
15982
16061
  stampName: `${pin.pairKey}:${pin.entryComponent}`,
15983
16062
  hostReps: [...new Set(pin.instances.map((i) => i.hostRep))],
@@ -16270,7 +16349,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16270
16349
  }
16271
16350
  process.stdout.write(`environment: chrome=${report.environment.chromeVersion ?? report.environment.chrome} fonts=${report.environment.fontsManifestSha256 ?? "UNRESOLVED"}
16272
16351
  `);
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")));
16352
+ const shippedFonts = requiredFontsManifest(cssFontFamilies(["styles.css", "tokens.css"].map((f) => path47.join(opts.bundleDir, f)).filter((f) => existsSync37(f)).map((f) => readFileSync34(f, "utf8")).join("\n")));
16274
16353
  if (shippedFonts.length > 0 && substitutedFamilies.length === 0) {
16275
16354
  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
16355
  `);
@@ -16325,7 +16404,7 @@ ${certified}/${statuses.length} certified \xB7 ${report.coverage.pass}/${statuse
16325
16404
  persistReport(opts, report, evidenceDir);
16326
16405
  }
16327
16406
  function persistReport(opts, report, evidenceDir) {
16328
- if (!existsSync36(evidenceDir)) return;
16407
+ if (!existsSync37(evidenceDir)) return;
16329
16408
  const rulerExit = process.exitCode === void 0 ? 0 : Number(process.exitCode);
16330
16409
  const withExit = {
16331
16410
  ...report,
@@ -16333,9 +16412,9 @@ function persistReport(opts, report, evidenceDir) {
16333
16412
  ...rulerExit !== 0 ? { rulerRefusal: EXIT_REFUSALS[rulerExit] ?? `ruler exited ${rulerExit}` } : {}
16334
16413
  };
16335
16414
  try {
16336
- writeFileSync15(
16337
- path46.join(evidenceDir, VERIFY_REPORT_FILENAME),
16338
- `${JSON.stringify(verifyReportForTransport(withExit, path46.basename(opts.bundleDir)), null, 2)}
16415
+ writeFileSync16(
16416
+ path47.join(evidenceDir, VERIFY_REPORT_FILENAME),
16417
+ `${JSON.stringify(verifyReportForTransport(withExit, path47.basename(opts.bundleDir)), null, 2)}
16339
16418
  `
16340
16419
  );
16341
16420
  } catch (e) {
@@ -16385,11 +16464,11 @@ __export(engine_exports, {
16385
16464
  runEngineBrief: () => runEngineBrief,
16386
16465
  runEngineScore: () => runEngineScore
16387
16466
  });
16388
- import { appendFileSync, existsSync as existsSync37, mkdirSync as mkdirSync10, readFileSync as readFileSync34, writeFileSync as writeFileSync16 } from "node:fs";
16389
- import path47 from "node:path";
16467
+ import { appendFileSync, existsSync as existsSync38, mkdirSync as mkdirSync11, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16468
+ import path48 from "node:path";
16390
16469
  function resolveEngineTask(opts, callerCwd) {
16391
- const asPath = path47.resolve(callerCwd, opts.taskOrSet);
16392
- const isSet = existsSync37(path47.join(asPath, "recording-set.json"));
16470
+ const asPath = path48.resolve(callerCwd, opts.taskOrSet);
16471
+ const isSet = existsSync38(path48.join(asPath, "recording-set.json"));
16393
16472
  const registry = TASKS[opts.taskOrSet];
16394
16473
  if (registry !== void 0 && !isSet) return { task: registry, name: opts.taskOrSet, ref: opts.taskOrSet, disclosures: [], interactionEvidence: void 0 };
16395
16474
  if (isSet) {
@@ -16398,7 +16477,7 @@ function resolveEngineTask(opts, callerCwd) {
16398
16477
  for (const d of authored.disclosures) warn(opts, d);
16399
16478
  return {
16400
16479
  task: authored.task,
16401
- name: path47.basename(asPath),
16480
+ name: path48.basename(asPath),
16402
16481
  ref: asPath,
16403
16482
  disclosures: authored.disclosures,
16404
16483
  interactionEvidence: authored.api.interactionEvidence,
@@ -16425,10 +16504,11 @@ function runEngineBrief(opts) {
16425
16504
  requireEntitlement(opts);
16426
16505
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16427
16506
  const { task, name, ref, disclosures } = resolveEngineTask(opts, callerCwd);
16507
+ void reportRunPresence(name, "implementing");
16428
16508
  const bar = BARS3[opts.bar];
16429
- if (existsSync37(path47.join(task.set, "recording-set.json"))) {
16509
+ if (existsSync38(path48.join(task.set, "recording-set.json"))) {
16430
16510
  try {
16431
- const { open, skippedParent } = compositionPairsFor(path47.resolve(task.set), [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16511
+ const { open, skippedParent } = compositionPairsFor(path48.resolve(task.set), [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16432
16512
  if (skippedParent !== void 0) {
16433
16513
  disclosures.push(
16434
16514
  `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 +16517,7 @@ function runEngineBrief(opts) {
16437
16517
  if (open.length > 0) {
16438
16518
  const componentNames = [...new Set(open.map((p) => p.displayName))];
16439
16519
  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.`
16520
+ `COMPOSITION PAIRS UNDECIDED: this recording references ${componentNames.length} other recorded component(s) (${open.map((p) => `${p.displayName} [pair-key ${p.key}]: ${p.instances.length} instance(s)`).join("; ")}) and no human has decided the pairing. This brief treats the component as SELF-CONTAINED \u2014 you will re-implement the partner's pixels locally. If composition is wanted: generate the partner bundle first, have a human run \`${tendrilCommand(`compose --set ${path48.resolve(task.set)}`)}\` in their terminal, and re-emit this brief. Proceeding as-is is valid; it just is not composition.`
16441
16521
  );
16442
16522
  }
16443
16523
  } catch (err) {
@@ -16454,9 +16534,9 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16454
16534
  const brief = buildBrief(task.systemApi, bar, { colorScheme: recordingIsDark(task) ? "dark" : "light" }) + disclosureBlock + motionBriefSection(task.set) + conventions;
16455
16535
  const segments = buildSegments(task, "files");
16456
16536
  let notRecorded;
16457
- const manifestPath2 = path47.join(task.set, "recording-set.json");
16458
- if (existsSync37(manifestPath2)) {
16459
- notRecorded = JSON.parse(readFileSync34(manifestPath2, "utf8")).notRecorded;
16537
+ const manifestPath2 = path48.join(task.set, "recording-set.json");
16538
+ if (existsSync38(manifestPath2)) {
16539
+ notRecorded = JSON.parse(readFileSync35(manifestPath2, "utf8")).notRecorded;
16460
16540
  }
16461
16541
  const unverified = notRecorded !== void 0 && notRecorded !== "" ? `
16462
16542
 
@@ -16464,7 +16544,7 @@ ${disclosures.map((d) => `- ${d}`).join("\n")}` : "";
16464
16544
  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
16545
  ${notRecorded}` : "";
16466
16546
  let fontProvisioning;
16467
- if (existsSync37(manifestPath2)) {
16547
+ if (existsSync38(manifestPath2)) {
16468
16548
  const missingFams = unprovisionedFamilies(task.set);
16469
16549
  const unprovided = unprovisionedFaces(task.set);
16470
16550
  const weightOnly = missingFams.length === 0;
@@ -16486,7 +16566,7 @@ ${notRecorded}` : "";
16486
16566
  };
16487
16567
  }
16488
16568
  }
16489
- const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16569
+ const pinsResult = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16490
16570
  const jsxProp = ([k, v]) => typeof v === "string" ? `${k}=${JSON.stringify(v)}` : `${k}={${JSON.stringify(v)}}`;
16491
16571
  const composedBlock = pinsResult.pins.length === 0 && pinsResult.issues.length === 0 ? "" : (pinsResult.pins.length > 0 ? `
16492
16572
 
@@ -16522,10 +16602,10 @@ ${pinsResult.issues.map((i) => `- ${i}`).join("\n")}` : "");
16522
16602
 
16523
16603
  === TASK PAYLOAD (recorded truth, verbatim) ===
16524
16604
  ${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);
16605
+ const payloadFile = path48.resolve(callerCwd, opts.out ?? `tendril-out/${name}-brief.md`);
16606
+ const candidateDirSuggestion = path48.resolve(callerCwd, `tendril-out/${name}-candidate`);
16607
+ mkdirSync11(path48.dirname(payloadFile), { recursive: true });
16608
+ writeFileSync17(payloadFile, payload);
16529
16609
  emitData(
16530
16610
  opts,
16531
16611
  {
@@ -16571,7 +16651,7 @@ ${segments}`;
16571
16651
  // command must search the same bundle roots the pins came
16572
16652
  // from, or the oracle and the brief describe different worlds.
16573
16653
  `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`
16654
+ `engine score ${quoteArg(ref)} ${quoteArg(candidateDirSuggestion)} --bar ${opts.bar} --model ${opts.model ?? "<declared-model>"}${opts.library !== void 0 ? ` --library ${quoteArg(path48.resolve(callerCwd, opts.library))}` : ""} --json`
16575
16655
  )}\` \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
16656
  "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
16657
  "Never edit recording sets; never claim scores yourself \u2014 only the score command's output counts."
@@ -16586,8 +16666,8 @@ ${segments}`;
16586
16666
  );
16587
16667
  }
16588
16668
  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;
16669
+ const file = path48.join(candidateDir, "score-history.jsonl");
16670
+ const starts = existsSync38(file) ? readFileSync35(file, "utf8").split("\n").filter((l) => l.includes('"event":"round-start"')).length : 0;
16591
16671
  const round = entry.event === "round-start" ? starts + 1 : Math.max(1, starts);
16592
16672
  appendFileSync(file, `${JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), round, ...entry })}
16593
16673
  `);
@@ -16595,9 +16675,10 @@ function appendScoreHistory(candidateDir, entry) {
16595
16675
  async function runEngineScore(opts) {
16596
16676
  requireEntitlement(opts);
16597
16677
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16598
- const candidateDir = path47.resolve(callerCwd, opts.candidateDir);
16678
+ const candidateDir = path48.resolve(callerCwd, opts.candidateDir);
16599
16679
  const { task, name, apiPin, interactionEvidence, unmappedInteractionEvidence: interactionEvidenceUnmapped, satisfiabilityDemoted, expertContract } = resolveEngineTask(opts, callerCwd);
16600
- if (!existsSync37(candidateDir)) {
16680
+ void reportRunPresence(name, "implementing");
16681
+ if (!existsSync38(candidateDir)) {
16601
16682
  fail(opts, ExitCode.InputValidation, {
16602
16683
  error: `candidate directory not found: ${candidateDir}`,
16603
16684
  code: "candidate-missing",
@@ -16622,10 +16703,10 @@ async function runEngineScore(opts) {
16622
16703
  for (const g of missingWeights(task.set)) {
16623
16704
  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
16705
  }
16625
- if (opts.rebind !== true && existsSync37(path47.join(candidateDir, "component.json"))) {
16706
+ if (opts.rebind !== true && existsSync38(path48.join(candidateDir, "component.json"))) {
16626
16707
  const prior = (() => {
16627
16708
  try {
16628
- const read = readBundleManifest(readFileSync34(path47.join(candidateDir, "component.json"), "utf8"));
16709
+ const read = readBundleManifest(readFileSync35(path48.join(candidateDir, "component.json"), "utf8"));
16629
16710
  return read.manifest === void 0 ? { unreadable: true } : { hash: read.manifest.provenance.recordingSet.hash, path: read.manifest.provenance.recordingSet.path };
16630
16711
  } catch {
16631
16712
  return { unreadable: true };
@@ -16647,13 +16728,13 @@ async function runEngineScore(opts) {
16647
16728
  }
16648
16729
  }
16649
16730
  const bar = BARS3[opts.bar];
16650
- const evidenceDir = path47.join(candidateDir, "verify-evidence");
16731
+ const evidenceDir = path48.join(candidateDir, "verify-evidence");
16651
16732
  const scores = await scoreBundleForTask(task, candidateDir, bar, { evidenceDir, onProgress: emitProgress });
16652
16733
  const hoverOpts = opts.hoverTimeoutMs !== void 0 ? { hoverBudgetMs: opts.hoverTimeoutMs } : {};
16653
16734
  const parity = await checkHoverParity(task, candidateDir, task.configs, { ...hoverOpts, failureShotDir: evidenceDir });
16654
16735
  const occlusionRows = await checkSiblingOcclusion(task, candidateDir, candidateCss(candidateDir), { authority: task.configs });
16655
16736
  const occlusionFailures = occlusionRows.filter((o) => !o.pass);
16656
- const scorePins = composedPins(task.set, [opts.library !== void 0 ? path47.resolve(callerCwd, opts.library) : callerCwd]);
16737
+ const scorePins = composedPins(task.set, [opts.library !== void 0 ? path48.resolve(callerCwd, opts.library) : callerCwd]);
16657
16738
  const composition = composedChecks(candidateDir, task.entry, scorePins.pins);
16658
16739
  const behaviors = [...await checkBehaviors(task, candidateDir, hoverOpts), ...parity, ...composition];
16659
16740
  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 +16939,7 @@ var init_engine2 = __esm({
16858
16939
  init_entitlement();
16859
16940
  init_verify();
16860
16941
  init_compose2();
16942
+ init_run_presence();
16861
16943
  init_src5();
16862
16944
  BARS3 = {
16863
16945
  pass: { sim: 0.95, ink: 0.95 },
@@ -16871,11 +16953,11 @@ var codeconnect_exports = {};
16871
16953
  __export(codeconnect_exports, {
16872
16954
  runCodeConnect: () => runCodeConnect
16873
16955
  });
16874
- import { existsSync as existsSync38, readFileSync as readFileSync35, writeFileSync as writeFileSync17 } from "node:fs";
16875
- import path48 from "node:path";
16956
+ import { existsSync as existsSync39, readFileSync as readFileSync36, writeFileSync as writeFileSync18 } from "node:fs";
16957
+ import path49 from "node:path";
16876
16958
  function runCodeConnect(opts) {
16877
16959
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
16878
- const bundleDir = path48.resolve(callerCwd, opts.bundleDir);
16960
+ const bundleDir = path49.resolve(callerCwd, opts.bundleDir);
16879
16961
  let url;
16880
16962
  try {
16881
16963
  url = new URL(opts.figmaUrl);
@@ -16891,7 +16973,7 @@ function runCodeConnect(opts) {
16891
16973
  }
16892
16974
  let manifest;
16893
16975
  try {
16894
- const read = readBundleManifest(readFileSync35(path48.join(bundleDir, "component.json"), "utf8"));
16976
+ const read = readBundleManifest(readFileSync36(path49.join(bundleDir, "component.json"), "utf8"));
16895
16977
  if (read.manifest === void 0) throw new Error(read.issues.map((i) => i.message).join("; ") || "no component.json");
16896
16978
  manifest = read.manifest;
16897
16979
  } catch (err) {
@@ -16901,8 +16983,8 @@ function runCodeConnect(opts) {
16901
16983
  remediation: "Point at a bundle produced by the Tendril pipeline (it carries component.json), and verify it first."
16902
16984
  });
16903
16985
  }
16904
- const setDir = path48.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16905
- if (!existsSync38(path48.join(setDir, "recording-set.json"))) {
16986
+ const setDir = path49.resolve(callerCwd, opts.set ?? manifest.provenance.recordingSet.path);
16987
+ if (!existsSync39(path49.join(setDir, "recording-set.json"))) {
16906
16988
  fail(opts, ExitCode.InputValidation, {
16907
16989
  error: `recording set not found at ${setDir}`,
16908
16990
  code: "codeconnect-no-set",
@@ -16923,10 +17005,10 @@ function runCodeConnect(opts) {
16923
17005
  const component = api.component;
16924
17006
  const recManifest = loadManifest(setDir);
16925
17007
  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;
17008
+ const meta = path49.join(setDir, r.slug, "get_metadata.json");
17009
+ if (!existsSync39(meta)) return void 0;
16928
17010
  try {
16929
- return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync35(meta, "utf8"))))?.[1];
17011
+ return /name="([^"]*)"/.exec(envelopeTextContent(JSON.parse(readFileSync36(meta, "utf8"))))?.[1];
16930
17012
  } catch {
16931
17013
  return void 0;
16932
17014
  }
@@ -16991,7 +17073,7 @@ function runCodeConnect(opts) {
16991
17073
  axisLines.push(`const ${varName} = instance.getEnum(${q(axis)}, { ${entries.join(", ")} })`);
16992
17074
  fragmentVars.push(varName);
16993
17075
  }
16994
- const entryRel = path48.relative(callerCwd, path48.join(bundleDir, manifest.entry));
17076
+ const entryRel = path49.relative(callerCwd, path49.join(bundleDir, manifest.entry));
16995
17077
  const trust = manifest.trustStatement.split("\n")[0] ?? "";
16996
17078
  const lines = [
16997
17079
  `// url=${opts.figmaUrl}`,
@@ -17015,8 +17097,8 @@ function runCodeConnect(opts) {
17015
17097
  `}`,
17016
17098
  ``
17017
17099
  ].join("\n");
17018
- const outFile = path48.resolve(callerCwd, opts.out ?? path48.join(bundleDir, `${component}.figma.ts`));
17019
- writeFileSync17(outFile, lines);
17100
+ const outFile = path49.resolve(callerCwd, opts.out ?? path49.join(bundleDir, `${component}.figma.ts`));
17101
+ writeFileSync18(outFile, lines);
17020
17102
  emitData(
17021
17103
  opts,
17022
17104
  {
@@ -17054,18 +17136,18 @@ var init_codeconnect = __esm({
17054
17136
  });
17055
17137
 
17056
17138
  // 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";
17139
+ import { createHash as createHash10 } from "node:crypto";
17140
+ import { existsSync as existsSync40, mkdtempSync as mkdtempSync3, readFileSync as readFileSync37, readdirSync as readdirSync16, writeFileSync as writeFileSync19 } from "node:fs";
17059
17141
  import os8 from "node:os";
17060
- import path49 from "node:path";
17142
+ import path50 from "node:path";
17061
17143
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17062
17144
  import { z as z14 } from "zod";
17063
17145
  function sourceHash() {
17064
- const dir = path49.dirname(fileURLToPath6(import.meta.url));
17065
- const h = createHash9("sha256");
17146
+ const dir = path50.dirname(fileURLToPath6(import.meta.url));
17147
+ const h = createHash10("sha256");
17066
17148
  for (const f of readdirSync16(dir).filter((n) => n.endsWith(".ts")).sort()) {
17067
17149
  h.update(f);
17068
- h.update(readFileSync36(path49.join(dir, f)));
17150
+ h.update(readFileSync37(path50.join(dir, f)));
17069
17151
  }
17070
17152
  return h.digest("hex").slice(0, 16);
17071
17153
  }
@@ -17073,10 +17155,10 @@ var REPO_ROOT3, CLI_BIN, BUNDLED_CLI, CLI_SPAWN, str, optStr, TOOLS, BOOT_HASH;
17073
17155
  var init_server = __esm({
17074
17156
  "packages/mcp/src/server.ts"() {
17075
17157
  "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] };
17158
+ REPO_ROOT3 = path50.resolve(path50.dirname(fileURLToPath6(import.meta.url)), "..", "..", "..");
17159
+ CLI_BIN = path50.join(REPO_ROOT3, "packages", "cli", "src", "bin.ts");
17160
+ BUNDLED_CLI = path50.join(path50.dirname(fileURLToPath6(import.meta.url)), "tendril.js");
17161
+ CLI_SPAWN = existsSync40(BUNDLED_CLI) ? { cmd: process.execPath, prefix: [BUNDLED_CLI] } : { cmd: "npx", prefix: ["tsx", CLI_BIN] };
17080
17162
  str = (d) => z14.string().describe(d);
17081
17163
  optStr = (d) => z14.string().optional().describe(d);
17082
17164
  TOOLS = [
@@ -17107,13 +17189,13 @@ var init_server = __esm({
17107
17189
  const single = i["metadata"];
17108
17190
  const parts = i["metadataParts"];
17109
17191
  if (single !== void 0 || parts !== void 0) {
17110
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17192
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17111
17193
  if (single !== void 0) {
17112
- writeFileSync18(tmp, single);
17194
+ writeFileSync19(tmp, single);
17113
17195
  argvOut.push("--metadata-raw-file", tmp);
17114
17196
  } else {
17115
17197
  if (parts.length === 0) throw new Error("`metadataParts` must be a non-empty array of block texts");
17116
- writeFileSync18(tmp, JSON.stringify(parts));
17198
+ writeFileSync19(tmp, JSON.stringify(parts));
17117
17199
  argvOut.push("--metadata-raw-parts-file", tmp);
17118
17200
  }
17119
17201
  }
@@ -17163,6 +17245,24 @@ var init_server = __esm({
17163
17245
  schema: z14.object({}),
17164
17246
  argv: () => ["login", "--device-wait"]
17165
17247
  },
17248
+ {
17249
+ name: "tendril_publish",
17250
+ description: "Publish a VERIFIED bundle to the user's portal \u2014 phase one of the browser-approved publish. Re-publishing an already-published component completes in one call. A component's FIRST publish is a human-only decision the portal enforces: this call requests the approval and returns the approve-page link \u2014 RELAY IT to the user verbatim (they click Approve in the browser where they are signed in; when the page shows a terms checkbox, ticking it is part of their decision \u2014 never advise them to just tick it). Then call tendril_publish_wait to finish. You cannot approve this yourself: the portal only accepts the decision from their signed-in browser, never from this machine's token. Requires a green verify (the CLI refuses a declined run) and a portal session (tendril_login).",
17251
+ schema: z14.object({
17252
+ bundleDir: str("bundle directory (verified \u2014 carries component.json and verify-evidence)"),
17253
+ portal: optStr("portal origin override for self-hosted portals (defaults to the stored session's portal)")
17254
+ }),
17255
+ argv: (i) => ["publish", i["bundleDir"], "--approve-start", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17256
+ },
17257
+ {
17258
+ name: "tendril_publish_wait",
17259
+ 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.",
17260
+ schema: z14.object({
17261
+ bundleDir: str("the same bundle directory tendril_publish was called with"),
17262
+ portal: optStr("portal origin override (must match tendril_publish's)")
17263
+ }),
17264
+ argv: (i) => ["publish", i["bundleDir"], "--approve-wait", ...typeof i["portal"] === "string" ? ["--to", i["portal"]] : []]
17265
+ },
17166
17266
  {
17167
17267
  name: "tendril_record_next",
17168
17268
  annotations: { readOnlyHint: true },
@@ -17204,14 +17304,14 @@ var init_server = __esm({
17204
17304
  const bridge = (label, single, parts) => {
17205
17305
  if (single !== void 0 && parts !== void 0) throw new Error(`pass at most one of \`${label}\` and \`${label}Parts\``);
17206
17306
  if (single === void 0 && parts === void 0) return;
17207
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17307
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17208
17308
  if (single !== void 0) {
17209
- writeFileSync18(tmp, single);
17309
+ writeFileSync19(tmp, single);
17210
17310
  argvOut.push(`--${label}-file`, tmp);
17211
17311
  } else {
17212
17312
  const blocks = parts;
17213
17313
  if (blocks.length === 0) throw new Error(`\`${label}Parts\` must be a non-empty array of block texts`);
17214
- writeFileSync18(tmp, JSON.stringify(blocks));
17314
+ writeFileSync19(tmp, JSON.stringify(blocks));
17215
17315
  argvOut.push(`--${label}-parts-file`, tmp);
17216
17316
  }
17217
17317
  };
@@ -17252,12 +17352,12 @@ var init_server = __esm({
17252
17352
  const file = i["file"];
17253
17353
  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
17354
  if (file !== void 0) return [...base, "--file", file];
17255
- const tmp = path49.join(mkdtempSync3(path49.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17355
+ const tmp = path50.join(mkdtempSync3(path50.join(os8.tmpdir(), "tendril-envelope-")), "response.txt");
17256
17356
  if (text !== void 0) {
17257
- writeFileSync18(tmp, text);
17357
+ writeFileSync19(tmp, text);
17258
17358
  return [...base, "--file", tmp, "--raw"];
17259
17359
  }
17260
- writeFileSync18(tmp, JSON.stringify(texts));
17360
+ writeFileSync19(tmp, JSON.stringify(texts));
17261
17361
  return [...base, "--file", tmp, "--raw-parts"];
17262
17362
  }
17263
17363
  },
@@ -17414,13 +17514,13 @@ __export(permissions_exports, {
17414
17514
  runPermissions: () => runPermissions,
17415
17515
  writeSelection: () => writeSelection
17416
17516
  });
17417
- import { existsSync as existsSync40, mkdirSync as mkdirSync11, readFileSync as readFileSync37, writeFileSync as writeFileSync19 } from "node:fs";
17517
+ import { existsSync as existsSync41, mkdirSync as mkdirSync12, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17418
17518
  import os9 from "node:os";
17419
- import path50 from "node:path";
17519
+ import path51 from "node:path";
17420
17520
  function mergeAllowlist(file, entries, denyEntries = []) {
17421
17521
  let settings = {};
17422
- if (existsSync40(file) && readFileSync37(file, "utf8").trim() !== "") {
17423
- settings = JSON.parse(readFileSync37(file, "utf8"));
17522
+ if (existsSync41(file) && readFileSync38(file, "utf8").trim() !== "") {
17523
+ settings = JSON.parse(readFileSync38(file, "utf8"));
17424
17524
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw new Error("settings root is not an object");
17425
17525
  }
17426
17526
  const permissions = settings["permissions"] ??= {};
@@ -17440,8 +17540,8 @@ function mergeAllowlist(file, entries, denyEntries = []) {
17440
17540
  }
17441
17541
  if (added.length > 0 || denyAdded.length > 0) {
17442
17542
  allow.push(...added);
17443
- mkdirSync11(path50.dirname(file), { recursive: true });
17444
- writeFileSync19(file, `${JSON.stringify(settings, null, 2)}
17543
+ mkdirSync12(path51.dirname(file), { recursive: true });
17544
+ writeFileSync20(file, `${JSON.stringify(settings, null, 2)}
17445
17545
  `);
17446
17546
  }
17447
17547
  return { added, alreadyPresent, denyAdded };
@@ -17505,7 +17605,7 @@ async function runPermissions(flags) {
17505
17605
  }
17506
17606
  if (flags.write) {
17507
17607
  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");
17608
+ const file = flags.user ? path51.join(os9.homedir(), ".claude", "settings.json") : path51.join(base, ".claude", "settings.local.json");
17509
17609
  const { entries, denyEntries } = writeSelection(result, flags.user === true);
17510
17610
  if (flags.dryRun) {
17511
17611
  emitData(flags, { file, wouldAdd: entries, wouldDeny: denyEntries }, () => {
@@ -17654,13 +17754,13 @@ __export(inspect_exports, {
17654
17754
  INSPECT_DESCRIPTION: () => INSPECT_DESCRIPTION,
17655
17755
  runInspect: () => runInspect
17656
17756
  });
17657
- import { existsSync as existsSync41, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
17658
- import path51 from "node:path";
17757
+ import { existsSync as existsSync42, readFileSync as readFileSync39, writeFileSync as writeFileSync21 } from "node:fs";
17758
+ import path52 from "node:path";
17659
17759
  function readVerifyReport(evidenceDir) {
17660
- const p = path51.join(evidenceDir, VERIFY_REPORT_FILENAME);
17661
- if (!existsSync41(p)) return void 0;
17760
+ const p = path52.join(evidenceDir, VERIFY_REPORT_FILENAME);
17761
+ if (!existsSync42(p)) return void 0;
17662
17762
  try {
17663
- return JSON.parse(readFileSync38(p, "utf8"));
17763
+ return JSON.parse(readFileSync39(p, "utf8"));
17664
17764
  } catch {
17665
17765
  return void 0;
17666
17766
  }
@@ -17688,17 +17788,17 @@ async function runInspect(opts) {
17688
17788
  printDescription(INSPECT_DESCRIPTION);
17689
17789
  return;
17690
17790
  }
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)) {
17791
+ const bundleDir = path52.resolve(opts.bundleDir);
17792
+ const evidenceDir = path52.join(bundleDir, "verify-evidence");
17793
+ const manifestPath2 = path52.join(bundleDir, "component.json");
17794
+ if (!existsSync42(evidenceDir) || !existsSync42(manifestPath2)) {
17695
17795
  fail(opts, ExitCode.InputValidation, {
17696
- error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync41(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17796
+ error: `nothing to inspect in ${bundleDir} \u2014 ${existsSync42(manifestPath2) ? "no verify-evidence directory" : "no component.json"}`,
17697
17797
  code: "no-evidence",
17698
17798
  remediation: `Run \`${tendrilCommand(`verify ${bundleDir}`)}\` first \u2014 inspect reads the ref/render evidence that run writes.`
17699
17799
  });
17700
17800
  }
17701
- const { manifest } = readBundleManifest(readFileSync38(manifestPath2, "utf8"));
17801
+ const { manifest } = readBundleManifest(readFileSync39(manifestPath2, "utf8"));
17702
17802
  if (manifest === void 0) {
17703
17803
  fail(opts, ExitCode.InputValidation, {
17704
17804
  error: "component.json did not parse as a bundle manifest",
@@ -17706,9 +17806,9 @@ async function runInspect(opts) {
17706
17806
  remediation: "Re-emit the bundle (score/verify rewrite component.json), then re-run inspect."
17707
17807
  });
17708
17808
  }
17709
- const setDir = path51.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17809
+ const setDir = path52.resolve(opts.set ?? manifest.provenance.recordingSet.path);
17710
17810
  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`)));
17811
+ const reps = Object.keys(manifest.propAdapter).filter((rep) => existsSync42(path52.join(evidenceDir, `${rep}-ref.png`)) && existsSync42(path52.join(evidenceDir, `${rep}-render.png`)));
17712
17812
  if (reps.length === 0) {
17713
17813
  fail(opts, ExitCode.InputValidation, {
17714
17814
  error: "verify-evidence holds no ref/render pairs for this bundle's configs",
@@ -17719,15 +17819,15 @@ async function runInspect(opts) {
17719
17819
  let crops = 0;
17720
17820
  const sections = [];
17721
17821
  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`)));
17822
+ const ref = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-ref.png`)));
17823
+ const render = new Uint8Array(readFileSync39(path52.join(evidenceDir, `${rep}-render.png`)));
17724
17824
  const nodes = smallSemanticNodes(setDir, rep, opts.maxArea ?? 1024).slice(0, 12);
17725
17825
  const cells = [];
17726
17826
  for (const [i, n] of nodes.entries()) {
17727
17827
  const rect = { x: n.x, y: n.y, w: n.w, h: n.h };
17728
17828
  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));
17829
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-ref.png`), zoomCrop(ref, rect));
17830
+ writeFileSync21(path52.join(evidenceDir, `${rep}-inspect-${i}-render.png`), zoomCrop(render, rect));
17731
17831
  } catch {
17732
17832
  continue;
17733
17833
  }
@@ -17744,8 +17844,8 @@ async function runInspect(opts) {
17744
17844
  if (reps.includes(c.rep)) continue;
17745
17845
  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
17846
  }
17747
- const sheet = path51.join(evidenceDir, "inspect.html");
17748
- writeFileSync20(
17847
+ const sheet = path52.join(evidenceDir, "inspect.html");
17848
+ writeFileSync21(
17749
17849
  sheet,
17750
17850
  `<!doctype html><meta charset="utf-8"><title>${esc(manifest.name)} \u2014 tendril inspect</title><style>
17751
17851
  body{font:14px/1.45 system-ui,sans-serif;margin:24px;background:#fff;color:#111}
@@ -17818,8 +17918,8 @@ __export(login_exports, {
17818
17918
  runLogout: () => runLogout
17819
17919
  });
17820
17920
  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";
17921
+ import { existsSync as existsSync43, mkdirSync as mkdirSync13, readFileSync as readFileSync40, rmSync as rmSync7, writeFileSync as writeFileSync22 } from "node:fs";
17922
+ import path53 from "node:path";
17823
17923
  import { isCancel as isCancel3, password as password2 } from "@clack/prompts";
17824
17924
  async function runLogin(opts, deps) {
17825
17925
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? DEFAULT_PORTAL_ORIGIN).replace(/\/+$/, "");
@@ -17913,13 +18013,13 @@ function settleDecision(opts, origin, outcome) {
17913
18013
  }
17914
18014
  }
17915
18015
  function pendingLoginPath() {
17916
- return path52.join(path52.dirname(sessionPath()), "pending-login.json");
18016
+ return path53.join(path53.dirname(sessionPath()), "pending-login.json");
17917
18017
  }
17918
18018
  async function deviceStartPhase(opts, origin, deps) {
17919
18019
  const started = await startHandshake(opts, origin, deps);
17920
18020
  const file = pendingLoginPath();
17921
- mkdirSync12(path52.dirname(file), { recursive: true });
17922
- writeFileSync21(file, `${JSON.stringify({ origin, ...started }, null, 2)}
18021
+ mkdirSync13(path53.dirname(file), { recursive: true });
18022
+ writeFileSync22(file, `${JSON.stringify({ origin, ...started }, null, 2)}
17923
18023
  `, { mode: 384 });
17924
18024
  deps.openBrowser(started.verificationUrl);
17925
18025
  emitData(
@@ -17944,9 +18044,9 @@ async function deviceStartPhase(opts, origin, deps) {
17944
18044
  async function deviceWaitPhase(opts, deps) {
17945
18045
  const file = pendingLoginPath();
17946
18046
  let pending;
17947
- if (existsSync42(file)) {
18047
+ if (existsSync43(file)) {
17948
18048
  try {
17949
- const parsed = JSON.parse(readFileSync39(file, "utf8"));
18049
+ const parsed = JSON.parse(readFileSync40(file, "utf8"));
17950
18050
  if (typeof parsed.origin === "string" && typeof parsed.deviceCode === "string" && typeof parsed.intervalSeconds === "number") {
17951
18051
  pending = parsed;
17952
18052
  }
@@ -17960,7 +18060,7 @@ async function deviceWaitPhase(opts, deps) {
17960
18060
  remediation: `Start one first: ${tendrilCommand("login --device-start")} (the tendril_login tool).`
17961
18061
  });
17962
18062
  }
17963
- const done = () => rmSync6(file, { force: true });
18063
+ const done = () => rmSync7(file, { force: true });
17964
18064
  const total = Math.ceil(660 / Math.max(1, pending.intervalSeconds));
17965
18065
  let ticks = 0;
17966
18066
  const outcome = await waitForDecision(opts, pending.origin, deps, pending, () => {
@@ -18197,10 +18297,10 @@ var publish_exports = {};
18197
18297
  __export(publish_exports, {
18198
18298
  runPublish: () => runPublish
18199
18299
  });
18200
- import { existsSync as existsSync43, readFileSync as readFileSync40 } from "node:fs";
18201
- import path53 from "node:path";
18300
+ import { existsSync as existsSync44, readFileSync as readFileSync41, rmSync as rmSync8, writeFileSync as writeFileSync23 } from "node:fs";
18301
+ import path54 from "node:path";
18202
18302
  async function runPublish(opts) {
18203
- const bundleDir = path53.resolve(opts.bundleDir);
18303
+ const bundleDir = path54.resolve(opts.bundleDir);
18204
18304
  const bundle = readBundle(opts, bundleDir);
18205
18305
  const report = bundle.report;
18206
18306
  if (typeof report["rulerExit"] !== "number" || !Number.isInteger(report["rulerExit"])) {
@@ -18245,7 +18345,7 @@ async function runPublish(opts) {
18245
18345
  const sheetEntry = surface.published.find((p) => p.role === "inspect-sheet");
18246
18346
  if (sheetEntry !== void 0) {
18247
18347
  const missingCrops = missingInspectCrops(
18248
- readFileSync40(path53.join(bundleDir, sheetEntry.path), "utf8"),
18348
+ readFileSync41(path54.join(bundleDir, sheetEntry.path), "utf8"),
18249
18349
  surface.published.map((p) => p.path)
18250
18350
  );
18251
18351
  if (missingCrops.length > 0) {
@@ -18312,46 +18412,40 @@ async function runPublish(opts) {
18312
18412
  }
18313
18413
  const origin = (opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "");
18314
18414
  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
- });
18321
- }
18322
- if (opts.acceptTerms === true) {
18323
- const accepted = await client.acceptTerms({ figmaFile });
18324
- if (!accepted.ok) refuse2(opts, accepted, "accept-terms-refused");
18415
+ if (opts.approveWait === true) {
18416
+ await approveWaitPhase(opts, client, bundleDir);
18325
18417
  }
18326
- const opened = await client.begin({
18418
+ void reportRunPresence(componentName, "publishing");
18419
+ let opened = await client.begin({
18327
18420
  componentName,
18328
18421
  figmaFile,
18329
18422
  entry: bundle.manifest.entry,
18330
18423
  files: bundle.files,
18331
- report: bundle.reportText,
18332
- confirmed: opts.confirmPublish === true
18424
+ report: bundle.reportText
18333
18425
  });
18426
+ if (!opened.ok && opened.needsConfirmation !== void 0 && opts.approveWait !== true) {
18427
+ const flow = await runApprovalFlow(opts, client, bundleDir, {
18428
+ componentName,
18429
+ figmaFile,
18430
+ begin: () => client.begin({ componentName, figmaFile, entry: bundle.manifest.entry, files: bundle.files, report: bundle.reportText })
18431
+ });
18432
+ if (flow === void 0) return;
18433
+ opened = flow;
18434
+ }
18334
18435
  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
18436
  if (opened.needsConfirmation !== void 0) {
18343
18437
  fail(opts, ExitCode.ConfirmationRequired, {
18344
18438
  error: `publishing ${JSON.stringify(opened.needsConfirmation.componentName)} for the first time is a decision a person makes, not a build step`,
18345
18439
  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.`
18440
+ remediation: "Approve it in your browser \u2014 run the publish again and open the link it prints."
18347
18441
  });
18348
18442
  }
18349
18443
  refuse2(opts, opened, "publish-refused");
18350
18444
  }
18351
18445
  const uploaded = [];
18352
18446
  for (const object of opened.value.plan.objects) {
18353
- const file = path53.join(bundleDir, object.relPath);
18354
- if (!existsSync43(file)) {
18447
+ const file = path54.join(bundleDir, object.relPath);
18448
+ if (!existsSync44(file)) {
18355
18449
  fail(opts, ExitCode.InputValidation, {
18356
18450
  error: `the portal expects ${object.relPath}, and it is not in ${opts.bundleDir}`,
18357
18451
  code: "planned-file-missing",
@@ -18361,12 +18455,15 @@ async function runPublish(opts) {
18361
18455
  const sent = await client.upload({
18362
18456
  publicationId: opened.value.publicationId,
18363
18457
  relPath: object.relPath,
18364
- bytes: new Uint8Array(readFileSync40(file))
18458
+ bytes: new Uint8Array(readFileSync41(file))
18365
18459
  });
18366
18460
  if (!sent.ok) refuse2(opts, sent, "upload-refused");
18367
18461
  uploaded.push({ relPath: object.relPath, deduplicated: sent.value.deduplicated });
18368
18462
  }
18369
18463
  const committed = await client.commit({ publicationId: opened.value.publicationId });
18464
+ if (committed.ok) {
18465
+ await endRunPresence(componentName);
18466
+ }
18370
18467
  if (!committed.ok) {
18371
18468
  if (committed.missing !== void 0 && committed.missing.length > 0) {
18372
18469
  fail(opts, ExitCode.General, {
@@ -18403,23 +18500,23 @@ async function runPublish(opts) {
18403
18500
  );
18404
18501
  }
18405
18502
  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)) {
18503
+ const manifestPath2 = path54.join(bundleDir, "component.json");
18504
+ const reportPath = path54.join(bundleDir, EVIDENCE_DIR, VERIFY_REPORT_FILENAME);
18505
+ if (!existsSync44(manifestPath2)) {
18409
18506
  fail(opts, ExitCode.InputValidation, {
18410
18507
  error: `there is no component.json in ${opts.bundleDir}, so this is not a bundle`,
18411
18508
  code: "not-a-bundle",
18412
18509
  remediation: `Point publish at a directory a Tendril run produced. \`${tendrilCommand("generate --help")}\` shows how one is made.`
18413
18510
  });
18414
18511
  }
18415
- if (!existsSync43(reportPath)) {
18512
+ if (!existsSync44(reportPath)) {
18416
18513
  fail(opts, ExitCode.InputValidation, {
18417
18514
  error: `this bundle has never been verified \u2014 there is no ${EVIDENCE_DIR}/${VERIFY_REPORT_FILENAME}`,
18418
18515
  code: "bundle-not-verified",
18419
18516
  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
18517
  });
18421
18518
  }
18422
- const { manifest } = readBundleManifest(readFileSync40(manifestPath2, "utf8"));
18519
+ const { manifest } = readBundleManifest(readFileSync41(manifestPath2, "utf8"));
18423
18520
  if (manifest === void 0) {
18424
18521
  fail(opts, ExitCode.InputValidation, {
18425
18522
  error: "component.json did not parse as a bundle manifest",
@@ -18427,7 +18524,7 @@ function readBundle(opts, bundleDir) {
18427
18524
  remediation: `Re-emit the bundle \u2014 \`${tendrilCommand(`verify ${opts.bundleDir}`)}\` rewrites component.json \u2014 then publish.`
18428
18525
  });
18429
18526
  }
18430
- const reportText = readFileSync40(reportPath, "utf8");
18527
+ const reportText = readFileSync41(reportPath, "utf8");
18431
18528
  let report;
18432
18529
  try {
18433
18530
  report = JSON.parse(reportText);
@@ -18487,6 +18584,96 @@ function refuse2(opts, sent, code) {
18487
18584
  remediation: sent.status === 401 ? `That session is no longer valid. Run \`${tendrilCommand(`login --to ${(opts.to ?? process.env["TENDRIL_PORTAL_URL"] ?? "").replace(/\/+$/, "")}`)}\` and paste a fresh token.` : sent.status >= 500 ? `The portal failed on its side. Quote the error id above to whoever runs it, then run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 a retry rejoins this same unfinished publication.` : `Fix what is named above and run \`${tendrilCommand(`publish ${opts.bundleDir}`)}\` again \u2014 it rejoins this same unfinished publication rather than starting a second one.`
18488
18585
  });
18489
18586
  }
18587
+ function pendingApprovalPath() {
18588
+ return path54.join(path54.dirname(sessionPath()), "pending-publish.json");
18589
+ }
18590
+ async function runApprovalFlow(opts, client, bundleDir, input) {
18591
+ const requested = await client.requestApproval({ componentName: input.componentName, figmaFile: input.figmaFile });
18592
+ if (!requested.ok) refuse2(opts, requested, "approval-request-refused");
18593
+ const approval = requested.value;
18594
+ if (opts.approveStart === true) {
18595
+ writeFileSync23(pendingApprovalPath(), `${JSON.stringify({ ...approval, bundleDir }, null, 2)}
18596
+ `, { mode: 384 });
18597
+ emitData(
18598
+ opts,
18599
+ {
18600
+ status: "approval-pending",
18601
+ approveUrl: approval.approveUrl,
18602
+ componentName: input.componentName,
18603
+ expiresAt: approval.expiresAt,
18604
+ next: "open the approve page in the signed-in browser, click Approve (tick the terms box if shown), then finish with --approve-wait"
18605
+ },
18606
+ () => {
18607
+ process.stdout.write(`Approval requested for ${JSON.stringify(input.componentName)}.
18608
+ `);
18609
+ process.stdout.write(`Approve it here: ${approval.approveUrl}
18610
+ `);
18611
+ process.stdout.write(`Then finish with: ${tendrilCommand(`publish ${opts.bundleDir} --approve-wait`)}
18612
+ `);
18613
+ }
18614
+ );
18615
+ return void 0;
18616
+ }
18617
+ process.stderr.write(`This component's FIRST publish needs your approval in the browser:
18618
+ ${approval.approveUrl}
18619
+ `);
18620
+ process.stderr.write(`Waiting for your decision (lapses at ${approval.expiresAt.slice(11, 16)} UTC)\u2026
18621
+ `);
18622
+ const decided = await waitForApproval(opts, client, approval);
18623
+ if (decided === "approved") return input.begin();
18624
+ failDecision(opts, decided);
18625
+ }
18626
+ async function approveWaitPhase(opts, client, bundleDir) {
18627
+ const file = pendingApprovalPath();
18628
+ let pending;
18629
+ if (existsSync44(file)) {
18630
+ try {
18631
+ const parsed = JSON.parse(readFileSync41(file, "utf8"));
18632
+ if (typeof parsed.approvalId === "string" && typeof parsed.approveUrl === "string" && typeof parsed.expiresAt === "string") {
18633
+ pending = { pollSeconds: 3, bundleDir, ...parsed };
18634
+ }
18635
+ } catch {
18636
+ }
18637
+ }
18638
+ if (pending === void 0) {
18639
+ fail(opts, ExitCode.InputValidation, {
18640
+ error: "there is no publish approval waiting to finish",
18641
+ code: "no-pending-approval",
18642
+ remediation: `Start one first: ${tendrilCommand(`publish ${opts.bundleDir} --approve-start`)} (the tendril_publish tool).`
18643
+ });
18644
+ }
18645
+ const done = () => rmSync8(file, { force: true });
18646
+ const decided = await waitForApproval(opts, client, pending);
18647
+ done();
18648
+ if (decided !== "approved") failDecision(opts, decided);
18649
+ }
18650
+ async function waitForApproval(opts, client, approval) {
18651
+ const interval = Math.max(1, approval.pollSeconds) * 1e3;
18652
+ const total = Math.ceil(APPROVAL_WAIT_CAP_MS / interval);
18653
+ for (let tick = 1; tick <= total; tick += 1) {
18654
+ const polled = await client.pollApproval({ approvalId: approval.approvalId });
18655
+ if (!polled.ok) refuse2(opts, polled, "approval-poll-refused");
18656
+ if (polled.value.status !== "pending") return polled.value.status;
18657
+ emitProgress(tick, total, "waiting for the browser approval");
18658
+ await new Promise((resolve) => setTimeout(resolve, interval));
18659
+ }
18660
+ return "expired";
18661
+ }
18662
+ function failDecision(opts, decided) {
18663
+ if (decided === "denied") {
18664
+ fail(opts, ExitCode.ConfirmationRequired, {
18665
+ error: "the publish was DENIED in the browser \u2014 the human said no",
18666
+ code: "publish-approval-denied",
18667
+ remediation: "Nothing was published. If minds change, run the publish again \u2014 it makes a fresh request."
18668
+ });
18669
+ }
18670
+ fail(opts, ExitCode.ConfirmationRequired, {
18671
+ error: decided === "expired" ? "the approval request lapsed before anyone decided it" : "the approval request is gone \u2014 it lapsed and was cleaned up, or was already spent",
18672
+ code: "publish-approval-lapsed",
18673
+ remediation: "Run the publish again for a fresh request, and decide it within its 30-minute window."
18674
+ });
18675
+ }
18676
+ var APPROVAL_WAIT_CAP_MS;
18490
18677
  var init_publish = __esm({
18491
18678
  "packages/cli/src/commands/publish.ts"() {
18492
18679
  "use strict";
@@ -18495,6 +18682,8 @@ var init_publish = __esm({
18495
18682
  init_invocation();
18496
18683
  init_output();
18497
18684
  init_publish_client();
18685
+ init_run_presence();
18686
+ APPROVAL_WAIT_CAP_MS = 31 * 6e4;
18498
18687
  }
18499
18688
  });
18500
18689
 
@@ -18522,17 +18711,17 @@ __export(generate_recorded_exports, {
18522
18711
  runGenerateRecorded: () => runGenerateRecorded
18523
18712
  });
18524
18713
  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";
18714
+ import { existsSync as existsSync45, readFileSync as readFileSync42 } from "node:fs";
18715
+ import path55 from "node:path";
18527
18716
  async function runGenerateRecorded(opts) {
18528
18717
  const callerCwd = process.env["INIT_CWD"] ?? process.cwd();
18529
- const outDirAbs = path54.resolve(callerCwd, opts.out);
18530
- const recordedAsPath = path54.resolve(callerCwd, opts.recorded);
18718
+ const outDirAbs = path55.resolve(callerCwd, opts.out);
18719
+ const recordedAsPath = path55.resolve(callerCwd, opts.recorded);
18531
18720
  let task;
18532
18721
  let taskName;
18533
18722
  let authoredApi;
18534
18723
  let composition;
18535
- const isSet = existsSync44(path54.join(recordedAsPath, "recording-set.json"));
18724
+ const isSet = existsSync45(path55.join(recordedAsPath, "recording-set.json"));
18536
18725
  const registry = TASKS[opts.recorded];
18537
18726
  if (registry !== void 0 && !isSet) {
18538
18727
  task = registry;
@@ -18541,7 +18730,7 @@ async function runGenerateRecorded(opts) {
18541
18730
  try {
18542
18731
  const authored = authorTaskFromSet(recordedAsPath);
18543
18732
  task = authored.task;
18544
- taskName = path54.basename(recordedAsPath);
18733
+ taskName = path55.basename(recordedAsPath);
18545
18734
  authoredApi = authored.api;
18546
18735
  const roles = RolesSchema.safeParse(loadManifest(recordedAsPath).roles);
18547
18736
  if (roles.success) composition = roles.data;
@@ -18575,7 +18764,7 @@ async function runGenerateRecorded(opts) {
18575
18764
  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
18765
  }
18577
18766
  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"))
18767
+ (c) => !existsSync45(path55.join(task.set, c.rep, "get_screenshot.json")) || !existsSync45(path55.join(task.set, c.rep, "get_metadata.json")) || !existsSync45(path55.join(task.set, c.rep, "get_design_context.json"))
18579
18768
  );
18580
18769
  if (missing.length > 0) {
18581
18770
  fail(opts, ExitCode.RecordingIncomplete, {
@@ -18645,8 +18834,8 @@ async function runGenerateRecorded(opts) {
18645
18834
  ` : `${line}
18646
18835
  `);
18647
18836
  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)})
18837
+ emitData(opts, { dryRun: true, task: taskName, model: modelId, consent, wouldWrite: path55.join(outDirAbs, taskName) }, () => {
18838
+ process.stdout.write(`dry-run: nothing sent, nothing written (would write ${path55.join(outDirAbs, taskName)})
18650
18839
  `);
18651
18840
  });
18652
18841
  return;
@@ -18669,10 +18858,10 @@ async function runGenerateRecorded(opts) {
18669
18858
  });
18670
18859
  }
18671
18860
  }
18672
- const bundleDir = path54.join(outDirAbs, taskName);
18673
- if (existsSync44(path54.join(bundleDir, "component.json"))) {
18861
+ const bundleDir = path55.join(outDirAbs, taskName);
18862
+ if (existsSync45(path55.join(bundleDir, "component.json"))) {
18674
18863
  try {
18675
- const prior = readBundleManifest(readFileSync41(path54.join(bundleDir, "component.json"), "utf8")).manifest;
18864
+ const prior = readBundleManifest(readFileSync42(path55.join(bundleDir, "component.json"), "utf8")).manifest;
18676
18865
  if (prior !== void 0 && prior.provenance.recordingSet.hash !== recordingSetHash(task.set, task.configs)) {
18677
18866
  fail(opts, ExitCode.InputValidation, {
18678
18867
  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 +20154,7 @@ function buildProgram() {
19965
20154
  ...local["revoke"] !== void 0 ? { revoke: local["revoke"] } : {}
19966
20155
  });
19967
20156
  });
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) => {
20157
+ 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
20158
  const flags = globalFlags(cmd.parent);
19970
20159
  const local = cmd.opts();
19971
20160
  const { runPublish: runPublish2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
@@ -19974,8 +20163,8 @@ function buildProgram() {
19974
20163
  bundleDir,
19975
20164
  ...local["to"] !== void 0 ? { to: local["to"] } : {},
19976
20165
  ...local["name"] !== void 0 ? { name: local["name"] } : {},
19977
- ...local["acceptTerms"] !== void 0 ? { acceptTerms: local["acceptTerms"] } : {},
19978
- ...local["confirmPublish"] !== void 0 ? { confirmPublish: local["confirmPublish"] } : {}
20166
+ ...local["approveStart"] !== void 0 ? { approveStart: local["approveStart"] } : {},
20167
+ ...local["approveWait"] !== void 0 ? { approveWait: local["approveWait"] } : {}
19979
20168
  });
19980
20169
  });
19981
20170
  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) => {