@mutmutco/hub 4.1.9 → 4.1.11
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 +495 -245
- 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,9 +49,11 @@ __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,
|
|
56
|
+
probeJervcodeInstallation: () => probeJervcodeInstallation,
|
|
51
57
|
probeKimiLaunchStaging: () => probeKimiLaunchStaging,
|
|
52
58
|
probeLaunchTargetVersion: () => probeLaunchTargetVersion,
|
|
53
59
|
progressiveRow: () => progressiveRow,
|
|
@@ -400,66 +406,39 @@ function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
400
406
|
}
|
|
401
407
|
|
|
402
408
|
// src/surfaces.ts
|
|
403
|
-
var
|
|
409
|
+
var import_node_fs8 = require("node:fs");
|
|
410
|
+
var import_node_os3 = require("node:os");
|
|
411
|
+
var import_node_path7 = require("node:path");
|
|
412
|
+
|
|
413
|
+
// src/arm-host-plugins.ts
|
|
414
|
+
var import_node_child_process5 = require("node:child_process");
|
|
415
|
+
var import_node_fs7 = require("node:fs");
|
|
404
416
|
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
|
-
}
|
|
417
|
+
var import_node_path6 = require("node:path");
|
|
439
418
|
|
|
440
|
-
// src/arm-
|
|
441
|
-
var
|
|
419
|
+
// src/arm-npm-global.ts
|
|
420
|
+
var import_node_fs5 = require("node:fs");
|
|
442
421
|
var import_node_child_process4 = require("node:child_process");
|
|
443
|
-
var
|
|
422
|
+
var import_node_path4 = require("node:path");
|
|
444
423
|
|
|
445
424
|
// src/pending.ts
|
|
446
425
|
var import_node_child_process3 = require("node:child_process");
|
|
447
|
-
var
|
|
448
|
-
var
|
|
426
|
+
var import_node_fs4 = require("node:fs");
|
|
427
|
+
var import_node_path3 = require("node:path");
|
|
449
428
|
function hubDistPath(env) {
|
|
450
429
|
const root = globalRoot(env);
|
|
451
|
-
return root ? (0,
|
|
430
|
+
return root ? (0, import_node_path3.join)(root, "@mutmutco", "hub", "dist", "index.cjs") : null;
|
|
452
431
|
}
|
|
453
432
|
var WATCHER_MAX_WAIT_MS = 4 * 60 * 60 * 1e3;
|
|
454
433
|
function pendingPath(paths) {
|
|
455
|
-
return (0,
|
|
434
|
+
return (0, import_node_path3.join)(paths.root, "pending.json");
|
|
456
435
|
}
|
|
457
436
|
function watcherLockPath(paths) {
|
|
458
|
-
return (0,
|
|
437
|
+
return (0, import_node_path3.join)(paths.root, "pending-watcher.lock.d");
|
|
459
438
|
}
|
|
460
439
|
function readPending(paths) {
|
|
461
440
|
try {
|
|
462
|
-
const parsed = JSON.parse((0,
|
|
441
|
+
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(pendingPath(paths), "utf8"));
|
|
463
442
|
if (!Array.isArray(parsed)) return [];
|
|
464
443
|
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
444
|
} catch {
|
|
@@ -467,12 +446,12 @@ function readPending(paths) {
|
|
|
467
446
|
}
|
|
468
447
|
}
|
|
469
448
|
function writePending(paths, records) {
|
|
470
|
-
(0,
|
|
449
|
+
(0, import_node_fs4.mkdirSync)(paths.root, { recursive: true });
|
|
471
450
|
const path = pendingPath(paths);
|
|
472
451
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
473
|
-
(0,
|
|
452
|
+
(0, import_node_fs4.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
474
453
|
`, "utf8");
|
|
475
|
-
(0,
|
|
454
|
+
(0, import_node_fs4.renameSync)(tmp, path);
|
|
476
455
|
}
|
|
477
456
|
function clearPending(paths, surface) {
|
|
478
457
|
const records = readPending(paths);
|
|
@@ -541,14 +520,14 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
|
|
|
541
520
|
const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
|
|
542
521
|
if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
|
|
543
522
|
const hubDist = seam.hubDist ?? hubDistPath(env);
|
|
544
|
-
if (!hubDist || !(0,
|
|
523
|
+
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
524
|
const command = buildWatcherCommand({
|
|
546
525
|
images: [...new Set(records.flatMap((row) => row.images))],
|
|
547
526
|
dirs: [...new Set(records.flatMap((row) => row.dirs))],
|
|
548
527
|
node: process.execPath,
|
|
549
528
|
hubDist,
|
|
550
|
-
codePath: (0,
|
|
551
|
-
logPath: (0,
|
|
529
|
+
codePath: (0, import_node_path3.join)(paths.root, "pending-result.code"),
|
|
530
|
+
logPath: (0, import_node_path3.join)(paths.root, "pending-result.log"),
|
|
552
531
|
lockPath: watcherLockPath(paths)
|
|
553
532
|
});
|
|
554
533
|
try {
|
|
@@ -584,88 +563,14 @@ function resumePendingWatcher(paths, env = process.env) {
|
|
|
584
563
|
return schedulePendingWatcher(paths, env);
|
|
585
564
|
}
|
|
586
565
|
|
|
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
566
|
// 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
567
|
function globalDist(env, packageName, distRel) {
|
|
663
568
|
const root = globalRoot(env);
|
|
664
|
-
return root ? (0,
|
|
569
|
+
return root ? (0, import_node_path4.join)(root, ...packageName.split("/"), ...distRel.split("/")) : null;
|
|
665
570
|
}
|
|
666
571
|
function probeVersion(distPath) {
|
|
667
572
|
if (!fileExists(distPath)) return null;
|
|
668
|
-
const probe = (0,
|
|
573
|
+
const probe = (0, import_node_child_process4.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
669
574
|
const version = (probe.stdout || "").trim();
|
|
670
575
|
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
671
576
|
}
|
|
@@ -674,11 +579,11 @@ function tail(output) {
|
|
|
674
579
|
}
|
|
675
580
|
function stagePackage(options) {
|
|
676
581
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
677
|
-
const stageRoot = (0,
|
|
678
|
-
const packageRoot = (0,
|
|
679
|
-
if (!fileExists((0,
|
|
680
|
-
(0,
|
|
681
|
-
(0,
|
|
582
|
+
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
583
|
+
const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
|
|
584
|
+
if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
|
|
585
|
+
(0, import_node_fs5.mkdirSync)(stageRoot, { recursive: true });
|
|
586
|
+
(0, import_node_fs5.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
682
587
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
683
588
|
if (install.status !== 0) {
|
|
684
589
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -686,7 +591,7 @@ function stagePackage(options) {
|
|
|
686
591
|
}
|
|
687
592
|
let stagedManifestVersion;
|
|
688
593
|
try {
|
|
689
|
-
stagedManifestVersion = JSON.parse((0,
|
|
594
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
690
595
|
} catch {
|
|
691
596
|
}
|
|
692
597
|
if (stagedManifestVersion !== target) {
|
|
@@ -718,7 +623,7 @@ function npmGlobalArm(options) {
|
|
|
718
623
|
if ("defer" in staged) {
|
|
719
624
|
return result(installed, target, "defer", staged.defer);
|
|
720
625
|
}
|
|
721
|
-
const stageDist = (0,
|
|
626
|
+
const stageDist = (0, import_node_path4.join)(staged.packageRoot, ...distRel.split("/"));
|
|
722
627
|
const stagedVersion = probeVersion(stageDist);
|
|
723
628
|
if (stagedVersion !== target) {
|
|
724
629
|
return result(installed, target, "defer", `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}`);
|
|
@@ -738,21 +643,56 @@ function npmGlobalArm(options) {
|
|
|
738
643
|
return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)`);
|
|
739
644
|
}
|
|
740
645
|
|
|
741
|
-
// src/
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
646
|
+
// src/launch-staging.ts
|
|
647
|
+
var import_node_fs6 = require("node:fs");
|
|
648
|
+
var import_node_path5 = require("node:path");
|
|
649
|
+
function readLaunchTarget(path) {
|
|
650
|
+
try {
|
|
651
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
652
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
653
|
+
const row = parsed;
|
|
654
|
+
if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
|
|
655
|
+
return row;
|
|
656
|
+
} catch {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function writeLaunchTarget(path, record) {
|
|
661
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
|
|
662
|
+
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
663
|
+
(0, import_node_fs6.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
|
|
664
|
+
`, "utf8");
|
|
665
|
+
(0, import_node_fs6.renameSync)(tmp, path);
|
|
666
|
+
}
|
|
667
|
+
function clearLaunchTarget(path) {
|
|
668
|
+
try {
|
|
669
|
+
(0, import_node_fs6.rmSync)(path, { force: true });
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function noteLaunchStaged(paths, env, options, seam = {}) {
|
|
674
|
+
writeLaunchTarget(options.launchTargetPath, {
|
|
675
|
+
surface: options.surface,
|
|
676
|
+
target: options.target,
|
|
677
|
+
since: (/* @__PURE__ */ new Date()).toISOString(),
|
|
678
|
+
incoming: options.incoming
|
|
748
679
|
});
|
|
680
|
+
return noteBusyDefer(paths, env, {
|
|
681
|
+
surface: options.surface,
|
|
682
|
+
target: options.target,
|
|
683
|
+
reason: options.reason,
|
|
684
|
+
images: options.images,
|
|
685
|
+
dirs: options.dirs
|
|
686
|
+
}, seam);
|
|
687
|
+
}
|
|
688
|
+
function probeLaunchTargetVersion(launchTargetPath, versionAt) {
|
|
689
|
+
const record = readLaunchTarget(launchTargetPath);
|
|
690
|
+
if (!record || !(0, import_node_fs6.existsSync)(record.incoming)) return null;
|
|
691
|
+
const version = versionAt(record.incoming);
|
|
692
|
+
return version === record.target ? record.target : null;
|
|
749
693
|
}
|
|
750
694
|
|
|
751
695
|
// 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
696
|
var REPO_URL_DEFAULT = "https://github.com/mutmutco/MMI-Hub.git";
|
|
757
697
|
var MMI_MARKETPLACE_REPO = "mutmutco/MMI-Hub";
|
|
758
698
|
function convergedAtOrAbove(installed, target, healthy) {
|
|
@@ -762,13 +702,13 @@ function cmdArg(value) {
|
|
|
762
702
|
return /^[A-Za-z0-9_@./:\\=+-]+$/.test(value) ? value : `"${value.replaceAll('"', '""')}"`;
|
|
763
703
|
}
|
|
764
704
|
var runHostCommand = (command, args, env) => {
|
|
765
|
-
const result = process.platform === "win32" ? (0,
|
|
705
|
+
const result = process.platform === "win32" ? (0, import_node_child_process5.spawnSync)("cmd.exe", ["/d", "/s", "/c", [command, ...args].map(cmdArg).join(" ")], {
|
|
766
706
|
encoding: "utf8",
|
|
767
707
|
env,
|
|
768
708
|
input: "",
|
|
769
709
|
timeout: 12e4,
|
|
770
710
|
windowsHide: true
|
|
771
|
-
}) : (0,
|
|
711
|
+
}) : (0, import_node_child_process5.spawnSync)(command, args, {
|
|
772
712
|
encoding: "utf8",
|
|
773
713
|
env,
|
|
774
714
|
input: "",
|
|
@@ -794,14 +734,18 @@ function claudeMmi(runner, env) {
|
|
|
794
734
|
if (!Array.isArray(parsed)) return { row: null, error: `claude plugin list --json failed: ${failure(result)}` };
|
|
795
735
|
return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
|
|
796
736
|
}
|
|
737
|
+
function claudeHomeDir(env) {
|
|
738
|
+
const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os2.homedir)();
|
|
739
|
+
return (0, import_node_path6.join)(home, ".claude");
|
|
740
|
+
}
|
|
797
741
|
function claudeMarketplacesPath(env) {
|
|
798
|
-
return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0,
|
|
742
|
+
return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path6.join)(claudeHomeDir(env), "plugins", "known_marketplaces.json");
|
|
799
743
|
}
|
|
800
744
|
function ensureClaudeMarketplace(env) {
|
|
801
745
|
const path = claudeMarketplacesPath(env);
|
|
802
746
|
let body;
|
|
803
747
|
try {
|
|
804
|
-
body = JSON.parse((0,
|
|
748
|
+
body = JSON.parse((0, import_node_fs7.readFileSync)(path, "utf8"));
|
|
805
749
|
} catch (error) {
|
|
806
750
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
807
751
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -821,54 +765,185 @@ function ensureClaudeMarketplace(env) {
|
|
|
821
765
|
}
|
|
822
766
|
return { ok: true };
|
|
823
767
|
}
|
|
768
|
+
var CLAUDE_PACKAGE = "@mutmutco/claude-plugin";
|
|
769
|
+
var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
|
|
770
|
+
var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json", "hooks/hooks.json", "bin/mmi-hook"];
|
|
771
|
+
function claudePluginsRoot(env) {
|
|
772
|
+
return (0, import_node_path6.join)(claudeHomeDir(env), "plugins");
|
|
773
|
+
}
|
|
774
|
+
function claudeMarkerVersion(root) {
|
|
775
|
+
try {
|
|
776
|
+
const version = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path6.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
|
|
777
|
+
return typeof version === "string" ? version : null;
|
|
778
|
+
} catch {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function claudeTreeHealthy(root) {
|
|
783
|
+
return CLAUDE_TREE_MARKERS.every((rel) => fileExists((0, import_node_path6.join)(root, ...rel.split("/"))));
|
|
784
|
+
}
|
|
785
|
+
var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
|
|
786
|
+
var HUB_UPDATE_COMMAND = /(?:\bmmi-hub(?:\.(?:cmd|exe|bat|ps1))?\b|@mutmutco[/\\]hub[/\\]dist[/\\]index\.(?:cjs|mjs|js))[\s'"]+update\b/i;
|
|
787
|
+
function claudeProcessToken(line) {
|
|
788
|
+
if (HUB_UPDATE_COMMAND.test(line)) return null;
|
|
789
|
+
for (const token of line.replace(/["']/g, " ").split(/\s+/)) {
|
|
790
|
+
for (const segment of token.split(/[/\\]/)) {
|
|
791
|
+
if (CLAUDE_PROCESS_SEGMENT.test(segment)) return segment;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
function realClaudeProcessListing() {
|
|
797
|
+
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 });
|
|
798
|
+
return result.status === 0 ? result.stdout ?? "" : null;
|
|
799
|
+
}
|
|
800
|
+
function claudeHostEvidence(env, processListProvider) {
|
|
801
|
+
for (const marker of ["CLAUDE_CODE_SESSION_ID", "CLAUDECODE", "CLAUDE_PLUGIN_ROOT"]) {
|
|
802
|
+
if (env[marker]?.trim()) return `env marker ${marker}`;
|
|
803
|
+
}
|
|
804
|
+
const listing = processListProvider ? processListProvider() : realClaudeProcessListing();
|
|
805
|
+
if (listing === null || !listing.trim()) return "the process list is unreadable (unsafe to write)";
|
|
806
|
+
for (const line of listing.split(/\r?\n/)) {
|
|
807
|
+
const token = claudeProcessToken(line);
|
|
808
|
+
if (token) return `a live process names claude (${token})`;
|
|
809
|
+
}
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
function probeClaudeLaunchStaging(env = process.env) {
|
|
813
|
+
return probeLaunchTargetVersion((0, import_node_path6.join)(claudePluginsRoot(env), "staging", "launch-target.json"), claudeMarkerVersion);
|
|
814
|
+
}
|
|
824
815
|
function claudeArm(options) {
|
|
825
816
|
const env = options.env ?? process.env;
|
|
826
817
|
const runner = options.runner ?? runHostCommand;
|
|
818
|
+
const paths = options.paths ?? ensureState(env);
|
|
819
|
+
const target = options.target;
|
|
827
820
|
const repoUrl = options.repoUrl ?? (env.MMI_UPDATER_REPO || REPO_URL_DEFAULT);
|
|
821
|
+
const home = claudeHomeDir(env);
|
|
822
|
+
const result = (from2, to, verdict, detail, launchStaged = false) => ({
|
|
823
|
+
surface: "claude",
|
|
824
|
+
from: from2,
|
|
825
|
+
to,
|
|
826
|
+
verdict,
|
|
827
|
+
detail,
|
|
828
|
+
launchStaged: launchStaged || void 0
|
|
829
|
+
});
|
|
828
830
|
const before = claudeMmi(runner, env);
|
|
829
|
-
if (before.error) return
|
|
831
|
+
if (before.error) return result(null, target, "defer", before.error);
|
|
830
832
|
const from = before.row?.version ?? null;
|
|
833
|
+
if (from && compareSemver(from, target) > 0) {
|
|
834
|
+
return result(from, from, "skip", `installed ${from} is above candidate ${target} (monotonic)`);
|
|
835
|
+
}
|
|
836
|
+
const highWater = journalHighWater(paths.journalPath, "claude");
|
|
837
|
+
if (highWater && compareSemver(highWater, target) > 0) {
|
|
838
|
+
return result(from, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
839
|
+
}
|
|
831
840
|
let marketplaceTrust = ensureClaudeMarketplace(env);
|
|
832
841
|
if (!marketplaceTrust.ok && marketplaceTrust.unregistered) {
|
|
833
842
|
const add = runner("claude", ["plugin", "marketplace", "add", repoUrl], env);
|
|
834
|
-
if (add.status !== 0) return
|
|
843
|
+
if (add.status !== 0) return result(from, target, "defer", `mutmutco marketplace was not registered and marketplace add failed: ${failure(add)}`);
|
|
835
844
|
marketplaceTrust = ensureClaudeMarketplace(env);
|
|
836
845
|
}
|
|
837
|
-
if (!marketplaceTrust.ok) return
|
|
846
|
+
if (!marketplaceTrust.ok) return result(from, target, "defer", marketplaceTrust.detail ?? "cannot trust the mutmutco marketplace registration");
|
|
847
|
+
const pluginsRoot = claudePluginsRoot(env);
|
|
848
|
+
const launchTargetPath = (0, import_node_path6.join)(pluginsRoot, "staging", "launch-target.json");
|
|
838
849
|
if (options.dryRun) {
|
|
839
|
-
return
|
|
850
|
+
return result(from, target, "ok", `dry-run: would refresh mutmutco, stage ${target} on the Claude volume, and converge mmi@mutmutco ${from ?? "absent"} -> ${target}`);
|
|
840
851
|
}
|
|
841
852
|
const marketplace = runner("claude", ["plugin", "marketplace", "update", "mutmutco"], env);
|
|
842
853
|
if (marketplace.status !== 0) {
|
|
843
|
-
return
|
|
854
|
+
return result(from, target, "defer", `marketplace update failed: ${failure(marketplace)}`);
|
|
844
855
|
}
|
|
845
856
|
let observed = claudeMmi(runner, env);
|
|
846
|
-
if (observed.error) return
|
|
847
|
-
if (observed.row?.version ===
|
|
848
|
-
|
|
857
|
+
if (observed.error) return result(from, target, "defer", observed.error);
|
|
858
|
+
if (observed.row?.version === target && observed.row.enabled === true) {
|
|
859
|
+
clearLaunchTarget(launchTargetPath);
|
|
860
|
+
return result(from, target, "ok", `catalog refreshed; installed+enabled at target ${target}`);
|
|
861
|
+
}
|
|
862
|
+
const incomingRoot = (0, import_node_path6.join)(pluginsRoot, "staging", "incoming");
|
|
863
|
+
const incoming = (0, import_node_path6.join)(incomingRoot, target);
|
|
864
|
+
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
865
|
+
const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && claudeMarkerVersion(incoming) === target && claudeTreeHealthy(incoming);
|
|
866
|
+
if (!reuseIncoming) {
|
|
867
|
+
const staged = stagePackage({ surface: "claude", packageName: CLAUDE_PACKAGE, target, witness: CLAUDE_MANIFEST, paths, env });
|
|
868
|
+
if ("defer" in staged) return result(from, target, "defer", staged.defer);
|
|
869
|
+
if (claudeMarkerVersion(staged.packageRoot) !== target || !claudeTreeHealthy(staged.packageRoot)) {
|
|
870
|
+
return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.join)(pluginsRoot, "staging"), { recursive: true });
|
|
874
|
+
(0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
875
|
+
(0, import_node_fs7.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
876
|
+
} catch (error) {
|
|
877
|
+
return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
|
|
878
|
+
}
|
|
879
|
+
if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
|
|
880
|
+
(0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
881
|
+
return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const busy = claudeHostEvidence(env, options.processListProvider);
|
|
885
|
+
if (busy) {
|
|
886
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
887
|
+
surface: "claude",
|
|
888
|
+
target,
|
|
889
|
+
reason: busy,
|
|
890
|
+
launchTargetPath,
|
|
891
|
+
incoming,
|
|
892
|
+
images: ["claude"],
|
|
893
|
+
dirs: [home]
|
|
894
|
+
});
|
|
895
|
+
return result(from, target, "ok", `staged ${target} for next launch while Claude Code is running (${busy}) \u2014 ${pending}`, true);
|
|
896
|
+
}
|
|
897
|
+
const started = claudeHostEvidence(env, options.processListProvider);
|
|
898
|
+
if (started) {
|
|
899
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
900
|
+
surface: "claude",
|
|
901
|
+
target,
|
|
902
|
+
reason: started,
|
|
903
|
+
launchTargetPath,
|
|
904
|
+
incoming,
|
|
905
|
+
images: ["claude"],
|
|
906
|
+
dirs: [home]
|
|
907
|
+
});
|
|
908
|
+
return result(from, target, "ok", `staged ${target} for next launch \u2014 Claude Code started during staging (${started}) \u2014 ${pending}`, true);
|
|
909
|
+
}
|
|
910
|
+
const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
|
|
911
|
+
try {
|
|
912
|
+
if (!(0, import_node_fs7.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
|
|
913
|
+
(0, import_node_fs7.rmSync)(cacheTarget, { recursive: true, force: true });
|
|
914
|
+
(0, import_node_fs7.mkdirSync)((0, import_node_path6.join)(cacheTarget, ".."), { recursive: true });
|
|
915
|
+
(0, import_node_fs7.cpSync)(incoming, cacheTarget, { recursive: true });
|
|
916
|
+
}
|
|
917
|
+
} catch (error) {
|
|
918
|
+
return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
|
|
849
919
|
}
|
|
850
920
|
const scope = observed.row?.scope || before.row?.scope || "user";
|
|
851
921
|
const command = observed.row ? ["plugin", "update", "mmi@mutmutco", "--scope", scope] : ["plugin", "install", "mmi@mutmutco", "--scope", scope];
|
|
852
922
|
const converge = runner("claude", command, env);
|
|
853
923
|
if (converge.status !== 0) {
|
|
854
|
-
return
|
|
924
|
+
return result(from, target, "defer", `${observed.row ? "plugin update" : "plugin install"} failed: ${failure(converge)}`);
|
|
855
925
|
}
|
|
856
926
|
observed = claudeMmi(runner, env);
|
|
857
|
-
if (observed.error) return
|
|
858
|
-
if (observed.row && observed.row.version ===
|
|
927
|
+
if (observed.error) return result(from, target, "defer", observed.error);
|
|
928
|
+
if (observed.row && observed.row.version === target && observed.row.enabled !== true) {
|
|
859
929
|
const enable = runner("claude", ["plugin", "enable", "mmi@mutmutco", "--scope", observed.row.scope || scope], env);
|
|
860
930
|
if (enable.status !== 0) {
|
|
861
|
-
return
|
|
931
|
+
return result(from, target, "defer", `plugin enable failed: ${failure(enable)}`);
|
|
862
932
|
}
|
|
863
933
|
observed = claudeMmi(runner, env);
|
|
864
934
|
}
|
|
865
|
-
if (!convergedAtOrAbove(observed.row?.version,
|
|
866
|
-
return
|
|
935
|
+
if (!convergedAtOrAbove(observed.row?.version, target, observed.row?.enabled === true)) {
|
|
936
|
+
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
937
|
}
|
|
868
|
-
if (observed.row.version !==
|
|
869
|
-
return
|
|
938
|
+
if (observed.row.version !== target) {
|
|
939
|
+
return result(from, observed.row.version, "skip", `installed ${observed.row.version} is above candidate ${target} (monotonic)`);
|
|
870
940
|
}
|
|
871
|
-
|
|
941
|
+
clearLaunchTarget(launchTargetPath);
|
|
942
|
+
try {
|
|
943
|
+
(0, import_node_fs7.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
|
|
872
947
|
}
|
|
873
948
|
var SNAPSHOT_MISSING = /marketplace snapshot|marketplace root does not contain/i;
|
|
874
949
|
function codexMmi(runner, env) {
|
|
@@ -927,11 +1002,11 @@ function codexArm(options) {
|
|
|
927
1002
|
}
|
|
928
1003
|
var KILO_PACKAGE = "@mutmutco/kilo-plugin";
|
|
929
1004
|
function kiloConfigPath(env) {
|
|
930
|
-
return env.MMI_UPDATER_KILO_CONFIG || (0,
|
|
1005
|
+
return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".config", "kilo", "opencode.json");
|
|
931
1006
|
}
|
|
932
1007
|
function readKiloConfig(env) {
|
|
933
1008
|
try {
|
|
934
|
-
const config = JSON.parse((0,
|
|
1009
|
+
const config = JSON.parse((0, import_node_fs7.readFileSync)(kiloConfigPath(env), "utf8"));
|
|
935
1010
|
const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
|
|
936
1011
|
return { config, specs };
|
|
937
1012
|
} catch {
|
|
@@ -951,8 +1026,8 @@ function normalizeKiloConfig(env, exact) {
|
|
|
951
1026
|
const path = kiloConfigPath(env);
|
|
952
1027
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
953
1028
|
try {
|
|
954
|
-
(0,
|
|
955
|
-
(0,
|
|
1029
|
+
(0, import_node_fs7.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
|
|
1030
|
+
(0, import_node_fs7.renameSync)(tmp, path);
|
|
956
1031
|
return null;
|
|
957
1032
|
} catch (error) {
|
|
958
1033
|
return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
|
|
@@ -1014,71 +1089,150 @@ function kiloArm(options) {
|
|
|
1014
1089
|
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
1090
|
}
|
|
1016
1091
|
|
|
1092
|
+
// src/surfaces.ts
|
|
1093
|
+
function kimiHome(env = process.env) {
|
|
1094
|
+
return env.KIMI_CODE_HOME?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".kimi-code");
|
|
1095
|
+
}
|
|
1096
|
+
function jervcodeAgentDir(env = process.env) {
|
|
1097
|
+
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");
|
|
1098
|
+
}
|
|
1099
|
+
function hermesHome(env = process.env, platform = process.platform) {
|
|
1100
|
+
if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
|
|
1101
|
+
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");
|
|
1102
|
+
return (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".hermes");
|
|
1103
|
+
}
|
|
1104
|
+
function hermesHostCommand(env = process.env) {
|
|
1105
|
+
return env.MMI_UPDATER_HERMES?.trim() || "hermes";
|
|
1106
|
+
}
|
|
1107
|
+
function hermesHostPresent(env = process.env, runner = runHostCommand) {
|
|
1108
|
+
return runner(hermesHostCommand(env), ["--version"], env).status === 0;
|
|
1109
|
+
}
|
|
1110
|
+
function enumerateSurfaces(env = process.env) {
|
|
1111
|
+
const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os3.homedir)();
|
|
1112
|
+
const codexHome = env.CODEX_HOME || (0, import_node_path7.join)(home, ".codex");
|
|
1113
|
+
return [
|
|
1114
|
+
{ id: "claude", marker: (0, import_node_path7.join)(home, ".claude", "plugins", "installed_plugins.json") },
|
|
1115
|
+
{ id: "codex", marker: (0, import_node_path7.join)(codexHome, "plugins", "installed_plugins.json") },
|
|
1116
|
+
// The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
|
|
1117
|
+
// plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
|
|
1118
|
+
// and the arm could never make the first install. The plugin's absence is a converge action, not
|
|
1119
|
+
// a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
|
|
1120
|
+
{ id: "cursor", marker: (0, import_node_path7.join)(home, ".cursor") },
|
|
1121
|
+
{ id: "kilo", marker: (0, import_node_path7.join)(home, ".config", "kilo") },
|
|
1122
|
+
// The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
|
|
1123
|
+
// itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
|
|
1124
|
+
// could never make the first install. The plugin's absence is a converge action, not a skip.
|
|
1125
|
+
{ id: "kimi", marker: kimiHome(env) },
|
|
1126
|
+
{ id: "jervcode", marker: (0, import_node_path7.join)(jervcodeAgentDir(env), "settings.json") },
|
|
1127
|
+
// Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
|
|
1128
|
+
// leftover plugin manifests — that residue must not enumerate as an installed host.
|
|
1129
|
+
{ id: "hermes", marker: `${hermesHostCommand(env)} --version` }
|
|
1130
|
+
].map((probe) => ({
|
|
1131
|
+
...probe,
|
|
1132
|
+
present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs8.existsSync)(probe.marker)
|
|
1133
|
+
}));
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// src/arm-cli.ts
|
|
1137
|
+
var import_node_fs9 = require("node:fs");
|
|
1138
|
+
var import_node_child_process6 = require("node:child_process");
|
|
1139
|
+
var import_node_path8 = require("node:path");
|
|
1140
|
+
function globalCliDist(env) {
|
|
1141
|
+
const root = globalRoot(env);
|
|
1142
|
+
return root ? (0, import_node_path8.join)(root, "@mutmutco", "cli", "dist", "index.cjs") : null;
|
|
1143
|
+
}
|
|
1144
|
+
function probeCliVersion(distPath) {
|
|
1145
|
+
if (!fileExists(distPath)) return null;
|
|
1146
|
+
const probe = (0, import_node_child_process6.spawnSync)(process.execPath, [distPath, "--version"], { encoding: "utf8", windowsHide: true });
|
|
1147
|
+
const version = (probe.stdout || "").trim();
|
|
1148
|
+
return probe.status === 0 && /^\d+\.\d+\.\d+/.test(version) ? version : null;
|
|
1149
|
+
}
|
|
1150
|
+
function probeCliInstallation(env = process.env) {
|
|
1151
|
+
const location = globalCliDist(env);
|
|
1152
|
+
const version = location ? probeCliVersion(location) : null;
|
|
1153
|
+
return { version, location, detail: version ? `absolute binary reported ${version}` : "global @mutmutco/cli binary was not readable" };
|
|
1154
|
+
}
|
|
1155
|
+
function cliArm(options) {
|
|
1156
|
+
const env = options.env ?? process.env;
|
|
1157
|
+
const { target, dryRun, paths } = options;
|
|
1158
|
+
const dist = globalCliDist(env);
|
|
1159
|
+
const installed = dist ? probeCliVersion(dist) : null;
|
|
1160
|
+
if (installed && compareSemver(installed, target) > 0) {
|
|
1161
|
+
return { surface: "cli", from: installed, to: installed, verdict: "skip", detail: `installed ${installed} is above candidate ${target} (monotonic)` };
|
|
1162
|
+
}
|
|
1163
|
+
const highWater = journalHighWater(paths.journalPath, "cli");
|
|
1164
|
+
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1165
|
+
return { surface: "cli", from: installed, to: highWater, verdict: "skip", detail: `journal high-water ${highWater} is above candidate ${target} (monotonic)` };
|
|
1166
|
+
}
|
|
1167
|
+
if (installed === target) {
|
|
1168
|
+
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `already at target ${target}` };
|
|
1169
|
+
}
|
|
1170
|
+
if (dryRun) {
|
|
1171
|
+
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `dry-run: would converge ${installed ?? "absent"} -> ${target}` };
|
|
1172
|
+
}
|
|
1173
|
+
const stageRoot = (0, import_node_path8.join)(paths.staging, "cli", target);
|
|
1174
|
+
const stageDist = (0, import_node_path8.join)(stageRoot, "node_modules", "@mutmutco", "cli", "dist", "index.cjs");
|
|
1175
|
+
if (!fileExists(stageDist)) {
|
|
1176
|
+
(0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
|
|
1177
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
1178
|
+
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `@mutmutco/cli@${target}`], env);
|
|
1179
|
+
if (install.status !== 0) {
|
|
1180
|
+
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staging install failed: ${(install.stderr || install.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}` };
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
let stagedManifestVersion;
|
|
1184
|
+
try {
|
|
1185
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path8.join)(stageRoot, "node_modules", "@mutmutco", "cli", "package.json"), "utf8")).version;
|
|
1186
|
+
} catch {
|
|
1187
|
+
}
|
|
1188
|
+
if (stagedManifestVersion !== target) {
|
|
1189
|
+
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged manifest is ${stagedManifestVersion ?? "unreadable"}, expected ${target}` };
|
|
1190
|
+
}
|
|
1191
|
+
const stagedVersion = probeCliVersion(stageDist);
|
|
1192
|
+
if (stagedVersion !== target) {
|
|
1193
|
+
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `staged binary probed ${stagedVersion ?? "dead"}, expected ${target}` };
|
|
1194
|
+
}
|
|
1195
|
+
const globalInstall = runNpm(["install", "-g", "--no-audit", "--no-fund", `@mutmutco/cli@${target}`], env);
|
|
1196
|
+
if (globalInstall.status !== 0) {
|
|
1197
|
+
const reason = `global install failed: ${(globalInstall.stderr || globalInstall.stdout).split("\n").filter(Boolean).slice(-3).join(" | ")}`;
|
|
1198
|
+
const root = globalRoot(env);
|
|
1199
|
+
const pending = lockShaped(reason) && root ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cli", target, reason, dirs: [root] })}` : "";
|
|
1200
|
+
return { surface: "cli", from: installed, to: target, verdict: "defer", detail: `${reason}${pending}` };
|
|
1201
|
+
}
|
|
1202
|
+
const switchedDist = globalCliDist(env);
|
|
1203
|
+
const switched = probeCliVersion(switchedDist ?? "");
|
|
1204
|
+
if (switched !== target) {
|
|
1205
|
+
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` };
|
|
1206
|
+
}
|
|
1207
|
+
return { surface: "cli", from: installed, to: target, verdict: "ok", detail: `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched)` };
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// src/arm-self.ts
|
|
1211
|
+
function selfArm(options) {
|
|
1212
|
+
return npmGlobalArm({
|
|
1213
|
+
surface: "updater",
|
|
1214
|
+
packageName: "@mutmutco/hub",
|
|
1215
|
+
distRel: "dist/index.cjs",
|
|
1216
|
+
...options
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1017
1220
|
// src/arm-kimi.ts
|
|
1018
1221
|
var import_node_child_process7 = require("node:child_process");
|
|
1019
1222
|
var import_node_fs12 = require("node:fs");
|
|
1020
1223
|
var import_node_path9 = require("node:path");
|
|
1021
1224
|
|
|
1022
1225
|
// src/compat.ts
|
|
1023
|
-
var
|
|
1226
|
+
var import_node_fs10 = require("node:fs");
|
|
1024
1227
|
function readManifestCompat(manifestPath) {
|
|
1025
1228
|
try {
|
|
1026
|
-
const parsed = JSON.parse((0,
|
|
1229
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(manifestPath, "utf8"));
|
|
1027
1230
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
1028
1231
|
} catch {
|
|
1029
1232
|
return void 0;
|
|
1030
1233
|
}
|
|
1031
1234
|
}
|
|
1032
1235
|
|
|
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
1236
|
// src/kimi-registration.ts
|
|
1083
1237
|
function validateKimiInstalledPlugin(document, pluginRoot, id) {
|
|
1084
1238
|
if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
|
|
@@ -1863,10 +2017,47 @@ function installedVersion(env) {
|
|
|
1863
2017
|
return null;
|
|
1864
2018
|
}
|
|
1865
2019
|
}
|
|
1866
|
-
function
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2020
|
+
function materializedManifestLocation(env) {
|
|
2021
|
+
return (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
2022
|
+
}
|
|
2023
|
+
var PI_LIST_SPEC = new RegExp(`npm:${PI_PACKAGE.replace("/", "\\/")}@(\\S+)`);
|
|
2024
|
+
function parseListText(stdout) {
|
|
2025
|
+
for (const line of stdout.split("\n")) {
|
|
2026
|
+
const match = line.match(PI_LIST_SPEC);
|
|
2027
|
+
if (match) return match[1];
|
|
2028
|
+
}
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
2032
|
+
const manifestLocation = materializedManifestLocation(env);
|
|
2033
|
+
const manifestVersion = installedVersion(env);
|
|
2034
|
+
const manifestEvidence = () => ({
|
|
2035
|
+
version: manifestVersion,
|
|
2036
|
+
location: manifestLocation,
|
|
2037
|
+
detail: manifestVersion ? "materialized pi package manifest" : "materialized pi package manifest was unreadable"
|
|
2038
|
+
});
|
|
2039
|
+
const hosted = hostEnv(env);
|
|
2040
|
+
const command = hostCommands(env).find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
2041
|
+
if (!command) return manifestEvidence();
|
|
2042
|
+
const hostLocation = `${command} list`;
|
|
2043
|
+
const result = runner(command, ["list"], hosted);
|
|
2044
|
+
const version = result.status === 0 ? parseListText(result.stdout) : null;
|
|
2045
|
+
if (version) {
|
|
2046
|
+
return {
|
|
2047
|
+
version,
|
|
2048
|
+
location: hostLocation,
|
|
2049
|
+
detail: "native host user package listing"
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
const hostError = result.status !== 0 ? `${hostLocation} failed: ${failure2(result)}` : `${hostLocation} did not list npm:${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) {
|
|
2088
2307
|
discard2(incomingRoot);
|
|
2089
|
-
return result(installed, options.target, "defer",
|
|
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) {
|
|
2312
|
+
discard2(incomingRoot);
|
|
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\``);
|
|
@@ -3303,6 +3538,15 @@ function reportFooterLines(summary, options = {}) {
|
|
|
3303
3538
|
};
|
|
3304
3539
|
const footer = dryRun ? `${counts.updated}/${counts.total} would update` : `${counts.updated}/${counts.total} updated` + (counts.retry ? ` \xB7 ${counts.retry} will retry` : "") + (counts.failed ? ` \xB7 ${counts.failed} failed` : "");
|
|
3305
3540
|
const out = ["", " " + footer];
|
|
3541
|
+
const self = summary.updater;
|
|
3542
|
+
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3543
|
+
out.push("");
|
|
3544
|
+
out.push(...noteLines(
|
|
3545
|
+
" note: ",
|
|
3546
|
+
`hub moved ${self.from} \u2192 ${self.to} after the surfaces above ran, so they used ${self.from} logic \u2014 rerun \`mmi-hub update\` to bring them to ${self.to}`,
|
|
3547
|
+
width
|
|
3548
|
+
));
|
|
3549
|
+
}
|
|
3306
3550
|
if (counts.failed) {
|
|
3307
3551
|
out.push("");
|
|
3308
3552
|
for (const row of rows.filter((row2) => row2.kind === "failed")) {
|
|
@@ -3539,6 +3783,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3539
3783
|
0 && (module.exports = {
|
|
3540
3784
|
acquireSharedDocLock,
|
|
3541
3785
|
buildWatcherCommand,
|
|
3786
|
+
claudeArm,
|
|
3787
|
+
claudeHostEvidence,
|
|
3542
3788
|
clearLaunchTarget,
|
|
3543
3789
|
clearPending,
|
|
3544
3790
|
enumerateSurfaces,
|
|
@@ -3546,7 +3792,9 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3546
3792
|
formatReconcileReport,
|
|
3547
3793
|
hermesArm,
|
|
3548
3794
|
hermesHome,
|
|
3795
|
+
hermesHostCommand,
|
|
3549
3796
|
hermesHostEvidence,
|
|
3797
|
+
hermesHostPresent,
|
|
3550
3798
|
hubStatus,
|
|
3551
3799
|
launchdPlist,
|
|
3552
3800
|
launcherSh,
|
|
@@ -3561,9 +3809,11 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3561
3809
|
parseNpmDistIntegrity,
|
|
3562
3810
|
pendingPath,
|
|
3563
3811
|
pidAlive,
|
|
3812
|
+
probeClaudeLaunchStaging,
|
|
3564
3813
|
probeCursorLaunchStaging,
|
|
3565
3814
|
probeHermesInstallation,
|
|
3566
3815
|
probeHermesLaunchStaging,
|
|
3816
|
+
probeJervcodeInstallation,
|
|
3567
3817
|
probeKimiLaunchStaging,
|
|
3568
3818
|
probeLaunchTargetVersion,
|
|
3569
3819
|
progressiveRow,
|
package/package.json
CHANGED