@wix/himalaya-cli 0.1069.0 → 0.1071.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -3957,8 +3957,8 @@ function committedProvisioningPath(name) {
3957
3957
  function committedShellFor(path2) {
3958
3958
  const normalized = path2.replace(/\\/g, "/");
3959
3959
  return SHELL_NAMES2.find((name) => {
3960
- const relative12 = committedProvisioningPath(name);
3961
- return normalized === relative12 || normalized.endsWith(`/${relative12}`);
3960
+ const relative13 = committedProvisioningPath(name);
3961
+ return normalized === relative13 || normalized.endsWith(`/${relative13}`);
3962
3962
  });
3963
3963
  }
3964
3964
  function isBrandable(repoRoot2, name) {
@@ -17420,15 +17420,20 @@ var init_gen_native_config = __esm({
17420
17420
  // src/progress.ts
17421
17421
  var progress_exports = {};
17422
17422
  __export(progress_exports, {
17423
+ AGENT_WINS_WITHIN_MS: () => AGENT_WINS_WITHIN_MS,
17423
17424
  APPROVALS_FILE: () => APPROVALS_FILE,
17424
17425
  APPROVAL_GATES: () => APPROVAL_GATES,
17426
+ AUTO_MILESTONES: () => AUTO_MILESTONES,
17427
+ CLI_AUTHOR: () => CLI_AUTHOR,
17425
17428
  HISTORY_DIR: () => HISTORY_DIR,
17426
17429
  JOURNEY_STEPS: () => JOURNEY_STEPS,
17427
17430
  MAX_IMAGE_BYTES: () => MAX_IMAGE_BYTES,
17428
17431
  MAX_MESSAGE_CHARS: () => MAX_MESSAGE_CHARS,
17432
+ MILESTONE_STEP: () => MILESTONE_STEP,
17429
17433
  PROGRESS_FILE: () => PROGRESS_FILE,
17430
17434
  ProgressError: () => ProgressError,
17431
17435
  UnsafeHimiDirError: () => UnsafeHimiDirError,
17436
+ agentCovers: () => agentCovers,
17432
17437
  appLabel: () => appLabel,
17433
17438
  appendApproval: () => appendApproval,
17434
17439
  appendProgress: () => appendProgress,
@@ -17438,7 +17443,10 @@ __export(progress_exports, {
17438
17443
  carryPlanningJournal: () => carryPlanningJournal,
17439
17444
  formatBrief: () => formatBrief,
17440
17445
  imageExtension: () => imageExtension,
17446
+ isCliEntry: () => isCliEntry,
17447
+ journalMilestone: () => journalMilestone,
17441
17448
  journalPath: () => journalPath,
17449
+ looksSecret: () => looksSecret,
17442
17450
  notifyDesktop: () => notifyDesktop,
17443
17451
  openApprovals: () => openApprovals,
17444
17452
  parseApprovalRef: () => parseApprovalRef,
@@ -17448,13 +17456,26 @@ __export(progress_exports, {
17448
17456
  readProgress: () => readProgress,
17449
17457
  realDirIn: () => realDirIn,
17450
17458
  summarizeProgress: () => summarizeProgress,
17459
+ visibleEntries: () => visibleEntries,
17451
17460
  writeFileAtomic: () => writeFileAtomic
17452
17461
  });
17453
17462
  import { spawn as spawn3 } from "node:child_process";
17454
17463
  import { createHash as createHash8 } from "node:crypto";
17455
17464
  import { randomUUID } from "node:crypto";
17456
17465
  import { appendFileSync, closeSync, existsSync as existsSync27, lstatSync as lstatSync5, mkdirSync as mkdirSync13, openSync, readdirSync as readdirSync15, readFileSync as readFileSync27, readSync, renameSync as renameSync3, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync12 } from "node:fs";
17457
- import { join as join30, relative as relative5, resolve as resolve19, sep as sep6 } from "node:path";
17466
+ import { isAbsolute as isAbsolute3, join as join30, relative as relative5, resolve as resolve19, sep as sep6 } from "node:path";
17467
+ function agentCovers(agent, cli) {
17468
+ if (isCliEntry(agent)) return false;
17469
+ const agentShas = shasOf(agent.images);
17470
+ if (!agentShas.size) return false;
17471
+ const gap = Math.abs(Date.parse(agent.ts) - Date.parse(cli.ts));
17472
+ if (!(gap <= AGENT_WINS_WITHIN_MS)) return false;
17473
+ if (cli.step && agent.step === cli.step) return true;
17474
+ return [...shasOf(cli.images)].some((sha) => agentShas.has(sha));
17475
+ }
17476
+ function visibleEntries(entries) {
17477
+ return entries.filter((e) => !isCliEntry(e) || !entries.some((a) => agentCovers(a, e)));
17478
+ }
17458
17479
  function parseStep(raw) {
17459
17480
  const v = raw.trim().toLowerCase();
17460
17481
  if (/^[1-8]$/.test(v)) return JOURNEY_STEPS[Number(v) - 1];
@@ -17543,12 +17564,13 @@ function appendProgress(dir, input) {
17543
17564
  `a postcard is a line or two (${message.length} chars; the cap is ${MAX_MESSAGE_CHARS}) \u2014 put the detail in your reply and journal the headline`
17544
17565
  );
17545
17566
  }
17546
- const images = (input.images ?? []).map((src) => archiveImage(dir, src));
17567
+ const images = (input.images ?? []).map((src) => archiveImage(dir, src)).map((img) => input.auto ? { ...img, source: packageRelative(dir, img.source) } : img);
17547
17568
  const entry = {
17548
17569
  ts: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
17549
17570
  message,
17550
17571
  ...input.step ? { step: input.step, stepNumber: JOURNEY_STEPS.indexOf(input.step) + 1 } : {},
17551
- ...images.length ? { images } : {}
17572
+ ...images.length ? { images } : {},
17573
+ ...input.auto ? { by: CLI_AUTHOR, milestone: input.auto.milestone, ...input.auto.ref ? { ref: input.auto.ref } : {} } : {}
17552
17574
  };
17553
17575
  if (!realDirIn(dir, ".himi")) throw new ProgressError("unsafe_himi_dir", new UnsafeHimiDirError(".himi").message);
17554
17576
  const file = join30(dir, PROGRESS_FILE);
@@ -17557,6 +17579,10 @@ function appendProgress(dir, input) {
17557
17579
  `);
17558
17580
  return entry;
17559
17581
  }
17582
+ function packageRelative(dir, p) {
17583
+ const rel = relative5(dir, resolve19(p));
17584
+ return rel && !rel.startsWith("..") && !isAbsolute3(rel) ? toPosix(rel) : p;
17585
+ }
17560
17586
  function endsWithNewline(file) {
17561
17587
  if (!existsSync27(file)) return true;
17562
17588
  const size = statSync5(file).size;
@@ -17575,7 +17601,7 @@ function isEntry(v) {
17575
17601
  const e = v;
17576
17602
  return typeof e.ts === "string" && typeof e.message === "string";
17577
17603
  }
17578
- function readProgress(dir) {
17604
+ function readProgress(dir, opts = {}) {
17579
17605
  const file = join30(dir, PROGRESS_FILE);
17580
17606
  if (!existsSync27(file)) return { entries: [], skipped: 0 };
17581
17607
  const entries = [];
@@ -17590,7 +17616,34 @@ function readProgress(dir) {
17590
17616
  skipped++;
17591
17617
  }
17592
17618
  }
17593
- return { entries, skipped };
17619
+ return { entries: opts.all ? entries : visibleEntries(entries), skipped };
17620
+ }
17621
+ function journalMilestone(dir, input) {
17622
+ try {
17623
+ if (looksSecret(input.message) || input.ref !== void 0 && (!REF_SHAPE.test(input.ref) || looksSecret(input.ref))) {
17624
+ return { journaled: false, reason: "unsafe" };
17625
+ }
17626
+ const { entries } = readProgress(dir, { all: true });
17627
+ const ref = input.ref ?? null;
17628
+ if (entries.some((e) => isCliEntry(e) && e.milestone === input.milestone && (e.ref ?? null) === ref)) {
17629
+ return { journaled: false, reason: "already-journaled" };
17630
+ }
17631
+ const now = input.now ?? /* @__PURE__ */ new Date();
17632
+ const step = MILESTONE_STEP[input.milestone];
17633
+ const abs = input.image ? locateImage(dir, input.image) : null;
17634
+ const images = abs ? [{ sha256: createHash8("sha256").update(readFileSync27(abs)).digest("hex") }] : [];
17635
+ if (entries.some((e) => agentCovers(e, { ts: now.toISOString(), step, images }))) return { journaled: false, reason: "agent-journaled" };
17636
+ const entry = appendProgress(dir, {
17637
+ message: input.message,
17638
+ step,
17639
+ ...abs ? { images: [abs] } : {},
17640
+ now,
17641
+ auto: { milestone: input.milestone, ...input.ref ? { ref: input.ref } : {} }
17642
+ });
17643
+ return { journaled: true, entry };
17644
+ } catch {
17645
+ return { journaled: false, reason: "failed" };
17646
+ }
17594
17647
  }
17595
17648
  function parseGate(raw) {
17596
17649
  const v = raw.trim().toLowerCase();
@@ -17721,7 +17774,7 @@ function formatBrief(input) {
17721
17774
  if (summary.last) lines.push(`Last postcard (${minute(summary.last.ts)}): ${summary.last.message}`);
17722
17775
  const pastPlan = (summary.current?.stepNumber ?? 0) > JOURNEY_STEPS.indexOf("plan") + 1;
17723
17776
  lines.push(
17724
- !summary.current ? "Resume from the journal: `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
17777
+ !summary.current ? "No journey step recorded yet: a new app starts with discovery (step 1); one whose SPEC.md the customer approved continues from its current state. `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
17725
17778
  );
17726
17779
  }
17727
17780
  if (open.length) {
@@ -17735,7 +17788,7 @@ function formatBrief(input) {
17735
17788
  }
17736
17789
  function summarizeProgress(entries) {
17737
17790
  if (!entries.length) return { count: 0 };
17738
- const stepped = [...entries].reverse().find((e) => e.step && JOURNEY_STEPS.includes(e.step));
17791
+ const stepped = [...entries].reverse().find((e) => !isCliEntry(e) && e.step && JOURNEY_STEPS.includes(e.step));
17739
17792
  const first = entries[0];
17740
17793
  const last = entries[entries.length - 1];
17741
17794
  const elapsed = Date.parse(last.ts) - Date.parse(first.ts);
@@ -17796,10 +17849,16 @@ function journalPath(dir) {
17796
17849
  const rel = relative5(process.cwd(), abs);
17797
17850
  return rel && !rel.startsWith("..") ? rel : abs;
17798
17851
  }
17799
- var JOURNEY_STEPS, PROGRESS_FILE, HISTORY_DIR, APPROVALS_FILE, APPROVAL_GATES, MAX_MESSAGE_CHARS, MAX_IMAGE_BYTES, ProgressError, toPosix, UnsafeHimiDirError, gateLabel, minute, NOTIFY_TIMEOUT_MS;
17852
+ var JOURNEY_STEPS, CLI_AUTHOR, AUTO_MILESTONES, MILESTONE_STEP, AGENT_WINS_WITHIN_MS, isCliEntry, shasOf, PROGRESS_FILE, HISTORY_DIR, APPROVALS_FILE, APPROVAL_GATES, MAX_MESSAGE_CHARS, MAX_IMAGE_BYTES, ProgressError, toPosix, UnsafeHimiDirError, SECRET_SHAPES, looksSecret, REF_SHAPE, gateLabel, minute, NOTIFY_TIMEOUT_MS;
17800
17853
  var init_progress = __esm({
17801
17854
  "src/progress.ts"() {
17802
17855
  JOURNEY_STEPS = ["discover", "classify", "plan", "implement", "brand", "validate", "review", "download"];
17856
+ CLI_AUTHOR = "himi";
17857
+ AUTO_MILESTONES = ["first-look", "test", "draft"];
17858
+ MILESTONE_STEP = { "first-look": "plan", test: "validate", draft: "review" };
17859
+ AGENT_WINS_WITHIN_MS = 5 * 60 * 1e3;
17860
+ isCliEntry = (e) => e.by === CLI_AUTHOR;
17861
+ shasOf = (images) => new Set(Array.isArray(images) ? images.flatMap((i) => i && typeof i === "object" && typeof i.sha256 === "string" ? [i.sha256] : []) : []);
17803
17862
  PROGRESS_FILE = join30(".himi", "progress.jsonl");
17804
17863
  HISTORY_DIR = join30(".himi", "history");
17805
17864
  APPROVALS_FILE = join30(".himi", "approvals.jsonl");
@@ -17826,6 +17885,17 @@ var init_progress = __esm({
17826
17885
  super(`${rel} is not a real directory inside the content package (a symlink?) \u2014 refusing to write through it`);
17827
17886
  }
17828
17887
  };
17888
+ SECRET_SHAPES = [
17889
+ /:\/\//,
17890
+ // any URL: a preview link carries its bearer in the query
17891
+ /[?&#;](?:pvt|h|token|code|handover|key|secret|access_token)=/i,
17892
+ // `?h=` is the handover code
17893
+ /\bpvt[_=-]/i,
17894
+ /\beyJ[A-Za-z0-9_-]{6,}/
17895
+ // a JWT (a control token), whose header always starts `{"`
17896
+ ];
17897
+ looksSecret = (s) => SECRET_SHAPES.some((re) => re.test(s));
17898
+ REF_SHAPE = /^[A-Za-z0-9._:-]{1,128}$/;
17829
17899
  gateLabel = (gate) => APPROVAL_GATES.find((g) => g.id === gate)?.label ?? gate;
17830
17900
  minute = (iso) => `${iso.slice(0, 16).replace("T", " ")} UTC`;
17831
17901
  NOTIFY_TIMEOUT_MS = 3e3;
@@ -17894,9 +17964,9 @@ ${after}`;
17894
17964
  function blockVersion(text5) {
17895
17965
  return STAMP_RE.exec(text5)?.[1] ?? null;
17896
17966
  }
17897
- function agentsMdBody(appName) {
17967
+ function agentsMdBody(appName2) {
17898
17968
  const gates = APPROVAL_GATES.map((g) => `- \`${g.id}\` \u2014 ${g.label}`).join("\n");
17899
- return `# ${appName ? `${appName} \u2014 a Himalaya app` : "A Himalaya app"}
17969
+ return `# ${appName2 ? `${appName2} \u2014 a Himalaya app` : "A Himalaya app"}
17900
17970
 
17901
17971
  This folder is a Himalaya content package: a native iOS / Android / web app whose screens are data
17902
17972
  (JSON descriptors) plus small JavaScript workers, built and shipped with the \`himi\` CLI
@@ -18957,9 +19027,9 @@ var init_store2 = __esm({
18957
19027
  Store = class {
18958
19028
  _db;
18959
19029
  dbPath;
18960
- constructor(appName) {
19030
+ constructor(appName2) {
18961
19031
  const dataRoot = process.env.HIMALAYA_DEV_STORE_DIR ? resolve23(process.env.HIMALAYA_DEV_STORE_DIR) : resolve23(ROOT3, "core/dev-server/data");
18962
- this.dbPath = resolve23(dataRoot, `${appName}.sqlite`);
19032
+ this.dbPath = resolve23(dataRoot, `${appName2}.sqlite`);
18963
19033
  }
18964
19034
  /** The node:sqlite handle — opened, bootstrapped, and seeded on first access. */
18965
19035
  get db() {
@@ -21684,8 +21754,8 @@ var init_net_mocks_rollover = __esm({
21684
21754
  });
21685
21755
 
21686
21756
  // ../../core/dev-server/src/player-proxy.ts
21687
- function isPlayerHost(appName) {
21688
- return appName === PLAYER_APP_ID || process.env.HIMALAYA_DEV_MULTI_APP === "1";
21757
+ function isPlayerHost(appName2) {
21758
+ return appName2 === PLAYER_APP_ID || process.env.HIMALAYA_DEV_MULTI_APP === "1";
21689
21759
  }
21690
21760
  function resolveServeBase() {
21691
21761
  const override = process.env.HIMALAYA_PLAYER_SERVE_URL;
@@ -33194,8 +33264,8 @@ function clampRule(rule, denyPrefixes, maxTTL) {
33194
33264
  ttl: Math.min(rule.ttl, maxTTL)
33195
33265
  };
33196
33266
  }
33197
- function resolveWixCachePolicy(appName) {
33198
- const appRules = CACHE_OVERRIDES[appName] ?? [];
33267
+ function resolveWixCachePolicy(appName2) {
33268
+ const appRules = CACHE_OVERRIDES[appName2] ?? [];
33199
33269
  const rules = appRules.map((r) => clampRule(r, PLATFORM_DENY_PREFIXES, PLATFORM_MAX_TTL));
33200
33270
  return { rules, defaultCacheable: false };
33201
33271
  }
@@ -33313,7 +33383,7 @@ function expandNavBundle(ref, ctx) {
33313
33383
  };
33314
33384
  }
33315
33385
  function deriveNavigationConfig(app, bundleCtx) {
33316
- const appName = app.name;
33386
+ const appName2 = app.name;
33317
33387
  const concrete = app.screenIds.map((screenId) => ({
33318
33388
  screenId,
33319
33389
  isPublic: true,
@@ -33326,7 +33396,7 @@ function deriveNavigationConfig(app, bundleCtx) {
33326
33396
  const keys = (pat.match(/\{([^}]+)\}/g) ?? []).map((m) => m.slice(1, -1));
33327
33397
  return { screenId: base, isPublic: true, paramKeys: keys, parentScreenId: "", tabId: "" };
33328
33398
  });
33329
- const ov = NAV_OVERRIDES[appName];
33399
+ const ov = NAV_OVERRIDES[appName2];
33330
33400
  if (ov?.parents) {
33331
33401
  for (const s of [...concrete, ...templated]) {
33332
33402
  if (ov.parents[s.screenId]) s.parentScreenId = ov.parents[s.screenId];
@@ -33337,7 +33407,7 @@ function deriveNavigationConfig(app, bundleCtx) {
33337
33407
  scheme: app.urlScheme,
33338
33408
  deepLinkPatterns: app.deepLinkPatterns ?? ov?.deepLinkPatterns ?? [],
33339
33409
  linkableScreens,
33340
- wixCachePolicy: resolveWixCachePolicy(appName),
33410
+ wixCachePolicy: resolveWixCachePolicy(appName2),
33341
33411
  // Fail-open baseline; tightened OTA via the admin app-config override. The
33342
33412
  // DESTINATION(s) are derived generically from the app's config (no app-name
33343
33413
  // special-casing) so the app chooses where telemetry goes via OTA:
@@ -33415,12 +33485,12 @@ function buildAppConfigDocument(req, meta, override, bundleCtx) {
33415
33485
  return { config, body, etag, locale };
33416
33486
  }
33417
33487
  function handleAppConfigRequest(req, res, meta, override, bundleCtx) {
33418
- const appName = decodeURIComponent(
33488
+ const appName2 = decodeURIComponent(
33419
33489
  (req.url ?? "").replace(/^\/v1\/app-config\//, "").split("?")[0]
33420
33490
  );
33421
- if (appName !== meta.name) {
33491
+ if (appName2 !== meta.name) {
33422
33492
  res.writeHead(404, { "Content-Type": "application/json" });
33423
- res.end(JSON.stringify({ error: "unknown_app", appName }));
33493
+ res.end(JSON.stringify({ error: "unknown_app", appName: appName2 }));
33424
33494
  return;
33425
33495
  }
33426
33496
  const { body, etag, locale } = buildAppConfigDocument(req, meta, override, bundleCtx);
@@ -36377,7 +36447,7 @@ async function handleToggleEval(req, res, state) {
36377
36447
  for (const id of requested) out[id] = state.toggles[id] ?? false;
36378
36448
  sendJson(res, 200, out);
36379
36449
  }
36380
- function evalContextFromReq(req, appName) {
36450
+ function evalContextFromReq(req, appName2) {
36381
36451
  const h = (k) => {
36382
36452
  const v = req.headers[k];
36383
36453
  return typeof v === "string" ? v : void 0;
@@ -36385,7 +36455,7 @@ function evalContextFromReq(req, appName) {
36385
36455
  return {
36386
36456
  sessionId: h("x-himalaya-session") ?? "dev-session",
36387
36457
  userId: h("x-himalaya-user"),
36388
- appId: appName,
36458
+ appId: appName2,
36389
36459
  platform: h("x-himalaya-platform"),
36390
36460
  appVersion: h("x-himalaya-app-version"),
36391
36461
  locale: typeof req.headers["accept-language"] === "string" ? req.headers["accept-language"].split(",")[0] : void 0
@@ -37533,8 +37603,8 @@ async function handleActionHandlerBundleGet(res, app) {
37533
37603
  }
37534
37604
  async function serveStatic(req, res, root, prefix) {
37535
37605
  const requested = decodeURIComponent((req.url ?? "").split("?")[0]);
37536
- const relative12 = requested.replace(prefix, "").replace(/^\//, "");
37537
- const safe = normalize3(relative12).replace(/^(\.\.[\/\\])+/, "");
37606
+ const relative13 = requested.replace(prefix, "").replace(/^\//, "");
37607
+ const safe = normalize3(relative13).replace(/^(\.\.[\/\\])+/, "");
37538
37608
  const fullPath = resolve31(root, safe);
37539
37609
  if (!fullPath.startsWith(root)) {
37540
37610
  return send(res, 403, "forbidden");
@@ -37698,19 +37768,19 @@ function handleAssetLinks(res, state) {
37698
37768
  });
37699
37769
  }
37700
37770
  function createDevServer(opts = {}) {
37701
- const appName = opts.app ?? "sample";
37771
+ const appName2 = opts.app ?? "sample";
37702
37772
  if (!opts.preloaded) {
37703
- throw new Error(`createDevServer requires a preloaded module for "${appName}". Preload via loadApp("${appName}").`);
37773
+ throw new Error(`createDevServer requires a preloaded module for "${appName2}". Preload via loadApp("${appName2}").`);
37704
37774
  }
37705
37775
  checkRegistry(opts.preloaded.module.actions);
37706
37776
  const port = opts.port ?? Number(process.env.HIMALAYA_DEV_PORT ?? 8080);
37707
37777
  const hotReloadEnabled = opts.enableHotReload ?? process.env.HIMALAYA_DEV_HOT_RELOAD === "1";
37708
37778
  const state = {
37709
- appName,
37779
+ appName: appName2,
37710
37780
  // Store construction is cheap now; its node:sqlite DB is opened LAZILY on first query
37711
37781
  // (store.ts), so the serve authoring plane can boot createDevServer on a Whirlwind guest
37712
37782
  // that has no node:sqlite — the stateless build/render path never queries the demo store.
37713
- store: new Store(appName),
37783
+ store: new Store(appName2),
37714
37784
  toggles: {
37715
37785
  pinned_notes_v2: false,
37716
37786
  weekly_review_v1: true,
@@ -37731,7 +37801,7 @@ function createDevServer(opts = {}) {
37731
37801
  localOverrides: /* @__PURE__ */ new Map(),
37732
37802
  localOverrideHistory: /* @__PURE__ */ new Map(),
37733
37803
  app: opts.preloaded,
37734
- moduleStore: opts.preloaded ? opts.preloaded.module.createStore(appName) : void 0,
37804
+ moduleStore: opts.preloaded ? opts.preloaded.module.createStore(appName2) : void 0,
37735
37805
  telemetryEvents: [],
37736
37806
  adminUrl: process.env.HIMALAYA_ADMIN_URL,
37737
37807
  wix: createWixGatewayState(),
@@ -42298,7 +42368,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
42298
42368
  ...collect2?.lint ? { lint, workers } : {},
42299
42369
  ...collect2?.render ? { renders } : {},
42300
42370
  ...collect2?.descriptors ? { descriptors: baked, linkableScreenIds: [...linkable] } : {},
42301
- ...collect2?.descriptors && typeof preloaded.meta.homeScreenId === "string" && preloaded.meta.homeScreenId ? { homeScreenId: preloaded.meta.homeScreenId } : {},
42371
+ ...typeof preloaded.meta.homeScreenId === "string" && preloaded.meta.homeScreenId ? { homeScreenId: preloaded.meta.homeScreenId } : {},
42302
42372
  ...renderTokens !== void 0 ? { tokens: renderTokens } : {},
42303
42373
  ...preloaded.meta.surfaces ? { surfaceNav: preloaded.meta.surfaces } : {},
42304
42374
  auth: (() => {
@@ -42595,7 +42665,7 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
42595
42665
  .sw{display:flex;gap:6px}.sw span{width:22px;height:22px;border-radius:50%;border:1px solid var(--line)}
42596
42666
  .nav{display:flex;flex-wrap:wrap;gap:6px}.nav span{font-size:12px;padding:3px 8px;border-radius:6px;background:var(--bg);border:1px solid var(--line)}
42597
42667
  .log{list-style:none;margin:0;padding:0}.log li{display:flex;gap:10px;padding:10px 0;border-top:1px solid var(--line)}.log li:first-child{border-top:0}
42598
- .log .t{color:var(--faint);font-size:12px;min-width:64px;font-variant-numeric:tabular-nums}.log .s{font-size:11px;color:var(--muted);text-transform:capitalize}.log .imgs{display:flex;gap:6px;margin-top:6px}.log .imgs img{height:72px;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
42668
+ .log .t{color:var(--faint);font-size:12px;min-width:64px;font-variant-numeric:tabular-nums}.log .s{font-size:11px;color:var(--muted);text-transform:capitalize}.log .by{font-size:11px;color:var(--faint)}.log .imgs{display:flex;gap:6px;margin-top:6px}.log .imgs img{height:72px;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
42599
42669
  .empty{color:var(--faint);font-size:13px;padding:8px 0}
42600
42670
  #lb{position:fixed;inset:0;background:rgba(0,0,0,.72);display:none;align-items:center;justify-content:center;z-index:9;cursor:zoom-out}#lb img{max-height:92vh;max-width:92vw;border-radius:14px;background:#fff}
42601
42671
  .enter{animation:enter .7s cubic-bezier(.2,.8,.2,1) both}@keyframes enter{from{opacity:0;transform:translateY(10px) scale(.94);filter:grayscale(1)}to{opacity:1;transform:none;filter:none}}
@@ -42724,7 +42794,7 @@ function render(s){
42724
42794
  s.steps.forEach(function(name,i){var d=el("div","step"+(i+1<n?" done":i+1===n?" cur":""));d.appendChild(el("div","bar"));d.appendChild(el("div","lbl",(i+1)+". "+name));steps.appendChild(d)});
42725
42795
  var last=s.journal[0];
42726
42796
  $("nowmsg").textContent=last?last.message:"Waiting for the first postcard.";
42727
- $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+ago(last.ts)):"Now";
42797
+ $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+(last.by?"noted by "+last.by+" \xB7 ":"")+ago(last.ts)):"Now";
42728
42798
  var film=$("film");clear(film);
42729
42799
  if(!rewinding){var n0=s.timelineFrames||0;$("scrub").hidden=n0<2;$("slider").max=String(Math.max(1,n0-1));$("slider").value=$("slider").max}
42730
42800
  if(rewinding){paintStill(s)}else{$("still").hidden=true;$("focus").hidden=false;var focus=$("focus");
@@ -42756,7 +42826,7 @@ function render(s){
42756
42826
  if(s.app.palette.length){var sw=el("div","sw");s.app.palette.forEach(function(c){var d=el("span");d.style.background=c.hex;d.title=c.name+" "+c.hex;sw.appendChild(d)});brand.appendChild(sw)}
42757
42827
  var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length&&!s.map?"":"none";drawMap(s.map);s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
42758
42828
  var log=$("log");clear(log);if(!s.journal.length)log.appendChild(el("li","empty","Postcards the agent journals appear here."));
42759
- s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));
42829
+ s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));if(j.by)body.appendChild(el("div","by","noted by "+j.by));
42760
42830
  if(j.images.length){var row=el("div","imgs");j.images.forEach(function(u){row.appendChild(img(u))});body.appendChild(row)}li.appendChild(body);log.appendChild(li)});
42761
42831
  paintPop(s);
42762
42832
  }
@@ -43529,9 +43599,10 @@ function buildView(dir, memory, now) {
43529
43599
  journal: entries.slice(-MAX_JOURNAL).reverse().map((e) => ({
43530
43600
  ts: e.ts,
43531
43601
  message: e.message,
43532
- step: e.step ?? null,
43533
- stepNumber: e.stepNumber ?? null,
43534
- images: (e.images ?? []).map((i) => historyUrl(basename10(i.file)))
43602
+ step: isCliEntry(e) ? null : e.step ?? null,
43603
+ stepNumber: isCliEntry(e) ? null : e.stepNumber ?? null,
43604
+ images: (e.images ?? []).map((i) => historyUrl(basename10(i.file))),
43605
+ by: isCliEntry(e) ? CLI_AUTHOR : null
43535
43606
  })),
43536
43607
  screens,
43537
43608
  focus,
@@ -44779,6 +44850,109 @@ var init_notify_setup = __esm({
44779
44850
  }
44780
44851
  });
44781
44852
 
44853
+ // src/milestones.ts
44854
+ var milestones_exports = {};
44855
+ __export(milestones_exports, {
44856
+ afterDraftPush: () => afterDraftPush,
44857
+ afterRenderPng: () => afterRenderPng,
44858
+ afterTestRun: () => afterTestRun,
44859
+ displayPath: () => displayPath,
44860
+ homeScreenIdOf: () => homeScreenIdOf,
44861
+ openBoardForSession: () => openBoardForSession,
44862
+ progressCommand: () => progressCommand,
44863
+ sessionSteps: () => sessionSteps,
44864
+ testPostcardMessage: () => testPostcardMessage
44865
+ });
44866
+ import { existsSync as existsSync44, readFileSync as readFileSync48 } from "node:fs";
44867
+ import { basename as basename12, isAbsolute as isAbsolute4, join as join42, relative as relative8 } from "node:path";
44868
+ function homeScreenIdOf(dir) {
44869
+ let src;
44870
+ try {
44871
+ src = readFileSync48(join42(dir, "dev", "index.ts"), "utf8");
44872
+ } catch {
44873
+ return null;
44874
+ }
44875
+ const m = /^[ \t]*homeScreenId[ \t]*:[ \t]*(?:"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)')/m.exec(src);
44876
+ const id = m ? m[1] ?? m[2] : void 0;
44877
+ return id && /^[A-Za-z0-9_.\-/{}]+$/.test(id) ? id : null;
44878
+ }
44879
+ function displayPath(p, cwd = process.cwd()) {
44880
+ const rel = relative8(cwd, p);
44881
+ return rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : p;
44882
+ }
44883
+ function progressCommand(opts) {
44884
+ return `himi progress "<${opts.what}>"${opts.step ? ` --step ${opts.step}` : ""}${opts.image ? ` --image ${shellQuote(opts.image)}` : ""}${contentArg(opts.content)}`;
44885
+ }
44886
+ function sessionSteps(opts) {
44887
+ const content = displayPath(opts.dir, opts.cwd);
44888
+ const home = opts.home ?? "<homeScreenId>";
44889
+ const shot = `.himi/shots/${home}.png`;
44890
+ return [
44891
+ `himi run --watch${contentArg(content)} & \u2014 now, in this session: keep it running in the background. The live preview repaints on every save and serves the build board at <its url>/__himi/studio/`,
44892
+ `himi render ${shellQuote(home)} --png --device iphone${contentArg(content)} \u2014 the first picture, written to ${shot}${opts.home ? "" : " (the id is the dev module's homeScreenId)"}; the CLI journals it as the first look`,
44893
+ `${progressCommand({ what: "where the build starts", step: "plan", image: shot, content })} \u2014 your postcard for the owner, with that picture`
44894
+ ];
44895
+ }
44896
+ async function openBoardForSession(dir, cwd = process.cwd()) {
44897
+ try {
44898
+ const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
44899
+ const { page, url } = openStudio2(dir);
44900
+ const rel = relative8(cwd, page);
44901
+ return { page: rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : page, url, opened: false };
44902
+ } catch (e) {
44903
+ return { opened: false, error: `the build board was not created: ${e.message} \u2014 \`himi studio --no-open\` in the app retries it` };
44904
+ }
44905
+ }
44906
+ function journal(dir, milestone, message, extra) {
44907
+ const r = journalMilestone(dir, { milestone, message, ...extra });
44908
+ return r.journaled ? { journaled: true, milestone, message } : void 0;
44909
+ }
44910
+ function afterRenderPng(dir, input) {
44911
+ const postcard = input.renderable && input.homeScreenId !== null && input.screenId === input.homeScreenId ? journal(dir, "first-look", `First look at ${appName(dir)}: the home screen, where the build starts.`, { image: input.pngFile, ...input.now ? { now: input.now } : {} }) : void 0;
44912
+ return {
44913
+ ...postcard ? { postcard } : {},
44914
+ next: input.renderable ? [progressCommand({ what: "what changed", image: displayPath(input.pngFile), content: input.content })] : []
44915
+ };
44916
+ }
44917
+ function testPostcardMessage(s) {
44918
+ const links = s.broken === void 0 ? "" : `, ${plural(s.broken, "broken link")}`;
44919
+ const unreached = s.unreached ? `, ${plural(s.unreached, "screen")} nothing links to` : "";
44920
+ return `Test crawl: ${plural(s.screens, "screen")} and ${plural(s.actions, "action")} exercised${links}${unreached} \u2014 ${s.errors ? `${plural(s.errors, "error")} to fix` : "no errors"}.`;
44921
+ }
44922
+ function afterTestRun(dir, input) {
44923
+ const postcard = journal(dir, "test", testPostcardMessage(input), {
44924
+ ref: input.runId,
44925
+ ...input.homeShot && existsSync44(input.homeShot) ? { image: input.homeShot } : {},
44926
+ ...input.now ? { now: input.now } : {}
44927
+ });
44928
+ return {
44929
+ ...postcard ? { postcard } : {},
44930
+ next: [
44931
+ input.boardOpen ? "the build board's App map now shows every screen this crawl reached, how they link, and any broken link \u2014 .himi/studio/index.html, or /__himi/studio/ under `himi run --watch`" : "`himi studio` opens the build board, whose App map draws every screen this crawl reached, how they link, and any broken link"
44932
+ ]
44933
+ };
44934
+ }
44935
+ function afterDraftPush(dir, input) {
44936
+ const postcard = journal(dir, "draft", `Draft pushed: release ${input.releaseId.slice(0, 12)} of ${input.app} \u2014 owner-private, not live.`, {
44937
+ ref: input.releaseId,
44938
+ ...input.now ? { now: input.now } : {}
44939
+ });
44940
+ return {
44941
+ ...postcard ? { postcard } : {},
44942
+ next: `${progressCommand({ what: "what the owner can review in this draft", step: "review", content: input.content })} \u2014 your postcard for the draft; share the preview link in chat, never in the journal`
44943
+ };
44944
+ }
44945
+ var contentArg, plural, appName;
44946
+ var init_milestones = __esm({
44947
+ "src/milestones.ts"() {
44948
+ init_progress();
44949
+ init_shell_quote();
44950
+ contentArg = (content) => content ? ` --content ${shellQuote(content)}` : "";
44951
+ plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
44952
+ appName = (dir) => appLabel(dir) ?? basename12(dir);
44953
+ }
44954
+ });
44955
+
44782
44956
  // src/site-analyze/types.ts
44783
44957
  function isRedirectUri(value) {
44784
44958
  return /^[a-z][a-z0-9+.-]*:\/\/\S*$/i.test(value);
@@ -45216,8 +45390,8 @@ __export(template_trim_exports, {
45216
45390
  routeMatcher: () => routeMatcher,
45217
45391
  runTemplateTrim: () => runTemplateTrim
45218
45392
  });
45219
- import { cpSync, existsSync as existsSync44, mkdirSync as mkdirSync20, readdirSync as readdirSync24, readFileSync as readFileSync48, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
45220
- import { dirname as dirname25, join as join42 } from "node:path";
45393
+ import { cpSync, existsSync as existsSync45, mkdirSync as mkdirSync20, readdirSync as readdirSync24, readFileSync as readFileSync49, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
45394
+ import { dirname as dirname25, join as join43 } from "node:path";
45221
45395
  function routeMatcher(pattern) {
45222
45396
  const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
45223
45397
  if (detail) {
@@ -45911,20 +46085,20 @@ function homeModulesPass(app, kept, edited) {
45911
46085
  return { text: patched.src, dropped: patched.droppedModules };
45912
46086
  }
45913
46087
  function listWorkers(dir) {
45914
- const root = join42(dir, "tier5-src");
45915
- if (!existsSync44(root)) return { dirs: [], files: {} };
45916
- const dirs = readdirSync24(root).filter((n) => statSync10(join42(root, n)).isDirectory()).sort();
46088
+ const root = join43(dir, "tier5-src");
46089
+ if (!existsSync45(root)) return { dirs: [], files: {} };
46090
+ const dirs = readdirSync24(root).filter((n) => statSync10(join43(root, n)).isDirectory()).sort();
45917
46091
  const files = {};
45918
46092
  const walk2 = (abs, rel) => {
45919
46093
  for (const name of readdirSync24(abs)) {
45920
46094
  if (name === "node_modules") continue;
45921
- const p = join42(abs, name);
46095
+ const p = join43(abs, name);
45922
46096
  const r = `${rel}/${name}`;
45923
46097
  if (statSync10(p).isDirectory()) walk2(p, r);
45924
- else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync48(p, "utf8");
46098
+ else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync49(p, "utf8");
45925
46099
  }
45926
46100
  };
45927
- for (const d of dirs) walk2(join42(root, d), `tier5-src/${d}`);
46101
+ for (const d of dirs) walk2(join43(root, d), `tier5-src/${d}`);
45928
46102
  return { dirs, files };
45929
46103
  }
45930
46104
  function csv(v) {
@@ -45947,16 +46121,16 @@ async function runTemplateTrim(req, deps) {
45947
46121
  links = req.links;
45948
46122
  }
45949
46123
  const dir = req.dir;
45950
- const devPath = join42(dir, "dev/index.ts");
45951
- const configPath = join42(dir, "config.ts");
45952
- if (!existsSync44(devPath) || !existsSync44(configPath)) {
46124
+ const devPath = join43(dir, "dev/index.ts");
46125
+ const configPath = join43(dir, "config.ts");
46126
+ if (!existsSync45(devPath) || !existsSync45(configPath)) {
45953
46127
  return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
45954
46128
  }
45955
46129
  if (isTemplateSourceTree(dir)) {
45956
46130
  return refuse3(new TrimRefusal(`${dir} is a template source tree (its identity is still __HIMI_APP_*__ sentinels) \u2014 trim the app \`himi init --template\` scaffolds from it, not the template itself`));
45957
46131
  }
45958
- const dev = readFileSync48(devPath, "utf8");
45959
- const config = readFileSync48(configPath, "utf8");
46132
+ const dev = readFileSync49(devPath, "utf8");
46133
+ const config = readFileSync49(configPath, "utf8");
45960
46134
  const workers = listWorkers(dir);
45961
46135
  let plan;
45962
46136
  try {
@@ -45966,23 +46140,23 @@ async function runTemplateTrim(req, deps) {
45966
46140
  throw e;
45967
46141
  }
45968
46142
  if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
45969
- const hadHimi = existsSync44(join42(dir, ".himi"));
45970
- const backupRel = join42(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
45971
- const backup = join42(dir, backupRel);
46143
+ const hadHimi = existsSync45(join43(dir, ".himi"));
46144
+ const backupRel = join43(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
46145
+ const backup = join43(dir, backupRel);
45972
46146
  const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
45973
46147
  const moved = [];
45974
46148
  const restore = () => {
45975
46149
  for (const [path2, rel] of originals) {
45976
- const original = readFileSync48(join42(backup, rel));
45977
- if (!existsSync44(path2) || !readFileSync48(path2).equals(original)) writeFileSync16(path2, original);
46150
+ const original = readFileSync49(join43(backup, rel));
46151
+ if (!existsSync45(path2) || !readFileSync49(path2).equals(original)) writeFileSync16(path2, original);
45978
46152
  }
45979
- for (const name of [...moved].reverse()) renameSync5(join42(backup, "tier5-src", name), join42(dir, "tier5-src", name));
46153
+ for (const name of [...moved].reverse()) renameSync5(join43(backup, "tier5-src", name), join43(dir, "tier5-src", name));
45980
46154
  moved.length = 0;
45981
46155
  };
45982
46156
  const apply = () => {
45983
46157
  const moveDir = deps.moveDir ?? renameSync5;
45984
46158
  for (const name of plan.removedWorkers) {
45985
- moveDir(join42(dir, "tier5-src", name), join42(backup, "tier5-src", name));
46159
+ moveDir(join43(dir, "tier5-src", name), join43(backup, "tier5-src", name));
45986
46160
  moved.push(name);
45987
46161
  }
45988
46162
  if (plan.dev !== dev) writeFileSync16(devPath, plan.dev);
@@ -45990,8 +46164,8 @@ async function runTemplateTrim(req, deps) {
45990
46164
  };
45991
46165
  const dropBackup = () => {
45992
46166
  rmSync8(backup, { recursive: true, force: true });
45993
- for (const d of [dirname25(backup), join42(dir, ".himi")]) {
45994
- if (d === join42(dir, ".himi") && hadHimi) break;
46167
+ for (const d of [dirname25(backup), join43(dir, ".himi")]) {
46168
+ if (d === join43(dir, ".himi") && hadHimi) break;
45995
46169
  try {
45996
46170
  if (!readdirSync24(d).length) rmSync8(d, { recursive: true });
45997
46171
  } catch {
@@ -46017,9 +46191,9 @@ async function runTemplateTrim(req, deps) {
46017
46191
  return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
46018
46192
  };
46019
46193
  try {
46020
- mkdirSync20(join42(backup, "dev"), { recursive: true });
46021
- for (const [path2, rel] of originals) cpSync(path2, join42(backup, rel));
46022
- if (plan.removedWorkers.length) mkdirSync20(join42(backup, "tier5-src"), { recursive: true });
46194
+ mkdirSync20(join43(backup, "dev"), { recursive: true });
46195
+ for (const [path2, rel] of originals) cpSync(path2, join43(backup, rel));
46196
+ if (plan.removedWorkers.length) mkdirSync20(join43(backup, "tier5-src"), { recursive: true });
46023
46197
  } catch (e) {
46024
46198
  dropBackup();
46025
46199
  return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
@@ -46032,7 +46206,7 @@ async function runTemplateTrim(req, deps) {
46032
46206
  let failure;
46033
46207
  let validate3;
46034
46208
  try {
46035
- const written = parseApp(readFileSync48(devPath, "utf8"), readFileSync48(configPath, "utf8"));
46209
+ const written = parseApp(readFileSync49(devPath, "utf8"), readFileSync49(configPath, "utf8"));
46036
46210
  const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
46037
46211
  if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
46038
46212
  else {
@@ -46069,7 +46243,7 @@ async function runTemplateTrim(req, deps) {
46069
46243
  else if (compared) {
46070
46244
  failure = {
46071
46245
  ...failure,
46072
- error: `himi validate --strict failed on the trimmed app \u2014 the trim introduced ${plural(compared.introduced.length, "error")} the untrimmed app does not have: ${summarize3(compared.introduced)}`,
46246
+ error: `himi validate --strict failed on the trimmed app \u2014 the trim introduced ${plural2(compared.introduced.length, "error")} the untrimmed app does not have: ${summarize3(compared.introduced)}`,
46073
46247
  introducedErrors: compared.introduced,
46074
46248
  preExistingErrors: compared.preExisting
46075
46249
  };
@@ -46085,7 +46259,7 @@ async function runTemplateTrim(req, deps) {
46085
46259
  ...base(validate3),
46086
46260
  ...preExisting.length ? {
46087
46261
  preExistingErrors: preExisting,
46088
- note: `trimmed; ${plural(preExisting.length, "error")} ${preExisting.length === 1 ? "was" : "were"} already present before trim: ${summarize3(preExisting)}`
46262
+ note: `trimmed; ${plural2(preExisting.length, "error")} ${preExisting.length === 1 ? "was" : "were"} already present before trim: ${summarize3(preExisting)}`
46089
46263
  } : {}
46090
46264
  }
46091
46265
  };
@@ -46136,7 +46310,7 @@ function summarize3(errors) {
46136
46310
  const shown2 = errors.slice(0, 5).map(one).join("; ");
46137
46311
  return errors.length > 5 ? `${shown2}; and ${errors.length - 5} more` : shown2;
46138
46312
  }
46139
- var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf2, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural;
46313
+ var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf2, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural2;
46140
46314
  var init_template_trim = __esm({
46141
46315
  "src/template-trim.ts"() {
46142
46316
  init_validate_descriptor();
@@ -46170,7 +46344,7 @@ var init_template_trim = __esm({
46170
46344
  NAV_SCREEN_KEYS = /* @__PURE__ */ new Set(["screenId", "parentScreenId", "contentScreenId", "badgeScreenId"]);
46171
46345
  sorted = (xs) => [...new Set(xs)].sort();
46172
46346
  refuse3 = (e) => ({ code: 2, result: { ok: false, error: e.message, ...e.detail } });
46173
- plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
46347
+ plural2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
46174
46348
  }
46175
46349
  });
46176
46350
 
@@ -46800,8 +46974,8 @@ __export(pack_exports, {
46800
46974
  });
46801
46975
  import { createHash as createHash22 } from "node:crypto";
46802
46976
  import { deflateRawSync, inflateRawSync } from "node:zlib";
46803
- import { existsSync as existsSync45, mkdirSync as mkdirSync21, readFileSync as readFileSync49, readdirSync as readdirSync25, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46804
- import { dirname as dirname26, join as join43, resolve as resolve37 } from "node:path";
46977
+ import { existsSync as existsSync46, mkdirSync as mkdirSync21, readFileSync as readFileSync50, readdirSync as readdirSync25, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46978
+ import { dirname as dirname26, join as join44, resolve as resolve37 } from "node:path";
46805
46979
  function writeZip(entries) {
46806
46980
  const sorted2 = entries.slice().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
46807
46981
  const total = sorted2.reduce((n, e) => n + e.bytes.length, 0);
@@ -46895,13 +47069,13 @@ function inflateEntry(buf, entry) {
46895
47069
  return bytes2;
46896
47070
  }
46897
47071
  function readIf(path2) {
46898
- return existsSync45(path2) && statSync11(path2).isFile() ? readFileSync49(path2) : null;
47072
+ return existsSync46(path2) && statSync11(path2).isFile() ? readFileSync50(path2) : null;
46899
47073
  }
46900
47074
  function listDir(dir) {
46901
- return existsSync45(dir) ? readdirSync25(dir).filter((f) => statSync11(join43(dir, f)).isFile()) : [];
47075
+ return existsSync46(dir) ? readdirSync25(dir).filter((f) => statSync11(join44(dir, f)).isFile()) : [];
46902
47076
  }
46903
47077
  function gatherReleaseEntries(appDir2) {
46904
- const manifestPath = join43(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
47078
+ const manifestPath = join44(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
46905
47079
  const manifestBytes = readIf(manifestPath);
46906
47080
  if (!manifestBytes) throw new Error(`no ${HIMI_PACKAGE_MANIFEST_PATH} in ${appDir2} \u2014 build a release there first`);
46907
47081
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
@@ -46925,7 +47099,7 @@ function gatherReleaseEntries(appDir2) {
46925
47099
  }
46926
47100
  const missing = [];
46927
47101
  for (const rel of wanted) {
46928
- const bytes2 = readIf(join43(appDir2, rel));
47102
+ const bytes2 = readIf(join44(appDir2, rel));
46929
47103
  if (bytes2) entries.push({ path: rel, bytes: bytes2 });
46930
47104
  else missing.push(rel);
46931
47105
  }
@@ -46936,12 +47110,12 @@ function gatherReleaseEntries(appDir2) {
46936
47110
  );
46937
47111
  }
46938
47112
  for (const name of SINGLETONS) {
46939
- const bytes2 = readIf(join43(appDir2, name));
47113
+ const bytes2 = readIf(join44(appDir2, name));
46940
47114
  if (bytes2) entries.push({ path: name, bytes: bytes2 });
46941
47115
  }
46942
47116
  const skipped = [];
46943
47117
  for (const sub of ["screens", "bundles", "assets", "widgets"]) {
46944
- for (const f of listDir(join43(appDir2, sub))) {
47118
+ for (const f of listDir(join44(appDir2, sub))) {
46945
47119
  if (!wanted.has(`${sub}/${f}`)) skipped.push(`${sub}/${f}`);
46946
47120
  }
46947
47121
  }
@@ -46974,18 +47148,18 @@ function buildEntries(appDir2, app, opts) {
46974
47148
  return { all, header, manifest, skipped };
46975
47149
  }
46976
47150
  function packRelease(releaseRoot, app, out, opts = {}) {
46977
- const appDir2 = join43(resolve37(releaseRoot), app);
47151
+ const appDir2 = join44(resolve37(releaseRoot), app);
46978
47152
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
46979
47153
  const buf = writeZip(all);
46980
- const isDir2 = existsSync45(out) && statSync11(out).isDirectory();
46981
- const outFile = isDir2 ? join43(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
47154
+ const isDir2 = existsSync46(out) && statSync11(out).isDirectory();
47155
+ const outFile = isDir2 ? join44(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
46982
47156
  mkdirSync21(dirname26(outFile), { recursive: true });
46983
47157
  writeFileSync17(outFile, buf);
46984
47158
  return { app, releaseId: header.releaseId, header, out: outFile, bytes: buf.length, entryCount: all.length, skipped };
46985
47159
  }
46986
47160
  function resolvePackageDirDest(outDir, app, releaseId) {
46987
47161
  const packagedApp = (dir) => {
46988
- const bytes2 = readIf(join43(dir, HIMI_PACKAGE_HEADER_PATH));
47162
+ const bytes2 = readIf(join44(dir, HIMI_PACKAGE_HEADER_PATH));
46989
47163
  if (!bytes2) return void 0;
46990
47164
  try {
46991
47165
  return JSON.parse(bytes2.toString("utf8")).app;
@@ -46994,15 +47168,15 @@ function resolvePackageDirDest(outDir, app, releaseId) {
46994
47168
  }
46995
47169
  };
46996
47170
  const replaceable = (dir) => {
46997
- if (!existsSync45(dir)) return true;
47171
+ if (!existsSync46(dir)) return true;
46998
47172
  if (!statSync11(dir).isDirectory()) return false;
46999
47173
  return readdirSync25(dir).length === 0 || packagedApp(dir) === app;
47000
47174
  };
47001
- if (existsSync45(outDir) && !statSync11(outDir).isDirectory()) {
47175
+ if (existsSync46(outDir) && !statSync11(outDir).isDirectory()) {
47002
47176
  throw new Error(`refusing to write a package tree over ${outDir} \u2014 it is a file, not a directory`);
47003
47177
  }
47004
47178
  if (replaceable(outDir)) return outDir;
47005
- const child = join43(outDir, packageDirName(app, releaseId));
47179
+ const child = join44(outDir, packageDirName(app, releaseId));
47006
47180
  if (!replaceable(child)) {
47007
47181
  throw new Error(
47008
47182
  `refusing to replace ${child}: it is not empty and is not a ${app} package (no ${HIMI_PACKAGE_HEADER_PATH}). Pass --out to a new or empty directory.`
@@ -47016,13 +47190,13 @@ function packReleaseToDir(releaseRoot, app, outDir, opts = {}) {
47016
47190
  "a package carrying the app's source has no --format dir form: readers that predate the source entry drop it before hashing a directory and refuse the package as tampered. Pack it as a .himi archive."
47017
47191
  );
47018
47192
  }
47019
- const appDir2 = join43(resolve37(releaseRoot), app);
47193
+ const appDir2 = join44(resolve37(releaseRoot), app);
47020
47194
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
47021
47195
  const dest = resolvePackageDirDest(resolve37(outDir), app, header.releaseId);
47022
47196
  rmSync9(dest, { recursive: true, force: true });
47023
47197
  let bytes2 = 0;
47024
47198
  for (const entry of all) {
47025
- const target = join43(dest, entry.path);
47199
+ const target = join44(dest, entry.path);
47026
47200
  mkdirSync21(dirname26(target), { recursive: true });
47027
47201
  writeFileSync17(target, entry.bytes);
47028
47202
  bytes2 += entry.bytes.length;
@@ -47045,21 +47219,21 @@ function openPackageBuffer(buf) {
47045
47219
  return openFromEntries(readZip(buf), { requireHeader: true });
47046
47220
  }
47047
47221
  function openPackageFile(file) {
47048
- return openPackageBuffer(readFileSync49(file));
47222
+ return openPackageBuffer(readFileSync50(file));
47049
47223
  }
47050
47224
  function openPackageDir(dir) {
47051
47225
  const root = resolve37(dir);
47052
47226
  const entries = /* @__PURE__ */ new Map();
47053
47227
  const walk2 = (rel) => {
47054
- const abs = rel ? join43(root, rel) : root;
47228
+ const abs = rel ? join44(root, rel) : root;
47055
47229
  for (const name of readdirSync25(abs)) {
47056
47230
  const childRel = rel ? `${rel}/${name}` : name;
47057
- const childAbs = join43(abs, name);
47231
+ const childAbs = join44(abs, name);
47058
47232
  if (statSync11(childAbs).isDirectory()) walk2(childRel);
47059
- else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync49(childAbs));
47233
+ else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync50(childAbs));
47060
47234
  }
47061
47235
  };
47062
- if (!existsSync45(root)) throw new Error(`no such package directory: ${root}`);
47236
+ if (!existsSync46(root)) throw new Error(`no such package directory: ${root}`);
47063
47237
  walk2("");
47064
47238
  const manifestBytes = entries.get(HIMI_PACKAGE_MANIFEST_PATH);
47065
47239
  if (manifestBytes) {
@@ -47101,10 +47275,10 @@ function unpackTo(buf, dest, appFallback) {
47101
47275
  if (!app) throw new Error("package declares no app id (no himi-package.json and no manifest.app)");
47102
47276
  if (!isSafeAppId(app)) throw new Error(`package declares an unsafe app id ${JSON.stringify(app)} \u2014 refusing to unpack it`);
47103
47277
  const root = resolve37(dest);
47104
- const appDir2 = join43(root, app);
47278
+ const appDir2 = join44(root, app);
47105
47279
  rmSync9(appDir2, { recursive: true, force: true });
47106
47280
  for (const [rel, bytes2] of entries) {
47107
- const target = join43(appDir2, rel);
47281
+ const target = join44(appDir2, rel);
47108
47282
  mkdirSync21(dirname26(target), { recursive: true });
47109
47283
  writeFileSync17(target, bytes2);
47110
47284
  }
@@ -47140,20 +47314,20 @@ __export(app_source_exports, {
47140
47314
  writeConflicts: () => writeConflicts,
47141
47315
  zipAppSource: () => zipAppSource
47142
47316
  });
47143
- import { readdirSync as readdirSync26, lstatSync as lstatSync8, readFileSync as readFileSync50, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync46, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
47144
- import { join as join44, resolve as resolve38, sep as sep10 } from "node:path";
47317
+ import { readdirSync as readdirSync26, lstatSync as lstatSync8, readFileSync as readFileSync51, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync47, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
47318
+ import { join as join45, resolve as resolve38, sep as sep10 } from "node:path";
47145
47319
  import { deflateRawSync as deflateRawSync2 } from "node:zlib";
47146
47320
  function walkSourceTree(dir, file, skip) {
47147
47321
  const walk2 = (relDir) => {
47148
- for (const name of readdirSync26(join44(dir, relDir))) {
47149
- const rel = relDir ? join44(relDir, name) : name;
47150
- const st = lstatSync8(join44(dir, rel));
47322
+ for (const name of readdirSync26(join45(dir, relDir))) {
47323
+ const rel = relDir ? join45(relDir, name) : name;
47324
+ const st = lstatSync8(join45(dir, rel));
47151
47325
  if (st.isSymbolicLink()) {
47152
47326
  skip(rel);
47153
47327
  continue;
47154
47328
  }
47155
47329
  if (st.isDirectory()) {
47156
- if (isDeniedSourceDir(name, relDir) || existsSync46(join44(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
47330
+ if (isDeniedSourceDir(name, relDir) || existsSync47(join45(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
47157
47331
  else walk2(rel);
47158
47332
  continue;
47159
47333
  }
@@ -47171,7 +47345,7 @@ function collectAppSource(dir) {
47171
47345
  const skipped = [];
47172
47346
  walkSourceTree(
47173
47347
  dir,
47174
- (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync50(join44(dir, hostRel)) }),
47348
+ (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync51(join45(dir, hostRel)) }),
47175
47349
  (hostRel) => skipped.push(hostRel)
47176
47350
  );
47177
47351
  entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
@@ -47225,13 +47399,13 @@ function writeAppSource(entries, destDir) {
47225
47399
  const segments = entry.path.split("/");
47226
47400
  let dir = root;
47227
47401
  for (const segment of segments.slice(0, -1)) {
47228
- dir = join44(dir, segment);
47402
+ dir = join45(dir, segment);
47229
47403
  const kind = pathKind(dir);
47230
47404
  if (kind?.isSymbolicLink()) unlinkSync3(dir);
47231
47405
  else if (kind) continue;
47232
47406
  mkdirSync22(dir);
47233
47407
  }
47234
- const target = join44(root, ...segments);
47408
+ const target = join45(root, ...segments);
47235
47409
  if (pathKind(target)?.isSymbolicLink()) unlinkSync3(target);
47236
47410
  writeFileSync18(target, entry.bytes);
47237
47411
  written.push(entry.path);
@@ -47239,7 +47413,7 @@ function writeAppSource(entries, destDir) {
47239
47413
  return written;
47240
47414
  }
47241
47415
  function staleSourcePaths(entries, destDir) {
47242
- if (!existsSync46(destDir)) return [];
47416
+ if (!existsSync47(destDir)) return [];
47243
47417
  const archived = new Set(entries.map((e) => e.path));
47244
47418
  const stale = [];
47245
47419
  walkSourceTree(destDir, (archivePath) => {
@@ -47266,7 +47440,7 @@ function writeConflicts(entries, destDir) {
47266
47440
  let dir = root;
47267
47441
  let stop = false;
47268
47442
  for (const [i, segment] of segments.slice(0, -1).entries()) {
47269
- dir = join44(dir, segment);
47443
+ dir = join45(dir, segment);
47270
47444
  const kind2 = pathKind(dir);
47271
47445
  if (!kind2 || kind2.isSymbolicLink()) {
47272
47446
  stop = true;
@@ -47278,7 +47452,7 @@ function writeConflicts(entries, destDir) {
47278
47452
  break;
47279
47453
  }
47280
47454
  if (stop) continue;
47281
- const kind = pathKind(join44(root, ...segments));
47455
+ const kind = pathKind(join45(root, ...segments));
47282
47456
  if (kind && !kind.isSymbolicLink() && kind.isDirectory()) {
47283
47457
  say(entry.path, "is a directory here, but the release has a file at that path");
47284
47458
  }
@@ -47288,22 +47462,22 @@ function writeConflicts(entries, destDir) {
47288
47462
  function removeStaleSource(entries, destDir) {
47289
47463
  const root = resolve38(destDir);
47290
47464
  const removed = staleSourcePaths(entries, root);
47291
- for (const rel of removed) rmSync10(join44(root, ...rel.split("/")), { force: true });
47465
+ for (const rel of removed) rmSync10(join45(root, ...rel.split("/")), { force: true });
47292
47466
  const needed = /* @__PURE__ */ new Set();
47293
47467
  for (const entry of entries) {
47294
47468
  if (isDeniedSourcePath(entry.path)) continue;
47295
47469
  const segments = entry.path.split("/");
47296
47470
  for (let i = 1; i < segments.length; i++) needed.add(segments.slice(0, i).join("/"));
47297
47471
  }
47298
- if (existsSync46(root)) pruneEmptyDirs(root, "", needed, removed);
47472
+ if (existsSync47(root)) pruneEmptyDirs(root, "", needed, removed);
47299
47473
  return removed.sort();
47300
47474
  }
47301
47475
  function pruneEmptyDirs(root, rel, needed, removed) {
47302
47476
  let empty = true;
47303
- for (const name of readdirSync26(rel ? join44(root, rel) : root)) {
47304
- const childRel = rel ? join44(rel, name) : name;
47477
+ for (const name of readdirSync26(rel ? join45(root, rel) : root)) {
47478
+ const childRel = rel ? join45(rel, name) : name;
47305
47479
  const posix4 = childRel.split(sep10).join("/");
47306
- const kind = lstatSync8(join44(root, childRel));
47480
+ const kind = lstatSync8(join45(root, childRel));
47307
47481
  if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
47308
47482
  empty = false;
47309
47483
  continue;
@@ -47313,7 +47487,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
47313
47487
  empty = false;
47314
47488
  continue;
47315
47489
  }
47316
- rmdirSync2(join44(root, childRel));
47490
+ rmdirSync2(join45(root, childRel));
47317
47491
  removed.push(`${posix4}/`);
47318
47492
  }
47319
47493
  return empty;
@@ -47368,13 +47542,13 @@ __export(eject_exports, {
47368
47542
  EJECTABLE: () => EJECTABLE,
47369
47543
  ejectModule: () => ejectModule
47370
47544
  });
47371
- import { existsSync as existsSync47, mkdirSync as mkdirSync23, readFileSync as readFileSync51, writeFileSync as writeFileSync19 } from "node:fs";
47372
- import { dirname as dirname27, join as join45, relative as relative8, resolve as resolve39, sep as sep11 } from "node:path";
47545
+ import { existsSync as existsSync48, mkdirSync as mkdirSync23, readFileSync as readFileSync52, writeFileSync as writeFileSync19 } from "node:fs";
47546
+ import { dirname as dirname27, join as join46, relative as relative9, resolve as resolve39, sep as sep11 } from "node:path";
47373
47547
  function locate(repoRootOrCwd, mod) {
47374
47548
  const inRepo = resolve39(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
47375
- if (existsSync47(inRepo)) return { path: inRepo, form: "source" };
47549
+ if (existsSync48(inRepo)) return { path: inRepo, form: "source" };
47376
47550
  const dist = resolve39(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
47377
- if (existsSync47(dist)) return { path: dist, form: "bundle" };
47551
+ if (existsSync48(dist)) return { path: dist, form: "bundle" };
47378
47552
  return null;
47379
47553
  }
47380
47554
  function specifiersFor(mod) {
@@ -47398,7 +47572,7 @@ function ejectModule(opts) {
47398
47572
  };
47399
47573
  }
47400
47574
  const ext = found.form === "source" ? "ts" : "mjs";
47401
- const target = join45(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
47575
+ const target = join46(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
47402
47576
  mkdirSync23(dirname27(target), { recursive: true });
47403
47577
  const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
47404
47578
  //
@@ -47412,17 +47586,17 @@ function ejectModule(opts) {
47412
47586
  // gate still applies. Ejecting buys layout and flow, never the floor.
47413
47587
 
47414
47588
  `;
47415
- const normalized = readFileSync51(found.path, "utf8").replace(
47589
+ const normalized = readFileSync52(found.path, "utf8").replace(
47416
47590
  /(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
47417
47591
  (_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
47418
47592
  );
47419
47593
  writeFileSync19(target, banner + normalized);
47420
47594
  const rewired = [];
47421
47595
  for (const f of files) {
47422
- if (!existsSync47(f)) continue;
47423
- const before = readFileSync51(f, "utf8");
47596
+ if (!existsSync48(f)) continue;
47597
+ const before = readFileSync52(f, "utf8");
47424
47598
  let after = before;
47425
- let rel = relative8(dirname27(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
47599
+ let rel = relative9(dirname27(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
47426
47600
  if (!rel.startsWith(".")) rel = `./${rel}`;
47427
47601
  for (const spec of specifiersFor(mod)) {
47428
47602
  const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -47540,8 +47714,8 @@ var init_app_map = __esm({
47540
47714
  });
47541
47715
 
47542
47716
  // src/wix-writes.ts
47543
- import { existsSync as existsSync48, readFileSync as readFileSync52 } from "node:fs";
47544
- import { join as join46 } from "node:path";
47717
+ import { existsSync as existsSync49, readFileSync as readFileSync53 } from "node:fs";
47718
+ import { join as join47 } from "node:path";
47545
47719
  function isProvenRead(method, path2) {
47546
47720
  if (/[#\x00-\x1F\x7F]/.test(path2)) return false;
47547
47721
  const verb = method.toUpperCase();
@@ -47622,11 +47796,11 @@ function parseDataAccess(text5, from) {
47622
47796
  }
47623
47797
  function readDataAccess(dir) {
47624
47798
  for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
47625
- const p = join46(dir, name);
47626
- if (!existsSync48(p)) continue;
47799
+ const p = join47(dir, name);
47800
+ if (!existsSync49(p)) continue;
47627
47801
  let text5;
47628
47802
  try {
47629
- text5 = readFileSync52(p, "utf8");
47803
+ text5 = readFileSync53(p, "utf8");
47630
47804
  } catch {
47631
47805
  continue;
47632
47806
  }
@@ -47733,14 +47907,14 @@ __export(test_exports, {
47733
47907
  scopeDescriptors: () => scopeDescriptors,
47734
47908
  writeRecordedMocks: () => writeRecordedMocks
47735
47909
  });
47736
- import { existsSync as existsSync49, mkdirSync as mkdirSync24, readFileSync as readFileSync53, writeFileSync as writeFileSync20 } from "node:fs";
47737
- import { join as join47, resolve as resolve40 } from "node:path";
47910
+ import { existsSync as existsSync50, mkdirSync as mkdirSync24, readFileSync as readFileSync54, writeFileSync as writeFileSync20 } from "node:fs";
47911
+ import { join as join48, resolve as resolve40 } from "node:path";
47738
47912
  function readTestConfig(dir) {
47739
47913
  const p = resolve40(dir, TEST_CONFIG_FILE);
47740
- if (!existsSync49(p)) return {};
47914
+ if (!existsSync50(p)) return {};
47741
47915
  let parsed;
47742
47916
  try {
47743
- parsed = JSON.parse(readFileSync53(p, "utf8"));
47917
+ parsed = JSON.parse(readFileSync54(p, "utf8"));
47744
47918
  } catch (e) {
47745
47919
  throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
47746
47920
  }
@@ -47787,7 +47961,7 @@ function readTestConfig(dir) {
47787
47961
  function readNetRules(path2) {
47788
47962
  let parsed;
47789
47963
  try {
47790
- parsed = JSON.parse(readFileSync53(path2, "utf8"));
47964
+ parsed = JSON.parse(readFileSync54(path2, "utf8"));
47791
47965
  } catch (e) {
47792
47966
  throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
47793
47967
  }
@@ -47808,7 +47982,7 @@ function readNetRules(path2) {
47808
47982
  function defaultNetRules(dir) {
47809
47983
  for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
47810
47984
  const p = resolve40(dir, rel);
47811
- if (existsSync49(p)) {
47985
+ if (existsSync50(p)) {
47812
47986
  const rules = readNetRules(p);
47813
47987
  if (rules.length) return { rules, from: rel };
47814
47988
  }
@@ -47824,7 +47998,7 @@ function readCannedResponses() {
47824
47998
  ];
47825
47999
  for (const url of candidates) {
47826
48000
  try {
47827
- const parsed = JSON.parse(readFileSync53(url, "utf8"));
48001
+ const parsed = JSON.parse(readFileSync54(url, "utf8"));
47828
48002
  const byKey = {};
47829
48003
  for (const [k, v] of Object.entries(parsed)) {
47830
48004
  if (!k.startsWith("//")) byKey[k] = v;
@@ -47914,8 +48088,8 @@ function writeRecordedMocks(opts) {
47914
48088
  mkdir: (p) => mkdirSync24(p, { recursive: true }),
47915
48089
  write: (p, s) => writeFileSync20(p, s)
47916
48090
  };
47917
- fs.mkdir(join47(opts.dir, "dev"));
47918
- fs.write(join47(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
48091
+ fs.mkdir(join48(opts.dir, "dev"));
48092
+ fs.write(join48(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
47919
48093
  return { ok: true, rules, to: "dev/net-mocks.json" };
47920
48094
  }
47921
48095
  function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
@@ -48418,14 +48592,14 @@ var mobile_ux_lint_exports = {};
48418
48592
  __export(mobile_ux_lint_exports, {
48419
48593
  mobileUxIssues: () => mobileUxIssues
48420
48594
  });
48421
- import { existsSync as existsSync50, readFileSync as readFileSync54 } from "node:fs";
48422
- import { join as join48 } from "node:path";
48595
+ import { existsSync as existsSync51, readFileSync as readFileSync55 } from "node:fs";
48596
+ import { join as join49 } from "node:path";
48423
48597
  function mobileUxIssues(dir, strict) {
48424
- const file = join48(dir, "MOBILE-UX.md");
48425
- if (!existsSync50(file)) return [];
48598
+ const file = join49(dir, "MOBILE-UX.md");
48599
+ if (!existsSync51(file)) return [];
48426
48600
  let text5;
48427
48601
  try {
48428
- text5 = readFileSync54(file, "utf8");
48602
+ text5 = readFileSync55(file, "utf8");
48429
48603
  } catch {
48430
48604
  return [];
48431
48605
  }
@@ -50100,8 +50274,8 @@ __export(functions_exports, {
50100
50274
  resolveFunctionsTarget: () => resolveFunctionsTarget,
50101
50275
  runRemoteFunctionOperation: () => runRemoteFunctionOperation
50102
50276
  });
50103
- import { readFileSync as readFileSync55, existsSync as existsSync51 } from "node:fs";
50104
- import { join as join49, resolve as resolve41 } from "node:path";
50277
+ import { readFileSync as readFileSync56, existsSync as existsSync52 } from "node:fs";
50278
+ import { join as join50, resolve as resolve41 } from "node:path";
50105
50279
  function resolveFunctionsTarget(input) {
50106
50280
  const explicitUrl = input.functionsUrl?.trim();
50107
50281
  if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
@@ -50120,9 +50294,9 @@ async function loadFunctionsApp(dir) {
50120
50294
  }
50121
50295
  const sources = {};
50122
50296
  for (const definition of config.functions.functions) {
50123
- const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join49(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync51);
50297
+ const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join50(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync52);
50124
50298
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
50125
- sources[definition.name] = readFileSync55(source, "utf8");
50299
+ sources[definition.name] = readFileSync56(source, "utf8");
50126
50300
  }
50127
50301
  const data = config.cms || config.data ? mergeDataResources(config) : void 0;
50128
50302
  const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
@@ -50282,10 +50456,10 @@ __export(studio_reel_exports, {
50282
50456
  utcDay: () => utcDay
50283
50457
  });
50284
50458
  import { createHash as createHash25 } from "node:crypto";
50285
- import { lstatSync as lstatSync9, readFileSync as readFileSync56 } from "node:fs";
50286
- import { basename as basename12, join as join50 } from "node:path";
50459
+ import { lstatSync as lstatSync9, readFileSync as readFileSync57 } from "node:fs";
50460
+ import { basename as basename13, join as join51 } from "node:path";
50287
50461
  function loadMemory2(dir) {
50288
- const m = readJson5(join50(dir, ".himi", "studio", "memory.json"));
50462
+ const m = readJson5(join51(dir, ".himi", "studio", "memory.json"));
50289
50463
  if (!isObj2(m) || m.version !== 1 || !isObj2(m.shots) || !Array.isArray(m.timeline)) return null;
50290
50464
  const shots = [];
50291
50465
  for (const [id, s] of Object.entries(m.shots)) {
@@ -50322,8 +50496,8 @@ function planReel(dir) {
50322
50496
  const memory = loadMemory2(dir);
50323
50497
  const { entries } = readProgress(dir);
50324
50498
  const summary = summarizeProgress(entries);
50325
- const manifest = readJson5(join50(dir, "himalaya.content.json"));
50326
- const tokens = readJson5(join50(dir, "tokens.json"));
50499
+ const manifest = readJson5(join51(dir, "himalaya.content.json"));
50500
+ const tokens = readJson5(join51(dir, "tokens.json"));
50327
50501
  const accentToken = tokens?.colors?.accentPrimary;
50328
50502
  const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
50329
50503
  const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
@@ -50337,14 +50511,15 @@ function planReel(dir) {
50337
50511
  const postcards = entries.map((e) => ({
50338
50512
  ts: e.ts,
50339
50513
  message: e.message,
50340
- step: str(e.step),
50341
- files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj2(i) && typeof i.file === "string" ? basename12(i.file) : "").filter((f) => HISTORY_NAME.test(f))
50514
+ step: isCliEntry(e) ? null : str(e.step),
50515
+ by: isCliEntry(e) ? CLI_AUTHOR : null,
50516
+ files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj2(i) && typeof i.file === "string" ? basename13(i.file) : "").filter((f) => HISTORY_NAME.test(f))
50342
50517
  }));
50343
50518
  const times = [summary.startedAt, ...timeline.map((t) => t.ts), memory?.createdAt].filter((x) => !!x).sort();
50344
50519
  const startedAt = times[0] ?? null;
50345
50520
  const endedAt = [summary.lastAt, ...timeline.map((t) => t.ts)].filter((x) => !!x).sort().pop() ?? null;
50346
50521
  return {
50347
- app: typeof manifest?.name === "string" && manifest.name ? manifest.name : basename12(dir),
50522
+ app: typeof manifest?.name === "string" && manifest.name ? manifest.name : basename13(dir),
50348
50523
  accent,
50349
50524
  startedAt,
50350
50525
  endedAt,
@@ -50365,10 +50540,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
50365
50540
  const verified = (f) => {
50366
50541
  if (!HISTORY_NAME.test(f)) return null;
50367
50542
  try {
50368
- const path2 = join50(dir, HISTORY_DIR, f);
50543
+ const path2 = join51(dir, HISTORY_DIR, f);
50369
50544
  const st = lstatSync9(path2);
50370
50545
  if (!st.isFile() || st.nlink !== 1) return null;
50371
- const bytes2 = readFileSync56(path2);
50546
+ const bytes2 = readFileSync57(path2);
50372
50547
  return createHash25("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
50373
50548
  } catch {
50374
50549
  return null;
@@ -50462,7 +50637,7 @@ function renderReel(plan, images) {
50462
50637
  .flipcap{flex:1;min-width:220px}.flipcap .t{font-size:40px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent)}.bar{height:4px;background:var(--line);border-radius:2px;margin-top:16px}.bar div{height:4px;background:var(--accent);border-radius:2px;width:0}
50463
50638
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:18px}.grid figure{margin:0;text-align:center}.grid img{width:100%;aspect-ratio:390/844;object-fit:cover;object-position:top;border:6px solid #111;border-radius:22px;background:#fff}
50464
50639
  figcaption{font-size:12px;color:var(--muted);margin-top:6px;word-break:break-all}
50465
- .log{list-style:none;padding:0;margin:0}.log li{display:flex;gap:14px;padding:12px 0;border-top:1px solid var(--line)}.log .t{min-width:52px;color:var(--muted);font-variant-numeric:tabular-nums}.log .s{font-size:12px;color:var(--muted);text-transform:capitalize}
50640
+ .log{list-style:none;padding:0;margin:0}.log li{display:flex;gap:14px;padding:12px 0;border-top:1px solid var(--line)}.log .t{min-width:52px;color:var(--muted);font-variant-numeric:tabular-nums}.log .s{font-size:12px;color:var(--muted);text-transform:capitalize}.log .by{font-size:12px;color:var(--muted)}
50466
50641
  .log .imgs{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}.log .imgs img{height:120px;border-radius:10px;border:1px solid var(--line)}
50467
50642
  footer{margin-top:48px;font-size:12px;color:var(--muted)}
50468
50643
  </style></head><body><div class="wrap">
@@ -50473,7 +50648,7 @@ ${flip}
50473
50648
  <h2>Where it ended up</h2>
50474
50649
  <div class="grid">${plan.finals.map((f) => `<figure>${img(f.file, f.id)}<figcaption>${escapeHtml(f.id)}</figcaption></figure>`).join("")}</div>
50475
50650
  <h2>Postcards</h2>
50476
- <ul class="log">${plan.postcards.map((c) => `<li><div class="t">${clock(c.ts)}</div><div><div>${escapeHtml(c.message)}</div>${c.step ? `<div class="s">step ${JOURNEY_STEPS.indexOf(c.step) + 1} \xB7 ${escapeHtml(c.step)}</div>` : ""}${c.files.length ? `<div class="imgs">${c.files.map((f) => img(f, c.message)).join("")}</div>` : ""}</div></li>`).join("") || `<li class="muted">No postcards were journaled.</li>`}</ul>
50651
+ <ul class="log">${plan.postcards.map((c) => `<li><div class="t">${clock(c.ts)}</div><div><div>${escapeHtml(c.message)}</div>${c.step ? `<div class="s">step ${JOURNEY_STEPS.indexOf(c.step) + 1} \xB7 ${escapeHtml(c.step)}</div>` : ""}${c.by ? `<div class="by">noted by ${escapeHtml(c.by)}</div>` : ""}${c.files.length ? `<div class="imgs">${c.files.map((f) => img(f, c.message)).join("")}</div>` : ""}</div></li>`).join("") || `<li class="muted">No postcards were journaled.</li>`}</ul>
50477
50652
  <footer>Built with himi.${plan.dropped ? ` ${count2(plan.dropped)} pictures were left out to keep this file small.` : ""}</footer>
50478
50653
  </div>
50479
50654
  <script>
@@ -50502,7 +50677,7 @@ var init_studio_reel = __esm({
50502
50677
  mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
50503
50678
  readJson5 = (file) => {
50504
50679
  try {
50505
- return JSON.parse(readFileSync56(file, "utf8"));
50680
+ return JSON.parse(readFileSync57(file, "utf8"));
50506
50681
  } catch {
50507
50682
  return null;
50508
50683
  }
@@ -50533,9 +50708,9 @@ __export(native_decode_exports, {
50533
50708
  runNativeDecode: () => runNativeDecode
50534
50709
  });
50535
50710
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
50536
- import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readFileSync as readFileSync57, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
50711
+ import { existsSync as existsSync53, mkdtempSync as mkdtempSync2, readFileSync as readFileSync58, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
50537
50712
  import { tmpdir as tmpdir3 } from "node:os";
50538
- import { join as join51 } from "node:path";
50713
+ import { join as join52 } from "node:path";
50539
50714
  import { fileURLToPath as fileURLToPath20 } from "node:url";
50540
50715
  function nativeDecodePackageDir() {
50541
50716
  return fileURLToPath20(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
@@ -50545,8 +50720,8 @@ function nativeDecodeAndroidHarnessDir() {
50545
50720
  }
50546
50721
  function nativeOracleShipped() {
50547
50722
  return {
50548
- ios: existsSync52(join51(nativeDecodePackageDir(), "Package.swift")),
50549
- android: existsSync52(join51(nativeDecodeAndroidHarnessDir(), "gradlew"))
50723
+ ios: existsSync53(join52(nativeDecodePackageDir(), "Package.swift")),
50724
+ android: existsSync53(join52(nativeDecodeAndroidHarnessDir(), "gradlew"))
50550
50725
  };
50551
50726
  }
50552
50727
  function detectNativeDecode(options = {}) {
@@ -50557,7 +50732,7 @@ function detectNativeDecode(options = {}) {
50557
50732
  return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
50558
50733
  }
50559
50734
  if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
50560
- const packageAvailable = options.packageAvailable ?? existsSync52(join51(packageDir, "Package.swift"));
50735
+ const packageAvailable = options.packageAvailable ?? existsSync53(join52(packageDir, "Package.swift"));
50561
50736
  if (!packageAvailable) {
50562
50737
  return {
50563
50738
  platform: "ios",
@@ -50569,7 +50744,7 @@ function detectNativeDecode(options = {}) {
50569
50744
  }
50570
50745
  function localPropertiesSdk(harnessDir) {
50571
50746
  try {
50572
- const match = readFileSync57(join51(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
50747
+ const match = readFileSync58(join52(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
50573
50748
  return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
50574
50749
  } catch {
50575
50750
  return null;
@@ -50577,8 +50752,8 @@ function localPropertiesSdk(harnessDir) {
50577
50752
  }
50578
50753
  function detectAndroidNativeDecode(options = {}) {
50579
50754
  const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
50580
- const gradlew = join51(harnessDir, "gradlew");
50581
- const gradlewAvailable = options.gradlewAvailable ?? existsSync52(gradlew);
50755
+ const gradlew = join52(harnessDir, "gradlew");
50756
+ const gradlewAvailable = options.gradlewAvailable ?? existsSync53(gradlew);
50582
50757
  if (!gradlewAvailable) {
50583
50758
  return {
50584
50759
  platform: "android",
@@ -50588,7 +50763,7 @@ function detectAndroidNativeDecode(options = {}) {
50588
50763
  }
50589
50764
  const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
50590
50765
  const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
50591
- const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync52(sdk));
50766
+ const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync53(sdk));
50592
50767
  if (!sdkAvailable) {
50593
50768
  return {
50594
50769
  platform: "android",
@@ -50660,14 +50835,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
50660
50835
  return verdicts;
50661
50836
  }
50662
50837
  function runNativeDecode(options) {
50663
- const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-"));
50838
+ const descriptorDir = mkdtempSync2(join52(tmpdir3(), "himi-native-decode-"));
50664
50839
  const startedAt = Date.now();
50665
50840
  try {
50666
50841
  for (const descriptor of options.descriptors) {
50667
50842
  if (typeof descriptor.descriptorJson !== "string") {
50668
50843
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50669
50844
  }
50670
- writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50845
+ writeFileSync21(join52(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50671
50846
  }
50672
50847
  options.progress?.("building cached Swift oracle");
50673
50848
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -50679,7 +50854,7 @@ function runNativeDecode(options) {
50679
50854
  encoding: "utf8"
50680
50855
  }).trim();
50681
50856
  options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50682
- const stdout = execFileSync5(join51(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50857
+ const stdout = execFileSync5(join52(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50683
50858
  stdio: ["ignore", "pipe", "pipe"],
50684
50859
  encoding: "utf8"
50685
50860
  });
@@ -50697,15 +50872,15 @@ function runNativeDecode(options) {
50697
50872
  }
50698
50873
  }
50699
50874
  function runAndroidNativeDecode(options) {
50700
- const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-android-"));
50701
- const outputPath = join51(descriptorDir, "verdicts.ndjson");
50875
+ const descriptorDir = mkdtempSync2(join52(tmpdir3(), "himi-native-decode-android-"));
50876
+ const outputPath = join52(descriptorDir, "verdicts.ndjson");
50702
50877
  const startedAt = Date.now();
50703
50878
  try {
50704
50879
  for (const descriptor of options.descriptors) {
50705
50880
  if (typeof descriptor.descriptorJson !== "string") {
50706
50881
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50707
50882
  }
50708
- writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50883
+ writeFileSync21(join52(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50709
50884
  }
50710
50885
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50711
50886
  execFileSync5(options.gradlew, [
@@ -50720,7 +50895,7 @@ function runAndroidNativeDecode(options) {
50720
50895
  stdio: ["ignore", "ignore", "pipe"],
50721
50896
  encoding: "utf8"
50722
50897
  });
50723
- const stdout = readFileSync57(outputPath, "utf8");
50898
+ const stdout = readFileSync58(outputPath, "utf8");
50724
50899
  return {
50725
50900
  platform: "android",
50726
50901
  verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
@@ -50790,13 +50965,13 @@ __export(browser_authoring_exports, {
50790
50965
  });
50791
50966
  import { createServer as createServer3 } from "node:http";
50792
50967
  import { createHash as createHash26, randomBytes as randomBytes4 } from "node:crypto";
50793
- import { existsSync as existsSync53, readdirSync as readdirSync27, readFileSync as readFileSync58, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50794
- import { join as join52, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
50968
+ import { existsSync as existsSync54, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50969
+ import { join as join53, posix as posix2, relative as relative10, sep as sep12 } from "node:path";
50795
50970
  function humanMs(ms) {
50796
50971
  return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
50797
50972
  }
50798
50973
  function collectWorkerSources(tier5SrcDir, bundleName) {
50799
- const root = join52(tier5SrcDir, bundleName);
50974
+ const root = join53(tier5SrcDir, bundleName);
50800
50975
  const files = {};
50801
50976
  const visitedDirs = /* @__PURE__ */ new Set();
50802
50977
  const walk2 = (abs, rel) => {
@@ -50804,16 +50979,16 @@ function collectWorkerSources(tier5SrcDir, bundleName) {
50804
50979
  if (visitedDirs.has(real)) return;
50805
50980
  visitedDirs.add(real);
50806
50981
  for (const name of readdirSync27(abs).sort()) {
50807
- const childAbs = join52(abs, name);
50982
+ const childAbs = join53(abs, name);
50808
50983
  const childRel = posix2.join(rel, name);
50809
50984
  if (statSync12(childAbs).isDirectory()) {
50810
50985
  walk2(childAbs, childRel);
50811
50986
  } else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
50812
- files[childRel] = readFileSync58(childAbs, "utf8");
50987
+ files[childRel] = readFileSync59(childAbs, "utf8");
50813
50988
  }
50814
50989
  }
50815
50990
  };
50816
- if (!existsSync53(root)) return null;
50991
+ if (!existsSync54(root)) return null;
50817
50992
  walk2(root, `/tier5-src/${bundleName}`);
50818
50993
  const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
50819
50994
  if (!entry) return null;
@@ -50830,20 +51005,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
50830
51005
  const spec = m[1];
50831
51006
  const virtual = posix2.normalize(posix2.join(dir, spec));
50832
51007
  if (!virtual.startsWith("/tier5-src/")) continue;
50833
- const onDisk = join52(tier5SrcDir, virtual.slice("/tier5-src/".length));
51008
+ const onDisk = join53(tier5SrcDir, virtual.slice("/tier5-src/".length));
50834
51009
  const candidates = [
50835
51010
  onDisk,
50836
51011
  `${onDisk}.ts`,
50837
51012
  `${onDisk}.js`,
50838
51013
  onDisk.replace(/\.js$/, ".ts"),
50839
- join52(onDisk, "index.ts"),
50840
- join52(onDisk, "index.js")
51014
+ join53(onDisk, "index.ts"),
51015
+ join53(onDisk, "index.js")
50841
51016
  ];
50842
51017
  for (const abs of candidates) {
50843
- if (!existsSync53(abs) || statSync12(abs).isDirectory()) continue;
50844
- const key2 = `/tier5-src/${relative9(tier5SrcDir, abs).split(sep12).join(posix2.sep)}`;
51018
+ if (!existsSync54(abs) || statSync12(abs).isDirectory()) continue;
51019
+ const key2 = `/tier5-src/${relative10(tier5SrcDir, abs).split(sep12).join(posix2.sep)}`;
50845
51020
  if (key2 in files) break;
50846
- files[key2] = readFileSync58(abs, "utf8");
51021
+ files[key2] = readFileSync59(abs, "utf8");
50847
51022
  queue.push(key2);
50848
51023
  break;
50849
51024
  }
@@ -50878,7 +51053,7 @@ function collectSdkModules(files, resolveSubpath) {
50878
51053
  function sdkSubpathReader(resolve48) {
50879
51054
  return (sub) => {
50880
51055
  try {
50881
- return readFileSync58(resolve48(`@wix/himalaya/${sub}`), "utf8");
51056
+ return readFileSync59(resolve48(`@wix/himalaya/${sub}`), "utf8");
50882
51057
  } catch {
50883
51058
  return null;
50884
51059
  }
@@ -51050,8 +51225,8 @@ __export(coverage_exports, {
51050
51225
  suppressedNativeKeys: () => suppressedNativeKeys,
51051
51226
  trendAndStore: () => trendAndStore
51052
51227
  });
51053
- import { existsSync as existsSync54, mkdirSync as mkdirSync25, readFileSync as readFileSync59, writeFileSync as writeFileSync22 } from "node:fs";
51054
- import { dirname as dirname28, join as join53 } from "node:path";
51228
+ import { existsSync as existsSync55, mkdirSync as mkdirSync25, readFileSync as readFileSync60, writeFileSync as writeFileSync22 } from "node:fs";
51229
+ import { dirname as dirname28, join as join54 } from "node:path";
51055
51230
  function suppressedNativeKeys(oracle) {
51056
51231
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
51057
51232
  }
@@ -51231,11 +51406,11 @@ function assembleLedger(r, options = {}) {
51231
51406
  return out;
51232
51407
  }
51233
51408
  function trendAndStore(dir, entries, suppressed) {
51234
- const p = join53(dir, LEDGER_FILE);
51409
+ const p = join54(dir, LEDGER_FILE);
51235
51410
  let previous = null;
51236
51411
  try {
51237
- if (existsSync54(p)) {
51238
- const parsed = JSON.parse(readFileSync59(p, "utf8"));
51412
+ if (existsSync55(p)) {
51413
+ const parsed = JSON.parse(readFileSync60(p, "utf8"));
51239
51414
  if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
51240
51415
  }
51241
51416
  } catch {
@@ -51274,11 +51449,11 @@ function formatLedger(entries, trend) {
51274
51449
  return out.join("\n");
51275
51450
  }
51276
51451
  function diffAndStoreObservations(dir, current) {
51277
- const p = join53(dir, OBS_FILE);
51452
+ const p = join54(dir, OBS_FILE);
51278
51453
  let previous = null;
51279
51454
  try {
51280
- if (existsSync54(p)) {
51281
- const parsed = JSON.parse(readFileSync59(p, "utf8"));
51455
+ if (existsSync55(p)) {
51456
+ const parsed = JSON.parse(readFileSync60(p, "utf8"));
51282
51457
  if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
51283
51458
  (v) => Array.isArray(v) && v.every((l) => typeof l === "string")
51284
51459
  )) {
@@ -51327,8 +51502,8 @@ var LEDGER_FILE, OBS_FILE;
51327
51502
  var init_coverage = __esm({
51328
51503
  "src/coverage.ts"() {
51329
51504
  init_native_decode();
51330
- LEDGER_FILE = join53(".himi", "test-ledger.json");
51331
- OBS_FILE = join53(".himi", "test-observations.json");
51505
+ LEDGER_FILE = join54(".himi", "test-ledger.json");
51506
+ OBS_FILE = join54(".himi", "test-observations.json");
51332
51507
  }
51333
51508
  });
51334
51509
 
@@ -51339,16 +51514,16 @@ __export(design_kits_exports, {
51339
51514
  kitsDir: () => kitsDir,
51340
51515
  listKits: () => listKits2
51341
51516
  });
51342
- import { existsSync as existsSync55, readFileSync as readFileSync60, readdirSync as readdirSync28 } from "node:fs";
51343
- import { join as join54, basename as basename13 } from "node:path";
51517
+ import { existsSync as existsSync56, readFileSync as readFileSync61, readdirSync as readdirSync28 } from "node:fs";
51518
+ import { join as join55, basename as basename14 } from "node:path";
51344
51519
  import { fileURLToPath as fileURLToPath21 } from "node:url";
51345
51520
  function kitsDir() {
51346
- return KIT_DIR_CANDIDATES.find((p) => existsSync55(p));
51521
+ return KIT_DIR_CANDIDATES.find((p) => existsSync56(p));
51347
51522
  }
51348
51523
  function listKits2() {
51349
51524
  const dir = kitsDir();
51350
51525
  if (!dir) return [];
51351
- return readdirSync28(dir).filter((f) => f.endsWith(".json")).map((f) => basename13(f, ".json")).sort();
51526
+ return readdirSync28(dir).filter((f) => f.endsWith(".json")).map((f) => basename14(f, ".json")).sort();
51352
51527
  }
51353
51528
  function kitReport(name) {
51354
51529
  const dir = kitsDir();
@@ -51367,7 +51542,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
51367
51542
  if (!kits.includes(name)) {
51368
51543
  return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
51369
51544
  }
51370
- const raw = readFileSync60(join54(dir, `${name}.json`), "utf8");
51545
+ const raw = readFileSync61(join55(dir, `${name}.json`), "utf8");
51371
51546
  let parsed;
51372
51547
  try {
51373
51548
  parsed = JSON.parse(raw);
@@ -51391,9 +51566,9 @@ var init_design_kits = __esm({
51391
51566
  "src/design-kits.ts"() {
51392
51567
  KIT_DIR_CANDIDATES = [
51393
51568
  // in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
51394
- join54(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
51569
+ join55(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
51395
51570
  // published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
51396
- join54(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
51571
+ join55(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
51397
51572
  ];
51398
51573
  }
51399
51574
  });
@@ -51407,11 +51582,11 @@ __export(assets_exports, {
51407
51582
  removeAssetPayload: () => removeAssetPayload,
51408
51583
  verifyReport: () => verifyReport
51409
51584
  });
51410
- import { readFileSync as readFileSync61 } from "node:fs";
51411
- import { basename as basename14 } from "node:path";
51585
+ import { readFileSync as readFileSync62 } from "node:fs";
51586
+ import { basename as basename15 } from "node:path";
51412
51587
  function declaredAssets(dir) {
51413
51588
  return collectAssets(dir).map((a) => {
51414
- const bytes2 = readFileSync61(a.path);
51589
+ const bytes2 = readFileSync62(a.path);
51415
51590
  return {
51416
51591
  name: a.name,
51417
51592
  sha256: assetSha256(bytes2),
@@ -51488,8 +51663,8 @@ function verifyReport(dir, app, manifest) {
51488
51663
  };
51489
51664
  }
51490
51665
  function addAssetPayload(manifest, filePath, opts) {
51491
- const bytes2 = readFileSync61(filePath);
51492
- const name = opts.name ?? basename14(filePath);
51666
+ const bytes2 = readFileSync62(filePath);
51667
+ const name = opts.name ?? basename15(filePath);
51493
51668
  const sha2565 = assetSha256(bytes2);
51494
51669
  const record2 = {
51495
51670
  sha256: sha2565,
@@ -53163,9 +53338,9 @@ __export(run_exports, {
53163
53338
  runPackage: () => runPackage,
53164
53339
  withoutProdServe: () => withoutProdServe
53165
53340
  });
53166
- import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync62, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
53341
+ import { cpSync as cpSync2, existsSync as existsSync57, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync63, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
53167
53342
  import { networkInterfaces as osNetworkInterfaces, tmpdir as tmpdir4 } from "node:os";
53168
- import { dirname as dirname29, join as join55 } from "node:path";
53343
+ import { dirname as dirname29, join as join56 } from "node:path";
53169
53344
  import { fileURLToPath as fileURLToPath22 } from "node:url";
53170
53345
  function makePackageRunHandler(opts) {
53171
53346
  const store = new FileReleaseStore(opts.releaseRoot);
@@ -53193,9 +53368,9 @@ async function fetchBytes(url) {
53193
53368
  }
53194
53369
  async function resolvePackage(source, workDir) {
53195
53370
  const isUrl2 = /^https?:\/\//i.test(source);
53196
- if (!isUrl2 && existsSync56(source) && statSync13(source).isDirectory()) {
53197
- const appDirManifest = join55(source, "release-manifest.json");
53198
- if (existsSync56(appDirManifest)) {
53371
+ if (!isUrl2 && existsSync57(source) && statSync13(source).isDirectory()) {
53372
+ const appDirManifest = join56(source, "release-manifest.json");
53373
+ if (existsSync57(appDirManifest)) {
53199
53374
  const opened2 = openPackageDir(source);
53200
53375
  if (!opened2.validation.ok) {
53201
53376
  throw new Error(`package is not valid:
@@ -53204,9 +53379,9 @@ async function resolvePackage(source, workDir) {
53204
53379
  const app2 = opened2.header?.app ?? opened2.manifest?.app;
53205
53380
  if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
53206
53381
  if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
53207
- const root2 = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
53208
- const target = join55(root2, app2);
53209
- if (join55(source) !== target) {
53382
+ const root2 = workDir ?? mkdtempSync3(join56(tmpdir4(), "himi-run-"));
53383
+ const target = join56(root2, app2);
53384
+ if (join56(source) !== target) {
53210
53385
  mkdirSync26(root2, { recursive: true });
53211
53386
  rmSync12(target, { recursive: true, force: true });
53212
53387
  cpSync2(source, target, { recursive: true });
@@ -53223,8 +53398,8 @@ async function resolvePackage(source, workDir) {
53223
53398
  }
53224
53399
  throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
53225
53400
  }
53226
- const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync62(source);
53227
- const root = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
53401
+ const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync63(source);
53402
+ const root = workDir ?? mkdtempSync3(join56(tmpdir4(), "himi-run-"));
53228
53403
  const { app, appDir: appDir2 } = unpackTo(bytes2, root);
53229
53404
  const opened = openPackageDir(appDir2);
53230
53405
  if (!opened.validation.ok) {
@@ -53247,14 +53422,14 @@ async function loadPreviewAssets() {
53247
53422
  const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
53248
53423
  const here = dirname29(fileURLToPath22(import.meta.url));
53249
53424
  const candidates = [
53250
- join55(here, "preview-assets.generated.bin"),
53425
+ join56(here, "preview-assets.generated.bin"),
53251
53426
  // published: copied next to the bundled CLI
53252
- join55(here, "../../../packages/serve/src/preview-assets.generated.bin")
53427
+ join56(here, "../../../packages/serve/src/preview-assets.generated.bin")
53253
53428
  // in-repo checkout
53254
53429
  ];
53255
53430
  for (const path2 of candidates) {
53256
- if (!existsSync56(path2)) continue;
53257
- const blob = readFileSync62(path2);
53431
+ if (!existsSync57(path2)) continue;
53432
+ const blob = readFileSync63(path2);
53258
53433
  return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
53259
53434
  }
53260
53435
  return /* @__PURE__ */ new Map();
@@ -53401,9 +53576,9 @@ __export(run_watch_exports, {
53401
53576
  watchedContentDir: () => watchedContentDir,
53402
53577
  writeRunRecord: () => writeRunRecord
53403
53578
  });
53404
- import { closeSync as closeSync2, constants, existsSync as existsSync57, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync63, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
53579
+ import { closeSync as closeSync2, constants, existsSync as existsSync58, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync64, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
53405
53580
  import { randomUUID as randomUUID3 } from "node:crypto";
53406
- import { extname as extname4, join as join56, resolve as resolve42, sep as sep13 } from "node:path";
53581
+ import { extname as extname4, join as join57, resolve as resolve42, sep as sep13 } from "node:path";
53407
53582
  function diffReleases(prev, next) {
53408
53583
  const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
53409
53584
  const changedBundles = new Set(bundles);
@@ -53444,7 +53619,7 @@ function watchContent(dir, onChange, opts = {}) {
53444
53619
  let timer = null;
53445
53620
  const pending = /* @__PURE__ */ new Set();
53446
53621
  const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
53447
- if (!file || ignoredChange(file, (rel) => isDir(join56(dir, rel)))) return;
53622
+ if (!file || ignoredChange(file, (rel) => isDir(join57(dir, rel)))) return;
53448
53623
  pending.add(file);
53449
53624
  if (timer) clearTimeout(timer);
53450
53625
  timer = setTimeout(() => {
@@ -53465,7 +53640,7 @@ function watchedContentDir(source) {
53465
53640
  if (!source || /^https?:\/\//i.test(source)) return null;
53466
53641
  const dir = resolve42(source);
53467
53642
  try {
53468
- return statSync14(dir).isDirectory() && existsSync57(join56(dir, "himalaya.content.json")) ? dir : null;
53643
+ return statSync14(dir).isDirectory() && existsSync58(join57(dir, "himalaya.content.json")) ? dir : null;
53469
53644
  } catch {
53470
53645
  return null;
53471
53646
  }
@@ -53522,7 +53697,7 @@ function serialize(task) {
53522
53697
  });
53523
53698
  }
53524
53699
  function himiDir(dir) {
53525
- const root = join56(dir, ".himi");
53700
+ const root = join57(dir, ".himi");
53526
53701
  try {
53527
53702
  return lstatSync10(root).isDirectory() ? root : null;
53528
53703
  } catch (e) {
@@ -53534,11 +53709,11 @@ function himiDir(dir) {
53534
53709
  function writeRunRecord(dir, record2) {
53535
53710
  const root = himiDir(dir);
53536
53711
  if (!root) return false;
53537
- const tmp = join56(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
53712
+ const tmp = join57(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
53538
53713
  try {
53539
53714
  writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
53540
53715
  `, { flag: "wx" });
53541
- renameSync6(tmp, join56(root, "run.json"));
53716
+ renameSync6(tmp, join57(root, "run.json"));
53542
53717
  } catch (e) {
53543
53718
  rmSync13(tmp, { force: true });
53544
53719
  throw e;
@@ -53546,7 +53721,7 @@ function writeRunRecord(dir, record2) {
53546
53721
  return true;
53547
53722
  }
53548
53723
  function removeRunRecord(dir, owner) {
53549
- const root = join56(dir, ".himi");
53724
+ const root = join57(dir, ".himi");
53550
53725
  try {
53551
53726
  if (!lstatSync10(root).isDirectory()) return;
53552
53727
  } catch {
@@ -53561,16 +53736,16 @@ function removeRunRecord(dir, owner) {
53561
53736
  }
53562
53737
  if (typeof pid === "number" && pid !== owner) return;
53563
53738
  }
53564
- rmSync13(join56(root, "run.json"), { force: true });
53739
+ rmSync13(join57(root, "run.json"), { force: true });
53565
53740
  }
53566
53741
  function boardFile(dir, pathname) {
53567
53742
  let rel = null;
53568
- if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join56("studio", "index.html");
53569
- else if (pathname === "/__himi/studio/state.js") rel = join56("studio", "state.js");
53570
- else if (pathname === "/__himi/studio/timeline.js") rel = join56("studio", "timeline.js");
53743
+ if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join57("studio", "index.html");
53744
+ else if (pathname === "/__himi/studio/state.js") rel = join57("studio", "state.js");
53745
+ else if (pathname === "/__himi/studio/timeline.js") rel = join57("studio", "timeline.js");
53571
53746
  else {
53572
53747
  const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
53573
- if (m) rel = join56("history", m[1]);
53748
+ if (m) rel = join57("history", m[1]);
53574
53749
  }
53575
53750
  if (!rel) return null;
53576
53751
  const bytes2 = readConfined(resolve42(dir, ".himi"), rel);
@@ -53586,7 +53761,7 @@ function readConfined(root, rel) {
53586
53761
  const parts = rel.split(sep13);
53587
53762
  let at = root;
53588
53763
  for (let i = 0; i < parts.length; i++) {
53589
- at = join56(at, parts[i]);
53764
+ at = join57(at, parts[i]);
53590
53765
  const st = lstatSync10(at);
53591
53766
  if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
53592
53767
  }
@@ -53595,7 +53770,7 @@ function readConfined(root, rel) {
53595
53770
  const fd = openSync2(at, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
53596
53771
  try {
53597
53772
  if (!fstatSync(fd).isFile()) return null;
53598
- return readFileSync63(fd);
53773
+ return readFileSync64(fd);
53599
53774
  } finally {
53600
53775
  closeSync2(fd);
53601
53776
  }
@@ -53647,7 +53822,7 @@ __export(run_browser_exports, {
53647
53822
  runInBrowser: () => runInBrowser
53648
53823
  });
53649
53824
  import { createServer as createServer5 } from "node:http";
53650
- import { existsSync as existsSync58, readFileSync as readFileSync64 } from "node:fs";
53825
+ import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
53651
53826
  import { spawnSync as spawnSync3 } from "node:child_process";
53652
53827
  import { dirname as dirname30, resolve as resolve43 } from "node:path";
53653
53828
  import { fileURLToPath as fileURLToPath23 } from "node:url";
@@ -53655,7 +53830,7 @@ function stagedDir() {
53655
53830
  return process.env.HIMI_BROWSER_PLANE_OUT ? resolve43(process.env.HIMI_BROWSER_PLANE_OUT) : resolve43(REPO, "packages", "serve", "dist", "browser-plane");
53656
53831
  }
53657
53832
  function missingStaged() {
53658
- return [...SERVED].filter((f) => !existsSync58(resolve43(stagedDir(), f))).sort();
53833
+ return [...SERVED].filter((f) => !existsSync59(resolve43(stagedDir(), f))).sort();
53659
53834
  }
53660
53835
  function ensureBrowserPlaneStaged() {
53661
53836
  if (missingStaged().length === 0) return { ok: true, detail: "already staged" };
@@ -53740,7 +53915,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53740
53915
  }
53741
53916
  const name = path2 === "/" || path2 === "/index.html" ? "host.html" : path2.slice(1);
53742
53917
  if (SERVED.has(name)) {
53743
- if (!existsSync58(resolve43(stagedDir(), name))) {
53918
+ if (!existsSync59(resolve43(stagedDir(), name))) {
53744
53919
  const restaged = ensureBrowserPlaneStaged();
53745
53920
  if (!restaged.ok) {
53746
53921
  res.writeHead(503, { "content-type": "application/json" });
@@ -53750,7 +53925,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53750
53925
  }
53751
53926
  let bytes2;
53752
53927
  try {
53753
- bytes2 = readFileSync64(resolve43(stagedDir(), name));
53928
+ bytes2 = readFileSync65(resolve43(stagedDir(), name));
53754
53929
  } catch (e) {
53755
53930
  res.writeHead(503, { "content-type": "application/json" });
53756
53931
  res.end(JSON.stringify({ error: "browser_plane_unstaged", file: name, detail: String(e) }));
@@ -54211,11 +54386,11 @@ __export(preflight_exports, {
54211
54386
  parseStoreConfig: () => parseStoreConfig,
54212
54387
  requireEnv: () => requireEnv
54213
54388
  });
54214
- import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
54215
- import { isAbsolute as isAbsolute3, relative as relative10, resolve as resolve44 } from "node:path";
54389
+ import { existsSync as existsSync60, readFileSync as readFileSync66 } from "node:fs";
54390
+ import { isAbsolute as isAbsolute5, relative as relative11, resolve as resolve44 } from "node:path";
54216
54391
  function isInsideRepo(p) {
54217
- const rel = relative10(REPO_ROOT3, resolve44(p));
54218
- return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
54392
+ const rel = relative11(REPO_ROOT3, resolve44(p));
54393
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute5(rel);
54219
54394
  }
54220
54395
  function assertCredentialOutsideRepo(label2, p) {
54221
54396
  if (isInsideRepo(p)) {
@@ -54268,10 +54443,10 @@ function parseStoreConfig(text5) {
54268
54443
  }
54269
54444
  function loadStoreConfig(app) {
54270
54445
  const p = storeConfigPath(app);
54271
- if (!existsSync59(p)) {
54446
+ if (!existsSync60(p)) {
54272
54447
  throw new Error(`No store config at ${p}. Create apps/${app}/store/store.config.yaml first (see /setup-store-signing).`);
54273
54448
  }
54274
- return parseStoreConfig(readFileSync65(p, "utf8"));
54449
+ return parseStoreConfig(readFileSync66(p, "utf8"));
54275
54450
  }
54276
54451
  var init_preflight = __esm({
54277
54452
  "../store-release/preflight.ts"() {
@@ -54290,19 +54465,19 @@ __export(credential_profile_exports, {
54290
54465
  profilePath: () => profilePath,
54291
54466
  resolveStoreCredentials: () => resolveStoreCredentials
54292
54467
  });
54293
- import { existsSync as existsSync60, readFileSync as readFileSync66 } from "node:fs";
54468
+ import { existsSync as existsSync61, readFileSync as readFileSync67 } from "node:fs";
54294
54469
  import { homedir as homedir5 } from "node:os";
54295
- import { join as join57 } from "node:path";
54470
+ import { join as join58 } from "node:path";
54296
54471
  function credentialsDir(home = homedir5()) {
54297
- return join57(home, ".himalaya", "store-credentials");
54472
+ return join58(home, ".himalaya", "store-credentials");
54298
54473
  }
54299
54474
  function profilePath(name, home = homedir5()) {
54300
54475
  if (!PROFILE_NAME.test(name)) {
54301
54476
  throw new Error(
54302
- `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join57(credentialsDir(home), "<name>.json")}; it is never a path.`
54477
+ `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join58(credentialsDir(home), "<name>.json")}; it is never a path.`
54303
54478
  );
54304
54479
  }
54305
- return join57(credentialsDir(home), `${name}.json`);
54480
+ return join58(credentialsDir(home), `${name}.json`);
54306
54481
  }
54307
54482
  function parseProfile(text5, label2) {
54308
54483
  let doc;
@@ -54333,8 +54508,8 @@ function resolveStoreCredentials(opts) {
54333
54508
  const { app, cfg } = opts;
54334
54509
  const env = opts.env ?? process.env;
54335
54510
  const home = opts.home ?? homedir5();
54336
- const read = opts.readProfile ?? ((p) => readFileSync66(p, "utf8"));
54337
- const exists = opts.profileExists ?? existsSync60;
54511
+ const read = opts.readProfile ?? ((p) => readFileSync67(p, "utf8"));
54512
+ const exists = opts.profileExists ?? existsSync61;
54338
54513
  const ios = cfg.ios ?? {};
54339
54514
  const flagName = opts.profileFlag?.trim();
54340
54515
  const named = namedProfile(cfg, opts.profileFlag);
@@ -54414,9 +54589,9 @@ __export(preflight_app_exports, {
54414
54589
  preflightApp: () => preflightApp,
54415
54590
  profileAccount: () => profileAccount
54416
54591
  });
54417
- import { readFileSync as readFileSync67, existsSync as existsSync61, readdirSync as readdirSync29, statSync as statSync15 } from "node:fs";
54592
+ import { readFileSync as readFileSync68, existsSync as existsSync62, readdirSync as readdirSync29, statSync as statSync15 } from "node:fs";
54418
54593
  import { createHash as createHash27 } from "node:crypto";
54419
- import { join as join58 } from "node:path";
54594
+ import { join as join59 } from "node:path";
54420
54595
  function analyze(projectYml, storeConfig, icon, account) {
54421
54596
  const f = [];
54422
54597
  const has = (re, s) => re.test(s);
@@ -54480,13 +54655,13 @@ function profileAccount(app, storeConfig, opts = {}) {
54480
54655
  }
54481
54656
  }
54482
54657
  async function preflightApp(app, opts = {}) {
54483
- const projectYml = existsSync61(iosProjectYml(app)) ? readFileSync67(iosProjectYml(app), "utf8") : "";
54484
- const storeConfig = existsSync61(storeConfigPath(app)) ? readFileSync67(storeConfigPath(app), "utf8") : "";
54658
+ const projectYml = existsSync62(iosProjectYml(app)) ? readFileSync68(iosProjectYml(app), "utf8") : "";
54659
+ const storeConfig = existsSync62(storeConfigPath(app)) ? readFileSync68(storeConfigPath(app), "utf8") : "";
54485
54660
  if (!storeConfig) return [{ level: "error", message: `no apps/${app}/store/store.config.yaml \u2014 run /setup-store-signing first.` }];
54486
54661
  const findings = analyze(
54487
54662
  projectYml,
54488
54663
  storeConfig,
54489
- await appIconState(join58(appDir(app), "ios")),
54664
+ await appIconState(join59(appDir(app), "ios")),
54490
54665
  profileAccount(app, storeConfig, opts)
54491
54666
  );
54492
54667
  findings.push(...analyzeListing(metadataFilesPresent(app), listScreenshots(app)));
@@ -54515,17 +54690,17 @@ function analyzeSubmissionRecord(appLevel, review, opts) {
54515
54690
  return f;
54516
54691
  }
54517
54692
  function appLevelMetadataFiles(app, locale = "en-US") {
54518
- const root = join58(storeDir(app), "metadata");
54693
+ const root = join59(storeDir(app), "metadata");
54519
54694
  const out = /* @__PURE__ */ new Set();
54520
- if (existsSync61(root)) {
54695
+ if (existsSync62(root)) {
54521
54696
  for (const n of readdirSync29(root)) if (n.endsWith(".txt")) out.add(n);
54522
54697
  }
54523
- if (existsSync61(join58(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
54698
+ if (existsSync62(join59(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
54524
54699
  return out;
54525
54700
  }
54526
54701
  function reviewInfoFiles(app) {
54527
- const dir = join58(storeDir(app), "review_information");
54528
- if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54702
+ const dir = join59(storeDir(app), "review_information");
54703
+ if (!existsSync62(dir)) return /* @__PURE__ */ new Set();
54529
54704
  return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
54530
54705
  }
54531
54706
  function isValidAppStoreSize(w, h) {
@@ -54635,12 +54810,12 @@ function analyzePlayListing(assets) {
54635
54810
  return f;
54636
54811
  }
54637
54812
  function playListingAssets(app, locale = "en-US") {
54638
- const dir = join58(storeDir(app), "play", locale, "images");
54813
+ const dir = join59(storeDir(app), "play", locale, "images");
54639
54814
  const read = (names) => {
54640
54815
  for (const name of names) {
54641
- const p = join58(dir, name);
54816
+ const p = join59(dir, name);
54642
54817
  try {
54643
- if (statSync15(p).isFile()) return { name, bytes: readFileSync67(p) };
54818
+ if (statSync15(p).isFile()) return { name, bytes: readFileSync68(p) };
54644
54819
  } catch {
54645
54820
  }
54646
54821
  }
@@ -54671,26 +54846,26 @@ function analyzeListing(metaFiles, shots) {
54671
54846
  return f;
54672
54847
  }
54673
54848
  function metadataFilesPresent(app, locale = "en-US") {
54674
- const dir = join58(storeDir(app), "metadata", locale);
54675
- if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54849
+ const dir = join59(storeDir(app), "metadata", locale);
54850
+ if (!existsSync62(dir)) return /* @__PURE__ */ new Set();
54676
54851
  return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
54677
54852
  }
54678
54853
  function listScreenshots(app, locale = "en-US") {
54679
54854
  const out = [];
54680
- const flat = join58(storeDir(app), "screenshots", locale);
54681
- if (existsSync61(flat)) {
54855
+ const flat = join59(storeDir(app), "screenshots", locale);
54856
+ if (existsSync62(flat)) {
54682
54857
  for (const png of readdirSync29(flat).filter((n) => n.endsWith(".png"))) {
54683
- const sz = pngSize(readFileSync67(join58(flat, png)));
54858
+ const sz = pngSize(readFileSync68(join59(flat, png)));
54684
54859
  if (sz) out.push({ name: png, w: sz.w, h: sz.h });
54685
54860
  }
54686
54861
  }
54687
- const legacy = join58(storeDir(app), "metadata", locale, "screenshots");
54688
- if (existsSync61(legacy)) {
54862
+ const legacy = join59(storeDir(app), "metadata", locale, "screenshots");
54863
+ if (existsSync62(legacy)) {
54689
54864
  for (const deviceDir of readdirSync29(legacy)) {
54690
- const d = join58(legacy, deviceDir);
54865
+ const d = join59(legacy, deviceDir);
54691
54866
  if (!statSync15(d).isDirectory()) continue;
54692
54867
  for (const png of readdirSync29(d).filter((n) => n.endsWith(".png"))) {
54693
- const sz = pngSize(readFileSync67(join58(d, png)));
54868
+ const sz = pngSize(readFileSync68(join59(d, png)));
54694
54869
  if (sz) out.push({ name: `${deviceDir}/${png}`, w: sz.w, h: sz.h });
54695
54870
  }
54696
54871
  }
@@ -54698,13 +54873,13 @@ function listScreenshots(app, locale = "en-US") {
54698
54873
  return out;
54699
54874
  }
54700
54875
  function placeholderHashes2() {
54701
- const templateIos = join58(appDir("_template"), "ios");
54702
- if (!existsSync61(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54876
+ const templateIos = join59(appDir("_template"), "ios");
54877
+ if (!existsSync62(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54703
54878
  for (const target of readdirSync29(templateIos)) {
54704
- const p = join58(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54879
+ const p = join59(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54705
54880
  try {
54706
54881
  if (!statSync15(p).isFile()) continue;
54707
- return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync67(p)).digest("hex")];
54882
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync68(p)).digest("hex")];
54708
54883
  } catch {
54709
54884
  }
54710
54885
  }
@@ -54735,12 +54910,12 @@ async function pixelIssues(bytes2) {
54735
54910
  }
54736
54911
  }
54737
54912
  async function appIconState(iosDir) {
54738
- if (!existsSync61(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54913
+ if (!existsSync62(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54739
54914
  for (const target of readdirSync29(iosDir)) {
54740
- const p = join58(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54915
+ const p = join59(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54741
54916
  try {
54742
54917
  if (!statSync15(p).isFile()) continue;
54743
- const bytes2 = readFileSync67(p);
54918
+ const bytes2 = readFileSync68(p);
54744
54919
  const hash = createHash27("sha256").update(bytes2).digest("hex");
54745
54920
  const pixels = await pixelIssues(bytes2);
54746
54921
  return {
@@ -54812,7 +54987,7 @@ __export(version_exports, {
54812
54987
  setAndroidVersion: () => setAndroidVersion,
54813
54988
  setIosVersion: () => setIosVersion
54814
54989
  });
54815
- import { readFileSync as readFileSync68, writeFileSync as writeFileSync24 } from "node:fs";
54990
+ import { readFileSync as readFileSync69, writeFileSync as writeFileSync24 } from "node:fs";
54816
54991
  function readIosVersion(projectYml) {
54817
54992
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
54818
54993
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -54870,8 +55045,8 @@ function setAndroidVersion(gradle, v) {
54870
55045
  function bumpVersion(app, opts) {
54871
55046
  const iosPath = iosProjectYml(app);
54872
55047
  const gradlePath = androidGradle(app);
54873
- const iosYml = readFileSync68(iosPath, "utf8");
54874
- const gradle = readFileSync68(gradlePath, "utf8");
55048
+ const iosYml = readFileSync69(iosPath, "utf8");
55049
+ const gradle = readFileSync69(gradlePath, "utf8");
54875
55050
  const current = readIosVersion(iosYml);
54876
55051
  const next = {
54877
55052
  marketing: opts.marketing ?? current.marketing,
@@ -54888,7 +55063,7 @@ var init_version = __esm({
54888
55063
  });
54889
55064
 
54890
55065
  // ../store-release/register-bundle.ts
54891
- import { readFileSync as readFileSync69 } from "node:fs";
55066
+ import { readFileSync as readFileSync70 } from "node:fs";
54892
55067
  import { createPrivateKey as createPrivateKey2, sign } from "node:crypto";
54893
55068
  function b64url2(b) {
54894
55069
  return Buffer.from(b).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -54906,7 +55081,7 @@ function bundleIdBody(identifier, name, platform = "IOS") {
54906
55081
  async function registerBundleId(opts) {
54907
55082
  const env = requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54908
55083
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54909
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync69(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
55084
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync70(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54910
55085
  const res = await fetch(`${ASC_API}/v1/bundleIds`, {
54911
55086
  method: "POST",
54912
55087
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
@@ -54960,7 +55135,7 @@ __export(asc_status_exports, {
54960
55135
  ascStatus: () => ascStatus,
54961
55136
  buildVerdict: () => buildVerdict
54962
55137
  });
54963
- import { readFileSync as readFileSync70 } from "node:fs";
55138
+ import { readFileSync as readFileSync71 } from "node:fs";
54964
55139
  function buildVerdict(builds) {
54965
55140
  if (!builds.length) {
54966
55141
  return "NO builds registered \u2014 if you uploaded recently and it's been >15 min, the binary was REJECTED IN PROCESSING (reason is email-only; run validate-binary.ts / scan-purpose-strings.ts to reproduce it).";
@@ -54974,7 +55149,7 @@ function buildVerdict(builds) {
54974
55149
  function client(creds) {
54975
55150
  const env = creds ?? requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54976
55151
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54977
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync70(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
55152
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync71(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54978
55153
  return async (path2) => {
54979
55154
  const r = await fetch(`${ASC}${path2}`, { headers: { Authorization: `Bearer ${jwt}` } });
54980
55155
  return { status: r.status, json: await r.json().catch(() => ({})) };
@@ -55134,7 +55309,7 @@ __export(secrets_exports, {
55134
55309
  runRemoteSecretsCommand: () => runRemoteSecretsCommand,
55135
55310
  runSecretsCommand: () => runSecretsCommand
55136
55311
  });
55137
- import { readFileSync as readFileSync71 } from "node:fs";
55312
+ import { readFileSync as readFileSync72 } from "node:fs";
55138
55313
  function environment(value) {
55139
55314
  if (value === "preview" || value === "production") return value;
55140
55315
  throw new Error("--environment must be preview or production");
@@ -55165,7 +55340,7 @@ async function runSecretsCommand(operation, flags, io, options = {}) {
55165
55340
  io.log(JSON.stringify(await vault.list({ app, environment: env })));
55166
55341
  return 0;
55167
55342
  }
55168
- const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
55343
+ const value = options.readValue ? options.readValue() : readFileSync72(0, "utf8").replace(/\r?\n$/, "");
55169
55344
  if (operation === "set") await vault.set({ app, environment: env, name, value });
55170
55345
  else if (operation === "rotate") await vault.rotate({ app, environment: env, name, value });
55171
55346
  else throw new Error(`unknown secrets operation ${operation}`);
@@ -55194,7 +55369,7 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
55194
55369
  }
55195
55370
  let body;
55196
55371
  if (operation !== "list") {
55197
- const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
55372
+ const value = options.readValue ? options.readValue() : readFileSync72(0, "utf8").replace(/\r?\n$/, "");
55198
55373
  body = JSON.stringify({ value });
55199
55374
  }
55200
55375
  const response = await (options.fetchImpl ?? fetch)(url, { method: operation === "list" ? "GET" : operation === "set" ? "PUT" : "POST", headers: headers2, body });
@@ -55230,15 +55405,15 @@ __export(notify_exports, {
55230
55405
  runNotify: () => runNotify,
55231
55406
  saveAdminSession: () => saveAdminSession
55232
55407
  });
55233
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync72, writeFileSync as writeFileSync25 } from "node:fs";
55408
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync73, writeFileSync as writeFileSync25 } from "node:fs";
55234
55409
  import { homedir as homedir6 } from "node:os";
55235
- import { dirname as dirname32, join as join59 } from "node:path";
55410
+ import { dirname as dirname32, join as join60 } from "node:path";
55236
55411
  function defaultSessionsPath() {
55237
- return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join59(homedir6(), ".himalaya", "admin-sessions.json");
55412
+ return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join60(homedir6(), ".himalaya", "admin-sessions.json");
55238
55413
  }
55239
55414
  function loadSessionsFile(path2 = defaultSessionsPath()) {
55240
55415
  try {
55241
- return JSON.parse(readFileSync72(path2, "utf8"));
55416
+ return JSON.parse(readFileSync73(path2, "utf8"));
55242
55417
  } catch {
55243
55418
  return {};
55244
55419
  }
@@ -56368,7 +56543,7 @@ function deriveAdoptedCmsCollections(cms) {
56368
56543
  return { collections, dropped };
56369
56544
  }
56370
56545
  function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56371
- const appName = deriveAppName(ctx, overrides.appName);
56546
+ const appName2 = deriveAppName(ctx, overrides.appName);
56372
56547
  const supportUrl = clean(overrides.supportUrl) || clean(ctx.url.primary);
56373
56548
  const privacyUrl = clean(overrides.privacyUrl);
56374
56549
  const termsUrl = clean(overrides.termsUrl);
@@ -56388,8 +56563,8 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56388
56563
  // empty; either way the key exists and the shell's list never survives the merge.
56389
56564
  cmsCollections: [],
56390
56565
  identity: {
56391
- appName,
56392
- businessName: clean(ctx.identity.businessName) || appName,
56566
+ appName: appName2,
56567
+ businessName: clean(ctx.identity.businessName) || appName2,
56393
56568
  tagline: clean(overrides.tagline) || clean(ctx.identity.description),
56394
56569
  // "" means UNKNOWN here, with `missing` above as the signal — the published overlay's
56395
56570
  // documented shape. Keeping an unknown url out of a MERGE is `apply.ts`'s job, at the
@@ -56399,7 +56574,7 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56399
56574
  termsUrl
56400
56575
  }
56401
56576
  };
56402
- return { appName, iconSource: deriveIconSource(ctx), provisioning, missing };
56577
+ return { appName: appName2, iconSource: deriveIconSource(ctx), provisioning, missing };
56403
56578
  }
56404
56579
  function bounded(s, max) {
56405
56580
  if (s.length <= max) return s;
@@ -58495,11 +58670,11 @@ function crawledPageName(look, page) {
58495
58670
  const hit = (page?.pageId ? sitePages.find((p) => p.pageId === page.pageId) : void 0) ?? (look.mainPageId ? sitePages.find((p) => p.pageId === look.mainPageId) : void 0) ?? sitePages.find((p) => p.slug === "home");
58496
58671
  return hit?.title.trim() ?? "";
58497
58672
  }
58498
- function guessedNameWarning(appName, crawledTitle, ownerLookupError) {
58499
- const from = crawledTitle && crawledTitle !== appName ? ` off the crawled page title "${crawledTitle}"` : " from the site crawl";
58673
+ function guessedNameWarning(appName2, crawledTitle, ownerLookupError) {
58674
+ const from = crawledTitle && crawledTitle !== appName2 ? ` off the crawled page title "${crawledTitle}"` : " from the site crawl";
58500
58675
  const why = ownerLookupError ? `the owner site-properties read FAILED (${ownerLookupError}), so the owner-entered name was never read` : "no owner token was available, so site-properties was never read";
58501
58676
  const remedy = ownerLookupError ? "resolve that failure and re-run the same owner-authenticated command" : "re-run with owner auth (`himi site analyze --site <msid>`)";
58502
- return `identity.appName "${appName}" is a GUESS, read${from} rather than from the owner-entered business name \u2014 ${why}. --apply writes it into provisioning.json as-is and it reaches both app stores verbatim, so check it: ${remedy}, or set identity.appName in provisioning.json yourself.`;
58677
+ return `identity.appName "${appName2}" is a GUESS, read${from} rather than from the owner-entered business name \u2014 ${why}. --apply writes it into provisioning.json as-is and it reaches both app stores verbatim, so check it: ${remedy}, or set identity.appName in provisioning.json yourself.`;
58503
58678
  }
58504
58679
  function mergeLookFromPublished(look, published, siteUrl) {
58505
58680
  const sitePages = withPageUrls(
@@ -59007,7 +59182,7 @@ var init_brand_font = __esm({
59007
59182
 
59008
59183
  // src/site-analyze/write-artifacts.ts
59009
59184
  import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
59010
- import { join as join60 } from "node:path";
59185
+ import { join as join61 } from "node:path";
59011
59186
  function attachVeloSources(book, sources, put) {
59012
59187
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
59013
59188
  for (const f of book.velo.files) {
@@ -59162,12 +59337,12 @@ async function downloadMedia(book, fetchImpl, put) {
59162
59337
  }
59163
59338
  async function writeBusinessBook(result, opts) {
59164
59339
  const out = opts.outDir;
59165
- mkdirSync29(join60(out, "evidence/pages"), { recursive: true });
59166
- mkdirSync29(join60(out, "media/catalog"), { recursive: true });
59340
+ mkdirSync29(join61(out, "evidence/pages"), { recursive: true });
59341
+ mkdirSync29(join61(out, "media/catalog"), { recursive: true });
59167
59342
  const files = [];
59168
59343
  const put = (rel, body) => {
59169
- const p = join60(out, rel);
59170
- mkdirSync29(join60(p, ".."), { recursive: true });
59344
+ const p = join61(out, rel);
59345
+ mkdirSync29(join61(p, ".."), { recursive: true });
59171
59346
  writeFileSync26(p, body);
59172
59347
  files.push(rel);
59173
59348
  };
@@ -59261,8 +59436,8 @@ var init_customer_build = __esm({
59261
59436
  });
59262
59437
 
59263
59438
  // src/site-analyze/apply.ts
59264
- import { existsSync as existsSync62, readFileSync as readFileSync73, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
59265
- import { dirname as dirname33, isAbsolute as isAbsolute4, join as join61, resolve as resolve45, sep as sep14 } from "node:path";
59439
+ import { existsSync as existsSync63, readFileSync as readFileSync74, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
59440
+ import { dirname as dirname33, isAbsolute as isAbsolute6, join as join62, resolve as resolve45, sep as sep14 } from "node:path";
59266
59441
  import { spawnSync as spawnSync4 } from "node:child_process";
59267
59442
  function unpairedRedirectNote(overlay) {
59268
59443
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
@@ -59277,7 +59452,7 @@ function committedShellProvisioning(path2) {
59277
59452
  function findRepoRoot(cwd) {
59278
59453
  let dir = cwd;
59279
59454
  for (let i = 0; i < 8; i++) {
59280
- if (SHELL_NAMES2.some((name) => existsSync62(join61(dir, shellAppDir(name), "package.json")))) return dir;
59455
+ if (SHELL_NAMES2.some((name) => existsSync63(join62(dir, shellAppDir(name), "package.json")))) return dir;
59281
59456
  const next = dirname33(dir);
59282
59457
  if (next === dir) break;
59283
59458
  dir = next;
@@ -59346,24 +59521,24 @@ function mergeCustomerOwned(committed, overlay) {
59346
59521
  }
59347
59522
  function buildMergedContract(options) {
59348
59523
  const { appDir: appDir2, packageDir, overlay } = options;
59349
- const committedPath = join61(appDir2, "provisioning.json");
59350
- if (!existsSync62(committedPath)) {
59524
+ const committedPath = join62(appDir2, "provisioning.json");
59525
+ if (!existsSync63(committedPath)) {
59351
59526
  throw new Error(`shell has no committed provisioning.json at ${committedPath} to build a contract from`);
59352
59527
  }
59353
- const committed = JSON.parse(readFileSync73(committedPath, "utf8"));
59528
+ const committed = JSON.parse(readFileSync74(committedPath, "utf8"));
59354
59529
  const merged = mergeCustomerOwned(committed, overlay);
59355
59530
  const overlayFontFamily = (overlay.branding ?? {}).fontFamily;
59356
59531
  const branding = merged.branding ?? {};
59357
59532
  const staged = [];
59358
59533
  const contained = (root, rel) => {
59359
- if (isAbsolute4(rel)) return void 0;
59534
+ if (isAbsolute6(rel)) return void 0;
59360
59535
  const full = resolve45(root, rel);
59361
59536
  return full.startsWith(resolve45(root) + sep14) ? full : void 0;
59362
59537
  };
59363
59538
  const stage = (fromRel, toRel) => {
59364
59539
  const from = contained(packageDir, fromRel);
59365
- const to = contained(join61(appDir2, STAGED), toRel.slice(STAGED.length + 1));
59366
- if (!from || !to || !existsSync62(from)) return void 0;
59540
+ const to = contained(join62(appDir2, STAGED), toRel.slice(STAGED.length + 1));
59541
+ if (!from || !to || !existsSync63(from)) return void 0;
59367
59542
  let real;
59368
59543
  let realRoot;
59369
59544
  try {
@@ -59385,11 +59560,11 @@ function buildMergedContract(options) {
59385
59560
  };
59386
59561
  const icon = stage("media/icon.png", `${STAGED}/icon.png`);
59387
59562
  if (icon) branding.appIconPath = icon;
59388
- const facesPath = join61(packageDir, "media/fonts/faces.json");
59389
- if (existsSync62(facesPath)) {
59563
+ const facesPath = join62(packageDir, "media/fonts/faces.json");
59564
+ if (existsSync63(facesPath)) {
59390
59565
  let faces = [];
59391
59566
  try {
59392
- const parsed = JSON.parse(readFileSync73(facesPath, "utf8"));
59567
+ const parsed = JSON.parse(readFileSync74(facesPath, "utf8"));
59393
59568
  faces = Array.isArray(parsed) ? parsed : [];
59394
59569
  } catch {
59395
59570
  faces = [];
@@ -59410,9 +59585,9 @@ function buildMergedContract(options) {
59410
59585
  merged.branding = branding;
59411
59586
  const body = `${JSON.stringify(merged, null, 2)}
59412
59587
  `;
59413
- const path2 = join61(packageDir, "provisioning.contract.json");
59588
+ const path2 = join62(packageDir, "provisioning.contract.json");
59414
59589
  writeFileSync27(path2, body);
59415
- const localPath = join61(appDir2, LOCAL_CONTRACT_REL);
59590
+ const localPath = join62(appDir2, LOCAL_CONTRACT_REL);
59416
59591
  mkdirSync30(dirname33(localPath), { recursive: true });
59417
59592
  writeFileSync27(localPath, body);
59418
59593
  staged.push(localPath);
@@ -59447,10 +59622,10 @@ function catalogTypography(overlay, template) {
59447
59622
  function staticFaqFor(overlay, overlayPath) {
59448
59623
  const own3 = overlay.faq;
59449
59624
  if (own3 !== void 0) return deriveStaticFaq(own3);
59450
- const bookPath = join61(dirname33(resolve45(overlayPath)), "book.json");
59451
- if (!existsSync62(bookPath)) return void 0;
59625
+ const bookPath = join62(dirname33(resolve45(overlayPath)), "book.json");
59626
+ if (!existsSync63(bookPath)) return void 0;
59452
59627
  try {
59453
- const book = JSON.parse(readFileSync73(bookPath, "utf8"));
59628
+ const book = JSON.parse(readFileSync74(bookPath, "utf8"));
59454
59629
  const copy = book?.copy;
59455
59630
  if (!copy || typeof copy !== "object" || copy.unavailable) return void 0;
59456
59631
  const pairs = deriveStaticFaq(copy.faq);
@@ -59464,8 +59639,8 @@ async function applyOverlays(input) {
59464
59639
  const cwd = resolve45(input.cwd);
59465
59640
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
59466
59641
  assertKnownShell(shell);
59467
- const overlay = JSON.parse(readFileSync73(input.overlayPath, "utf8"));
59468
- const tokens = JSON.parse(readFileSync73(input.tokensPath, "utf8"));
59642
+ const overlay = JSON.parse(readFileSync74(input.overlayPath, "utf8"));
59643
+ const tokens = JSON.parse(readFileSync74(input.tokensPath, "utf8"));
59469
59644
  const faq = staticFaqFor(overlay, input.overlayPath);
59470
59645
  if (faq !== void 0) overlay.faq = faq;
59471
59646
  const wrote = [];
@@ -59477,9 +59652,9 @@ async function applyOverlays(input) {
59477
59652
  `shell "${shell}" cannot bake a customer brand \u2014 it ships no prepare:customer script`
59478
59653
  );
59479
59654
  }
59480
- const appDir2 = join61(repoRoot2, shellAppDir(shell));
59481
- const committedPath = join61(appDir2, "provisioning.json");
59482
- const committedSite = existsSync62(committedPath) ? JSON.parse(readFileSync73(committedPath, "utf8")).site : void 0;
59655
+ const appDir2 = join62(repoRoot2, shellAppDir(shell));
59656
+ const committedPath = join62(appDir2, "provisioning.json");
59657
+ const committedSite = existsSync63(committedPath) ? JSON.parse(readFileSync74(committedPath, "utf8")).site : void 0;
59483
59658
  if (committedSite !== void 0 && overlay.site === void 0) {
59484
59659
  throw new Error(
59485
59660
  `shell "${shell}" is a site shell: its contract names the customer's website (a \`site\` block), which this analysis does not produce yet. Write the site block into a contract and run \`npm run prepare:customer -- --contract <file>\` in ${shellAppDir(shell)} (or \`himi dist --shell ${shell}\`).`
@@ -59506,11 +59681,11 @@ async function applyOverlays(input) {
59506
59681
  note: `prepare:customer --contract with a merged contract (committed provisioning.json untouched)${unpairedInRepo ? ` \xB7 ${unpairedInRepo}` : ""}`
59507
59682
  };
59508
59683
  }
59509
- const pkgProv = join61(cwd, "provisioning.json");
59684
+ const pkgProv = join62(cwd, "provisioning.json");
59510
59685
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
59511
59686
  let packageFaq = faq;
59512
- if (existsSync62(pkgProv)) {
59513
- const cur = JSON.parse(readFileSync73(pkgProv, "utf8"));
59687
+ if (existsSync63(pkgProv)) {
59688
+ const cur = JSON.parse(readFileSync74(pkgProv, "utf8"));
59514
59689
  const merged = deepMerge4(cur, overlay);
59515
59690
  packageFaq = merged.faq;
59516
59691
  writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
@@ -59519,9 +59694,9 @@ async function applyOverlays(input) {
59519
59694
  writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
59520
59695
  wrote.push(pkgProv);
59521
59696
  }
59522
- const pkgTokens = join61(cwd, "tokens.json");
59523
- if (existsSync62(pkgTokens) && (tokens.colors || tokens.typography)) {
59524
- const cur = JSON.parse(readFileSync73(pkgTokens, "utf8"));
59697
+ const pkgTokens = join62(cwd, "tokens.json");
59698
+ if (existsSync63(pkgTokens) && (tokens.colors || tokens.typography)) {
59699
+ const cur = JSON.parse(readFileSync74(pkgTokens, "utf8"));
59525
59700
  let tokensIn = tokens;
59526
59701
  if (tokens.typography) {
59527
59702
  const fonts = catalogTypography(tokens.typography, cur.typography);
@@ -59546,9 +59721,9 @@ async function applyOverlays(input) {
59546
59721
  wrote.push(pkgTokens);
59547
59722
  }
59548
59723
  const homeAction = homeRefreshActionId(shell);
59549
- const homeSrc = join61(cwd, "dev/index.ts");
59550
- if (homeAction && existsSync62(homeSrc)) {
59551
- const prev = readFileSync73(homeSrc, "utf8");
59724
+ const homeSrc = join62(cwd, "dev/index.ts");
59725
+ if (homeAction && existsSync63(homeSrc)) {
59726
+ const prev = readFileSync74(homeSrc, "utf8");
59552
59727
  const patched = patchBrandParams(prev, overlay, homeAction);
59553
59728
  let next = patched.src;
59554
59729
  if (!patched.foreign && faq?.length && prev.includes('"tier5.himiBrandedFaq.load"') && !prev.includes('"staticFaq"')) {
@@ -59579,13 +59754,13 @@ async function applyOverlays(input) {
59579
59754
  notes.push(`${patched.unparsed} onAppear params block(s) in ${homeSrc} could not be read \u2014 any brand values inside them were not applied`);
59580
59755
  }
59581
59756
  }
59582
- if (!input.iconPath || !existsSync62(input.iconPath)) {
59757
+ if (!input.iconPath || !existsSync63(input.iconPath)) {
59583
59758
  const book = input.iconPath ? dirname33(dirname33(resolve45(input.iconPath))) : void 0;
59584
59759
  notes.push(
59585
59760
  book ? `no icon installed \u2014 ${siteLogoAbsence(book).hint}` : "no icon installed and no business book to say why \u2014 `himi icon generate` draws one from the accent, or `himi icon set <file.png>` installs the customer's own mark"
59586
59761
  );
59587
59762
  }
59588
- if (input.iconPath && existsSync62(input.iconPath)) {
59763
+ if (input.iconPath && existsSync63(input.iconPath)) {
59589
59764
  try {
59590
59765
  const installed = await installIcon(cwd, input.iconPath, { derivePlay: true });
59591
59766
  wrote.push(...installed.wrote);
@@ -63772,7 +63947,7 @@ __export(cli_exports, {
63772
63947
  runSiteAnalyze: () => runSiteAnalyze,
63773
63948
  runSiteFunctionCli: () => runSiteFunctionCli
63774
63949
  });
63775
- import { readFileSync as readFileSync74 } from "node:fs";
63950
+ import { readFileSync as readFileSync75 } from "node:fs";
63776
63951
  import { resolve as resolve46 } from "node:path";
63777
63952
  function assetFetchFor(_opts) {
63778
63953
  return pinnedFetch2;
@@ -63871,7 +64046,7 @@ async function runSiteFunctionCli(opts) {
63871
64046
  let source;
63872
64047
  if (opts.sourcePath) {
63873
64048
  const p = resolve46(opts.cwd ?? process.cwd(), opts.sourcePath);
63874
- source = readFileSync74(p, "utf8");
64049
+ source = readFileSync75(p, "utf8");
63875
64050
  }
63876
64051
  let body;
63877
64052
  if (opts.bodyRaw != null) {
@@ -64130,7 +64305,7 @@ __export(site_create_exports, {
64130
64305
  runSiteList: () => runSiteList,
64131
64306
  writeProvisioningBinding: () => writeProvisioningBinding
64132
64307
  });
64133
- import { existsSync as existsSync63, readFileSync as readFileSync75, writeFileSync as writeFileSync28 } from "node:fs";
64308
+ import { existsSync as existsSync64, readFileSync as readFileSync76, writeFileSync as writeFileSync28 } from "node:fs";
64134
64309
  import { resolve as resolvePath2 } from "node:path";
64135
64310
  function oauthClientName(projectName) {
64136
64311
  return `${projectName} client`;
@@ -64279,7 +64454,7 @@ async function runSiteCreate(opts) {
64279
64454
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
64280
64455
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
64281
64456
  }
64282
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync63, readFileSync: readFileSync75, writeFileSync: writeFileSync28 }) {
64457
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync64, readFileSync: readFileSync76, writeFileSync: writeFileSync28 }) {
64283
64458
  const msid = binding2.siteId;
64284
64459
  const path2 = resolvePath2(target);
64285
64460
  const committed = committedShellProvisioning(path2);
@@ -64952,11 +65127,11 @@ var init_context = __esm({
64952
65127
  });
64953
65128
 
64954
65129
  // src/cli.ts
64955
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync76, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync64, rmSync as rmSync14, readdirSync as readdirSync30, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
65130
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync77, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync65, rmSync as rmSync14, readdirSync as readdirSync30, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
64956
65131
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
64957
65132
  import { createHash as createHash28, randomUUID as randomUUID5 } from "node:crypto";
64958
65133
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
64959
- import { join as join62, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
65134
+ import { join as join63, resolve as resolve47, relative as relative12, basename as basename16, dirname as dirname34 } from "node:path";
64960
65135
  import { fileURLToPath as fileURLToPath24 } from "node:url";
64961
65136
 
64962
65137
  // ../serve-cli/src/client.ts
@@ -67072,8 +67247,8 @@ var str6 = (v) => typeof v === "string" ? v : void 0;
67072
67247
  var num2 = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
67073
67248
  function embeddedTemplateCatalog() {
67074
67249
  return availableTemplates().map((name) => {
67075
- const path2 = join62(templateSourceDir(name), TEMPLATE_META_FILE);
67076
- const meta = JSON.parse(readFileSync76(path2, "utf8"));
67250
+ const path2 = join63(templateSourceDir(name), TEMPLATE_META_FILE);
67251
+ const meta = JSON.parse(readFileSync77(path2, "utf8"));
67077
67252
  if (!meta || typeof meta.title !== "string" || typeof meta.description !== "string" || !Array.isArray(meta.tags)) {
67078
67253
  throw new Error(`${path2} must be { title: string, description: string, tags: string[], category?: string }`);
67079
67254
  }
@@ -67103,7 +67278,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67103
67278
  scaffold a starter content package (--template <t> starts
67104
67279
  from a reference app; --remote fetches it off the serve plane),
67105
67280
  with the agent files inside it (AGENTS.md, CLAUDE.md, the himalaya
67106
- skill, a session-start hook); --no-agent skips them
67281
+ skill, a session-start hook); --no-agent skips them. Also makes
67282
+ the build board (no browser is opened), and its next[] leads with
67283
+ THIS session's steps: the live preview, the first picture, a postcard
67107
67284
  himi template list [--remote] list starter templates + metadata (title/description/tags) + each one's start prompt
67108
67285
  himi template prompt <name> print the prompt that starts an app from that template
67109
67286
  himi template publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted]
@@ -67694,7 +67871,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
67694
67871
  himi docs releasing-with-himi-serve`;
67695
67872
  var COMMANDS = [
67696
67873
  { name: "captcha", summary: "create and store native CAPTCHA keys in Serve, or inspect setup status; customer setup verifies Wix site ownership; does not enable member CAPTCHA SDKs", required: "setup|status --app <app> [--content <dir>] [--site <msid>] [--ios-bundle-id <id>] [--android-package-name <id>] [--target <t>]" },
67697
- { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] | --templates" },
67874
+ { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and its `next[]` LEADS with the steps for THIS session \u2014 `himi run --watch &`, the first `himi render <homeScreenId> --png --device iphone`, and that picture's `himi progress` postcard \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] | --templates" },
67698
67875
  { name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps)", required: "list [--remote] | prompt <name> | publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted] [--target <t>] | trim --keep <id,id,\u2026> [--content <dir>] [--links follow|drop] [--dry-run] [--force]" },
67699
67876
  { name: "build", summary: "compile tier5-src/<name>/index.ts -> dist/bundles/<name>.bundle.js with esbuild (--binary = resolve published @himalaya/*); semantic TypeScript checking belongs to himi validate", required: "[--content <dir>] [--binary]" },
67700
67877
  { name: "functions", summary: "inspect/invoke server functions and operate durable jobs or schedules", required: "list|describe|openapi|invoke | jobs list|get|cancel|retry|dlq | schedule describe|invoke [--app <a>] [--environment preview|production] [--release <id>]" },
@@ -67702,8 +67879,8 @@ var COMMANDS = [
67702
67879
  { name: "validate", summary: "lint the content package (no push): fixed-contract TypeScript checking for tier5-src workers + schema/proto + token refs + component allowlist + $service bindings + strict-enum foot-guns + capability decls + worker boot dry-run + build/compile failures. Type issues are advisory warnings normally; --strict promotes them and hygiene warnings to errors. Exit 1 on any error. Off-repo, the CLI supplies its compiler and SDK declarations.", required: "[--content <dir>] [--strict]" },
67703
67880
  { name: "test", summary: "DERIVED behavioral crawl \u2014 no test files to write. Boots each screen's real worker, replays its onAppear, then dispatches every action site the descriptor already declares (each from a fresh boot) and checks a narrow set of unambiguous invariants: no uncaught throw, never stuck on `loading`, no `$service:` token or NaN/undefined/[object Object] reaching visible text, no binding aimed at state the worker never defines, no nav param silently dropped, no dead nav target. Catches what validate/render cannot \u2014 a WRONG REDUCER passes both with ok:true. Runs the worker in a DEVICE-PARITY sandbox (no setTimeout/setInterval, as on JSC/WebView) so a bundle that would die on device fails here. Crawls on HERMETIC DATA by default \u2014 zero network: the app's own dev/net-mocks.json first (--net <file> overrides), then the config's CMS `seed:` rows, then a canned fixture of frozen real vertical responses shipped with the CLI (exercises code paths, not API-contract truth) \u2014 so the success/mapping path runs, not just the failure UI. Two boot-only HOSTILE passes (every list empty; every request 500) then check the failure/empty day \u2014 corpus-proven, so their findings are errors. NAV EDGES are followed too: every himi.open site ($item params resolved against row 0 of its list, $service against worker state) and worker _nav directive is harvested, and each baked target is booted WITH the params the source sends \u2014 detail screens finally run their content arm; edge findings are errors tagged edge:<source>. --native runs both real native decoders over the exact baked JSON bytes; --native ios or --native android selects one. It is explicit, runs only from a Himalaya SOURCE CHECKOUT (the decoder oracles are that tree's own HimalayaCore Swift package and Gradle harness, which a published install does not ship), and needs the selected local toolchain. From a source checkout, default runs name missing native verdicts in the coverage ledger; elsewhere the ledger stays silent about them rather than offering a remedy you cannot run. --offline strips all data layers (the old reject-all axis); --no-hostile skips the hostile passes. --record (EXPLICIT, never automatic) refreshes dev/net-mocks.json from live reads first: it boots each screen (load + root onAppear ONLY \u2014 nothing the app's own launch would not fire; the tap/action crawl never sees the live transport, and only proven reads \u2014 GET, or read-shaped POST (/query, /search, /list*, \u2026) \u2014 ever reach the wire), freezes the round-trips, then crawls hermetically on the fresh fixture; record against a QA site, not production. Nodes it cannot judge headless are named, never silently passed. Exit 1 on any error. The CLI carries the authoring runtime off-repo; no per-app SDK install is required. --browser runs the whole loop (build \u2192 extract \u2192 validate \u2192 render \u2192 test) in YOUR browser instead of here, so the source never reaches a pod: it opens one screen's worker + descriptor in a tab, blocks on a loopback callback, and prints the report it posts back. One screen per tab \u2014 name it. --no-open prints the URL rather than launching a browser (SSH); --timeout <ms> bounds the wait, which matters because the page has no execution deadline of its own and an infinite loop in the worker hangs the tab.", required: "[<screenId>\u2026] [--content <dir>] [--net <file>] [--budget <ms>] [--deep [n]] [--record] [--native [ios|android]] [--no-shots] [--offline] [--no-hostile] [--all-screens] [--browser] [--no-open] [--timeout <ms>] [--json]" },
67704
67881
  { name: "ux", summary: "audit or record mobile UX evidence. `audit` checks the public-safe contract, seeded blank acceptance rows, placeholder source copy, app-specific mocks, and iOS/Android audit records. It warns while existing templates migrate; `--strict` promotes missing evidence. `record` stores checks performed on an installed iOS/Android build: `--checks` is required for passed|failed and empty for unavailable, which drove nothing and needs a `--reason` instead. `record` also STAMPS the release it audited (it resolves the id from a build, the same one a push would publish), because an unforced `himi push` of a release with screens refuses when no drive is recorded for THAT release, or when the one that is recorded says `failed` \u2014 the artifact stopped being advice nobody reads (#2955). `unavailable` still satisfies it; only `failed` refuses.", required: "audit [--content <dir>] [--strict] [--json] | record --content <dir> --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...] [--reason <text>]" },
67705
- { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
67706
- { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push); --out to put it elsewhere.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
67882
+ { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals three milestones by itself \u2014 the first PNG render of the home screen (once per app), every completed `himi test` crawl, every draft push \u2014 marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
67883
+ { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command (or `himi init`, which makes the board for every app it creates, without opening a browser) has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push); --out to put it elsewhere.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
67707
67884
  { name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
67708
67885
  { name: "cms", summary: "inspect, provision, and seed the app's CMS (Wix Data) collections, declared as a `cms` block in config.ts. `query` is READ-ONLY \u2014 by default it fetches ONLY the physical ids `config.ts` declares (item counts + schema drift) and reports `siteScanned: false`; it never lists the site, so an UNDECLARED `himi_<app>_` collection this app owns is not reached without `--all`. `--all` SCANS the site \u2014 every collection it lists is labeled `owned`/`adopted`/`foreign`, and it describes every foreign one \u2014 id, non-system field keys + types, and the site's read role \u2014 so an app with no `cms` block at all can DISCOVER a collection to adopt (`--all` is the one case where a missing `cms` block is not 'nothing to do'). Foreign collections are described up to a cap and are never item-counted (`itemCount: null`), because a row count is one query each; `foreign.count` is how many the scan LISTED \u2014 it follows the list endpoint's pages, and `listingIncomplete: true` says when the walk stopped short of the end, with `unlistedCollections` counting what it left out when Wix gave a paging total \u2014 and `foreign.truncated` says how many of those went undescribed, each still NAMED in `foreign.others` (id + displayName), so a collection the site already has is never hidden behind the cap. The scan is OWNER-scoped \u2014 it needs --site <msid> (or WIX_SITE_ID) plus owner auth via `wix token`; a visitor token can read a public collection's ITEMS but can never enumerate collections, so there is no tokenless discovery path. `create` reconciles declared collections additively and idempotently, and REFUSES any id outside the app's `himi_<app>_` namespace, so it is safe against a production site. It reports declared-vs-site `permissionDrift` but never writes permissions unless asked with --apply-permissions, so a field reconcile can't silently revert access control changed in the dashboard. `seed` inserts the git-tracked `seed:` rows into EMPTY owned collections (--force to add to a non-empty one; --ai writes placeholder rows plus a refinement prompt for you to review). Adopted collections (declared by `wixId`) are read-only and never created, updated, or seeded. Site from --site <msid> or WIX_SITE_ID; owner auth via `wix token`.", required: "query|create|seed [--site <msid>] [--content <dir>] [--all] [--dry-run] [--force] [--allow-destructive] [--apply-permissions] [--only <id>] [--ai] [--count <n>]" },
67709
67886
  { name: "data", summary: "read-only plan or explicitly provision/seed declared KV and Wix Data resources. Preview and production are separate scopes. KV administration uses the operator data plane from --data-url/HIMI_DATA_ADMIN_URL and HIMI_DATA_ADMIN_TOKEN; draft push never calls it.", required: "plan|create|seed [--site <msid>] [--environment preview|production] [--content <dir>] [--data-url <url>] [--dry-run] [--force]" },
@@ -67752,7 +67929,7 @@ var COMMANDS = [
67752
67929
  ];
67753
67930
  function cliVersion() {
67754
67931
  try {
67755
- return JSON.parse(readFileSync76(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67932
+ return JSON.parse(readFileSync77(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67756
67933
  } catch {
67757
67934
  return "unknown";
67758
67935
  }
@@ -67870,7 +68047,7 @@ function ageInDays(publishedAt, now = Date.now()) {
67870
68047
  }
67871
68048
  function packageVersion(path2) {
67872
68049
  try {
67873
- const version2 = JSON.parse(readFileSync76(path2, "utf8")).version;
68050
+ const version2 = JSON.parse(readFileSync77(path2, "utf8")).version;
67874
68051
  return typeof version2 === "string" && version2 ? version2 : void 0;
67875
68052
  } catch {
67876
68053
  return void 0;
@@ -67879,7 +68056,7 @@ function packageVersion(path2) {
67879
68056
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
67880
68057
  let dir = resolve47(fromDir);
67881
68058
  for (; ; ) {
67882
- const version2 = packageVersion(join62(dir, "node_modules", "@wix", "himalaya", "package.json"));
68059
+ const version2 = packageVersion(join63(dir, "node_modules", "@wix", "himalaya", "package.json"));
67883
68060
  if (version2) return { version: version2, source: "local" };
67884
68061
  const parent = dirname34(dir);
67885
68062
  if (parent === dir) break;
@@ -67889,7 +68066,7 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
67889
68066
  if (bundled) return { version: bundled, source: "bundled" };
67890
68067
  try {
67891
68068
  const root = execFileSync7("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
67892
- const version2 = root ? packageVersion(join62(root, "@wix", "himalaya", "package.json")) : void 0;
68069
+ const version2 = root ? packageVersion(join63(root, "@wix", "himalaya", "package.json")) : void 0;
67893
68070
  return version2 ? { version: version2, source: "global" } : {};
67894
68071
  } catch {
67895
68072
  return {};
@@ -67936,11 +68113,11 @@ var UPDATE_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1e3;
67936
68113
  var UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1e3;
67937
68114
  var UPDATE_REEXEC_ENV = "HIMI_UPDATED_ONCE";
67938
68115
  function updateCachePath(env = process.env) {
67939
- return env.HIMI_UPDATE_CACHE_FILE ?? join62(homedir7(), ".himalaya", "update-check.json");
68116
+ return env.HIMI_UPDATE_CACHE_FILE ?? join63(homedir7(), ".himalaya", "update-check.json");
67940
68117
  }
67941
68118
  function readUpdateCache(path2) {
67942
68119
  try {
67943
- const parsed = JSON.parse(readFileSync76(path2, "utf8"));
68120
+ const parsed = JSON.parse(readFileSync77(path2, "utf8"));
67944
68121
  return parsed && typeof parsed === "object" ? parsed : {};
67945
68122
  } catch {
67946
68123
  return {};
@@ -68008,12 +68185,12 @@ function createUpdateRunner(spawn7 = spawnSync5) {
68008
68185
  },
68009
68186
  installedVersion: () => {
68010
68187
  const root = globalNodeModulesRoot();
68011
- return root ? packageVersion(join62(root, "@wix", "himalaya-cli", "package.json")) : void 0;
68188
+ return root ? packageVersion(join63(root, "@wix", "himalaya-cli", "package.json")) : void 0;
68012
68189
  },
68013
68190
  reexec: (argv) => {
68014
68191
  const root = globalNodeModulesRoot();
68015
- const entry = root ? join62(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
68016
- if (!entry || !existsSync64(entry)) return void 0;
68192
+ const entry = root ? join63(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
68193
+ if (!entry || !existsSync65(entry)) return void 0;
68017
68194
  const result = spawnSync5(process.execPath, [entry, ...argv], {
68018
68195
  // inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
68019
68196
  stdio: "inherit",
@@ -68060,7 +68237,7 @@ function staleUpdateLockFile(cachePath, now) {
68060
68237
  for (const path2 of [`${cachePath}.lock.break`, `${cachePath}.lock`]) {
68061
68238
  let content;
68062
68239
  try {
68063
- content = readFileSync76(path2, "utf8");
68240
+ content = readFileSync77(path2, "utf8");
68064
68241
  } catch {
68065
68242
  continue;
68066
68243
  }
@@ -68083,7 +68260,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
68083
68260
  const mine = lockStamp(now);
68084
68261
  const readLock = (path2) => {
68085
68262
  try {
68086
- return readFileSync76(path2, "utf8");
68263
+ return readFileSync77(path2, "utf8");
68087
68264
  } catch {
68088
68265
  return void 0;
68089
68266
  }
@@ -68667,7 +68844,7 @@ function executableOnPath(name, env = process.env) {
68667
68844
  const suffixes = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
68668
68845
  for (const dir of entries) {
68669
68846
  for (const suffix of suffixes) {
68670
- const candidate = join62(dir, `${name}${suffix}`);
68847
+ const candidate = join63(dir, `${name}${suffix}`);
68671
68848
  try {
68672
68849
  if (!statSync17(candidate).isFile()) continue;
68673
68850
  if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
@@ -68776,7 +68953,7 @@ async function nativeConfigAdvisories(app, configSource) {
68776
68953
  const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
68777
68954
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
68778
68955
  const iosYmlPath = iosProjectYml2(app);
68779
- const iosYml = existsSync64(iosYmlPath) ? readFileSync76(iosYmlPath, "utf8") : "";
68956
+ const iosYml = existsSync65(iosYmlPath) ? readFileSync77(iosYmlPath, "utf8") : "";
68780
68957
  let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
68781
68958
  if (!optedIn) {
68782
68959
  const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
@@ -68790,7 +68967,7 @@ async function nativeConfigAdvisories(app, configSource) {
68790
68967
  }
68791
68968
  function readConfigSource(dir) {
68792
68969
  try {
68793
- return readFileSync76(join62(dir, "config.ts"), "utf8");
68970
+ return readFileSync77(join63(dir, "config.ts"), "utf8");
68794
68971
  } catch {
68795
68972
  return null;
68796
68973
  }
@@ -68826,7 +69003,7 @@ function keptPlan(kept) {
68826
69003
  }
68827
69004
  async function initFromRemoteTemplate(name, parentDir, template, c) {
68828
69005
  const dir = resolve47(parentDir, name);
68829
- if (existsSync64(join62(dir, "himalaya.content.json"))) {
69006
+ if (existsSync65(join63(dir, "himalaya.content.json"))) {
68830
69007
  throw new Error(`content package already exists at ${dir}`);
68831
69008
  }
68832
69009
  const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
@@ -68869,7 +69046,7 @@ function symlinkDestinationIssue(dest) {
68869
69046
  function restoreDestinationIssue(dest, force) {
68870
69047
  const link = symlinkDestinationIssue(dest);
68871
69048
  if (link) return link;
68872
- if (!existsSync64(dest)) return null;
69049
+ if (!existsSync65(dest)) return null;
68873
69050
  if (!statSync17(dest).isDirectory()) {
68874
69051
  return { error: `${dest} exists and is not a directory`, hint: "pass --out/--name to land somewhere else, or remove the file" };
68875
69052
  }
@@ -68884,7 +69061,7 @@ function restoreDestinationIssue(dest, force) {
68884
69061
  function isHimalayaMonorepoCheckout(start) {
68885
69062
  let dir = resolve47(start);
68886
69063
  for (; ; ) {
68887
- if (existsSync64(join62(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync64(join62(dir, "core", "serve", "src", "server.ts"))) return true;
69064
+ if (existsSync65(join63(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync65(join63(dir, "core", "serve", "src", "server.ts"))) return true;
68888
69065
  const parent = dirname34(dir);
68889
69066
  if (parent === dir) return false;
68890
69067
  dir = parent;
@@ -68922,7 +69099,7 @@ function shellShaDivergenceWarnings(jobs) {
68922
69099
  }
68923
69100
  async function templatePublishName(sourceDir) {
68924
69101
  const id = await appId(sourceDir);
68925
- return IDENTITY_SENTINELS.some((s) => id.includes(s)) ? basename15(sourceDir) : id;
69102
+ return IDENTITY_SENTINELS.some((s) => id.includes(s)) ? basename16(sourceDir) : id;
68926
69103
  }
68927
69104
  function templateNameError(name, from) {
68928
69105
  if (name.startsWith(TEMPLATE_APP_PREFIX)) {
@@ -68945,7 +69122,7 @@ function materializeForPreview(appDir2, slug, displayName) {
68945
69122
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
68946
69123
  );
68947
69124
  const repoRoot2 = resolve47(fileURLToPath24(new URL("../../..", import.meta.url)));
68948
- const root = join62(repoRoot2, "node_modules", ".cache", "himi-template-preview");
69125
+ const root = join63(repoRoot2, "node_modules", ".cache", "himi-template-preview");
68949
69126
  const stillRunning = (pid) => {
68950
69127
  try {
68951
69128
  process.kill(pid, 0);
@@ -68959,12 +69136,12 @@ function materializeForPreview(appDir2, slug, displayName) {
68959
69136
  if (!entry.isDirectory()) continue;
68960
69137
  const pid = Number(entry.name.startsWith(`${slug}-`) ? entry.name.slice(slug.length + 1) : NaN);
68961
69138
  if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && !stillRunning(pid)) {
68962
- rmSync14(join62(root, entry.name), { recursive: true, force: true });
69139
+ rmSync14(join63(root, entry.name), { recursive: true, force: true });
68963
69140
  }
68964
69141
  }
68965
69142
  } catch {
68966
69143
  }
68967
- const dest = join62(root, `${slug}-${process.pid}`);
69144
+ const dest = join63(root, `${slug}-${process.pid}`);
68968
69145
  rmSync14(dest, { recursive: true, force: true });
68969
69146
  mkdirSync31(dest, { recursive: true });
68970
69147
  try {
@@ -68977,17 +69154,17 @@ function materializeForPreview(appDir2, slug, displayName) {
68977
69154
  }
68978
69155
  async function appId(dir) {
68979
69156
  try {
68980
- const { readFileSync: readFileSync77 } = await import("node:fs");
68981
- const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
69157
+ const { readFileSync: readFileSync78 } = await import("node:fs");
69158
+ const m = JSON.parse(readFileSync78(join63(dir, "himalaya.content.json"), "utf8"));
68982
69159
  if (m.name) return m.name;
68983
69160
  } catch {
68984
69161
  }
68985
- return basename15(dir);
69162
+ return basename16(dir);
68986
69163
  }
68987
69164
  async function contentVisibility(dir) {
68988
69165
  try {
68989
- const { readFileSync: readFileSync77 } = await import("node:fs");
68990
- const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
69166
+ const { readFileSync: readFileSync78 } = await import("node:fs");
69167
+ const m = JSON.parse(readFileSync78(join63(dir, "himalaya.content.json"), "utf8"));
68991
69168
  if (m.visibility === "public" || m.visibility === "unlisted") return m.visibility;
68992
69169
  } catch {
68993
69170
  }
@@ -69211,7 +69388,7 @@ Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
69211
69388
  ...res.removed.length ? { removed: res.removed, note: "older copies at the app root, moved to .himi/legacy-agent-files/ (a backup, in case one became app code) \u2014 the current ones live in .agents/skills/himalaya/" } : {},
69212
69389
  // In a git repo the refresh moves the diff identity an earlier `himi test` attested (that
69213
69390
  // hash is shared with the repo gates and deliberately exempts nothing but its own metadata).
69214
- ...existsSync64(join62(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
69391
+ ...existsSync65(join63(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
69215
69392
  ...res.skipped.length ? { skipped: res.skipped } : {},
69216
69393
  portalUrl: idx.portalUrl
69217
69394
  });
@@ -69234,7 +69411,7 @@ async function recordedReleaseForContentQuietly(dir, resolution, onDrift) {
69234
69411
  try {
69235
69412
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69236
69413
  const bres = await buildContentForValidation2(dir, { resolution });
69237
- out = mkdtempSync4(join62(tmpdir5(), "himi-ux-record-"));
69414
+ out = mkdtempSync4(join63(tmpdir5(), "himi-ux-record-"));
69238
69415
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69239
69416
  const outcome = await buildReleaseFromContent2(dir, out, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {}, collect: { lint: true } });
69240
69417
  const releaseId = outcome.manifest.releaseId;
@@ -69284,8 +69461,8 @@ function uxRestoreCommands(ctx) {
69284
69461
  };
69285
69462
  }
69286
69463
  function uxHandoverHint(uxEvidence, ctx) {
69287
- const { releaseId, contentArg, door, app } = ctx;
69288
- const uxRemedy = uxRecordCommands(contentArg);
69464
+ const { releaseId, contentArg: contentArg2, door, app } = ctx;
69465
+ const uxRemedy = uxRecordCommands(contentArg2);
69289
69466
  if (uxEvidence.state === "inherited-screens") {
69290
69467
  const restore = uxRestoreCommands(ctx);
69291
69468
  const alsoOwed = uxEvidence.supersededState === "failed" ? ` This package's own drive FAILED on ${uxEvidence.failedPlatforms.join(", ")}, so you owe more than a record: fix what that drive found, then record it against the restored tree \u2014 re-recording a failure without changing the app writes the same failure down again.` : uxEvidence.supersededState ? ` This package also has no drive this gate accepts (${uxEvidence.supersededState}), so the record below is a step you take AFTER the restore, not instead of it.` : "";
@@ -69412,7 +69589,7 @@ async function producedWidgetFamilies(dir) {
69412
69589
  let js;
69413
69590
  for (const candidate of [`${name}.bundle.js`, `${name}.js`, name]) {
69414
69591
  try {
69415
- js = readFileSync76(join62(outDir, candidate), "utf8");
69592
+ js = readFileSync77(join63(outDir, candidate), "utf8");
69416
69593
  break;
69417
69594
  } catch {
69418
69595
  }
@@ -69443,7 +69620,7 @@ async function producedWidgetFamilies(dir) {
69443
69620
  }
69444
69621
  return { families, source: "dist/bundles" };
69445
69622
  }
69446
- var boardOpen = (dir) => existsSync64(join62(dir, ".himi", "studio", "index.html"));
69623
+ var boardOpen = (dir) => existsSync65(join63(dir, ".himi", "studio", "index.html"));
69447
69624
  var BOARD_REFRESHERS = /* @__PURE__ */ new Set(["render", "test", "validate", "build", "push", "icon", "site", "ux", "progress"]);
69448
69625
  async function leaveForBoard(dir, record2, value) {
69449
69626
  if (!boardOpen(dir)) return;
@@ -69738,12 +69915,18 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69738
69915
  const owed = openApprovals2(readApprovals2(dir).events).length;
69739
69916
  next.push(`carried the planning journal into the app (${carried.join(", ")}${owed ? `; ${owed} approval${owed === 1 ? "" : "s"} still owed` : ""}) \u2014 \`himi progress --brief\` there shows it`);
69740
69917
  }
69741
- const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync64(join62(parentDir, f)));
69918
+ const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync65(join63(parentDir, f)));
69742
69919
  if (planInParent.length) {
69743
69920
  next.push(`${planInParent.join(" and ")} in ${parentDir} ${planInParent.length > 1 ? "are" : "is"} not the app's \u2014 if that is the approved plan, move it over the template's boilerplate in ${dir}`);
69744
69921
  }
69745
69922
  return { ...seeded, next };
69746
69923
  };
69924
+ const startSession = async (dir, agent2) => {
69925
+ const { openBoardForSession: openBoardForSession2, sessionSteps: sessionSteps2, homeScreenIdOf: homeScreenIdOf2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
69926
+ const board = await openBoardForSession2(dir);
69927
+ const { next: later2, ...rest } = agent2;
69928
+ return { ...rest, board, next: [...sessionSteps2({ dir, home: homeScreenIdOf2(dir) }), ...later2 ?? []] };
69929
+ };
69747
69930
  if (template && flags.remote === true) {
69748
69931
  let fetched;
69749
69932
  try {
@@ -69756,14 +69939,14 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69756
69939
  io.error(JSON.stringify({ warning: `remote template fetch failed (${e.message}); falling back to the bundled "${template}"` }));
69757
69940
  }
69758
69941
  if (fetched) {
69759
- const agent2 = await withAgentFiles(fetched.dir);
69942
+ const agent2 = await startSession(fetched.dir, await withAgentFiles(fetched.dir));
69760
69943
  const pushSetup2 = await offerPushSetup2({ contentDir: fetched.dir, flags, io });
69761
69944
  print({ ok: true, app: name, dir: fetched.dir, template, source: "remote", files: fetched.files, ...keptPlan(fetched.kept), ...agent2, pushSetup: pushSetup2 });
69762
69945
  return 0;
69763
69946
  }
69764
69947
  }
69765
69948
  const res = scaffoldContentPackage(name, parentDir, template);
69766
- const agent = await withAgentFiles(res.dir);
69949
+ const agent = await startSession(res.dir, await withAgentFiles(res.dir));
69767
69950
  const pushSetup = await offerPushSetup2({ contentDir: res.dir, flags, io });
69768
69951
  print({ ok: true, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, ...keptPlan(res.kept), ...agent, pushSetup });
69769
69952
  return 0;
@@ -69865,7 +70048,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69865
70048
  const sourceDir = resolve47(str6(flags["source-dir"]) ?? appDir2);
69866
70049
  let meta;
69867
70050
  try {
69868
- meta = JSON.parse(readFileSync76(join62(sourceDir, TEMPLATE_META_FILE), "utf8"));
70051
+ meta = JSON.parse(readFileSync77(join63(sourceDir, TEMPLATE_META_FILE), "utf8"));
69869
70052
  } catch {
69870
70053
  io.error(JSON.stringify({ error: `no ${TEMPLATE_META_FILE} in ${sourceDir} \u2014 a template must declare { title, description, tags } to be published`, hint: `add ${TEMPLATE_META_FILE} next to himalaya.content.json (or pass --source-dir <dir>)` }));
69871
70054
  return 2;
@@ -69875,7 +70058,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69875
70058
  return 2;
69876
70059
  }
69877
70060
  try {
69878
- const appearance = extractTemplateAppearance(JSON.parse(readFileSync76(join62(sourceDir, "tokens.json"), "utf8")));
70061
+ const appearance = extractTemplateAppearance(JSON.parse(readFileSync77(join63(sourceDir, "tokens.json"), "utf8")));
69879
70062
  meta = { ...appearance, ...meta };
69880
70063
  } catch {
69881
70064
  }
@@ -69897,7 +70080,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69897
70080
  const force = flags.force === true;
69898
70081
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69899
70082
  const bres = await buildContentForValidation2(buildDir, { resolution: resolutionFlag(flags) });
69900
- const out = mkdtempSync4(join62(tmpdir5(), "himi-template-"));
70083
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-template-"));
69901
70084
  publishScratch.push(out);
69902
70085
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69903
70086
  const outcome = await buildReleaseFromContent2(buildDir, out, {
@@ -69951,7 +70134,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69951
70134
  let iconHash;
69952
70135
  if (iconPath) {
69953
70136
  try {
69954
- const bytes2 = readFileSync76(iconPath);
70137
+ const bytes2 = readFileSync77(iconPath);
69955
70138
  payload.icon = bytes2.toString("base64");
69956
70139
  iconHash = createHash28("sha256").update(bytes2).digest("hex");
69957
70140
  } catch {
@@ -70194,7 +70377,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70194
70377
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
70195
70378
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
70196
70379
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
70197
- const out = mkdtempSync4(join62(tmpdir5(), "himi-validate-"));
70380
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-validate-"));
70198
70381
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70199
70382
  const outcome = await buildReleaseFromContent2(dir, out, {
70200
70383
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70237,7 +70420,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70237
70420
  const path2 = dpxSessionTokenPath();
70238
70421
  let raw;
70239
70422
  try {
70240
- raw = readFileSync76(path2, "utf8");
70423
+ raw = readFileSync77(path2, "utf8");
70241
70424
  } catch {
70242
70425
  throw new Error(`no DPX BO session at ${path2}; run dpx auth login, write dpx auth token to that path, then himi login --dpx --target bo`);
70243
70426
  }
@@ -70357,13 +70540,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70357
70540
  case "studio": {
70358
70541
  const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
70359
70542
  const dir = contentDir(flags);
70360
- if (!existsSync64(join62(dir, "himalaya.content.json"))) {
70543
+ if (!existsSync65(join63(dir, "himalaya.content.json"))) {
70361
70544
  io.error(JSON.stringify({ ok: false, error: "not_a_content_package", dir, hint: "run from the app directory (or pass --content <dir>) \u2014 the board reads that package's .himi/" }));
70362
70545
  return 2;
70363
70546
  }
70364
70547
  if (pos[1] === "reel") {
70365
70548
  const { buildReel: buildReel2 } = await Promise.resolve().then(() => (init_studio_reel(), studio_reel_exports));
70366
- const out = resolve47(str6(flags.out) ?? join62(dir, ".himi", "making-of.html"));
70549
+ const out = resolve47(str6(flags.out) ?? join63(dir, ".himi", "making-of.html"));
70367
70550
  const { html, plan } = buildReel2(dir);
70368
70551
  mkdirSync31(dirname34(out), { recursive: true });
70369
70552
  writeFileSync29(out, html);
@@ -70429,8 +70612,8 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70429
70612
  formatBrief: formatBrief2
70430
70613
  } = await Promise.resolve().then(() => (init_progress(), progress_exports));
70431
70614
  const dir = contentDir(flags);
70432
- const journal = journalPath2(dir);
70433
- const isContentPackage = existsSync64(join62(dir, "himalaya.content.json"));
70615
+ const journal2 = journalPath2(dir);
70616
+ const isContentPackage = existsSync65(join63(dir, "himalaya.content.json"));
70434
70617
  const hint = isContentPackage ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
70435
70618
  const message = pos.slice(1).join(" ").trim();
70436
70619
  const approvalFlags = ["approve", "done", "drop"].filter((f) => flags[f] !== void 0);
@@ -70482,7 +70665,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70482
70665
  }
70483
70666
  const { entries, skipped } = readProgress2(dir);
70484
70667
  const approvals = openApprovals2(readApprovals2(dir).events);
70485
- print({ ok: true, journal, ...summarizeProgress2(entries), entries, ...approvals.length ? { approvals } : {}, ...skipped ? { skipped } : {}, ...hint });
70668
+ print({ ok: true, journal: journal2, ...summarizeProgress2(entries), entries, ...approvals.length ? { approvals } : {}, ...skipped ? { skipped } : {}, ...hint });
70486
70669
  return 0;
70487
70670
  }
70488
70671
  let step;
@@ -70503,7 +70686,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70503
70686
  const entry = appendProgress2(dir, { message, ...step ? { step } : {}, images });
70504
70687
  const notified = flags.notify === true ? await notifyDesktop2(`himi \xB7 ${appLabel2(dir) ?? "your app"}`, message) : void 0;
70505
70688
  const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
70506
- print({ ok: true, entry, journal, count: count2, ...notified === void 0 ? {} : { notified }, ...hint });
70689
+ print({ ok: true, entry, journal: journal2, count: count2, ...notified === void 0 ? {} : { notified }, ...hint });
70507
70690
  return 0;
70508
70691
  } catch (e) {
70509
70692
  if (e instanceof ProgressError2) {
@@ -70566,7 +70749,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70566
70749
  case "test": {
70567
70750
  const dir = contentDir(flags);
70568
70751
  const structuredOutput = flags.json === true || io.isTTY !== true;
70569
- if (existsSync64(join62(dir, "functions"))) {
70752
+ if (existsSync65(join63(dir, "functions"))) {
70570
70753
  const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
70571
70754
  const loaded = await loadFunctionsApp2(dir);
70572
70755
  const results = [];
@@ -70653,7 +70836,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70653
70836
  }
70654
70837
  }
70655
70838
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
70656
- const out = mkdtempSync4(join62(tmpdir5(), "himi-test-"));
70839
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-test-"));
70657
70840
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70658
70841
  const outcome = await buildReleaseFromContent2(dir, out, {
70659
70842
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70698,7 +70881,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70698
70881
  io.error(JSON.stringify({ error: `screen "${screen.screenId}" has no Tier-5 worker, so there is nothing for the browser loop to build` }));
70699
70882
  return 2;
70700
70883
  }
70701
- const sources = collectWorkerSources2(join62(dir, "tier5-src"), screen.bundleName);
70884
+ const sources = collectWorkerSources2(join63(dir, "tier5-src"), screen.bundleName);
70702
70885
  if (!sources) {
70703
70886
  io.error(JSON.stringify({ error: `no worker source found at tier5-src/${screen.bundleName} \u2014 the browser loop compiles SOURCE, not the built bundle` }));
70704
70887
  return 2;
@@ -70863,7 +71046,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70863
71046
  const sp = await runShotsPass2({
70864
71047
  descriptors: scopeDescriptors2(outcome.descriptors ?? [], pos.slice(1), invalidScreens),
70865
71048
  tokens: outcome.tokens,
70866
- outDir: join62(dir, ".himi", "shots"),
71049
+ outDir: join63(dir, ".himi", "shots"),
70867
71050
  width: 390,
70868
71051
  layers: { rules: shotRules, ...cmsSeed ? { cmsSeed } : {}, ...canned ? { canned: canned.byKey } : {} },
70869
71052
  ...budgetMs ? { budgetMs } : {},
@@ -70901,7 +71084,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70901
71084
  releaseId: result.releaseId,
70902
71085
  releasePayload: release,
70903
71086
  descriptors: attestedDescriptors,
70904
- screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join62(dir, ".himi", "shots", name)])),
71087
+ screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join63(dir, ".himi", "shots", name)])),
70905
71088
  coverage: {
70906
71089
  screens: crawledScreens.size,
70907
71090
  actionsDispatched: result.actions.exercised,
@@ -70913,14 +71096,30 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70913
71096
  counts: { errors: result.counts.errors, warnings: result.counts.warnings, gaps: ledger.length }
70914
71097
  }
70915
71098
  });
71099
+ const home = outcome.homeScreenId;
71100
+ const { afterTestRun: afterTestRun2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
71101
+ const afterRun = afterTestRun2(dir, {
71102
+ runId: attestation.runId,
71103
+ screens: crawledScreens.size,
71104
+ actions: result.actions.exercised,
71105
+ errors: result.counts.errors,
71106
+ ...result.navGraph ? { broken: result.navGraph.broken.length, unreached: result.navGraph.unreached.length } : {},
71107
+ ...home && writtenShots.includes(`${home}.png`) ? { homeShot: join63(dir, ".himi", "shots", `${home}.png`) } : {},
71108
+ boardOpen: boardOpen(dir)
71109
+ });
70916
71110
  if (structuredOutput) {
70917
- print(foldLegacyReport(legacy, {
70918
- source: "test",
70919
- evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
70920
- gaps: ledger,
70921
- artifacts: result.shots?.dir ? [{ kind: "shots-dir", path: result.shots.dir }] : []
70922
- }));
70923
- } else io.log(formatReport2(result) + formatDelta2(delta) + formatLedger2(ledger, trend));
71111
+ print({
71112
+ ...foldLegacyReport(legacy, {
71113
+ source: "test",
71114
+ evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71115
+ gaps: ledger,
71116
+ artifacts: result.shots?.dir ? [{ kind: "shots-dir", path: result.shots.dir }] : []
71117
+ }),
71118
+ ...afterRun.postcard ? { postcard: afterRun.postcard } : {},
71119
+ next: afterRun.next
71120
+ });
71121
+ } else io.log(formatReport2(result) + formatDelta2(delta) + formatLedger2(ledger, trend) + `
71122
+ ${afterRun.next.join("\n")}`);
70924
71123
  await leaveForBoard(dir, "edges", result.edges ?? []);
70925
71124
  if (result.navGraph) await leaveForBoard(dir, "map", result.navGraph);
70926
71125
  return result.ok ? 0 : 1;
@@ -70992,7 +71191,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70992
71191
  return 2;
70993
71192
  }
70994
71193
  try {
70995
- const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync76(stateArg.replace(/^@/, ""), "utf8");
71194
+ const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync77(stateArg.replace(/^@/, ""), "utf8");
70996
71195
  injectedState = JSON.parse(raw);
70997
71196
  } catch (e) {
70998
71197
  io.error(JSON.stringify({ error: `--state must be inline JSON or a path to a .json file \u2014 ${e.message}` }));
@@ -71007,7 +71206,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71007
71206
  return 2;
71008
71207
  }
71009
71208
  try {
71010
- const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync76(inputArg.replace(/^@/, ""), "utf8");
71209
+ const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync77(inputArg.replace(/^@/, ""), "utf8");
71011
71210
  const parsed = JSON.parse(raw);
71012
71211
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("must be a JSON object");
71013
71212
  injectedInput = parsed;
@@ -71056,7 +71255,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71056
71255
  }
71057
71256
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71058
71257
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
71059
- const out = mkdtempSync4(join62(tmpdir5(), "himi-render-"));
71258
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-render-"));
71060
71259
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71061
71260
  const outcome = await buildReleaseFromContent2(dir, out, {
71062
71261
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71070,7 +71269,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71070
71269
  const diagnostics = (outcome.lint ?? []).find((s) => s.screenId === screenId)?.issues ?? [];
71071
71270
  let pngFile;
71072
71271
  let attestationPngFile;
71073
- const shotPath = join62(dir, ".himi", "shots", `${screenId}.png`);
71272
+ const shotPath = join63(dir, ".himi", "shots", `${screenId}.png`);
71074
71273
  const pngTarget = typeof flags.png === "string" ? str6(flags.png) : flags.png === true ? shotPath : void 0;
71075
71274
  if (pngTarget && r.pngBase64) {
71076
71275
  pngFile = writePngFile(pngTarget, r.pngBase64);
@@ -71138,15 +71337,20 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71138
71337
  coverage: { screens: 1, actionsDispatched: 0, uncoveredFns: 0, gaps: provisional.counts.gaps },
71139
71338
  report: { ok: provisional.ok, counts: provisional.counts }
71140
71339
  });
71141
- print(foldLegacyReport(legacyRender, {
71142
- source: "render",
71143
- app,
71144
- screenId,
71145
- releaseId: outcome.manifest.releaseId,
71146
- findings: diagnostics,
71147
- evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71148
- artifacts: pngFile ? [{ kind: "png", path: pngFile }] : []
71149
- }));
71340
+ const afterPng = pngFile ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterRenderPng(dir, { screenId, homeScreenId: outcome.homeScreenId ?? null, pngFile, renderable: r.renderable, content: str6(flags.content) }) : null;
71341
+ print({
71342
+ ...foldLegacyReport(legacyRender, {
71343
+ source: "render",
71344
+ app,
71345
+ screenId,
71346
+ releaseId: outcome.manifest.releaseId,
71347
+ findings: diagnostics,
71348
+ evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71349
+ artifacts: pngFile ? [{ kind: "png", path: pngFile }] : []
71350
+ }),
71351
+ ...afterPng?.postcard ? { postcard: afterPng.postcard } : {},
71352
+ ...afterPng?.next.length ? { next: afterPng.next } : {}
71353
+ });
71150
71354
  return r.renderable ? 0 : 1;
71151
71355
  }
71152
71356
  case "tokens": {
@@ -71406,7 +71610,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71406
71610
  io.error(JSON.stringify(linked));
71407
71611
  return 1;
71408
71612
  }
71409
- if (existsSync64(dest)) {
71613
+ if (existsSync65(dest)) {
71410
71614
  if (!statSync17(dest).isDirectory()) {
71411
71615
  io.error(JSON.stringify({
71412
71616
  error: `${dest} exists and is not a directory`,
@@ -71419,7 +71623,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71419
71623
  io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway (merging, and naming what it keeps), --replace to restore exactly what was archived, or --out/--name to land somewhere else" }));
71420
71624
  return 1;
71421
71625
  }
71422
- if (replace && occupied && !existsSync64(join62(dest, "himalaya.content.json"))) {
71626
+ if (replace && occupied && !existsSync65(join63(dest, "himalaya.content.json"))) {
71423
71627
  io.error(JSON.stringify({
71424
71628
  error: `${dest} is not a content package \u2014 refusing to --replace it`,
71425
71629
  hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
@@ -71560,7 +71764,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71560
71764
  const force = flags.force === true;
71561
71765
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71562
71766
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
71563
- const out = mkdtempSync4(join62(tmpdir5(), "himi-push-"));
71767
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-push-"));
71564
71768
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71565
71769
  const outcome = await buildReleaseFromContent2(dir, out, {
71566
71770
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71586,8 +71790,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71586
71790
  const manifest = outcome.manifest;
71587
71791
  const payload = await loadReleaseFromDir(declared, out);
71588
71792
  const { shellQuote: shellQuote2 } = await Promise.resolve().then(() => (init_shell_quote(), shell_quote_exports));
71589
- const contentRel = relative11(process.cwd(), dir) || ".";
71590
- const contentArg = shellQuote2(contentRel);
71793
+ const contentRel = relative12(process.cwd(), dir) || ".";
71794
+ const contentArg2 = shellQuote2(contentRel);
71591
71795
  if (onlyRaw !== void 0) {
71592
71796
  const onlyDraft = flags.draft === true;
71593
71797
  const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
@@ -71631,7 +71835,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71631
71835
  });
71632
71836
  const patchUxContext = {
71633
71837
  releaseId: manifest.releaseId,
71634
- contentArg,
71838
+ contentArg: contentArg2,
71635
71839
  contentDir: contentRel,
71636
71840
  app,
71637
71841
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71726,7 +71930,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71726
71930
  const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) were recorded at this commit, but none describes the content being published` : null;
71727
71931
  const attestationErrors = [...evidence.historyErrors, ...staleEvidence ? [staleEvidence] : []];
71728
71932
  if (attestationErrors.length > 0 && !force) {
71729
- const remedy = `\`himi test --content ${contentArg}\` (or \`himi render <screenId> --content ${contentArg}\`)`;
71933
+ const remedy = `\`himi test --content ${contentArg2}\` (or \`himi render <screenId> --content ${contentArg2}\`)`;
71730
71934
  const corrupted = evidence.historyErrors.length > 0;
71731
71935
  print({
71732
71936
  ok: false,
@@ -71764,7 +71968,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71764
71968
  // fixing one door and not the other is the drift the shared helpers exist to stop.
71765
71969
  hint: uxHandoverHint(uxEvidence, {
71766
71970
  releaseId: manifest.releaseId,
71767
- contentArg,
71971
+ contentArg: contentArg2,
71768
71972
  contentDir: contentRel,
71769
71973
  app,
71770
71974
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71873,11 +72077,13 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71873
72077
  const base = serveBase();
71874
72078
  const explicitTarget = contextualDefaultTarget !== void 0 || flags.target !== void 0 || flags["serve-url"] !== void 0 || !!process.env.HIMI_SERVE_TARGET;
71875
72079
  const loopbackWarning = base ? loopbackTargetWarning(base, explicitTarget) : null;
72080
+ const afterDraft = draft ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterDraftPush(dir, { app, releaseId: release, content: str6(flags.content) }) : null;
71876
72081
  print({
71877
72082
  ok: true,
71878
72083
  app,
71879
72084
  releaseId: release,
71880
72085
  draft,
72086
+ ...afterDraft?.postcard ? { postcard: afterDraft.postcard } : {},
71881
72087
  // Say so when the push acquired the slug on the caller's behalf. A claim takes a name
71882
72088
  // out of a global first-come namespace, counts against quota, and cannot be undone —
71883
72089
  // so it must never happen silently.
@@ -71918,7 +72124,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71918
72124
  // behind that: the plane keeps the previous attestation for this release id when a
71919
72125
  // push carries none, so the published release can look attested by an earlier run.
71920
72126
  ...!attestation && staleEvidence ? {
71921
- attestationNote: `${staleEvidence} \u2014 this release was published with no headless evidence of its own, and the serve plane may still show the attestation from an earlier push of this release id. Re-attest with \`himi test --content ${contentArg}\` and push again to attach one.`
72127
+ attestationNote: `${staleEvidence} \u2014 this release was published with no headless evidence of its own, and the serve plane may still show the attestation from an earlier push of this release id. Re-attest with \`himi test --content ${contentArg2}\` and push again to attach one.`
71922
72128
  } : {},
71923
72129
  // #2955 — the SAME honesty for the UX gate: `--force` published this preview without a
71924
72130
  // recorded mobile-UX drive for it, and the link below goes to a customer. Say it in the
@@ -71926,7 +72132,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71926
72132
  // the same silence one step apart.
71927
72133
  ...uxHandoverOverride(uxEvidence, {
71928
72134
  releaseId: manifest.releaseId,
71929
- contentArg,
72135
+ contentArg: contentArg2,
71930
72136
  contentDir: contentRel,
71931
72137
  app,
71932
72138
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71972,7 +72178,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71972
72178
  next: [
71973
72179
  `himi context --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
71974
72180
  `himi screens --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
71975
- `himi telemetry --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`
72181
+ `himi telemetry --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
72182
+ ...afterDraft ? [afterDraft.next] : []
71976
72183
  ],
71977
72184
  promote: draft ? `himi promote --app ${app} --release ${release}${forcedPastErrors ? " --force" : ""}${targetName() !== "local" ? ` --target ${targetName()}` : ""}` : null,
71978
72185
  ...(outcome.manifest.functions?.length ?? 0) > 0 ? {
@@ -72016,7 +72223,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72016
72223
  const displayName = str6(flags.name);
72017
72224
  const launchMode = flags.snapshot === true ? "snapshot" : "live";
72018
72225
  const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName ? { displayName } : {} };
72019
- const writePack = (root, appName, out, source) => format === "dir" ? packReleaseToDir2(root, appName, out, packOpts) : packRelease2(root, appName, out, { ...packOpts, ...source ? { source } : {} });
72226
+ const writePack = (root, appName2, out, source) => format === "dir" ? packReleaseToDir2(root, appName2, out, packOpts) : packRelease2(root, appName2, out, { ...packOpts, ...source ? { source } : {} });
72020
72227
  const releaseDir = str6(flags["release-dir"]);
72021
72228
  if (releaseDir) {
72022
72229
  const only = str6(flags.app);
@@ -72081,7 +72288,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72081
72288
  }
72082
72289
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72083
72290
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
72084
- const built = mkdtempSync4(join62(tmpdir5(), "himi-pack-"));
72291
+ const built = mkdtempSync4(join63(tmpdir5(), "himi-pack-"));
72085
72292
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
72086
72293
  const outcome = await buildReleaseFromContent2(dir, built, {
72087
72294
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -72286,7 +72493,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72286
72493
  }));
72287
72494
  return 1;
72288
72495
  }
72289
- const archive = readFileSync76(resolve47(file));
72496
+ const archive = readFileSync77(resolve47(file));
72290
72497
  const result = await client2().createPackageShare({
72291
72498
  app: opened.header.app,
72292
72499
  releaseId: opened.header.releaseId,
@@ -72346,11 +72553,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72346
72553
  const app = await appId(dir2);
72347
72554
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72348
72555
  const bres = await buildContentBundles2(dir2, { resolution: resolutionFlag(flags) });
72349
- temp = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
72556
+ temp = mkdtempSync4(join63(tmpdir5(), "himi-run-build-"));
72350
72557
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
72351
72558
  const initial = await buildReleaseFromContent2(dir2, temp, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir2) } : {} });
72352
72559
  servedMissing = initial.missingBundles;
72353
- source = join62(temp, app);
72560
+ source = join63(temp, app);
72354
72561
  }
72355
72562
  const chromeArg = str6(flags.chrome) ?? "preview";
72356
72563
  if (chromeArg !== "preview" && chromeArg !== "app") {
@@ -72386,7 +72593,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72386
72593
  if (!recordKit) return;
72387
72594
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
72388
72595
  const failure = record2(served2, { ok: true, at: startedAt });
72389
- if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join62(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
72596
+ if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join63(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
72390
72597
  await refreshBoard();
72391
72598
  };
72392
72599
  const retract = async () => {
@@ -72417,7 +72624,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72417
72624
  }
72418
72625
  const watchKit = watch2 ? await Promise.resolve().then(() => (init_run_watch(), run_watch_exports)) : null;
72419
72626
  const bus = watchKit ? new watchKit.ReloadBus() : void 0;
72420
- if (watch2) served = mkdtempSync4(join62(tmpdir5(), "himi-run-served-"));
72627
+ if (watch2) served = mkdtempSync4(join63(tmpdir5(), "himi-run-served-"));
72421
72628
  const running = await runPackage2(source, {
72422
72629
  port,
72423
72630
  chrome: chromeArg,
@@ -72439,8 +72646,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72439
72646
  await announce(running);
72440
72647
  if (watchKit && bus) {
72441
72648
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72442
- const live2 = join62(running.releaseRoot, running.app);
72443
- const manifestOf = (tree) => JSON.parse(readFileSync76(join62(tree, "release-manifest.json"), "utf8"));
72649
+ const live2 = join63(running.releaseRoot, running.app);
72650
+ const manifestOf = (tree) => JSON.parse(readFileSync77(join63(tree, "release-manifest.json"), "utf8"));
72444
72651
  let current = manifestOf(live2);
72445
72652
  const failed2 = async (message) => {
72446
72653
  if (stopping) return;
@@ -72451,7 +72658,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72451
72658
  };
72452
72659
  rebuild = watchKit.serialize(async () => {
72453
72660
  const t0 = Date.now();
72454
- const next = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
72661
+ const next = mkdtempSync4(join63(tmpdir5(), "himi-run-build-"));
72455
72662
  try {
72456
72663
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
72457
72664
  if (bres.compileErrors.length) {
@@ -72468,16 +72675,16 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72468
72675
  return;
72469
72676
  }
72470
72677
  if (stopping) return;
72471
- const fresh = manifestOf(join62(next, running.app));
72678
+ const fresh = manifestOf(join63(next, running.app));
72472
72679
  const diff = watchKit.diffReleases(current, fresh);
72473
72680
  const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
72474
- const changes = describeChanges2(live2, join62(next, running.app), diff);
72681
+ const changes = describeChanges2(live2, join63(next, running.app), diff);
72475
72682
  const retired = `${live2}.retired-${Date.now()}`;
72476
72683
  renameSync7(live2, retired);
72477
72684
  try {
72478
- renameSync7(join62(next, running.app), live2);
72685
+ renameSync7(join63(next, running.app), live2);
72479
72686
  } catch {
72480
- cpSync3(join62(next, running.app), live2, { recursive: true });
72687
+ cpSync3(join63(next, running.app), live2, { recursive: true });
72481
72688
  }
72482
72689
  rmSync14(retired, { recursive: true, force: true });
72483
72690
  current = fresh;
@@ -73087,7 +73294,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73087
73294
  io.error(JSON.stringify({ error: `patch ${kind} needs --file`, usage: PATCH_USAGE }));
73088
73295
  return 2;
73089
73296
  }
73090
- if (!existsSync64(file)) {
73297
+ if (!existsSync65(file)) {
73091
73298
  io.error(JSON.stringify({ error: `no such file: ${file}` }));
73092
73299
  return 2;
73093
73300
  }
@@ -73143,7 +73350,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73143
73350
  lintNote: "`himi patch` sends a raw file. Nothing here built or linted your app \u2014 `himi push --only <selector>` is the same edit with the full lint gate in front of it."
73144
73351
  };
73145
73352
  if (kind === "tokens" || kind === "app-config") {
73146
- const body = readFileSync76(file, "utf8");
73353
+ const body = readFileSync77(file, "utf8");
73147
73354
  const patch2 = kind === "tokens" ? { tokens: body } : { appConfig: body };
73148
73355
  return runReleasePatch({
73149
73356
  ...common,
@@ -73158,7 +73365,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73158
73365
  io.error(JSON.stringify({ error: "patch screen needs a <screenId>", usage: PATCH_USAGE }));
73159
73366
  return 2;
73160
73367
  }
73161
- const descriptor = readFileSync76(file, "utf8");
73368
+ const descriptor = readFileSync77(file, "utf8");
73162
73369
  try {
73163
73370
  JSON.parse(descriptor);
73164
73371
  } catch (e) {
@@ -73198,7 +73405,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73198
73405
  extra: { kind, screen: id, file: { path: file, bytes: Buffer.byteLength(descriptor, "utf8") } }
73199
73406
  });
73200
73407
  }
73201
- const bytes2 = readFileSync76(file);
73408
+ const bytes2 = readFileSync77(file);
73202
73409
  const sha = createHash28("sha256").update(bytes2).digest("hex");
73203
73410
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
73204
73411
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
@@ -73342,7 +73549,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73342
73549
  }
73343
73550
  let def;
73344
73551
  try {
73345
- const parsed = JSON.parse(arg.startsWith("@") ? readFileSync76(arg.slice(1), "utf8") : arg);
73552
+ const parsed = JSON.parse(arg.startsWith("@") ? readFileSync77(arg.slice(1), "utf8") : arg);
73346
73553
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
73347
73554
  io.error(JSON.stringify({ error: "invalid_definition", problems: [`--definition must be a JSON object, got ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed}`] }));
73348
73555
  return 2;
@@ -73806,7 +74013,7 @@ ${label2}`);
73806
74013
  const platform = str6(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
73807
74014
  {
73808
74015
  const distDir = contentDir(flags);
73809
- if (existsSync64(join62(distDir, "himalaya.content.json"))) {
74016
+ if (existsSync65(join63(distDir, "himalaya.content.json"))) {
73810
74017
  const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
73811
74018
  const errors = issues.filter((i) => i.severity === "error");
73812
74019
  for (const i of issues.filter((i2) => i2.severity === "warning")) {
@@ -74075,7 +74282,7 @@ ${label2}`);
74075
74282
  const dpxPath = dpxSessionTokenPath();
74076
74283
  let dpx;
74077
74284
  try {
74078
- dpx = readFileSync76(dpxPath, "utf8").trim();
74285
+ dpx = readFileSync77(dpxPath, "utf8").trim();
74079
74286
  } catch {
74080
74287
  io.error(JSON.stringify({ error: `no dpx session at ${dpxPath} \u2014 run \`dpx auth login\` then \`dpx auth token > ${dpxPath}\` (dpx 2.x keeps its own state in ~/.dpx/auth.json and does NOT write the legacy session file), or omit --dpx` }));
74081
74288
  return 1;
@@ -74483,7 +74690,7 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
74483
74690
  const path2 = dpxSessionTokenPath();
74484
74691
  let raw;
74485
74692
  try {
74486
- raw = readFileSync76(path2, "utf8");
74693
+ raw = readFileSync77(path2, "utf8");
74487
74694
  } catch {
74488
74695
  io.error(JSON.stringify({ error: `no DPX BO session at ${path2}` }));
74489
74696
  return 1;