@mutmutco/hub 4.1.8 → 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.
- package/dist/index.cjs +486 -246
- 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
|
|
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
|
|
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-
|
|
441
|
-
var
|
|
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
|
|
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
|
|
448
|
-
var
|
|
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,
|
|
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,
|
|
433
|
+
return (0, import_node_path3.join)(paths.root, "pending.json");
|
|
456
434
|
}
|
|
457
435
|
function watcherLockPath(paths) {
|
|
458
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
451
|
+
(0, import_node_fs4.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
474
452
|
`, "utf8");
|
|
475
|
-
(0,
|
|
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,
|
|
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,
|
|
551
|
-
logPath: (0,
|
|
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,
|
|
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,
|
|
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,
|
|
678
|
-
const packageRoot = (0,
|
|
679
|
-
if (!fileExists((0,
|
|
680
|
-
(0,
|
|
681
|
-
(0,
|
|
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,
|
|
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,
|
|
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/
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
847
|
-
if (observed.row?.version ===
|
|
848
|
-
|
|
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
|
|
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
|
|
858
|
-
if (observed.row && observed.row.version ===
|
|
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
|
|
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,
|
|
866
|
-
return
|
|
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 !==
|
|
869
|
-
return
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
955
|
-
(0,
|
|
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
|
|
1225
|
+
var import_node_fs10 = require("node:fs");
|
|
1024
1226
|
function readManifestCompat(manifestPath) {
|
|
1025
1227
|
try {
|
|
1026
|
-
const parsed = JSON.parse((0,
|
|
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
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
|
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
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
|
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 (
|
|
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 (!(
|
|
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
|
-
|
|
2069
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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,
|
|
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
|
|
3058
|
-
if (surface.id === "claude"
|
|
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\``);
|
|
@@ -3272,7 +3507,7 @@ function updateHeader(target, dryRun = false) {
|
|
|
3272
3507
|
function renderRowLines(row, ctx) {
|
|
3273
3508
|
const paint = (kind, text) => ctx.color ? colorFor(kind) + text + RESET : text;
|
|
3274
3509
|
const versions = row.from !== null && row.from !== row.to ? `${row.from} \u2192 ${row.to}` : `${row.to}`;
|
|
3275
|
-
const glyph = ctx.tty ? glyphFor(row.kind) +
|
|
3510
|
+
const glyph = ctx.tty ? glyphFor(row.kind) + " " : "";
|
|
3276
3511
|
const status = statusText(row.kind, ctx.dryRun).padEnd(ctx.statusWidth);
|
|
3277
3512
|
const prefix = " " + row.name.padEnd(NAME_WIDTH) + " " + versions.padEnd(VERSION_WIDTH) + " " + paint(row.kind, glyph + status);
|
|
3278
3513
|
return row.note ? noteLines(prefix, row.note, ctx.width) : [prefix.replace(/\s+$/, "")];
|
|
@@ -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