@mutmutco/hub 4.2.6 → 4.3.0
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 +291 -822
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
clearPending: () => clearPending,
|
|
30
30
|
enumerateSurfaces: () => enumerateSurfaces,
|
|
31
31
|
failConsequence: () => failConsequence,
|
|
32
|
+
fleetStamp: () => fleetStamp,
|
|
32
33
|
formatReconcileReport: () => formatReconcileReport,
|
|
33
34
|
hermesArm: () => hermesArm,
|
|
34
35
|
hermesHome: () => hermesHome,
|
|
@@ -54,15 +55,14 @@ __export(index_exports, {
|
|
|
54
55
|
probeHermesInstallation: () => probeHermesInstallation,
|
|
55
56
|
probeHermesLaunchStaging: () => probeHermesLaunchStaging,
|
|
56
57
|
probeJervcodeInstallation: () => probeJervcodeInstallation,
|
|
57
|
-
probeKimiLaunchStaging: () => probeKimiLaunchStaging,
|
|
58
58
|
probeLaunchTargetVersion: () => probeLaunchTargetVersion,
|
|
59
59
|
progressiveRow: () => progressiveRow,
|
|
60
60
|
readLaunchTarget: () => readLaunchTarget,
|
|
61
61
|
readPending: () => readPending,
|
|
62
62
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
63
|
-
reconcileKimiInstalledPlugin: () => reconcileKimiInstalledPlugin,
|
|
64
63
|
releaseSharedDocLock: () => releaseSharedDocLock,
|
|
65
64
|
reportFooterLines: () => reportFooterLines,
|
|
65
|
+
retiredSurfaces: () => retiredSurfaces,
|
|
66
66
|
retryHint: () => retryHint,
|
|
67
67
|
schedulePendingWatcher: () => schedulePendingWatcher,
|
|
68
68
|
schedulerStatus: () => schedulerStatus,
|
|
@@ -71,11 +71,11 @@ __export(index_exports, {
|
|
|
71
71
|
sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
|
|
72
72
|
shouldHandOff: () => shouldHandOff,
|
|
73
73
|
startSpinner: () => startSpinner,
|
|
74
|
+
surfaceArmPlan: () => surfaceArmPlan,
|
|
74
75
|
systemdServiceUnit: () => systemdServiceUnit,
|
|
75
76
|
systemdTimerUnit: () => systemdTimerUnit,
|
|
76
77
|
taskXml: () => taskXml,
|
|
77
78
|
updateHeader: () => updateHeader,
|
|
78
|
-
validateKimiInstalledPlugin: () => validateKimiInstalledPlugin,
|
|
79
79
|
watcherLockPath: () => watcherLockPath,
|
|
80
80
|
withSharedDocLock: () => withSharedDocLock,
|
|
81
81
|
wrapWords: () => wrapWords,
|
|
@@ -83,9 +83,9 @@ __export(index_exports, {
|
|
|
83
83
|
writePending: () => writePending
|
|
84
84
|
});
|
|
85
85
|
module.exports = __toCommonJS(index_exports);
|
|
86
|
-
var
|
|
86
|
+
var import_node_child_process10 = require("node:child_process");
|
|
87
87
|
var import_node_fs19 = require("node:fs");
|
|
88
|
-
var
|
|
88
|
+
var import_node_path14 = require("node:path");
|
|
89
89
|
|
|
90
90
|
// src/state.ts
|
|
91
91
|
var import_node_fs = require("node:fs");
|
|
@@ -208,6 +208,18 @@ function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
|
208
208
|
}
|
|
209
209
|
return lastVerdict === "fail";
|
|
210
210
|
}
|
|
211
|
+
function retiredSurfaces(events) {
|
|
212
|
+
const newest = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const event of events) {
|
|
214
|
+
if (event.dryRun || !event.surface) continue;
|
|
215
|
+
if (event.kind === "arm" || event.kind === "surface") newest.set(event.surface, event);
|
|
216
|
+
}
|
|
217
|
+
const retired = /* @__PURE__ */ new Set();
|
|
218
|
+
for (const [surface, event] of newest) {
|
|
219
|
+
if (event.kind === "surface" && event.retired) retired.add(surface);
|
|
220
|
+
}
|
|
221
|
+
return retired;
|
|
222
|
+
}
|
|
211
223
|
function parseSemver(version) {
|
|
212
224
|
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(version.trim());
|
|
213
225
|
if (!match) return null;
|
|
@@ -770,7 +782,7 @@ function ensureClaudeMarketplace(env) {
|
|
|
770
782
|
}
|
|
771
783
|
var CLAUDE_PACKAGE = "@mutmutco/claude-plugin";
|
|
772
784
|
var CLAUDE_MANIFEST = ".claude-plugin/plugin.json";
|
|
773
|
-
var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json"
|
|
785
|
+
var CLAUDE_TREE_MARKERS = [".claude-plugin/plugin.json"];
|
|
774
786
|
function claudePluginsRoot(env) {
|
|
775
787
|
return (0, import_node_path6.join)(claudeHomeDir(env), "plugins");
|
|
776
788
|
}
|
|
@@ -1003,99 +1015,24 @@ function codexArm(options) {
|
|
|
1003
1015
|
}
|
|
1004
1016
|
return { surface: "codex", from, to: options.target, verdict: "ok", detail: `marketplace upgraded; payload refresh and native evidence verified at ${options.target}` };
|
|
1005
1017
|
}
|
|
1006
|
-
var KILO_PACKAGE = "@mutmutco/kilo-plugin";
|
|
1007
|
-
function kiloConfigPath(env) {
|
|
1008
|
-
return env.MMI_UPDATER_KILO_CONFIG || (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".config", "kilo", "opencode.json");
|
|
1009
|
-
}
|
|
1010
|
-
function readKiloConfig(env) {
|
|
1011
|
-
try {
|
|
1012
|
-
const config = JSON.parse((0, import_node_fs7.readFileSync)(kiloConfigPath(env), "utf8"));
|
|
1013
|
-
const specs = Array.isArray(config.plugin) && config.plugin.every((entry) => typeof entry === "string") ? config.plugin : [];
|
|
1014
|
-
return { config, specs };
|
|
1015
|
-
} catch {
|
|
1016
|
-
return null;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
function isLegacyMmiKiloPath(spec) {
|
|
1020
|
-
const normalized = spec.replaceAll("\\", "/");
|
|
1021
|
-
return /\/(?:MMI-Hub|@jervaise\/jerv-cli)\/\.kilo-plugin\/?$/i.test(normalized);
|
|
1022
|
-
}
|
|
1023
|
-
function normalizeKiloConfig(env, exact) {
|
|
1024
|
-
const current = readKiloConfig(env);
|
|
1025
|
-
if (!current) return `cannot read ${kiloConfigPath(env)}`;
|
|
1026
|
-
const next = current.specs.filter((spec) => !isLegacyMmiKiloPath(spec));
|
|
1027
|
-
if (!next.includes(exact)) next.push(exact);
|
|
1028
|
-
if (JSON.stringify(next) === JSON.stringify(current.specs)) return null;
|
|
1029
|
-
const path = kiloConfigPath(env);
|
|
1030
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
1031
|
-
try {
|
|
1032
|
-
(0, import_node_fs7.writeFileSync)(tmp, JSON.stringify({ ...current.config, plugin: next }, null, 2) + "\n", "utf8");
|
|
1033
|
-
(0, import_node_fs7.renameSync)(tmp, path);
|
|
1034
|
-
return null;
|
|
1035
|
-
} catch (error) {
|
|
1036
|
-
return `cannot retire legacy Kilo registrations: ${error?.message ?? error}`;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
function kiloVersion(specs) {
|
|
1040
|
-
const exact = specs?.find((entry) => entry.startsWith(`${KILO_PACKAGE}@`));
|
|
1041
|
-
return exact?.slice(`${KILO_PACKAGE}@`.length) || null;
|
|
1042
|
-
}
|
|
1043
1018
|
function probeNativeHostInstallation(surface, env = process.env, runner = runHostCommand) {
|
|
1044
1019
|
if (surface === "claude") {
|
|
1045
|
-
const
|
|
1020
|
+
const observed2 = claudeMmi(runner, env);
|
|
1046
1021
|
return {
|
|
1047
|
-
version:
|
|
1022
|
+
version: observed2.row?.version ?? null,
|
|
1048
1023
|
location: "claude plugin list --json",
|
|
1049
|
-
detail:
|
|
1050
|
-
};
|
|
1051
|
-
}
|
|
1052
|
-
if (surface === "codex") {
|
|
1053
|
-
const observed = codexMmi(runner, env);
|
|
1054
|
-
return {
|
|
1055
|
-
version: observed.row?.version ?? null,
|
|
1056
|
-
location: "codex plugin list --json",
|
|
1057
|
-
detail: observed.error ?? `native host reports installed=${String(observed.row?.installed)}, enabled=${String(observed.row?.enabled)}`
|
|
1024
|
+
detail: observed2.error ?? `native host reports enabled=${String(observed2.row?.enabled)}`
|
|
1058
1025
|
};
|
|
1059
1026
|
}
|
|
1060
|
-
const
|
|
1027
|
+
const observed = codexMmi(runner, env);
|
|
1061
1028
|
return {
|
|
1062
|
-
version:
|
|
1063
|
-
location:
|
|
1064
|
-
detail:
|
|
1029
|
+
version: observed.row?.version ?? null,
|
|
1030
|
+
location: "codex plugin list --json",
|
|
1031
|
+
detail: observed.error ?? `native host reports installed=${String(observed.row?.installed)}, enabled=${String(observed.row?.enabled)}`
|
|
1065
1032
|
};
|
|
1066
1033
|
}
|
|
1067
|
-
function kiloArm(options) {
|
|
1068
|
-
const env = options.env ?? process.env;
|
|
1069
|
-
const runner = options.runner ?? runHostCommand;
|
|
1070
|
-
const exact = `${KILO_PACKAGE}@${options.target}`;
|
|
1071
|
-
const before = readKiloConfig(env);
|
|
1072
|
-
if (!before) return { surface: "kilo", from: null, to: options.target, verdict: "defer", detail: `cannot read ${kiloConfigPath(env)}` };
|
|
1073
|
-
const from = kiloVersion(before.specs);
|
|
1074
|
-
const legacyCount = before.specs.filter(isLegacyMmiKiloPath).length;
|
|
1075
|
-
if (options.dryRun) {
|
|
1076
|
-
const action = before.specs.includes(exact) ? "keep exact pin" : `configure ${exact} globally`;
|
|
1077
|
-
return { surface: "kilo", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacyCount ? ` and retire ${legacyCount} legacy MMI path${legacyCount === 1 ? "" : "s"}` : ""} (payload provisions next session)` };
|
|
1078
|
-
}
|
|
1079
|
-
if (!before.specs.includes(exact)) {
|
|
1080
|
-
const install = runner("kilo", ["plugin", exact, "--global", "--force"], env);
|
|
1081
|
-
if (install.status !== 0) {
|
|
1082
|
-
return { surface: "kilo", from, to: options.target, verdict: "defer", detail: `kilo plugin failed: ${failure(install)}` };
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
const normalizeError = normalizeKiloConfig(env, exact);
|
|
1086
|
-
if (normalizeError) return { surface: "kilo", from, to: options.target, verdict: "defer", detail: normalizeError };
|
|
1087
|
-
const after = readKiloConfig(env);
|
|
1088
|
-
const legacyAfter = after?.specs.filter(isLegacyMmiKiloPath) ?? [];
|
|
1089
|
-
if (!after?.specs.includes(exact) || legacyAfter.length) {
|
|
1090
|
-
return { surface: "kilo", from, to: kiloVersion(after?.specs ?? null) ?? "unknown", verdict: "fail", detail: `post-install config must contain ${exact} and no legacy MMI paths (legacy=${legacyAfter.length})` };
|
|
1091
|
-
}
|
|
1092
|
-
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` };
|
|
1093
|
-
}
|
|
1094
1034
|
|
|
1095
1035
|
// src/surfaces.ts
|
|
1096
|
-
function kimiHome(env = process.env) {
|
|
1097
|
-
return env.KIMI_CODE_HOME?.trim() || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".kimi-code");
|
|
1098
|
-
}
|
|
1099
1036
|
function jervcodeAgentDir(env = process.env) {
|
|
1100
1037
|
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");
|
|
1101
1038
|
}
|
|
@@ -1110,6 +1047,10 @@ function hermesHostCommand(env = process.env) {
|
|
|
1110
1047
|
function hermesHostPresent(env = process.env, runner = runHostCommand) {
|
|
1111
1048
|
return runner(hermesHostCommand(env), ["--version"], env).status === 0;
|
|
1112
1049
|
}
|
|
1050
|
+
function surfaceArmPlan(token, managedSurfaces) {
|
|
1051
|
+
if (!managedSurfaces) return "arm";
|
|
1052
|
+
return managedSurfaces.includes(token) ? "arm" : "retired";
|
|
1053
|
+
}
|
|
1113
1054
|
function enumerateSurfaces(env = process.env) {
|
|
1114
1055
|
const home = env.HOME?.trim() || env.USERPROFILE?.trim() || (0, import_node_os3.homedir)();
|
|
1115
1056
|
const codexHome = env.CODEX_HOME || (0, import_node_path7.join)(home, ".codex");
|
|
@@ -1121,11 +1062,6 @@ function enumerateSurfaces(env = process.env) {
|
|
|
1121
1062
|
// and the arm could never make the first install. The plugin's absence is a converge action, not
|
|
1122
1063
|
// a skip. The host root mirrors surfaceConfigRoot('cursor') in cli/src/plugin-guard-io.ts.
|
|
1123
1064
|
{ id: "cursor", marker: (0, import_node_path7.join)(home, ".cursor") },
|
|
1124
|
-
{ id: "kilo", marker: (0, import_node_path7.join)(home, ".config", "kilo") },
|
|
1125
|
-
// The host, not the payload (#4951): this marker used to be plugins/managed/mmi — the MMI plugin
|
|
1126
|
-
// itself — so a machine running Kimi WITHOUT the plugin enumerated as "no Kimi" and the arm
|
|
1127
|
-
// could never make the first install. The plugin's absence is a converge action, not a skip.
|
|
1128
|
-
{ id: "kimi", marker: kimiHome(env) },
|
|
1129
1065
|
{ id: "jervcode", marker: (0, import_node_path7.join)(jervcodeAgentDir(env), "settings.json") },
|
|
1130
1066
|
// Host CLI, not HERMES_HOME (#5673): the config tree can survive an uninstall and still carry
|
|
1131
1067
|
// leftover plugin manifests — that residue must not enumerate as an installed host.
|
|
@@ -1225,9 +1161,10 @@ function selfArm(options) {
|
|
|
1225
1161
|
});
|
|
1226
1162
|
}
|
|
1227
1163
|
|
|
1228
|
-
// src/arm-
|
|
1229
|
-
var import_node_child_process7 = require("node:child_process");
|
|
1164
|
+
// src/arm-cursor.ts
|
|
1230
1165
|
var import_node_fs12 = require("node:fs");
|
|
1166
|
+
var import_node_child_process7 = require("node:child_process");
|
|
1167
|
+
var import_node_os5 = require("node:os");
|
|
1231
1168
|
var import_node_path9 = require("node:path");
|
|
1232
1169
|
|
|
1233
1170
|
// src/compat.ts
|
|
@@ -1241,21 +1178,6 @@ function readManifestCompat(manifestPath) {
|
|
|
1241
1178
|
}
|
|
1242
1179
|
}
|
|
1243
1180
|
|
|
1244
|
-
// src/kimi-registration.ts
|
|
1245
|
-
function validateKimiInstalledPlugin(document, pluginRoot, id) {
|
|
1246
|
-
if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
|
|
1247
|
-
const plugins = document.plugins;
|
|
1248
|
-
if (!Array.isArray(plugins)) return "malformed-document";
|
|
1249
|
-
const entry = plugins.find(
|
|
1250
|
-
(candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.id === id
|
|
1251
|
-
);
|
|
1252
|
-
if (!entry) return "absent-record";
|
|
1253
|
-
const record = entry;
|
|
1254
|
-
if (typeof record.root !== "string") return "malformed-record";
|
|
1255
|
-
if (record.root !== pluginRoot) return "foreign-root";
|
|
1256
|
-
return record.enabled ? "healthy" : "disabled";
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
1181
|
// src/host-doc-lock.ts
|
|
1260
1182
|
var import_node_fs11 = require("node:fs");
|
|
1261
1183
|
var import_node_os4 = require("node:os");
|
|
@@ -1366,311 +1288,22 @@ function withSharedDocLock(documentPath, options, fn) {
|
|
|
1366
1288
|
}
|
|
1367
1289
|
}
|
|
1368
1290
|
|
|
1369
|
-
// src/arm-kimi.ts
|
|
1370
|
-
var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
|
|
1371
|
-
var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
|
|
1372
|
-
function treeHealthy(root) {
|
|
1373
|
-
return TREE_MARKERS.every((rel) => fileExists((0, import_node_path9.join)(root, ...rel.split("/"))));
|
|
1374
|
-
}
|
|
1375
|
-
function markerVersion(root) {
|
|
1376
|
-
try {
|
|
1377
|
-
const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
|
|
1378
|
-
return typeof version === "string" ? version : null;
|
|
1379
|
-
} catch {
|
|
1380
|
-
return null;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
function packageVersion(root) {
|
|
1384
|
-
try {
|
|
1385
|
-
const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, "package.json"), "utf8")).version;
|
|
1386
|
-
return typeof version === "string" ? version : null;
|
|
1387
|
-
} catch {
|
|
1388
|
-
return null;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
var KIMI_REGISTRATION_LOCK_OWNER = "mmi-hub:kimi-registration";
|
|
1392
|
-
function reconcileKimiInstalledPlugin(kimiHomeDir, pluginRoot, id, lock = {}) {
|
|
1393
|
-
const path = (0, import_node_path9.join)(kimiHomeDir, "plugins", "installed.json");
|
|
1394
|
-
(0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(kimiHomeDir, "plugins"), { recursive: true });
|
|
1395
|
-
return withSharedDocLock(path, { owner: KIMI_REGISTRATION_LOCK_OWNER, ...lock }, () => mergeKimiInstalledPlugin(path, pluginRoot, id));
|
|
1396
|
-
}
|
|
1397
|
-
function mergeKimiInstalledPlugin(path, pluginRoot, id) {
|
|
1398
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1399
|
-
const fresh = { id, root: pluginRoot, enabled: true, installedAt: now, updatedAt: now, originalSource: pluginRoot, source: "local-path" };
|
|
1400
|
-
let document = { version: 1, plugins: [] };
|
|
1401
|
-
if ((0, import_node_fs12.existsSync)(path)) {
|
|
1402
|
-
let parsed;
|
|
1403
|
-
try {
|
|
1404
|
-
parsed = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
|
|
1405
|
-
} catch {
|
|
1406
|
-
return "conflict";
|
|
1407
|
-
}
|
|
1408
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.plugins)) return "conflict";
|
|
1409
|
-
document = parsed;
|
|
1410
|
-
}
|
|
1411
|
-
const plugins = [...document.plugins];
|
|
1412
|
-
const registration = validateKimiInstalledPlugin(document, pluginRoot, id);
|
|
1413
|
-
const index = plugins.findIndex((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && entry.id === id);
|
|
1414
|
-
if (registration === "malformed-record" || registration === "foreign-root") return "conflict";
|
|
1415
|
-
if (index === -1) {
|
|
1416
|
-
plugins.push(fresh);
|
|
1417
|
-
} else {
|
|
1418
|
-
const prior = plugins[index];
|
|
1419
|
-
const next = {
|
|
1420
|
-
...prior,
|
|
1421
|
-
root: pluginRoot,
|
|
1422
|
-
// A user's explicit disable is their choice and survives; anything else converges to enabled.
|
|
1423
|
-
enabled: prior.enabled === false ? false : true,
|
|
1424
|
-
installedAt: typeof prior.installedAt === "string" ? prior.installedAt : now,
|
|
1425
|
-
originalSource: pluginRoot,
|
|
1426
|
-
source: "local-path"
|
|
1427
|
-
};
|
|
1428
|
-
if (JSON.stringify(next) === JSON.stringify(prior)) return "current";
|
|
1429
|
-
plugins[index] = { ...next, updatedAt: now };
|
|
1430
|
-
}
|
|
1431
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
1432
|
-
(0, import_node_fs12.writeFileSync)(tmp, `${JSON.stringify({ ...document, version: 1, plugins }, null, 2)}
|
|
1433
|
-
`, "utf8");
|
|
1434
|
-
(0, import_node_fs12.renameSync)(tmp, path);
|
|
1435
|
-
return "updated";
|
|
1436
|
-
}
|
|
1437
|
-
function probeKimiInstallation(env = process.env) {
|
|
1438
|
-
const location = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed", "mmi");
|
|
1439
|
-
const version = markerVersion(location);
|
|
1440
|
-
return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
|
|
1441
|
-
}
|
|
1442
|
-
function tail2(output) {
|
|
1443
|
-
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
1444
|
-
}
|
|
1445
|
-
function scratchRoot(env) {
|
|
1446
|
-
return (0, import_node_path9.join)(kimiHome(env), ".mmi-updater");
|
|
1447
|
-
}
|
|
1448
|
-
function pruneScratchDir(root, keep) {
|
|
1449
|
-
try {
|
|
1450
|
-
for (const name of (0, import_node_fs12.readdirSync)(root)) {
|
|
1451
|
-
if (name === keep) continue;
|
|
1452
|
-
try {
|
|
1453
|
-
(0, import_node_fs12.rmSync)((0, import_node_path9.join)(root, name), { recursive: true, force: true });
|
|
1454
|
-
} catch {
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
} catch {
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
var KIMI_PROCESS_SEGMENT = /^kimi(-cli|-code)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/i;
|
|
1461
|
-
function kimiProcessToken(line) {
|
|
1462
|
-
for (const token of line.replace(/["']/g, " ").split(/\s+/)) {
|
|
1463
|
-
if (token && token.split(/[/\\]/).some((segment) => KIMI_PROCESS_SEGMENT.test(segment))) return token;
|
|
1464
|
-
}
|
|
1465
|
-
return null;
|
|
1466
|
-
}
|
|
1467
|
-
function kimiHostRunning(env) {
|
|
1468
|
-
if (env.KIMI_PLUGIN_ROOT?.trim()) return { running: true, evidence: "KIMI_PLUGIN_ROOT is set \u2014 this process is itself inside a Kimi hook" };
|
|
1469
|
-
const result = process.platform === "win32" ? (0, import_node_child_process7.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process7.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
1470
|
-
if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
|
|
1471
|
-
for (const line of result.stdout.split(/\r?\n/)) {
|
|
1472
|
-
const token = kimiProcessToken(line);
|
|
1473
|
-
if (token) return { running: true, evidence: `a live process names kimi (${token})` };
|
|
1474
|
-
}
|
|
1475
|
-
return { running: false, evidence: "no kimi-named process in the process list" };
|
|
1476
|
-
}
|
|
1477
|
-
function restoreQuarantine(quarantine, live) {
|
|
1478
|
-
try {
|
|
1479
|
-
(0, import_node_fs12.renameSync)(quarantine, live);
|
|
1480
|
-
} catch (error) {
|
|
1481
|
-
return { restored: false, detail: `${error?.message ?? error}` };
|
|
1482
|
-
}
|
|
1483
|
-
if (!(0, import_node_fs12.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
|
|
1484
|
-
return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
|
|
1485
|
-
}
|
|
1486
|
-
function kimiArm(options) {
|
|
1487
|
-
const env = options.env ?? process.env;
|
|
1488
|
-
const { target, dryRun, paths } = options;
|
|
1489
|
-
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
1490
|
-
surface: "kimi",
|
|
1491
|
-
from,
|
|
1492
|
-
to,
|
|
1493
|
-
verdict,
|
|
1494
|
-
detail,
|
|
1495
|
-
launchStaged: launchStaged || void 0,
|
|
1496
|
-
// C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
|
|
1497
|
-
// manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
|
|
1498
|
-
compat: readManifestCompat((0, import_node_path9.join)(live, ".kimi-plugin", "plugin.json"))
|
|
1499
|
-
});
|
|
1500
|
-
const managedParent = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed");
|
|
1501
|
-
const live = (0, import_node_path9.join)(managedParent, "mmi");
|
|
1502
|
-
const installed = markerVersion(live);
|
|
1503
|
-
const ensureRegistered = (detail) => {
|
|
1504
|
-
if (!(0, import_node_fs12.existsSync)(live) || !treeHealthy(live)) return { detail };
|
|
1505
|
-
if (dryRun) return { detail: `${detail}; installed.json untouched (dry-run)` };
|
|
1506
|
-
let registration;
|
|
1507
|
-
try {
|
|
1508
|
-
registration = reconcileKimiInstalledPlugin(kimiHome(env), live, "mmi");
|
|
1509
|
-
} catch (error) {
|
|
1510
|
-
return { fail: true, detail: `${detail}; installed.json registration FAILED: ${error?.message ?? error}` };
|
|
1511
|
-
}
|
|
1512
|
-
if (registration === "conflict") {
|
|
1513
|
-
return { fail: true, detail: `${detail}; installed.json carries an unparsable or foreign mmi record \u2014 the tree is INERT until a human resolves it; the file was never touched` };
|
|
1514
|
-
}
|
|
1515
|
-
return { detail: `${detail}; installed.json record ${registration === "updated" ? "written" : "already current"}` };
|
|
1516
|
-
};
|
|
1517
|
-
if (installed && compareSemver(installed, target) > 0) {
|
|
1518
|
-
const reg2 = ensureRegistered(`installed ${installed} is above candidate ${target} (monotonic)`);
|
|
1519
|
-
return result(installed, installed, reg2.fail ? "fail" : "skip", reg2.detail);
|
|
1520
|
-
}
|
|
1521
|
-
const highWater = journalHighWater(paths.journalPath, "kimi");
|
|
1522
|
-
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1523
|
-
const reg2 = ensureRegistered(`journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1524
|
-
return result(installed, highWater, reg2.fail ? "fail" : "skip", reg2.detail);
|
|
1525
|
-
}
|
|
1526
|
-
if (installed === target && treeHealthy(live)) {
|
|
1527
|
-
const reg2 = ensureRegistered(`already at target ${target}`);
|
|
1528
|
-
return result(installed, target, reg2.fail ? "fail" : "ok", reg2.detail);
|
|
1529
|
-
}
|
|
1530
|
-
if (dryRun) {
|
|
1531
|
-
return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
|
|
1532
|
-
}
|
|
1533
|
-
const stageRoot = (0, import_node_path9.join)(paths.staging, "kimi", target);
|
|
1534
|
-
const staged = (0, import_node_path9.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
|
|
1535
|
-
if (!treeHealthy(staged)) {
|
|
1536
|
-
(0, import_node_fs12.mkdirSync)(stageRoot, { recursive: true });
|
|
1537
|
-
(0, import_node_fs12.writeFileSync)((0, import_node_path9.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
1538
|
-
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
|
|
1539
|
-
if (install.status !== 0) {
|
|
1540
|
-
return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
const stagedPackage = packageVersion(staged);
|
|
1544
|
-
if (stagedPackage !== target) {
|
|
1545
|
-
return result(installed, target, "defer", `staged package manifest is ${stagedPackage ?? "unreadable"}, expected ${target}`);
|
|
1546
|
-
}
|
|
1547
|
-
const stagedMarker = markerVersion(staged);
|
|
1548
|
-
if (stagedMarker !== target) {
|
|
1549
|
-
return result(installed, target, "defer", `staged plugin manifest is ${stagedMarker ?? "unreadable"}, expected ${target}`);
|
|
1550
|
-
}
|
|
1551
|
-
if (!treeHealthy(staged)) {
|
|
1552
|
-
return result(installed, target, "defer", `staged tree is missing one of ${TREE_MARKERS.join(", ")}`);
|
|
1553
|
-
}
|
|
1554
|
-
const scratch = scratchRoot(env);
|
|
1555
|
-
const launchTargetPath = (0, import_node_path9.join)(scratch, "launch-target.json");
|
|
1556
|
-
const incomingRoot = (0, import_node_path9.join)(scratch, "incoming");
|
|
1557
|
-
const incoming = (0, import_node_path9.join)(incomingRoot, target);
|
|
1558
|
-
const quarantineRoot = (0, import_node_path9.join)(scratch, "quarantine");
|
|
1559
|
-
const rejectedRoot = (0, import_node_path9.join)(scratch, "rejected");
|
|
1560
|
-
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1561
|
-
const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && markerVersion(incoming) === target && treeHealthy(incoming);
|
|
1562
|
-
if (!reuseIncoming) {
|
|
1563
|
-
try {
|
|
1564
|
-
(0, import_node_fs12.mkdirSync)(managedParent, { recursive: true });
|
|
1565
|
-
(0, import_node_fs12.mkdirSync)(quarantineRoot, { recursive: true });
|
|
1566
|
-
(0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1567
|
-
(0, import_node_fs12.cpSync)(staged, incoming, { recursive: true });
|
|
1568
|
-
} catch (error) {
|
|
1569
|
-
return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
|
|
1570
|
-
}
|
|
1571
|
-
if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
|
|
1572
|
-
return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
const quiescence = kimiHostRunning(env);
|
|
1576
|
-
if (quiescence.running) {
|
|
1577
|
-
const pending = noteLaunchStaged(paths, env, {
|
|
1578
|
-
surface: "kimi",
|
|
1579
|
-
target,
|
|
1580
|
-
reason: quiescence.evidence,
|
|
1581
|
-
launchTargetPath,
|
|
1582
|
-
incoming,
|
|
1583
|
-
dirs: ["kimi"]
|
|
1584
|
-
});
|
|
1585
|
-
return result(installed, target, "ok", `staged ${target} for next launch while Kimi is running (${quiescence.evidence}) \u2014 ${pending}`, true);
|
|
1586
|
-
}
|
|
1587
|
-
const started = kimiHostRunning(env);
|
|
1588
|
-
if (started.running) {
|
|
1589
|
-
const pending = noteLaunchStaged(paths, env, {
|
|
1590
|
-
surface: "kimi",
|
|
1591
|
-
target,
|
|
1592
|
-
reason: started.evidence,
|
|
1593
|
-
launchTargetPath,
|
|
1594
|
-
incoming,
|
|
1595
|
-
dirs: ["kimi"]
|
|
1596
|
-
});
|
|
1597
|
-
return result(installed, target, "ok", `staged ${target} for next launch \u2014 Kimi started during staging (${started.evidence}) \u2014 ${pending}`, true);
|
|
1598
|
-
}
|
|
1599
|
-
const hadLive = (0, import_node_fs12.existsSync)(live);
|
|
1600
|
-
const generation = `${installed ?? "unknown"}-${Date.now()}`;
|
|
1601
|
-
const quarantine = (0, import_node_path9.join)(quarantineRoot, generation);
|
|
1602
|
-
if (hadLive) {
|
|
1603
|
-
try {
|
|
1604
|
-
(0, import_node_fs12.renameSync)(live, quarantine);
|
|
1605
|
-
} catch (error) {
|
|
1606
|
-
const reason = String(error?.message ?? error);
|
|
1607
|
-
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
|
|
1608
|
-
return result(installed, target, "defer", `cannot quarantine the live generation: ${reason}${pending}`);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
try {
|
|
1612
|
-
(0, import_node_fs12.renameSync)(incoming, live);
|
|
1613
|
-
} catch (error) {
|
|
1614
|
-
const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
|
|
1615
|
-
if (!hadLive) {
|
|
1616
|
-
return result(installed, target, "defer", `${failed} \u2014 no generation was displaced; ${live} is still absent`);
|
|
1617
|
-
}
|
|
1618
|
-
const restore = restoreQuarantine(quarantine, live);
|
|
1619
|
-
const pending = restore.restored && lockShaped(failed) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: failed, dirs: ["kimi"] })}` : "";
|
|
1620
|
-
return restore.restored ? result(installed, installed ?? "unknown", "defer", `${failed} \u2014 the previous generation is back in place (${restore.detail})${pending}`) : result(installed, "none", "fail", `${failed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
|
|
1621
|
-
}
|
|
1622
|
-
const switched = markerVersion(live);
|
|
1623
|
-
if (switched !== target || !treeHealthy(live)) {
|
|
1624
|
-
const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
|
|
1625
|
-
const rejectedName = `${target}-${Date.now()}`;
|
|
1626
|
-
try {
|
|
1627
|
-
(0, import_node_fs12.mkdirSync)(rejectedRoot, { recursive: true });
|
|
1628
|
-
(0, import_node_fs12.renameSync)(live, (0, import_node_path9.join)(rejectedRoot, rejectedName));
|
|
1629
|
-
} catch (error) {
|
|
1630
|
-
return result(installed, switched ?? "unknown", "fail", `${observed} \u2014 the unverified tree could NOT be moved aside (${error?.message ?? error}) and is STILL LIVE at ${live}${hadLive ? `; the previous generation is intact at ${quarantine}` : ""}`);
|
|
1631
|
-
}
|
|
1632
|
-
pruneScratchDir(rejectedRoot, rejectedName);
|
|
1633
|
-
if (!hadLive) {
|
|
1634
|
-
return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path9.join)(rejectedRoot, rejectedName)}; ${live} is absent (there was no previous generation to restore)`);
|
|
1635
|
-
}
|
|
1636
|
-
const restore = restoreQuarantine(quarantine, live);
|
|
1637
|
-
return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0, import_node_path9.join)(rejectedRoot, rejectedName)}; rolled back to ${installed ?? "the previous generation"} and read back (${restore.detail})`) : result(installed, "none", "fail", `${observed}; ROLLBACK FAILED (${restore.detail}) \u2014 ${live} now has NO plugin and Kimi enforces nothing until this is repaired; the known-good generation is intact at ${quarantine}`);
|
|
1638
|
-
}
|
|
1639
|
-
pruneScratchDir(quarantineRoot, hadLive ? generation : null);
|
|
1640
|
-
pruneScratchDir(rejectedRoot, null);
|
|
1641
|
-
try {
|
|
1642
|
-
(0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1643
|
-
} catch {
|
|
1644
|
-
}
|
|
1645
|
-
clearLaunchTarget(launchTargetPath);
|
|
1646
|
-
const reg = ensureRegistered(`converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
|
|
1647
|
-
return result(installed, target, reg.fail ? "fail" : "ok", reg.detail);
|
|
1648
|
-
}
|
|
1649
|
-
function probeKimiLaunchStaging(env = process.env) {
|
|
1650
|
-
return probeLaunchTargetVersion((0, import_node_path9.join)(scratchRoot(env), "launch-target.json"), markerVersion);
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
1291
|
// src/arm-cursor.ts
|
|
1654
|
-
var import_node_fs13 = require("node:fs");
|
|
1655
|
-
var import_node_child_process8 = require("node:child_process");
|
|
1656
|
-
var import_node_os5 = require("node:os");
|
|
1657
|
-
var import_node_path10 = require("node:path");
|
|
1658
1292
|
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
1659
1293
|
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
1660
1294
|
var USER_HOOKS_FILE = "hooks.json";
|
|
1661
|
-
var PLUGIN_HOOKS_REL = ["hooks", "cursor-hooks.json"];
|
|
1662
1295
|
var KEEP_GENERATIONS = 2;
|
|
1663
1296
|
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
1664
|
-
var TREE_FILES = [MANIFEST, "skills/
|
|
1297
|
+
var TREE_FILES = [MANIFEST, "skills/bootstrap/SKILL.md"];
|
|
1665
1298
|
function cursorPluginsRoot() {
|
|
1666
|
-
return (0,
|
|
1299
|
+
return (0, import_node_path9.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
|
|
1667
1300
|
}
|
|
1668
|
-
function
|
|
1669
|
-
return TREE_FILES.every((rel) => (0,
|
|
1301
|
+
function treeHealthy(root) {
|
|
1302
|
+
return TREE_FILES.every((rel) => (0, import_node_fs12.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
|
|
1670
1303
|
}
|
|
1671
1304
|
function readJson(root, rel) {
|
|
1672
1305
|
try {
|
|
1673
|
-
return JSON.parse((0,
|
|
1306
|
+
return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
|
|
1674
1307
|
} catch {
|
|
1675
1308
|
return null;
|
|
1676
1309
|
}
|
|
@@ -1679,7 +1312,7 @@ function manifest(root) {
|
|
|
1679
1312
|
return readJson(root, MANIFEST);
|
|
1680
1313
|
}
|
|
1681
1314
|
function probeCursorInstallation() {
|
|
1682
|
-
const location = (0,
|
|
1315
|
+
const location = (0, import_node_path9.join)(cursorPluginsRoot(), "local", "mmi");
|
|
1683
1316
|
const version = manifest(location)?.version ?? null;
|
|
1684
1317
|
return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
|
|
1685
1318
|
}
|
|
@@ -1694,7 +1327,7 @@ function message(error) {
|
|
|
1694
1327
|
}
|
|
1695
1328
|
function discard(path) {
|
|
1696
1329
|
try {
|
|
1697
|
-
(0,
|
|
1330
|
+
(0, import_node_fs12.rmSync)(path, { recursive: true, force: true });
|
|
1698
1331
|
} catch {
|
|
1699
1332
|
}
|
|
1700
1333
|
}
|
|
@@ -1713,7 +1346,7 @@ function cursorHostEvidence(env) {
|
|
|
1713
1346
|
if (env[marker]?.trim()) return `env marker ${marker}`;
|
|
1714
1347
|
}
|
|
1715
1348
|
if (env.CURSOR_AGENT === "1") return "env marker CURSOR_AGENT=1";
|
|
1716
|
-
const result = process.platform === "win32" ? (0,
|
|
1349
|
+
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 });
|
|
1717
1350
|
if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process table is unreadable (unsafe to write)";
|
|
1718
1351
|
for (const line of result.stdout.split(/\r?\n/)) {
|
|
1719
1352
|
const executable = cursorExecutable(line);
|
|
@@ -1722,73 +1355,45 @@ function cursorHostEvidence(env) {
|
|
|
1722
1355
|
return null;
|
|
1723
1356
|
}
|
|
1724
1357
|
function resolvedPluginRoot(root) {
|
|
1725
|
-
return (0,
|
|
1358
|
+
return (0, import_node_path9.join)(root).replace(/\\/g, "/");
|
|
1726
1359
|
}
|
|
1727
1360
|
function isMmiEntry(entry, pluginRoot) {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
return command.replaceAll("${CURSOR_PLUGIN_ROOT}", resolvedPluginRoot(pluginRoot));
|
|
1732
|
-
}
|
|
1733
|
-
function desiredUserHooks(pluginRoot) {
|
|
1734
|
-
try {
|
|
1735
|
-
const doc = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
|
|
1736
|
-
const hooks = doc.hooks;
|
|
1737
|
-
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1738
|
-
const desired = /* @__PURE__ */ new Map();
|
|
1739
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
1740
|
-
if (!Array.isArray(entries)) return null;
|
|
1741
|
-
desired.set(event, entries.map((entry) => {
|
|
1742
|
-
const command = typeof entry.command === "string" ? entry.command : "";
|
|
1743
|
-
return { ...entry, command: resolveCommand(command, pluginRoot) };
|
|
1744
|
-
}));
|
|
1745
|
-
}
|
|
1746
|
-
return desired;
|
|
1747
|
-
} catch {
|
|
1748
|
-
return null;
|
|
1749
|
-
}
|
|
1361
|
+
if (typeof entry !== "object" || entry === null || typeof entry.command !== "string") return false;
|
|
1362
|
+
const command = entry.command;
|
|
1363
|
+
return command.includes(resolvedPluginRoot(pluginRoot)) || /(?:mmi-hook|hook-run\.mjs|hook-policy\.mjs)/i.test(command);
|
|
1750
1364
|
}
|
|
1751
|
-
function
|
|
1365
|
+
function stripUserHooks(existing, pluginRoot) {
|
|
1752
1366
|
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) return null;
|
|
1753
|
-
const
|
|
1754
|
-
const hooks =
|
|
1367
|
+
const stripped = { ...existing };
|
|
1368
|
+
const hooks = stripped.hooks;
|
|
1755
1369
|
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1756
|
-
const nextHooks = {};
|
|
1370
|
+
const nextHooks = { ...hooks };
|
|
1757
1371
|
for (const [event, entries] of Object.entries(hooks)) {
|
|
1758
1372
|
if (!Array.isArray(entries)) return null;
|
|
1759
1373
|
const kept = entries.filter((entry) => !isMmiEntry(entry, pluginRoot));
|
|
1760
|
-
nextHooks[event] =
|
|
1374
|
+
if (kept.length) nextHooks[event] = kept;
|
|
1375
|
+
else delete nextHooks[event];
|
|
1761
1376
|
}
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
merged.hooks = nextHooks;
|
|
1766
|
-
return merged;
|
|
1377
|
+
stripped.hooks = nextHooks;
|
|
1378
|
+
return stripped;
|
|
1767
1379
|
}
|
|
1768
1380
|
var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
1769
1381
|
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
1770
|
-
const
|
|
1771
|
-
if (!desired) return "source-unreadable";
|
|
1772
|
-
const path = (0, import_node_path10.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1382
|
+
const path = (0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1773
1383
|
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
1774
|
-
if (!(0,
|
|
1775
|
-
const fresh = `${path}.tmp-${process.pid}`;
|
|
1776
|
-
(0, import_node_fs13.writeFileSync)(fresh, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
|
|
1777
|
-
(0, import_node_fs13.renameSync)(fresh, path);
|
|
1778
|
-
return "updated";
|
|
1779
|
-
}
|
|
1384
|
+
if (!(0, import_node_fs12.existsSync)(path)) return "current";
|
|
1780
1385
|
let existing;
|
|
1781
1386
|
try {
|
|
1782
|
-
existing = JSON.parse((0,
|
|
1387
|
+
existing = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
|
|
1783
1388
|
} catch {
|
|
1784
1389
|
return "user-conflict";
|
|
1785
1390
|
}
|
|
1786
|
-
const
|
|
1787
|
-
if (!
|
|
1788
|
-
if (JSON.stringify(
|
|
1391
|
+
const stripped = stripUserHooks(existing, pluginRoot);
|
|
1392
|
+
if (!stripped) return "user-conflict";
|
|
1393
|
+
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
1789
1394
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1790
|
-
(0,
|
|
1791
|
-
(0,
|
|
1395
|
+
(0, import_node_fs12.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
1396
|
+
(0, import_node_fs12.renameSync)(tmp, path);
|
|
1792
1397
|
return "updated";
|
|
1793
1398
|
});
|
|
1794
1399
|
}
|
|
@@ -1801,45 +1406,43 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1801
1406
|
}
|
|
1802
1407
|
switch (verdict) {
|
|
1803
1408
|
case "updated":
|
|
1804
|
-
return "; user hooks
|
|
1409
|
+
return "; user hooks legacy entries stripped";
|
|
1805
1410
|
case "user-conflict":
|
|
1806
|
-
return `; USER HOOKS CONFLICT at ${(0,
|
|
1807
|
-
case "source-unreadable":
|
|
1808
|
-
return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
|
|
1411
|
+
return `; USER HOOKS CONFLICT at ${(0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 legacy entries were not changed`;
|
|
1809
1412
|
default:
|
|
1810
1413
|
return "";
|
|
1811
1414
|
}
|
|
1812
1415
|
}
|
|
1813
1416
|
function writeRollbackPointer(root, entry) {
|
|
1814
|
-
const path = (0,
|
|
1417
|
+
const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
|
|
1815
1418
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1816
1419
|
try {
|
|
1817
|
-
(0,
|
|
1818
|
-
(0,
|
|
1420
|
+
(0, import_node_fs12.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
1421
|
+
(0, import_node_fs12.renameSync)(tmp, path);
|
|
1819
1422
|
} catch {
|
|
1820
1423
|
discard(tmp);
|
|
1821
1424
|
}
|
|
1822
1425
|
}
|
|
1823
1426
|
function mtimeMs(path) {
|
|
1824
1427
|
try {
|
|
1825
|
-
return (0,
|
|
1428
|
+
return (0, import_node_fs12.statSync)(path).mtimeMs;
|
|
1826
1429
|
} catch {
|
|
1827
1430
|
return 0;
|
|
1828
1431
|
}
|
|
1829
1432
|
}
|
|
1830
1433
|
function pruneQuarantine(root, keep) {
|
|
1831
|
-
const dir = (0,
|
|
1434
|
+
const dir = (0, import_node_path9.join)(root, "quarantine");
|
|
1832
1435
|
let generations;
|
|
1833
1436
|
try {
|
|
1834
|
-
generations = (0,
|
|
1437
|
+
generations = (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path9.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
|
|
1835
1438
|
} catch {
|
|
1836
1439
|
return 0;
|
|
1837
1440
|
}
|
|
1838
1441
|
let stranded = 0;
|
|
1839
1442
|
try {
|
|
1840
|
-
for (const entry of (0,
|
|
1443
|
+
for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
|
|
1841
1444
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1842
|
-
const path = (0,
|
|
1445
|
+
const path = (0, import_node_path9.join)(dir, entry.name);
|
|
1843
1446
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
1844
1447
|
discard(path);
|
|
1845
1448
|
stranded += 1;
|
|
@@ -1857,16 +1460,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1857
1460
|
}
|
|
1858
1461
|
function restorePrevious(live, quarantined, broken) {
|
|
1859
1462
|
try {
|
|
1860
|
-
(0,
|
|
1463
|
+
(0, import_node_fs12.renameSync)(live, broken);
|
|
1861
1464
|
} catch {
|
|
1862
1465
|
return "kept-broken";
|
|
1863
1466
|
}
|
|
1864
1467
|
try {
|
|
1865
|
-
(0,
|
|
1468
|
+
(0, import_node_fs12.renameSync)(quarantined, live);
|
|
1866
1469
|
return "restored";
|
|
1867
1470
|
} catch {
|
|
1868
1471
|
try {
|
|
1869
|
-
(0,
|
|
1472
|
+
(0, import_node_fs12.renameSync)(broken, live);
|
|
1870
1473
|
return "kept-broken";
|
|
1871
1474
|
} catch {
|
|
1872
1475
|
return "lost";
|
|
@@ -1877,7 +1480,7 @@ function cursorArm(options) {
|
|
|
1877
1480
|
const env = options.env ?? process.env;
|
|
1878
1481
|
const { target, dryRun, paths } = options;
|
|
1879
1482
|
const root = cursorPluginsRoot();
|
|
1880
|
-
const live = (0,
|
|
1483
|
+
const live = (0, import_node_path9.join)(root, "local", "mmi");
|
|
1881
1484
|
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
1882
1485
|
surface: "cursor",
|
|
1883
1486
|
from,
|
|
@@ -1887,7 +1490,7 @@ function cursorArm(options) {
|
|
|
1887
1490
|
launchStaged: launchStaged || void 0,
|
|
1888
1491
|
// C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
|
|
1889
1492
|
// switch reports the restored generation's declaration, not the rejected one's (#4973).
|
|
1890
|
-
compat: readManifestCompat((0,
|
|
1493
|
+
compat: readManifestCompat((0, import_node_path9.join)(live, MANIFEST))
|
|
1891
1494
|
});
|
|
1892
1495
|
const current = manifest(live);
|
|
1893
1496
|
const installed = current?.version ?? null;
|
|
@@ -1898,10 +1501,10 @@ function cursorArm(options) {
|
|
|
1898
1501
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1899
1502
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1900
1503
|
}
|
|
1901
|
-
if (installed === target &&
|
|
1504
|
+
if (installed === target && treeHealthy(live)) {
|
|
1902
1505
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1903
1506
|
}
|
|
1904
|
-
if ((0,
|
|
1507
|
+
if ((0, import_node_fs12.existsSync)(live) && !mmiOwned(live)) {
|
|
1905
1508
|
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)`);
|
|
1906
1509
|
}
|
|
1907
1510
|
if (dryRun) {
|
|
@@ -1912,24 +1515,24 @@ function cursorArm(options) {
|
|
|
1912
1515
|
return result(installed, target, "defer", staged.defer);
|
|
1913
1516
|
}
|
|
1914
1517
|
const stagedMarker = manifest(staged.packageRoot)?.version;
|
|
1915
|
-
if (stagedMarker !== target || !
|
|
1916
|
-
return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${
|
|
1518
|
+
if (stagedMarker !== target || !treeHealthy(staged.packageRoot)) {
|
|
1519
|
+
return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
|
|
1917
1520
|
}
|
|
1918
|
-
const launchTargetPath = (0,
|
|
1521
|
+
const launchTargetPath = (0, import_node_path9.join)(root, "staging", "launch-target.json");
|
|
1919
1522
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1920
|
-
let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0,
|
|
1523
|
+
let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path9.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1921
1524
|
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
1922
|
-
if (!reuseIncoming || !(0,
|
|
1923
|
-
incoming = (0,
|
|
1924
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
1525
|
+
if (!reuseIncoming || !(0, import_node_fs12.existsSync)(incoming)) {
|
|
1526
|
+
incoming = (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1527
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
|
|
1925
1528
|
try {
|
|
1926
1529
|
discard(incoming);
|
|
1927
|
-
(0,
|
|
1530
|
+
(0, import_node_fs12.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1928
1531
|
} catch (error) {
|
|
1929
1532
|
discard(incoming);
|
|
1930
1533
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
1931
1534
|
}
|
|
1932
|
-
if (manifest(incoming)?.version !== target || !
|
|
1535
|
+
if (manifest(incoming)?.version !== target || !treeHealthy(incoming)) {
|
|
1933
1536
|
discard(incoming);
|
|
1934
1537
|
return result(installed, target, "defer", "the copy on the Cursor volume did not reproduce the verified tree");
|
|
1935
1538
|
}
|
|
@@ -1947,8 +1550,8 @@ function cursorArm(options) {
|
|
|
1947
1550
|
return result(installed, target, "ok", `staged ${target} for next launch while Cursor is running (${busy}) \u2014 ${pending}`, true);
|
|
1948
1551
|
}
|
|
1949
1552
|
const stamp = Date.now();
|
|
1950
|
-
const quarantined = (0,
|
|
1951
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
1553
|
+
const quarantined = (0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1554
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
|
|
1952
1555
|
const started = cursorHostEvidence(env);
|
|
1953
1556
|
if (started) {
|
|
1954
1557
|
const pending = noteLaunchStaged(paths, env, {
|
|
@@ -1963,15 +1566,15 @@ function cursorArm(options) {
|
|
|
1963
1566
|
}
|
|
1964
1567
|
let displaced = false;
|
|
1965
1568
|
try {
|
|
1966
|
-
if ((0,
|
|
1967
|
-
(0,
|
|
1569
|
+
if ((0, import_node_fs12.existsSync)(live)) {
|
|
1570
|
+
(0, import_node_fs12.renameSync)(live, quarantined);
|
|
1968
1571
|
displaced = true;
|
|
1969
1572
|
}
|
|
1970
|
-
(0,
|
|
1573
|
+
(0, import_node_fs12.renameSync)(incoming, live);
|
|
1971
1574
|
} catch (error) {
|
|
1972
|
-
if (displaced && !(0,
|
|
1575
|
+
if (displaced && !(0, import_node_fs12.existsSync)(live)) {
|
|
1973
1576
|
try {
|
|
1974
|
-
(0,
|
|
1577
|
+
(0, import_node_fs12.renameSync)(quarantined, live);
|
|
1975
1578
|
} catch {
|
|
1976
1579
|
}
|
|
1977
1580
|
}
|
|
@@ -1981,8 +1584,8 @@ function cursorArm(options) {
|
|
|
1981
1584
|
return result(installed, target, "defer", `generation switch failed, previous generation retained: ${reason}${pending}`);
|
|
1982
1585
|
}
|
|
1983
1586
|
const evidence = manifest(live)?.version;
|
|
1984
|
-
if (evidence !== target || !
|
|
1985
|
-
const broken = `${(0,
|
|
1587
|
+
if (evidence !== target || !treeHealthy(live)) {
|
|
1588
|
+
const broken = `${(0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
|
|
1986
1589
|
const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
|
|
1987
1590
|
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";
|
|
1988
1591
|
writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
|
|
@@ -1995,12 +1598,12 @@ function cursorArm(options) {
|
|
|
1995
1598
|
return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1996
1599
|
}
|
|
1997
1600
|
function probeCursorLaunchStaging() {
|
|
1998
|
-
return probeLaunchTargetVersion((0,
|
|
1601
|
+
return probeLaunchTargetVersion((0, import_node_path9.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
|
|
1999
1602
|
}
|
|
2000
1603
|
|
|
2001
1604
|
// src/arm-jervcode.ts
|
|
2002
|
-
var
|
|
2003
|
-
var
|
|
1605
|
+
var import_node_fs13 = require("node:fs");
|
|
1606
|
+
var import_node_path10 = require("node:path");
|
|
2004
1607
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
2005
1608
|
function hostEnv(env) {
|
|
2006
1609
|
const dir = jervcodeAgentDir(env);
|
|
@@ -2011,7 +1614,7 @@ function hostCommands(_env) {
|
|
|
2011
1614
|
}
|
|
2012
1615
|
function readPackageSpecs(env) {
|
|
2013
1616
|
try {
|
|
2014
|
-
const settings = JSON.parse((0,
|
|
1617
|
+
const settings = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
2015
1618
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
2016
1619
|
} catch {
|
|
2017
1620
|
return null;
|
|
@@ -2019,14 +1622,14 @@ function readPackageSpecs(env) {
|
|
|
2019
1622
|
}
|
|
2020
1623
|
function installedVersion(env) {
|
|
2021
1624
|
try {
|
|
2022
|
-
const manifest2 = JSON.parse((0,
|
|
1625
|
+
const manifest2 = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
|
|
2023
1626
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
2024
1627
|
} catch {
|
|
2025
1628
|
return null;
|
|
2026
1629
|
}
|
|
2027
1630
|
}
|
|
2028
1631
|
function materializedManifestLocation(env) {
|
|
2029
|
-
return (0,
|
|
1632
|
+
return (0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
2030
1633
|
}
|
|
2031
1634
|
var PI_LIST_SPEC = new RegExp(`npm:${PI_PACKAGE.replace("/", "\\/")}@(\\S+)`);
|
|
2032
1635
|
function parseListText(stdout) {
|
|
@@ -2085,32 +1688,32 @@ function jervcodeArm(options) {
|
|
|
2085
1688
|
detail,
|
|
2086
1689
|
// C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
|
|
2087
1690
|
// carry the previous payload's declaration (#4973 reconsult ruling).
|
|
2088
|
-
compat: readManifestCompat((0,
|
|
1691
|
+
compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
|
|
2089
1692
|
});
|
|
2090
1693
|
const before = readPackageSpecs(env);
|
|
2091
|
-
if (!before) return result(null, options.target, "defer", `cannot read ${(0,
|
|
1694
|
+
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
2092
1695
|
const from = installedVersion(env);
|
|
2093
1696
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
2094
1697
|
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
2095
1698
|
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;
|
|
2096
1699
|
if (options.dryRun) {
|
|
2097
1700
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
2098
|
-
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,
|
|
1701
|
+
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_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
2099
1702
|
}
|
|
2100
1703
|
const hosted = hostEnv(env);
|
|
2101
1704
|
const commands = hostCommands(env);
|
|
2102
1705
|
const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
2103
|
-
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,
|
|
1706
|
+
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_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
2104
1707
|
if (!floor && (!before.includes(exact) || from !== options.target)) {
|
|
2105
1708
|
const install = runner(command, ["install", exact], hosted);
|
|
2106
1709
|
if (install.status !== 0) {
|
|
2107
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0,
|
|
1710
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
2108
1711
|
}
|
|
2109
1712
|
}
|
|
2110
1713
|
for (const stale of legacy) {
|
|
2111
1714
|
const removed = runner(command, ["remove", stale], hosted);
|
|
2112
1715
|
if (removed.status !== 0) {
|
|
2113
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0,
|
|
1716
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
2114
1717
|
}
|
|
2115
1718
|
}
|
|
2116
1719
|
const after = readPackageSpecs(env);
|
|
@@ -2118,7 +1721,7 @@ function jervcodeArm(options) {
|
|
|
2118
1721
|
const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
|
|
2119
1722
|
const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
|
|
2120
1723
|
if (floor && !legacyAfter.length) {
|
|
2121
|
-
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0,
|
|
1724
|
+
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
2122
1725
|
}
|
|
2123
1726
|
if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
|
|
2124
1727
|
return {
|
|
@@ -2139,15 +1742,15 @@ function jervcodeArm(options) {
|
|
|
2139
1742
|
}
|
|
2140
1743
|
|
|
2141
1744
|
// src/arm-hermes.ts
|
|
2142
|
-
var
|
|
2143
|
-
var
|
|
2144
|
-
var
|
|
1745
|
+
var import_node_fs14 = require("node:fs");
|
|
1746
|
+
var import_node_child_process8 = require("node:child_process");
|
|
1747
|
+
var import_node_path11 = require("node:path");
|
|
2145
1748
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
2146
1749
|
var MANIFEST2 = "plugin.yaml";
|
|
2147
1750
|
var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
|
|
2148
1751
|
function yamlVersion(root) {
|
|
2149
1752
|
try {
|
|
2150
|
-
const text = (0,
|
|
1753
|
+
const text = (0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
|
|
2151
1754
|
try {
|
|
2152
1755
|
const version = JSON.parse(text).version;
|
|
2153
1756
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -2161,7 +1764,7 @@ function yamlVersion(root) {
|
|
|
2161
1764
|
}
|
|
2162
1765
|
function packageTreeMarkers(root) {
|
|
2163
1766
|
try {
|
|
2164
|
-
const files = JSON.parse((0,
|
|
1767
|
+
const files = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).files;
|
|
2165
1768
|
if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
2166
1769
|
return files.map((entry) => entry.trim());
|
|
2167
1770
|
}
|
|
@@ -2170,10 +1773,10 @@ function packageTreeMarkers(root) {
|
|
|
2170
1773
|
return TREE_MARKERS_FALLBACK;
|
|
2171
1774
|
}
|
|
2172
1775
|
function markerPresent(root, rel) {
|
|
2173
|
-
const path = (0,
|
|
2174
|
-
if (!(0,
|
|
1776
|
+
const path = (0, import_node_path11.join)(root, ...rel.split("/"));
|
|
1777
|
+
if (!(0, import_node_fs14.existsSync)(path)) return false;
|
|
2175
1778
|
try {
|
|
2176
|
-
const stat = (0,
|
|
1779
|
+
const stat = (0, import_node_fs14.statSync)(path);
|
|
2177
1780
|
return stat.isFile() || stat.isDirectory();
|
|
2178
1781
|
} catch {
|
|
2179
1782
|
return false;
|
|
@@ -2183,7 +1786,7 @@ function treeHealth(root) {
|
|
|
2183
1786
|
const missing = packageTreeMarkers(root).filter((rel) => !markerPresent(root, rel));
|
|
2184
1787
|
return { healthy: missing.length === 0, missing };
|
|
2185
1788
|
}
|
|
2186
|
-
function
|
|
1789
|
+
function treeHealthy2(root) {
|
|
2187
1790
|
return treeHealth(root).healthy;
|
|
2188
1791
|
}
|
|
2189
1792
|
function treeIncompleteDetail(root, label) {
|
|
@@ -2192,13 +1795,13 @@ function treeIncompleteDetail(root, label) {
|
|
|
2192
1795
|
}
|
|
2193
1796
|
function packageIdentity(root) {
|
|
2194
1797
|
try {
|
|
2195
|
-
return JSON.parse((0,
|
|
1798
|
+
return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
2196
1799
|
} catch {
|
|
2197
1800
|
return false;
|
|
2198
1801
|
}
|
|
2199
1802
|
}
|
|
2200
1803
|
function probeHermesInstallation(env = process.env) {
|
|
2201
|
-
const location = (0,
|
|
1804
|
+
const location = (0, import_node_path11.join)(hermesHome(env), "plugins", "mmi");
|
|
2202
1805
|
const version = yamlVersion(location);
|
|
2203
1806
|
return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
|
|
2204
1807
|
}
|
|
@@ -2207,7 +1810,7 @@ function mmiOwned2(root) {
|
|
|
2207
1810
|
}
|
|
2208
1811
|
function discard2(path) {
|
|
2209
1812
|
try {
|
|
2210
|
-
(0,
|
|
1813
|
+
(0, import_node_fs14.rmSync)(path, { recursive: true, force: true });
|
|
2211
1814
|
} catch {
|
|
2212
1815
|
}
|
|
2213
1816
|
}
|
|
@@ -2226,7 +1829,7 @@ function hermesProcessToken(line, home) {
|
|
|
2226
1829
|
return hermesToken(tokens) ?? (home && normalized.toLowerCase().includes(home.toLowerCase()) ? home : null);
|
|
2227
1830
|
}
|
|
2228
1831
|
function realProcessListing() {
|
|
2229
|
-
const result = process.platform === "win32" ? (0,
|
|
1832
|
+
const result = process.platform === "win32" ? (0, import_node_child_process8.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process8.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
|
|
2230
1833
|
return result.status === 0 ? result.stdout ?? "" : null;
|
|
2231
1834
|
}
|
|
2232
1835
|
function hermesHostEvidence(env, home, processListProvider) {
|
|
@@ -2239,19 +1842,19 @@ function hermesHostEvidence(env, home, processListProvider) {
|
|
|
2239
1842
|
return null;
|
|
2240
1843
|
}
|
|
2241
1844
|
function materializeHermesSkills(home) {
|
|
2242
|
-
const pluginSkills = (0,
|
|
2243
|
-
const skillsRoot = (0,
|
|
2244
|
-
const targetRoot = (0,
|
|
1845
|
+
const pluginSkills = (0, import_node_path11.join)(home, "plugins", "mmi", "skills");
|
|
1846
|
+
const skillsRoot = (0, import_node_path11.join)(home, "skills");
|
|
1847
|
+
const targetRoot = (0, import_node_path11.join)(skillsRoot, "mmi");
|
|
2245
1848
|
try {
|
|
2246
|
-
if (!(0,
|
|
2247
|
-
const names = (0,
|
|
1849
|
+
if (!(0, import_node_fs14.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
1850
|
+
const names = (0, import_node_fs14.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
2248
1851
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
2249
|
-
(0,
|
|
2250
|
-
const incoming = (0,
|
|
1852
|
+
(0, import_node_fs14.mkdirSync)(skillsRoot, { recursive: true });
|
|
1853
|
+
const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
|
|
2251
1854
|
discard2(incoming);
|
|
2252
|
-
(0,
|
|
1855
|
+
(0, import_node_fs14.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
2253
1856
|
discard2(targetRoot);
|
|
2254
|
-
(0,
|
|
1857
|
+
(0, import_node_fs14.renameSync)(incoming, targetRoot);
|
|
2255
1858
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
2256
1859
|
} catch (error) {
|
|
2257
1860
|
return { ok: false, detail: message2(error) };
|
|
@@ -2260,8 +1863,8 @@ function materializeHermesSkills(home) {
|
|
|
2260
1863
|
function hermesArm(options) {
|
|
2261
1864
|
const env = options.env ?? process.env;
|
|
2262
1865
|
const home = hermesHome(env);
|
|
2263
|
-
const parent = (0,
|
|
2264
|
-
const live = (0,
|
|
1866
|
+
const parent = (0, import_node_path11.join)(home, "plugins");
|
|
1867
|
+
const live = (0, import_node_path11.join)(parent, "mmi");
|
|
2265
1868
|
const installed = yamlVersion(live);
|
|
2266
1869
|
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
2267
1870
|
surface: "hermes",
|
|
@@ -2274,18 +1877,18 @@ function hermesArm(options) {
|
|
|
2274
1877
|
if (!hermesHostPresent(env, options.hostCommandRunner)) {
|
|
2275
1878
|
return result(null, options.target, "skip", `host absent (${hermesHostCommand(env)} CLI not on PATH) \u2014 skipped without writes`);
|
|
2276
1879
|
}
|
|
2277
|
-
if ((0,
|
|
1880
|
+
if ((0, import_node_fs14.existsSync)(live) && !mmiOwned2(live)) {
|
|
2278
1881
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
2279
1882
|
}
|
|
2280
1883
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
2281
1884
|
const highWater = journalHighWater(options.paths.journalPath, "hermes");
|
|
2282
1885
|
if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
|
|
2283
|
-
if (installed === options.target &&
|
|
1886
|
+
if (installed === options.target && treeHealthy2(live)) {
|
|
2284
1887
|
const skills2 = materializeHermesSkills(home);
|
|
2285
1888
|
if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
|
|
2286
1889
|
return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
|
|
2287
1890
|
}
|
|
2288
|
-
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,
|
|
1891
|
+
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_path11.join)(home, "skills", "mmi")}`);
|
|
2289
1892
|
const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
|
|
2290
1893
|
if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
|
|
2291
1894
|
const stagedVersion = yamlVersion(staged.packageRoot);
|
|
@@ -2294,19 +1897,19 @@ function hermesArm(options) {
|
|
|
2294
1897
|
}
|
|
2295
1898
|
const stagedTree = treeIncompleteDetail(staged.packageRoot, "staged Hermes");
|
|
2296
1899
|
if (stagedTree) return result(installed, options.target, "defer", stagedTree);
|
|
2297
|
-
const scratch = (0,
|
|
2298
|
-
const launchTargetPath = (0,
|
|
2299
|
-
const incomingRoot = (0,
|
|
2300
|
-
const incoming = (0,
|
|
2301
|
-
const quarantine = (0,
|
|
1900
|
+
const scratch = (0, import_node_path11.join)(home, ".mmi-updater");
|
|
1901
|
+
const launchTargetPath = (0, import_node_path11.join)(scratch, "launch-target.json");
|
|
1902
|
+
const incomingRoot = (0, import_node_path11.join)(scratch, "incoming");
|
|
1903
|
+
const incoming = (0, import_node_path11.join)(incomingRoot, options.target);
|
|
1904
|
+
const quarantine = (0, import_node_path11.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
|
|
2302
1905
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
2303
|
-
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target &&
|
|
1906
|
+
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
|
|
2304
1907
|
if (!reuseIncoming) {
|
|
2305
1908
|
try {
|
|
2306
|
-
(0,
|
|
2307
|
-
(0,
|
|
1909
|
+
(0, import_node_fs14.mkdirSync)(parent, { recursive: true });
|
|
1910
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
|
|
2308
1911
|
discard2(incomingRoot);
|
|
2309
|
-
(0,
|
|
1912
|
+
(0, import_node_fs14.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
2310
1913
|
} catch (error) {
|
|
2311
1914
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
2312
1915
|
}
|
|
@@ -2334,13 +1937,13 @@ function hermesArm(options) {
|
|
|
2334
1937
|
});
|
|
2335
1938
|
return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
|
|
2336
1939
|
}
|
|
2337
|
-
const displaced = (0,
|
|
1940
|
+
const displaced = (0, import_node_fs14.existsSync)(live);
|
|
2338
1941
|
try {
|
|
2339
|
-
if (displaced) (0,
|
|
2340
|
-
(0,
|
|
1942
|
+
if (displaced) (0, import_node_fs14.renameSync)(live, quarantine);
|
|
1943
|
+
(0, import_node_fs14.renameSync)(incoming, live);
|
|
2341
1944
|
} catch (error) {
|
|
2342
|
-
if (displaced && !(0,
|
|
2343
|
-
(0,
|
|
1945
|
+
if (displaced && !(0, import_node_fs14.existsSync)(live)) try {
|
|
1946
|
+
(0, import_node_fs14.renameSync)(quarantine, live);
|
|
2344
1947
|
} catch {
|
|
2345
1948
|
}
|
|
2346
1949
|
discard2(incomingRoot);
|
|
@@ -2351,12 +1954,12 @@ function hermesArm(options) {
|
|
|
2351
1954
|
const liveVersion = yamlVersion(live);
|
|
2352
1955
|
const liveTree = treeIncompleteDetail(live, "live Hermes");
|
|
2353
1956
|
if (liveVersion !== options.target || liveTree) {
|
|
2354
|
-
const broken = (0,
|
|
1957
|
+
const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
2355
1958
|
const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
|
|
2356
1959
|
try {
|
|
2357
|
-
(0,
|
|
2358
|
-
(0,
|
|
2359
|
-
if (displaced) (0,
|
|
1960
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
|
|
1961
|
+
(0, import_node_fs14.renameSync)(live, broken);
|
|
1962
|
+
if (displaced) (0, import_node_fs14.renameSync)(quarantine, live);
|
|
2360
1963
|
} catch (error) {
|
|
2361
1964
|
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
2362
1965
|
}
|
|
@@ -2369,21 +1972,17 @@ function hermesArm(options) {
|
|
|
2369
1972
|
return result(installed, options.target, "ok", `converged ${installed ?? "absent"} -> ${options.target} (staged, verified, atomically switched${displaced ? `; previous generation quarantined at ${quarantine}` : ""}); ${skills.detail}`);
|
|
2370
1973
|
}
|
|
2371
1974
|
function probeHermesLaunchStaging(env = process.env) {
|
|
2372
|
-
return probeLaunchTargetVersion((0,
|
|
1975
|
+
return probeLaunchTargetVersion((0, import_node_path11.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
|
|
2373
1976
|
}
|
|
2374
1977
|
|
|
2375
1978
|
// src/reap.ts
|
|
2376
|
-
var
|
|
2377
|
-
var
|
|
2378
|
-
var import_node_path13 = require("node:path");
|
|
1979
|
+
var import_node_fs15 = require("node:fs");
|
|
1980
|
+
var import_node_path12 = require("node:path");
|
|
2379
1981
|
var KEEP_GENERATIONS2 = 2;
|
|
2380
1982
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
2381
|
-
var ORPHAN_MIN_AGE_MS = 10 * 6e4;
|
|
2382
|
-
var SNAPSHOT_TIMEOUT_MS = 2e4;
|
|
2383
|
-
var MAX_UNWIND_ROUNDS = 4;
|
|
2384
1983
|
function listDirs(path) {
|
|
2385
1984
|
try {
|
|
2386
|
-
return (0,
|
|
1985
|
+
return (0, import_node_fs15.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2387
1986
|
} catch {
|
|
2388
1987
|
return [];
|
|
2389
1988
|
}
|
|
@@ -2391,7 +1990,7 @@ function listDirs(path) {
|
|
|
2391
1990
|
function generationSets(root) {
|
|
2392
1991
|
const children = listDirs(root);
|
|
2393
1992
|
if (children.some((name) => parseSemver(name))) return [root];
|
|
2394
|
-
return children.map((name) => (0,
|
|
1993
|
+
return children.map((name) => (0, import_node_path12.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
|
|
2395
1994
|
}
|
|
2396
1995
|
function pruneGenerations(root, category, dryRun) {
|
|
2397
1996
|
let reaped = 0;
|
|
@@ -2404,7 +2003,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2404
2003
|
continue;
|
|
2405
2004
|
}
|
|
2406
2005
|
try {
|
|
2407
|
-
(0,
|
|
2006
|
+
(0, import_node_fs15.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
|
|
2408
2007
|
reaped += 1;
|
|
2409
2008
|
} catch (error) {
|
|
2410
2009
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2418,8 +2017,8 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2418
2017
|
}
|
|
2419
2018
|
var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
|
|
2420
2019
|
function atomicWriteScratch(path) {
|
|
2421
|
-
const literal = (0,
|
|
2422
|
-
return { dir: (0,
|
|
2020
|
+
const literal = (0, import_node_path12.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2021
|
+
return { dir: (0, import_node_path12.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
|
|
2423
2022
|
}
|
|
2424
2023
|
function reapScratch(context, dryRun) {
|
|
2425
2024
|
const category = "scratch";
|
|
@@ -2437,7 +2036,7 @@ function reapScratch(context, dryRun) {
|
|
|
2437
2036
|
for (const [dir, patterns] of scanned) {
|
|
2438
2037
|
let entries;
|
|
2439
2038
|
try {
|
|
2440
|
-
entries = (0,
|
|
2039
|
+
entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
|
|
2441
2040
|
} catch {
|
|
2442
2041
|
unreadable.push(dir);
|
|
2443
2042
|
continue;
|
|
@@ -2445,10 +2044,10 @@ function reapScratch(context, dryRun) {
|
|
|
2445
2044
|
for (const entry of entries) {
|
|
2446
2045
|
if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
|
|
2447
2046
|
if (!entry.isFile()) continue;
|
|
2448
|
-
const path = (0,
|
|
2047
|
+
const path = (0, import_node_path12.join)(dir, entry.name);
|
|
2449
2048
|
let ageMs;
|
|
2450
2049
|
try {
|
|
2451
|
-
ageMs = now - (0,
|
|
2050
|
+
ageMs = now - (0, import_node_fs15.statSync)(path).mtimeMs;
|
|
2452
2051
|
} catch {
|
|
2453
2052
|
continue;
|
|
2454
2053
|
}
|
|
@@ -2458,7 +2057,7 @@ function reapScratch(context, dryRun) {
|
|
|
2458
2057
|
continue;
|
|
2459
2058
|
}
|
|
2460
2059
|
try {
|
|
2461
|
-
(0,
|
|
2060
|
+
(0, import_node_fs15.rmSync)(path, { force: true });
|
|
2462
2061
|
reaped += 1;
|
|
2463
2062
|
} catch (error) {
|
|
2464
2063
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2471,194 +2070,6 @@ function reapScratch(context, dryRun) {
|
|
|
2471
2070
|
const stuck = failures.length ? `; ${failures.length} locked (${failures.join(", ")})` : "";
|
|
2472
2071
|
return { category, reaped, verdict: failures.length ? "defer" : "ok", detail: `${verb} ${reaped} aged scratch entr(ies) under ${swept.join(", ")}${stuck}` };
|
|
2473
2072
|
}
|
|
2474
|
-
var HOOK_IMAGES = /* @__PURE__ */ new Set(["mmi-hook.exe", "node.exe", "cmd.exe", "powershell.exe", "pwsh.exe"]);
|
|
2475
|
-
var HOOK_ARTIFACT = /(?:^|[\\/])(?:mmi-hook(?:-console)?(?:\.exe|\.cmd|\.bat)?|hook-run\.mjs)$/i;
|
|
2476
|
-
var NODE_INTERPRETER = /(?:^|[\\/])node(?:\.exe)?$/i;
|
|
2477
|
-
var NODE_VALUE_FLAGS = /* @__PURE__ */ new Set(["-e", "--eval", "-p", "--print", "-r", "--require", "--import", "--loader", "--experimental-loader", "-C", "--conditions"]);
|
|
2478
|
-
var CMD_SHELL = /(?:^|[\\/])cmd(?:\.exe)?$/i;
|
|
2479
|
-
var CMD_RUN_FLAGS = /* @__PURE__ */ new Set(["/c", "/k"]);
|
|
2480
|
-
var PS_SHELL = /(?:^|[\\/])(?:powershell|pwsh)(?:\.exe)?$/i;
|
|
2481
|
-
var PS_COMMAND_FLAGS = /* @__PURE__ */ new Set(["-command", "-c"]);
|
|
2482
|
-
var SEPARATORS = ["&", "|"];
|
|
2483
|
-
function tokenize(commandLine) {
|
|
2484
|
-
const tokens = [];
|
|
2485
|
-
let current = "";
|
|
2486
|
-
let quoted = false;
|
|
2487
|
-
let slashes = 0;
|
|
2488
|
-
for (const char of commandLine) {
|
|
2489
|
-
if (char === "\\") {
|
|
2490
|
-
slashes += 1;
|
|
2491
|
-
continue;
|
|
2492
|
-
}
|
|
2493
|
-
if (char === '"') {
|
|
2494
|
-
current += "\\".repeat(slashes >> 1);
|
|
2495
|
-
if (slashes % 2) current += '"';
|
|
2496
|
-
else quoted = !quoted;
|
|
2497
|
-
slashes = 0;
|
|
2498
|
-
continue;
|
|
2499
|
-
}
|
|
2500
|
-
current += "\\".repeat(slashes);
|
|
2501
|
-
slashes = 0;
|
|
2502
|
-
if (!quoted && /\s/.test(char)) {
|
|
2503
|
-
if (current) tokens.push(current);
|
|
2504
|
-
current = "";
|
|
2505
|
-
continue;
|
|
2506
|
-
}
|
|
2507
|
-
current += char;
|
|
2508
|
-
}
|
|
2509
|
-
current += "\\".repeat(slashes);
|
|
2510
|
-
if (current) tokens.push(current);
|
|
2511
|
-
return tokens;
|
|
2512
|
-
}
|
|
2513
|
-
function invocation(commandLine) {
|
|
2514
|
-
let tokens = tokenize(commandLine);
|
|
2515
|
-
if (tokens.length && CMD_SHELL.test(tokens[0])) {
|
|
2516
|
-
const flag = tokens.findIndex((token) => CMD_RUN_FLAGS.has(token.toLowerCase()));
|
|
2517
|
-
if (flag < 0) return [];
|
|
2518
|
-
const rest = tokens.slice(flag + 1);
|
|
2519
|
-
tokens = rest.length === 1 ? tokenize(rest[0]) : rest;
|
|
2520
|
-
} else if (tokens.length && PS_SHELL.test(tokens[0])) {
|
|
2521
|
-
const flag = tokens.findIndex((token) => PS_COMMAND_FLAGS.has(token.toLowerCase()));
|
|
2522
|
-
if (flag < 0) return [];
|
|
2523
|
-
const rest = tokens.slice(flag + 1);
|
|
2524
|
-
tokens = rest.length === 1 ? tokenize(rest[0]) : rest;
|
|
2525
|
-
if (tokens[0] === "&") tokens = tokens.slice(1);
|
|
2526
|
-
}
|
|
2527
|
-
const cut = tokens.findIndex((token) => SEPARATORS.some((separator) => token.includes(separator)));
|
|
2528
|
-
return cut < 0 ? tokens : tokens.slice(0, cut);
|
|
2529
|
-
}
|
|
2530
|
-
function hookProgram(tokens) {
|
|
2531
|
-
if (!tokens.length) return null;
|
|
2532
|
-
if (HOOK_ARTIFACT.test(tokens[0])) return tokens[0];
|
|
2533
|
-
if (!NODE_INTERPRETER.test(tokens[0])) return null;
|
|
2534
|
-
let at = 1;
|
|
2535
|
-
while (at < tokens.length && tokens[at].startsWith("-")) {
|
|
2536
|
-
const flag = tokens[at].toLowerCase();
|
|
2537
|
-
at += !flag.includes("=") && NODE_VALUE_FLAGS.has(flag) ? 2 : 1;
|
|
2538
|
-
}
|
|
2539
|
-
return at < tokens.length && HOOK_ARTIFACT.test(tokens[at]) ? tokens[at] : null;
|
|
2540
|
-
}
|
|
2541
|
-
function flagValue(tokens, flag) {
|
|
2542
|
-
const at = tokens.indexOf(flag);
|
|
2543
|
-
return at >= 0 && at + 1 < tokens.length ? tokens[at + 1] : null;
|
|
2544
|
-
}
|
|
2545
|
-
function selectHookOrphans(rows, livePids, options) {
|
|
2546
|
-
const kills = [];
|
|
2547
|
-
for (const row of rows) {
|
|
2548
|
-
if (row.pid === options.selfPid) continue;
|
|
2549
|
-
if (!HOOK_IMAGES.has(row.name.toLowerCase())) continue;
|
|
2550
|
-
const tokens = invocation(row.commandLine);
|
|
2551
|
-
const program = hookProgram(tokens);
|
|
2552
|
-
if (!program) continue;
|
|
2553
|
-
const surface = flagValue(tokens, "--surface");
|
|
2554
|
-
const gate = flagValue(tokens, "--gate");
|
|
2555
|
-
if (!surface || !gate) continue;
|
|
2556
|
-
if (livePids.has(row.ppid)) continue;
|
|
2557
|
-
if (row.startedMs === null) continue;
|
|
2558
|
-
const ageMs = options.now - row.startedMs;
|
|
2559
|
-
if (ageMs < options.minAgeMs) continue;
|
|
2560
|
-
kills.push({
|
|
2561
|
-
pid: row.pid,
|
|
2562
|
-
name: row.name,
|
|
2563
|
-
reason: `runs ${program} --surface ${surface} --gate ${gate}; parent ${row.ppid} absent from the snapshot; alive ${Math.round(ageMs / 6e4)}m`,
|
|
2564
|
-
identity: row
|
|
2565
|
-
});
|
|
2566
|
-
}
|
|
2567
|
-
return kills;
|
|
2568
|
-
}
|
|
2569
|
-
function stillTheProvenProcess(rows, kill) {
|
|
2570
|
-
const live = rows.find((row) => row.pid === kill.pid);
|
|
2571
|
-
if (!live) return false;
|
|
2572
|
-
const same = live.name === kill.identity.name && live.commandLine === kill.identity.commandLine && live.startedMs === kill.identity.startedMs && live.ppid === kill.identity.ppid;
|
|
2573
|
-
return same && !rows.some((row) => row.pid === kill.identity.ppid);
|
|
2574
|
-
}
|
|
2575
|
-
var SNAPSHOT_COMMAND = "Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,CommandLine,@{n='Started';e={if ($_.CreationDate) { $_.CreationDate.ToUniversalTime().ToString('o') }}} | ConvertTo-Json -Compress";
|
|
2576
|
-
function processSnapshot() {
|
|
2577
|
-
const result = (0, import_node_child_process10.spawnSync)("powershell.exe", ["-NoProfile", "-Command", SNAPSHOT_COMMAND], { encoding: "utf8", windowsHide: true, timeout: SNAPSHOT_TIMEOUT_MS });
|
|
2578
|
-
if (result.status !== 0 || !(result.stdout ?? "").trim()) return null;
|
|
2579
|
-
try {
|
|
2580
|
-
const parsed = JSON.parse(result.stdout);
|
|
2581
|
-
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
2582
|
-
return rows.filter((row) => typeof row?.ProcessId === "number").map((row) => ({
|
|
2583
|
-
pid: row.ProcessId,
|
|
2584
|
-
ppid: typeof row.ParentProcessId === "number" ? row.ParentProcessId : -1,
|
|
2585
|
-
name: typeof row.Name === "string" ? row.Name : "",
|
|
2586
|
-
commandLine: typeof row.CommandLine === "string" ? row.CommandLine : "",
|
|
2587
|
-
startedMs: typeof row.Started === "string" ? Number.isNaN(Date.parse(row.Started)) ? null : Date.parse(row.Started) : null
|
|
2588
|
-
}));
|
|
2589
|
-
} catch {
|
|
2590
|
-
return null;
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
function reapHookOrphans(dryRun) {
|
|
2594
|
-
const category = "hook-orphans";
|
|
2595
|
-
if (process.platform !== "win32") {
|
|
2596
|
-
return { category, reaped: 0, verdict: "skip", detail: "dead-parent hook reap is Windows-only (#4943 class) \u2014 skipped" };
|
|
2597
|
-
}
|
|
2598
|
-
const snapshot = processSnapshot();
|
|
2599
|
-
if (!snapshot) {
|
|
2600
|
-
return { category, reaped: 0, verdict: "skip", detail: "process list unreadable \u2014 nothing is provably an orphan, so nothing was killed" };
|
|
2601
|
-
}
|
|
2602
|
-
const select = (rows) => selectHookOrphans(rows, new Set(rows.map((row) => row.pid)), { now: Date.now(), selfPid: process.pid, minAgeMs: ORPHAN_MIN_AGE_MS });
|
|
2603
|
-
const first = select(snapshot);
|
|
2604
|
-
if (!first.length) return { category, reaped: 0, verdict: "skip", detail: `no dead-parent MMI hook descendants among ${snapshot.length} processes` };
|
|
2605
|
-
if (dryRun) {
|
|
2606
|
-
const named2 = first.map((kill) => `${kill.name}#${kill.pid} (${kill.reason})`).join("; ");
|
|
2607
|
-
return { category, reaped: first.length, verdict: "ok", detail: `dry-run: would kill ${first.length} dead-parent hook orphan(s) in the first unwind round: ${named2}` };
|
|
2608
|
-
}
|
|
2609
|
-
const reaped = [];
|
|
2610
|
-
const survived = [];
|
|
2611
|
-
const declined = [];
|
|
2612
|
-
const attempted = /* @__PURE__ */ new Set();
|
|
2613
|
-
let pending = first;
|
|
2614
|
-
let rounds = 0;
|
|
2615
|
-
let halted = "";
|
|
2616
|
-
while (pending.length && rounds < MAX_UNWIND_ROUNDS) {
|
|
2617
|
-
rounds += 1;
|
|
2618
|
-
const confirm = processSnapshot();
|
|
2619
|
-
if (!confirm) {
|
|
2620
|
-
halted = "a re-validation snapshot was unreadable, so the remaining rows were not killed";
|
|
2621
|
-
break;
|
|
2622
|
-
}
|
|
2623
|
-
const proven = pending.filter((kill) => stillTheProvenProcess(confirm, kill));
|
|
2624
|
-
declined.push(...pending.filter((kill) => !proven.includes(kill)));
|
|
2625
|
-
for (const kill of pending) attempted.add(kill.pid);
|
|
2626
|
-
pending = [];
|
|
2627
|
-
if (!proven.length) break;
|
|
2628
|
-
for (const orphan of proven) {
|
|
2629
|
-
try {
|
|
2630
|
-
process.kill(orphan.pid);
|
|
2631
|
-
} catch {
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
const after = processSnapshot();
|
|
2635
|
-
if (!after) {
|
|
2636
|
-
halted = `${proven.length} kill(s) were issued but the verification snapshot was unreadable \u2014 they are not claimed reaped`;
|
|
2637
|
-
break;
|
|
2638
|
-
}
|
|
2639
|
-
const alive = new Set(after.map((row) => row.pid));
|
|
2640
|
-
for (const orphan of proven) (alive.has(orphan.pid) ? survived : reaped).push(orphan);
|
|
2641
|
-
pending = select(after).filter((kill) => !attempted.has(kill.pid));
|
|
2642
|
-
}
|
|
2643
|
-
const notes = [];
|
|
2644
|
-
if (survived.length) notes.push(`${survived.length} survived the kill (${survived.map((kill) => kill.pid).join(", ")})`);
|
|
2645
|
-
if (declined.length) notes.push(`${declined.length} declined at re-validation \u2014 gone on its own, or no longer the row that was proven (${declined.map((kill) => kill.pid).join(", ")})`);
|
|
2646
|
-
if (pending.length) {
|
|
2647
|
-
const why = rounds >= MAX_UNWIND_ROUNDS ? `the ${MAX_UNWIND_ROUNDS}-round unwind budget ran out` : "this tick stopped short";
|
|
2648
|
-
notes.push(`${pending.length} hook orphan(s) still standing because ${why} (${pending.map((kill) => `${kill.name}#${kill.pid}`).join(", ")}) \u2014 next tick continues`);
|
|
2649
|
-
}
|
|
2650
|
-
if (halted) notes.push(halted);
|
|
2651
|
-
const named = reaped.map((kill) => `${kill.name}#${kill.pid} (${kill.reason})`).join("; ");
|
|
2652
|
-
if (!notes.length) {
|
|
2653
|
-
return { category, reaped: reaped.length, verdict: "ok", detail: `reaped ${reaped.length} dead-parent hook orphan(s) over ${rounds} unwind round(s), verified gone: ${named}` };
|
|
2654
|
-
}
|
|
2655
|
-
return {
|
|
2656
|
-
category,
|
|
2657
|
-
reaped: reaped.length,
|
|
2658
|
-
verdict: "defer",
|
|
2659
|
-
detail: `reaped ${reaped.length} dead-parent hook orphan(s) over ${rounds} unwind round(s)${named ? ` (${named})` : ""}; ${notes.join("; ")}`
|
|
2660
|
-
};
|
|
2661
|
-
}
|
|
2662
2073
|
function reap(options) {
|
|
2663
2074
|
const { paths, dryRun } = options;
|
|
2664
2075
|
const env = options.env ?? process.env;
|
|
@@ -2669,11 +2080,10 @@ function reap(options) {
|
|
|
2669
2080
|
return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}` };
|
|
2670
2081
|
}
|
|
2671
2082
|
};
|
|
2672
|
-
const atomicWritePaths = [claudeMarketplacesPath(env)
|
|
2083
|
+
const atomicWritePaths = [claudeMarketplacesPath(env)];
|
|
2673
2084
|
return [
|
|
2674
2085
|
guarded("staging-prune", () => pruneGenerations(paths.staging, "staging-prune", dryRun)),
|
|
2675
|
-
guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun))
|
|
2676
|
-
guarded("hook-orphans", () => reapHookOrphans(dryRun))
|
|
2086
|
+
guarded("scratch", () => reapScratch({ root: paths.root, atomicWritePaths }, dryRun))
|
|
2677
2087
|
];
|
|
2678
2088
|
}
|
|
2679
2089
|
|
|
@@ -2698,7 +2108,7 @@ function reconcile(options) {
|
|
|
2698
2108
|
const stamped = options.dryRun ? { ...event, dryRun: true } : event;
|
|
2699
2109
|
if (!appendJournal(paths.journalPath, { ts: (/* @__PURE__ */ new Date()).toISOString(), run, ...stamped })) summary.journalOk = false;
|
|
2700
2110
|
};
|
|
2701
|
-
const summary = { run, target: null, gate: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, reexec: null, exit: 0 };
|
|
2111
|
+
const summary = { run, target: null, gate: null, managedSurfaces: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, reexec: null, exit: 0 };
|
|
2702
2112
|
const recordArm = (arm) => {
|
|
2703
2113
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2704
2114
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
@@ -2747,6 +2157,7 @@ function reconcile(options) {
|
|
|
2747
2157
|
continue;
|
|
2748
2158
|
}
|
|
2749
2159
|
summary.target = candidate;
|
|
2160
|
+
summary.managedSurfaces = Array.isArray(gate.bom?.managedSurfaces) ? gate.bom.managedSurfaces.map(String) : null;
|
|
2750
2161
|
summary.gate = `tag v${candidate} reachable from main, BOM stamp coherent`;
|
|
2751
2162
|
summary.atomicity = atomicity;
|
|
2752
2163
|
journal({ kind: "target", to: candidate, verdict: "ok", detail: summary.gate });
|
|
@@ -2786,8 +2197,14 @@ function reconcile(options) {
|
|
|
2786
2197
|
journal({ kind: "surface", surface: probe.id, present: false, verdict: "skip", detail: `absent (${probe.marker}) \u2014 skipped` });
|
|
2787
2198
|
continue;
|
|
2788
2199
|
}
|
|
2200
|
+
if (surfaceArmPlan(probe.id, summary.managedSurfaces) === "retired") {
|
|
2201
|
+
summary.surfaces.push({ id: probe.id, present: true, action: "retired-at-source" });
|
|
2202
|
+
journal({ kind: "surface", surface: probe.id, present: true, verdict: "skip", retired: true, detail: `not in release v${summary.target} managedSurfaces \u2014 retired at source` });
|
|
2203
|
+
say(`${probe.id}: retired at source (not in release v${summary.target})`);
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2789
2206
|
say(`${probe.id}: converging to v${summary.target}`);
|
|
2790
|
-
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 === "
|
|
2207
|
+
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 === "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;
|
|
2791
2208
|
if (arm) {
|
|
2792
2209
|
summary.plugins.push(arm);
|
|
2793
2210
|
summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
|
|
@@ -2814,10 +2231,10 @@ function reconcile(options) {
|
|
|
2814
2231
|
}
|
|
2815
2232
|
|
|
2816
2233
|
// src/scheduler.ts
|
|
2817
|
-
var
|
|
2818
|
-
var
|
|
2234
|
+
var import_node_child_process9 = require("node:child_process");
|
|
2235
|
+
var import_node_fs16 = require("node:fs");
|
|
2819
2236
|
var import_node_os6 = require("node:os");
|
|
2820
|
-
var
|
|
2237
|
+
var import_node_path13 = require("node:path");
|
|
2821
2238
|
var TASK_NAME = "MMI Fleet Updater";
|
|
2822
2239
|
var LAUNCHER_FILE = "reconcile-hidden.vbs";
|
|
2823
2240
|
var RUN_BOUND = "PT15M";
|
|
@@ -2833,15 +2250,15 @@ var SYSTEMD_UNIT = "mmi-hub-updater";
|
|
|
2833
2250
|
var SYSTEMD_SERVICE_NAME = `${SYSTEMD_UNIT}.service`;
|
|
2834
2251
|
var SYSTEMD_TIMER_NAME = `${SYSTEMD_UNIT}.timer`;
|
|
2835
2252
|
function schtasks(args) {
|
|
2836
|
-
const result = (0,
|
|
2253
|
+
const result = (0, import_node_child_process9.spawnSync)("schtasks.exe", args, { encoding: "utf8", windowsHide: true });
|
|
2837
2254
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2838
2255
|
}
|
|
2839
2256
|
function launchctl(args) {
|
|
2840
|
-
const result = (0,
|
|
2257
|
+
const result = (0, import_node_child_process9.spawnSync)("launchctl", args, { encoding: "utf8", windowsHide: true });
|
|
2841
2258
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2842
2259
|
}
|
|
2843
2260
|
function systemctlUser(args) {
|
|
2844
|
-
const result = (0,
|
|
2261
|
+
const result = (0, import_node_child_process9.spawnSync)("systemctl", ["--user", ...args], { encoding: "utf8", windowsHide: true });
|
|
2845
2262
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
2846
2263
|
}
|
|
2847
2264
|
function uid() {
|
|
@@ -2866,7 +2283,7 @@ WScript.Quit code
|
|
|
2866
2283
|
`;
|
|
2867
2284
|
}
|
|
2868
2285
|
function loginPath(fallback = process.env.PATH ?? "") {
|
|
2869
|
-
const probe = (0,
|
|
2286
|
+
const probe = (0, import_node_child_process9.spawnSync)("/bin/sh", ["-lc", 'printf %s "$PATH"'], { encoding: "utf8", windowsHide: true, timeout: 1e4 });
|
|
2870
2287
|
const captured = (probe.status === 0 ? probe.stdout ?? "" : "").trim();
|
|
2871
2288
|
const source = captured || fallback;
|
|
2872
2289
|
const durable = source.split(":").filter((entry) => entry && !/^\/(?:private\/)?(?:tmp|var\/folders)\//.test(entry));
|
|
@@ -2975,7 +2392,7 @@ WantedBy=timers.target
|
|
|
2975
2392
|
}
|
|
2976
2393
|
function readFileOrNull(path) {
|
|
2977
2394
|
try {
|
|
2978
|
-
return (0,
|
|
2395
|
+
return (0, import_node_fs16.readFileSync)(path, "utf8");
|
|
2979
2396
|
} catch {
|
|
2980
2397
|
return null;
|
|
2981
2398
|
}
|
|
@@ -2987,7 +2404,7 @@ function elevatedRelaunch(mode, env) {
|
|
|
2987
2404
|
if (env.MMI_UPDATER_ELEVATED_RETRY === "1") return false;
|
|
2988
2405
|
const self = process.argv[1];
|
|
2989
2406
|
const command = self ? `"${process.execPath}" "${self}" autoupdate ${mode}` : `mmi-hub autoupdate ${mode}`;
|
|
2990
|
-
const relaunch = (0,
|
|
2407
|
+
const relaunch = (0, import_node_child_process9.spawnSync)(
|
|
2991
2408
|
"powershell.exe",
|
|
2992
2409
|
["-NoProfile", "-Command", `Start-Process -Verb RunAs -Wait -WindowStyle Hidden -FilePath 'cmd.exe' -ArgumentList '/c',${psQuote(`set MMI_UPDATER_ELEVATED_RETRY=1&& ${command}`)}`],
|
|
2993
2410
|
{ encoding: "utf8", windowsHide: true }
|
|
@@ -2998,7 +2415,7 @@ var NEVER_RUN_RESULT = 267011;
|
|
|
2998
2415
|
var NEVER_RUN_YEAR = 1999;
|
|
2999
2416
|
var EMPTY_RUN_INFO = { lastRunTime: null, lastTaskResult: null, nextRunTime: null };
|
|
3000
2417
|
function taskRunInfo() {
|
|
3001
|
-
const result = (0,
|
|
2418
|
+
const result = (0, import_node_child_process9.spawnSync)(
|
|
3002
2419
|
"powershell.exe",
|
|
3003
2420
|
["-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`],
|
|
3004
2421
|
{ encoding: "utf8", windowsHide: true, timeout: 3e4 }
|
|
@@ -3024,7 +2441,7 @@ function windowsSchedulerStatus(env) {
|
|
|
3024
2441
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
3025
2442
|
}
|
|
3026
2443
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3027
|
-
const launcherPath = (0,
|
|
2444
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
3028
2445
|
const info = taskRunInfo();
|
|
3029
2446
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
3030
2447
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
@@ -3049,13 +2466,13 @@ function windowsSchedulerStatus(env) {
|
|
|
3049
2466
|
return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
|
|
3050
2467
|
}
|
|
3051
2468
|
function darwinSchedulerStatus(env) {
|
|
3052
|
-
const plistPath = (0,
|
|
2469
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
3053
2470
|
const plist = readFileOrNull(plistPath);
|
|
3054
2471
|
if (!plist) {
|
|
3055
2472
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
3056
2473
|
}
|
|
3057
2474
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3058
|
-
const launcherPath = (0,
|
|
2475
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
3059
2476
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
3060
2477
|
if (!plist.includes(launcherPath)) {
|
|
3061
2478
|
return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -3075,15 +2492,15 @@ function darwinSchedulerStatus(env) {
|
|
|
3075
2492
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
3076
2493
|
}
|
|
3077
2494
|
function linuxSchedulerStatus(env) {
|
|
3078
|
-
const unitDir = (0,
|
|
3079
|
-
const timerPath = (0,
|
|
2495
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2496
|
+
const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
3080
2497
|
const timer = readFileOrNull(timerPath);
|
|
3081
|
-
const service = readFileOrNull((0,
|
|
2498
|
+
const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
|
|
3082
2499
|
if (!timer || !service) {
|
|
3083
2500
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
3084
2501
|
}
|
|
3085
2502
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3086
|
-
const launcherPath = (0,
|
|
2503
|
+
const launcherPath = (0, import_node_path13.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
3087
2504
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
3088
2505
|
if (!service.includes(launcherPath)) {
|
|
3089
2506
|
return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -3122,10 +2539,10 @@ function installWindowsTask(env) {
|
|
|
3122
2539
|
const paths = ensureState(env);
|
|
3123
2540
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
3124
2541
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
3125
|
-
const launcherPath = (0,
|
|
3126
|
-
(0,
|
|
3127
|
-
const xmlPath = (0,
|
|
3128
|
-
(0,
|
|
2542
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, LAUNCHER_FILE);
|
|
2543
|
+
(0, import_node_fs16.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2544
|
+
const xmlPath = (0, import_node_path13.join)(paths.root, "task.xml");
|
|
2545
|
+
(0, import_node_fs16.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
3129
2546
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
3130
2547
|
if (create.status !== 0) {
|
|
3131
2548
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -3141,11 +2558,11 @@ function refreshPosixLauncher(env) {
|
|
|
3141
2558
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3142
2559
|
if (!hubDist) return null;
|
|
3143
2560
|
const paths = ensureState(env);
|
|
3144
|
-
const launcherPath = (0,
|
|
2561
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
3145
2562
|
const desired = launcherSh(process.execPath, hubDist);
|
|
3146
2563
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
3147
|
-
(0,
|
|
3148
|
-
(0,
|
|
2564
|
+
(0, import_node_fs16.writeFileSync)(launcherPath, desired, "utf8");
|
|
2565
|
+
(0, import_node_fs16.chmodSync)(launcherPath, 493);
|
|
3149
2566
|
return launcherPath;
|
|
3150
2567
|
}
|
|
3151
2568
|
function installLaunchd(env) {
|
|
@@ -3157,13 +2574,13 @@ function installLaunchd(env) {
|
|
|
3157
2574
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3158
2575
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
3159
2576
|
const paths = ensureState(env);
|
|
3160
|
-
const launcherPath = (0,
|
|
3161
|
-
(0,
|
|
3162
|
-
(0,
|
|
3163
|
-
const agentsDir = (0,
|
|
3164
|
-
(0,
|
|
3165
|
-
const plistPath = (0,
|
|
3166
|
-
(0,
|
|
2577
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2578
|
+
(0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2579
|
+
(0, import_node_fs16.chmodSync)(launcherPath, 493);
|
|
2580
|
+
const agentsDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
|
|
2581
|
+
(0, import_node_fs16.mkdirSync)(agentsDir, { recursive: true });
|
|
2582
|
+
const plistPath = (0, import_node_path13.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2583
|
+
(0, import_node_fs16.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
3167
2584
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
3168
2585
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
3169
2586
|
if (load.status !== 0) {
|
|
@@ -3181,13 +2598,13 @@ function installSystemdTimer(env) {
|
|
|
3181
2598
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
3182
2599
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
3183
2600
|
const paths = ensureState(env);
|
|
3184
|
-
const launcherPath = (0,
|
|
3185
|
-
(0,
|
|
3186
|
-
(0,
|
|
3187
|
-
const unitDir = (0,
|
|
3188
|
-
(0,
|
|
3189
|
-
(0,
|
|
3190
|
-
(0,
|
|
2601
|
+
const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2602
|
+
(0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2603
|
+
(0, import_node_fs16.chmodSync)(launcherPath, 493);
|
|
2604
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2605
|
+
(0, import_node_fs16.mkdirSync)(unitDir, { recursive: true });
|
|
2606
|
+
(0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
2607
|
+
(0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
3191
2608
|
const reload = systemctlUser(["daemon-reload"]);
|
|
3192
2609
|
if (reload.status !== 0) {
|
|
3193
2610
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -3225,28 +2642,28 @@ function uninstallWindowsTask(env) {
|
|
|
3225
2642
|
return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
|
|
3226
2643
|
}
|
|
3227
2644
|
function uninstallLaunchd(env) {
|
|
3228
|
-
const plistPath = (0,
|
|
2645
|
+
const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
3229
2646
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
3230
|
-
if (boot.status !== 0 && (0,
|
|
2647
|
+
if (boot.status !== 0 && (0, import_node_fs16.existsSync)(plistPath)) {
|
|
3231
2648
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
3232
2649
|
}
|
|
3233
2650
|
try {
|
|
3234
|
-
(0,
|
|
2651
|
+
(0, import_node_fs16.rmSync)(plistPath, { force: true });
|
|
3235
2652
|
} catch {
|
|
3236
2653
|
}
|
|
3237
2654
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
3238
2655
|
return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
|
|
3239
2656
|
}
|
|
3240
2657
|
function uninstallSystemdTimer(env) {
|
|
3241
|
-
const unitDir = (0,
|
|
3242
|
-
const timerPath = (0,
|
|
2658
|
+
const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2659
|
+
const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
3243
2660
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
3244
|
-
if (disable.status !== 0 && (0,
|
|
2661
|
+
if (disable.status !== 0 && (0, import_node_fs16.existsSync)(timerPath)) {
|
|
3245
2662
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
3246
2663
|
}
|
|
3247
2664
|
try {
|
|
3248
|
-
(0,
|
|
3249
|
-
(0,
|
|
2665
|
+
(0, import_node_fs16.rmSync)(timerPath, { force: true });
|
|
2666
|
+
(0, import_node_fs16.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
3250
2667
|
} catch {
|
|
3251
2668
|
}
|
|
3252
2669
|
systemctlUser(["daemon-reload"]);
|
|
@@ -3261,16 +2678,16 @@ function uninstallTask(env = process.env) {
|
|
|
3261
2678
|
}
|
|
3262
2679
|
function dropLauncher(env, file) {
|
|
3263
2680
|
try {
|
|
3264
|
-
(0,
|
|
2681
|
+
(0, import_node_fs16.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
|
|
3265
2682
|
} catch {
|
|
3266
2683
|
}
|
|
3267
2684
|
}
|
|
3268
2685
|
|
|
3269
2686
|
// src/status.ts
|
|
3270
|
-
var
|
|
2687
|
+
var import_node_fs17 = require("node:fs");
|
|
3271
2688
|
function readJournal(path) {
|
|
3272
2689
|
try {
|
|
3273
|
-
const events = (0,
|
|
2690
|
+
const events = (0, import_node_fs17.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3274
2691
|
try {
|
|
3275
2692
|
return JSON.parse(line);
|
|
3276
2693
|
} catch {
|
|
@@ -3307,12 +2724,9 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
3307
2724
|
if (surface.id === "claude") {
|
|
3308
2725
|
const probe = probeNativeHostInstallation("claude", env);
|
|
3309
2726
|
actual.set(surface.id, { version: probe.version, staged: probeClaudeLaunchStaging(env), location: probe.location, detail: probe.detail });
|
|
3310
|
-
} else if (surface.id === "codex"
|
|
3311
|
-
const probe = probeNativeHostInstallation(
|
|
2727
|
+
} else if (surface.id === "codex") {
|
|
2728
|
+
const probe = probeNativeHostInstallation("codex", env);
|
|
3312
2729
|
actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
|
|
3313
|
-
} else if (surface.id === "kimi") {
|
|
3314
|
-
const probe = probeKimiInstallation(env);
|
|
3315
|
-
actual.set(surface.id, { version: probe.version, staged: probeKimiLaunchStaging(env), location: probe.location, detail: probe.detail });
|
|
3316
2730
|
} else if (surface.id === "cursor") {
|
|
3317
2731
|
const probe = probeCursorInstallation();
|
|
3318
2732
|
actual.set(surface.id, { version: probe.version, staged: probeCursorLaunchStaging(), location: probe.location, detail: probe.detail });
|
|
@@ -3325,6 +2739,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
3325
2739
|
}
|
|
3326
2740
|
}
|
|
3327
2741
|
const installed = {};
|
|
2742
|
+
const retired = retiredSurfaces(realEvents);
|
|
3328
2743
|
for (const [surface, probe] of actual) {
|
|
3329
2744
|
const staged = probe.staged ?? null;
|
|
3330
2745
|
installed[surface] = {
|
|
@@ -3332,7 +2747,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
3332
2747
|
staged,
|
|
3333
2748
|
expected: expectedVersion,
|
|
3334
2749
|
location: probe.location,
|
|
3335
|
-
state: versionState(probe.version, staged, expectedVersion),
|
|
2750
|
+
state: retired.has(surface) ? "retired" : versionState(probe.version, staged, expectedVersion),
|
|
3336
2751
|
detail: probe.detail
|
|
3337
2752
|
};
|
|
3338
2753
|
}
|
|
@@ -3342,6 +2757,7 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
3342
2757
|
if (!events.length && !error) failures.push("no maintenance journal yet \u2014 run `mmi-hub install`");
|
|
3343
2758
|
if (!schedule.ok && schedule.supported) failures.push(schedule.detail);
|
|
3344
2759
|
for (const [surface, row] of Object.entries(installed)) {
|
|
2760
|
+
if (row.state === "retired") continue;
|
|
3345
2761
|
if (!row.installed) failures.push(`${surface}: installed version is unreadable at ${row.location ?? "an unknown location"} \u2014 run \`mmi-hub update\``);
|
|
3346
2762
|
if (row.state === "behind") failures.push(`${surface}: installed ${row.installed} is behind expected ${row.expected} \u2014 run \`mmi-hub update\``);
|
|
3347
2763
|
}
|
|
@@ -3395,10 +2811,8 @@ var MIN_NOTE_SPACE = 8;
|
|
|
3395
2811
|
var ACTIVATION_NOTE = {
|
|
3396
2812
|
claude: "next session",
|
|
3397
2813
|
codex: "next session",
|
|
3398
|
-
kimi: "next session",
|
|
3399
2814
|
jervcode: "next launch",
|
|
3400
2815
|
cursor: "on reload",
|
|
3401
|
-
kilo: "after restart",
|
|
3402
2816
|
hermes: "after restart",
|
|
3403
2817
|
updater: "next run"
|
|
3404
2818
|
};
|
|
@@ -3409,7 +2823,7 @@ var RESET = "\x1B[0m";
|
|
|
3409
2823
|
var RETRY_HINTS = [
|
|
3410
2824
|
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
3411
2825
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
3412
|
-
[/is running|live process
|
|
2826
|
+
[/is running|live process/, "close app, rerun update"],
|
|
3413
2827
|
[/staging install failed|etarget|e404|network|fetch failed/, "check network/login, rerun"],
|
|
3414
2828
|
[/not on npm|no green|registry not ready/, "release still publishing"],
|
|
3415
2829
|
[/elevat|access is denied/, "run from elevated terminal"]
|
|
@@ -3672,10 +3086,65 @@ function startSpinner(enabled) {
|
|
|
3672
3086
|
};
|
|
3673
3087
|
}
|
|
3674
3088
|
|
|
3089
|
+
// src/stamp.ts
|
|
3090
|
+
var import_node_fs18 = require("node:fs");
|
|
3091
|
+
var ALWAYS_ARMED = ["cli", "updater"];
|
|
3092
|
+
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
|
|
3093
|
+
function fleetStamp(env = process.env) {
|
|
3094
|
+
const paths = statePaths(env);
|
|
3095
|
+
let events = [];
|
|
3096
|
+
try {
|
|
3097
|
+
events = (0, import_node_fs18.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3098
|
+
try {
|
|
3099
|
+
return JSON.parse(line);
|
|
3100
|
+
} catch {
|
|
3101
|
+
return null;
|
|
3102
|
+
}
|
|
3103
|
+
}).filter((event) => event !== null);
|
|
3104
|
+
} catch {
|
|
3105
|
+
return "fleet: no journal yet \u2014 MMI Hub maintenance has not run on this machine";
|
|
3106
|
+
}
|
|
3107
|
+
const target = [...events].reverse().find((event) => event.kind === "target" && event.verdict === "ok" && event.to)?.to ?? null;
|
|
3108
|
+
if (!target) {
|
|
3109
|
+
return "fleet: no gated release observed yet (registry not ready is a state, not a failure)";
|
|
3110
|
+
}
|
|
3111
|
+
const lastArm = /* @__PURE__ */ new Map();
|
|
3112
|
+
for (const event of events) {
|
|
3113
|
+
if (event.dryRun) continue;
|
|
3114
|
+
if (event.kind === "arm" && event.surface) lastArm.set(event.surface, event);
|
|
3115
|
+
}
|
|
3116
|
+
const probes = enumerateSurfaces(env);
|
|
3117
|
+
const retired = retiredSurfaces(events);
|
|
3118
|
+
const required = [
|
|
3119
|
+
...ALWAYS_ARMED,
|
|
3120
|
+
...probes.filter((probe) => probe.present && PLUGIN_ARMS.has(probe.id) && !retired.has(probe.id)).map((probe) => probe.id)
|
|
3121
|
+
];
|
|
3122
|
+
const drift = [];
|
|
3123
|
+
const healthy = [];
|
|
3124
|
+
for (const surface of required) {
|
|
3125
|
+
const arm = lastArm.get(surface);
|
|
3126
|
+
if (!arm) {
|
|
3127
|
+
drift.push(`${surface} never converged`);
|
|
3128
|
+
} else if (arm.verdict === "ok" && (arm.to === target || (arm.detail ?? "").includes("already at target"))) {
|
|
3129
|
+
healthy.push(surface);
|
|
3130
|
+
} else {
|
|
3131
|
+
drift.push(`${surface} ${arm.from ?? "absent"} \u2192 ${arm.to ?? "?"} (${arm.verdict})`);
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
const pending = probes.filter((probe) => probe.present && !PLUGIN_ARMS.has(probe.id)).map((probe) => probe.id);
|
|
3135
|
+
const retiredPresent = probes.filter((probe) => probe.present && retired.has(probe.id)).map((probe) => probe.id);
|
|
3136
|
+
if (drift.length) {
|
|
3137
|
+
return `fleet drift: target ${target} \u2014 ${drift.join("; ")}`;
|
|
3138
|
+
}
|
|
3139
|
+
const pendingNote = pending.length ? ` \xB7 ${pending.length} surface${pending.length === 1 ? "" : "s"} pending an arm: ${pending.join(" ")}` : "";
|
|
3140
|
+
const retiredNote = retiredPresent.length ? ` \xB7 retired at source: ${retiredPresent.join(" ")}` : "";
|
|
3141
|
+
return `fleet ${target} \u2713 ${healthy.join("\xB7")}${pendingNote}${retiredNote}`;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3675
3144
|
// src/index.ts
|
|
3676
3145
|
function ownVersion() {
|
|
3677
3146
|
try {
|
|
3678
|
-
return JSON.parse((0, import_node_fs19.readFileSync)((0,
|
|
3147
|
+
return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
3679
3148
|
} catch {
|
|
3680
3149
|
return "0.0.0";
|
|
3681
3150
|
}
|
|
@@ -3714,7 +3183,7 @@ function handOff(reexec, args, json) {
|
|
|
3714
3183
|
|
|
3715
3184
|
`);
|
|
3716
3185
|
}
|
|
3717
|
-
const child = (0,
|
|
3186
|
+
const child = (0, import_node_child_process10.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
3718
3187
|
stdio: "inherit",
|
|
3719
3188
|
windowsHide: true,
|
|
3720
3189
|
env: { ...process.env, MMI_UPDATER_REEXEC: "1" }
|
|
@@ -3840,6 +3309,7 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3840
3309
|
clearPending,
|
|
3841
3310
|
enumerateSurfaces,
|
|
3842
3311
|
failConsequence,
|
|
3312
|
+
fleetStamp,
|
|
3843
3313
|
formatReconcileReport,
|
|
3844
3314
|
hermesArm,
|
|
3845
3315
|
hermesHome,
|
|
@@ -3865,15 +3335,14 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3865
3335
|
probeHermesInstallation,
|
|
3866
3336
|
probeHermesLaunchStaging,
|
|
3867
3337
|
probeJervcodeInstallation,
|
|
3868
|
-
probeKimiLaunchStaging,
|
|
3869
3338
|
probeLaunchTargetVersion,
|
|
3870
3339
|
progressiveRow,
|
|
3871
3340
|
readLaunchTarget,
|
|
3872
3341
|
readPending,
|
|
3873
3342
|
reconcileCursorUserHooks,
|
|
3874
|
-
reconcileKimiInstalledPlugin,
|
|
3875
3343
|
releaseSharedDocLock,
|
|
3876
3344
|
reportFooterLines,
|
|
3345
|
+
retiredSurfaces,
|
|
3877
3346
|
retryHint,
|
|
3878
3347
|
schedulePendingWatcher,
|
|
3879
3348
|
schedulerStatus,
|
|
@@ -3882,11 +3351,11 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3882
3351
|
sharedDocLockTimeoutReceipt,
|
|
3883
3352
|
shouldHandOff,
|
|
3884
3353
|
startSpinner,
|
|
3354
|
+
surfaceArmPlan,
|
|
3885
3355
|
systemdServiceUnit,
|
|
3886
3356
|
systemdTimerUnit,
|
|
3887
3357
|
taskXml,
|
|
3888
3358
|
updateHeader,
|
|
3889
|
-
validateKimiInstalledPlugin,
|
|
3890
3359
|
watcherLockPath,
|
|
3891
3360
|
withSharedDocLock,
|
|
3892
3361
|
wrapWords,
|