@mutmutco/hub 4.4.10 → 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 +941 -698
- 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,7 +186,14 @@ 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);
|
|
@@ -192,28 +201,45 @@ function createFace({ product, color = false, columns, env = process.env }) {
|
|
|
192
201
|
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
193
202
|
const continuedPhases = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((phase) => phase.trim()).filter(Boolean));
|
|
194
203
|
const continuesFace = continuedPhases.size > 0;
|
|
195
|
-
const
|
|
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 ? [] : [
|
|
196
219
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
197
220
|
bar(),
|
|
198
|
-
`${bar()} ${identity.warm}`,
|
|
221
|
+
`${bar()} ${operation === "install" ? identity.installWarm : identity.warm}`,
|
|
199
222
|
bar()
|
|
200
223
|
];
|
|
201
224
|
const continues = (phase, kind = "ok") => {
|
|
202
225
|
const inherited = continuedPhases.delete(String(phase).trim());
|
|
203
226
|
return kind === "fail" ? false : inherited;
|
|
204
227
|
};
|
|
205
|
-
const step = (title,
|
|
228
|
+
const step = (title, measure = null, kind = "ok") => {
|
|
229
|
+
if (emitMilestone(title, measure, kind)) return "";
|
|
206
230
|
const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
|
|
207
|
-
const
|
|
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;
|
|
208
233
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
209
|
-
const reserved = time ? 6 : 0;
|
|
234
|
+
const reserved = time ? Math.max(6, visibleWidth(time) + 2) : 0;
|
|
210
235
|
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
211
236
|
const head = `${bar()} ${glyph} ${first}`;
|
|
212
237
|
const pad = Math.max(1, column - visibleWidth(head));
|
|
213
238
|
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
214
239
|
};
|
|
215
|
-
const relay = (text) => String(text).split("\n").
|
|
216
|
-
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 [];
|
|
217
243
|
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
218
244
|
const line = String(raw);
|
|
219
245
|
if (visibleWidth(line) <= width - 6) return [line];
|
|
@@ -229,87 +255,713 @@ function createFace({ product, color = false, columns, env = process.env }) {
|
|
|
229
255
|
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
230
256
|
];
|
|
231
257
|
};
|
|
232
|
-
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`)}`;
|
|
233
270
|
const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
|
|
234
|
-
return { identity, width, welcome, continues, step, relay, receipt, signOff, refusal, paint };
|
|
271
|
+
return { identity, width, nested, emitsProgress: progressFd !== null, welcome, continues, step, relay, receipt, outcome, signOff, refusal, paint };
|
|
235
272
|
}
|
|
273
|
+
|
|
274
|
+
// ../installer/face/src/shell.ts
|
|
236
275
|
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
var
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const tail2 = instances.length > 1 ? `${row.state} ${SEPARATOR} ${instances.join(", ")}` : row.state;
|
|
258
|
-
const body = `${clip(subject, column).padEnd(column)} ${row.mark ? `${markGlyph(face, row.mark)} ` : ""}${tail2}`;
|
|
259
|
-
lines.push(...face.relay(body).split("\n"));
|
|
260
|
-
if (row.note) {
|
|
261
|
-
for (const part of wrapWords(row.note, Math.max(16, face.width - TITLE_COLUMN))) {
|
|
262
|
-
lines.push(...face.relay(part).split("\n"));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
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);
|
|
265
296
|
}
|
|
266
|
-
return lines;
|
|
267
|
-
}
|
|
268
|
-
function markGlyph(face, kind) {
|
|
269
|
-
const glyph = kind === "fail" ? GLYPH.cross : kind === "note" ? GLYPH.hollow : GLYPH.check;
|
|
270
|
-
return face.paint(kind === "fail" ? face.identity.accent : kind === "note" ? "38;2;150;150;150" : "38;2;31;209;138", glyph);
|
|
271
297
|
}
|
|
272
|
-
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|
|
278
308
|
let timer = null;
|
|
279
|
-
let
|
|
309
|
+
let worker = null;
|
|
310
|
+
let control = null;
|
|
311
|
+
let frames = [];
|
|
280
312
|
let frame = 0;
|
|
281
|
-
const
|
|
282
|
-
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)}`) : [];
|
|
283
324
|
};
|
|
284
325
|
const draw = () => {
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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;
|
|
289
341
|
};
|
|
290
342
|
return {
|
|
291
|
-
start(
|
|
292
|
-
|
|
343
|
+
start(text, measure = null) {
|
|
344
|
+
if (!animate) return;
|
|
345
|
+
halt();
|
|
346
|
+
render(text, measure);
|
|
293
347
|
frame = 0;
|
|
294
348
|
draw();
|
|
295
|
-
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
|
+
}
|
|
296
370
|
},
|
|
297
|
-
say(
|
|
298
|
-
|
|
371
|
+
say(text, measure = null) {
|
|
372
|
+
if (!animate) return;
|
|
373
|
+
pause();
|
|
374
|
+
render(text, measure);
|
|
299
375
|
draw();
|
|
376
|
+
worker?.postMessage({ frames, frame });
|
|
300
377
|
},
|
|
301
378
|
stop() {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
clear();
|
|
379
|
+
halt();
|
|
380
|
+
if (animate) write("\r\x1B[2K");
|
|
305
381
|
}
|
|
306
382
|
};
|
|
307
383
|
}
|
|
308
384
|
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
385
|
+
|
|
386
|
+
// ../installer/face/src/conformance.ts
|
|
309
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
|
+
};
|
|
310
962
|
|
|
311
963
|
// src/state.ts
|
|
312
|
-
var
|
|
964
|
+
var import_node_fs5 = require("node:fs");
|
|
313
965
|
var import_node_os = require("node:os");
|
|
314
966
|
var import_node_path = require("node:path");
|
|
315
967
|
function stateRoot(env = process.env) {
|
|
@@ -323,8 +975,8 @@ function statePaths(env = process.env) {
|
|
|
323
975
|
}
|
|
324
976
|
function ensureState(env = process.env) {
|
|
325
977
|
const paths = statePaths(env);
|
|
326
|
-
(0,
|
|
327
|
-
(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 });
|
|
328
980
|
return paths;
|
|
329
981
|
}
|
|
330
982
|
var UpdaterBusy = class extends Error {
|
|
@@ -334,15 +986,15 @@ var LEASE_STALE_MS = 10 * 6e4;
|
|
|
334
986
|
function tryAcquire(path) {
|
|
335
987
|
const token = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
336
988
|
try {
|
|
337
|
-
const fd = (0,
|
|
338
|
-
(0,
|
|
339
|
-
(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);
|
|
340
992
|
return {
|
|
341
993
|
token,
|
|
342
994
|
release() {
|
|
343
995
|
try {
|
|
344
996
|
const current2 = readLease(path);
|
|
345
|
-
if (current2 && current2.token === token) (0,
|
|
997
|
+
if (current2 && current2.token === token) (0, import_node_fs5.unlinkSync)(path);
|
|
346
998
|
} catch {
|
|
347
999
|
}
|
|
348
1000
|
}
|
|
@@ -353,7 +1005,7 @@ function tryAcquire(path) {
|
|
|
353
1005
|
const current = readLease(path);
|
|
354
1006
|
if (current && Date.now() - current.ts > LEASE_STALE_MS) {
|
|
355
1007
|
try {
|
|
356
|
-
(0,
|
|
1008
|
+
(0, import_node_fs5.renameSync)(path, `${path}.stale-${Date.now()}`);
|
|
357
1009
|
return "stolen-stale";
|
|
358
1010
|
} catch {
|
|
359
1011
|
}
|
|
@@ -362,7 +1014,7 @@ function tryAcquire(path) {
|
|
|
362
1014
|
}
|
|
363
1015
|
function readLease(path) {
|
|
364
1016
|
try {
|
|
365
|
-
return JSON.parse((0,
|
|
1017
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
366
1018
|
} catch {
|
|
367
1019
|
return null;
|
|
368
1020
|
}
|
|
@@ -387,10 +1039,10 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
387
1039
|
}
|
|
388
1040
|
function appendJournal(path, event) {
|
|
389
1041
|
try {
|
|
390
|
-
(0,
|
|
391
|
-
const fd = (0,
|
|
392
|
-
(0,
|
|
393
|
-
(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);
|
|
394
1046
|
return true;
|
|
395
1047
|
} catch {
|
|
396
1048
|
return false;
|
|
@@ -409,7 +1061,7 @@ function journalHighWater(path, surface, events) {
|
|
|
409
1061
|
function parseJournal(path) {
|
|
410
1062
|
const events = [];
|
|
411
1063
|
try {
|
|
412
|
-
for (const line of (0,
|
|
1064
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
413
1065
|
if (!line.trim()) continue;
|
|
414
1066
|
try {
|
|
415
1067
|
events.push(JSON.parse(line));
|
|
@@ -423,7 +1075,7 @@ function parseJournal(path) {
|
|
|
423
1075
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
424
1076
|
let lastVerdict = null;
|
|
425
1077
|
try {
|
|
426
|
-
for (const line of (0,
|
|
1078
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
427
1079
|
if (!line.trim()) continue;
|
|
428
1080
|
try {
|
|
429
1081
|
const event = JSON.parse(line);
|
|
@@ -466,7 +1118,7 @@ function compareSemver(a, b) {
|
|
|
466
1118
|
return va.pre.join(".") < vb.pre.join(".") ? -1 : va.pre.join(".") > vb.pre.join(".") ? 1 : 0;
|
|
467
1119
|
}
|
|
468
1120
|
function fileExists(path) {
|
|
469
|
-
return (0,
|
|
1121
|
+
return (0, import_node_fs5.existsSync)(path);
|
|
470
1122
|
}
|
|
471
1123
|
|
|
472
1124
|
// src/reconcile.ts
|
|
@@ -474,7 +1126,7 @@ var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
|
474
1126
|
|
|
475
1127
|
// src/npm.ts
|
|
476
1128
|
var import_node_child_process = require("node:child_process");
|
|
477
|
-
var
|
|
1129
|
+
var import_node_fs6 = require("node:fs");
|
|
478
1130
|
var import_node_path2 = require("node:path");
|
|
479
1131
|
var cachedNpm = null;
|
|
480
1132
|
function resolveNpm(env = process.env) {
|
|
@@ -485,7 +1137,7 @@ function resolveNpm(env = process.env) {
|
|
|
485
1137
|
const first = where.stdout.split("\n").map((l) => l.trim()).find(Boolean);
|
|
486
1138
|
if (where.status === 0 && first) {
|
|
487
1139
|
const script = (0, import_node_path2.join)((0, import_node_path2.dirname)(first), "node_modules", "npm", "bin", "npm-cli.js");
|
|
488
|
-
if ((0,
|
|
1140
|
+
if ((0, import_node_fs6.existsSync)(script)) {
|
|
489
1141
|
cachedNpm = { kind: "node-script", script };
|
|
490
1142
|
return cachedNpm;
|
|
491
1143
|
}
|
|
@@ -494,13 +1146,13 @@ function resolveNpm(env = process.env) {
|
|
|
494
1146
|
}
|
|
495
1147
|
}
|
|
496
1148
|
const bundled = (0, import_node_path2.join)((0, import_node_path2.dirname)(process.execPath), "..", "lib", "node_modules", "npm", "bin", "npm-cli.js");
|
|
497
|
-
if ((0,
|
|
1149
|
+
if ((0, import_node_fs6.existsSync)(bundled)) {
|
|
498
1150
|
cachedNpm = { kind: "node-script", script: bundled };
|
|
499
1151
|
return cachedNpm;
|
|
500
1152
|
}
|
|
501
1153
|
const located = (0, import_node_child_process.spawnSync)("/bin/sh", ["-lc", "command -v npm"], { encoding: "utf8", windowsHide: true });
|
|
502
1154
|
const resolved = (located.stdout ?? "").trim().split("\n").map((line) => line.trim()).find(Boolean);
|
|
503
|
-
if (located.status === 0 && resolved && (0,
|
|
1155
|
+
if (located.status === 0 && resolved && (0, import_node_fs6.existsSync)(resolved)) {
|
|
504
1156
|
cachedNpm = { kind: "command", command: resolved };
|
|
505
1157
|
return cachedNpm;
|
|
506
1158
|
}
|
|
@@ -582,7 +1234,7 @@ function npmVersions(packageName, env = process.env) {
|
|
|
582
1234
|
|
|
583
1235
|
// src/gate.ts
|
|
584
1236
|
var import_node_child_process2 = require("node:child_process");
|
|
585
|
-
var
|
|
1237
|
+
var import_node_fs7 = require("node:fs");
|
|
586
1238
|
function parseBom(text) {
|
|
587
1239
|
const bom = JSON.parse(text);
|
|
588
1240
|
if (typeof bom?.version !== "string" || typeof bom?.sourceCommit !== "string" || !Array.isArray(bom?.artifacts)) {
|
|
@@ -604,7 +1256,7 @@ function ensureBareCache(cachePath, repoUrl) {
|
|
|
604
1256
|
if (probe.status !== 0) {
|
|
605
1257
|
throw new GateEnvError("git is not available \u2014 the release gate needs a git client");
|
|
606
1258
|
}
|
|
607
|
-
if (!(0,
|
|
1259
|
+
if (!(0, import_node_fs7.existsSync)(cachePath)) {
|
|
608
1260
|
const init = (0, import_node_child_process2.spawnSync)("git", ["init", "--bare", cachePath], { encoding: "utf8", windowsHide: true });
|
|
609
1261
|
if (init.status !== 0) throw new GateEnvError(`cannot create bare cache at ${cachePath}: ${init.stderr}`);
|
|
610
1262
|
}
|
|
@@ -711,24 +1363,24 @@ async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
711
1363
|
}
|
|
712
1364
|
|
|
713
1365
|
// src/surfaces.ts
|
|
714
|
-
var
|
|
1366
|
+
var import_node_fs12 = require("node:fs");
|
|
715
1367
|
var import_node_os3 = require("node:os");
|
|
716
1368
|
var import_node_path7 = require("node:path");
|
|
717
1369
|
|
|
718
1370
|
// src/arm-host-plugins.ts
|
|
719
1371
|
var import_node_child_process5 = require("node:child_process");
|
|
720
|
-
var
|
|
1372
|
+
var import_node_fs11 = require("node:fs");
|
|
721
1373
|
var import_node_os2 = require("node:os");
|
|
722
1374
|
var import_node_path6 = require("node:path");
|
|
723
1375
|
|
|
724
1376
|
// src/arm-npm-global.ts
|
|
725
|
-
var
|
|
1377
|
+
var import_node_fs9 = require("node:fs");
|
|
726
1378
|
var import_node_child_process4 = require("node:child_process");
|
|
727
1379
|
var import_node_path4 = require("node:path");
|
|
728
1380
|
|
|
729
1381
|
// src/pending.ts
|
|
730
1382
|
var import_node_child_process3 = require("node:child_process");
|
|
731
|
-
var
|
|
1383
|
+
var import_node_fs8 = require("node:fs");
|
|
732
1384
|
var import_node_path3 = require("node:path");
|
|
733
1385
|
function hubDistPath(env) {
|
|
734
1386
|
const root = globalRoot(env);
|
|
@@ -743,7 +1395,7 @@ function watcherLockPath(paths) {
|
|
|
743
1395
|
}
|
|
744
1396
|
function readPending(paths) {
|
|
745
1397
|
try {
|
|
746
|
-
const parsed = JSON.parse((0,
|
|
1398
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(pendingPath(paths), "utf8"));
|
|
747
1399
|
if (!Array.isArray(parsed)) return [];
|
|
748
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));
|
|
749
1401
|
} catch {
|
|
@@ -751,12 +1403,12 @@ function readPending(paths) {
|
|
|
751
1403
|
}
|
|
752
1404
|
}
|
|
753
1405
|
function writePending(paths, records) {
|
|
754
|
-
(0,
|
|
1406
|
+
(0, import_node_fs8.mkdirSync)(paths.root, { recursive: true });
|
|
755
1407
|
const path = pendingPath(paths);
|
|
756
1408
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
757
|
-
(0,
|
|
1409
|
+
(0, import_node_fs8.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
758
1410
|
`, "utf8");
|
|
759
|
-
(0,
|
|
1411
|
+
(0, import_node_fs8.renameSync)(tmp, path);
|
|
760
1412
|
}
|
|
761
1413
|
function clearPending(paths, surface) {
|
|
762
1414
|
const records = readPending(paths);
|
|
@@ -825,7 +1477,7 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
|
|
|
825
1477
|
const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
|
|
826
1478
|
if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
|
|
827
1479
|
const hubDist = seam.hubDist ?? hubDistPath(env);
|
|
828
|
-
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" };
|
|
829
1481
|
const command = buildWatcherCommand({
|
|
830
1482
|
images: [...new Set(records.flatMap((row) => row.images))],
|
|
831
1483
|
dirs: [...new Set(records.flatMap((row) => row.dirs))],
|
|
@@ -885,7 +1537,7 @@ function tail(output) {
|
|
|
885
1537
|
function manifestVersion(packageRoot) {
|
|
886
1538
|
if (!packageRoot) return null;
|
|
887
1539
|
try {
|
|
888
|
-
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;
|
|
889
1541
|
return typeof version === "string" ? version : null;
|
|
890
1542
|
} catch {
|
|
891
1543
|
return null;
|
|
@@ -896,8 +1548,8 @@ function stagePackage(options) {
|
|
|
896
1548
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
897
1549
|
const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
|
|
898
1550
|
if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
|
|
899
|
-
(0,
|
|
900
|
-
(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");
|
|
901
1553
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
902
1554
|
if (install.status !== 0) {
|
|
903
1555
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -905,7 +1557,7 @@ function stagePackage(options) {
|
|
|
905
1557
|
}
|
|
906
1558
|
let stagedManifestVersion;
|
|
907
1559
|
try {
|
|
908
|
-
stagedManifestVersion = JSON.parse((0,
|
|
1560
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
909
1561
|
} catch {
|
|
910
1562
|
}
|
|
911
1563
|
if (stagedManifestVersion !== target) {
|
|
@@ -963,11 +1615,11 @@ function npmGlobalArm(options) {
|
|
|
963
1615
|
}
|
|
964
1616
|
|
|
965
1617
|
// src/launch-staging.ts
|
|
966
|
-
var
|
|
1618
|
+
var import_node_fs10 = require("node:fs");
|
|
967
1619
|
var import_node_path5 = require("node:path");
|
|
968
1620
|
function readLaunchTarget(path) {
|
|
969
1621
|
try {
|
|
970
|
-
const parsed = JSON.parse((0,
|
|
1622
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
971
1623
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
972
1624
|
const row = parsed;
|
|
973
1625
|
if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
|
|
@@ -977,15 +1629,15 @@ function readLaunchTarget(path) {
|
|
|
977
1629
|
}
|
|
978
1630
|
}
|
|
979
1631
|
function writeLaunchTarget(path, record) {
|
|
980
|
-
(0,
|
|
1632
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
|
|
981
1633
|
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
982
|
-
(0,
|
|
1634
|
+
(0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
|
|
983
1635
|
`, "utf8");
|
|
984
|
-
(0,
|
|
1636
|
+
(0, import_node_fs10.renameSync)(tmp, path);
|
|
985
1637
|
}
|
|
986
1638
|
function clearLaunchTarget(path) {
|
|
987
1639
|
try {
|
|
988
|
-
(0,
|
|
1640
|
+
(0, import_node_fs10.rmSync)(path, { force: true });
|
|
989
1641
|
} catch {
|
|
990
1642
|
}
|
|
991
1643
|
}
|
|
@@ -1006,7 +1658,7 @@ function noteLaunchStaged(paths, env, options, seam = {}) {
|
|
|
1006
1658
|
}
|
|
1007
1659
|
function probeLaunchTargetVersion(launchTargetPath, versionAt) {
|
|
1008
1660
|
const record = readLaunchTarget(launchTargetPath);
|
|
1009
|
-
if (!record || !(0,
|
|
1661
|
+
if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
|
|
1010
1662
|
const version = versionAt(record.incoming);
|
|
1011
1663
|
return version === record.target ? record.target : null;
|
|
1012
1664
|
}
|
|
@@ -1064,7 +1716,7 @@ function ensureClaudeMarketplace(env) {
|
|
|
1064
1716
|
const path = claudeMarketplacesPath(env);
|
|
1065
1717
|
let body;
|
|
1066
1718
|
try {
|
|
1067
|
-
body = JSON.parse((0,
|
|
1719
|
+
body = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
1068
1720
|
} catch (error) {
|
|
1069
1721
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
1070
1722
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -1092,7 +1744,7 @@ function claudePluginsRoot(env) {
|
|
|
1092
1744
|
}
|
|
1093
1745
|
function claudeMarkerVersion(root) {
|
|
1094
1746
|
try {
|
|
1095
|
-
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;
|
|
1096
1748
|
return typeof version === "string" ? version : null;
|
|
1097
1749
|
} catch {
|
|
1098
1750
|
return null;
|
|
@@ -1189,14 +1841,14 @@ function claudeArm(options) {
|
|
|
1189
1841
|
return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
|
|
1190
1842
|
}
|
|
1191
1843
|
try {
|
|
1192
|
-
(0,
|
|
1193
|
-
(0,
|
|
1194
|
-
(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 });
|
|
1195
1847
|
} catch (error) {
|
|
1196
1848
|
return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
|
|
1197
1849
|
}
|
|
1198
1850
|
if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
|
|
1199
|
-
(0,
|
|
1851
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1200
1852
|
return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
|
|
1201
1853
|
}
|
|
1202
1854
|
}
|
|
@@ -1228,10 +1880,10 @@ function claudeArm(options) {
|
|
|
1228
1880
|
}
|
|
1229
1881
|
const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
|
|
1230
1882
|
try {
|
|
1231
|
-
if (!(0,
|
|
1232
|
-
(0,
|
|
1233
|
-
(0,
|
|
1234
|
-
(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 });
|
|
1235
1887
|
}
|
|
1236
1888
|
} catch (error) {
|
|
1237
1889
|
return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
|
|
@@ -1259,7 +1911,7 @@ function claudeArm(options) {
|
|
|
1259
1911
|
}
|
|
1260
1912
|
clearLaunchTarget(launchTargetPath);
|
|
1261
1913
|
try {
|
|
1262
|
-
(0,
|
|
1914
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1263
1915
|
} catch {
|
|
1264
1916
|
}
|
|
1265
1917
|
return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
|
|
@@ -1372,21 +2024,21 @@ function enumerateSurfaces(env = process.env) {
|
|
|
1372
2024
|
{ id: "hermes", marker: `${hermesHostCommand(env)} --version` }
|
|
1373
2025
|
].map((probe) => ({
|
|
1374
2026
|
...probe,
|
|
1375
|
-
present: probe.id === "hermes" ? hermesHostPresent(env) : (0,
|
|
2027
|
+
present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs12.existsSync)(probe.marker)
|
|
1376
2028
|
}));
|
|
1377
2029
|
}
|
|
1378
2030
|
|
|
1379
2031
|
// src/arm-cursor.ts
|
|
1380
|
-
var
|
|
2032
|
+
var import_node_fs15 = require("node:fs");
|
|
1381
2033
|
var import_node_child_process6 = require("node:child_process");
|
|
1382
2034
|
var import_node_os5 = require("node:os");
|
|
1383
2035
|
var import_node_path8 = require("node:path");
|
|
1384
2036
|
|
|
1385
2037
|
// src/compat.ts
|
|
1386
|
-
var
|
|
2038
|
+
var import_node_fs13 = require("node:fs");
|
|
1387
2039
|
function readManifestCompat(manifestPath) {
|
|
1388
2040
|
try {
|
|
1389
|
-
const parsed = JSON.parse((0,
|
|
2041
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(manifestPath, "utf8"));
|
|
1390
2042
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
1391
2043
|
} catch {
|
|
1392
2044
|
return void 0;
|
|
@@ -1394,7 +2046,7 @@ function readManifestCompat(manifestPath) {
|
|
|
1394
2046
|
}
|
|
1395
2047
|
|
|
1396
2048
|
// src/host-doc-lock.ts
|
|
1397
|
-
var
|
|
2049
|
+
var import_node_fs14 = require("node:fs");
|
|
1398
2050
|
var import_node_os4 = require("node:os");
|
|
1399
2051
|
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
1400
2052
|
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
@@ -1408,7 +2060,7 @@ function lockContent(options) {
|
|
|
1408
2060
|
}
|
|
1409
2061
|
function readHolder(lockPath) {
|
|
1410
2062
|
try {
|
|
1411
|
-
const parsed = JSON.parse((0,
|
|
2063
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(lockPath, "utf8"));
|
|
1412
2064
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
1413
2065
|
return parsed;
|
|
1414
2066
|
} catch {
|
|
@@ -1420,7 +2072,7 @@ function lockAgeMs(lockPath, now) {
|
|
|
1420
2072
|
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
1421
2073
|
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
1422
2074
|
try {
|
|
1423
|
-
return Math.max(0, now - (0,
|
|
2075
|
+
return Math.max(0, now - (0, import_node_fs14.statSync)(lockPath).mtimeMs);
|
|
1424
2076
|
} catch {
|
|
1425
2077
|
return 0;
|
|
1426
2078
|
}
|
|
@@ -1443,9 +2095,9 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1443
2095
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1444
2096
|
let fd;
|
|
1445
2097
|
try {
|
|
1446
|
-
fd = (0,
|
|
1447
|
-
(0,
|
|
1448
|
-
(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);
|
|
1449
2101
|
return {
|
|
1450
2102
|
documentPath,
|
|
1451
2103
|
lockPath,
|
|
@@ -1458,11 +2110,11 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1458
2110
|
} catch (error) {
|
|
1459
2111
|
if (fd !== void 0) {
|
|
1460
2112
|
try {
|
|
1461
|
-
(0,
|
|
2113
|
+
(0, import_node_fs14.closeSync)(fd);
|
|
1462
2114
|
} catch {
|
|
1463
2115
|
}
|
|
1464
2116
|
try {
|
|
1465
|
-
(0,
|
|
2117
|
+
(0, import_node_fs14.unlinkSync)(lockPath);
|
|
1466
2118
|
} catch {
|
|
1467
2119
|
}
|
|
1468
2120
|
}
|
|
@@ -1471,12 +2123,12 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1471
2123
|
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
1472
2124
|
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
1473
2125
|
try {
|
|
1474
|
-
(0,
|
|
2126
|
+
(0, import_node_fs14.renameSync)(lockPath, aside);
|
|
1475
2127
|
} catch {
|
|
1476
2128
|
continue;
|
|
1477
2129
|
}
|
|
1478
2130
|
try {
|
|
1479
|
-
(0,
|
|
2131
|
+
(0, import_node_fs14.rmSync)(aside, { force: true });
|
|
1480
2132
|
} catch {
|
|
1481
2133
|
}
|
|
1482
2134
|
continue;
|
|
@@ -1489,7 +2141,7 @@ function releaseSharedDocLock(handle) {
|
|
|
1489
2141
|
try {
|
|
1490
2142
|
const holder = readHolder(handle.lockPath);
|
|
1491
2143
|
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
1492
|
-
(0,
|
|
2144
|
+
(0, import_node_fs14.unlinkSync)(handle.lockPath);
|
|
1493
2145
|
}
|
|
1494
2146
|
} catch {
|
|
1495
2147
|
}
|
|
@@ -1514,11 +2166,11 @@ function cursorPluginsRoot() {
|
|
|
1514
2166
|
return (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
|
|
1515
2167
|
}
|
|
1516
2168
|
function treeHealthy(root) {
|
|
1517
|
-
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("/"))));
|
|
1518
2170
|
}
|
|
1519
2171
|
function readJson(root, rel) {
|
|
1520
2172
|
try {
|
|
1521
|
-
return JSON.parse((0,
|
|
2173
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path8.join)(root, ...rel.split("/")), "utf8"));
|
|
1522
2174
|
} catch {
|
|
1523
2175
|
return null;
|
|
1524
2176
|
}
|
|
@@ -1542,7 +2194,7 @@ function message(error) {
|
|
|
1542
2194
|
}
|
|
1543
2195
|
function discard(path) {
|
|
1544
2196
|
try {
|
|
1545
|
-
(0,
|
|
2197
|
+
(0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
|
|
1546
2198
|
} catch {
|
|
1547
2199
|
}
|
|
1548
2200
|
}
|
|
@@ -1596,10 +2248,10 @@ var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
|
1596
2248
|
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
1597
2249
|
const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1598
2250
|
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
1599
|
-
if (!(0,
|
|
2251
|
+
if (!(0, import_node_fs15.existsSync)(path)) return "current";
|
|
1600
2252
|
let existing;
|
|
1601
2253
|
try {
|
|
1602
|
-
existing = JSON.parse((0,
|
|
2254
|
+
existing = JSON.parse((0, import_node_fs15.readFileSync)(path, "utf8"));
|
|
1603
2255
|
} catch {
|
|
1604
2256
|
return "user-conflict";
|
|
1605
2257
|
}
|
|
@@ -1607,8 +2259,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
|
1607
2259
|
if (!stripped) return "user-conflict";
|
|
1608
2260
|
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
1609
2261
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1610
|
-
(0,
|
|
1611
|
-
(0,
|
|
2262
|
+
(0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
2263
|
+
(0, import_node_fs15.renameSync)(tmp, path);
|
|
1612
2264
|
return "updated";
|
|
1613
2265
|
});
|
|
1614
2266
|
}
|
|
@@ -1632,15 +2284,15 @@ function writeRollbackPointer(root, entry) {
|
|
|
1632
2284
|
const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
|
|
1633
2285
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1634
2286
|
try {
|
|
1635
|
-
(0,
|
|
1636
|
-
(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);
|
|
1637
2289
|
} catch {
|
|
1638
2290
|
discard(tmp);
|
|
1639
2291
|
}
|
|
1640
2292
|
}
|
|
1641
2293
|
function mtimeMs(path) {
|
|
1642
2294
|
try {
|
|
1643
|
-
return (0,
|
|
2295
|
+
return (0, import_node_fs15.statSync)(path).mtimeMs;
|
|
1644
2296
|
} catch {
|
|
1645
2297
|
return 0;
|
|
1646
2298
|
}
|
|
@@ -1649,13 +2301,13 @@ function pruneQuarantine(root, keep) {
|
|
|
1649
2301
|
const dir = (0, import_node_path8.join)(root, "quarantine");
|
|
1650
2302
|
let generations;
|
|
1651
2303
|
try {
|
|
1652
|
-
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));
|
|
1653
2305
|
} catch {
|
|
1654
2306
|
return 0;
|
|
1655
2307
|
}
|
|
1656
2308
|
let stranded = 0;
|
|
1657
2309
|
try {
|
|
1658
|
-
for (const entry of (0,
|
|
2310
|
+
for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
|
|
1659
2311
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1660
2312
|
const path = (0, import_node_path8.join)(dir, entry.name);
|
|
1661
2313
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
@@ -1675,16 +2327,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1675
2327
|
}
|
|
1676
2328
|
function restorePrevious(live, quarantined, broken) {
|
|
1677
2329
|
try {
|
|
1678
|
-
(0,
|
|
2330
|
+
(0, import_node_fs15.renameSync)(live, broken);
|
|
1679
2331
|
} catch {
|
|
1680
2332
|
return "kept-broken";
|
|
1681
2333
|
}
|
|
1682
2334
|
try {
|
|
1683
|
-
(0,
|
|
2335
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1684
2336
|
return "restored";
|
|
1685
2337
|
} catch {
|
|
1686
2338
|
try {
|
|
1687
|
-
(0,
|
|
2339
|
+
(0, import_node_fs15.renameSync)(broken, live);
|
|
1688
2340
|
return "kept-broken";
|
|
1689
2341
|
} catch {
|
|
1690
2342
|
return "lost";
|
|
@@ -1719,7 +2371,7 @@ function cursorArm(options) {
|
|
|
1719
2371
|
if (installed === target && treeHealthy(live)) {
|
|
1720
2372
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1721
2373
|
}
|
|
1722
|
-
if ((0,
|
|
2374
|
+
if ((0, import_node_fs15.existsSync)(live) && !mmiOwned(live)) {
|
|
1723
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)`);
|
|
1724
2376
|
}
|
|
1725
2377
|
if (dryRun) {
|
|
@@ -1737,12 +2389,12 @@ function cursorArm(options) {
|
|
|
1737
2389
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1738
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()}`);
|
|
1739
2391
|
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
1740
|
-
if (!reuseIncoming || !(0,
|
|
2392
|
+
if (!reuseIncoming || !(0, import_node_fs15.existsSync)(incoming)) {
|
|
1741
2393
|
incoming = (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1742
|
-
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 });
|
|
1743
2395
|
try {
|
|
1744
2396
|
discard(incoming);
|
|
1745
|
-
(0,
|
|
2397
|
+
(0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1746
2398
|
} catch (error) {
|
|
1747
2399
|
discard(incoming);
|
|
1748
2400
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
@@ -1766,7 +2418,7 @@ function cursorArm(options) {
|
|
|
1766
2418
|
}
|
|
1767
2419
|
const stamp = Date.now();
|
|
1768
2420
|
const quarantined = (0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1769
|
-
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 });
|
|
1770
2422
|
const started = cursorHostEvidence(env);
|
|
1771
2423
|
if (started) {
|
|
1772
2424
|
const pending = noteLaunchStaged(paths, env, {
|
|
@@ -1781,15 +2433,15 @@ function cursorArm(options) {
|
|
|
1781
2433
|
}
|
|
1782
2434
|
let displaced = false;
|
|
1783
2435
|
try {
|
|
1784
|
-
if ((0,
|
|
1785
|
-
(0,
|
|
2436
|
+
if ((0, import_node_fs15.existsSync)(live)) {
|
|
2437
|
+
(0, import_node_fs15.renameSync)(live, quarantined);
|
|
1786
2438
|
displaced = true;
|
|
1787
2439
|
}
|
|
1788
|
-
(0,
|
|
2440
|
+
(0, import_node_fs15.renameSync)(incoming, live);
|
|
1789
2441
|
} catch (error) {
|
|
1790
|
-
if (displaced && !(0,
|
|
2442
|
+
if (displaced && !(0, import_node_fs15.existsSync)(live)) {
|
|
1791
2443
|
try {
|
|
1792
|
-
(0,
|
|
2444
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1793
2445
|
} catch {
|
|
1794
2446
|
}
|
|
1795
2447
|
}
|
|
@@ -1817,7 +2469,7 @@ function probeCursorLaunchStaging() {
|
|
|
1817
2469
|
}
|
|
1818
2470
|
|
|
1819
2471
|
// src/arm-jervcode.ts
|
|
1820
|
-
var
|
|
2472
|
+
var import_node_fs16 = require("node:fs");
|
|
1821
2473
|
var import_node_path9 = require("node:path");
|
|
1822
2474
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1823
2475
|
function hostEnv(env) {
|
|
@@ -1829,7 +2481,7 @@ function hostCommands(_env) {
|
|
|
1829
2481
|
}
|
|
1830
2482
|
function readPackageSpecs(env) {
|
|
1831
2483
|
try {
|
|
1832
|
-
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"));
|
|
1833
2485
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1834
2486
|
} catch {
|
|
1835
2487
|
return null;
|
|
@@ -1837,7 +2489,7 @@ function readPackageSpecs(env) {
|
|
|
1837
2489
|
}
|
|
1838
2490
|
function installedVersion(env) {
|
|
1839
2491
|
try {
|
|
1840
|
-
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"));
|
|
1841
2493
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1842
2494
|
} catch {
|
|
1843
2495
|
return null;
|
|
@@ -1957,7 +2609,7 @@ function jervcodeArm(options) {
|
|
|
1957
2609
|
}
|
|
1958
2610
|
|
|
1959
2611
|
// src/arm-hermes.ts
|
|
1960
|
-
var
|
|
2612
|
+
var import_node_fs17 = require("node:fs");
|
|
1961
2613
|
var import_node_child_process7 = require("node:child_process");
|
|
1962
2614
|
var import_node_path10 = require("node:path");
|
|
1963
2615
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
@@ -1965,7 +2617,7 @@ var MANIFEST2 = "plugin.yaml";
|
|
|
1965
2617
|
var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
|
|
1966
2618
|
function yamlVersion(root) {
|
|
1967
2619
|
try {
|
|
1968
|
-
const text = (0,
|
|
2620
|
+
const text = (0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, MANIFEST2), "utf8");
|
|
1969
2621
|
try {
|
|
1970
2622
|
const version = JSON.parse(text).version;
|
|
1971
2623
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1979,7 +2631,7 @@ function yamlVersion(root) {
|
|
|
1979
2631
|
}
|
|
1980
2632
|
function packageTreeMarkers(root) {
|
|
1981
2633
|
try {
|
|
1982
|
-
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;
|
|
1983
2635
|
if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1984
2636
|
return files.map((entry) => entry.trim());
|
|
1985
2637
|
}
|
|
@@ -1989,9 +2641,9 @@ function packageTreeMarkers(root) {
|
|
|
1989
2641
|
}
|
|
1990
2642
|
function markerPresent(root, rel) {
|
|
1991
2643
|
const path = (0, import_node_path10.join)(root, ...rel.split("/"));
|
|
1992
|
-
if (!(0,
|
|
2644
|
+
if (!(0, import_node_fs17.existsSync)(path)) return false;
|
|
1993
2645
|
try {
|
|
1994
|
-
const stat = (0,
|
|
2646
|
+
const stat = (0, import_node_fs17.statSync)(path);
|
|
1995
2647
|
return stat.isFile() || stat.isDirectory();
|
|
1996
2648
|
} catch {
|
|
1997
2649
|
return false;
|
|
@@ -2010,7 +2662,7 @@ function treeIncompleteDetail(root, label) {
|
|
|
2010
2662
|
}
|
|
2011
2663
|
function packageIdentity(root) {
|
|
2012
2664
|
try {
|
|
2013
|
-
return JSON.parse((0,
|
|
2665
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
2014
2666
|
} catch {
|
|
2015
2667
|
return false;
|
|
2016
2668
|
}
|
|
@@ -2025,7 +2677,7 @@ function mmiOwned2(root) {
|
|
|
2025
2677
|
}
|
|
2026
2678
|
function discard2(path) {
|
|
2027
2679
|
try {
|
|
2028
|
-
(0,
|
|
2680
|
+
(0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
|
|
2029
2681
|
} catch {
|
|
2030
2682
|
}
|
|
2031
2683
|
}
|
|
@@ -2061,15 +2713,15 @@ function materializeHermesSkills(home) {
|
|
|
2061
2713
|
const skillsRoot = (0, import_node_path10.join)(home, "skills");
|
|
2062
2714
|
const targetRoot = (0, import_node_path10.join)(skillsRoot, "mmi");
|
|
2063
2715
|
try {
|
|
2064
|
-
if (!(0,
|
|
2065
|
-
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);
|
|
2066
2718
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
2067
|
-
(0,
|
|
2719
|
+
(0, import_node_fs17.mkdirSync)(skillsRoot, { recursive: true });
|
|
2068
2720
|
const incoming = (0, import_node_path10.join)(skillsRoot, ".mmi-incoming");
|
|
2069
2721
|
discard2(incoming);
|
|
2070
|
-
(0,
|
|
2722
|
+
(0, import_node_fs17.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
2071
2723
|
discard2(targetRoot);
|
|
2072
|
-
(0,
|
|
2724
|
+
(0, import_node_fs17.renameSync)(incoming, targetRoot);
|
|
2073
2725
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
2074
2726
|
} catch (error) {
|
|
2075
2727
|
return { ok: false, detail: message2(error) };
|
|
@@ -2091,7 +2743,7 @@ function hermesHomes(env = process.env) {
|
|
|
2091
2743
|
const root = hermesHome(env);
|
|
2092
2744
|
const homes = [root];
|
|
2093
2745
|
try {
|
|
2094
|
-
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))) {
|
|
2095
2747
|
if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path10.join)(root, "profiles", entry.name));
|
|
2096
2748
|
}
|
|
2097
2749
|
} catch {
|
|
@@ -2140,7 +2792,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2140
2792
|
detail,
|
|
2141
2793
|
launchStaged: launchStaged || void 0
|
|
2142
2794
|
});
|
|
2143
|
-
if ((0,
|
|
2795
|
+
if ((0, import_node_fs17.existsSync)(live) && !mmiOwned2(live)) {
|
|
2144
2796
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
2145
2797
|
}
|
|
2146
2798
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -2172,10 +2824,10 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2172
2824
|
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
|
|
2173
2825
|
if (!reuseIncoming) {
|
|
2174
2826
|
try {
|
|
2175
|
-
(0,
|
|
2176
|
-
(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 });
|
|
2177
2829
|
discard2(incomingRoot);
|
|
2178
|
-
(0,
|
|
2830
|
+
(0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
2179
2831
|
} catch (error) {
|
|
2180
2832
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
2181
2833
|
}
|
|
@@ -2190,7 +2842,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2190
2842
|
return result(installed, options.target, "defer", incomingTree);
|
|
2191
2843
|
}
|
|
2192
2844
|
}
|
|
2193
|
-
const displaced = (0,
|
|
2845
|
+
const displaced = (0, import_node_fs17.existsSync)(live);
|
|
2194
2846
|
const busy = displaced ? hermesHostEvidence(env, home, options.processListProvider) : null;
|
|
2195
2847
|
if (busy) {
|
|
2196
2848
|
const pending = noteLaunchStaged(options.paths, env, {
|
|
@@ -2205,11 +2857,11 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2205
2857
|
return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
|
|
2206
2858
|
}
|
|
2207
2859
|
try {
|
|
2208
|
-
if (displaced) (0,
|
|
2209
|
-
(0,
|
|
2860
|
+
if (displaced) (0, import_node_fs17.renameSync)(live, quarantine);
|
|
2861
|
+
(0, import_node_fs17.renameSync)(incoming, live);
|
|
2210
2862
|
} catch (error) {
|
|
2211
|
-
if (displaced && !(0,
|
|
2212
|
-
(0,
|
|
2863
|
+
if (displaced && !(0, import_node_fs17.existsSync)(live)) try {
|
|
2864
|
+
(0, import_node_fs17.renameSync)(quarantine, live);
|
|
2213
2865
|
} catch {
|
|
2214
2866
|
}
|
|
2215
2867
|
discard2(incomingRoot);
|
|
@@ -2223,9 +2875,9 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2223
2875
|
const broken = (0, import_node_path10.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
2224
2876
|
const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
|
|
2225
2877
|
try {
|
|
2226
|
-
(0,
|
|
2227
|
-
(0,
|
|
2228
|
-
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);
|
|
2229
2881
|
} catch (error) {
|
|
2230
2882
|
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
2231
2883
|
}
|
|
@@ -2244,7 +2896,7 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
2244
2896
|
}
|
|
2245
2897
|
|
|
2246
2898
|
// src/reap.ts
|
|
2247
|
-
var
|
|
2899
|
+
var import_node_fs18 = require("node:fs");
|
|
2248
2900
|
var import_node_path11 = require("node:path");
|
|
2249
2901
|
var import_node_perf_hooks = require("node:perf_hooks");
|
|
2250
2902
|
function elapsedMs(startedAt) {
|
|
@@ -2254,7 +2906,7 @@ var KEEP_GENERATIONS2 = 2;
|
|
|
2254
2906
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
2255
2907
|
function listDirs(path) {
|
|
2256
2908
|
try {
|
|
2257
|
-
return (0,
|
|
2909
|
+
return (0, import_node_fs18.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2258
2910
|
} catch {
|
|
2259
2911
|
return [];
|
|
2260
2912
|
}
|
|
@@ -2275,7 +2927,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2275
2927
|
continue;
|
|
2276
2928
|
}
|
|
2277
2929
|
try {
|
|
2278
|
-
(0,
|
|
2930
|
+
(0, import_node_fs18.rmSync)((0, import_node_path11.join)(set, version), { recursive: true, force: true });
|
|
2279
2931
|
reaped += 1;
|
|
2280
2932
|
} catch (error) {
|
|
2281
2933
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2308,7 +2960,7 @@ function reapScratch(context, dryRun) {
|
|
|
2308
2960
|
for (const [dir, patterns] of scanned) {
|
|
2309
2961
|
let entries;
|
|
2310
2962
|
try {
|
|
2311
|
-
entries = (0,
|
|
2963
|
+
entries = (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true });
|
|
2312
2964
|
} catch {
|
|
2313
2965
|
unreadable.push(dir);
|
|
2314
2966
|
continue;
|
|
@@ -2319,7 +2971,7 @@ function reapScratch(context, dryRun) {
|
|
|
2319
2971
|
const path = (0, import_node_path11.join)(dir, entry.name);
|
|
2320
2972
|
let ageMs;
|
|
2321
2973
|
try {
|
|
2322
|
-
ageMs = now - (0,
|
|
2974
|
+
ageMs = now - (0, import_node_fs18.statSync)(path).mtimeMs;
|
|
2323
2975
|
} catch {
|
|
2324
2976
|
continue;
|
|
2325
2977
|
}
|
|
@@ -2329,7 +2981,7 @@ function reapScratch(context, dryRun) {
|
|
|
2329
2981
|
continue;
|
|
2330
2982
|
}
|
|
2331
2983
|
try {
|
|
2332
|
-
(0,
|
|
2984
|
+
(0, import_node_fs18.rmSync)(path, { force: true });
|
|
2333
2985
|
reaped += 1;
|
|
2334
2986
|
} catch (error) {
|
|
2335
2987
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2386,7 +3038,7 @@ async function reconcile(options) {
|
|
|
2386
3038
|
const recordArm = (arm, durationMs) => {
|
|
2387
3039
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2388
3040
|
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat, durationMs });
|
|
2389
|
-
options.onArm?.(arm);
|
|
3041
|
+
options.onArm?.({ ...arm, ...durationMs === void 0 ? {} : { durationMs } });
|
|
2390
3042
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2391
3043
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
2392
3044
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
@@ -2551,7 +3203,7 @@ async function reconcile(options) {
|
|
|
2551
3203
|
|
|
2552
3204
|
// src/scheduler.ts
|
|
2553
3205
|
var import_node_child_process8 = require("node:child_process");
|
|
2554
|
-
var
|
|
3206
|
+
var import_node_fs19 = require("node:fs");
|
|
2555
3207
|
var import_node_os6 = require("node:os");
|
|
2556
3208
|
var import_node_path12 = require("node:path");
|
|
2557
3209
|
var TASK_NAME = "MMI Fleet Updater";
|
|
@@ -2711,7 +3363,7 @@ WantedBy=timers.target
|
|
|
2711
3363
|
}
|
|
2712
3364
|
function readFileOrNull(path) {
|
|
2713
3365
|
try {
|
|
2714
|
-
return (0,
|
|
3366
|
+
return (0, import_node_fs19.readFileSync)(path, "utf8");
|
|
2715
3367
|
} catch {
|
|
2716
3368
|
return null;
|
|
2717
3369
|
}
|
|
@@ -2763,22 +3415,22 @@ function windowsSchedulerStatus(env) {
|
|
|
2763
3415
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2764
3416
|
const info = taskRunInfo();
|
|
2765
3417
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2766
|
-
const
|
|
3418
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
2767
3419
|
if (!query.stdout.includes(launcherPath)) {
|
|
2768
|
-
return
|
|
3420
|
+
return fail2(`"${TASK_NAME}" action does not run the hidden launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2769
3421
|
}
|
|
2770
3422
|
const launcher = readFileOrNull(launcherPath);
|
|
2771
3423
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2772
|
-
return
|
|
3424
|
+
return fail2(`"${TASK_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2773
3425
|
}
|
|
2774
3426
|
if (query.stdout.includes("<LogonTrigger>")) {
|
|
2775
|
-
return
|
|
3427
|
+
return fail2(`"${TASK_NAME}" still carries the dormant logon-only trigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2776
3428
|
}
|
|
2777
3429
|
if (!query.stdout.includes("<TimeTrigger>") || !query.stdout.includes(`<Interval>${REPEAT_INTERVAL}</Interval>`)) {
|
|
2778
|
-
return
|
|
3430
|
+
return fail2(`"${TASK_NAME}" has no ${REPEAT_INTERVAL}-repeating TimeTrigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2779
3431
|
}
|
|
2780
3432
|
if (info && neverRan(info) && !info.nextRunTime) {
|
|
2781
|
-
return
|
|
3433
|
+
return fail2(`"${TASK_NAME}" has never run and has no next run time \u2014 run \`mmi-hub autoupdate on\``);
|
|
2782
3434
|
}
|
|
2783
3435
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2784
3436
|
const ran = !info ? "run history unreadable" : neverRan(info) ? "never run yet" : `last run ${info.lastRunTime} \u2192 exit ${info.lastTaskResult ?? "unknown"}`;
|
|
@@ -2792,20 +3444,20 @@ function darwinSchedulerStatus(env) {
|
|
|
2792
3444
|
}
|
|
2793
3445
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2794
3446
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2795
|
-
const
|
|
3447
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2796
3448
|
if (!plist.includes(launcherPath)) {
|
|
2797
|
-
return
|
|
3449
|
+
return fail2(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2798
3450
|
}
|
|
2799
3451
|
const launcher = readFileOrNull(launcherPath);
|
|
2800
3452
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2801
|
-
return
|
|
3453
|
+
return fail2(`"${LAUNCHD_LABEL}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2802
3454
|
}
|
|
2803
3455
|
if (!plist.includes(`<integer>${STARTINTERVAL_SECONDS}</integer>`)) {
|
|
2804
|
-
return
|
|
3456
|
+
return fail2(`"${LAUNCHD_LABEL}" has no ${STARTINTERVAL_SECONDS}s StartInterval \u2014 run \`mmi-hub autoupdate on\``);
|
|
2805
3457
|
}
|
|
2806
3458
|
const loaded = launchctl(["print", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2807
3459
|
if (loaded.status !== 0) {
|
|
2808
|
-
return
|
|
3460
|
+
return fail2(`"${LAUNCHD_LABEL}" is registered but not loaded \u2014 run \`mmi-hub autoupdate on\``);
|
|
2809
3461
|
}
|
|
2810
3462
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2811
3463
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
@@ -2820,20 +3472,20 @@ function linuxSchedulerStatus(env) {
|
|
|
2820
3472
|
}
|
|
2821
3473
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2822
3474
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2823
|
-
const
|
|
3475
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2824
3476
|
if (!service.includes(launcherPath)) {
|
|
2825
|
-
return
|
|
3477
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2826
3478
|
}
|
|
2827
3479
|
const launcher = readFileOrNull(launcherPath);
|
|
2828
3480
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2829
|
-
return
|
|
3481
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2830
3482
|
}
|
|
2831
3483
|
if (!timer.includes(`OnUnitActiveSec=${STARTINTERVAL_SECONDS}`)) {
|
|
2832
|
-
return
|
|
3484
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" has no ${STARTINTERVAL_SECONDS}s OnUnitActiveSec \u2014 run \`mmi-hub autoupdate on\``);
|
|
2833
3485
|
}
|
|
2834
3486
|
const enabled = systemctlUser(["is-enabled", SYSTEMD_TIMER_NAME]);
|
|
2835
3487
|
if (enabled.status !== 0) {
|
|
2836
|
-
return
|
|
3488
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" is registered but not enabled \u2014 run \`mmi-hub autoupdate on\``);
|
|
2837
3489
|
}
|
|
2838
3490
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2839
3491
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${SYSTEMD_TIMER_NAME}" registered ${POSIX_CADENCE}, ${RUN_BOUND_SECONDS}s bound; ${points}` };
|
|
@@ -2859,9 +3511,9 @@ function installWindowsTask(env) {
|
|
|
2859
3511
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2860
3512
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2861
3513
|
const launcherPath = (0, import_node_path12.join)(paths.root, LAUNCHER_FILE);
|
|
2862
|
-
(0,
|
|
3514
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2863
3515
|
const xmlPath = (0, import_node_path12.join)(paths.root, "task.xml");
|
|
2864
|
-
(0,
|
|
3516
|
+
(0, import_node_fs19.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
2865
3517
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2866
3518
|
if (create.status !== 0) {
|
|
2867
3519
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2880,8 +3532,8 @@ function refreshPosixLauncher(env) {
|
|
|
2880
3532
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2881
3533
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2882
3534
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2883
|
-
(0,
|
|
2884
|
-
(0,
|
|
3535
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, desired, "utf8");
|
|
3536
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2885
3537
|
return launcherPath;
|
|
2886
3538
|
}
|
|
2887
3539
|
function installLaunchd(env) {
|
|
@@ -2894,12 +3546,12 @@ function installLaunchd(env) {
|
|
|
2894
3546
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2895
3547
|
const paths = ensureState(env);
|
|
2896
3548
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2897
|
-
(0,
|
|
2898
|
-
(0,
|
|
3549
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3550
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2899
3551
|
const agentsDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
|
|
2900
|
-
(0,
|
|
3552
|
+
(0, import_node_fs19.mkdirSync)(agentsDir, { recursive: true });
|
|
2901
3553
|
const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2902
|
-
(0,
|
|
3554
|
+
(0, import_node_fs19.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2903
3555
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2904
3556
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2905
3557
|
if (load.status !== 0) {
|
|
@@ -2918,12 +3570,12 @@ function installSystemdTimer(env) {
|
|
|
2918
3570
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2919
3571
|
const paths = ensureState(env);
|
|
2920
3572
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2921
|
-
(0,
|
|
2922
|
-
(0,
|
|
3573
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3574
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2923
3575
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2924
|
-
(0,
|
|
2925
|
-
(0,
|
|
2926
|
-
(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");
|
|
2927
3579
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2928
3580
|
if (reload.status !== 0) {
|
|
2929
3581
|
return { ok: false, detail: `systemctl --user daemon-reload failed: ${(reload.stderr || reload.stdout).trim() || "unknown error \u2014 is a user systemd instance running?"}` };
|
|
@@ -2963,11 +3615,11 @@ function uninstallWindowsTask(env) {
|
|
|
2963
3615
|
function uninstallLaunchd(env) {
|
|
2964
3616
|
const plistPath = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2965
3617
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2966
|
-
if (boot.status !== 0 && (0,
|
|
3618
|
+
if (boot.status !== 0 && (0, import_node_fs19.existsSync)(plistPath)) {
|
|
2967
3619
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2968
3620
|
}
|
|
2969
3621
|
try {
|
|
2970
|
-
(0,
|
|
3622
|
+
(0, import_node_fs19.rmSync)(plistPath, { force: true });
|
|
2971
3623
|
} catch {
|
|
2972
3624
|
}
|
|
2973
3625
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
@@ -2977,12 +3629,12 @@ function uninstallSystemdTimer(env) {
|
|
|
2977
3629
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2978
3630
|
const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2979
3631
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2980
|
-
if (disable.status !== 0 && (0,
|
|
3632
|
+
if (disable.status !== 0 && (0, import_node_fs19.existsSync)(timerPath)) {
|
|
2981
3633
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2982
3634
|
}
|
|
2983
3635
|
try {
|
|
2984
|
-
(0,
|
|
2985
|
-
(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 });
|
|
2986
3638
|
} catch {
|
|
2987
3639
|
}
|
|
2988
3640
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2997,16 +3649,16 @@ function uninstallTask(env = process.env) {
|
|
|
2997
3649
|
}
|
|
2998
3650
|
function dropLauncher(env, file) {
|
|
2999
3651
|
try {
|
|
3000
|
-
(0,
|
|
3652
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
|
|
3001
3653
|
} catch {
|
|
3002
3654
|
}
|
|
3003
3655
|
}
|
|
3004
3656
|
|
|
3005
3657
|
// src/status.ts
|
|
3006
|
-
var
|
|
3658
|
+
var import_node_fs20 = require("node:fs");
|
|
3007
3659
|
function readJournal(path) {
|
|
3008
3660
|
try {
|
|
3009
|
-
const events = (0,
|
|
3661
|
+
const events = (0, import_node_fs20.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3010
3662
|
try {
|
|
3011
3663
|
return JSON.parse(line);
|
|
3012
3664
|
} catch {
|
|
@@ -3129,31 +3781,6 @@ function formatHubStatus(status) {
|
|
|
3129
3781
|
}
|
|
3130
3782
|
|
|
3131
3783
|
// src/report.ts
|
|
3132
|
-
var NAME_WIDTH = 8;
|
|
3133
|
-
var VERSION_WIDTH = 15;
|
|
3134
|
-
var DEFAULT_WIDTH = 100;
|
|
3135
|
-
var MIN_NOTE_SPACE = 8;
|
|
3136
|
-
var ACTIVATION_NOTE = {
|
|
3137
|
-
claude: "next session",
|
|
3138
|
-
codex: "next session",
|
|
3139
|
-
jervcode: "next launch",
|
|
3140
|
-
cursor: "on reload",
|
|
3141
|
-
hermes: "after restart",
|
|
3142
|
-
updater: "next run"
|
|
3143
|
-
};
|
|
3144
|
-
var GREEN = "\x1B[32m";
|
|
3145
|
-
var YELLOW = "\x1B[33m";
|
|
3146
|
-
var RED = "\x1B[31m";
|
|
3147
|
-
var RESET = "\x1B[0m";
|
|
3148
|
-
var ACCENT = "\x1B[38;2;125;211;252m";
|
|
3149
|
-
var MUTED = "\x1B[38;2;150;150;150m";
|
|
3150
|
-
var MM_GREEN = "\x1B[38;2;31;209;138m";
|
|
3151
|
-
function accent(text, color) {
|
|
3152
|
-
return color ? ACCENT + text + RESET : text;
|
|
3153
|
-
}
|
|
3154
|
-
function brandLine(rest, color = false) {
|
|
3155
|
-
return `${accent("mmi-hub", color)} \xB7 Mutatis Mutandis \u2014 ${rest}`;
|
|
3156
|
-
}
|
|
3157
3784
|
var RETRY_HINTS = [
|
|
3158
3785
|
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
3159
3786
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
@@ -3182,257 +3809,34 @@ function failConsequence(row) {
|
|
|
3182
3809
|
}
|
|
3183
3810
|
return matchFirst(FAIL_CONSEQUENCE, detail) ?? "update failed \u2014 previous version kept";
|
|
3184
3811
|
}
|
|
3185
|
-
function classify(arm) {
|
|
3186
|
-
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";
|
|
3187
|
-
const note = kind === "updated" ? arm.launchStaged ? "next launch" : ACTIVATION_NOTE[arm.surface] ?? null : kind === "keep" ? "above target" : kind === "retry" ? retryHint(arm.detail) : null;
|
|
3188
|
-
return { name: arm.surface === "updater" ? "hub" : arm.surface, from: arm.from, to: arm.to, kind, note, detail: arm.detail, repeatFailure: arm.repeatFailure === true };
|
|
3189
|
-
}
|
|
3190
|
-
function statusText(kind, dryRun) {
|
|
3191
|
-
if (dryRun && kind === "updated") return "would update";
|
|
3192
|
-
switch (kind) {
|
|
3193
|
-
case "updated":
|
|
3194
|
-
return "updated";
|
|
3195
|
-
case "current":
|
|
3196
|
-
return "current";
|
|
3197
|
-
case "keep":
|
|
3198
|
-
return "keep";
|
|
3199
|
-
case "retry":
|
|
3200
|
-
return "retry";
|
|
3201
|
-
case "failed":
|
|
3202
|
-
return "failed";
|
|
3203
|
-
case "pending":
|
|
3204
|
-
return "pending";
|
|
3205
|
-
}
|
|
3206
|
-
}
|
|
3207
|
-
function glyphFor(kind) {
|
|
3208
|
-
switch (kind) {
|
|
3209
|
-
case "updated":
|
|
3210
|
-
return "\u2713";
|
|
3211
|
-
case "current":
|
|
3212
|
-
return "\u2713";
|
|
3213
|
-
case "keep":
|
|
3214
|
-
return "\u2713";
|
|
3215
|
-
case "retry":
|
|
3216
|
-
return "\u21BB";
|
|
3217
|
-
case "failed":
|
|
3218
|
-
return "\u2717";
|
|
3219
|
-
case "pending":
|
|
3220
|
-
return "\xB7";
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
function colorFor(kind) {
|
|
3224
|
-
return kind === "failed" ? RED : kind === "retry" ? YELLOW : GREEN;
|
|
3225
|
-
}
|
|
3226
|
-
var ARM_TITLE = {
|
|
3227
|
-
updater: "Updating mmi-hub",
|
|
3228
|
-
cli: "Installing mmi-cli",
|
|
3229
|
-
claude: "Arming Claude Code",
|
|
3230
|
-
codex: "Arming Codex",
|
|
3231
|
-
cursor: "Arming Cursor",
|
|
3232
|
-
hermes: "Arming Hermes",
|
|
3233
|
-
jervcode: "Arming JervCode"
|
|
3234
|
-
};
|
|
3235
|
-
var FACE_PRODUCT = "mmi-hub";
|
|
3236
|
-
function updaterFace(options = {}) {
|
|
3237
|
-
return createFace({ product: FACE_PRODUCT, color: options.color ?? false, columns: options.width });
|
|
3238
|
-
}
|
|
3239
|
-
var STATE_WORD = {
|
|
3240
|
-
updated: "updated",
|
|
3241
|
-
current: "current",
|
|
3242
|
-
keep: "kept",
|
|
3243
|
-
retry: "will retry",
|
|
3244
|
-
failed: "failed",
|
|
3245
|
-
pending: "pending"
|
|
3246
|
-
};
|
|
3247
|
-
function stepKindFor(kind) {
|
|
3248
|
-
return kind === "failed" ? "fail" : kind === "retry" ? "note" : "ok";
|
|
3249
|
-
}
|
|
3250
|
-
function checklistLine(arm, seconds, face, options = {}) {
|
|
3251
|
-
const { kind } = classify(arm);
|
|
3252
|
-
const state = options.dryRun && kind === "updated" ? "would update" : STATE_WORD[kind];
|
|
3253
|
-
const title = `${ARM_TITLE[arm.surface] ?? `Arming ${arm.surface}`} \u2014 ${state}`;
|
|
3254
|
-
const truthful = kind === "failed" || kind === "retry" ? null : seconds;
|
|
3255
|
-
return face.step(title, truthful, stepKindFor(kind));
|
|
3256
|
-
}
|
|
3257
|
-
function receiptText(summary, autoupdate, options = {}) {
|
|
3258
|
-
const dry = options.dryRun ?? false;
|
|
3259
|
-
const counts = reportCounts(summary);
|
|
3260
|
-
const health = "Check health any time: mmi-hub status";
|
|
3261
|
-
if (!summary.target) {
|
|
3262
|
-
return [`${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)`, "", health];
|
|
3263
|
-
}
|
|
3264
|
-
const ready = counts.failed === 0;
|
|
3265
|
-
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25CF" : "\u2714";
|
|
3266
|
-
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`;
|
|
3267
|
-
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3268
|
-
return [`${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}`, "", health];
|
|
3269
|
-
}
|
|
3270
|
-
function autoupdateLine(state) {
|
|
3271
|
-
return state.ok ? 'Auto-update on \u2014 hourly task "MMI Fleet Updater".' : `Auto-update NOT enabled \u2014 ${state.detail}`;
|
|
3272
|
-
}
|
|
3273
|
-
function attentionRows(summary) {
|
|
3274
|
-
return collectRows(summary).filter((row) => row.kind === "failed" || row.kind === "retry" || row.kind === "pending").map((row) => ({
|
|
3275
|
-
surface: row.name,
|
|
3276
|
-
state: statusText(row.kind, false),
|
|
3277
|
-
mark: row.kind === "failed" ? "fail" : "note",
|
|
3278
|
-
note: row.kind === "failed" ? failConsequence(row) : row.kind === "retry" ? retryHint(row.detail) : "arm not landed"
|
|
3279
|
-
}));
|
|
3280
|
-
}
|
|
3281
|
-
function wrapWords2(text, available) {
|
|
3282
|
-
if (available < 1) return [text];
|
|
3283
|
-
const lines = [];
|
|
3284
|
-
let current = "";
|
|
3285
|
-
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
3286
|
-
const piece = word.length > available ? word.slice(0, Math.max(1, available - 1)) + "\u2026" : word;
|
|
3287
|
-
if (!current) current = piece;
|
|
3288
|
-
else if (current.length + 1 + piece.length <= available) current += " " + piece;
|
|
3289
|
-
else {
|
|
3290
|
-
lines.push(current);
|
|
3291
|
-
current = piece;
|
|
3292
|
-
}
|
|
3293
|
-
}
|
|
3294
|
-
if (current) lines.push(current);
|
|
3295
|
-
return lines.length ? lines : [""];
|
|
3296
|
-
}
|
|
3297
|
-
function noteLines(prefix, note, width) {
|
|
3298
|
-
const used = stripAnsi(prefix).length;
|
|
3299
|
-
const available = width - used - 1;
|
|
3300
|
-
const indent = available >= MIN_NOTE_SPACE ? " ".repeat(used + 1) : " ".repeat(Math.min(used + 1, Math.max(2, width - MIN_NOTE_SPACE)));
|
|
3301
|
-
const room = available >= MIN_NOTE_SPACE ? available : width - indent.length;
|
|
3302
|
-
const parts = wrapWords2(note, room);
|
|
3303
|
-
const first = available >= MIN_NOTE_SPACE ? prefix + " " + parts[0] : prefix.replace(/\s+$/, "");
|
|
3304
|
-
const lines = [first];
|
|
3305
|
-
for (const part of parts.slice(available >= MIN_NOTE_SPACE ? 1 : 0)) lines.push(indent + part);
|
|
3306
|
-
return lines;
|
|
3307
|
-
}
|
|
3308
|
-
function stripAnsi(text) {
|
|
3309
|
-
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3310
|
-
}
|
|
3311
|
-
function collectRows(summary) {
|
|
3312
|
-
const rows = [];
|
|
3313
|
-
if (summary.updater) rows.push(classify(summary.updater));
|
|
3314
|
-
if (summary.cli) rows.push(classify(summary.cli));
|
|
3315
|
-
for (const arm of summary.plugins) rows.push(classify(arm));
|
|
3316
|
-
for (const surface of summary.surfaces) {
|
|
3317
|
-
if (surface.present && surface.action === "arm-pending") {
|
|
3318
|
-
rows.push({ name: surface.id, from: null, to: summary.target ?? "?", kind: "pending", note: "arm not landed", detail: "", repeatFailure: false });
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
return rows;
|
|
3322
|
-
}
|
|
3323
|
-
function headline(detail, width) {
|
|
3324
|
-
const first = (detail ?? "").split(/(?<=[.;:])\s/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
3325
|
-
return wrapWords2(first || "blocked", Math.max(10, width - 4))[0];
|
|
3326
|
-
}
|
|
3327
|
-
function updateHeader(target, dryRun = false, options) {
|
|
3328
|
-
return brandLine(
|
|
3329
|
-
dryRun ? `update plan to ${target} (dry run)` : `updating to ${target} (verified)`,
|
|
3330
|
-
Boolean(options?.color)
|
|
3331
|
-
);
|
|
3332
|
-
}
|
|
3333
|
-
function renderRowLines(row, ctx) {
|
|
3334
|
-
const paint = (kind, text) => ctx.color ? colorFor(kind) + text + RESET : text;
|
|
3335
|
-
const versions = row.from !== null && row.from !== row.to ? `${row.from} \u2192 ${row.to}` : `${row.to}`;
|
|
3336
|
-
const glyph = ctx.tty ? glyphFor(row.kind) + " " : "";
|
|
3337
|
-
const status = statusText(row.kind, ctx.dryRun).padEnd(ctx.statusWidth);
|
|
3338
|
-
const prefix = " " + row.name.padEnd(NAME_WIDTH) + " " + versions.padEnd(VERSION_WIDTH) + " " + paint(row.kind, glyph + status);
|
|
3339
|
-
return row.note ? noteLines(prefix, row.note, ctx.width) : [prefix.replace(/\s+$/, "")];
|
|
3340
|
-
}
|
|
3341
|
-
function progressiveStatusWidth(dryRun) {
|
|
3342
|
-
return Math.max((dryRun ? "would update" : "updated").length, 7);
|
|
3343
|
-
}
|
|
3344
|
-
function progressiveRow(arm, options = {}) {
|
|
3345
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH);
|
|
3346
|
-
const tty = options.tty ?? false;
|
|
3347
|
-
const color = tty && (options.color ?? true);
|
|
3348
|
-
const dryRun = options.dryRun ?? false;
|
|
3349
|
-
return renderRowLines(classify(arm), { tty, color, dryRun, width, statusWidth: progressiveStatusWidth(dryRun) }).join("\n");
|
|
3350
|
-
}
|
|
3351
3812
|
function reportCounts(summary) {
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
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
|
|
3359
3819
|
};
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
|
|
3368
|
-
const rows = collectRows(summary);
|
|
3369
|
-
const counts = reportCounts(summary);
|
|
3370
|
-
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` : "");
|
|
3371
|
-
const out = ["", " " + footer];
|
|
3372
|
-
if (tty && !dryRun && counts.failed === 0) {
|
|
3373
|
-
out.push(" " + (color ? MUTED + "Suggested next: mmi-cli doctor" + RESET : "Suggested next: mmi-cli doctor"));
|
|
3374
|
-
}
|
|
3375
|
-
const self = summary.updater;
|
|
3376
|
-
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3377
|
-
out.push("");
|
|
3378
|
-
out.push(...noteLines(
|
|
3379
|
-
" note: ",
|
|
3380
|
-
`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}`,
|
|
3381
|
-
width
|
|
3382
|
-
));
|
|
3383
|
-
}
|
|
3384
|
-
if (counts.failed) {
|
|
3385
|
-
out.push("");
|
|
3386
|
-
for (const row of rows.filter((row2) => row2.kind === "failed")) {
|
|
3387
|
-
const mark = tty ? paint("failed", "\u2717 ") : "";
|
|
3388
|
-
out.push(...noteLines(" " + mark + row.name, failConsequence(row), width));
|
|
3389
|
-
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));
|
|
3390
|
-
out.push(...noteLines(" log: ", journalPath, width));
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
return out;
|
|
3394
|
-
}
|
|
3395
|
-
function formatReconcileReport(summary, options = {}) {
|
|
3396
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH);
|
|
3397
|
-
const tty = options.tty ?? false;
|
|
3398
|
-
const color = tty && (options.color ?? true);
|
|
3399
|
-
const dryRun = options.dryRun ?? false;
|
|
3400
|
-
const out = [];
|
|
3401
|
-
const paint = (kind, text) => color ? colorFor(kind) + text + RESET : text;
|
|
3402
|
-
const rows = collectRows(summary);
|
|
3403
|
-
const counts = reportCounts(summary);
|
|
3404
|
-
if (!summary.target) {
|
|
3405
|
-
if ((summary.exit >= 1 || summary.deferred) && summary.detail) {
|
|
3406
|
-
out.push("MMI Hub \u2014 cannot update now");
|
|
3407
|
-
out.push(...noteLines(" " + paint("failed", "blocked"), headline(summary.detail, width), width));
|
|
3408
|
-
out.push(...noteLines(" fix:", retryHint(summary.detail), width));
|
|
3409
|
-
} else {
|
|
3410
|
-
out.push("MMI Hub \u2014 release still publishing, nothing changed (hourly retry)");
|
|
3411
|
-
}
|
|
3412
|
-
return out.join("\n") + "\n";
|
|
3413
|
-
}
|
|
3414
|
-
const allCurrent = rows.length > 0 && counts.updated === 0 && counts.retry === 0 && counts.failed === 0 && rows.every((row) => row.kind === "current" || row.kind === "keep");
|
|
3415
|
-
if (allCurrent && !dryRun) {
|
|
3416
|
-
out.push(`MMI Hub \u2014 MMI is up to date (${summary.target} \xB7 verified)`);
|
|
3417
|
-
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++;
|
|
3418
3827
|
}
|
|
3419
|
-
|
|
3420
|
-
out.push("");
|
|
3421
|
-
const statusWidth = Math.max(...rows.map((row) => statusText(row.kind, dryRun).length), 7);
|
|
3422
|
-
for (const row of rows) out.push(...renderRowLines(row, { tty, color, dryRun, width, statusWidth }));
|
|
3423
|
-
out.push(...reportFooterLines(summary, options));
|
|
3424
|
-
return out.join("\n") + "\n";
|
|
3828
|
+
return counts2;
|
|
3425
3829
|
}
|
|
3426
3830
|
|
|
3427
3831
|
// src/stamp.ts
|
|
3428
|
-
var
|
|
3832
|
+
var import_node_fs21 = require("node:fs");
|
|
3429
3833
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
3430
3834
|
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
|
|
3431
3835
|
function fleetStamp(env = process.env) {
|
|
3432
3836
|
const paths = statePaths(env);
|
|
3433
3837
|
let events = [];
|
|
3434
3838
|
try {
|
|
3435
|
-
events = (0,
|
|
3839
|
+
events = (0, import_node_fs21.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3436
3840
|
try {
|
|
3437
3841
|
return JSON.parse(line);
|
|
3438
3842
|
} catch {
|
|
@@ -3482,7 +3886,7 @@ function fleetStamp(env = process.env) {
|
|
|
3482
3886
|
// src/index.ts
|
|
3483
3887
|
function ownVersion() {
|
|
3484
3888
|
try {
|
|
3485
|
-
return JSON.parse((0,
|
|
3889
|
+
return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path13.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
3486
3890
|
} catch {
|
|
3487
3891
|
return "0.0.0";
|
|
3488
3892
|
}
|
|
@@ -3502,147 +3906,34 @@ var HELP = `mmi-hub ${ownVersion()} \u2014 install and maintain MMI tooling
|
|
|
3502
3906
|
State remains under $MMI_UPDATER_HOME (default ~/.mmi/updater), and the Windows task name remains
|
|
3503
3907
|
"MMI Fleet Updater", so existing installations continue in place.
|
|
3504
3908
|
Status performs no npm install, host repair, scheduler write, state mkdir, or repository cleanup.`;
|
|
3505
|
-
function
|
|
3506
|
-
return {
|
|
3507
|
-
tty: Boolean(process.stdout.isTTY),
|
|
3508
|
-
color: !process.env.NO_COLOR,
|
|
3509
|
-
width: process.stdout.columns ?? 100,
|
|
3510
|
-
dryRun,
|
|
3511
|
-
journalPath: statePaths(process.env).journalPath
|
|
3512
|
-
};
|
|
3513
|
-
}
|
|
3514
|
-
function printReconcile(summary, json, dryRun = false) {
|
|
3515
|
-
if (json) {
|
|
3516
|
-
process.stdout.write(JSON.stringify(summary, null, 2) + "\n");
|
|
3517
|
-
return;
|
|
3518
|
-
}
|
|
3519
|
-
process.stdout.write(formatReconcileReport(summary, humanOptions(dryRun)));
|
|
3520
|
-
}
|
|
3521
|
-
function faceEnabled(args, json) {
|
|
3522
|
-
return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
3523
|
-
}
|
|
3524
|
-
function openFace(args, json, dryRun) {
|
|
3525
|
-
if (!faceEnabled(args, json)) return null;
|
|
3526
|
-
const face = updaterFace(humanOptions(dryRun));
|
|
3527
|
-
if (!process.env.MMI_UPDATER_REEXEC) for (const line of face.welcome()) process.stdout.write(line + "\n");
|
|
3528
|
-
return face;
|
|
3529
|
-
}
|
|
3530
|
-
function finishFace(face, summary, autoupdate, dryRun) {
|
|
3531
|
-
const options = humanOptions(dryRun);
|
|
3532
|
-
const write = (line) => {
|
|
3533
|
-
process.stdout.write(line + "\n");
|
|
3534
|
-
};
|
|
3535
|
-
if (!summary.target) {
|
|
3536
|
-
process.stdout.write(formatReconcileReport(summary, { ...options, tty: false }));
|
|
3537
|
-
} else {
|
|
3538
|
-
for (const line of renderRows(face, attentionRows(summary))) write(line);
|
|
3539
|
-
}
|
|
3540
|
-
const body = receiptText(summary, autoupdate, { dryRun });
|
|
3541
|
-
const counts = reportCounts(summary);
|
|
3542
|
-
if (counts.failed > 0) {
|
|
3543
|
-
body.push("", "Fix: mmi-cli doctor, then mmi-hub update", `Log: ${options.journalPath}`);
|
|
3544
|
-
}
|
|
3545
|
-
const self = summary.updater;
|
|
3546
|
-
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3547
|
-
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}`);
|
|
3548
|
-
}
|
|
3549
|
-
for (const line of face.receipt(body)) write(line);
|
|
3550
|
-
write(face.signOff());
|
|
3551
|
-
}
|
|
3552
|
-
function handOff(reexec, args) {
|
|
3909
|
+
function handOff(reexec, args, env) {
|
|
3553
3910
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
3554
3911
|
stdio: "inherit",
|
|
3555
3912
|
windowsHide: true,
|
|
3556
|
-
env: { ...
|
|
3913
|
+
env: { ...env, MMI_UPDATER_REEXEC: "1" }
|
|
3557
3914
|
});
|
|
3558
3915
|
if (child.error || typeof child.status !== "number") {
|
|
3559
|
-
|
|
3560
|
-
`);
|
|
3561
|
-
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`);
|
|
3562
3917
|
}
|
|
3563
3918
|
return child.status;
|
|
3564
3919
|
}
|
|
3565
|
-
function heldOutput(write) {
|
|
3566
|
-
const held = [];
|
|
3567
|
-
let streaming = false;
|
|
3568
|
-
return {
|
|
3569
|
-
emit: (line) => {
|
|
3570
|
-
if (streaming) write(line);
|
|
3571
|
-
else held.push(line);
|
|
3572
|
-
},
|
|
3573
|
-
stream: () => {
|
|
3574
|
-
if (streaming) return;
|
|
3575
|
-
streaming = true;
|
|
3576
|
-
for (const line of held) write(line);
|
|
3577
|
-
held.length = 0;
|
|
3578
|
-
}
|
|
3579
|
-
};
|
|
3580
|
-
}
|
|
3581
3920
|
function shippedTargetFrom(args) {
|
|
3582
3921
|
const index = args.indexOf("--shipped");
|
|
3583
3922
|
if (index < 0) return void 0;
|
|
3584
3923
|
const value = (args[index + 1] ?? "").trim();
|
|
3585
3924
|
return value && !value.startsWith("--") ? value : void 0;
|
|
3586
3925
|
}
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
}
|
|
3598
|
-
const reportOptions = humanOptions(dryRun);
|
|
3599
|
-
const spinner = createSpinner(face ?? updaterFace(reportOptions), { animate: true, stream: process.stderr });
|
|
3600
|
-
let spinning = false;
|
|
3601
|
-
const phase = (line) => {
|
|
3602
|
-
if (spinning) spinner.say(line);
|
|
3603
|
-
else {
|
|
3604
|
-
spinner.start(line);
|
|
3605
|
-
spinning = true;
|
|
3606
|
-
}
|
|
3607
|
-
};
|
|
3608
|
-
const persist = (line) => {
|
|
3609
|
-
if (spinning) {
|
|
3610
|
-
spinner.stop();
|
|
3611
|
-
spinning = false;
|
|
3612
|
-
}
|
|
3613
|
-
process.stdout.write(line + "\n");
|
|
3614
|
-
};
|
|
3615
|
-
let headerEmitted = false;
|
|
3616
|
-
let summary;
|
|
3617
|
-
const { emit, stream } = heldOutput(persist);
|
|
3618
|
-
let arms = 0;
|
|
3619
|
-
let mark = Date.now();
|
|
3620
|
-
try {
|
|
3621
|
-
summary = await reconcile({
|
|
3622
|
-
dryRun,
|
|
3623
|
-
shippedTarget,
|
|
3624
|
-
onPhase: (line) => phase(line),
|
|
3625
|
-
onTargetResolved: face ? void 0 : (target) => {
|
|
3626
|
-
emit(updateHeader(target, dryRun, reportOptions));
|
|
3627
|
-
emit("");
|
|
3628
|
-
headerEmitted = true;
|
|
3629
|
-
},
|
|
3630
|
-
onArm: (arm) => {
|
|
3631
|
-
const seconds = (Date.now() - mark) / 1e3;
|
|
3632
|
-
mark = Date.now();
|
|
3633
|
-
if (arms++) stream();
|
|
3634
|
-
emit(face ? checklistLine(arm, seconds, face, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3635
|
-
}
|
|
3636
|
-
});
|
|
3637
|
-
} finally {
|
|
3638
|
-
spinner.stop();
|
|
3639
|
-
}
|
|
3640
|
-
if (summary.reexec) return { summary, rendered: true };
|
|
3641
|
-
stream();
|
|
3642
|
-
if (face) return { summary, rendered: true };
|
|
3643
|
-
if (headerEmitted) process.stdout.write(reportFooterLines(summary, reportOptions).join("\n") + "\n");
|
|
3644
|
-
else process.stdout.write(formatReconcileReport(summary, reportOptions));
|
|
3645
|
-
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
|
+
});
|
|
3646
3937
|
}
|
|
3647
3938
|
async function main(args = process.argv.slice(2)) {
|
|
3648
3939
|
if (args.includes("--version")) {
|
|
@@ -3660,41 +3951,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
3660
3951
|
process.stdout.write(json ? JSON.stringify(status, null, 2) + "\n" : formatHubStatus(status) + "\n");
|
|
3661
3952
|
return status.failures.length ? 1 : 0;
|
|
3662
3953
|
}
|
|
3663
|
-
if (command === "update")
|
|
3664
|
-
const dryRun = args.includes("--dry-run");
|
|
3665
|
-
const face = openFace(args, json, dryRun);
|
|
3666
|
-
const { summary, rendered } = await runUpdate(args, json, face);
|
|
3667
|
-
if (summary.reexec) return handOff(summary.reexec, args);
|
|
3668
|
-
if (face) finishFace(face, summary, null, dryRun);
|
|
3669
|
-
else if (!rendered) printReconcile(summary, json, dryRun);
|
|
3670
|
-
return summary.exit;
|
|
3671
|
-
}
|
|
3672
|
-
if (command === "install") {
|
|
3673
|
-
const dryRun = args.includes("--dry-run");
|
|
3674
|
-
const face = openFace(args, json, dryRun);
|
|
3675
|
-
const shippedTarget = shippedTargetFrom(args);
|
|
3676
|
-
const { summary, rendered } = await runUpdate(args, json, face, shippedTarget);
|
|
3677
|
-
if (summary.reexec) {
|
|
3678
|
-
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install" && arg !== "--shipped" && arg !== shippedTarget)]);
|
|
3679
|
-
const scheduled = installTask();
|
|
3680
|
-
if (!json) {
|
|
3681
|
-
process.stdout.write(autoupdateLine(scheduled) + "\n");
|
|
3682
|
-
} else {
|
|
3683
|
-
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: scheduled }, null, 2) + "\n");
|
|
3684
|
-
}
|
|
3685
|
-
return Math.max(handedOff, scheduled.ok ? 0 : 1);
|
|
3686
|
-
}
|
|
3687
|
-
const schedule = installTask();
|
|
3688
|
-
if (json) {
|
|
3689
|
-
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
|
|
3690
|
-
} else if (face) {
|
|
3691
|
-
finishFace(face, summary, schedule, dryRun);
|
|
3692
|
-
} else {
|
|
3693
|
-
if (!rendered) printReconcile(summary, false, dryRun);
|
|
3694
|
-
process.stdout.write(autoupdateLine(schedule) + "\n");
|
|
3695
|
-
}
|
|
3696
|
-
return Math.max(summary.exit, schedule.ok ? 0 : 1);
|
|
3697
|
-
}
|
|
3954
|
+
if (command === "update" || command === "install") return runMaintenance2(args);
|
|
3698
3955
|
if (command === "autoupdate") {
|
|
3699
3956
|
const commandIndex = args.indexOf(command);
|
|
3700
3957
|
const mode = args[commandIndex + 1];
|
|
@@ -3719,27 +3976,18 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3719
3976
|
}
|
|
3720
3977
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3721
3978
|
0 && (module.exports = {
|
|
3722
|
-
ACCENT,
|
|
3723
|
-
FACE_PRODUCT,
|
|
3724
|
-
MM_GREEN,
|
|
3725
|
-
RESET,
|
|
3726
|
-
accent,
|
|
3727
3979
|
acquireSharedDocLock,
|
|
3728
|
-
|
|
3729
|
-
autoupdateLine,
|
|
3730
|
-
brandLine,
|
|
3980
|
+
assertFaceConformance,
|
|
3731
3981
|
buildWatcherCommand,
|
|
3732
3982
|
checkAtomicity,
|
|
3733
|
-
checklistLine,
|
|
3734
3983
|
claudeArm,
|
|
3735
3984
|
claudeHostEvidence,
|
|
3736
3985
|
clearLaunchTarget,
|
|
3737
3986
|
clearPending,
|
|
3987
|
+
createInstallerRun,
|
|
3738
3988
|
enumerateSurfaces,
|
|
3739
3989
|
failConsequence,
|
|
3740
3990
|
fleetStamp,
|
|
3741
|
-
formatReconcileReport,
|
|
3742
|
-
heldOutput,
|
|
3743
3991
|
hermesArm,
|
|
3744
3992
|
hermesHome,
|
|
3745
3993
|
hermesHomes,
|
|
@@ -3766,17 +4014,15 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3766
4014
|
probeHermesLaunchStaging,
|
|
3767
4015
|
probeJervcodeInstallation,
|
|
3768
4016
|
probeLaunchTargetVersion,
|
|
3769
|
-
progressiveRow,
|
|
3770
4017
|
readLaunchTarget,
|
|
3771
4018
|
readPending,
|
|
3772
|
-
receiptText,
|
|
3773
4019
|
reconcile,
|
|
3774
4020
|
reconcileCursorUserHooks,
|
|
3775
4021
|
releaseSharedDocLock,
|
|
3776
4022
|
reportCounts,
|
|
3777
|
-
reportFooterLines,
|
|
3778
4023
|
retiredSurfaces,
|
|
3779
4024
|
retryHint,
|
|
4025
|
+
runMaintenance,
|
|
3780
4026
|
schedulePendingWatcher,
|
|
3781
4027
|
schedulerStatus,
|
|
3782
4028
|
sharedDocLockPath,
|
|
@@ -3787,11 +4033,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3787
4033
|
systemdServiceUnit,
|
|
3788
4034
|
systemdTimerUnit,
|
|
3789
4035
|
taskXml,
|
|
3790
|
-
updateHeader,
|
|
3791
|
-
updaterFace,
|
|
3792
4036
|
watcherLockPath,
|
|
3793
4037
|
withSharedDocLock,
|
|
3794
|
-
wrapWords,
|
|
3795
4038
|
writeLaunchTarget,
|
|
3796
4039
|
writePending
|
|
3797
4040
|
});
|