@mutmutco/hub 4.4.6 → 4.4.7
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 +478 -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,206 @@ 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
|
+
return [
|
|
220
|
+
frame(GLYPH.boxTop, GLYPH.boxTopEnd),
|
|
221
|
+
...body.map((line) => `${paint(identity.accent, GLYPH.bar)} ${line}${" ".repeat(Math.max(0, content - visibleWidth(line)))} ${paint(identity.accent, GLYPH.bar)}`),
|
|
222
|
+
frame(GLYPH.boxBottom, GLYPH.boxBottomEnd)
|
|
223
|
+
];
|
|
224
|
+
};
|
|
225
|
+
const signOff = () => `${paint(identity.accent, GLYPH.diamond)} ${paint(identity.accent, `${identity.name} \xB7 Mutatis Mutandis`)}`;
|
|
226
|
+
const refusal = (message3) => `${bar()} ${paint(identity.accent, GLYPH.cross)} ${message3}`;
|
|
227
|
+
return { identity, width, welcome, step, relay, receipt, signOff, refusal, paint };
|
|
228
|
+
}
|
|
229
|
+
var RELAY_INDENT = " ".repeat(TITLE_COLUMN - 1);
|
|
230
|
+
var SEPARATOR = "\xB7";
|
|
231
|
+
var subjectOf = (row) => row.aspect ? `${row.surface} ${row.aspect}` : row.surface;
|
|
232
|
+
var groupKey = (row) => `${subjectOf(row)}\0${row.state}\0${row.mark ?? ""}\0${row.note ?? ""}`;
|
|
233
|
+
function renderRows(face, rows) {
|
|
234
|
+
if (rows.length === 0) return [];
|
|
235
|
+
const groups = /* @__PURE__ */ new Map();
|
|
236
|
+
for (const row of rows) {
|
|
237
|
+
const key = groupKey(row);
|
|
238
|
+
const found = groups.get(key);
|
|
239
|
+
if (found) {
|
|
240
|
+
if (row.instance) found.instances.push(row.instance);
|
|
241
|
+
} else {
|
|
242
|
+
groups.set(key, { row, instances: row.instance ? [row.instance] : [] });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const subjects = [...groups.values()].map(({ row, instances }) => instances.length > 1 ? subjectOf(row) : [subjectOf(row), instances[0]].filter(Boolean).join(` ${SEPARATOR} `));
|
|
246
|
+
const column = Math.min(Math.max(...subjects.map(visibleWidth)), Math.max(12, face.width - 24));
|
|
247
|
+
const lines = [];
|
|
248
|
+
for (const [index, { row, instances }] of [...groups.values()].entries()) {
|
|
249
|
+
const subject = subjects[index];
|
|
250
|
+
const tail2 = instances.length > 1 ? `${row.state} ${SEPARATOR} ${instances.join(", ")}` : row.state;
|
|
251
|
+
const body = `${clip(subject, column).padEnd(column)} ${row.mark ? `${markGlyph(face, row.mark)} ` : ""}${tail2}`;
|
|
252
|
+
lines.push(...face.relay(body).split("\n"));
|
|
253
|
+
if (row.note) {
|
|
254
|
+
for (const part of wrapWords(row.note, Math.max(16, face.width - column - 10))) {
|
|
255
|
+
lines.push(...face.relay(`${" ".repeat(column)} ${part}`).split("\n"));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return lines;
|
|
260
|
+
}
|
|
261
|
+
function markGlyph(face, kind) {
|
|
262
|
+
const glyph = kind === "fail" ? GLYPH.cross : kind === "note" ? GLYPH.hollow : GLYPH.check;
|
|
263
|
+
return face.paint(kind === "fail" ? face.identity.accent : kind === "note" ? "38;2;150;150;150" : "38;2;31;209;138", glyph);
|
|
264
|
+
}
|
|
265
|
+
function clip(text, width) {
|
|
266
|
+
if (visibleWidth(text) <= width) return text;
|
|
267
|
+
return `\u2026${[...text].slice(-(width - 1)).join("")}`;
|
|
268
|
+
}
|
|
269
|
+
var FRAMES = ["\u25D2", "\u25D0", "\u25D3", "\u25D1"];
|
|
270
|
+
function createSpinner(face, { animate, stream = process.stderr, intervalMs = 90 }) {
|
|
271
|
+
let timer = null;
|
|
272
|
+
let text = "";
|
|
273
|
+
let frame = 0;
|
|
274
|
+
const clear = () => {
|
|
275
|
+
if (animate) stream.write("\r\x1B[2K");
|
|
276
|
+
};
|
|
277
|
+
const draw = () => {
|
|
278
|
+
if (!animate) return;
|
|
279
|
+
const line = face.step(text, null, "note").split("\n")[0].replace(GLYPH.hollow, FRAMES[frame % FRAMES.length]);
|
|
280
|
+
stream.write(`\r\x1B[2K${line}`);
|
|
281
|
+
frame += 1;
|
|
282
|
+
};
|
|
283
|
+
return {
|
|
284
|
+
start(next) {
|
|
285
|
+
text = next;
|
|
286
|
+
frame = 0;
|
|
287
|
+
draw();
|
|
288
|
+
if (animate && !timer) timer = setInterval(draw, intervalMs).unref();
|
|
289
|
+
},
|
|
290
|
+
say(next) {
|
|
291
|
+
text = next;
|
|
292
|
+
draw();
|
|
293
|
+
},
|
|
294
|
+
stop() {
|
|
295
|
+
if (timer) clearInterval(timer);
|
|
296
|
+
timer = null;
|
|
297
|
+
clear();
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
var SPINNER_FRAMES = Object.freeze([...FRAMES]);
|
|
302
|
+
var ALLOWED = new Set(Object.values(GLYPH));
|
|
303
|
+
|
|
104
304
|
// src/state.ts
|
|
105
305
|
var import_node_fs = require("node:fs");
|
|
106
306
|
var import_node_os = require("node:os");
|
|
@@ -165,11 +365,12 @@ function withLease(path, fn, waitMs = LEASE_WAIT_MS) {
|
|
|
165
365
|
for (; ; ) {
|
|
166
366
|
const attempt = tryAcquire(path);
|
|
167
367
|
if (typeof attempt === "object") {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
attempt.release();
|
|
368
|
+
const result = fn();
|
|
369
|
+
if (result && typeof result.then === "function") {
|
|
370
|
+
return result.finally(() => attempt.release());
|
|
172
371
|
}
|
|
372
|
+
attempt.release();
|
|
373
|
+
return result;
|
|
173
374
|
}
|
|
174
375
|
if (Date.now() >= deadline) {
|
|
175
376
|
throw new UpdaterBusy(`another updater holds ${path} (waited ${waitMs}ms) \u2014 deferring`);
|
|
@@ -188,23 +389,29 @@ function appendJournal(path, event) {
|
|
|
188
389
|
return false;
|
|
189
390
|
}
|
|
190
391
|
}
|
|
191
|
-
function journalHighWater(path, surface) {
|
|
392
|
+
function journalHighWater(path, surface, events) {
|
|
192
393
|
let high = null;
|
|
394
|
+
for (const event of events ?? parseJournal(path)) {
|
|
395
|
+
if (event.dryRun) continue;
|
|
396
|
+
if (event.surface === surface && event.verdict === "ok" && event.to && (!high || compareSemver(event.to, high) > 0)) {
|
|
397
|
+
high = event.to;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return high;
|
|
401
|
+
}
|
|
402
|
+
function parseJournal(path) {
|
|
403
|
+
const events = [];
|
|
193
404
|
try {
|
|
194
405
|
for (const line of (0, import_node_fs.readFileSync)(path, "utf8").split("\n")) {
|
|
195
406
|
if (!line.trim()) continue;
|
|
196
407
|
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
|
-
}
|
|
408
|
+
events.push(JSON.parse(line));
|
|
202
409
|
} catch {
|
|
203
410
|
}
|
|
204
411
|
}
|
|
205
412
|
} catch {
|
|
206
413
|
}
|
|
207
|
-
return
|
|
414
|
+
return events;
|
|
208
415
|
}
|
|
209
416
|
function journalSurfaceRepeatedFail(path, surface, currentRun) {
|
|
210
417
|
let lastVerdict = null;
|
|
@@ -255,6 +462,9 @@ function fileExists(path) {
|
|
|
255
462
|
return (0, import_node_fs.existsSync)(path);
|
|
256
463
|
}
|
|
257
464
|
|
|
465
|
+
// src/reconcile.ts
|
|
466
|
+
var import_node_perf_hooks2 = require("node:perf_hooks");
|
|
467
|
+
|
|
258
468
|
// src/npm.ts
|
|
259
469
|
var import_node_child_process = require("node:child_process");
|
|
260
470
|
var import_node_fs2 = require("node:fs");
|
|
@@ -295,10 +505,40 @@ function runNpm(args, env = process.env, options = {}) {
|
|
|
295
505
|
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
506
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
297
507
|
}
|
|
508
|
+
function runNpmAsync(args, env = process.env, options = {}) {
|
|
509
|
+
const npm = resolveNpm(env);
|
|
510
|
+
const [file, argv, shell] = npm.kind === "node-script" ? [process.execPath, [npm.script, ...args], false] : [npm.command, args, process.platform === "win32"];
|
|
511
|
+
return new Promise((resolve) => {
|
|
512
|
+
let stdout = "";
|
|
513
|
+
let stderr = "";
|
|
514
|
+
let child;
|
|
515
|
+
try {
|
|
516
|
+
child = (0, import_node_child_process.spawn)(file, argv, { env, cwd: options.cwd, windowsHide: true, shell });
|
|
517
|
+
} catch (error) {
|
|
518
|
+
resolve({ status: null, stdout, stderr: String(error?.message ?? error) });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
child.stdout?.setEncoding("utf8");
|
|
522
|
+
child.stderr?.setEncoding("utf8");
|
|
523
|
+
child.stdout?.on("data", (chunk) => {
|
|
524
|
+
stdout += chunk;
|
|
525
|
+
});
|
|
526
|
+
child.stderr?.on("data", (chunk) => {
|
|
527
|
+
stderr += chunk;
|
|
528
|
+
});
|
|
529
|
+
child.on("error", (error) => resolve({ status: null, stdout, stderr: String(error?.message ?? error) }));
|
|
530
|
+
child.on("close", (status) => resolve({ status, stdout, stderr }));
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
var globalRootMemo = /* @__PURE__ */ new WeakMap();
|
|
298
534
|
function globalRoot(env = process.env) {
|
|
535
|
+
const key = env;
|
|
536
|
+
const memoized = globalRootMemo.get(key);
|
|
537
|
+
if (memoized !== void 0) return memoized;
|
|
299
538
|
const root = runNpm(["root", "-g"], env);
|
|
300
|
-
|
|
301
|
-
|
|
539
|
+
const resolved = root.status !== 0 || !root.stdout.trim() ? null : root.stdout.trim();
|
|
540
|
+
globalRootMemo.set(key, resolved);
|
|
541
|
+
return resolved;
|
|
302
542
|
}
|
|
303
543
|
function npmView(spec, field, env = process.env) {
|
|
304
544
|
const args = ["view", spec, "--json"];
|
|
@@ -318,8 +558,15 @@ function parseNpmDistIntegrity(value) {
|
|
|
318
558
|
return typeof only === "string" && only.length > 0 ? only : null;
|
|
319
559
|
}
|
|
320
560
|
var npmStringField = parseNpmDistIntegrity;
|
|
321
|
-
function
|
|
322
|
-
|
|
561
|
+
async function npmDistIntegrityAsync(spec, env = process.env) {
|
|
562
|
+
const args = ["view", spec, "dist.integrity", "--json"];
|
|
563
|
+
const result = await runNpmAsync(args, env);
|
|
564
|
+
if (result.status !== 0 || !result.stdout.trim()) return null;
|
|
565
|
+
try {
|
|
566
|
+
return parseNpmDistIntegrity(JSON.parse(result.stdout));
|
|
567
|
+
} catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
323
570
|
}
|
|
324
571
|
function npmVersions(packageName, env = process.env) {
|
|
325
572
|
const versions = npmView(packageName, "versions", env);
|
|
@@ -399,38 +646,60 @@ function gateCandidate(cachePath, repoUrl, candidate) {
|
|
|
399
646
|
}
|
|
400
647
|
return { ok: true, tagCommit: commit, bom };
|
|
401
648
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
649
|
+
var ATOMICITY_CONCURRENCY = 4;
|
|
650
|
+
async function mapBounded(items, bound, fn) {
|
|
651
|
+
const results = new Array(items.length);
|
|
652
|
+
let next = 0;
|
|
653
|
+
const workers = Array.from({ length: Math.min(bound, items.length) }, async () => {
|
|
654
|
+
for (; ; ) {
|
|
655
|
+
const index = next++;
|
|
656
|
+
if (index >= items.length) return;
|
|
657
|
+
results[index] = await fn(items[index], index);
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
await Promise.all(workers);
|
|
661
|
+
return results;
|
|
662
|
+
}
|
|
663
|
+
async function checkAtomicity(cachePath, tagCommit, bom, candidate, npm) {
|
|
664
|
+
const bomRows = npmPackRows(bom);
|
|
665
|
+
const verdicts = new Array(bomRows.length).fill(null);
|
|
666
|
+
const pending = [];
|
|
667
|
+
bomRows.forEach((row, index) => {
|
|
405
668
|
if (row.version !== candidate) {
|
|
406
|
-
|
|
407
|
-
|
|
669
|
+
verdicts[index] = { id: row.id, ok: false, detail: `BOM row at ${row.version}, candidate ${candidate}` };
|
|
670
|
+
return;
|
|
408
671
|
}
|
|
409
672
|
const manifestText = showFile(cachePath, tagCommit, `${row.identity.packagePath}/package.json`);
|
|
410
673
|
let name;
|
|
411
|
-
let
|
|
674
|
+
let manifestVersion2;
|
|
412
675
|
try {
|
|
413
676
|
const manifest2 = JSON.parse(manifestText ?? "");
|
|
414
677
|
name = manifest2.name;
|
|
415
|
-
|
|
678
|
+
manifestVersion2 = manifest2.version;
|
|
416
679
|
} catch {
|
|
417
680
|
}
|
|
418
|
-
if (!name ||
|
|
419
|
-
|
|
420
|
-
|
|
681
|
+
if (!name || manifestVersion2 !== candidate) {
|
|
682
|
+
verdicts[index] = { id: row.id, name, ok: false, detail: `package.json at ${row.identity.packagePath} is not stamped ${candidate}` };
|
|
683
|
+
return;
|
|
421
684
|
}
|
|
422
|
-
|
|
685
|
+
pending.push({ index, id: row.id, name });
|
|
686
|
+
});
|
|
687
|
+
const integrities = await mapBounded(pending, ATOMICITY_CONCURRENCY, (entry) => Promise.resolve(npm.distIntegrity(`${entry.name}@${candidate}`)));
|
|
688
|
+
pending.forEach((entry, i) => {
|
|
689
|
+
const integrity = integrities[i];
|
|
423
690
|
if (!integrity) {
|
|
424
|
-
|
|
425
|
-
|
|
691
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} not on npm` };
|
|
692
|
+
return;
|
|
426
693
|
}
|
|
694
|
+
const row = bomRows[entry.index];
|
|
427
695
|
const legacySsri = /^sha\d+-/.test(row.identity.value ?? "");
|
|
428
696
|
if (legacySsri && integrity !== row.identity.value) {
|
|
429
|
-
|
|
430
|
-
|
|
697
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: false, detail: `${entry.name}@${candidate} dist.integrity differs from BOM identity` };
|
|
698
|
+
return;
|
|
431
699
|
}
|
|
432
|
-
|
|
433
|
-
}
|
|
700
|
+
verdicts[entry.index] = { id: entry.id, name: entry.name, ok: true, detail: legacySsri ? `${entry.name}@${candidate} published, integrity matches` : `${entry.name}@${candidate} published` };
|
|
701
|
+
});
|
|
702
|
+
const rows = verdicts.map((row, index) => row ?? { id: bomRows[index].id, ok: false, detail: "atomicity probe produced no verdict" });
|
|
434
703
|
return { ok: rows.length > 0 && rows.every((row) => row.ok), rows };
|
|
435
704
|
}
|
|
436
705
|
|
|
@@ -606,6 +875,15 @@ function probeVersion(distPath) {
|
|
|
606
875
|
function tail(output) {
|
|
607
876
|
return output.split("\n").filter(Boolean).slice(-3).join(" | ");
|
|
608
877
|
}
|
|
878
|
+
function manifestVersion(packageRoot) {
|
|
879
|
+
if (!packageRoot) return null;
|
|
880
|
+
try {
|
|
881
|
+
const version = JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path4.join)(packageRoot, "package.json"), "utf8")).version;
|
|
882
|
+
return typeof version === "string" ? version : null;
|
|
883
|
+
} catch {
|
|
884
|
+
return null;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
609
887
|
function stagePackage(options) {
|
|
610
888
|
const { surface, packageName, target, witness, paths, env } = options;
|
|
611
889
|
const stageRoot = (0, import_node_path4.join)(paths.staging, surface, target);
|
|
@@ -633,12 +911,17 @@ function npmGlobalArm(options) {
|
|
|
633
911
|
const { surface, packageName, distRel, target, dryRun, paths } = options;
|
|
634
912
|
const spec = `${packageName}@${target}`;
|
|
635
913
|
const result = (from, to, verdict, detail) => ({ surface, from, to, verdict, detail });
|
|
914
|
+
const highWater = journalHighWater(paths.journalPath, surface, options.journalEvents);
|
|
915
|
+
const localRoot = globalRoot(env);
|
|
916
|
+
const localVersion = localRoot ? manifestVersion((0, import_node_path4.join)(localRoot, ...packageName.split("/"))) : null;
|
|
917
|
+
if (localVersion === target && highWater === target) {
|
|
918
|
+
return result(localVersion, target, "ok", `already at target ${target} (installed manifest + journal high-water)`);
|
|
919
|
+
}
|
|
636
920
|
const dist = globalDist(env, packageName, distRel);
|
|
637
921
|
const installed = dist ? probeVersion(dist) : null;
|
|
638
922
|
if (installed && compareSemver(installed, target) > 0) {
|
|
639
923
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
640
924
|
}
|
|
641
|
-
const highWater = journalHighWater(paths.journalPath, surface);
|
|
642
925
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
643
926
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
644
927
|
}
|
|
@@ -862,7 +1145,7 @@ function claudeArm(options) {
|
|
|
862
1145
|
if (from && compareSemver(from, target) > 0) {
|
|
863
1146
|
return result(from, from, "skip", `installed ${from} is above candidate ${target} (monotonic)`);
|
|
864
1147
|
}
|
|
865
|
-
const highWater = journalHighWater(paths.journalPath, "claude");
|
|
1148
|
+
const highWater = journalHighWater(paths.journalPath, "claude", options.journalEvents);
|
|
866
1149
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
867
1150
|
return result(from, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
868
1151
|
}
|
|
@@ -1422,7 +1705,7 @@ function cursorArm(options) {
|
|
|
1422
1705
|
if (installed && compareSemver(installed, target) > 0) {
|
|
1423
1706
|
return result(installed, installed, "skip", `installed ${installed} is above candidate ${target} (monotonic)`);
|
|
1424
1707
|
}
|
|
1425
|
-
const highWater = journalHighWater(paths.journalPath, "cursor");
|
|
1708
|
+
const highWater = journalHighWater(paths.journalPath, "cursor", options.journalEvents);
|
|
1426
1709
|
if (highWater && compareSemver(highWater, target) > 0) {
|
|
1427
1710
|
return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${target} (monotonic)`);
|
|
1428
1711
|
}
|
|
@@ -1566,11 +1849,11 @@ function parseListText(stdout) {
|
|
|
1566
1849
|
}
|
|
1567
1850
|
function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
1568
1851
|
const manifestLocation = materializedManifestLocation(env);
|
|
1569
|
-
const
|
|
1852
|
+
const manifestVersion2 = installedVersion(env);
|
|
1570
1853
|
const manifestEvidence = () => ({
|
|
1571
|
-
version:
|
|
1854
|
+
version: manifestVersion2,
|
|
1572
1855
|
location: manifestLocation,
|
|
1573
|
-
detail:
|
|
1856
|
+
detail: manifestVersion2 ? "materialized pi package manifest" : "materialized pi package manifest was unreadable"
|
|
1574
1857
|
});
|
|
1575
1858
|
const hosted = hostEnv(env);
|
|
1576
1859
|
const command = hostCommands(env).find((bin) => runner(bin, ["--version"], hosted).status === 0);
|
|
@@ -1586,9 +1869,9 @@ function probeJervcodeInstallation(env = process.env, runner = runHostCommand) {
|
|
|
1586
1869
|
};
|
|
1587
1870
|
}
|
|
1588
1871
|
const hostError = result.status !== 0 ? `${hostLocation} failed: ${failure2(result)}` : `${hostLocation} did not list npm:${PI_PACKAGE}`;
|
|
1589
|
-
if (
|
|
1872
|
+
if (manifestVersion2) {
|
|
1590
1873
|
return {
|
|
1591
|
-
version:
|
|
1874
|
+
version: manifestVersion2,
|
|
1592
1875
|
location: manifestLocation,
|
|
1593
1876
|
detail: `${hostError}; using materialized pi package manifest`
|
|
1594
1877
|
};
|
|
@@ -1619,7 +1902,7 @@ function jervcodeArm(options) {
|
|
|
1619
1902
|
if (!before) return result(null, options.target, "defer", `cannot read ${(0, import_node_path9.join)(jervcodeAgentDir(env), "settings.json")}`);
|
|
1620
1903
|
const from = installedVersion(env);
|
|
1621
1904
|
const legacy = before.filter(isLegacyMmiPiPath);
|
|
1622
|
-
const highWater = journalHighWater(options.paths.journalPath, "jervcode");
|
|
1905
|
+
const highWater = journalHighWater(options.paths.journalPath, "jervcode", options.journalEvents);
|
|
1623
1906
|
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
1907
|
if (options.dryRun) {
|
|
1625
1908
|
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 +2094,7 @@ function hermesHomes(env = process.env) {
|
|
|
1811
2094
|
var VERDICT_RANK = { fail: 3, defer: 2, ok: 1, skip: 0 };
|
|
1812
2095
|
function hermesArm(options) {
|
|
1813
2096
|
const env = options.env ?? process.env;
|
|
1814
|
-
if (!hermesHostPresent(env, options.hostCommandRunner)) {
|
|
2097
|
+
if (!(options.hostPresent ?? hermesHostPresent(env, options.hostCommandRunner))) {
|
|
1815
2098
|
return {
|
|
1816
2099
|
surface: "hermes",
|
|
1817
2100
|
from: null,
|
|
@@ -1854,7 +2137,7 @@ function armHermesHome(home, env, options, primary) {
|
|
|
1854
2137
|
return result(installed, options.target, "defer", `${live} carries no ${PACKAGE2} package identity \u2014 refusing to displace an unmanaged Hermes mmi plugin`);
|
|
1855
2138
|
}
|
|
1856
2139
|
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;
|
|
2140
|
+
const highWater = primary ? journalHighWater(options.paths.journalPath, "hermes", options.journalEvents) : null;
|
|
1858
2141
|
if (highWater && compareSemver(highWater, options.target) > 0) return result(installed, highWater, "skip", `journal high-water ${highWater} is above candidate ${options.target} (monotonic)`);
|
|
1859
2142
|
const hostRunner = options.hostCommandRunner ?? runHostCommand;
|
|
1860
2143
|
if (installed === options.target && treeHealthy2(live)) {
|
|
@@ -1956,6 +2239,10 @@ function probeHermesLaunchStaging(env = process.env) {
|
|
|
1956
2239
|
// src/reap.ts
|
|
1957
2240
|
var import_node_fs14 = require("node:fs");
|
|
1958
2241
|
var import_node_path11 = require("node:path");
|
|
2242
|
+
var import_node_perf_hooks = require("node:perf_hooks");
|
|
2243
|
+
function elapsedMs(startedAt) {
|
|
2244
|
+
return Math.round((import_node_perf_hooks.performance.now() - startedAt) * 10) / 10;
|
|
2245
|
+
}
|
|
1959
2246
|
var KEEP_GENERATIONS2 = 2;
|
|
1960
2247
|
var SCRATCH_AGE_MS = 24 * 60 * 6e4;
|
|
1961
2248
|
function listDirs(path) {
|
|
@@ -2052,10 +2339,11 @@ function reap(options) {
|
|
|
2052
2339
|
const { paths, dryRun } = options;
|
|
2053
2340
|
const env = options.env ?? process.env;
|
|
2054
2341
|
const guarded = (category, run) => {
|
|
2342
|
+
const startedAt = import_node_perf_hooks.performance.now();
|
|
2055
2343
|
try {
|
|
2056
|
-
return run();
|
|
2344
|
+
return { ...run(), durationMs: elapsedMs(startedAt) };
|
|
2057
2345
|
} catch (error) {
|
|
2058
|
-
return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}
|
|
2346
|
+
return { category, reaped: 0, verdict: "fail", detail: `reap failed: ${error?.message ?? error}`, durationMs: elapsedMs(startedAt) };
|
|
2059
2347
|
}
|
|
2060
2348
|
};
|
|
2061
2349
|
const atomicWritePaths = [claudeMarketplacesPath(env)];
|
|
@@ -2072,7 +2360,7 @@ function shouldHandOff(self, options) {
|
|
|
2072
2360
|
if (!options.dist) return false;
|
|
2073
2361
|
return self.verdict === "ok" && Boolean(self.from) && self.from !== self.to;
|
|
2074
2362
|
}
|
|
2075
|
-
function reconcile(options) {
|
|
2363
|
+
async function reconcile(options) {
|
|
2076
2364
|
const env = options.env ?? process.env;
|
|
2077
2365
|
const shipped = options.shippedTarget?.trim() || null;
|
|
2078
2366
|
const say = (line) => {
|
|
@@ -2088,9 +2376,9 @@ function reconcile(options) {
|
|
|
2088
2376
|
if (!appendJournal(paths.journalPath, { ts: (/* @__PURE__ */ new Date()).toISOString(), run, ...stamped })) summary.journalOk = false;
|
|
2089
2377
|
};
|
|
2090
2378
|
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) => {
|
|
2379
|
+
const recordArm = (arm, durationMs) => {
|
|
2092
2380
|
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 });
|
|
2381
|
+
journal({ kind: "arm", surface: arm.surface, from: arm.from, to: arm.to, verdict: arm.verdict, detail: arm.detail, compat: arm.compat, durationMs });
|
|
2094
2382
|
options.onArm?.(arm);
|
|
2095
2383
|
if (arm.verdict === "ok" && !options.dryRun && !arm.launchStaged) clearPending(paths, arm.surface);
|
|
2096
2384
|
if (arm.verdict === "defer") summary.exit = Math.max(summary.exit, 2);
|
|
@@ -2099,7 +2387,8 @@ function reconcile(options) {
|
|
|
2099
2387
|
try {
|
|
2100
2388
|
say("acquiring single-writer lease");
|
|
2101
2389
|
const leaseWaitMs = Number(env.MMI_UPDATER_LEASE_WAIT_MS) || void 0;
|
|
2102
|
-
return withLease(paths.leasePath, () => {
|
|
2390
|
+
return await withLease(paths.leasePath, async () => {
|
|
2391
|
+
const journalEvents = parseJournal(paths.journalPath);
|
|
2103
2392
|
if (!options.dryRun) {
|
|
2104
2393
|
const pendingBefore = readPending(paths);
|
|
2105
2394
|
if (pendingBefore.length) {
|
|
@@ -2108,7 +2397,7 @@ function reconcile(options) {
|
|
|
2108
2397
|
}
|
|
2109
2398
|
}
|
|
2110
2399
|
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 });
|
|
2400
|
+
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
2401
|
for (const row of summary.reap.filter((entry) => entry.verdict !== "skip")) say(`reap ${row.category}: ${row.verdict}${row.detail ? " \u2014 " + row.detail : ""}`);
|
|
2113
2402
|
if (shipped) {
|
|
2114
2403
|
say(`shipped target v${shipped} \u2014 the payload is the release; no npm poll, no gate`);
|
|
@@ -2118,41 +2407,48 @@ function reconcile(options) {
|
|
|
2118
2407
|
journal({ kind: "target", to: shipped, verdict: "ok", detail: "shipped payload" });
|
|
2119
2408
|
} else {
|
|
2120
2409
|
say("polling npm for @mutmutco/cli versions");
|
|
2410
|
+
const pollStartedAt = import_node_perf_hooks2.performance.now();
|
|
2121
2411
|
const versions = npmVersions("@mutmutco/cli", env).sort((a, b) => compareSemver(b, a));
|
|
2412
|
+
const pollMs = elapsedMs(pollStartedAt);
|
|
2122
2413
|
if (!versions.length) {
|
|
2123
|
-
journal({ kind: "poll", verdict: "fail", detail: "@mutmutco/cli has no npm versions \u2014 cannot discover a candidate" });
|
|
2414
|
+
journal({ kind: "poll", verdict: "fail", durationMs: pollMs, detail: "@mutmutco/cli has no npm versions \u2014 cannot discover a candidate" });
|
|
2124
2415
|
summary.exit = 1;
|
|
2125
2416
|
summary.detail = "npm poll returned no versions for @mutmutco/cli";
|
|
2126
2417
|
return summary;
|
|
2127
2418
|
}
|
|
2419
|
+
journal({ kind: "poll", verdict: "ok", count: versions.length, durationMs: pollMs, detail: `${versions.length} versions; newest ${versions[0]}` });
|
|
2128
2420
|
say("fetching release BOM gate cache");
|
|
2421
|
+
const fetchStartedAt = import_node_perf_hooks2.performance.now();
|
|
2129
2422
|
ensureBareCache(paths.gitCache, repoUrl);
|
|
2130
2423
|
fetchIntoCache(paths.gitCache, repoUrl, "refs/heads/main:refs/heads/main");
|
|
2424
|
+
journal({ kind: "gate", surface: "cache", verdict: "ok", durationMs: elapsedMs(fetchStartedAt), detail: "release BOM gate cache: refs/heads/main fetched" });
|
|
2425
|
+
const walkStartedAt = import_node_perf_hooks2.performance.now();
|
|
2131
2426
|
for (const candidate of versions.slice(0, bound)) {
|
|
2132
2427
|
say(`gating candidate v${candidate}`);
|
|
2428
|
+
const candidateStartedAt = import_node_perf_hooks2.performance.now();
|
|
2133
2429
|
const gate = gateCandidate(paths.gitCache, repoUrl, candidate);
|
|
2134
2430
|
if (!gate.ok) {
|
|
2135
2431
|
say(`candidate v${candidate} rejected (${gate.reason ?? "gate"})`);
|
|
2136
|
-
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", detail: gate.reason ?? "gate rejected" });
|
|
2432
|
+
journal({ kind: "candidate-reject", to: candidate, verdict: "skip", durationMs: elapsedMs(candidateStartedAt), detail: gate.reason ?? "gate rejected" });
|
|
2137
2433
|
continue;
|
|
2138
2434
|
}
|
|
2139
|
-
const atomicity = checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
|
|
2140
|
-
distIntegrity: (spec) =>
|
|
2435
|
+
const atomicity = await checkAtomicity(paths.gitCache, gate.tagCommit, gate.bom, candidate, {
|
|
2436
|
+
distIntegrity: (spec) => npmDistIntegrityAsync(spec, env)
|
|
2141
2437
|
});
|
|
2142
2438
|
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("; ")}` });
|
|
2439
|
+
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
2440
|
continue;
|
|
2145
2441
|
}
|
|
2146
2442
|
summary.target = candidate;
|
|
2147
2443
|
summary.managedSurfaces = Array.isArray(gate.bom?.managedSurfaces) ? gate.bom.managedSurfaces.map(String) : null;
|
|
2148
2444
|
summary.gate = `tag v${candidate} reachable from main, BOM stamp coherent`;
|
|
2149
2445
|
summary.atomicity = atomicity;
|
|
2150
|
-
journal({ kind: "target", to: candidate, verdict: "ok", detail: summary.gate });
|
|
2446
|
+
journal({ kind: "target", to: candidate, verdict: "ok", durationMs: elapsedMs(candidateStartedAt), detail: summary.gate });
|
|
2151
2447
|
break;
|
|
2152
2448
|
}
|
|
2153
2449
|
if (!summary.target) {
|
|
2154
2450
|
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` });
|
|
2451
|
+
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
2452
|
for (const probe of enumerateSurfaces(env)) {
|
|
2157
2453
|
summary.surfaces.push({ id: probe.id, present: probe.present, action: probe.present ? "deferred-no-target" : "absent-skip" });
|
|
2158
2454
|
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 +2459,7 @@ function reconcile(options) {
|
|
|
2163
2459
|
say(`target v${summary.target} (${summary.gate})`);
|
|
2164
2460
|
options.onTargetResolved?.(summary.target, summary.gate);
|
|
2165
2461
|
say("updater: self-bootstrap");
|
|
2462
|
+
const selfStartedAt = import_node_perf_hooks2.performance.now();
|
|
2166
2463
|
const self = npmGlobalArm({
|
|
2167
2464
|
surface: "updater",
|
|
2168
2465
|
packageName: "@mutmutco/hub",
|
|
@@ -2170,10 +2467,11 @@ function reconcile(options) {
|
|
|
2170
2467
|
target: summary.target,
|
|
2171
2468
|
dryRun: options.dryRun,
|
|
2172
2469
|
paths,
|
|
2173
|
-
env
|
|
2470
|
+
env,
|
|
2471
|
+
journalEvents
|
|
2174
2472
|
});
|
|
2175
2473
|
summary.updater = self;
|
|
2176
|
-
recordArm(self);
|
|
2474
|
+
recordArm(self, elapsedMs(selfStartedAt));
|
|
2177
2475
|
say(`updater: ${self.verdict}${self.detail ? " \u2014 " + self.detail : ""}`);
|
|
2178
2476
|
const handoffDist = globalDist(env, "@mutmutco/hub", "dist/index.cjs");
|
|
2179
2477
|
if (shouldHandOff(self, { dryRun: options.dryRun, dist: handoffDist, guard: env.MMI_UPDATER_REEXEC })) {
|
|
@@ -2183,6 +2481,7 @@ function reconcile(options) {
|
|
|
2183
2481
|
return summary;
|
|
2184
2482
|
}
|
|
2185
2483
|
say(`cli: converging to v${summary.target}`);
|
|
2484
|
+
const cliStartedAt = import_node_perf_hooks2.performance.now();
|
|
2186
2485
|
const cli = npmGlobalArm({
|
|
2187
2486
|
surface: "cli",
|
|
2188
2487
|
packageName: "@mutmutco/cli",
|
|
@@ -2190,10 +2489,11 @@ function reconcile(options) {
|
|
|
2190
2489
|
target: summary.target,
|
|
2191
2490
|
dryRun: options.dryRun,
|
|
2192
2491
|
paths,
|
|
2193
|
-
env
|
|
2492
|
+
env,
|
|
2493
|
+
journalEvents
|
|
2194
2494
|
});
|
|
2195
2495
|
summary.cli = cli;
|
|
2196
|
-
recordArm(cli);
|
|
2496
|
+
recordArm(cli, elapsedMs(cliStartedAt));
|
|
2197
2497
|
say(`cli: ${cli.verdict}${cli.detail ? " \u2014 " + cli.detail : ""}`);
|
|
2198
2498
|
for (const probe of enumerateSurfaces(env)) {
|
|
2199
2499
|
if (!probe.present) {
|
|
@@ -2208,11 +2508,12 @@ function reconcile(options) {
|
|
|
2208
2508
|
continue;
|
|
2209
2509
|
}
|
|
2210
2510
|
say(`${probe.id}: converging to v${summary.target}`);
|
|
2211
|
-
const
|
|
2511
|
+
const armStartedAt = import_node_perf_hooks2.performance.now();
|
|
2512
|
+
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
2513
|
if (arm) {
|
|
2213
2514
|
summary.plugins.push(arm);
|
|
2214
2515
|
summary.surfaces.push({ id: probe.id, present: true, action: `arm-${arm.verdict}` });
|
|
2215
|
-
recordArm(arm);
|
|
2516
|
+
recordArm(arm, elapsedMs(armStartedAt));
|
|
2216
2517
|
say(`${probe.id}: ${arm.verdict}${arm.detail ? " \u2014 " + arm.detail : ""}`);
|
|
2217
2518
|
} else {
|
|
2218
2519
|
summary.surfaces.push({ id: probe.id, present: true, action: "arm-pending" });
|
|
@@ -2924,28 +3225,50 @@ var ARM_TITLE = {
|
|
|
2924
3225
|
hermes: "Arming Hermes",
|
|
2925
3226
|
jervcode: "Arming JervCode"
|
|
2926
3227
|
};
|
|
2927
|
-
var
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
3228
|
+
var FACE_PRODUCT = "mmi-hub";
|
|
3229
|
+
function updaterFace(options = {}) {
|
|
3230
|
+
return createFace({ product: FACE_PRODUCT, color: options.color ?? false, columns: options.width });
|
|
3231
|
+
}
|
|
3232
|
+
var STATE_WORD = {
|
|
3233
|
+
updated: "updated",
|
|
3234
|
+
current: "current",
|
|
3235
|
+
keep: "kept",
|
|
3236
|
+
retry: "will retry",
|
|
3237
|
+
failed: "failed",
|
|
3238
|
+
pending: "pending"
|
|
2934
3239
|
};
|
|
2935
|
-
function
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
const kind = classify(arm)
|
|
2940
|
-
const
|
|
2941
|
-
const
|
|
2942
|
-
const
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
3240
|
+
function stepKindFor(kind) {
|
|
3241
|
+
return kind === "failed" ? "fail" : kind === "retry" ? "note" : "ok";
|
|
3242
|
+
}
|
|
3243
|
+
function checklistLine(arm, seconds, face, options = {}) {
|
|
3244
|
+
const { kind } = classify(arm);
|
|
3245
|
+
const state = options.dryRun && kind === "updated" ? "would update" : STATE_WORD[kind];
|
|
3246
|
+
const title = `${ARM_TITLE[arm.surface] ?? `Arming ${arm.surface}`} \u2014 ${state}`;
|
|
3247
|
+
const truthful = kind === "failed" || kind === "retry" ? null : seconds;
|
|
3248
|
+
return face.step(title, truthful, stepKindFor(kind));
|
|
3249
|
+
}
|
|
3250
|
+
function receiptText(summary, autoupdate, options = {}) {
|
|
3251
|
+
const dry = options.dryRun ?? false;
|
|
3252
|
+
const counts = reportCounts(summary);
|
|
3253
|
+
const health = "Check health any time: mmi-hub status";
|
|
3254
|
+
if (!summary.target) {
|
|
3255
|
+
return [`${summary.exit >= 1 ? "\u2716" : "\u25CF"} mmi-hub is not ready \u2014 no green release yet (hourly retry)`, "", health];
|
|
3256
|
+
}
|
|
3257
|
+
const ready = counts.failed === 0;
|
|
3258
|
+
const glyph = counts.failed > 0 ? "\u2716" : counts.retry > 0 ? "\u25CF" : dry ? "\u25C7" : "\u2714";
|
|
3259
|
+
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`;
|
|
3260
|
+
const tail2 = autoupdate ? ` \xB7 ${autoupdate.ok ? "auto-update on" : `auto-update NOT enabled \u2014 ${autoupdate.detail}`}` : ` \xB7 ${summary.target}${dry ? " (planned)" : " (verified)"}`;
|
|
3261
|
+
return [`${glyph} mmi-hub is ${ready ? "ready" : "not ready"} \u2014 ${state}${tail2}`, "", health];
|
|
3262
|
+
}
|
|
3263
|
+
function attentionRows(summary) {
|
|
3264
|
+
return collectRows(summary).filter((row) => row.kind === "failed" || row.kind === "retry" || row.kind === "pending").map((row) => ({
|
|
3265
|
+
surface: row.name,
|
|
3266
|
+
state: statusText(row.kind, false),
|
|
3267
|
+
mark: row.kind === "failed" ? "fail" : "note",
|
|
3268
|
+
note: row.kind === "failed" ? failConsequence(row) : row.kind === "retry" ? retryHint(row.detail) : "arm not landed"
|
|
3269
|
+
}));
|
|
3270
|
+
}
|
|
3271
|
+
function wrapWords2(text, available) {
|
|
2949
3272
|
if (available < 1) return [text];
|
|
2950
3273
|
const lines = [];
|
|
2951
3274
|
let current = "";
|
|
@@ -2966,7 +3289,7 @@ function noteLines(prefix, note, width) {
|
|
|
2966
3289
|
const available = width - used - 1;
|
|
2967
3290
|
const indent = available >= MIN_NOTE_SPACE ? " ".repeat(used + 1) : " ".repeat(Math.min(used + 1, Math.max(2, width - MIN_NOTE_SPACE)));
|
|
2968
3291
|
const room = available >= MIN_NOTE_SPACE ? available : width - indent.length;
|
|
2969
|
-
const parts =
|
|
3292
|
+
const parts = wrapWords2(note, room);
|
|
2970
3293
|
const first = available >= MIN_NOTE_SPACE ? prefix + " " + parts[0] : prefix.replace(/\s+$/, "");
|
|
2971
3294
|
const lines = [first];
|
|
2972
3295
|
for (const part of parts.slice(available >= MIN_NOTE_SPACE ? 1 : 0)) lines.push(indent + part);
|
|
@@ -2989,7 +3312,7 @@ function collectRows(summary) {
|
|
|
2989
3312
|
}
|
|
2990
3313
|
function headline(detail, width) {
|
|
2991
3314
|
const first = (detail ?? "").split(/(?<=[.;:])\s/)[0]?.replace(/\s+/g, " ").trim() ?? "";
|
|
2992
|
-
return
|
|
3315
|
+
return wrapWords2(first || "blocked", Math.max(10, width - 4))[0];
|
|
2993
3316
|
}
|
|
2994
3317
|
function updateHeader(target, dryRun = false, options) {
|
|
2995
3318
|
return brandLine(
|
|
@@ -3091,124 +3414,6 @@ function formatReconcileReport(summary, options = {}) {
|
|
|
3091
3414
|
return out.join("\n") + "\n";
|
|
3092
3415
|
}
|
|
3093
3416
|
|
|
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
3417
|
// src/stamp.ts
|
|
3213
3418
|
var import_node_fs17 = require("node:fs");
|
|
3214
3419
|
var ALWAYS_ARMED = ["cli", "updater"];
|
|
@@ -3306,17 +3511,33 @@ function printReconcile(summary, json, dryRun = false) {
|
|
|
3306
3511
|
function faceEnabled(args, json) {
|
|
3307
3512
|
return !json && !args.includes("--verbose") && !process.env.NO_COLOR && process.env.TERM !== "dumb" && Boolean(process.stdout.isTTY) && Boolean(process.stderr.isTTY);
|
|
3308
3513
|
}
|
|
3309
|
-
function openFace(args, json) {
|
|
3310
|
-
|
|
3311
|
-
|
|
3514
|
+
function openFace(args, json, dryRun) {
|
|
3515
|
+
if (!faceEnabled(args, json)) return null;
|
|
3516
|
+
const face = updaterFace(humanOptions(dryRun));
|
|
3517
|
+
if (!process.env.MMI_UPDATER_REEXEC) for (const line of face.welcome()) process.stdout.write(line + "\n");
|
|
3312
3518
|
return face;
|
|
3313
3519
|
}
|
|
3314
|
-
function finishFace(summary, autoupdate, dryRun) {
|
|
3520
|
+
function finishFace(face, summary, autoupdate, dryRun) {
|
|
3315
3521
|
const options = humanOptions(dryRun);
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3522
|
+
const write = (line) => {
|
|
3523
|
+
process.stdout.write(line + "\n");
|
|
3524
|
+
};
|
|
3525
|
+
if (!summary.target) {
|
|
3526
|
+
process.stdout.write(formatReconcileReport(summary, { ...options, tty: false }));
|
|
3527
|
+
} else {
|
|
3528
|
+
for (const line of renderRows(face, attentionRows(summary))) write(line);
|
|
3529
|
+
}
|
|
3530
|
+
const body = receiptText(summary, autoupdate, { dryRun });
|
|
3531
|
+
const counts = reportCounts(summary);
|
|
3532
|
+
if (counts.failed > 0) {
|
|
3533
|
+
body.push("", "Fix: mmi-cli doctor, then mmi-hub update", `Log: ${options.journalPath}`);
|
|
3534
|
+
}
|
|
3535
|
+
const self = summary.updater;
|
|
3536
|
+
if (!dryRun && self && self.from && self.to && self.from !== self.to && self.verdict !== "fail") {
|
|
3537
|
+
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}`);
|
|
3538
|
+
}
|
|
3539
|
+
for (const line of face.receipt(body)) write(line);
|
|
3540
|
+
write(face.signOff());
|
|
3320
3541
|
}
|
|
3321
3542
|
function handOff(reexec, args) {
|
|
3322
3543
|
const child = (0, import_node_child_process9.spawnSync)(process.execPath, [reexec.dist, ...args], {
|
|
@@ -3353,7 +3574,7 @@ function shippedTargetFrom(args) {
|
|
|
3353
3574
|
const value = (args[index + 1] ?? "").trim();
|
|
3354
3575
|
return value && !value.startsWith("--") ? value : void 0;
|
|
3355
3576
|
}
|
|
3356
|
-
function runUpdate(args, json, face =
|
|
3577
|
+
async function runUpdate(args, json, face = null, shippedTarget) {
|
|
3357
3578
|
const dryRun = args.includes("--dry-run");
|
|
3358
3579
|
const verbose = args.includes("--verbose");
|
|
3359
3580
|
const narrate = (line) => {
|
|
@@ -3362,20 +3583,35 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3362
3583
|
};
|
|
3363
3584
|
const progressive = !json && !verbose && Boolean(process.stderr.isTTY);
|
|
3364
3585
|
if (!progressive) {
|
|
3365
|
-
return
|
|
3586
|
+
return reconcile({ dryRun, narrate: verbose ? narrate : void 0, shippedTarget }).then((summary2) => ({ summary: summary2, rendered: false }));
|
|
3366
3587
|
}
|
|
3367
3588
|
const reportOptions = humanOptions(dryRun);
|
|
3368
|
-
const spinner =
|
|
3589
|
+
const spinner = createSpinner(face ?? updaterFace(reportOptions), { animate: true, stream: process.stderr });
|
|
3590
|
+
let spinning = false;
|
|
3591
|
+
const phase = (line) => {
|
|
3592
|
+
if (spinning) spinner.say(line);
|
|
3593
|
+
else {
|
|
3594
|
+
spinner.start(line);
|
|
3595
|
+
spinning = true;
|
|
3596
|
+
}
|
|
3597
|
+
};
|
|
3598
|
+
const persist = (line) => {
|
|
3599
|
+
if (spinning) {
|
|
3600
|
+
spinner.stop();
|
|
3601
|
+
spinning = false;
|
|
3602
|
+
}
|
|
3603
|
+
process.stdout.write(line + "\n");
|
|
3604
|
+
};
|
|
3369
3605
|
let headerEmitted = false;
|
|
3370
3606
|
let summary;
|
|
3371
|
-
const { emit, stream } = heldOutput(
|
|
3607
|
+
const { emit, stream } = heldOutput(persist);
|
|
3372
3608
|
let arms = 0;
|
|
3373
3609
|
let mark = Date.now();
|
|
3374
3610
|
try {
|
|
3375
|
-
summary = reconcile({
|
|
3611
|
+
summary = await reconcile({
|
|
3376
3612
|
dryRun,
|
|
3377
3613
|
shippedTarget,
|
|
3378
|
-
onPhase: (line) =>
|
|
3614
|
+
onPhase: (line) => phase(line),
|
|
3379
3615
|
onTargetResolved: face ? void 0 : (target) => {
|
|
3380
3616
|
emit(updateHeader(target, dryRun, reportOptions));
|
|
3381
3617
|
emit("");
|
|
@@ -3385,7 +3621,7 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3385
3621
|
const seconds = (Date.now() - mark) / 1e3;
|
|
3386
3622
|
mark = Date.now();
|
|
3387
3623
|
if (arms++) stream();
|
|
3388
|
-
emit(face ? checklistLine(arm, seconds, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3624
|
+
emit(face ? checklistLine(arm, seconds, face, reportOptions) : progressiveRow(arm, reportOptions));
|
|
3389
3625
|
}
|
|
3390
3626
|
});
|
|
3391
3627
|
} finally {
|
|
@@ -3398,7 +3634,7 @@ function runUpdate(args, json, face = false, shippedTarget) {
|
|
|
3398
3634
|
else process.stdout.write(formatReconcileReport(summary, reportOptions));
|
|
3399
3635
|
return { summary, rendered: true };
|
|
3400
3636
|
}
|
|
3401
|
-
function main(args = process.argv.slice(2)) {
|
|
3637
|
+
async function main(args = process.argv.slice(2)) {
|
|
3402
3638
|
if (args.includes("--version")) {
|
|
3403
3639
|
process.stdout.write(ownVersion() + "\n");
|
|
3404
3640
|
return 0;
|
|
@@ -3415,18 +3651,19 @@ function main(args = process.argv.slice(2)) {
|
|
|
3415
3651
|
return status.failures.length ? 1 : 0;
|
|
3416
3652
|
}
|
|
3417
3653
|
if (command === "update") {
|
|
3418
|
-
const
|
|
3419
|
-
const
|
|
3654
|
+
const dryRun = args.includes("--dry-run");
|
|
3655
|
+
const face = openFace(args, json, dryRun);
|
|
3656
|
+
const { summary, rendered } = await runUpdate(args, json, face);
|
|
3420
3657
|
if (summary.reexec) return handOff(summary.reexec, args);
|
|
3421
|
-
if (face) finishFace(summary, null,
|
|
3422
|
-
else if (!rendered) printReconcile(summary, json,
|
|
3658
|
+
if (face) finishFace(face, summary, null, dryRun);
|
|
3659
|
+
else if (!rendered) printReconcile(summary, json, dryRun);
|
|
3423
3660
|
return summary.exit;
|
|
3424
3661
|
}
|
|
3425
3662
|
if (command === "install") {
|
|
3426
|
-
const face = openFace(args, json);
|
|
3427
3663
|
const dryRun = args.includes("--dry-run");
|
|
3664
|
+
const face = openFace(args, json, dryRun);
|
|
3428
3665
|
const shippedTarget = shippedTargetFrom(args);
|
|
3429
|
-
const { summary, rendered } = runUpdate(args, json, face, shippedTarget);
|
|
3666
|
+
const { summary, rendered } = await runUpdate(args, json, face, shippedTarget);
|
|
3430
3667
|
if (summary.reexec) {
|
|
3431
3668
|
const handedOff = handOff(summary.reexec, ["update", ...args.filter((arg) => arg !== "install" && arg !== "--shipped" && arg !== shippedTarget)]);
|
|
3432
3669
|
const scheduled = installTask();
|
|
@@ -3443,7 +3680,7 @@ function main(args = process.argv.slice(2)) {
|
|
|
3443
3680
|
if (json) {
|
|
3444
3681
|
process.stdout.write(JSON.stringify({ convergence: summary, autoupdate: schedule }, null, 2) + "\n");
|
|
3445
3682
|
} else if (face) {
|
|
3446
|
-
finishFace(summary, schedule, dryRun);
|
|
3683
|
+
finishFace(face, summary, schedule, dryRun);
|
|
3447
3684
|
} else {
|
|
3448
3685
|
if (!rendered) printReconcile(summary, false, dryRun);
|
|
3449
3686
|
process.stdout.write(schedule.ok ? ` Auto-update on \u2014 hourly task "MMI Fleet Updater".
|
|
@@ -3468,17 +3705,24 @@ function main(args = process.argv.slice(2)) {
|
|
|
3468
3705
|
return 1;
|
|
3469
3706
|
}
|
|
3470
3707
|
if (typeof require !== "undefined" && require.main === module) {
|
|
3471
|
-
process.exit(
|
|
3708
|
+
main().then((code) => process.exit(code)).catch((error) => {
|
|
3709
|
+
process.stderr.write(`mmi-hub: ${error?.stack ?? error}
|
|
3710
|
+
`);
|
|
3711
|
+
process.exit(1);
|
|
3712
|
+
});
|
|
3472
3713
|
}
|
|
3473
3714
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3474
3715
|
0 && (module.exports = {
|
|
3475
3716
|
ACCENT,
|
|
3717
|
+
FACE_PRODUCT,
|
|
3476
3718
|
MM_GREEN,
|
|
3477
3719
|
RESET,
|
|
3478
3720
|
accent,
|
|
3479
3721
|
acquireSharedDocLock,
|
|
3722
|
+
attentionRows,
|
|
3480
3723
|
brandLine,
|
|
3481
3724
|
buildWatcherCommand,
|
|
3725
|
+
checkAtomicity,
|
|
3482
3726
|
checklistLine,
|
|
3483
3727
|
claudeArm,
|
|
3484
3728
|
claudeHostEvidence,
|
|
@@ -3518,7 +3762,6 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3518
3762
|
progressiveRow,
|
|
3519
3763
|
readLaunchTarget,
|
|
3520
3764
|
readPending,
|
|
3521
|
-
receipt,
|
|
3522
3765
|
receiptText,
|
|
3523
3766
|
reconcile,
|
|
3524
3767
|
reconcileCursorUserHooks,
|
|
@@ -3533,15 +3776,13 @@ if (typeof require !== "undefined" && require.main === module) {
|
|
|
3533
3776
|
sharedDocLockTimeoutReceipt,
|
|
3534
3777
|
shippedTargetFrom,
|
|
3535
3778
|
shouldHandOff,
|
|
3536
|
-
signOff,
|
|
3537
|
-
startSpinner,
|
|
3538
3779
|
surfaceArmPlan,
|
|
3539
3780
|
systemdServiceUnit,
|
|
3540
3781
|
systemdTimerUnit,
|
|
3541
3782
|
taskXml,
|
|
3542
3783
|
updateHeader,
|
|
3784
|
+
updaterFace,
|
|
3543
3785
|
watcherLockPath,
|
|
3544
|
-
welcome,
|
|
3545
3786
|
withSharedDocLock,
|
|
3546
3787
|
wrapWords,
|
|
3547
3788
|
writeLaunchTarget,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/hub",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.7",
|
|
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.0"
|
|
30
|
+
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@types/node": "^22.0.0",
|
|
30
33
|
"esbuild": "^0.28.1",
|