@mutmutco/hub 4.4.10 → 4.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1034 -699
- 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 get you ready.",
|
|
109
99
|
accent: "38;2;249;115;22",
|
|
110
|
-
warm: "Welcome.
|
|
100
|
+
warm: "Welcome back. Checking for updates...",
|
|
101
|
+
doctor: "mm-strategy doctor"
|
|
111
102
|
}),
|
|
112
103
|
"mmi-hub": Object.freeze({
|
|
113
104
|
name: "mmi-hub",
|
|
105
|
+
installWarm: "Welcome. Let's get you ready.",
|
|
114
106
|
accent: "38;2;125;211;252",
|
|
115
|
-
warm: "Welcome back. Checking
|
|
107
|
+
warm: "Welcome back. Checking for updates...",
|
|
108
|
+
doctor: "mmi doctor"
|
|
116
109
|
}),
|
|
117
110
|
"jerv-hub": Object.freeze({
|
|
118
111
|
name: "jerv-hub",
|
|
112
|
+
installWarm: "Welcome. Let's get you ready.",
|
|
119
113
|
accent: "38;2;248;113;113",
|
|
120
|
-
warm: "Welcome back. Checking
|
|
114
|
+
warm: "Welcome back. Checking for updates...",
|
|
115
|
+
doctor: "jerv doctor"
|
|
121
116
|
}),
|
|
122
117
|
jervcode: Object.freeze({
|
|
123
118
|
name: "JervCode",
|
|
119
|
+
installWarm: "Welcome. Let's get you ready.",
|
|
124
120
|
accent: "38;2;192;132;252",
|
|
125
|
-
warm: "Welcome back.
|
|
121
|
+
warm: "Welcome back. Checking for updates...",
|
|
122
|
+
doctor: "jervcode doctor"
|
|
126
123
|
})
|
|
127
124
|
});
|
|
128
125
|
function identityFor(product) {
|
|
@@ -132,7 +129,10 @@ function identityFor(product) {
|
|
|
132
129
|
}
|
|
133
130
|
return identity;
|
|
134
131
|
}
|
|
132
|
+
|
|
133
|
+
// ../installer/face/src/face.ts
|
|
135
134
|
var GLYPH = Object.freeze({
|
|
135
|
+
clock: "\u23F0",
|
|
136
136
|
diamond: "\u25C6",
|
|
137
137
|
hollow: "\u25C7",
|
|
138
138
|
bar: "\u2502",
|
|
@@ -156,6 +156,9 @@ var ANSI = /\u001b\[[0-9;]*m/g;
|
|
|
156
156
|
function visibleWidth(text) {
|
|
157
157
|
return [...String(text).replace(ANSI, "")].length;
|
|
158
158
|
}
|
|
159
|
+
function stripColor(text) {
|
|
160
|
+
return String(text).replace(ANSI, "");
|
|
161
|
+
}
|
|
159
162
|
function faceWidth(columns) {
|
|
160
163
|
const raw = Number(columns);
|
|
161
164
|
return Math.max(40, Math.min(100, Number.isFinite(raw) && raw > 0 ? raw : 100));
|
|
@@ -184,7 +187,14 @@ function wrapWords(text, width) {
|
|
|
184
187
|
if (line) lines.push(line);
|
|
185
188
|
return lines.length ? lines : [""];
|
|
186
189
|
}
|
|
187
|
-
|
|
190
|
+
var PROGRESS_PROTOCOL = 1;
|
|
191
|
+
function readProgressFd(env) {
|
|
192
|
+
const fd = Number.parseInt(String(env.MM_PROGRESS_FD ?? ""), 10);
|
|
193
|
+
if (!Number.isInteger(fd) || fd <= 0) return null;
|
|
194
|
+
const protocol = Number.parseInt(String(env.MM_PROGRESS_PROTOCOL ?? PROGRESS_PROTOCOL), 10);
|
|
195
|
+
return protocol === PROGRESS_PROTOCOL ? fd : null;
|
|
196
|
+
}
|
|
197
|
+
function createFace({ product, color = false, columns, env = process.env, operation }) {
|
|
188
198
|
const identity = identityFor(product);
|
|
189
199
|
const width = faceWidth(columns);
|
|
190
200
|
const paint = (sgr, text) => color ? `\x1B[${sgr}m${text}\x1B[0m` : String(text);
|
|
@@ -192,28 +202,49 @@ function createFace({ product, color = false, columns, env = process.env }) {
|
|
|
192
202
|
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
193
203
|
const continuedPhases = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((phase) => phase.trim()).filter(Boolean));
|
|
194
204
|
const continuesFace = continuedPhases.size > 0;
|
|
195
|
-
const
|
|
205
|
+
const nested = env.MM_OUTER_CONSOLE === "1";
|
|
206
|
+
const progressFd = readProgressFd(env);
|
|
207
|
+
const progressFile = env.MM_PROGRESS_PROTOCOL === "1" ? env.MM_PROGRESS_FILE : void 0;
|
|
208
|
+
const emitMilestone = (title, measure, kind) => {
|
|
209
|
+
if (progressFd === null && !progressFile) return false;
|
|
210
|
+
const record = { v: PROGRESS_PROTOCOL, step: stripColor(title), state: kind };
|
|
211
|
+
if (typeof measure === "number") record.ms = Math.max(0, Math.round(measure * 1e3));
|
|
212
|
+
if (kind === "running" && typeof measure === "string") record.measure = measure;
|
|
213
|
+
try {
|
|
214
|
+
if (progressFile) (0, import_node_fs.appendFileSync)(progressFile, `${JSON.stringify(record)}
|
|
215
|
+
`);
|
|
216
|
+
else (0, import_node_fs.writeSync)(progressFd, `${JSON.stringify(record)}
|
|
217
|
+
`);
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const welcome = () => continuesFace || nested ? [] : [
|
|
196
224
|
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
197
225
|
bar(),
|
|
198
|
-
`${bar()} ${identity.warm}`,
|
|
226
|
+
`${bar()} ${operation === "install" ? identity.installWarm : identity.warm}`,
|
|
199
227
|
bar()
|
|
200
228
|
];
|
|
201
229
|
const continues = (phase, kind = "ok") => {
|
|
202
230
|
const inherited = continuedPhases.delete(String(phase).trim());
|
|
203
231
|
return kind === "fail" ? false : inherited;
|
|
204
232
|
};
|
|
205
|
-
const step = (title,
|
|
206
|
-
|
|
207
|
-
const
|
|
233
|
+
const step = (title, measure = null, kind = "ok") => {
|
|
234
|
+
if (emitMilestone(title, measure, kind)) return "";
|
|
235
|
+
const glyph = title === "Armed hourly updates" && kind === "ok" ? GLYPH.clock : kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.dot) : paint(PALETTE.green, GLYPH.check);
|
|
236
|
+
const measured = measure === null || measure === void 0 ? "" : paint(PALETTE.muted, typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : String(measure).trim());
|
|
237
|
+
const time = measured;
|
|
208
238
|
const column = Math.min(44, Math.max(0, width - 8));
|
|
209
|
-
const reserved = time ? 6 : 0;
|
|
239
|
+
const reserved = time ? Math.max(6, visibleWidth(time) + 2) : 0;
|
|
210
240
|
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
211
241
|
const head = `${bar()} ${glyph} ${first}`;
|
|
212
242
|
const pad = Math.max(1, column - visibleWidth(head));
|
|
213
243
|
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
214
244
|
};
|
|
215
|
-
const relay = (text) => String(text).
|
|
216
|
-
const receipt = (lines) => {
|
|
245
|
+
const relay = (text) => String(text).replace(/\r?\n$/, "").split(/\r?\n/).flatMap((line) => line.trim() === "" ? [bar()] : (visibleWidth(line) <= width - TITLE_COLUMN ? [line] : wrapWords(line, width - TITLE_COLUMN)).map((part) => `${bar()}${indent}${part}`)).join("\n");
|
|
246
|
+
const receipt = (lines, { ready = true } = {}) => {
|
|
247
|
+
if (ready && nested) return [];
|
|
217
248
|
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
218
249
|
const line = String(raw);
|
|
219
250
|
if (visibleWidth(line) <= width - 6) return [line];
|
|
@@ -229,87 +260,800 @@ function createFace({ product, color = false, columns, env = process.env }) {
|
|
|
229
260
|
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
230
261
|
];
|
|
231
262
|
};
|
|
232
|
-
const
|
|
263
|
+
const outcome = (changed, options = {}) => {
|
|
264
|
+
const fact = String(changed ?? "").trim();
|
|
265
|
+
if (!fact) {
|
|
266
|
+
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})`);
|
|
267
|
+
}
|
|
268
|
+
return receipt([
|
|
269
|
+
`${GLYPH.check} ${identity.name} is ready.`,
|
|
270
|
+
fact,
|
|
271
|
+
`Check health any time: ${identity.doctor}`
|
|
272
|
+
], options);
|
|
273
|
+
};
|
|
274
|
+
const signOff = () => nested ? "" : `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
233
275
|
const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
|
|
234
|
-
return { identity, width, welcome, continues, step, relay, receipt, signOff, refusal, paint };
|
|
276
|
+
return { identity, width, nested, emitsProgress: progressFd !== null || Boolean(progressFile), welcome, continues, step, progress: (title, measure = null) => emitMilestone(title, measure, "running"), relay, receipt, outcome, signOff, refusal, paint };
|
|
235
277
|
}
|
|
278
|
+
|
|
279
|
+
// ../installer/face/src/shell.ts
|
|
236
280
|
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
|
-
}
|
|
281
|
+
|
|
282
|
+
// ../installer/face/src/spinner.ts
|
|
283
|
+
var import_node_fs2 = require("node:fs");
|
|
284
|
+
var import_node_worker_threads = require("node:worker_threads");
|
|
285
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
286
|
+
var WORKER_SOURCE = `
|
|
287
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
288
|
+
const { writeSync, appendFileSync } = require('node:fs');
|
|
289
|
+
const control = new Int32Array(workerData.control);
|
|
290
|
+
let frames = workerData.frames, frame = workerData.frame;
|
|
291
|
+
function draw() {
|
|
292
|
+
if (Atomics.compareExchange(control, 1, 0, 1) !== 0) return;
|
|
293
|
+
try {
|
|
294
|
+
if (Atomics.load(control, 0) || Atomics.load(control, 2) || !frames.length) return;
|
|
295
|
+
const text = frames[frame++ % frames.length].replace('__elapsed__', Math.floor((Date.now() - workerData.started) / 1000) + 's');
|
|
296
|
+
writeSync(2, text);
|
|
297
|
+
if (workerData.transcriptPath) appendFileSync(workerData.transcriptPath, JSON.stringify({ channel: 'spinner', text }) + '\\n');
|
|
298
|
+
} finally {
|
|
299
|
+
Atomics.store(control, 1, 0);
|
|
300
|
+
Atomics.notify(control, 1);
|
|
265
301
|
}
|
|
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
|
-
}
|
|
272
|
-
function clip(text, width) {
|
|
273
|
-
if (visibleWidth(text) <= width) return text;
|
|
274
|
-
return `\u2026${[...text].slice(-(width - 1)).join("")}`;
|
|
275
302
|
}
|
|
276
|
-
|
|
277
|
-
|
|
303
|
+
parentPort.on('message', (next) => {
|
|
304
|
+
if (Atomics.load(control, 2)) return;
|
|
305
|
+
frames = next.frames;
|
|
306
|
+
frame = next.frame;
|
|
307
|
+
});
|
|
308
|
+
setInterval(draw, workerData.intervalMs);
|
|
309
|
+
`;
|
|
310
|
+
function createSpinner(face, { animate, stream, intervalMs = 90, transcriptPath }) {
|
|
311
|
+
animate = animate && !face.nested && !face.emitsProgress;
|
|
278
312
|
let timer = null;
|
|
279
|
-
let
|
|
313
|
+
let worker = null;
|
|
314
|
+
let control = null;
|
|
315
|
+
let frames = [];
|
|
280
316
|
let frame = 0;
|
|
281
|
-
|
|
282
|
-
|
|
317
|
+
let started = 0;
|
|
318
|
+
let suspended = false;
|
|
319
|
+
const write = (text) => {
|
|
320
|
+
if (stream) stream.write(text);
|
|
321
|
+
else {
|
|
322
|
+
(0, import_node_fs2.writeSync)(2, text);
|
|
323
|
+
if (transcriptPath) (0, import_node_fs2.appendFileSync)(transcriptPath, `${JSON.stringify({ channel: "spinner", text })}
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const render = (text, measure) => {
|
|
328
|
+
const line = face.step(text, measure ?? "__elapsed__", "note").split("\n")[0];
|
|
329
|
+
frames = line ? FRAMES.map((glyph) => `\r\x1B[2K${line.replace(GLYPH.dot, glyph)}`) : [];
|
|
283
330
|
};
|
|
284
331
|
const draw = () => {
|
|
285
|
-
if (!
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
332
|
+
if (animate && !suspended && frames.length) write(frames[frame++ % frames.length].replace("__elapsed__", `${Math.floor((Date.now() - started) / 1e3)}s`));
|
|
333
|
+
};
|
|
334
|
+
const pause = () => {
|
|
335
|
+
if (!control) return;
|
|
336
|
+
Atomics.store(control, 0, 1);
|
|
337
|
+
while (Atomics.load(control, 1)) Atomics.wait(control, 1, 1);
|
|
338
|
+
};
|
|
339
|
+
const halt = () => {
|
|
340
|
+
if (timer) clearInterval(timer);
|
|
341
|
+
timer = null;
|
|
342
|
+
if (control) Atomics.store(control, 2, 1);
|
|
343
|
+
pause();
|
|
344
|
+
if (worker) void worker.terminate();
|
|
345
|
+
worker = null;
|
|
346
|
+
control = null;
|
|
289
347
|
};
|
|
290
348
|
return {
|
|
291
|
-
start(
|
|
292
|
-
|
|
349
|
+
start(text, measure = null) {
|
|
350
|
+
if (!animate) return;
|
|
351
|
+
halt();
|
|
352
|
+
suspended = false;
|
|
353
|
+
started = Date.now();
|
|
354
|
+
render(text, measure);
|
|
293
355
|
frame = 0;
|
|
294
356
|
draw();
|
|
295
|
-
if (
|
|
357
|
+
if (!frames.length) return;
|
|
358
|
+
if (stream) timer = setInterval(draw, intervalMs).unref();
|
|
359
|
+
else {
|
|
360
|
+
control = new Int32Array(new SharedArrayBuffer(12));
|
|
361
|
+
try {
|
|
362
|
+
worker = new import_node_worker_threads.Worker(WORKER_SOURCE, { eval: true, workerData: {
|
|
363
|
+
control: control.buffer,
|
|
364
|
+
frames,
|
|
365
|
+
frame,
|
|
366
|
+
intervalMs,
|
|
367
|
+
transcriptPath,
|
|
368
|
+
started
|
|
369
|
+
} });
|
|
370
|
+
const active = worker;
|
|
371
|
+
worker.on("error", () => {
|
|
372
|
+
if (worker === active) halt();
|
|
373
|
+
});
|
|
374
|
+
worker.unref();
|
|
375
|
+
} catch {
|
|
376
|
+
halt();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
296
379
|
},
|
|
297
|
-
say(
|
|
298
|
-
|
|
380
|
+
say(text, measure = null) {
|
|
381
|
+
if (!animate) return;
|
|
382
|
+
pause();
|
|
383
|
+
render(text, measure);
|
|
299
384
|
draw();
|
|
385
|
+
worker?.postMessage({ frames, frame });
|
|
386
|
+
if (control && !suspended) Atomics.store(control, 0, 0);
|
|
387
|
+
},
|
|
388
|
+
pause() {
|
|
389
|
+
if (suspended) return;
|
|
390
|
+
suspended = true;
|
|
391
|
+
pause();
|
|
392
|
+
if (animate && frames.length) write("\r\x1B[2K");
|
|
393
|
+
},
|
|
394
|
+
resume() {
|
|
395
|
+
suspended = false;
|
|
396
|
+
if (control) Atomics.store(control, 0, 0);
|
|
397
|
+
if (timer || worker) draw();
|
|
300
398
|
},
|
|
301
399
|
stop() {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
400
|
+
halt();
|
|
401
|
+
frames = [];
|
|
402
|
+
if (animate) write("\r\x1B[2K");
|
|
305
403
|
}
|
|
306
404
|
};
|
|
307
405
|
}
|
|
308
406
|
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
407
|
+
|
|
408
|
+
// ../installer/face/src/conformance.ts
|
|
309
409
|
var ALLOWED = new Set(Object.values(GLYPH));
|
|
410
|
+
var FORBIDDEN = /* @__PURE__ */ new Map([
|
|
411
|
+
["\u2713", "U+2713 is not in the guidebook glyph set; the check is U+2714"],
|
|
412
|
+
["\u2717", "U+2717 is not in the guidebook glyph set; the cross is U+2716"],
|
|
413
|
+
["\u21BB", "U+21BB is not in the guidebook glyph set; a retry uses the dot U+25CF"],
|
|
414
|
+
["\u250C", "U+250C is clack's intro corner; the welcome opens with the branded diamond"],
|
|
415
|
+
["\u251C", "U+251C is clack's note frame; a receipt box pairs U+256D/U+256E over U+2570/U+256F"],
|
|
416
|
+
["\u2514", "U+2514 is clack's outro corner; the sign-off carries the diamond"]
|
|
417
|
+
]);
|
|
418
|
+
var EMOJI = /[\u{1f300}-\u{1faff}\u{2600}-\u{27bf}]/u;
|
|
419
|
+
function fail(rule, detail) {
|
|
420
|
+
throw new Error(`installer face: ${rule} \u2014 ${detail}`);
|
|
421
|
+
}
|
|
422
|
+
function assertFaceConformance(lines, options) {
|
|
423
|
+
const identity = identityFor(options.product);
|
|
424
|
+
const width = options.columns ?? 100;
|
|
425
|
+
const rendered = lines.flatMap((line) => String(line).split("\n"));
|
|
426
|
+
const plain = rendered.map(stripColor);
|
|
427
|
+
const head = plain[0] ?? "";
|
|
428
|
+
const signOffLine = `${GLYPH.diamond} ${identity.name} \xB7 Mutatis Mutandis`;
|
|
429
|
+
const opensWithWelcome = head.startsWith(GLYPH.diamond) && head !== signOffLine;
|
|
430
|
+
if (options.complete && options.tty !== false) {
|
|
431
|
+
const welcome = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
|
|
432
|
+
const welcomes = plain.filter((line) => line === welcome).length;
|
|
433
|
+
const receipts = plain.filter((line) => line.startsWith(GLYPH.boxTop)).length;
|
|
434
|
+
const signOffs = plain.filter((line) => line === signOffLine).length;
|
|
435
|
+
if (welcomes !== (options.nested ? 0 : 1) || signOffs !== (options.nested ? 0 : 1) || !options.nested && receipts !== 1) {
|
|
436
|
+
fail("complete run", "expected one console owner, one welcome, one receipt and one sign-off");
|
|
437
|
+
}
|
|
438
|
+
for (const line of plain) {
|
|
439
|
+
if (line && ![GLYPH.bar, GLYPH.diamond, GLYPH.hollow, GLYPH.boxTop, GLYPH.boxBottom].some((glyph) => line.startsWith(glyph))) {
|
|
440
|
+
fail("complete run", `unframed output: ${JSON.stringify(line)}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (rendered.length === 0) fail("R7 render before you ship", "no rendered lines were given to the guard");
|
|
445
|
+
const greetings = plain.filter((line) => ["\u2014", "-"].some((separator) => line.includes(`${identity.name} ${separator} Mutatis Mutandis`)));
|
|
446
|
+
if (greetings.length > 1) fail("R8 one welcome per run", "duplicate product greeting");
|
|
447
|
+
if (plain.filter((line) => line.startsWith(GLYPH.boxTop)).length > 1) {
|
|
448
|
+
fail("R8 one receipt per run", "duplicate receipt");
|
|
449
|
+
}
|
|
450
|
+
if (options.nested === true) {
|
|
451
|
+
if (opensWithWelcome) {
|
|
452
|
+
fail("R8 a nested run draws no welcome", `an outer console already greeted the user; this run opened with ${JSON.stringify(head)}`);
|
|
453
|
+
}
|
|
454
|
+
const signOffAt = plain.findIndex((line) => line === signOffLine);
|
|
455
|
+
if (signOffAt !== -1) {
|
|
456
|
+
fail("R8 a nested run draws no sign-off", `the outer console closes the run; line ${signOffAt + 1} signs off`);
|
|
457
|
+
}
|
|
458
|
+
const boxAt = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
|
|
459
|
+
if (boxAt !== -1) {
|
|
460
|
+
const headline = plain.slice(boxAt + 1).find((line) => line.startsWith(GLYPH.bar) && line.trim() !== GLYPH.bar) ?? "";
|
|
461
|
+
if (headline.includes(GLYPH.check)) {
|
|
462
|
+
fail("R8 a nested run draws no success receipt", `the outer console owns the outcome; this run printed ${JSON.stringify(headline.trim())}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (options.tty === false) {
|
|
467
|
+
for (const line of plain) {
|
|
468
|
+
const glyph = [...line].find((char) => ALLOWED.has(char));
|
|
469
|
+
if (glyph) fail("non-TTY lane is plain", `a piped run emitted ${JSON.stringify(glyph)} in ${JSON.stringify(line)}`);
|
|
470
|
+
}
|
|
471
|
+
for (const line of rendered) {
|
|
472
|
+
if (line.includes("\x1B[")) fail("non-TTY lane is plain", `a piped run emitted colour in ${JSON.stringify(line)}`);
|
|
473
|
+
}
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
for (const line of plain) {
|
|
477
|
+
if (line.startsWith(`${GLYPH.bar} ${GLYPH.hollow} `)) {
|
|
478
|
+
fail("note step uses U+25CF", `U+25C7 is the note-box opener in ${JSON.stringify(line)}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const line of plain) {
|
|
482
|
+
if (/[\u25c6\u25c7\u2714\u2716\u25cf] (?! )/u.test(line)) {
|
|
483
|
+
fail("R4 two spaces after a glyph", `a single space follows the glyph in ${JSON.stringify(line)}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
for (const line of plain) {
|
|
487
|
+
for (const [glyph, why] of FORBIDDEN) {
|
|
488
|
+
if (line.includes(glyph)) fail("glyph set", `${why} (in ${JSON.stringify(line)})`);
|
|
489
|
+
}
|
|
490
|
+
const emoji = [...line].find((char) => EMOJI.test(char) && !ALLOWED.has(char));
|
|
491
|
+
if (emoji) fail("glyph set", `emoji ${JSON.stringify(emoji)} in ${JSON.stringify(line)}`);
|
|
492
|
+
}
|
|
493
|
+
const top = plain.findIndex((line) => line.startsWith(GLYPH.boxTop));
|
|
494
|
+
if (top !== -1) {
|
|
495
|
+
const bottom = plain.findIndex((line, index) => index > top && line.startsWith(GLYPH.boxBottom));
|
|
496
|
+
if (bottom === -1) fail("R5 receipt frame", "the receipt box opens and never closes");
|
|
497
|
+
if (!plain[top].endsWith(GLYPH.boxTopEnd)) fail("R5 receipt frame", `the top row does not pair its corners: ${JSON.stringify(plain[top])}`);
|
|
498
|
+
if (!plain[bottom].endsWith(GLYPH.boxBottomEnd)) fail("R5 receipt frame", `the bottom row does not pair its corners: ${JSON.stringify(plain[bottom])}`);
|
|
499
|
+
const widths = new Set(plain.slice(top, bottom + 1).map(visibleWidth));
|
|
500
|
+
if (widths.size !== 1) fail("R5 receipt frame", `the box is ragged: widths ${[...widths].join(", ")}`);
|
|
501
|
+
const rows = plain.slice(top + 1, bottom);
|
|
502
|
+
if (rows.length > 0 && rows[0].includes(GLYPH.check)) {
|
|
503
|
+
if (rows.length < 3) {
|
|
504
|
+
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()))}`);
|
|
505
|
+
}
|
|
506
|
+
const changed = rows[1].replace(GLYPH.bar, "").trim();
|
|
507
|
+
if (changed === "") fail("R9 the receipt says what changed", "the middle line of the success receipt is blank");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const inReceipt = (index) => top !== -1 && index >= top && index <= plain.findIndex((line, at) => at > top && line.startsWith(GLYPH.boxBottom));
|
|
511
|
+
const welcomeEnd = opensWithWelcome ? 4 : 0;
|
|
512
|
+
for (const [index, line] of plain.entries()) {
|
|
513
|
+
if (index < welcomeEnd || inReceipt(index)) continue;
|
|
514
|
+
if (!line.startsWith(GLYPH.bar) || line.trim() === GLYPH.bar) continue;
|
|
515
|
+
const after = [...line.slice(1)];
|
|
516
|
+
const at = after.findIndex((char) => char !== " ");
|
|
517
|
+
const edge = at + 2;
|
|
518
|
+
if (edge === 4 && ALLOWED.has(after[at])) continue;
|
|
519
|
+
if (edge !== TITLE_COLUMN + 1) {
|
|
520
|
+
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)}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
for (const line of plain) {
|
|
524
|
+
if (/\(node:\d+\)|DeprecationWarning|ExperimentalWarning/u.test(line)) {
|
|
525
|
+
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`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const receiptStart = top === -1 ? plain.length : top;
|
|
529
|
+
for (const line of plain.slice(0, receiptStart)) {
|
|
530
|
+
if (/[\u2714\u2716\u25cf\u25c7]/u.test(line) && /([A-Za-z]:\\|\/(?:home|Users|opt|usr)\/)/u.test(line)) {
|
|
531
|
+
fail("R3 paths live in the receipt", `a step line carries a path: ${JSON.stringify(line)}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
for (const line of plain) {
|
|
535
|
+
if (visibleWidth(line) > width) {
|
|
536
|
+
fail("width cap", `a line is ${visibleWidth(line)} columns wide, cap ${width}: ${JSON.stringify(line)}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (opensWithWelcome) {
|
|
540
|
+
const expected = `${GLYPH.diamond} ${identity.name} \u2014 Mutatis Mutandis`;
|
|
541
|
+
if (head !== expected) fail("welcome is the product table's", `expected ${JSON.stringify(expected)}, got ${JSON.stringify(head)}`);
|
|
542
|
+
const warm = plain.slice(1, 4).find((line) => line.includes(identity.warm) || line.includes(identity.installWarm));
|
|
543
|
+
if (!warm) fail("welcome is the product table's", `the warm line for ${options.product} is missing or reworded`);
|
|
544
|
+
}
|
|
545
|
+
const painted = rendered.join("\n");
|
|
546
|
+
if (painted.includes("\x1B[")) {
|
|
547
|
+
if (!painted.includes(identity.accent) && !painted.includes(PALETTE.muted)) {
|
|
548
|
+
fail("accent", `${options.product} rendered colour that is neither its accent nor a neutral token`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ../installer/face/src/run.ts
|
|
554
|
+
var import_node_fs4 = require("node:fs");
|
|
555
|
+
|
|
556
|
+
// ../installer/face/src/outcome.ts
|
|
557
|
+
var import_node_fs3 = require("node:fs");
|
|
558
|
+
var counts = ["total", "updated", "failed"];
|
|
559
|
+
var strings = ["version", "retry", "detail", "logPath"];
|
|
560
|
+
var flags = ["dryRun", "installed", "deferred", "operationFailed"];
|
|
561
|
+
function validateInstallerOutcome(value) {
|
|
562
|
+
const invalid = () => {
|
|
563
|
+
throw new Error("installer outcome: invalid child result");
|
|
564
|
+
};
|
|
565
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return invalid();
|
|
566
|
+
const facts = value;
|
|
567
|
+
const allowed = [...counts, ...strings, ...flags, "rollback", "logState"];
|
|
568
|
+
if (Object.keys(facts).some((key) => !allowed.includes(key))) return invalid();
|
|
569
|
+
for (const key of counts) if (!Number.isSafeInteger(facts[key]) || facts[key] < 0) return invalid();
|
|
570
|
+
if (facts.updated + facts.failed > facts.total) return invalid();
|
|
571
|
+
for (const key of strings) if (facts[key] !== void 0 && (typeof facts[key] !== "string" || facts[key].length > 4096)) return invalid();
|
|
572
|
+
for (const key of flags) if (facts[key] !== void 0 && typeof facts[key] !== "boolean") return invalid();
|
|
573
|
+
if (facts.rollback !== void 0 && !["completed", "partial", "not-needed", "unknown"].includes(facts.rollback)) return invalid();
|
|
574
|
+
if (facts.logState !== void 0 && !["unavailable", "omitted"].includes(facts.logState)) return invalid();
|
|
575
|
+
if (facts.logPath !== void 0 && (!facts.logPath.trim() || /[\x00-\x1f\x7f]/u.test(facts.logPath))) return invalid();
|
|
576
|
+
return { ...facts };
|
|
577
|
+
}
|
|
578
|
+
function writeInstallerOutcome(path, value) {
|
|
579
|
+
(0, import_node_fs3.writeFileSync)(path, JSON.stringify(validateInstallerOutcome(value)), { encoding: "utf8", mode: 384 });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ../installer/face/src/run.ts
|
|
583
|
+
function validateInstallerProduct(value) {
|
|
584
|
+
const fail2 = (field) => {
|
|
585
|
+
throw new Error(`installer product: invalid ${field}`);
|
|
586
|
+
};
|
|
587
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail2("declaration");
|
|
588
|
+
const input = value;
|
|
589
|
+
const text = (value2, field) => typeof value2 === "string" && value2.trim() && !/[\r\n\x00-\x1f]/u.test(value2) ? value2 : fail2(field);
|
|
590
|
+
const key = text(input.product, "product");
|
|
591
|
+
const product = { mmi: "mmi-hub", jerv: "jerv-hub" }[key] ?? key;
|
|
592
|
+
const identity = identityFor(product);
|
|
593
|
+
const gate = text(input.gate, "gate");
|
|
594
|
+
let gateUrl;
|
|
595
|
+
try {
|
|
596
|
+
gateUrl = new URL(gate);
|
|
597
|
+
} catch {
|
|
598
|
+
return fail2("gate");
|
|
599
|
+
}
|
|
600
|
+
if (!["https:", "http:"].includes(gateUrl.protocol) || gateUrl.username || gateUrl.password) return fail2("gate");
|
|
601
|
+
const doctor = text(input.doctor, "doctor");
|
|
602
|
+
if (doctor !== identity.doctor) return fail2("doctor");
|
|
603
|
+
if (!Array.isArray(input.surfaces) || input.surfaces.length === 0) return fail2("surfaces");
|
|
604
|
+
const ids = /* @__PURE__ */ new Set();
|
|
605
|
+
const surfaces = input.surfaces.map((raw) => {
|
|
606
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return fail2("surface");
|
|
607
|
+
const source = raw;
|
|
608
|
+
const id = text(source.id, "surface.id");
|
|
609
|
+
if (ids.has(id)) return fail2("duplicate surface.id");
|
|
610
|
+
ids.add(id);
|
|
611
|
+
const surface = { id };
|
|
612
|
+
for (const field of ["npm", "bin", "kind", "activation"]) {
|
|
613
|
+
if (source[field] !== void 0) surface[field] = text(source[field], `surface.${field}`);
|
|
614
|
+
}
|
|
615
|
+
if (Boolean(surface.npm) !== Boolean(surface.bin)) return fail2("surface npm/bin pair");
|
|
616
|
+
if (surface.kind !== void 0 && !["agent-home", "payload"].includes(surface.kind)) return fail2("surface.kind");
|
|
617
|
+
if (!surface.npm && !surface.kind) return fail2("surface implementation");
|
|
618
|
+
return surface;
|
|
619
|
+
});
|
|
620
|
+
return { product, gate, doctor, surfaces };
|
|
621
|
+
}
|
|
622
|
+
var PHASES = {
|
|
623
|
+
preflight: ["Checking prerequisites", "Checked prerequisites"],
|
|
624
|
+
resolve: ["Resolving the release", "Resolved the release"],
|
|
625
|
+
download: ["Downloading the payload", "Downloaded the payload"],
|
|
626
|
+
"sign-in": ["Signing in", "Signed in"],
|
|
627
|
+
check: ["Checking surfaces", "Checked surfaces"],
|
|
628
|
+
arm: ["Scheduling updates", "Armed hourly updates"],
|
|
629
|
+
"verify-release": ["Checking the release version", "Verified the release version"],
|
|
630
|
+
verify: ["Verifying the payload", "Verified the payload"],
|
|
631
|
+
install: ["Installing the product", "Installed the product"],
|
|
632
|
+
configure: ["Configuring the product", "Configured the product"],
|
|
633
|
+
activate: ["Activating surfaces", "Activated surfaces"],
|
|
634
|
+
doctor: ["Checking health", "Checked health"],
|
|
635
|
+
rollback: ["Restoring the previous version", "Restored the previous version"]
|
|
636
|
+
};
|
|
637
|
+
function createInstallerRun(value, options = {}) {
|
|
638
|
+
const declaration = validateInstallerProduct(value);
|
|
639
|
+
const env = options.env ?? process.env;
|
|
640
|
+
const tty = options.tty ?? Boolean(process.stdout.isTTY);
|
|
641
|
+
const face = createFace({
|
|
642
|
+
operation: options.operation,
|
|
643
|
+
product: declaration.product,
|
|
644
|
+
columns: options.columns,
|
|
645
|
+
env,
|
|
646
|
+
color: tty && options.color !== false && env.NO_COLOR === void 0 && env.TERM !== "dumb"
|
|
647
|
+
});
|
|
648
|
+
const errors = [];
|
|
649
|
+
const write = options.write ? (text, channel) => {
|
|
650
|
+
try {
|
|
651
|
+
options.write(text, channel);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
errors.push(`installer output observer: ${error instanceof Error ? error.message : String(error)}`);
|
|
654
|
+
}
|
|
655
|
+
} : (text, channel) => {
|
|
656
|
+
(channel === "stdout" ? process.stdout : process.stderr).write(text);
|
|
657
|
+
};
|
|
658
|
+
const emit = (text, channel = "stdout", recorded = text) => {
|
|
659
|
+
if (!text) return;
|
|
660
|
+
write(text, channel);
|
|
661
|
+
if (env.MM_FACE_TRANSCRIPT) {
|
|
662
|
+
(0, import_node_fs4.appendFileSync)(env.MM_FACE_TRANSCRIPT, `${JSON.stringify({ channel, text: recorded })}
|
|
663
|
+
`, "utf8");
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
const lines = (rows, channel = "stdout") => {
|
|
667
|
+
for (const row of rows.flatMap((row2) => row2.split("\n"))) if (row) emit(`${row}
|
|
668
|
+
`, channel);
|
|
669
|
+
};
|
|
670
|
+
const spinner = createSpinner(face, {
|
|
671
|
+
animate: tty && env.TERM !== "dumb" && (options.animate ?? Boolean(process.stderr.isTTY)),
|
|
672
|
+
...options.write ? { stream: { write: (text) => {
|
|
673
|
+
emit(String(text), "spinner");
|
|
674
|
+
return true;
|
|
675
|
+
} } } : { transcriptPath: env.MM_FACE_TRANSCRIPT }
|
|
676
|
+
});
|
|
677
|
+
let activeTitle;
|
|
678
|
+
let chunkOpen = false;
|
|
679
|
+
let chunkChannel = "stdout";
|
|
680
|
+
let chunkColumn = 0;
|
|
681
|
+
const trailingCR = /* @__PURE__ */ new Set();
|
|
682
|
+
let deferredActivity;
|
|
683
|
+
const closeChunk = () => {
|
|
684
|
+
if (chunkOpen) emit("\n", chunkChannel);
|
|
685
|
+
chunkOpen = false;
|
|
686
|
+
chunkColumn = 0;
|
|
687
|
+
};
|
|
688
|
+
let started = false;
|
|
689
|
+
let finished = false;
|
|
690
|
+
const start = () => {
|
|
691
|
+
if (started || finished) return;
|
|
692
|
+
started = true;
|
|
693
|
+
if (options.quiet) return;
|
|
694
|
+
const welcome = face.welcome();
|
|
695
|
+
if (tty) lines(welcome);
|
|
696
|
+
else if (welcome.length) {
|
|
697
|
+
const warm = options.operation === "install" ? face.identity.installWarm : face.identity.warm;
|
|
698
|
+
lines([`${face.identity.name} - Mutatis Mutandis`, warm.replaceAll("\u2014", "-").replaceAll("\u2026", "...")]);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const durable = (title, measure, kind) => {
|
|
702
|
+
closeChunk();
|
|
703
|
+
activeTitle = void 0;
|
|
704
|
+
deferredActivity = void 0;
|
|
705
|
+
spinner.stop();
|
|
706
|
+
const rendered = face.step(title, measure, kind);
|
|
707
|
+
if (rendered) lines([tty ? rendered : `${kind === "fail" ? "Failed: " : ""}${title}${measure === null ? "" : ` (${typeof measure === "number" ? `${Math.max(0, Math.round(measure))}s` : measure})`}`]);
|
|
708
|
+
};
|
|
709
|
+
const run = {
|
|
710
|
+
get errors() {
|
|
711
|
+
return [...errors];
|
|
712
|
+
},
|
|
713
|
+
start,
|
|
714
|
+
phase(id, facts = {}) {
|
|
715
|
+
if (finished) throw new Error("installer run already finished");
|
|
716
|
+
if (!Object.hasOwn(PHASES, id)) throw new Error("installer run: unknown phase");
|
|
717
|
+
start();
|
|
718
|
+
const state = facts.state ?? "ok";
|
|
719
|
+
const title = PHASES[id][state === "ok" ? 1 : 0];
|
|
720
|
+
if (state === "running") {
|
|
721
|
+
if (face.progress(title, facts.measure ?? null)) return;
|
|
722
|
+
if (activeTitle === title) spinner.say(title, facts.measure ?? null);
|
|
723
|
+
else {
|
|
724
|
+
activeTitle = title;
|
|
725
|
+
spinner.start(title, facts.measure ?? null);
|
|
726
|
+
}
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (!face.continues(id, state)) durable(title, facts.seconds ?? facts.measure ?? null, state);
|
|
730
|
+
if (facts.detail) run.relay(facts.detail);
|
|
731
|
+
},
|
|
732
|
+
surface(facts) {
|
|
733
|
+
if (finished) throw new Error("installer run already finished");
|
|
734
|
+
const surface = declaration.surfaces.find((surface2) => surface2.id === facts.id);
|
|
735
|
+
if (!surface) throw new Error("installer run: undeclared surface");
|
|
736
|
+
start();
|
|
737
|
+
const versions = facts.to ? facts.from && facts.from !== facts.to ? ` ${facts.from} ${tty ? "\u2192" : "->"} ${facts.to}` : ` ${facts.to}` : "";
|
|
738
|
+
const status = { updated: options.dryRun ? "would update" : "updated", current: "already current", failed: "failed", skipped: "skipped", retry: "retrying", pending: "pending", kept: "kept" }[facts.state];
|
|
739
|
+
if (!status) throw new Error("installer run: unknown surface state");
|
|
740
|
+
const separator = tty ? "\xB7" : "-";
|
|
741
|
+
const activation = facts.state === "updated" && surface.activation ? ` ${separator} ${surface.activation}` : "";
|
|
742
|
+
const completed = ["updated", "current", "kept", "skipped"].includes(facts.state);
|
|
743
|
+
durable(`${facts.id}${versions} ${separator} ${status}${activation}`, completed ? facts.seconds ?? null : null, facts.state === "failed" ? "fail" : facts.state === "updated" ? "ok" : "note");
|
|
744
|
+
if (facts.detail) run.relay(facts.detail);
|
|
745
|
+
},
|
|
746
|
+
milestone({ step, state, ms, measure }) {
|
|
747
|
+
start();
|
|
748
|
+
if (state === "running") {
|
|
749
|
+
if (chunkOpen) {
|
|
750
|
+
deferredActivity = { title: step, measure: measure ?? null };
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (activeTitle === step) spinner.say(step, measure ?? null);
|
|
754
|
+
else {
|
|
755
|
+
activeTitle = step;
|
|
756
|
+
spinner.start(step, measure ?? null);
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
durable(step, ms === void 0 ? null : ms / 1e3, state);
|
|
761
|
+
},
|
|
762
|
+
signIn({ url, code }) {
|
|
763
|
+
start();
|
|
764
|
+
spinner.stop();
|
|
765
|
+
for (const text of [`Open ${url}`, `Enter code: ${code}`]) {
|
|
766
|
+
const rendered = `${tty ? face.relay(text) : text}
|
|
767
|
+
`;
|
|
768
|
+
emit(rendered, "stdout", rendered.replace(code, "[redacted]"));
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
// Only pass safe diagnostic text, never authentication output or credentials.
|
|
772
|
+
relay(text, channel = "stdout", record = true) {
|
|
773
|
+
closeChunk();
|
|
774
|
+
spinner.pause();
|
|
775
|
+
const rendered = tty ? face.relay(text) : stripColor(text);
|
|
776
|
+
for (const row of rendered.split("\n")) if (row) {
|
|
777
|
+
emit(`${row}
|
|
778
|
+
`, channel, record ? `${row}
|
|
779
|
+
` : `${tty ? face.relay("[external output omitted]") : "[external output omitted]"}
|
|
780
|
+
`);
|
|
781
|
+
}
|
|
782
|
+
spinner.resume();
|
|
783
|
+
},
|
|
784
|
+
relayChunk(text, channel = "stdout") {
|
|
785
|
+
spinner.pause();
|
|
786
|
+
if (chunkOpen && chunkChannel !== channel) closeChunk();
|
|
787
|
+
chunkChannel = channel;
|
|
788
|
+
if (trailingCR.delete(channel) && text.startsWith("\n")) text = text.slice(1);
|
|
789
|
+
if (text.endsWith("\r")) trailingCR.add(channel);
|
|
790
|
+
const parts = text.replace(/\r\n|\r/g, "\n").split("\n");
|
|
791
|
+
for (let i = 0; i < parts.length; i++) {
|
|
792
|
+
const characters = [...stripColor(parts[i])];
|
|
793
|
+
while (characters.length) {
|
|
794
|
+
if (tty && chunkColumn >= face.width - 6) closeChunk();
|
|
795
|
+
const part = characters.splice(0, tty ? face.width - 6 - chunkColumn : characters.length).join("");
|
|
796
|
+
emit(tty && !chunkOpen ? face.relay(part) : part, channel, chunkOpen ? "" : tty ? face.relay("[external output omitted]") : "[external output omitted]");
|
|
797
|
+
chunkColumn += [...part].length;
|
|
798
|
+
chunkOpen = true;
|
|
799
|
+
}
|
|
800
|
+
if (i < parts.length - 1) {
|
|
801
|
+
emit("\n", channel);
|
|
802
|
+
chunkOpen = false;
|
|
803
|
+
chunkColumn = 0;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (!chunkOpen) {
|
|
807
|
+
if (deferredActivity) {
|
|
808
|
+
activeTitle = deferredActivity.title;
|
|
809
|
+
spinner.start(deferredActivity.title, deferredActivity.measure);
|
|
810
|
+
deferredActivity = void 0;
|
|
811
|
+
} else spinner.resume();
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
cancel() {
|
|
815
|
+
if (finished) return;
|
|
816
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(
|
|
817
|
+
env.MM_INSTALLER_OUTCOME_FILE,
|
|
818
|
+
{ total: 0, updated: 0, failed: 0, deferred: true, detail: "Operation cancelled." }
|
|
819
|
+
);
|
|
820
|
+
start();
|
|
821
|
+
closeChunk();
|
|
822
|
+
activeTitle = void 0;
|
|
823
|
+
spinner.stop();
|
|
824
|
+
finished = true;
|
|
825
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE) {
|
|
826
|
+
lines(tty ? face.receipt(["Operation cancelled."], { ready: false }) : ["Operation cancelled."]);
|
|
827
|
+
}
|
|
828
|
+
if (tty) lines([face.signOff()]);
|
|
829
|
+
},
|
|
830
|
+
finish(facts) {
|
|
831
|
+
if (finished) return;
|
|
832
|
+
validateInstallerOutcome(facts);
|
|
833
|
+
if (env.MM_INSTALLER_OUTCOME_FILE) writeInstallerOutcome(env.MM_INSTALLER_OUTCOME_FILE, facts);
|
|
834
|
+
start();
|
|
835
|
+
closeChunk();
|
|
836
|
+
activeTitle = void 0;
|
|
837
|
+
spinner.stop();
|
|
838
|
+
finished = true;
|
|
839
|
+
if (options.quiet && facts.updated === 0 && facts.failed === 0 && !facts.operationFailed) return;
|
|
840
|
+
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}.`;
|
|
841
|
+
const ready = facts.failed === 0 && !facts.dryRun && !facts.deferred && !facts.operationFailed && Boolean(facts.version);
|
|
842
|
+
const body = [
|
|
843
|
+
`${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."}`,
|
|
844
|
+
changed,
|
|
845
|
+
...facts.failed ? [`Failed ${facts.failed} of ${facts.total} surfaces.`] : [],
|
|
846
|
+
...facts.detail ? [facts.detail] : [],
|
|
847
|
+
...facts.rollback ? [`Rollback: ${facts.rollback}`] : [],
|
|
848
|
+
...facts.logPath ? [`Log: ${facts.logPath}`] : facts.logState ? [`Log: ${facts.logState}`] : [],
|
|
849
|
+
facts.retry && (facts.failed > 0 || facts.operationFailed) ? `Retry: ${facts.retry}` : `Check health any time: ${declaration.doctor}`
|
|
850
|
+
];
|
|
851
|
+
if (!face.nested || !env.MM_INSTALLER_OUTCOME_FILE && (facts.failed > 0 || facts.operationFailed)) {
|
|
852
|
+
lines(tty ? face.receipt(body, { ready }) : body.map((row) => row.replace(/^[✔✖●] /u, "")));
|
|
853
|
+
}
|
|
854
|
+
if (tty) lines([face.signOff()]);
|
|
855
|
+
},
|
|
856
|
+
stop() {
|
|
857
|
+
closeChunk();
|
|
858
|
+
activeTitle = void 0;
|
|
859
|
+
spinner.stop();
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
return run;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ../installer/face/src/maintenance.ts
|
|
866
|
+
async function runMaintenance(product, args, dependencies) {
|
|
867
|
+
const json = args.includes("--json");
|
|
868
|
+
const quiet = args.includes("--quiet");
|
|
869
|
+
const dryRun = args.includes("--dry-run");
|
|
870
|
+
const installed = args.includes("install");
|
|
871
|
+
const render = dependencies.render;
|
|
872
|
+
const env = render?.env ?? process.env;
|
|
873
|
+
const tty = render?.tty ?? Boolean(process.stdout.isTTY);
|
|
874
|
+
const run = json ? null : dependencies.run ?? createInstallerRun(product, {
|
|
875
|
+
...render,
|
|
876
|
+
tty,
|
|
877
|
+
env,
|
|
878
|
+
color: tty && render?.color !== false && !args.includes("--no-color") && env.TERM !== "dumb",
|
|
879
|
+
columns: render?.columns ?? process.stdout.columns,
|
|
880
|
+
dryRun,
|
|
881
|
+
quiet,
|
|
882
|
+
operation: installed ? "install" : "update"
|
|
883
|
+
});
|
|
884
|
+
if (!quiet) run?.start();
|
|
885
|
+
let active;
|
|
886
|
+
let activeFacts;
|
|
887
|
+
let handedOff;
|
|
888
|
+
const shown = new Set((env.MM_FACE_CONTINUES ?? "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
889
|
+
if (!quiet && !json) shown.add("welcome");
|
|
890
|
+
let phaseFailed = false;
|
|
891
|
+
let armFailed = false;
|
|
892
|
+
let knownFailure;
|
|
893
|
+
const phaseStarted = /* @__PURE__ */ new Map();
|
|
894
|
+
const phase = (event) => {
|
|
895
|
+
if (handedOff !== void 0) return;
|
|
896
|
+
if (event.state === "fail") phaseFailed = true;
|
|
897
|
+
const started = phaseStarted.get(event.id);
|
|
898
|
+
if (event.state === "running") {
|
|
899
|
+
if (started === void 0) phaseStarted.set(event.id, Date.now());
|
|
900
|
+
active = event.id;
|
|
901
|
+
} else {
|
|
902
|
+
phaseStarted.delete(event.id);
|
|
903
|
+
if (active === event.id) active = void 0;
|
|
904
|
+
}
|
|
905
|
+
if (!quiet && event.state === "running" && event.detail) run?.relay(event.detail);
|
|
906
|
+
const measured = event.measured;
|
|
907
|
+
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;
|
|
908
|
+
const facts = {
|
|
909
|
+
state: event.state,
|
|
910
|
+
detail: event.detail,
|
|
911
|
+
measure,
|
|
912
|
+
...event.state !== "running" && started !== void 0 ? { seconds: (Date.now() - started) / 1e3 } : {}
|
|
913
|
+
};
|
|
914
|
+
if (event.state === "running") activeFacts = facts;
|
|
915
|
+
if (!quiet) {
|
|
916
|
+
run?.phase(event.id, facts);
|
|
917
|
+
if (event.state === "ok" && run) shown.add(event.id);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
const pending = [];
|
|
921
|
+
let streaming = false;
|
|
922
|
+
const emitArm = (arm) => {
|
|
923
|
+
if (quiet) return;
|
|
924
|
+
run?.surface({
|
|
925
|
+
id: arm.surface,
|
|
926
|
+
from: arm.from ?? void 0,
|
|
927
|
+
to: arm.to ?? void 0,
|
|
928
|
+
state: arm.verdict === "fail" ? "failed" : arm.verdict === "defer" ? "retry" : arm.verdict === "skip" ? "kept" : arm.from === arm.to && !arm.launchStaged ? "current" : "updated",
|
|
929
|
+
...arm.durationMs === void 0 ? {} : { seconds: arm.durationMs / 1e3 },
|
|
930
|
+
detail: dependencies.diagnose?.(arm)
|
|
931
|
+
});
|
|
932
|
+
};
|
|
933
|
+
const writeJson = (summary, schedule) => {
|
|
934
|
+
process.stdout.write(JSON.stringify(dependencies.jsonResult ? dependencies.jsonResult(summary, schedule) : installed ? { convergence: summary, autoupdate: schedule } : summary, null, 2) + "\n");
|
|
935
|
+
};
|
|
936
|
+
try {
|
|
937
|
+
const summary = await dependencies.engine({
|
|
938
|
+
dryRun,
|
|
939
|
+
withCustody: async (operation) => {
|
|
940
|
+
if (handedOff !== void 0) throw new Error("installer terminal ownership already transferred");
|
|
941
|
+
run?.stop();
|
|
942
|
+
const result = await operation({ faceContinues: [...shown] });
|
|
943
|
+
if (result.kind === "reexec") {
|
|
944
|
+
if (!Number.isSafeInteger(result.code) || result.code < 0) throw new Error("invalid handoff exit code");
|
|
945
|
+
handedOff = result.code;
|
|
946
|
+
} else if (result.kind === "resume") {
|
|
947
|
+
if (!quiet && active) run?.phase(active, activeFacts);
|
|
948
|
+
} else throw new Error("invalid terminal custody result");
|
|
949
|
+
return result;
|
|
950
|
+
},
|
|
951
|
+
shippedTarget: installed ? dependencies.shippedTarget : void 0,
|
|
952
|
+
narrate: !quiet && !json && args.includes("--verbose") ? (text) => {
|
|
953
|
+
if (handedOff === void 0) run?.relay(text, "stderr", false);
|
|
954
|
+
} : void 0,
|
|
955
|
+
relay: (text, channel = "stdout", record = false) => {
|
|
956
|
+
if (handedOff === void 0) run?.relay(text, channel, record);
|
|
957
|
+
},
|
|
958
|
+
onTargetResolved: (target) => phase({ id: "verify-release", detail: target, state: "ok" }),
|
|
959
|
+
onPhase: () => {
|
|
960
|
+
if (!quiet && handedOff === void 0) run?.phase("check", { state: "running" });
|
|
961
|
+
},
|
|
962
|
+
onPhaseEvent: phase,
|
|
963
|
+
onArm: (arm) => {
|
|
964
|
+
if (handedOff !== void 0) return;
|
|
965
|
+
if (arm.verdict === "fail") armFailed = true;
|
|
966
|
+
pending.push(arm);
|
|
967
|
+
if (pending.length > 1) streaming = true;
|
|
968
|
+
if (streaming) for (const result of pending.splice(0)) emitArm(result);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
if (handedOff !== void 0) return handedOff;
|
|
972
|
+
if (summary.reexec) {
|
|
973
|
+
if (!dependencies.handOff) {
|
|
974
|
+
knownFailure = "The updated version needs a handoff, but no handoff is configured.";
|
|
975
|
+
throw new Error(knownFailure);
|
|
976
|
+
}
|
|
977
|
+
run?.stop();
|
|
978
|
+
knownFailure = "The updated updater could not launch.";
|
|
979
|
+
return await dependencies.handOff(summary.reexec, args, { ...process.env, MM_FACE_CONTINUES: "welcome" });
|
|
980
|
+
}
|
|
981
|
+
const counts2 = dependencies.counts(summary);
|
|
982
|
+
validateInstallerOutcome({ total: counts2.total, updated: counts2.updated, failed: counts2.failed });
|
|
983
|
+
if (!Number.isSafeInteger(counts2.retry) || counts2.retry < 0 || counts2.retry > counts2.total) throw new Error("invalid retry count");
|
|
984
|
+
if (summary.cancelled && summary.exit === 0 && !phaseFailed && !armFailed && counts2.failed === 0 && counts2.updated === 0) {
|
|
985
|
+
if (json) writeJson(summary, null);
|
|
986
|
+
else run?.cancel();
|
|
987
|
+
return 0;
|
|
988
|
+
}
|
|
989
|
+
for (const arm of pending) emitArm(arm);
|
|
990
|
+
const waiting = summary.surfaces.filter((surface) => surface.present && surface.action === "arm-pending");
|
|
991
|
+
if (!quiet) for (const surface of waiting) run?.surface({ id: surface.id, state: "pending" });
|
|
992
|
+
let schedule = null;
|
|
993
|
+
if (installed && !dryRun && dependencies.schedule) {
|
|
994
|
+
phase({ id: "arm", state: "running" });
|
|
995
|
+
schedule = dependencies.schedule();
|
|
996
|
+
if (schedule) phase({ id: "arm", state: schedule.ok ? "ok" : "fail", detail: schedule.ok ? void 0 : schedule.detail });
|
|
997
|
+
}
|
|
998
|
+
const failed = phaseFailed || armFailed || counts2.failed > 0 || Boolean(schedule && !schedule.ok);
|
|
999
|
+
const exit = Math.max(summary.exit, failed ? 1 : 0);
|
|
1000
|
+
if (json) {
|
|
1001
|
+
writeJson({ ...summary, exit }, schedule);
|
|
1002
|
+
} else {
|
|
1003
|
+
run.finish({
|
|
1004
|
+
version: summary.target ?? void 0,
|
|
1005
|
+
total: counts2.total,
|
|
1006
|
+
updated: counts2.updated,
|
|
1007
|
+
failed: counts2.failed,
|
|
1008
|
+
operationFailed: exit !== 0,
|
|
1009
|
+
deferred: !failed && Boolean(counts2.retry || waiting.length || summary.deferred || summary.cancelled || !summary.target),
|
|
1010
|
+
dryRun,
|
|
1011
|
+
installed,
|
|
1012
|
+
detail: schedule && !schedule.ok ? schedule.detail : summary.detail ?? (summary.cancelled ? "Operation cancelled." : void 0),
|
|
1013
|
+
retry: summary.retry,
|
|
1014
|
+
rollback: summary.rollback,
|
|
1015
|
+
logPath: summary.logPath,
|
|
1016
|
+
logState: summary.logState
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
return exit;
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
if (handedOff !== void 0) throw error;
|
|
1022
|
+
let detail = knownFailure ?? (active === "sign-in" ? "Sign-in did not complete." : installed ? "Installation did not complete." : "Update did not complete.");
|
|
1023
|
+
if (!knownFailure && dependencies.safeErrorDetail) {
|
|
1024
|
+
try {
|
|
1025
|
+
detail = dependencies.safeErrorDetail(error);
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (active) phase({ id: active, state: "fail" });
|
|
1030
|
+
run?.relay(detail, "stderr");
|
|
1031
|
+
run?.finish({ total: 0, updated: 0, failed: 0, operationFailed: true, detail });
|
|
1032
|
+
if (json) process.stdout.write(JSON.stringify({ error: detail }) + "\n");
|
|
1033
|
+
return 1;
|
|
1034
|
+
} finally {
|
|
1035
|
+
run?.stop();
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// ../installer.product.json
|
|
1040
|
+
var installer_product_default = {
|
|
1041
|
+
product: "mmi",
|
|
1042
|
+
gate: "https://gate.mutmut.work/mmi",
|
|
1043
|
+
doctor: "mmi doctor",
|
|
1044
|
+
surfaces: [
|
|
1045
|
+
{ id: "updater", npm: "@mutmutco/hub", bin: "mmi-hub" },
|
|
1046
|
+
{ id: "cli", npm: "@mutmutco/cli", bin: "mmi-cli" },
|
|
1047
|
+
{ id: "claude", kind: "agent-home", activation: "next launch" },
|
|
1048
|
+
{ id: "codex", kind: "agent-home", activation: "next launch" },
|
|
1049
|
+
{ id: "cursor", kind: "agent-home", activation: "next launch" },
|
|
1050
|
+
{ id: "hermes", kind: "agent-home", activation: "next launch" },
|
|
1051
|
+
{ id: "jervcode", kind: "agent-home", activation: "next launch" }
|
|
1052
|
+
]
|
|
1053
|
+
};
|
|
310
1054
|
|
|
311
1055
|
// src/state.ts
|
|
312
|
-
var
|
|
1056
|
+
var import_node_fs5 = require("node:fs");
|
|
313
1057
|
var import_node_os = require("node:os");
|
|
314
1058
|
var import_node_path = require("node:path");
|
|
315
1059
|
function stateRoot(env = process.env) {
|
|
@@ -323,8 +1067,8 @@ function statePaths(env = process.env) {
|
|
|
323
1067
|
}
|
|
324
1068
|
function ensureState(env = process.env) {
|
|
325
1069
|
const paths = statePaths(env);
|
|
326
|
-
(0,
|
|
327
|
-
(0,
|
|
1070
|
+
(0, import_node_fs5.mkdirSync)(paths.staging, { recursive: true });
|
|
1071
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(paths.gitCache), { recursive: true });
|
|
328
1072
|
return paths;
|
|
329
1073
|
}
|
|
330
1074
|
var UpdaterBusy = class extends Error {
|
|
@@ -334,15 +1078,15 @@ var LEASE_STALE_MS = 10 * 6e4;
|
|
|
334
1078
|
function tryAcquire(path) {
|
|
335
1079
|
const token = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
336
1080
|
try {
|
|
337
|
-
const fd = (0,
|
|
338
|
-
(0,
|
|
339
|
-
(0,
|
|
1081
|
+
const fd = (0, import_node_fs5.openSync)(path, "wx");
|
|
1082
|
+
(0, import_node_fs5.writeFileSync)(fd, JSON.stringify({ token, pid: process.pid, ts: Date.now() }) + "\n");
|
|
1083
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
340
1084
|
return {
|
|
341
1085
|
token,
|
|
342
1086
|
release() {
|
|
343
1087
|
try {
|
|
344
1088
|
const current2 = readLease(path);
|
|
345
|
-
if (current2 && current2.token === token) (0,
|
|
1089
|
+
if (current2 && current2.token === token) (0, import_node_fs5.unlinkSync)(path);
|
|
346
1090
|
} catch {
|
|
347
1091
|
}
|
|
348
1092
|
}
|
|
@@ -353,7 +1097,7 @@ function tryAcquire(path) {
|
|
|
353
1097
|
const current = readLease(path);
|
|
354
1098
|
if (current && Date.now() - current.ts > LEASE_STALE_MS) {
|
|
355
1099
|
try {
|
|
356
|
-
(0,
|
|
1100
|
+
(0, import_node_fs5.renameSync)(path, `${path}.stale-${Date.now()}`);
|
|
357
1101
|
return "stolen-stale";
|
|
358
1102
|
} catch {
|
|
359
1103
|
}
|
|
@@ -362,7 +1106,7 @@ function tryAcquire(path) {
|
|
|
362
1106
|
}
|
|
363
1107
|
function readLease(path) {
|
|
364
1108
|
try {
|
|
365
|
-
return JSON.parse((0,
|
|
1109
|
+
return JSON.parse((0, import_node_fs5.readFileSync)(path, "utf8"));
|
|
366
1110
|
} catch {
|
|
367
1111
|
return null;
|
|
368
1112
|
}
|
|
@@ -387,10 +1131,10 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
387
1131
|
}
|
|
388
1132
|
function appendJournal(path, event) {
|
|
389
1133
|
try {
|
|
390
|
-
(0,
|
|
391
|
-
const fd = (0,
|
|
392
|
-
(0,
|
|
393
|
-
(0,
|
|
1134
|
+
(0, import_node_fs5.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
1135
|
+
const fd = (0, import_node_fs5.openSync)(path, "a");
|
|
1136
|
+
(0, import_node_fs5.writeFileSync)(fd, JSON.stringify(event) + "\n");
|
|
1137
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
394
1138
|
return true;
|
|
395
1139
|
} catch {
|
|
396
1140
|
return false;
|
|
@@ -409,7 +1153,7 @@ function journalHighWater(path, surface, events) {
|
|
|
409
1153
|
function parseJournal(path) {
|
|
410
1154
|
const events = [];
|
|
411
1155
|
try {
|
|
412
|
-
for (const line of (0,
|
|
1156
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
413
1157
|
if (!line.trim()) continue;
|
|
414
1158
|
try {
|
|
415
1159
|
events.push(JSON.parse(line));
|
|
@@ -423,7 +1167,7 @@ function parseJournal(path) {
|
|
|
423
1167
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
424
1168
|
let lastVerdict = null;
|
|
425
1169
|
try {
|
|
426
|
-
for (const line of (0,
|
|
1170
|
+
for (const line of (0, import_node_fs5.readFileSync)(path, "utf8").split("\n")) {
|
|
427
1171
|
if (!line.trim()) continue;
|
|
428
1172
|
try {
|
|
429
1173
|
const event = JSON.parse(line);
|
|
@@ -466,7 +1210,7 @@ function compareSemver(a, b) {
|
|
|
466
1210
|
return va.pre.join(".") < vb.pre.join(".") ? -1 : va.pre.join(".") > vb.pre.join(".") ? 1 : 0;
|
|
467
1211
|
}
|
|
468
1212
|
function fileExists(path) {
|
|
469
|
-
return (0,
|
|
1213
|
+
return (0, import_node_fs5.existsSync)(path);
|
|
470
1214
|
}
|
|
471
1215
|
|
|
472
1216
|
// src/reconcile.ts
|
|
@@ -474,7 +1218,7 @@ var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
|
474
1218
|
|
|
475
1219
|
// src/npm.ts
|
|
476
1220
|
var import_node_child_process = require("node:child_process");
|
|
477
|
-
var
|
|
1221
|
+
var import_node_fs6 = require("node:fs");
|
|
478
1222
|
var import_node_path2 = require("node:path");
|
|
479
1223
|
var cachedNpm = null;
|
|
480
1224
|
function resolveNpm(env = process.env) {
|
|
@@ -485,7 +1229,7 @@ function resolveNpm(env = process.env) {
|
|
|
485
1229
|
const first = where.stdout.split("\n").map((l) => l.trim()).find(Boolean);
|
|
486
1230
|
if (where.status === 0 && first) {
|
|
487
1231
|
const script = (0, import_node_path2.join)((0, import_node_path2.dirname)(first), "node_modules", "npm", "bin", "npm-cli.js");
|
|
488
|
-
if ((0,
|
|
1232
|
+
if ((0, import_node_fs6.existsSync)(script)) {
|
|
489
1233
|
cachedNpm = { kind: "node-script", script };
|
|
490
1234
|
return cachedNpm;
|
|
491
1235
|
}
|
|
@@ -494,13 +1238,13 @@ function resolveNpm(env = process.env) {
|
|
|
494
1238
|
}
|
|
495
1239
|
}
|
|
496
1240
|
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,
|
|
1241
|
+
if ((0, import_node_fs6.existsSync)(bundled)) {
|
|
498
1242
|
cachedNpm = { kind: "node-script", script: bundled };
|
|
499
1243
|
return cachedNpm;
|
|
500
1244
|
}
|
|
501
1245
|
const located = (0, import_node_child_process.spawnSync)("/bin/sh", ["-lc", "command -v npm"], { encoding: "utf8", windowsHide: true });
|
|
502
1246
|
const resolved = (located.stdout ?? "").trim().split("\n").map((line) => line.trim()).find(Boolean);
|
|
503
|
-
if (located.status === 0 && resolved && (0,
|
|
1247
|
+
if (located.status === 0 && resolved && (0, import_node_fs6.existsSync)(resolved)) {
|
|
504
1248
|
cachedNpm = { kind: "command", command: resolved };
|
|
505
1249
|
return cachedNpm;
|
|
506
1250
|
}
|
|
@@ -582,7 +1326,7 @@ function npmVersions(packageName, env = process.env) {
|
|
|
582
1326
|
|
|
583
1327
|
// src/gate.ts
|
|
584
1328
|
var import_node_child_process2 = require("node:child_process");
|
|
585
|
-
var
|
|
1329
|
+
var import_node_fs7 = require("node:fs");
|
|
586
1330
|
function parseBom(text) {
|
|
587
1331
|
const bom = JSON.parse(text);
|
|
588
1332
|
if (typeof bom?.version !== "string" || typeof bom?.sourceCommit !== "string" || !Array.isArray(bom?.artifacts)) {
|
|
@@ -604,7 +1348,7 @@ function ensureBareCache(cachePath, repoUrl) {
|
|
|
604
1348
|
if (probe.status !== 0) {
|
|
605
1349
|
throw new GateEnvError("git is not available \u2014 the release gate needs a git client");
|
|
606
1350
|
}
|
|
607
|
-
if (!(0,
|
|
1351
|
+
if (!(0, import_node_fs7.existsSync)(cachePath)) {
|
|
608
1352
|
const init = (0, import_node_child_process2.spawnSync)("git", ["init", "--bare", cachePath], { encoding: "utf8", windowsHide: true });
|
|
609
1353
|
if (init.status !== 0) throw new GateEnvError(`cannot create bare cache at ${cachePath}: ${init.stderr}`);
|
|
610
1354
|
}
|
|
@@ -711,24 +1455,24 @@ async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
|
711
1455
|
}
|
|
712
1456
|
|
|
713
1457
|
// src/surfaces.ts
|
|
714
|
-
var
|
|
1458
|
+
var import_node_fs12 = require("node:fs");
|
|
715
1459
|
var import_node_os3 = require("node:os");
|
|
716
1460
|
var import_node_path7 = require("node:path");
|
|
717
1461
|
|
|
718
1462
|
// src/arm-host-plugins.ts
|
|
719
1463
|
var import_node_child_process5 = require("node:child_process");
|
|
720
|
-
var
|
|
1464
|
+
var import_node_fs11 = require("node:fs");
|
|
721
1465
|
var import_node_os2 = require("node:os");
|
|
722
1466
|
var import_node_path6 = require("node:path");
|
|
723
1467
|
|
|
724
1468
|
// src/arm-npm-global.ts
|
|
725
|
-
var
|
|
1469
|
+
var import_node_fs9 = require("node:fs");
|
|
726
1470
|
var import_node_child_process4 = require("node:child_process");
|
|
727
1471
|
var import_node_path4 = require("node:path");
|
|
728
1472
|
|
|
729
1473
|
// src/pending.ts
|
|
730
1474
|
var import_node_child_process3 = require("node:child_process");
|
|
731
|
-
var
|
|
1475
|
+
var import_node_fs8 = require("node:fs");
|
|
732
1476
|
var import_node_path3 = require("node:path");
|
|
733
1477
|
function hubDistPath(env) {
|
|
734
1478
|
const root = globalRoot(env);
|
|
@@ -743,7 +1487,7 @@ function watcherLockPath(paths) {
|
|
|
743
1487
|
}
|
|
744
1488
|
function readPending(paths) {
|
|
745
1489
|
try {
|
|
746
|
-
const parsed = JSON.parse((0,
|
|
1490
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(pendingPath(paths), "utf8"));
|
|
747
1491
|
if (!Array.isArray(parsed)) return [];
|
|
748
1492
|
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
1493
|
} catch {
|
|
@@ -751,12 +1495,12 @@ function readPending(paths) {
|
|
|
751
1495
|
}
|
|
752
1496
|
}
|
|
753
1497
|
function writePending(paths, records) {
|
|
754
|
-
(0,
|
|
1498
|
+
(0, import_node_fs8.mkdirSync)(paths.root, { recursive: true });
|
|
755
1499
|
const path = pendingPath(paths);
|
|
756
1500
|
const tmp = `${path}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
757
|
-
(0,
|
|
1501
|
+
(0, import_node_fs8.writeFileSync)(tmp, `${JSON.stringify(records, null, 2)}
|
|
758
1502
|
`, "utf8");
|
|
759
|
-
(0,
|
|
1503
|
+
(0, import_node_fs8.renameSync)(tmp, path);
|
|
760
1504
|
}
|
|
761
1505
|
function clearPending(paths, surface) {
|
|
762
1506
|
const records = readPending(paths);
|
|
@@ -825,7 +1569,7 @@ function schedulePendingWatcher(paths, env = process.env, seam = {}) {
|
|
|
825
1569
|
const live = records.find((row) => row.watcherPid && pidAlive(row.watcherPid));
|
|
826
1570
|
if (live) return { scheduled: false, reason: `watcher pid ${live.watcherPid} is already waiting` };
|
|
827
1571
|
const hubDist = seam.hubDist ?? hubDistPath(env);
|
|
828
|
-
if (!hubDist || !(0,
|
|
1572
|
+
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
1573
|
const command = buildWatcherCommand({
|
|
830
1574
|
images: [...new Set(records.flatMap((row) => row.images))],
|
|
831
1575
|
dirs: [...new Set(records.flatMap((row) => row.dirs))],
|
|
@@ -885,7 +1629,7 @@ function tail(output) {
|
|
|
885
1629
|
function manifestVersion(packageRoot) {
|
|
886
1630
|
if (!packageRoot) return null;
|
|
887
1631
|
try {
|
|
888
|
-
const version = JSON.parse((0,
|
|
1632
|
+
const version = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
889
1633
|
return typeof version === "string" ? version : null;
|
|
890
1634
|
} catch {
|
|
891
1635
|
return null;
|
|
@@ -896,8 +1640,8 @@ function stagePackage(options) {
|
|
|
896
1640
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
897
1641
|
const packageRoot = (0, import_node_path4.join)(stageRoot, "node_modules", ...packageName.split("/"));
|
|
898
1642
|
if (!fileExists((0, import_node_path4.join)(packageRoot, ...witness.split("/")))) {
|
|
899
|
-
(0,
|
|
900
|
-
(0,
|
|
1643
|
+
(0, import_node_fs9.mkdirSync)(stageRoot, { recursive: true });
|
|
1644
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path4.join)(stageRoot, "package.json"), JSON.stringify({ name: "mmi-updater-stage", private: true }) + "\n");
|
|
901
1645
|
const install = runNpm(["install", "--prefix", stageRoot, "--no-audit", "--no-fund", "--no-package-lock", `${packageName}@${target}`], env);
|
|
902
1646
|
if (install.status !== 0) {
|
|
903
1647
|
return { defer: `staging install failed: ${tail(install.stderr || install.stdout)}` };
|
|
@@ -905,7 +1649,7 @@ function stagePackage(options) {
|
|
|
905
1649
|
}
|
|
906
1650
|
let stagedManifestVersion;
|
|
907
1651
|
try {
|
|
908
|
-
stagedManifestVersion = JSON.parse((0,
|
|
1652
|
+
stagedManifestVersion = JSON.parse((0, import_node_fs9.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
909
1653
|
} catch {
|
|
910
1654
|
}
|
|
911
1655
|
if (stagedManifestVersion !== target) {
|
|
@@ -963,11 +1707,11 @@ function npmGlobalArm(options) {
|
|
|
963
1707
|
}
|
|
964
1708
|
|
|
965
1709
|
// src/launch-staging.ts
|
|
966
|
-
var
|
|
1710
|
+
var import_node_fs10 = require("node:fs");
|
|
967
1711
|
var import_node_path5 = require("node:path");
|
|
968
1712
|
function readLaunchTarget(path) {
|
|
969
1713
|
try {
|
|
970
|
-
const parsed = JSON.parse((0,
|
|
1714
|
+
const parsed = JSON.parse((0, import_node_fs10.readFileSync)(path, "utf8"));
|
|
971
1715
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
972
1716
|
const row = parsed;
|
|
973
1717
|
if (typeof row.surface !== "string" || typeof row.target !== "string" || typeof row.since !== "string" || typeof row.incoming !== "string") return null;
|
|
@@ -977,15 +1721,15 @@ function readLaunchTarget(path) {
|
|
|
977
1721
|
}
|
|
978
1722
|
}
|
|
979
1723
|
function writeLaunchTarget(path, record) {
|
|
980
|
-
(0,
|
|
1724
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path5.join)(path, ".."), { recursive: true });
|
|
981
1725
|
const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
982
|
-
(0,
|
|
1726
|
+
(0, import_node_fs10.writeFileSync)(tmp, `${JSON.stringify(record, null, 2)}
|
|
983
1727
|
`, "utf8");
|
|
984
|
-
(0,
|
|
1728
|
+
(0, import_node_fs10.renameSync)(tmp, path);
|
|
985
1729
|
}
|
|
986
1730
|
function clearLaunchTarget(path) {
|
|
987
1731
|
try {
|
|
988
|
-
(0,
|
|
1732
|
+
(0, import_node_fs10.rmSync)(path, { force: true });
|
|
989
1733
|
} catch {
|
|
990
1734
|
}
|
|
991
1735
|
}
|
|
@@ -1006,7 +1750,7 @@ function noteLaunchStaged(paths, env, options, seam = {}) {
|
|
|
1006
1750
|
}
|
|
1007
1751
|
function probeLaunchTargetVersion(launchTargetPath, versionAt) {
|
|
1008
1752
|
const record = readLaunchTarget(launchTargetPath);
|
|
1009
|
-
if (!record || !(0,
|
|
1753
|
+
if (!record || !(0, import_node_fs10.existsSync)(record.incoming)) return null;
|
|
1010
1754
|
const version = versionAt(record.incoming);
|
|
1011
1755
|
return version === record.target ? record.target : null;
|
|
1012
1756
|
}
|
|
@@ -1064,7 +1808,7 @@ function ensureClaudeMarketplace(env) {
|
|
|
1064
1808
|
const path = claudeMarketplacesPath(env);
|
|
1065
1809
|
let body;
|
|
1066
1810
|
try {
|
|
1067
|
-
body = JSON.parse((0,
|
|
1811
|
+
body = JSON.parse((0, import_node_fs11.readFileSync)(path, "utf8"));
|
|
1068
1812
|
} catch (error) {
|
|
1069
1813
|
if (error?.code === "ENOENT") return { ok: false, detail: `${path} does not exist \u2014 mutmutco marketplace is not registered`, unregistered: true };
|
|
1070
1814
|
return { ok: false, detail: `cannot read ${path}` };
|
|
@@ -1092,7 +1836,7 @@ function claudePluginsRoot(env) {
|
|
|
1092
1836
|
}
|
|
1093
1837
|
function claudeMarkerVersion(root) {
|
|
1094
1838
|
try {
|
|
1095
|
-
const version = JSON.parse((0,
|
|
1839
|
+
const version = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(root, ...CLAUDE_MANIFEST.split("/")), "utf8")).version;
|
|
1096
1840
|
return typeof version === "string" ? version : null;
|
|
1097
1841
|
} catch {
|
|
1098
1842
|
return null;
|
|
@@ -1189,14 +1933,14 @@ function claudeArm(options) {
|
|
|
1189
1933
|
return result(from, target, "defer", `staged Claude manifest is ${claudeMarkerVersion(staged.packageRoot) ?? "unreadable"} or its tree is incomplete (expected ${target})`);
|
|
1190
1934
|
}
|
|
1191
1935
|
try {
|
|
1192
|
-
(0,
|
|
1193
|
-
(0,
|
|
1194
|
-
(0,
|
|
1936
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(pluginsRoot, "staging"), { recursive: true });
|
|
1937
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1938
|
+
(0, import_node_fs11.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1195
1939
|
} catch (error) {
|
|
1196
1940
|
return result(from, target, "defer", `cannot place incoming Claude generation: ${error?.message ?? error}`);
|
|
1197
1941
|
}
|
|
1198
1942
|
if (claudeMarkerVersion(incoming) !== target || !claudeTreeHealthy(incoming)) {
|
|
1199
|
-
(0,
|
|
1943
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1200
1944
|
return result(from, target, "defer", "incoming Claude generation did not reproduce the verified payload");
|
|
1201
1945
|
}
|
|
1202
1946
|
}
|
|
@@ -1228,10 +1972,10 @@ function claudeArm(options) {
|
|
|
1228
1972
|
}
|
|
1229
1973
|
const cacheTarget = (0, import_node_path6.join)(pluginsRoot, "cache", "mutmutco", "mmi", target);
|
|
1230
1974
|
try {
|
|
1231
|
-
if (!(0,
|
|
1232
|
-
(0,
|
|
1233
|
-
(0,
|
|
1234
|
-
(0,
|
|
1975
|
+
if (!(0, import_node_fs11.existsSync)(cacheTarget) || claudeMarkerVersion(cacheTarget) !== target || !claudeTreeHealthy(cacheTarget)) {
|
|
1976
|
+
(0, import_node_fs11.rmSync)(cacheTarget, { recursive: true, force: true });
|
|
1977
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path6.join)(cacheTarget, ".."), { recursive: true });
|
|
1978
|
+
(0, import_node_fs11.cpSync)(incoming, cacheTarget, { recursive: true });
|
|
1235
1979
|
}
|
|
1236
1980
|
} catch (error) {
|
|
1237
1981
|
return result(from, target, "defer", `cannot populate Claude plugin cache: ${error?.message ?? error}`);
|
|
@@ -1259,7 +2003,7 @@ function claudeArm(options) {
|
|
|
1259
2003
|
}
|
|
1260
2004
|
clearLaunchTarget(launchTargetPath);
|
|
1261
2005
|
try {
|
|
1262
|
-
(0,
|
|
2006
|
+
(0, import_node_fs11.rmSync)(incomingRoot, { recursive: true, force: true });
|
|
1263
2007
|
} catch {
|
|
1264
2008
|
}
|
|
1265
2009
|
return result(from, target, "ok", `catalog refreshed; converged and verified ${from ?? "absent"} -> ${target}`);
|
|
@@ -1372,21 +2116,21 @@ function enumerateSurfaces(env = process.env) {
|
|
|
1372
2116
|
{ id: "hermes", marker: `${hermesHostCommand(env)} --version` }
|
|
1373
2117
|
].map((probe) => ({
|
|
1374
2118
|
...probe,
|
|
1375
|
-
present: probe.id === "hermes" ? hermesHostPresent(env) : (0,
|
|
2119
|
+
present: probe.id === "hermes" ? hermesHostPresent(env) : (0, import_node_fs12.existsSync)(probe.marker)
|
|
1376
2120
|
}));
|
|
1377
2121
|
}
|
|
1378
2122
|
|
|
1379
2123
|
// src/arm-cursor.ts
|
|
1380
|
-
var
|
|
2124
|
+
var import_node_fs15 = require("node:fs");
|
|
1381
2125
|
var import_node_child_process6 = require("node:child_process");
|
|
1382
2126
|
var import_node_os5 = require("node:os");
|
|
1383
2127
|
var import_node_path8 = require("node:path");
|
|
1384
2128
|
|
|
1385
2129
|
// src/compat.ts
|
|
1386
|
-
var
|
|
2130
|
+
var import_node_fs13 = require("node:fs");
|
|
1387
2131
|
function readManifestCompat(manifestPath) {
|
|
1388
2132
|
try {
|
|
1389
|
-
const parsed = JSON.parse((0,
|
|
2133
|
+
const parsed = JSON.parse((0, import_node_fs13.readFileSync)(manifestPath, "utf8"));
|
|
1390
2134
|
return typeof parsed.mmiCompat === "string" ? parsed.mmiCompat : void 0;
|
|
1391
2135
|
} catch {
|
|
1392
2136
|
return void 0;
|
|
@@ -1394,7 +2138,7 @@ function readManifestCompat(manifestPath) {
|
|
|
1394
2138
|
}
|
|
1395
2139
|
|
|
1396
2140
|
// src/host-doc-lock.ts
|
|
1397
|
-
var
|
|
2141
|
+
var import_node_fs14 = require("node:fs");
|
|
1398
2142
|
var import_node_os4 = require("node:os");
|
|
1399
2143
|
var SHARED_DOC_LOCK_STALE_MS = 3e4;
|
|
1400
2144
|
var SHARED_DOC_LOCK_ATTEMPTS = 50;
|
|
@@ -1408,7 +2152,7 @@ function lockContent(options) {
|
|
|
1408
2152
|
}
|
|
1409
2153
|
function readHolder(lockPath) {
|
|
1410
2154
|
try {
|
|
1411
|
-
const parsed = JSON.parse((0,
|
|
2155
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(lockPath, "utf8"));
|
|
1412
2156
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
1413
2157
|
return parsed;
|
|
1414
2158
|
} catch {
|
|
@@ -1420,7 +2164,7 @@ function lockAgeMs(lockPath, now) {
|
|
|
1420
2164
|
const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
|
|
1421
2165
|
if (!Number.isNaN(declared)) return Math.max(0, now - declared);
|
|
1422
2166
|
try {
|
|
1423
|
-
return Math.max(0, now - (0,
|
|
2167
|
+
return Math.max(0, now - (0, import_node_fs14.statSync)(lockPath).mtimeMs);
|
|
1424
2168
|
} catch {
|
|
1425
2169
|
return 0;
|
|
1426
2170
|
}
|
|
@@ -1443,9 +2187,9 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1443
2187
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1444
2188
|
let fd;
|
|
1445
2189
|
try {
|
|
1446
|
-
fd = (0,
|
|
1447
|
-
(0,
|
|
1448
|
-
(0,
|
|
2190
|
+
fd = (0, import_node_fs14.openSync)(lockPath, "wx");
|
|
2191
|
+
(0, import_node_fs14.writeSync)(fd, content);
|
|
2192
|
+
(0, import_node_fs14.closeSync)(fd);
|
|
1449
2193
|
return {
|
|
1450
2194
|
documentPath,
|
|
1451
2195
|
lockPath,
|
|
@@ -1458,11 +2202,11 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1458
2202
|
} catch (error) {
|
|
1459
2203
|
if (fd !== void 0) {
|
|
1460
2204
|
try {
|
|
1461
|
-
(0,
|
|
2205
|
+
(0, import_node_fs14.closeSync)(fd);
|
|
1462
2206
|
} catch {
|
|
1463
2207
|
}
|
|
1464
2208
|
try {
|
|
1465
|
-
(0,
|
|
2209
|
+
(0, import_node_fs14.unlinkSync)(lockPath);
|
|
1466
2210
|
} catch {
|
|
1467
2211
|
}
|
|
1468
2212
|
}
|
|
@@ -1471,12 +2215,12 @@ function acquireSharedDocLock(documentPath, options) {
|
|
|
1471
2215
|
if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
|
|
1472
2216
|
const aside = `${lockPath}.stale-${Date.now()}`;
|
|
1473
2217
|
try {
|
|
1474
|
-
(0,
|
|
2218
|
+
(0, import_node_fs14.renameSync)(lockPath, aside);
|
|
1475
2219
|
} catch {
|
|
1476
2220
|
continue;
|
|
1477
2221
|
}
|
|
1478
2222
|
try {
|
|
1479
|
-
(0,
|
|
2223
|
+
(0, import_node_fs14.rmSync)(aside, { force: true });
|
|
1480
2224
|
} catch {
|
|
1481
2225
|
}
|
|
1482
2226
|
continue;
|
|
@@ -1489,7 +2233,7 @@ function releaseSharedDocLock(handle) {
|
|
|
1489
2233
|
try {
|
|
1490
2234
|
const holder = readHolder(handle.lockPath);
|
|
1491
2235
|
if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
|
|
1492
|
-
(0,
|
|
2236
|
+
(0, import_node_fs14.unlinkSync)(handle.lockPath);
|
|
1493
2237
|
}
|
|
1494
2238
|
} catch {
|
|
1495
2239
|
}
|
|
@@ -1514,11 +2258,11 @@ function cursorPluginsRoot() {
|
|
|
1514
2258
|
return (0, import_node_path8.join)((0, import_node_os5.homedir)(), ".cursor", "plugins");
|
|
1515
2259
|
}
|
|
1516
2260
|
function treeHealthy(root) {
|
|
1517
|
-
return TREE_FILES.every((rel) => (0,
|
|
2261
|
+
return TREE_FILES.every((rel) => (0, import_node_fs15.existsSync)((0, import_node_path8.join)(root, ...rel.split("/"))));
|
|
1518
2262
|
}
|
|
1519
2263
|
function readJson(root, rel) {
|
|
1520
2264
|
try {
|
|
1521
|
-
return JSON.parse((0,
|
|
2265
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path8.join)(root, ...rel.split("/")), "utf8"));
|
|
1522
2266
|
} catch {
|
|
1523
2267
|
return null;
|
|
1524
2268
|
}
|
|
@@ -1542,7 +2286,7 @@ function message(error) {
|
|
|
1542
2286
|
}
|
|
1543
2287
|
function discard(path) {
|
|
1544
2288
|
try {
|
|
1545
|
-
(0,
|
|
2289
|
+
(0, import_node_fs15.rmSync)(path, { recursive: true, force: true });
|
|
1546
2290
|
} catch {
|
|
1547
2291
|
}
|
|
1548
2292
|
}
|
|
@@ -1596,10 +2340,10 @@ var CURSOR_HOOKS_LOCK_OWNER = "mmi-hub:cursor-hooks";
|
|
|
1596
2340
|
function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
1597
2341
|
const path = (0, import_node_path8.join)(homeDir, ".cursor", USER_HOOKS_FILE);
|
|
1598
2342
|
return withSharedDocLock(path, { owner: CURSOR_HOOKS_LOCK_OWNER, ...lock }, () => {
|
|
1599
|
-
if (!(0,
|
|
2343
|
+
if (!(0, import_node_fs15.existsSync)(path)) return "current";
|
|
1600
2344
|
let existing;
|
|
1601
2345
|
try {
|
|
1602
|
-
existing = JSON.parse((0,
|
|
2346
|
+
existing = JSON.parse((0, import_node_fs15.readFileSync)(path, "utf8"));
|
|
1603
2347
|
} catch {
|
|
1604
2348
|
return "user-conflict";
|
|
1605
2349
|
}
|
|
@@ -1607,8 +2351,8 @@ function reconcileCursorUserHooks(pluginRoot, homeDir, lock = {}) {
|
|
|
1607
2351
|
if (!stripped) return "user-conflict";
|
|
1608
2352
|
if (JSON.stringify(stripped) === JSON.stringify(existing)) return "current";
|
|
1609
2353
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1610
|
-
(0,
|
|
1611
|
-
(0,
|
|
2354
|
+
(0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(stripped, null, 2) + "\n", "utf8");
|
|
2355
|
+
(0, import_node_fs15.renameSync)(tmp, path);
|
|
1612
2356
|
return "updated";
|
|
1613
2357
|
});
|
|
1614
2358
|
}
|
|
@@ -1632,15 +2376,15 @@ function writeRollbackPointer(root, entry) {
|
|
|
1632
2376
|
const path = (0, import_node_path8.join)(root, "quarantine", "rollback.json");
|
|
1633
2377
|
const tmp = `${path}.tmp-${process.pid}`;
|
|
1634
2378
|
try {
|
|
1635
|
-
(0,
|
|
1636
|
-
(0,
|
|
2379
|
+
(0, import_node_fs15.writeFileSync)(tmp, JSON.stringify({ surface: "cursor", ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry }, null, 2) + "\n", "utf8");
|
|
2380
|
+
(0, import_node_fs15.renameSync)(tmp, path);
|
|
1637
2381
|
} catch {
|
|
1638
2382
|
discard(tmp);
|
|
1639
2383
|
}
|
|
1640
2384
|
}
|
|
1641
2385
|
function mtimeMs(path) {
|
|
1642
2386
|
try {
|
|
1643
|
-
return (0,
|
|
2387
|
+
return (0, import_node_fs15.statSync)(path).mtimeMs;
|
|
1644
2388
|
} catch {
|
|
1645
2389
|
return 0;
|
|
1646
2390
|
}
|
|
@@ -1649,13 +2393,13 @@ function pruneQuarantine(root, keep) {
|
|
|
1649
2393
|
const dir = (0, import_node_path8.join)(root, "quarantine");
|
|
1650
2394
|
let generations;
|
|
1651
2395
|
try {
|
|
1652
|
-
generations = (0,
|
|
2396
|
+
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
2397
|
} catch {
|
|
1654
2398
|
return 0;
|
|
1655
2399
|
}
|
|
1656
2400
|
let stranded = 0;
|
|
1657
2401
|
try {
|
|
1658
|
-
for (const entry of (0,
|
|
2402
|
+
for (const entry of (0, import_node_fs15.readdirSync)(dir, { withFileTypes: true })) {
|
|
1659
2403
|
if (!entry.isFile() || !/^rollback\.json\.tmp-\d+$/.test(entry.name)) continue;
|
|
1660
2404
|
const path = (0, import_node_path8.join)(dir, entry.name);
|
|
1661
2405
|
if (Date.now() - mtimeMs(path) < STRANDED_TMP_AGE_MS) continue;
|
|
@@ -1675,16 +2419,16 @@ function pruneQuarantine(root, keep) {
|
|
|
1675
2419
|
}
|
|
1676
2420
|
function restorePrevious(live, quarantined, broken) {
|
|
1677
2421
|
try {
|
|
1678
|
-
(0,
|
|
2422
|
+
(0, import_node_fs15.renameSync)(live, broken);
|
|
1679
2423
|
} catch {
|
|
1680
2424
|
return "kept-broken";
|
|
1681
2425
|
}
|
|
1682
2426
|
try {
|
|
1683
|
-
(0,
|
|
2427
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1684
2428
|
return "restored";
|
|
1685
2429
|
} catch {
|
|
1686
2430
|
try {
|
|
1687
|
-
(0,
|
|
2431
|
+
(0, import_node_fs15.renameSync)(broken, live);
|
|
1688
2432
|
return "kept-broken";
|
|
1689
2433
|
} catch {
|
|
1690
2434
|
return "lost";
|
|
@@ -1719,7 +2463,7 @@ function cursorArm(options) {
|
|
|
1719
2463
|
if (installed === target && treeHealthy(live)) {
|
|
1720
2464
|
return result(installed, target, "ok", `already at target ${target}${userHooksDetail(live, (0, import_node_os5.homedir)())}`);
|
|
1721
2465
|
}
|
|
1722
|
-
if ((0,
|
|
2466
|
+
if ((0, import_node_fs15.existsSync)(live) && !mmiOwned(live)) {
|
|
1723
2467
|
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
2468
|
}
|
|
1725
2469
|
if (dryRun) {
|
|
@@ -1737,12 +2481,12 @@ function cursorArm(options) {
|
|
|
1737
2481
|
const priorLaunch = readLaunchTarget(launchTargetPath);
|
|
1738
2482
|
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
2483
|
const reuseIncoming = incoming === priorLaunch?.incoming;
|
|
1740
|
-
if (!reuseIncoming || !(0,
|
|
2484
|
+
if (!reuseIncoming || !(0, import_node_fs15.existsSync)(incoming)) {
|
|
1741
2485
|
incoming = (0, import_node_path8.join)(root, "staging", `mmi-${safeSegment(target)}-${Date.now()}`);
|
|
1742
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
2486
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
|
|
1743
2487
|
try {
|
|
1744
2488
|
discard(incoming);
|
|
1745
|
-
(0,
|
|
2489
|
+
(0, import_node_fs15.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
1746
2490
|
} catch (error) {
|
|
1747
2491
|
discard(incoming);
|
|
1748
2492
|
return result(installed, target, "defer", `copy into ${root} failed: ${message(error)}`);
|
|
@@ -1766,7 +2510,7 @@ function cursorArm(options) {
|
|
|
1766
2510
|
}
|
|
1767
2511
|
const stamp = Date.now();
|
|
1768
2512
|
const quarantined = (0, import_node_path8.join)(root, "quarantine", `mmi-${safeSegment(installed ?? "unknown")}-${stamp}`);
|
|
1769
|
-
for (const dir of ["local", "staging", "quarantine"]) (0,
|
|
2513
|
+
for (const dir of ["local", "staging", "quarantine"]) (0, import_node_fs15.mkdirSync)((0, import_node_path8.join)(root, dir), { recursive: true });
|
|
1770
2514
|
const started = cursorHostEvidence(env);
|
|
1771
2515
|
if (started) {
|
|
1772
2516
|
const pending = noteLaunchStaged(paths, env, {
|
|
@@ -1781,15 +2525,15 @@ function cursorArm(options) {
|
|
|
1781
2525
|
}
|
|
1782
2526
|
let displaced = false;
|
|
1783
2527
|
try {
|
|
1784
|
-
if ((0,
|
|
1785
|
-
(0,
|
|
2528
|
+
if ((0, import_node_fs15.existsSync)(live)) {
|
|
2529
|
+
(0, import_node_fs15.renameSync)(live, quarantined);
|
|
1786
2530
|
displaced = true;
|
|
1787
2531
|
}
|
|
1788
|
-
(0,
|
|
2532
|
+
(0, import_node_fs15.renameSync)(incoming, live);
|
|
1789
2533
|
} catch (error) {
|
|
1790
|
-
if (displaced && !(0,
|
|
2534
|
+
if (displaced && !(0, import_node_fs15.existsSync)(live)) {
|
|
1791
2535
|
try {
|
|
1792
|
-
(0,
|
|
2536
|
+
(0, import_node_fs15.renameSync)(quarantined, live);
|
|
1793
2537
|
} catch {
|
|
1794
2538
|
}
|
|
1795
2539
|
}
|
|
@@ -1817,7 +2561,7 @@ function probeCursorLaunchStaging() {
|
|
|
1817
2561
|
}
|
|
1818
2562
|
|
|
1819
2563
|
// src/arm-jervcode.ts
|
|
1820
|
-
var
|
|
2564
|
+
var import_node_fs16 = require("node:fs");
|
|
1821
2565
|
var import_node_path9 = require("node:path");
|
|
1822
2566
|
var PI_PACKAGE = "@mutmutco/pi-plugin";
|
|
1823
2567
|
function hostEnv(env) {
|
|
@@ -1829,7 +2573,7 @@ function hostCommands(_env) {
|
|
|
1829
2573
|
}
|
|
1830
2574
|
function readPackageSpecs(env) {
|
|
1831
2575
|
try {
|
|
1832
|
-
const settings = JSON.parse((0,
|
|
2576
|
+
const settings = JSON.parse((0, import_node_fs16.readFileSync)((0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json"), "utf8"));
|
|
1833
2577
|
return Array.isArray(settings.packages) ? settings.packages.filter((entry) => typeof entry === "string") : [];
|
|
1834
2578
|
} catch {
|
|
1835
2579
|
return null;
|
|
@@ -1837,7 +2581,7 @@ function readPackageSpecs(env) {
|
|
|
1837
2581
|
}
|
|
1838
2582
|
function installedVersion(env) {
|
|
1839
2583
|
try {
|
|
1840
|
-
const manifest2 = JSON.parse((0,
|
|
2584
|
+
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
2585
|
return typeof manifest2.version === "string" ? manifest2.version : null;
|
|
1842
2586
|
} catch {
|
|
1843
2587
|
return null;
|
|
@@ -1957,7 +2701,7 @@ function jervcodeArm(options) {
|
|
|
1957
2701
|
}
|
|
1958
2702
|
|
|
1959
2703
|
// src/arm-hermes.ts
|
|
1960
|
-
var
|
|
2704
|
+
var import_node_fs17 = require("node:fs");
|
|
1961
2705
|
var import_node_child_process7 = require("node:child_process");
|
|
1962
2706
|
var import_node_path10 = require("node:path");
|
|
1963
2707
|
var PACKAGE2 = "@mutmutco/hermes-plugin";
|
|
@@ -1965,7 +2709,7 @@ var MANIFEST2 = "plugin.yaml";
|
|
|
1965
2709
|
var TREE_MARKERS_FALLBACK = [MANIFEST2, "__init__.py", "skills"];
|
|
1966
2710
|
function yamlVersion(root) {
|
|
1967
2711
|
try {
|
|
1968
|
-
const text = (0,
|
|
2712
|
+
const text = (0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, MANIFEST2), "utf8");
|
|
1969
2713
|
try {
|
|
1970
2714
|
const version = JSON.parse(text).version;
|
|
1971
2715
|
if (typeof version === "string" && version.trim()) return version.trim();
|
|
@@ -1979,7 +2723,7 @@ function yamlVersion(root) {
|
|
|
1979
2723
|
}
|
|
1980
2724
|
function packageTreeMarkers(root) {
|
|
1981
2725
|
try {
|
|
1982
|
-
const files = JSON.parse((0,
|
|
2726
|
+
const files = JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).files;
|
|
1983
2727
|
if (Array.isArray(files) && files.length && files.every((entry) => typeof entry === "string" && entry.trim())) {
|
|
1984
2728
|
return files.map((entry) => entry.trim());
|
|
1985
2729
|
}
|
|
@@ -1989,9 +2733,9 @@ function packageTreeMarkers(root) {
|
|
|
1989
2733
|
}
|
|
1990
2734
|
function markerPresent(root, rel) {
|
|
1991
2735
|
const path = (0, import_node_path10.join)(root, ...rel.split("/"));
|
|
1992
|
-
if (!(0,
|
|
2736
|
+
if (!(0, import_node_fs17.existsSync)(path)) return false;
|
|
1993
2737
|
try {
|
|
1994
|
-
const stat = (0,
|
|
2738
|
+
const stat = (0, import_node_fs17.statSync)(path);
|
|
1995
2739
|
return stat.isFile() || stat.isDirectory();
|
|
1996
2740
|
} catch {
|
|
1997
2741
|
return false;
|
|
@@ -2010,7 +2754,7 @@ function treeIncompleteDetail(root, label) {
|
|
|
2010
2754
|
}
|
|
2011
2755
|
function packageIdentity(root) {
|
|
2012
2756
|
try {
|
|
2013
|
-
return JSON.parse((0,
|
|
2757
|
+
return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path10.join)(root, "package.json"), "utf8")).name === PACKAGE2;
|
|
2014
2758
|
} catch {
|
|
2015
2759
|
return false;
|
|
2016
2760
|
}
|
|
@@ -2025,7 +2769,7 @@ function mmiOwned2(root) {
|
|
|
2025
2769
|
}
|
|
2026
2770
|
function discard2(path) {
|
|
2027
2771
|
try {
|
|
2028
|
-
(0,
|
|
2772
|
+
(0, import_node_fs17.rmSync)(path, { recursive: true, force: true });
|
|
2029
2773
|
} catch {
|
|
2030
2774
|
}
|
|
2031
2775
|
}
|
|
@@ -2061,15 +2805,15 @@ function materializeHermesSkills(home) {
|
|
|
2061
2805
|
const skillsRoot = (0, import_node_path10.join)(home, "skills");
|
|
2062
2806
|
const targetRoot = (0, import_node_path10.join)(skillsRoot, "mmi");
|
|
2063
2807
|
try {
|
|
2064
|
-
if (!(0,
|
|
2065
|
-
const names = (0,
|
|
2808
|
+
if (!(0, import_node_fs17.existsSync)(pluginSkills)) return { ok: false, detail: `plugin skills tree missing at ${pluginSkills}` };
|
|
2809
|
+
const names = (0, import_node_fs17.readdirSync)(pluginSkills, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name);
|
|
2066
2810
|
if (!names.length) return { ok: false, detail: `plugin skills tree is empty at ${pluginSkills}` };
|
|
2067
|
-
(0,
|
|
2811
|
+
(0, import_node_fs17.mkdirSync)(skillsRoot, { recursive: true });
|
|
2068
2812
|
const incoming = (0, import_node_path10.join)(skillsRoot, ".mmi-incoming");
|
|
2069
2813
|
discard2(incoming);
|
|
2070
|
-
(0,
|
|
2814
|
+
(0, import_node_fs17.cpSync)(pluginSkills, incoming, { recursive: true });
|
|
2071
2815
|
discard2(targetRoot);
|
|
2072
|
-
(0,
|
|
2816
|
+
(0, import_node_fs17.renameSync)(incoming, targetRoot);
|
|
2073
2817
|
return { ok: true, detail: `provisioned ${names.length} skills to ${targetRoot}` };
|
|
2074
2818
|
} catch (error) {
|
|
2075
2819
|
return { ok: false, detail: message2(error) };
|
|
@@ -2091,7 +2835,7 @@ function hermesHomes(env = process.env) {
|
|
|
2091
2835
|
const root = hermesHome(env);
|
|
2092
2836
|
const homes = [root];
|
|
2093
2837
|
try {
|
|
2094
|
-
for (const entry of (0,
|
|
2838
|
+
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
2839
|
if (entry.isDirectory() && !entry.name.startsWith(".")) homes.push((0, import_node_path10.join)(root, "profiles", entry.name));
|
|
2096
2840
|
}
|
|
2097
2841
|
} catch {
|
|
@@ -2140,7 +2884,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2140
2884
|
detail,
|
|
2141
2885
|
launchStaged: launchStaged || void 0
|
|
2142
2886
|
});
|
|
2143
|
-
if ((0,
|
|
2887
|
+
if ((0, import_node_fs17.existsSync)(live) && !mmiOwned2(live)) {
|
|
2144
2888
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
2145
2889
|
}
|
|
2146
2890
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
@@ -2172,10 +2916,10 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2172
2916
|
const reuseIncoming = priorLaunch?.target === options.target && priorLaunch.incoming === incoming && yamlVersion(incoming) === options.target && treeHealthy2(incoming);
|
|
2173
2917
|
if (!reuseIncoming) {
|
|
2174
2918
|
try {
|
|
2175
|
-
(0,
|
|
2176
|
-
(0,
|
|
2919
|
+
(0, import_node_fs17.mkdirSync)(parent, { recursive: true });
|
|
2920
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "quarantine"), { recursive: true });
|
|
2177
2921
|
discard2(incomingRoot);
|
|
2178
|
-
(0,
|
|
2922
|
+
(0, import_node_fs17.cpSync)(staged.packageRoot, incoming, { recursive: true });
|
|
2179
2923
|
} catch (error) {
|
|
2180
2924
|
return result(installed, options.target, "defer", `cannot place incoming Hermes generation: ${message2(error)}`);
|
|
2181
2925
|
}
|
|
@@ -2190,7 +2934,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2190
2934
|
return result(installed, options.target, "defer", incomingTree);
|
|
2191
2935
|
}
|
|
2192
2936
|
}
|
|
2193
|
-
const displaced = (0,
|
|
2937
|
+
const displaced = (0, import_node_fs17.existsSync)(live);
|
|
2194
2938
|
const busy = displaced ? hermesHostEvidence(env, home, options.processListProvider) : null;
|
|
2195
2939
|
if (busy) {
|
|
2196
2940
|
const pending = noteLaunchStaged(options.paths, env, {
|
|
@@ -2205,11 +2949,11 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2205
2949
|
return result(installed, options.target, "ok", `staged ${options.target} for next launch while Hermes is running (${busy}) \u2014 ${pending}`, true);
|
|
2206
2950
|
}
|
|
2207
2951
|
try {
|
|
2208
|
-
if (displaced) (0,
|
|
2209
|
-
(0,
|
|
2952
|
+
if (displaced) (0, import_node_fs17.renameSync)(live, quarantine);
|
|
2953
|
+
(0, import_node_fs17.renameSync)(incoming, live);
|
|
2210
2954
|
} catch (error) {
|
|
2211
|
-
if (displaced && !(0,
|
|
2212
|
-
(0,
|
|
2955
|
+
if (displaced && !(0, import_node_fs17.existsSync)(live)) try {
|
|
2956
|
+
(0, import_node_fs17.renameSync)(quarantine, live);
|
|
2213
2957
|
} catch {
|
|
2214
2958
|
}
|
|
2215
2959
|
discard2(incomingRoot);
|
|
@@ -2223,9 +2967,9 @@ function armHermesHome(home, env, options, primary) {
|
|
|
2223
2967
|
const broken = (0, import_node_path10.join)(scratch, "rejected", `${options.target}-${Date.now()}`);
|
|
2224
2968
|
const verifyDetail = liveVersion !== options.target ? `manifest is ${liveVersion ?? "unreadable"}, expected ${options.target}` : liveTree;
|
|
2225
2969
|
try {
|
|
2226
|
-
(0,
|
|
2227
|
-
(0,
|
|
2228
|
-
if (displaced) (0,
|
|
2970
|
+
(0, import_node_fs17.mkdirSync)((0, import_node_path10.join)(scratch, "rejected"), { recursive: true });
|
|
2971
|
+
(0, import_node_fs17.renameSync)(live, broken);
|
|
2972
|
+
if (displaced) (0, import_node_fs17.renameSync)(quarantine, live);
|
|
2229
2973
|
} catch (error) {
|
|
2230
2974
|
return result(installed, liveVersion ?? "unknown", "fail", `post-switch Hermes verification failed and recovery failed: ${message2(error)}`);
|
|
2231
2975
|
}
|
|
@@ -2244,7 +2988,7 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
2244
2988
|
}
|
|
2245
2989
|
|
|
2246
2990
|
// src/reap.ts
|
|
2247
|
-
var
|
|
2991
|
+
var import_node_fs18 = require("node:fs");
|
|
2248
2992
|
var import_node_path11 = require("node:path");
|
|
2249
2993
|
var import_node_perf_hooks = require("node:perf_hooks");
|
|
2250
2994
|
function elapsedMs(startedAt) {
|
|
@@ -2254,7 +2998,7 @@ var KEEP_GENERATIONS2 = 2;
|
|
|
2254
2998
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
2255
2999
|
function listDirs(path) {
|
|
2256
3000
|
try {
|
|
2257
|
-
return (0,
|
|
3001
|
+
return (0, import_node_fs18.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2258
3002
|
} catch {
|
|
2259
3003
|
return [];
|
|
2260
3004
|
}
|
|
@@ -2275,7 +3019,7 @@ function pruneGenerations(root, category, dryRun) {
|
|
|
2275
3019
|
continue;
|
|
2276
3020
|
}
|
|
2277
3021
|
try {
|
|
2278
|
-
(0,
|
|
3022
|
+
(0, import_node_fs18.rmSync)((0, import_node_path11.join)(set, version), { recursive: true, force: true });
|
|
2279
3023
|
reaped += 1;
|
|
2280
3024
|
} catch (error) {
|
|
2281
3025
|
failures.push(`${version}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2308,7 +3052,7 @@ function reapScratch(context, dryRun) {
|
|
|
2308
3052
|
for (const [dir, patterns] of scanned) {
|
|
2309
3053
|
let entries;
|
|
2310
3054
|
try {
|
|
2311
|
-
entries = (0,
|
|
3055
|
+
entries = (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true });
|
|
2312
3056
|
} catch {
|
|
2313
3057
|
unreadable.push(dir);
|
|
2314
3058
|
continue;
|
|
@@ -2319,7 +3063,7 @@ function reapScratch(context, dryRun) {
|
|
|
2319
3063
|
const path = (0, import_node_path11.join)(dir, entry.name);
|
|
2320
3064
|
let ageMs;
|
|
2321
3065
|
try {
|
|
2322
|
-
ageMs = now - (0,
|
|
3066
|
+
ageMs = now - (0, import_node_fs18.statSync)(path).mtimeMs;
|
|
2323
3067
|
} catch {
|
|
2324
3068
|
continue;
|
|
2325
3069
|
}
|
|
@@ -2329,7 +3073,7 @@ function reapScratch(context, dryRun) {
|
|
|
2329
3073
|
continue;
|
|
2330
3074
|
}
|
|
2331
3075
|
try {
|
|
2332
|
-
(0,
|
|
3076
|
+
(0, import_node_fs18.rmSync)(path, { force: true });
|
|
2333
3077
|
reaped += 1;
|
|
2334
3078
|
} catch (error) {
|
|
2335
3079
|
failures.push(`${entry.name}: ${error?.code ?? error?.message ?? error}`);
|
|
@@ -2386,7 +3130,7 @@ async function reconcile(options) {
|
|
|
2386
3130
|
const recordArm = (arm, durationMs) => {
|
|
2387
3131
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2388
3132
|
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);
|
|
3133
|
+
options.onArm?.({ ...arm, ...durationMs === void 0 ? {} : { durationMs } });
|
|
2390
3134
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2391
3135
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
2392
3136
|
if (arm.verdict === "fail") summary.exit = Math.max(summary.exit, 3);
|
|
@@ -2551,7 +3295,7 @@ async function reconcile(options) {
|
|
|
2551
3295
|
|
|
2552
3296
|
// src/scheduler.ts
|
|
2553
3297
|
var import_node_child_process8 = require("node:child_process");
|
|
2554
|
-
var
|
|
3298
|
+
var import_node_fs19 = require("node:fs");
|
|
2555
3299
|
var import_node_os6 = require("node:os");
|
|
2556
3300
|
var import_node_path12 = require("node:path");
|
|
2557
3301
|
var TASK_NAME = "MMI Fleet Updater";
|
|
@@ -2711,7 +3455,7 @@ WantedBy=timers.target
|
|
|
2711
3455
|
}
|
|
2712
3456
|
function readFileOrNull(path) {
|
|
2713
3457
|
try {
|
|
2714
|
-
return (0,
|
|
3458
|
+
return (0, import_node_fs19.readFileSync)(path, "utf8");
|
|
2715
3459
|
} catch {
|
|
2716
3460
|
return null;
|
|
2717
3461
|
}
|
|
@@ -2763,22 +3507,22 @@ function windowsSchedulerStatus(env) {
|
|
|
2763
3507
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, LAUNCHER_FILE);
|
|
2764
3508
|
const info = taskRunInfo();
|
|
2765
3509
|
const runInfo = info ?? EMPTY_RUN_INFO;
|
|
2766
|
-
const
|
|
3510
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...runInfo, detail });
|
|
2767
3511
|
if (!query.stdout.includes(launcherPath)) {
|
|
2768
|
-
return
|
|
3512
|
+
return fail2(`"${TASK_NAME}" action does not run the hidden launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2769
3513
|
}
|
|
2770
3514
|
const launcher = readFileOrNull(launcherPath);
|
|
2771
3515
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2772
|
-
return
|
|
3516
|
+
return fail2(`"${TASK_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2773
3517
|
}
|
|
2774
3518
|
if (query.stdout.includes("<LogonTrigger>")) {
|
|
2775
|
-
return
|
|
3519
|
+
return fail2(`"${TASK_NAME}" still carries the dormant logon-only trigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2776
3520
|
}
|
|
2777
3521
|
if (!query.stdout.includes("<TimeTrigger>") || !query.stdout.includes(`<Interval>${REPEAT_INTERVAL}</Interval>`)) {
|
|
2778
|
-
return
|
|
3522
|
+
return fail2(`"${TASK_NAME}" has no ${REPEAT_INTERVAL}-repeating TimeTrigger \u2014 run \`mmi-hub autoupdate on\``);
|
|
2779
3523
|
}
|
|
2780
3524
|
if (info && neverRan(info) && !info.nextRunTime) {
|
|
2781
|
-
return
|
|
3525
|
+
return fail2(`"${TASK_NAME}" has never run and has no next run time \u2014 run \`mmi-hub autoupdate on\``);
|
|
2782
3526
|
}
|
|
2783
3527
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2784
3528
|
const ran = !info ? "run history unreadable" : neverRan(info) ? "never run yet" : `last run ${info.lastRunTime} \u2192 exit ${info.lastTaskResult ?? "unknown"}`;
|
|
@@ -2792,20 +3536,20 @@ function darwinSchedulerStatus(env) {
|
|
|
2792
3536
|
}
|
|
2793
3537
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2794
3538
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2795
|
-
const
|
|
3539
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2796
3540
|
if (!plist.includes(launcherPath)) {
|
|
2797
|
-
return
|
|
3541
|
+
return fail2(`"${LAUNCHD_LABEL}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2798
3542
|
}
|
|
2799
3543
|
const launcher = readFileOrNull(launcherPath);
|
|
2800
3544
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2801
|
-
return
|
|
3545
|
+
return fail2(`"${LAUNCHD_LABEL}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2802
3546
|
}
|
|
2803
3547
|
if (!plist.includes(`<integer>${STARTINTERVAL_SECONDS}</integer>`)) {
|
|
2804
|
-
return
|
|
3548
|
+
return fail2(`"${LAUNCHD_LABEL}" has no ${STARTINTERVAL_SECONDS}s StartInterval \u2014 run \`mmi-hub autoupdate on\``);
|
|
2805
3549
|
}
|
|
2806
3550
|
const loaded = launchctl(["print", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2807
3551
|
if (loaded.status !== 0) {
|
|
2808
|
-
return
|
|
3552
|
+
return fail2(`"${LAUNCHD_LABEL}" is registered but not loaded \u2014 run \`mmi-hub autoupdate on\``);
|
|
2809
3553
|
}
|
|
2810
3554
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2811
3555
|
return { ok: true, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail: `"${LAUNCHD_LABEL}" registered ${POSIX_CADENCE}; ${points}` };
|
|
@@ -2820,20 +3564,20 @@ function linuxSchedulerStatus(env) {
|
|
|
2820
3564
|
}
|
|
2821
3565
|
const hubDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2822
3566
|
const launcherPath = (0, import_node_path12.join)(statePaths(env).root, POSIX_LAUNCHER_FILE);
|
|
2823
|
-
const
|
|
3567
|
+
const fail2 = (detail) => ({ ok: false, supported: true, enabled: true, ...EMPTY_RUN_INFO, detail });
|
|
2824
3568
|
if (!service.includes(launcherPath)) {
|
|
2825
|
-
return
|
|
3569
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" does not run the reconcile launcher ${launcherPath} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2826
3570
|
}
|
|
2827
3571
|
const launcher = readFileOrNull(launcherPath);
|
|
2828
3572
|
if (hubDist && !launcher?.includes(hubDist)) {
|
|
2829
|
-
return
|
|
3573
|
+
return fail2(`"${SYSTEMD_SERVICE_NAME}" launcher does not point at the current @mutmutco/hub dist ${hubDist} \u2014 run \`mmi-hub autoupdate on\``);
|
|
2830
3574
|
}
|
|
2831
3575
|
if (!timer.includes(`OnUnitActiveSec=${STARTINTERVAL_SECONDS}`)) {
|
|
2832
|
-
return
|
|
3576
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" has no ${STARTINTERVAL_SECONDS}s OnUnitActiveSec \u2014 run \`mmi-hub autoupdate on\``);
|
|
2833
3577
|
}
|
|
2834
3578
|
const enabled = systemctlUser(["is-enabled", SYSTEMD_TIMER_NAME]);
|
|
2835
3579
|
if (enabled.status !== 0) {
|
|
2836
|
-
return
|
|
3580
|
+
return fail2(`"${SYSTEMD_TIMER_NAME}" is registered but not enabled \u2014 run \`mmi-hub autoupdate on\``);
|
|
2837
3581
|
}
|
|
2838
3582
|
const points = `points at ${hubDist ?? "the registered Hub dist"}`;
|
|
2839
3583
|
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 +3603,9 @@ function installWindowsTask(env) {
|
|
|
2859
3603
|
const userId = `${env.USERDOMAIN || env.COMPUTERNAME || ""}\\${env.USERNAME || ""}`.replace(/^\\/, "");
|
|
2860
3604
|
if (!env.USERNAME) return { ok: false, detail: "cannot resolve the registering user (USERNAME unset) \u2014 the task principal must be explicit" };
|
|
2861
3605
|
const launcherPath = (0, import_node_path12.join)(paths.root, LAUNCHER_FILE);
|
|
2862
|
-
(0,
|
|
3606
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherVbs(process.execPath, hubDist), "utf8");
|
|
2863
3607
|
const xmlPath = (0, import_node_path12.join)(paths.root, "task.xml");
|
|
2864
|
-
(0,
|
|
3608
|
+
(0, import_node_fs19.writeFileSync)(xmlPath, "\uFEFF" + taskXml(launcherPath, paths.root, userId, localStartBoundary(/* @__PURE__ */ new Date())), "utf16le");
|
|
2865
3609
|
const create = schtasks(["/create", "/tn", TASK_NAME, "/xml", xmlPath, "/f"]);
|
|
2866
3610
|
if (create.status !== 0) {
|
|
2867
3611
|
const denied = /access is denied/i.test(create.stderr + create.stdout);
|
|
@@ -2880,8 +3624,8 @@ function refreshPosixLauncher(env) {
|
|
|
2880
3624
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2881
3625
|
const desired = launcherSh(process.execPath, hubDist);
|
|
2882
3626
|
if (readFileOrNull(launcherPath) === desired) return null;
|
|
2883
|
-
(0,
|
|
2884
|
-
(0,
|
|
3627
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, desired, "utf8");
|
|
3628
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2885
3629
|
return launcherPath;
|
|
2886
3630
|
}
|
|
2887
3631
|
function installLaunchd(env) {
|
|
@@ -2894,12 +3638,12 @@ function installLaunchd(env) {
|
|
|
2894
3638
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2895
3639
|
const paths = ensureState(env);
|
|
2896
3640
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2897
|
-
(0,
|
|
2898
|
-
(0,
|
|
3641
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3642
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2899
3643
|
const agentsDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents");
|
|
2900
|
-
(0,
|
|
3644
|
+
(0, import_node_fs19.mkdirSync)(agentsDir, { recursive: true });
|
|
2901
3645
|
const plistPath = (0, import_node_path12.join)(agentsDir, LAUNCHD_PLIST_NAME);
|
|
2902
|
-
(0,
|
|
3646
|
+
(0, import_node_fs19.writeFileSync)(plistPath, launchdPlist(launcherPath, paths.root), "utf8");
|
|
2903
3647
|
launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2904
3648
|
const load = launchctl(["bootstrap", `gui/${uid()}`, plistPath]);
|
|
2905
3649
|
if (load.status !== 0) {
|
|
@@ -2918,12 +3662,12 @@ function installSystemdTimer(env) {
|
|
|
2918
3662
|
if (!hubDist) return { ok: false, detail: "cannot resolve the global @mutmutco/hub dist \u2014 run `npm install -g @mutmutco/hub` first" };
|
|
2919
3663
|
const paths = ensureState(env);
|
|
2920
3664
|
const launcherPath = (0, import_node_path12.join)(paths.root, POSIX_LAUNCHER_FILE);
|
|
2921
|
-
(0,
|
|
2922
|
-
(0,
|
|
3665
|
+
(0, import_node_fs19.writeFileSync)(launcherPath, launcherSh(process.execPath, hubDist), "utf8");
|
|
3666
|
+
(0, import_node_fs19.chmodSync)(launcherPath, 493);
|
|
2923
3667
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2924
|
-
(0,
|
|
2925
|
-
(0,
|
|
2926
|
-
(0,
|
|
3668
|
+
(0, import_node_fs19.mkdirSync)(unitDir, { recursive: true });
|
|
3669
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), systemdServiceUnit(launcherPath, paths.root), "utf8");
|
|
3670
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME), systemdTimerUnit(), "utf8");
|
|
2927
3671
|
const reload = systemctlUser(["daemon-reload"]);
|
|
2928
3672
|
if (reload.status !== 0) {
|
|
2929
3673
|
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 +3707,11 @@ function uninstallWindowsTask(env) {
|
|
|
2963
3707
|
function uninstallLaunchd(env) {
|
|
2964
3708
|
const plistPath = (0, import_node_path12.join)((0, import_node_os6.homedir)(), "Library", "LaunchAgents", LAUNCHD_PLIST_NAME);
|
|
2965
3709
|
const boot = launchctl(["bootout", `gui/${uid()}/${LAUNCHD_LABEL}`]);
|
|
2966
|
-
if (boot.status !== 0 && (0,
|
|
3710
|
+
if (boot.status !== 0 && (0, import_node_fs19.existsSync)(plistPath)) {
|
|
2967
3711
|
return { ok: false, detail: `launchctl bootout failed: ${(boot.stderr || boot.stdout).trim() || "unknown error"}` };
|
|
2968
3712
|
}
|
|
2969
3713
|
try {
|
|
2970
|
-
(0,
|
|
3714
|
+
(0, import_node_fs19.rmSync)(plistPath, { force: true });
|
|
2971
3715
|
} catch {
|
|
2972
3716
|
}
|
|
2973
3717
|
dropLauncher(env, POSIX_LAUNCHER_FILE);
|
|
@@ -2977,12 +3721,12 @@ function uninstallSystemdTimer(env) {
|
|
|
2977
3721
|
const unitDir = (0, import_node_path12.join)((0, import_node_os6.homedir)(), ".config", "systemd", "user");
|
|
2978
3722
|
const timerPath = (0, import_node_path12.join)(unitDir, SYSTEMD_TIMER_NAME);
|
|
2979
3723
|
const disable = systemctlUser(["disable", "--now", SYSTEMD_TIMER_NAME]);
|
|
2980
|
-
if (disable.status !== 0 && (0,
|
|
3724
|
+
if (disable.status !== 0 && (0, import_node_fs19.existsSync)(timerPath)) {
|
|
2981
3725
|
return { ok: false, detail: `systemctl --user disable --now ${SYSTEMD_TIMER_NAME} failed: ${(disable.stderr || disable.stdout).trim() || "unknown error"}` };
|
|
2982
3726
|
}
|
|
2983
3727
|
try {
|
|
2984
|
-
(0,
|
|
2985
|
-
(0,
|
|
3728
|
+
(0, import_node_fs19.rmSync)(timerPath, { force: true });
|
|
3729
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(unitDir, SYSTEMD_SERVICE_NAME), { force: true });
|
|
2986
3730
|
} catch {
|
|
2987
3731
|
}
|
|
2988
3732
|
systemctlUser(["daemon-reload"]);
|
|
@@ -2997,16 +3741,16 @@ function uninstallTask(env = process.env) {
|
|
|
2997
3741
|
}
|
|
2998
3742
|
function dropLauncher(env, file) {
|
|
2999
3743
|
try {
|
|
3000
|
-
(0,
|
|
3744
|
+
(0, import_node_fs19.rmSync)((0, import_node_path12.join)(statePaths(env).root, file), { force: true });
|
|
3001
3745
|
} catch {
|
|
3002
3746
|
}
|
|
3003
3747
|
}
|
|
3004
3748
|
|
|
3005
3749
|
// src/status.ts
|
|
3006
|
-
var
|
|
3750
|
+
var import_node_fs20 = require("node:fs");
|
|
3007
3751
|
function readJournal(path) {
|
|
3008
3752
|
try {
|
|
3009
|
-
const events = (0,
|
|
3753
|
+
const events = (0, import_node_fs20.readFileSync)(path, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3010
3754
|
try {
|
|
3011
3755
|
return JSON.parse(line);
|
|
3012
3756
|
} catch {
|
|
@@ -3129,31 +3873,6 @@ function formatHubStatus(status) {
|
|
|
3129
3873
|
}
|
|
3130
3874
|
|
|
3131
3875
|
// 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
3876
|
var RETRY_HINTS = [
|
|
3158
3877
|
[/applies when the host exits|applies on a quiet tick/, "installs when the app closes"],
|
|
3159
3878
|
[/in flight|changed while/, "rerun update \u2014 host race"],
|
|
@@ -3182,257 +3901,34 @@ function failConsequence(row) {
|
|
|
3182
3901
|
}
|
|
3183
3902
|
return matchFirst(FAIL_CONSEQUENCE, detail) ?? "update failed \u2014 previous version kept";
|
|
3184
3903
|
}
|
|
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
3904
|
function reportCounts(summary) {
|
|
3352
|
-
const
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
total: rows.length
|
|
3905
|
+
const counts2 = {
|
|
3906
|
+
updated: 0,
|
|
3907
|
+
current: 0,
|
|
3908
|
+
retry: 0,
|
|
3909
|
+
failed: 0,
|
|
3910
|
+
total: summary.surfaces.filter((surface) => surface.present && surface.action === "arm-pending").length
|
|
3359
3911
|
};
|
|
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";
|
|
3912
|
+
for (const arm of [summary.updater, summary.cli, ...summary.plugins]) {
|
|
3913
|
+
if (!arm) continue;
|
|
3914
|
+
counts2.total++;
|
|
3915
|
+
if (arm.verdict === "fail") counts2.failed++;
|
|
3916
|
+
else if (arm.verdict === "defer") counts2.retry++;
|
|
3917
|
+
else if (arm.verdict === "skip" || arm.from !== null && arm.from === arm.to && !arm.launchStaged) counts2.current++;
|
|
3918
|
+
else counts2.updated++;
|
|
3418
3919
|
}
|
|
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";
|
|
3920
|
+
return counts2;
|
|
3425
3921
|
}
|
|
3426
3922
|
|
|
3427
3923
|
// src/stamp.ts
|
|
3428
|
-
var
|
|
3924
|
+
var import_node_fs21 = require("node:fs");
|
|
3429
3925
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
3430
3926
|
var PLUGIN_ARMS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "cursor", "hermes"]);
|
|
3431
3927
|
function fleetStamp(env = process.env) {
|
|
3432
3928
|
const paths = statePaths(env);
|
|
3433
3929
|
let events = [];
|
|
3434
3930
|
try {
|
|
3435
|
-
events = (0,
|
|
3931
|
+
events = (0, import_node_fs21.readFileSync)(paths.journalPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => {
|
|
3436
3932
|
try {
|
|
3437
3933
|
return JSON.parse(line);
|
|
3438
3934
|
} catch {
|
|
@@ -3482,7 +3978,7 @@ function fleetStamp(env = process.env) {
|
|
|
3482
3978
|
// src/index.ts
|
|
3483
3979
|
function ownVersion() {
|
|
3484
3980
|
try {
|
|
3485
|
-
return JSON.parse((0,
|
|
3981
|
+
return JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path13.join)(__dirname, "..", "package.json"), "utf8")).version;
|
|
3486
3982
|
} catch {
|
|
3487
3983
|
return "0.0.0";
|
|
3488
3984
|
}
|
|
@@ -3502,147 +3998,34 @@ var HELP = `mmi-hub ${ownVersion()} \u2014 install and maintain MMI tooling
|
|
|
3502
3998
|
State remains under $MMI_UPDATER_HOME (default ~/.mmi/updater), and the Windows task name remains
|
|
3503
3999
|
"MMI Fleet Updater", so existing installations continue in place.
|
|
3504
4000
|
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) {
|
|
4001
|
+
function handOff(reexec, args, env) {
|
|
3553
4002
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
3554
4003
|
stdio: "inherit",
|
|
3555
4004
|
windowsHide: true,
|
|
3556
|
-
env: { ...
|
|
4005
|
+
env: { ...env, MMI_UPDATER_REEXEC: "1" }
|
|
3557
4006
|
});
|
|
3558
4007
|
if (child.error || typeof child.status !== "number") {
|
|
3559
|
-
|
|
3560
|
-
`);
|
|
3561
|
-
return 1;
|
|
4008
|
+
throw new Error(`could not run the updated updater at ${reexec.dist} \u2014 ${child.error?.message ?? "no exit status"}; rerun mmi-hub update`);
|
|
3562
4009
|
}
|
|
3563
4010
|
return child.status;
|
|
3564
4011
|
}
|
|
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
4012
|
function shippedTargetFrom(args) {
|
|
3582
4013
|
const index = args.indexOf("--shipped");
|
|
3583
4014
|
if (index < 0) return void 0;
|
|
3584
4015
|
const value = (args[index + 1] ?? "").trim();
|
|
3585
4016
|
return value && !value.startsWith("--") ? value : void 0;
|
|
3586
4017
|
}
|
|
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 };
|
|
4018
|
+
function runMaintenance2(args, dependencies = {}) {
|
|
4019
|
+
return runMaintenance(installer_product_default, args, {
|
|
4020
|
+
engine: dependencies.reconcile ?? reconcile,
|
|
4021
|
+
schedule: dependencies.schedule ?? installTask,
|
|
4022
|
+
handOff: dependencies.handOff ?? handOff,
|
|
4023
|
+
run: dependencies.run,
|
|
4024
|
+
shippedTarget: shippedTargetFrom(args),
|
|
4025
|
+
counts: reportCounts,
|
|
4026
|
+
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}
|
|
4027
|
+
${retryHint(arm.detail)}` : void 0
|
|
4028
|
+
});
|
|
3646
4029
|
}
|
|
3647
4030
|
async function main(args = process.argv.slice(2)) {
|
|
3648
4031
|
if (args.includes("--version")) {
|
|
@@ -3660,41 +4043,7 @@ async function main(args = process.argv.slice(2)) {
|
|
|
3660
4043
|
process.stdout.write(json ? JSON.stringify(status, null, 2) + "\n" : formatHubStatus(status) + "\n");
|
|
3661
4044
|
return status.failures.length ? 1 : 0;
|
|
3662
4045
|
}
|
|
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
|
-
}
|
|
4046
|
+
if (command === "update" || command === "install") return runMaintenance2(args);
|
|
3698
4047
|
if (command === "autoupdate") {
|
|
3699
4048
|
const commandIndex = args.indexOf(command);
|
|
3700
4049
|
const mode = args[commandIndex + 1];
|
|
@@ -3719,27 +4068,18 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3719
4068
|
}
|
|
3720
4069
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3721
4070
|
0 && (module.exports = {
|
|
3722
|
-
ACCENT,
|
|
3723
|
-
FACE_PRODUCT,
|
|
3724
|
-
MM_GREEN,
|
|
3725
|
-
RESET,
|
|
3726
|
-
accent,
|
|
3727
4071
|
acquireSharedDocLock,
|
|
3728
|
-
|
|
3729
|
-
autoupdateLine,
|
|
3730
|
-
brandLine,
|
|
4072
|
+
assertFaceConformance,
|
|
3731
4073
|
buildWatcherCommand,
|
|
3732
4074
|
checkAtomicity,
|
|
3733
|
-
checklistLine,
|
|
3734
4075
|
claudeArm,
|
|
3735
4076
|
claudeHostEvidence,
|
|
3736
4077
|
clearLaunchTarget,
|
|
3737
4078
|
clearPending,
|
|
4079
|
+
createInstallerRun,
|
|
3738
4080
|
enumerateSurfaces,
|
|
3739
4081
|
failConsequence,
|
|
3740
4082
|
fleetStamp,
|
|
3741
|
-
formatReconcileReport,
|
|
3742
|
-
heldOutput,
|
|
3743
4083
|
hermesArm,
|
|
3744
4084
|
hermesHome,
|
|
3745
4085
|
hermesHomes,
|
|
@@ -3766,17 +4106,15 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3766
4106
|
probeHermesLaunchStaging,
|
|
3767
4107
|
probeJervcodeInstallation,
|
|
3768
4108
|
probeLaunchTargetVersion,
|
|
3769
|
-
progressiveRow,
|
|
3770
4109
|
readLaunchTarget,
|
|
3771
4110
|
readPending,
|
|
3772
|
-
receiptText,
|
|
3773
4111
|
reconcile,
|
|
3774
4112
|
reconcileCursorUserHooks,
|
|
3775
4113
|
releaseSharedDocLock,
|
|
3776
4114
|
reportCounts,
|
|
3777
|
-
reportFooterLines,
|
|
3778
4115
|
retiredSurfaces,
|
|
3779
4116
|
retryHint,
|
|
4117
|
+
runMaintenance,
|
|
3780
4118
|
schedulePendingWatcher,
|
|
3781
4119
|
schedulerStatus,
|
|
3782
4120
|
sharedDocLockPath,
|
|
@@ -3787,11 +4125,8 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3787
4125
|
systemdServiceUnit,
|
|
3788
4126
|
systemdTimerUnit,
|
|
3789
4127
|
taskXml,
|
|
3790
|
-
updateHeader,
|
|
3791
|
-
updaterFace,
|
|
3792
4128
|
watcherLockPath,
|
|
3793
4129
|
withSharedDocLock,
|
|
3794
|
-
wrapWords,
|
|
3795
4130
|
writeLaunchTarget,
|
|
3796
4131
|
writePending
|
|
3797
4132
|
});
|