@mutmutco/hub 4.1.3 → 4.1.4

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.
Files changed (2) hide show
  1. package/dist/index.cjs +378 -184
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ acquireSharedDocLock: () => acquireSharedDocLock,
24
25
  buildWatcherCommand: () => buildWatcherCommand,
25
26
  clearPending: () => clearPending,
26
27
  enumerateSurfaces: () => enumerateSurfaces,
@@ -28,6 +29,7 @@ __export(index_exports, {
28
29
  formatReconcileReport: () => formatReconcileReport,
29
30
  hermesArm: () => hermesArm,
30
31
  hermesHome: () => hermesHome,
32
+ hermesHostEvidence: () => hermesHostEvidence,
31
33
  hubStatus: () => hubStatus,
32
34
  launchdPlist: () => launchdPlist,
33
35
  launcherSh: () => launcherSh,
@@ -36,28 +38,36 @@ __export(index_exports, {
36
38
  lockShaped: () => lockShaped,
37
39
  main: () => main,
38
40
  noteBusyDefer: () => noteBusyDefer,
41
+ npmStringField: () => npmStringField,
39
42
  ownVersion: () => ownVersion,
43
+ parseNpmDistIntegrity: () => parseNpmDistIntegrity,
40
44
  pendingPath: () => pendingPath,
41
45
  pidAlive: () => pidAlive,
42
46
  probeHermesInstallation: () => probeHermesInstallation,
43
47
  progressiveRow: () => progressiveRow,
44
48
  readPending: () => readPending,
45
49
  reconcileCursorUserHooks: () => reconcileCursorUserHooks,
50
+ reconcileKimiInstalledPlugin: () => reconcileKimiInstalledPlugin,
51
+ releaseSharedDocLock: () => releaseSharedDocLock,
46
52
  reportFooterLines: () => reportFooterLines,
47
53
  retryHint: () => retryHint,
48
54
  schedulePendingWatcher: () => schedulePendingWatcher,
49
55
  schedulerStatus: () => schedulerStatus,
56
+ sharedDocLockPath: () => sharedDocLockPath,
57
+ sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
50
58
  startSpinner: () => startSpinner,
51
59
  systemdServiceUnit: () => systemdServiceUnit,
52
60
  systemdTimerUnit: () => systemdTimerUnit,
53
61
  taskXml: () => taskXml,
54
62
  updateHeader: () => updateHeader,
63
+ validateKimiInstalledPlugin: () => validateKimiInstalledPlugin,
55
64
  watcherLockPath: () => watcherLockPath,
65
+ withSharedDocLock: () => withSharedDocLock,
56
66
  wrapWords: () => wrapWords,
57
67
  writePending: () => writePending
58
68
  });
59
69
  module.exports = __toCommonJS(index_exports);
60
- var import_node_fs17 = require("node:fs");
70
+ var import_node_fs18 = require("node:fs");
61
71
  var import_node_path14 = require("node:path");
62
72
 
63
73
  // src/state.ts
@@ -258,6 +268,16 @@ function npmView(spec, field, env = process.env) {
258
268
  return null;
259
269
  }
260
270
  }
271
+ function parseNpmDistIntegrity(value) {
272
+ if (typeof value === "string") return value.length > 0 ? value : null;
273
+ if (!Array.isArray(value) || value.length !== 1) return null;
274
+ const only = value[0];
275
+ return typeof only === "string" && only.length > 0 ? only : null;
276
+ }
277
+ var npmStringField = parseNpmDistIntegrity;
278
+ function npmDistIntegrity(spec, env = process.env) {
279
+ return parseNpmDistIntegrity(npmView(spec, "dist.integrity", env));
280
+ }
261
281
  function npmVersions(packageName, env = process.env) {
262
282
  const versions = npmView(packageName, "versions", env);
263
283
  return Array.isArray(versions) ? versions.filter((v) => typeof v === "string") : [];
@@ -769,27 +789,7 @@ function claudeMmi(runner, env) {
769
789
  function claudeMarketplacesPath(env) {
770
790
  return env.MMI_UPDATER_CLAUDE_MARKETPLACES || (0, import_node_path7.join)((0, import_node_os3.homedir)(), ".claude", "plugins", "known_marketplaces.json");
771
791
  }
772
- var CLAUDE_PROCESS_SEGMENT = /^claude(-code|-cli)?(\.(exe|cmd|bat|ps1|js|mjs|cjs|py))?$/;
773
- function claudeProcessSegment(line) {
774
- for (const token of line.replace(/["']/g, " ").split(/\s+/)) {
775
- for (const segment of token.split(/[/\\]/)) {
776
- if (CLAUDE_PROCESS_SEGMENT.test(segment)) return segment;
777
- }
778
- }
779
- return null;
780
- }
781
- function claudeHostRunning(env) {
782
- const marker = env.CLAUDE_CODE_SESSION_ID?.trim() ? "CLAUDE_CODE_SESSION_ID" : env.CLAUDECODE?.trim() ? "CLAUDECODE" : env.CLAUDE_PLUGIN_ROOT?.trim() ? "CLAUDE_PLUGIN_ROOT" : null;
783
- if (marker) return { running: true, evidence: `${marker} is set \u2014 this process is itself inside a Claude hook` };
784
- const result = process.platform === "win32" ? (0, import_node_child_process6.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process6.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
785
- if (result.status !== 0 || !(result.stdout ?? "").trim()) return { running: true, evidence: "the process list is unreadable \u2014 unsafe to write" };
786
- for (const line of result.stdout.split(/\r?\n/)) {
787
- const segment = claudeProcessSegment(line);
788
- if (segment) return { running: true, evidence: `a live process names claude (${segment})` };
789
- }
790
- return { running: false, evidence: "no claude-named process in the process list" };
791
- }
792
- function ensureClaudeSingleWriter(env, dryRun) {
792
+ function ensureClaudeMarketplace(env) {
793
793
  const path = claudeMarketplacesPath(env);
794
794
  let body;
795
795
  try {
@@ -811,22 +811,7 @@ function ensureClaudeSingleWriter(env, dryRun) {
811
811
  detail: `mutmutco marketplace is registered to ${registeredRepo ?? "a non-github source"}, not ${MMI_MARKETPLACE_REPO} \u2014 a retired or foreign repo is squatting the name; re-register it (claude plugin marketplace remove mutmutco && claude plugin marketplace add ${MMI_MARKETPLACE_REPO}) before the updater can trust it`
812
812
  };
813
813
  }
814
- if (registration.autoUpdate !== true) return { ok: true };
815
- if (dryRun) return { ok: true, detail: "would disable Claude background autoUpdate (updater is the single writer)" };
816
- const quiescence = claudeHostRunning(env);
817
- if (quiescence.running) {
818
- return { ok: false, detail: `Claude background autoUpdate is on, but Claude Code is running and would overwrite the registration \u2014 defer until the host is absent: ${quiescence.evidence}` };
819
- }
820
- registration.autoUpdate = false;
821
- const tmp = `${path}.tmp-${process.pid}`;
822
- try {
823
- (0, import_node_fs8.writeFileSync)(tmp, JSON.stringify(body, null, 2) + "\n", "utf8");
824
- (0, import_node_fs8.renameSync)(tmp, path);
825
- const reread = JSON.parse((0, import_node_fs8.readFileSync)(path, "utf8"));
826
- return reread?.mutmutco?.autoUpdate === false ? { ok: true, detail: "disabled Claude background autoUpdate (updater is the single writer)" } : { ok: false, detail: "Claude background autoUpdate write did not persist" };
827
- } catch (error) {
828
- return { ok: false, detail: `cannot disable Claude background autoUpdate: ${error?.message ?? error}` };
829
- }
814
+ return { ok: true };
830
815
  }
831
816
  function claudeArm(options) {
832
817
  const env = options.env ?? process.env;
@@ -835,16 +820,15 @@ function claudeArm(options) {
835
820
  const before = claudeMmi(runner, env);
836
821
  if (before.error) return { surface: "claude", from: null, to: options.target, verdict: "defer", detail: before.error };
837
822
  const from = before.row?.version ?? null;
838
- let singleWriter = ensureClaudeSingleWriter(env, options.dryRun);
839
- if (!singleWriter.ok && singleWriter.unregistered) {
823
+ let marketplaceTrust = ensureClaudeMarketplace(env);
824
+ if (!marketplaceTrust.ok && marketplaceTrust.unregistered) {
840
825
  const add = runner("claude", ["plugin", "marketplace", "add", repoUrl], env);
841
826
  if (add.status !== 0) return { surface: "claude", from, to: options.target, verdict: "defer", detail: `mutmutco marketplace was not registered and marketplace add failed: ${failure(add)}` };
842
- singleWriter = ensureClaudeSingleWriter(env, options.dryRun);
827
+ marketplaceTrust = ensureClaudeMarketplace(env);
843
828
  }
844
- if (!singleWriter.ok) return { surface: "claude", from, to: options.target, verdict: "defer", detail: singleWriter.detail ?? "cannot establish updater as the single writer" };
829
+ if (!marketplaceTrust.ok) return { surface: "claude", from, to: options.target, verdict: "defer", detail: marketplaceTrust.detail ?? "cannot trust the mutmutco marketplace registration" };
845
830
  if (options.dryRun) {
846
- const writer = singleWriter.detail ? `; ${singleWriter.detail}` : "";
847
- return { surface: "claude", from, to: options.target, verdict: "ok", detail: `dry-run: would refresh mutmutco and converge mmi@mutmutco ${from ?? "absent"} -> ${options.target}${writer}` };
831
+ return { surface: "claude", from, to: options.target, verdict: "ok", detail: `dry-run: would refresh mutmutco and converge mmi@mutmutco ${from ?? "absent"} -> ${options.target}` };
848
832
  }
849
833
  const marketplace = runner("claude", ["plugin", "marketplace", "update", "mutmutco"], env);
850
834
  if (marketplace.status !== 0) {
@@ -853,8 +837,7 @@ function claudeArm(options) {
853
837
  let observed = claudeMmi(runner, env);
854
838
  if (observed.error) return { surface: "claude", from, to: options.target, verdict: "defer", detail: observed.error };
855
839
  if (observed.row?.version === options.target && observed.row.enabled === true) {
856
- const writer = singleWriter.detail ? `${singleWriter.detail}; ` : "";
857
- return { surface: "claude", from, to: options.target, verdict: "ok", detail: `${writer}catalog refreshed; installed+enabled at target ${options.target}` };
840
+ return { surface: "claude", from, to: options.target, verdict: "ok", detail: `catalog refreshed; installed+enabled at target ${options.target}` };
858
841
  }
859
842
  const scope = observed.row?.scope || before.row?.scope || "user";
860
843
  const command = observed.row ? ["plugin", "update", "mmi@mutmutco", "--scope", scope] : ["plugin", "install", "mmi@mutmutco", "--scope", scope];
@@ -1025,7 +1008,7 @@ function kiloArm(options) {
1025
1008
 
1026
1009
  // src/arm-kimi.ts
1027
1010
  var import_node_child_process7 = require("node:child_process");
1028
- var import_node_fs10 = require("node:fs");
1011
+ var import_node_fs11 = require("node:fs");
1029
1012
  var import_node_path8 = require("node:path");
1030
1013
 
1031
1014
  // src/compat.ts
@@ -1039,6 +1022,131 @@ function readManifestCompat(manifestPath) {
1039
1022
  }
1040
1023
  }
1041
1024
 
1025
+ // src/kimi-registration.ts
1026
+ function validateKimiInstalledPlugin(document, pluginRoot, id) {
1027
+ if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
1028
+ const plugins = document.plugins;
1029
+ if (!Array.isArray(plugins)) return "malformed-document";
1030
+ const entry = plugins.find(
1031
+ (candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate) && candidate.id === id
1032
+ );
1033
+ if (!entry) return "absent-record";
1034
+ const record = entry;
1035
+ if (typeof record.root !== "string") return "malformed-record";
1036
+ if (record.root !== pluginRoot) return "foreign-root";
1037
+ return record.enabled ? "healthy" : "disabled";
1038
+ }
1039
+
1040
+ // src/host-doc-lock.ts
1041
+ var import_node_fs10 = require("node:fs");
1042
+ var import_node_os4 = require("node:os");
1043
+ var SHARED_DOC_LOCK_STALE_MS = 3e4;
1044
+ var SHARED_DOC_LOCK_ATTEMPTS = 50;
1045
+ var SHARED_DOC_LOCK_INTERVAL_MS = 100;
1046
+ function sharedDocLockPath(documentPath) {
1047
+ return `${documentPath}.jerv-lock`;
1048
+ }
1049
+ function lockContent(options) {
1050
+ return `${JSON.stringify(options, null, 2)}
1051
+ `;
1052
+ }
1053
+ function readHolder(lockPath) {
1054
+ try {
1055
+ const parsed = JSON.parse((0, import_node_fs10.readFileSync)(lockPath, "utf8"));
1056
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1057
+ return parsed;
1058
+ } catch {
1059
+ return null;
1060
+ }
1061
+ }
1062
+ function lockAgeMs(lockPath, now) {
1063
+ const holder = readHolder(lockPath);
1064
+ const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
1065
+ if (!Number.isNaN(declared)) return Math.max(0, now - declared);
1066
+ try {
1067
+ return Math.max(0, now - (0, import_node_fs10.statSync)(lockPath).mtimeMs);
1068
+ } catch {
1069
+ return 0;
1070
+ }
1071
+ }
1072
+ function sharedDocLockTimeoutReceipt(documentPath, lockPath, holder, waitedMs) {
1073
+ const named = holder && typeof holder === "object";
1074
+ const who = named ? `held by ${String(holder.owner ?? "unknown")} (pid ${String(holder.pid ?? "?")}, host ${String(holder.host ?? "?")})` : `held by an unreadable lock at ${lockPath}`;
1075
+ return `shared-document lock timeout: ${documentPath} is ${who} \u2014 waited ${waitedMs}ms, nothing was written \u2014 retry next tick or remove ${lockPath} if stale`;
1076
+ }
1077
+ function acquireSharedDocLock(documentPath, options) {
1078
+ const lockPath = sharedDocLockPath(documentPath);
1079
+ const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
1080
+ const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
1081
+ const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
1082
+ const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os4.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
1083
+ const content = lockContent(identity);
1084
+ const sleep = (ms) => {
1085
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1086
+ };
1087
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1088
+ let fd;
1089
+ try {
1090
+ fd = (0, import_node_fs10.openSync)(lockPath, "wx");
1091
+ (0, import_node_fs10.writeSync)(fd, content);
1092
+ (0, import_node_fs10.closeSync)(fd);
1093
+ return {
1094
+ documentPath,
1095
+ lockPath,
1096
+ owner: identity.owner,
1097
+ pid: identity.pid,
1098
+ host: identity.host,
1099
+ acquiredAt: identity.acquiredAt,
1100
+ release: () => releaseSharedDocLock({ lockPath, owner: identity.owner, pid: identity.pid })
1101
+ };
1102
+ } catch (error) {
1103
+ if (fd !== void 0) {
1104
+ try {
1105
+ (0, import_node_fs10.closeSync)(fd);
1106
+ } catch {
1107
+ }
1108
+ try {
1109
+ (0, import_node_fs10.unlinkSync)(lockPath);
1110
+ } catch {
1111
+ }
1112
+ }
1113
+ if (error?.code !== "EEXIST") throw error;
1114
+ }
1115
+ if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
1116
+ const aside = `${lockPath}.stale-${Date.now()}`;
1117
+ try {
1118
+ (0, import_node_fs10.renameSync)(lockPath, aside);
1119
+ } catch {
1120
+ continue;
1121
+ }
1122
+ try {
1123
+ (0, import_node_fs10.rmSync)(aside, { force: true });
1124
+ } catch {
1125
+ }
1126
+ continue;
1127
+ }
1128
+ if (attempt < attempts - 1) sleep(intervalMs);
1129
+ }
1130
+ throw new Error(sharedDocLockTimeoutReceipt(documentPath, lockPath, readHolder(lockPath), Math.max(0, attempts * intervalMs)));
1131
+ }
1132
+ function releaseSharedDocLock(handle) {
1133
+ try {
1134
+ const holder = readHolder(handle.lockPath);
1135
+ if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
1136
+ (0, import_node_fs10.unlinkSync)(handle.lockPath);
1137
+ }
1138
+ } catch {
1139
+ }
1140
+ }
1141
+ function withSharedDocLock(documentPath, options, fn) {
1142
+ const lock = acquireSharedDocLock(documentPath, options);
1143
+ try {
1144
+ return fn(lock.documentPath);
1145
+ } finally {
1146
+ lock.release();
1147
+ }
1148
+ }
1149
+
1042
1150
  // src/arm-kimi.ts
1043
1151
  var KIMI_PACKAGE = "@mutmutco/kimi-plugin";
1044
1152
  var TREE_MARKERS = [".kimi-plugin/plugin.json", "skills/mmi/SKILL.md", "scripts/hook-run.mjs"];
@@ -1047,7 +1155,7 @@ function treeHealthy(root) {
1047
1155
  }
1048
1156
  function markerVersion(root) {
1049
1157
  try {
1050
- const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
1158
+ const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path8.join)(root, ".kimi-plugin", "plugin.json"), "utf8")).version;
1051
1159
  return typeof version === "string" ? version : null;
1052
1160
  } catch {
1053
1161
  return null;
@@ -1055,12 +1163,58 @@ function markerVersion(root) {
1055
1163
  }
1056
1164
  function packageVersion(root) {
1057
1165
  try {
1058
- const version = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(root, "package.json"), "utf8")).version;
1166
+ const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path8.join)(root, "package.json"), "utf8")).version;
1059
1167
  return typeof version === "string" ? version : null;
1060
1168
  } catch {
1061
1169
  return null;
1062
1170
  }
1063
1171
  }
1172
+ var KIMI_REGISTRATION_LOCK_OWNER = "mmi-hub:kimi-registration";
1173
+ function reconcileKimiInstalledPlugin(kimiHomeDir, pluginRoot, id, lock = {}) {
1174
+ const path = (0, import_node_path8.join)(kimiHomeDir, "plugins", "installed.json");
1175
+ (0, import_node_fs11.mkdirSync)((0, import_node_path8.join)(kimiHomeDir, "plugins"), { recursive: true });
1176
+ return withSharedDocLock(path, { owner: KIMI_REGISTRATION_LOCK_OWNER, ...lock }, () => mergeKimiInstalledPlugin(path, pluginRoot, id));
1177
+ }
1178
+ function mergeKimiInstalledPlugin(path, pluginRoot, id) {
1179
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1180
+ const fresh = { id, root: pluginRoot, enabled: true, installedAt: now, updatedAt: now, originalSource: pluginRoot, source: "local-path" };
1181
+ let document = { version: 1, plugins: [] };
1182
+ if ((0, import_node_fs11.existsSync)(path)) {
1183
+ let parsed;
1184
+ try {
1185
+ parsed = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
1186
+ } catch {
1187
+ return "conflict";
1188
+ }
1189
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || !Array.isArray(parsed.plugins)) return "conflict";
1190
+ document = parsed;
1191
+ }
1192
+ const plugins = [...document.plugins];
1193
+ const registration = validateKimiInstalledPlugin(document, pluginRoot, id);
1194
+ const index = plugins.findIndex((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && entry.id === id);
1195
+ if (registration === "malformed-record" || registration === "foreign-root") return "conflict";
1196
+ if (index === -1) {
1197
+ plugins.push(fresh);
1198
+ } else {
1199
+ const prior = plugins[index];
1200
+ const next = {
1201
+ ...prior,
1202
+ root: pluginRoot,
1203
+ // A user's explicit disable is their choice and survives; anything else converges to enabled.
1204
+ enabled: prior.enabled === false ? false : true,
1205
+ installedAt: typeof prior.installedAt === "string" ? prior.installedAt : now,
1206
+ originalSource: pluginRoot,
1207
+ source: "local-path"
1208
+ };
1209
+ if (JSON.stringify(next) === JSON.stringify(prior)) return "current";
1210
+ plugins[index] = { ...next, updatedAt: now };
1211
+ }
1212
+ const tmp = `${path}.tmp-${process.pid}`;
1213
+ (0, import_node_fs11.writeFileSync)(tmp, `${JSON.stringify({ ...document, version: 1, plugins }, null, 2)}
1214
+ `, "utf8");
1215
+ (0, import_node_fs11.renameSync)(tmp, path);
1216
+ return "updated";
1217
+ }
1064
1218
  function probeKimiInstallation(env = process.env) {
1065
1219
  const location = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed", "mmi");
1066
1220
  const version = markerVersion(location);
@@ -1074,10 +1228,10 @@ function scratchRoot(env) {
1074
1228
  }
1075
1229
  function pruneScratchDir(root, keep) {
1076
1230
  try {
1077
- for (const name of (0, import_node_fs10.readdirSync)(root)) {
1231
+ for (const name of (0, import_node_fs11.readdirSync)(root)) {
1078
1232
  if (name === keep) continue;
1079
1233
  try {
1080
- (0, import_node_fs10.rmSync)((0, import_node_path8.join)(root, name), { recursive: true, force: true });
1234
+ (0, import_node_fs11.rmSync)((0, import_node_path8.join)(root, name), { recursive: true, force: true });
1081
1235
  } catch {
1082
1236
  }
1083
1237
  }
@@ -1103,11 +1257,11 @@ function kimiHostRunning(env) {
1103
1257
  }
1104
1258
  function restoreQuarantine(quarantine, live) {
1105
1259
  try {
1106
- (0, import_node_fs10.renameSync)(quarantine, live);
1260
+ (0, import_node_fs11.renameSync)(quarantine, live);
1107
1261
  } catch (error) {
1108
1262
  return { restored: false, detail: `${error?.message ?? error}` };
1109
1263
  }
1110
- if (!(0, import_node_fs10.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
1264
+ if (!(0, import_node_fs11.existsSync)(live)) return { restored: false, detail: `${live} is still absent after the restore rename` };
1111
1265
  return { restored: true, detail: `marker ${markerVersion(live) ?? "unreadable"}` };
1112
1266
  }
1113
1267
  function kimiArm(options) {
@@ -1126,15 +1280,32 @@ function kimiArm(options) {
1126
1280
  const managedParent = (0, import_node_path8.join)(kimiHome(env), "plugins", "managed");
1127
1281
  const live = (0, import_node_path8.join)(managedParent, "mmi");
1128
1282
  const installed = markerVersion(live);
1283
+ const ensureRegistered = (detail) => {
1284
+ if (!(0, import_node_fs11.existsSync)(live) || !treeHealthy(live)) return { detail };
1285
+ if (dryRun) return { detail: `${detail}; installed.json untouched (dry-run)` };
1286
+ let registration;
1287
+ try {
1288
+ registration = reconcileKimiInstalledPlugin(kimiHome(env), live, "mmi");
1289
+ } catch (error) {
1290
+ return { fail: true, detail: `${detail}; installed.json registration FAILED: ${error?.message ?? error}` };
1291
+ }
1292
+ if (registration === "conflict") {
1293
+ 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` };
1294
+ }
1295
+ return { detail: `${detail}; installed.json record ${registration === "updated" ? "written" : "already current"}` };
1296
+ };
1129
1297
  if (installed && compareSemver(installed, target) > 0) {
1130
- return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
1298
+ const reg2 = ensureRegistered(`installed ${installed} is above candidate ${target} (monotonic)`);
1299
+ return result(installed, installed, reg2.fail ? "fail" : "skip", reg2.detail);
1131
1300
  }
1132
1301
  const highWater = journalHighWater(paths.journalPath, "kimi");
1133
1302
  if (highWater && compareSemver(highWater, target) > 0) {
1134
- return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
1303
+ const reg2 = ensureRegistered(`journal high-water ${highWater} is above candidate ${target} (monotonic)`);
1304
+ return result(installed, highWater, reg2.fail ? "fail" : "skip", reg2.detail);
1135
1305
  }
1136
1306
  if (installed === target && treeHealthy(live)) {
1137
- return result(installed, target, "ok", `already at target ${target}`);
1307
+ const reg2 = ensureRegistered(`already at target ${target}`);
1308
+ return result(installed, target, reg2.fail ? "fail" : "ok", reg2.detail);
1138
1309
  }
1139
1310
  if (dryRun) {
1140
1311
  return result(installed, target, "ok", `dry-run: would converge ${installed ?? "absent"} -> ${target} (tarball generation switch while Kimi is absent)`);
@@ -1142,8 +1313,8 @@ function kimiArm(options) {
1142
1313
  const stageRoot = (0, import_node_path8.join)(paths.staging, "kimi", target);
1143
1314
  const staged = (0, import_node_path8.join)(stageRoot, "node_modules", ...KIMI_PACKAGE.split("/"));
1144
1315
  if (!treeHealthy(staged)) {
1145
- (0, import_node_fs10.mkdirSync)(stageRoot, { recursive: true });
1146
- (0, import_node_fs10.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1316
+ (0, import_node_fs11.mkdirSync)(stageRoot, { recursive: true });
1317
+ (0, import_node_fs11.writeFileSync)((0, import_node_path8.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
1147
1318
  const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${KIMI_PACKAGE}@${target}`], env);
1148
1319
  if (install.status !== 0) {
1149
1320
  return result(installed, target, "defer", `staging install failed: ${tail2(install.stderr || install.stdout)}`);
@@ -1171,22 +1342,22 @@ function kimiArm(options) {
1171
1342
  const quarantineRoot = (0, import_node_path8.join)(scratch, "quarantine");
1172
1343
  const rejectedRoot = (0, import_node_path8.join)(scratch, "rejected");
1173
1344
  try {
1174
- (0, import_node_fs10.mkdirSync)(managedParent, { recursive: true });
1175
- (0, import_node_fs10.mkdirSync)(quarantineRoot, { recursive: true });
1176
- (0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
1177
- (0, import_node_fs10.cpSync)(staged, incoming, { recursive: true });
1345
+ (0, import_node_fs11.mkdirSync)(managedParent, { recursive: true });
1346
+ (0, import_node_fs11.mkdirSync)(quarantineRoot, { recursive: true });
1347
+ (0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
1348
+ (0, import_node_fs11.cpSync)(staged, incoming, { recursive: true });
1178
1349
  } catch (error) {
1179
1350
  return result(installed, target, "defer", `cannot place the incoming generation under ${scratch}: ${error?.message ?? error}`);
1180
1351
  }
1181
1352
  if (markerVersion(incoming) !== target || !treeHealthy(incoming)) {
1182
1353
  return result(installed, target, "defer", `incoming generation verifies as ${markerVersion(incoming) ?? "unreadable"}, expected ${target}`);
1183
1354
  }
1184
- const hadLive = (0, import_node_fs10.existsSync)(live);
1355
+ const hadLive = (0, import_node_fs11.existsSync)(live);
1185
1356
  const generation = `${installed ?? "unknown"}-${Date.now()}`;
1186
1357
  const quarantine = (0, import_node_path8.join)(quarantineRoot, generation);
1187
1358
  if (hadLive) {
1188
1359
  try {
1189
- (0, import_node_fs10.renameSync)(live, quarantine);
1360
+ (0, import_node_fs11.renameSync)(live, quarantine);
1190
1361
  } catch (error) {
1191
1362
  const reason = String(error?.message ?? error);
1192
1363
  const pending = lockShaped(reason) ? ` \u2014 ${noteBusyDefer(options.paths, env, { surface: "kimi", target, reason, dirs: ["kimi"] })}` : "";
@@ -1194,7 +1365,7 @@ function kimiArm(options) {
1194
1365
  }
1195
1366
  }
1196
1367
  try {
1197
- (0, import_node_fs10.renameSync)(incoming, live);
1368
+ (0, import_node_fs11.renameSync)(incoming, live);
1198
1369
  } catch (error) {
1199
1370
  const failed = `cannot switch the new generation in: ${error?.message ?? error}`;
1200
1371
  if (!hadLive) {
@@ -1209,8 +1380,8 @@ function kimiArm(options) {
1209
1380
  const observed = `post-switch marker is ${switched ?? "unreadable"}, expected ${target}`;
1210
1381
  const rejectedName = `${target}-${Date.now()}`;
1211
1382
  try {
1212
- (0, import_node_fs10.mkdirSync)(rejectedRoot, { recursive: true });
1213
- (0, import_node_fs10.renameSync)(live, (0, import_node_path8.join)(rejectedRoot, rejectedName));
1383
+ (0, import_node_fs11.mkdirSync)(rejectedRoot, { recursive: true });
1384
+ (0, import_node_fs11.renameSync)(live, (0, import_node_path8.join)(rejectedRoot, rejectedName));
1214
1385
  } catch (error) {
1215
1386
  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}` : ""}`);
1216
1387
  }
@@ -1224,16 +1395,17 @@ function kimiArm(options) {
1224
1395
  pruneScratchDir(quarantineRoot, hadLive ? generation : null);
1225
1396
  pruneScratchDir(rejectedRoot, null);
1226
1397
  try {
1227
- (0, import_node_fs10.rmSync)(incomingRoot, { recursive: true, force: true });
1398
+ (0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
1228
1399
  } catch {
1229
1400
  }
1230
- return result(installed, target, "ok", `converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
1401
+ const reg = ensureRegistered(`converged ${installed ?? "absent"} -> ${target} (staged, verified, switched${hadLive ? `; previous generation quarantined at ${quarantine}` : ""})`);
1402
+ return result(installed, target, reg.fail ? "fail" : "ok", reg.detail);
1231
1403
  }
1232
1404
 
1233
1405
  // src/arm-cursor.ts
1234
- var import_node_fs11 = require("node:fs");
1406
+ var import_node_fs12 = require("node:fs");
1235
1407
  var import_node_child_process8 = require("node:child_process");
1236
- var import_node_os4 = require("node:os");
1408
+ var import_node_os5 = require("node:os");
1237
1409
  var import_node_path9 = require("node:path");
1238
1410
  var PACKAGE = "@mutmutco/cursor-plugin";
1239
1411
  var MANIFEST = ".cursor-plugin/plugin.json";
@@ -1243,14 +1415,14 @@ var KEEP_GENERATIONS = 2;
1243
1415
  var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
1244
1416
  var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
1245
1417
  function cursorPluginsRoot() {
1246
- return (0, import_node_path9.join)((0, import_node_os4.homedir)(), ".cursor", "plugins");
1418
+ return (0, import_node_path9.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
1247
1419
  }
1248
1420
  function treeHealthy2(root) {
1249
- return TREE_FILES.every((rel) => (0, import_node_fs11.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
1421
+ return TREE_FILES.every((rel) => (0, import_node_fs12.existsSync)((0, import_node_path9.join)(root, ...rel.split("/"))));
1250
1422
  }
1251
1423
  function readJson(root, rel) {
1252
1424
  try {
1253
- return JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
1425
+ return JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(root, ...rel.split("/")), "utf8"));
1254
1426
  } catch {
1255
1427
  return null;
1256
1428
  }
@@ -1274,7 +1446,7 @@ function message(error) {
1274
1446
  }
1275
1447
  function discard(path) {
1276
1448
  try {
1277
- (0, import_node_fs11.rmSync)(path, { recursive: true, force: true });
1449
+ (0, import_node_fs12.rmSync)(path, { recursive: true, force: true });
1278
1450
  } catch {
1279
1451
  }
1280
1452
  }
@@ -1312,7 +1484,7 @@ function resolveCommand(command, pluginRoot) {
1312
1484
  }
1313
1485
  function desiredUserHooks(pluginRoot) {
1314
1486
  try {
1315
- const doc = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path9.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
1487
+ const doc = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path9.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
1316
1488
  const hooks = doc.hooks;
1317
1489
  if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
1318
1490
  const desired = /* @__PURE__ */ new Map();
@@ -1345,30 +1517,41 @@ function mergeUserHooks(existing, desired, pluginRoot) {
1345
1517
  merged.hooks = nextHooks;
1346
1518
  return merged;
1347
1519
  }
1348
- function reconcileCursorUserHooks(pluginRoot, homeDir) {
1520
+ var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
1521
+ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
1349
1522
  const desired = desiredUserHooks(pluginRoot);
1350
1523
  if (!desired) return "source-unreadable";
1351
1524
  const path = (0, import_node_path9.join)(homeDir, ".cursor", USER_HOOKS_FILE);
1352
- if (!(0, import_node_fs11.existsSync)(path)) {
1353
- (0, import_node_fs11.writeFileSync)(path, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
1525
+ return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
1526
+ if (!(0, import_node_fs12.existsSync)(path)) {
1527
+ const fresh = `${path}.tmp-${process.pid}`;
1528
+ (0, import_node_fs12.writeFileSync)(fresh, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
1529
+ (0, import_node_fs12.renameSync)(fresh, path);
1530
+ return "updated";
1531
+ }
1532
+ let existing;
1533
+ try {
1534
+ existing = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf8"));
1535
+ } catch {
1536
+ return "user-conflict";
1537
+ }
1538
+ const merged = mergeUserHooks(existing, desired, pluginRoot);
1539
+ if (!merged) return "user-conflict";
1540
+ if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
1541
+ const tmp = `${path}.tmp-${process.pid}`;
1542
+ (0, import_node_fs12.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
1543
+ (0, import_node_fs12.renameSync)(tmp, path);
1354
1544
  return "updated";
1355
- }
1356
- let existing;
1357
- try {
1358
- existing = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
1359
- } catch {
1360
- return "user-conflict";
1361
- }
1362
- const merged = mergeUserHooks(existing, desired, pluginRoot);
1363
- if (!merged) return "user-conflict";
1364
- if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
1365
- const tmp = `${path}.tmp-${process.pid}`;
1366
- (0, import_node_fs11.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
1367
- (0, import_node_fs11.renameSync)(tmp, path);
1368
- return "updated";
1545
+ });
1369
1546
  }
1370
1547
  function userHooksDetail(pluginRoot, homeDir) {
1371
- switch (reconcileCursorUserHooks(pluginRoot, homeDir)) {
1548
+ let verdict;
1549
+ try {
1550
+ verdict = reconcileCursorUserHooks(pluginRoot, homeDir);
1551
+ } catch (error) {
1552
+ return `; ${error?.message ?? error}`;
1553
+ }
1554
+ switch (verdict) {
1372
1555
  case "updated":
1373
1556
  return "; user hooks bridged into ~/.cursor/hooks.json";
1374
1557
  case "user-conflict":
@@ -1383,15 +1566,15 @@ function writeRollbackPointer(root, entry) {
1383
1566
  const path = (0, import_node_path9.join)(root, "quarantine", "rollback.json");
1384
1567
  const tmp = `${path}.tmp-${process.pid}`;
1385
1568
  try {
1386
- (0, import_node_fs11.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1387
- (0, import_node_fs11.renameSync)(tmp, path);
1569
+ (0, import_node_fs12.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
1570
+ (0, import_node_fs12.renameSync)(tmp, path);
1388
1571
  } catch {
1389
1572
  discard(tmp);
1390
1573
  }
1391
1574
  }
1392
1575
  function mtimeMs(path) {
1393
1576
  try {
1394
- return (0, import_node_fs11.statSync)(path).mtimeMs;
1577
+ return (0, import_node_fs12.statSync)(path).mtimeMs;
1395
1578
  } catch {
1396
1579
  return 0;
1397
1580
  }
@@ -1400,13 +1583,13 @@ function pruneQuarantine(root, keep) {
1400
1583
  const dir = (0, import_node_path9.join)(root, "quarantine");
1401
1584
  let generations;
1402
1585
  try {
1403
- generations = (0, import_node_fs11.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));
1586
+ 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));
1404
1587
  } catch {
1405
1588
  return 0;
1406
1589
  }
1407
1590
  let stranded = 0;
1408
1591
  try {
1409
- for (const entry of (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true })) {
1592
+ for (const entry of (0, import_node_fs12.readdirSync)(dir, { withFileTypes: true })) {
1410
1593
  if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
1411
1594
  const path = (0, import_node_path9.join)(dir, entry.name);
1412
1595
  if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
@@ -1426,16 +1609,16 @@ function pruneQuarantine(root, keep) {
1426
1609
  }
1427
1610
  function restorePrevious(live, quarantined, broken) {
1428
1611
  try {
1429
- (0, import_node_fs11.renameSync)(live, broken);
1612
+ (0, import_node_fs12.renameSync)(live, broken);
1430
1613
  } catch {
1431
1614
  return "kept-broken";
1432
1615
  }
1433
1616
  try {
1434
- (0, import_node_fs11.renameSync)(quarantined, live);
1617
+ (0, import_node_fs12.renameSync)(quarantined, live);
1435
1618
  return "restored";
1436
1619
  } catch {
1437
1620
  try {
1438
- (0, import_node_fs11.renameSync)(broken, live);
1621
+ (0, import_node_fs12.renameSync)(broken, live);
1439
1622
  return "kept-broken";
1440
1623
  } catch {
1441
1624
  return "lost";
@@ -1467,9 +1650,9 @@ function cursorArm(options) {
1467
1650
  return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
1468
1651
  }
1469
1652
  if (installed === target && treeHealthy2(live)) {
1470
- return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
1653
+ return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
1471
1654
  }
1472
- if ((0, import_node_fs11.existsSync)(live) && !mmiOwned(live)) {
1655
+ if ((0, import_node_fs12.existsSync)(live) && !mmiOwned(live)) {
1473
1656
  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)`);
1474
1657
  }
1475
1658
  if (dryRun) {
@@ -1491,10 +1674,10 @@ function cursorArm(options) {
1491
1674
  const stamp = Date.now();
1492
1675
  const incoming = (0, import_node_path9.join)(root, "staging", `mmi-${safeSegment(target)}-${stamp}`);
1493
1676
  const quarantined = (0, import_node_path9.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
1494
- for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs11.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
1677
+ for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs12.mkdirSync)((0, import_node_path9.join)(root, dir), { recursive: true });
1495
1678
  try {
1496
1679
  discard(incoming);
1497
- (0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
1680
+ (0, import_node_fs12.cpSync)(staged.packageRoot, incoming, { recursive: true });
1498
1681
  } catch (error) {
1499
1682
  discard(incoming);
1500
1683
  return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
@@ -1511,15 +1694,15 @@ function cursorArm(options) {
1511
1694
  }
1512
1695
  let displaced = false;
1513
1696
  try {
1514
- if ((0, import_node_fs11.existsSync)(live)) {
1515
- (0, import_node_fs11.renameSync)(live, quarantined);
1697
+ if ((0, import_node_fs12.existsSync)(live)) {
1698
+ (0, import_node_fs12.renameSync)(live, quarantined);
1516
1699
  displaced = true;
1517
1700
  }
1518
- (0, import_node_fs11.renameSync)(incoming, live);
1701
+ (0, import_node_fs12.renameSync)(incoming, live);
1519
1702
  } catch (error) {
1520
- if (displaced && !(0, import_node_fs11.existsSync)(live)) {
1703
+ if (displaced && !(0, import_node_fs12.existsSync)(live)) {
1521
1704
  try {
1522
- (0, import_node_fs11.renameSync)(quarantined, live);
1705
+ (0, import_node_fs12.renameSync)(quarantined, live);
1523
1706
  } catch {
1524
1707
  }
1525
1708
  }
@@ -1539,11 +1722,11 @@ function cursorArm(options) {
1539
1722
  writeRollbackPointer(root, { installed: target, previous: installed, generation: displaced ? quarantined : null });
1540
1723
  const pruned = pruneQuarantine(root, displaced ? quarantined : null);
1541
1724
  const previous = displaced ? `previous generation quarantined at ${quarantined}` : "no previous generation to quarantine";
1542
- return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
1725
+ 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)())}`);
1543
1726
  }
1544
1727
 
1545
1728
  // src/arm-jervcode.ts
1546
- var import_node_fs12 = require("node:fs");
1729
+ var import_node_fs13 = require("node:fs");
1547
1730
  var import_node_path10 = require("node:path");
1548
1731
  var PI_PACKAGE = "@mutmutco/pi-plugin";
1549
1732
  function hostEnv(env) {
@@ -1555,7 +1738,7 @@ function hostCommands(_env) {
1555
1738
  }
1556
1739
  function readPackageSpecs(env) {
1557
1740
  try {
1558
- const settings = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
1741
+ const settings = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
1559
1742
  return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
1560
1743
  } catch {
1561
1744
  return null;
@@ -1563,7 +1746,7 @@ function readPackageSpecs(env) {
1563
1746
  }
1564
1747
  function installedVersion(env) {
1565
1748
  try {
1566
- const manifest2 = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path10.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
1749
+ 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"));
1567
1750
  return typeof manifest2.version === "string" ? manifest2.version : null;
1568
1751
  } catch {
1569
1752
  return null;
@@ -1646,7 +1829,7 @@ function jervcodeArm(options) {
1646
1829
  }
1647
1830
 
1648
1831
  // src/arm-hermes.ts
1649
- var import_node_fs13 = require("node:fs");
1832
+ var import_node_fs14 = require("node:fs");
1650
1833
  var import_node_child_process9 = require("node:child_process");
1651
1834
  var import_node_path11 = require("node:path");
1652
1835
  var PACKAGE2 = "@mutmutco/hermes-plugin";
@@ -1654,7 +1837,7 @@ var MANIFEST2 = "plugin.yaml";
1654
1837
  var TREE_MARKERS2 = [MANIFEST2, "__init__.py", "skills"];
1655
1838
  function yamlVersion(root) {
1656
1839
  try {
1657
- const text = (0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
1840
+ const text = (0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, MANIFEST2), "utf8");
1658
1841
  try {
1659
1842
  const version = JSON.parse(text).version;
1660
1843
  if (typeof version === "string" && version.trim()) return version.trim();
@@ -1667,9 +1850,9 @@ function yamlVersion(root) {
1667
1850
  }
1668
1851
  }
1669
1852
  function treeHealthy3(root) {
1670
- return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs13.existsSync)((0, import_node_path11.join)(root, file))) && (() => {
1853
+ return TREE_MARKERS2.slice(0, 2).every((file) => (0, import_node_fs14.existsSync)((0, import_node_path11.join)(root, file))) && (() => {
1671
1854
  try {
1672
- return (0, import_node_fs13.statSync)((0, import_node_path11.join)(root, "skills")).isDirectory();
1855
+ return (0, import_node_fs14.statSync)((0, import_node_path11.join)(root, "skills")).isDirectory();
1673
1856
  } catch {
1674
1857
  return false;
1675
1858
  }
@@ -1677,7 +1860,7 @@ function treeHealthy3(root) {
1677
1860
  }
1678
1861
  function packageIdentity(root) {
1679
1862
  try {
1680
- return JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
1863
+ return JSON.parse((0, import_node_fs14.readFileSync)((0, import_node_path11.join)(root, "package.json"), "utf8")).name === PACKAGE2;
1681
1864
  } catch {
1682
1865
  return false;
1683
1866
  }
@@ -1692,7 +1875,7 @@ function mmiOwned2(root) {
1692
1875
  }
1693
1876
  function discard2(path) {
1694
1877
  try {
1695
- (0, import_node_fs13.rmSync)(path, { recursive: true, force: true });
1878
+ (0, import_node_fs14.rmSync)(path, { recursive: true, force: true });
1696
1879
  } catch {
1697
1880
  }
1698
1881
  }
@@ -1708,10 +1891,14 @@ function hermesProcessToken(line, home) {
1708
1891
  }
1709
1892
  return null;
1710
1893
  }
1711
- function hermesHostEvidence(env, home) {
1894
+ function realProcessListing() {
1712
1895
  const result = process.platform === "win32" ? (0, import_node_child_process9.spawnSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.Name + ' ' + $_.CommandLine }"], { encoding: "utf8", windowsHide: true, timeout: 15e3 }) : (0, import_node_child_process9.spawnSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
1713
- if (result.status !== 0 || !(result.stdout ?? "").trim()) return "the process list is unreadable (unsafe to write)";
1714
- for (const line of result.stdout.split(/\r?\n/)) {
1896
+ return result.status === 0 ? result.stdout ?? "" : null;
1897
+ }
1898
+ function hermesHostEvidence(env, home, processListProvider) {
1899
+ const listing = processListProvider ? processListProvider() : realProcessListing();
1900
+ if (listing === null || !listing.trim()) return "the process list is unreadable (unsafe to write)";
1901
+ for (const line of listing.split(/\r?\n/)) {
1715
1902
  const token = hermesProcessToken(line, home);
1716
1903
  if (token) return `a live process names hermes (${token})`;
1717
1904
  }
@@ -1722,15 +1909,15 @@ function materializeHermesSkills(home) {
1722
1909
  const skillsRoot = (0, import_node_path11.join)(home, "skills");
1723
1910
  const targetRoot = (0, import_node_path11.join)(skillsRoot, "mmi");
1724
1911
  try {
1725
- if (!(0, import_node_fs13.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
1726
- const names = (0, import_node_fs13.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
1912
+ if (!(0, import_node_fs14.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
1913
+ const names = (0, import_node_fs14.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
1727
1914
  if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
1728
- (0, import_node_fs13.mkdirSync)(skillsRoot, { recursive: true });
1915
+ (0, import_node_fs14.mkdirSync)(skillsRoot, { recursive: true });
1729
1916
  const incoming = (0, import_node_path11.join)(skillsRoot, ".mmi-incoming");
1730
1917
  discard2(incoming);
1731
- (0, import_node_fs13.cpSync)(pluginSkills, incoming, { recursive: true });
1918
+ (0, import_node_fs14.cpSync)(pluginSkills, incoming, { recursive: true });
1732
1919
  discard2(targetRoot);
1733
- (0, import_node_fs13.renameSync)(incoming, targetRoot);
1920
+ (0, import_node_fs14.renameSync)(incoming, targetRoot);
1734
1921
  return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
1735
1922
  } catch (error) {
1736
1923
  return { ok: false, detail: message2(error) };
@@ -1749,8 +1936,8 @@ function hermesArm(options) {
1749
1936
  verdict,
1750
1937
  detail
1751
1938
  });
1752
- if (!(0, import_node_fs13.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
1753
- if ((0, import_node_fs13.existsSync)(live) && !mmiOwned2(live)) {
1939
+ if (!(0, import_node_fs14.existsSync)(home)) return result(null, options.target, "skip", `absent (${home}) \u2014 skipped without writes`);
1940
+ if ((0, import_node_fs14.existsSync)(live) && !mmiOwned2(live)) {
1754
1941
  return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
1755
1942
  }
1756
1943
  if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
@@ -1772,10 +1959,10 @@ function hermesArm(options) {
1772
1959
  const incoming = (0, import_node_path11.join)(incomingRoot, options.target);
1773
1960
  const quarantine = (0, import_node_path11.join)(scratch, "quarantine", `${installed ?? "unknown"}-${Date.now()}`);
1774
1961
  try {
1775
- (0, import_node_fs13.mkdirSync)(parent, { recursive: true });
1776
- (0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
1962
+ (0, import_node_fs14.mkdirSync)(parent, { recursive: true });
1963
+ (0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "quarantine"), { recursive: true });
1777
1964
  discard2(incomingRoot);
1778
- (0, import_node_fs13.cpSync)(staged.packageRoot, incoming, { recursive: true });
1965
+ (0, import_node_fs14.cpSync)(staged.packageRoot, incoming, { recursive: true });
1779
1966
  } catch (error) {
1780
1967
  return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
1781
1968
  }
@@ -1783,19 +1970,19 @@ function hermesArm(options) {
1783
1970
  discard2(incomingRoot);
1784
1971
  return result(installed, options.target, "defer", "incoming Hermes generation did not reproduce the verified payload");
1785
1972
  }
1786
- const busy = hermesHostEvidence(env, home);
1973
+ const busy = hermesHostEvidence(env, home, options.processListProvider);
1787
1974
  if (busy) {
1788
1975
  discard2(incomingRoot);
1789
1976
  const pending = noteBusyDefer(options.paths, env, { surface: "hermes", target: options.target, reason: busy, images: ["hermes"], dirs: [home] });
1790
1977
  return result(installed, options.target, "defer", `Hermes may be running \u2014 the plugin tree is only swapped while the host is absent: ${busy} \u2014 ${pending}`);
1791
1978
  }
1792
- const displaced = (0, import_node_fs13.existsSync)(live);
1979
+ const displaced = (0, import_node_fs14.existsSync)(live);
1793
1980
  try {
1794
- if (displaced) (0, import_node_fs13.renameSync)(live, quarantine);
1795
- (0, import_node_fs13.renameSync)(incoming, live);
1981
+ if (displaced) (0, import_node_fs14.renameSync)(live, quarantine);
1982
+ (0, import_node_fs14.renameSync)(incoming, live);
1796
1983
  } catch (error) {
1797
- if (displaced && !(0, import_node_fs13.existsSync)(live)) try {
1798
- (0, import_node_fs13.renameSync)(quarantine, live);
1984
+ if (displaced && !(0, import_node_fs14.existsSync)(live)) try {
1985
+ (0, import_node_fs14.renameSync)(quarantine, live);
1799
1986
  } catch {
1800
1987
  }
1801
1988
  discard2(incomingRoot);
@@ -1806,9 +1993,9 @@ function hermesArm(options) {
1806
1993
  if (yamlVersion(live) !== options.target || !treeHealthy3(live)) {
1807
1994
  const broken = (0, import_node_path11.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
1808
1995
  try {
1809
- (0, import_node_fs13.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
1810
- (0, import_node_fs13.renameSync)(live, broken);
1811
- if (displaced) (0, import_node_fs13.renameSync)(quarantine, live);
1996
+ (0, import_node_fs14.mkdirSync)((0, import_node_path11.join)(scratch, "rejected"), { recursive: true });
1997
+ (0, import_node_fs14.renameSync)(live, broken);
1998
+ if (displaced) (0, import_node_fs14.renameSync)(quarantine, live);
1812
1999
  } catch (error) {
1813
2000
  return result(installed, yamlVersion(live) ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
1814
2001
  }
@@ -1822,7 +2009,7 @@ function hermesArm(options) {
1822
2009
 
1823
2010
  // src/reap.ts
1824
2011
  var import_node_child_process10 = require("node:child_process");
1825
- var import_node_fs14 = require("node:fs");
2012
+ var import_node_fs15 = require("node:fs");
1826
2013
  var import_node_path12 = require("node:path");
1827
2014
  var KEEP_GENERATIONS2 = 2;
1828
2015
  var SCRATCH_AGE_MS = 24 * 60 * 6e4;
@@ -1831,7 +2018,7 @@ var SNAPSHOT_TIMEOUT_MS = 2e4;
1831
2018
  var MAX_UNWIND_ROUNDS = 4;
1832
2019
  function listDirs(path) {
1833
2020
  try {
1834
- return (0, import_node_fs14.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
2021
+ return (0, import_node_fs15.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1835
2022
  } catch {
1836
2023
  return [];
1837
2024
  }
@@ -1852,7 +2039,7 @@ function pruneGenerations(root, category, dryRun) {
1852
2039
  continue;
1853
2040
  }
1854
2041
  try {
1855
- (0, import_node_fs14.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
2042
+ (0, import_node_fs15.rmSync)((0, import_node_path12.join)(set, version), { recursive: true, force: true });
1856
2043
  reaped += 1;
1857
2044
  } catch (error) {
1858
2045
  failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
@@ -1885,7 +2072,7 @@ function reapScratch(context, dryRun) {
1885
2072
  for (const [dir, patterns] of scanned) {
1886
2073
  let entries;
1887
2074
  try {
1888
- entries = (0, import_node_fs14.readdirSync)(dir, { withFileTypes: true });
2075
+ entries = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true });
1889
2076
  } catch {
1890
2077
  unreadable.push(dir);
1891
2078
  continue;
@@ -1896,7 +2083,7 @@ function reapScratch(context, dryRun) {
1896
2083
  const path = (0, import_node_path12.join)(dir, entry.name);
1897
2084
  let ageMs;
1898
2085
  try {
1899
- ageMs = now - (0, import_node_fs14.statSync)(path).mtimeMs;
2086
+ ageMs = now - (0, import_node_fs15.statSync)(path).mtimeMs;
1900
2087
  } catch {
1901
2088
  continue;
1902
2089
  }
@@ -1906,7 +2093,7 @@ function reapScratch(context, dryRun) {
1906
2093
  continue;
1907
2094
  }
1908
2095
  try {
1909
- (0, import_node_fs14.rmSync)(path, { force: true });
2096
+ (0, import_node_fs15.rmSync)(path, { force: true });
1910
2097
  reaped += 1;
1911
2098
  } catch (error) {
1912
2099
  failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
@@ -2183,10 +2370,7 @@ function reconcile(options) {
2183
2370
  continue;
2184
2371
  }
2185
2372
  const atomicity = checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
2186
- distIntegrity: (spec) => {
2187
- const integrity = npmView(spec, "dist.integrity", env);
2188
- return typeof integrity === "string" ? integrity : null;
2189
- }
2373
+ distIntegrity: (spec) => npmDistIntegrity(spec, env)
2190
2374
  });
2191
2375
  if (!atomicity.ok) {
2192
2376
  journal({ kind: "candidate-reject", to: candidate, verdict: "skip", detail: `atomicity: ${atomicity.rows.filter((row) => !row.ok).map((row) => row.detail).join("; ")}` });
@@ -2254,8 +2438,8 @@ function reconcile(options) {
2254
2438
 
2255
2439
  // src/scheduler.ts
2256
2440
  var import_node_child_process11 = require("node:child_process");
2257
- var import_node_fs15 = require("node:fs");
2258
- var import_node_os5 = require("node:os");
2441
+ var import_node_fs16 = require("node:fs");
2442
+ var import_node_os6 = require("node:os");
2259
2443
  var import_node_path13 = require("node:path");
2260
2444
  var TASK_NAME = "MMI Fleet Updater";
2261
2445
  var LAUNCHER_FILE = "reconcile-hidden.vbs";
@@ -2414,7 +2598,7 @@ WantedBy=timers.target
2414
2598
  }
2415
2599
  function readFileOrNull(path) {
2416
2600
  try {
2417
- return (0, import_node_fs15.readFileSync)(path, "utf8");
2601
+ return (0, import_node_fs16.readFileSync)(path, "utf8");
2418
2602
  } catch {
2419
2603
  return null;
2420
2604
  }
@@ -2488,7 +2672,7 @@ function windowsSchedulerStatus(env) {
2488
2672
  return { ok: true, supported: true, enabled: true, ...runInfo, detail: `"${TASK_NAME}" registered ${CADENCE}; ${points}; ${ran}; next ${runInfo.nextRunTime ?? "unscheduled"}` };
2489
2673
  }
2490
2674
  function darwinSchedulerStatus(env) {
2491
- const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2675
+ const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2492
2676
  const plist = readFileOrNull(plistPath);
2493
2677
  if (!plist) {
2494
2678
  return { ok: false, supported: true, enabled: false, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" is not registered \u2014 run \`mmi-hub autoupdate on\`` };
@@ -2514,7 +2698,7 @@ function darwinSchedulerStatus(env) {
2514
2698
  return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
2515
2699
  }
2516
2700
  function linuxSchedulerStatus(env) {
2517
- const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2701
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2518
2702
  const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
2519
2703
  const timer = readFileOrNull(timerPath);
2520
2704
  const service = readFileOrNull((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME));
@@ -2562,9 +2746,9 @@ function installWindowsTask(env) {
2562
2746
  const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
2563
2747
  if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
2564
2748
  const launcherPath = (0, import_node_path13.join)(paths.root, LAUNCHER_FILE);
2565
- (0, import_node_fs15.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
2749
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
2566
2750
  const xmlPath = (0, import_node_path13.join)(paths.root, "task.xml");
2567
- (0, import_node_fs15.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
2751
+ (0, import_node_fs16.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
2568
2752
  const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
2569
2753
  if (create.status !== 0) {
2570
2754
  const denied = /access is denied/i.test(create.stderr + create.stdout);
@@ -2583,8 +2767,8 @@ function refreshPosixLauncher(env) {
2583
2767
  const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2584
2768
  const desired = launcherSh(process.execPath, hubDist);
2585
2769
  if (readFileOrNull(launcherPath) === desired) return null;
2586
- (0, import_node_fs15.writeFileSync)(launcherPath, desired, "utf8");
2587
- (0, import_node_fs15.chmodSync)(launcherPath, 493);
2770
+ (0, import_node_fs16.writeFileSync)(launcherPath, desired, "utf8");
2771
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
2588
2772
  return launcherPath;
2589
2773
  }
2590
2774
  function installLaunchd(env) {
@@ -2597,12 +2781,12 @@ function installLaunchd(env) {
2597
2781
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
2598
2782
  const paths = ensureState(env);
2599
2783
  const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2600
- (0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2601
- (0, import_node_fs15.chmodSync)(launcherPath, 493);
2602
- const agentsDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents");
2603
- (0, import_node_fs15.mkdirSync)(agentsDir, { recursive: true });
2784
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2785
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
2786
+ const agentsDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
2787
+ (0, import_node_fs16.mkdirSync)(agentsDir, { recursive: true });
2604
2788
  const plistPath = (0, import_node_path13.join)(agentsDir, LAUNCHD_PLIST_NAME);
2605
- (0, import_node_fs15.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
2789
+ (0, import_node_fs16.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
2606
2790
  launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
2607
2791
  const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
2608
2792
  if (load.status !== 0) {
@@ -2621,12 +2805,12 @@ function installSystemdTimer(env) {
2621
2805
  if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
2622
2806
  const paths = ensureState(env);
2623
2807
  const launcherPath = (0, import_node_path13.join)(paths.root, POSIX_LAUNCHER_FILE);
2624
- (0, import_node_fs15.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2625
- (0, import_node_fs15.chmodSync)(launcherPath, 493);
2626
- const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2627
- (0, import_node_fs15.mkdirSync)(unitDir, { recursive: true });
2628
- (0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
2629
- (0, import_node_fs15.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
2808
+ (0, import_node_fs16.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
2809
+ (0, import_node_fs16.chmodSync)(launcherPath, 493);
2810
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2811
+ (0, import_node_fs16.mkdirSync)(unitDir, { recursive: true });
2812
+ (0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
2813
+ (0, import_node_fs16.writeFileSync)((0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
2630
2814
  const reload = systemctlUser(["daemon-reload"]);
2631
2815
  if (reload.status !== 0) {
2632
2816
  return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
@@ -2664,28 +2848,28 @@ function uninstallWindowsTask(env) {
2664
2848
  return { ok: true, detail: `automatic updates off \u2014 removed "${TASK_NAME}"; installed tooling was kept` };
2665
2849
  }
2666
2850
  function uninstallLaunchd(env) {
2667
- const plistPath = (0, import_node_path13.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2851
+ const plistPath = (0, import_node_path13.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
2668
2852
  const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
2669
- if (boot.status !== 0 && (0, import_node_fs15.existsSync)(plistPath)) {
2853
+ if (boot.status !== 0 && (0, import_node_fs16.existsSync)(plistPath)) {
2670
2854
  return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
2671
2855
  }
2672
2856
  try {
2673
- (0, import_node_fs15.rmSync)(plistPath, { force: true });
2857
+ (0, import_node_fs16.rmSync)(plistPath, { force: true });
2674
2858
  } catch {
2675
2859
  }
2676
2860
  dropLauncher(env, POSIX_LAUNCHER_FILE);
2677
2861
  return { ok: true, detail: `automatic updates off \u2014 removed "${LAUNCHD_LABEL}"; installed tooling was kept` };
2678
2862
  }
2679
2863
  function uninstallSystemdTimer(env) {
2680
- const unitDir = (0, import_node_path13.join)((0, import_node_os5.homedir)(), ".config", "systemd", "user");
2864
+ const unitDir = (0, import_node_path13.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
2681
2865
  const timerPath = (0, import_node_path13.join)(unitDir, SYSTEMD_TIMER_NAME);
2682
2866
  const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
2683
- if (disable.status !== 0 && (0, import_node_fs15.existsSync)(timerPath)) {
2867
+ if (disable.status !== 0 && (0, import_node_fs16.existsSync)(timerPath)) {
2684
2868
  return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
2685
2869
  }
2686
2870
  try {
2687
- (0, import_node_fs15.rmSync)(timerPath, { force: true });
2688
- (0, import_node_fs15.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
2871
+ (0, import_node_fs16.rmSync)(timerPath, { force: true });
2872
+ (0, import_node_fs16.rmSync)((0, import_node_path13.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
2689
2873
  } catch {
2690
2874
  }
2691
2875
  systemctlUser(["daemon-reload"]);
@@ -2700,16 +2884,16 @@ function uninstallTask(env = process.env) {
2700
2884
  }
2701
2885
  function dropLauncher(env, file) {
2702
2886
  try {
2703
- (0, import_node_fs15.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
2887
+ (0, import_node_fs16.rmSync)((0, import_node_path13.join)(statePaths(env).root, file), { force: true });
2704
2888
  } catch {
2705
2889
  }
2706
2890
  }
2707
2891
 
2708
2892
  // src/status.ts
2709
- var import_node_fs16 = require("node:fs");
2893
+ var import_node_fs17 = require("node:fs");
2710
2894
  function readJournal(path) {
2711
2895
  try {
2712
- const events = (0, import_node_fs16.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
2896
+ const events = (0, import_node_fs17.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
2713
2897
  try {
2714
2898
  return JSON.parse(line);
2715
2899
  } catch {
@@ -3087,7 +3271,7 @@ function startSpinner(enabled) {
3087
3271
  // src/index.ts
3088
3272
  function ownVersion() {
3089
3273
  try {
3090
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
3274
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path14.join)(__dirname, "..", "package.json"), "utf8")).version;
3091
3275
  } catch {
3092
3276
  return "0.0.0";
3093
3277
  }
@@ -3213,6 +3397,7 @@ if (typeof require !== "undefined" && require.main === module) {
3213
3397
  }
3214
3398
  // Annotate the CommonJS export names for ESM import in node:
3215
3399
  0 && (module.exports = {
3400
+ acquireSharedDocLock,
3216
3401
  buildWatcherCommand,
3217
3402
  clearPending,
3218
3403
  enumerateSurfaces,
@@ -3220,6 +3405,7 @@ if (typeof require !== "undefined" && require.main === module) {
3220
3405
  formatReconcileReport,
3221
3406
  hermesArm,
3222
3407
  hermesHome,
3408
+ hermesHostEvidence,
3223
3409
  hubStatus,
3224
3410
  launchdPlist,
3225
3411
  launcherSh,
@@ -3228,23 +3414,31 @@ if (typeof require !== "undefined" && require.main === module) {
3228
3414
  lockShaped,
3229
3415
  main,
3230
3416
  noteBusyDefer,
3417
+ npmStringField,
3231
3418
  ownVersion,
3419
+ parseNpmDistIntegrity,
3232
3420
  pendingPath,
3233
3421
  pidAlive,
3234
3422
  probeHermesInstallation,
3235
3423
  progressiveRow,
3236
3424
  readPending,
3237
3425
  reconcileCursorUserHooks,
3426
+ reconcileKimiInstalledPlugin,
3427
+ releaseSharedDocLock,
3238
3428
  reportFooterLines,
3239
3429
  retryHint,
3240
3430
  schedulePendingWatcher,
3241
3431
  schedulerStatus,
3432
+ sharedDocLockPath,
3433
+ sharedDocLockTimeoutReceipt,
3242
3434
  startSpinner,
3243
3435
  systemdServiceUnit,
3244
3436
  systemdTimerUnit,
3245
3437
  taskXml,
3246
3438
  updateHeader,
3439
+ validateKimiInstalledPlugin,
3247
3440
  watcherLockPath,
3441
+ withSharedDocLock,
3248
3442
  wrapWords,
3249
3443
  writePending
3250
3444
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/hub",
3
- "version": "4.1.3",
3
+ "version": "4.1.4",
4
4
  "description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",