@mutmutco/hub 4.1.2 → 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.
- package/dist/index.cjs +378 -184
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
839
|
-
if (!
|
|
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
|
-
|
|
827
|
+
marketplaceTrust = ensureClaudeMarketplace(env);
|
|
843
828
|
}
|
|
844
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
1231
|
+
for (const name of (0, import_node_fs11.readdirSync)(root)) {
|
|
1078
1232
|
if (name === keep) continue;
|
|
1079
1233
|
try {
|
|
1080
|
-
(0,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
1146
|
-
(0,
|
|
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,
|
|
1175
|
-
(0,
|
|
1176
|
-
(0,
|
|
1177
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1213
|
-
(0,
|
|
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,
|
|
1398
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1228
1399
|
} catch {
|
|
1229
1400
|
}
|
|
1230
|
-
|
|
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
|
|
1406
|
+
var import_node_fs12 = require("node:fs");
|
|
1235
1407
|
var import_node_child_process8 = require("node:child_process");
|
|
1236
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
1353
|
-
(0,
|
|
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
|
-
|
|
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,
|
|
1387
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1612
|
+
(0, import_node_fs12.renameSync)(live, broken);
|
|
1430
1613
|
} catch {
|
|
1431
1614
|
return "kept-broken";
|
|
1432
1615
|
}
|
|
1433
1616
|
try {
|
|
1434
|
-
(0,
|
|
1617
|
+
(0, import_node_fs12.renameSync)(quarantined, live);
|
|
1435
1618
|
return "restored";
|
|
1436
1619
|
} catch {
|
|
1437
1620
|
try {
|
|
1438
|
-
(0,
|
|
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,
|
|
1653
|
+
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1471
1654
|
}
|
|
1472
|
-
if ((0,
|
|
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,
|
|
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,
|
|
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,
|
|
1515
|
-
(0,
|
|
1697
|
+
if ((0, import_node_fs12.existsSync)(live)) {
|
|
1698
|
+
(0, import_node_fs12.renameSync)(live, quarantined);
|
|
1516
1699
|
displaced = true;
|
|
1517
1700
|
}
|
|
1518
|
-
(0,
|
|
1701
|
+
(0, import_node_fs12.renameSync)(incoming, live);
|
|
1519
1702
|
} catch (error) {
|
|
1520
|
-
if (displaced && !(0,
|
|
1703
|
+
if (displaced && !(0, import_node_fs12.existsSync)(live)) {
|
|
1521
1704
|
try {
|
|
1522
|
-
(0,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
1714
|
-
|
|
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,
|
|
1726
|
-
const names = (0,
|
|
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,
|
|
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,
|
|
1918
|
+
(0, import_node_fs14.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
1732
1919
|
discard2(targetRoot);
|
|
1733
|
-
(0,
|
|
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,
|
|
1753
|
-
if ((0,
|
|
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,
|
|
1776
|
-
(0,
|
|
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,
|
|
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,
|
|
1979
|
+
const displaced = (0, import_node_fs14.existsSync)(live);
|
|
1793
1980
|
try {
|
|
1794
|
-
if (displaced) (0,
|
|
1795
|
-
(0,
|
|
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,
|
|
1798
|
-
(0,
|
|
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,
|
|
1810
|
-
(0,
|
|
1811
|
-
if (displaced) (0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
2258
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2587
|
-
(0,
|
|
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,
|
|
2601
|
-
(0,
|
|
2602
|
-
const agentsDir = (0, import_node_path13.join)((0,
|
|
2603
|
-
(0,
|
|
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,
|
|
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,
|
|
2625
|
-
(0,
|
|
2626
|
-
const unitDir = (0, import_node_path13.join)((0,
|
|
2627
|
-
(0,
|
|
2628
|
-
(0,
|
|
2629
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
2688
|
-
(0,
|
|
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,
|
|
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
|
|
2893
|
+
var import_node_fs17 = require("node:fs");
|
|
2710
2894
|
function readJournal(path) {
|
|
2711
2895
|
try {
|
|
2712
|
-
const events = (0,
|
|
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,
|
|
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