@mutmutco/hub 4.4.11 → 4.4.13

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.
Files changed (2) hide show
  1. package/dist/index.cjs +171 -27
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -95,30 +95,30 @@ var import_node_fs = require("node:fs");
95
95
  var PRODUCTS = Object.freeze({
96
96
  "mm-strategy": Object.freeze({
97
97
  name: "MM Strategy",
98
- installWarm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
98
+ installWarm: "Welcome. Let's get you ready.",
99
99
  accent: "38;2;249;115;22",
100
- warm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
100
+ warm: "Welcome back. Checking for updates...",
101
101
  doctor: "mm-strategy doctor"
102
102
  }),
103
103
  "mmi-hub": Object.freeze({
104
104
  name: "mmi-hub",
105
- installWarm: "Welcome. Setting up mmi-hub \u2014 about a minute.",
105
+ installWarm: "Welcome. Let's get you ready.",
106
106
  accent: "38;2;125;211;252",
107
- warm: "Welcome back. Checking your surfaces\u2026",
107
+ warm: "Welcome back. Checking for updates...",
108
108
  doctor: "mmi doctor"
109
109
  }),
110
110
  "jerv-hub": Object.freeze({
111
111
  name: "jerv-hub",
112
- installWarm: "Welcome. Setting up jerv-hub \u2014 about a minute.",
112
+ installWarm: "Welcome. Let's get you ready.",
113
113
  accent: "38;2;248;113;113",
114
- warm: "Welcome back. Checking your surfaces\u2026",
114
+ warm: "Welcome back. Checking for updates...",
115
115
  doctor: "jerv doctor"
116
116
  }),
117
117
  jervcode: Object.freeze({
118
118
  name: "JervCode",
119
- installWarm: "Welcome. Setting up JervCode \u2014 about a minute.",
119
+ installWarm: "Welcome. Let's get you ready.",
120
120
  accent: "38;2;192;132;252",
121
- warm: "Welcome back. Keeping JervCode current\u2026",
121
+ warm: "Welcome back. Checking for updates...",
122
122
  doctor: "jervcode doctor"
123
123
  })
124
124
  });
@@ -132,6 +132,7 @@ function identityFor(product) {
132
132
 
133
133
  // ../installer/face/src/face.ts
134
134
  var GLYPH = Object.freeze({
135
+ clock: "\u23F0",
135
136
  diamond: "\u25C6",
136
137
  hollow: "\u25C7",
137
138
  bar: "\u2502",
@@ -203,12 +204,16 @@ function createFace({ product, color = false, columns, env = process.env, operat
203
204
  const continuesFace = continuedPhases.size > 0;
204
205
  const nested = env.MM_OUTER_CONSOLE === "1";
205
206
  const progressFd = readProgressFd(env);
207
+ const progressFile = env.MM_PROGRESS_PROTOCOL === "1" ? env.MM_PROGRESS_FILE : void 0;
206
208
  const emitMilestone = (title, measure, kind) => {
207
- if (progressFd === null) return false;
209
+ if (progressFd === null && !progressFile) return false;
208
210
  const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
209
211
  if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
212
+ if (kind === "running" && typeof measure === "string") record.measure = measure;
210
213
  try {
211
- (0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
214
+ if (progressFile) (0, import_node_fs.appendFileSync)(progressFile, `${JSON.stringify(record)}
215
+ `);
216
+ else (0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
212
217
  `);
213
218
  return true;
214
219
  } catch {
@@ -227,7 +232,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
227
232
  };
228
233
  const step = (title, measure = null, kind = "ok") => {
229
234
  if (emitMilestone(title, measure, kind)) return "";
230
- const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
235
+ const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
231
236
  const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
232
237
  const time = measured;
233
238
  const column = Math.min(44, Math.max(0, width - 8));
@@ -237,7 +242,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
237
242
  const pad = Math.max(1, column - visibleWidth(head));
238
243
  return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
239
244
  };
240
- const relay = (text) => String(text).split("\n").flatMap((line) => line.trim() === "" ? [bar()] : (visibleWidth(line) <= width - TITLE_COLUMN ? [line] : wrapWords(line, width - TITLE_COLUMN)).map((part) => `${bar()}${indent}${part}`)).join("\n");
245
+ const relay = (text) => String(text).replace(/\r?\n$/, "").split(/\r?\n/).flatMap((line) => line.trim() === "" ? [bar()] : (visibleWidth(line) <= width - TITLE_COLUMN ? [line] : wrapWords(line, width - TITLE_COLUMN)).map((part) => `${bar()}${indent}${part}`)).join("\n");
241
246
  const receipt = (lines, { ready = true } = {}) => {
242
247
  if (ready && nested) return [];
243
248
  const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
@@ -268,7 +273,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
268
273
  };
269
274
  const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
270
275
  const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
271
- return { identity, width, nested, emitsProgress: progressFd !== null, welcome, continues, step, relay, receipt, outcome, signOff, refusal, paint };
276
+ return { identity, width, nested, emitsProgress: progressFd !== null || Boolean(progressFile), welcome, continues, step, progress: (title, measure = null) => emitMilestone(title, measure, "running"), relay, receipt, outcome, signOff, refusal, paint };
272
277
  }
273
278
 
274
279
  // ../installer/face/src/shell.ts
@@ -277,7 +282,26 @@ var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
277
282
  // ../installer/face/src/spinner.ts
278
283
  var import_node_fs2 = require("node:fs");
279
284
  var import_node_worker_threads = require("node:worker_threads");
280
- var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
285
+
286
+ // ../installer/face/src/encoding.ts
287
+ var UNICODE_SPINNER_FRAMES = Object.freeze(["\u25D2", "\u25D0", "\u25D3", "\u25D1"]);
288
+ var ASCII_SPINNER_FRAMES = Object.freeze(["-", "\\", "|", "/"]);
289
+ function consoleRendersUnicode(env = process.env, platform = process.platform) {
290
+ if (env.MM_FACE_ASCII === "1" || env.NO_UNICODE === "1") return false;
291
+ const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
292
+ if (locale === "C" || locale === "POSIX" || /^C\./u.test(locale) || /^POSIX\./u.test(locale)) return false;
293
+ if (/cp437|ibm437|\b437\b/iu.test(locale)) return false;
294
+ if (/utf-?8/iu.test(locale)) return true;
295
+ if (platform === "win32") {
296
+ return Boolean(env.WT_SESSION) || env.TERM_PROGRAM === "vscode" || env.CHCP === "65001" || env.ConEmuANSI === "ON";
297
+ }
298
+ return true;
299
+ }
300
+ function spinnerFramesFor(env = process.env, platform = process.platform) {
301
+ return consoleRendersUnicode(env, platform) ? UNICODE_SPINNER_FRAMES : ASCII_SPINNER_FRAMES;
302
+ }
303
+
304
+ // ../installer/face/src/spinner.ts
281
305
  var WORKER_SOURCE = `
282
306
  const { parentPort, workerData } = require('node:worker_threads');
283
307
  const { writeSync, appendFileSync } = require('node:fs');
@@ -287,7 +311,7 @@ function draw() {
287
311
  if (Atomics.compareExchange(control, 1, 0, 1) !== 0) return;
288
312
  try {
289
313
  if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
290
- const text = frames[frame++ % frames.length];
314
+ const text = frames[frame++ % frames.length].replace('__elapsed__', Math.floor((Date.now() - workerData.started) / 1000) + 's');
291
315
  writeSync(2, text);
292
316
  if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
293
317
  } finally {
@@ -299,17 +323,20 @@ parentPort.on('message', (next) => {
299
323
  if (Atomics.load(control, 2)) return;
300
324
  frames = next.frames;
301
325
  frame = next.frame;
302
- Atomics.store(control, 0, 0);
303
326
  });
304
327
  setInterval(draw, workerData.intervalMs);
305
328
  `;
306
- function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
329
+ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath, env = process.env, platform = process.platform }) {
307
330
  animate = animate && !face.nested && !face.emitsProgress;
331
+ const glyphs = spinnerFramesFor(env, platform);
332
+ const ascii = glyphs[0] === ASCII_SPINNER_FRAMES[0];
308
333
  let timer = null;
309
334
  let worker = null;
310
335
  let control = null;
311
336
  let frames = [];
312
337
  let frame = 0;
338
+ let started = 0;
339
+ let suspended = false;
313
340
  const write = (text) => {
314
341
  if (stream) stream.write(text);
315
342
  else {
@@ -319,11 +346,12 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
319
346
  }
320
347
  };
321
348
  const render = (text, measure) => {
322
- const line = face.step(text, measure, "note").split("\n")[0];
323
- frames = line ? FRAMES.map((glyph) => `\r\x1B[2K${line.replace(GLYPH.dot, glyph)}`) : [];
349
+ const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
350
+ const prepared = line && ascii ? line.replaceAll(GLYPH.bar, "|") : line;
351
+ frames = prepared ? glyphs.map((glyph) => `\r\x1B[2K${prepared.replace(GLYPH.dot, glyph)}`) : [];
324
352
  };
325
353
  const draw = () => {
326
- if (animate && frames.length) write(frames[frame++ % frames.length]);
354
+ if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
327
355
  };
328
356
  const pause = () => {
329
357
  if (!control) return;
@@ -343,6 +371,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
343
371
  start(text, measure = null) {
344
372
  if (!animate) return;
345
373
  halt();
374
+ suspended = false;
375
+ started = Date.now();
346
376
  render(text, measure);
347
377
  frame = 0;
348
378
  draw();
@@ -356,7 +386,8 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
356
386
  frames,
357
387
  frame,
358
388
  intervalMs,
359
- transcriptPath
389
+ transcriptPath,
390
+ started
360
391
  } });
361
392
  const active = worker;
362
393
  worker.on("error", () => {
@@ -374,14 +405,26 @@ function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath
374
405
  render(text, measure);
375
406
  draw();
376
407
  worker?.postMessage({ frames, frame });
408
+ if (control && !suspended) Atomics.store(control, 0, 0);
409
+ },
410
+ pause() {
411
+ if (suspended) return;
412
+ suspended = true;
413
+ pause();
414
+ if (animate && frames.length) write("\r\x1B[2K");
415
+ },
416
+ resume() {
417
+ suspended = false;
418
+ if (control) Atomics.store(control, 0, 0);
419
+ if (timer || worker) draw();
377
420
  },
378
421
  stop() {
379
422
  halt();
423
+ frames = [];
380
424
  if (animate) write("\r\x1B[2K");
381
425
  }
382
426
  };
383
427
  }
384
- var SPINNER_FRAMES = Object.freeze([...FRAMES]);
385
428
 
386
429
  // ../installer/face/src/conformance.ts
387
430
  var ALLOWED = new Set(Object.values(GLYPH));
@@ -607,10 +650,19 @@ var PHASES = {
607
650
  "verify-release": ["Checking the release version", "Verified the release version"],
608
651
  verify: ["Verifying the payload", "Verified the payload"],
609
652
  install: ["Installing the product", "Installed the product"],
653
+ configure: ["Configuring the product", "Configured the product"],
610
654
  activate: ["Activating surfaces", "Activated surfaces"],
611
655
  doctor: ["Checking health", "Checked health"],
612
656
  rollback: ["Restoring the previous version", "Restored the previous version"]
613
657
  };
658
+ var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
659
+ "preflight",
660
+ "resolve",
661
+ "verify-release",
662
+ "download",
663
+ "sign-in",
664
+ "check"
665
+ ]);
614
666
  function createInstallerRun(value, options = {}) {
615
667
  const declaration = validateInstallerProduct(value);
616
668
  const env = options.env ?? process.env;
@@ -646,13 +698,26 @@ function createInstallerRun(value, options = {}) {
646
698
  };
647
699
  const spinner = createSpinner(face, {
648
700
  animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
701
+ env,
649
702
  ...options.write ? { stream: { write: (text) => {
650
703
  emit(String(text), "spinner");
651
704
  return true;
652
705
  } } } : { transcriptPath: env.MM_FACE_TRANSCRIPT }
653
706
  });
707
+ let activeTitle;
708
+ let chunkOpen = false;
709
+ let chunkChannel = "stdout";
710
+ let chunkColumn = 0;
711
+ const trailingCR = /* @__PURE__ */ new Set();
712
+ let deferredActivity;
713
+ const closeChunk = () => {
714
+ if (chunkOpen) emit("\n", chunkChannel);
715
+ chunkOpen = false;
716
+ chunkColumn = 0;
717
+ };
654
718
  let started = false;
655
719
  let finished = false;
720
+ let operation = options.operation;
656
721
  const start = () => {
657
722
  if (started || finished) return;
658
723
  started = true;
@@ -660,11 +725,14 @@ function createInstallerRun(value, options = {}) {
660
725
  const welcome = face.welcome();
661
726
  if (tty) lines(welcome);
662
727
  else if (welcome.length) {
663
- const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
728
+ const warm = operation === "install" ? face.identity.installWarm : face.identity.warm;
664
729
  lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
665
730
  }
666
731
  };
667
732
  const durable = (title, measure, kind) => {
733
+ closeChunk();
734
+ activeTitle = void 0;
735
+ deferredActivity = void 0;
668
736
  spinner.stop();
669
737
  const rendered = face.step(title, measure, kind);
670
738
  if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
@@ -673,15 +741,31 @@ function createInstallerRun(value, options = {}) {
673
741
  get errors() {
674
742
  return [...errors];
675
743
  },
744
+ get operation() {
745
+ return operation;
746
+ },
747
+ set operation(value2) {
748
+ operation = value2;
749
+ },
676
750
  start,
677
751
  phase(id, facts = {}) {
678
752
  if (finished) throw new Error("installer run already finished");
679
753
  if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
680
754
  start();
681
755
  const state = facts.state ?? "ok";
682
- const title = PHASES[id][state === "ok" ? 1 : 0];
756
+ const fold = operation === "update" && UPDATE_CHECK_PHASES.has(id) && state !== "fail";
757
+ const title = PHASES[fold ? "check" : id][state === "ok" ? 1 : 0];
683
758
  if (state === "running") {
684
- spinner.start(title, facts.measure ?? null);
759
+ if (face.progress(title, facts.measure ?? null)) return;
760
+ if (activeTitle === title) spinner.say(title, facts.measure ?? null);
761
+ else {
762
+ activeTitle = title;
763
+ spinner.start(title, facts.measure ?? null);
764
+ }
765
+ return;
766
+ }
767
+ if (fold && id !== "check") {
768
+ if (facts.detail) run.relay(facts.detail);
685
769
  return;
686
770
  }
687
771
  if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
@@ -701,8 +785,20 @@ function createInstallerRun(value, options = {}) {
701
785
  durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
702
786
  if (facts.detail) run.relay(facts.detail);
703
787
  },
704
- milestone({ step, state, ms }) {
788
+ milestone({ step, state, ms, measure }) {
705
789
  start();
790
+ if (state === "running") {
791
+ if (chunkOpen) {
792
+ deferredActivity = { title: step, measure: measure ?? null };
793
+ return;
794
+ }
795
+ if (activeTitle === step) spinner.say(step, measure ?? null);
796
+ else {
797
+ activeTitle = step;
798
+ spinner.start(step, measure ?? null);
799
+ }
800
+ return;
801
+ }
706
802
  durable(step, ms === void 0 ? null : ms / 1e3, state);
707
803
  },
708
804
  signIn({ url, code }) {
@@ -716,7 +812,8 @@ function createInstallerRun(value, options = {}) {
716
812
  },
717
813
  // Only pass safe diagnostic text, never authentication output or credentials.
718
814
  relay(text, channel = "stdout", record = true) {
719
- spinner.stop();
815
+ closeChunk();
816
+ spinner.pause();
720
817
  const rendered = tty ? face.relay(text) : stripColor(text);
721
818
  for (const row of rendered.split("\n")) if (row) {
722
819
  emit(`${row}
@@ -724,6 +821,37 @@ function createInstallerRun(value, options = {}) {
724
821
  ` : `${tty ? face.relay("[external output omitted]") : "[external output omitted]"}
725
822
  `);
726
823
  }
824
+ spinner.resume();
825
+ },
826
+ relayChunk(text, channel = "stdout") {
827
+ spinner.pause();
828
+ if (chunkOpen && chunkChannel !== channel) closeChunk();
829
+ chunkChannel = channel;
830
+ if (trailingCR.delete(channel) && text.startsWith("\n")) text = text.slice(1);
831
+ if (text.endsWith("\r")) trailingCR.add(channel);
832
+ const parts = text.replace(/\r\n|\r/g, "\n").split("\n");
833
+ for (let i = 0; i < parts.length; i++) {
834
+ const characters = [...stripColor(parts[i])];
835
+ while (characters.length) {
836
+ if (tty && chunkColumn >= face.width - 6) closeChunk();
837
+ const part = characters.splice(0, tty ? face.width - 6 - chunkColumn : characters.length).join("");
838
+ emit(tty && !chunkOpen ? face.relay(part) : part, channel, chunkOpen ? "" : tty ? face.relay("[external output omitted]") : "[external output omitted]");
839
+ chunkColumn += [...part].length;
840
+ chunkOpen = true;
841
+ }
842
+ if (i < parts.length - 1) {
843
+ emit("\n", channel);
844
+ chunkOpen = false;
845
+ chunkColumn = 0;
846
+ }
847
+ }
848
+ if (!chunkOpen) {
849
+ if (deferredActivity) {
850
+ activeTitle = deferredActivity.title;
851
+ spinner.start(deferredActivity.title, deferredActivity.measure);
852
+ deferredActivity = void 0;
853
+ } else spinner.resume();
854
+ }
727
855
  },
728
856
  cancel() {
729
857
  if (finished) return;
@@ -732,6 +860,8 @@ function createInstallerRun(value, options = {}) {
732
860
  { total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
733
861
  );
734
862
  start();
863
+ closeChunk();
864
+ activeTitle = void 0;
735
865
  spinner.stop();
736
866
  finished = true;
737
867
  if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
@@ -744,6 +874,8 @@ function createInstallerRun(value, options = {}) {
744
874
  validateInstallerOutcome(facts);
745
875
  if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(env.MM_INSTALLER_OUTCOME_FILE, facts);
746
876
  start();
877
+ closeChunk();
878
+ activeTitle = void 0;
747
879
  spinner.stop();
748
880
  finished = true;
749
881
  if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
@@ -764,6 +896,8 @@ function createInstallerRun(value, options = {}) {
764
896
  if (tty) lines([face.signOff()]);
765
897
  },
766
898
  stop() {
899
+ closeChunk();
900
+ activeTitle = void 0;
767
901
  spinner.stop();
768
902
  }
769
903
  };
@@ -789,6 +923,7 @@ async function runMaintenance(product, args, dependencies) {
789
923
  quiet,
790
924
  operation: installed ? "install" : "update"
791
925
  });
926
+ if (run) run.operation = installed ? "install" : "update";
792
927
  if (!quiet) run?.start();
793
928
  let active;
794
929
  let activeFacts;
@@ -3026,6 +3161,9 @@ async function reconcile(options) {
3026
3161
  options.narrate?.(line);
3027
3162
  options.onPhase?.(line);
3028
3163
  };
3164
+ const phase = (id, state, detail) => {
3165
+ options.onPhaseEvent?.({ id, state, ...detail ? { detail } : {} });
3166
+ };
3029
3167
  const run = `${(/* @__PURE__ */ new Date()).toISOString()}-${process.pid}`;
3030
3168
  const paths = ensureState(env);
3031
3169
  const repoUrl = env.MMI_UPDATER_REPO || REPO_URL_DEFAULT;
@@ -3047,6 +3185,7 @@ async function reconcile(options) {
3047
3185
  say("acquiring single-writer lease");
3048
3186
  const leaseWaitMs = Number(env.MMI_UPDATER_LEASE_WAIT_MS) || void 0;
3049
3187
  return await withLease(paths.leasePath, async () => {
3188
+ phase("preflight", "running");
3050
3189
  const journalEvents = parseJournal(paths.journalPath);
3051
3190
  if (!options.dryRun) {
3052
3191
  const pendingBefore = readPending(paths);
@@ -3058,6 +3197,8 @@ async function reconcile(options) {
3058
3197
  summary.reap = reap({ paths, dryRun: options.dryRun, env });
3059
3198
  for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail, durationMs: row.durationMs });
3060
3199
  for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
3200
+ phase("preflight", "ok");
3201
+ phase("resolve", "running");
3061
3202
  if (shipped) {
3062
3203
  say(`shipped target v${shipped} \u2014 the payload is the release; no npm poll, no gate`);
3063
3204
  summary.target = shipped;
@@ -3116,7 +3257,9 @@ async function reconcile(options) {
3116
3257
  }
3117
3258
  }
3118
3259
  say(`target v${summary.target} (${summary.gate})`);
3260
+ phase("resolve", "ok");
3119
3261
  options.onTargetResolved?.(summary.target, summary.gate);
3262
+ phase("check", "running");
3120
3263
  say("updater: self-bootstrap");
3121
3264
  const selfStartedAt = import_node_perf_hooks2.performance.now();
3122
3265
  const self = npmGlobalArm({
@@ -3179,6 +3322,7 @@ async function reconcile(options) {
3179
3322
  journal({ kind: "surface", surface: probe.id, present: true, verdict: "skip", detail: `present, its converge arm has not landed yet (target ${summary.target})` });
3180
3323
  }
3181
3324
  }
3325
+ phase("check", "ok");
3182
3326
  return summary;
3183
3327
  }, leaseWaitMs);
3184
3328
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.4.11",
3
+ "version": "4.4.13",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",