@mutmutco/hub 4.4.9 → 4.4.11
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 +949 -701
- package/package.json +1 -4
package/dist/index.cjs
CHANGED
|
@@ -21,27 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
-
ACCENT: () => ACCENT,
|
|
25
|
-
FACE_PRODUCT: () => FACE_PRODUCT,
|
|
26
|
-
MM_GREEN: () => MM_GREEN,
|
|
27
|
-
RESET: () => RESET,
|
|
28
|
-
accent: () => accent,
|
|
29
24
|
acquireSharedDocLock: () => acquireSharedDocLock,
|
|
30
|
-
|
|
31
|
-
autoupdateLine: () => autoupdateLine,
|
|
32
|
-
brandLine: () => brandLine,
|
|
25
|
+
assertFaceConformance: () => assertFaceConformance,
|
|
33
26
|
buildWatcherCommand: () => buildWatcherCommand,
|
|
34
27
|
checkAtomicity: () => checkAtomicity,
|
|
35
|
-
checklistLine: () => checklistLine,
|
|
36
28
|
claudeArm: () => claudeArm,
|
|
37
29
|
claudeHostEvidence: () => claudeHostEvidence,
|
|
38
30
|
clearLaunchTarget: () => clearLaunchTarget,
|
|
39
31
|
clearPending: () => clearPending,
|
|
32
|
+
createInstallerRun: () => createInstallerRun,
|
|
40
33
|
enumerateSurfaces: () => enumerateSurfaces,
|
|
41
34
|
failConsequence: () => failConsequence,
|
|
42
35
|
fleetStamp: () => fleetStamp,
|
|
43
|
-
formatReconcileReport: () => formatReconcileReport,
|
|
44
|
-
heldOutput: () => heldOutput,
|
|
45
36
|
hermesArm: () => hermesArm,
|
|
46
37
|
hermesHome: () => hermesHome,
|
|
47
38
|
hermesHomes: () => hermesHomes,
|
|
@@ -68,17 +59,15 @@ __export(index_exports, {
|
|
|
68
59
|
probeHermesLaunchStaging: () => probeHermesLaunchStaging,
|
|
69
60
|
probeJervcodeInstallation: () => probeJervcodeInstallation,
|
|
70
61
|
probeLaunchTargetVersion: () => probeLaunchTargetVersion,
|
|
71
|
-
progressiveRow: () => progressiveRow,
|
|
72
62
|
readLaunchTarget: () => readLaunchTarget,
|
|
73
63
|
readPending: () => readPending,
|
|
74
|
-
receiptText: () => receiptText,
|
|
75
64
|
reconcile: () => reconcile,
|
|
76
65
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
77
66
|
releaseSharedDocLock: () => releaseSharedDocLock,
|
|
78
67
|
reportCounts: () => reportCounts,
|
|
79
|
-
reportFooterLines: () => reportFooterLines,
|
|
80
68
|
retiredSurfaces: () => retiredSurfaces,
|
|
81
69
|
retryHint: () => retryHint,
|
|
70
|
+
runMaintenance: () => runMaintenance2,
|
|
82
71
|
schedulePendingWatcher: () => schedulePendingWatcher,
|
|
83
72
|
schedulerStatus: () => schedulerStatus,
|
|
84
73
|
sharedDocLockPath: () => sharedDocLockPath,
|
|
@@ -89,40 +78,48 @@ __export(index_exports, {
|
|
|
89
78
|
systemdServiceUnit: () => systemdServiceUnit,
|
|
90
79
|
systemdTimerUnit: () => systemdTimerUnit,
|
|
91
80
|
taskXml: () => taskXml,
|
|
92
|
-
updateHeader: () => updateHeader,
|
|
93
|
-
updaterFace: () => updaterFace,
|
|
94
81
|
watcherLockPath: () => watcherLockPath,
|
|
95
82
|
withSharedDocLock: () => withSharedDocLock,
|
|
96
|
-
wrapWords: () => wrapWords2,
|
|
97
83
|
writeLaunchTarget: () => writeLaunchTarget,
|
|
98
84
|
writePending: () => writePending
|
|
99
85
|
});
|
|
100
86
|
module.exports = __toCommonJS(index_exports);
|
|
101
87
|
var import_node_child_process9 = require("node:child_process");
|
|
102
|
-
var
|
|
88
|
+
var import_node_fs22 = require("node:fs");
|
|
103
89
|
var import_node_path13 = require("node:path");
|
|
104
90
|
|
|
105
|
-
//
|
|
91
|
+
// ../installer/face/src/face.ts
|
|
92
|
+
var import_node_fs = require("node:fs");
|
|
93
|
+
|
|
94
|
+
// ../installer/face/src/products.ts
|
|
106
95
|
var PRODUCTS = Object.freeze({
|
|
107
96
|
"mm-strategy": Object.freeze({
|
|
108
97
|
name: "MM Strategy",
|
|
98
|
+
installWarm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
|
|
109
99
|
accent: "38;2;249;115;22",
|
|
110
|
-
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute."
|
|
100
|
+
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute.",
|
|
101
|
+
doctor: "mm-strategy doctor"
|
|
111
102
|
}),
|
|
112
103
|
"mmi-hub": Object.freeze({
|
|
113
104
|
name: "mmi-hub",
|
|
105
|
+
installWarm: "Welcome. Setting up mmi-hub \u2014 about a minute.",
|
|
114
106
|
accent: "38;2;125;211;252",
|
|
115
|
-
warm: "Welcome back. Checking your surfaces\u2026"
|
|
107
|
+
warm: "Welcome back. Checking your surfaces\u2026",
|
|
108
|
+
doctor: "mmi doctor"
|
|
116
109
|
}),
|
|
117
110
|
"jerv-hub": Object.freeze({
|
|
118
111
|
name: "jerv-hub",
|
|
112
|
+
installWarm: "Welcome. Setting up jerv-hub \u2014 about a minute.",
|
|
119
113
|
accent: "38;2;248;113;113",
|
|
120
|
-
warm: "Welcome back. Checking your surfaces\u2026"
|
|
114
|
+
warm: "Welcome back. Checking your surfaces\u2026",
|
|
115
|
+
doctor: "jerv doctor"
|
|
121
116
|
}),
|
|
122
117
|
jervcode: Object.freeze({
|
|
123
118
|
name: "JervCode",
|
|
119
|
+
installWarm: "Welcome. Setting up JervCode \u2014 about a minute.",
|
|
124
120
|
accent: "38;2;192;132;252",
|
|
125
|
-
warm: "Welcome back. Keeping JervCode current\u2026"
|
|
121
|
+
warm: "Welcome back. Keeping JervCode current\u2026",
|
|
122
|
+
doctor: "jervcode doctor"
|
|
126
123
|
})
|
|
127
124
|
});
|
|
128
125
|
function identityFor(product) {
|
|
@@ -132,6 +129,8 @@ function identityFor(product) {
|
|
|
132
129
|
}
|
|
133
130
|
return identity;
|
|
134
131
|
}
|
|
132
|
+
|
|
133
|
+
// ../installer/face/src/face.ts
|
|
135
134
|
var GLYPH = Object.freeze({
|
|
136
135
|
diamond: "\u25C6",
|
|
137
136
|
hollow: "\u25C7",
|
|
@@ -156,6 +155,9 @@ var ANSI = /\u001b\[[0-9;]*m/g;
|
|
|
156
155
|
function visibleWidth(text) {
|
|
157
156
|
return [...String(text).replace(ANSI, "")].length;
|
|
158
157
|
}
|
|
158
|
+
function stripColor(text) {
|
|
159
|
+
return String(text).replace(ANSI, "");
|
|
160
|
+
}
|
|
159
161
|
function faceWidth(columns) {
|
|
160
162
|
const raw = Number(columns);
|
|
161
163
|
return Math.max(40, Math.min(100, Number.isFinite(raw) && raw > 0 ? raw : 100));
|
|
@@ -184,35 +186,64 @@ function wrapWords(text, width) {
|
|
|
184
186
|
if (line) lines.push(line);
|
|
185
187
|
return lines.length ? lines : [""];
|
|
186
188
|
}
|
|
187
|
-
|
|
189
|
+
var PROGRESS_PROTOCOL = 1;
|
|
190
|
+
function readProgressFd(env) {
|
|
191
|
+
const fd = Number.parseInt(String(env.MM_PROGRESS_FD ?? ""), 10);
|
|
192
|
+
if (!Number.isInteger(fd) || fd <= 0) return null;
|
|
193
|
+
const protocol = Number.parseInt(String(env.MM_PROGRESS_PROTOCOL ?? PROGRESS_PROTOCOL), 10);
|
|
194
|
+
return protocol === PROGRESS_PROTOCOL ? fd : null;
|
|
195
|
+
}
|
|
196
|
+
function createFace({ product, color = false, columns, env = process.env, operation }) {
|
|
188
197
|
const identity = identityFor(product);
|
|
189
198
|
const width = faceWidth(columns);
|
|
190
199
|
const paint = (sgr, text) => color ? `\x1B[${sgr}m${text}\x1B[0m` : String(text);
|
|
191
200
|
const bar = () => paint(PALETTE.muted, GLYPH.bar);
|
|
192
201
|
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
193
|
-
const
|
|
202
|
+
const continuedPhases = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((phase) => phase.trim()).filter(Boolean));
|
|
203
|
+
const continuesFace = continuedPhases.size > 0;
|
|
204
|
+
const nested = env.MM_OUTER_CONSOLE === "1";
|
|
205
|
+
const progressFd = readProgressFd(env);
|
|
206
|
+
const emitMilestone = (title, measure, kind) => {
|
|
207
|
+
if (progressFd === null) return false;
|
|
208
|
+
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
209
|
+
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
210
|
+
try {
|
|
211
|
+
(0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
|
|
212
|
+
`);
|
|
213
|
+
return true;
|
|
214
|
+
} catch {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const welcome = () => continuesFace || nested ? [] : [
|
|
194
219
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
195
220
|
bar(),
|
|
196
|
-
`${bar()} ${identity.warm}`,
|
|
221
|
+
`${bar()} ${operation === "install" ? identity.installWarm : identity.warm}`,
|
|
197
222
|
bar()
|
|
198
223
|
];
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
224
|
+
const continues = (phase, kind = "ok") => {
|
|
225
|
+
const inherited = continuedPhases.delete(String(phase).trim());
|
|
226
|
+
return kind === "fail" ? false : inherited;
|
|
227
|
+
};
|
|
228
|
+
const step = (title, measure = null, kind = "ok") => {
|
|
229
|
+
if (emitMilestone(title, measure, kind)) return "";
|
|
230
|
+
const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
|
|
231
|
+
const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
|
|
232
|
+
const time = measured;
|
|
202
233
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
203
|
-
const reserved = time ? 6 : 0;
|
|
234
|
+
const reserved = time ? Math.max(6, visibleWidth(time) + 2) : 0;
|
|
204
235
|
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
205
236
|
const head = `${bar()} ${glyph} ${first}`;
|
|
206
237
|
const pad = Math.max(1, column - visibleWidth(head));
|
|
207
238
|
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
208
239
|
};
|
|
209
|
-
const relay = (text) => String(text).split("\n").
|
|
210
|
-
const receipt = (lines) => {
|
|
240
|
+
const relay = (text) => String(text).split("\n").flatMap((line) => line.trim() === "" ? [bar()] : (visibleWidth(line) <= width - TITLE_COLUMN ? [line] : wrapWords(line, width - TITLE_COLUMN)).map((part) => `${bar()}${indent}${part}`)).join("\n");
|
|
241
|
+
const receipt = (lines, { ready = true } = {}) => {
|
|
242
|
+
if (ready && nested) return [];
|
|
211
243
|
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
212
244
|
const line = String(raw);
|
|
213
245
|
if (visibleWidth(line) <= width - 6) return [line];
|
|
214
|
-
|
|
215
|
-
return wrapWords(line, width - 6 - lead.length).map((part) => `${lead}${part}`);
|
|
246
|
+
return wrapWords(line, width - 6);
|
|
216
247
|
});
|
|
217
248
|
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
218
249
|
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
@@ -224,87 +255,713 @@ function createFace({ product, color = false, columns }) {
|
|
|
224
255
|
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
225
256
|
];
|
|
226
257
|
};
|
|
227
|
-
const
|
|
258
|
+
const outcome = (changed, options = {}) => {
|
|
259
|
+
const fact = String(changed ?? "").trim();
|
|
260
|
+
if (!fact) {
|
|
261
|
+
throw new Error(`installer face: the receipt must say what changed \u2014 pass the one measured fact this run produced, such as "Updated 2 of 7 surfaces to 4.4.10." (${identity.name})`);
|
|
262
|
+
}
|
|
263
|
+
return receipt([
|
|
264
|
+
`${GLYPH.check} ${identity.name} is ready.`,
|
|
265
|
+
fact,
|
|
266
|
+
`Check health any time: ${identity.doctor}`
|
|
267
|
+
], options);
|
|
268
|
+
};
|
|
269
|
+
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
228
270
|
const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
|
|
229
|
-
return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
|
|
271
|
+
return { identity, width, nested, emitsProgress: progressFd !== null, welcome, continues, step, relay, receipt, outcome, signOff, refusal, paint };
|
|
230
272
|
}
|
|
273
|
+
|
|
274
|
+
// ../installer/face/src/shell.ts
|
|
231
275
|
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
var
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const tail2 = instances.length > 1 ? `${row.state} ${SEPARATOR} ${instances.join(", ")}` : row.state;
|
|
253
|
-
const body = `${clip(subject, column).padEnd(column)} ${row.mark ? `${markGlyph(face, row.mark)} ` : ""}${tail2}`;
|
|
254
|
-
lines.push(...face.relay(body).split("\n"));
|
|
255
|
-
if (row.note) {
|
|
256
|
-
for (const part of wrapWords(row.note, Math.max(16, face.width - TITLE_COLUMN))) {
|
|
257
|
-
lines.push(...face.relay(part).split("\n"));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
276
|
+
|
|
277
|
+
// ../installer/face/src/spinner.ts
|
|
278
|
+
var import_node_fs2 = require("node:fs");
|
|
279
|
+
var import_node_worker_threads = require("node:worker_threads");
|
|
280
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
281
|
+
var WORKER_SOURCE = `
|
|
282
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
283
|
+
const { writeSync, appendFileSync } = require('node:fs');
|
|
284
|
+
const control = new Int32Array(workerData.control);
|
|
285
|
+
let frames = workerData.frames, frame = workerData.frame;
|
|
286
|
+
function draw() {
|
|
287
|
+
if (Atomics.compareExchange(control, 1, 0, 1) !== 0) return;
|
|
288
|
+
try {
|
|
289
|
+
if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
|
|
290
|
+
const text = frames[frame++ % frames.length];
|
|
291
|
+
writeSync(2, text);
|
|
292
|
+
if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
|
|
293
|
+
} finally {
|
|
294
|
+
Atomics.store(control, 1, 0);
|
|
295
|
+
Atomics.notify(control, 1);
|
|
260
296
|
}
|
|
261
|
-
return lines;
|
|
262
|
-
}
|
|
263
|
-
function markGlyph(face, kind) {
|
|
264
|
-
const glyph = kind === "fail" ? GLYPH.cross : kind === "note" ? GLYPH.hollow : GLYPH.check;
|
|
265
|
-
return face.paint(kind === "fail" ? face.identity.accent : kind === "note" ? "38;2;150;150;150" : "38;2;31;209;138", glyph);
|
|
266
|
-
}
|
|
267
|
-
function clip(text, width) {
|
|
268
|
-
if (visibleWidth(text) <= width) return text;
|
|
269
|
-
return `\u2026${[...text].slice(-(width - 1)).join("")}`;
|
|
270
297
|
}
|
|
271
|
-
|
|
272
|
-
|
|
298
|
+
parentPort.on('message', (next) => {
|
|
299
|
+
if (Atomics.load(control, 2)) return;
|
|
300
|
+
frames = next.frames;
|
|
301
|
+
frame = next.frame;
|
|
302
|
+
Atomics.store(control, 0, 0);
|
|
303
|
+
});
|
|
304
|
+
setInterval(draw, workerData.intervalMs);
|
|
305
|
+
`;
|
|
306
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
307
|
+
animate = animate && !face.nested && !face.emitsProgress;
|
|
273
308
|
let timer = null;
|
|
274
|
-
let
|
|
309
|
+
let worker = null;
|
|
310
|
+
let control = null;
|
|
311
|
+
let frames = [];
|
|
275
312
|
let frame = 0;
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
313
|
+
const write = (text) => {
|
|
314
|
+
if (stream) stream.write(text);
|
|
315
|
+
else {
|
|
316
|
+
(0, import_node_fs2.writeSync)(2, text);
|
|
317
|
+
if (transcriptPath) (0, import_node_fs2.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
318
|
+
`);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const render = (text, measure) => {
|
|
322
|
+
const line = face.step(text, measure, "note").split("\n")[0];
|
|
323
|
+
frames = line ? FRAMES.map((glyph) => `\r\x1B[2K${line.replace(GLYPH.dot, glyph)}`) : [];
|
|
278
324
|
};
|
|
279
325
|
const draw = () => {
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
326
|
+
if (animate && frames.length) write(frames[frame++ % frames.length]);
|
|
327
|
+
};
|
|
328
|
+
const pause = () => {
|
|
329
|
+
if (!control) return;
|
|
330
|
+
Atomics.store(control, 0, 1);
|
|
331
|
+
while (Atomics.load(control, 1)) Atomics.wait(control, 1, 1);
|
|
332
|
+
};
|
|
333
|
+
const halt = () => {
|
|
334
|
+
if (timer) clearInterval(timer);
|
|
335
|
+
timer = null;
|
|
336
|
+
if (control) Atomics.store(control, 2, 1);
|
|
337
|
+
pause();
|
|
338
|
+
if (worker) void worker.terminate();
|
|
339
|
+
worker = null;
|
|
340
|
+
control = null;
|
|
284
341
|
};
|
|
285
342
|
return {
|
|
286
|
-
start(
|
|
287
|
-
|
|
343
|
+
start(text, measure = null) {
|
|
344
|
+
if (!animate) return;
|
|
345
|
+
halt();
|
|
346
|
+
render(text, measure);
|
|
288
347
|
frame = 0;
|
|
289
348
|
draw();
|
|
290
|
-
if (
|
|
349
|
+
if (!frames.length) return;
|
|
350
|
+
if (stream) timer = setInterval(draw, intervalMs).unref();
|
|
351
|
+
else {
|
|
352
|
+
control = new Int32Array(new SharedArrayBuffer(12));
|
|
353
|
+
try {
|
|
354
|
+
worker = new import_node_worker_threads.Worker(WORKER_SOURCE, { eval: true, workerData: {
|
|
355
|
+
control: control.buffer,
|
|
356
|
+
frames,
|
|
357
|
+
frame,
|
|
358
|
+
intervalMs,
|
|
359
|
+
transcriptPath
|
|
360
|
+
} });
|
|
361
|
+
const active = worker;
|
|
362
|
+
worker.on("error", () => {
|
|
363
|
+
if (worker === active) halt();
|
|
364
|
+
});
|
|
365
|
+
worker.unref();
|
|
366
|
+
} catch {
|
|
367
|
+
halt();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
291
370
|
},
|
|
292
|
-
say(
|
|
293
|
-
|
|
371
|
+
say(text, measure = null) {
|
|
372
|
+
if (!animate) return;
|
|
373
|
+
pause();
|
|
374
|
+
render(text, measure);
|
|
294
375
|
draw();
|
|
376
|
+
worker?.postMessage({ frames, frame });
|
|
295
377
|
},
|
|
296
378
|
stop() {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
clear();
|
|
379
|
+
halt();
|
|
380
|
+
if (animate) write("\r\x1B[2K");
|
|
300
381
|
}
|
|
301
382
|
};
|
|
302
383
|
}
|
|
303
384
|
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
385
|
+
|
|
386
|
+
// ../installer/face/src/conformance.ts
|
|
304
387
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
388
|
+
var FORBIDDEN = /* @__PURE__ */ new Map([
|
|
389
|
+
["\u2713", "U+2713 is not in the guidebook glyph set; the check is U+2714"],
|
|
390
|
+
["\u2717", "U+2717 is not in the guidebook glyph set; the cross is U+2716"],
|
|
391
|
+
["\u21BB", "U+21BB is not in the guidebook glyph set; a retry uses the dot U+25CF"],
|
|
392
|
+
["\u250C", "U+250C is clack's intro corner; the welcome opens with the branded diamond"],
|
|
393
|
+
["\u251C", "U+251C is clack's note frame; a receipt box pairs U+256D/U+256E over U+2570/U+256F"],
|
|
394
|
+
["\u2514", "U+2514 is clack's outro corner; the sign-off carries the diamond"]
|
|
395
|
+
]);
|
|
396
|
+
var EMOJI = /[\u{1f300}-\u{1faff}\u{2600}-\u{27bf}]/u;
|
|
397
|
+
function fail(rule, detail) {
|
|
398
|
+
throw new Error(`installer face: ${rule} \u2014 ${detail}`);
|
|
399
|
+
}
|
|
400
|
+
function assertFaceConformance(lines, options) {
|
|
401
|
+
const identity = identityFor(options.product);
|
|
402
|
+
const width = options.columns ?? 100;
|
|
403
|
+
const rendered = lines.flatMap((line) => String(line).split("\n"));
|
|
404
|
+
const plain = rendered.map(stripColor);
|
|
405
|
+
const head = plain[0] ?? "";
|
|
406
|
+
const signOffLine = `${GLYPH.diamond} ${identity.name} \xB7 Mutatis Mutandis`;
|
|
407
|
+
const opensWithWelcome = head.startsWith(GLYPH.diamond) && head !== signOffLine;
|
|
408
|
+
if (options.complete && options.tty !== false) {
|
|
409
|
+
const welcome = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
|
|
410
|
+
const welcomes = plain.filter((line) => line === welcome).length;
|
|
411
|
+
const receipts = plain.filter((line) => line.startsWith(GLYPH.boxTop)).length;
|
|
412
|
+
const signOffs = plain.filter((line) => line === signOffLine).length;
|
|
413
|
+
if (welcomes !== (options.nested ? 0 : 1) || signOffs !== (options.nested ? 0 : 1) || !options.nested && receipts !== 1) {
|
|
414
|
+
fail("complete run", "expected one console owner, one welcome, one receipt and one sign-off");
|
|
415
|
+
}
|
|
416
|
+
for (const line of plain) {
|
|
417
|
+
if (line && ![GLYPH.bar, GLYPH.diamond, GLYPH.hollow, GLYPH.boxTop, GLYPH.boxBottom].some((glyph) => line.startsWith(glyph))) {
|
|
418
|
+
fail("complete run", `unframed output: ${JSON.stringify(line)}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (rendered.length === 0) fail("R7 render before you ship", "no rendered lines were given to the guard");
|
|
423
|
+
const greetings = plain.filter((line) => ["\u2014", "-"].some((separator) => line.includes(`${identity.name} ${separator} Mutatis Mutandis`)));
|
|
424
|
+
if (greetings.length > 1) fail("R8 one welcome per run", "duplicate product greeting");
|
|
425
|
+
if (plain.filter((line) => line.startsWith(GLYPH.boxTop)).length > 1) {
|
|
426
|
+
fail("R8 one receipt per run", "duplicate receipt");
|
|
427
|
+
}
|
|
428
|
+
if (options.nested === true) {
|
|
429
|
+
if (opensWithWelcome) {
|
|
430
|
+
fail("R8 a nested run draws no welcome", `an outer console already greeted the user; this run opened with ${JSON.stringify(head)}`);
|
|
431
|
+
}
|
|
432
|
+
const signOffAt = plain.findIndex((line) => line === signOffLine);
|
|
433
|
+
if (signOffAt !== -1) {
|
|
434
|
+
fail("R8 a nested run draws no sign-off", `the outer console closes the run; line ${signOffAt + 1} signs off`);
|
|
435
|
+
}
|
|
436
|
+
const boxAt = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
|
|
437
|
+
if (boxAt !== -1) {
|
|
438
|
+
const headline = plain.slice(boxAt + 1).find((line) => line.startsWith(GLYPH.bar) && line.trim() !== GLYPH.bar) ?? "";
|
|
439
|
+
if (headline.includes(GLYPH.check)) {
|
|
440
|
+
fail("R8 a nested run draws no success receipt", `the outer console owns the outcome; this run printed ${JSON.stringify(headline.trim())}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (options.tty === false) {
|
|
445
|
+
for (const line of plain) {
|
|
446
|
+
const glyph = [...line].find((char) => ALLOWED.has(char));
|
|
447
|
+
if (glyph) fail("non-TTY lane is plain", `a piped run emitted ${JSON.stringify(glyph)} in ${JSON.stringify(line)}`);
|
|
448
|
+
}
|
|
449
|
+
for (const line of rendered) {
|
|
450
|
+
if (line.includes("\x1B[")) fail("non-TTY lane is plain", `a piped run emitted colour in ${JSON.stringify(line)}`);
|
|
451
|
+
}
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
for (const line of plain) {
|
|
455
|
+
if (line.startsWith(`${GLYPH.bar} ${GLYPH.hollow} `)) {
|
|
456
|
+
fail("note step uses U+25CF", `U+25C7 is the note-box opener in ${JSON.stringify(line)}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
for (const line of plain) {
|
|
460
|
+
if (/[\u25c6\u25c7\u2714\u2716\u25cf] (?! )/u.test(line)) {
|
|
461
|
+
fail("R4 two spaces after a glyph", `a single space follows the glyph in ${JSON.stringify(line)}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
for (const line of plain) {
|
|
465
|
+
for (const [glyph, why] of FORBIDDEN) {
|
|
466
|
+
if (line.includes(glyph)) fail("glyph set", `${why} (in ${JSON.stringify(line)})`);
|
|
467
|
+
}
|
|
468
|
+
const emoji = [...line].find((char) => EMOJI.test(char) && !ALLOWED.has(char));
|
|
469
|
+
if (emoji) fail("glyph set", `emoji ${JSON.stringify(emoji)} in ${JSON.stringify(line)}`);
|
|
470
|
+
}
|
|
471
|
+
const top = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
|
|
472
|
+
if (top !== -1) {
|
|
473
|
+
const bottom = plain.findIndex((line, index) => index > top && line.startsWith(GLYPH.boxBottom));
|
|
474
|
+
if (bottom === -1) fail("R5 receipt frame", "the receipt box opens and never closes");
|
|
475
|
+
if (!plain[top].endsWith(GLYPH.boxTopEnd)) fail("R5 receipt frame", `the top row does not pair its corners: ${JSON.stringify(plain[top])}`);
|
|
476
|
+
if (!plain[bottom].endsWith(GLYPH.boxBottomEnd)) fail("R5 receipt frame", `the bottom row does not pair its corners: ${JSON.stringify(plain[bottom])}`);
|
|
477
|
+
const widths = new Set(plain.slice(top, bottom + 1).map(visibleWidth));
|
|
478
|
+
if (widths.size !== 1) fail("R5 receipt frame", `the box is ragged: widths ${[...widths].join(", ")}`);
|
|
479
|
+
const rows = plain.slice(top + 1, bottom);
|
|
480
|
+
if (rows.length > 0 && rows[0].includes(GLYPH.check)) {
|
|
481
|
+
if (rows.length < 3) {
|
|
482
|
+
fail("R9 the receipt says what changed", `a success receipt has three lines \u2014 ready, what changed, the health command \u2014 and this one has ${rows.length}: ${JSON.stringify(rows.map((row) => row.trim()))}`);
|
|
483
|
+
}
|
|
484
|
+
const changed = rows[1].replace(GLYPH.bar, "").trim();
|
|
485
|
+
if (changed === "") fail("R9 the receipt says what changed", "the middle line of the success receipt is blank");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const inReceipt = (index) => top !== -1 && index >= top && index <= plain.findIndex((line, at) => at > top && line.startsWith(GLYPH.boxBottom));
|
|
489
|
+
const welcomeEnd = opensWithWelcome ? 4 : 0;
|
|
490
|
+
for (const [index, line] of plain.entries()) {
|
|
491
|
+
if (index < welcomeEnd || inReceipt(index)) continue;
|
|
492
|
+
if (!line.startsWith(GLYPH.bar) || line.trim() === GLYPH.bar) continue;
|
|
493
|
+
const after = [...line.slice(1)];
|
|
494
|
+
const at = after.findIndex((char) => char !== " ");
|
|
495
|
+
const edge = at + 2;
|
|
496
|
+
if (edge === 4 && ALLOWED.has(after[at])) continue;
|
|
497
|
+
if (edge !== TITLE_COLUMN + 1) {
|
|
498
|
+
fail("R1 one rail, one left edge", `text starts at column ${edge} without a glyph there; a line that carries no glyph belongs at the title column (${TITLE_COLUMN + 1}): ${JSON.stringify(line)}`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
for (const line of plain) {
|
|
502
|
+
if (/\(node:\d+\)|DeprecationWarning|ExperimentalWarning/u.test(line)) {
|
|
503
|
+
fail("R2 no foreign output in the face", `a Node warning reached the face: ${JSON.stringify(line)} \u2014 fix the spawn, or run the child with --no-deprecation`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const receiptStart = top === -1 ? plain.length : top;
|
|
507
|
+
for (const line of plain.slice(0, receiptStart)) {
|
|
508
|
+
if (/[\u2714\u2716\u25cf\u25c7]/u.test(line) && /([A-Za-z]:\\|\/(?:home|Users|opt|usr)\/)/u.test(line)) {
|
|
509
|
+
fail("R3 paths live in the receipt", `a step line carries a path: ${JSON.stringify(line)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
for (const line of plain) {
|
|
513
|
+
if (visibleWidth(line) > width) {
|
|
514
|
+
fail("width cap", `a line is ${visibleWidth(line)} columns wide, cap ${width}: ${JSON.stringify(line)}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
if (opensWithWelcome) {
|
|
518
|
+
const expected = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
|
|
519
|
+
if (head !== expected) fail("welcome is the product table's", `expected ${JSON.stringify(expected)}, got ${JSON.stringify(head)}`);
|
|
520
|
+
const warm = plain.slice(1, 4).find((line) => line.includes(identity.warm) || line.includes(identity.installWarm));
|
|
521
|
+
if (!warm) fail("welcome is the product table's", `the warm line for ${options.product} is missing or reworded`);
|
|
522
|
+
}
|
|
523
|
+
const painted = rendered.join("\n");
|
|
524
|
+
if (painted.includes("\x1B[")) {
|
|
525
|
+
if (!painted.includes(identity.accent) && !painted.includes(PALETTE.muted)) {
|
|
526
|
+
fail("accent", `${options.product} rendered colour that is neither its accent nor a neutral token`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// ../installer/face/src/run.ts
|
|
532
|
+
var import_node_fs4 = require("node:fs");
|
|
533
|
+
|
|
534
|
+
// ../installer/face/src/outcome.ts
|
|
535
|
+
var import_node_fs3 = require("node:fs");
|
|
536
|
+
var counts = ["total", "updated", "failed"];
|
|
537
|
+
var strings = ["version", "retry", "detail", "logPath"];
|
|
538
|
+
var flags = ["dryRun", "installed", "deferred", "operationFailed"];
|
|
539
|
+
function validateInstallerOutcome(value) {
|
|
540
|
+
const invalid = () => {
|
|
541
|
+
throw new Error("installer outcome: invalid child result");
|
|
542
|
+
};
|
|
543
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
|
|
544
|
+
const facts = value;
|
|
545
|
+
const allowed = [...counts, ...strings, ...flags, "rollback", "logState"];
|
|
546
|
+
if (Object.keys(facts).some((key) => !allowed.includes(key))) return invalid();
|
|
547
|
+
for (const key of counts) if (!Number.isSafeInteger(facts[key]) || facts[key] < 0) return invalid();
|
|
548
|
+
if (facts.updated + facts.failed > facts.total) return invalid();
|
|
549
|
+
for (const key of strings) if (facts[key] !== void 0 && (typeof facts[key] !== "string" || facts[key].length > 4096)) return invalid();
|
|
550
|
+
for (const key of flags) if (facts[key] !== void 0 && typeof facts[key] !== "boolean") return invalid();
|
|
551
|
+
if (facts.rollback !== void 0 && !["completed", "partial", "not-needed", "unknown"].includes(facts.rollback)) return invalid();
|
|
552
|
+
if (facts.logState !== void 0 && !["unavailable", "omitted"].includes(facts.logState)) return invalid();
|
|
553
|
+
if (facts.logPath !== void 0 && (!facts.logPath.trim() || /[\x00-\x1f\x7f]/u.test(facts.logPath))) return invalid();
|
|
554
|
+
return { ...facts };
|
|
555
|
+
}
|
|
556
|
+
function writeInstallerOutcome(path, value) {
|
|
557
|
+
(0, import_node_fs3.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ../installer/face/src/run.ts
|
|
561
|
+
function validateInstallerProduct(value) {
|
|
562
|
+
const fail2 = (field) => {
|
|
563
|
+
throw new Error(`installer product: invalid ${field}`);
|
|
564
|
+
};
|
|
565
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail2("declaration");
|
|
566
|
+
const input = value;
|
|
567
|
+
const text = (value2, field) => typeof value2 === "string" && value2.trim() && !/[\r\n\x00-\x1f]/u.test(value2) ? value2 : fail2(field);
|
|
568
|
+
const key = text(input.product, "product");
|
|
569
|
+
const product = { mmi: "mmi-hub", jerv: "jerv-hub" }[key] ?? key;
|
|
570
|
+
const identity = identityFor(product);
|
|
571
|
+
const gate = text(input.gate, "gate");
|
|
572
|
+
let gateUrl;
|
|
573
|
+
try {
|
|
574
|
+
gateUrl = new URL(gate);
|
|
575
|
+
} catch {
|
|
576
|
+
return fail2("gate");
|
|
577
|
+
}
|
|
578
|
+
if (!["https:", "http:"].includes(gateUrl.protocol) || gateUrl.username || gateUrl.password) return fail2("gate");
|
|
579
|
+
const doctor = text(input.doctor, "doctor");
|
|
580
|
+
if (doctor !== identity.doctor) return fail2("doctor");
|
|
581
|
+
if (!Array.isArray(input.surfaces) || input.surfaces.length === 0) return fail2("surfaces");
|
|
582
|
+
const ids = /* @__PURE__ */ new Set();
|
|
583
|
+
const surfaces = input.surfaces.map((raw) => {
|
|
584
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fail2("surface");
|
|
585
|
+
const source = raw;
|
|
586
|
+
const id = text(source.id, "surface.id");
|
|
587
|
+
if (ids.has(id)) return fail2("duplicate surface.id");
|
|
588
|
+
ids.add(id);
|
|
589
|
+
const surface = { id };
|
|
590
|
+
for (const field of ["npm", "bin", "kind", "activation"]) {
|
|
591
|
+
if (source[field] !== void 0) surface[field] = text(source[field], `surface.${field}`);
|
|
592
|
+
}
|
|
593
|
+
if (Boolean(surface.npm) !== Boolean(surface.bin)) return fail2("surface npm/bin pair");
|
|
594
|
+
if (surface.kind !== void 0 && !["agent-home", "payload"].includes(surface.kind)) return fail2("surface.kind");
|
|
595
|
+
if (!surface.npm && !surface.kind) return fail2("surface implementation");
|
|
596
|
+
return surface;
|
|
597
|
+
});
|
|
598
|
+
return { product, gate, doctor, surfaces };
|
|
599
|
+
}
|
|
600
|
+
var PHASES = {
|
|
601
|
+
preflight: ["Checking prerequisites", "Checked prerequisites"],
|
|
602
|
+
resolve: ["Resolving the release", "Resolved the release"],
|
|
603
|
+
download: ["Downloading the payload", "Downloaded the payload"],
|
|
604
|
+
"sign-in": ["Signing in", "Signed in"],
|
|
605
|
+
check: ["Checking surfaces", "Checked surfaces"],
|
|
606
|
+
arm: ["Scheduling updates", "Armed hourly updates"],
|
|
607
|
+
"verify-release": ["Checking the release version", "Verified the release version"],
|
|
608
|
+
verify: ["Verifying the payload", "Verified the payload"],
|
|
609
|
+
install: ["Installing the product", "Installed the product"],
|
|
610
|
+
activate: ["Activating surfaces", "Activated surfaces"],
|
|
611
|
+
doctor: ["Checking health", "Checked health"],
|
|
612
|
+
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
613
|
+
};
|
|
614
|
+
function createInstallerRun(value, options = {}) {
|
|
615
|
+
const declaration = validateInstallerProduct(value);
|
|
616
|
+
const env = options.env ?? process.env;
|
|
617
|
+
const tty = options.tty ?? Boolean(process.stdout.isTTY);
|
|
618
|
+
const face = createFace({
|
|
619
|
+
operation: options.operation,
|
|
620
|
+
product: declaration.product,
|
|
621
|
+
columns: options.columns,
|
|
622
|
+
env,
|
|
623
|
+
color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
|
|
624
|
+
});
|
|
625
|
+
const errors = [];
|
|
626
|
+
const write = options.write ? (text, channel) => {
|
|
627
|
+
try {
|
|
628
|
+
options.write(text, channel);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
errors.push(`installer output observer: ${error instanceof Error ? error.message : String(error)}`);
|
|
631
|
+
}
|
|
632
|
+
} : (text, channel) => {
|
|
633
|
+
(channel === "stdout" ? process.stdout : process.stderr).write(text);
|
|
634
|
+
};
|
|
635
|
+
const emit = (text, channel = "stdout", recorded = text) => {
|
|
636
|
+
if (!text) return;
|
|
637
|
+
write(text, channel);
|
|
638
|
+
if (env.MM_FACE_TRANSCRIPT) {
|
|
639
|
+
(0, import_node_fs4.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
|
|
640
|
+
`, "utf8");
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
const lines = (rows, channel = "stdout") => {
|
|
644
|
+
for (const row of rows.flatMap((row2) => row2.split("\n"))) if (row) emit(`${row}
|
|
645
|
+
`, channel);
|
|
646
|
+
};
|
|
647
|
+
const spinner = createSpinner(face, {
|
|
648
|
+
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
649
|
+
...options.write ? { stream: { write: (text) => {
|
|
650
|
+
emit(String(text), "spinner");
|
|
651
|
+
return true;
|
|
652
|
+
} } } : { transcriptPath: env.MM_FACE_TRANSCRIPT }
|
|
653
|
+
});
|
|
654
|
+
let started = false;
|
|
655
|
+
let finished = false;
|
|
656
|
+
const start = () => {
|
|
657
|
+
if (started || finished) return;
|
|
658
|
+
started = true;
|
|
659
|
+
if (options.quiet) return;
|
|
660
|
+
const welcome = face.welcome();
|
|
661
|
+
if (tty) lines(welcome);
|
|
662
|
+
else if (welcome.length) {
|
|
663
|
+
const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
664
|
+
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
const durable = (title, measure, kind) => {
|
|
668
|
+
spinner.stop();
|
|
669
|
+
const rendered = face.step(title, measure, kind);
|
|
670
|
+
if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
|
|
671
|
+
};
|
|
672
|
+
const run = {
|
|
673
|
+
get errors() {
|
|
674
|
+
return [...errors];
|
|
675
|
+
},
|
|
676
|
+
start,
|
|
677
|
+
phase(id, facts = {}) {
|
|
678
|
+
if (finished) throw new Error("installer run already finished");
|
|
679
|
+
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
680
|
+
start();
|
|
681
|
+
const state = facts.state ?? "ok";
|
|
682
|
+
const title = PHASES[id][state === "ok" ? 1 : 0];
|
|
683
|
+
if (state === "running") {
|
|
684
|
+
spinner.start(title, facts.measure ?? null);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
688
|
+
if (facts.detail) run.relay(facts.detail);
|
|
689
|
+
},
|
|
690
|
+
surface(facts) {
|
|
691
|
+
if (finished) throw new Error("installer run already finished");
|
|
692
|
+
const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
|
|
693
|
+
if (!surface) throw new Error("installer run: undeclared surface");
|
|
694
|
+
start();
|
|
695
|
+
const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
|
|
696
|
+
const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
|
|
697
|
+
if (!status) throw new Error("installer run: unknown surface state");
|
|
698
|
+
const separator = tty ? "\xB7" : "-";
|
|
699
|
+
const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
|
|
700
|
+
const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
|
|
701
|
+
durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
|
|
702
|
+
if (facts.detail) run.relay(facts.detail);
|
|
703
|
+
},
|
|
704
|
+
milestone({ step, state, ms }) {
|
|
705
|
+
start();
|
|
706
|
+
durable(step, ms === void 0 ? null : ms / 1e3, state);
|
|
707
|
+
},
|
|
708
|
+
signIn({ url, code }) {
|
|
709
|
+
start();
|
|
710
|
+
spinner.stop();
|
|
711
|
+
for (const text of [`Open ${url}`, `Enter code: ${code}`]) {
|
|
712
|
+
const rendered = `${tty ? face.relay(text) : text}
|
|
713
|
+
`;
|
|
714
|
+
emit(rendered, "stdout", rendered.replace(code, "[redacted]"));
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
// Only pass safe diagnostic text, never authentication output or credentials.
|
|
718
|
+
relay(text, channel = "stdout", record = true) {
|
|
719
|
+
spinner.stop();
|
|
720
|
+
const rendered = tty ? face.relay(text) : stripColor(text);
|
|
721
|
+
for (const row of rendered.split("\n")) if (row) {
|
|
722
|
+
emit(`${row}
|
|
723
|
+
`, channel, record ? `${row}
|
|
724
|
+
` : `${tty ? face.relay("[external output omitted]") : "[external output omitted]"}
|
|
725
|
+
`);
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
cancel() {
|
|
729
|
+
if (finished) return;
|
|
730
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
|
|
731
|
+
env.MM_INSTALLER_OUTCOME_FILE,
|
|
732
|
+
{ total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
|
|
733
|
+
);
|
|
734
|
+
start();
|
|
735
|
+
spinner.stop();
|
|
736
|
+
finished = true;
|
|
737
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
|
|
738
|
+
lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
|
|
739
|
+
}
|
|
740
|
+
if (tty) lines([face.signOff()]);
|
|
741
|
+
},
|
|
742
|
+
finish(facts) {
|
|
743
|
+
if (finished) return;
|
|
744
|
+
validateInstallerOutcome(facts);
|
|
745
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(env.MM_INSTALLER_OUTCOME_FILE, facts);
|
|
746
|
+
start();
|
|
747
|
+
spinner.stop();
|
|
748
|
+
finished = true;
|
|
749
|
+
if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
|
|
750
|
+
const changed = !facts.version ? "No release target is available." : facts.dryRun ? `Would update ${facts.updated} of ${facts.total} surfaces to ${facts.version}.` : facts.installed ? `Installed ${facts.version} across ${facts.total} surfaces.` : `Updated ${facts.updated} of ${facts.total} surfaces to ${facts.version}.`;
|
|
751
|
+
const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
|
|
752
|
+
const body = [
|
|
753
|
+
`${ready ? GLYPH.check : facts.dryRun ? GLYPH.dot : GLYPH.cross} ${face.identity.name}${ready ? " is ready." : facts.dryRun ? " preview complete." : facts.deferred ? " update deferred." : " is not ready yet."}`,
|
|
754
|
+
changed,
|
|
755
|
+
...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
|
|
756
|
+
...facts.detail ? [facts.detail] : [],
|
|
757
|
+
...facts.rollback ? [`Rollback: ${facts.rollback}`] : [],
|
|
758
|
+
...facts.logPath ? [`Log: ${facts.logPath}`] : facts.logState ? [`Log: ${facts.logState}`] : [],
|
|
759
|
+
facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
|
|
760
|
+
];
|
|
761
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
|
|
762
|
+
lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
|
|
763
|
+
}
|
|
764
|
+
if (tty) lines([face.signOff()]);
|
|
765
|
+
},
|
|
766
|
+
stop() {
|
|
767
|
+
spinner.stop();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
return run;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ../installer/face/src/maintenance.ts
|
|
774
|
+
async function runMaintenance(product, args, dependencies) {
|
|
775
|
+
const json = args.includes("--json");
|
|
776
|
+
const quiet = args.includes("--quiet");
|
|
777
|
+
const dryRun = args.includes("--dry-run");
|
|
778
|
+
const installed = args.includes("install");
|
|
779
|
+
const render = dependencies.render;
|
|
780
|
+
const env = render?.env ?? process.env;
|
|
781
|
+
const tty = render?.tty ?? Boolean(process.stdout.isTTY);
|
|
782
|
+
const run = json ? null : dependencies.run ?? createInstallerRun(product, {
|
|
783
|
+
...render,
|
|
784
|
+
tty,
|
|
785
|
+
env,
|
|
786
|
+
color: tty && render?.color !== false && !args.includes("--no-color") && env.TERM !== "dumb",
|
|
787
|
+
columns: render?.columns ?? process.stdout.columns,
|
|
788
|
+
dryRun,
|
|
789
|
+
quiet,
|
|
790
|
+
operation: installed ? "install" : "update"
|
|
791
|
+
});
|
|
792
|
+
if (!quiet) run?.start();
|
|
793
|
+
let active;
|
|
794
|
+
let activeFacts;
|
|
795
|
+
let handedOff;
|
|
796
|
+
const shown = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
797
|
+
if (!quiet && !json) shown.add("welcome");
|
|
798
|
+
let phaseFailed = false;
|
|
799
|
+
let armFailed = false;
|
|
800
|
+
let knownFailure;
|
|
801
|
+
const phaseStarted = /* @__PURE__ */ new Map();
|
|
802
|
+
const phase = (event) => {
|
|
803
|
+
if (handedOff !== void 0) return;
|
|
804
|
+
if (event.state === "fail") phaseFailed = true;
|
|
805
|
+
const started = phaseStarted.get(event.id);
|
|
806
|
+
if (event.state === "running") {
|
|
807
|
+
if (started === void 0) phaseStarted.set(event.id, Date.now());
|
|
808
|
+
active = event.id;
|
|
809
|
+
} else {
|
|
810
|
+
phaseStarted.delete(event.id);
|
|
811
|
+
if (active === event.id) active = void 0;
|
|
812
|
+
}
|
|
813
|
+
if (!quiet && event.state === "running" && event.detail) run?.relay(event.detail);
|
|
814
|
+
const measured = event.measured;
|
|
815
|
+
const measure = measured?.kind === "percent" ? `${Math.max(0, Math.min(100, Math.round(measured.percent)))}%` : measured?.kind === "bytes" ? `${measured.done}/${measured.total} B` : measured?.kind === "count" ? `${measured.done}${measured.total === void 0 ? "" : `/${measured.total}`}` : event.measure;
|
|
816
|
+
const facts = {
|
|
817
|
+
state: event.state,
|
|
818
|
+
detail: event.detail,
|
|
819
|
+
measure,
|
|
820
|
+
...event.state !== "running" && started !== void 0 ? { seconds: (Date.now() - started) / 1e3 } : {}
|
|
821
|
+
};
|
|
822
|
+
if (event.state === "running") activeFacts = facts;
|
|
823
|
+
if (!quiet) {
|
|
824
|
+
run?.phase(event.id, facts);
|
|
825
|
+
if (event.state === "ok" && run) shown.add(event.id);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
const pending = [];
|
|
829
|
+
let streaming = false;
|
|
830
|
+
const emitArm = (arm) => {
|
|
831
|
+
if (quiet) return;
|
|
832
|
+
run?.surface({
|
|
833
|
+
id: arm.surface,
|
|
834
|
+
from: arm.from ?? void 0,
|
|
835
|
+
to: arm.to ?? void 0,
|
|
836
|
+
state: arm.verdict === "fail" ? "failed" : arm.verdict === "defer" ? "retry" : arm.verdict === "skip" ? "kept" : arm.from === arm.to && !arm.launchStaged ? "current" : "updated",
|
|
837
|
+
...arm.durationMs === void 0 ? {} : { seconds: arm.durationMs / 1e3 },
|
|
838
|
+
detail: dependencies.diagnose?.(arm)
|
|
839
|
+
});
|
|
840
|
+
};
|
|
841
|
+
const writeJson = (summary, schedule) => {
|
|
842
|
+
process.stdout.write(JSON.stringify(dependencies.jsonResult ? dependencies.jsonResult(summary, schedule) : installed ? { convergence: summary, autoupdate: schedule } : summary, null, 2) + "\n");
|
|
843
|
+
};
|
|
844
|
+
try {
|
|
845
|
+
const summary = await dependencies.engine({
|
|
846
|
+
dryRun,
|
|
847
|
+
withCustody: async (operation) => {
|
|
848
|
+
if (handedOff !== void 0) throw new Error("installer terminal ownership already transferred");
|
|
849
|
+
run?.stop();
|
|
850
|
+
const result = await operation({ faceContinues: [...shown] });
|
|
851
|
+
if (result.kind === "reexec") {
|
|
852
|
+
if (!Number.isSafeInteger(result.code) || result.code < 0) throw new Error("invalid handoff exit code");
|
|
853
|
+
handedOff = result.code;
|
|
854
|
+
} else if (result.kind === "resume") {
|
|
855
|
+
if (!quiet && active) run?.phase(active, activeFacts);
|
|
856
|
+
} else throw new Error("invalid terminal custody result");
|
|
857
|
+
return result;
|
|
858
|
+
},
|
|
859
|
+
shippedTarget: installed ? dependencies.shippedTarget : void 0,
|
|
860
|
+
narrate: !quiet && !json && args.includes("--verbose") ? (text) => {
|
|
861
|
+
if (handedOff === void 0) run?.relay(text, "stderr", false);
|
|
862
|
+
} : void 0,
|
|
863
|
+
relay: (text, channel = "stdout", record = false) => {
|
|
864
|
+
if (handedOff === void 0) run?.relay(text, channel, record);
|
|
865
|
+
},
|
|
866
|
+
onTargetResolved: (target) => phase({ id: "verify-release", detail: target, state: "ok" }),
|
|
867
|
+
onPhase: () => {
|
|
868
|
+
if (!quiet && handedOff === void 0) run?.phase("check", { state: "running" });
|
|
869
|
+
},
|
|
870
|
+
onPhaseEvent: phase,
|
|
871
|
+
onArm: (arm) => {
|
|
872
|
+
if (handedOff !== void 0) return;
|
|
873
|
+
if (arm.verdict === "fail") armFailed = true;
|
|
874
|
+
pending.push(arm);
|
|
875
|
+
if (pending.length > 1) streaming = true;
|
|
876
|
+
if (streaming) for (const result of pending.splice(0)) emitArm(result);
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
if (handedOff !== void 0) return handedOff;
|
|
880
|
+
if (summary.reexec) {
|
|
881
|
+
if (!dependencies.handOff) {
|
|
882
|
+
knownFailure = "The updated version needs a handoff, but no handoff is configured.";
|
|
883
|
+
throw new Error(knownFailure);
|
|
884
|
+
}
|
|
885
|
+
run?.stop();
|
|
886
|
+
knownFailure = "The updated updater could not launch.";
|
|
887
|
+
return await dependencies.handOff(summary.reexec, args, { ...process.env, MM_FACE_CONTINUES: "welcome" });
|
|
888
|
+
}
|
|
889
|
+
const counts2 = dependencies.counts(summary);
|
|
890
|
+
validateInstallerOutcome({ total: counts2.total, updated: counts2.updated, failed: counts2.failed });
|
|
891
|
+
if (!Number.isSafeInteger(counts2.retry) || counts2.retry < 0 || counts2.retry > counts2.total) throw new Error("invalid retry count");
|
|
892
|
+
if (summary.cancelled && summary.exit === 0 && !phaseFailed && !armFailed && counts2.failed === 0 && counts2.updated === 0) {
|
|
893
|
+
if (json) writeJson(summary, null);
|
|
894
|
+
else run?.cancel();
|
|
895
|
+
return 0;
|
|
896
|
+
}
|
|
897
|
+
for (const arm of pending) emitArm(arm);
|
|
898
|
+
const waiting = summary.surfaces.filter((surface) => surface.present && surface.action === "arm-pending");
|
|
899
|
+
if (!quiet) for (const surface of waiting) run?.surface({ id: surface.id, state: "pending" });
|
|
900
|
+
let schedule = null;
|
|
901
|
+
if (installed && !dryRun && dependencies.schedule) {
|
|
902
|
+
phase({ id: "arm", state: "running" });
|
|
903
|
+
schedule = dependencies.schedule();
|
|
904
|
+
if (schedule) phase({ id: "arm", state: schedule.ok ? "ok" : "fail", detail: schedule.ok ? void 0 : schedule.detail });
|
|
905
|
+
}
|
|
906
|
+
const failed = phaseFailed || armFailed || counts2.failed > 0 || Boolean(schedule && !schedule.ok);
|
|
907
|
+
const exit = Math.max(summary.exit, failed ? 1 : 0);
|
|
908
|
+
if (json) {
|
|
909
|
+
writeJson({ ...summary, exit }, schedule);
|
|
910
|
+
} else {
|
|
911
|
+
run.finish({
|
|
912
|
+
version: summary.target ?? void 0,
|
|
913
|
+
total: counts2.total,
|
|
914
|
+
updated: counts2.updated,
|
|
915
|
+
failed: counts2.failed,
|
|
916
|
+
operationFailed: exit !== 0,
|
|
917
|
+
deferred: !failed && Boolean(counts2.retry || waiting.length || summary.deferred || summary.cancelled || !summary.target),
|
|
918
|
+
dryRun,
|
|
919
|
+
installed,
|
|
920
|
+
detail: schedule && !schedule.ok ? schedule.detail : summary.detail ?? (summary.cancelled ? "Operation cancelled." : void 0),
|
|
921
|
+
retry: summary.retry,
|
|
922
|
+
rollback: summary.rollback,
|
|
923
|
+
logPath: summary.logPath,
|
|
924
|
+
logState: summary.logState
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
return exit;
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (handedOff !== void 0) throw error;
|
|
930
|
+
let detail = knownFailure ?? (active === "sign-in" ? "Sign-in did not complete." : installed ? "Installation did not complete." : "Update did not complete.");
|
|
931
|
+
if (!knownFailure && dependencies.safeErrorDetail) {
|
|
932
|
+
try {
|
|
933
|
+
detail = dependencies.safeErrorDetail(error);
|
|
934
|
+
} catch {
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (active) phase({ id: active, state: "fail" });
|
|
938
|
+
run?.relay(detail, "stderr");
|
|
939
|
+
run?.finish({ total: 0, updated: 0, failed: 0, operationFailed: true, detail });
|
|
940
|
+
if (json) process.stdout.write(JSON.stringify({ error: detail }) + "\n");
|
|
941
|
+
return 1;
|
|
942
|
+
} finally {
|
|
943
|
+
run?.stop();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
// ../installer.product.json
|
|
948
|
+
var installer_product_default = {
|
|
949
|
+
product: "mmi",
|
|
950
|
+
gate: "https://gate.mutmut.work/mmi",
|
|
951
|
+
doctor: "mmi doctor",
|
|
952
|
+
surfaces: [
|
|
953
|
+
{ id: "updater", npm: "@mutmutco/hub", bin: "mmi-hub" },
|
|
954
|
+
{ id: "cli", npm: "@mutmutco/cli", bin: "mmi-cli" },
|
|
955
|
+
{ id: "claude", kind: "agent-home", activation: "next launch" },
|
|
956
|
+
{ id: "codex", kind: "agent-home", activation: "next launch" },
|
|
957
|
+
{ id: "cursor", kind: "agent-home", activation: "next launch" },
|
|
958
|
+
{ id: "hermes", kind: "agent-home", activation: "next launch" },
|
|
959
|
+
{ id: "jervcode", kind: "agent-home", activation: "next launch" }
|
|
960
|
+
]
|
|
961
|
+
};
|
|
305
962
|
|
|
306
963
|
// src/state.ts
|
|
307
|
-
var
|
|
964
|
+
var import_node_fs5 = require("node:fs");
|
|
308
965
|
var import_node_os = require("node:os");
|
|
309
966
|
var import_node_path = require("node:path");
|
|
310
967
|
function stateRoot(env = process.env) {
|
|
@@ -318,8 +975,8 @@ function statePaths(env = process.env) {
|
|
|
318
975
|
}
|
|
319
976
|
function ensureState(env = process.env) {
|
|
320
977
|
const paths = statePaths(env);
|
|
321
|
-
(0,
|
|
322
|
-
(0,
|
|
978
|
+
(0, import_node_fs5.mkdirSync)(paths.staging, { recursive: true });
|
|
979
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(paths.gitCache), { recursive: true });
|
|
323
980
|
return paths;
|
|
324
981
|
}
|
|
325
982
|
var UpdaterBusy = class extends Error {
|
|
@@ -329,15 +986,15 @@ var LEASE_STALE_MS = 10 * 6e4;
|
|
|
329
986
|
function tryAcquire(path) {
|
|
330
987
|
const token = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
331
988
|
try {
|
|
332
|
-
const fd = (0,
|
|
333
|
-
(0,
|
|
334
|
-
(0,
|
|
989
|
+
const fd = (0, import_node_fs5.openSync)(path, "wx");
|
|
990
|
+
(0, import_node_fs5.writeFileSync)(fd, JSON.stringify({ token, pid: process.pid, ts: Date.now() }) + "\n");
|
|
991
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
335
992
|
return {
|
|
336
993
|
token,
|
|
337
994
|
release() {
|
|
338
995
|
try {
|
|
339
996
|
const current2 = readLease(path);
|
|
340
|
-
if (current2 && current2.token === token) (0,
|
|
997
|
+
if (current2 && current2.token === token) (0, import_node_fs5.unlinkSync)(path);
|
|
341
998
|
} catch {
|
|
342
999
|
}
|
|
343
1000
|
}
|
|
@@ -348,7 +1005,7 @@ function tryAcquire(path) {
|
|
|
348
1005
|
const current = readLease(path);
|
|
349
1006
|
if (current && Date.now() - current.ts > LEASE_STALE_MS) {
|
|
350
1007
|
try {
|
|
351
|
-
(0,
|
|
1008
|
+
(0, import_node_fs5.renameSync)(path, `${path}.stale-${Date.now()}`);
|
|
352
1009
|
return "stolen-stale";
|
|
353
1010
|
} catch {
|
|
354
1011
|
}
|
|
@@ -357,7 +1014,7 @@ function tryAcquire(path) {
|
|
|
357
1014
|
}
|
|
358
1015
|
function readLease(path) {
|
|
359
1016
|
try {
|
|
360
|
-
return JSON.parse((0,
|
|
1017
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
361
1018
|
} catch {
|
|
362
1019
|
return null;
|
|
363
1020
|
}
|
|
@@ -382,10 +1039,10 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
382
1039
|
}
|
|
383
1040
|
function appendJournal(path, event) {
|
|
384
1041
|
try {
|
|
385
|
-
(0,
|
|
386
|
-
const fd = (0,
|
|
387
|
-
(0,
|
|
388
|
-
(0,
|
|
1042
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
1043
|
+
const fd = (0, import_node_fs5.openSync)(path, "a");
|
|
1044
|
+
(0, import_node_fs5.writeFileSync)(fd, JSON.stringify(event) + "\n");
|
|
1045
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
389
1046
|
return true;
|
|
390
1047
|
} catch {
|
|
391
1048
|
return false;
|
|
@@ -404,7 +1061,7 @@ function journalHighWater(path, surface, events) {
|
|
|
404
1061
|
function parseJournal(path) {
|
|
405
1062
|
const events = [];
|
|
406
1063
|
try {
|
|
407
|
-
for (const line of (0,
|
|
1064
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
408
1065
|
if (!line.trim()) continue;
|
|
409
1066
|
try {
|
|
410
1067
|
events.push(JSON.parse(line));
|
|
@@ -418,7 +1075,7 @@ function parseJournal(path) {
|
|
|
418
1075
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
419
1076
|
let lastVerdict = null;
|
|
420
1077
|
try {
|
|
421
|
-
for (const line of (0,
|
|
1078
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
422
1079
|
if (!line.trim()) continue;
|
|
423
1080
|
try {
|
|
424
1081
|
const event = JSON.parse(line);
|
|
@@ -461,7 +1118,7 @@ function compareSemver(a, b) {
|
|
|
461
1118
|
return va.pre.join(".") < vb.pre.join(".") ? -1 : va.pre.join(".") > vb.pre.join(".") ? 1 : 0;
|
|
462
1119
|
}
|
|
463
1120
|
function fileExists(path) {
|
|
464
|
-
return (0,
|
|
1121
|
+
return (0, import_node_fs5.existsSync)(path);
|
|
465
1122
|
}
|
|
466
1123
|
|
|
467
1124
|
// src/reconcile.ts
|
|
@@ -469,7 +1126,7 @@ var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
|
469
1126
|
|
|
470
1127
|
// src/npm.ts
|
|
471
1128
|
var import_node_child_process = require("node:child_process");
|
|
472
|
-
var
|
|
1129
|
+
var import_node_fs6 = require("node:fs");
|
|
473
1130
|
var import_node_path2 = require("node:path");
|
|
474
1131
|
var cachedNpm = null;
|
|
475
1132
|
function resolveNpm(env = process.env) {
|
|
@@ -480,7 +1137,7 @@ function resolveNpm(env = process.env) {
|
|
|
480
1137
|
const first = where.stdout.split("\n").map((l) => l.trim()).find(Boolean);
|
|
481
1138
|
if (where.status === 0 && first) {
|
|
482
1139
|
const script = (0, import_node_path2.join)((0, import_node_path2.dirname)(first), "node_modules", "npm", "bin", "npm-cli.js");
|
|
483
|
-
if ((0,
|
|
1140
|
+
if ((0, import_node_fs6.existsSync)(script)) {
|
|
484
1141
|
cachedNpm = { kind: "node-script", script };
|
|
485
1142
|
return cachedNpm;
|
|
486
1143
|
}
|
|
@@ -489,13 +1146,13 @@ function resolveNpm(env = process.env) {
|
|
|
489
1146
|
}
|
|
490
1147
|
}
|
|
491
1148
|
const bundled = (0, import_node_path2.join)((0, import_node_path2.dirname)(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
492
|
-
if ((0,
|
|
1149
|
+
if ((0, import_node_fs6.existsSync)(bundled)) {
|
|
493
1150
|
cachedNpm = { kind: "node-script", script: bundled };
|
|
494
1151
|
return cachedNpm;
|
|
495
1152
|
}
|
|
496
1153
|
const located = (0, import_node_child_process.spawnSync)("/bin/sh", ["-lc", "command -v npm"], { encoding: "utf8", windowsHide: true });
|
|
497
1154
|
const resolved = (located.stdout ?? "").trim().split("\n").map((line) => line.trim()).find(Boolean);
|
|
498
|
-
if (located.status === 0 && resolved && (0,
|
|
1155
|
+
if (located.status === 0 && resolved && (0, import_node_fs6.existsSync)(resolved)) {
|
|
499
1156
|
cachedNpm = { kind: "command", command: resolved };
|
|
500
1157
|
return cachedNpm;
|
|
501
1158
|
}
|
|
@@ -577,7 +1234,7 @@ function npmVersions(packageName, env = process.env) {
|
|
|
577
1234
|
|
|
578
1235
|
// src/gate.ts
|
|
579
1236
|
var import_node_child_process2 = require("node:child_process");
|
|
580
|
-
var
|
|
1237
|
+
var import_node_fs7 = require("node:fs");
|
|
581
1238
|
function parseBom(text) {
|
|
582
1239
|
const bom = JSON.parse(text);
|
|
583
1240
|
if (typeof bom?.version !== "string" || typeof bom?.sourceCommit !== "string" || !Array.isArray(bom?.artifacts)) {
|
|
@@ -599,7 +1256,7 @@ function ensureBareCache(cachePath, repoUrl) {
|
|
|
599
1256
|
if (probe.status !== 0) {
|
|
600
1257
|
throw new GateEnvError("git is not available \u2014 the release gate needs a git client");
|
|
601
1258
|
}
|
|
602
|
-
if (!(0,
|
|
1259
|
+
if (!(0, import_node_fs7.existsSync)(cachePath)) {
|
|
603
1260
|
const init = (0, import_node_child_process2.spawnSync)("git", ["init", "--bare", cachePath], { encoding: "utf8", windowsHide: true });
|
|
604
1261
|
if (init.status !== 0) throw new GateEnvError(`cannot create bare cache at ${cachePath}: ${init.stderr}`);
|
|
605
1262
|
}
|
|
@@ -706,24 +1363,24 @@ async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
706
1363
|
}
|
|
707
1364
|
|
|
708
1365
|
// src/surfaces.ts
|
|
709
|
-
var
|
|
1366
|
+
var import_node_fs12 = require("node:fs");
|
|
710
1367
|
var import_node_os3 = require("node:os");
|
|
711
1368
|
var import_node_path7 = require("node:path");
|
|
712
1369
|
|
|
713
1370
|
// src/arm-host-plugins.ts
|
|
714
1371
|
var import_node_child_process5 = require("node:child_process");
|
|
715
|
-
var
|
|
1372
|
+
var import_node_fs11 = require("node:fs");
|
|
716
1373
|
var import_node_os2 = require("node:os");
|
|
717
1374
|
var import_node_path6 = require("node:path");
|
|
718
1375
|
|
|
719
1376
|
// src/arm-npm-global.ts
|
|
720
|
-
var
|
|
1377
|
+
var import_node_fs9 = require("node:fs");
|
|
721
1378
|
var import_node_child_process4 = require("node:child_process");
|
|
722
1379
|
var import_node_path4 = require("node:path");
|
|
723
1380
|
|
|
724
1381
|
// src/pending.ts
|
|
725
1382
|
var import_node_child_process3 = require("node:child_process");
|
|
726
|
-
var
|
|
1383
|
+
var import_node_fs8 = require("node:fs");
|
|
727
1384
|
var import_node_path3 = require("node:path");
|
|
728
1385
|
function hubDistPath(env) {
|
|
729
1386
|
const root = globalRoot(env);
|
|
@@ -738,7 +1395,7 @@ function watcherLockPath(paths) {
|
|
|
738
1395
|
}
|
|
739
1396
|
function readPending(paths) {
|
|
740
1397
|
try {
|
|
741
|
-
const parsed = JSON.parse((0,
|
|
1398
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(pendingPath(paths), "utf8"));
|
|
742
1399
|
if (!Array.isArray(parsed)) return [];
|
|
743
1400
|
return parsed.filter((row) => Boolean(row) && typeof row === "object" && typeof row.surface === "string" && typeof row.target === "string" && Array.isArray(row.images) && Array.isArray(row.dirs));
|
|
744
1401
|
} catch {
|
|
@@ -746,12 +1403,12 @@ function readPending(paths) {
|
|
|
746
1403
|
}
|
|
747
1404
|
}
|
|
748
1405
|
function writePending(paths, records) {
|
|
749
|
-
(0,
|
|
1406
|
+
(0, import_node_fs8.mkdirSync)(paths.root, { recursive: true });
|
|
750
1407
|
const path = pendingPath(paths);
|
|
751
1408
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
752
|
-
(0,
|
|
1409
|
+
(0, import_node_fs8.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
753
1410
|
`, "utf8");
|
|
754
|
-
(0,
|
|
1411
|
+
(0, import_node_fs8.renameSync)(tmp, path);
|
|
755
1412
|
}
|
|
756
1413
|
function clearPending(paths, surface) {
|
|
757
1414
|
const records = readPending(paths);
|
|
@@ -820,7 +1477,7 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
|
|
|
820
1477
|
const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
|
|
821
1478
|
if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
|
|
822
1479
|
const hubDist = seam.hubDist ?? hubDistPath(env);
|
|
823
|
-
if (!hubDist || !(0,
|
|
1480
|
+
if (!hubDist || !(0, import_node_fs8.existsSync)(hubDist)) return { scheduled: false, reason: "the installed hub bundle is unresolvable \u2014 the hourly tick remains the retry" };
|
|
824
1481
|
const command = buildWatcherCommand({
|
|
825
1482
|
images: [...new Set(records.flatMap((row) => row.images))],
|
|
826
1483
|
dirs: [...new Set(records.flatMap((row) => row.dirs))],
|
|
@@ -880,7 +1537,7 @@ function tail(output) {
|
|
|
880
1537
|
function manifestVersion(packageRoot) {
|
|
881
1538
|
if (!packageRoot) return null;
|
|
882
1539
|
try {
|
|
883
|
-
const version = JSON.parse((0,
|
|
1540
|
+
const version = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
884
1541
|
return typeof version === "string" ? version : null;
|
|
885
1542
|
} catch {
|
|
886
1543
|
return null;
|
|
@@ -891,8 +1548,8 @@ function stagePackage(options) {
|
|
|
891
1548
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
892
1549
|
const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
|
|
893
1550
|
if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
|
|
894
|
-
(0,
|
|
895
|
-
(0,
|
|
1551
|
+
(0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
|
|
1552
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
896
1553
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
897
1554
|
if (install.status !== 0) {
|
|
898
1555
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -900,7 +1557,7 @@ function stagePackage(options) {
|
|
|
900
1557
|
}
|
|
901
1558
|
let stagedManifestVersion;
|
|
902
1559
|
try {
|
|
903
|
-
stagedManifestVersion = JSON.parse((0,
|
|
1560
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
904
1561
|
} catch {
|
|
905
1562
|
}
|
|
906
1563
|
if (stagedManifestVersion !== target) {
|
|
@@ -958,11 +1615,11 @@ function npmGlobalArm(options) {
|
|
|
958
1615
|
}
|
|
959
1616
|
|
|
960
1617
|
// src/launch-staging.ts
|
|
961
|
-
var
|
|
1618
|
+
var import_node_fs10 = require("node:fs");
|
|
962
1619
|
var import_node_path5 = require("node:path");
|
|
963
1620
|
function readLaunchTarget(path) {
|
|
964
1621
|
try {
|
|
965
|
-
const parsed = JSON.parse((0,
|
|
1622
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
966
1623
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
967
1624
|
const row = parsed;
|
|
968
1625
|
if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
|
|
@@ -972,15 +1629,15 @@ function readLaunchTarget(path) {
|
|
|
972
1629
|
}
|
|
973
1630
|
}
|
|
974
1631
|
function writeLaunchTarget(path, record) {
|
|
975
|
-
(0,
|
|
1632
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
|
|
976
1633
|
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
977
|
-
(0,
|
|
1634
|
+
(0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
|
|
978
1635
|
`, "utf8");
|
|
979
|
-
(0,
|
|
1636
|
+
(0, import_node_fs10.renameSync)(tmp, path);
|
|
980
1637
|
}
|
|
981
1638
|
function clearLaunchTarget(path) {
|
|
982
1639
|
try {
|
|
983
|
-
(0,
|
|
1640
|
+
(0, import_node_fs10.rmSync)(path, { force: true });
|
|
984
1641
|
} catch {
|
|
985
1642
|
}
|
|
986
1643
|
}
|
|
@@ -1001,7 +1658,7 @@ function noteLaunchStaged(paths, env, options, seam = {}) {
|
|
|
1001
1658
|
}
|
|
1002
1659
|
function probeLaunchTargetVersion(launchTargetPath, versionAt) {
|
|
1003
1660
|
const record = readLaunchTarget(launchTargetPath);
|
|
1004
|
-
if (!record || !(0,
|
|
1661
|
+
if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
|
|
1005
1662
|
const version = versionAt(record.incoming);
|
|
1006
1663
|
return version === record.target ? record.target : null;
|
|
1007
1664
|
}
|
|
@@ -1059,7 +1716,7 @@ function ensureClaudeMarketplace(env) {
|
|
|
1059
1716
|
const path = claudeMarketplacesPath(env);
|
|
1060
1717
|
let body;
|
|
1061
1718
|
try {
|
|
1062
|
-
body = JSON.parse((0,
|
|
1719
|
+
body = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
1063
1720
|
} catch (error) {
|
|
1064
1721
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
1065
1722
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -1087,7 +1744,7 @@ function claudePluginsRoot(env) {
|
|
|
1087
1744
|
}
|
|
1088
1745
|
function claudeMarkerVersion(root) {
|
|
1089
1746
|
try {
|
|
1090
|
-
const version = JSON.parse((0,
|
|
1747
|
+
const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
|
|
1091
1748
|
return typeof version === "string" ? version : null;
|
|
1092
1749
|
} catch {
|
|
1093
1750
|
return null;
|
|
@@ -1184,14 +1841,14 @@ function claudeArm(options) {
|
|
|
1184
1841
|
return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
|
|
1185
1842
|
}
|
|
1186
1843
|
try {
|
|
1187
|
-
(0,
|
|
1188
|
-
(0,
|
|
1189
|
-
(0,
|
|
1844
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(pluginsRoot, "staging"), { recursive: true });
|
|
1845
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1846
|
+
(0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1190
1847
|
} catch (error) {
|
|
1191
1848
|
return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
|
|
1192
1849
|
}
|
|
1193
1850
|
if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
|
|
1194
|
-
(0,
|
|
1851
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1195
1852
|
return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
|
|
1196
1853
|
}
|
|
1197
1854
|
}
|
|
@@ -1223,10 +1880,10 @@ function claudeArm(options) {
|
|
|
1223
1880
|
}
|
|
1224
1881
|
const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
|
|
1225
1882
|
try {
|
|
1226
|
-
if (!(0,
|
|
1227
|
-
(0,
|
|
1228
|
-
(0,
|
|
1229
|
-
(0,
|
|
1883
|
+
if (!(0, import_node_fs11.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
|
|
1884
|
+
(0, import_node_fs11.rmSync)(cacheTarget, { recursive: true, force: true });
|
|
1885
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(cacheTarget, ".."), { recursive: true });
|
|
1886
|
+
(0, import_node_fs11.cpSync)(incoming, cacheTarget, { recursive: true });
|
|
1230
1887
|
}
|
|
1231
1888
|
} catch (error) {
|
|
1232
1889
|
return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
|
|
@@ -1254,7 +1911,7 @@ function claudeArm(options) {
|
|
|
1254
1911
|
}
|
|
1255
1912
|
clearLaunchTarget(launchTargetPath);
|
|
1256
1913
|
try {
|
|
1257
|
-
(0,
|
|
1914
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1258
1915
|
} catch {
|
|
1259
1916
|
}
|
|
1260
1917
|
return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
|
|
@@ -1367,21 +2024,21 @@ function enumerateSurfaces(env = process.env) {
|
|
|
1367
2024
|
{ id: "hermes", marker: `${hermesHostCommand(env)} --version` }
|
|
1368
2025
|
].map((probe) => ({
|
|
1369
2026
|
...probe,
|
|
1370
|
-
present: probe.id === "hermes" ? hermesHostPresent(env) : (0,
|
|
2027
|
+
present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs12.existsSync)(probe.marker)
|
|
1371
2028
|
}));
|
|
1372
2029
|
}
|
|
1373
2030
|
|
|
1374
2031
|
// src/arm-cursor.ts
|
|
1375
|
-
var
|
|
2032
|
+
var import_node_fs15 = require("node:fs");
|
|
1376
2033
|
var import_node_child_process6 = require("node:child_process");
|
|
1377
2034
|
var import_node_os5 = require("node:os");
|
|
1378
2035
|
var import_node_path8 = require("node:path");
|
|
1379
2036
|
|
|
1380
2037
|
// src/compat.ts
|
|
1381
|
-
var
|
|
2038
|
+
var import_node_fs13 = require("node:fs");
|
|
1382
2039
|
function readManifestCompat(manifestPath) {
|
|
1383
2040
|
try {
|
|
1384
|
-
const parsed = JSON.parse((0,
|
|
2041
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(manifestPath, "utf8"));
|
|
1385
2042
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
1386
2043
|
} catch {
|
|
1387
2044
|
return void 0;
|
|
@@ -1389,7 +2046,7 @@ function readManifestCompat(manifestPath) {
|
|
|
1389
2046
|
}
|
|
1390
2047
|
|
|
1391
2048
|
// src/host-doc-lock.ts
|
|
1392
|
-
var
|
|
2049
|
+
var import_node_fs14 = require("node:fs");
|
|
1393
2050
|
var import_node_os4 = require("node:os");
|
|
1394
2051
|
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
1395
2052
|
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
@@ -1403,7 +2060,7 @@ function lockContent(options) {
|
|
|
1403
2060
|
}
|
|
1404
2061
|
function readHolder(lockPath) {
|
|
1405
2062
|
try {
|
|
1406
|
-
const parsed = JSON.parse((0,
|
|
2063
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(lockPath, "utf8"));
|
|
1407
2064
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
1408
2065
|
return parsed;
|
|
1409
2066
|
} catch {
|
|
@@ -1415,7 +2072,7 @@ function lockAgeMs(lockPath, now) {
|
|
|
1415
2072
|
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
1416
2073
|
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
1417
2074
|
try {
|
|
1418
|
-
return Math.max(0, now - (0,
|
|
2075
|
+
return Math.max(0, now - (0, import_node_fs14.statSync)(lockPath).mtimeMs);
|
|
1419
2076
|
} catch {
|
|
1420
2077
|
return 0;
|
|
1421
2078
|
}
|
|
@@ -1438,9 +2095,9 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1438
2095
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1439
2096
|
let fd;
|
|
1440
2097
|
try {
|
|
1441
|
-
fd = (0,
|
|
1442
|
-
(0,
|
|
1443
|
-
(0,
|
|
2098
|
+
fd = (0, import_node_fs14.openSync)(lockPath, "wx");
|
|
2099
|
+
(0, import_node_fs14.writeSync)(fd, content);
|
|
2100
|
+
(0, import_node_fs14.closeSync)(fd);
|
|
1444
2101
|
return {
|
|
1445
2102
|
documentPath,
|
|
1446
2103
|
lockPath,
|
|
@@ -1453,11 +2110,11 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1453
2110
|
} catch (error) {
|
|
1454
2111
|
if (fd !== void 0) {
|
|
1455
2112
|
try {
|
|
1456
|
-
(0,
|
|
2113
|
+
(0, import_node_fs14.closeSync)(fd);
|
|
1457
2114
|
} catch {
|
|
1458
2115
|
}
|
|
1459
2116
|
try {
|
|
1460
|
-
(0,
|
|
2117
|
+
(0, import_node_fs14.unlinkSync)(lockPath);
|
|
1461
2118
|
} catch {
|
|
1462
2119
|
}
|
|
1463
2120
|
}
|
|
@@ -1466,12 +2123,12 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1466
2123
|
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
1467
2124
|
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
1468
2125
|
try {
|
|
1469
|
-
(0,
|
|
2126
|
+
(0, import_node_fs14.renameSync)(lockPath, aside);
|
|
1470
2127
|
} catch {
|
|
1471
2128
|
continue;
|
|
1472
2129
|
}
|
|
1473
2130
|
try {
|
|
1474
|
-
(0,
|
|
2131
|
+
(0, import_node_fs14.rmSync)(aside, { force: true });
|
|
1475
2132
|
} catch {
|
|
1476
2133
|
}
|
|
1477
2134
|
continue;
|
|
@@ -1484,7 +2141,7 @@ function releaseSharedDocLock(handle) {
|
|
|
1484
2141
|
try {
|
|
1485
2142
|
const holder = readHolder(handle.lockPath);
|
|
1486
2143
|
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
1487
|
-
(0,
|
|
2144
|
+
(0, import_node_fs14.unlinkSync)(handle.lockPath);
|
|
1488
2145
|
}
|
|
1489
2146
|
} catch {
|
|
1490
2147
|
}
|
|
@@ -1509,11 +2166,11 @@ function cursorPluginsRoot() {
|
|
|
1509
2166
|
return (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
|
|
1510
2167
|
}
|
|
1511
2168
|
function treeHealthy(root) {
|
|
1512
|
-
return TREE_FILES.every((rel) => (0,
|
|
2169
|
+
return TREE_FILES.every((rel) => (0, import_node_fs15.existsSync)((0, import_node_path8.join)(root, ...rel.split("/"))));
|
|
1513
2170
|
}
|
|
1514
2171
|
function readJson(root, rel) {
|
|
1515
2172
|
try {
|
|
1516
|
-
return JSON.parse((0,
|
|
2173
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path8.join)(root, ...rel.split("/")), "utf8"));
|
|
1517
2174
|
} catch {
|
|
1518
2175
|
return null;
|
|
1519
2176
|
}
|
|
@@ -1537,7 +2194,7 @@ function message(error) {
|
|
|
1537
2194
|
}
|
|
1538
2195
|
function discard(path) {
|
|
1539
2196
|
try {
|
|
1540
|
-
(0,
|
|
2197
|
+
(0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
|
|
1541
2198
|
} catch {
|
|
1542
2199
|
}
|
|
1543
2200
|
}
|
|
@@ -1591,10 +2248,10 @@ var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
|
1591
2248
|
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
1592
2249
|
const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1593
2250
|
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
1594
|
-
if (!(0,
|
|
2251
|
+
if (!(0, import_node_fs15.existsSync)(path)) return "current";
|
|
1595
2252
|
let existing;
|
|
1596
2253
|
try {
|
|
1597
|
-
existing = JSON.parse((0,
|
|
2254
|
+
existing = JSON.parse((0, import_node_fs15.readFileSync)(path, "utf8"));
|
|
1598
2255
|
} catch {
|
|
1599
2256
|
return "user-conflict";
|
|
1600
2257
|
}
|
|
@@ -1602,8 +2259,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
|
1602
2259
|
if (!stripped) return "user-conflict";
|
|
1603
2260
|
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
1604
2261
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1605
|
-
(0,
|
|
1606
|
-
(0,
|
|
2262
|
+
(0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
2263
|
+
(0, import_node_fs15.renameSync)(tmp, path);
|
|
1607
2264
|
return "updated";
|
|
1608
2265
|
});
|
|
1609
2266
|
}
|
|
@@ -1627,15 +2284,15 @@ function writeRollbackPointer(root, entry) {
|
|
|
1627
2284
|
const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
|
|
1628
2285
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1629
2286
|
try {
|
|
1630
|
-
(0,
|
|
1631
|
-
(0,
|
|
2287
|
+
(0, import_node_fs15.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
2288
|
+
(0, import_node_fs15.renameSync)(tmp, path);
|
|
1632
2289
|
} catch {
|
|
1633
2290
|
discard(tmp);
|
|
1634
2291
|
}
|
|
1635
2292
|
}
|
|
1636
2293
|
function mtimeMs(path) {
|
|
1637
2294
|
try {
|
|
1638
|
-
return (0,
|
|
2295
|
+
return (0, import_node_fs15.statSync)(path).mtimeMs;
|
|
1639
2296
|
} catch {
|
|
1640
2297
|
return 0;
|
|
1641
2298
|
}
|
|
@@ -1644,13 +2301,13 @@ function pruneQuarantine(root, keep) {
|
|
|
1644
2301
|
const dir = (0, import_node_path8.join)(root, "quarantine");
|
|
1645
2302
|
let generations;
|
|
1646
2303
|
try {
|
|
1647
|
-
generations = (0,
|
|
2304
|
+
generations = (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("mmi-")).map((entry) => (0, import_node_path8.join)(dir, entry.name)).sort((a, b) => mtimeMs(b) - mtimeMs(a));
|
|
1648
2305
|
} catch {
|
|
1649
2306
|
return 0;
|
|
1650
2307
|
}
|
|
1651
2308
|
let stranded = 0;
|
|
1652
2309
|
try {
|
|
1653
|
-
for (const entry of (0,
|
|
2310
|
+
for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
|
|
1654
2311
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1655
2312
|
const path = (0, import_node_path8.join)(dir, entry.name);
|
|
1656
2313
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
@@ -1670,16 +2327,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1670
2327
|
}
|
|
1671
2328
|
function restorePrevious(live, quarantined, broken) {
|
|
1672
2329
|
try {
|
|
1673
|
-
(0,
|
|
2330
|
+
(0, import_node_fs15.renameSync)(live, broken);
|
|
1674
2331
|
} catch {
|
|
1675
2332
|
return "kept-broken";
|
|
1676
2333
|
}
|
|
1677
2334
|
try {
|
|
1678
|
-
(0,
|
|
2335
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1679
2336
|
return "restored";
|
|
1680
2337
|
} catch {
|
|
1681
2338
|
try {
|
|
1682
|
-
(0,
|
|
2339
|
+
(0, import_node_fs15.renameSync)(broken, live);
|
|
1683
2340
|
return "kept-broken";
|
|
1684
2341
|
} catch {
|
|
1685
2342
|
return "lost";
|
|
@@ -1714,7 +2371,7 @@ function cursorArm(options) {
|
|
|
1714
2371
|
if (installed === target && treeHealthy(live)) {
|
|
1715
2372
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1716
2373
|
}
|
|
1717
|
-
if ((0,
|
|
2374
|
+
if ((0, import_node_fs15.existsSync)(live) && !mmiOwned(live)) {
|
|
1718
2375
|
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)`);
|
|
1719
2376
|
}
|
|
1720
2377
|
if (dryRun) {
|
|
@@ -1732,12 +2389,12 @@ function cursorArm(options) {
|
|
|
1732
2389
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1733
2390
|
let incoming = priorLaunch?.target === target && priorLaunch.incoming.startsWith((0, import_node_path8.join)(root, "staging")) && manifest(priorLaunch.incoming)?.version === target && treeHealthy(priorLaunch.incoming) ? priorLaunch.incoming : (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1734
2391
|
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
1735
|
-
if (!reuseIncoming || !(0,
|
|
2392
|
+
if (!reuseIncoming || !(0, import_node_fs15.existsSync)(incoming)) {
|
|
1736
2393
|
incoming = (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1737
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
2394
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
|
|
1738
2395
|
try {
|
|
1739
2396
|
discard(incoming);
|
|
1740
|
-
(0,
|
|
2397
|
+
(0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1741
2398
|
} catch (error) {
|
|
1742
2399
|
discard(incoming);
|
|
1743
2400
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
@@ -1761,7 +2418,7 @@ function cursorArm(options) {
|
|
|
1761
2418
|
}
|
|
1762
2419
|
const stamp = Date.now();
|
|
1763
2420
|
const quarantined = (0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1764
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
2421
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
|
|
1765
2422
|
const started = cursorHostEvidence(env);
|
|
1766
2423
|
if (started) {
|
|
1767
2424
|
const pending = noteLaunchStaged(paths, env, {
|
|
@@ -1776,15 +2433,15 @@ function cursorArm(options) {
|
|
|
1776
2433
|
}
|
|
1777
2434
|
let displaced = false;
|
|
1778
2435
|
try {
|
|
1779
|
-
if ((0,
|
|
1780
|
-
(0,
|
|
2436
|
+
if ((0, import_node_fs15.existsSync)(live)) {
|
|
2437
|
+
(0, import_node_fs15.renameSync)(live, quarantined);
|
|
1781
2438
|
displaced = true;
|
|
1782
2439
|
}
|
|
1783
|
-
(0,
|
|
2440
|
+
(0, import_node_fs15.renameSync)(incoming, live);
|
|
1784
2441
|
} catch (error) {
|
|
1785
|
-
if (displaced && !(0,
|
|
2442
|
+
if (displaced && !(0, import_node_fs15.existsSync)(live)) {
|
|
1786
2443
|
try {
|
|
1787
|
-
(0,
|
|
2444
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1788
2445
|
} catch {
|
|
1789
2446
|
}
|
|
1790
2447
|
}
|
|
@@ -1812,7 +2469,7 @@ function probeCursorLaunchStaging() {
|
|
|
1812
2469
|
}
|
|
1813
2470
|
|
|
1814
2471
|
// src/arm-jervcode.ts
|
|
1815
|
-
var
|
|
2472
|
+
var import_node_fs16 = require("node:fs");
|
|
1816
2473
|
var import_node_path9 = require("node:path");
|
|
1817
2474
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1818
2475
|
function hostEnv(env) {
|
|
@@ -1824,7 +2481,7 @@ function hostCommands(_env) {
|
|
|
1824
2481
|
}
|
|
1825
2482
|
function readPackageSpecs(env) {
|
|
1826
2483
|
try {
|
|
1827
|
-
const settings = JSON.parse((0,
|
|
2484
|
+
const settings = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
1828
2485
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1829
2486
|
} catch {
|
|
1830
2487
|
return null;
|
|
@@ -1832,7 +2489,7 @@ function readPackageSpecs(env) {
|
|
|
1832
2489
|
}
|
|
1833
2490
|
function installedVersion(env) {
|
|
1834
2491
|
try {
|
|
1835
|
-
const manifest2 = JSON.parse((0,
|
|
2492
|
+
const manifest2 = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "npm", "node_modules", ...PI_PACKAGE.split("/"), "package.json"), "utf8"));
|
|
1836
2493
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1837
2494
|
} catch {
|
|
1838
2495
|
return null;
|
|
@@ -1952,7 +2609,7 @@ function jervcodeArm(options) {
|
|
|
1952
2609
|
}
|
|
1953
2610
|
|
|
1954
2611
|
// src/arm-hermes.ts
|
|
1955
|
-
var
|
|
2612
|
+
var import_node_fs17 = require("node:fs");
|
|
1956
2613
|
var import_node_child_process7 = require("node:child_process");
|
|
1957
2614
|
var import_node_path10 = require("node:path");
|
|
1958
2615
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
@@ -1960,7 +2617,7 @@ var MANIFEST2 = "plugin.yaml";
|
|
|
1960
2617
|
var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
|
|
1961
2618
|
function yamlVersion(root) {
|
|
1962
2619
|
try {
|
|
1963
|
-
const text = (0,
|
|
2620
|
+
const text = (0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, MANIFEST2), "utf8");
|
|
1964
2621
|
try {
|
|
1965
2622
|
const version = JSON.parse(text).version;
|
|
1966
2623
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1974,7 +2631,7 @@ function yamlVersion(root) {
|
|
|
1974
2631
|
}
|
|
1975
2632
|
function packageTreeMarkers(root) {
|
|
1976
2633
|
try {
|
|
1977
|
-
const files = JSON.parse((0,
|
|
2634
|
+
const files = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).files;
|
|
1978
2635
|
if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1979
2636
|
return files.map((entry) => entry.trim());
|
|
1980
2637
|
}
|
|
@@ -1984,9 +2641,9 @@ function packageTreeMarkers(root) {
|
|
|
1984
2641
|
}
|
|
1985
2642
|
function markerPresent(root, rel) {
|
|
1986
2643
|
const path = (0, import_node_path10.join)(root, ...rel.split("/"));
|
|
1987
|
-
if (!(0,
|
|
2644
|
+
if (!(0, import_node_fs17.existsSync)(path)) return false;
|
|
1988
2645
|
try {
|
|
1989
|
-
const stat = (0,
|
|
2646
|
+
const stat = (0, import_node_fs17.statSync)(path);
|
|
1990
2647
|
return stat.isFile() || stat.isDirectory();
|
|
1991
2648
|
} catch {
|
|
1992
2649
|
return false;
|
|
@@ -2005,7 +2662,7 @@ function treeIncompleteDetail(root, label) {
|
|
|
2005
2662
|
}
|
|
2006
2663
|
function packageIdentity(root) {
|
|
2007
2664
|
try {
|
|
2008
|
-
return JSON.parse((0,
|
|
2665
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
2009
2666
|
} catch {
|
|
2010
2667
|
return false;
|
|
2011
2668
|
}
|
|
@@ -2020,7 +2677,7 @@ function mmiOwned2(root) {
|
|
|
2020
2677
|
}
|
|
2021
2678
|
function discard2(path) {
|
|
2022
2679
|
try {
|
|
2023
|
-
(0,
|
|
2680
|
+
(0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
|
|
2024
2681
|
} catch {
|
|
2025
2682
|
}
|
|
2026
2683
|
}
|
|
@@ -2056,15 +2713,15 @@ function materializeHermesSkills(home) {
|
|
|
2056
2713
|
const skillsRoot = (0, import_node_path10.join)(home, "skills");
|
|
2057
2714
|
const targetRoot = (0, import_node_path10.join)(skillsRoot, "mmi");
|
|
2058
2715
|
try {
|
|
2059
|
-
if (!(0,
|
|
2060
|
-
const names = (0,
|
|
2716
|
+
if (!(0, import_node_fs17.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
2717
|
+
const names = (0, import_node_fs17.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
2061
2718
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
2062
|
-
(0,
|
|
2719
|
+
(0, import_node_fs17.mkdirSync)(skillsRoot, { recursive: true });
|
|
2063
2720
|
const incoming = (0, import_node_path10.join)(skillsRoot, ".mmi-incoming");
|
|
2064
2721
|
discard2(incoming);
|
|
2065
|
-
(0,
|
|
2722
|
+
(0, import_node_fs17.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
2066
2723
|
discard2(targetRoot);
|
|
2067
|
-
(0,
|
|
2724
|
+
(0, import_node_fs17.renameSync)(incoming, targetRoot);
|
|
2068
2725
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
2069
2726
|
} catch (error) {
|
|
2070
2727
|
return { ok: false, detail: message2(error) };
|
|
@@ -2086,7 +2743,7 @@ function hermesHomes(env = process.env) {
|
|
|
2086
2743
|
const root = hermesHome(env);
|
|
2087
2744
|
const homes = [root];
|
|
2088
2745
|
try {
|
|
2089
|
-
for (const entry of (0,
|
|
2746
|
+
for (const entry of (0, import_node_fs17.readdirSync)((0, import_node_path10.join)(root, "profiles"), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2090
2747
|
if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path10.join)(root, "profiles", entry.name));
|
|
2091
2748
|
}
|
|
2092
2749
|
} catch {
|
|
@@ -2135,7 +2792,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2135
2792
|
detail,
|
|
2136
2793
|
launchStaged: launchStaged || void 0
|
|
2137
2794
|
});
|
|
2138
|
-
if ((0,
|
|
2795
|
+
if ((0, import_node_fs17.existsSync)(live) && !mmiOwned2(live)) {
|
|
2139
2796
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
2140
2797
|
}
|
|
2141
2798
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -2167,10 +2824,10 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2167
2824
|
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
|
|
2168
2825
|
if (!reuseIncoming) {
|
|
2169
2826
|
try {
|
|
2170
|
-
(0,
|
|
2171
|
-
(0,
|
|
2827
|
+
(0, import_node_fs17.mkdirSync)(parent, { recursive: true });
|
|
2828
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "quarantine"), { recursive: true });
|
|
2172
2829
|
discard2(incomingRoot);
|
|
2173
|
-
(0,
|
|
2830
|
+
(0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
2174
2831
|
} catch (error) {
|
|
2175
2832
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
2176
2833
|
}
|
|
@@ -2185,7 +2842,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2185
2842
|
return result(installed, options.target, "defer", incomingTree);
|
|
2186
2843
|
}
|
|
2187
2844
|
}
|
|
2188
|
-
const displaced = (0,
|
|
2845
|
+
const displaced = (0, import_node_fs17.existsSync)(live);
|
|
2189
2846
|
const busy = displaced ? hermesHostEvidence(env, home, options.processListProvider) : null;
|
|
2190
2847
|
if (busy) {
|
|
2191
2848
|
const pending = noteLaunchStaged(options.paths, env, {
|
|
@@ -2200,11 +2857,11 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2200
2857
|
return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
|
|
2201
2858
|
}
|
|
2202
2859
|
try {
|
|
2203
|
-
if (displaced) (0,
|
|
2204
|
-
(0,
|
|
2860
|
+
if (displaced) (0, import_node_fs17.renameSync)(live, quarantine);
|
|
2861
|
+
(0, import_node_fs17.renameSync)(incoming, live);
|
|
2205
2862
|
} catch (error) {
|
|
2206
|
-
if (displaced && !(0,
|
|
2207
|
-
(0,
|
|
2863
|
+
if (displaced && !(0, import_node_fs17.existsSync)(live)) try {
|
|
2864
|
+
(0, import_node_fs17.renameSync)(quarantine, live);
|
|
2208
2865
|
} catch {
|
|
2209
2866
|
}
|
|
2210
2867
|
discard2(incomingRoot);
|
|
@@ -2218,9 +2875,9 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2218
2875
|
const broken = (0, import_node_path10.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
2219
2876
|
const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
|
|
2220
2877
|
try {
|
|
2221
|
-
(0,
|
|
2222
|
-
(0,
|
|
2223
|
-
if (displaced) (0,
|
|
2878
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "rejected"), { recursive: true });
|
|
2879
|
+
(0, import_node_fs17.renameSync)(live, broken);
|
|
2880
|
+
if (displaced) (0, import_node_fs17.renameSync)(quarantine, live);
|
|
2224
2881
|
} catch (error) {
|
|
2225
2882
|
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
2226
2883
|
}
|
|
@@ -2239,7 +2896,7 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
2239
2896
|
}
|
|
2240
2897
|
|
|
2241
2898
|
// src/reap.ts
|
|
2242
|
-
var
|
|
2899
|
+
var import_node_fs18 = require("node:fs");
|
|
2243
2900
|
var import_node_path11 = require("node:path");
|
|
2244
2901
|
var import_node_perf_hooks = require("node:perf_hooks");
|
|
2245
2902
|
function elapsedMs(startedAt) {
|
|
@@ -2249,7 +2906,7 @@ var KEEP_GENERATIONS2 = 2;
|
|
|
2249
2906
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
2250
2907
|
function listDirs(path) {
|
|
2251
2908
|
try {
|
|
2252
|
-
return (0,
|
|
2909
|
+
return (0, import_node_fs18.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2253
2910
|
} catch {
|
|
2254
2911
|
return [];
|
|
2255
2912
|
}
|
|
@@ -2270,7 +2927,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2270
2927
|
continue;
|
|
2271
2928
|
}
|
|
2272
2929
|
try {
|
|
2273
|
-
(0,
|
|
2930
|
+
(0, import_node_fs18.rmSync)((0, import_node_path11.join)(set, version), { recursive: true, force: true });
|
|
2274
2931
|
reaped += 1;
|
|
2275
2932
|
} catch (error) {
|
|
2276
2933
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2303,7 +2960,7 @@ function reapScratch(context, dryRun) {
|
|
|
2303
2960
|
for (const [dir, patterns] of scanned) {
|
|
2304
2961
|
let entries;
|
|
2305
2962
|
try {
|
|
2306
|
-
entries = (0,
|
|
2963
|
+
entries = (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true });
|
|
2307
2964
|
} catch {
|
|
2308
2965
|
unreadable.push(dir);
|
|
2309
2966
|
continue;
|
|
@@ -2314,7 +2971,7 @@ function reapScratch(context, dryRun) {
|
|
|
2314
2971
|
const path = (0, import_node_path11.join)(dir, entry.name);
|
|
2315
2972
|
let ageMs;
|
|
2316
2973
|
try {
|
|
2317
|
-
ageMs = now - (0,
|
|
2974
|
+
ageMs = now - (0, import_node_fs18.statSync)(path).mtimeMs;
|
|
2318
2975
|
} catch {
|
|
2319
2976
|
continue;
|
|
2320
2977
|
}
|
|
@@ -2324,7 +2981,7 @@ function reapScratch(context, dryRun) {
|
|
|
2324
2981
|
continue;
|
|
2325
2982
|
}
|
|
2326
2983
|
try {
|
|
2327
|
-
(0,
|
|
2984
|
+
(0, import_node_fs18.rmSync)(path, { force: true });
|
|
2328
2985
|
reaped += 1;
|
|
2329
2986
|
} catch (error) {
|
|
2330
2987
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2381,7 +3038,7 @@ async function reconcile(options) {
|
|
|
2381
3038
|
const recordArm = (arm, durationMs) => {
|
|
2382
3039
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2383
3040
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat, durationMs });
|
|
2384
|
-
options.onArm?.(arm);
|
|
3041
|
+
options.onArm?.({ ...arm, ...durationMs === void 0 ? {} : { durationMs } });
|
|
2385
3042
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2386
3043
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
2387
3044
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
@@ -2546,7 +3203,7 @@ async function reconcile(options) {
|
|
|
2546
3203
|
|
|
2547
3204
|
// src/scheduler.ts
|
|
2548
3205
|
var import_node_child_process8 = require("node:child_process");
|
|
2549
|
-
var
|
|
3206
|
+
var import_node_fs19 = require("node:fs");
|
|
2550
3207
|
var import_node_os6 = require("node:os");
|
|
2551
3208
|
var import_node_path12 = require("node:path");
|
|
2552
3209
|
var TASK_NAME = "MMI Fleet Updater";
|
|
@@ -2706,7 +3363,7 @@ WantedBy=timers.target
|
|
|
2706
3363
|
}
|
|
2707
3364
|
function readFileOrNull(path) {
|
|
2708
3365
|
try {
|
|
2709
|
-
return (0,
|
|
3366
|
+
return (0, import_node_fs19.readFileSync)(path, "utf8");
|
|
2710
3367
|
} catch {
|
|
2711
3368
|
return null;
|
|
2712
3369
|
}
|
|
@@ -2758,22 +3415,22 @@ function windowsSchedulerStatus(env) {
|
|
|
2758
3415
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2759
3416
|
const info = taskRunInfo();
|
|
2760
3417
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2761
|
-
const
|
|
3418
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
2762
3419
|
if (!query.stdout.includes(launcherPath)) {
|
|
2763
|
-
return
|
|
3420
|
+
return fail2(`"${TASK_NAME}" action does not run the hidden launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2764
3421
|
}
|
|
2765
3422
|
const launcher = readFileOrNull(launcherPath);
|
|
2766
3423
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2767
|
-
return
|
|
3424
|
+
return fail2(`"${TASK_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2768
3425
|
}
|
|
2769
3426
|
if (query.stdout.includes("<LogonTrigger>")) {
|
|
2770
|
-
return
|
|
3427
|
+
return fail2(`"${TASK_NAME}" still carries the dormant logon-only trigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2771
3428
|
}
|
|
2772
3429
|
if (!query.stdout.includes("<TimeTrigger>") || !query.stdout.includes(`<Interval>${REPEAT_INTERVAL}</Interval>`)) {
|
|
2773
|
-
return
|
|
3430
|
+
return fail2(`"${TASK_NAME}" has no ${REPEAT_INTERVAL}-repeating TimeTrigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2774
3431
|
}
|
|
2775
3432
|
if (info && neverRan(info) && !info.nextRunTime) {
|
|
2776
|
-
return
|
|
3433
|
+
return fail2(`"${TASK_NAME}" has never run and has no next run time \u2014 run \`mmi-hub autoupdate on\``);
|
|
2777
3434
|
}
|
|
2778
3435
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2779
3436
|
const ran = !info ? "run history unreadable" : neverRan(info) ? "never run yet" : `last run ${info.lastRunTime} \u2192 exit ${info.lastTaskResult ?? "unknown"}`;
|
|
@@ -2787,20 +3444,20 @@ function darwinSchedulerStatus(env) {
|
|
|
2787
3444
|
}
|
|
2788
3445
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2789
3446
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2790
|
-
const
|
|
3447
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2791
3448
|
if (!plist.includes(launcherPath)) {
|
|
2792
|
-
return
|
|
3449
|
+
return fail2(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2793
3450
|
}
|
|
2794
3451
|
const launcher = readFileOrNull(launcherPath);
|
|
2795
3452
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2796
|
-
return
|
|
3453
|
+
return fail2(`"${LAUNCHD_LABEL}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2797
3454
|
}
|
|
2798
3455
|
if (!plist.includes(`<integer>${STARTINTERVAL_SECONDS}</integer>`)) {
|
|
2799
|
-
return
|
|
3456
|
+
return fail2(`"${LAUNCHD_LABEL}" has no ${STARTINTERVAL_SECONDS}s StartInterval \u2014 run \`mmi-hub autoupdate on\``);
|
|
2800
3457
|
}
|
|
2801
3458
|
const loaded = launchctl(["print", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2802
3459
|
if (loaded.status !== 0) {
|
|
2803
|
-
return
|
|
3460
|
+
return fail2(`"${LAUNCHD_LABEL}" is registered but not loaded \u2014 run \`mmi-hub autoupdate on\``);
|
|
2804
3461
|
}
|
|
2805
3462
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2806
3463
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
@@ -2815,20 +3472,20 @@ function linuxSchedulerStatus(env) {
|
|
|
2815
3472
|
}
|
|
2816
3473
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2817
3474
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2818
|
-
const
|
|
3475
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2819
3476
|
if (!service.includes(launcherPath)) {
|
|
2820
|
-
return
|
|
3477
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2821
3478
|
}
|
|
2822
3479
|
const launcher = readFileOrNull(launcherPath);
|
|
2823
3480
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2824
|
-
return
|
|
3481
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2825
3482
|
}
|
|
2826
3483
|
if (!timer.includes(`OnUnitActiveSec=${STARTINTERVAL_SECONDS}`)) {
|
|
2827
|
-
return
|
|
3484
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" has no ${STARTINTERVAL_SECONDS}s OnUnitActiveSec \u2014 run \`mmi-hub autoupdate on\``);
|
|
2828
3485
|
}
|
|
2829
3486
|
const enabled = systemctlUser(["is-enabled", SYSTEMD_TIMER_NAME]);
|
|
2830
3487
|
if (enabled.status !== 0) {
|
|
2831
|
-
return
|
|
3488
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" is registered but not enabled \u2014 run \`mmi-hub autoupdate on\``);
|
|
2832
3489
|
}
|
|
2833
3490
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2834
3491
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" registered ${POSIX_CADENCE}, ${RUN_BOUND_SECONDS}s bound; ${points}` };
|
|
@@ -2854,9 +3511,9 @@ function installWindowsTask(env) {
|
|
|
2854
3511
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2855
3512
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2856
3513
|
const launcherPath = (0, import_node_path12.join)(paths.root, LAUNCHER_FILE);
|
|
2857
|
-
(0,
|
|
3514
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2858
3515
|
const xmlPath = (0, import_node_path12.join)(paths.root, "task.xml");
|
|
2859
|
-
(0,
|
|
3516
|
+
(0, import_node_fs19.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
2860
3517
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2861
3518
|
if (create.status !== 0) {
|
|
2862
3519
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2875,8 +3532,8 @@ function refreshPosixLauncher(env) {
|
|
|
2875
3532
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2876
3533
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2877
3534
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2878
|
-
(0,
|
|
2879
|
-
(0,
|
|
3535
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, desired, "utf8");
|
|
3536
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2880
3537
|
return launcherPath;
|
|
2881
3538
|
}
|
|
2882
3539
|
function installLaunchd(env) {
|
|
@@ -2889,12 +3546,12 @@ function installLaunchd(env) {
|
|
|
2889
3546
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2890
3547
|
const paths = ensureState(env);
|
|
2891
3548
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2892
|
-
(0,
|
|
2893
|
-
(0,
|
|
3549
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3550
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2894
3551
|
const agentsDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
|
|
2895
|
-
(0,
|
|
3552
|
+
(0, import_node_fs19.mkdirSync)(agentsDir, { recursive: true });
|
|
2896
3553
|
const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2897
|
-
(0,
|
|
3554
|
+
(0, import_node_fs19.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2898
3555
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2899
3556
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2900
3557
|
if (load.status !== 0) {
|
|
@@ -2913,12 +3570,12 @@ function installSystemdTimer(env) {
|
|
|
2913
3570
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2914
3571
|
const paths = ensureState(env);
|
|
2915
3572
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2916
|
-
(0,
|
|
2917
|
-
(0,
|
|
3573
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3574
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2918
3575
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2919
|
-
(0,
|
|
2920
|
-
(0,
|
|
2921
|
-
(0,
|
|
3576
|
+
(0, import_node_fs19.mkdirSync)(unitDir, { recursive: true });
|
|
3577
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
3578
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
2922
3579
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2923
3580
|
if (reload.status !== 0) {
|
|
2924
3581
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -2958,11 +3615,11 @@ function uninstallWindowsTask(env) {
|
|
|
2958
3615
|
function uninstallLaunchd(env) {
|
|
2959
3616
|
const plistPath = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2960
3617
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2961
|
-
if (boot.status !== 0 && (0,
|
|
3618
|
+
if (boot.status !== 0 && (0, import_node_fs19.existsSync)(plistPath)) {
|
|
2962
3619
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2963
3620
|
}
|
|
2964
3621
|
try {
|
|
2965
|
-
(0,
|
|
3622
|
+
(0, import_node_fs19.rmSync)(plistPath, { force: true });
|
|
2966
3623
|
} catch {
|
|
2967
3624
|
}
|
|
2968
3625
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
@@ -2972,12 +3629,12 @@ function uninstallSystemdTimer(env) {
|
|
|
2972
3629
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2973
3630
|
const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2974
3631
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2975
|
-
if (disable.status !== 0 && (0,
|
|
3632
|
+
if (disable.status !== 0 && (0, import_node_fs19.existsSync)(timerPath)) {
|
|
2976
3633
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2977
3634
|
}
|
|
2978
3635
|
try {
|
|
2979
|
-
(0,
|
|
2980
|
-
(0,
|
|
3636
|
+
(0, import_node_fs19.rmSync)(timerPath, { force: true });
|
|
3637
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
2981
3638
|
} catch {
|
|
2982
3639
|
}
|
|
2983
3640
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2992,16 +3649,16 @@ function uninstallTask(env = process.env) {
|
|
|
2992
3649
|
}
|
|
2993
3650
|
function dropLauncher(env, file) {
|
|
2994
3651
|
try {
|
|
2995
|
-
(0,
|
|
3652
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
|
|
2996
3653
|
} catch {
|
|
2997
3654
|
}
|
|
2998
3655
|
}
|
|
2999
3656
|
|
|
3000
3657
|
// src/status.ts
|
|
3001
|
-
var
|
|
3658
|
+
var import_node_fs20 = require("node:fs");
|
|
3002
3659
|
function readJournal(path) {
|
|
3003
3660
|
try {
|
|
3004
|
-
const events = (0,
|
|
3661
|
+
const events = (0, import_node_fs20.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3005
3662
|
try {
|
|
3006
3663
|
return JSON.parse(line);
|
|
3007
3664
|
} catch {
|
|
@@ -3124,31 +3781,6 @@ function formatHubStatus(status) {
|
|
|
3124
3781
|
}
|
|
3125
3782
|
|
|
3126
3783
|
// src/report.ts
|
|
3127
|
-
var NAME_WIDTH = 8;
|
|
3128
|
-
var VERSION_WIDTH = 15;
|
|
3129
|
-
var DEFAULT_WIDTH = 100;
|
|
3130
|
-
var MIN_NOTE_SPACE = 8;
|
|
3131
|
-
var ACTIVATION_NOTE = {
|
|
3132
|
-
claude: "next session",
|
|
3133
|
-
codex: "next session",
|
|
3134
|
-
jervcode: "next launch",
|
|
3135
|
-
cursor: "on reload",
|
|
3136
|
-
hermes: "after restart",
|
|
3137
|
-
updater: "next run"
|
|
3138
|
-
};
|
|
3139
|
-
var GREEN = "\x1B[32m";
|
|
3140
|
-
var YELLOW = "\x1B[33m";
|
|
3141
|
-
var RED = "\x1B[31m";
|
|
3142
|
-
var RESET = "\x1B[0m";
|
|
3143
|
-
var ACCENT = "\x1B[38;2;125;211;252m";
|
|
3144
|
-
var MUTED = "\x1B[38;2;150;150;150m";
|
|
3145
|
-
var MM_GREEN = "\x1B[38;2;31;209;138m";
|
|
3146
|
-
function accent(text, color) {
|
|
3147
|
-
return color ? ACCENT + text + RESET : text;
|
|
3148
|
-
}
|
|
3149
|
-
function brandLine(rest, color = false) {
|
|
3150
|
-
return `${accent("mmi-hub", color)} \xB7 Mutatis Mutandis \u2014 ${rest}`;
|
|
3151
|
-
}
|
|
3152
3784
|
var RETRY_HINTS = [
|
|
3153
3785
|
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
3154
3786
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
@@ -3177,257 +3809,34 @@ function failConsequence(row) {
|
|
|
3177
3809
|
}
|
|
3178
3810
|
return matchFirst(FAIL_CONSEQUENCE, detail) ?? "update failed \u2014 previous version kept";
|
|
3179
3811
|
}
|
|
3180
|
-
function classify(arm) {
|
|
3181
|
-
const kind = arm.verdict === "ok" ? arm.from !== null && arm.from === arm.to && !arm.launchStaged ? "current" : "updated" : arm.verdict === "skip" ? "keep" : arm.verdict === "defer" ? "retry" : "failed";
|
|
3182
|
-
const note = kind === "updated" ? arm.launchStaged ? "next launch" : ACTIVATION_NOTE[arm.surface] ?? null : kind === "keep" ? "above target" : kind === "retry" ? retryHint(arm.detail) : null;
|
|
3183
|
-
return { name: arm.surface === "updater" ? "hub" : arm.surface, from: arm.from, to: arm.to, kind, note, detail: arm.detail, repeatFailure: arm.repeatFailure === true };
|
|
3184
|
-
}
|
|
3185
|
-
function statusText(kind, dryRun) {
|
|
3186
|
-
if (dryRun && kind === "updated") return "would update";
|
|
3187
|
-
switch (kind) {
|
|
3188
|
-
case "updated":
|
|
3189
|
-
return "updated";
|
|
3190
|
-
case "current":
|
|
3191
|
-
return "current";
|
|
3192
|
-
case "keep":
|
|
3193
|
-
return "keep";
|
|
3194
|
-
case "retry":
|
|
3195
|
-
return "retry";
|
|
3196
|
-
case "failed":
|
|
3197
|
-
return "failed";
|
|
3198
|
-
case "pending":
|
|
3199
|
-
return "pending";
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
function glyphFor(kind) {
|
|
3203
|
-
switch (kind) {
|
|
3204
|
-
case "updated":
|
|
3205
|
-
return "\u2713";
|
|
3206
|
-
case "current":
|
|
3207
|
-
return "\u2713";
|
|
3208
|
-
case "keep":
|
|
3209
|
-
return "\u2713";
|
|
3210
|
-
case "retry":
|
|
3211
|
-
return "\u21BB";
|
|
3212
|
-
case "failed":
|
|
3213
|
-
return "\u2717";
|
|
3214
|
-
case "pending":
|
|
3215
|
-
return "\xB7";
|
|
3216
|
-
}
|
|
3217
|
-
}
|
|
3218
|
-
function colorFor(kind) {
|
|
3219
|
-
return kind === "failed" ? RED : kind === "retry" ? YELLOW : GREEN;
|
|
3220
|
-
}
|
|
3221
|
-
var ARM_TITLE = {
|
|
3222
|
-
updater: "Updating mmi-hub",
|
|
3223
|
-
cli: "Installing mmi-cli",
|
|
3224
|
-
claude: "Arming Claude Code",
|
|
3225
|
-
codex: "Arming Codex",
|
|
3226
|
-
cursor: "Arming Cursor",
|
|
3227
|
-
hermes: "Arming Hermes",
|
|
3228
|
-
jervcode: "Arming JervCode"
|
|
3229
|
-
};
|
|
3230
|
-
var FACE_PRODUCT = "mmi-hub";
|
|
3231
|
-
function updaterFace(options = {}) {
|
|
3232
|
-
return createFace({ product: FACE_PRODUCT, color: options.color ?? false, columns: options.width });
|
|
3233
|
-
}
|
|
3234
|
-
var STATE_WORD = {
|
|
3235
|
-
updated: "updated",
|
|
3236
|
-
current: "current",
|
|
3237
|
-
keep: "kept",
|
|
3238
|
-
retry: "will retry",
|
|
3239
|
-
failed: "failed",
|
|
3240
|
-
pending: "pending"
|
|
3241
|
-
};
|
|
3242
|
-
function stepKindFor(kind) {
|
|
3243
|
-
return kind === "failed" ? "fail" : kind === "retry" ? "note" : "ok";
|
|
3244
|
-
}
|
|
3245
|
-
function checklistLine(arm, seconds, face, options = {}) {
|
|
3246
|
-
const { kind } = classify(arm);
|
|
3247
|
-
const state = options.dryRun && kind === "updated" ? "would update" : STATE_WORD[kind];
|
|
3248
|
-
const title = `${ARM_TITLE[arm.surface] ?? `Arming ${arm.surface}`} \u2014 ${state}`;
|
|
3249
|
-
const truthful = kind === "failed" || kind === "retry" ? null : seconds;
|
|
3250
|
-
return face.step(title, truthful, stepKindFor(kind));
|
|
3251
|
-
}
|
|
3252
|
-
function receiptText(summary, autoupdate, options = {}) {
|
|
3253
|
-
const dry = options.dryRun ?? false;
|
|
3254
|
-
const counts = reportCounts(summary);
|
|
3255
|
-
const health = "Check health any time: mmi-hub status";
|
|
3256
|
-
if (!summary.target) {
|
|
3257
|
-
return [`${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)`, "", health];
|
|
3258
|
-
}
|
|
3259
|
-
const ready = counts.failed === 0;
|
|
3260
|
-
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25C7" : "\u2714";
|
|
3261
|
-
const state = counts.failed > 0 ? `${counts.failed} of ${counts.total} surfaces failed` : counts.retry > 0 ? `${counts.retry} of ${counts.total} surfaces will retry` : counts.updated > 0 ? `${counts.updated}/${counts.total} surfaces ${dry ? "would update" : "updated"}` : `all ${counts.total} surfaces current`;
|
|
3262
|
-
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3263
|
-
return [`${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}`, "", health];
|
|
3264
|
-
}
|
|
3265
|
-
function autoupdateLine(state) {
|
|
3266
|
-
return state.ok ? 'Auto-update on \u2014 hourly task "MMI Fleet Updater".' : `Auto-update NOT enabled \u2014 ${state.detail}`;
|
|
3267
|
-
}
|
|
3268
|
-
function attentionRows(summary) {
|
|
3269
|
-
return collectRows(summary).filter((row) => row.kind === "failed" || row.kind === "retry" || row.kind === "pending").map((row) => ({
|
|
3270
|
-
surface: row.name,
|
|
3271
|
-
state: statusText(row.kind, false),
|
|
3272
|
-
mark: row.kind === "failed" ? "fail" : "note",
|
|
3273
|
-
note: row.kind === "failed" ? failConsequence(row) : row.kind === "retry" ? retryHint(row.detail) : "arm not landed"
|
|
3274
|
-
}));
|
|
3275
|
-
}
|
|
3276
|
-
function wrapWords2(text, available) {
|
|
3277
|
-
if (available < 1) return [text];
|
|
3278
|
-
const lines = [];
|
|
3279
|
-
let current = "";
|
|
3280
|
-
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
3281
|
-
const piece = word.length > available ? word.slice(0, Math.max(1, available - 1)) + "\u2026" : word;
|
|
3282
|
-
if (!current) current = piece;
|
|
3283
|
-
else if (current.length + 1 + piece.length <= available) current += " " + piece;
|
|
3284
|
-
else {
|
|
3285
|
-
lines.push(current);
|
|
3286
|
-
current = piece;
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
if (current) lines.push(current);
|
|
3290
|
-
return lines.length ? lines : [""];
|
|
3291
|
-
}
|
|
3292
|
-
function noteLines(prefix, note, width) {
|
|
3293
|
-
const used = stripAnsi(prefix).length;
|
|
3294
|
-
const available = width - used - 1;
|
|
3295
|
-
const indent = available >= MIN_NOTE_SPACE ? " ".repeat(used + 1) : " ".repeat(Math.min(used + 1, Math.max(2, width - MIN_NOTE_SPACE)));
|
|
3296
|
-
const room = available >= MIN_NOTE_SPACE ? available : width - indent.length;
|
|
3297
|
-
const parts = wrapWords2(note, room);
|
|
3298
|
-
const first = available >= MIN_NOTE_SPACE ? prefix + " " + parts[0] : prefix.replace(/\s+$/, "");
|
|
3299
|
-
const lines = [first];
|
|
3300
|
-
for (const part of parts.slice(available >= MIN_NOTE_SPACE ? 1 : 0)) lines.push(indent + part);
|
|
3301
|
-
return lines;
|
|
3302
|
-
}
|
|
3303
|
-
function stripAnsi(text) {
|
|
3304
|
-
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3305
|
-
}
|
|
3306
|
-
function collectRows(summary) {
|
|
3307
|
-
const rows = [];
|
|
3308
|
-
if (summary.updater) rows.push(classify(summary.updater));
|
|
3309
|
-
if (summary.cli) rows.push(classify(summary.cli));
|
|
3310
|
-
for (const arm of summary.plugins) rows.push(classify(arm));
|
|
3311
|
-
for (const surface of summary.surfaces) {
|
|
3312
|
-
if (surface.present && surface.action === "arm-pending") {
|
|
3313
|
-
rows.push({ name: surface.id, from: null, to: summary.target ?? "?", kind: "pending", note: "arm not landed", detail: "", repeatFailure: false });
|
|
3314
|
-
}
|
|
3315
|
-
}
|
|
3316
|
-
return rows;
|
|
3317
|
-
}
|
|
3318
|
-
function headline(detail, width) {
|
|
3319
|
-
const first = (detail ?? "").split(/(?<=[.;:])\s/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
3320
|
-
return wrapWords2(first || "blocked", Math.max(10, width - 4))[0];
|
|
3321
|
-
}
|
|
3322
|
-
function updateHeader(target, dryRun = false, options) {
|
|
3323
|
-
return brandLine(
|
|
3324
|
-
dryRun ? `update plan to ${target} (dry run)` : `updating to ${target} (verified)`,
|
|
3325
|
-
Boolean(options?.color)
|
|
3326
|
-
);
|
|
3327
|
-
}
|
|
3328
|
-
function renderRowLines(row, ctx) {
|
|
3329
|
-
const paint = (kind, text) => ctx.color ? colorFor(kind) + text + RESET : text;
|
|
3330
|
-
const versions = row.from !== null && row.from !== row.to ? `${row.from} \u2192 ${row.to}` : `${row.to}`;
|
|
3331
|
-
const glyph = ctx.tty ? glyphFor(row.kind) + " " : "";
|
|
3332
|
-
const status = statusText(row.kind, ctx.dryRun).padEnd(ctx.statusWidth);
|
|
3333
|
-
const prefix = " " + row.name.padEnd(NAME_WIDTH) + " " + versions.padEnd(VERSION_WIDTH) + " " + paint(row.kind, glyph + status);
|
|
3334
|
-
return row.note ? noteLines(prefix, row.note, ctx.width) : [prefix.replace(/\s+$/, "")];
|
|
3335
|
-
}
|
|
3336
|
-
function progressiveStatusWidth(dryRun) {
|
|
3337
|
-
return Math.max((dryRun ? "would update" : "updated").length, 7);
|
|
3338
|
-
}
|
|
3339
|
-
function progressiveRow(arm, options = {}) {
|
|
3340
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH);
|
|
3341
|
-
const tty = options.tty ?? false;
|
|
3342
|
-
const color = tty && (options.color ?? true);
|
|
3343
|
-
const dryRun = options.dryRun ?? false;
|
|
3344
|
-
return renderRowLines(classify(arm), { tty, color, dryRun, width, statusWidth: progressiveStatusWidth(dryRun) }).join("\n");
|
|
3345
|
-
}
|
|
3346
3812
|
function reportCounts(summary) {
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
total: rows.length
|
|
3813
|
+
const counts2 = {
|
|
3814
|
+
updated: 0,
|
|
3815
|
+
current: 0,
|
|
3816
|
+
retry: 0,
|
|
3817
|
+
failed: 0,
|
|
3818
|
+
total: summary.surfaces.filter((surface) => surface.present && surface.action === "arm-pending").length
|
|
3354
3819
|
};
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
|
|
3363
|
-
const rows = collectRows(summary);
|
|
3364
|
-
const counts = reportCounts(summary);
|
|
3365
|
-
const footer = dryRun ? `${counts.updated}/${counts.total} would update` : `${counts.updated}/${counts.total} updated` + (counts.retry ? ` \xB7 ${counts.retry} will retry` : "") + (counts.failed ? ` \xB7 ${counts.failed} failed` : "");
|
|
3366
|
-
const out = ["", " " + footer];
|
|
3367
|
-
if (tty && !dryRun && counts.failed === 0) {
|
|
3368
|
-
out.push(" " + (color ? MUTED + "Suggested next: mmi-cli doctor" + RESET : "Suggested next: mmi-cli doctor"));
|
|
3369
|
-
}
|
|
3370
|
-
const self = summary.updater;
|
|
3371
|
-
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3372
|
-
out.push("");
|
|
3373
|
-
out.push(...noteLines(
|
|
3374
|
-
" note: ",
|
|
3375
|
-
`hub moved ${self.from} \u2192 ${self.to} while the surfaces above were converging, so they used ${self.from} logic \u2014 rerun \`mmi-hub update\` to bring them to ${self.to}`,
|
|
3376
|
-
width
|
|
3377
|
-
));
|
|
3378
|
-
}
|
|
3379
|
-
if (counts.failed) {
|
|
3380
|
-
out.push("");
|
|
3381
|
-
for (const row of rows.filter((row2) => row2.kind === "failed")) {
|
|
3382
|
-
const mark = tty ? paint("failed", "\u2717 ") : "";
|
|
3383
|
-
out.push(...noteLines(" " + mark + row.name, failConsequence(row), width));
|
|
3384
|
-
out.push(...noteLines(" fix: ", row.repeatFailure ? "doctor + update already tried and did not clear this \u2014 see log, do not repeat" : "mmi-cli doctor, then mmi-hub update", width));
|
|
3385
|
-
out.push(...noteLines(" log: ", journalPath, width));
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
return out;
|
|
3389
|
-
}
|
|
3390
|
-
function formatReconcileReport(summary, options = {}) {
|
|
3391
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH);
|
|
3392
|
-
const tty = options.tty ?? false;
|
|
3393
|
-
const color = tty && (options.color ?? true);
|
|
3394
|
-
const dryRun = options.dryRun ?? false;
|
|
3395
|
-
const out = [];
|
|
3396
|
-
const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
|
|
3397
|
-
const rows = collectRows(summary);
|
|
3398
|
-
const counts = reportCounts(summary);
|
|
3399
|
-
if (!summary.target) {
|
|
3400
|
-
if ((summary.exit >= 1 || summary.deferred) && summary.detail) {
|
|
3401
|
-
out.push("MMI Hub \u2014 cannot update now");
|
|
3402
|
-
out.push(...noteLines(" " + paint("failed", "blocked"), headline(summary.detail, width), width));
|
|
3403
|
-
out.push(...noteLines(" fix:", retryHint(summary.detail), width));
|
|
3404
|
-
} else {
|
|
3405
|
-
out.push("MMI Hub \u2014 release still publishing, nothing changed (hourly retry)");
|
|
3406
|
-
}
|
|
3407
|
-
return out.join("\n") + "\n";
|
|
3408
|
-
}
|
|
3409
|
-
const allCurrent = rows.length > 0 && counts.updated === 0 && counts.retry === 0 && counts.failed === 0 && rows.every((row) => row.kind === "current" || row.kind === "keep");
|
|
3410
|
-
if (allCurrent && !dryRun) {
|
|
3411
|
-
out.push(`MMI Hub \u2014 MMI is up to date (${summary.target} \xB7 verified)`);
|
|
3412
|
-
return out.join("\n") + "\n";
|
|
3820
|
+
for (const arm of [summary.updater, summary.cli, ...summary.plugins]) {
|
|
3821
|
+
if (!arm) continue;
|
|
3822
|
+
counts2.total++;
|
|
3823
|
+
if (arm.verdict === "fail") counts2.failed++;
|
|
3824
|
+
else if (arm.verdict === "defer") counts2.retry++;
|
|
3825
|
+
else if (arm.verdict === "skip" || arm.from !== null && arm.from === arm.to && !arm.launchStaged) counts2.current++;
|
|
3826
|
+
else counts2.updated++;
|
|
3413
3827
|
}
|
|
3414
|
-
|
|
3415
|
-
out.push("");
|
|
3416
|
-
const statusWidth = Math.max(...rows.map((row) => statusText(row.kind, dryRun).length), 7);
|
|
3417
|
-
for (const row of rows) out.push(...renderRowLines(row, { tty, color, dryRun, width, statusWidth }));
|
|
3418
|
-
out.push(...reportFooterLines(summary, options));
|
|
3419
|
-
return out.join("\n") + "\n";
|
|
3828
|
+
return counts2;
|
|
3420
3829
|
}
|
|
3421
3830
|
|
|
3422
3831
|
// src/stamp.ts
|
|
3423
|
-
var
|
|
3832
|
+
var import_node_fs21 = require("node:fs");
|
|
3424
3833
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
3425
3834
|
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
|
|
3426
3835
|
function fleetStamp(env = process.env) {
|
|
3427
3836
|
const paths = statePaths(env);
|
|
3428
3837
|
let events = [];
|
|
3429
3838
|
try {
|
|
3430
|
-
events = (0,
|
|
3839
|
+
events = (0, import_node_fs21.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3431
3840
|
try {
|
|
3432
3841
|
return JSON.parse(line);
|
|
3433
3842
|
} catch {
|
|
@@ -3477,7 +3886,7 @@ function fleetStamp(env = process.env) {
|
|
|
3477
3886
|
// src/index.ts
|
|
3478
3887
|
function ownVersion() {
|
|
3479
3888
|
try {
|
|
3480
|
-
return JSON.parse((0,
|
|
3889
|
+
return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path13.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
3481
3890
|
} catch {
|
|
3482
3891
|
return "0.0.0";
|
|
3483
3892
|
}
|
|
@@ -3497,147 +3906,34 @@ var HELP = `mmi-hub ${ownVersion()} \u2014 install and maintain MMI tooling
|
|
|
3497
3906
|
State remains under $MMI_UPDATER_HOME (default ~/.mmi/updater), and the Windows task name remains
|
|
3498
3907
|
"MMI Fleet Updater", so existing installations continue in place.
|
|
3499
3908
|
Status performs no npm install, host repair, scheduler write, state mkdir, or repository cleanup.`;
|
|
3500
|
-
function
|
|
3501
|
-
return {
|
|
3502
|
-
tty: Boolean(process.stdout.isTTY),
|
|
3503
|
-
color: !process.env.NO_COLOR,
|
|
3504
|
-
width: process.stdout.columns ?? 100,
|
|
3505
|
-
dryRun,
|
|
3506
|
-
journalPath: statePaths(process.env).journalPath
|
|
3507
|
-
};
|
|
3508
|
-
}
|
|
3509
|
-
function printReconcile(summary, json, dryRun = false) {
|
|
3510
|
-
if (json) {
|
|
3511
|
-
process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
|
|
3512
|
-
return;
|
|
3513
|
-
}
|
|
3514
|
-
process.stdout.write(formatReconcileReport(summary, humanOptions(dryRun)));
|
|
3515
|
-
}
|
|
3516
|
-
function faceEnabled(args, json) {
|
|
3517
|
-
return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
3518
|
-
}
|
|
3519
|
-
function openFace(args, json, dryRun) {
|
|
3520
|
-
if (!faceEnabled(args, json)) return null;
|
|
3521
|
-
const face = updaterFace(humanOptions(dryRun));
|
|
3522
|
-
if (!process.env.MMI_UPDATER_REEXEC) for (const line of face.welcome()) process.stdout.write(line + "\n");
|
|
3523
|
-
return face;
|
|
3524
|
-
}
|
|
3525
|
-
function finishFace(face, summary, autoupdate, dryRun) {
|
|
3526
|
-
const options = humanOptions(dryRun);
|
|
3527
|
-
const write = (line) => {
|
|
3528
|
-
process.stdout.write(line + "\n");
|
|
3529
|
-
};
|
|
3530
|
-
if (!summary.target) {
|
|
3531
|
-
process.stdout.write(formatReconcileReport(summary, { ...options, tty: false }));
|
|
3532
|
-
} else {
|
|
3533
|
-
for (const line of renderRows(face, attentionRows(summary))) write(line);
|
|
3534
|
-
}
|
|
3535
|
-
const body = receiptText(summary, autoupdate, { dryRun });
|
|
3536
|
-
const counts = reportCounts(summary);
|
|
3537
|
-
if (counts.failed > 0) {
|
|
3538
|
-
body.push("", "Fix: mmi-cli doctor, then mmi-hub update", `Log: ${options.journalPath}`);
|
|
3539
|
-
}
|
|
3540
|
-
const self = summary.updater;
|
|
3541
|
-
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3542
|
-
body.push("", `Note: hub moved ${self.from} \u2192 ${self.to} while the surfaces above were converging, so they used ${self.from} logic \u2014 rerun \`mmi-hub update\` to bring them to ${self.to}`);
|
|
3543
|
-
}
|
|
3544
|
-
for (const line of face.receipt(body)) write(line);
|
|
3545
|
-
write(face.signOff());
|
|
3546
|
-
}
|
|
3547
|
-
function handOff(reexec, args) {
|
|
3909
|
+
function handOff(reexec, args, env) {
|
|
3548
3910
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
3549
3911
|
stdio: "inherit",
|
|
3550
3912
|
windowsHide: true,
|
|
3551
|
-
env: { ...
|
|
3913
|
+
env: { ...env, MMI_UPDATER_REEXEC: "1" }
|
|
3552
3914
|
});
|
|
3553
3915
|
if (child.error || typeof child.status !== "number") {
|
|
3554
|
-
|
|
3555
|
-
`);
|
|
3556
|
-
return 1;
|
|
3916
|
+
throw new Error(`could not run the updated updater at ${reexec.dist} \u2014 ${child.error?.message ?? "no exit status"}; rerun mmi-hub update`);
|
|
3557
3917
|
}
|
|
3558
3918
|
return child.status;
|
|
3559
3919
|
}
|
|
3560
|
-
function heldOutput(write) {
|
|
3561
|
-
const held = [];
|
|
3562
|
-
let streaming = false;
|
|
3563
|
-
return {
|
|
3564
|
-
emit: (line) => {
|
|
3565
|
-
if (streaming) write(line);
|
|
3566
|
-
else held.push(line);
|
|
3567
|
-
},
|
|
3568
|
-
stream: () => {
|
|
3569
|
-
if (streaming) return;
|
|
3570
|
-
streaming = true;
|
|
3571
|
-
for (const line of held) write(line);
|
|
3572
|
-
held.length = 0;
|
|
3573
|
-
}
|
|
3574
|
-
};
|
|
3575
|
-
}
|
|
3576
3920
|
function shippedTargetFrom(args) {
|
|
3577
3921
|
const index = args.indexOf("--shipped");
|
|
3578
3922
|
if (index < 0) return void 0;
|
|
3579
3923
|
const value = (args[index + 1] ?? "").trim();
|
|
3580
3924
|
return value && !value.startsWith("--") ? value : void 0;
|
|
3581
3925
|
}
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
}
|
|
3593
|
-
const reportOptions = humanOptions(dryRun);
|
|
3594
|
-
const spinner = createSpinner(face ?? updaterFace(reportOptions), { animate: true, stream: process.stderr });
|
|
3595
|
-
let spinning = false;
|
|
3596
|
-
const phase = (line) => {
|
|
3597
|
-
if (spinning) spinner.say(line);
|
|
3598
|
-
else {
|
|
3599
|
-
spinner.start(line);
|
|
3600
|
-
spinning = true;
|
|
3601
|
-
}
|
|
3602
|
-
};
|
|
3603
|
-
const persist = (line) => {
|
|
3604
|
-
if (spinning) {
|
|
3605
|
-
spinner.stop();
|
|
3606
|
-
spinning = false;
|
|
3607
|
-
}
|
|
3608
|
-
process.stdout.write(line + "\n");
|
|
3609
|
-
};
|
|
3610
|
-
let headerEmitted = false;
|
|
3611
|
-
let summary;
|
|
3612
|
-
const { emit, stream } = heldOutput(persist);
|
|
3613
|
-
let arms = 0;
|
|
3614
|
-
let mark = Date.now();
|
|
3615
|
-
try {
|
|
3616
|
-
summary = await reconcile({
|
|
3617
|
-
dryRun,
|
|
3618
|
-
shippedTarget,
|
|
3619
|
-
onPhase: (line) => phase(line),
|
|
3620
|
-
onTargetResolved: face ? void 0 : (target) => {
|
|
3621
|
-
emit(updateHeader(target, dryRun, reportOptions));
|
|
3622
|
-
emit("");
|
|
3623
|
-
headerEmitted = true;
|
|
3624
|
-
},
|
|
3625
|
-
onArm: (arm) => {
|
|
3626
|
-
const seconds = (Date.now() - mark) / 1e3;
|
|
3627
|
-
mark = Date.now();
|
|
3628
|
-
if (arms++) stream();
|
|
3629
|
-
emit(face ? checklistLine(arm, seconds, face, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3630
|
-
}
|
|
3631
|
-
});
|
|
3632
|
-
} finally {
|
|
3633
|
-
spinner.stop();
|
|
3634
|
-
}
|
|
3635
|
-
if (summary.reexec) return { summary, rendered: true };
|
|
3636
|
-
stream();
|
|
3637
|
-
if (face) return { summary, rendered: true };
|
|
3638
|
-
if (headerEmitted) process.stdout.write(reportFooterLines(summary, reportOptions).join("\n") + "\n");
|
|
3639
|
-
else process.stdout.write(formatReconcileReport(summary, reportOptions));
|
|
3640
|
-
return { summary, rendered: true };
|
|
3926
|
+
function runMaintenance2(args, dependencies = {}) {
|
|
3927
|
+
return runMaintenance(installer_product_default, args, {
|
|
3928
|
+
engine: dependencies.reconcile ?? reconcile,
|
|
3929
|
+
schedule: dependencies.schedule ?? installTask,
|
|
3930
|
+
handOff: dependencies.handOff ?? handOff,
|
|
3931
|
+
run: dependencies.run,
|
|
3932
|
+
shippedTarget: shippedTargetFrom(args),
|
|
3933
|
+
counts: reportCounts,
|
|
3934
|
+
diagnose: (arm) => arm.verdict === "fail" ? [arm.detail, failConsequence(arm), arm.repeatFailure ? "This failure persisted after repair; do not repeat the same remedy. Inspect the updater journal." : retryHint(arm.detail)].join("\n") : arm.verdict === "defer" ? `${arm.detail}
|
|
3935
|
+
${retryHint(arm.detail)}` : void 0
|
|
3936
|
+
});
|
|
3641
3937
|
}
|
|
3642
3938
|
async function main(args = process.argv.slice(2)) {
|
|
3643
3939
|
if (args.includes("--version")) {
|
|
@@ -3655,41 +3951,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
3655
3951
|
process.stdout.write(json ? JSON.stringify(status, null, 2) + "\n" : formatHubStatus(status) + "\n");
|
|
3656
3952
|
return status.failures.length ? 1 : 0;
|
|
3657
3953
|
}
|
|
3658
|
-
if (command === "update")
|
|
3659
|
-
const dryRun = args.includes("--dry-run");
|
|
3660
|
-
const face = openFace(args, json, dryRun);
|
|
3661
|
-
const { summary, rendered } = await runUpdate(args, json, face);
|
|
3662
|
-
if (summary.reexec) return handOff(summary.reexec, args);
|
|
3663
|
-
if (face) finishFace(face, summary, null, dryRun);
|
|
3664
|
-
else if (!rendered) printReconcile(summary, json, dryRun);
|
|
3665
|
-
return summary.exit;
|
|
3666
|
-
}
|
|
3667
|
-
if (command === "install") {
|
|
3668
|
-
const dryRun = args.includes("--dry-run");
|
|
3669
|
-
const face = openFace(args, json, dryRun);
|
|
3670
|
-
const shippedTarget = shippedTargetFrom(args);
|
|
3671
|
-
const { summary, rendered } = await runUpdate(args, json, face, shippedTarget);
|
|
3672
|
-
if (summary.reexec) {
|
|
3673
|
-
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install" && arg !== "--shipped" && arg !== shippedTarget)]);
|
|
3674
|
-
const scheduled = installTask();
|
|
3675
|
-
if (!json) {
|
|
3676
|
-
process.stdout.write(autoupdateLine(scheduled) + "\n");
|
|
3677
|
-
} else {
|
|
3678
|
-
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: scheduled }, null, 2) + "\n");
|
|
3679
|
-
}
|
|
3680
|
-
return Math.max(handedOff, scheduled.ok ? 0 : 1);
|
|
3681
|
-
}
|
|
3682
|
-
const schedule = installTask();
|
|
3683
|
-
if (json) {
|
|
3684
|
-
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
|
|
3685
|
-
} else if (face) {
|
|
3686
|
-
finishFace(face, summary, schedule, dryRun);
|
|
3687
|
-
} else {
|
|
3688
|
-
if (!rendered) printReconcile(summary, false, dryRun);
|
|
3689
|
-
process.stdout.write(autoupdateLine(schedule) + "\n");
|
|
3690
|
-
}
|
|
3691
|
-
return Math.max(summary.exit, schedule.ok ? 0 : 1);
|
|
3692
|
-
}
|
|
3954
|
+
if (command === "update" || command === "install") return runMaintenance2(args);
|
|
3693
3955
|
if (command === "autoupdate") {
|
|
3694
3956
|
const commandIndex = args.indexOf(command);
|
|
3695
3957
|
const mode = args[commandIndex + 1];
|
|
@@ -3714,27 +3976,18 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3714
3976
|
}
|
|
3715
3977
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3716
3978
|
0 && (module.exports = {
|
|
3717
|
-
ACCENT,
|
|
3718
|
-
FACE_PRODUCT,
|
|
3719
|
-
MM_GREEN,
|
|
3720
|
-
RESET,
|
|
3721
|
-
accent,
|
|
3722
3979
|
acquireSharedDocLock,
|
|
3723
|
-
|
|
3724
|
-
autoupdateLine,
|
|
3725
|
-
brandLine,
|
|
3980
|
+
assertFaceConformance,
|
|
3726
3981
|
buildWatcherCommand,
|
|
3727
3982
|
checkAtomicity,
|
|
3728
|
-
checklistLine,
|
|
3729
3983
|
claudeArm,
|
|
3730
3984
|
claudeHostEvidence,
|
|
3731
3985
|
clearLaunchTarget,
|
|
3732
3986
|
clearPending,
|
|
3987
|
+
createInstallerRun,
|
|
3733
3988
|
enumerateSurfaces,
|
|
3734
3989
|
failConsequence,
|
|
3735
3990
|
fleetStamp,
|
|
3736
|
-
formatReconcileReport,
|
|
3737
|
-
heldOutput,
|
|
3738
3991
|
hermesArm,
|
|
3739
3992
|
hermesHome,
|
|
3740
3993
|
hermesHomes,
|
|
@@ -3761,17 +4014,15 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3761
4014
|
probeHermesLaunchStaging,
|
|
3762
4015
|
probeJervcodeInstallation,
|
|
3763
4016
|
probeLaunchTargetVersion,
|
|
3764
|
-
progressiveRow,
|
|
3765
4017
|
readLaunchTarget,
|
|
3766
4018
|
readPending,
|
|
3767
|
-
receiptText,
|
|
3768
4019
|
reconcile,
|
|
3769
4020
|
reconcileCursorUserHooks,
|
|
3770
4021
|
releaseSharedDocLock,
|
|
3771
4022
|
reportCounts,
|
|
3772
|
-
reportFooterLines,
|
|
3773
4023
|
retiredSurfaces,
|
|
3774
4024
|
retryHint,
|
|
4025
|
+
runMaintenance,
|
|
3775
4026
|
schedulePendingWatcher,
|
|
3776
4027
|
schedulerStatus,
|
|
3777
4028
|
sharedDocLockPath,
|
|
@@ -3782,11 +4033,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3782
4033
|
systemdServiceUnit,
|
|
3783
4034
|
systemdTimerUnit,
|
|
3784
4035
|
taskXml,
|
|
3785
|
-
updateHeader,
|
|
3786
|
-
updaterFace,
|
|
3787
4036
|
watcherLockPath,
|
|
3788
4037
|
withSharedDocLock,
|
|
3789
|
-
wrapWords,
|
|
3790
4038
|
writeLaunchTarget,
|
|
3791
4039
|
writePending
|
|
3792
4040
|
});
|