@mutmutco/hub 4.4.6 → 4.4.8
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 +479 -237
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -22,12 +22,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
24
|
ACCENT: () => ACCENT,
|
|
25
|
+
FACE_PRODUCT: () => FACE_PRODUCT,
|
|
25
26
|
MM_GREEN: () => MM_GREEN,
|
|
26
27
|
RESET: () => RESET,
|
|
27
28
|
accent: () => accent,
|
|
28
29
|
acquireSharedDocLock: () => acquireSharedDocLock,
|
|
30
|
+
attentionRows: () => attentionRows,
|
|
29
31
|
brandLine: () => brandLine,
|
|
30
32
|
buildWatcherCommand: () => buildWatcherCommand,
|
|
33
|
+
checkAtomicity: () => checkAtomicity,
|
|
31
34
|
checklistLine: () => checklistLine,
|
|
32
35
|
claudeArm: () => claudeArm,
|
|
33
36
|
claudeHostEvidence: () => claudeHostEvidence,
|
|
@@ -67,7 +70,6 @@ __export(index_exports, {
|
|
|
67
70
|
progressiveRow: () => progressiveRow,
|
|
68
71
|
readLaunchTarget: () => readLaunchTarget,
|
|
69
72
|
readPending: () => readPending,
|
|
70
|
-
receipt: () => receipt,
|
|
71
73
|
receiptText: () => receiptText,
|
|
72
74
|
reconcile: () => reconcile,
|
|
73
75
|
reconcileCursorUserHooks: () => reconcileCursorUserHooks,
|
|
@@ -82,17 +84,15 @@ __export(index_exports, {
|
|
|
82
84
|
sharedDocLockTimeoutReceipt: () => sharedDocLockTimeoutReceipt,
|
|
83
85
|
shippedTargetFrom: () => shippedTargetFrom,
|
|
84
86
|
shouldHandOff: () => shouldHandOff,
|
|
85
|
-
signOff: () => signOff,
|
|
86
|
-
startSpinner: () => startSpinner,
|
|
87
87
|
surfaceArmPlan: () => surfaceArmPlan,
|
|
88
88
|
systemdServiceUnit: () => systemdServiceUnit,
|
|
89
89
|
systemdTimerUnit: () => systemdTimerUnit,
|
|
90
90
|
taskXml: () => taskXml,
|
|
91
91
|
updateHeader: () => updateHeader,
|
|
92
|
+
updaterFace: () => updaterFace,
|
|
92
93
|
watcherLockPath: () => watcherLockPath,
|
|
93
|
-
welcome: () => welcome,
|
|
94
94
|
withSharedDocLock: () => withSharedDocLock,
|
|
95
|
-
wrapWords: () =>
|
|
95
|
+
wrapWords: () => wrapWords2,
|
|
96
96
|
writeLaunchTarget: () => writeLaunchTarget,
|
|
97
97
|
writePending: () => writePending
|
|
98
98
|
});
|
|
@@ -101,6 +101,207 @@ var import_node_child_process9 = require("node:child_process");
|
|
|
101
101
|
var import_node_fs18 = require("node:fs");
|
|
102
102
|
var import_node_path13 = require("node:path");
|
|
103
103
|
|
|
104
|
+
// node_modules/@mutmutco/installer-face/dist/index.js
|
|
105
|
+
var PRODUCTS = Object.freeze({
|
|
106
|
+
"mm-strategy": Object.freeze({
|
|
107
|
+
name: "MM Strategy",
|
|
108
|
+
accent: "38;2;249;115;22",
|
|
109
|
+
warm: "Welcome. Let's set up MM Strategy \u2014 about a minute."
|
|
110
|
+
}),
|
|
111
|
+
"mmi-hub": Object.freeze({
|
|
112
|
+
name: "mmi-hub",
|
|
113
|
+
accent: "38;2;125;211;252",
|
|
114
|
+
warm: "Welcome back. Checking your surfaces\u2026"
|
|
115
|
+
}),
|
|
116
|
+
"jerv-hub": Object.freeze({
|
|
117
|
+
name: "jerv-hub",
|
|
118
|
+
accent: "38;2;248;113;113",
|
|
119
|
+
warm: "Welcome back. Checking your surfaces\u2026"
|
|
120
|
+
}),
|
|
121
|
+
jervcode: Object.freeze({
|
|
122
|
+
name: "JervCode",
|
|
123
|
+
accent: "38;2;192;132;252",
|
|
124
|
+
warm: "Welcome back. Keeping JervCode current\u2026"
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
function identityFor(product) {
|
|
128
|
+
const identity = PRODUCTS[product];
|
|
129
|
+
if (!identity) {
|
|
130
|
+
throw new Error(`unknown product ${JSON.stringify(product)} \u2014 add it to installer/face/src/products.ts rather than composing a greeting locally (known: ${Object.keys(PRODUCTS).join(", ")})`);
|
|
131
|
+
}
|
|
132
|
+
return identity;
|
|
133
|
+
}
|
|
134
|
+
var GLYPH = Object.freeze({
|
|
135
|
+
diamond: "\u25C6",
|
|
136
|
+
hollow: "\u25C7",
|
|
137
|
+
bar: "\u2502",
|
|
138
|
+
check: "\u2714",
|
|
139
|
+
cross: "\u2716",
|
|
140
|
+
dot: "\u25CF",
|
|
141
|
+
boxTop: "\u256D",
|
|
142
|
+
boxTopEnd: "\u256E",
|
|
143
|
+
boxBottom: "\u2570",
|
|
144
|
+
boxBottomEnd: "\u256F",
|
|
145
|
+
rule: "\u2500"
|
|
146
|
+
});
|
|
147
|
+
var PALETTE = Object.freeze({
|
|
148
|
+
green: "38;2;31;209;138",
|
|
149
|
+
ink: "38;2;237;237;237",
|
|
150
|
+
muted: "38;2;150;150;150",
|
|
151
|
+
line: "38;2;42;42;42"
|
|
152
|
+
});
|
|
153
|
+
var TITLE_COLUMN = 6;
|
|
154
|
+
var ANSI = /\u001b\[[0-9;]*m/g;
|
|
155
|
+
function visibleWidth(text) {
|
|
156
|
+
return [...String(text).replace(ANSI, "")].length;
|
|
157
|
+
}
|
|
158
|
+
function faceWidth(columns) {
|
|
159
|
+
const raw = Number(columns);
|
|
160
|
+
return Math.max(40, Math.min(100, Number.isFinite(raw) && raw > 0 ? raw : 100));
|
|
161
|
+
}
|
|
162
|
+
function wrapWords(text, width) {
|
|
163
|
+
const limit = Math.max(8, Math.floor(width));
|
|
164
|
+
const lines = [];
|
|
165
|
+
let line = "";
|
|
166
|
+
for (const word of String(text).split(/\s+/u).filter(Boolean)) {
|
|
167
|
+
let token = word;
|
|
168
|
+
while (visibleWidth(token) > limit) {
|
|
169
|
+
if (line) {
|
|
170
|
+
lines.push(line);
|
|
171
|
+
line = "";
|
|
172
|
+
}
|
|
173
|
+
lines.push([...token].slice(0, limit).join(""));
|
|
174
|
+
token = [...token].slice(limit).join("");
|
|
175
|
+
}
|
|
176
|
+
if (!line) line = token;
|
|
177
|
+
else if (visibleWidth(line) + 1 + visibleWidth(token) <= limit) line += ` ${token}`;
|
|
178
|
+
else {
|
|
179
|
+
lines.push(line);
|
|
180
|
+
line = token;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (line) lines.push(line);
|
|
184
|
+
return lines.length ? lines : [""];
|
|
185
|
+
}
|
|
186
|
+
function createFace({ product, color = false, columns }) {
|
|
187
|
+
const identity = identityFor(product);
|
|
188
|
+
const width = faceWidth(columns);
|
|
189
|
+
const paint = (sgr, text) => color ? `\x1B[${sgr}m${text}\x1B[0m` : String(text);
|
|
190
|
+
const bar = () => paint(PALETTE.muted, GLYPH.bar);
|
|
191
|
+
const indent = " ".repeat(TITLE_COLUMN - 1);
|
|
192
|
+
const welcome = () => [
|
|
193
|
+
`${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, identity.name)} \u2014 Mutatis Mutandis`,
|
|
194
|
+
bar(),
|
|
195
|
+
`${bar()} ${identity.warm}`,
|
|
196
|
+
bar()
|
|
197
|
+
];
|
|
198
|
+
const step = (title, seconds = null, kind = "ok") => {
|
|
199
|
+
const glyph = kind === "fail" ? paint(identity.accent, GLYPH.cross) : kind === "note" ? paint(PALETTE.muted, GLYPH.hollow) : paint(PALETTE.green, GLYPH.check);
|
|
200
|
+
const time = seconds === null || seconds === void 0 ? "" : paint(PALETTE.muted, `${Math.max(0, Math.round(seconds))}s`);
|
|
201
|
+
const column = Math.min(44, Math.max(0, width - 8));
|
|
202
|
+
const reserved = time ? 6 : 0;
|
|
203
|
+
const [first, ...rest] = wrapWords(title, width - TITLE_COLUMN - reserved);
|
|
204
|
+
const head = `${bar()} ${glyph} ${first}`;
|
|
205
|
+
const pad = Math.max(1, column - visibleWidth(head));
|
|
206
|
+
return [time ? `${head}${" ".repeat(pad)}${time}` : head, ...rest.map((line) => `${bar()}${indent}${line}`)].join("\n");
|
|
207
|
+
};
|
|
208
|
+
const relay = (text) => String(text).split("\n").map((line) => line.trim() === "" ? bar() : `${bar()}${indent}${line}`).join("\n");
|
|
209
|
+
const receipt = (lines) => {
|
|
210
|
+
const body = (Array.isArray(lines) ? lines : String(lines).split("\n")).flatMap((raw) => {
|
|
211
|
+
const line = String(raw);
|
|
212
|
+
if (visibleWidth(line) <= width - 6) return [line];
|
|
213
|
+
const lead = /^\s*/u.exec(line)?.[0] ?? "";
|
|
214
|
+
return wrapWords(line, width - 6 - lead.length).map((part) => `${lead}${part}`);
|
|
215
|
+
});
|
|
216
|
+
const content = Math.min(width - 6, Math.max(...body.map(visibleWidth)));
|
|
217
|
+
const rule = paint(identity.accent, GLYPH.rule.repeat(content + 4));
|
|
218
|
+
const frame = (left, right) => `${paint(identity.accent, left)}${rule}${paint(identity.accent, right)}`;
|
|
219
|
+
const bodyRow = (line) => line.startsWith(GLYPH.check) ? `${paint(PALETTE.green, GLYPH.check)}${line.slice(1)}` : line;
|
|
220
|
+
return [
|
|
221
|
+
frame(GLYPH.boxTop, GLYPH.boxTopEnd),
|
|
222
|
+
...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${bodyRow(line)}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
|
|
223
|
+
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
224
|
+
];
|
|
225
|
+
};
|
|
226
|
+
const signOff = () => `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
227
|
+
const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
|
|
228
|
+
return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
|
|
229
|
+
}
|
|
230
|
+
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
231
|
+
var SEPARATOR = "\xB7";
|
|
232
|
+
var subjectOf = (row) => row.aspect ? `${row.surface} ${row.aspect}` : row.surface;
|
|
233
|
+
var groupKey = (row) => `${subjectOf(row)}\0${row.state}\0${row.mark ?? ""}\0${row.note ?? ""}`;
|
|
234
|
+
function renderRows(face, rows) {
|
|
235
|
+
if (rows.length === 0) return [];
|
|
236
|
+
const groups = /* @__PURE__ */ new Map();
|
|
237
|
+
for (const row of rows) {
|
|
238
|
+
const key = groupKey(row);
|
|
239
|
+
const found = groups.get(key);
|
|
240
|
+
if (found) {
|
|
241
|
+
if (row.instance) found.instances.push(row.instance);
|
|
242
|
+
} else {
|
|
243
|
+
groups.set(key, { row, instances: row.instance ? [row.instance] : [] });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const subjects = [...groups.values()].map(({ row, instances }) => instances.length > 1 ? subjectOf(row) : [subjectOf(row), instances[0]].filter(Boolean).join(` ${SEPARATOR} `));
|
|
247
|
+
const column = Math.min(Math.max(...subjects.map(visibleWidth)), Math.max(12, face.width - 24));
|
|
248
|
+
const lines = [];
|
|
249
|
+
for (const [index, { row, instances }] of [...groups.values()].entries()) {
|
|
250
|
+
const subject = subjects[index];
|
|
251
|
+
const tail2 = instances.length > 1 ? `${row.state} ${SEPARATOR} ${instances.join(", ")}` : row.state;
|
|
252
|
+
const body = `${clip(subject, column).padEnd(column)} ${row.mark ? `${markGlyph(face, row.mark)} ` : ""}${tail2}`;
|
|
253
|
+
lines.push(...face.relay(body).split("\n"));
|
|
254
|
+
if (row.note) {
|
|
255
|
+
for (const part of wrapWords(row.note, Math.max(16, face.width - TITLE_COLUMN))) {
|
|
256
|
+
lines.push(...face.relay(part).split("\n"));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
function markGlyph(face, kind) {
|
|
263
|
+
const glyph = kind === "fail" ? GLYPH.cross : kind === "note" ? GLYPH.hollow : GLYPH.check;
|
|
264
|
+
return face.paint(kind === "fail" ? face.identity.accent : kind === "note" ? "38;2;150;150;150" : "38;2;31;209;138", glyph);
|
|
265
|
+
}
|
|
266
|
+
function clip(text, width) {
|
|
267
|
+
if (visibleWidth(text) <= width) return text;
|
|
268
|
+
return `\u2026${[...text].slice(-(width - 1)).join("")}`;
|
|
269
|
+
}
|
|
270
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
271
|
+
function createSpinner(face, { animate, stream = process.stderr, intervalMs = 90 }) {
|
|
272
|
+
let timer = null;
|
|
273
|
+
let text = "";
|
|
274
|
+
let frame = 0;
|
|
275
|
+
const clear = () => {
|
|
276
|
+
if (animate) stream.write("\r\x1B[2K");
|
|
277
|
+
};
|
|
278
|
+
const draw = () => {
|
|
279
|
+
if (!animate) return;
|
|
280
|
+
const line = face.step(text, null, "note").split("\n")[0].replace(GLYPH.hollow, FRAMES[frame % FRAMES.length]);
|
|
281
|
+
stream.write(`\r\x1B[2K${line}`);
|
|
282
|
+
frame += 1;
|
|
283
|
+
};
|
|
284
|
+
return {
|
|
285
|
+
start(next) {
|
|
286
|
+
text = next;
|
|
287
|
+
frame = 0;
|
|
288
|
+
draw();
|
|
289
|
+
if (animate && !timer) timer = setInterval(draw, intervalMs).unref();
|
|
290
|
+
},
|
|
291
|
+
say(next) {
|
|
292
|
+
text = next;
|
|
293
|
+
draw();
|
|
294
|
+
},
|
|
295
|
+
stop() {
|
|
296
|
+
if (timer) clearInterval(timer);
|
|
297
|
+
timer = null;
|
|
298
|
+
clear();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
303
|
+
var ALLOWED = new Set(Object.values(GLYPH));
|
|
304
|
+
|
|
104
305
|
// src/state.ts
|
|
105
306
|
var import_node_fs = require("node:fs");
|
|
106
307
|
var import_node_os = require("node:os");
|
|
@@ -165,11 +366,12 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
165
366
|
for (; ; ) {
|
|
166
367
|
const attempt = tryAcquire(path);
|
|
167
368
|
if (typeof attempt === "object") {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
attempt.release();
|
|
369
|
+
const result = fn();
|
|
370
|
+
if (result && typeof result.then === "function") {
|
|
371
|
+
return result.finally(() => attempt.release());
|
|
172
372
|
}
|
|
373
|
+
attempt.release();
|
|
374
|
+
return result;
|
|
173
375
|
}
|
|
174
376
|
if (Date.now() >= deadline) {
|
|
175
377
|
throw new UpdaterBusy(`another updater holds ${path} (waited ${waitMs}ms) \u2014 deferring`);
|
|
@@ -188,23 +390,29 @@ function appendJournal(path, event) {
|
|
|
188
390
|
return false;
|
|
189
391
|
}
|
|
190
392
|
}
|
|
191
|
-
function journalHighWater(path, surface) {
|
|
393
|
+
function journalHighWater(path, surface, events) {
|
|
192
394
|
let high = null;
|
|
395
|
+
for (const event of events ?? parseJournal(path)) {
|
|
396
|
+
if (event.dryRun) continue;
|
|
397
|
+
if (event.surface === surface && event.verdict === "ok" && event.to && (!high || compareSemver(event.to, high) > 0)) {
|
|
398
|
+
high = event.to;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return high;
|
|
402
|
+
}
|
|
403
|
+
function parseJournal(path) {
|
|
404
|
+
const events = [];
|
|
193
405
|
try {
|
|
194
406
|
for (const line of (0, import_node_fs.readFileSync)(path, "utf8").split("\n")) {
|
|
195
407
|
if (!line.trim()) continue;
|
|
196
408
|
try {
|
|
197
|
-
|
|
198
|
-
if (event.dryRun) continue;
|
|
199
|
-
if (event.surface === surface && event.verdict === "ok" && event.to && (!high || compareSemver(event.to, high) > 0)) {
|
|
200
|
-
high = event.to;
|
|
201
|
-
}
|
|
409
|
+
events.push(JSON.parse(line));
|
|
202
410
|
} catch {
|
|
203
411
|
}
|
|
204
412
|
}
|
|
205
413
|
} catch {
|
|
206
414
|
}
|
|
207
|
-
return
|
|
415
|
+
return events;
|
|
208
416
|
}
|
|
209
417
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
210
418
|
let lastVerdict = null;
|
|
@@ -255,6 +463,9 @@ function fileExists(path) {
|
|
|
255
463
|
return (0, import_node_fs.existsSync)(path);
|
|
256
464
|
}
|
|
257
465
|
|
|
466
|
+
// src/reconcile.ts
|
|
467
|
+
var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
468
|
+
|
|
258
469
|
// src/npm.ts
|
|
259
470
|
var import_node_child_process = require("node:child_process");
|
|
260
471
|
var import_node_fs2 = require("node:fs");
|
|
@@ -295,10 +506,40 @@ function runNpm(args, env = process.env, options = {}) {
|
|
|
295
506
|
const result = npm.kind === "node-script" ? (0, import_node_child_process.spawnSync)(process.execPath, [npm.script, ...args], { encoding: "utf8", env, cwd: options.cwd, windowsHide: true }) : (0, import_node_child_process.spawnSync)(npm.command, args, { encoding: "utf8", env, cwd: options.cwd, shell: process.platform === "win32", windowsHide: true });
|
|
296
507
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
297
508
|
}
|
|
509
|
+
function runNpmAsync(args, env = process.env, options = {}) {
|
|
510
|
+
const npm = resolveNpm(env);
|
|
511
|
+
const [file, argv, shell] = npm.kind === "node-script" ? [process.execPath, [npm.script, ...args], false] : [npm.command, args, process.platform === "win32"];
|
|
512
|
+
return new Promise((resolve) => {
|
|
513
|
+
let stdout = "";
|
|
514
|
+
let stderr = "";
|
|
515
|
+
let child;
|
|
516
|
+
try {
|
|
517
|
+
child = (0, import_node_child_process.spawn)(file, argv, { env, cwd: options.cwd, windowsHide: true, shell });
|
|
518
|
+
} catch (error) {
|
|
519
|
+
resolve({ status: null, stdout, stderr: String(error?.message ?? error) });
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
child.stdout?.setEncoding("utf8");
|
|
523
|
+
child.stderr?.setEncoding("utf8");
|
|
524
|
+
child.stdout?.on("data", (chunk) => {
|
|
525
|
+
stdout += chunk;
|
|
526
|
+
});
|
|
527
|
+
child.stderr?.on("data", (chunk) => {
|
|
528
|
+
stderr += chunk;
|
|
529
|
+
});
|
|
530
|
+
child.on("error", (error) => resolve({ status: null, stdout, stderr: String(error?.message ?? error) }));
|
|
531
|
+
child.on("close", (status) => resolve({ status, stdout, stderr }));
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
var globalRootMemo = /* @__PURE__ */ new WeakMap();
|
|
298
535
|
function globalRoot(env = process.env) {
|
|
536
|
+
const key = env;
|
|
537
|
+
const memoized = globalRootMemo.get(key);
|
|
538
|
+
if (memoized !== void 0) return memoized;
|
|
299
539
|
const root = runNpm(["root", "-g"], env);
|
|
300
|
-
|
|
301
|
-
|
|
540
|
+
const resolved = root.status !== 0 || !root.stdout.trim() ? null : root.stdout.trim();
|
|
541
|
+
globalRootMemo.set(key, resolved);
|
|
542
|
+
return resolved;
|
|
302
543
|
}
|
|
303
544
|
function npmView(spec, field, env = process.env) {
|
|
304
545
|
const args = ["view", spec, "--json"];
|
|
@@ -318,8 +559,15 @@ function parseNpmDistIntegrity(value) {
|
|
|
318
559
|
return typeof only === "string" && only.length > 0 ? only : null;
|
|
319
560
|
}
|
|
320
561
|
var npmStringField = parseNpmDistIntegrity;
|
|
321
|
-
function
|
|
322
|
-
|
|
562
|
+
async function npmDistIntegrityAsync(spec, env = process.env) {
|
|
563
|
+
const args = ["view", spec, "dist.integrity", "--json"];
|
|
564
|
+
const result = await runNpmAsync(args, env);
|
|
565
|
+
if (result.status !== 0 || !result.stdout.trim()) return null;
|
|
566
|
+
try {
|
|
567
|
+
return parseNpmDistIntegrity(JSON.parse(result.stdout));
|
|
568
|
+
} catch {
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
323
571
|
}
|
|
324
572
|
function npmVersions(packageName, env = process.env) {
|
|
325
573
|
const versions = npmView(packageName, "versions", env);
|
|
@@ -399,38 +647,60 @@ function gateCandidate(cachePath, repoUrl, candidate) {
|
|
|
399
647
|
}
|
|
400
648
|
return { ok: true, tagCommit: commit, bom };
|
|
401
649
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
650
|
+
var ATOMICITY_CONCURRENCY = 4;
|
|
651
|
+
async function mapBounded(items, bound, fn) {
|
|
652
|
+
const results = new Array(items.length);
|
|
653
|
+
let next = 0;
|
|
654
|
+
const workers = Array.from({ length: Math.min(bound, items.length) }, async () => {
|
|
655
|
+
for (; ; ) {
|
|
656
|
+
const index = next++;
|
|
657
|
+
if (index >= items.length) return;
|
|
658
|
+
results[index] = await fn(items[index], index);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
await Promise.all(workers);
|
|
662
|
+
return results;
|
|
663
|
+
}
|
|
664
|
+
async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
665
|
+
const bomRows = npmPackRows(bom);
|
|
666
|
+
const verdicts = new Array(bomRows.length).fill(null);
|
|
667
|
+
const pending = [];
|
|
668
|
+
bomRows.forEach((row, index) => {
|
|
405
669
|
if (row.version !== candidate) {
|
|
406
|
-
|
|
407
|
-
|
|
670
|
+
verdicts[index] = { id: row.id, ok: false, detail: `BOM row at ${row.version}, candidate ${candidate}` };
|
|
671
|
+
return;
|
|
408
672
|
}
|
|
409
673
|
const manifestText = showFile(cachePath, tagCommit, `${row.identity.packagePath}/package.json`);
|
|
410
674
|
let name;
|
|
411
|
-
let
|
|
675
|
+
let manifestVersion2;
|
|
412
676
|
try {
|
|
413
677
|
const manifest2 = JSON.parse(manifestText ?? "");
|
|
414
678
|
name = manifest2.name;
|
|
415
|
-
|
|
679
|
+
manifestVersion2 = manifest2.version;
|
|
416
680
|
} catch {
|
|
417
681
|
}
|
|
418
|
-
if (!name ||
|
|
419
|
-
|
|
420
|
-
|
|
682
|
+
if (!name || manifestVersion2 !== candidate) {
|
|
683
|
+
verdicts[index] = { id: row.id, name, ok: false, detail: `package.json at ${row.identity.packagePath} is not stamped ${candidate}` };
|
|
684
|
+
return;
|
|
421
685
|
}
|
|
422
|
-
|
|
686
|
+
pending.push({ index, id: row.id, name });
|
|
687
|
+
});
|
|
688
|
+
const integrities = await mapBounded(pending, ATOMICITY_CONCURRENCY, (entry) => Promise.resolve(npm.distIntegrity(`${entry.name}@${candidate}`)));
|
|
689
|
+
pending.forEach((entry, i) => {
|
|
690
|
+
const integrity = integrities[i];
|
|
423
691
|
if (!integrity) {
|
|
424
|
-
|
|
425
|
-
|
|
692
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} not on npm` };
|
|
693
|
+
return;
|
|
426
694
|
}
|
|
695
|
+
const row = bomRows[entry.index];
|
|
427
696
|
const legacySsri = /^sha\d+-/.test(row.identity.value ?? "");
|
|
428
697
|
if (legacySsri && integrity !== row.identity.value) {
|
|
429
|
-
|
|
430
|
-
|
|
698
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} dist.integrity differs from BOM identity` };
|
|
699
|
+
return;
|
|
431
700
|
}
|
|
432
|
-
|
|
433
|
-
}
|
|
701
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: true, detail: legacySsri ? `${entry.name}@${candidate} published, integrity matches` : `${entry.name}@${candidate} published` };
|
|
702
|
+
});
|
|
703
|
+
const rows = verdicts.map((row, index) => row ?? { id: bomRows[index].id, ok: false, detail: "atomicity probe produced no verdict" });
|
|
434
704
|
return { ok: rows.length > 0 && rows.every((row) => row.ok), rows };
|
|
435
705
|
}
|
|
436
706
|
|
|
@@ -606,6 +876,15 @@ function probeVersion(distPath) {
|
|
|
606
876
|
function tail(output) {
|
|
607
877
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
608
878
|
}
|
|
879
|
+
function manifestVersion(packageRoot) {
|
|
880
|
+
if (!packageRoot) return null;
|
|
881
|
+
try {
|
|
882
|
+
const version = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
883
|
+
return typeof version === "string" ? version : null;
|
|
884
|
+
} catch {
|
|
885
|
+
return null;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
609
888
|
function stagePackage(options) {
|
|
610
889
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
611
890
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
@@ -633,12 +912,17 @@ function npmGlobalArm(options) {
|
|
|
633
912
|
const { surface, packageName, distRel, target, dryRun, paths } = options;
|
|
634
913
|
const spec = `${packageName}@${target}`;
|
|
635
914
|
const result = (from, to, verdict, detail) => ({ surface, from, to, verdict, detail });
|
|
915
|
+
const highWater = journalHighWater(paths.journalPath, surface, options.journalEvents);
|
|
916
|
+
const localRoot = globalRoot(env);
|
|
917
|
+
const localVersion = localRoot ? manifestVersion((0, import_node_path4.join)(localRoot, ...packageName.split("/"))) : null;
|
|
918
|
+
if (localVersion === target && highWater === target) {
|
|
919
|
+
return result(localVersion, target, "ok", `already at target ${target} (installed manifest + journal high-water)`);
|
|
920
|
+
}
|
|
636
921
|
const dist = globalDist(env, packageName, distRel);
|
|
637
922
|
const installed = dist ? probeVersion(dist) : null;
|
|
638
923
|
if (installed && compareSemver(installed, target) > 0) {
|
|
639
924
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
640
925
|
}
|
|
641
|
-
const highWater = journalHighWater(paths.journalPath, surface);
|
|
642
926
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
643
927
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
644
928
|
}
|
|
@@ -862,7 +1146,7 @@ function claudeArm(options) {
|
|
|
862
1146
|
if (from && compareSemver(from, target) > 0) {
|
|
863
1147
|
return result(from, from, "skip", `installed ${from} is above candidate ${target} (monotonic)`);
|
|
864
1148
|
}
|
|
865
|
-
const highWater = journalHighWater(paths.journalPath, "claude");
|
|
1149
|
+
const highWater = journalHighWater(paths.journalPath, "claude", options.journalEvents);
|
|
866
1150
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
867
1151
|
return result(from, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
868
1152
|
}
|
|
@@ -1422,7 +1706,7 @@ function cursorArm(options) {
|
|
|
1422
1706
|
if (installed && compareSemver(installed, target) > 0) {
|
|
1423
1707
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
1424
1708
|
}
|
|
1425
|
-
const highWater = journalHighWater(paths.journalPath, "cursor");
|
|
1709
|
+
const highWater = journalHighWater(paths.journalPath, "cursor", options.journalEvents);
|
|
1426
1710
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1427
1711
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1428
1712
|
}
|
|
@@ -1566,11 +1850,11 @@ function parseListText(stdout) {
|
|
|
1566
1850
|
}
|
|
1567
1851
|
function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
1568
1852
|
const manifestLocation = materializedManifestLocation(env);
|
|
1569
|
-
const
|
|
1853
|
+
const manifestVersion2 = installedVersion(env);
|
|
1570
1854
|
const manifestEvidence = () => ({
|
|
1571
|
-
version:
|
|
1855
|
+
version: manifestVersion2,
|
|
1572
1856
|
location: manifestLocation,
|
|
1573
|
-
detail:
|
|
1857
|
+
detail: manifestVersion2 ? "materialized pi package manifest" : "materialized pi package manifest was unreadable"
|
|
1574
1858
|
});
|
|
1575
1859
|
const hosted = hostEnv(env);
|
|
1576
1860
|
const command = hostCommands(env).find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
@@ -1586,9 +1870,9 @@ function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
|
1586
1870
|
};
|
|
1587
1871
|
}
|
|
1588
1872
|
const hostError = result.status !== 0 ? `${hostLocation} failed: ${failure2(result)}` : `${hostLocation} did not list npm:${PI_PACKAGE}`;
|
|
1589
|
-
if (
|
|
1873
|
+
if (manifestVersion2) {
|
|
1590
1874
|
return {
|
|
1591
|
-
version:
|
|
1875
|
+
version: manifestVersion2,
|
|
1592
1876
|
location: manifestLocation,
|
|
1593
1877
|
detail: `${hostError}; using materialized pi package manifest`
|
|
1594
1878
|
};
|
|
@@ -1619,7 +1903,7 @@ function jervcodeArm(options) {
|
|
|
1619
1903
|
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
1620
1904
|
const from = installedVersion(env);
|
|
1621
1905
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1622
|
-
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1906
|
+
const highWater = journalHighWater(options.paths.journalPath, "jervcode", options.journalEvents);
|
|
1623
1907
|
const floor = from && compareSemver(from, options.target) > 0 ? `installed ${from} is above candidate ${options.target} (monotonic)` : highWater && compareSemver(highWater, options.target) > 0 ? `journal high-water ${highWater} is above candidate ${options.target} (monotonic)` : null;
|
|
1624
1908
|
if (options.dryRun) {
|
|
1625
1909
|
const action = floor ? `hold ${from ?? "the install"} and skip the install (${floor})` : before.includes(exact) && from === options.target ? "keep exact pin" : `install ${exact}`;
|
|
@@ -1811,7 +2095,7 @@ function hermesHomes(env = process.env) {
|
|
|
1811
2095
|
var VERDICT_RANK = { fail: 3, defer: 2, ok: 1, skip: 0 };
|
|
1812
2096
|
function hermesArm(options) {
|
|
1813
2097
|
const env = options.env ?? process.env;
|
|
1814
|
-
if (!hermesHostPresent(env, options.hostCommandRunner)) {
|
|
2098
|
+
if (!(options.hostPresent ?? hermesHostPresent(env, options.hostCommandRunner))) {
|
|
1815
2099
|
return {
|
|
1816
2100
|
surface: "hermes",
|
|
1817
2101
|
from: null,
|
|
@@ -1854,7 +2138,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
1854
2138
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1855
2139
|
}
|
|
1856
2140
|
if (installed && compareSemver(installed, options.target) > 0) return result(installed, installed, "skip", `installed ${installed} is above candidate ${options.target} (monotonic)`);
|
|
1857
|
-
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes") : null;
|
|
2141
|
+
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes", options.journalEvents) : null;
|
|
1858
2142
|
if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
|
|
1859
2143
|
const hostRunner = options.hostCommandRunner ?? runHostCommand;
|
|
1860
2144
|
if (installed === options.target && treeHealthy2(live)) {
|
|
@@ -1956,6 +2240,10 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
1956
2240
|
// src/reap.ts
|
|
1957
2241
|
var import_node_fs14 = require("node:fs");
|
|
1958
2242
|
var import_node_path11 = require("node:path");
|
|
2243
|
+
var import_node_perf_hooks = require("node:perf_hooks");
|
|
2244
|
+
function elapsedMs(startedAt) {
|
|
2245
|
+
return Math.round((import_node_perf_hooks.performance.now() - startedAt) * 10) / 10;
|
|
2246
|
+
}
|
|
1959
2247
|
var KEEP_GENERATIONS2 = 2;
|
|
1960
2248
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
1961
2249
|
function listDirs(path) {
|
|
@@ -2052,10 +2340,11 @@ function reap(options) {
|
|
|
2052
2340
|
const { paths, dryRun } = options;
|
|
2053
2341
|
const env = options.env ?? process.env;
|
|
2054
2342
|
const guarded = (category, run) => {
|
|
2343
|
+
const startedAt = import_node_perf_hooks.performance.now();
|
|
2055
2344
|
try {
|
|
2056
|
-
return run();
|
|
2345
|
+
return { ...run(), durationMs: elapsedMs(startedAt) };
|
|
2057
2346
|
} catch (error) {
|
|
2058
|
-
return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}
|
|
2347
|
+
return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}`, durationMs: elapsedMs(startedAt) };
|
|
2059
2348
|
}
|
|
2060
2349
|
};
|
|
2061
2350
|
const atomicWritePaths = [claudeMarketplacesPath(env)];
|
|
@@ -2072,7 +2361,7 @@ function shouldHandOff(self, options) {
|
|
|
2072
2361
|
if (!options.dist) return false;
|
|
2073
2362
|
return self.verdict === "ok" && Boolean(self.from) && self.from !== self.to;
|
|
2074
2363
|
}
|
|
2075
|
-
function reconcile(options) {
|
|
2364
|
+
async function reconcile(options) {
|
|
2076
2365
|
const env = options.env ?? process.env;
|
|
2077
2366
|
const shipped = options.shippedTarget?.trim() || null;
|
|
2078
2367
|
const say = (line) => {
|
|
@@ -2088,9 +2377,9 @@ function reconcile(options) {
|
|
|
2088
2377
|
if (!appendJournal(paths.journalPath, { ts: (/* @__PURE__ */ new Date()).toISOString(), run, ...stamped })) summary.journalOk = false;
|
|
2089
2378
|
};
|
|
2090
2379
|
const summary = { run, target: null, gate: null, managedSurfaces: null, atomicity: null, cli: null, updater: null, plugins: [], reap: [], surfaces: [], journalOk: true, reexec: null, exit: 0 };
|
|
2091
|
-
const recordArm = (arm) => {
|
|
2380
|
+
const recordArm = (arm, durationMs) => {
|
|
2092
2381
|
if (arm.verdict === "fail") arm.repeatFailure = journalSurfaceRepeatedFail(paths.journalPath, arm.surface, run);
|
|
2093
|
-
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat });
|
|
2382
|
+
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat, durationMs });
|
|
2094
2383
|
options.onArm?.(arm);
|
|
2095
2384
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2096
2385
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
@@ -2099,7 +2388,8 @@ function reconcile(options) {
|
|
|
2099
2388
|
try {
|
|
2100
2389
|
say("acquiring single-writer lease");
|
|
2101
2390
|
const leaseWaitMs = Number(env.MMI_UPDATER_LEASE_WAIT_MS) || void 0;
|
|
2102
|
-
return withLease(paths.leasePath, () => {
|
|
2391
|
+
return await withLease(paths.leasePath, async () => {
|
|
2392
|
+
const journalEvents = parseJournal(paths.journalPath);
|
|
2103
2393
|
if (!options.dryRun) {
|
|
2104
2394
|
const pendingBefore = readPending(paths);
|
|
2105
2395
|
if (pendingBefore.length) {
|
|
@@ -2108,7 +2398,7 @@ function reconcile(options) {
|
|
|
2108
2398
|
}
|
|
2109
2399
|
}
|
|
2110
2400
|
summary.reap = reap({ paths, dryRun: options.dryRun, env });
|
|
2111
|
-
for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail });
|
|
2401
|
+
for (const row of summary.reap) journal({ kind: "reap", surface: row.category, verdict: row.verdict, count: row.reaped, detail: row.detail, durationMs: row.durationMs });
|
|
2112
2402
|
for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
|
|
2113
2403
|
if (shipped) {
|
|
2114
2404
|
say(`shipped target v${shipped} \u2014 the payload is the release; no npm poll, no gate`);
|
|
@@ -2118,41 +2408,48 @@ function reconcile(options) {
|
|
|
2118
2408
|
journal({ kind: "target", to: shipped, verdict: "ok", detail: "shipped payload" });
|
|
2119
2409
|
} else {
|
|
2120
2410
|
say("polling npm for @mutmutco/cli versions");
|
|
2411
|
+
const pollStartedAt = import_node_perf_hooks2.performance.now();
|
|
2121
2412
|
const versions = npmVersions("@mutmutco/cli", env).sort((a, b) => compareSemver(b, a));
|
|
2413
|
+
const pollMs = elapsedMs(pollStartedAt);
|
|
2122
2414
|
if (!versions.length) {
|
|
2123
|
-
journal({ kind: "poll", verdict: "fail", detail: "@mutmutco/cli has no npm versions \u2014 cannot discover a candidate" });
|
|
2415
|
+
journal({ kind: "poll", verdict: "fail", durationMs: pollMs, detail: "@mutmutco/cli has no npm versions \u2014 cannot discover a candidate" });
|
|
2124
2416
|
summary.exit = 1;
|
|
2125
2417
|
summary.detail = "npm poll returned no versions for @mutmutco/cli";
|
|
2126
2418
|
return summary;
|
|
2127
2419
|
}
|
|
2420
|
+
journal({ kind: "poll", verdict: "ok", count: versions.length, durationMs: pollMs, detail: `${versions.length} versions; newest ${versions[0]}` });
|
|
2128
2421
|
say("fetching release BOM gate cache");
|
|
2422
|
+
const fetchStartedAt = import_node_perf_hooks2.performance.now();
|
|
2129
2423
|
ensureBareCache(paths.gitCache, repoUrl);
|
|
2130
2424
|
fetchIntoCache(paths.gitCache, repoUrl, "refs/heads/main:refs/heads/main");
|
|
2425
|
+
journal({ kind: "gate", surface: "cache", verdict: "ok", durationMs: elapsedMs(fetchStartedAt), detail: "release BOM gate cache: refs/heads/main fetched" });
|
|
2426
|
+
const walkStartedAt = import_node_perf_hooks2.performance.now();
|
|
2131
2427
|
for (const candidate of versions.slice(0, bound)) {
|
|
2132
2428
|
say(`gating candidate v${candidate}`);
|
|
2429
|
+
const candidateStartedAt = import_node_perf_hooks2.performance.now();
|
|
2133
2430
|
const gate = gateCandidate(paths.gitCache, repoUrl, candidate);
|
|
2134
2431
|
if (!gate.ok) {
|
|
2135
2432
|
say(`candidate v${candidate} rejected (${gate.reason ?? "gate"})`);
|
|
2136
|
-
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", detail: gate.reason ?? "gate rejected" });
|
|
2433
|
+
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", durationMs: elapsedMs(candidateStartedAt), detail: gate.reason ?? "gate rejected" });
|
|
2137
2434
|
continue;
|
|
2138
2435
|
}
|
|
2139
|
-
const atomicity = checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
|
|
2140
|
-
distIntegrity: (spec) =>
|
|
2436
|
+
const atomicity = await checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
|
|
2437
|
+
distIntegrity: (spec) => npmDistIntegrityAsync(spec, env)
|
|
2141
2438
|
});
|
|
2142
2439
|
if (!atomicity.ok) {
|
|
2143
|
-
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", detail: `atomicity: ${atomicity.rows.filter((row) => !row.ok).map((row) => row.detail).join("; ")}` });
|
|
2440
|
+
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", durationMs: elapsedMs(candidateStartedAt), detail: `atomicity: ${atomicity.rows.filter((row) => !row.ok).map((row) => row.detail).join("; ")}` });
|
|
2144
2441
|
continue;
|
|
2145
2442
|
}
|
|
2146
2443
|
summary.target = candidate;
|
|
2147
2444
|
summary.managedSurfaces = Array.isArray(gate.bom?.managedSurfaces) ? gate.bom.managedSurfaces.map(String) : null;
|
|
2148
2445
|
summary.gate = `tag v${candidate} reachable from main, BOM stamp coherent`;
|
|
2149
2446
|
summary.atomicity = atomicity;
|
|
2150
|
-
journal({ kind: "target", to: candidate, verdict: "ok", detail: summary.gate });
|
|
2447
|
+
journal({ kind: "target", to: candidate, verdict: "ok", durationMs: elapsedMs(candidateStartedAt), detail: summary.gate });
|
|
2151
2448
|
break;
|
|
2152
2449
|
}
|
|
2153
2450
|
if (!summary.target) {
|
|
2154
2451
|
say("no green target \u2014 deferred");
|
|
2155
|
-
journal({ kind: "target", verdict: "defer", detail: `no green candidate in the newest ${Math.min(bound, versions.length)} versions \u2014 registry not ready for this fleet stamp yet` });
|
|
2452
|
+
journal({ kind: "target", verdict: "defer", durationMs: elapsedMs(walkStartedAt), detail: `no green candidate in the newest ${Math.min(bound, versions.length)} versions \u2014 registry not ready for this fleet stamp yet` });
|
|
2156
2453
|
for (const probe of enumerateSurfaces(env)) {
|
|
2157
2454
|
summary.surfaces.push({ id: probe.id, present: probe.present, action: probe.present ? "deferred-no-target" : "absent-skip" });
|
|
2158
2455
|
journal({ kind: "surface", surface: probe.id, present: probe.present, verdict: "skip", detail: probe.present ? "present; no green target yet" : `absent (${probe.marker}) \u2014 skipped` });
|
|
@@ -2163,6 +2460,7 @@ function reconcile(options) {
|
|
|
2163
2460
|
say(`target v${summary.target} (${summary.gate})`);
|
|
2164
2461
|
options.onTargetResolved?.(summary.target, summary.gate);
|
|
2165
2462
|
say("updater: self-bootstrap");
|
|
2463
|
+
const selfStartedAt = import_node_perf_hooks2.performance.now();
|
|
2166
2464
|
const self = npmGlobalArm({
|
|
2167
2465
|
surface: "updater",
|
|
2168
2466
|
packageName: "@mutmutco/hub",
|
|
@@ -2170,10 +2468,11 @@ function reconcile(options) {
|
|
|
2170
2468
|
target: summary.target,
|
|
2171
2469
|
dryRun: options.dryRun,
|
|
2172
2470
|
paths,
|
|
2173
|
-
env
|
|
2471
|
+
env,
|
|
2472
|
+
journalEvents
|
|
2174
2473
|
});
|
|
2175
2474
|
summary.updater = self;
|
|
2176
|
-
recordArm(self);
|
|
2475
|
+
recordArm(self, elapsedMs(selfStartedAt));
|
|
2177
2476
|
say(`updater: ${self.verdict}${self.detail ? " \u2014 " + self.detail : ""}`);
|
|
2178
2477
|
const handoffDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2179
2478
|
if (shouldHandOff(self, { dryRun: options.dryRun, dist: handoffDist, guard: env.MMI_UPDATER_REEXEC })) {
|
|
@@ -2183,6 +2482,7 @@ function reconcile(options) {
|
|
|
2183
2482
|
return summary;
|
|
2184
2483
|
}
|
|
2185
2484
|
say(`cli: converging to v${summary.target}`);
|
|
2485
|
+
const cliStartedAt = import_node_perf_hooks2.performance.now();
|
|
2186
2486
|
const cli = npmGlobalArm({
|
|
2187
2487
|
surface: "cli",
|
|
2188
2488
|
packageName: "@mutmutco/cli",
|
|
@@ -2190,10 +2490,11 @@ function reconcile(options) {
|
|
|
2190
2490
|
target: summary.target,
|
|
2191
2491
|
dryRun: options.dryRun,
|
|
2192
2492
|
paths,
|
|
2193
|
-
env
|
|
2493
|
+
env,
|
|
2494
|
+
journalEvents
|
|
2194
2495
|
});
|
|
2195
2496
|
summary.cli = cli;
|
|
2196
|
-
recordArm(cli);
|
|
2497
|
+
recordArm(cli, elapsedMs(cliStartedAt));
|
|
2197
2498
|
say(`cli: ${cli.verdict}${cli.detail ? " \u2014 " + cli.detail : ""}`);
|
|
2198
2499
|
for (const probe of enumerateSurfaces(env)) {
|
|
2199
2500
|
if (!probe.present) {
|
|
@@ -2208,11 +2509,12 @@ function reconcile(options) {
|
|
|
2208
2509
|
continue;
|
|
2209
2510
|
}
|
|
2210
2511
|
say(`${probe.id}: converging to v${summary.target}`);
|
|
2211
|
-
const
|
|
2512
|
+
const armStartedAt = import_node_perf_hooks2.performance.now();
|
|
2513
|
+
const arm = probe.id === "claude" ? claudeArm({ target: summary.target, dryRun: options.dryRun, paths, env, repoUrl, journalEvents }) : probe.id === "codex" ? codexArm({ target: summary.target, dryRun: options.dryRun, env, repoUrl }) : probe.id === "cursor" ? cursorArm({ target: summary.target, dryRun: options.dryRun, paths, env, journalEvents }) : probe.id === "jervcode" ? jervcodeArm({ target: summary.target, dryRun: options.dryRun, paths, env, journalEvents }) : probe.id === "hermes" ? hermesArm({ target: summary.target, dryRun: options.dryRun, paths, env, hostPresent: probe.present, journalEvents }) : null;
|
|
2212
2514
|
if (arm) {
|
|
2213
2515
|
summary.plugins.push(arm);
|
|
2214
2516
|
summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
|
|
2215
|
-
recordArm(arm);
|
|
2517
|
+
recordArm(arm, elapsedMs(armStartedAt));
|
|
2216
2518
|
say(`${probe.id}: ${arm.verdict}${arm.detail ? " \u2014 " + arm.detail : ""}`);
|
|
2217
2519
|
} else {
|
|
2218
2520
|
summary.surfaces.push({ id: probe.id, present: true, action: "arm-pending" });
|
|
@@ -2924,28 +3226,50 @@ var ARM_TITLE = {
|
|
|
2924
3226
|
hermes: "Arming Hermes",
|
|
2925
3227
|
jervcode: "Arming JervCode"
|
|
2926
3228
|
};
|
|
2927
|
-
var
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
3229
|
+
var FACE_PRODUCT = "mmi-hub";
|
|
3230
|
+
function updaterFace(options = {}) {
|
|
3231
|
+
return createFace({ product: FACE_PRODUCT, color: options.color ?? false, columns: options.width });
|
|
3232
|
+
}
|
|
3233
|
+
var STATE_WORD = {
|
|
3234
|
+
updated: "updated",
|
|
3235
|
+
current: "current",
|
|
3236
|
+
keep: "kept",
|
|
3237
|
+
retry: "will retry",
|
|
3238
|
+
failed: "failed",
|
|
3239
|
+
pending: "pending"
|
|
2934
3240
|
};
|
|
2935
|
-
function
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
const kind = classify(arm)
|
|
2940
|
-
const
|
|
2941
|
-
const
|
|
2942
|
-
const
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3241
|
+
function stepKindFor(kind) {
|
|
3242
|
+
return kind === "failed" ? "fail" : kind === "retry" ? "note" : "ok";
|
|
3243
|
+
}
|
|
3244
|
+
function checklistLine(arm, seconds, face, options = {}) {
|
|
3245
|
+
const { kind } = classify(arm);
|
|
3246
|
+
const state = options.dryRun && kind === "updated" ? "would update" : STATE_WORD[kind];
|
|
3247
|
+
const title = `${ARM_TITLE[arm.surface] ?? `Arming ${arm.surface}`} \u2014 ${state}`;
|
|
3248
|
+
const truthful = kind === "failed" || kind === "retry" ? null : seconds;
|
|
3249
|
+
return face.step(title, truthful, stepKindFor(kind));
|
|
3250
|
+
}
|
|
3251
|
+
function receiptText(summary, autoupdate, options = {}) {
|
|
3252
|
+
const dry = options.dryRun ?? false;
|
|
3253
|
+
const counts = reportCounts(summary);
|
|
3254
|
+
const health = "Check health any time: mmi-hub status";
|
|
3255
|
+
if (!summary.target) {
|
|
3256
|
+
return [`${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)`, "", health];
|
|
3257
|
+
}
|
|
3258
|
+
const ready = counts.failed === 0;
|
|
3259
|
+
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25C7" : "\u2714";
|
|
3260
|
+
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`;
|
|
3261
|
+
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3262
|
+
return [`${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}`, "", health];
|
|
3263
|
+
}
|
|
3264
|
+
function attentionRows(summary) {
|
|
3265
|
+
return collectRows(summary).filter((row) => row.kind === "failed" || row.kind === "retry" || row.kind === "pending").map((row) => ({
|
|
3266
|
+
surface: row.name,
|
|
3267
|
+
state: statusText(row.kind, false),
|
|
3268
|
+
mark: row.kind === "failed" ? "fail" : "note",
|
|
3269
|
+
note: row.kind === "failed" ? failConsequence(row) : row.kind === "retry" ? retryHint(row.detail) : "arm not landed"
|
|
3270
|
+
}));
|
|
3271
|
+
}
|
|
3272
|
+
function wrapWords2(text, available) {
|
|
2949
3273
|
if (available < 1) return [text];
|
|
2950
3274
|
const lines = [];
|
|
2951
3275
|
let current = "";
|
|
@@ -2966,7 +3290,7 @@ function noteLines(prefix, note, width) {
|
|
|
2966
3290
|
const available = width - used - 1;
|
|
2967
3291
|
const indent = available >= MIN_NOTE_SPACE ? " ".repeat(used + 1) : " ".repeat(Math.min(used + 1, Math.max(2, width - MIN_NOTE_SPACE)));
|
|
2968
3292
|
const room = available >= MIN_NOTE_SPACE ? available : width - indent.length;
|
|
2969
|
-
const parts =
|
|
3293
|
+
const parts = wrapWords2(note, room);
|
|
2970
3294
|
const first = available >= MIN_NOTE_SPACE ? prefix + " " + parts[0] : prefix.replace(/\s+$/, "");
|
|
2971
3295
|
const lines = [first];
|
|
2972
3296
|
for (const part of parts.slice(available >= MIN_NOTE_SPACE ? 1 : 0)) lines.push(indent + part);
|
|
@@ -2989,7 +3313,7 @@ function collectRows(summary) {
|
|
|
2989
3313
|
}
|
|
2990
3314
|
function headline(detail, width) {
|
|
2991
3315
|
const first = (detail ?? "").split(/(?<=[.;:])\s/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
2992
|
-
return
|
|
3316
|
+
return wrapWords2(first || "blocked", Math.max(10, width - 4))[0];
|
|
2993
3317
|
}
|
|
2994
3318
|
function updateHeader(target, dryRun = false, options) {
|
|
2995
3319
|
return brandLine(
|
|
@@ -3091,124 +3415,6 @@ function formatReconcileReport(summary, options = {}) {
|
|
|
3091
3415
|
return out.join("\n") + "\n";
|
|
3092
3416
|
}
|
|
3093
3417
|
|
|
3094
|
-
// src/face.ts
|
|
3095
|
-
var DEFAULT_WIDTH2 = 100;
|
|
3096
|
-
function welcome(color, output = process.stdout) {
|
|
3097
|
-
const mark = color ? MM_GREEN + "\u25C6" + RESET : "\u25C6";
|
|
3098
|
-
output.write(`${mark} ${accent("mmi-hub", color)} \u2014 Mutatis Mutandis
|
|
3099
|
-
`);
|
|
3100
|
-
output.write("\u2502\n");
|
|
3101
|
-
output.write("\u2502 Welcome back. Checking your surfaces\u2026\n");
|
|
3102
|
-
output.write("\u2502\n");
|
|
3103
|
-
}
|
|
3104
|
-
var TONES = { "\u2714": MM_GREEN, "\u2716": RED, "\u25CF": YELLOW };
|
|
3105
|
-
function paintGlyph(line, color) {
|
|
3106
|
-
if (!color) return line;
|
|
3107
|
-
const glyph = line.trimStart()[0];
|
|
3108
|
-
const tone = TONES[glyph];
|
|
3109
|
-
if (!tone) return line;
|
|
3110
|
-
const at = line.indexOf(glyph);
|
|
3111
|
-
return line.slice(0, at) + tone + glyph + RESET + line.slice(at + 1);
|
|
3112
|
-
}
|
|
3113
|
-
function receipt(text, options = {}) {
|
|
3114
|
-
const color = options.color ?? false;
|
|
3115
|
-
const width = Math.max(40, options.width ?? DEFAULT_WIDTH2);
|
|
3116
|
-
const output = options.output ?? process.stdout;
|
|
3117
|
-
const edges = 2 * 2 + 2;
|
|
3118
|
-
const available = width - edges;
|
|
3119
|
-
const lines = [];
|
|
3120
|
-
for (const paragraph of text.split("\n")) {
|
|
3121
|
-
if (!paragraph.trim()) lines.push(null);
|
|
3122
|
-
else if (paragraph.length <= available) lines.push(paragraph);
|
|
3123
|
-
else for (const line of wrapWords(paragraph, available)) lines.push(line);
|
|
3124
|
-
}
|
|
3125
|
-
const inner = Math.min(available, Math.max(20, ...lines.map((line) => line?.length ?? 0)));
|
|
3126
|
-
const rule = "\u2500".repeat(inner + 2 * 2);
|
|
3127
|
-
const frame = (text2) => color ? ACCENT + text2 + RESET : text2;
|
|
3128
|
-
const blank = `${frame("\u2502")}${" ".repeat(inner + 2 * 2)}${frame("\u2502")}`;
|
|
3129
|
-
output.write(frame(`\u256D${rule}\u256E`) + "\n");
|
|
3130
|
-
for (const line of lines) {
|
|
3131
|
-
output.write(line === null ? blank + "\n" : `${frame("\u2502")} ${paintGlyph(line.padEnd(inner), color)} ${frame("\u2502")}
|
|
3132
|
-
`);
|
|
3133
|
-
}
|
|
3134
|
-
output.write(frame(`\u2570${rule}\u256F`) + "\n");
|
|
3135
|
-
}
|
|
3136
|
-
function signOff(color, output = process.stdout) {
|
|
3137
|
-
output.write(`\u2570 ${accent("mmi-hub \xB7 Mutatis Mutandis", color)}
|
|
3138
|
-
`);
|
|
3139
|
-
}
|
|
3140
|
-
function receiptText(summary, autoupdate, options = {}) {
|
|
3141
|
-
const dry = options.dryRun ?? false;
|
|
3142
|
-
const counts = reportCounts(summary);
|
|
3143
|
-
const health = "Check health any time: mmi-hub status";
|
|
3144
|
-
if (!summary.target) {
|
|
3145
|
-
return `${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)
|
|
3146
|
-
${health}`;
|
|
3147
|
-
}
|
|
3148
|
-
const ready = counts.failed === 0;
|
|
3149
|
-
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25C7" : "\u2714";
|
|
3150
|
-
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`;
|
|
3151
|
-
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3152
|
-
return `${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}
|
|
3153
|
-
${health}`;
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
|
-
// src/spinner.ts
|
|
3157
|
-
var import_node_worker_threads = require("node:worker_threads");
|
|
3158
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3159
|
-
var WORKER_SRC = `
|
|
3160
|
-
const { parentPort, workerData } = require('node:worker_threads');
|
|
3161
|
-
const frames = ${JSON.stringify(FRAMES)};
|
|
3162
|
-
const control = new Int32Array(workerData.sab);
|
|
3163
|
-
let frame = 0, label = '', timer = null;
|
|
3164
|
-
function tick() {
|
|
3165
|
-
if (Atomics.load(control, 0) === 1) return; // paused by the main thread
|
|
3166
|
-
if (!label) return;
|
|
3167
|
-
process.stderr.write('\\r' + frames[frame++ % frames.length] + ' ' + label + '\\x1b[K');
|
|
3168
|
-
}
|
|
3169
|
-
parentPort.on('message', (message) => {
|
|
3170
|
-
if (message && message.type === 'label') { label = message.label; if (!timer) timer = setInterval(tick, 80); }
|
|
3171
|
-
else if (message === 'stop') { if (timer) clearInterval(timer); process.stderr.write('\\r\\x1b[K'); process.exit(0); }
|
|
3172
|
-
});
|
|
3173
|
-
`;
|
|
3174
|
-
var NOOP = { label() {
|
|
3175
|
-
}, persist() {
|
|
3176
|
-
}, stop() {
|
|
3177
|
-
}, active: false };
|
|
3178
|
-
function startSpinner(enabled) {
|
|
3179
|
-
if (!enabled) return NOOP;
|
|
3180
|
-
let worker;
|
|
3181
|
-
let control;
|
|
3182
|
-
try {
|
|
3183
|
-
const sab = new SharedArrayBuffer(4);
|
|
3184
|
-
control = new Int32Array(sab);
|
|
3185
|
-
worker = new import_node_worker_threads.Worker(WORKER_SRC, { eval: true, workerData: { sab } });
|
|
3186
|
-
worker.unref();
|
|
3187
|
-
worker.on("error", () => {
|
|
3188
|
-
});
|
|
3189
|
-
} catch {
|
|
3190
|
-
return NOOP;
|
|
3191
|
-
}
|
|
3192
|
-
let stopped = false;
|
|
3193
|
-
return {
|
|
3194
|
-
active: true,
|
|
3195
|
-
label: (text) => {
|
|
3196
|
-
if (!stopped) worker.postMessage({ type: "label", label: text });
|
|
3197
|
-
},
|
|
3198
|
-
persist: (line) => {
|
|
3199
|
-
Atomics.store(control, 0, 1);
|
|
3200
|
-
process.stderr.write("\r\x1B[K");
|
|
3201
|
-
process.stdout.write(line + "\n");
|
|
3202
|
-
Atomics.store(control, 0, 0);
|
|
3203
|
-
},
|
|
3204
|
-
stop: () => {
|
|
3205
|
-
if (stopped) return;
|
|
3206
|
-
stopped = true;
|
|
3207
|
-
worker.postMessage("stop");
|
|
3208
|
-
}
|
|
3209
|
-
};
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
3418
|
// src/stamp.ts
|
|
3213
3419
|
var import_node_fs17 = require("node:fs");
|
|
3214
3420
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
@@ -3306,17 +3512,33 @@ function printReconcile(summary, json, dryRun = false) {
|
|
|
3306
3512
|
function faceEnabled(args, json) {
|
|
3307
3513
|
return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
3308
3514
|
}
|
|
3309
|
-
function openFace(args, json) {
|
|
3310
|
-
|
|
3311
|
-
|
|
3515
|
+
function openFace(args, json, dryRun) {
|
|
3516
|
+
if (!faceEnabled(args, json)) return null;
|
|
3517
|
+
const face = updaterFace(humanOptions(dryRun));
|
|
3518
|
+
if (!process.env.MMI_UPDATER_REEXEC) for (const line of face.welcome()) process.stdout.write(line + "\n");
|
|
3312
3519
|
return face;
|
|
3313
3520
|
}
|
|
3314
|
-
function finishFace(summary, autoupdate, dryRun) {
|
|
3521
|
+
function finishFace(face, summary, autoupdate, dryRun) {
|
|
3315
3522
|
const options = humanOptions(dryRun);
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3523
|
+
const write = (line) => {
|
|
3524
|
+
process.stdout.write(line + "\n");
|
|
3525
|
+
};
|
|
3526
|
+
if (!summary.target) {
|
|
3527
|
+
process.stdout.write(formatReconcileReport(summary, { ...options, tty: false }));
|
|
3528
|
+
} else {
|
|
3529
|
+
for (const line of renderRows(face, attentionRows(summary))) write(line);
|
|
3530
|
+
}
|
|
3531
|
+
const body = receiptText(summary, autoupdate, { dryRun });
|
|
3532
|
+
const counts = reportCounts(summary);
|
|
3533
|
+
if (counts.failed > 0) {
|
|
3534
|
+
body.push("", "Fix: mmi-cli doctor, then mmi-hub update", `Log: ${options.journalPath}`);
|
|
3535
|
+
}
|
|
3536
|
+
const self = summary.updater;
|
|
3537
|
+
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3538
|
+
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}`);
|
|
3539
|
+
}
|
|
3540
|
+
for (const line of face.receipt(body)) write(line);
|
|
3541
|
+
write(face.signOff());
|
|
3320
3542
|
}
|
|
3321
3543
|
function handOff(reexec, args) {
|
|
3322
3544
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
@@ -3353,7 +3575,7 @@ function shippedTargetFrom(args) {
|
|
|
3353
3575
|
const value = (args[index + 1] ?? "").trim();
|
|
3354
3576
|
return value && !value.startsWith("--") ? value : void 0;
|
|
3355
3577
|
}
|
|
3356
|
-
function runUpdate(args, json, face =
|
|
3578
|
+
async function runUpdate(args, json, face = null, shippedTarget) {
|
|
3357
3579
|
const dryRun = args.includes("--dry-run");
|
|
3358
3580
|
const verbose = args.includes("--verbose");
|
|
3359
3581
|
const narrate = (line) => {
|
|
@@ -3362,20 +3584,35 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3362
3584
|
};
|
|
3363
3585
|
const progressive = !json && !verbose && Boolean(process.stderr.isTTY);
|
|
3364
3586
|
if (!progressive) {
|
|
3365
|
-
return
|
|
3587
|
+
return reconcile({ dryRun, narrate: verbose ? narrate : void 0, shippedTarget }).then((summary2) => ({ summary: summary2, rendered: false }));
|
|
3366
3588
|
}
|
|
3367
3589
|
const reportOptions = humanOptions(dryRun);
|
|
3368
|
-
const spinner =
|
|
3590
|
+
const spinner = createSpinner(face ?? updaterFace(reportOptions), { animate: true, stream: process.stderr });
|
|
3591
|
+
let spinning = false;
|
|
3592
|
+
const phase = (line) => {
|
|
3593
|
+
if (spinning) spinner.say(line);
|
|
3594
|
+
else {
|
|
3595
|
+
spinner.start(line);
|
|
3596
|
+
spinning = true;
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
const persist = (line) => {
|
|
3600
|
+
if (spinning) {
|
|
3601
|
+
spinner.stop();
|
|
3602
|
+
spinning = false;
|
|
3603
|
+
}
|
|
3604
|
+
process.stdout.write(line + "\n");
|
|
3605
|
+
};
|
|
3369
3606
|
let headerEmitted = false;
|
|
3370
3607
|
let summary;
|
|
3371
|
-
const { emit, stream } = heldOutput(
|
|
3608
|
+
const { emit, stream } = heldOutput(persist);
|
|
3372
3609
|
let arms = 0;
|
|
3373
3610
|
let mark = Date.now();
|
|
3374
3611
|
try {
|
|
3375
|
-
summary = reconcile({
|
|
3612
|
+
summary = await reconcile({
|
|
3376
3613
|
dryRun,
|
|
3377
3614
|
shippedTarget,
|
|
3378
|
-
onPhase: (line) =>
|
|
3615
|
+
onPhase: (line) => phase(line),
|
|
3379
3616
|
onTargetResolved: face ? void 0 : (target) => {
|
|
3380
3617
|
emit(updateHeader(target, dryRun, reportOptions));
|
|
3381
3618
|
emit("");
|
|
@@ -3385,7 +3622,7 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3385
3622
|
const seconds = (Date.now() - mark) / 1e3;
|
|
3386
3623
|
mark = Date.now();
|
|
3387
3624
|
if (arms++) stream();
|
|
3388
|
-
emit(face ? checklistLine(arm, seconds, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3625
|
+
emit(face ? checklistLine(arm, seconds, face, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3389
3626
|
}
|
|
3390
3627
|
});
|
|
3391
3628
|
} finally {
|
|
@@ -3398,7 +3635,7 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3398
3635
|
else process.stdout.write(formatReconcileReport(summary, reportOptions));
|
|
3399
3636
|
return { summary, rendered: true };
|
|
3400
3637
|
}
|
|
3401
|
-
function main(args = process.argv.slice(2)) {
|
|
3638
|
+
async function main(args = process.argv.slice(2)) {
|
|
3402
3639
|
if (args.includes("--version")) {
|
|
3403
3640
|
process.stdout.write(ownVersion() + "\n");
|
|
3404
3641
|
return 0;
|
|
@@ -3415,18 +3652,19 @@ function main(args = process.argv.slice(2)) {
|
|
|
3415
3652
|
return status.failures.length ? 1 : 0;
|
|
3416
3653
|
}
|
|
3417
3654
|
if (command === "update") {
|
|
3418
|
-
const
|
|
3419
|
-
const
|
|
3655
|
+
const dryRun = args.includes("--dry-run");
|
|
3656
|
+
const face = openFace(args, json, dryRun);
|
|
3657
|
+
const { summary, rendered } = await runUpdate(args, json, face);
|
|
3420
3658
|
if (summary.reexec) return handOff(summary.reexec, args);
|
|
3421
|
-
if (face) finishFace(summary, null,
|
|
3422
|
-
else if (!rendered) printReconcile(summary, json,
|
|
3659
|
+
if (face) finishFace(face, summary, null, dryRun);
|
|
3660
|
+
else if (!rendered) printReconcile(summary, json, dryRun);
|
|
3423
3661
|
return summary.exit;
|
|
3424
3662
|
}
|
|
3425
3663
|
if (command === "install") {
|
|
3426
|
-
const face = openFace(args, json);
|
|
3427
3664
|
const dryRun = args.includes("--dry-run");
|
|
3665
|
+
const face = openFace(args, json, dryRun);
|
|
3428
3666
|
const shippedTarget = shippedTargetFrom(args);
|
|
3429
|
-
const { summary, rendered } = runUpdate(args, json, face, shippedTarget);
|
|
3667
|
+
const { summary, rendered } = await runUpdate(args, json, face, shippedTarget);
|
|
3430
3668
|
if (summary.reexec) {
|
|
3431
3669
|
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install" && arg !== "--shipped" && arg !== shippedTarget)]);
|
|
3432
3670
|
const scheduled = installTask();
|
|
@@ -3443,7 +3681,7 @@ function main(args = process.argv.slice(2)) {
|
|
|
3443
3681
|
if (json) {
|
|
3444
3682
|
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
|
|
3445
3683
|
} else if (face) {
|
|
3446
|
-
finishFace(summary, schedule, dryRun);
|
|
3684
|
+
finishFace(face, summary, schedule, dryRun);
|
|
3447
3685
|
} else {
|
|
3448
3686
|
if (!rendered) printReconcile(summary, false, dryRun);
|
|
3449
3687
|
process.stdout.write(schedule.ok ? ` Auto-update on \u2014 hourly task "MMI Fleet Updater".
|
|
@@ -3468,17 +3706,24 @@ function main(args = process.argv.slice(2)) {
|
|
|
3468
3706
|
return 1;
|
|
3469
3707
|
}
|
|
3470
3708
|
if (typeof require !== "undefined" && require.main === module) {
|
|
3471
|
-
process.exit(
|
|
3709
|
+
main().then((code) => process.exit(code)).catch((error) => {
|
|
3710
|
+
process.stderr.write(`mmi-hub: ${error?.stack ?? error}
|
|
3711
|
+
`);
|
|
3712
|
+
process.exit(1);
|
|
3713
|
+
});
|
|
3472
3714
|
}
|
|
3473
3715
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3474
3716
|
0 && (module.exports = {
|
|
3475
3717
|
ACCENT,
|
|
3718
|
+
FACE_PRODUCT,
|
|
3476
3719
|
MM_GREEN,
|
|
3477
3720
|
RESET,
|
|
3478
3721
|
accent,
|
|
3479
3722
|
acquireSharedDocLock,
|
|
3723
|
+
attentionRows,
|
|
3480
3724
|
brandLine,
|
|
3481
3725
|
buildWatcherCommand,
|
|
3726
|
+
checkAtomicity,
|
|
3482
3727
|
checklistLine,
|
|
3483
3728
|
claudeArm,
|
|
3484
3729
|
claudeHostEvidence,
|
|
@@ -3518,7 +3763,6 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3518
3763
|
progressiveRow,
|
|
3519
3764
|
readLaunchTarget,
|
|
3520
3765
|
readPending,
|
|
3521
|
-
receipt,
|
|
3522
3766
|
receiptText,
|
|
3523
3767
|
reconcile,
|
|
3524
3768
|
reconcileCursorUserHooks,
|
|
@@ -3533,15 +3777,13 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3533
3777
|
sharedDocLockTimeoutReceipt,
|
|
3534
3778
|
shippedTargetFrom,
|
|
3535
3779
|
shouldHandOff,
|
|
3536
|
-
signOff,
|
|
3537
|
-
startSpinner,
|
|
3538
3780
|
surfaceArmPlan,
|
|
3539
3781
|
systemdServiceUnit,
|
|
3540
3782
|
systemdTimerUnit,
|
|
3541
3783
|
taskXml,
|
|
3542
3784
|
updateHeader,
|
|
3785
|
+
updaterFace,
|
|
3543
3786
|
watcherLockPath,
|
|
3544
|
-
welcome,
|
|
3545
3787
|
withSharedDocLock,
|
|
3546
3788
|
wrapWords,
|
|
3547
3789
|
writeLaunchTarget,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/hub",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.8",
|
|
4
4
|
"description": "Install and maintain the MMI CLI and every present host surface from one release-gated Hub command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"test": "node build.mjs && node --test test/*.test.cjs",
|
|
26
26
|
"typecheck": "tsc --noEmit"
|
|
27
27
|
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@mutmutco/installer-face": "^0.2.1"
|
|
30
|
+
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@types/node": "^22.0.0",
|
|
30
33
|
"esbuild": "^0.28.1",
|