@mutmutco/hub 4.1.7 → 4.1.8
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 +399 -253
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -23,6 +23,7 @@ var index_exports = {};
|
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
acquireSharedDocLock: () => acquireSharedDocLock,
|
|
25
25
|
buildWatcherCommand: () => buildWatcherCommand,
|
|
26
|
+
clearLaunchTarget: () => clearLaunchTarget,
|
|
26
27
|
clearPending: () => clearPending,
|
|
27
28
|
enumerateSurfaces: () => enumerateSurfaces,
|
|
28
29
|
failConsequence: () => failConsequence,
|
|
@@ -38,13 +39,19 @@ __export(index_exports, {
|
|
|
38
39
|
lockShaped: () => lockShaped,
|
|
39
40
|
main: () => main,
|
|
40
41
|
noteBusyDefer: () => noteBusyDefer,
|
|
42
|
+
noteLaunchStaged: () => noteLaunchStaged,
|
|
41
43
|
npmStringField: () => npmStringField,
|
|
42
44
|
ownVersion: () => ownVersion,
|
|
43
45
|
parseNpmDistIntegrity: () => parseNpmDistIntegrity,
|
|
44
46
|
pendingPath: () => pendingPath,
|
|
45
47
|
pidAlive: () => pidAlive,
|
|
48
|
+
probeCursorLaunchStaging: () => probeCursorLaunchStaging,
|
|
46
49
|
probeHermesInstallation: () => probeHermesInstallation,
|
|
50
|
+
probeHermesLaunchStaging: () => probeHermesLaunchStaging,
|
|
51
|
+
probeKimiLaunchStaging: () => probeKimiLaunchStaging,
|
|
52
|
+
probeLaunchTargetVersion: () => probeLaunchTargetVersion,
|
|
47
53
|
progressiveRow: () => progressiveRow,
|
|
54
|
+
readLaunchTarget: () => readLaunchTarget,
|
|
48
55
|
readPending: () => readPending,
|
|
49
56
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
50
57
|
reconcileKimiInstalledPlugin: () => reconcileKimiInstalledPlugin,
|
|
@@ -64,11 +71,12 @@ __export(index_exports, {
|
|
|
64
71
|
watcherLockPath: () => watcherLockPath,
|
|
65
72
|
withSharedDocLock: () => withSharedDocLock,
|
|
66
73
|
wrapWords: () => wrapWords,
|
|
74
|
+
writeLaunchTarget: () => writeLaunchTarget,
|
|
67
75
|
writePending: () => writePending
|
|
68
76
|
});
|
|
69
77
|
module.exports = __toCommonJS(index_exports);
|
|
70
|
-
var
|
|
71
|
-
var
|
|
78
|
+
var import_node_fs19 = require("node:fs");
|
|
79
|
+
var import_node_path15 = require("node:path");
|
|
72
80
|
|
|
73
81
|
// src/state.ts
|
|
74
82
|
var import_node_fs = require("node:fs");
|
|
@@ -1008,8 +1016,8 @@ function kiloArm(options) {
|
|
|
1008
1016
|
|
|
1009
1017
|
// src/arm-kimi.ts
|
|
1010
1018
|
var import_node_child_process7 = require("node:child_process");
|
|
1011
|
-
var
|
|
1012
|
-
var
|
|
1019
|
+
var import_node_fs12 = require("node:fs");
|
|
1020
|
+
var import_node_path9 = require("node:path");
|
|
1013
1021
|
|
|
1014
1022
|
// src/compat.ts
|
|
1015
1023
|
var import_node_fs9 = require("node:fs");
|
|
@@ -1022,6 +1030,55 @@ function readManifestCompat(manifestPath) {
|
|
|
1022
1030
|
}
|
|
1023
1031
|
}
|
|
1024
1032
|
|
|
1033
|
+
// src/launch-staging.ts
|
|
1034
|
+
var import_node_fs10 = require("node:fs");
|
|
1035
|
+
var import_node_path8 = require("node:path");
|
|
1036
|
+
function readLaunchTarget(path) {
|
|
1037
|
+
try {
|
|
1038
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
1039
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
1040
|
+
const row = parsed;
|
|
1041
|
+
if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
|
|
1042
|
+
return row;
|
|
1043
|
+
} catch {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function writeLaunchTarget(path, record) {
|
|
1048
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path8.join)(path, ".."), { recursive: true });
|
|
1049
|
+
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
1050
|
+
(0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
|
|
1051
|
+
`, "utf8");
|
|
1052
|
+
(0, import_node_fs10.renameSync)(tmp, path);
|
|
1053
|
+
}
|
|
1054
|
+
function clearLaunchTarget(path) {
|
|
1055
|
+
try {
|
|
1056
|
+
(0, import_node_fs10.rmSync)(path, { force: true });
|
|
1057
|
+
} catch {
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function noteLaunchStaged(paths, env, options, seam = {}) {
|
|
1061
|
+
writeLaunchTarget(options.launchTargetPath, {
|
|
1062
|
+
surface: options.surface,
|
|
1063
|
+
target: options.target,
|
|
1064
|
+
since: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1065
|
+
incoming: options.incoming
|
|
1066
|
+
});
|
|
1067
|
+
return noteBusyDefer(paths, env, {
|
|
1068
|
+
surface: options.surface,
|
|
1069
|
+
target: options.target,
|
|
1070
|
+
reason: options.reason,
|
|
1071
|
+
images: options.images,
|
|
1072
|
+
dirs: options.dirs
|
|
1073
|
+
}, seam);
|
|
1074
|
+
}
|
|
1075
|
+
function probeLaunchTargetVersion(launchTargetPath, versionAt) {
|
|
1076
|
+
const record = readLaunchTarget(launchTargetPath);
|
|
1077
|
+
if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
|
|
1078
|
+
const version = versionAt(record.incoming);
|
|
1079
|
+
return version === record.target ? record.target : null;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1025
1082
|
// src/kimi-registration.ts
|
|
1026
1083
|
function validateKimiInstalledPlugin(document, pluginRoot, id) {
|
|
1027
1084
|
if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
|
|
@@ -1038,7 +1095,7 @@ function validateKimiInstalledPlugin(document, pluginRoot, id) {
|
|
|
1038
1095
|
}
|
|
1039
1096
|
|
|
1040
1097
|
// src/host-doc-lock.ts
|
|
1041
|
-
var
|
|
1098
|
+
var import_node_fs11 = require("node:fs");
|
|
1042
1099
|
var import_node_os4 = require("node:os");
|
|
1043
1100
|
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
1044
1101
|
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
@@ -1052,7 +1109,7 @@ function lockContent(options) {
|
|
|
1052
1109
|
}
|
|
1053
1110
|
function readHolder(lockPath) {
|
|
1054
1111
|
try {
|
|
1055
|
-
const parsed = JSON.parse((0,
|
|
1112
|
+
const parsed = JSON.parse((0, import_node_fs11.readFileSync)(lockPath, "utf8"));
|
|
1056
1113
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
1057
1114
|
return parsed;
|
|
1058
1115
|
} catch {
|
|
@@ -1064,7 +1121,7 @@ function lockAgeMs(lockPath, now) {
|
|
|
1064
1121
|
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
1065
1122
|
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
1066
1123
|
try {
|
|
1067
|
-
return Math.max(0, now - (0,
|
|
1124
|
+
return Math.max(0, now - (0, import_node_fs11.statSync)(lockPath).mtimeMs);
|
|
1068
1125
|
} catch {
|
|
1069
1126
|
return 0;
|
|
1070
1127
|
}
|
|
@@ -1087,9 +1144,9 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1087
1144
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1088
1145
|
let fd;
|
|
1089
1146
|
try {
|
|
1090
|
-
fd = (0,
|
|
1091
|
-
(0,
|
|
1092
|
-
(0,
|
|
1147
|
+
fd = (0, import_node_fs11.openSync)(lockPath, "wx");
|
|
1148
|
+
(0, import_node_fs11.writeSync)(fd, content);
|
|
1149
|
+
(0, import_node_fs11.closeSync)(fd);
|
|
1093
1150
|
return {
|
|
1094
1151
|
documentPath,
|
|
1095
1152
|
lockPath,
|
|
@@ -1102,11 +1159,11 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1102
1159
|
} catch (error) {
|
|
1103
1160
|
if (fd !== void 0) {
|
|
1104
1161
|
try {
|
|
1105
|
-
(0,
|
|
1162
|
+
(0, import_node_fs11.closeSync)(fd);
|
|
1106
1163
|
} catch {
|
|
1107
1164
|
}
|
|
1108
1165
|
try {
|
|
1109
|
-
(0,
|
|
1166
|
+
(0, import_node_fs11.unlinkSync)(lockPath);
|
|
1110
1167
|
} catch {
|
|
1111
1168
|
}
|
|
1112
1169
|
}
|
|
@@ -1115,12 +1172,12 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1115
1172
|
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
1116
1173
|
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
1117
1174
|
try {
|
|
1118
|
-
(0,
|
|
1175
|
+
(0, import_node_fs11.renameSync)(lockPath, aside);
|
|
1119
1176
|
} catch {
|
|
1120
1177
|
continue;
|
|
1121
1178
|
}
|
|
1122
1179
|
try {
|
|
1123
|
-
(0,
|
|
1180
|
+
(0, import_node_fs11.rmSync)(aside, { force: true });
|
|
1124
1181
|
} catch {
|
|
1125
1182
|
}
|
|
1126
1183
|
continue;
|
|
@@ -1133,7 +1190,7 @@ function releaseSharedDocLock(handle) {
|
|
|
1133
1190
|
try {
|
|
1134
1191
|
const holder = readHolder(handle.lockPath);
|
|
1135
1192
|
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
1136
|
-
(0,
|
|
1193
|
+
(0, import_node_fs11.unlinkSync)(handle.lockPath);
|
|
1137
1194
|
}
|
|
1138
1195
|
} catch {
|
|
1139
1196
|
}
|
|
@@ -1151,11 +1208,11 @@ function withSharedDocLock(documentPath, options, fn) {
|
|
|
1151
1208
|
var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
|
|
1152
1209
|
var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
|
|
1153
1210
|
function treeHealthy(root) {
|
|
1154
|
-
return TREE_MARKERS.every((rel) => fileExists((0,
|
|
1211
|
+
return TREE_MARKERS.every((rel) => fileExists((0, import_node_path9.join)(root, ...rel.split("/"))));
|
|
1155
1212
|
}
|
|
1156
1213
|
function markerVersion(root) {
|
|
1157
1214
|
try {
|
|
1158
|
-
const version = JSON.parse((0,
|
|
1215
|
+
const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
|
|
1159
1216
|
return typeof version === "string" ? version : null;
|
|
1160
1217
|
} catch {
|
|
1161
1218
|
return null;
|
|
@@ -1163,7 +1220,7 @@ function markerVersion(root) {
|
|
|
1163
1220
|
}
|
|
1164
1221
|
function packageVersion(root) {
|
|
1165
1222
|
try {
|
|
1166
|
-
const version = JSON.parse((0,
|
|
1223
|
+
const version = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, "package.json"), "utf8")).version;
|
|
1167
1224
|
return typeof version === "string" ? version : null;
|
|
1168
1225
|
} catch {
|
|
1169
1226
|
return null;
|
|
@@ -1171,18 +1228,18 @@ function packageVersion(root) {
|
|
|
1171
1228
|
}
|
|
1172
1229
|
var KIMI_REGISTRATION_LOCK_OWNER = "mmi-hub:kimi-registration";
|
|
1173
1230
|
function reconcileKimiInstalledPlugin(kimiHomeDir, pluginRoot, id, lock = {}) {
|
|
1174
|
-
const path = (0,
|
|
1175
|
-
(0,
|
|
1231
|
+
const path = (0, import_node_path9.join)(kimiHomeDir, "plugins", "installed.json");
|
|
1232
|
+
(0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(kimiHomeDir, "plugins"), { recursive: true });
|
|
1176
1233
|
return withSharedDocLock(path, { owner: KIMI_REGISTRATION_LOCK_OWNER, ...lock }, () => mergeKimiInstalledPlugin(path, pluginRoot, id));
|
|
1177
1234
|
}
|
|
1178
1235
|
function mergeKimiInstalledPlugin(path, pluginRoot, id) {
|
|
1179
1236
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1180
1237
|
const fresh = { id, root: pluginRoot, enabled: true, installedAt: now, updatedAt: now, originalSource: pluginRoot, source: "local-path" };
|
|
1181
1238
|
let document = { version: 1, plugins: [] };
|
|
1182
|
-
if ((0,
|
|
1239
|
+
if ((0, import_node_fs12.existsSync)(path)) {
|
|
1183
1240
|
let parsed;
|
|
1184
1241
|
try {
|
|
1185
|
-
parsed = JSON.parse((0,
|
|
1242
|
+
parsed = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
|
|
1186
1243
|
} catch {
|
|
1187
1244
|
return "conflict";
|
|
1188
1245
|
}
|
|
@@ -1210,13 +1267,13 @@ function mergeKimiInstalledPlugin(path, pluginRoot, id) {
|
|
|
1210
1267
|
plugins[index] = { ...next, updatedAt: now };
|
|
1211
1268
|
}
|
|
1212
1269
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1213
|
-
(0,
|
|
1270
|
+
(0, import_node_fs12.writeFileSync)(tmp, `${JSON.stringify({ ...document, version: 1, plugins }, null, 2)}
|
|
1214
1271
|
`, "utf8");
|
|
1215
|
-
(0,
|
|
1272
|
+
(0, import_node_fs12.renameSync)(tmp, path);
|
|
1216
1273
|
return "updated";
|
|
1217
1274
|
}
|
|
1218
1275
|
function probeKimiInstallation(env = process.env) {
|
|
1219
|
-
const location = (0,
|
|
1276
|
+
const location = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed", "mmi");
|
|
1220
1277
|
const version = markerVersion(location);
|
|
1221
1278
|
return { version, location, detail: version ? "installed Kimi plugin manifest" : "installed Kimi plugin manifest was unreadable" };
|
|
1222
1279
|
}
|
|
@@ -1224,14 +1281,14 @@ function tail2(output) {
|
|
|
1224
1281
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
1225
1282
|
}
|
|
1226
1283
|
function scratchRoot(env) {
|
|
1227
|
-
return (0,
|
|
1284
|
+
return (0, import_node_path9.join)(kimiHome(env), ".mmi-updater");
|
|
1228
1285
|
}
|
|
1229
1286
|
function pruneScratchDir(root, keep) {
|
|
1230
1287
|
try {
|
|
1231
|
-
for (const name of (0,
|
|
1288
|
+
for (const name of (0, import_node_fs12.readdirSync)(root)) {
|
|
1232
1289
|
if (name === keep) continue;
|
|
1233
1290
|
try {
|
|
1234
|
-
(0,
|
|
1291
|
+
(0, import_node_fs12.rmSync)((0, import_node_path9.join)(root, name), { recursive: true, force: true });
|
|
1235
1292
|
} catch {
|
|
1236
1293
|
}
|
|
1237
1294
|
}
|
|
@@ -1257,31 +1314,32 @@ function kimiHostRunning(env) {
|
|
|
1257
1314
|
}
|
|
1258
1315
|
function restoreQuarantine(quarantine, live) {
|
|
1259
1316
|
try {
|
|
1260
|
-
(0,
|
|
1317
|
+
(0, import_node_fs12.renameSync)(quarantine, live);
|
|
1261
1318
|
} catch (error) {
|
|
1262
1319
|
return { restored: false, detail: `${error?.message ?? error}` };
|
|
1263
1320
|
}
|
|
1264
|
-
if (!(0,
|
|
1321
|
+
if (!(0, import_node_fs12.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
|
|
1265
1322
|
return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
|
|
1266
1323
|
}
|
|
1267
1324
|
function kimiArm(options) {
|
|
1268
1325
|
const env = options.env ?? process.env;
|
|
1269
1326
|
const { target, dryRun, paths } = options;
|
|
1270
|
-
const result = (from, to, verdict, detail) => ({
|
|
1327
|
+
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
1271
1328
|
surface: "kimi",
|
|
1272
1329
|
from,
|
|
1273
1330
|
to,
|
|
1274
1331
|
verdict,
|
|
1275
1332
|
detail,
|
|
1333
|
+
launchStaged: launchStaged || void 0,
|
|
1276
1334
|
// C11: the constraint of the tree LEFT at `live` — read at result time, so ok carries the new
|
|
1277
1335
|
// manifest's declaration and defer/fail carry the previous tree's (#4973 reconsult ruling).
|
|
1278
|
-
compat: readManifestCompat((0,
|
|
1336
|
+
compat: readManifestCompat((0, import_node_path9.join)(live, ".kimi-plugin", "plugin.json"))
|
|
1279
1337
|
});
|
|
1280
|
-
const managedParent = (0,
|
|
1281
|
-
const live = (0,
|
|
1338
|
+
const managedParent = (0, import_node_path9.join)(kimiHome(env), "plugins", "managed");
|
|
1339
|
+
const live = (0, import_node_path9.join)(managedParent, "mmi");
|
|
1282
1340
|
const installed = markerVersion(live);
|
|
1283
1341
|
const ensureRegistered = (detail) => {
|
|
1284
|
-
if (!(0,
|
|
1342
|
+
if (!(0, import_node_fs12.existsSync)(live) || !treeHealthy(live)) return { detail };
|
|
1285
1343
|
if (dryRun) return { detail: `${detail}; installed.json untouched (dry-run)` };
|
|
1286
1344
|
let registration;
|
|
1287
1345
|
try {
|
|
@@ -1310,11 +1368,11 @@ function kimiArm(options) {
|
|
|
1310
1368
|
if (dryRun) {
|
|
1311
1369
|
return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
|
|
1312
1370
|
}
|
|
1313
|
-
const stageRoot = (0,
|
|
1314
|
-
const staged = (0,
|
|
1371
|
+
const stageRoot = (0, import_node_path9.join)(paths.staging, "kimi", target);
|
|
1372
|
+
const staged = (0, import_node_path9.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
|
|
1315
1373
|
if (!treeHealthy(staged)) {
|
|
1316
|
-
(0,
|
|
1317
|
-
(0,
|
|
1374
|
+
(0, import_node_fs12.mkdirSync)(stageRoot, { recursive: true });
|
|
1375
|
+
(0, import_node_fs12.writeFileSync)((0, import_node_path9.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
1318
1376
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
|
|
1319
1377
|
if (install.status !== 0) {
|
|
1320
1378
|
return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
|
|
@@ -1331,33 +1389,57 @@ function kimiArm(options) {
|
|
|
1331
1389
|
if (!treeHealthy(staged)) {
|
|
1332
1390
|
return result(installed, target, "defer", `staged tree is missing one of ${TREE_MARKERS.join(", ")}`);
|
|
1333
1391
|
}
|
|
1334
|
-
const quiescence = kimiHostRunning(env);
|
|
1335
|
-
if (quiescence.running) {
|
|
1336
|
-
const pending = options.dryRun ? "" : ` ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason: quiescence.evidence, dirs: ["kimi"] })}`;
|
|
1337
|
-
return result(installed, target, "defer", `Kimi may be running \u2014 the managed tree is switched only while the host is absent: ${quiescence.evidence}${pending ? ` \u2014${pending}` : ""}`);
|
|
1338
|
-
}
|
|
1339
1392
|
const scratch = scratchRoot(env);
|
|
1340
|
-
const
|
|
1341
|
-
const
|
|
1342
|
-
const
|
|
1343
|
-
const
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1393
|
+
const launchTargetPath = (0, import_node_path9.join)(scratch, "launch-target.json");
|
|
1394
|
+
const incomingRoot = (0, import_node_path9.join)(scratch, "incoming");
|
|
1395
|
+
const incoming = (0, import_node_path9.join)(incomingRoot, target);
|
|
1396
|
+
const quarantineRoot = (0, import_node_path9.join)(scratch, "quarantine");
|
|
1397
|
+
const rejectedRoot = (0, import_node_path9.join)(scratch, "rejected");
|
|
1398
|
+
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1399
|
+
const reuseIncoming = priorLaunch?.target === target && priorLaunch.incoming === incoming && markerVersion(incoming) === target && treeHealthy(incoming);
|
|
1400
|
+
if (!reuseIncoming) {
|
|
1401
|
+
try {
|
|
1402
|
+
(0, import_node_fs12.mkdirSync)(managedParent, { recursive: true });
|
|
1403
|
+
(0, import_node_fs12.mkdirSync)(quarantineRoot, { recursive: true });
|
|
1404
|
+
(0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1405
|
+
(0, import_node_fs12.cpSync)(staged, incoming, { recursive: true });
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
|
|
1408
|
+
}
|
|
1409
|
+
if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
|
|
1410
|
+
return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
|
|
1411
|
+
}
|
|
1351
1412
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1413
|
+
const quiescence = kimiHostRunning(env);
|
|
1414
|
+
if (quiescence.running) {
|
|
1415
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
1416
|
+
surface: "kimi",
|
|
1417
|
+
target,
|
|
1418
|
+
reason: quiescence.evidence,
|
|
1419
|
+
launchTargetPath,
|
|
1420
|
+
incoming,
|
|
1421
|
+
dirs: ["kimi"]
|
|
1422
|
+
});
|
|
1423
|
+
return result(installed, target, "ok", `staged ${target} for next launch while Kimi is running (${quiescence.evidence}) \u2014 ${pending}`, true);
|
|
1424
|
+
}
|
|
1425
|
+
const started = kimiHostRunning(env);
|
|
1426
|
+
if (started.running) {
|
|
1427
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
1428
|
+
surface: "kimi",
|
|
1429
|
+
target,
|
|
1430
|
+
reason: started.evidence,
|
|
1431
|
+
launchTargetPath,
|
|
1432
|
+
incoming,
|
|
1433
|
+
dirs: ["kimi"]
|
|
1434
|
+
});
|
|
1435
|
+
return result(installed, target, "ok", `staged ${target} for next launch \u2014 Kimi started during staging (${started.evidence}) \u2014 ${pending}`, true);
|
|
1354
1436
|
}
|
|
1355
|
-
const hadLive = (0,
|
|
1437
|
+
const hadLive = (0, import_node_fs12.existsSync)(live);
|
|
1356
1438
|
const generation = `${installed ?? "unknown"}-${Date.now()}`;
|
|
1357
|
-
const quarantine = (0,
|
|
1439
|
+
const quarantine = (0, import_node_path9.join)(quarantineRoot, generation);
|
|
1358
1440
|
if (hadLive) {
|
|
1359
1441
|
try {
|
|
1360
|
-
(0,
|
|
1442
|
+
(0, import_node_fs12.renameSync)(live, quarantine);
|
|
1361
1443
|
} catch (error) {
|
|
1362
1444
|
const reason = String(error?.message ?? error);
|
|
1363
1445
|
const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
|
|
@@ -1365,7 +1447,7 @@ function kimiArm(options) {
|
|
|
1365
1447
|
}
|
|
1366
1448
|
}
|
|
1367
1449
|
try {
|
|
1368
|
-
(0,
|
|
1450
|
+
(0, import_node_fs12.renameSync)(incoming, live);
|
|
1369
1451
|
} catch (error) {
|
|
1370
1452
|
const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
|
|
1371
1453
|
if (!hadLive) {
|
|
@@ -1380,33 +1462,37 @@ function kimiArm(options) {
|
|
|
1380
1462
|
const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
|
|
1381
1463
|
const rejectedName = `${target}-${Date.now()}`;
|
|
1382
1464
|
try {
|
|
1383
|
-
(0,
|
|
1384
|
-
(0,
|
|
1465
|
+
(0, import_node_fs12.mkdirSync)(rejectedRoot, { recursive: true });
|
|
1466
|
+
(0, import_node_fs12.renameSync)(live, (0, import_node_path9.join)(rejectedRoot, rejectedName));
|
|
1385
1467
|
} catch (error) {
|
|
1386
1468
|
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}` : ""}`);
|
|
1387
1469
|
}
|
|
1388
1470
|
pruneScratchDir(rejectedRoot, rejectedName);
|
|
1389
1471
|
if (!hadLive) {
|
|
1390
|
-
return result(installed, "none", "fail", `${observed} \u2014 the unverified tree is parked at ${(0,
|
|
1472
|
+
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)`);
|
|
1391
1473
|
}
|
|
1392
1474
|
const restore = restoreQuarantine(quarantine, live);
|
|
1393
|
-
return restore.restored ? result(installed, installed ?? "unknown", "fail", `${observed} \u2014 the unverified tree is parked at ${(0,
|
|
1475
|
+
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}`);
|
|
1394
1476
|
}
|
|
1395
1477
|
pruneScratchDir(quarantineRoot, hadLive ? generation : null);
|
|
1396
1478
|
pruneScratchDir(rejectedRoot, null);
|
|
1397
1479
|
try {
|
|
1398
|
-
(0,
|
|
1480
|
+
(0, import_node_fs12.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1399
1481
|
} catch {
|
|
1400
1482
|
}
|
|
1483
|
+
clearLaunchTarget(launchTargetPath);
|
|
1401
1484
|
const reg = ensureRegistered(`converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
|
|
1402
1485
|
return result(installed, target, reg.fail ? "fail" : "ok", reg.detail);
|
|
1403
1486
|
}
|
|
1487
|
+
function probeKimiLaunchStaging(env = process.env) {
|
|
1488
|
+
return probeLaunchTargetVersion((0, import_node_path9.join)(scratchRoot(env), "launch-target.json"), markerVersion);
|
|
1489
|
+
}
|
|
1404
1490
|
|
|
1405
1491
|
// src/arm-cursor.ts
|
|
1406
|
-
var
|
|
1492
|
+
var import_node_fs13 = require("node:fs");
|
|
1407
1493
|
var import_node_child_process8 = require("node:child_process");
|
|
1408
1494
|
var import_node_os5 = require("node:os");
|
|
1409
|
-
var
|
|
1495
|
+
var import_node_path10 = require("node:path");
|
|
1410
1496
|
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
1411
1497
|
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
1412
1498
|
var USER_HOOKS_FILE = "hooks.json";
|
|
@@ -1415,14 +1501,14 @@ var KEEP_GENERATIONS = 2;
|
|
|
1415
1501
|
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
1416
1502
|
var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
|
|
1417
1503
|
function cursorPluginsRoot() {
|
|
1418
|
-
return (0,
|
|
1504
|
+
return (0, import_node_path10.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
|
|
1419
1505
|
}
|
|
1420
1506
|
function treeHealthy2(root) {
|
|
1421
|
-
return TREE_FILES.every((rel) => (0,
|
|
1507
|
+
return TREE_FILES.every((rel) => (0, import_node_fs13.existsSync)((0, import_node_path10.join)(root, ...rel.split("/"))));
|
|
1422
1508
|
}
|
|
1423
1509
|
function readJson(root, rel) {
|
|
1424
1510
|
try {
|
|
1425
|
-
return JSON.parse((0,
|
|
1511
|
+
return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(root, ...rel.split("/")), "utf8"));
|
|
1426
1512
|
} catch {
|
|
1427
1513
|
return null;
|
|
1428
1514
|
}
|
|
@@ -1431,7 +1517,7 @@ function manifest(root) {
|
|
|
1431
1517
|
return readJson(root, MANIFEST);
|
|
1432
1518
|
}
|
|
1433
1519
|
function probeCursorInstallation() {
|
|
1434
|
-
const location = (0,
|
|
1520
|
+
const location = (0, import_node_path10.join)(cursorPluginsRoot(), "local", "mmi");
|
|
1435
1521
|
const version = manifest(location)?.version ?? null;
|
|
1436
1522
|
return { version, location, detail: version ? "installed Cursor plugin manifest" : "installed Cursor plugin manifest was unreadable" };
|
|
1437
1523
|
}
|
|
@@ -1446,7 +1532,7 @@ function message(error) {
|
|
|
1446
1532
|
}
|
|
1447
1533
|
function discard(path) {
|
|
1448
1534
|
try {
|
|
1449
|
-
(0,
|
|
1535
|
+
(0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
|
|
1450
1536
|
} catch {
|
|
1451
1537
|
}
|
|
1452
1538
|
}
|
|
@@ -1474,7 +1560,7 @@ function cursorHostEvidence(env) {
|
|
|
1474
1560
|
return null;
|
|
1475
1561
|
}
|
|
1476
1562
|
function resolvedPluginRoot(root) {
|
|
1477
|
-
return (0,
|
|
1563
|
+
return (0, import_node_path10.join)(root).replace(/\\/g, "/");
|
|
1478
1564
|
}
|
|
1479
1565
|
function isMmiEntry(entry, pluginRoot) {
|
|
1480
1566
|
return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
|
|
@@ -1484,7 +1570,7 @@ function resolveCommand(command, pluginRoot) {
|
|
|
1484
1570
|
}
|
|
1485
1571
|
function desiredUserHooks(pluginRoot) {
|
|
1486
1572
|
try {
|
|
1487
|
-
const doc = JSON.parse((0,
|
|
1573
|
+
const doc = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
|
|
1488
1574
|
const hooks = doc.hooks;
|
|
1489
1575
|
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1490
1576
|
const desired = /* @__PURE__ */ new Map();
|
|
@@ -1521,17 +1607,17 @@ var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
|
1521
1607
|
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
1522
1608
|
const desired = desiredUserHooks(pluginRoot);
|
|
1523
1609
|
if (!desired) return "source-unreadable";
|
|
1524
|
-
const path = (0,
|
|
1610
|
+
const path = (0, import_node_path10.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1525
1611
|
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
1526
|
-
if (!(0,
|
|
1612
|
+
if (!(0, import_node_fs13.existsSync)(path)) {
|
|
1527
1613
|
const fresh = `${path}.tmp-${process.pid}`;
|
|
1528
|
-
(0,
|
|
1529
|
-
(0,
|
|
1614
|
+
(0, import_node_fs13.writeFileSync)(fresh, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
|
|
1615
|
+
(0, import_node_fs13.renameSync)(fresh, path);
|
|
1530
1616
|
return "updated";
|
|
1531
1617
|
}
|
|
1532
1618
|
let existing;
|
|
1533
1619
|
try {
|
|
1534
|
-
existing = JSON.parse((0,
|
|
1620
|
+
existing = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf8"));
|
|
1535
1621
|
} catch {
|
|
1536
1622
|
return "user-conflict";
|
|
1537
1623
|
}
|
|
@@ -1539,8 +1625,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
|
1539
1625
|
if (!merged) return "user-conflict";
|
|
1540
1626
|
if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
|
|
1541
1627
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1542
|
-
(0,
|
|
1543
|
-
(0,
|
|
1628
|
+
(0, import_node_fs13.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1629
|
+
(0, import_node_fs13.renameSync)(tmp, path);
|
|
1544
1630
|
return "updated";
|
|
1545
1631
|
});
|
|
1546
1632
|
}
|
|
@@ -1555,7 +1641,7 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1555
1641
|
case "updated":
|
|
1556
1642
|
return "; user hooks bridged into ~/.cursor/hooks.json";
|
|
1557
1643
|
case "user-conflict":
|
|
1558
|
-
return `; USER HOOKS CONFLICT at ${(0,
|
|
1644
|
+
return `; USER HOOKS CONFLICT at ${(0, import_node_path10.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
|
|
1559
1645
|
case "source-unreadable":
|
|
1560
1646
|
return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
|
|
1561
1647
|
default:
|
|
@@ -1563,35 +1649,35 @@ function userHooksDetail(pluginRoot, homeDir) {
|
|
|
1563
1649
|
}
|
|
1564
1650
|
}
|
|
1565
1651
|
function writeRollbackPointer(root, entry) {
|
|
1566
|
-
const path = (0,
|
|
1652
|
+
const path = (0, import_node_path10.join)(root, "quarantine", "rollback.json");
|
|
1567
1653
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1568
1654
|
try {
|
|
1569
|
-
(0,
|
|
1570
|
-
(0,
|
|
1655
|
+
(0, import_node_fs13.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
1656
|
+
(0, import_node_fs13.renameSync)(tmp, path);
|
|
1571
1657
|
} catch {
|
|
1572
1658
|
discard(tmp);
|
|
1573
1659
|
}
|
|
1574
1660
|
}
|
|
1575
1661
|
function mtimeMs(path) {
|
|
1576
1662
|
try {
|
|
1577
|
-
return (0,
|
|
1663
|
+
return (0, import_node_fs13.statSync)(path).mtimeMs;
|
|
1578
1664
|
} catch {
|
|
1579
1665
|
return 0;
|
|
1580
1666
|
}
|
|
1581
1667
|
}
|
|
1582
1668
|
function pruneQuarantine(root, keep) {
|
|
1583
|
-
const dir = (0,
|
|
1669
|
+
const dir = (0, import_node_path10.join)(root, "quarantine");
|
|
1584
1670
|
let generations;
|
|
1585
1671
|
try {
|
|
1586
|
-
generations = (0,
|
|
1672
|
+
generations = (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path10.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
|
|
1587
1673
|
} catch {
|
|
1588
1674
|
return 0;
|
|
1589
1675
|
}
|
|
1590
1676
|
let stranded = 0;
|
|
1591
1677
|
try {
|
|
1592
|
-
for (const entry of (0,
|
|
1678
|
+
for (const entry of (0, import_node_fs13.readdirSync)(dir, { withFileTypes: true })) {
|
|
1593
1679
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1594
|
-
const path = (0,
|
|
1680
|
+
const path = (0, import_node_path10.join)(dir, entry.name);
|
|
1595
1681
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
1596
1682
|
discard(path);
|
|
1597
1683
|
stranded += 1;
|
|
@@ -1609,16 +1695,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1609
1695
|
}
|
|
1610
1696
|
function restorePrevious(live, quarantined, broken) {
|
|
1611
1697
|
try {
|
|
1612
|
-
(0,
|
|
1698
|
+
(0, import_node_fs13.renameSync)(live, broken);
|
|
1613
1699
|
} catch {
|
|
1614
1700
|
return "kept-broken";
|
|
1615
1701
|
}
|
|
1616
1702
|
try {
|
|
1617
|
-
(0,
|
|
1703
|
+
(0, import_node_fs13.renameSync)(quarantined, live);
|
|
1618
1704
|
return "restored";
|
|
1619
1705
|
} catch {
|
|
1620
1706
|
try {
|
|
1621
|
-
(0,
|
|
1707
|
+
(0, import_node_fs13.renameSync)(broken, live);
|
|
1622
1708
|
return "kept-broken";
|
|
1623
1709
|
} catch {
|
|
1624
1710
|
return "lost";
|
|
@@ -1629,16 +1715,17 @@ function cursorArm(options) {
|
|
|
1629
1715
|
const env = options.env ?? process.env;
|
|
1630
1716
|
const { target, dryRun, paths } = options;
|
|
1631
1717
|
const root = cursorPluginsRoot();
|
|
1632
|
-
const live = (0,
|
|
1633
|
-
const result = (from, to, verdict, detail) => ({
|
|
1718
|
+
const live = (0, import_node_path10.join)(root, "local", "mmi");
|
|
1719
|
+
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
1634
1720
|
surface: "cursor",
|
|
1635
1721
|
from,
|
|
1636
1722
|
to,
|
|
1637
1723
|
verdict,
|
|
1638
1724
|
detail,
|
|
1725
|
+
launchStaged: launchStaged || void 0,
|
|
1639
1726
|
// C11: the constraint of the generation LEFT at `live` — read at result time, so a failed
|
|
1640
1727
|
// switch reports the restored generation's declaration, not the rejected one's (#4973).
|
|
1641
|
-
compat: readManifestCompat((0,
|
|
1728
|
+
compat: readManifestCompat((0, import_node_path10.join)(live, MANIFEST))
|
|
1642
1729
|
});
|
|
1643
1730
|
const current = manifest(live);
|
|
1644
1731
|
const installed = current?.version ?? null;
|
|
@@ -1652,7 +1739,7 @@ function cursorArm(options) {
|
|
|
1652
1739
|
if (installed === target && treeHealthy2(live)) {
|
|
1653
1740
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1654
1741
|
}
|
|
1655
|
-
if ((0,
|
|
1742
|
+
if ((0, import_node_fs13.existsSync)(live) && !mmiOwned(live)) {
|
|
1656
1743
|
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)`);
|
|
1657
1744
|
}
|
|
1658
1745
|
if (dryRun) {
|
|
@@ -1666,43 +1753,63 @@ function cursorArm(options) {
|
|
|
1666
1753
|
if (stagedMarker !== target || !treeHealthy2(staged.packageRoot)) {
|
|
1667
1754
|
return result(installed, target, "defer", `staged tree marker is ${stagedMarker ?? "unreadable"} and the tree is ${treeHealthy2(staged.packageRoot) ? "complete" : "incomplete"}, expected ${target} and complete`);
|
|
1668
1755
|
}
|
|
1756
|
+
const launchTargetPath = (0, import_node_path10.join)(root, "staging", "launch-target.json");
|
|
1757
|
+
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1758
|
+
let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path10.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy2(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path10.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1759
|
+
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
1760
|
+
if (!reuseIncoming || !(0, import_node_fs13.existsSync)(incoming)) {
|
|
1761
|
+
incoming = (0, import_node_path10.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1762
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs13.mkdirSync)((0, import_node_path10.join)(root, dir), { recursive: true });
|
|
1763
|
+
try {
|
|
1764
|
+
discard(incoming);
|
|
1765
|
+
(0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1766
|
+
} catch (error) {
|
|
1767
|
+
discard(incoming);
|
|
1768
|
+
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
1769
|
+
}
|
|
1770
|
+
if (manifest(incoming)?.version !== target || !treeHealthy2(incoming)) {
|
|
1771
|
+
discard(incoming);
|
|
1772
|
+
return result(installed, target, "defer", "the copy on the Cursor volume did not reproduce the verified tree");
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1669
1775
|
const busy = cursorHostEvidence(env);
|
|
1670
1776
|
if (busy) {
|
|
1671
|
-
const pending =
|
|
1672
|
-
|
|
1777
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
1778
|
+
surface: "cursor",
|
|
1779
|
+
target,
|
|
1780
|
+
reason: busy,
|
|
1781
|
+
launchTargetPath,
|
|
1782
|
+
incoming,
|
|
1783
|
+
images: ["Cursor"]
|
|
1784
|
+
});
|
|
1785
|
+
return result(installed, target, "ok", `staged ${target} for next launch while Cursor is running (${busy}) \u2014 ${pending}`, true);
|
|
1673
1786
|
}
|
|
1674
1787
|
const stamp = Date.now();
|
|
1675
|
-
const
|
|
1676
|
-
const
|
|
1677
|
-
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
|
|
1678
|
-
try {
|
|
1679
|
-
discard(incoming);
|
|
1680
|
-
(0, import_node_fs12.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1681
|
-
} catch (error) {
|
|
1682
|
-
discard(incoming);
|
|
1683
|
-
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
1684
|
-
}
|
|
1685
|
-
if (manifest(incoming)?.version !== target || !treeHealthy2(incoming)) {
|
|
1686
|
-
discard(incoming);
|
|
1687
|
-
return result(installed, target, "defer", "the copy on the Cursor volume did not reproduce the verified tree");
|
|
1688
|
-
}
|
|
1788
|
+
const quarantined = (0, import_node_path10.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1789
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs13.mkdirSync)((0, import_node_path10.join)(root, dir), { recursive: true });
|
|
1689
1790
|
const started = cursorHostEvidence(env);
|
|
1690
1791
|
if (started) {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1792
|
+
const pending = noteLaunchStaged(paths, env, {
|
|
1793
|
+
surface: "cursor",
|
|
1794
|
+
target,
|
|
1795
|
+
reason: started,
|
|
1796
|
+
launchTargetPath,
|
|
1797
|
+
incoming,
|
|
1798
|
+
images: ["Cursor"]
|
|
1799
|
+
});
|
|
1800
|
+
return result(installed, target, "ok", `staged ${target} for next launch \u2014 Cursor started during staging (${started}) \u2014 ${pending}`, true);
|
|
1694
1801
|
}
|
|
1695
1802
|
let displaced = false;
|
|
1696
1803
|
try {
|
|
1697
|
-
if ((0,
|
|
1698
|
-
(0,
|
|
1804
|
+
if ((0, import_node_fs13.existsSync)(live)) {
|
|
1805
|
+
(0, import_node_fs13.renameSync)(live, quarantined);
|
|
1699
1806
|
displaced = true;
|
|
1700
1807
|
}
|
|
1701
|
-
(0,
|
|
1808
|
+
(0, import_node_fs13.renameSync)(incoming, live);
|
|
1702
1809
|
} catch (error) {
|
|
1703
|
-
if (displaced && !(0,
|
|
1810
|
+
if (displaced && !(0, import_node_fs13.existsSync)(live)) {
|
|
1704
1811
|
try {
|
|
1705
|
-
(0,
|
|
1812
|
+
(0, import_node_fs13.renameSync)(quarantined, live);
|
|
1706
1813
|
} catch {
|
|
1707
1814
|
}
|
|
1708
1815
|
}
|
|
@@ -1713,21 +1820,25 @@ function cursorArm(options) {
|
|
|
1713
1820
|
}
|
|
1714
1821
|
const evidence = manifest(live)?.version;
|
|
1715
1822
|
if (evidence !== target || !treeHealthy2(live)) {
|
|
1716
|
-
const broken = `${(0,
|
|
1823
|
+
const broken = `${(0, import_node_path10.join)(root, "quarantine", `mmi-${safeSegment(evidence ?? "broken")}-${stamp}`)}-failed`;
|
|
1717
1824
|
const outcome = displaced ? restorePrevious(live, quarantined, broken) : "none";
|
|
1718
1825
|
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";
|
|
1719
1826
|
writeRollbackPointer(root, outcome === "restored" ? { installed: installed ?? "unknown", previous: null, generation: null } : { installed: evidence ?? "unknown", previous: installed, generation: displaced ? quarantined : null });
|
|
1720
1827
|
return result(installed, outcome === "restored" ? installed ?? "unknown" : evidence ?? "unknown", "fail", `post-switch marker reads ${evidence ?? "missing"}, expected ${target}; ${detail}`);
|
|
1721
1828
|
}
|
|
1722
1829
|
writeRollbackPointer(root, { installed: target, previous: installed, generation: displaced ? quarantined : null });
|
|
1830
|
+
clearLaunchTarget(launchTargetPath);
|
|
1723
1831
|
const pruned = pruneQuarantine(root, displaced ? quarantined : null);
|
|
1724
1832
|
const previous = displaced ? `previous generation quarantined at ${quarantined}` : "no previous generation to quarantine";
|
|
1725
1833
|
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)())}`);
|
|
1726
1834
|
}
|
|
1835
|
+
function probeCursorLaunchStaging() {
|
|
1836
|
+
return probeLaunchTargetVersion((0, import_node_path10.join)(cursorPluginsRoot(), "staging", "launch-target.json"), (root) => manifest(root)?.version ?? null);
|
|
1837
|
+
}
|
|
1727
1838
|
|
|
1728
1839
|
// src/arm-jervcode.ts
|
|
1729
|
-
var
|
|
1730
|
-
var
|
|
1840
|
+
var import_node_fs14 = require("node:fs");
|
|
1841
|
+
var import_node_path11 = require("node:path");
|
|
1731
1842
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1732
1843
|
function hostEnv(env) {
|
|
1733
1844
|
const dir = jervcodeAgentDir(env);
|
|
@@ -1738,7 +1849,7 @@ function hostCommands(_env) {
|
|
|
1738
1849
|
}
|
|
1739
1850
|
function readPackageSpecs(env) {
|
|
1740
1851
|
try {
|
|
1741
|
-
const settings = JSON.parse((0,
|
|
1852
|
+
const settings = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
1742
1853
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1743
1854
|
} catch {
|
|
1744
1855
|
return null;
|
|
@@ -1746,14 +1857,14 @@ function readPackageSpecs(env) {
|
|
|
1746
1857
|
}
|
|
1747
1858
|
function installedVersion(env) {
|
|
1748
1859
|
try {
|
|
1749
|
-
const manifest2 = JSON.parse((0,
|
|
1860
|
+
const manifest2 = JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
|
|
1750
1861
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1751
1862
|
} catch {
|
|
1752
1863
|
return null;
|
|
1753
1864
|
}
|
|
1754
1865
|
}
|
|
1755
1866
|
function probeJervcodeInstallation(env = process.env) {
|
|
1756
|
-
const location = (0,
|
|
1867
|
+
const location = (0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"));
|
|
1757
1868
|
const version = installedVersion(env);
|
|
1758
1869
|
return { version, location, detail: version ? "materialized pi package manifest" : "materialized pi package manifest was unreadable" };
|
|
1759
1870
|
}
|
|
@@ -1775,32 +1886,32 @@ function jervcodeArm(options) {
|
|
|
1775
1886
|
detail,
|
|
1776
1887
|
// C11: the constraint of the package pi LEFT materialised — read at result time, so defer/fail
|
|
1777
1888
|
// carry the previous payload's declaration (#4973 reconsult ruling).
|
|
1778
|
-
compat: readManifestCompat((0,
|
|
1889
|
+
compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"))
|
|
1779
1890
|
});
|
|
1780
1891
|
const before = readPackageSpecs(env);
|
|
1781
|
-
if (!before) return result(null, options.target, "defer", `cannot read ${(0,
|
|
1892
|
+
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path11.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
1782
1893
|
const from = installedVersion(env);
|
|
1783
1894
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1784
1895
|
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1785
1896
|
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;
|
|
1786
1897
|
if (options.dryRun) {
|
|
1787
1898
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
1788
|
-
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,
|
|
1899
|
+
return { surface: "jervcode", from, to: options.target, verdict: "ok", detail: `dry-run: would ${action}${legacy.length ? ` and retire ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : ""}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1789
1900
|
}
|
|
1790
1901
|
const hosted = hostEnv(env);
|
|
1791
1902
|
const commands = hostCommands(env);
|
|
1792
1903
|
const command = commands.find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
1793
|
-
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,
|
|
1904
|
+
if (!command) return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: "neither `jervcode` nor `pi` answered --version \u2014 the host CLI is not on PATH", compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1794
1905
|
if (!floor && (!before.includes(exact) || from !== options.target)) {
|
|
1795
1906
|
const install = runner(command, ["install", exact], hosted);
|
|
1796
1907
|
if (install.status !== 0) {
|
|
1797
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0,
|
|
1908
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} install ${exact} failed: ${failure2(install)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1798
1909
|
}
|
|
1799
1910
|
}
|
|
1800
1911
|
for (const stale of legacy) {
|
|
1801
1912
|
const removed = runner(command, ["remove", stale], hosted);
|
|
1802
1913
|
if (removed.status !== 0) {
|
|
1803
|
-
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0,
|
|
1914
|
+
return { surface: "jervcode", from, to: options.target, verdict: "defer", detail: `${command} remove of the legacy registration failed: ${failure2(removed)}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1804
1915
|
}
|
|
1805
1916
|
}
|
|
1806
1917
|
const after = readPackageSpecs(env);
|
|
@@ -1808,7 +1919,7 @@ function jervcodeArm(options) {
|
|
|
1808
1919
|
const legacyAfter = after?.filter(isLegacyMmiPiPath) ?? [];
|
|
1809
1920
|
const retired = legacy.length ? `; retired ${legacy.length} legacy claude-cache path registration${legacy.length === 1 ? "" : "s"}` : "";
|
|
1810
1921
|
if (floor && !legacyAfter.length) {
|
|
1811
|
-
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0,
|
|
1922
|
+
return { surface: "jervcode", from, to: converged ?? highWater ?? options.target, verdict: "skip", detail: `${floor}${retired}`, compat: readManifestCompat((0, import_node_path11.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json")) };
|
|
1812
1923
|
}
|
|
1813
1924
|
if (!after?.includes(exact) || converged !== options.target || legacyAfter.length) {
|
|
1814
1925
|
return {
|
|
@@ -1829,15 +1940,15 @@ function jervcodeArm(options) {
|
|
|
1829
1940
|
}
|
|
1830
1941
|
|
|
1831
1942
|
// src/arm-hermes.ts
|
|
1832
|
-
var
|
|
1943
|
+
var import_node_fs15 = require("node:fs");
|
|
1833
1944
|
var import_node_child_process9 = require("node:child_process");
|
|
1834
|
-
var
|
|
1945
|
+
var import_node_path12 = require("node:path");
|
|
1835
1946
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
1836
1947
|
var MANIFEST2 = "plugin.yaml";
|
|
1837
1948
|
var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
|
|
1838
1949
|
function yamlVersion(root) {
|
|
1839
1950
|
try {
|
|
1840
|
-
const text = (0,
|
|
1951
|
+
const text = (0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, MANIFEST2), "utf8");
|
|
1841
1952
|
try {
|
|
1842
1953
|
const version = JSON.parse(text).version;
|
|
1843
1954
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1850,9 +1961,9 @@ function yamlVersion(root) {
|
|
|
1850
1961
|
}
|
|
1851
1962
|
}
|
|
1852
1963
|
function treeHealthy3(root) {
|
|
1853
|
-
return TREE_MARKERS2.slice(0, 2).every((file) => (0,
|
|
1964
|
+
return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs15.existsSync)((0, import_node_path12.join)(root, file))) && (() => {
|
|
1854
1965
|
try {
|
|
1855
|
-
return (0,
|
|
1966
|
+
return (0, import_node_fs15.statSync)((0, import_node_path12.join)(root, "skills")).isDirectory();
|
|
1856
1967
|
} catch {
|
|
1857
1968
|
return false;
|
|
1858
1969
|
}
|
|
@@ -1860,13 +1971,13 @@ function treeHealthy3(root) {
|
|
|
1860
1971
|
}
|
|
1861
1972
|
function packageIdentity(root) {
|
|
1862
1973
|
try {
|
|
1863
|
-
return JSON.parse((0,
|
|
1974
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path12.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
1864
1975
|
} catch {
|
|
1865
1976
|
return false;
|
|
1866
1977
|
}
|
|
1867
1978
|
}
|
|
1868
1979
|
function probeHermesInstallation(env = process.env) {
|
|
1869
|
-
const location = (0,
|
|
1980
|
+
const location = (0, import_node_path12.join)(hermesHome(env), "plugins", "mmi");
|
|
1870
1981
|
const version = yamlVersion(location);
|
|
1871
1982
|
return { version, location, detail: version ? "installed Hermes plugin manifest" : "installed Hermes plugin manifest was unreadable" };
|
|
1872
1983
|
}
|
|
@@ -1875,7 +1986,7 @@ function mmiOwned2(root) {
|
|
|
1875
1986
|
}
|
|
1876
1987
|
function discard2(path) {
|
|
1877
1988
|
try {
|
|
1878
|
-
(0,
|
|
1989
|
+
(0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
|
|
1879
1990
|
} catch {
|
|
1880
1991
|
}
|
|
1881
1992
|
}
|
|
@@ -1907,19 +2018,19 @@ function hermesHostEvidence(env, home, processListProvider) {
|
|
|
1907
2018
|
return null;
|
|
1908
2019
|
}
|
|
1909
2020
|
function materializeHermesSkills(home) {
|
|
1910
|
-
const pluginSkills = (0,
|
|
1911
|
-
const skillsRoot = (0,
|
|
1912
|
-
const targetRoot = (0,
|
|
2021
|
+
const pluginSkills = (0, import_node_path12.join)(home, "plugins", "mmi", "skills");
|
|
2022
|
+
const skillsRoot = (0, import_node_path12.join)(home, "skills");
|
|
2023
|
+
const targetRoot = (0, import_node_path12.join)(skillsRoot, "mmi");
|
|
1913
2024
|
try {
|
|
1914
|
-
if (!(0,
|
|
1915
|
-
const names = (0,
|
|
2025
|
+
if (!(0, import_node_fs15.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
2026
|
+
const names = (0, import_node_fs15.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
1916
2027
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
1917
|
-
(0,
|
|
1918
|
-
const incoming = (0,
|
|
2028
|
+
(0, import_node_fs15.mkdirSync)(skillsRoot, { recursive: true });
|
|
2029
|
+
const incoming = (0, import_node_path12.join)(skillsRoot, ".mmi-incoming");
|
|
1919
2030
|
discard2(incoming);
|
|
1920
|
-
(0,
|
|
2031
|
+
(0, import_node_fs15.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
1921
2032
|
discard2(targetRoot);
|
|
1922
|
-
(0,
|
|
2033
|
+
(0, import_node_fs15.renameSync)(incoming, targetRoot);
|
|
1923
2034
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
1924
2035
|
} catch (error) {
|
|
1925
2036
|
return { ok: false, detail: message2(error) };
|
|
@@ -1928,18 +2039,19 @@ function materializeHermesSkills(home) {
|
|
|
1928
2039
|
function hermesArm(options) {
|
|
1929
2040
|
const env = options.env ?? process.env;
|
|
1930
2041
|
const home = hermesHome(env);
|
|
1931
|
-
const parent = (0,
|
|
1932
|
-
const live = (0,
|
|
2042
|
+
const parent = (0, import_node_path12.join)(home, "plugins");
|
|
2043
|
+
const live = (0, import_node_path12.join)(parent, "mmi");
|
|
1933
2044
|
const installed = yamlVersion(live);
|
|
1934
|
-
const result = (from, to, verdict, detail) => ({
|
|
2045
|
+
const result = (from, to, verdict, detail, launchStaged = false) => ({
|
|
1935
2046
|
surface: "hermes",
|
|
1936
2047
|
from,
|
|
1937
2048
|
to,
|
|
1938
2049
|
verdict,
|
|
1939
|
-
detail
|
|
2050
|
+
detail,
|
|
2051
|
+
launchStaged: launchStaged || void 0
|
|
1940
2052
|
});
|
|
1941
|
-
if (!(0,
|
|
1942
|
-
if ((0,
|
|
2053
|
+
if (!(0, import_node_fs15.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
|
|
2054
|
+
if ((0, import_node_fs15.existsSync)(live) && !mmiOwned2(live)) {
|
|
1943
2055
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1944
2056
|
}
|
|
1945
2057
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -1950,41 +2062,53 @@ function hermesArm(options) {
|
|
|
1950
2062
|
if (!skills2.ok) return result(installed, options.target, "fail", `plugin at target ${options.target}, but skills provisioning failed: ${skills2.detail}`);
|
|
1951
2063
|
return result(installed, options.target, "ok", `already at target ${options.target}; ${skills2.detail}`);
|
|
1952
2064
|
}
|
|
1953
|
-
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,
|
|
2065
|
+
if (options.dryRun) return result(installed, options.target, "ok", `dry-run: would stage ${PACKAGE2}@${options.target}, atomically switch ${installed ?? "absent"} -> ${options.target}, then provision skills into ${(0, import_node_path12.join)(home, "skills", "mmi")}`);
|
|
1954
2066
|
const staged = stagePackage({ surface: "hermes", packageName: PACKAGE2, target: options.target, witness: MANIFEST2, paths: options.paths, env });
|
|
1955
2067
|
if ("defer" in staged) return result(installed, options.target, "defer", staged.defer);
|
|
1956
2068
|
if (yamlVersion(staged.packageRoot) !== options.target || !treeHealthy3(staged.packageRoot)) {
|
|
1957
2069
|
return result(installed, options.target, "defer", `staged Hermes manifest is ${yamlVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${options.target})`);
|
|
1958
2070
|
}
|
|
1959
|
-
const scratch = (0,
|
|
1960
|
-
const
|
|
1961
|
-
const
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2071
|
+
const scratch = (0, import_node_path12.join)(home, ".mmi-updater");
|
|
2072
|
+
const launchTargetPath = (0, import_node_path12.join)(scratch, "launch-target.json");
|
|
2073
|
+
const incomingRoot = (0, import_node_path12.join)(scratch, "incoming");
|
|
2074
|
+
const incoming = (0, import_node_path12.join)(incomingRoot, options.target);
|
|
2075
|
+
const quarantine = (0, import_node_path12.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
|
|
2076
|
+
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
2077
|
+
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy3(incoming);
|
|
2078
|
+
if (!reuseIncoming) {
|
|
2079
|
+
try {
|
|
2080
|
+
(0, import_node_fs15.mkdirSync)(parent, { recursive: true });
|
|
2081
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path12.join)(scratch, "quarantine"), { recursive: true });
|
|
2082
|
+
discard2(incomingRoot);
|
|
2083
|
+
(0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
2086
|
+
}
|
|
2087
|
+
if (yamlVersion(incoming) !== options.target || !treeHealthy3(incoming)) {
|
|
2088
|
+
discard2(incomingRoot);
|
|
2089
|
+
return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
|
|
2090
|
+
}
|
|
1974
2091
|
}
|
|
1975
2092
|
const busy = hermesHostEvidence(env, home, options.processListProvider);
|
|
1976
2093
|
if (busy) {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2094
|
+
const pending = noteLaunchStaged(options.paths, env, {
|
|
2095
|
+
surface: "hermes",
|
|
2096
|
+
target: options.target,
|
|
2097
|
+
reason: busy,
|
|
2098
|
+
launchTargetPath,
|
|
2099
|
+
incoming,
|
|
2100
|
+
images: ["hermes"],
|
|
2101
|
+
dirs: [home]
|
|
2102
|
+
});
|
|
2103
|
+
return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
|
|
1980
2104
|
}
|
|
1981
|
-
const displaced = (0,
|
|
2105
|
+
const displaced = (0, import_node_fs15.existsSync)(live);
|
|
1982
2106
|
try {
|
|
1983
|
-
if (displaced) (0,
|
|
1984
|
-
(0,
|
|
2107
|
+
if (displaced) (0, import_node_fs15.renameSync)(live, quarantine);
|
|
2108
|
+
(0, import_node_fs15.renameSync)(incoming, live);
|
|
1985
2109
|
} catch (error) {
|
|
1986
|
-
if (displaced && !(0,
|
|
1987
|
-
(0,
|
|
2110
|
+
if (displaced && !(0, import_node_fs15.existsSync)(live)) try {
|
|
2111
|
+
(0, import_node_fs15.renameSync)(quarantine, live);
|
|
1988
2112
|
} catch {
|
|
1989
2113
|
}
|
|
1990
2114
|
discard2(incomingRoot);
|
|
@@ -1993,26 +2117,30 @@ function hermesArm(options) {
|
|
|
1993
2117
|
return result(installed, options.target, "defer", `Hermes generation switch failed; previous generation retained: ${reason}${pending}`);
|
|
1994
2118
|
}
|
|
1995
2119
|
if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
|
|
1996
|
-
const broken = (0,
|
|
2120
|
+
const broken = (0, import_node_path12.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
1997
2121
|
try {
|
|
1998
|
-
(0,
|
|
1999
|
-
(0,
|
|
2000
|
-
if (displaced) (0,
|
|
2122
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path12.join)(scratch, "rejected"), { recursive: true });
|
|
2123
|
+
(0, import_node_fs15.renameSync)(live, broken);
|
|
2124
|
+
if (displaced) (0, import_node_fs15.renameSync)(quarantine, live);
|
|
2001
2125
|
} catch (error) {
|
|
2002
2126
|
return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
2003
2127
|
}
|
|
2004
2128
|
return result(installed, displaced ? installed ?? "unknown" : "none", "fail", `post-switch Hermes verification failed; rejected generation parked at ${broken}${displaced ? "; previous generation restored" : ""}`);
|
|
2005
2129
|
}
|
|
2006
2130
|
discard2(incomingRoot);
|
|
2131
|
+
clearLaunchTarget(launchTargetPath);
|
|
2007
2132
|
const skills = materializeHermesSkills(home);
|
|
2008
2133
|
if (!skills.ok) return result(installed, options.target, "fail", `plugin converged to ${options.target}, but skills provisioning failed: ${skills.detail}`);
|
|
2009
2134
|
return result(installed, options.target, "ok", `converged ${installed ?? "absent"} -> ${options.target} (staged, verified, atomically switched${displaced ? `; previous generation quarantined at ${quarantine}` : ""}); ${skills.detail}`);
|
|
2010
2135
|
}
|
|
2136
|
+
function probeHermesLaunchStaging(env = process.env) {
|
|
2137
|
+
return probeLaunchTargetVersion((0, import_node_path12.join)(hermesHome(env), ".mmi-updater", "launch-target.json"), yamlVersion);
|
|
2138
|
+
}
|
|
2011
2139
|
|
|
2012
2140
|
// src/reap.ts
|
|
2013
2141
|
var import_node_child_process10 = require("node:child_process");
|
|
2014
|
-
var
|
|
2015
|
-
var
|
|
2142
|
+
var import_node_fs16 = require("node:fs");
|
|
2143
|
+
var import_node_path13 = require("node:path");
|
|
2016
2144
|
var KEEP_GENERATIONS2 = 2;
|
|
2017
2145
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
2018
2146
|
var ORPHAN_MIN_AGE_MS = 10 * 6e4;
|
|
@@ -2020,7 +2148,7 @@ var SNAPSHOT_TIMEOUT_MS = 2e4;
|
|
|
2020
2148
|
var MAX_UNWIND_ROUNDS = 4;
|
|
2021
2149
|
function listDirs(path) {
|
|
2022
2150
|
try {
|
|
2023
|
-
return (0,
|
|
2151
|
+
return (0, import_node_fs16.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2024
2152
|
} catch {
|
|
2025
2153
|
return [];
|
|
2026
2154
|
}
|
|
@@ -2028,7 +2156,7 @@ function listDirs(path) {
|
|
|
2028
2156
|
function generationSets(root) {
|
|
2029
2157
|
const children = listDirs(root);
|
|
2030
2158
|
if (children.some((name) => parseSemver(name))) return [root];
|
|
2031
|
-
return children.map((name) => (0,
|
|
2159
|
+
return children.map((name) => (0, import_node_path13.join)(root, name)).filter((path) => listDirs(path).some((name) => parseSemver(name)));
|
|
2032
2160
|
}
|
|
2033
2161
|
function pruneGenerations(root, category, dryRun) {
|
|
2034
2162
|
let reaped = 0;
|
|
@@ -2041,7 +2169,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2041
2169
|
continue;
|
|
2042
2170
|
}
|
|
2043
2171
|
try {
|
|
2044
|
-
(0,
|
|
2172
|
+
(0, import_node_fs16.rmSync)((0, import_node_path13.join)(set, version), { recursive: true, force: true });
|
|
2045
2173
|
reaped += 1;
|
|
2046
2174
|
} catch (error) {
|
|
2047
2175
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2055,8 +2183,8 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2055
2183
|
}
|
|
2056
2184
|
var LEASE_STEAL_SCRATCH = /^lease\.lock\.stale-\d+$/;
|
|
2057
2185
|
function atomicWriteScratch(path) {
|
|
2058
|
-
const literal = (0,
|
|
2059
|
-
return { dir: (0,
|
|
2186
|
+
const literal = (0, import_node_path13.basename)(path).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2187
|
+
return { dir: (0, import_node_path13.dirname)(path), pattern: new RegExp(`^${literal}\\.tmp-\\d+$`) };
|
|
2060
2188
|
}
|
|
2061
2189
|
function reapScratch(context, dryRun) {
|
|
2062
2190
|
const category = "scratch";
|
|
@@ -2074,7 +2202,7 @@ function reapScratch(context, dryRun) {
|
|
|
2074
2202
|
for (const [dir, patterns] of scanned) {
|
|
2075
2203
|
let entries;
|
|
2076
2204
|
try {
|
|
2077
|
-
entries = (0,
|
|
2205
|
+
entries = (0, import_node_fs16.readdirSync)(dir, { withFileTypes: true });
|
|
2078
2206
|
} catch {
|
|
2079
2207
|
unreadable.push(dir);
|
|
2080
2208
|
continue;
|
|
@@ -2082,10 +2210,10 @@ function reapScratch(context, dryRun) {
|
|
|
2082
2210
|
for (const entry of entries) {
|
|
2083
2211
|
if (!patterns.some((pattern) => pattern.test(entry.name))) continue;
|
|
2084
2212
|
if (!entry.isFile()) continue;
|
|
2085
|
-
const path = (0,
|
|
2213
|
+
const path = (0, import_node_path13.join)(dir, entry.name);
|
|
2086
2214
|
let ageMs;
|
|
2087
2215
|
try {
|
|
2088
|
-
ageMs = now - (0,
|
|
2216
|
+
ageMs = now - (0, import_node_fs16.statSync)(path).mtimeMs;
|
|
2089
2217
|
} catch {
|
|
2090
2218
|
continue;
|
|
2091
2219
|
}
|
|
@@ -2095,7 +2223,7 @@ function reapScratch(context, dryRun) {
|
|
|
2095
2223
|
continue;
|
|
2096
2224
|
}
|
|
2097
2225
|
try {
|
|
2098
|
-
(0,
|
|
2226
|
+
(0, import_node_fs16.rmSync)(path, { force: true });
|
|
2099
2227
|
reaped += 1;
|
|
2100
2228
|
} catch (error) {
|
|
2101
2229
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2335,7 +2463,7 @@ function reconcile(options) {
|
|
|
2335
2463
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2336
2464
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
2337
2465
|
options.onArm?.(arm);
|
|
2338
|
-
if (arm.verdict === "ok" && !options.dryRun) clearPending(paths, arm.surface);
|
|
2466
|
+
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2339
2467
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
2340
2468
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
2341
2469
|
};
|
|
@@ -2440,9 +2568,9 @@ function reconcile(options) {
|
|
|
2440
2568
|
|
|
2441
2569
|
// src/scheduler.ts
|
|
2442
2570
|
var import_node_child_process11 = require("node:child_process");
|
|
2443
|
-
var
|
|
2571
|
+
var import_node_fs17 = require("node:fs");
|
|
2444
2572
|
var import_node_os6 = require("node:os");
|
|
2445
|
-
var
|
|
2573
|
+
var import_node_path14 = require("node:path");
|
|
2446
2574
|
var TASK_NAME = "MMI Fleet Updater";
|
|
2447
2575
|
var LAUNCHER_FILE = "reconcile-hidden.vbs";
|
|
2448
2576
|
var RUN_BOUND = "PT15M";
|
|
@@ -2600,7 +2728,7 @@ WantedBy=timers.target
|
|
|
2600
2728
|
}
|
|
2601
2729
|
function readFileOrNull(path) {
|
|
2602
2730
|
try {
|
|
2603
|
-
return (0,
|
|
2731
|
+
return (0, import_node_fs17.readFileSync)(path, "utf8");
|
|
2604
2732
|
} catch {
|
|
2605
2733
|
return null;
|
|
2606
2734
|
}
|
|
@@ -2649,7 +2777,7 @@ function windowsSchedulerStatus(env) {
|
|
|
2649
2777
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${TASK_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2650
2778
|
}
|
|
2651
2779
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2652
|
-
const launcherPath = (0,
|
|
2780
|
+
const launcherPath = (0, import_node_path14.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2653
2781
|
const info = taskRunInfo();
|
|
2654
2782
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2655
2783
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
@@ -2674,13 +2802,13 @@ function windowsSchedulerStatus(env) {
|
|
|
2674
2802
|
return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
|
|
2675
2803
|
}
|
|
2676
2804
|
function darwinSchedulerStatus(env) {
|
|
2677
|
-
const plistPath = (0,
|
|
2805
|
+
const plistPath = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2678
2806
|
const plist = readFileOrNull(plistPath);
|
|
2679
2807
|
if (!plist) {
|
|
2680
2808
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2681
2809
|
}
|
|
2682
2810
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2683
|
-
const launcherPath = (0,
|
|
2811
|
+
const launcherPath = (0, import_node_path14.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2684
2812
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2685
2813
|
if (!plist.includes(launcherPath)) {
|
|
2686
2814
|
return fail(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2700,15 +2828,15 @@ function darwinSchedulerStatus(env) {
|
|
|
2700
2828
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
2701
2829
|
}
|
|
2702
2830
|
function linuxSchedulerStatus(env) {
|
|
2703
|
-
const unitDir = (0,
|
|
2704
|
-
const timerPath = (0,
|
|
2831
|
+
const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2832
|
+
const timerPath = (0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2705
2833
|
const timer = readFileOrNull(timerPath);
|
|
2706
|
-
const service = readFileOrNull((0,
|
|
2834
|
+
const service = readFileOrNull((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME));
|
|
2707
2835
|
if (!timer || !service) {
|
|
2708
2836
|
return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
|
|
2709
2837
|
}
|
|
2710
2838
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2711
|
-
const launcherPath = (0,
|
|
2839
|
+
const launcherPath = (0, import_node_path14.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2712
2840
|
const fail = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2713
2841
|
if (!service.includes(launcherPath)) {
|
|
2714
2842
|
return fail(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
@@ -2747,10 +2875,10 @@ function installWindowsTask(env) {
|
|
|
2747
2875
|
const paths = ensureState(env);
|
|
2748
2876
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2749
2877
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2750
|
-
const launcherPath = (0,
|
|
2751
|
-
(0,
|
|
2752
|
-
const xmlPath = (0,
|
|
2753
|
-
(0,
|
|
2878
|
+
const launcherPath = (0, import_node_path14.join)(paths.root, LAUNCHER_FILE);
|
|
2879
|
+
(0, import_node_fs17.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2880
|
+
const xmlPath = (0, import_node_path14.join)(paths.root, "task.xml");
|
|
2881
|
+
(0, import_node_fs17.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
2754
2882
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2755
2883
|
if (create.status !== 0) {
|
|
2756
2884
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2766,11 +2894,11 @@ function refreshPosixLauncher(env) {
|
|
|
2766
2894
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2767
2895
|
if (!hubDist) return null;
|
|
2768
2896
|
const paths = ensureState(env);
|
|
2769
|
-
const launcherPath = (0,
|
|
2897
|
+
const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2770
2898
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2771
2899
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2772
|
-
(0,
|
|
2773
|
-
(0,
|
|
2900
|
+
(0, import_node_fs17.writeFileSync)(launcherPath, desired, "utf8");
|
|
2901
|
+
(0, import_node_fs17.chmodSync)(launcherPath, 493);
|
|
2774
2902
|
return launcherPath;
|
|
2775
2903
|
}
|
|
2776
2904
|
function installLaunchd(env) {
|
|
@@ -2782,13 +2910,13 @@ function installLaunchd(env) {
|
|
|
2782
2910
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2783
2911
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2784
2912
|
const paths = ensureState(env);
|
|
2785
|
-
const launcherPath = (0,
|
|
2786
|
-
(0,
|
|
2787
|
-
(0,
|
|
2788
|
-
const agentsDir = (0,
|
|
2789
|
-
(0,
|
|
2790
|
-
const plistPath = (0,
|
|
2791
|
-
(0,
|
|
2913
|
+
const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2914
|
+
(0, import_node_fs17.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2915
|
+
(0, import_node_fs17.chmodSync)(launcherPath, 493);
|
|
2916
|
+
const agentsDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
|
|
2917
|
+
(0, import_node_fs17.mkdirSync)(agentsDir, { recursive: true });
|
|
2918
|
+
const plistPath = (0, import_node_path14.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2919
|
+
(0, import_node_fs17.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2792
2920
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2793
2921
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2794
2922
|
if (load.status !== 0) {
|
|
@@ -2806,13 +2934,13 @@ function installSystemdTimer(env) {
|
|
|
2806
2934
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2807
2935
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2808
2936
|
const paths = ensureState(env);
|
|
2809
|
-
const launcherPath = (0,
|
|
2810
|
-
(0,
|
|
2811
|
-
(0,
|
|
2812
|
-
const unitDir = (0,
|
|
2813
|
-
(0,
|
|
2814
|
-
(0,
|
|
2815
|
-
(0,
|
|
2937
|
+
const launcherPath = (0, import_node_path14.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2938
|
+
(0, import_node_fs17.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
2939
|
+
(0, import_node_fs17.chmodSync)(launcherPath, 493);
|
|
2940
|
+
const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2941
|
+
(0, import_node_fs17.mkdirSync)(unitDir, { recursive: true });
|
|
2942
|
+
(0, import_node_fs17.writeFileSync)((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
2943
|
+
(0, import_node_fs17.writeFileSync)((0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
2816
2944
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2817
2945
|
if (reload.status !== 0) {
|
|
2818
2946
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -2850,28 +2978,28 @@ function uninstallWindowsTask(env) {
|
|
|
2850
2978
|
return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
|
|
2851
2979
|
}
|
|
2852
2980
|
function uninstallLaunchd(env) {
|
|
2853
|
-
const plistPath = (0,
|
|
2981
|
+
const plistPath = (0, import_node_path14.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2854
2982
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2855
|
-
if (boot.status !== 0 && (0,
|
|
2983
|
+
if (boot.status !== 0 && (0, import_node_fs17.existsSync)(plistPath)) {
|
|
2856
2984
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2857
2985
|
}
|
|
2858
2986
|
try {
|
|
2859
|
-
(0,
|
|
2987
|
+
(0, import_node_fs17.rmSync)(plistPath, { force: true });
|
|
2860
2988
|
} catch {
|
|
2861
2989
|
}
|
|
2862
2990
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
2863
2991
|
return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
|
|
2864
2992
|
}
|
|
2865
2993
|
function uninstallSystemdTimer(env) {
|
|
2866
|
-
const unitDir = (0,
|
|
2867
|
-
const timerPath = (0,
|
|
2994
|
+
const unitDir = (0, import_node_path14.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2995
|
+
const timerPath = (0, import_node_path14.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2868
2996
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2869
|
-
if (disable.status !== 0 && (0,
|
|
2997
|
+
if (disable.status !== 0 && (0, import_node_fs17.existsSync)(timerPath)) {
|
|
2870
2998
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2871
2999
|
}
|
|
2872
3000
|
try {
|
|
2873
|
-
(0,
|
|
2874
|
-
(0,
|
|
3001
|
+
(0, import_node_fs17.rmSync)(timerPath, { force: true });
|
|
3002
|
+
(0, import_node_fs17.rmSync)((0, import_node_path14.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
2875
3003
|
} catch {
|
|
2876
3004
|
}
|
|
2877
3005
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2886,16 +3014,16 @@ function uninstallTask(env = process.env) {
|
|
|
2886
3014
|
}
|
|
2887
3015
|
function dropLauncher(env, file) {
|
|
2888
3016
|
try {
|
|
2889
|
-
(0,
|
|
3017
|
+
(0, import_node_fs17.rmSync)((0, import_node_path14.join)(statePaths(env).root, file), { force: true });
|
|
2890
3018
|
} catch {
|
|
2891
3019
|
}
|
|
2892
3020
|
}
|
|
2893
3021
|
|
|
2894
3022
|
// src/status.ts
|
|
2895
|
-
var
|
|
3023
|
+
var import_node_fs18 = require("node:fs");
|
|
2896
3024
|
function readJournal(path) {
|
|
2897
3025
|
try {
|
|
2898
|
-
const events = (0,
|
|
3026
|
+
const events = (0, import_node_fs18.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
2899
3027
|
try {
|
|
2900
3028
|
return JSON.parse(line);
|
|
2901
3029
|
} catch {
|
|
@@ -2907,7 +3035,8 @@ function readJournal(path) {
|
|
|
2907
3035
|
return error?.code === "ENOENT" ? { events: [], error: null } : { events: [], error: `journal unreadable: ${error?.message ?? error}` };
|
|
2908
3036
|
}
|
|
2909
3037
|
}
|
|
2910
|
-
function versionState(installed, expected) {
|
|
3038
|
+
function versionState(installed, staged, expected) {
|
|
3039
|
+
if (staged && expected && staged === expected && installed !== expected) return "staged";
|
|
2911
3040
|
if (!installed || !expected) return "unknown";
|
|
2912
3041
|
const relation = compareSemver(installed, expected);
|
|
2913
3042
|
return relation === 0 ? "current" : relation > 0 ? "ahead" : "behind";
|
|
@@ -2922,28 +3051,36 @@ function hubStatus(hubVersion, env = process.env, invokedPath = process.argv[1])
|
|
|
2922
3051
|
const actual = /* @__PURE__ */ new Map();
|
|
2923
3052
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2924
3053
|
const globalHubVersion = hubDist ? probeVersion(hubDist) : null;
|
|
2925
|
-
actual.set("hub", globalHubVersion ? { version: globalHubVersion, location: hubDist, detail: "absolute global Hub binary" } : { version: hubVersion, location: invokedPath ?? null, detail: "currently executing Hub bundle (global package location unavailable)" });
|
|
2926
|
-
|
|
3054
|
+
actual.set("hub", globalHubVersion ? { version: globalHubVersion, staged: null, location: hubDist, detail: "absolute global Hub binary" } : { version: hubVersion, staged: null, location: invokedPath ?? null, detail: "currently executing Hub bundle (global package location unavailable)" });
|
|
3055
|
+
const cliProbe = probeCliInstallation(env);
|
|
3056
|
+
actual.set("cli", { version: cliProbe.version, staged: null, location: cliProbe.location, detail: cliProbe.detail });
|
|
2927
3057
|
for (const surface of enumerateSurfaces(env).filter((probe) => probe.present)) {
|
|
2928
3058
|
if (surface.id === "claude" || surface.id === "codex" || surface.id === "kilo") {
|
|
2929
|
-
|
|
3059
|
+
const probe = probeNativeHostInstallation(surface.id, env);
|
|
3060
|
+
actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
|
|
2930
3061
|
} else if (surface.id === "kimi") {
|
|
2931
|
-
|
|
3062
|
+
const probe = probeKimiInstallation(env);
|
|
3063
|
+
actual.set(surface.id, { version: probe.version, staged: probeKimiLaunchStaging(env), location: probe.location, detail: probe.detail });
|
|
2932
3064
|
} else if (surface.id === "cursor") {
|
|
2933
|
-
|
|
3065
|
+
const probe = probeCursorInstallation();
|
|
3066
|
+
actual.set(surface.id, { version: probe.version, staged: probeCursorLaunchStaging(), location: probe.location, detail: probe.detail });
|
|
2934
3067
|
} else if (surface.id === "jervcode") {
|
|
2935
|
-
|
|
3068
|
+
const probe = probeJervcodeInstallation(env);
|
|
3069
|
+
actual.set(surface.id, { version: probe.version, staged: null, location: probe.location, detail: probe.detail });
|
|
2936
3070
|
} else if (surface.id === "hermes") {
|
|
2937
|
-
|
|
3071
|
+
const probe = probeHermesInstallation(env);
|
|
3072
|
+
actual.set(surface.id, { version: probe.version, staged: probeHermesLaunchStaging(env), location: probe.location, detail: probe.detail });
|
|
2938
3073
|
}
|
|
2939
3074
|
}
|
|
2940
3075
|
const installed = {};
|
|
2941
3076
|
for (const [surface, probe] of actual) {
|
|
3077
|
+
const staged = probe.staged ?? null;
|
|
2942
3078
|
installed[surface] = {
|
|
2943
3079
|
installed: probe.version,
|
|
3080
|
+
staged,
|
|
2944
3081
|
expected: expectedVersion,
|
|
2945
3082
|
location: probe.location,
|
|
2946
|
-
state: versionState(probe.version, expectedVersion),
|
|
3083
|
+
state: versionState(probe.version, staged, expectedVersion),
|
|
2947
3084
|
detail: probe.detail
|
|
2948
3085
|
};
|
|
2949
3086
|
}
|
|
@@ -2980,7 +3117,8 @@ function formatHubStatus(status) {
|
|
|
2980
3117
|
" installed:"
|
|
2981
3118
|
];
|
|
2982
3119
|
for (const [surface, row] of Object.entries(status.installed)) {
|
|
2983
|
-
|
|
3120
|
+
const staged = row.staged ? `; staged ${row.staged} for next launch` : "";
|
|
3121
|
+
lines.push(` ${surface}: loaded ${row.installed ?? "unknown"}${staged} (expected ${row.expected ?? "unknown"}; ${row.state})`);
|
|
2984
3122
|
lines.push(` ${row.location ?? "location unavailable"} \u2014 ${row.detail}`);
|
|
2985
3123
|
}
|
|
2986
3124
|
lines.push(` autoupdate: ${status.schedule.enabled ? "on" : "off"} \u2014 ${status.schedule.detail}`);
|
|
@@ -3042,8 +3180,8 @@ function failConsequence(row) {
|
|
|
3042
3180
|
return matchFirst(FAIL_CONSEQUENCE, detail) ?? "update failed \u2014 previous version kept";
|
|
3043
3181
|
}
|
|
3044
3182
|
function classify(arm) {
|
|
3045
|
-
const kind = arm.verdict === "ok" ? arm.from !== null && arm.from === arm.to ? "current" : "updated" : arm.verdict === "skip" ? "keep" : arm.verdict === "defer" ? "retry" : "failed";
|
|
3046
|
-
const note = kind === "updated" ? ACTIVATION_NOTE[arm.surface] ?? null : kind === "keep" ? "above target" : kind === "retry" ? retryHint(arm.detail) : null;
|
|
3183
|
+
const kind = arm.verdict === "ok" ? arm.from !== null && arm.from === arm.to && !arm.launchStaged ? "current" : "updated" : arm.verdict === "skip" ? "keep" : arm.verdict === "defer" ? "retry" : "failed";
|
|
3184
|
+
const note = kind === "updated" ? arm.launchStaged ? "next launch" : ACTIVATION_NOTE[arm.surface] ?? null : kind === "keep" ? "above target" : kind === "retry" ? retryHint(arm.detail) : null;
|
|
3047
3185
|
return { name: arm.surface === "updater" ? "hub" : arm.surface, from: arm.from, to: arm.to, kind, note, detail: arm.detail, repeatFailure: arm.repeatFailure === true };
|
|
3048
3186
|
}
|
|
3049
3187
|
function statusText(kind, dryRun) {
|
|
@@ -3273,7 +3411,7 @@ function startSpinner(enabled) {
|
|
|
3273
3411
|
// src/index.ts
|
|
3274
3412
|
function ownVersion() {
|
|
3275
3413
|
try {
|
|
3276
|
-
return JSON.parse((0,
|
|
3414
|
+
return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path15.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
3277
3415
|
} catch {
|
|
3278
3416
|
return "0.0.0";
|
|
3279
3417
|
}
|
|
@@ -3401,6 +3539,7 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3401
3539
|
0 && (module.exports = {
|
|
3402
3540
|
acquireSharedDocLock,
|
|
3403
3541
|
buildWatcherCommand,
|
|
3542
|
+
clearLaunchTarget,
|
|
3404
3543
|
clearPending,
|
|
3405
3544
|
enumerateSurfaces,
|
|
3406
3545
|
failConsequence,
|
|
@@ -3416,13 +3555,19 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3416
3555
|
lockShaped,
|
|
3417
3556
|
main,
|
|
3418
3557
|
noteBusyDefer,
|
|
3558
|
+
noteLaunchStaged,
|
|
3419
3559
|
npmStringField,
|
|
3420
3560
|
ownVersion,
|
|
3421
3561
|
parseNpmDistIntegrity,
|
|
3422
3562
|
pendingPath,
|
|
3423
3563
|
pidAlive,
|
|
3564
|
+
probeCursorLaunchStaging,
|
|
3424
3565
|
probeHermesInstallation,
|
|
3566
|
+
probeHermesLaunchStaging,
|
|
3567
|
+
probeKimiLaunchStaging,
|
|
3568
|
+
probeLaunchTargetVersion,
|
|
3425
3569
|
progressiveRow,
|
|
3570
|
+
readLaunchTarget,
|
|
3426
3571
|
readPending,
|
|
3427
3572
|
reconcileCursorUserHooks,
|
|
3428
3573
|
reconcileKimiInstalledPlugin,
|
|
@@ -3442,5 +3587,6 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3442
3587
|
watcherLockPath,
|
|
3443
3588
|
withSharedDocLock,
|
|
3444
3589
|
wrapWords,
|
|
3590
|
+
writeLaunchTarget,
|
|
3445
3591
|
writePending
|
|
3446
3592
|
});
|