@mutmutco/hub 4.1.9 → 4.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +485 -245
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -23,6 +23,8 @@ var index_exports = {};
23
23
  __export(index_exports, {
24
24
  acquireSharedDocLock: () => acquireSharedDocLock,
25
25
  buildWatcherCommand: () => buildWatcherCommand,
26
+ claudeArm: () => claudeArm,
27
+ claudeHostEvidence: () => claudeHostEvidence,
26
28
  clearLaunchTarget: () => clearLaunchTarget,
27
29
  clearPending: () => clearPending,
28
30
  enumerateSurfaces: () => enumerateSurfaces,
@@ -30,7 +32,9 @@ __export(index_exports, {
30
32
  formatReconcileReport: () => formatReconcileReport,
31
33
  hermesArm: () => hermesArm,
32
34
  hermesHome: () => hermesHome,
35
+ hermesHostCommand: () => hermesHostCommand,
33
36
  hermesHostEvidence: () => hermesHostEvidence,
37
+ hermesHostPresent: () => hermesHostPresent,
34
38
  hubStatus: () => hubStatus,
35
39
  launchdPlist: () => launchdPlist,
36
40
  launcherSh: () => launcherSh,
@@ -45,6 +49,7 @@ __export(index_exports, {
45
49
  parseNpmDistIntegrity: () => parseNpmDistIntegrity,
46
50
  pendingPath: () => pendingPath,
47
51
  pidAlive: () => pidAlive,
52
+ probeClaudeLaunchStaging: () => probeClaudeLaunchStaging,
48
53
  probeCursorLaunchStaging: () => probeCursorLaunchStaging,
49
54
  probeHermesInstallation: () => probeHermesInstallation,
50
55
  probeHermesLaunchStaging: () => probeHermesLaunchStaging,
@@ -400,66 +405,39 @@ function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
400
405
  }
401
406
 
402
407
  // src/surfaces.ts
403
- var import_node_fs4 = require("node:fs");
408
+ var import_node_fs8 = require("node:fs");
409
+ var import_node_os3 = require("node:os");
410
+ var import_node_path7 = require("node:path");
411
+
412
+ // src/arm-host-plugins.ts
413
+ var import_node_child_process5 = require("node:child_process");
414
+ var import_node_fs7 = require("node:fs");
404
415
  var import_node_os2 = require("node:os");
405
- var import_node_path3 = require("node:path");
406
- function kimiHome(env = process.env) {
407
- return env.KIMI_CODE_HOME?.trim() || (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".kimi-code");
408
- }
409
- function jervcodeAgentDir(env = process.env) {
410
- return env.MMI_UPDATER_JERVCODE_AGENT_DIR?.trim() || env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".jerv", "agent");
411
- }
412
- function hermesHome(env = process.env, platform = process.platform) {
413
- if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
414
- if (platform === "win32") return (0, import_node_path3.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path3.join)((0, import_node_os2.homedir)(), "AppData", "Local"), "hermes");
415
- return (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".hermes");
416
- }
417
- function enumerateSurfaces(env = process.env) {
418
- const home = (0, import_node_os2.homedir)();
419
- const codexHome = env.CODEX_HOME || (0, import_node_path3.join)(home, ".codex");
420
- return [
421
- { id: "claude", marker: (0, import_node_path3.join)(home, ".claude", "plugins", "installed_plugins.json") },
422
- { id: "codex", marker: (0, import_node_path3.join)(codexHome, "plugins", "installed_plugins.json") },
423
- // The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
424
- // plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
425
- // and the arm could never make the first install. The plugin's absence is a converge action, not
426
- // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
427
- { id: "cursor", marker: (0, import_node_path3.join)(home, ".cursor") },
428
- { id: "kilo", marker: (0, import_node_path3.join)(home, ".config", "kilo") },
429
- // The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
430
- // itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
431
- // could never make the first install. The plugin's absence is a converge action, not a skip.
432
- { id: "kimi", marker: kimiHome(env) },
433
- { id: "jervcode", marker: (0, import_node_path3.join)(jervcodeAgentDir(env), "settings.json") },
434
- // The host root is the marker, not plugins/mmi: an installed Hermes without MMI must be
435
- // discovered so its native plugin tree can be converged for the first time.
436
- { id: "hermes", marker: hermesHome(env) }
437
- ].map((probe) => ({ ...probe, present: (0, import_node_fs4.existsSync)(probe.marker) }));
438
- }
416
+ var import_node_path6 = require("node:path");
439
417
 
440
- // src/arm-cli.ts
441
- var import_node_fs6 = require("node:fs");
418
+ // src/arm-npm-global.ts
419
+ var import_node_fs5 = require("node:fs");
442
420
  var import_node_child_process4 = require("node:child_process");
443
- var import_node_path5 = require("node:path");
421
+ var import_node_path4 = require("node:path");
444
422
 
445
423
  // src/pending.ts
446
424
  var import_node_child_process3 = require("node:child_process");
447
- var import_node_fs5 = require("node:fs");
448
- var import_node_path4 = require("node:path");
425
+ var import_node_fs4 = require("node:fs");
426
+ var import_node_path3 = require("node:path");
449
427
  function hubDistPath(env) {
450
428
  const root = globalRoot(env);
451
- return root ? (0, import_node_path4.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
429
+ return root ? (0, import_node_path3.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
452
430
  }
453
431
  var WATCHER_MAX_WAIT_MS = 4 * 60 * 60 * 1e3;
454
432
  function pendingPath(paths) {
455
- return (0, import_node_path4.join)(paths.root, "pending.json");
433
+ return (0, import_node_path3.join)(paths.root, "pending.json");
456
434
  }
457
435
  function watcherLockPath(paths) {
458
- return (0, import_node_path4.join)(paths.root, "pending-watcher.lock.d");
436
+ return (0, import_node_path3.join)(paths.root, "pending-watcher.lock.d");
459
437
  }
460
438
  function readPending(paths) {
461
439
  try {
462
- const parsed = JSON.parse((0, import_node_fs5.readFileSync)(pendingPath(paths), "utf8"));
440
+ const parsed = JSON.parse((0, import_node_fs4.readFileSync)(pendingPath(paths), "utf8"));
463
441
  if (!Array.isArray(parsed)) return [];
464
442
  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));
465
443
  } catch {
@@ -467,12 +445,12 @@ function readPending(paths) {
467
445
  }
468
446
  }
469
447
  function writePending(paths, records) {
470
- (0, import_node_fs5.mkdirSync)(paths.root, { recursive: true });
448
+ (0, import_node_fs4.mkdirSync)(paths.root, { recursive: true });
471
449
  const path = pendingPath(paths);
472
450
  const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
473
- (0, import_node_fs5.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
451
+ (0, import_node_fs4.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
474
452
  `, "utf8");
475
- (0, import_node_fs5.renameSync)(tmp, path);
453
+ (0, import_node_fs4.renameSync)(tmp, path);
476
454
  }
477
455
  function clearPending(paths, surface) {
478
456
  const records = readPending(paths);
@@ -541,14 +519,14 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
541
519
  const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
542
520
  if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
543
521
  const hubDist = seam.hubDist ?? hubDistPath(env);
544
- if (!hubDist || !(0, import_node_fs5.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
522
+ if (!hubDist || !(0, import_node_fs4.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
545
523
  const command = buildWatcherCommand({
546
524
  images: [...new Set(records.flatMap((row) => row.images))],
547
525
  dirs: [...new Set(records.flatMap((row) => row.dirs))],
548
526
  node: process.execPath,
549
527
  hubDist,
550
- codePath: (0, import_node_path4.join)(paths.root, "pending-result.code"),
551
- logPath: (0, import_node_path4.join)(paths.root, "pending-result.log"),
528
+ codePath: (0, import_node_path3.join)(paths.root, "pending-result.code"),
529
+ logPath: (0, import_node_path3.join)(paths.root, "pending-result.log"),
552
530
  lockPath: watcherLockPath(paths)
553
531
  });
554
532
  try {
@@ -584,88 +562,14 @@ function resumePendingWatcher(paths, env = process.env) {
584
562
  return schedulePendingWatcher(paths, env);
585
563
  }
586
564
 
587
- // src/arm-cli.ts
588
- function globalCliDist(env) {
589
- const root = globalRoot(env);
590
- return root ? (0, import_node_path5.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
591
- }
592
- function probeCliVersion(distPath) {
593
- if (!fileExists(distPath)) return null;
594
- const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
595
- const version = (probe.stdout || "").trim();
596
- return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
597
- }
598
- function probeCliInstallation(env = process.env) {
599
- const location = globalCliDist(env);
600
- const version = location ? probeCliVersion(location) : null;
601
- return { version, location, detail: version ? `absolute binary reported ${version}` : "global @mutmutco/cli binary was not readable" };
602
- }
603
- function cliArm(options) {
604
- const env = options.env ?? process.env;
605
- const { target, dryRun, paths } = options;
606
- const dist = globalCliDist(env);
607
- const installed = dist ? probeCliVersion(dist) : null;
608
- if (installed && compareSemver(installed, target) > 0) {
609
- return { surface: "cli", from: installed, to: installed, verdict: "skip", detail: `installed ${installed} is above candidate ${target} (monotonic)` };
610
- }
611
- const highWater = journalHighWater(paths.journalPath, "cli");
612
- if (highWater && compareSemver(highWater, target) > 0) {
613
- return { surface: "cli", from: installed, to: highWater, verdict: "skip", detail: `journal high-water ${highWater} is above candidate ${target} (monotonic)` };
614
- }
615
- if (installed === target) {
616
- return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `already at target ${target}` };
617
- }
618
- if (dryRun) {
619
- return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
620
- }
621
- const stageRoot = (0, import_node_path5.join)(paths.staging, "cli", target);
622
- const stageDist = (0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
623
- if (!fileExists(stageDist)) {
624
- (0, import_node_fs6.mkdirSync)(stageRoot, { recursive: true });
625
- (0, import_node_fs6.writeFileSync)((0, import_node_path5.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
626
- const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
627
- if (install.status !== 0) {
628
- return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staging install failed: ${(install.stderr || install.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
629
- }
630
- }
631
- let stagedManifestVersion;
632
- try {
633
- stagedManifestVersion = JSON.parse((0, import_node_fs6.readFileSync)((0, import_node_path5.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
634
- } catch {
635
- }
636
- if (stagedManifestVersion !== target) {
637
- return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged manifest is ${stagedManifestVersion ?? "unreadable"}, expected ${target}` };
638
- }
639
- const stagedVersion = probeCliVersion(stageDist);
640
- if (stagedVersion !== target) {
641
- return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}` };
642
- }
643
- const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
644
- if (globalInstall.status !== 0) {
645
- const reason = `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}`;
646
- const root = globalRoot(env);
647
- const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cli", target, reason, dirs: [root] })}` : "";
648
- return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `${reason}${pending}` };
649
- }
650
- const switchedDist = globalCliDist(env);
651
- const switched = probeCliVersion(switchedDist ?? "");
652
- if (switched !== target) {
653
- return { surface: "cli", from: installed, to: switched ?? installed ?? "unknown", verdict: "fail", detail: `post-switch probe returned ${switched ?? "dead"} at ${switchedDist ?? "an unresolvable path"}, expected ${target}; the global install already ran \u2014 nothing was rolled back` };
654
- }
655
- return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)` };
656
- }
657
-
658
565
  // src/arm-npm-global.ts
659
- var import_node_fs7 = require("node:fs");
660
- var import_node_child_process5 = require("node:child_process");
661
- var import_node_path6 = require("node:path");
662
566
  function globalDist(env, packageName, distRel) {
663
567
  const root = globalRoot(env);
664
- return root ? (0, import_node_path6.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
568
+ return root ? (0, import_node_path4.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
665
569
  }
666
570
  function probeVersion(distPath) {
667
571
  if (!fileExists(distPath)) return null;
668
- const probe = (0, import_node_child_process5.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
572
+ const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
669
573
  const version = (probe.stdout || "").trim();
670
574
  return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
671
575
  }
@@ -674,11 +578,11 @@ function tail(output) {
674
578
  }
675
579
  function stagePackage(options) {
676
580
  const { surface, packageName, target, witness, paths, env } = options;
677
- const stageRoot = (0, import_node_path6.join)(paths.staging, surface, target);
678
- const packageRoot = (0, import_node_path6.join)(stageRoot, "node_modules", ...packageName.split("/"));
679
- if (!fileExists((0, import_node_path6.join)(packageRoot, ...witness.split("/")))) {
680
- (0, import_node_fs7.mkdirSync)(stageRoot, { recursive: true });
681
- (0, import_node_fs7.writeFileSync)((0, import_node_path6.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
581
+ const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
582
+ const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
583
+ if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
584
+ (0, import_node_fs5.mkdirSync)(stageRoot, { recursive: true });
585
+ (0, import_node_fs5.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
682
586
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
683
587
  if (install.status !== 0) {
684
588
  return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
@@ -686,7 +590,7 @@ function stagePackage(options) {
686
590
  }
687
591
  let stagedManifestVersion;
688
592
  try {
689
- stagedManifestVersion = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(packageRoot, "package.json"), "utf8")).version;
593
+ stagedManifestVersion = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
690
594
  } catch {
691
595
  }
692
596
  if (stagedManifestVersion !== target) {
@@ -718,7 +622,7 @@ function npmGlobalArm(options) {
718
622
  if ("defer" in staged) {
719
623
  return result(installed, target, "defer", staged.defer);
720
624
  }
721
- const stageDist = (0, import_node_path6.join)(staged.packageRoot, ...distRel.split("/"));
625
+ const stageDist = (0, import_node_path4.join)(staged.packageRoot, ...distRel.split("/"));
722
626
  const stagedVersion = probeVersion(stageDist);
723
627
  if (stagedVersion !== target) {
724
628
  return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
@@ -738,21 +642,56 @@ function npmGlobalArm(options) {
738
642
  return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)`);
739
643
  }
740
644
 
741
- // src/arm-self.ts
742
- function selfArm(options) {
743
- return npmGlobalArm({
744
- surface: "updater",
745
- packageName: "@mutmutco/hub",
746
- distRel: "dist/index.cjs",
747
- ...options
645
+ // src/launch-staging.ts
646
+ var import_node_fs6 = require("node:fs");
647
+ var import_node_path5 = require("node:path");
648
+ function readLaunchTarget(path) {
649
+ try {
650
+ const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
651
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
652
+ const row = parsed;
653
+ if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
654
+ return row;
655
+ } catch {
656
+ return null;
657
+ }
658
+ }
659
+ function writeLaunchTarget(path, record) {
660
+ (0, import_node_fs6.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
661
+ const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
662
+ (0, import_node_fs6.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
663
+ `, "utf8");
664
+ (0, import_node_fs6.renameSync)(tmp, path);
665
+ }
666
+ function clearLaunchTarget(path) {
667
+ try {
668
+ (0, import_node_fs6.rmSync)(path, { force: true });
669
+ } catch {
670
+ }
671
+ }
672
+ function noteLaunchStaged(paths, env, options, seam = {}) {
673
+ writeLaunchTarget(options.launchTargetPath, {
674
+ surface: options.surface,
675
+ target: options.target,
676
+ since: (/* @__PURE__ */ new Date()).toISOString(),
677
+ incoming: options.incoming
748
678
  });
679
+ return noteBusyDefer(paths, env, {
680
+ surface: options.surface,
681
+ target: options.target,
682
+ reason: options.reason,
683
+ images: options.images,
684
+ dirs: options.dirs
685
+ }, seam);
686
+ }
687
+ function probeLaunchTargetVersion(launchTargetPath, versionAt) {
688
+ const record = readLaunchTarget(launchTargetPath);
689
+ if (!record || !(0, import_node_fs6.existsSync)(record.incoming)) return null;
690
+ const version = versionAt(record.incoming);
691
+ return version === record.target ? record.target : null;
749
692
  }
750
693
 
751
694
  // src/arm-host-plugins.ts
752
- var import_node_child_process6 = require("node:child_process");
753
- var import_node_fs8 = require("node:fs");
754
- var import_node_os3 = require("node:os");
755
- var import_node_path7 = require("node:path");
756
695
  var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
757
696
  var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
758
697
  function convergedAtOrAbove(installed, target, healthy) {
@@ -762,13 +701,13 @@ function cmdArg(value) {
762
701
  return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
763
702
  }
764
703
  var runHostCommand = (command, args, env) => {
765
- const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
704
+ const result = process.platform === "win32" ? (0, import_node_child_process5.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
766
705
  encoding: "utf8",
767
706
  env,
768
707
  input: "",
769
708
  timeout: 12e4,
770
709
  windowsHide: true
771
- }) : (0, import_node_child_process6.spawnSync)(command, args, {
710
+ }) : (0, import_node_child_process5.spawnSync)(command, args, {
772
711
  encoding: "utf8",
773
712
  env,
774
713
  input: "",
@@ -794,14 +733,18 @@ function claudeMmi(runner, env) {
794
733
  if (!Array.isArray(parsed)) return { row: null, error: `claude plugin list --json failed: ${failure(result)}` };
795
734
  return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
796
735
  }
736
+ function claudeHomeDir(env) {
737
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os2.homedir)();
738
+ return (0, import_node_path6.join)(home, ".claude");
739
+ }
797
740
  function claudeMarketplacesPath(env) {
798
- return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".claude", "plugins", "known_marketplaces.json");
741
+ return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path6.join)(claudeHomeDir(env), "plugins", "known_marketplaces.json");
799
742
  }
800
743
  function ensureClaudeMarketplace(env) {
801
744
  const path = claudeMarketplacesPath(env);
802
745
  let body;
803
746
  try {
804
- body = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
747
+ body = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
805
748
  } catch (error) {
806
749
  if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
807
750
  return { ok: false, detail: `cannot read ${path}` };
@@ -821,54 +764,185 @@ function ensureClaudeMarketplace(env) {
821
764
  }
822
765
  return { ok: true };
823
766
  }
767
+ var CLAUDE_PACKAGE = "@mutmutco/claude-plugin";
768
+ var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
769
+ var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json", "hooks/hooks.json", "bin/mmi-hook"];
770
+ function claudePluginsRoot(env) {
771
+ return (0, import_node_path6.join)(claudeHomeDir(env), "plugins");
772
+ }
773
+ function claudeMarkerVersion(root) {
774
+ try {
775
+ const version = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
776
+ return typeof version === "string" ? version : null;
777
+ } catch {
778
+ return null;
779
+ }
780
+ }
781
+ function claudeTreeHealthy(root) {
782
+ return CLAUDE_TREE_MARKERS.every((rel) => fileExists((0, import_node_path6.join)(root, ...rel.split("/"))));
783
+ }
784
+ var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
785
+ var HUB_UPDATE_COMMAND = /(?:\bmmi-hub(?:\.(?:cmd|exe|bat|ps1))?\b|@mutmutco[/\\]hub[/\\]dist[/\\]index\.(?:cjs|mjs|js))[\s'"]+update\b/i;
786
+ function claudeProcessToken(line) {
787
+ if (HUB_UPDATE_COMMAND.test(line)) return null;
788
+ for (const token of line.replace(/["']/g, " ").split(/\s+/)) {
789
+ for (const segment of token.split(/[/\\]/)) {
790
+ if (CLAUDE_PROCESS_SEGMENT.test(segment)) return segment;
791
+ }
792
+ }
793
+ return null;
794
+ }
795
+ function realClaudeProcessListing() {
796
+ const result = process.platform === "win32" ? (0, import_node_child_process5.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process5.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
797
+ return result.status === 0 ? result.stdout ?? "" : null;
798
+ }
799
+ function claudeHostEvidence(env, processListProvider) {
800
+ for (const marker of ["CLAUDE_CODE_SESSION_ID", "CLAUDECODE", "CLAUDE_PLUGIN_ROOT"]) {
801
+ if (env[marker]?.trim()) return `env marker ${marker}`;
802
+ }
803
+ const listing = processListProvider ? processListProvider() : realClaudeProcessListing();
804
+ if (listing === null || !listing.trim()) return "the process list is unreadable (unsafe to write)";
805
+ for (const line of listing.split(/\r?\n/)) {
806
+ const token = claudeProcessToken(line);
807
+ if (token) return `a live process names claude (${token})`;
808
+ }
809
+ return null;
810
+ }
811
+ function probeClaudeLaunchStaging(env = process.env) {
812
+ return probeLaunchTargetVersion((0, import_node_path6.join)(claudePluginsRoot(env), "staging", "launch-target.json"), claudeMarkerVersion);
813
+ }
824
814
  function claudeArm(options) {
825
815
  const env = options.env ?? process.env;
826
816
  const runner = options.runner ?? runHostCommand;
817
+ const paths = options.paths ?? ensureState(env);
818
+ const target = options.target;
827
819
  const repoUrl = options.repoUrl ?? (env.MMI_UPDATER_REPO || REPO_URL_DEFAULT);
820
+ const home = claudeHomeDir(env);
821
+ const result = (from2, to, verdict, detail, launchStaged = false) => ({
822
+ surface: "claude",
823
+ from: from2,
824
+ to,
825
+ verdict,
826
+ detail,
827
+ launchStaged: launchStaged || void 0
828
+ });
828
829
  const before = claudeMmi(runner, env);
829
- if (before.error) return { surface: "claude", from: null, to: options.target, verdict: "defer", detail: before.error };
830
+ if (before.error) return result(null, target, "defer", before.error);
830
831
  const from = before.row?.version ?? null;
832
+ if (from && compareSemver(from, target) > 0) {
833
+ return result(from, from, "skip", `installed ${from} is above candidate ${target} (monotonic)`);
834
+ }
835
+ const highWater = journalHighWater(paths.journalPath, "claude");
836
+ if (highWater && compareSemver(highWater, target) > 0) {
837
+ return result(from, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
838
+ }
831
839
  let marketplaceTrust = ensureClaudeMarketplace(env);
832
840
  if (!marketplaceTrust.ok && marketplaceTrust.unregistered) {
833
841
  const add = runner("claude", ["plugin", "marketplace", "add", repoUrl], env);
834
- if (add.status !== 0) return { surface: "claude", from, to: options.target, verdict: "defer", detail: `mutmutco marketplace was not registered and marketplace add failed: ${failure(add)}` };
842
+ if (add.status !== 0) return result(from, target, "defer", `mutmutco marketplace was not registered and marketplace add failed: ${failure(add)}`);
835
843
  marketplaceTrust = ensureClaudeMarketplace(env);
836
844
  }
837
- if (!marketplaceTrust.ok) return { surface: "claude", from, to: options.target, verdict: "defer", detail: marketplaceTrust.detail ?? "cannot trust the mutmutco marketplace registration" };
845
+ if (!marketplaceTrust.ok) return result(from, target, "defer", marketplaceTrust.detail ?? "cannot trust the mutmutco marketplace registration");
846
+ const pluginsRoot = claudePluginsRoot(env);
847
+ const launchTargetPath = (0, import_node_path6.join)(pluginsRoot, "staging", "launch-target.json");
838
848
  if (options.dryRun) {
839
- return { surface: "claude", from, to: options.target, verdict: "ok", detail: `dry-run: would refresh mutmutco and converge mmi@mutmutco ${from ?? "absent"} -> ${options.target}` };
849
+ return result(from, target, "ok", `dry-run: would refresh mutmutco, stage ${target} on the Claude volume, and converge mmi@mutmutco ${from ?? "absent"} -> ${target}`);
840
850
  }
841
851
  const marketplace = runner("claude", ["plugin", "marketplace", "update", "mutmutco"], env);
842
852
  if (marketplace.status !== 0) {
843
- return { surface: "claude", from, to: options.target, verdict: "defer", detail: `marketplace update failed: ${failure(marketplace)}` };
853
+ return result(from, target, "defer", `marketplace update failed: ${failure(marketplace)}`);
844
854
  }
845
855
  let observed = claudeMmi(runner, env);
846
- if (observed.error) return { surface: "claude", from, to: options.target, verdict: "defer", detail: observed.error };
847
- if (observed.row?.version === options.target && observed.row.enabled === true) {
848
- return { surface: "claude", from, to: options.target, verdict: "ok", detail: `catalog refreshed; installed+enabled at target ${options.target}` };
856
+ if (observed.error) return result(from, target, "defer", observed.error);
857
+ if (observed.row?.version === target && observed.row.enabled === true) {
858
+ clearLaunchTarget(launchTargetPath);
859
+ return result(from, target, "ok", `catalog refreshed; installed+enabled at target ${target}`);
860
+ }
861
+ const incomingRoot = (0, import_node_path6.join)(pluginsRoot, "staging", "incoming");
862
+ const incoming = (0, import_node_path6.join)(incomingRoot, target);
863
+ const priorLaunch = readLaunchTarget(launchTargetPath);
864
+ const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && claudeMarkerVersion(incoming) === target && claudeTreeHealthy(incoming);
865
+ if (!reuseIncoming) {
866
+ const staged = stagePackage({ surface: "claude", packageName: CLAUDE_PACKAGE, target, witness: CLAUDE_MANIFEST, paths, env });
867
+ if ("defer" in staged) return result(from, target, "defer", staged.defer);
868
+ if (claudeMarkerVersion(staged.packageRoot) !== target || !claudeTreeHealthy(staged.packageRoot)) {
869
+ return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
870
+ }
871
+ try {
872
+ (0, import_node_fs7.mkdirSync)((0, import_node_path6.join)(pluginsRoot, "staging"), { recursive: true });
873
+ (0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
874
+ (0, import_node_fs7.cpSync)(staged.packageRoot, incoming, { recursive: true });
875
+ } catch (error) {
876
+ return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
877
+ }
878
+ if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
879
+ (0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
880
+ return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
881
+ }
882
+ }
883
+ const busy = claudeHostEvidence(env, options.processListProvider);
884
+ if (busy) {
885
+ const pending = noteLaunchStaged(paths, env, {
886
+ surface: "claude",
887
+ target,
888
+ reason: busy,
889
+ launchTargetPath,
890
+ incoming,
891
+ images: ["claude"],
892
+ dirs: [home]
893
+ });
894
+ return result(from, target, "ok", `staged ${target} for next launch while Claude Code is running (${busy}) \u2014 ${pending}`, true);
895
+ }
896
+ const started = claudeHostEvidence(env, options.processListProvider);
897
+ if (started) {
898
+ const pending = noteLaunchStaged(paths, env, {
899
+ surface: "claude",
900
+ target,
901
+ reason: started,
902
+ launchTargetPath,
903
+ incoming,
904
+ images: ["claude"],
905
+ dirs: [home]
906
+ });
907
+ return result(from, target, "ok", `staged ${target} for next launch \u2014 Claude Code started during staging (${started}) \u2014 ${pending}`, true);
908
+ }
909
+ const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
910
+ try {
911
+ if (!(0, import_node_fs7.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
912
+ (0, import_node_fs7.rmSync)(cacheTarget, { recursive: true, force: true });
913
+ (0, import_node_fs7.mkdirSync)((0, import_node_path6.join)(cacheTarget, ".."), { recursive: true });
914
+ (0, import_node_fs7.cpSync)(incoming, cacheTarget, { recursive: true });
915
+ }
916
+ } catch (error) {
917
+ return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
849
918
  }
850
919
  const scope = observed.row?.scope || before.row?.scope || "user";
851
920
  const command = observed.row ? ["plugin", "update", "mmi@mutmutco", "--scope", scope] : ["plugin", "install", "mmi@mutmutco", "--scope", scope];
852
921
  const converge = runner("claude", command, env);
853
922
  if (converge.status !== 0) {
854
- return { surface: "claude", from, to: options.target, verdict: "defer", detail: `${observed.row ? "plugin update" : "plugin install"} failed: ${failure(converge)}` };
923
+ return result(from, target, "defer", `${observed.row ? "plugin update" : "plugin install"} failed: ${failure(converge)}`);
855
924
  }
856
925
  observed = claudeMmi(runner, env);
857
- if (observed.error) return { surface: "claude", from, to: options.target, verdict: "defer", detail: observed.error };
858
- if (observed.row && observed.row.version === options.target && observed.row.enabled !== true) {
926
+ if (observed.error) return result(from, target, "defer", observed.error);
927
+ if (observed.row && observed.row.version === target && observed.row.enabled !== true) {
859
928
  const enable = runner("claude", ["plugin", "enable", "mmi@mutmutco", "--scope", observed.row.scope || scope], env);
860
929
  if (enable.status !== 0) {
861
- return { surface: "claude", from, to: options.target, verdict: "defer", detail: `plugin enable failed: ${failure(enable)}` };
930
+ return result(from, target, "defer", `plugin enable failed: ${failure(enable)}`);
862
931
  }
863
932
  observed = claudeMmi(runner, env);
864
933
  }
865
- if (!convergedAtOrAbove(observed.row?.version, options.target, observed.row?.enabled === true)) {
866
- return { surface: "claude", from, to: observed.row?.version ?? "unknown", verdict: "fail", detail: `post-update evidence is version ${observed.row?.version ?? "missing"}, enabled=${String(observed.row?.enabled)}, expected ${options.target}/true` };
934
+ if (!convergedAtOrAbove(observed.row?.version, target, observed.row?.enabled === true)) {
935
+ return result(from, observed.row?.version ?? "unknown", "fail", `post-update evidence is version ${observed.row?.version ?? "missing"}, enabled=${String(observed.row?.enabled)}, expected ${target}/true`);
867
936
  }
868
- if (observed.row.version !== options.target) {
869
- return { surface: "claude", from, to: observed.row.version, verdict: "skip", detail: `installed ${observed.row.version} is above candidate ${options.target} (monotonic)` };
937
+ if (observed.row.version !== target) {
938
+ return result(from, observed.row.version, "skip", `installed ${observed.row.version} is above candidate ${target} (monotonic)`);
870
939
  }
871
- return { surface: "claude", from, to: options.target, verdict: "ok", detail: `catalog refreshed; converged and verified ${from ?? "absent"} -> ${options.target}` };
940
+ clearLaunchTarget(launchTargetPath);
941
+ try {
942
+ (0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
943
+ } catch {
944
+ }
945
+ return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
872
946
  }
873
947
  var SNAPSHOT_MISSING = /marketplace snapshot|marketplace root does not contain/i;
874
948
  function codexMmi(runner, env) {
@@ -927,11 +1001,11 @@ function codexArm(options) {
927
1001
  }
928
1002
  var KILO_PACKAGE = "@mutmutco/kilo-plugin";
929
1003
  function kiloConfigPath(env) {
930
- return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".config", "kilo", "opencode.json");
1004
+ return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".config", "kilo", "opencode.json");
931
1005
  }
932
1006
  function readKiloConfig(env) {
933
1007
  try {
934
- const config = JSON.parse((0, import_node_fs8.readFileSync)(kiloConfigPath(env), "utf8"));
1008
+ const config = JSON.parse((0, import_node_fs7.readFileSync)(kiloConfigPath(env), "utf8"));
935
1009
  const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
936
1010
  return { config, specs };
937
1011
  } catch {
@@ -951,8 +1025,8 @@ function normalizeKiloConfig(env, exact) {
951
1025
  const path = kiloConfigPath(env);
952
1026
  const tmp = `${path}.tmp-${process.pid}`;
953
1027
  try {
954
- (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
955
- (0, import_node_fs8.renameSync)(tmp, path);
1028
+ (0, import_node_fs7.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
1029
+ (0, import_node_fs7.renameSync)(tmp, path);
956
1030
  return null;
957
1031
  } catch (error) {
958
1032
  return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
@@ -1014,71 +1088,150 @@ function kiloArm(options) {
1014
1088
  return { surface: "kilo", from, to: options.target, verdict: "ok", detail: `global config verified at ${exact}; ${legacyCount ? `retired ${legacyCount} legacy MMI path${legacyCount === 1 ? "" : "s"}; ` : ""}payload provisions at next session start` };
1015
1089
  }
1016
1090
 
1091
+ // src/surfaces.ts
1092
+ function kimiHome(env = process.env) {
1093
+ return env.KIMI_CODE_HOME?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".kimi-code");
1094
+ }
1095
+ function jervcodeAgentDir(env = process.env) {
1096
+ 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");
1097
+ }
1098
+ function hermesHome(env = process.env, platform = process.platform) {
1099
+ if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
1100
+ 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");
1101
+ return (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".hermes");
1102
+ }
1103
+ function hermesHostCommand(env = process.env) {
1104
+ return env.MMI_UPDATER_HERMES?.trim() || "hermes";
1105
+ }
1106
+ function hermesHostPresent(env = process.env, runner = runHostCommand) {
1107
+ return runner(hermesHostCommand(env), ["--version"], env).status === 0;
1108
+ }
1109
+ function enumerateSurfaces(env = process.env) {
1110
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os3.homedir)();
1111
+ const codexHome = env.CODEX_HOME || (0, import_node_path7.join)(home, ".codex");
1112
+ return [
1113
+ { id: "claude", marker: (0, import_node_path7.join)(home, ".claude", "plugins", "installed_plugins.json") },
1114
+ { id: "codex", marker: (0, import_node_path7.join)(codexHome, "plugins", "installed_plugins.json") },
1115
+ // The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
1116
+ // plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
1117
+ // and the arm could never make the first install. The plugin's absence is a converge action, not
1118
+ // a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
1119
+ { id: "cursor", marker: (0, import_node_path7.join)(home, ".cursor") },
1120
+ { id: "kilo", marker: (0, import_node_path7.join)(home, ".config", "kilo") },
1121
+ // The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
1122
+ // itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
1123
+ // could never make the first install. The plugin's absence is a converge action, not a skip.
1124
+ { id: "kimi", marker: kimiHome(env) },
1125
+ { id: "jervcode", marker: (0, import_node_path7.join)(jervcodeAgentDir(env), "settings.json") },
1126
+ // Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
1127
+ // leftover plugin manifests — that residue must not enumerate as an installed host.
1128
+ { id: "hermes", marker: `${hermesHostCommand(env)} --version` }
1129
+ ].map((probe) => ({
1130
+ ...probe,
1131
+ present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs8.existsSync)(probe.marker)
1132
+ }));
1133
+ }
1134
+
1135
+ // src/arm-cli.ts
1136
+ var import_node_fs9 = require("node:fs");
1137
+ var import_node_child_process6 = require("node:child_process");
1138
+ var import_node_path8 = require("node:path");
1139
+ function globalCliDist(env) {
1140
+ const root = globalRoot(env);
1141
+ return root ? (0, import_node_path8.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
1142
+ }
1143
+ function probeCliVersion(distPath) {
1144
+ if (!fileExists(distPath)) return null;
1145
+ const probe = (0, import_node_child_process6.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
1146
+ const version = (probe.stdout || "").trim();
1147
+ return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
1148
+ }
1149
+ function probeCliInstallation(env = process.env) {
1150
+ const location = globalCliDist(env);
1151
+ const version = location ? probeCliVersion(location) : null;
1152
+ return { version, location, detail: version ? `absolute binary reported ${version}` : "global @mutmutco/cli binary was not readable" };
1153
+ }
1154
+ function cliArm(options) {
1155
+ const env = options.env ?? process.env;
1156
+ const { target, dryRun, paths } = options;
1157
+ const dist = globalCliDist(env);
1158
+ const installed = dist ? probeCliVersion(dist) : null;
1159
+ if (installed && compareSemver(installed, target) > 0) {
1160
+ return { surface: "cli", from: installed, to: installed, verdict: "skip", detail: `installed ${installed} is above candidate ${target} (monotonic)` };
1161
+ }
1162
+ const highWater = journalHighWater(paths.journalPath, "cli");
1163
+ if (highWater && compareSemver(highWater, target) > 0) {
1164
+ return { surface: "cli", from: installed, to: highWater, verdict: "skip", detail: `journal high-water ${highWater} is above candidate ${target} (monotonic)` };
1165
+ }
1166
+ if (installed === target) {
1167
+ return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `already at target ${target}` };
1168
+ }
1169
+ if (dryRun) {
1170
+ return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
1171
+ }
1172
+ const stageRoot = (0, import_node_path8.join)(paths.staging, "cli", target);
1173
+ const stageDist = (0, import_node_path8.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
1174
+ if (!fileExists(stageDist)) {
1175
+ (0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
1176
+ (0, import_node_fs9.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1177
+ const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
1178
+ if (install.status !== 0) {
1179
+ return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staging install failed: ${(install.stderr || install.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
1180
+ }
1181
+ }
1182
+ let stagedManifestVersion;
1183
+ try {
1184
+ stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path8.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
1185
+ } catch {
1186
+ }
1187
+ if (stagedManifestVersion !== target) {
1188
+ return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged manifest is ${stagedManifestVersion ?? "unreadable"}, expected ${target}` };
1189
+ }
1190
+ const stagedVersion = probeCliVersion(stageDist);
1191
+ if (stagedVersion !== target) {
1192
+ return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}` };
1193
+ }
1194
+ const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
1195
+ if (globalInstall.status !== 0) {
1196
+ const reason = `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}`;
1197
+ const root = globalRoot(env);
1198
+ const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cli", target, reason, dirs: [root] })}` : "";
1199
+ return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `${reason}${pending}` };
1200
+ }
1201
+ const switchedDist = globalCliDist(env);
1202
+ const switched = probeCliVersion(switchedDist ?? "");
1203
+ if (switched !== target) {
1204
+ return { surface: "cli", from: installed, to: switched ?? installed ?? "unknown", verdict: "fail", detail: `post-switch probe returned ${switched ?? "dead"} at ${switchedDist ?? "an unresolvable path"}, expected ${target}; the global install already ran \u2014 nothing was rolled back` };
1205
+ }
1206
+ return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)` };
1207
+ }
1208
+
1209
+ // src/arm-self.ts
1210
+ function selfArm(options) {
1211
+ return npmGlobalArm({
1212
+ surface: "updater",
1213
+ packageName: "@mutmutco/hub",
1214
+ distRel: "dist/index.cjs",
1215
+ ...options
1216
+ });
1217
+ }
1218
+
1017
1219
  // src/arm-kimi.ts
1018
1220
  var import_node_child_process7 = require("node:child_process");
1019
1221
  var import_node_fs12 = require("node:fs");
1020
1222
  var import_node_path9 = require("node:path");
1021
1223
 
1022
1224
  // src/compat.ts
1023
- var import_node_fs9 = require("node:fs");
1225
+ var import_node_fs10 = require("node:fs");
1024
1226
  function readManifestCompat(manifestPath) {
1025
1227
  try {
1026
- const parsed = JSON.parse((0, import_node_fs9.readFileSync)(manifestPath, "utf8"));
1228
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(manifestPath, "utf8"));
1027
1229
  return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
1028
1230
  } catch {
1029
1231
  return void 0;
1030
1232
  }
1031
1233
  }
1032
1234
 
1033
- // src/launch-staging.ts
1034
- var import_node_fs10 = require("node:fs");
1035
- var import_node_path8 = require("node:path");
1036
- function readLaunchTarget(path) {
1037
- try {
1038
- const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
1039
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1040
- const row = parsed;
1041
- if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
1042
- return row;
1043
- } catch {
1044
- return null;
1045
- }
1046
- }
1047
- function writeLaunchTarget(path, record) {
1048
- (0, import_node_fs10.mkdirSync)((0, import_node_path8.join)(path, ".."), { recursive: true });
1049
- const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
1050
- (0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
1051
- `, "utf8");
1052
- (0, import_node_fs10.renameSync)(tmp, path);
1053
- }
1054
- function clearLaunchTarget(path) {
1055
- try {
1056
- (0, import_node_fs10.rmSync)(path, { force: true });
1057
- } catch {
1058
- }
1059
- }
1060
- function noteLaunchStaged(paths, env, options, seam = {}) {
1061
- writeLaunchTarget(options.launchTargetPath, {
1062
- surface: options.surface,
1063
- target: options.target,
1064
- since: (/* @__PURE__ */ new Date()).toISOString(),
1065
- incoming: options.incoming
1066
- });
1067
- return noteBusyDefer(paths, env, {
1068
- surface: options.surface,
1069
- target: options.target,
1070
- reason: options.reason,
1071
- images: options.images,
1072
- dirs: options.dirs
1073
- }, seam);
1074
- }
1075
- function probeLaunchTargetVersion(launchTargetPath, versionAt) {
1076
- const record = readLaunchTarget(launchTargetPath);
1077
- if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
1078
- const version = versionAt(record.incoming);
1079
- return version === record.target ? record.target : null;
1080
- }
1081
-
1082
1235
  // src/kimi-registration.ts
1083
1236
  function validateKimiInstalledPlugin(document, pluginRoot, id) {
1084
1237
  if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
@@ -1863,10 +2016,48 @@ function installedVersion(env) {
1863
2016
  return null;
1864
2017
  }
1865
2018
  }
1866
- function probeJervcodeInstallation(env = process.env) {
1867
- const location = (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
1868
- const version = installedVersion(env);
1869
- return { version, location, detail: version ? "materialized pi package manifest" : "materialized pi package manifest was unreadable" };
2019
+ function materializedManifestLocation(env) {
2020
+ return (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
2021
+ }
2022
+ function parseHostJson(result) {
2023
+ if (result.status !== 0) return null;
2024
+ try {
2025
+ return JSON.parse(result.stdout);
2026
+ } catch {
2027
+ return null;
2028
+ }
2029
+ }
2030
+ function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
2031
+ const manifestLocation = materializedManifestLocation(env);
2032
+ const manifestVersion = installedVersion(env);
2033
+ const manifestEvidence = () => ({
2034
+ version: manifestVersion,
2035
+ location: manifestLocation,
2036
+ detail: manifestVersion ? "materialized pi package manifest" : "materialized pi package manifest was unreadable"
2037
+ });
2038
+ const hosted = hostEnv(env);
2039
+ const command = hostCommands(env).find((bin) => runner(bin, ["--version"], hosted).status === 0);
2040
+ if (!command) return manifestEvidence();
2041
+ const hostLocation = `${command} plugin list --json`;
2042
+ const result = runner(command, ["plugin", "list", "--json"], hosted);
2043
+ const parsed = parseHostJson(result);
2044
+ const row = Array.isArray(parsed?.npm) ? parsed.npm.find((entry) => entry.name === PI_PACKAGE) ?? null : null;
2045
+ if (row?.version) {
2046
+ return {
2047
+ version: row.version,
2048
+ location: hostLocation,
2049
+ detail: `native host reports enabled=${String(row.enabled ?? true)}`
2050
+ };
2051
+ }
2052
+ const hostError = !parsed || !Array.isArray(parsed.npm) ? `${command} plugin list --json failed: ${failure2(result)}` : `${command} plugin list --json did not list ${PI_PACKAGE}`;
2053
+ if (manifestVersion) {
2054
+ return {
2055
+ version: manifestVersion,
2056
+ location: manifestLocation,
2057
+ detail: `${hostError}; using materialized pi package manifest`
2058
+ };
2059
+ }
2060
+ return { version: null, location: hostLocation, detail: hostError };
1870
2061
  }
1871
2062
  function isLegacyMmiPiPath(spec) {
1872
2063
  return /[/\\]mutmutco[/\\]mmi[/\\]\d+\.\d+\.\d+[/\\]\.pi-plugin[/\\]?$/i.test(spec);
@@ -1945,7 +2136,7 @@ var import_node_child_process9 = require("node:child_process");
1945
2136
  var import_node_path12 = require("node:path");
1946
2137
  var PACKAGE2 = "@mutmutco/hermes-plugin";
1947
2138
  var MANIFEST2 = "plugin.yaml";
1948
- var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
2139
+ var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
1949
2140
  function yamlVersion(root) {
1950
2141
  try {
1951
2142
  const text = (0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, MANIFEST2), "utf8");
@@ -1960,14 +2151,36 @@ function yamlVersion(root) {
1960
2151
  return null;
1961
2152
  }
1962
2153
  }
1963
- function treeHealthy3(root) {
1964
- return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs15.existsSync)((0, import_node_path12.join)(root, file))) && (() => {
1965
- try {
1966
- return (0, import_node_fs15.statSync)((0, import_node_path12.join)(root, "skills")).isDirectory();
1967
- } catch {
1968
- return false;
2154
+ function packageTreeMarkers(root) {
2155
+ try {
2156
+ const files = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).files;
2157
+ if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
2158
+ return files.map((entry) => entry.trim());
1969
2159
  }
1970
- })();
2160
+ } catch {
2161
+ }
2162
+ return TREE_MARKERS_FALLBACK;
2163
+ }
2164
+ function markerPresent(root, rel) {
2165
+ const path = (0, import_node_path12.join)(root, ...rel.split("/"));
2166
+ if (!(0, import_node_fs15.existsSync)(path)) return false;
2167
+ try {
2168
+ const stat = (0, import_node_fs15.statSync)(path);
2169
+ return stat.isFile() || stat.isDirectory();
2170
+ } catch {
2171
+ return false;
2172
+ }
2173
+ }
2174
+ function treeHealth(root) {
2175
+ const missing = packageTreeMarkers(root).filter((rel) => !markerPresent(root, rel));
2176
+ return { healthy: missing.length === 0, missing };
2177
+ }
2178
+ function treeHealthy3(root) {
2179
+ return treeHealth(root).healthy;
2180
+ }
2181
+ function treeIncompleteDetail(root, label) {
2182
+ const { healthy, missing } = treeHealth(root);
2183
+ return healthy ? null : `${label} tree is missing ${missing.join(", ")}`;
1971
2184
  }
1972
2185
  function packageIdentity(root) {
1973
2186
  try {
@@ -1994,14 +2207,14 @@ function message2(error) {
1994
2207
  return String(error?.message ?? error).replace(/\s+/g, " ").slice(0, 200);
1995
2208
  }
1996
2209
  var HERMES_PROCESS_SEGMENT = /^hermes(-cli|-code)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
1997
- var HUB_UPDATE_COMMAND = /(?:\bmmi-hub(?:\.(?:cmd|exe|bat|ps1))?\b|@mutmutco[/\\]hub[/\\]dist[/\\]index\.(?:cjs|mjs|js))[\s'"]+update\b/i;
2210
+ var HUB_UPDATE_COMMAND2 = /(?:\bmmi-hub(?:\.(?:cmd|exe|bat|ps1))?\b|@mutmutco[/\\]hub[/\\]dist[/\\]index\.(?:cjs|mjs|js))[\s'"]+update\b/i;
1998
2211
  function hermesProcessToken(line, home) {
1999
2212
  const normalized = line.replace(/["']/g, " ");
2000
2213
  const tokens = normalized.split(/\s+/);
2001
2214
  const hermesToken = (candidates) => candidates.find((token) => token && token.split(/[/\\]/).some((segment) => HERMES_PROCESS_SEGMENT.test(segment))) ?? null;
2002
2215
  const direct = hermesToken(tokens.slice(0, 2));
2003
2216
  if (direct) return direct;
2004
- if (HUB_UPDATE_COMMAND.test(line)) return null;
2217
+ if (HUB_UPDATE_COMMAND2.test(line)) return null;
2005
2218
  return hermesToken(tokens) ?? (home && normalized.toLowerCase().includes(home.toLowerCase()) ? home : null);
2006
2219
  }
2007
2220
  function realProcessListing() {
@@ -2050,7 +2263,9 @@ function hermesArm(options) {
2050
2263
  detail,
2051
2264
  launchStaged: launchStaged || void 0
2052
2265
  });
2053
- if (!(0, import_node_fs15.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
2266
+ if (!hermesHostPresent(env, options.hostCommandRunner)) {
2267
+ return result(null, options.target, "skip", `host absent (${hermesHostCommand(env)} CLI not on PATH) \u2014 skipped without writes`);
2268
+ }
2054
2269
  if ((0, import_node_fs15.existsSync)(live) && !mmiOwned2(live)) {
2055
2270
  return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
2056
2271
  }
@@ -2065,9 +2280,12 @@ function hermesArm(options) {
2065
2280
  if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0, import_node_path12.join)(home, "skills", "mmi")}`);
2066
2281
  const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
2067
2282
  if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
2068
- if (yamlVersion(staged.packageRoot) !== options.target || !treeHealthy3(staged.packageRoot)) {
2069
- return result(installed, options.target, "defer", `staged Hermes manifest is ${yamlVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${options.target})`);
2283
+ const stagedVersion = yamlVersion(staged.packageRoot);
2284
+ if (stagedVersion !== options.target) {
2285
+ return result(installed, options.target, "defer", `staged Hermes manifest is ${stagedVersion ?? "unreadable"}, expected ${options.target}`);
2070
2286
  }
2287
+ const stagedTree = treeIncompleteDetail(staged.packageRoot, "staged Hermes");
2288
+ if (stagedTree) return result(installed, options.target, "defer", stagedTree);
2071
2289
  const scratch = (0, import_node_path12.join)(home, ".mmi-updater");
2072
2290
  const launchTargetPath = (0, import_node_path12.join)(scratch, "launch-target.json");
2073
2291
  const incomingRoot = (0, import_node_path12.join)(scratch, "incoming");
@@ -2084,9 +2302,15 @@ function hermesArm(options) {
2084
2302
  } catch (error) {
2085
2303
  return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
2086
2304
  }
2087
- if (yamlVersion(incoming) !== options.target || !treeHealthy3(incoming)) {
2305
+ const incomingVersion = yamlVersion(incoming);
2306
+ if (incomingVersion !== options.target) {
2307
+ discard2(incomingRoot);
2308
+ return result(installed, options.target, "defer", `incoming Hermes manifest is ${incomingVersion ?? "unreadable"}, expected ${options.target}`);
2309
+ }
2310
+ const incomingTree = treeIncompleteDetail(incoming, "incoming Hermes");
2311
+ if (incomingTree) {
2088
2312
  discard2(incomingRoot);
2089
- return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
2313
+ return result(installed, options.target, "defer", incomingTree);
2090
2314
  }
2091
2315
  }
2092
2316
  const busy = hermesHostEvidence(env, home, options.processListProvider);
@@ -2116,16 +2340,19 @@ function hermesArm(options) {
2116
2340
  const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason, images: ["hermes"], dirs: [home] })}` : "";
2117
2341
  return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${reason}${pending}`);
2118
2342
  }
2119
- if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
2343
+ const liveVersion = yamlVersion(live);
2344
+ const liveTree = treeIncompleteDetail(live, "live Hermes");
2345
+ if (liveVersion !== options.target || liveTree) {
2120
2346
  const broken = (0, import_node_path12.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
2347
+ const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
2121
2348
  try {
2122
2349
  (0, import_node_fs15.mkdirSync)((0, import_node_path12.join)(scratch, "rejected"), { recursive: true });
2123
2350
  (0, import_node_fs15.renameSync)(live, broken);
2124
2351
  if (displaced) (0, import_node_fs15.renameSync)(quarantine, live);
2125
2352
  } catch (error) {
2126
- return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
2353
+ return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
2127
2354
  }
2128
- return result(installed, displaced ? installed ?? "unknown" : "none", "fail", `post-switch Hermes verification failed; rejected generation parked at ${broken}${displaced ? "; previous generation restored" : ""}`);
2355
+ return result(installed, displaced ? installed ?? "unknown" : "none", "fail", `post-switch Hermes verification failed (${verifyDetail}); rejected generation parked at ${broken}${displaced ? "; previous generation restored" : ""}`);
2129
2356
  }
2130
2357
  discard2(incomingRoot);
2131
2358
  clearLaunchTarget(launchTargetPath);
@@ -2535,7 +2762,7 @@ function reconcile(options) {
2535
2762
  continue;
2536
2763
  }
2537
2764
  say(`${probe.id}: converging to v${summary.target}`);
2538
- const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "kilo" ? kiloArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kimi" ? kimiArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
2765
+ const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "kilo" ? kiloArm({ target: summary.target, dryRun: options.dryRun, env }) : probe.id === "kimi" ? kimiArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env }) : null;
2539
2766
  if (arm) {
2540
2767
  summary.plugins.push(arm);
2541
2768
  summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
@@ -3048,14 +3275,19 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3048
3275
  const expectedVersion = [...realEvents].reverse().find((event) => event.kind === "target" && event.verdict === "ok" && event.to)?.to ?? null;
3049
3276
  const latestEvent = realEvents.at(-1) ?? null;
3050
3277
  const latestRunEvents = latestEvent ? realEvents.filter((event) => event.run === latestEvent.run) : [];
3278
+ const probes = enumerateSurfaces(env);
3279
+ const presentSurfaces = new Set(probes.filter((probe) => probe.present).map((probe) => probe.id));
3051
3280
  const actual = /* @__PURE__ */ new Map();
3052
3281
  const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
3053
3282
  const globalHubVersion = hubDist ? probeVersion(hubDist) : null;
3054
3283
  actual.set("hub", globalHubVersion ? { version: globalHubVersion, staged: null, location: hubDist, detail: "absolute global Hub binary" } : { version: hubVersion, staged: null, location: invokedPath ?? null, detail: "currently executing Hub bundle (global package location unavailable)" });
3055
3284
  const cliProbe = probeCliInstallation(env);
3056
3285
  actual.set("cli", { version: cliProbe.version, staged: null, location: cliProbe.location, detail: cliProbe.detail });
3057
- for (const surface of enumerateSurfaces(env).filter((probe) => probe.present)) {
3058
- if (surface.id === "claude" || surface.id === "codex" || surface.id === "kilo") {
3286
+ for (const surface of probes.filter((probe) => probe.present)) {
3287
+ if (surface.id === "claude") {
3288
+ const probe = probeNativeHostInstallation("claude", env);
3289
+ actual.set(surface.id, { version: probe.version, staged: probeClaudeLaunchStaging(env), location: probe.location, detail: probe.detail });
3290
+ } else if (surface.id === "codex" || surface.id === "kilo") {
3059
3291
  const probe = probeNativeHostInstallation(surface.id, env);
3060
3292
  actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
3061
3293
  } else if (surface.id === "kimi") {
@@ -3095,6 +3327,9 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
3095
3327
  }
3096
3328
  for (const event of latestRunEvents) {
3097
3329
  if ((event.kind === "arm" || event.kind === "reconcile") && (event.verdict === "fail" || event.verdict === "defer")) {
3330
+ if (event.kind === "arm" && event.surface && event.surface !== "cli" && event.surface !== "updater" && !presentSurfaces.has(event.surface)) {
3331
+ continue;
3332
+ }
3098
3333
  const surface = event.surface === "updater" ? "hub" : event.surface ?? event.kind;
3099
3334
  const repeat = event.kind === "arm" && event.verdict === "fail" && event.surface && journalSurfaceRepeatedFail(paths.journalPath, event.surface, latestEvent.run);
3100
3335
  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\``);
@@ -3539,6 +3774,8 @@ if (typeof require !== "undefined" && require.main === module) {
3539
3774
  0 && (module.exports = {
3540
3775
  acquireSharedDocLock,
3541
3776
  buildWatcherCommand,
3777
+ claudeArm,
3778
+ claudeHostEvidence,
3542
3779
  clearLaunchTarget,
3543
3780
  clearPending,
3544
3781
  enumerateSurfaces,
@@ -3546,7 +3783,9 @@ if (typeof require !== "undefined" && require.main === module) {
3546
3783
  formatReconcileReport,
3547
3784
  hermesArm,
3548
3785
  hermesHome,
3786
+ hermesHostCommand,
3549
3787
  hermesHostEvidence,
3788
+ hermesHostPresent,
3550
3789
  hubStatus,
3551
3790
  launchdPlist,
3552
3791
  launcherSh,
@@ -3561,6 +3800,7 @@ if (typeof require !== "undefined" && require.main === module) {
3561
3800
  parseNpmDistIntegrity,
3562
3801
  pendingPath,
3563
3802
  pidAlive,
3803
+ probeClaudeLaunchStaging,
3564
3804
  probeCursorLaunchStaging,
3565
3805
  probeHermesInstallation,
3566
3806
  probeHermesLaunchStaging,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.1.9",
3
+ "version": "4.1.10",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",