@mutmutco/hub 4.5.86 → 4.5.88
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 +402 -571
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -53,19 +53,20 @@ __export(index_exports, {
|
|
|
53
53
|
pendingPath: () => pendingPath,
|
|
54
54
|
pidAlive: () => pidAlive,
|
|
55
55
|
probeClaudeLaunchStaging: () => probeClaudeLaunchStaging,
|
|
56
|
-
probeCursorLaunchStaging: () => probeCursorLaunchStaging,
|
|
57
56
|
probeHermesInstallation: () => probeHermesInstallation,
|
|
58
57
|
probeHermesLaunchStaging: () => probeHermesLaunchStaging,
|
|
59
58
|
probeJervcodeInstallation: () => probeJervcodeInstallation,
|
|
60
59
|
probeLaunchTargetVersion: () => probeLaunchTargetVersion,
|
|
61
60
|
readLaunchTarget: () => readLaunchTarget,
|
|
62
61
|
readPending: () => readPending,
|
|
62
|
+
reap: () => reap,
|
|
63
63
|
reconcile: () => reconcile,
|
|
64
64
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
65
65
|
releaseSharedDocLock: () => releaseSharedDocLock,
|
|
66
66
|
renderNotice: () => renderNotice,
|
|
67
67
|
renderReport: () => renderReport,
|
|
68
68
|
reportCounts: () => reportCounts,
|
|
69
|
+
retireCursor: () => retireCursor,
|
|
69
70
|
retiredSurfaces: () => retiredSurfaces,
|
|
70
71
|
retryHint: () => retryHint,
|
|
71
72
|
runMaintenance: () => runMaintenance2,
|
|
@@ -86,7 +87,7 @@ __export(index_exports, {
|
|
|
86
87
|
writePending: () => writePending
|
|
87
88
|
});
|
|
88
89
|
module.exports = __toCommonJS(index_exports);
|
|
89
|
-
var
|
|
90
|
+
var import_node_child_process8 = require("node:child_process");
|
|
90
91
|
var import_node_fs24 = require("node:fs");
|
|
91
92
|
var import_node_path16 = require("node:path");
|
|
92
93
|
|
|
@@ -304,7 +305,7 @@ function createFace({ product, color = false, columns, env = process.env, operat
|
|
|
304
305
|
], options);
|
|
305
306
|
};
|
|
306
307
|
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
307
|
-
const refusal = (
|
|
308
|
+
const refusal = (message2) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message2}`;
|
|
308
309
|
return { identity, width, nested, emitsProgress: progressFd !== null || Boolean(progressFile), welcome, continues, step, progress: (title, measure = null) => emitMilestone(title, measure, "running"), relay, receipt, outcome, signOff, refusal, paint };
|
|
309
310
|
}
|
|
310
311
|
|
|
@@ -1060,7 +1061,7 @@ function renderUsage(product, usage2, render) {
|
|
|
1060
1061
|
const face = faceFor(product, lane);
|
|
1061
1062
|
const all = [...usage2.commands, ...usage2.options ?? []];
|
|
1062
1063
|
const column = Math.min(Math.max(...all.map((entry) => entry.usage.length)), 34);
|
|
1063
|
-
const table = (
|
|
1064
|
+
const table = (entries2) => entries2.flatMap((entry) => {
|
|
1064
1065
|
const [first, ...rest] = wrapWords(plain(entry.text, lane), Math.max(20, face.width - column - 6));
|
|
1065
1066
|
const head = entry.usage.length > column ? [` ${entry.usage}`, ` ${" ".repeat(column)} ${first}`] : [` ${entry.usage.padEnd(column)} ${first}`];
|
|
1066
1067
|
return [...head, ...rest.map((line) => ` ${" ".repeat(column)} ${line}`)];
|
|
@@ -1311,8 +1312,8 @@ function defaultProductDir(product) {
|
|
|
1311
1312
|
const base = process.env.LOCALAPPDATA ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), "launcher-fallback");
|
|
1312
1313
|
return (0, import_node_path.join)(base, product);
|
|
1313
1314
|
}
|
|
1314
|
-
const
|
|
1315
|
-
return (0, import_node_path.join)(
|
|
1315
|
+
const home2 = process.env.HOME ?? (0, import_node_os.tmpdir)();
|
|
1316
|
+
return (0, import_node_path.join)(home2, `.${product}`);
|
|
1316
1317
|
}
|
|
1317
1318
|
|
|
1318
1319
|
// ../installer/launcher/src/payload.ts
|
|
@@ -1354,12 +1355,12 @@ function publicKeyBytes(config) {
|
|
|
1354
1355
|
}
|
|
1355
1356
|
|
|
1356
1357
|
// ../installer/launcher/src/payload.ts
|
|
1357
|
-
function canonicalManifestBytes(
|
|
1358
|
+
function canonicalManifestBytes(manifest) {
|
|
1358
1359
|
return Buffer.from(
|
|
1359
1360
|
canonicalJson({
|
|
1360
|
-
created:
|
|
1361
|
-
files:
|
|
1362
|
-
version:
|
|
1361
|
+
created: manifest.created,
|
|
1362
|
+
files: manifest.files.map((file) => ({ path: file.path, sha256: file.sha256, size: file.size })),
|
|
1363
|
+
version: manifest.version
|
|
1363
1364
|
}),
|
|
1364
1365
|
"utf8"
|
|
1365
1366
|
);
|
|
@@ -1371,11 +1372,11 @@ function ed25519PublicKey(config) {
|
|
|
1371
1372
|
format: "jwk"
|
|
1372
1373
|
});
|
|
1373
1374
|
}
|
|
1374
|
-
function verifyManifest(
|
|
1375
|
+
function verifyManifest(manifest, signature, config) {
|
|
1375
1376
|
try {
|
|
1376
1377
|
const signatureBytes = Buffer.from(signature, "base64");
|
|
1377
1378
|
if (signatureBytes.length === 0) return false;
|
|
1378
|
-
return (0, import_node_crypto.verify)(null, canonicalManifestBytes(
|
|
1379
|
+
return (0, import_node_crypto.verify)(null, canonicalManifestBytes(manifest), ed25519PublicKey(config), signatureBytes);
|
|
1379
1380
|
} catch {
|
|
1380
1381
|
return false;
|
|
1381
1382
|
}
|
|
@@ -1651,7 +1652,6 @@ var installer_product_default = {
|
|
|
1651
1652
|
{ id: "cli", npm: "@mutmutco/cli", bin: "mmi-cli" },
|
|
1652
1653
|
{ id: "claude", kind: "agent-home", activation: "next launch" },
|
|
1653
1654
|
{ id: "codex", kind: "agent-home", activation: "next launch" },
|
|
1654
|
-
{ id: "cursor", kind: "agent-home", activation: "next launch" },
|
|
1655
1655
|
{ id: "hermes", kind: "agent-home", activation: "next launch" },
|
|
1656
1656
|
{ id: "jervcode", kind: "agent-home", activation: "next launch" }
|
|
1657
1657
|
]
|
|
@@ -2029,9 +2029,9 @@ async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
2029
2029
|
let name;
|
|
2030
2030
|
let manifestVersion2;
|
|
2031
2031
|
try {
|
|
2032
|
-
const
|
|
2033
|
-
name =
|
|
2034
|
-
manifestVersion2 =
|
|
2032
|
+
const manifest = JSON.parse(manifestText ?? "");
|
|
2033
|
+
name = manifest.name;
|
|
2034
|
+
manifestVersion2 = manifest.version;
|
|
2035
2035
|
} catch {
|
|
2036
2036
|
}
|
|
2037
2037
|
if (!name || manifestVersion2 !== candidate) {
|
|
@@ -2403,8 +2403,8 @@ function claudeMmi(runner, env) {
|
|
|
2403
2403
|
return { row: parsed.find((row) => row.id === "mmi@mutmutco") ?? null };
|
|
2404
2404
|
}
|
|
2405
2405
|
function claudeHomeDir(env) {
|
|
2406
|
-
const
|
|
2407
|
-
return (0, import_node_path9.join)(
|
|
2406
|
+
const home2 = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os4.homedir)();
|
|
2407
|
+
return (0, import_node_path9.join)(home2, ".claude");
|
|
2408
2408
|
}
|
|
2409
2409
|
function claudeMarketplacesPath(env) {
|
|
2410
2410
|
return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path9.join)(claudeHomeDir(env), "plugins", "known_marketplaces.json");
|
|
@@ -2653,16 +2653,11 @@ function surfaceArmPlan(token, managedSurfaces) {
|
|
|
2653
2653
|
return managedSurfaces.includes(token) ? "arm" : "retired";
|
|
2654
2654
|
}
|
|
2655
2655
|
function enumerateSurfaces(env = process.env) {
|
|
2656
|
-
const
|
|
2657
|
-
const codexHome = env.CODEX_HOME || (0, import_node_path10.join)(
|
|
2656
|
+
const home2 = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os5.homedir)();
|
|
2657
|
+
const codexHome = env.CODEX_HOME || (0, import_node_path10.join)(home2, ".codex");
|
|
2658
2658
|
return [
|
|
2659
|
-
{ id: "claude", marker: (0, import_node_path10.join)(
|
|
2659
|
+
{ id: "claude", marker: (0, import_node_path10.join)(home2, ".claude", "plugins", "installed_plugins.json") },
|
|
2660
2660
|
{ id: "codex", marker: (0, import_node_path10.join)(codexHome, "plugins", "installed_plugins.json") },
|
|
2661
|
-
// The host, not the payload (#4951, #5356): this marker used to be plugins/local/mmi — the MMI
|
|
2662
|
-
// plugin tree itself — so a machine running Cursor WITHOUT the plugin enumerated as "no Cursor"
|
|
2663
|
-
// and the arm could never make the first install. The plugin's absence is a converge action, not
|
|
2664
|
-
// a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
|
|
2665
|
-
{ id: "cursor", marker: (0, import_node_path10.join)(home, ".cursor") },
|
|
2666
2661
|
{ id: "jervcode", marker: (0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json") },
|
|
2667
2662
|
// Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
|
|
2668
2663
|
// leftover plugin manifests — that residue must not enumerate as an installed host.
|
|
@@ -2673,10 +2668,8 @@ function enumerateSurfaces(env = process.env) {
|
|
|
2673
2668
|
}));
|
|
2674
2669
|
}
|
|
2675
2670
|
|
|
2676
|
-
// src/arm-
|
|
2677
|
-
var
|
|
2678
|
-
var import_node_child_process7 = require("node:child_process");
|
|
2679
|
-
var import_node_os7 = require("node:os");
|
|
2671
|
+
// src/arm-jervcode.ts
|
|
2672
|
+
var import_node_fs16 = require("node:fs");
|
|
2680
2673
|
var import_node_path11 = require("node:path");
|
|
2681
2674
|
|
|
2682
2675
|
// src/compat.ts
|
|
@@ -2690,432 +2683,7 @@ function readManifestCompat(manifestPath) {
|
|
|
2690
2683
|
}
|
|
2691
2684
|
}
|
|
2692
2685
|
|
|
2693
|
-
// src/host-doc-lock.ts
|
|
2694
|
-
var import_node_fs16 = require("node:fs");
|
|
2695
|
-
var import_node_os6 = require("node:os");
|
|
2696
|
-
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
2697
|
-
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
2698
|
-
var SHARED_DOC_LOCK_INTERVAL_MS = 100;
|
|
2699
|
-
function sharedDocLockPath(documentPath) {
|
|
2700
|
-
return `${documentPath}.jerv-lock`;
|
|
2701
|
-
}
|
|
2702
|
-
function lockContent(options) {
|
|
2703
|
-
return `${JSON.stringify(options, null, 2)}
|
|
2704
|
-
`;
|
|
2705
|
-
}
|
|
2706
|
-
function readHolder(lockPath) {
|
|
2707
|
-
try {
|
|
2708
|
-
const parsed = JSON.parse((0, import_node_fs16.readFileSync)(lockPath, "utf8"));
|
|
2709
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
2710
|
-
return parsed;
|
|
2711
|
-
} catch {
|
|
2712
|
-
return null;
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
function lockAgeMs(lockPath, now) {
|
|
2716
|
-
const holder = readHolder(lockPath);
|
|
2717
|
-
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
2718
|
-
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
2719
|
-
try {
|
|
2720
|
-
return Math.max(0, now - (0, import_node_fs16.statSync)(lockPath).mtimeMs);
|
|
2721
|
-
} catch {
|
|
2722
|
-
return 0;
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
2725
|
-
function sharedDocLockTimeoutReceipt(documentPath, lockPath, holder, waitedMs) {
|
|
2726
|
-
const named = holder && typeof holder === "object";
|
|
2727
|
-
const who = named ? `held by ${String(holder.owner ?? "unknown")} (pid ${String(holder.pid ?? "?")}, host ${String(holder.host ?? "?")})` : `held by an unreadable lock at ${lockPath}`;
|
|
2728
|
-
return `shared-document lock timeout: ${documentPath} is ${who} \u2014 waited ${waitedMs}ms, nothing was written \u2014 retry next tick or remove ${lockPath} if stale`;
|
|
2729
|
-
}
|
|
2730
|
-
function acquireSharedDocLock(documentPath, options) {
|
|
2731
|
-
const lockPath = sharedDocLockPath(documentPath);
|
|
2732
|
-
const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
|
|
2733
|
-
const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
|
|
2734
|
-
const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
|
|
2735
|
-
const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os6.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2736
|
-
const content = lockContent(identity);
|
|
2737
|
-
const sleep = (ms) => {
|
|
2738
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
2739
|
-
};
|
|
2740
|
-
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2741
|
-
let fd;
|
|
2742
|
-
try {
|
|
2743
|
-
fd = (0, import_node_fs16.openSync)(lockPath, "wx");
|
|
2744
|
-
(0, import_node_fs16.writeSync)(fd, content);
|
|
2745
|
-
(0, import_node_fs16.closeSync)(fd);
|
|
2746
|
-
return {
|
|
2747
|
-
documentPath,
|
|
2748
|
-
lockPath,
|
|
2749
|
-
owner: identity.owner,
|
|
2750
|
-
pid: identity.pid,
|
|
2751
|
-
host: identity.host,
|
|
2752
|
-
acquiredAt: identity.acquiredAt,
|
|
2753
|
-
release: () => releaseSharedDocLock({ lockPath, owner: identity.owner, pid: identity.pid })
|
|
2754
|
-
};
|
|
2755
|
-
} catch (error) {
|
|
2756
|
-
if (fd !== void 0) {
|
|
2757
|
-
try {
|
|
2758
|
-
(0, import_node_fs16.closeSync)(fd);
|
|
2759
|
-
} catch {
|
|
2760
|
-
}
|
|
2761
|
-
try {
|
|
2762
|
-
(0, import_node_fs16.unlinkSync)(lockPath);
|
|
2763
|
-
} catch {
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
if (error?.code !== "EEXIST") throw error;
|
|
2767
|
-
}
|
|
2768
|
-
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
2769
|
-
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
2770
|
-
try {
|
|
2771
|
-
(0, import_node_fs16.renameSync)(lockPath, aside);
|
|
2772
|
-
} catch {
|
|
2773
|
-
continue;
|
|
2774
|
-
}
|
|
2775
|
-
try {
|
|
2776
|
-
(0, import_node_fs16.rmSync)(aside, { force: true });
|
|
2777
|
-
} catch {
|
|
2778
|
-
}
|
|
2779
|
-
continue;
|
|
2780
|
-
}
|
|
2781
|
-
if (attempt < attempts - 1) sleep(intervalMs);
|
|
2782
|
-
}
|
|
2783
|
-
throw new Error(sharedDocLockTimeoutReceipt(documentPath, lockPath, readHolder(lockPath), Math.max(0, attempts * intervalMs)));
|
|
2784
|
-
}
|
|
2785
|
-
function releaseSharedDocLock(handle) {
|
|
2786
|
-
try {
|
|
2787
|
-
const holder = readHolder(handle.lockPath);
|
|
2788
|
-
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
2789
|
-
(0, import_node_fs16.unlinkSync)(handle.lockPath);
|
|
2790
|
-
}
|
|
2791
|
-
} catch {
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
function withSharedDocLock(documentPath, options, fn) {
|
|
2795
|
-
const lock = acquireSharedDocLock(documentPath, options);
|
|
2796
|
-
try {
|
|
2797
|
-
return fn(lock.documentPath);
|
|
2798
|
-
} finally {
|
|
2799
|
-
lock.release();
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
|
|
2803
|
-
// src/arm-cursor.ts
|
|
2804
|
-
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
2805
|
-
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
2806
|
-
var USER_HOOKS_FILE = "hooks.json";
|
|
2807
|
-
var KEEP_GENERATIONS = 2;
|
|
2808
|
-
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
2809
|
-
var TREE_FILES = [MANIFEST, "skills/bootstrap/SKILL.md"];
|
|
2810
|
-
function cursorPluginsRoot() {
|
|
2811
|
-
return (0, import_node_path11.join)((0, import_node_os7.homedir)(), ".cursor", "plugins");
|
|
2812
|
-
}
|
|
2813
|
-
function treeHealthy(root) {
|
|
2814
|
-
return TREE_FILES.every((rel) => (0, import_node_fs17.existsSync)((0, import_node_path11.join)(root, ...rel.split("/"))));
|
|
2815
|
-
}
|
|
2816
|
-
function readJson(root, rel) {
|
|
2817
|
-
try {
|
|
2818
|
-
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path11.join)(root, ...rel.split("/")), "utf8"));
|
|
2819
|
-
} catch {
|
|
2820
|
-
return null;
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
function manifest(root) {
|
|
2824
|
-
return readJson(root, MANIFEST);
|
|
2825
|
-
}
|
|
2826
|
-
function probeCursorInstallation() {
|
|
2827
|
-
const location = (0, import_node_path11.join)(cursorPluginsRoot(), "local", "mmi");
|
|
2828
|
-
const version = manifest(location)?.version ?? null;
|
|
2829
|
-
return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
|
|
2830
|
-
}
|
|
2831
|
-
function mmiOwned(root) {
|
|
2832
|
-
return manifest(root)?.name === "mmi" || readJson(root, "package.json")?.name === PACKAGE;
|
|
2833
|
-
}
|
|
2834
|
-
function safeSegment(value) {
|
|
2835
|
-
return value.replace(/[^0-9A-Za-z.+-]/g, "_").slice(0, 40) || "unknown";
|
|
2836
|
-
}
|
|
2837
|
-
function message(error) {
|
|
2838
|
-
return String(error?.message ?? error).trim().replace(/\s+/g, " ").slice(0, 200);
|
|
2839
|
-
}
|
|
2840
|
-
function discard(path) {
|
|
2841
|
-
try {
|
|
2842
|
-
(0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
|
|
2843
|
-
} catch {
|
|
2844
|
-
}
|
|
2845
|
-
}
|
|
2846
|
-
function cursorExecutable(line) {
|
|
2847
|
-
const trimmed = line.trim();
|
|
2848
|
-
if (!trimmed) return null;
|
|
2849
|
-
const quoted = /^"([^"]+)"|^'([^']+)'/.exec(trimmed);
|
|
2850
|
-
const executable = quoted ? quoted[1] ?? quoted[2] : trimmed.split(/\s+/)[0];
|
|
2851
|
-
const base = executable.split(/[/\\]/).pop() ?? "";
|
|
2852
|
-
if (/[\\/]chrome_crashpad_handler(\.exe)?(["']|\s|$)/i.test(trimmed)) return null;
|
|
2853
|
-
const host = /^cursor(\.exe)?$/i.test(base) || /[/\\]Cursor\.app[/\\]/i.test(executable);
|
|
2854
|
-
return host ? executable.slice(0, 160) : null;
|
|
2855
|
-
}
|
|
2856
|
-
function cursorHostEvidence(env) {
|
|
2857
|
-
for (const marker of ["CURSOR_TRACE_ID", "CURSOR_SESSION_ID", "CURSOR_EXTENSION_HOST_ROLE"]) {
|
|
2858
|
-
if (env[marker]?.trim()) return `env marker ${marker}`;
|
|
2859
|
-
}
|
|
2860
|
-
if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
|
|
2861
|
-
const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
2862
|
-
if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
|
|
2863
|
-
for (const line of result.stdout.split(/\r?\n/)) {
|
|
2864
|
-
const executable = cursorExecutable(line);
|
|
2865
|
-
if (executable) return `process ${executable}`;
|
|
2866
|
-
}
|
|
2867
|
-
return null;
|
|
2868
|
-
}
|
|
2869
|
-
function resolvedPluginRoot(root) {
|
|
2870
|
-
return (0, import_node_path11.join)(root).replace(/\\/g, "/");
|
|
2871
|
-
}
|
|
2872
|
-
function isMmiEntry(entry, pluginRoot) {
|
|
2873
|
-
if (typeof entry !== "object" || entry === null || typeof entry.command !== "string") return false;
|
|
2874
|
-
const command = entry.command;
|
|
2875
|
-
return command.includes(resolvedPluginRoot(pluginRoot)) || /(?:mmi-hook|hook-run\.mjs|hook-policy\.mjs)/i.test(command);
|
|
2876
|
-
}
|
|
2877
|
-
function stripUserHooks(existing, pluginRoot) {
|
|
2878
|
-
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) return null;
|
|
2879
|
-
const stripped = { ...existing };
|
|
2880
|
-
const hooks = stripped.hooks;
|
|
2881
|
-
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
2882
|
-
const nextHooks = { ...hooks };
|
|
2883
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
2884
|
-
if (!Array.isArray(entries)) return null;
|
|
2885
|
-
const kept = entries.filter((entry) => !isMmiEntry(entry, pluginRoot));
|
|
2886
|
-
if (kept.length) nextHooks[event] = kept;
|
|
2887
|
-
else delete nextHooks[event];
|
|
2888
|
-
}
|
|
2889
|
-
stripped.hooks = nextHooks;
|
|
2890
|
-
return stripped;
|
|
2891
|
-
}
|
|
2892
|
-
var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
2893
|
-
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
2894
|
-
const path = (0, import_node_path11.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
2895
|
-
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
2896
|
-
if (!(0, import_node_fs17.existsSync)(path)) return "current";
|
|
2897
|
-
let existing;
|
|
2898
|
-
try {
|
|
2899
|
-
existing = JSON.parse((0, import_node_fs17.readFileSync)(path, "utf8"));
|
|
2900
|
-
} catch {
|
|
2901
|
-
return "user-conflict";
|
|
2902
|
-
}
|
|
2903
|
-
const stripped = stripUserHooks(existing, pluginRoot);
|
|
2904
|
-
if (!stripped) return "user-conflict";
|
|
2905
|
-
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
2906
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
2907
|
-
(0, import_node_fs17.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
2908
|
-
(0, import_node_fs17.renameSync)(tmp, path);
|
|
2909
|
-
return "updated";
|
|
2910
|
-
});
|
|
2911
|
-
}
|
|
2912
|
-
function userHooksDetail(pluginRoot, homeDir) {
|
|
2913
|
-
let verdict;
|
|
2914
|
-
try {
|
|
2915
|
-
verdict = reconcileCursorUserHooks(pluginRoot, homeDir);
|
|
2916
|
-
} catch (error) {
|
|
2917
|
-
return `; ${error?.message ?? error}`;
|
|
2918
|
-
}
|
|
2919
|
-
switch (verdict) {
|
|
2920
|
-
case "updated":
|
|
2921
|
-
return "; user hooks legacy entries stripped";
|
|
2922
|
-
case "user-conflict":
|
|
2923
|
-
return `; USER HOOKS CONFLICT at ${(0, import_node_path11.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 legacy entries were not changed`;
|
|
2924
|
-
default:
|
|
2925
|
-
return "";
|
|
2926
|
-
}
|
|
2927
|
-
}
|
|
2928
|
-
function writeRollbackPointer(root, entry) {
|
|
2929
|
-
const path = (0, import_node_path11.join)(root, "quarantine", "rollback.json");
|
|
2930
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
2931
|
-
try {
|
|
2932
|
-
(0, import_node_fs17.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
2933
|
-
(0, import_node_fs17.renameSync)(tmp, path);
|
|
2934
|
-
} catch {
|
|
2935
|
-
discard(tmp);
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
function mtimeMs(path) {
|
|
2939
|
-
try {
|
|
2940
|
-
return (0, import_node_fs17.statSync)(path).mtimeMs;
|
|
2941
|
-
} catch {
|
|
2942
|
-
return 0;
|
|
2943
|
-
}
|
|
2944
|
-
}
|
|
2945
|
-
function pruneQuarantine(root, keep) {
|
|
2946
|
-
const dir = (0, import_node_path11.join)(root, "quarantine");
|
|
2947
|
-
let generations;
|
|
2948
|
-
try {
|
|
2949
|
-
generations = (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path11.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
|
|
2950
|
-
} catch {
|
|
2951
|
-
return 0;
|
|
2952
|
-
}
|
|
2953
|
-
let stranded = 0;
|
|
2954
|
-
try {
|
|
2955
|
-
for (const entry of (0, import_node_fs17.readdirSync)(dir, { withFileTypes: true })) {
|
|
2956
|
-
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
2957
|
-
const path = (0, import_node_path11.join)(dir, entry.name);
|
|
2958
|
-
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
2959
|
-
discard(path);
|
|
2960
|
-
stranded += 1;
|
|
2961
|
-
}
|
|
2962
|
-
} catch {
|
|
2963
|
-
}
|
|
2964
|
-
const retained = new Set([...new Set([keep, ...generations].filter((path) => !!path))].slice(0, KEEP_GENERATIONS - 1));
|
|
2965
|
-
let pruned = 0;
|
|
2966
|
-
for (const path of generations) {
|
|
2967
|
-
if (retained.has(path)) continue;
|
|
2968
|
-
discard(path);
|
|
2969
|
-
pruned += 1;
|
|
2970
|
-
}
|
|
2971
|
-
return pruned + stranded;
|
|
2972
|
-
}
|
|
2973
|
-
function restorePrevious(live, quarantined, broken) {
|
|
2974
|
-
try {
|
|
2975
|
-
(0, import_node_fs17.renameSync)(live, broken);
|
|
2976
|
-
} catch {
|
|
2977
|
-
return "kept-broken";
|
|
2978
|
-
}
|
|
2979
|
-
try {
|
|
2980
|
-
(0, import_node_fs17.renameSync)(quarantined, live);
|
|
2981
|
-
return "restored";
|
|
2982
|
-
} catch {
|
|
2983
|
-
try {
|
|
2984
|
-
(0, import_node_fs17.renameSync)(broken, live);
|
|
2985
|
-
return "kept-broken";
|
|
2986
|
-
} catch {
|
|
2987
|
-
return "lost";
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
function cursorArm(options) {
|
|
2992
|
-
const env = options.env ?? process.env;
|
|
2993
|
-
const { target, dryRun, paths } = options;
|
|
2994
|
-
const root = cursorPluginsRoot();
|
|
2995
|
-
const live = (0, import_node_path11.join)(root, "local", "mmi");
|
|
2996
|
-
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
2997
|
-
surface: "cursor",
|
|
2998
|
-
from,
|
|
2999
|
-
to,
|
|
3000
|
-
verdict,
|
|
3001
|
-
detail,
|
|
3002
|
-
launchStaged: launchStaged || void 0,
|
|
3003
|
-
// C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
|
|
3004
|
-
// switch reports the restored generation's declaration, not the rejected one's (#4973).
|
|
3005
|
-
compat: readManifestCompat((0, import_node_path11.join)(live, MANIFEST))
|
|
3006
|
-
});
|
|
3007
|
-
const current = manifest(live);
|
|
3008
|
-
const installed = current?.version ?? null;
|
|
3009
|
-
if (installed && compareSemver(installed, target) > 0) {
|
|
3010
|
-
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
3011
|
-
}
|
|
3012
|
-
const highWater = journalHighWater(paths.journalPath, "cursor", options.journalEvents);
|
|
3013
|
-
if (highWater && compareSemver(highWater, target) > 0) {
|
|
3014
|
-
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
3015
|
-
}
|
|
3016
|
-
if (installed === target && treeHealthy(live)) {
|
|
3017
|
-
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os7.homedir)())}`);
|
|
3018
|
-
}
|
|
3019
|
-
if ((0, import_node_fs17.existsSync)(live) && !mmiOwned(live)) {
|
|
3020
|
-
return result(installed, target, "defer", `${live} carries no MMI ownership marker (plugin manifest name, or a ${PACKAGE} package.json) \u2014 refusing to displace an unmanaged directory (mmi-cli plugin heal adopts it)`);
|
|
3021
|
-
}
|
|
3022
|
-
if (dryRun) {
|
|
3023
|
-
return result(installed, target, "ok", `dry-run: would stage ${PACKAGE}@${target} and switch ${installed ?? "absent"} -> ${target} while Cursor is absent`);
|
|
3024
|
-
}
|
|
3025
|
-
const staged = stagePackage({ surface: "cursor", packageName: PACKAGE, target, witness: MANIFEST, paths, env });
|
|
3026
|
-
if ("defer" in staged) {
|
|
3027
|
-
return result(installed, target, "defer", staged.defer);
|
|
3028
|
-
}
|
|
3029
|
-
const stagedMarker = manifest(staged.packageRoot)?.version;
|
|
3030
|
-
if (stagedMarker !== target || !treeHealthy(staged.packageRoot)) {
|
|
3031
|
-
return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
|
|
3032
|
-
}
|
|
3033
|
-
const launchTargetPath = (0, import_node_path11.join)(root, "staging", "launch-target.json");
|
|
3034
|
-
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
3035
|
-
let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path11.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path11.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
3036
|
-
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
3037
|
-
if (!reuseIncoming || !(0, import_node_fs17.existsSync)(incoming)) {
|
|
3038
|
-
incoming = (0, import_node_path11.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
3039
|
-
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs17.mkdirSync)((0, import_node_path11.join)(root, dir), { recursive: true });
|
|
3040
|
-
try {
|
|
3041
|
-
discard(incoming);
|
|
3042
|
-
(0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
3043
|
-
} catch (error) {
|
|
3044
|
-
discard(incoming);
|
|
3045
|
-
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
3046
|
-
}
|
|
3047
|
-
if (manifest(incoming)?.version !== target || !treeHealthy(incoming)) {
|
|
3048
|
-
discard(incoming);
|
|
3049
|
-
return result(installed, target, "defer", "the copy on the Cursor volume did not reproduce the verified tree");
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
|
-
const busy = cursorHostEvidence(env);
|
|
3053
|
-
if (busy) {
|
|
3054
|
-
const pending = noteLaunchStaged(paths, env, {
|
|
3055
|
-
surface: "cursor",
|
|
3056
|
-
target,
|
|
3057
|
-
reason: busy,
|
|
3058
|
-
launchTargetPath,
|
|
3059
|
-
incoming,
|
|
3060
|
-
images: ["Cursor"]
|
|
3061
|
-
});
|
|
3062
|
-
return result(installed, target, "ok", `staged ${target} for next launch while Cursor is running (${busy}) \u2014 ${pending}`, true);
|
|
3063
|
-
}
|
|
3064
|
-
const stamp = Date.now();
|
|
3065
|
-
const quarantined = (0, import_node_path11.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
3066
|
-
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs17.mkdirSync)((0, import_node_path11.join)(root, dir), { recursive: true });
|
|
3067
|
-
const started = cursorHostEvidence(env);
|
|
3068
|
-
if (started) {
|
|
3069
|
-
const pending = noteLaunchStaged(paths, env, {
|
|
3070
|
-
surface: "cursor",
|
|
3071
|
-
target,
|
|
3072
|
-
reason: started,
|
|
3073
|
-
launchTargetPath,
|
|
3074
|
-
incoming,
|
|
3075
|
-
images: ["Cursor"]
|
|
3076
|
-
});
|
|
3077
|
-
return result(installed, target, "ok", `staged ${target} for next launch \u2014 Cursor started during staging (${started}) \u2014 ${pending}`, true);
|
|
3078
|
-
}
|
|
3079
|
-
let displaced = false;
|
|
3080
|
-
try {
|
|
3081
|
-
if ((0, import_node_fs17.existsSync)(live)) {
|
|
3082
|
-
(0, import_node_fs17.renameSync)(live, quarantined);
|
|
3083
|
-
displaced = true;
|
|
3084
|
-
}
|
|
3085
|
-
(0, import_node_fs17.renameSync)(incoming, live);
|
|
3086
|
-
} catch (error) {
|
|
3087
|
-
if (displaced && !(0, import_node_fs17.existsSync)(live)) {
|
|
3088
|
-
try {
|
|
3089
|
-
(0, import_node_fs17.renameSync)(quarantined, live);
|
|
3090
|
-
} catch {
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
discard(incoming);
|
|
3094
|
-
const reason = message(error);
|
|
3095
|
-
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(paths, env, { surface: "cursor", target, reason, images: ["Cursor"] })}` : "";
|
|
3096
|
-
return result(installed, target, "defer", `generation switch failed, previous generation retained: ${reason}${pending}`);
|
|
3097
|
-
}
|
|
3098
|
-
const evidence = manifest(live)?.version;
|
|
3099
|
-
if (evidence !== target || !treeHealthy(live)) {
|
|
3100
|
-
const broken = `${(0, import_node_path11.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
|
|
3101
|
-
const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
|
|
3102
|
-
const detail = outcome === "restored" ? `restored the previous generation ${installed ?? "unknown"}, unverified tree kept at ${broken}` : outcome === "lost" ? `restore failed and ${live} is missing \u2014 the previous generation is at ${quarantined}` : displaced ? `restore failed, the unverified tree is still live and the previous generation is at ${quarantined}` : "nothing was displaced \u2014 there is no previous generation to restore";
|
|
3103
|
-
writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
|
|
3104
|
-
return result(installed, outcome === "restored" ? installed ?? "unknown" : evidence ?? "unknown", "fail", `post-switch marker reads ${evidence ?? "missing"}, expected ${target}; ${detail}`);
|
|
3105
|
-
}
|
|
3106
|
-
writeRollbackPointer(root, { installed: target, previous: installed, generation: displaced ? quarantined : null });
|
|
3107
|
-
clearLaunchTarget(launchTargetPath);
|
|
3108
|
-
const pruned = pruneQuarantine(root, displaced ? quarantined : null);
|
|
3109
|
-
const previous = displaced ? `previous generation quarantined at ${quarantined}` : "no previous generation to quarantine";
|
|
3110
|
-
return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os7.homedir)())}`);
|
|
3111
|
-
}
|
|
3112
|
-
function probeCursorLaunchStaging() {
|
|
3113
|
-
return probeLaunchTargetVersion((0, import_node_path11.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
2686
|
// src/arm-jervcode.ts
|
|
3117
|
-
var import_node_fs18 = require("node:fs");
|
|
3118
|
-
var import_node_path12 = require("node:path");
|
|
3119
2687
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
3120
2688
|
function hostEnv(env) {
|
|
3121
2689
|
const dir = jervcodeAgentDir(env);
|
|
@@ -3126,7 +2694,7 @@ function hostCommands(_env) {
|
|
|
3126
2694
|
}
|
|
3127
2695
|
function readPackageSpecs(env) {
|
|
3128
2696
|
try {
|
|
3129
|
-
const settings = JSON.parse((0,
|
|
2697
|
+
const settings = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
3130
2698
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
3131
2699
|
} catch {
|
|
3132
2700
|
return null;
|
|
@@ -3134,14 +2702,14 @@ function readPackageSpecs(env) {
|
|
|
3134
2702
|
}
|
|
3135
2703
|
function installedVersion(env) {
|
|
3136
2704
|
try {
|
|
3137
|
-
const
|
|
3138
|
-
return typeof
|
|
2705
|
+
const manifest = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
|
|
2706
|
+
return typeof manifest.version === "string" ? manifest.version : null;
|
|
3139
2707
|
} catch {
|
|
3140
2708
|
return null;
|
|
3141
2709
|
}
|
|
3142
2710
|
}
|
|
3143
2711
|
function materializedManifestLocation(env) {
|
|
3144
|
-
return (0,
|
|
2712
|
+
return (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
3145
2713
|
}
|
|
3146
2714
|
var PI_LIST_SPEC = new RegExp(`npm:${PI_PACKAGE.replace("/", "\\/")}@(\\S+)`);
|
|
3147
2715
|
function parseListText(stdout) {
|
|
@@ -3200,32 +2768,32 @@ function jervcodeArm(options) {
|
|
|
3200
2768
|
detail,
|
|
3201
2769
|
// C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
|
|
3202
2770
|
// carry the previous payload's declaration (#4973 reconsult ruling).
|
|
3203
|
-
compat: readManifestCompat((0,
|
|
2771
|
+
compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
|
|
3204
2772
|
});
|
|
3205
2773
|
const before = readPackageSpecs(env);
|
|
3206
|
-
if (!before) return result(null, options.target, "defer", `cannot read ${(0,
|
|
2774
|
+
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
3207
2775
|
const from = installedVersion(env);
|
|
3208
2776
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
3209
2777
|
const highWater = journalHighWater(options.paths.journalPath, "jervcode", options.journalEvents);
|
|
3210
2778
|
const floor = from && compareSemver(from, options.target) > 0 ? `installed ${from} is above candidate ${options.target} (monotonic)` : highWater && compareSemver(highWater, options.target) > 0 ? `journal high-water ${highWater} is above candidate ${options.target} (monotonic)` : null;
|
|
3211
2779
|
if (options.dryRun) {
|
|
3212
2780
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
3213
|
-
return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0,
|
|
2781
|
+
return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
3214
2782
|
}
|
|
3215
2783
|
const hosted = hostEnv(env);
|
|
3216
2784
|
const commands = hostCommands(env);
|
|
3217
2785
|
const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
3218
|
-
if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0,
|
|
2786
|
+
if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
3219
2787
|
if (!floor && (!before.includes(exact) || from !== options.target)) {
|
|
3220
2788
|
const install = runner(command, ["install", exact], hosted);
|
|
3221
2789
|
if (install.status !== 0) {
|
|
3222
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0,
|
|
2790
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
3223
2791
|
}
|
|
3224
2792
|
}
|
|
3225
2793
|
for (const stale of legacy) {
|
|
3226
2794
|
const removed = runner(command, ["remove", stale], hosted);
|
|
3227
2795
|
if (removed.status !== 0) {
|
|
3228
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0,
|
|
2796
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
3229
2797
|
}
|
|
3230
2798
|
}
|
|
3231
2799
|
const after = readPackageSpecs(env);
|
|
@@ -3233,7 +2801,7 @@ function jervcodeArm(options) {
|
|
|
3233
2801
|
const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
|
|
3234
2802
|
const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
|
|
3235
2803
|
if (floor && !legacyAfter.length) {
|
|
3236
|
-
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0,
|
|
2804
|
+
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
3237
2805
|
}
|
|
3238
2806
|
if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
|
|
3239
2807
|
return {
|
|
@@ -3254,14 +2822,14 @@ function jervcodeArm(options) {
|
|
|
3254
2822
|
}
|
|
3255
2823
|
|
|
3256
2824
|
// src/arm-hermes.ts
|
|
3257
|
-
var
|
|
3258
|
-
var
|
|
3259
|
-
var
|
|
3260
|
-
var
|
|
3261
|
-
var TREE_MARKERS_FALLBACK = [
|
|
2825
|
+
var import_node_fs17 = require("node:fs");
|
|
2826
|
+
var import_node_path12 = require("node:path");
|
|
2827
|
+
var PACKAGE = "@mutmutco/hermes-plugin";
|
|
2828
|
+
var MANIFEST = "plugin.yaml";
|
|
2829
|
+
var TREE_MARKERS_FALLBACK = [MANIFEST, "__init__.py", "skills"];
|
|
3262
2830
|
function yamlVersion(root) {
|
|
3263
2831
|
try {
|
|
3264
|
-
const text = (0,
|
|
2832
|
+
const text = (0, import_node_fs17.readFileSync)((0, import_node_path12.join)(root, MANIFEST), "utf8");
|
|
3265
2833
|
try {
|
|
3266
2834
|
const version = JSON.parse(text).version;
|
|
3267
2835
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -3275,7 +2843,7 @@ function yamlVersion(root) {
|
|
|
3275
2843
|
}
|
|
3276
2844
|
function packageTreeMarkers(root) {
|
|
3277
2845
|
try {
|
|
3278
|
-
const files = JSON.parse((0,
|
|
2846
|
+
const files = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).files;
|
|
3279
2847
|
if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
3280
2848
|
return files.map((entry) => entry.trim());
|
|
3281
2849
|
}
|
|
@@ -3284,10 +2852,10 @@ function packageTreeMarkers(root) {
|
|
|
3284
2852
|
return TREE_MARKERS_FALLBACK;
|
|
3285
2853
|
}
|
|
3286
2854
|
function markerPresent(root, rel) {
|
|
3287
|
-
const path = (0,
|
|
3288
|
-
if (!(0,
|
|
2855
|
+
const path = (0, import_node_path12.join)(root, ...rel.split("/"));
|
|
2856
|
+
if (!(0, import_node_fs17.existsSync)(path)) return false;
|
|
3289
2857
|
try {
|
|
3290
|
-
const stat = (0,
|
|
2858
|
+
const stat = (0, import_node_fs17.statSync)(path);
|
|
3291
2859
|
return stat.isFile() || stat.isDirectory();
|
|
3292
2860
|
} catch {
|
|
3293
2861
|
return false;
|
|
@@ -3297,7 +2865,7 @@ function treeHealth(root) {
|
|
|
3297
2865
|
const missing = packageTreeMarkers(root).filter((rel) => !markerPresent(root, rel));
|
|
3298
2866
|
return { healthy: missing.length === 0, missing };
|
|
3299
2867
|
}
|
|
3300
|
-
function
|
|
2868
|
+
function treeHealthy(root) {
|
|
3301
2869
|
return treeHealth(root).healthy;
|
|
3302
2870
|
}
|
|
3303
2871
|
function treeIncompleteDetail(root, label) {
|
|
@@ -3306,45 +2874,45 @@ function treeIncompleteDetail(root, label) {
|
|
|
3306
2874
|
}
|
|
3307
2875
|
function packageIdentity(root) {
|
|
3308
2876
|
try {
|
|
3309
|
-
return JSON.parse((0,
|
|
2877
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).name === PACKAGE;
|
|
3310
2878
|
} catch {
|
|
3311
2879
|
return false;
|
|
3312
2880
|
}
|
|
3313
2881
|
}
|
|
3314
2882
|
function probeHermesInstallation(env = process.env) {
|
|
3315
|
-
const location = (0,
|
|
2883
|
+
const location = (0, import_node_path12.join)(hermesHome(env), "plugins", "mmi");
|
|
3316
2884
|
const version = yamlVersion(location);
|
|
3317
2885
|
return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
|
|
3318
2886
|
}
|
|
3319
|
-
function
|
|
2887
|
+
function mmiOwned(root) {
|
|
3320
2888
|
return packageIdentity(root);
|
|
3321
2889
|
}
|
|
3322
|
-
function
|
|
2890
|
+
function discard(path) {
|
|
3323
2891
|
try {
|
|
3324
|
-
(0,
|
|
2892
|
+
(0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
|
|
3325
2893
|
} catch {
|
|
3326
2894
|
}
|
|
3327
2895
|
}
|
|
3328
|
-
function
|
|
2896
|
+
function message(error) {
|
|
3329
2897
|
return String(error?.message ?? error).replace(/\s+/g, " ").slice(0, 200);
|
|
3330
2898
|
}
|
|
3331
|
-
function materializeHermesSkills(
|
|
3332
|
-
const pluginSkills = (0,
|
|
3333
|
-
const skillsRoot = (0,
|
|
3334
|
-
const targetRoot = (0,
|
|
2899
|
+
function materializeHermesSkills(home2) {
|
|
2900
|
+
const pluginSkills = (0, import_node_path12.join)(home2, "plugins", "mmi", "skills");
|
|
2901
|
+
const skillsRoot = (0, import_node_path12.join)(home2, "skills");
|
|
2902
|
+
const targetRoot = (0, import_node_path12.join)(skillsRoot, "mmi");
|
|
3335
2903
|
try {
|
|
3336
|
-
if (!(0,
|
|
3337
|
-
const names = (0,
|
|
2904
|
+
if (!(0, import_node_fs17.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
2905
|
+
const names = (0, import_node_fs17.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
3338
2906
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
3339
|
-
(0,
|
|
3340
|
-
const incoming = (0,
|
|
3341
|
-
|
|
3342
|
-
(0,
|
|
3343
|
-
|
|
3344
|
-
(0,
|
|
2907
|
+
(0, import_node_fs17.mkdirSync)(skillsRoot, { recursive: true });
|
|
2908
|
+
const incoming = (0, import_node_path12.join)(skillsRoot, ".mmi-incoming");
|
|
2909
|
+
discard(incoming);
|
|
2910
|
+
(0, import_node_fs17.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
2911
|
+
discard(targetRoot);
|
|
2912
|
+
(0, import_node_fs17.renameSync)(incoming, targetRoot);
|
|
3345
2913
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
3346
2914
|
} catch (error) {
|
|
3347
|
-
return { ok: false, detail:
|
|
2915
|
+
return { ok: false, detail: message(error) };
|
|
3348
2916
|
}
|
|
3349
2917
|
}
|
|
3350
2918
|
function enableHermesPlugin(env, runner) {
|
|
@@ -3353,18 +2921,18 @@ function enableHermesPlugin(env, runner) {
|
|
|
3353
2921
|
try {
|
|
3354
2922
|
result = runner(command, ["plugins", "enable", "mmi", "--no-allow-tool-override"], env);
|
|
3355
2923
|
} catch (error) {
|
|
3356
|
-
return { ok: false, detail: `\`${command} plugins enable mmi\` could not run: ${
|
|
2924
|
+
return { ok: false, detail: `\`${command} plugins enable mmi\` could not run: ${message(error)}` };
|
|
3357
2925
|
}
|
|
3358
2926
|
if (result.status === 0) return { ok: true, detail: "enabled in plugins.enabled" };
|
|
3359
|
-
const reason =
|
|
2927
|
+
const reason = message(result.stderr?.trim() || result.stdout?.trim() || `exit ${result.status ?? "unknown"}`);
|
|
3360
2928
|
return { ok: false, detail: `\`${command} plugins enable mmi\` failed: ${reason}` };
|
|
3361
2929
|
}
|
|
3362
2930
|
function hermesHomes(env = process.env) {
|
|
3363
2931
|
const root = hermesHome(env);
|
|
3364
2932
|
const homes = [root];
|
|
3365
2933
|
try {
|
|
3366
|
-
for (const entry of (0,
|
|
3367
|
-
if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0,
|
|
2934
|
+
for (const entry of (0, import_node_fs17.readdirSync)((0, import_node_path12.join)(root, "profiles"), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2935
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path12.join)(root, "profiles", entry.name));
|
|
3368
2936
|
}
|
|
3369
2937
|
} catch {
|
|
3370
2938
|
}
|
|
@@ -3383,14 +2951,14 @@ function hermesArm(options) {
|
|
|
3383
2951
|
};
|
|
3384
2952
|
}
|
|
3385
2953
|
const homes = hermesHomes(env);
|
|
3386
|
-
const results = homes.map((
|
|
3387
|
-
home,
|
|
3388
|
-
result: armHermesHome(
|
|
2954
|
+
const results = homes.map((home2, index) => ({
|
|
2955
|
+
home: home2,
|
|
2956
|
+
result: armHermesHome(home2, { ...env, HERMES_HOME: home2 }, options, index === 0)
|
|
3389
2957
|
}));
|
|
3390
2958
|
const primary = results[0].result;
|
|
3391
2959
|
if (results.length === 1) return primary;
|
|
3392
2960
|
const worst = results.reduce((a, b) => VERDICT_RANK[b.result.verdict] > VERDICT_RANK[a.result.verdict] ? b : a);
|
|
3393
|
-
const detail = results.map(({ home, result }, index) => `${index === 0 ? "home" : `profile ${(0,
|
|
2961
|
+
const detail = results.map(({ home: home2, result }, index) => `${index === 0 ? "home" : `profile ${(0, import_node_path12.basename)(home2)}`}: ${result.detail}`).join(" | ");
|
|
3394
2962
|
return {
|
|
3395
2963
|
surface: "hermes",
|
|
3396
2964
|
from: primary.from,
|
|
@@ -3400,9 +2968,9 @@ function hermesArm(options) {
|
|
|
3400
2968
|
launchStaged: results.some(({ result }) => result.launchStaged) || void 0
|
|
3401
2969
|
};
|
|
3402
2970
|
}
|
|
3403
|
-
function armHermesHome(
|
|
3404
|
-
const parent = (0,
|
|
3405
|
-
const live = (0,
|
|
2971
|
+
function armHermesHome(home2, env, options, primary) {
|
|
2972
|
+
const parent = (0, import_node_path12.join)(home2, "plugins");
|
|
2973
|
+
const live = (0, import_node_path12.join)(parent, "mmi");
|
|
3406
2974
|
const installed = yamlVersion(live);
|
|
3407
2975
|
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
3408
2976
|
surface: "hermes",
|
|
@@ -3412,22 +2980,22 @@ function armHermesHome(home, env, options, primary) {
|
|
|
3412
2980
|
detail,
|
|
3413
2981
|
launchStaged: launchStaged || void 0
|
|
3414
2982
|
});
|
|
3415
|
-
if ((0,
|
|
3416
|
-
return result(installed, options.target, "defer", `${live} carries no ${
|
|
2983
|
+
if ((0, import_node_fs17.existsSync)(live) && !mmiOwned(live)) {
|
|
2984
|
+
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
3417
2985
|
}
|
|
3418
2986
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
3419
2987
|
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes", options.journalEvents) : null;
|
|
3420
2988
|
if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
|
|
3421
2989
|
const hostRunner = options.hostCommandRunner ?? runHostCommand;
|
|
3422
|
-
if (installed === options.target &&
|
|
3423
|
-
const skills2 = materializeHermesSkills(
|
|
2990
|
+
if (installed === options.target && treeHealthy(live)) {
|
|
2991
|
+
const skills2 = materializeHermesSkills(home2);
|
|
3424
2992
|
if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
|
|
3425
2993
|
const enabled2 = enableHermesPlugin(env, hostRunner);
|
|
3426
2994
|
if (!enabled2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but ${enabled2.detail}`);
|
|
3427
2995
|
return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}; ${enabled2.detail}`);
|
|
3428
2996
|
}
|
|
3429
|
-
if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${
|
|
3430
|
-
const staged = stagePackage({ surface: "hermes", packageName:
|
|
2997
|
+
if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, provision skills into ${(0, import_node_path12.join)(home2, "skills", "mmi")}, then enable mmi in plugins.enabled`);
|
|
2998
|
+
const staged = stagePackage({ surface: "hermes", packageName: PACKAGE, target: options.target, witness: MANIFEST, paths: options.paths, env });
|
|
3431
2999
|
if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
|
|
3432
3000
|
const stagedVersion = yamlVersion(staged.packageRoot);
|
|
3433
3001
|
if (stagedVersion !== options.target) {
|
|
@@ -3435,82 +3003,346 @@ function armHermesHome(home, env, options, primary) {
|
|
|
3435
3003
|
}
|
|
3436
3004
|
const stagedTree = treeIncompleteDetail(staged.packageRoot, "staged Hermes");
|
|
3437
3005
|
if (stagedTree) return result(installed, options.target, "defer", stagedTree);
|
|
3438
|
-
const scratch = (0,
|
|
3439
|
-
const launchTargetPath = (0,
|
|
3440
|
-
const incomingRoot = (0,
|
|
3441
|
-
const incoming = (0,
|
|
3442
|
-
const quarantine = (0,
|
|
3006
|
+
const scratch = (0, import_node_path12.join)(home2, ".mmi-updater");
|
|
3007
|
+
const launchTargetPath = (0, import_node_path12.join)(scratch, "launch-target.json");
|
|
3008
|
+
const incomingRoot = (0, import_node_path12.join)(scratch, "incoming");
|
|
3009
|
+
const incoming = (0, import_node_path12.join)(incomingRoot, options.target);
|
|
3010
|
+
const quarantine = (0, import_node_path12.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
|
|
3443
3011
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
3444
|
-
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target &&
|
|
3012
|
+
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy(incoming);
|
|
3445
3013
|
if (!reuseIncoming) {
|
|
3446
3014
|
try {
|
|
3447
|
-
(0,
|
|
3448
|
-
(0,
|
|
3449
|
-
|
|
3450
|
-
(0,
|
|
3015
|
+
(0, import_node_fs17.mkdirSync)(parent, { recursive: true });
|
|
3016
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path12.join)(scratch, "quarantine"), { recursive: true });
|
|
3017
|
+
discard(incomingRoot);
|
|
3018
|
+
(0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
3451
3019
|
} catch (error) {
|
|
3452
|
-
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${
|
|
3020
|
+
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message(error)}`);
|
|
3453
3021
|
}
|
|
3454
3022
|
const incomingVersion = yamlVersion(incoming);
|
|
3455
3023
|
if (incomingVersion !== options.target) {
|
|
3456
|
-
|
|
3024
|
+
discard(incomingRoot);
|
|
3457
3025
|
return result(installed, options.target, "defer", `incoming Hermes manifest is ${incomingVersion ?? "unreadable"}, expected ${options.target}`);
|
|
3458
3026
|
}
|
|
3459
3027
|
const incomingTree = treeIncompleteDetail(incoming, "incoming Hermes");
|
|
3460
3028
|
if (incomingTree) {
|
|
3461
|
-
|
|
3029
|
+
discard(incomingRoot);
|
|
3462
3030
|
return result(installed, options.target, "defer", incomingTree);
|
|
3463
3031
|
}
|
|
3464
3032
|
}
|
|
3465
|
-
const displaced = (0,
|
|
3033
|
+
const displaced = (0, import_node_fs17.existsSync)(live);
|
|
3466
3034
|
try {
|
|
3467
|
-
(0,
|
|
3468
|
-
if (displaced) (0,
|
|
3469
|
-
(0,
|
|
3035
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path12.join)(scratch, "quarantine"), { recursive: true });
|
|
3036
|
+
if (displaced) (0, import_node_fs17.renameSync)(live, quarantine);
|
|
3037
|
+
(0, import_node_fs17.renameSync)(incoming, live);
|
|
3470
3038
|
} catch (error) {
|
|
3471
|
-
if (displaced && !(0,
|
|
3472
|
-
(0,
|
|
3039
|
+
if (displaced && !(0, import_node_fs17.existsSync)(live)) try {
|
|
3040
|
+
(0, import_node_fs17.renameSync)(quarantine, live);
|
|
3473
3041
|
} catch {
|
|
3474
3042
|
}
|
|
3475
|
-
|
|
3476
|
-
const reason =
|
|
3477
|
-
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason, images: ["hermes"], dirs: [
|
|
3043
|
+
discard(incomingRoot);
|
|
3044
|
+
const reason = message(error);
|
|
3045
|
+
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason, images: ["hermes"], dirs: [home2] })}` : "";
|
|
3478
3046
|
return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${reason}${pending}`);
|
|
3479
3047
|
}
|
|
3480
3048
|
const liveVersion = yamlVersion(live);
|
|
3481
3049
|
const liveTree = treeIncompleteDetail(live, "live Hermes");
|
|
3482
3050
|
if (liveVersion !== options.target || liveTree) {
|
|
3483
|
-
const broken = (0,
|
|
3051
|
+
const broken = (0, import_node_path12.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
3484
3052
|
const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
|
|
3485
3053
|
try {
|
|
3486
|
-
(0,
|
|
3487
|
-
(0,
|
|
3488
|
-
if (displaced) (0,
|
|
3054
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path12.join)(scratch, "rejected"), { recursive: true });
|
|
3055
|
+
(0, import_node_fs17.renameSync)(live, broken);
|
|
3056
|
+
if (displaced) (0, import_node_fs17.renameSync)(quarantine, live);
|
|
3489
3057
|
} catch (error) {
|
|
3490
|
-
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${
|
|
3058
|
+
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message(error)}`);
|
|
3491
3059
|
}
|
|
3492
3060
|
return result(installed, displaced ? installed ?? "unknown" : "none", "fail", `post-switch Hermes verification failed (${verifyDetail}); rejected generation parked at ${broken}${displaced ? "; previous generation restored" : ""}`);
|
|
3493
3061
|
}
|
|
3494
|
-
|
|
3062
|
+
discard(incomingRoot);
|
|
3495
3063
|
clearLaunchTarget(launchTargetPath);
|
|
3496
|
-
const skills = materializeHermesSkills(
|
|
3064
|
+
const skills = materializeHermesSkills(home2);
|
|
3497
3065
|
if (!skills.ok) return result(installed, options.target, "fail", `plugin converged to ${options.target}, but skills provisioning failed: ${skills.detail}`);
|
|
3498
3066
|
const enabled = enableHermesPlugin(env, hostRunner);
|
|
3499
3067
|
if (!enabled.ok) return result(installed, options.target, "fail", `plugin converged to ${options.target}, but ${enabled.detail}`);
|
|
3500
3068
|
return result(installed, options.target, "ok", `converged ${installed ?? "absent"} -> ${options.target} (staged, verified, switched${displaced ? `; previous generation quarantined at ${quarantine}` : ""}); ${skills.detail}; ${enabled.detail}`);
|
|
3501
3069
|
}
|
|
3502
3070
|
function probeHermesLaunchStaging(env = process.env) {
|
|
3503
|
-
return probeLaunchTargetVersion((0,
|
|
3071
|
+
return probeLaunchTargetVersion((0, import_node_path12.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
|
|
3504
3072
|
}
|
|
3505
3073
|
|
|
3506
3074
|
// src/reap.ts
|
|
3507
3075
|
var import_node_fs20 = require("node:fs");
|
|
3508
3076
|
var import_node_path14 = require("node:path");
|
|
3509
3077
|
var import_node_perf_hooks = require("node:perf_hooks");
|
|
3078
|
+
|
|
3079
|
+
// src/retire-cursor.ts
|
|
3080
|
+
var import_node_fs19 = require("node:fs");
|
|
3081
|
+
var import_node_os7 = require("node:os");
|
|
3082
|
+
var import_node_path13 = require("node:path");
|
|
3083
|
+
|
|
3084
|
+
// src/host-doc-lock.ts
|
|
3085
|
+
var import_node_fs18 = require("node:fs");
|
|
3086
|
+
var import_node_os6 = require("node:os");
|
|
3087
|
+
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
3088
|
+
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
3089
|
+
var SHARED_DOC_LOCK_INTERVAL_MS = 100;
|
|
3090
|
+
function sharedDocLockPath(documentPath) {
|
|
3091
|
+
return `${documentPath}.jerv-lock`;
|
|
3092
|
+
}
|
|
3093
|
+
function lockContent(options) {
|
|
3094
|
+
return `${JSON.stringify(options, null, 2)}
|
|
3095
|
+
`;
|
|
3096
|
+
}
|
|
3097
|
+
function readHolder(lockPath) {
|
|
3098
|
+
try {
|
|
3099
|
+
const parsed = JSON.parse((0, import_node_fs18.readFileSync)(lockPath, "utf8"));
|
|
3100
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
3101
|
+
return parsed;
|
|
3102
|
+
} catch {
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
function lockAgeMs(lockPath, now) {
|
|
3107
|
+
const holder = readHolder(lockPath);
|
|
3108
|
+
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
3109
|
+
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
3110
|
+
try {
|
|
3111
|
+
return Math.max(0, now - (0, import_node_fs18.statSync)(lockPath).mtimeMs);
|
|
3112
|
+
} catch {
|
|
3113
|
+
return 0;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
function sharedDocLockTimeoutReceipt(documentPath, lockPath, holder, waitedMs) {
|
|
3117
|
+
const named = holder && typeof holder === "object";
|
|
3118
|
+
const who = named ? `held by ${String(holder.owner ?? "unknown")} (pid ${String(holder.pid ?? "?")}, host ${String(holder.host ?? "?")})` : `held by an unreadable lock at ${lockPath}`;
|
|
3119
|
+
return `shared-document lock timeout: ${documentPath} is ${who} \u2014 waited ${waitedMs}ms, nothing was written \u2014 retry next tick or remove ${lockPath} if stale`;
|
|
3120
|
+
}
|
|
3121
|
+
function acquireSharedDocLock(documentPath, options) {
|
|
3122
|
+
const lockPath = sharedDocLockPath(documentPath);
|
|
3123
|
+
const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
|
|
3124
|
+
const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
|
|
3125
|
+
const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
|
|
3126
|
+
const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os6.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3127
|
+
const content = lockContent(identity);
|
|
3128
|
+
const sleep = (ms) => {
|
|
3129
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
3130
|
+
};
|
|
3131
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
3132
|
+
let fd;
|
|
3133
|
+
try {
|
|
3134
|
+
fd = (0, import_node_fs18.openSync)(lockPath, "wx");
|
|
3135
|
+
(0, import_node_fs18.writeSync)(fd, content);
|
|
3136
|
+
(0, import_node_fs18.closeSync)(fd);
|
|
3137
|
+
return {
|
|
3138
|
+
documentPath,
|
|
3139
|
+
lockPath,
|
|
3140
|
+
owner: identity.owner,
|
|
3141
|
+
pid: identity.pid,
|
|
3142
|
+
host: identity.host,
|
|
3143
|
+
acquiredAt: identity.acquiredAt,
|
|
3144
|
+
release: () => releaseSharedDocLock({ lockPath, owner: identity.owner, pid: identity.pid })
|
|
3145
|
+
};
|
|
3146
|
+
} catch (error) {
|
|
3147
|
+
if (fd !== void 0) {
|
|
3148
|
+
try {
|
|
3149
|
+
(0, import_node_fs18.closeSync)(fd);
|
|
3150
|
+
} catch {
|
|
3151
|
+
}
|
|
3152
|
+
try {
|
|
3153
|
+
(0, import_node_fs18.unlinkSync)(lockPath);
|
|
3154
|
+
} catch {
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
if (error?.code !== "EEXIST") throw error;
|
|
3158
|
+
}
|
|
3159
|
+
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
3160
|
+
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
3161
|
+
try {
|
|
3162
|
+
(0, import_node_fs18.renameSync)(lockPath, aside);
|
|
3163
|
+
} catch {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
try {
|
|
3167
|
+
(0, import_node_fs18.rmSync)(aside, { force: true });
|
|
3168
|
+
} catch {
|
|
3169
|
+
}
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
if (attempt < attempts - 1) sleep(intervalMs);
|
|
3173
|
+
}
|
|
3174
|
+
throw new Error(sharedDocLockTimeoutReceipt(documentPath, lockPath, readHolder(lockPath), Math.max(0, attempts * intervalMs)));
|
|
3175
|
+
}
|
|
3176
|
+
function releaseSharedDocLock(handle) {
|
|
3177
|
+
try {
|
|
3178
|
+
const holder = readHolder(handle.lockPath);
|
|
3179
|
+
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
3180
|
+
(0, import_node_fs18.unlinkSync)(handle.lockPath);
|
|
3181
|
+
}
|
|
3182
|
+
} catch {
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
function withSharedDocLock(documentPath, options, fn) {
|
|
3186
|
+
const lock = acquireSharedDocLock(documentPath, options);
|
|
3187
|
+
try {
|
|
3188
|
+
return fn(lock.documentPath);
|
|
3189
|
+
} finally {
|
|
3190
|
+
lock.release();
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
// src/retire-cursor.ts
|
|
3195
|
+
var PACKAGE2 = "@mutmutco/cursor-plugin";
|
|
3196
|
+
var MANIFEST2 = [".cursor-plugin", "plugin.json"];
|
|
3197
|
+
var USER_HOOKS_FILE = "hooks.json";
|
|
3198
|
+
var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
3199
|
+
var CANONICAL_REMOTE = /github\.com[:/]mutmutco\/MMI-Hub(?:\.git)?\s*$/im;
|
|
3200
|
+
function home(env) {
|
|
3201
|
+
return env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os7.homedir)();
|
|
3202
|
+
}
|
|
3203
|
+
function readJson(path) {
|
|
3204
|
+
try {
|
|
3205
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
|
|
3206
|
+
} catch {
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
function mmiOwned2(root) {
|
|
3211
|
+
if (readJson((0, import_node_path13.join)(root, ...MANIFEST2))?.name === "mmi") return true;
|
|
3212
|
+
if (readJson((0, import_node_path13.join)(root, "package.json"))?.name === PACKAGE2) return true;
|
|
3213
|
+
try {
|
|
3214
|
+
return CANONICAL_REMOTE.test((0, import_node_fs19.readFileSync)((0, import_node_path13.join)(root, ".git", "config"), "utf8").split(/\[remote "origin"\]/)[1]?.split(/\n\[/)[0] ?? "");
|
|
3215
|
+
} catch {
|
|
3216
|
+
return false;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function entries(dir) {
|
|
3220
|
+
try {
|
|
3221
|
+
return (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true }).map((entry) => ({ name: entry.name, dir: entry.isDirectory() }));
|
|
3222
|
+
} catch {
|
|
3223
|
+
return [];
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
function isMmiEntry(entry, pluginRoot) {
|
|
3227
|
+
if (typeof entry !== "object" || entry === null || typeof entry.command !== "string") return false;
|
|
3228
|
+
const command = entry.command;
|
|
3229
|
+
return command.includes((0, import_node_path13.join)(pluginRoot).replace(/\\/g, "/")) || /(?:mmi-hook|hook-run\.mjs|hook-policy\.mjs)/i.test(command);
|
|
3230
|
+
}
|
|
3231
|
+
function stripUserHooks(existing, pluginRoot) {
|
|
3232
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) return null;
|
|
3233
|
+
const stripped = { ...existing };
|
|
3234
|
+
const hooks = stripped.hooks;
|
|
3235
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
3236
|
+
const nextHooks = { ...hooks };
|
|
3237
|
+
for (const [event, list] of Object.entries(hooks)) {
|
|
3238
|
+
if (!Array.isArray(list)) return null;
|
|
3239
|
+
const kept = list.filter((entry) => !isMmiEntry(entry, pluginRoot));
|
|
3240
|
+
if (kept.length) nextHooks[event] = kept;
|
|
3241
|
+
else delete nextHooks[event];
|
|
3242
|
+
}
|
|
3243
|
+
stripped.hooks = nextHooks;
|
|
3244
|
+
return stripped;
|
|
3245
|
+
}
|
|
3246
|
+
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
3247
|
+
const path = (0, import_node_path13.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
3248
|
+
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
3249
|
+
if (!(0, import_node_fs19.existsSync)(path)) return "current";
|
|
3250
|
+
let existing;
|
|
3251
|
+
try {
|
|
3252
|
+
existing = JSON.parse((0, import_node_fs19.readFileSync)(path, "utf8"));
|
|
3253
|
+
} catch {
|
|
3254
|
+
return "user-conflict";
|
|
3255
|
+
}
|
|
3256
|
+
const stripped = stripUserHooks(existing, pluginRoot);
|
|
3257
|
+
if (!stripped) return "user-conflict";
|
|
3258
|
+
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
3259
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
3260
|
+
(0, import_node_fs19.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
3261
|
+
(0, import_node_fs19.renameSync)(tmp, path);
|
|
3262
|
+
return "updated";
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
function retireCursor(options) {
|
|
3266
|
+
const category = "cursor-retire";
|
|
3267
|
+
const env = options.env ?? process.env;
|
|
3268
|
+
const { paths, dryRun } = options;
|
|
3269
|
+
const homeDir = home(env);
|
|
3270
|
+
const plugins = (0, import_node_path13.join)(homeDir, ".cursor", "plugins");
|
|
3271
|
+
const live = (0, import_node_path13.join)(plugins, "local", "mmi");
|
|
3272
|
+
const doomed = [];
|
|
3273
|
+
const foreign = [];
|
|
3274
|
+
if ((0, import_node_fs19.existsSync)(live)) (mmiOwned2(live) ? doomed : foreign).push(live);
|
|
3275
|
+
for (const sub of ["staging", "quarantine"]) {
|
|
3276
|
+
for (const entry of entries((0, import_node_path13.join)(plugins, sub))) {
|
|
3277
|
+
const ours = entry.dir ? entry.name.startsWith("mmi-") : /^(?:launch-target|rollback)\.json(?:\.tmp-[0-9A-Za-z.-]+)?$/.test(entry.name);
|
|
3278
|
+
if (ours) doomed.push((0, import_node_path13.join)(plugins, sub, entry.name));
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
const updaterStaging = (0, import_node_path13.join)(paths.staging, "cursor");
|
|
3282
|
+
if ((0, import_node_fs19.existsSync)(updaterStaging)) doomed.push(updaterStaging);
|
|
3283
|
+
const pending = readPending(paths).some((row) => row.surface === "cursor");
|
|
3284
|
+
const hooksCandidate = doomed.length > 0 && (0, import_node_fs19.existsSync)((0, import_node_path13.join)(homeDir, ".cursor", USER_HOOKS_FILE));
|
|
3285
|
+
const note = foreign.length ? `; left unowned ${foreign.join(", ")}` : "";
|
|
3286
|
+
if (!doomed.length && !pending) {
|
|
3287
|
+
return { category, reaped: 0, verdict: "skip", detail: `no MMI Cursor footprint under ${plugins}${note}` };
|
|
3288
|
+
}
|
|
3289
|
+
if (dryRun) {
|
|
3290
|
+
return { category, reaped: doomed.length, verdict: "ok", detail: `dry-run: would remove ${doomed.length} MMI Cursor path(s)${pending ? " and the pending cursor apply" : ""}${note}` };
|
|
3291
|
+
}
|
|
3292
|
+
const failures = [];
|
|
3293
|
+
let hooks = "";
|
|
3294
|
+
if (hooksCandidate) {
|
|
3295
|
+
try {
|
|
3296
|
+
const verdict = reconcileCursorUserHooks(live, homeDir);
|
|
3297
|
+
if (verdict === "updated") hooks = "; stripped MMI legacy entries from ~/.cursor/hooks.json";
|
|
3298
|
+
if (verdict === "user-conflict") hooks = "; ~/.cursor/hooks.json is unparsable or hand-shaped \u2014 left unchanged";
|
|
3299
|
+
} catch (error) {
|
|
3300
|
+
failures.push(`hooks.json: ${error?.message ?? error}`);
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
let reaped = 0;
|
|
3304
|
+
for (const path of doomed) {
|
|
3305
|
+
try {
|
|
3306
|
+
if (path === live) {
|
|
3307
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path13.join)(plugins, "staging"), { recursive: true });
|
|
3308
|
+
const aside = (0, import_node_path13.join)(plugins, "staging", `mmi-retired-${Date.now()}`);
|
|
3309
|
+
(0, import_node_fs19.renameSync)(live, aside);
|
|
3310
|
+
(0, import_node_fs19.rmSync)(aside, { recursive: true, force: true });
|
|
3311
|
+
} else {
|
|
3312
|
+
(0, import_node_fs19.rmSync)(path, { recursive: true, force: true });
|
|
3313
|
+
}
|
|
3314
|
+
reaped += 1;
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
failures.push(`${path}: ${error?.code ?? error?.message ?? error}`);
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
for (const sub of ["staging", "quarantine", "local"]) {
|
|
3320
|
+
try {
|
|
3321
|
+
if (!entries((0, import_node_path13.join)(plugins, sub)).length) (0, import_node_fs19.rmdirSync)((0, import_node_path13.join)(plugins, sub));
|
|
3322
|
+
} catch {
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
if (pending) {
|
|
3326
|
+
try {
|
|
3327
|
+
clearPending(paths, "cursor");
|
|
3328
|
+
} catch (error) {
|
|
3329
|
+
failures.push(`pending.json: ${error?.message ?? error}`);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
const stuck = failures.length ? `; ${failures.length} deferred (${failures.join(", ")})` : "";
|
|
3333
|
+
return {
|
|
3334
|
+
category,
|
|
3335
|
+
reaped,
|
|
3336
|
+
verdict: failures.length ? "defer" : "ok",
|
|
3337
|
+
detail: `Cursor is no longer supported (#7778): removed ${reaped} MMI Cursor path(s)${pending ? ", cleared the pending cursor apply" : ""}${hooks}${note}${stuck}`
|
|
3338
|
+
};
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
// src/reap.ts
|
|
3510
3342
|
function elapsedMs(startedAt) {
|
|
3511
3343
|
return Math.round((import_node_perf_hooks.performance.now() - startedAt) * 10) / 10;
|
|
3512
3344
|
}
|
|
3513
|
-
var
|
|
3345
|
+
var KEEP_GENERATIONS = 2;
|
|
3514
3346
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
3515
3347
|
function listDirs(path) {
|
|
3516
3348
|
try {
|
|
@@ -3529,7 +3361,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
3529
3361
|
const failures = [];
|
|
3530
3362
|
for (const set of generationSets(root)) {
|
|
3531
3363
|
const versions = listDirs(set).filter((name) => parseSemver(name)).sort((a, b) => compareSemver(b, a));
|
|
3532
|
-
for (const version of versions.slice(
|
|
3364
|
+
for (const version of versions.slice(KEEP_GENERATIONS)) {
|
|
3533
3365
|
if (dryRun) {
|
|
3534
3366
|
reaped += 1;
|
|
3535
3367
|
continue;
|
|
@@ -3542,10 +3374,10 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
3542
3374
|
}
|
|
3543
3375
|
}
|
|
3544
3376
|
}
|
|
3545
|
-
if (!reaped && !failures.length) return { category, reaped: 0, verdict: "skip", detail: `${root}: within keep-${
|
|
3377
|
+
if (!reaped && !failures.length) return { category, reaped: 0, verdict: "skip", detail: `${root}: within keep-${KEEP_GENERATIONS}` };
|
|
3546
3378
|
const verb = dryRun ? "dry-run: would prune" : "pruned";
|
|
3547
3379
|
const stuck = failures.length ? `; ${failures.length} locked (${failures.join(", ")})` : "";
|
|
3548
|
-
return { category, reaped, verdict: failures.length ? "defer" : "ok", detail: `${verb} ${reaped} generation(s) below keep-${
|
|
3380
|
+
return { category, reaped, verdict: failures.length ? "defer" : "ok", detail: `${verb} ${reaped} generation(s) below keep-${KEEP_GENERATIONS} under ${root}${stuck}` };
|
|
3549
3381
|
}
|
|
3550
3382
|
var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
|
|
3551
3383
|
function atomicWriteScratch(path) {
|
|
@@ -3566,14 +3398,14 @@ function reapScratch(context, dryRun) {
|
|
|
3566
3398
|
const failures = [];
|
|
3567
3399
|
const unreadable = [];
|
|
3568
3400
|
for (const [dir, patterns] of scanned) {
|
|
3569
|
-
let
|
|
3401
|
+
let entries2;
|
|
3570
3402
|
try {
|
|
3571
|
-
|
|
3403
|
+
entries2 = (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true });
|
|
3572
3404
|
} catch {
|
|
3573
3405
|
unreadable.push(dir);
|
|
3574
3406
|
continue;
|
|
3575
3407
|
}
|
|
3576
|
-
for (const entry of
|
|
3408
|
+
for (const entry of entries2) {
|
|
3577
3409
|
if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
|
|
3578
3410
|
if (!entry.isFile()) continue;
|
|
3579
3411
|
const path = (0, import_node_path14.join)(dir, entry.name);
|
|
@@ -3616,7 +3448,8 @@ function reap(options) {
|
|
|
3616
3448
|
const atomicWritePaths = [claudeMarketplacesPath(env)];
|
|
3617
3449
|
return [
|
|
3618
3450
|
guarded("staging-prune", () => pruneGenerations(paths.staging, "staging-prune", dryRun)),
|
|
3619
|
-
guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun))
|
|
3451
|
+
guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun)),
|
|
3452
|
+
guarded("cursor-retire", () => retireCursor({ paths, dryRun, env }))
|
|
3620
3453
|
];
|
|
3621
3454
|
}
|
|
3622
3455
|
|
|
@@ -3784,7 +3617,7 @@ async function reconcile(options) {
|
|
|
3784
3617
|
}
|
|
3785
3618
|
say(`${probe.id}: converging to v${summary.target}`);
|
|
3786
3619
|
const armStartedAt = import_node_perf_hooks2.performance.now();
|
|
3787
|
-
const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl, journalEvents }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "
|
|
3620
|
+
const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl, journalEvents }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env, journalEvents }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env, hostPresent: probe.present, journalEvents }) : null;
|
|
3788
3621
|
if (arm) {
|
|
3789
3622
|
summary.plugins.push(arm);
|
|
3790
3623
|
summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
|
|
@@ -3819,7 +3652,7 @@ async function reconcile(options) {
|
|
|
3819
3652
|
}
|
|
3820
3653
|
|
|
3821
3654
|
// src/scheduler.ts
|
|
3822
|
-
var
|
|
3655
|
+
var import_node_child_process7 = require("node:child_process");
|
|
3823
3656
|
var import_node_fs21 = require("node:fs");
|
|
3824
3657
|
var import_node_os8 = require("node:os");
|
|
3825
3658
|
var import_node_path15 = require("node:path");
|
|
@@ -3838,15 +3671,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
|
|
|
3838
3671
|
var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
|
|
3839
3672
|
var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
|
|
3840
3673
|
function schtasks(args) {
|
|
3841
|
-
const result = (0,
|
|
3674
|
+
const result = (0, import_node_child_process7.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
|
|
3842
3675
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
3843
3676
|
}
|
|
3844
3677
|
function launchctl(args) {
|
|
3845
|
-
const result = (0,
|
|
3678
|
+
const result = (0, import_node_child_process7.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
|
|
3846
3679
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
3847
3680
|
}
|
|
3848
3681
|
function systemctlUser(args) {
|
|
3849
|
-
const result = (0,
|
|
3682
|
+
const result = (0, import_node_child_process7.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
|
|
3850
3683
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
3851
3684
|
}
|
|
3852
3685
|
function uid() {
|
|
@@ -3871,7 +3704,7 @@ WScript.Quit code
|
|
|
3871
3704
|
`;
|
|
3872
3705
|
}
|
|
3873
3706
|
function loginPath(fallback = process.env.PATH ?? "") {
|
|
3874
|
-
const probe = (0,
|
|
3707
|
+
const probe = (0, import_node_child_process7.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
|
|
3875
3708
|
const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
|
|
3876
3709
|
const source = captured || fallback;
|
|
3877
3710
|
const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
|
|
@@ -3992,7 +3825,7 @@ function elevatedRelaunch(mode, env) {
|
|
|
3992
3825
|
if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
|
|
3993
3826
|
const self = process.argv[1];
|
|
3994
3827
|
const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
|
|
3995
|
-
const relaunch = (0,
|
|
3828
|
+
const relaunch = (0, import_node_child_process7.spawnSync)(
|
|
3996
3829
|
"powershell.exe",
|
|
3997
3830
|
["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
|
|
3998
3831
|
{ encoding: "utf8", windowsHide: true }
|
|
@@ -4003,7 +3836,7 @@ var NEVER_RUN_RESULT = 267011;
|
|
|
4003
3836
|
var NEVER_RUN_YEAR = 1999;
|
|
4004
3837
|
var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
4005
3838
|
function taskRunInfo() {
|
|
4006
|
-
const result = (0,
|
|
3839
|
+
const result = (0, import_node_child_process7.spawnSync)(
|
|
4007
3840
|
"powershell.exe",
|
|
4008
3841
|
["-NoProfile", "-Command", `$stamp = { param($t) if ($t) { $t.ToString('yyyy-MM-dd HH:mm:ss') } }; Get-ScheduledTaskInfo -TaskName '${TASK_NAME}' | Select-Object @{n='LastRunTime';e={& $stamp $_.LastRunTime}},LastTaskResult,@{n='NextRunTime';e={& $stamp $_.NextRunTime}} | ConvertTo-Json -Compress`],
|
|
4009
3842
|
{ encoding: "utf8", windowsHide: true, timeout: 3e4 }
|
|
@@ -4324,9 +4157,6 @@ function hubStatusReport(hubVersion, env = process.env, invokedPath = process.ar
|
|
|
4324
4157
|
} else if (surface.id === "codex") {
|
|
4325
4158
|
const probe = probeNativeHostInstallation("codex", env);
|
|
4326
4159
|
actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
|
|
4327
|
-
} else if (surface.id === "cursor") {
|
|
4328
|
-
const probe = probeCursorInstallation();
|
|
4329
|
-
actual.set(surface.id, { version: probe.version, staged: probeCursorLaunchStaging(), location: probe.location, detail: probe.detail });
|
|
4330
4160
|
} else if (surface.id === "jervcode") {
|
|
4331
4161
|
const probe = probeJervcodeInstallation(env);
|
|
4332
4162
|
actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
|
|
@@ -4486,7 +4316,7 @@ function reportCounts(summary) {
|
|
|
4486
4316
|
// src/stamp.ts
|
|
4487
4317
|
var import_node_fs23 = require("node:fs");
|
|
4488
4318
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
4489
|
-
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "
|
|
4319
|
+
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "hermes"]);
|
|
4490
4320
|
function fleetStamp(env = process.env) {
|
|
4491
4321
|
const paths = statePaths(env);
|
|
4492
4322
|
let events = [];
|
|
@@ -4574,7 +4404,7 @@ function notice(screen, render) {
|
|
|
4574
4404
|
printScreen(renderNotice(FACE, screen, render), screen.state === "fail" ? "stderr" : "stdout");
|
|
4575
4405
|
}
|
|
4576
4406
|
function handOff(reexec, args, env) {
|
|
4577
|
-
const child = (0,
|
|
4407
|
+
const child = (0, import_node_child_process8.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
4578
4408
|
stdio: "inherit",
|
|
4579
4409
|
windowsHide: true,
|
|
4580
4410
|
env: { ...env, MMI_UPDATER_REEXEC: "1" }
|
|
@@ -4626,9 +4456,9 @@ async function main(args = process.argv.slice(2)) {
|
|
|
4626
4456
|
if (command === "autoupdate") {
|
|
4627
4457
|
const mode = args[args.indexOf(command) + 1];
|
|
4628
4458
|
if (mode !== "on" && mode !== "off") {
|
|
4629
|
-
const
|
|
4630
|
-
if (json) process.stdout.write(JSON.stringify({ error:
|
|
4631
|
-
else notice({ state: "fail", message:
|
|
4459
|
+
const message2 = mode === void 0 || mode.startsWith("--") ? "Say on or off: mmi-hub autoupdate on, or mmi-hub autoupdate off." : `Unknown auto-update mode: ${mode}. Say on or off.`;
|
|
4460
|
+
if (json) process.stdout.write(JSON.stringify({ error: message2 }, null, 2) + "\n");
|
|
4461
|
+
else notice({ state: "fail", message: message2, next: "mmi-hub autoupdate on" }, render);
|
|
4632
4462
|
return 1;
|
|
4633
4463
|
}
|
|
4634
4464
|
const result = mode === "on" ? installTask() : uninstallTask();
|
|
@@ -4649,9 +4479,9 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
4649
4479
|
main().then((code) => process.exit(code)).catch((error) => {
|
|
4650
4480
|
const args = process.argv.slice(2);
|
|
4651
4481
|
const first = String(error?.message ?? error).split("\n")[0].replaceAll("`", "").trim();
|
|
4652
|
-
const
|
|
4653
|
-
if (args.includes("--json")) process.stdout.write(JSON.stringify({ error:
|
|
4654
|
-
else notice({ state: "fail", message:
|
|
4482
|
+
const message2 = first ? /[.!?]$/.test(first) ? first : `${first}.` : "mmi-hub stopped unexpectedly.";
|
|
4483
|
+
if (args.includes("--json")) process.stdout.write(JSON.stringify({ error: message2 }, null, 2) + "\n");
|
|
4484
|
+
else notice({ state: "fail", message: message2, next: declaration.doctor }, { color: !args.includes("--no-color") });
|
|
4655
4485
|
if (args.includes("--verbose") && error?.stack) process.stderr.write(`${error.stack}
|
|
4656
4486
|
`);
|
|
4657
4487
|
process.exit(1);
|
|
@@ -4691,19 +4521,20 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
4691
4521
|
pendingPath,
|
|
4692
4522
|
pidAlive,
|
|
4693
4523
|
probeClaudeLaunchStaging,
|
|
4694
|
-
probeCursorLaunchStaging,
|
|
4695
4524
|
probeHermesInstallation,
|
|
4696
4525
|
probeHermesLaunchStaging,
|
|
4697
4526
|
probeJervcodeInstallation,
|
|
4698
4527
|
probeLaunchTargetVersion,
|
|
4699
4528
|
readLaunchTarget,
|
|
4700
4529
|
readPending,
|
|
4530
|
+
reap,
|
|
4701
4531
|
reconcile,
|
|
4702
4532
|
reconcileCursorUserHooks,
|
|
4703
4533
|
releaseSharedDocLock,
|
|
4704
4534
|
renderNotice,
|
|
4705
4535
|
renderReport,
|
|
4706
4536
|
reportCounts,
|
|
4537
|
+
retireCursor,
|
|
4707
4538
|
retiredSurfaces,
|
|
4708
4539
|
retryHint,
|
|
4709
4540
|
runMaintenance,
|