@mutmutco/hub 4.5.6 → 4.5.7

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 (3) hide show
  1. package/README.md +20 -15
  2. package/dist/index.cjs +954 -410
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  hermesHostEvidence: () => hermesHostEvidence,
41
41
  hermesHostPresent: () => hermesHostPresent,
42
42
  hubStatus: () => hubStatus,
43
+ hubStatusReport: () => hubStatusReport,
43
44
  launchdPlist: () => launchdPlist,
44
45
  launcherSh: () => launcherSh,
45
46
  launcherVbs: () => launcherVbs,
@@ -64,6 +65,8 @@ __export(index_exports, {
64
65
  reconcile: () => reconcile,
65
66
  reconcileCursorUserHooks: () => reconcileCursorUserHooks,
66
67
  releaseSharedDocLock: () => releaseSharedDocLock,
68
+ renderNotice: () => renderNotice,
69
+ renderReport: () => renderReport,
67
70
  reportCounts: () => reportCounts,
68
71
  retiredSurfaces: () => retiredSurfaces,
69
72
  retryHint: () => retryHint,
@@ -74,6 +77,7 @@ __export(index_exports, {
74
77
  sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
75
78
  shippedTargetFrom: () => shippedTargetFrom,
76
79
  shouldHandOff: () => shouldHandOff,
80
+ statusScreen: () => statusScreen,
77
81
  surfaceArmPlan: () => surfaceArmPlan,
78
82
  systemdServiceUnit: () => systemdServiceUnit,
79
83
  systemdTimerUnit: () => systemdTimerUnit,
@@ -84,9 +88,9 @@ __export(index_exports, {
84
88
  writePending: () => writePending
85
89
  });
86
90
  module.exports = __toCommonJS(index_exports);
87
- var import_node_child_process10 = require("node:child_process");
88
- var import_node_fs22 = require("node:fs");
89
- var import_node_path13 = require("node:path");
91
+ var import_node_child_process11 = require("node:child_process");
92
+ var import_node_fs24 = require("node:fs");
93
+ var import_node_path16 = require("node:path");
90
94
 
91
95
  // ../installer/face/src/face.ts
92
96
  var import_node_fs = require("node:fs");
@@ -96,6 +100,7 @@ var PRODUCTS = Object.freeze({
96
100
  "mm-strategy": Object.freeze({
97
101
  name: "MM Strategy",
98
102
  installWarm: "Welcome. Let's get you ready.",
103
+ checkWarm: "Here's where things stand.",
99
104
  accent: "38;2;249;115;22",
100
105
  warm: "Welcome back. Checking for updates...",
101
106
  doctor: "mm-strategy doctor"
@@ -103,6 +108,7 @@ var PRODUCTS = Object.freeze({
103
108
  "mmi-hub": Object.freeze({
104
109
  name: "mmi-hub",
105
110
  installWarm: "Welcome. Let's get you ready.",
111
+ checkWarm: "Here's where things stand.",
106
112
  accent: "38;2;125;211;252",
107
113
  warm: "Welcome back. Checking for updates...",
108
114
  doctor: "mmi doctor"
@@ -110,6 +116,7 @@ var PRODUCTS = Object.freeze({
110
116
  "jerv-hub": Object.freeze({
111
117
  name: "jerv-hub",
112
118
  installWarm: "Welcome. Let's get you ready.",
119
+ checkWarm: "Here's where things stand.",
113
120
  accent: "38;2;248;113;113",
114
121
  warm: "Welcome back. Checking for updates...",
115
122
  doctor: "jerv doctor"
@@ -117,6 +124,7 @@ var PRODUCTS = Object.freeze({
117
124
  jervcode: Object.freeze({
118
125
  name: "JervCode",
119
126
  installWarm: "Welcome. Let's get you ready.",
127
+ checkWarm: "Here's where things stand.",
120
128
  accent: "38;2;192;132;252",
121
129
  warm: "Welcome back. Checking for updates...",
122
130
  doctor: "jervcode doctor"
@@ -232,7 +240,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
232
240
  const welcome = () => continuesFace || nested ? [] : [
233
241
  `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
234
242
  bar(),
235
- `${bar()} ${operation === "install" ? identity.installWarm : identity.warm}`,
243
+ `${bar()} ${operation === "install" ? identity.installWarm : operation === "check" ? identity.checkWarm : identity.warm}`,
236
244
  bar()
237
245
  ];
238
246
  const continues = (phase, kind = "ok") => {
@@ -262,7 +270,11 @@ function createFace({ product, color = false, columns, env = process.env, operat
262
270
  const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
263
271
  const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
264
272
  const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
265
- const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(identity.accent, GLYPH.check)}${line.slice(1)}` : line;
273
+ const headGlyph = { [GLYPH.check]: identity.accent, [GLYPH.cross]: identity.accent, [GLYPH.dot]: PALETTE.muted };
274
+ const bodyRow = (line) => {
275
+ const sgr = headGlyph[line.charAt(0)];
276
+ return sgr ? `${paint(sgr, line.charAt(0))}${line.slice(1)}` : line;
277
+ };
266
278
  return [
267
279
  frame(GLYPH.boxTop, GLYPH.boxTopEnd),
268
280
  ...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
@@ -288,6 +300,9 @@ function createFace({ product, color = false, columns, env = process.env, operat
288
300
  // ../installer/face/src/shell.ts
289
301
  var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
290
302
 
303
+ // ../installer/face/src/rows.ts
304
+ var plainText = (text) => String(text).replaceAll("\u2192", "->").replaceAll("\xB7", "-").replaceAll("\u2014", "-").replaceAll("\u2026", "...");
305
+
291
306
  // ../installer/face/src/spinner.ts
292
307
  var import_node_fs2 = require("node:fs");
293
308
  var import_node_worker_threads = require("node:worker_threads");
@@ -468,48 +483,48 @@ function assertFaceConformance(lines, options) {
468
483
  const identity = identityFor(options.product);
469
484
  const width = options.columns ?? 100;
470
485
  const rendered = lines.flatMap((line) => String(line).split("\n"));
471
- const plain = rendered.map(stripColor);
472
- const head = plain[0] ?? "";
486
+ const plain2 = rendered.map(stripColor);
487
+ const head = plain2[0] ?? "";
473
488
  const signOffLine = `${GLYPH.diamond} ${identity.name} \xB7 Mutatis Mutandis`;
474
489
  const opensWithWelcome = head.startsWith(GLYPH.diamond) && head !== signOffLine;
475
490
  if (options.complete && options.tty !== false) {
476
491
  const welcome = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
477
- const welcomes = plain.filter((line) => line === welcome).length;
478
- const receipts = plain.filter((line) => line.startsWith(GLYPH.boxTop)).length;
479
- const signOffs = plain.filter((line) => line === signOffLine).length;
492
+ const welcomes = plain2.filter((line) => line === welcome).length;
493
+ const receipts = plain2.filter((line) => line.startsWith(GLYPH.boxTop)).length;
494
+ const signOffs = plain2.filter((line) => line === signOffLine).length;
480
495
  if (welcomes !== (options.nested ? 0 : 1) || signOffs !== (options.nested ? 0 : 1) || !options.nested && receipts !== 1) {
481
496
  fail("complete run", "expected one console owner, one welcome, one receipt and one sign-off");
482
497
  }
483
- for (const line of plain) {
498
+ for (const line of plain2) {
484
499
  if (line && ![GLYPH.bar, GLYPH.diamond, GLYPH.hollow, GLYPH.boxTop, GLYPH.boxBottom].some((glyph) => line.startsWith(glyph))) {
485
500
  fail("complete run", `unframed output: ${JSON.stringify(line)}`);
486
501
  }
487
502
  }
488
503
  }
489
504
  if (rendered.length === 0) fail("R7 render before you ship", "no rendered lines were given to the guard");
490
- const greetings = plain.filter((line) => ["\u2014", "-"].some((separator) => line.includes(`${identity.name} ${separator} Mutatis Mutandis`)));
505
+ const greetings = plain2.filter((line) => ["\u2014", "-"].some((separator) => line.includes(`${identity.name} ${separator} Mutatis Mutandis`)));
491
506
  if (greetings.length > 1) fail("R8 one welcome per run", "duplicate product greeting");
492
- if (plain.filter((line) => line.startsWith(GLYPH.boxTop)).length > 1) {
507
+ if (plain2.filter((line) => line.startsWith(GLYPH.boxTop)).length > 1) {
493
508
  fail("R8 one receipt per run", "duplicate receipt");
494
509
  }
495
510
  if (options.nested === true) {
496
511
  if (opensWithWelcome) {
497
512
  fail("R8 a nested run draws no welcome", `an outer console already greeted the user; this run opened with ${JSON.stringify(head)}`);
498
513
  }
499
- const signOffAt = plain.findIndex((line) => line === signOffLine);
514
+ const signOffAt = plain2.findIndex((line) => line === signOffLine);
500
515
  if (signOffAt !== -1) {
501
516
  fail("R8 a nested run draws no sign-off", `the outer console closes the run; line ${signOffAt + 1} signs off`);
502
517
  }
503
- const boxAt = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
518
+ const boxAt = plain2.findIndex((line) => line.startsWith(GLYPH.boxTop));
504
519
  if (boxAt !== -1) {
505
- const headline = plain.slice(boxAt + 1).find((line) => line.startsWith(GLYPH.bar) && line.trim() !== GLYPH.bar) ?? "";
520
+ const headline = plain2.slice(boxAt + 1).find((line) => line.startsWith(GLYPH.bar) && line.trim() !== GLYPH.bar) ?? "";
506
521
  if (headline.includes(GLYPH.check)) {
507
522
  fail("R8 a nested run draws no success receipt", `the outer console owns the outcome; this run printed ${JSON.stringify(headline.trim())}`);
508
523
  }
509
524
  }
510
525
  }
511
526
  if (options.tty === false) {
512
- for (const line of plain) {
527
+ for (const line of plain2) {
513
528
  const glyph = [...line].find((char) => ALLOWED.has(char));
514
529
  if (glyph) fail("non-TTY lane is plain", `a piped run emitted ${JSON.stringify(glyph)} in ${JSON.stringify(line)}`);
515
530
  }
@@ -518,32 +533,32 @@ function assertFaceConformance(lines, options) {
518
533
  }
519
534
  return;
520
535
  }
521
- for (const line of plain) {
536
+ for (const line of plain2) {
522
537
  if (line.startsWith(`${GLYPH.bar} ${GLYPH.hollow} `)) {
523
538
  fail("note step uses U+25CF", `U+25C7 is the note-box opener in ${JSON.stringify(line)}`);
524
539
  }
525
540
  }
526
- for (const line of plain) {
541
+ for (const line of plain2) {
527
542
  if (/[\u25c6\u25c7\u2714\u2716\u25cf] (?! )/u.test(line)) {
528
543
  fail("R4 two spaces after a glyph", `a single space follows the glyph in ${JSON.stringify(line)}`);
529
544
  }
530
545
  }
531
- for (const line of plain) {
546
+ for (const line of plain2) {
532
547
  for (const [glyph, why] of FORBIDDEN) {
533
548
  if (line.includes(glyph)) fail("glyph set", `${why} (in ${JSON.stringify(line)})`);
534
549
  }
535
550
  const emoji = [...line].find((char) => EMOJI.test(char) && !ALLOWED.has(char));
536
551
  if (emoji) fail("glyph set", `emoji ${JSON.stringify(emoji)} in ${JSON.stringify(line)}`);
537
552
  }
538
- const top = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
553
+ const top = plain2.findIndex((line) => line.startsWith(GLYPH.boxTop));
539
554
  if (top !== -1) {
540
- const bottom = plain.findIndex((line, index) => index > top && line.startsWith(GLYPH.boxBottom));
555
+ const bottom = plain2.findIndex((line, index) => index > top && line.startsWith(GLYPH.boxBottom));
541
556
  if (bottom === -1) fail("R5 receipt frame", "the receipt box opens and never closes");
542
- if (!plain[top].endsWith(GLYPH.boxTopEnd)) fail("R5 receipt frame", `the top row does not pair its corners: ${JSON.stringify(plain[top])}`);
543
- if (!plain[bottom].endsWith(GLYPH.boxBottomEnd)) fail("R5 receipt frame", `the bottom row does not pair its corners: ${JSON.stringify(plain[bottom])}`);
544
- const widths = new Set(plain.slice(top, bottom + 1).map(visibleWidth));
557
+ if (!plain2[top].endsWith(GLYPH.boxTopEnd)) fail("R5 receipt frame", `the top row does not pair its corners: ${JSON.stringify(plain2[top])}`);
558
+ if (!plain2[bottom].endsWith(GLYPH.boxBottomEnd)) fail("R5 receipt frame", `the bottom row does not pair its corners: ${JSON.stringify(plain2[bottom])}`);
559
+ const widths = new Set(plain2.slice(top, bottom + 1).map(visibleWidth));
545
560
  if (widths.size !== 1) fail("R5 receipt frame", `the box is ragged: widths ${[...widths].join(", ")}`);
546
- const rows = plain.slice(top + 1, bottom);
561
+ const rows = plain2.slice(top + 1, bottom);
547
562
  if (rows.length > 0 && rows[0].includes(GLYPH.check)) {
548
563
  if (rows.length < 3) {
549
564
  fail("R9 the receipt says what changed", `a success receipt has three lines \u2014 ready, what changed, the health command \u2014 and this one has ${rows.length}: ${JSON.stringify(rows.map((row) => row.trim()))}`);
@@ -552,9 +567,9 @@ function assertFaceConformance(lines, options) {
552
567
  if (changed === "") fail("R9 the receipt says what changed", "the middle line of the success receipt is blank");
553
568
  }
554
569
  }
555
- const inReceipt = (index) => top !== -1 && index >= top && index <= plain.findIndex((line, at) => at > top && line.startsWith(GLYPH.boxBottom));
570
+ const inReceipt = (index) => top !== -1 && index >= top && index <= plain2.findIndex((line, at) => at > top && line.startsWith(GLYPH.boxBottom));
556
571
  const welcomeEnd = opensWithWelcome ? 4 : 0;
557
- for (const [index, line] of plain.entries()) {
572
+ for (const [index, line] of plain2.entries()) {
558
573
  if (index < welcomeEnd || inReceipt(index)) continue;
559
574
  if (!line.startsWith(GLYPH.bar) || line.trim() === GLYPH.bar) continue;
560
575
  const after = [...line.slice(1)];
@@ -565,18 +580,18 @@ function assertFaceConformance(lines, options) {
565
580
  fail("R1 one rail, one left edge", `text starts at column ${edge} without a glyph there; a line that carries no glyph belongs at the title column (${TITLE_COLUMN + 1}): ${JSON.stringify(line)}`);
566
581
  }
567
582
  }
568
- for (const line of plain) {
583
+ for (const line of plain2) {
569
584
  if (/\(node:\d+\)|DeprecationWarning|ExperimentalWarning/u.test(line)) {
570
585
  fail("R2 no foreign output in the face", `a Node warning reached the face: ${JSON.stringify(line)} \u2014 fix the spawn, or run the child with --no-deprecation`);
571
586
  }
572
587
  }
573
- const receiptStart = top === -1 ? plain.length : top;
574
- for (const line of plain.slice(0, receiptStart)) {
588
+ const receiptStart = top === -1 ? plain2.length : top;
589
+ for (const line of plain2.slice(0, receiptStart)) {
575
590
  if (/[\u2714\u2716\u25cf\u25c7]/u.test(line) && /([A-Za-z]:\\|\/(?:home|Users|opt|usr)\/)/u.test(line)) {
576
591
  fail("R3 paths live in the receipt", `a step line carries a path: ${JSON.stringify(line)}`);
577
592
  }
578
593
  }
579
- for (const line of plain) {
594
+ for (const line of plain2) {
580
595
  if (visibleWidth(line) > width) {
581
596
  fail("width cap", `a line is ${visibleWidth(line)} columns wide, cap ${width}: ${JSON.stringify(line)}`);
582
597
  }
@@ -584,7 +599,7 @@ function assertFaceConformance(lines, options) {
584
599
  if (opensWithWelcome) {
585
600
  const expected = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
586
601
  if (head !== expected) fail("welcome is the product table's", `expected ${JSON.stringify(expected)}, got ${JSON.stringify(head)}`);
587
- const warm = plain.slice(1, 4).find((line) => line.includes(identity.warm) || line.includes(identity.installWarm));
602
+ const warm = plain2.slice(1, 4).find((line) => [identity.warm, identity.installWarm, identity.checkWarm].some((text) => line.includes(text)));
588
603
  if (!warm) fail("welcome is the product table's", `the warm line for ${options.product} is missing or reworded`);
589
604
  }
590
605
  const painted = rendered.join("\n");
@@ -647,22 +662,44 @@ function validateInstallerProduct(value) {
647
662
  if (doctor !== identity.doctor) return fail2("doctor");
648
663
  if (!Array.isArray(input.surfaces) || input.surfaces.length === 0) return fail2("surfaces");
649
664
  const ids = /* @__PURE__ */ new Set();
650
- const surfaces = input.surfaces.map((raw) => {
651
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fail2("surface");
652
- const source = raw;
653
- const id = text(source.id, "surface.id");
665
+ const surfaces = input.surfaces.map((raw2) => {
666
+ if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) return fail2("surface");
667
+ const source2 = raw2;
668
+ const id = text(source2.id, "surface.id");
654
669
  if (ids.has(id)) return fail2("duplicate surface.id");
655
670
  ids.add(id);
656
671
  const surface = { id };
657
672
  for (const field of ["npm", "bin", "kind", "activation"]) {
658
- if (source[field] !== void 0) surface[field] = text(source[field], `surface.${field}`);
673
+ if (source2[field] !== void 0) surface[field] = text(source2[field], `surface.${field}`);
659
674
  }
660
675
  if (Boolean(surface.npm) !== Boolean(surface.bin)) return fail2("surface npm/bin pair");
661
676
  if (surface.kind !== void 0 && !["agent-home", "payload"].includes(surface.kind)) return fail2("surface.kind");
662
677
  if (!surface.npm && !surface.kind) return fail2("surface implementation");
663
678
  return surface;
664
679
  });
665
- return { product, gate, doctor, surfaces };
680
+ if (input.launcher === void 0) return { product, gate, doctor, surfaces };
681
+ const raw = input.launcher;
682
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fail2("launcher");
683
+ const source = raw;
684
+ const https = (value2, field) => {
685
+ const url = text(value2, field).replace(/\/+$/u, "");
686
+ let parsed;
687
+ try {
688
+ parsed = new URL(url);
689
+ } catch {
690
+ return fail2(field);
691
+ }
692
+ return parsed.protocol === "https:" && !parsed.username && !parsed.password ? url : fail2(field);
693
+ };
694
+ const launcher = {
695
+ product: text(source.product, "launcher.product"),
696
+ bin: text(source.bin, "launcher.bin"),
697
+ key: text(source.key, "launcher.key"),
698
+ host: https(source.host, "launcher.host")
699
+ };
700
+ if (source.dl !== void 0) launcher.dl = https(source.dl, "launcher.dl");
701
+ if (Buffer.from(launcher.key, "base64").length !== 32) return fail2("launcher.key");
702
+ return { product, gate, doctor, surfaces, launcher };
666
703
  }
667
704
  var PHASES = {
668
705
  preflight: ["Checking prerequisites", "Checked prerequisites"],
@@ -687,13 +724,30 @@ var UPDATE_CHECK_PHASES = /* @__PURE__ */ new Set([
687
724
  "sign-in",
688
725
  "check"
689
726
  ]);
727
+ var SURFACE_WORDS = Object.freeze({
728
+ updated: "updated",
729
+ current: "already current",
730
+ failed: "failed",
731
+ skipped: "skipped",
732
+ retry: "retrying",
733
+ pending: "pending",
734
+ kept: "kept",
735
+ /** Read-only report states. */
736
+ behind: "update available",
737
+ ahead: "ahead of the release",
738
+ staged: "next launch",
739
+ unknown: "unknown",
740
+ missing: "not installed"
741
+ });
742
+ var AUTO_UPDATE = "auto-update";
743
+ var LAUNCHER_SURFACE = "launcher";
690
744
  function createInstallerRun(value, options = {}) {
691
- const declaration = validateInstallerProduct(value);
745
+ const declaration2 = validateInstallerProduct(value);
692
746
  const env = options.env ?? process.env;
693
747
  const tty = options.tty ?? Boolean(process.stdout.isTTY);
694
748
  const face = createFace({
695
749
  operation: options.operation,
696
- product: declaration.product,
750
+ product: declaration2.product,
697
751
  columns: options.columns,
698
752
  env,
699
753
  color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
@@ -814,11 +868,11 @@ function createInstallerRun(value, options = {}) {
814
868
  },
815
869
  surface(facts) {
816
870
  if (finished) throw new Error("installer run already finished");
817
- const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
871
+ const surface = declaration2.surfaces.find((surface2) => surface2.id === facts.id) ?? (facts.id === LAUNCHER_SURFACE ? { id: LAUNCHER_SURFACE } : void 0);
818
872
  if (!surface) throw new Error("installer run: undeclared surface");
819
873
  start();
820
874
  const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
821
- const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
875
+ const status = facts.state === "updated" && options.dryRun ? "would update" : SURFACE_WORDS[facts.state];
822
876
  if (!status) throw new Error("installer run: unknown surface state");
823
877
  const separator = tty ? "\xB7" : "-";
824
878
  const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
@@ -926,14 +980,15 @@ function createInstallerRun(value, options = {}) {
926
980
  if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
927
981
  const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
928
982
  const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
983
+ const deferred = Boolean(facts.deferred) && facts.failed === 0 && !facts.operationFailed;
929
984
  const body = [
930
- `${ready ? GLYPH.check : facts.dryRun ? GLYPH.dot : GLYPH.cross} ${face.identity.name}${ready ? " is ready." : facts.dryRun ? " preview complete." : facts.deferred ? " update deferred." : " is not ready yet."}`,
985
+ `${ready ? GLYPH.check : facts.dryRun || deferred ? GLYPH.dot : GLYPH.cross} ${face.identity.name}${ready ? " is ready." : facts.dryRun ? " preview complete." : deferred ? ` ${operation === "install" ? "install" : "update"} deferred.` : " is not ready yet."}`,
931
986
  changed,
932
987
  ...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
933
988
  ...facts.detail ? [facts.detail] : [],
934
989
  ...facts.rollback ? [`Rollback: ${facts.rollback}`] : [],
935
990
  ...facts.logPath ? [`Log: ${facts.logPath}`] : facts.logState ? [`Log: ${facts.logState}`] : [],
936
- facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
991
+ facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration2.doctor}`
937
992
  ];
938
993
  if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
939
994
  lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
@@ -950,6 +1005,89 @@ function createInstallerRun(value, options = {}) {
950
1005
  return run;
951
1006
  }
952
1007
 
1008
+ // ../installer/face/src/screens.ts
1009
+ function screenLane(render = {}, stream = "stdout") {
1010
+ const env = render.env ?? process.env;
1011
+ const out = stream === "stdout" ? process.stdout : process.stderr;
1012
+ const tty = render.tty ?? Boolean(out.isTTY);
1013
+ const color = tty && render.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb";
1014
+ return { tty, color, columns: render.columns ?? out.columns, env };
1015
+ }
1016
+ var faceFor = (product, lane, operation) => createFace({ product, color: lane.color, columns: lane.columns, env: { ...lane.env, MM_OUTER_CONSOLE: void 0, MM_FACE_CONTINUES: void 0, MM_PROGRESS_FD: void 0, MM_PROGRESS_FILE: void 0 }, operation });
1017
+ function renderNotice(product, notice2, render) {
1018
+ const lane = screenLane(render, notice2.state === "fail" ? "stderr" : "stdout");
1019
+ const face = faceFor(product, lane);
1020
+ const width = face.width - 3;
1021
+ const [first, ...rest] = wrapWords(plain(notice2.message, lane), width);
1022
+ if (!lane.tty) {
1023
+ return [`${notice2.state === "fail" ? "Error: " : ""}${[first, ...rest].join(" ")}`, ...notice2.next ? [`Next: ${notice2.next}`] : []];
1024
+ }
1025
+ const glyph = notice2.state === "fail" ? face.paint(face.identity.accent, GLYPH.cross) : notice2.state === "note" ? face.paint(PALETTE.muted, GLYPH.dot) : face.paint(face.identity.accent, GLYPH.check);
1026
+ return [
1027
+ `${glyph} ${first}`,
1028
+ ...rest.map((line) => ` ${line}`),
1029
+ ...notice2.next ? [` ${face.paint(PALETTE.muted, "Next:")} ${notice2.next}`] : []
1030
+ ];
1031
+ }
1032
+ function renderReport(product, report, render) {
1033
+ const lane = screenLane(render);
1034
+ const face = faceFor(product, lane, "check");
1035
+ const { verdict } = report;
1036
+ const headline = `${face.identity.name} ${verdict.headline}`;
1037
+ if (!lane.tty) {
1038
+ return [
1039
+ `${face.identity.name} - Mutatis Mutandis`,
1040
+ face.identity.checkWarm,
1041
+ ...report.rows.map((row) => `${row.mark === "fail" ? "Failed: " : ""}${plainText(rowTitle(row))}`),
1042
+ ...report.rows.flatMap((row) => row.note ? [` ${plainText(row.note)}`] : []),
1043
+ `${verdict.state === "fail" ? "Error: " : ""}${plainText(headline)}`,
1044
+ ...(verdict.facts ?? []).map(plainText),
1045
+ `Next: ${verdict.next}`
1046
+ ];
1047
+ }
1048
+ const glyph = verdict.state === "fail" ? GLYPH.cross : verdict.state === "note" ? GLYPH.dot : GLYPH.check;
1049
+ const facts = verdict.facts?.length ? verdict.facts : [verdict.state === "ok" ? "Everything checked passed." : "See the rows above."];
1050
+ return [
1051
+ ...face.welcome(),
1052
+ // A report row IS an update row: glyph, subject, state (#7071). Two layouts for one fact — a
1053
+ // mark mid-row after a subject column here, a leading glyph in `update` — is the drift itself.
1054
+ ...report.rows.flatMap((row) => [face.step(rowTitle(row), null, row.mark ?? "note"), ...row.note ? [face.relay(row.note)] : []]),
1055
+ ...report.rows.length ? [face.paint(PALETTE.muted, GLYPH.bar)] : [],
1056
+ ...face.receipt([`${glyph} ${headline}`, ...facts, `Next: ${verdict.next}`], { ready: verdict.state === "ok" }),
1057
+ face.signOff()
1058
+ ].filter((line) => line !== "");
1059
+ }
1060
+ function renderUsage(product, usage2, render) {
1061
+ const lane = screenLane(render);
1062
+ const face = faceFor(product, lane);
1063
+ const all = [...usage2.commands, ...usage2.options ?? []];
1064
+ const column = Math.min(Math.max(...all.map((entry) => entry.usage.length)), 34);
1065
+ const table = (entries) => entries.flatMap((entry) => {
1066
+ const [first, ...rest] = wrapWords(plain(entry.text, lane), Math.max(20, face.width - column - 6));
1067
+ const head = entry.usage.length > column ? [` ${entry.usage}`, ` ${" ".repeat(column)} ${first}`] : [` ${entry.usage.padEnd(column)} ${first}`];
1068
+ return [...head, ...rest.map((line) => ` ${" ".repeat(column)} ${line}`)];
1069
+ });
1070
+ return [
1071
+ `${face.paint(face.identity.accent, usage2.bin)} ${usage2.version} ${lane.tty ? "\xB7" : "-"} ${plain(usage2.summary, lane)}`,
1072
+ "",
1073
+ `Usage: ${usage2.bin} <command> [options]`,
1074
+ "",
1075
+ "Commands:",
1076
+ ...table(usage2.commands),
1077
+ ...usage2.options?.length ? ["", "Options:", ...table(usage2.options)] : []
1078
+ ];
1079
+ }
1080
+ function renderVersion(bin, version, launcher) {
1081
+ return `${bin} ${version}${launcher ? ` (launcher ${launcher})` : ""}`;
1082
+ }
1083
+ function printScreen(lines, stream = "stdout") {
1084
+ const out = stream === "stdout" ? process.stdout : process.stderr;
1085
+ out.write(lines.map((line) => `${line}
1086
+ `).join(""));
1087
+ }
1088
+ var plain = (text, lane) => lane.tty ? String(text) : plainText(stripColor(String(text)));
1089
+ var rowTitle = (row) => `${row.surface}${row.aspect ? ` ${row.aspect}` : ""}${row.instance ? ` \xB7 ${row.instance}` : ""} ${row.state}`;
1090
+
953
1091
  // ../installer/face/src/maintenance.ts
954
1092
  async function runMaintenance(product, args, dependencies) {
955
1093
  const json = args.includes("--json");
@@ -1016,11 +1154,13 @@ async function runMaintenance(product, args, dependencies) {
1016
1154
  to: arm.to ?? void 0,
1017
1155
  state: arm.verdict === "fail" ? "failed" : arm.verdict === "defer" ? "retry" : arm.verdict === "skip" ? "kept" : arm.from === arm.to && !arm.launchStaged ? "current" : "updated",
1018
1156
  ...arm.durationMs === void 0 ? {} : { seconds: arm.durationMs / 1e3 },
1019
- detail: dependencies.diagnose?.(arm)
1157
+ detail: arm.surface === LAUNCHER_SURFACE ? arm.verdict === "ok" ? void 0 : arm.detail || void 0 : dependencies.diagnose?.(arm)
1020
1158
  });
1021
1159
  };
1160
+ let launcherArm = null;
1022
1161
  const writeJson = (summary, schedule) => {
1023
- process.stdout.write(JSON.stringify(dependencies.jsonResult ? dependencies.jsonResult(summary, schedule) : installed ? { convergence: summary, autoupdate: schedule } : summary, null, 2) + "\n");
1162
+ const result = dependencies.jsonResult ? dependencies.jsonResult(summary, schedule) : installed ? { convergence: summary, autoupdate: schedule } : summary;
1163
+ process.stdout.write(JSON.stringify(launcherArm ? { ...result, launcher: launcherArm } : result, null, 2) + "\n");
1024
1164
  };
1025
1165
  try {
1026
1166
  const summary = await dependencies.engine({
@@ -1076,6 +1216,14 @@ async function runMaintenance(product, args, dependencies) {
1076
1216
  return 0;
1077
1217
  }
1078
1218
  for (const arm of pending) emitArm(arm);
1219
+ if (dependencies.launcher && env.MM_OUTER_CONSOLE !== "1") {
1220
+ const arm = await dependencies.launcher({ dryRun });
1221
+ launcherArm = arm;
1222
+ if (arm) {
1223
+ if (arm.verdict === "fail") armFailed = true;
1224
+ emitArm(arm);
1225
+ }
1226
+ }
1079
1227
  const waiting = summary.surfaces.filter((surface) => surface.present && surface.action === "arm-pending");
1080
1228
  if (!quiet) for (const surface of waiting) run?.surface({ id: surface.id, state: "pending" });
1081
1229
  let schedule = null;
@@ -1125,11 +1273,337 @@ async function runMaintenance(product, args, dependencies) {
1125
1273
  }
1126
1274
  }
1127
1275
 
1276
+ // ../installer/launcher/src/refresh.ts
1277
+ var import_node_child_process2 = require("node:child_process");
1278
+ var import_node_fs6 = require("node:fs");
1279
+ var import_node_os2 = require("node:os");
1280
+ var import_node_path3 = require("node:path");
1281
+
1282
+ // ../installer/launcher/src/acquisition.ts
1283
+ var import_node_crypto2 = require("node:crypto");
1284
+ var import_node_fs5 = require("node:fs");
1285
+ var import_node_path2 = require("node:path");
1286
+
1287
+ // ../installer/launcher/src/download.ts
1288
+ async function readDownload(response, onProgress, expectedSize) {
1289
+ const length = expectedSize ?? Number(response.headers.get("content-length"));
1290
+ const total = Number.isSafeInteger(length) && length > 0 ? length : void 0;
1291
+ let done = 0;
1292
+ const report = () => onProgress?.({ done, ...total === void 0 ? {} : { total } });
1293
+ report();
1294
+ const chunks = [];
1295
+ if (response.body) {
1296
+ for await (const chunk of response.body) {
1297
+ chunks.push(chunk);
1298
+ done += chunk.length;
1299
+ report();
1300
+ }
1301
+ }
1302
+ return Buffer.concat(chunks);
1303
+ }
1304
+
1305
+ // ../installer/launcher/src/store.ts
1306
+ var import_node_os = require("node:os");
1307
+ var import_node_path = require("node:path");
1308
+ function defaultProductDir(product) {
1309
+ if (process.platform === "win32") {
1310
+ const base = process.env.LOCALAPPDATA ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), "launcher-fallback");
1311
+ return (0, import_node_path.join)(base, product);
1312
+ }
1313
+ const home = process.env.HOME ?? (0, import_node_os.tmpdir)();
1314
+ return (0, import_node_path.join)(home, `.${product}`);
1315
+ }
1316
+
1317
+ // ../installer/launcher/src/payload.ts
1318
+ var import_node_crypto = require("node:crypto");
1319
+
1320
+ // ../installer/launcher/src/canonical.ts
1321
+ function canonicalJson(value) {
1322
+ return encode(value);
1323
+ }
1324
+ function encode(value) {
1325
+ if (value === null) return "null";
1326
+ if (typeof value === "string") return JSON.stringify(value);
1327
+ if (typeof value === "boolean") return value ? "true" : "false";
1328
+ if (typeof value === "number") {
1329
+ if (!Number.isFinite(value)) {
1330
+ throw new TypeError("canonicalJson: cannot encode a non-finite number");
1331
+ }
1332
+ return JSON.stringify(value);
1333
+ }
1334
+ if (Array.isArray(value)) {
1335
+ return `[${value.map((entry) => encode(entry)).join(",")}]`;
1336
+ }
1337
+ if (typeof value === "object") {
1338
+ const record = value;
1339
+ const keys = Object.keys(record).filter((key) => record[key] !== void 0).sort();
1340
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${encode(record[key])}`).join(",")}}`;
1341
+ }
1342
+ throw new TypeError(`canonicalJson: cannot encode a value of type ${typeof value}`);
1343
+ }
1344
+
1345
+ // ../installer/launcher/src/config.ts
1346
+ var import_node_sea = require("node:sea");
1347
+ function publicKeyBytes(config) {
1348
+ const raw = Buffer.from(config.publicKey, "base64");
1349
+ if (raw.length !== 32) {
1350
+ throw new Error(`product config for "${config.product}" has a bad publicKey (want 32 raw bytes)`);
1351
+ }
1352
+ return raw;
1353
+ }
1354
+
1355
+ // ../installer/launcher/src/payload.ts
1356
+ function canonicalManifestBytes(manifest2) {
1357
+ return Buffer.from(
1358
+ canonicalJson({
1359
+ created: manifest2.created,
1360
+ files: manifest2.files.map((file) => ({ path: file.path, sha256: file.sha256, size: file.size })),
1361
+ version: manifest2.version
1362
+ }),
1363
+ "utf8"
1364
+ );
1365
+ }
1366
+ function ed25519PublicKey(config) {
1367
+ const raw = publicKeyBytes(config);
1368
+ return (0, import_node_crypto.createPublicKey)({
1369
+ key: { kty: "OKP", crv: "Ed25519", x: raw.toString("base64url") },
1370
+ format: "jwk"
1371
+ });
1372
+ }
1373
+ function verifyManifest(manifest2, signature, config) {
1374
+ try {
1375
+ const signatureBytes = Buffer.from(signature, "base64");
1376
+ if (signatureBytes.length === 0) return false;
1377
+ return (0, import_node_crypto.verify)(null, canonicalManifestBytes(manifest2), ed25519PublicKey(config), signatureBytes);
1378
+ } catch {
1379
+ return false;
1380
+ }
1381
+ }
1382
+ function verifyFileBytes(entry, bytes) {
1383
+ if (entry.size !== bytes.length) return false;
1384
+ return (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex") === entry.sha256.toLowerCase();
1385
+ }
1386
+ function parseManifest(json) {
1387
+ if (typeof json !== "object" || json === null) throw new Error("the release manifest is not an object");
1388
+ const record = json;
1389
+ if (typeof record.version !== "string" || !record.version) throw new Error("the release manifest has no version");
1390
+ if (typeof record.created !== "string" || !record.created) throw new Error("the release manifest has no created stamp");
1391
+ if (!Array.isArray(record.files)) throw new Error("the release manifest has no files list");
1392
+ if (typeof record.signature !== "string" || !record.signature) {
1393
+ throw new Error("the release manifest is unsigned");
1394
+ }
1395
+ const files = record.files.map((entry) => {
1396
+ const file = entry;
1397
+ if (typeof file.path !== "string" || typeof file.sha256 !== "string" || typeof file.size !== "number") {
1398
+ throw new Error("the release manifest lists a malformed file");
1399
+ }
1400
+ const safe = safeManifestPath(file.path);
1401
+ if (safe === null) throw new Error(`the release manifest lists an unsafe path: ${file.path}`);
1402
+ return { path: safe, sha256: file.sha256, size: file.size };
1403
+ });
1404
+ return { version: record.version, created: record.created, files, signature: record.signature };
1405
+ }
1406
+ function safeManifestPath(rawPath) {
1407
+ if (rawPath.length === 0 || rawPath.includes("\0") || rawPath.includes("\\")) return null;
1408
+ if (rawPath.startsWith("/")) return null;
1409
+ const segments = rawPath.split("/");
1410
+ for (const segment of segments) {
1411
+ if (segment === "" || segment === "." || segment === "..") return null;
1412
+ }
1413
+ return segments.join("/");
1414
+ }
1415
+
1416
+ // ../installer/launcher/src/acquisition.ts
1417
+ function lockInstallation(dir) {
1418
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
1419
+ const path = (0, import_node_path2.join)(dir, "installation.lock");
1420
+ const owner = `${process.pid}:${(0, import_node_crypto2.randomUUID)()}`;
1421
+ try {
1422
+ (0, import_node_fs5.writeFileSync)(path, owner, { flag: "wx", mode: 384 });
1423
+ } catch (error) {
1424
+ if (error.code !== "EEXIST") throw error;
1425
+ const recovery = `${path}.recovery`;
1426
+ try {
1427
+ (0, import_node_fs5.writeFileSync)(recovery, owner, { flag: "wx", mode: 384, flush: true });
1428
+ } catch {
1429
+ throw new Error("installation lock recovery is already pending");
1430
+ }
1431
+ try {
1432
+ const previous = (0, import_node_fs5.readFileSync)(path, "utf8");
1433
+ const pid = Number(previous.split(":")[0]);
1434
+ if (!Number.isInteger(pid) || pid <= 0) throw new Error("installation lock is malformed; recovery required");
1435
+ try {
1436
+ process.kill(pid, 0);
1437
+ throw new Error("another installer is running");
1438
+ } catch (probe) {
1439
+ if (probe.code !== "ESRCH") throw probe;
1440
+ }
1441
+ (0, import_node_fs5.unlinkSync)(path);
1442
+ (0, import_node_fs5.writeFileSync)(path, owner, { flag: "wx", mode: 384, flush: true });
1443
+ } finally {
1444
+ (0, import_node_fs5.unlinkSync)(recovery);
1445
+ }
1446
+ }
1447
+ let released = false;
1448
+ return () => {
1449
+ if (!released && (0, import_node_fs5.readFileSync)(path, "utf8") === owner) (0, import_node_fs5.unlinkSync)(path);
1450
+ released = true;
1451
+ };
1452
+ }
1453
+
1454
+ // ../installer/launcher/src/refresh.ts
1455
+ var LAUNCHER_RECORD_FILE = "launcher.json";
1456
+ var RECORD_TIMEOUT_MS = 1e4;
1457
+ function platformKey(platform, arch) {
1458
+ if (platform === "win32") return arch === "x64" || arch === "arm64" ? `win-${arch}` : null;
1459
+ return platform === "darwin" && arch === "arm64" ? "darwin-arm64" : null;
1460
+ }
1461
+ function verifierFor(target) {
1462
+ return { product: target.product, host: target.host, loginKind: "github", publicKey: target.key, binName: target.bin };
1463
+ }
1464
+ async function fetchRecord(target, options) {
1465
+ let json;
1466
+ try {
1467
+ const response = await (options.fetch ?? fetch)(
1468
+ `${target.host.replace(/\/+$/u, "")}/release/launcher`,
1469
+ { signal: AbortSignal.timeout(options.timeoutMs ?? RECORD_TIMEOUT_MS) }
1470
+ );
1471
+ if (!response.ok) return null;
1472
+ json = await response.json();
1473
+ } catch {
1474
+ return null;
1475
+ }
1476
+ let record;
1477
+ try {
1478
+ record = parseManifest(json);
1479
+ } catch {
1480
+ return null;
1481
+ }
1482
+ if (!verifyManifest(record, record.signature, verifierFor(target))) return "bad-signature";
1483
+ return { version: record.version, files: record.files };
1484
+ }
1485
+ function compareVersions(a, b) {
1486
+ const split = (v) => {
1487
+ const [core, pre = ""] = v.split("-", 2);
1488
+ return { nums: core.split(".").map((n) => Number.parseInt(n, 10) || 0), pre };
1489
+ };
1490
+ const x = split(a);
1491
+ const y = split(b);
1492
+ for (let i = 0; i < 3; i++) if ((x.nums[i] ?? 0) !== (y.nums[i] ?? 0)) return (x.nums[i] ?? 0) > (y.nums[i] ?? 0) ? 1 : -1;
1493
+ if (x.pre === y.pre) return 0;
1494
+ if (!x.pre) return 1;
1495
+ if (!y.pre) return -1;
1496
+ return x.pre > y.pre ? 1 : -1;
1497
+ }
1498
+ function recordedPath(productDir) {
1499
+ try {
1500
+ const saved = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path3.join)(productDir, LAUNCHER_RECORD_FILE), "utf8"));
1501
+ return typeof saved.path === "string" && saved.path ? saved.path : null;
1502
+ } catch {
1503
+ return null;
1504
+ }
1505
+ }
1506
+ function installedPath(target, productDir, platform) {
1507
+ return platform === "win32" ? (0, import_node_path3.join)(productDir, "bin", `${target.bin}.exe`) : (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".local", "bin", target.bin);
1508
+ }
1509
+ function parseLauncherVersion(stdout, bin) {
1510
+ const text = String(stdout).trim();
1511
+ if (!text.startsWith(`${bin} `)) return null;
1512
+ return text.match(/launcher (\d+\.\d+\.\d+[^\s)]*)/u)?.[1] ?? null;
1513
+ }
1514
+ function runVersion(path, bin) {
1515
+ return new Promise((resolve2) => {
1516
+ (0, import_node_child_process2.execFile)(path, ["--version"], { timeout: 3e4, windowsHide: true, encoding: "utf8" }, (error, stdout) => {
1517
+ resolve2(error ? null : parseLauncherVersion(stdout, bin));
1518
+ });
1519
+ });
1520
+ }
1521
+ function removeStale(file) {
1522
+ const prefix = `${(0, import_node_path3.basename)(file)}.old-`;
1523
+ let names;
1524
+ try {
1525
+ names = (0, import_node_fs6.readdirSync)((0, import_node_path3.dirname)(file));
1526
+ } catch {
1527
+ return;
1528
+ }
1529
+ for (const name of names) {
1530
+ if (!name.startsWith(prefix)) continue;
1531
+ try {
1532
+ (0, import_node_fs6.rmSync)((0, import_node_path3.join)((0, import_node_path3.dirname)(file), name), { force: true });
1533
+ } catch {
1534
+ }
1535
+ }
1536
+ }
1537
+ async function refreshLauncher(target, options = {}) {
1538
+ const started = Date.now();
1539
+ const platform = options.platform ?? process.platform;
1540
+ const key = platformKey(platform, options.arch ?? process.arch);
1541
+ if (!key) return null;
1542
+ const productDir = options.productDir ?? defaultProductDir(target.product);
1543
+ const recorded = recordedPath(productDir);
1544
+ const file = options.path ?? (recorded && (0, import_node_fs6.existsSync)(recorded) ? recorded : installedPath(target, productDir, platform));
1545
+ if (!(0, import_node_fs6.existsSync)(file) || (0, import_node_fs6.lstatSync)(file).isSymbolicLink()) return null;
1546
+ if (!options.dryRun) removeStale(file);
1547
+ const from = await (options.readVersion ?? ((path) => runVersion(path, target.bin)))(file);
1548
+ if (!from) return null;
1549
+ const arm = (verdict, to, detail) => ({ surface: "launcher", from, to, verdict, detail, durationMs: Date.now() - started });
1550
+ const record = await fetchRecord(target, options);
1551
+ if (record === "bad-signature") return arm("fail", from, "The launcher record failed its signature check.");
1552
+ const entry = record?.files.find((candidate) => candidate.path === key);
1553
+ if (!record || !entry) return arm("skip", from, "No launcher update is offered yet.");
1554
+ if (compareVersions(record.version, from) <= 0) return arm("ok", from, "");
1555
+ if (options.dryRun) return arm("ok", record.version, "");
1556
+ let unlock;
1557
+ if (!options.lockHeld) {
1558
+ try {
1559
+ unlock = lockInstallation(productDir);
1560
+ } catch {
1561
+ return arm("defer", from, "Another install is running; the launcher updates next time.");
1562
+ }
1563
+ }
1564
+ const next = `${file}.new-${process.pid}`;
1565
+ let moved;
1566
+ try {
1567
+ let bytes;
1568
+ try {
1569
+ const response = await (options.fetch ?? fetch)(
1570
+ `${(target.dl ?? target.host).replace(/\/+$/u, "")}/dl/${target.product}/${key}`,
1571
+ { signal: AbortSignal.timeout(3e5) }
1572
+ );
1573
+ if (!response.ok) return arm("defer", from, "The launcher download did not complete; it retries next time.");
1574
+ bytes = await readDownload(response, void 0, entry.size);
1575
+ } catch {
1576
+ return arm("defer", from, "The launcher download did not complete; it retries next time.");
1577
+ }
1578
+ if (!verifyFileBytes(entry, bytes)) return arm("fail", from, "The downloaded launcher failed its checksum.");
1579
+ try {
1580
+ (0, import_node_fs6.writeFileSync)(next, bytes, { mode: 493 });
1581
+ if (platform !== "win32") (0, import_node_fs6.chmodSync)(next, 493);
1582
+ if (platform === "win32") {
1583
+ moved = `${file}.old-${process.pid}-${Date.now()}`;
1584
+ (0, import_node_fs6.renameSync)(file, moved);
1585
+ }
1586
+ (0, import_node_fs6.renameSync)(next, file);
1587
+ } catch {
1588
+ try {
1589
+ if (moved && !(0, import_node_fs6.existsSync)(file)) (0, import_node_fs6.renameSync)(moved, file);
1590
+ } catch {
1591
+ }
1592
+ (0, import_node_fs6.rmSync)(next, { force: true });
1593
+ return arm("fail", from, "The launcher could not be replaced.");
1594
+ }
1595
+ return arm("ok", record.version, "");
1596
+ } finally {
1597
+ unlock?.();
1598
+ }
1599
+ }
1600
+
1128
1601
  // ../installer.product.json
1129
1602
  var installer_product_default = {
1130
1603
  product: "mmi",
1131
1604
  gate: "https://gate.mutmut.work/mmi",
1132
1605
  doctor: "mmi doctor",
1606
+ launcher: { product: "mmi", bin: "mmi", key: "sFcp6EE2MtSEiDtnrIqwMoQVhvu+69rIy4Pw5QLeosk=", host: "https://get.mutmut.work/mmi" },
1133
1607
  surfaces: [
1134
1608
  { id: "updater", npm: "@mutmutco/hub", bin: "mmi-hub" },
1135
1609
  { id: "cli", npm: "@mutmutco/cli", bin: "mmi-cli" },
@@ -1142,22 +1616,22 @@ var installer_product_default = {
1142
1616
  };
1143
1617
 
1144
1618
  // src/state.ts
1145
- var import_node_fs5 = require("node:fs");
1146
- var import_node_os = require("node:os");
1147
- var import_node_path = require("node:path");
1619
+ var import_node_fs7 = require("node:fs");
1620
+ var import_node_os3 = require("node:os");
1621
+ var import_node_path4 = require("node:path");
1148
1622
  function stateRoot(env = process.env) {
1149
- return env.MMI_UPDATER_HOME || (0, import_node_path.join)((0, import_node_os.homedir)(), ".mmi", "updater");
1623
+ return env.MMI_UPDATER_HOME || (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".mmi", "updater");
1150
1624
  }
1151
1625
  function statePaths(env = process.env) {
1152
1626
  const root = stateRoot(env);
1153
- const staging = (0, import_node_path.join)(root, "staging");
1154
- const gitCache = (0, import_node_path.join)(root, "git-cache", "mmi-hub.git");
1155
- return { root, staging, gitCache, journalPath: (0, import_node_path.join)(root, "journal.jsonl"), leasePath: (0, import_node_path.join)(root, "lease.lock") };
1627
+ const staging = (0, import_node_path4.join)(root, "staging");
1628
+ const gitCache = (0, import_node_path4.join)(root, "git-cache", "mmi-hub.git");
1629
+ return { root, staging, gitCache, journalPath: (0, import_node_path4.join)(root, "journal.jsonl"), leasePath: (0, import_node_path4.join)(root, "lease.lock") };
1156
1630
  }
1157
1631
  function ensureState(env = process.env) {
1158
1632
  const paths = statePaths(env);
1159
- (0, import_node_fs5.mkdirSync)(paths.staging, { recursive: true });
1160
- (0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(paths.gitCache), { recursive: true });
1633
+ (0, import_node_fs7.mkdirSync)(paths.staging, { recursive: true });
1634
+ (0, import_node_fs7.mkdirSync)((0, import_node_path4.dirname)(paths.gitCache), { recursive: true });
1161
1635
  return paths;
1162
1636
  }
1163
1637
  var UpdaterBusy = class extends Error {
@@ -1167,15 +1641,15 @@ var LEASE_STALE_MS = 10 * 6e4;
1167
1641
  function tryAcquire(path) {
1168
1642
  const token = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
1169
1643
  try {
1170
- const fd = (0, import_node_fs5.openSync)(path, "wx");
1171
- (0, import_node_fs5.writeFileSync)(fd, JSON.stringify({ token, pid: process.pid, ts: Date.now() }) + "\n");
1172
- (0, import_node_fs5.closeSync)(fd);
1644
+ const fd = (0, import_node_fs7.openSync)(path, "wx");
1645
+ (0, import_node_fs7.writeFileSync)(fd, JSON.stringify({ token, pid: process.pid, ts: Date.now() }) + "\n");
1646
+ (0, import_node_fs7.closeSync)(fd);
1173
1647
  return {
1174
1648
  token,
1175
1649
  release() {
1176
1650
  try {
1177
1651
  const current2 = readLease(path);
1178
- if (current2 && current2.token === token) (0, import_node_fs5.unlinkSync)(path);
1652
+ if (current2 && current2.token === token) (0, import_node_fs7.unlinkSync)(path);
1179
1653
  } catch {
1180
1654
  }
1181
1655
  }
@@ -1186,7 +1660,7 @@ function tryAcquire(path) {
1186
1660
  const current = readLease(path);
1187
1661
  if (current && Date.now() - current.ts > LEASE_STALE_MS) {
1188
1662
  try {
1189
- (0, import_node_fs5.renameSync)(path, `${path}.stale-${Date.now()}`);
1663
+ (0, import_node_fs7.renameSync)(path, `${path}.stale-${Date.now()}`);
1190
1664
  return "stolen-stale";
1191
1665
  } catch {
1192
1666
  }
@@ -1195,7 +1669,7 @@ function tryAcquire(path) {
1195
1669
  }
1196
1670
  function readLease(path) {
1197
1671
  try {
1198
- return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
1672
+ return JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
1199
1673
  } catch {
1200
1674
  return null;
1201
1675
  }
@@ -1220,10 +1694,10 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
1220
1694
  }
1221
1695
  function appendJournal(path, event) {
1222
1696
  try {
1223
- (0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
1224
- const fd = (0, import_node_fs5.openSync)(path, "a");
1225
- (0, import_node_fs5.writeFileSync)(fd, JSON.stringify(event) + "\n");
1226
- (0, import_node_fs5.closeSync)(fd);
1697
+ (0, import_node_fs7.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
1698
+ const fd = (0, import_node_fs7.openSync)(path, "a");
1699
+ (0, import_node_fs7.writeFileSync)(fd, JSON.stringify(event) + "\n");
1700
+ (0, import_node_fs7.closeSync)(fd);
1227
1701
  return true;
1228
1702
  } catch {
1229
1703
  return false;
@@ -1242,7 +1716,7 @@ function journalHighWater(path, surface, events) {
1242
1716
  function parseJournal(path) {
1243
1717
  const events = [];
1244
1718
  try {
1245
- for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
1719
+ for (const line of (0, import_node_fs7.readFileSync)(path, "utf8").split("\n")) {
1246
1720
  if (!line.trim()) continue;
1247
1721
  try {
1248
1722
  events.push(JSON.parse(line));
@@ -1256,7 +1730,7 @@ function parseJournal(path) {
1256
1730
  function journalSurfaceRepeatedFail(path, surface, currentRun) {
1257
1731
  let lastVerdict = null;
1258
1732
  try {
1259
- for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
1733
+ for (const line of (0, import_node_fs7.readFileSync)(path, "utf8").split("\n")) {
1260
1734
  if (!line.trim()) continue;
1261
1735
  try {
1262
1736
  const event = JSON.parse(line);
@@ -1299,26 +1773,26 @@ function compareSemver(a, b) {
1299
1773
  return va.pre.join(".") < vb.pre.join(".") ? -1 : va.pre.join(".") > vb.pre.join(".") ? 1 : 0;
1300
1774
  }
1301
1775
  function fileExists(path) {
1302
- return (0, import_node_fs5.existsSync)(path);
1776
+ return (0, import_node_fs7.existsSync)(path);
1303
1777
  }
1304
1778
 
1305
1779
  // src/reconcile.ts
1306
1780
  var import_node_perf_hooks2 = require("node:perf_hooks");
1307
1781
 
1308
1782
  // src/npm.ts
1309
- var import_node_child_process2 = require("node:child_process");
1310
- var import_node_fs6 = require("node:fs");
1311
- var import_node_path2 = require("node:path");
1783
+ var import_node_child_process3 = require("node:child_process");
1784
+ var import_node_fs8 = require("node:fs");
1785
+ var import_node_path5 = require("node:path");
1312
1786
  var cachedNpm = null;
1313
1787
  function resolveNpm(env = process.env) {
1314
1788
  if (env.MMI_UPDATER_NPM) return { kind: "command", command: env.MMI_UPDATER_NPM };
1315
1789
  if (cachedNpm) return cachedNpm;
1316
1790
  if (process.platform === "win32") {
1317
- const where = (0, import_node_child_process2.spawnSync)("where.exe", ["npm.cmd"], { encoding: "utf8", windowsHide: true });
1791
+ const where = (0, import_node_child_process3.spawnSync)("where.exe", ["npm.cmd"], { encoding: "utf8", windowsHide: true });
1318
1792
  const first = where.stdout.split("\n").map((l) => l.trim()).find(Boolean);
1319
1793
  if (where.status === 0 && first) {
1320
- const script = (0, import_node_path2.join)((0, import_node_path2.dirname)(first), "node_modules", "npm", "bin", "npm-cli.js");
1321
- if ((0, import_node_fs6.existsSync)(script)) {
1794
+ const script = (0, import_node_path5.join)((0, import_node_path5.dirname)(first), "node_modules", "npm", "bin", "npm-cli.js");
1795
+ if ((0, import_node_fs8.existsSync)(script)) {
1322
1796
  cachedNpm = { kind: "node-script", script };
1323
1797
  return cachedNpm;
1324
1798
  }
@@ -1326,14 +1800,14 @@ function resolveNpm(env = process.env) {
1326
1800
  return cachedNpm;
1327
1801
  }
1328
1802
  }
1329
- const bundled = (0, import_node_path2.join)((0, import_node_path2.dirname)(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
1330
- if ((0, import_node_fs6.existsSync)(bundled)) {
1803
+ const bundled = (0, import_node_path5.join)((0, import_node_path5.dirname)(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
1804
+ if ((0, import_node_fs8.existsSync)(bundled)) {
1331
1805
  cachedNpm = { kind: "node-script", script: bundled };
1332
1806
  return cachedNpm;
1333
1807
  }
1334
- const located = (0, import_node_child_process2.spawnSync)("/bin/sh", ["-lc", "command -v npm"], { encoding: "utf8", windowsHide: true });
1808
+ const located = (0, import_node_child_process3.spawnSync)("/bin/sh", ["-lc", "command -v npm"], { encoding: "utf8", windowsHide: true });
1335
1809
  const resolved = (located.stdout ?? "").trim().split("\n").map((line) => line.trim()).find(Boolean);
1336
- if (located.status === 0 && resolved && (0, import_node_fs6.existsSync)(resolved)) {
1810
+ if (located.status === 0 && resolved && (0, import_node_fs8.existsSync)(resolved)) {
1337
1811
  cachedNpm = { kind: "command", command: resolved };
1338
1812
  return cachedNpm;
1339
1813
  }
@@ -1342,20 +1816,20 @@ function resolveNpm(env = process.env) {
1342
1816
  }
1343
1817
  function runNpm(args, env = process.env, options = {}) {
1344
1818
  const npm = resolveNpm(env);
1345
- const result = npm.kind === "node-script" ? (0, import_node_child_process2.spawnSync)(process.execPath, [npm.script, ...args], { encoding: "utf8", env, cwd: options.cwd, windowsHide: true }) : (0, import_node_child_process2.spawnSync)(npm.command, args, { encoding: "utf8", env, cwd: options.cwd, shell: process.platform === "win32", windowsHide: true });
1819
+ const result = npm.kind === "node-script" ? (0, import_node_child_process3.spawnSync)(process.execPath, [npm.script, ...args], { encoding: "utf8", env, cwd: options.cwd, windowsHide: true }) : (0, import_node_child_process3.spawnSync)(npm.command, args, { encoding: "utf8", env, cwd: options.cwd, shell: process.platform === "win32", windowsHide: true });
1346
1820
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
1347
1821
  }
1348
1822
  function runNpmAsync(args, env = process.env, options = {}) {
1349
1823
  const npm = resolveNpm(env);
1350
1824
  const [file, argv, shell] = npm.kind === "node-script" ? [process.execPath, [npm.script, ...args], false] : [npm.command, args, process.platform === "win32"];
1351
- return new Promise((resolve) => {
1825
+ return new Promise((resolve2) => {
1352
1826
  let stdout = "";
1353
1827
  let stderr = "";
1354
1828
  let child;
1355
1829
  try {
1356
- child = (0, import_node_child_process2.spawn)(file, argv, { env, cwd: options.cwd, windowsHide: true, shell });
1830
+ child = (0, import_node_child_process3.spawn)(file, argv, { env, cwd: options.cwd, windowsHide: true, shell });
1357
1831
  } catch (error) {
1358
- resolve({ status: null, stdout, stderr: String(error?.message ?? error) });
1832
+ resolve2({ status: null, stdout, stderr: String(error?.message ?? error) });
1359
1833
  return;
1360
1834
  }
1361
1835
  child.stdout?.setEncoding("utf8");
@@ -1366,8 +1840,8 @@ function runNpmAsync(args, env = process.env, options = {}) {
1366
1840
  child.stderr?.on("data", (chunk) => {
1367
1841
  stderr += chunk;
1368
1842
  });
1369
- child.on("error", (error) => resolve({ status: null, stdout, stderr: String(error?.message ?? error) }));
1370
- child.on("close", (status) => resolve({ status, stdout, stderr }));
1843
+ child.on("error", (error) => resolve2({ status: null, stdout, stderr: String(error?.message ?? error) }));
1844
+ child.on("close", (status) => resolve2({ status, stdout, stderr }));
1371
1845
  });
1372
1846
  }
1373
1847
  var globalRootMemo = /* @__PURE__ */ new WeakMap();
@@ -1414,8 +1888,8 @@ function npmVersions(packageName, env = process.env) {
1414
1888
  }
1415
1889
 
1416
1890
  // src/gate.ts
1417
- var import_node_child_process3 = require("node:child_process");
1418
- var import_node_fs7 = require("node:fs");
1891
+ var import_node_child_process4 = require("node:child_process");
1892
+ var import_node_fs9 = require("node:fs");
1419
1893
  function parseBom(text) {
1420
1894
  const bom = JSON.parse(text);
1421
1895
  if (typeof bom?.version !== "string" || typeof bom?.sourceCommit !== "string" || !Array.isArray(bom?.artifacts)) {
@@ -1429,16 +1903,16 @@ function npmPackRows(bom) {
1429
1903
  var GateEnvError = class extends Error {
1430
1904
  };
1431
1905
  function git(cachePath, args) {
1432
- const result = (0, import_node_child_process3.spawnSync)("git", ["-C", cachePath, ...args], { encoding: "utf8", windowsHide: true });
1906
+ const result = (0, import_node_child_process4.spawnSync)("git", ["-C", cachePath, ...args], { encoding: "utf8", windowsHide: true });
1433
1907
  return { status: result.status, stdout: (result.stdout ?? "").trim(), stderr: result.stderr ?? "" };
1434
1908
  }
1435
1909
  function ensureBareCache(cachePath, repoUrl) {
1436
- const probe = (0, import_node_child_process3.spawnSync)("git", ["--version"], { encoding: "utf8", windowsHide: true });
1910
+ const probe = (0, import_node_child_process4.spawnSync)("git", ["--version"], { encoding: "utf8", windowsHide: true });
1437
1911
  if (probe.status !== 0) {
1438
1912
  throw new GateEnvError("git is not available \u2014 the release gate needs a git client");
1439
1913
  }
1440
- if (!(0, import_node_fs7.existsSync)(cachePath)) {
1441
- const init = (0, import_node_child_process3.spawnSync)("git", ["init", "--bare", cachePath], { encoding: "utf8", windowsHide: true });
1914
+ if (!(0, import_node_fs9.existsSync)(cachePath)) {
1915
+ const init = (0, import_node_child_process4.spawnSync)("git", ["init", "--bare", cachePath], { encoding: "utf8", windowsHide: true });
1442
1916
  if (init.status !== 0) throw new GateEnvError(`cannot create bare cache at ${cachePath}: ${init.stderr}`);
1443
1917
  }
1444
1918
  }
@@ -1544,39 +2018,39 @@ async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
1544
2018
  }
1545
2019
 
1546
2020
  // src/surfaces.ts
1547
- var import_node_fs12 = require("node:fs");
1548
- var import_node_os3 = require("node:os");
1549
- var import_node_path7 = require("node:path");
2021
+ var import_node_fs14 = require("node:fs");
2022
+ var import_node_os5 = require("node:os");
2023
+ var import_node_path10 = require("node:path");
1550
2024
 
1551
2025
  // src/arm-host-plugins.ts
1552
- var import_node_child_process6 = require("node:child_process");
1553
- var import_node_fs11 = require("node:fs");
1554
- var import_node_os2 = require("node:os");
1555
- var import_node_path6 = require("node:path");
2026
+ var import_node_child_process7 = require("node:child_process");
2027
+ var import_node_fs13 = require("node:fs");
2028
+ var import_node_os4 = require("node:os");
2029
+ var import_node_path9 = require("node:path");
1556
2030
 
1557
2031
  // src/arm-npm-global.ts
1558
- var import_node_fs9 = require("node:fs");
1559
- var import_node_child_process5 = require("node:child_process");
1560
- var import_node_path4 = require("node:path");
2032
+ var import_node_fs11 = require("node:fs");
2033
+ var import_node_child_process6 = require("node:child_process");
2034
+ var import_node_path7 = require("node:path");
1561
2035
 
1562
2036
  // src/pending.ts
1563
- var import_node_child_process4 = require("node:child_process");
1564
- var import_node_fs8 = require("node:fs");
1565
- var import_node_path3 = require("node:path");
2037
+ var import_node_child_process5 = require("node:child_process");
2038
+ var import_node_fs10 = require("node:fs");
2039
+ var import_node_path6 = require("node:path");
1566
2040
  function hubDistPath(env) {
1567
2041
  const root = globalRoot(env);
1568
- return root ? (0, import_node_path3.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
2042
+ return root ? (0, import_node_path6.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
1569
2043
  }
1570
2044
  var WATCHER_MAX_WAIT_MS = 4 * 60 * 60 * 1e3;
1571
2045
  function pendingPath(paths) {
1572
- return (0, import_node_path3.join)(paths.root, "pending.json");
2046
+ return (0, import_node_path6.join)(paths.root, "pending.json");
1573
2047
  }
1574
2048
  function watcherLockPath(paths) {
1575
- return (0, import_node_path3.join)(paths.root, "pending-watcher.lock.d");
2049
+ return (0, import_node_path6.join)(paths.root, "pending-watcher.lock.d");
1576
2050
  }
1577
2051
  function readPending(paths) {
1578
2052
  try {
1579
- const parsed = JSON.parse((0, import_node_fs8.readFileSync)(pendingPath(paths), "utf8"));
2053
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(pendingPath(paths), "utf8"));
1580
2054
  if (!Array.isArray(parsed)) return [];
1581
2055
  return parsed.filter((row) => Boolean(row) && typeof row === "object" && typeof row.surface === "string" && typeof row.target === "string" && Array.isArray(row.images) && Array.isArray(row.dirs));
1582
2056
  } catch {
@@ -1584,12 +2058,12 @@ function readPending(paths) {
1584
2058
  }
1585
2059
  }
1586
2060
  function writePending(paths, records) {
1587
- (0, import_node_fs8.mkdirSync)(paths.root, { recursive: true });
2061
+ (0, import_node_fs10.mkdirSync)(paths.root, { recursive: true });
1588
2062
  const path = pendingPath(paths);
1589
2063
  const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
1590
- (0, import_node_fs8.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
2064
+ (0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
1591
2065
  `, "utf8");
1592
- (0, import_node_fs8.renameSync)(tmp, path);
2066
+ (0, import_node_fs10.renameSync)(tmp, path);
1593
2067
  }
1594
2068
  function clearPending(paths, surface) {
1595
2069
  const records = readPending(paths);
@@ -1641,7 +2115,7 @@ function buildWatcherCommand(opts) {
1641
2115
  return { file: "sh", args: ["-c", `${testBusy}; ${wait}; ${lock}; ${run}`] };
1642
2116
  }
1643
2117
  function defaultDetachSpawn(file, args) {
1644
- const child = (0, import_node_child_process4.spawn)(file, [...args], {
2118
+ const child = (0, import_node_child_process5.spawn)(file, [...args], {
1645
2119
  detached: process.platform !== "win32",
1646
2120
  stdio: "ignore",
1647
2121
  windowsHide: true,
@@ -1658,14 +2132,14 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
1658
2132
  const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
1659
2133
  if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
1660
2134
  const hubDist = seam.hubDist ?? hubDistPath(env);
1661
- if (!hubDist || !(0, import_node_fs8.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
2135
+ if (!hubDist || !(0, import_node_fs10.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
1662
2136
  const command = buildWatcherCommand({
1663
2137
  images: [...new Set(records.flatMap((row) => row.images))],
1664
2138
  dirs: [...new Set(records.flatMap((row) => row.dirs))],
1665
2139
  node: process.execPath,
1666
2140
  hubDist,
1667
- codePath: (0, import_node_path3.join)(paths.root, "pending-result.code"),
1668
- logPath: (0, import_node_path3.join)(paths.root, "pending-result.log"),
2141
+ codePath: (0, import_node_path6.join)(paths.root, "pending-result.code"),
2142
+ logPath: (0, import_node_path6.join)(paths.root, "pending-result.log"),
1669
2143
  lockPath: watcherLockPath(paths)
1670
2144
  });
1671
2145
  try {
@@ -1704,11 +2178,11 @@ function resumePendingWatcher(paths, env = process.env) {
1704
2178
  // src/arm-npm-global.ts
1705
2179
  function globalDist(env, packageName, distRel) {
1706
2180
  const root = globalRoot(env);
1707
- return root ? (0, import_node_path4.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
2181
+ return root ? (0, import_node_path7.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
1708
2182
  }
1709
2183
  function probeVersion(distPath) {
1710
2184
  if (!fileExists(distPath)) return null;
1711
- const probe = (0, import_node_child_process5.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
2185
+ const probe = (0, import_node_child_process6.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
1712
2186
  const version = (probe.stdout || "").trim();
1713
2187
  return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
1714
2188
  }
@@ -1718,7 +2192,7 @@ function tail(output) {
1718
2192
  function manifestVersion(packageRoot) {
1719
2193
  if (!packageRoot) return null;
1720
2194
  try {
1721
- const version = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
2195
+ const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path7.join)(packageRoot, "package.json"), "utf8")).version;
1722
2196
  return typeof version === "string" ? version : null;
1723
2197
  } catch {
1724
2198
  return null;
@@ -1726,11 +2200,11 @@ function manifestVersion(packageRoot) {
1726
2200
  }
1727
2201
  function stagePackage(options) {
1728
2202
  const { surface, packageName, target, witness, paths, env } = options;
1729
- const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
1730
- const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
1731
- if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
1732
- (0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
1733
- (0, import_node_fs9.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
2203
+ const stageRoot = (0, import_node_path7.join)(paths.staging, surface, target);
2204
+ const packageRoot = (0, import_node_path7.join)(stageRoot, "node_modules", ...packageName.split("/"));
2205
+ if (!fileExists((0, import_node_path7.join)(packageRoot, ...witness.split("/")))) {
2206
+ (0, import_node_fs11.mkdirSync)(stageRoot, { recursive: true });
2207
+ (0, import_node_fs11.writeFileSync)((0, import_node_path7.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1734
2208
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
1735
2209
  if (install.status !== 0) {
1736
2210
  return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
@@ -1738,7 +2212,7 @@ function stagePackage(options) {
1738
2212
  }
1739
2213
  let stagedManifestVersion;
1740
2214
  try {
1741
- stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
2215
+ stagedManifestVersion = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path7.join)(packageRoot, "package.json"), "utf8")).version;
1742
2216
  } catch {
1743
2217
  }
1744
2218
  if (stagedManifestVersion !== target) {
@@ -1753,7 +2227,7 @@ function npmGlobalArm(options) {
1753
2227
  const result = (from, to, verdict, detail) => ({ surface, from, to, verdict, detail });
1754
2228
  const highWater = journalHighWater(paths.journalPath, surface, options.journalEvents);
1755
2229
  const localRoot = globalRoot(env);
1756
- const localVersion = localRoot ? manifestVersion((0, import_node_path4.join)(localRoot, ...packageName.split("/"))) : null;
2230
+ const localVersion = localRoot ? manifestVersion((0, import_node_path7.join)(localRoot, ...packageName.split("/"))) : null;
1757
2231
  if (localVersion === target && highWater === target) {
1758
2232
  return result(localVersion, target, "ok", `already at target ${target} (installed manifest + journal high-water)`);
1759
2233
  }
@@ -1775,7 +2249,7 @@ function npmGlobalArm(options) {
1775
2249
  if ("defer" in staged) {
1776
2250
  return result(installed, target, "defer", staged.defer);
1777
2251
  }
1778
- const stageDist = (0, import_node_path4.join)(staged.packageRoot, ...distRel.split("/"));
2252
+ const stageDist = (0, import_node_path7.join)(staged.packageRoot, ...distRel.split("/"));
1779
2253
  const stagedVersion = probeVersion(stageDist);
1780
2254
  if (stagedVersion !== target) {
1781
2255
  return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
@@ -1796,11 +2270,11 @@ function npmGlobalArm(options) {
1796
2270
  }
1797
2271
 
1798
2272
  // src/launch-staging.ts
1799
- var import_node_fs10 = require("node:fs");
1800
- var import_node_path5 = require("node:path");
2273
+ var import_node_fs12 = require("node:fs");
2274
+ var import_node_path8 = require("node:path");
1801
2275
  function readLaunchTarget(path) {
1802
2276
  try {
1803
- const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
2277
+ const parsed = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
1804
2278
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1805
2279
  const row = parsed;
1806
2280
  if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
@@ -1810,15 +2284,15 @@ function readLaunchTarget(path) {
1810
2284
  }
1811
2285
  }
1812
2286
  function writeLaunchTarget(path, record) {
1813
- (0, import_node_fs10.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
2287
+ (0, import_node_fs12.mkdirSync)((0, import_node_path8.join)(path, ".."), { recursive: true });
1814
2288
  const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1815
- (0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
2289
+ (0, import_node_fs12.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
1816
2290
  `, "utf8");
1817
- (0, import_node_fs10.renameSync)(tmp, path);
2291
+ (0, import_node_fs12.renameSync)(tmp, path);
1818
2292
  }
1819
2293
  function clearLaunchTarget(path) {
1820
2294
  try {
1821
- (0, import_node_fs10.rmSync)(path, { force: true });
2295
+ (0, import_node_fs12.rmSync)(path, { force: true });
1822
2296
  } catch {
1823
2297
  }
1824
2298
  }
@@ -1839,7 +2313,7 @@ function noteLaunchStaged(paths, env, options, seam = {}) {
1839
2313
  }
1840
2314
  function probeLaunchTargetVersion(launchTargetPath, versionAt) {
1841
2315
  const record = readLaunchTarget(launchTargetPath);
1842
- if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
2316
+ if (!record || !(0, import_node_fs12.existsSync)(record.incoming)) return null;
1843
2317
  const version = versionAt(record.incoming);
1844
2318
  return version === record.target ? record.target : null;
1845
2319
  }
@@ -1854,13 +2328,13 @@ function cmdArg(value) {
1854
2328
  return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
1855
2329
  }
1856
2330
  var runHostCommand = (command, args, env) => {
1857
- const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
2331
+ const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
1858
2332
  encoding: "utf8",
1859
2333
  env,
1860
2334
  input: "",
1861
2335
  timeout: 12e4,
1862
2336
  windowsHide: true
1863
- }) : (0, import_node_child_process6.spawnSync)(command, args, {
2337
+ }) : (0, import_node_child_process7.spawnSync)(command, args, {
1864
2338
  encoding: "utf8",
1865
2339
  env,
1866
2340
  input: "",
@@ -1887,17 +2361,17 @@ function claudeMmi(runner, env) {
1887
2361
  return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
1888
2362
  }
1889
2363
  function claudeHomeDir(env) {
1890
- const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os2.homedir)();
1891
- return (0, import_node_path6.join)(home, ".claude");
2364
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os4.homedir)();
2365
+ return (0, import_node_path9.join)(home, ".claude");
1892
2366
  }
1893
2367
  function claudeMarketplacesPath(env) {
1894
- return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path6.join)(claudeHomeDir(env), "plugins", "known_marketplaces.json");
2368
+ return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path9.join)(claudeHomeDir(env), "plugins", "known_marketplaces.json");
1895
2369
  }
1896
2370
  function ensureClaudeMarketplace(env) {
1897
2371
  const path = claudeMarketplacesPath(env);
1898
2372
  let body;
1899
2373
  try {
1900
- body = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
2374
+ body = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
1901
2375
  } catch (error) {
1902
2376
  if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
1903
2377
  return { ok: false, detail: `cannot read ${path}` };
@@ -1921,18 +2395,18 @@ var CLAUDE_PACKAGE = "@mutmutco/claude-plugin";
1921
2395
  var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
1922
2396
  var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json"];
1923
2397
  function claudePluginsRoot(env) {
1924
- return (0, import_node_path6.join)(claudeHomeDir(env), "plugins");
2398
+ return (0, import_node_path9.join)(claudeHomeDir(env), "plugins");
1925
2399
  }
1926
2400
  function claudeMarkerVersion(root) {
1927
2401
  try {
1928
- const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
2402
+ const version = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
1929
2403
  return typeof version === "string" ? version : null;
1930
2404
  } catch {
1931
2405
  return null;
1932
2406
  }
1933
2407
  }
1934
2408
  function claudeTreeHealthy(root) {
1935
- return CLAUDE_TREE_MARKERS.every((rel) => fileExists((0, import_node_path6.join)(root, ...rel.split("/"))));
2409
+ return CLAUDE_TREE_MARKERS.every((rel) => fileExists((0, import_node_path9.join)(root, ...rel.split("/"))));
1936
2410
  }
1937
2411
  var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
1938
2412
  var HUB_UPDATE_COMMAND = /(?:\bmmi-hub(?:\.(?:cmd|exe|bat|ps1))?\b|@mutmutco[/\\]hub[/\\]dist[/\\]index\.(?:cjs|mjs|js))[\s'"]+update\b/i;
@@ -1946,7 +2420,7 @@ function claudeProcessToken(line) {
1946
2420
  return null;
1947
2421
  }
1948
2422
  function realClaudeProcessListing() {
1949
- const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process6.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2423
+ const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1950
2424
  return result.status === 0 ? result.stdout ?? "" : null;
1951
2425
  }
1952
2426
  function claudeHostEvidence(env, processListProvider) {
@@ -1962,7 +2436,7 @@ function claudeHostEvidence(env, processListProvider) {
1962
2436
  return null;
1963
2437
  }
1964
2438
  function probeClaudeLaunchStaging(env = process.env) {
1965
- return probeLaunchTargetVersion((0, import_node_path6.join)(claudePluginsRoot(env), "staging", "launch-target.json"), claudeMarkerVersion);
2439
+ return probeLaunchTargetVersion((0, import_node_path9.join)(claudePluginsRoot(env), "staging", "launch-target.json"), claudeMarkerVersion);
1966
2440
  }
1967
2441
  function claudeArm(options) {
1968
2442
  const env = options.env ?? process.env;
@@ -1997,7 +2471,7 @@ function claudeArm(options) {
1997
2471
  }
1998
2472
  if (!marketplaceTrust.ok) return result(from, target, "defer", marketplaceTrust.detail ?? "cannot trust the mutmutco marketplace registration");
1999
2473
  const pluginsRoot = claudePluginsRoot(env);
2000
- const launchTargetPath = (0, import_node_path6.join)(pluginsRoot, "staging", "launch-target.json");
2474
+ const launchTargetPath = (0, import_node_path9.join)(pluginsRoot, "staging", "launch-target.json");
2001
2475
  if (options.dryRun) {
2002
2476
  return result(from, target, "ok", `dry-run: would refresh mutmutco, stage ${target} on the Claude volume, and converge mmi@mutmutco ${from ?? "absent"} -> ${target}`);
2003
2477
  }
@@ -2011,8 +2485,8 @@ function claudeArm(options) {
2011
2485
  clearLaunchTarget(launchTargetPath);
2012
2486
  return result(from, target, "ok", `catalog refreshed; installed+enabled at target ${target}`);
2013
2487
  }
2014
- const incomingRoot = (0, import_node_path6.join)(pluginsRoot, "staging", "incoming");
2015
- const incoming = (0, import_node_path6.join)(incomingRoot, target);
2488
+ const incomingRoot = (0, import_node_path9.join)(pluginsRoot, "staging", "incoming");
2489
+ const incoming = (0, import_node_path9.join)(incomingRoot, target);
2016
2490
  const priorLaunch = readLaunchTarget(launchTargetPath);
2017
2491
  const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && claudeMarkerVersion(incoming) === target && claudeTreeHealthy(incoming);
2018
2492
  if (!reuseIncoming) {
@@ -2022,14 +2496,14 @@ function claudeArm(options) {
2022
2496
  return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
2023
2497
  }
2024
2498
  try {
2025
- (0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(pluginsRoot, "staging"), { recursive: true });
2026
- (0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
2027
- (0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
2499
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(pluginsRoot, "staging"), { recursive: true });
2500
+ (0, import_node_fs13.rmSync)(incomingRoot, { recursive: true, force: true });
2501
+ (0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
2028
2502
  } catch (error) {
2029
2503
  return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
2030
2504
  }
2031
2505
  if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
2032
- (0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
2506
+ (0, import_node_fs13.rmSync)(incomingRoot, { recursive: true, force: true });
2033
2507
  return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
2034
2508
  }
2035
2509
  }
@@ -2059,12 +2533,12 @@ function claudeArm(options) {
2059
2533
  });
2060
2534
  return result(from, target, "ok", `staged ${target} for next launch \u2014 Claude Code started during staging (${started}) \u2014 ${pending}`, true);
2061
2535
  }
2062
- const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
2536
+ const cacheTarget = (0, import_node_path9.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
2063
2537
  try {
2064
- if (!(0, import_node_fs11.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
2065
- (0, import_node_fs11.rmSync)(cacheTarget, { recursive: true, force: true });
2066
- (0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(cacheTarget, ".."), { recursive: true });
2067
- (0, import_node_fs11.cpSync)(incoming, cacheTarget, { recursive: true });
2538
+ if (!(0, import_node_fs13.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
2539
+ (0, import_node_fs13.rmSync)(cacheTarget, { recursive: true, force: true });
2540
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(cacheTarget, ".."), { recursive: true });
2541
+ (0, import_node_fs13.cpSync)(incoming, cacheTarget, { recursive: true });
2068
2542
  }
2069
2543
  } catch (error) {
2070
2544
  return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
@@ -2092,7 +2566,7 @@ function claudeArm(options) {
2092
2566
  }
2093
2567
  clearLaunchTarget(launchTargetPath);
2094
2568
  try {
2095
- (0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
2569
+ (0, import_node_fs13.rmSync)(incomingRoot, { recursive: true, force: true });
2096
2570
  } catch {
2097
2571
  }
2098
2572
  return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
@@ -2171,12 +2645,12 @@ function probeNativeHostInstallation(surface, env = process.env, runner = runHos
2171
2645
 
2172
2646
  // src/surfaces.ts
2173
2647
  function jervcodeAgentDir(env = process.env) {
2174
- return env.MMI_UPDATER_JERVCODE_AGENT_DIR?.trim() || env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".jerv", "agent");
2648
+ return env.MMI_UPDATER_JERVCODE_AGENT_DIR?.trim() || env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".jerv", "agent");
2175
2649
  }
2176
2650
  function hermesHome(env = process.env, platform = process.platform) {
2177
2651
  if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
2178
- if (platform === "win32") return (0, import_node_path7.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), "AppData", "Local"), "hermes");
2179
- return (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".hermes");
2652
+ if (platform === "win32") return (0, import_node_path10.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path10.join)((0, import_node_os5.homedir)(), "AppData", "Local"), "hermes");
2653
+ return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".hermes");
2180
2654
  }
2181
2655
  function hermesHostCommand(env = process.env) {
2182
2656
  return env.MMI_UPDATER_HERMES?.trim() || "hermes";
@@ -2189,37 +2663,37 @@ function surfaceArmPlan(token, managedSurfaces) {
2189
2663
  return managedSurfaces.includes(token) ? "arm" : "retired";
2190
2664
  }
2191
2665
  function enumerateSurfaces(env = process.env) {
2192
- const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os3.homedir)();
2193
- const codexHome = env.CODEX_HOME || (0, import_node_path7.join)(home, ".codex");
2666
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os5.homedir)();
2667
+ const codexHome = env.CODEX_HOME || (0, import_node_path10.join)(home, ".codex");
2194
2668
  return [
2195
- { id: "claude", marker: (0, import_node_path7.join)(home, ".claude", "plugins", "installed_plugins.json") },
2196
- { id: "codex", marker: (0, import_node_path7.join)(codexHome, "plugins", "installed_plugins.json") },
2669
+ { id: "claude", marker: (0, import_node_path10.join)(home, ".claude", "plugins", "installed_plugins.json") },
2670
+ { id: "codex", marker: (0, import_node_path10.join)(codexHome, "plugins", "installed_plugins.json") },
2197
2671
  // The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
2198
2672
  // plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
2199
2673
  // and the arm could never make the first install. The plugin's absence is a converge action, not
2200
2674
  // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
2201
- { id: "cursor", marker: (0, import_node_path7.join)(home, ".cursor") },
2202
- { id: "jervcode", marker: (0, import_node_path7.join)(jervcodeAgentDir(env), "settings.json") },
2675
+ { id: "cursor", marker: (0, import_node_path10.join)(home, ".cursor") },
2676
+ { id: "jervcode", marker: (0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json") },
2203
2677
  // Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
2204
2678
  // leftover plugin manifests — that residue must not enumerate as an installed host.
2205
2679
  { id: "hermes", marker: `${hermesHostCommand(env)} --version` }
2206
2680
  ].map((probe) => ({
2207
2681
  ...probe,
2208
- present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs12.existsSync)(probe.marker)
2682
+ present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs14.existsSync)(probe.marker)
2209
2683
  }));
2210
2684
  }
2211
2685
 
2212
2686
  // src/arm-cursor.ts
2213
- var import_node_fs15 = require("node:fs");
2214
- var import_node_child_process7 = require("node:child_process");
2215
- var import_node_os5 = require("node:os");
2216
- var import_node_path8 = require("node:path");
2687
+ var import_node_fs17 = require("node:fs");
2688
+ var import_node_child_process8 = require("node:child_process");
2689
+ var import_node_os7 = require("node:os");
2690
+ var import_node_path11 = require("node:path");
2217
2691
 
2218
2692
  // src/compat.ts
2219
- var import_node_fs13 = require("node:fs");
2693
+ var import_node_fs15 = require("node:fs");
2220
2694
  function readManifestCompat(manifestPath) {
2221
2695
  try {
2222
- const parsed = JSON.parse((0, import_node_fs13.readFileSync)(manifestPath, "utf8"));
2696
+ const parsed = JSON.parse((0, import_node_fs15.readFileSync)(manifestPath, "utf8"));
2223
2697
  return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
2224
2698
  } catch {
2225
2699
  return void 0;
@@ -2227,8 +2701,8 @@ function readManifestCompat(manifestPath) {
2227
2701
  }
2228
2702
 
2229
2703
  // src/host-doc-lock.ts
2230
- var import_node_fs14 = require("node:fs");
2231
- var import_node_os4 = require("node:os");
2704
+ var import_node_fs16 = require("node:fs");
2705
+ var import_node_os6 = require("node:os");
2232
2706
  var SHARED_DOC_LOCK_STALE_MS = 3e4;
2233
2707
  var SHARED_DOC_LOCK_ATTEMPTS = 50;
2234
2708
  var SHARED_DOC_LOCK_INTERVAL_MS = 100;
@@ -2241,7 +2715,7 @@ function lockContent(options) {
2241
2715
  }
2242
2716
  function readHolder(lockPath) {
2243
2717
  try {
2244
- const parsed = JSON.parse((0, import_node_fs14.readFileSync)(lockPath, "utf8"));
2718
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(lockPath, "utf8"));
2245
2719
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
2246
2720
  return parsed;
2247
2721
  } catch {
@@ -2253,7 +2727,7 @@ function lockAgeMs(lockPath, now) {
2253
2727
  const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
2254
2728
  if (!Number.isNaN(declared)) return Math.max(0, now - declared);
2255
2729
  try {
2256
- return Math.max(0, now - (0, import_node_fs14.statSync)(lockPath).mtimeMs);
2730
+ return Math.max(0, now - (0, import_node_fs16.statSync)(lockPath).mtimeMs);
2257
2731
  } catch {
2258
2732
  return 0;
2259
2733
  }
@@ -2268,7 +2742,7 @@ function acquireSharedDocLock(documentPath, options) {
2268
2742
  const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
2269
2743
  const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
2270
2744
  const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
2271
- const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os4.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
2745
+ const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os6.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
2272
2746
  const content = lockContent(identity);
2273
2747
  const sleep = (ms) => {
2274
2748
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
@@ -2276,9 +2750,9 @@ function acquireSharedDocLock(documentPath, options) {
2276
2750
  for (let attempt = 0; attempt < attempts; attempt += 1) {
2277
2751
  let fd;
2278
2752
  try {
2279
- fd = (0, import_node_fs14.openSync)(lockPath, "wx");
2280
- (0, import_node_fs14.writeSync)(fd, content);
2281
- (0, import_node_fs14.closeSync)(fd);
2753
+ fd = (0, import_node_fs16.openSync)(lockPath, "wx");
2754
+ (0, import_node_fs16.writeSync)(fd, content);
2755
+ (0, import_node_fs16.closeSync)(fd);
2282
2756
  return {
2283
2757
  documentPath,
2284
2758
  lockPath,
@@ -2291,11 +2765,11 @@ function acquireSharedDocLock(documentPath, options) {
2291
2765
  } catch (error) {
2292
2766
  if (fd !== void 0) {
2293
2767
  try {
2294
- (0, import_node_fs14.closeSync)(fd);
2768
+ (0, import_node_fs16.closeSync)(fd);
2295
2769
  } catch {
2296
2770
  }
2297
2771
  try {
2298
- (0, import_node_fs14.unlinkSync)(lockPath);
2772
+ (0, import_node_fs16.unlinkSync)(lockPath);
2299
2773
  } catch {
2300
2774
  }
2301
2775
  }
@@ -2304,12 +2778,12 @@ function acquireSharedDocLock(documentPath, options) {
2304
2778
  if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
2305
2779
  const aside = `${lockPath}.stale-${Date.now()}`;
2306
2780
  try {
2307
- (0, import_node_fs14.renameSync)(lockPath, aside);
2781
+ (0, import_node_fs16.renameSync)(lockPath, aside);
2308
2782
  } catch {
2309
2783
  continue;
2310
2784
  }
2311
2785
  try {
2312
- (0, import_node_fs14.rmSync)(aside, { force: true });
2786
+ (0, import_node_fs16.rmSync)(aside, { force: true });
2313
2787
  } catch {
2314
2788
  }
2315
2789
  continue;
@@ -2322,7 +2796,7 @@ function releaseSharedDocLock(handle) {
2322
2796
  try {
2323
2797
  const holder = readHolder(handle.lockPath);
2324
2798
  if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
2325
- (0, import_node_fs14.unlinkSync)(handle.lockPath);
2799
+ (0, import_node_fs16.unlinkSync)(handle.lockPath);
2326
2800
  }
2327
2801
  } catch {
2328
2802
  }
@@ -2344,14 +2818,14 @@ var KEEP_GENERATIONS = 2;
2344
2818
  var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
2345
2819
  var TREE_FILES = [MANIFEST, "skills/bootstrap/SKILL.md"];
2346
2820
  function cursorPluginsRoot() {
2347
- return (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
2821
+ return (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".cursor", "plugins");
2348
2822
  }
2349
2823
  function treeHealthy(root) {
2350
- return TREE_FILES.every((rel) => (0, import_node_fs15.existsSync)((0, import_node_path8.join)(root, ...rel.split("/"))));
2824
+ return TREE_FILES.every((rel) => (0, import_node_fs17.existsSync)((0, import_node_path11.join)(root, ...rel.split("/"))));
2351
2825
  }
2352
2826
  function readJson(root, rel) {
2353
2827
  try {
2354
- return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path8.join)(root, ...rel.split("/")), "utf8"));
2828
+ return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path11.join)(root, ...rel.split("/")), "utf8"));
2355
2829
  } catch {
2356
2830
  return null;
2357
2831
  }
@@ -2360,7 +2834,7 @@ function manifest(root) {
2360
2834
  return readJson(root, MANIFEST);
2361
2835
  }
2362
2836
  function probeCursorInstallation() {
2363
- const location = (0, import_node_path8.join)(cursorPluginsRoot(), "local", "mmi");
2837
+ const location = (0, import_node_path11.join)(cursorPluginsRoot(), "local", "mmi");
2364
2838
  const version = manifest(location)?.version ?? null;
2365
2839
  return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
2366
2840
  }
@@ -2375,7 +2849,7 @@ function message(error) {
2375
2849
  }
2376
2850
  function discard(path) {
2377
2851
  try {
2378
- (0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
2852
+ (0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
2379
2853
  } catch {
2380
2854
  }
2381
2855
  }
@@ -2394,7 +2868,7 @@ function cursorHostEvidence(env) {
2394
2868
  if (env[marker]?.trim()) return `env marker ${marker}`;
2395
2869
  }
2396
2870
  if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
2397
- const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2871
+ const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2398
2872
  if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
2399
2873
  for (const line of result.stdout.split(/\r?\n/)) {
2400
2874
  const executable = cursorExecutable(line);
@@ -2403,7 +2877,7 @@ function cursorHostEvidence(env) {
2403
2877
  return null;
2404
2878
  }
2405
2879
  function resolvedPluginRoot(root) {
2406
- return (0, import_node_path8.join)(root).replace(/\\/g, "/");
2880
+ return (0, import_node_path11.join)(root).replace(/\\/g, "/");
2407
2881
  }
2408
2882
  function isMmiEntry(entry, pluginRoot) {
2409
2883
  if (typeof entry !== "object" || entry === null || typeof entry.command !== "string") return false;
@@ -2427,12 +2901,12 @@ function stripUserHooks(existing, pluginRoot) {
2427
2901
  }
2428
2902
  var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
2429
2903
  function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
2430
- const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
2904
+ const path = (0, import_node_path11.join)(homeDir, ".cursor", USER_HOOKS_FILE);
2431
2905
  return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
2432
- if (!(0, import_node_fs15.existsSync)(path)) return "current";
2906
+ if (!(0, import_node_fs17.existsSync)(path)) return "current";
2433
2907
  let existing;
2434
2908
  try {
2435
- existing = JSON.parse((0, import_node_fs15.readFileSync)(path, "utf8"));
2909
+ existing = JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
2436
2910
  } catch {
2437
2911
  return "user-conflict";
2438
2912
  }
@@ -2440,8 +2914,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
2440
2914
  if (!stripped) return "user-conflict";
2441
2915
  if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
2442
2916
  const tmp = `${path}.tmp-${process.pid}`;
2443
- (0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
2444
- (0, import_node_fs15.renameSync)(tmp, path);
2917
+ (0, import_node_fs17.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
2918
+ (0, import_node_fs17.renameSync)(tmp, path);
2445
2919
  return "updated";
2446
2920
  });
2447
2921
  }
@@ -2456,41 +2930,41 @@ function userHooksDetail(pluginRoot, homeDir) {
2456
2930
  case "updated":
2457
2931
  return "; user hooks legacy entries stripped";
2458
2932
  case "user-conflict":
2459
- return `; USER HOOKS CONFLICT at ${(0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 legacy entries were not changed`;
2933
+ return `; USER HOOKS CONFLICT at ${(0, import_node_path11.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 legacy entries were not changed`;
2460
2934
  default:
2461
2935
  return "";
2462
2936
  }
2463
2937
  }
2464
2938
  function writeRollbackPointer(root, entry) {
2465
- const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
2939
+ const path = (0, import_node_path11.join)(root, "quarantine", "rollback.json");
2466
2940
  const tmp = `${path}.tmp-${process.pid}`;
2467
2941
  try {
2468
- (0, import_node_fs15.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
2469
- (0, import_node_fs15.renameSync)(tmp, path);
2942
+ (0, import_node_fs17.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
2943
+ (0, import_node_fs17.renameSync)(tmp, path);
2470
2944
  } catch {
2471
2945
  discard(tmp);
2472
2946
  }
2473
2947
  }
2474
2948
  function mtimeMs(path) {
2475
2949
  try {
2476
- return (0, import_node_fs15.statSync)(path).mtimeMs;
2950
+ return (0, import_node_fs17.statSync)(path).mtimeMs;
2477
2951
  } catch {
2478
2952
  return 0;
2479
2953
  }
2480
2954
  }
2481
2955
  function pruneQuarantine(root, keep) {
2482
- const dir = (0, import_node_path8.join)(root, "quarantine");
2956
+ const dir = (0, import_node_path11.join)(root, "quarantine");
2483
2957
  let generations;
2484
2958
  try {
2485
- generations = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path8.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
2959
+ generations = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path11.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
2486
2960
  } catch {
2487
2961
  return 0;
2488
2962
  }
2489
2963
  let stranded = 0;
2490
2964
  try {
2491
- for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
2965
+ for (const entry of (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true })) {
2492
2966
  if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
2493
- const path = (0, import_node_path8.join)(dir, entry.name);
2967
+ const path = (0, import_node_path11.join)(dir, entry.name);
2494
2968
  if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
2495
2969
  discard(path);
2496
2970
  stranded += 1;
@@ -2508,16 +2982,16 @@ function pruneQuarantine(root, keep) {
2508
2982
  }
2509
2983
  function restorePrevious(live, quarantined, broken) {
2510
2984
  try {
2511
- (0, import_node_fs15.renameSync)(live, broken);
2985
+ (0, import_node_fs17.renameSync)(live, broken);
2512
2986
  } catch {
2513
2987
  return "kept-broken";
2514
2988
  }
2515
2989
  try {
2516
- (0, import_node_fs15.renameSync)(quarantined, live);
2990
+ (0, import_node_fs17.renameSync)(quarantined, live);
2517
2991
  return "restored";
2518
2992
  } catch {
2519
2993
  try {
2520
- (0, import_node_fs15.renameSync)(broken, live);
2994
+ (0, import_node_fs17.renameSync)(broken, live);
2521
2995
  return "kept-broken";
2522
2996
  } catch {
2523
2997
  return "lost";
@@ -2528,7 +3002,7 @@ function cursorArm(options) {
2528
3002
  const env = options.env ?? process.env;
2529
3003
  const { target, dryRun, paths } = options;
2530
3004
  const root = cursorPluginsRoot();
2531
- const live = (0, import_node_path8.join)(root, "local", "mmi");
3005
+ const live = (0, import_node_path11.join)(root, "local", "mmi");
2532
3006
  const result = (from, to, verdict, detail, launchStaged = false) => ({
2533
3007
  surface: "cursor",
2534
3008
  from,
@@ -2538,7 +3012,7 @@ function cursorArm(options) {
2538
3012
  launchStaged: launchStaged || void 0,
2539
3013
  // C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
2540
3014
  // switch reports the restored generation's declaration, not the rejected one's (#4973).
2541
- compat: readManifestCompat((0, import_node_path8.join)(live, MANIFEST))
3015
+ compat: readManifestCompat((0, import_node_path11.join)(live, MANIFEST))
2542
3016
  });
2543
3017
  const current = manifest(live);
2544
3018
  const installed = current?.version ?? null;
@@ -2550,9 +3024,9 @@ function cursorArm(options) {
2550
3024
  return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
2551
3025
  }
2552
3026
  if (installed === target && treeHealthy(live)) {
2553
- return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
3027
+ return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os7.homedir)())}`);
2554
3028
  }
2555
- if ((0, import_node_fs15.existsSync)(live) && !mmiOwned(live)) {
3029
+ if ((0, import_node_fs17.existsSync)(live) && !mmiOwned(live)) {
2556
3030
  return result(installed, target, "defer", `${live} carries no MMI ownership marker (plugin manifest name, or a ${PACKAGE} package.json) \u2014 refusing to displace an unmanaged directory (mmi-cli plugin heal adopts it)`);
2557
3031
  }
2558
3032
  if (dryRun) {
@@ -2566,16 +3040,16 @@ function cursorArm(options) {
2566
3040
  if (stagedMarker !== target || !treeHealthy(staged.packageRoot)) {
2567
3041
  return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
2568
3042
  }
2569
- const launchTargetPath = (0, import_node_path8.join)(root, "staging", "launch-target.json");
3043
+ const launchTargetPath = (0, import_node_path11.join)(root, "staging", "launch-target.json");
2570
3044
  const priorLaunch = readLaunchTarget(launchTargetPath);
2571
- let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path8.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
3045
+ let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path11.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path11.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
2572
3046
  const reuseIncoming = incoming === priorLaunch?.incoming;
2573
- if (!reuseIncoming || !(0, import_node_fs15.existsSync)(incoming)) {
2574
- incoming = (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
2575
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
3047
+ if (!reuseIncoming || !(0, import_node_fs17.existsSync)(incoming)) {
3048
+ incoming = (0, import_node_path11.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
3049
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs17.mkdirSync)((0, import_node_path11.join)(root, dir), { recursive: true });
2576
3050
  try {
2577
3051
  discard(incoming);
2578
- (0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
3052
+ (0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
2579
3053
  } catch (error) {
2580
3054
  discard(incoming);
2581
3055
  return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
@@ -2598,8 +3072,8 @@ function cursorArm(options) {
2598
3072
  return result(installed, target, "ok", `staged ${target} for next launch while Cursor is running (${busy}) \u2014 ${pending}`, true);
2599
3073
  }
2600
3074
  const stamp = Date.now();
2601
- const quarantined = (0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
2602
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
3075
+ const quarantined = (0, import_node_path11.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
3076
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs17.mkdirSync)((0, import_node_path11.join)(root, dir), { recursive: true });
2603
3077
  const started = cursorHostEvidence(env);
2604
3078
  if (started) {
2605
3079
  const pending = noteLaunchStaged(paths, env, {
@@ -2614,15 +3088,15 @@ function cursorArm(options) {
2614
3088
  }
2615
3089
  let displaced = false;
2616
3090
  try {
2617
- if ((0, import_node_fs15.existsSync)(live)) {
2618
- (0, import_node_fs15.renameSync)(live, quarantined);
3091
+ if ((0, import_node_fs17.existsSync)(live)) {
3092
+ (0, import_node_fs17.renameSync)(live, quarantined);
2619
3093
  displaced = true;
2620
3094
  }
2621
- (0, import_node_fs15.renameSync)(incoming, live);
3095
+ (0, import_node_fs17.renameSync)(incoming, live);
2622
3096
  } catch (error) {
2623
- if (displaced && !(0, import_node_fs15.existsSync)(live)) {
3097
+ if (displaced && !(0, import_node_fs17.existsSync)(live)) {
2624
3098
  try {
2625
- (0, import_node_fs15.renameSync)(quarantined, live);
3099
+ (0, import_node_fs17.renameSync)(quarantined, live);
2626
3100
  } catch {
2627
3101
  }
2628
3102
  }
@@ -2633,7 +3107,7 @@ function cursorArm(options) {
2633
3107
  }
2634
3108
  const evidence = manifest(live)?.version;
2635
3109
  if (evidence !== target || !treeHealthy(live)) {
2636
- const broken = `${(0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
3110
+ const broken = `${(0, import_node_path11.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
2637
3111
  const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
2638
3112
  const detail = outcome === "restored" ? `restored the previous generation ${installed ?? "unknown"}, unverified tree kept at ${broken}` : outcome === "lost" ? `restore failed and ${live} is missing \u2014 the previous generation is at ${quarantined}` : displaced ? `restore failed, the unverified tree is still live and the previous generation is at ${quarantined}` : "nothing was displaced \u2014 there is no previous generation to restore";
2639
3113
  writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
@@ -2643,15 +3117,15 @@ function cursorArm(options) {
2643
3117
  clearLaunchTarget(launchTargetPath);
2644
3118
  const pruned = pruneQuarantine(root, displaced ? quarantined : null);
2645
3119
  const previous = displaced ? `previous generation quarantined at ${quarantined}` : "no previous generation to quarantine";
2646
- return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
3120
+ return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os7.homedir)())}`);
2647
3121
  }
2648
3122
  function probeCursorLaunchStaging() {
2649
- return probeLaunchTargetVersion((0, import_node_path8.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
3123
+ return probeLaunchTargetVersion((0, import_node_path11.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
2650
3124
  }
2651
3125
 
2652
3126
  // src/arm-jervcode.ts
2653
- var import_node_fs16 = require("node:fs");
2654
- var import_node_path9 = require("node:path");
3127
+ var import_node_fs18 = require("node:fs");
3128
+ var import_node_path12 = require("node:path");
2655
3129
  var PI_PACKAGE = "@mutmutco/pi-plugin";
2656
3130
  function hostEnv(env) {
2657
3131
  const dir = jervcodeAgentDir(env);
@@ -2662,7 +3136,7 @@ function hostCommands(_env) {
2662
3136
  }
2663
3137
  function readPackageSpecs(env) {
2664
3138
  try {
2665
- const settings = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
3139
+ const settings = JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path12.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
2666
3140
  return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
2667
3141
  } catch {
2668
3142
  return null;
@@ -2670,14 +3144,14 @@ function readPackageSpecs(env) {
2670
3144
  }
2671
3145
  function installedVersion(env) {
2672
3146
  try {
2673
- const manifest2 = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
3147
+ const manifest2 = JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
2674
3148
  return typeof manifest2.version === "string" ? manifest2.version : null;
2675
3149
  } catch {
2676
3150
  return null;
2677
3151
  }
2678
3152
  }
2679
3153
  function materializedManifestLocation(env) {
2680
- return (0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
3154
+ return (0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
2681
3155
  }
2682
3156
  var PI_LIST_SPEC = new RegExp(`npm:${PI_PACKAGE.replace("/", "\\/")}@(\\S+)`);
2683
3157
  function parseListText(stdout) {
@@ -2736,32 +3210,32 @@ function jervcodeArm(options) {
2736
3210
  detail,
2737
3211
  // C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
2738
3212
  // carry the previous payload's declaration (#4973 reconsult ruling).
2739
- compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
3213
+ compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
2740
3214
  });
2741
3215
  const before = readPackageSpecs(env);
2742
- if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json")}`);
3216
+ if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path12.join)(jervcodeAgentDir(env), "settings.json")}`);
2743
3217
  const from = installedVersion(env);
2744
3218
  const legacy = before.filter(isLegacyMmiPiPath);
2745
3219
  const highWater = journalHighWater(options.paths.journalPath, "jervcode", options.journalEvents);
2746
3220
  const floor = from && compareSemver(from, options.target) > 0 ? `installed ${from} is above candidate ${options.target} (monotonic)` : highWater && compareSemver(highWater, options.target) > 0 ? `journal high-water ${highWater} is above candidate ${options.target} (monotonic)` : null;
2747
3221
  if (options.dryRun) {
2748
3222
  const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
2749
- return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
3223
+ return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
2750
3224
  }
2751
3225
  const hosted = hostEnv(env);
2752
3226
  const commands = hostCommands(env);
2753
3227
  const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
2754
- if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
3228
+ if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
2755
3229
  if (!floor && (!before.includes(exact) || from !== options.target)) {
2756
3230
  const install = runner(command, ["install", exact], hosted);
2757
3231
  if (install.status !== 0) {
2758
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
3232
+ return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
2759
3233
  }
2760
3234
  }
2761
3235
  for (const stale of legacy) {
2762
3236
  const removed = runner(command, ["remove", stale], hosted);
2763
3237
  if (removed.status !== 0) {
2764
- return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
3238
+ return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
2765
3239
  }
2766
3240
  }
2767
3241
  const after = readPackageSpecs(env);
@@ -2769,7 +3243,7 @@ function jervcodeArm(options) {
2769
3243
  const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
2770
3244
  const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
2771
3245
  if (floor && !legacyAfter.length) {
2772
- return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
3246
+ return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path12.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
2773
3247
  }
2774
3248
  if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
2775
3249
  return {
@@ -2790,15 +3264,15 @@ function jervcodeArm(options) {
2790
3264
  }
2791
3265
 
2792
3266
  // src/arm-hermes.ts
2793
- var import_node_fs17 = require("node:fs");
2794
- var import_node_child_process8 = require("node:child_process");
2795
- var import_node_path10 = require("node:path");
3267
+ var import_node_fs19 = require("node:fs");
3268
+ var import_node_child_process9 = require("node:child_process");
3269
+ var import_node_path13 = require("node:path");
2796
3270
  var PACKAGE2 = "@mutmutco/hermes-plugin";
2797
3271
  var MANIFEST2 = "plugin.yaml";
2798
3272
  var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
2799
3273
  function yamlVersion(root) {
2800
3274
  try {
2801
- const text = (0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, MANIFEST2), "utf8");
3275
+ const text = (0, import_node_fs19.readFileSync)((0, import_node_path13.join)(root, MANIFEST2), "utf8");
2802
3276
  try {
2803
3277
  const version = JSON.parse(text).version;
2804
3278
  if (typeof version === "string" && version.trim()) return version.trim();
@@ -2812,7 +3286,7 @@ function yamlVersion(root) {
2812
3286
  }
2813
3287
  function packageTreeMarkers(root) {
2814
3288
  try {
2815
- const files = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).files;
3289
+ const files = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path13.join)(root, "package.json"), "utf8")).files;
2816
3290
  if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
2817
3291
  return files.map((entry) => entry.trim());
2818
3292
  }
@@ -2821,10 +3295,10 @@ function packageTreeMarkers(root) {
2821
3295
  return TREE_MARKERS_FALLBACK;
2822
3296
  }
2823
3297
  function markerPresent(root, rel) {
2824
- const path = (0, import_node_path10.join)(root, ...rel.split("/"));
2825
- if (!(0, import_node_fs17.existsSync)(path)) return false;
3298
+ const path = (0, import_node_path13.join)(root, ...rel.split("/"));
3299
+ if (!(0, import_node_fs19.existsSync)(path)) return false;
2826
3300
  try {
2827
- const stat = (0, import_node_fs17.statSync)(path);
3301
+ const stat = (0, import_node_fs19.statSync)(path);
2828
3302
  return stat.isFile() || stat.isDirectory();
2829
3303
  } catch {
2830
3304
  return false;
@@ -2843,13 +3317,13 @@ function treeIncompleteDetail(root, label) {
2843
3317
  }
2844
3318
  function packageIdentity(root) {
2845
3319
  try {
2846
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).name === PACKAGE2;
3320
+ return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path13.join)(root, "package.json"), "utf8")).name === PACKAGE2;
2847
3321
  } catch {
2848
3322
  return false;
2849
3323
  }
2850
3324
  }
2851
3325
  function probeHermesInstallation(env = process.env) {
2852
- const location = (0, import_node_path10.join)(hermesHome(env), "plugins", "mmi");
3326
+ const location = (0, import_node_path13.join)(hermesHome(env), "plugins", "mmi");
2853
3327
  const version = yamlVersion(location);
2854
3328
  return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
2855
3329
  }
@@ -2858,7 +3332,7 @@ function mmiOwned2(root) {
2858
3332
  }
2859
3333
  function discard2(path) {
2860
3334
  try {
2861
- (0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
3335
+ (0, import_node_fs19.rmSync)(path, { recursive: true, force: true });
2862
3336
  } catch {
2863
3337
  }
2864
3338
  }
@@ -2877,7 +3351,7 @@ function hermesProcessToken(line, home) {
2877
3351
  return hermesToken(tokens) ?? (home && normalized.toLowerCase().includes(home.toLowerCase()) ? home : null);
2878
3352
  }
2879
3353
  function realProcessListing() {
2880
- const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
3354
+ const result = process.platform === "win32" ? (0, import_node_child_process9.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process9.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2881
3355
  return result.status === 0 ? result.stdout ?? "" : null;
2882
3356
  }
2883
3357
  function hermesHostEvidence(env, home, processListProvider) {
@@ -2890,19 +3364,19 @@ function hermesHostEvidence(env, home, processListProvider) {
2890
3364
  return null;
2891
3365
  }
2892
3366
  function materializeHermesSkills(home) {
2893
- const pluginSkills = (0, import_node_path10.join)(home, "plugins", "mmi", "skills");
2894
- const skillsRoot = (0, import_node_path10.join)(home, "skills");
2895
- const targetRoot = (0, import_node_path10.join)(skillsRoot, "mmi");
3367
+ const pluginSkills = (0, import_node_path13.join)(home, "plugins", "mmi", "skills");
3368
+ const skillsRoot = (0, import_node_path13.join)(home, "skills");
3369
+ const targetRoot = (0, import_node_path13.join)(skillsRoot, "mmi");
2896
3370
  try {
2897
- if (!(0, import_node_fs17.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
2898
- const names = (0, import_node_fs17.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
3371
+ if (!(0, import_node_fs19.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
3372
+ const names = (0, import_node_fs19.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
2899
3373
  if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
2900
- (0, import_node_fs17.mkdirSync)(skillsRoot, { recursive: true });
2901
- const incoming = (0, import_node_path10.join)(skillsRoot, ".mmi-incoming");
3374
+ (0, import_node_fs19.mkdirSync)(skillsRoot, { recursive: true });
3375
+ const incoming = (0, import_node_path13.join)(skillsRoot, ".mmi-incoming");
2902
3376
  discard2(incoming);
2903
- (0, import_node_fs17.cpSync)(pluginSkills, incoming, { recursive: true });
3377
+ (0, import_node_fs19.cpSync)(pluginSkills, incoming, { recursive: true });
2904
3378
  discard2(targetRoot);
2905
- (0, import_node_fs17.renameSync)(incoming, targetRoot);
3379
+ (0, import_node_fs19.renameSync)(incoming, targetRoot);
2906
3380
  return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
2907
3381
  } catch (error) {
2908
3382
  return { ok: false, detail: message2(error) };
@@ -2924,8 +3398,8 @@ function hermesHomes(env = process.env) {
2924
3398
  const root = hermesHome(env);
2925
3399
  const homes = [root];
2926
3400
  try {
2927
- for (const entry of (0, import_node_fs17.readdirSync)((0, import_node_path10.join)(root, "profiles"), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
2928
- if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path10.join)(root, "profiles", entry.name));
3401
+ for (const entry of (0, import_node_fs19.readdirSync)((0, import_node_path13.join)(root, "profiles"), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
3402
+ if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path13.join)(root, "profiles", entry.name));
2929
3403
  }
2930
3404
  } catch {
2931
3405
  }
@@ -2951,7 +3425,7 @@ function hermesArm(options) {
2951
3425
  const primary = results[0].result;
2952
3426
  if (results.length === 1) return primary;
2953
3427
  const worst = results.reduce((a, b) => VERDICT_RANK[b.result.verdict] > VERDICT_RANK[a.result.verdict] ? b : a);
2954
- const detail = results.map(({ home, result }, index) => `${index === 0 ? "home" : `profile ${(0, import_node_path10.basename)(home)}`}: ${result.detail}`).join(" | ");
3428
+ const detail = results.map(({ home, result }, index) => `${index === 0 ? "home" : `profile ${(0, import_node_path13.basename)(home)}`}: ${result.detail}`).join(" | ");
2955
3429
  return {
2956
3430
  surface: "hermes",
2957
3431
  from: primary.from,
@@ -2962,8 +3436,8 @@ function hermesArm(options) {
2962
3436
  };
2963
3437
  }
2964
3438
  function armHermesHome(home, env, options, primary) {
2965
- const parent = (0, import_node_path10.join)(home, "plugins");
2966
- const live = (0, import_node_path10.join)(parent, "mmi");
3439
+ const parent = (0, import_node_path13.join)(home, "plugins");
3440
+ const live = (0, import_node_path13.join)(parent, "mmi");
2967
3441
  const installed = yamlVersion(live);
2968
3442
  const result = (from, to, verdict, detail, launchStaged = false) => ({
2969
3443
  surface: "hermes",
@@ -2973,7 +3447,7 @@ function armHermesHome(home, env, options, primary) {
2973
3447
  detail,
2974
3448
  launchStaged: launchStaged || void 0
2975
3449
  });
2976
- if ((0, import_node_fs17.existsSync)(live) && !mmiOwned2(live)) {
3450
+ if ((0, import_node_fs19.existsSync)(live) && !mmiOwned2(live)) {
2977
3451
  return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
2978
3452
  }
2979
3453
  if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
@@ -2987,7 +3461,7 @@ function armHermesHome(home, env, options, primary) {
2987
3461
  if (!enabled2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but ${enabled2.detail}`);
2988
3462
  return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}; ${enabled2.detail}`);
2989
3463
  }
2990
- if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, provision skills into ${(0, import_node_path10.join)(home, "skills", "mmi")}, then enable mmi in plugins.enabled`);
3464
+ if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, provision skills into ${(0, import_node_path13.join)(home, "skills", "mmi")}, then enable mmi in plugins.enabled`);
2991
3465
  const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
2992
3466
  if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
2993
3467
  const stagedVersion = yamlVersion(staged.packageRoot);
@@ -2996,19 +3470,19 @@ function armHermesHome(home, env, options, primary) {
2996
3470
  }
2997
3471
  const stagedTree = treeIncompleteDetail(staged.packageRoot, "staged Hermes");
2998
3472
  if (stagedTree) return result(installed, options.target, "defer", stagedTree);
2999
- const scratch = (0, import_node_path10.join)(home, ".mmi-updater");
3000
- const launchTargetPath = (0, import_node_path10.join)(scratch, "launch-target.json");
3001
- const incomingRoot = (0, import_node_path10.join)(scratch, "incoming");
3002
- const incoming = (0, import_node_path10.join)(incomingRoot, options.target);
3003
- const quarantine = (0, import_node_path10.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
3473
+ const scratch = (0, import_node_path13.join)(home, ".mmi-updater");
3474
+ const launchTargetPath = (0, import_node_path13.join)(scratch, "launch-target.json");
3475
+ const incomingRoot = (0, import_node_path13.join)(scratch, "incoming");
3476
+ const incoming = (0, import_node_path13.join)(incomingRoot, options.target);
3477
+ const quarantine = (0, import_node_path13.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
3004
3478
  const priorLaunch = readLaunchTarget(launchTargetPath);
3005
3479
  const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
3006
3480
  if (!reuseIncoming) {
3007
3481
  try {
3008
- (0, import_node_fs17.mkdirSync)(parent, { recursive: true });
3009
- (0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "quarantine"), { recursive: true });
3482
+ (0, import_node_fs19.mkdirSync)(parent, { recursive: true });
3483
+ (0, import_node_fs19.mkdirSync)((0, import_node_path13.join)(scratch, "quarantine"), { recursive: true });
3010
3484
  discard2(incomingRoot);
3011
- (0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
3485
+ (0, import_node_fs19.cpSync)(staged.packageRoot, incoming, { recursive: true });
3012
3486
  } catch (error) {
3013
3487
  return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
3014
3488
  }
@@ -3023,7 +3497,7 @@ function armHermesHome(home, env, options, primary) {
3023
3497
  return result(installed, options.target, "defer", incomingTree);
3024
3498
  }
3025
3499
  }
3026
- const displaced = (0, import_node_fs17.existsSync)(live);
3500
+ const displaced = (0, import_node_fs19.existsSync)(live);
3027
3501
  const busy = displaced ? hermesHostEvidence(env, home, options.processListProvider) : null;
3028
3502
  if (busy) {
3029
3503
  const pending = noteLaunchStaged(options.paths, env, {
@@ -3038,11 +3512,11 @@ function armHermesHome(home, env, options, primary) {
3038
3512
  return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
3039
3513
  }
3040
3514
  try {
3041
- if (displaced) (0, import_node_fs17.renameSync)(live, quarantine);
3042
- (0, import_node_fs17.renameSync)(incoming, live);
3515
+ if (displaced) (0, import_node_fs19.renameSync)(live, quarantine);
3516
+ (0, import_node_fs19.renameSync)(incoming, live);
3043
3517
  } catch (error) {
3044
- if (displaced && !(0, import_node_fs17.existsSync)(live)) try {
3045
- (0, import_node_fs17.renameSync)(quarantine, live);
3518
+ if (displaced && !(0, import_node_fs19.existsSync)(live)) try {
3519
+ (0, import_node_fs19.renameSync)(quarantine, live);
3046
3520
  } catch {
3047
3521
  }
3048
3522
  discard2(incomingRoot);
@@ -3053,12 +3527,12 @@ function armHermesHome(home, env, options, primary) {
3053
3527
  const liveVersion = yamlVersion(live);
3054
3528
  const liveTree = treeIncompleteDetail(live, "live Hermes");
3055
3529
  if (liveVersion !== options.target || liveTree) {
3056
- const broken = (0, import_node_path10.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
3530
+ const broken = (0, import_node_path13.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
3057
3531
  const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
3058
3532
  try {
3059
- (0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "rejected"), { recursive: true });
3060
- (0, import_node_fs17.renameSync)(live, broken);
3061
- if (displaced) (0, import_node_fs17.renameSync)(quarantine, live);
3533
+ (0, import_node_fs19.mkdirSync)((0, import_node_path13.join)(scratch, "rejected"), { recursive: true });
3534
+ (0, import_node_fs19.renameSync)(live, broken);
3535
+ if (displaced) (0, import_node_fs19.renameSync)(quarantine, live);
3062
3536
  } catch (error) {
3063
3537
  return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
3064
3538
  }
@@ -3073,12 +3547,12 @@ function armHermesHome(home, env, options, primary) {
3073
3547
  return result(installed, options.target, "ok", `converged ${installed ?? "absent"} -> ${options.target} (staged, verified, atomically switched${displaced ? `; previous generation quarantined at ${quarantine}` : ""}); ${skills.detail}; ${enabled.detail}`);
3074
3548
  }
3075
3549
  function probeHermesLaunchStaging(env = process.env) {
3076
- return probeLaunchTargetVersion((0, import_node_path10.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
3550
+ return probeLaunchTargetVersion((0, import_node_path13.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
3077
3551
  }
3078
3552
 
3079
3553
  // src/reap.ts
3080
- var import_node_fs18 = require("node:fs");
3081
- var import_node_path11 = require("node:path");
3554
+ var import_node_fs20 = require("node:fs");
3555
+ var import_node_path14 = require("node:path");
3082
3556
  var import_node_perf_hooks = require("node:perf_hooks");
3083
3557
  function elapsedMs(startedAt) {
3084
3558
  return Math.round((import_node_perf_hooks.performance.now() - startedAt) * 10) / 10;
@@ -3087,7 +3561,7 @@ var KEEP_GENERATIONS2 = 2;
3087
3561
  var SCRATCH_AGE_MS = 24 * 60 * 6e4;
3088
3562
  function listDirs(path) {
3089
3563
  try {
3090
- return (0, import_node_fs18.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3564
+ return (0, import_node_fs20.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3091
3565
  } catch {
3092
3566
  return [];
3093
3567
  }
@@ -3095,7 +3569,7 @@ function listDirs(path) {
3095
3569
  function generationSets(root) {
3096
3570
  const children = listDirs(root);
3097
3571
  if (children.some((name) => parseSemver(name))) return [root];
3098
- return children.map((name) => (0, import_node_path11.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
3572
+ return children.map((name) => (0, import_node_path14.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
3099
3573
  }
3100
3574
  function pruneGenerations(root, category, dryRun) {
3101
3575
  let reaped = 0;
@@ -3108,7 +3582,7 @@ function pruneGenerations(root, category, dryRun) {
3108
3582
  continue;
3109
3583
  }
3110
3584
  try {
3111
- (0, import_node_fs18.rmSync)((0, import_node_path11.join)(set, version), { recursive: true, force: true });
3585
+ (0, import_node_fs20.rmSync)((0, import_node_path14.join)(set, version), { recursive: true, force: true });
3112
3586
  reaped += 1;
3113
3587
  } catch (error) {
3114
3588
  failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
@@ -3122,8 +3596,8 @@ function pruneGenerations(root, category, dryRun) {
3122
3596
  }
3123
3597
  var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
3124
3598
  function atomicWriteScratch(path) {
3125
- const literal = (0, import_node_path11.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3126
- return { dir: (0, import_node_path11.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
3599
+ const literal = (0, import_node_path14.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3600
+ return { dir: (0, import_node_path14.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
3127
3601
  }
3128
3602
  function reapScratch(context, dryRun) {
3129
3603
  const category = "scratch";
@@ -3141,7 +3615,7 @@ function reapScratch(context, dryRun) {
3141
3615
  for (const [dir, patterns] of scanned) {
3142
3616
  let entries;
3143
3617
  try {
3144
- entries = (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true });
3618
+ entries = (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true });
3145
3619
  } catch {
3146
3620
  unreadable.push(dir);
3147
3621
  continue;
@@ -3149,10 +3623,10 @@ function reapScratch(context, dryRun) {
3149
3623
  for (const entry of entries) {
3150
3624
  if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
3151
3625
  if (!entry.isFile()) continue;
3152
- const path = (0, import_node_path11.join)(dir, entry.name);
3626
+ const path = (0, import_node_path14.join)(dir, entry.name);
3153
3627
  let ageMs;
3154
3628
  try {
3155
- ageMs = now - (0, import_node_fs18.statSync)(path).mtimeMs;
3629
+ ageMs = now - (0, import_node_fs20.statSync)(path).mtimeMs;
3156
3630
  } catch {
3157
3631
  continue;
3158
3632
  }
@@ -3162,7 +3636,7 @@ function reapScratch(context, dryRun) {
3162
3636
  continue;
3163
3637
  }
3164
3638
  try {
3165
- (0, import_node_fs18.rmSync)(path, { force: true });
3639
+ (0, import_node_fs20.rmSync)(path, { force: true });
3166
3640
  reaped += 1;
3167
3641
  } catch (error) {
3168
3642
  failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
@@ -3392,10 +3866,10 @@ async function reconcile(options) {
3392
3866
  }
3393
3867
 
3394
3868
  // src/scheduler.ts
3395
- var import_node_child_process9 = require("node:child_process");
3396
- var import_node_fs19 = require("node:fs");
3397
- var import_node_os6 = require("node:os");
3398
- var import_node_path12 = require("node:path");
3869
+ var import_node_child_process10 = require("node:child_process");
3870
+ var import_node_fs21 = require("node:fs");
3871
+ var import_node_os8 = require("node:os");
3872
+ var import_node_path15 = require("node:path");
3399
3873
  var TASK_NAME = "MMI Fleet Updater";
3400
3874
  var LAUNCHER_FILE = "reconcile-hidden.vbs";
3401
3875
  var RUN_BOUND = "PT15M";
@@ -3411,15 +3885,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
3411
3885
  var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
3412
3886
  var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
3413
3887
  function schtasks(args) {
3414
- const result = (0, import_node_child_process9.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
3888
+ const result = (0, import_node_child_process10.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
3415
3889
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
3416
3890
  }
3417
3891
  function launchctl(args) {
3418
- const result = (0, import_node_child_process9.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
3892
+ const result = (0, import_node_child_process10.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
3419
3893
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
3420
3894
  }
3421
3895
  function systemctlUser(args) {
3422
- const result = (0, import_node_child_process9.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
3896
+ const result = (0, import_node_child_process10.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
3423
3897
  return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
3424
3898
  }
3425
3899
  function uid() {
@@ -3444,7 +3918,7 @@ WScript.Quit code
3444
3918
  `;
3445
3919
  }
3446
3920
  function loginPath(fallback = process.env.PATH ?? "") {
3447
- const probe = (0, import_node_child_process9.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
3921
+ const probe = (0, import_node_child_process10.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
3448
3922
  const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
3449
3923
  const source = captured || fallback;
3450
3924
  const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
@@ -3553,7 +4027,7 @@ WantedBy=timers.target
3553
4027
  }
3554
4028
  function readFileOrNull(path) {
3555
4029
  try {
3556
- return (0, import_node_fs19.readFileSync)(path, "utf8");
4030
+ return (0, import_node_fs21.readFileSync)(path, "utf8");
3557
4031
  } catch {
3558
4032
  return null;
3559
4033
  }
@@ -3565,7 +4039,7 @@ function elevatedRelaunch(mode, env) {
3565
4039
  if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
3566
4040
  const self = process.argv[1];
3567
4041
  const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
3568
- const relaunch = (0, import_node_child_process9.spawnSync)(
4042
+ const relaunch = (0, import_node_child_process10.spawnSync)(
3569
4043
  "powershell.exe",
3570
4044
  ["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
3571
4045
  { encoding: "utf8", windowsHide: true }
@@ -3576,7 +4050,7 @@ var NEVER_RUN_RESULT = 267011;
3576
4050
  var NEVER_RUN_YEAR = 1999;
3577
4051
  var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
3578
4052
  function taskRunInfo() {
3579
- const result = (0, import_node_child_process9.spawnSync)(
4053
+ const result = (0, import_node_child_process10.spawnSync)(
3580
4054
  "powershell.exe",
3581
4055
  ["-NoProfile", "-Command", `$stamp = { param($t) if ($t) { $t.ToString('yyyy-MM-dd HH:mm:ss') } }; Get-ScheduledTaskInfo -TaskName '${TASK_NAME}' | Select-Object @{n='LastRunTime';e={& $stamp $_.LastRunTime}},LastTaskResult,@{n='NextRunTime';e={& $stamp $_.NextRunTime}} | ConvertTo-Json -Compress`],
3582
4056
  { encoding: "utf8", windowsHide: true, timeout: 3e4 }
@@ -3602,7 +4076,7 @@ function windowsSchedulerStatus(env) {
3602
4076
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3603
4077
  }
3604
4078
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3605
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
4079
+ const launcherPath = (0, import_node_path15.join)(statePaths(env).root, LAUNCHER_FILE);
3606
4080
  const info = taskRunInfo();
3607
4081
  const runInfo = info ?? EMPTY_RUN_INFO;
3608
4082
  const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
@@ -3627,13 +4101,13 @@ function windowsSchedulerStatus(env) {
3627
4101
  return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
3628
4102
  }
3629
4103
  function darwinSchedulerStatus(env) {
3630
- const plistPath = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
4104
+ const plistPath = (0, import_node_path15.join)((0, import_node_os8.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
3631
4105
  const plist = readFileOrNull(plistPath);
3632
4106
  if (!plist) {
3633
4107
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3634
4108
  }
3635
4109
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3636
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
4110
+ const launcherPath = (0, import_node_path15.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
3637
4111
  const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
3638
4112
  if (!plist.includes(launcherPath)) {
3639
4113
  return fail2(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -3653,15 +4127,15 @@ function darwinSchedulerStatus(env) {
3653
4127
  return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
3654
4128
  }
3655
4129
  function linuxSchedulerStatus(env) {
3656
- const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3657
- const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
4130
+ const unitDir = (0, import_node_path15.join)((0, import_node_os8.homedir)(), ".config", "systemd", "user");
4131
+ const timerPath = (0, import_node_path15.join)(unitDir, SYSTEMD_TIMER_NAME);
3658
4132
  const timer = readFileOrNull(timerPath);
3659
- const service = readFileOrNull((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME));
4133
+ const service = readFileOrNull((0, import_node_path15.join)(unitDir, SYSTEMD_SERVICE_NAME));
3660
4134
  if (!timer || !service) {
3661
4135
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
3662
4136
  }
3663
4137
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3664
- const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
4138
+ const launcherPath = (0, import_node_path15.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
3665
4139
  const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
3666
4140
  if (!service.includes(launcherPath)) {
3667
4141
  return fail2(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
@@ -3700,18 +4174,18 @@ function installWindowsTask(env) {
3700
4174
  const paths = ensureState(env);
3701
4175
  const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
3702
4176
  if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
3703
- const launcherPath = (0, import_node_path12.join)(paths.root, LAUNCHER_FILE);
3704
- (0, import_node_fs19.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
3705
- const xmlPath = (0, import_node_path12.join)(paths.root, "task.xml");
3706
- (0, import_node_fs19.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
4177
+ const launcherPath = (0, import_node_path15.join)(paths.root, LAUNCHER_FILE);
4178
+ (0, import_node_fs21.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
4179
+ const xmlPath = (0, import_node_path15.join)(paths.root, "task.xml");
4180
+ (0, import_node_fs21.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
3707
4181
  const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
3708
4182
  if (create.status !== 0) {
3709
4183
  const denied = /access is denied/i.test(create.stderr + create.stdout);
3710
4184
  if (!denied || !elevatedRelaunch("on", env)) {
3711
4185
  return { ok: false, detail: denied ? "task creation needs elevation and UAC consent was not granted \u2014 run `mmi-hub autoupdate on` from an elevated terminal" : `schtasks /create failed: ${(create.stderr || create.stdout).trim()}` };
3712
4186
  }
3713
- const verify = windowsSchedulerStatus(env);
3714
- return verify.ok ? { ok: true, detail: `enabled "${TASK_NAME}" (elevated) \u2014 ${CADENCE}` } : { ok: false, detail: `elevated registration did not take: ${verify.detail}` };
4187
+ const verify2 = windowsSchedulerStatus(env);
4188
+ return verify2.ok ? { ok: true, detail: `enabled "${TASK_NAME}" (elevated) \u2014 ${CADENCE}` } : { ok: false, detail: `elevated registration did not take: ${verify2.detail}` };
3715
4189
  }
3716
4190
  return { ok: true, detail: `enabled "${TASK_NAME}" \u2014 ${CADENCE} \u2192 ${hubDist}` };
3717
4191
  }
@@ -3719,11 +4193,11 @@ function refreshPosixLauncher(env) {
3719
4193
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3720
4194
  if (!hubDist) return null;
3721
4195
  const paths = ensureState(env);
3722
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
4196
+ const launcherPath = (0, import_node_path15.join)(paths.root, POSIX_LAUNCHER_FILE);
3723
4197
  const desired = launcherSh(process.execPath, hubDist);
3724
4198
  if (readFileOrNull(launcherPath) === desired) return null;
3725
- (0, import_node_fs19.writeFileSync)(launcherPath, desired, "utf8");
3726
- (0, import_node_fs19.chmodSync)(launcherPath, 493);
4199
+ (0, import_node_fs21.writeFileSync)(launcherPath, desired, "utf8");
4200
+ (0, import_node_fs21.chmodSync)(launcherPath, 493);
3727
4201
  return launcherPath;
3728
4202
  }
3729
4203
  function installLaunchd(env) {
@@ -3735,20 +4209,20 @@ function installLaunchd(env) {
3735
4209
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3736
4210
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
3737
4211
  const paths = ensureState(env);
3738
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
3739
- (0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
3740
- (0, import_node_fs19.chmodSync)(launcherPath, 493);
3741
- const agentsDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
3742
- (0, import_node_fs19.mkdirSync)(agentsDir, { recursive: true });
3743
- const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
3744
- (0, import_node_fs19.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
4212
+ const launcherPath = (0, import_node_path15.join)(paths.root, POSIX_LAUNCHER_FILE);
4213
+ (0, import_node_fs21.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
4214
+ (0, import_node_fs21.chmodSync)(launcherPath, 493);
4215
+ const agentsDir = (0, import_node_path15.join)((0, import_node_os8.homedir)(), "Library", "LaunchAgents");
4216
+ (0, import_node_fs21.mkdirSync)(agentsDir, { recursive: true });
4217
+ const plistPath = (0, import_node_path15.join)(agentsDir, LAUNCHD_PLIST_NAME);
4218
+ (0, import_node_fs21.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
3745
4219
  launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
3746
4220
  const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
3747
4221
  if (load.status !== 0) {
3748
4222
  return { ok: false, detail: `launchctl bootstrap failed: ${(load.stderr || load.stdout).trim() || "unknown error"}` };
3749
4223
  }
3750
- const verify = darwinSchedulerStatus(env);
3751
- return verify.ok ? { ok: true, detail: `enabled "${LAUNCHD_LABEL}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify.detail}` };
4224
+ const verify2 = darwinSchedulerStatus(env);
4225
+ return verify2.ok ? { ok: true, detail: `enabled "${LAUNCHD_LABEL}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify2.detail}` };
3752
4226
  }
3753
4227
  function installSystemdTimer(env) {
3754
4228
  const current = linuxSchedulerStatus(env);
@@ -3759,13 +4233,13 @@ function installSystemdTimer(env) {
3759
4233
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3760
4234
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
3761
4235
  const paths = ensureState(env);
3762
- const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
3763
- (0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
3764
- (0, import_node_fs19.chmodSync)(launcherPath, 493);
3765
- const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3766
- (0, import_node_fs19.mkdirSync)(unitDir, { recursive: true });
3767
- (0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
3768
- (0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
4236
+ const launcherPath = (0, import_node_path15.join)(paths.root, POSIX_LAUNCHER_FILE);
4237
+ (0, import_node_fs21.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
4238
+ (0, import_node_fs21.chmodSync)(launcherPath, 493);
4239
+ const unitDir = (0, import_node_path15.join)((0, import_node_os8.homedir)(), ".config", "systemd", "user");
4240
+ (0, import_node_fs21.mkdirSync)(unitDir, { recursive: true });
4241
+ (0, import_node_fs21.writeFileSync)((0, import_node_path15.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
4242
+ (0, import_node_fs21.writeFileSync)((0, import_node_path15.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
3769
4243
  const reload = systemctlUser(["daemon-reload"]);
3770
4244
  if (reload.status !== 0) {
3771
4245
  return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
@@ -3774,8 +4248,8 @@ function installSystemdTimer(env) {
3774
4248
  if (enable.status !== 0) {
3775
4249
  return { ok: false, detail: `systemctl --user enable --now ${SYSTEMD_TIMER_NAME} failed: ${(enable.stderr || enable.stdout).trim() || "unknown error"}` };
3776
4250
  }
3777
- const verify = linuxSchedulerStatus(env);
3778
- return verify.ok ? { ok: true, detail: `enabled "${SYSTEMD_TIMER_NAME}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify.detail}` };
4251
+ const verify2 = linuxSchedulerStatus(env);
4252
+ return verify2.ok ? { ok: true, detail: `enabled "${SYSTEMD_TIMER_NAME}" \u2014 ${POSIX_CADENCE} \u2192 ${hubDist}` } : { ok: false, detail: `registration did not take: ${verify2.detail}` };
3779
4253
  }
3780
4254
  function installTask(env = process.env) {
3781
4255
  if (process.platform === "win32") return installWindowsTask(env);
@@ -3803,28 +4277,28 @@ function uninstallWindowsTask(env) {
3803
4277
  return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
3804
4278
  }
3805
4279
  function uninstallLaunchd(env) {
3806
- const plistPath = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
4280
+ const plistPath = (0, import_node_path15.join)((0, import_node_os8.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
3807
4281
  const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
3808
- if (boot.status !== 0 && (0, import_node_fs19.existsSync)(plistPath)) {
4282
+ if (boot.status !== 0 && (0, import_node_fs21.existsSync)(plistPath)) {
3809
4283
  return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
3810
4284
  }
3811
4285
  try {
3812
- (0, import_node_fs19.rmSync)(plistPath, { force: true });
4286
+ (0, import_node_fs21.rmSync)(plistPath, { force: true });
3813
4287
  } catch {
3814
4288
  }
3815
4289
  dropLauncher(env, POSIX_LAUNCHER_FILE);
3816
4290
  return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
3817
4291
  }
3818
4292
  function uninstallSystemdTimer(env) {
3819
- const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
3820
- const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
4293
+ const unitDir = (0, import_node_path15.join)((0, import_node_os8.homedir)(), ".config", "systemd", "user");
4294
+ const timerPath = (0, import_node_path15.join)(unitDir, SYSTEMD_TIMER_NAME);
3821
4295
  const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
3822
- if (disable.status !== 0 && (0, import_node_fs19.existsSync)(timerPath)) {
4296
+ if (disable.status !== 0 && (0, import_node_fs21.existsSync)(timerPath)) {
3823
4297
  return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
3824
4298
  }
3825
4299
  try {
3826
- (0, import_node_fs19.rmSync)(timerPath, { force: true });
3827
- (0, import_node_fs19.rmSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
4300
+ (0, import_node_fs21.rmSync)(timerPath, { force: true });
4301
+ (0, import_node_fs21.rmSync)((0, import_node_path15.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
3828
4302
  } catch {
3829
4303
  }
3830
4304
  systemctlUser(["daemon-reload"]);
@@ -3839,16 +4313,16 @@ function uninstallTask(env = process.env) {
3839
4313
  }
3840
4314
  function dropLauncher(env, file) {
3841
4315
  try {
3842
- (0, import_node_fs19.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
4316
+ (0, import_node_fs21.rmSync)((0, import_node_path15.join)(statePaths(env).root, file), { force: true });
3843
4317
  } catch {
3844
4318
  }
3845
4319
  }
3846
4320
 
3847
4321
  // src/status.ts
3848
- var import_node_fs20 = require("node:fs");
4322
+ var import_node_fs22 = require("node:fs");
3849
4323
  function readJournal(path) {
3850
4324
  try {
3851
- const events = (0, import_node_fs20.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
4325
+ const events = (0, import_node_fs22.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
3852
4326
  try {
3853
4327
  return JSON.parse(line);
3854
4328
  } catch {
@@ -3867,6 +4341,9 @@ function versionState(installed, staged, expected) {
3867
4341
  return relation === 0 ? "current" : relation > 0 ? "ahead" : "behind";
3868
4342
  }
3869
4343
  function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1]) {
4344
+ return hubStatusReport(hubVersion, env, invokedPath).status;
4345
+ }
4346
+ function hubStatusReport(hubVersion, env = process.env, invokedPath = process.argv[1]) {
3870
4347
  const paths = statePaths(env);
3871
4348
  const { events, error } = readJournal(paths.journalPath);
3872
4349
  const realEvents = events.filter((event) => !event.dryRun);
@@ -3928,6 +4405,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3928
4405
  if (!row.installed) failures.push(`${surface}: installed version is unreadable at ${row.location ?? "an unknown location"} \u2014 run \`mmi-hub update\``);
3929
4406
  if (row.state === "behind") failures.push(`${surface}: installed ${row.installed} is behind expected ${row.expected} \u2014 run \`mmi-hub update\``);
3930
4407
  }
4408
+ const lastRunProblems = [];
3931
4409
  for (const event of latestRunEvents) {
3932
4410
  if ((event.kind === "arm" || event.kind === "reconcile") && (event.verdict === "fail" || event.verdict === "defer")) {
3933
4411
  if (event.kind === "arm" && event.surface && event.surface !== "cli" && event.surface !== "updater" && !presentSurfaces.has(event.surface)) {
@@ -3935,10 +4413,11 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3935
4413
  }
3936
4414
  const surface = event.surface === "updater" ? "hub" : event.surface ?? event.kind;
3937
4415
  const repeat = event.kind === "arm" && event.verdict === "fail" && event.surface && journalSurfaceRepeatedFail(paths.journalPath, event.surface, latestEvent.run);
4416
+ lastRunProblems.push({ surface: event.surface ?? (event.kind === "reconcile" ? "update run" : event.kind), verdict: event.verdict, detail: event.detail ?? event.verdict, repeat: Boolean(repeat) });
3938
4417
  failures.push(repeat ? `last run ${surface}: ${event.detail ?? event.verdict} \u2014 already failed the previous run too; \`mmi-hub update\` will not clear it, inspect the journal` : `last run ${surface}: ${event.detail ?? event.verdict} \u2014 run \`mmi-hub update\``);
3939
4418
  }
3940
4419
  }
3941
- return {
4420
+ const status = {
3942
4421
  hubVersion,
3943
4422
  expectedVersion,
3944
4423
  installed,
@@ -3947,27 +4426,60 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3947
4426
  journalPath: paths.journalPath,
3948
4427
  failures: [...new Set(failures)]
3949
4428
  };
3950
- }
3951
- function formatHubStatus(status) {
3952
- const lines = [
3953
- `mmi-hub ${status.hubVersion} status`,
3954
- ` expected: ${status.expectedVersion ?? "unknown (no gated target recorded)"}`,
3955
- " installed:"
3956
- ];
3957
- for (const [surface, row] of Object.entries(status.installed)) {
3958
- const staged = row.staged ? `; staged ${row.staged} for next launch` : "";
3959
- lines.push(` ${surface}: loaded ${row.installed ?? "unknown"}${staged} (expected ${row.expected ?? "unknown"}; ${row.state})`);
3960
- lines.push(` ${row.location ?? "location unavailable"} \u2014 ${row.detail}`);
3961
- }
3962
- lines.push(` autoupdate: ${status.schedule.enabled ? "on" : "off"} \u2014 ${status.schedule.detail}`);
3963
- lines.push(` last run: ${status.lastRun ? `${status.lastRun.at} (${status.lastRun.id})` : "never recorded"}`);
3964
- if (status.failures.length) {
3965
- lines.push(" actionable failures:");
3966
- for (const failure3 of status.failures) lines.push(` - ${failure3}`);
3967
- } else {
3968
- lines.push(" actionable failures: none");
3969
- }
3970
- return lines.join("\n");
4429
+ return { status, journalError: error, lastRunProblems };
4430
+ }
4431
+ var rowId = (surface) => surface === "hub" ? "updater" : surface;
4432
+ var oneLine = (text) => text.split("\n")[0].replaceAll("`", "").trim();
4433
+ var when = (ts) => /^\d{4}-\d\d-\d\dT\d\d:\d\d.*Z$/.test(ts) ? `${ts.slice(0, 16).replace("T", " ")} UTC` : ts;
4434
+ function versionRow(surface, row) {
4435
+ const id = rowId(surface);
4436
+ const loaded = row.installed ?? "unknown";
4437
+ if (row.state === "retired") return { surface: id, state: SURFACE_WORDS.skipped, mark: "note" };
4438
+ if (!row.installed) return { surface: id, state: SURFACE_WORDS.unknown, mark: "fail", note: "Its version could not be read." };
4439
+ if (row.state === "current") return { surface: id, state: `${loaded} \xB7 ${SURFACE_WORDS.current}`, mark: "ok" };
4440
+ if (row.state === "behind") return { surface: id, state: `${loaded} \u2192 ${row.expected} \xB7 ${SURFACE_WORDS.behind}`, mark: "note" };
4441
+ if (row.state === "staged") return { surface: id, state: `${loaded} \u2192 ${row.staged} \xB7 ${SURFACE_WORDS.staged}`, mark: "note" };
4442
+ return { surface: id, state: `${loaded} \xB7 ${SURFACE_WORDS[row.state]}`, mark: "note" };
4443
+ }
4444
+ function statusScreen(report, doctor) {
4445
+ const { status } = report;
4446
+ const rows = Object.entries(status.installed).map(([surface, row]) => versionRow(surface, row));
4447
+ const nexts = /* @__PURE__ */ new Set();
4448
+ for (const row of rows) if (row.mark === "fail") nexts.add("mmi-hub update");
4449
+ const schedule = status.schedule;
4450
+ const autoRow = { surface: AUTO_UPDATE, state: schedule.enabled ? "on \xB7 hourly" : "off", mark: schedule.enabled ? "ok" : "note" };
4451
+ if (!schedule.supported) autoRow.note = "Not available on this system.";
4452
+ else if (!schedule.ok) {
4453
+ Object.assign(autoRow, { mark: "fail", note: schedule.enabled ? "Auto-update is not set up correctly." : "Auto-update is not set up." });
4454
+ nexts.add("mmi-hub autoupdate on");
4455
+ } else if (typeof schedule.lastTaskResult === "number" && schedule.lastTaskResult !== 0 && schedule.lastTaskResult !== 267011) {
4456
+ autoRow.note = `The last automatic run ended with an error (code ${schedule.lastTaskResult}).`;
4457
+ }
4458
+ rows.push(autoRow);
4459
+ for (const problem of report.lastRunProblems) {
4460
+ const id = rowId(problem.surface);
4461
+ const note = `${problem.repeat ? "Failed twice in a row" : problem.verdict === "defer" ? "Last update will retry" : "Last update failed"}: ${oneLine(problem.detail)}`;
4462
+ const word = problem.verdict === "defer" ? SURFACE_WORDS.retry : SURFACE_WORDS.failed;
4463
+ const loaded = Object.entries(status.installed).find(([surface]) => rowId(surface) === id)?.[1].installed;
4464
+ const row = rows.find((candidate) => candidate.surface === id);
4465
+ if (row) Object.assign(row, { state: loaded ? `${loaded} \xB7 ${word}` : word, mark: "fail", note });
4466
+ else rows.push({ surface: id, state: word, mark: "fail", note });
4467
+ nexts.add(problem.repeat ? doctor : "mmi-hub update");
4468
+ }
4469
+ const facts = [];
4470
+ if (status.expectedVersion) facts.push(`Latest release: ${status.expectedVersion}.`);
4471
+ if (report.journalError) {
4472
+ facts.push(`The update log could not be read: ${status.journalPath}`);
4473
+ nexts.add(doctor);
4474
+ } else if (!status.lastRun) {
4475
+ facts.push("No update has run on this machine yet.");
4476
+ nexts.add("mmi-hub install");
4477
+ } else facts.push(`Last update: ${when(status.lastRun.at)}.`);
4478
+ const behind = rows.some((row) => row.state.endsWith(SURFACE_WORDS.behind));
4479
+ const next = ["mmi-hub install", "mmi-hub update", "mmi-hub autoupdate on", doctor].find((command) => nexts.has(command));
4480
+ if (next) return { rows, verdict: { state: "fail", headline: "needs attention.", facts, next } };
4481
+ if (behind) return { rows, verdict: { state: "note", headline: "has an update available.", facts, next: "mmi-hub update" } };
4482
+ return { rows, verdict: { state: "ok", headline: "is ready.", facts, next: doctor } };
3971
4483
  }
3972
4484
 
3973
4485
  // src/report.ts
@@ -4019,14 +4531,14 @@ function reportCounts(summary) {
4019
4531
  }
4020
4532
 
4021
4533
  // src/stamp.ts
4022
- var import_node_fs21 = require("node:fs");
4534
+ var import_node_fs23 = require("node:fs");
4023
4535
  var ALWAYS_ARMED = ["cli", "updater"];
4024
4536
  var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
4025
4537
  function fleetStamp(env = process.env) {
4026
4538
  const paths = statePaths(env);
4027
4539
  let events = [];
4028
4540
  try {
4029
- events = (0, import_node_fs21.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
4541
+ events = (0, import_node_fs23.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
4030
4542
  try {
4031
4543
  return JSON.parse(line);
4032
4544
  } catch {
@@ -4070,34 +4582,46 @@ function fleetStamp(env = process.env) {
4070
4582
  }
4071
4583
  const pendingNote = pending.length ? ` \xB7 ${pending.length} surface${pending.length === 1 ? "" : "s"} pending an arm: ${pending.join(" ")}` : "";
4072
4584
  const retiredNote = retiredPresent.length ? ` \xB7 retired at source: ${retiredPresent.join(" ")}` : "";
4073
- return `fleet ${target} \u2713 ${healthy.join("\xB7")}${pendingNote}${retiredNote}`;
4585
+ return `fleet ${target} ok ${healthy.join("\xB7")}${pendingNote}${retiredNote}`;
4074
4586
  }
4075
4587
 
4076
4588
  // src/index.ts
4077
4589
  function ownVersion() {
4078
4590
  try {
4079
- return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path13.join)(__dirname, "..", "package.json"), "utf8")).version;
4591
+ return JSON.parse((0, import_node_fs24.readFileSync)((0, import_node_path16.join)(__dirname, "..", "package.json"), "utf8")).version;
4080
4592
  } catch {
4081
4593
  return "0.0.0";
4082
4594
  }
4083
4595
  }
4084
- var HELP = `mmi-hub ${ownVersion()} \u2014 install and maintain MMI tooling
4085
-
4086
- status [--json] read installed/expected versions, hourly schedule, last run,
4087
- and actionable failures (default; never heals)
4088
- install [--json] converge the CLI and every present host surface now, then enable
4089
- hourly automatic updates
4090
- update [--dry-run] [--json] [--verbose]
4091
- converge immediately to the newest gated release: one line per
4092
- surface, a short reason only when not ok; --verbose traces steps
4093
- autoupdate on|off idempotently enable or disable hourly convergence; off keeps tools
4094
- --version print the Hub maintenance package version
4095
-
4096
- State remains under $MMI_UPDATER_HOME (default ~/.mmi/updater), and the Windows task name remains
4097
- "MMI Fleet Updater", so existing installations continue in place.
4098
- Status performs no npm install, host repair, scheduler write, state mkdir, or repository cleanup.`;
4596
+ var FACE = "mmi-hub";
4597
+ var declaration = validateInstallerProduct(installer_product_default);
4598
+ function usage(render) {
4599
+ return renderUsage(FACE, {
4600
+ bin: "mmi-hub",
4601
+ version: ownVersion(),
4602
+ summary: "install and keep MMI tooling up to date",
4603
+ commands: [
4604
+ { usage: "status", text: "Show what is installed, whether auto-update is on, and anything to fix. The default. Changes nothing." },
4605
+ { usage: "install", text: "Update everything now, then turn on hourly auto-update." },
4606
+ { usage: "update", text: "Update everything to the newest release now." },
4607
+ { usage: "autoupdate on|off", text: "Turn hourly auto-update on or off. Off keeps installed tools." }
4608
+ ],
4609
+ options: [
4610
+ { usage: "--json", text: "Print machine-readable JSON instead of the screen." },
4611
+ { usage: "--dry-run", text: "With update: show what would change, change nothing." },
4612
+ { usage: "--verbose", text: "With update or install: show each step as it runs." },
4613
+ { usage: "--quiet", text: "With update or install: skip the progress lines." },
4614
+ { usage: "--no-color", text: "Turn colours off." },
4615
+ { usage: "--version", text: "Print the version." },
4616
+ { usage: "--help", text: "Print this help." }
4617
+ ]
4618
+ }, render);
4619
+ }
4620
+ function notice(screen, render) {
4621
+ printScreen(renderNotice(FACE, screen, render), screen.state === "fail" ? "stderr" : "stdout");
4622
+ }
4099
4623
  function handOff(reexec, args, env) {
4100
- const child = (0, import_node_child_process10.spawnSync)(process.execPath, [reexec.dist, ...args], {
4624
+ const child = (0, import_node_child_process11.spawnSync)(process.execPath, [reexec.dist, ...args], {
4101
4625
  stdio: "inherit",
4102
4626
  windowsHide: true,
4103
4627
  env: { ...env, MMI_UPDATER_REEXEC: "1" }
@@ -4120,46 +4644,62 @@ function runMaintenance2(args, dependencies = {}) {
4120
4644
  handOff: dependencies.handOff ?? handOff,
4121
4645
  run: dependencies.run,
4122
4646
  shippedTarget: shippedTargetFrom(args),
4647
+ launcher: declaration.launcher ? ({ dryRun }) => (dependencies.refreshLauncher ?? refreshLauncher)(declaration.launcher, { dryRun }) : void 0,
4123
4648
  counts: reportCounts,
4124
4649
  diagnose: (arm) => arm.verdict === "fail" ? [arm.detail, failConsequence(arm), arm.repeatFailure ? "This failure persisted after repair; do not repeat the same remedy. Inspect the updater journal." : retryHint(arm.detail)].join("\n") : arm.verdict === "defer" ? `${arm.detail}
4125
4650
  ${retryHint(arm.detail)}` : void 0
4126
4651
  });
4127
4652
  }
4128
4653
  async function main(args = process.argv.slice(2)) {
4654
+ const render = { color: !args.includes("--no-color") };
4129
4655
  if (args.includes("--version")) {
4130
- process.stdout.write(ownVersion() + "\n");
4656
+ process.stdout.write(`${process.stdout.isTTY ? renderVersion("mmi-hub", ownVersion()) : ownVersion()}
4657
+ `);
4131
4658
  return 0;
4132
4659
  }
4133
4660
  if (args.includes("--help") || args.includes("-h")) {
4134
- process.stdout.write(HELP + "\n");
4661
+ printScreen(usage(render));
4135
4662
  return 0;
4136
4663
  }
4137
4664
  const command = args.find((arg) => !arg.startsWith("--")) ?? "status";
4138
4665
  const json = args.includes("--json");
4139
4666
  if (command === "status") {
4140
- const status = hubStatus(ownVersion());
4141
- process.stdout.write(json ? JSON.stringify(status, null, 2) + "\n" : formatHubStatus(status) + "\n");
4142
- return status.failures.length ? 1 : 0;
4667
+ const report = hubStatusReport(ownVersion());
4668
+ if (json) process.stdout.write(JSON.stringify(report.status, null, 2) + "\n");
4669
+ else printScreen(renderReport(FACE, statusScreen(report, declaration.doctor), render));
4670
+ return report.status.failures.length ? 1 : 0;
4143
4671
  }
4144
4672
  if (command === "update" || command === "install") return runMaintenance2(args);
4145
4673
  if (command === "autoupdate") {
4146
- const commandIndex = args.indexOf(command);
4147
- const mode = args[commandIndex + 1];
4674
+ const mode = args[args.indexOf(command) + 1];
4148
4675
  if (mode !== "on" && mode !== "off") {
4149
- process.stderr.write("mmi-hub autoupdate requires on or off\n");
4676
+ const message3 = mode === void 0 || mode.startsWith("--") ? "Say on or off: mmi-hub autoupdate on, or mmi-hub autoupdate off." : `Unknown auto-update mode: ${mode}. Say on or off.`;
4677
+ if (json) process.stdout.write(JSON.stringify({ error: message3 }, null, 2) + "\n");
4678
+ else notice({ state: "fail", message: message3, next: "mmi-hub autoupdate on" }, render);
4150
4679
  return 1;
4151
4680
  }
4152
4681
  const result = mode === "on" ? installTask() : uninstallTask();
4153
- process.stdout.write(json ? JSON.stringify({ mode, ...result }, null, 2) + "\n" : `mmi-hub autoupdate ${mode}: ${result.detail}
4154
- `);
4682
+ if (json) process.stdout.write(JSON.stringify({ mode, ...result }, null, 2) + "\n");
4683
+ else if (!result.ok) notice({ state: "fail", message: `Auto-update could not be turned ${mode}: ${plainDetail(result.detail)}`, next: `mmi-hub autoupdate ${mode}` }, render);
4684
+ else if (mode === "on") notice({ state: "ok", message: "Auto-update is on. mmi-hub checks for updates every hour." }, render);
4685
+ else notice({ state: "ok", message: "Auto-update is off. Installed tools were kept." }, render);
4155
4686
  return result.ok ? 0 : 1;
4156
4687
  }
4157
- process.stdout.write(HELP + "\n");
4688
+ notice({ state: "fail", message: `Unknown command: ${command}.`, next: "mmi-hub --help" }, render);
4158
4689
  return 1;
4159
4690
  }
4691
+ function plainDetail(detail) {
4692
+ const text = detail.split(" \u2014 run `")[0].replaceAll("`", "").trim();
4693
+ return /[.!?]$/.test(text) ? text : `${text}.`;
4694
+ }
4160
4695
  if (typeof require !== "undefined" && require.main === module) {
4161
4696
  main().then((code) => process.exit(code)).catch((error) => {
4162
- process.stderr.write(`mmi-hub: ${error?.stack ?? error}
4697
+ const args = process.argv.slice(2);
4698
+ const first = String(error?.message ?? error).split("\n")[0].replaceAll("`", "").trim();
4699
+ const message3 = first ? /[.!?]$/.test(first) ? first : `${first}.` : "mmi-hub stopped unexpectedly.";
4700
+ if (args.includes("--json")) process.stdout.write(JSON.stringify({ error: message3 }, null, 2) + "\n");
4701
+ else notice({ state: "fail", message: message3, next: declaration.doctor }, { color: !args.includes("--no-color") });
4702
+ if (args.includes("--verbose") && error?.stack) process.stderr.write(`${error.stack}
4163
4703
  `);
4164
4704
  process.exit(1);
4165
4705
  });
@@ -4185,6 +4725,7 @@ if (typeof require !== "undefined" && require.main === module) {
4185
4725
  hermesHostEvidence,
4186
4726
  hermesHostPresent,
4187
4727
  hubStatus,
4728
+ hubStatusReport,
4188
4729
  launchdPlist,
4189
4730
  launcherSh,
4190
4731
  launcherVbs,
@@ -4209,6 +4750,8 @@ if (typeof require !== "undefined" && require.main === module) {
4209
4750
  reconcile,
4210
4751
  reconcileCursorUserHooks,
4211
4752
  releaseSharedDocLock,
4753
+ renderNotice,
4754
+ renderReport,
4212
4755
  reportCounts,
4213
4756
  retiredSurfaces,
4214
4757
  retryHint,
@@ -4219,6 +4762,7 @@ if (typeof require !== "undefined" && require.main === module) {
4219
4762
  sharedDocLockTimeoutReceipt,
4220
4763
  shippedTargetFrom,
4221
4764
  shouldHandOff,
4765
+ statusScreen,
4222
4766
  surfaceArmPlan,
4223
4767
  systemdServiceUnit,
4224
4768
  systemdTimerUnit,