@mutmutco/hub 4.0.11 → 4.0.12
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 +84 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
ownVersion: () => ownVersion,
|
|
36
36
|
probeHermesInstallation: () => probeHermesInstallation,
|
|
37
37
|
progressiveRow: () => progressiveRow,
|
|
38
|
+
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
38
39
|
reportFooterLines: () => reportFooterLines,
|
|
39
40
|
retryHint: () => retryHint,
|
|
40
41
|
schedulerStatus: () => schedulerStatus,
|
|
@@ -1038,6 +1039,8 @@ var import_node_os4 = require("node:os");
|
|
|
1038
1039
|
var import_node_path8 = require("node:path");
|
|
1039
1040
|
var PACKAGE = "@mutmutco/cursor-plugin";
|
|
1040
1041
|
var MANIFEST = ".cursor-plugin/plugin.json";
|
|
1042
|
+
var USER_HOOKS_FILE = "hooks.json";
|
|
1043
|
+
var PLUGIN_HOOKS_REL = ["hooks", "cursor-hooks.json"];
|
|
1041
1044
|
var KEEP_GENERATIONS = 2;
|
|
1042
1045
|
var STRANDED_TMP_AGE_MS = 24 * 60 * 6e4;
|
|
1043
1046
|
var TREE_FILES = [MANIFEST, "skills/mmi/SKILL.md", "hooks/cursor-hooks.json", "scripts/hook-run.mjs", "scripts/hook-policy.mjs"];
|
|
@@ -1100,6 +1103,84 @@ function cursorHostEvidence(env) {
|
|
|
1100
1103
|
}
|
|
1101
1104
|
return null;
|
|
1102
1105
|
}
|
|
1106
|
+
function resolvedPluginRoot(root) {
|
|
1107
|
+
return (0, import_node_path8.join)(root).replace(/\\/g, "/");
|
|
1108
|
+
}
|
|
1109
|
+
function isMmiEntry(entry, pluginRoot) {
|
|
1110
|
+
return typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(resolvedPluginRoot(pluginRoot));
|
|
1111
|
+
}
|
|
1112
|
+
function resolveCommand(command, pluginRoot) {
|
|
1113
|
+
return command.replaceAll("${CURSOR_PLUGIN_ROOT}", resolvedPluginRoot(pluginRoot));
|
|
1114
|
+
}
|
|
1115
|
+
function desiredUserHooks(pluginRoot) {
|
|
1116
|
+
try {
|
|
1117
|
+
const doc = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path8.join)(pluginRoot, ...PLUGIN_HOOKS_REL), "utf8"));
|
|
1118
|
+
const hooks = doc.hooks;
|
|
1119
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1120
|
+
const desired = /* @__PURE__ */ new Map();
|
|
1121
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
1122
|
+
if (!Array.isArray(entries)) return null;
|
|
1123
|
+
desired.set(event, entries.map((entry) => {
|
|
1124
|
+
const command = typeof entry.command === "string" ? entry.command : "";
|
|
1125
|
+
return { ...entry, command: resolveCommand(command, pluginRoot) };
|
|
1126
|
+
}));
|
|
1127
|
+
}
|
|
1128
|
+
return desired;
|
|
1129
|
+
} catch {
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
function mergeUserHooks(existing, desired, pluginRoot) {
|
|
1134
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) return null;
|
|
1135
|
+
const merged = { ...existing };
|
|
1136
|
+
const hooks = merged.hooks ?? {};
|
|
1137
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return null;
|
|
1138
|
+
const nextHooks = {};
|
|
1139
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
1140
|
+
if (!Array.isArray(entries)) return null;
|
|
1141
|
+
const kept = entries.filter((entry) => !isMmiEntry(entry, pluginRoot));
|
|
1142
|
+
nextHooks[event] = [...kept, ...desired.get(event) ?? []];
|
|
1143
|
+
}
|
|
1144
|
+
for (const [event, entries] of desired) {
|
|
1145
|
+
if (!(event in hooks)) nextHooks[event] = entries;
|
|
1146
|
+
}
|
|
1147
|
+
merged.hooks = nextHooks;
|
|
1148
|
+
return merged;
|
|
1149
|
+
}
|
|
1150
|
+
function reconcileCursorUserHooks(pluginRoot, homeDir) {
|
|
1151
|
+
const desired = desiredUserHooks(pluginRoot);
|
|
1152
|
+
if (!desired) return "source-unreadable";
|
|
1153
|
+
const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1154
|
+
if (!(0, import_node_fs10.existsSync)(path)) {
|
|
1155
|
+
(0, import_node_fs10.writeFileSync)(path, JSON.stringify({ version: 1, hooks: Object.fromEntries(desired) }, null, 2) + "\n", "utf8");
|
|
1156
|
+
return "updated";
|
|
1157
|
+
}
|
|
1158
|
+
let existing;
|
|
1159
|
+
try {
|
|
1160
|
+
existing = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
1161
|
+
} catch {
|
|
1162
|
+
return "user-conflict";
|
|
1163
|
+
}
|
|
1164
|
+
const merged = mergeUserHooks(existing, desired, pluginRoot);
|
|
1165
|
+
if (!merged) return "user-conflict";
|
|
1166
|
+
if (JSON.stringify(merged) === JSON.stringify(existing)) return "current";
|
|
1167
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
1168
|
+
(0, import_node_fs10.writeFileSync)(tmp, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
1169
|
+
(0, import_node_fs10.renameSync)(tmp, path);
|
|
1170
|
+
return "updated";
|
|
1171
|
+
}
|
|
1172
|
+
function userHooksDetail(pluginRoot, homeDir) {
|
|
1173
|
+
switch (reconcileCursorUserHooks(pluginRoot, homeDir)) {
|
|
1174
|
+
case "updated":
|
|
1175
|
+
return "; user hooks bridged into ~/.cursor/hooks.json";
|
|
1176
|
+
case "user-conflict":
|
|
1177
|
+
return `; USER HOOKS CONFLICT at ${(0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE)} \u2014 Cursor gates are NOT bridged; fix or remove that file and reconcile`;
|
|
1178
|
+
case "source-unreadable":
|
|
1179
|
+
return "; user hooks source unreadable \u2014 Cursor gates are NOT bridged";
|
|
1180
|
+
default:
|
|
1181
|
+
return "";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1103
1184
|
function writeRollbackPointer(root, entry) {
|
|
1104
1185
|
const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
|
|
1105
1186
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
@@ -1188,7 +1269,7 @@ function cursorArm(options) {
|
|
|
1188
1269
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1189
1270
|
}
|
|
1190
1271
|
if (installed === target && treeHealthy2(live)) {
|
|
1191
|
-
return result(installed, target, "ok", `already at target ${target}`);
|
|
1272
|
+
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os4.homedir)())}`);
|
|
1192
1273
|
}
|
|
1193
1274
|
if ((0, import_node_fs10.existsSync)(live) && !mmiOwned(live)) {
|
|
1194
1275
|
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)`);
|
|
@@ -1256,7 +1337,7 @@ function cursorArm(options) {
|
|
|
1256
1337
|
writeRollbackPointer(root, { installed: target, previous: installed, generation: displaced ? quarantined : null });
|
|
1257
1338
|
const pruned = pruneQuarantine(root, displaced ? quarantined : null);
|
|
1258
1339
|
const previous = displaced ? `previous generation quarantined at ${quarantined}` : "no previous generation to quarantine";
|
|
1259
|
-
return result(installed, target, "ok", `switched ${installed ?? "absent"} -> ${target}; ${previous}${pruned ? `; pruned ${pruned} older generation${pruned === 1 ? "" : "s"}` : ""}`);
|
|
1340
|
+
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)())}`);
|
|
1260
1341
|
}
|
|
1261
1342
|
|
|
1262
1343
|
// src/arm-jervcode.ts
|
|
@@ -2909,6 +2990,7 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
2909
2990
|
ownVersion,
|
|
2910
2991
|
probeHermesInstallation,
|
|
2911
2992
|
progressiveRow,
|
|
2993
|
+
reconcileCursorUserHooks,
|
|
2912
2994
|
reportFooterLines,
|
|
2913
2995
|
retryHint,
|
|
2914
2996
|
schedulerStatus,
|
package/package.json
CHANGED